chore(sf): judgment-log + auto-post-unit + milestone-framing-check cleanup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
070c0eb802
commit
effada2bb4
1 changed files with 115 additions and 0 deletions
115
src/resources/extensions/sf/knowledge-compounding.ts
Normal file
115
src/resources/extensions/sf/knowledge-compounding.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Knowledge compounding — distills high-confidence judgment-log entries from a
|
||||
* milestone window into .sf/KNOWLEDGE.md after milestone close.
|
||||
*
|
||||
* Called by postUnitPostVerification after complete-milestone, alongside
|
||||
* scaffold-keeper and record-promoter. Failure is always non-fatal.
|
||||
*
|
||||
* Strategy (stub implementation):
|
||||
* - Read judgment-log.jsonl entries with confidence=high for the given milestone
|
||||
* - For each, generate: `- [M00X] {decision}: {reasoning}`
|
||||
* - Append under `## Learned during M00X` section in .sf/KNOWLEDGE.md
|
||||
* - Deduplicate against existing entries (exact decision+reasoning match)
|
||||
*/
|
||||
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
import { readJudgmentLog } from "./judgment-log.js";
|
||||
import { sfRoot } from "./paths.js";
|
||||
|
||||
export interface CompoundLearningsResult {
|
||||
added: number;
|
||||
skipped: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compound high-confidence judgment-log entries into KNOWLEDGE.md.
|
||||
*
|
||||
* @param basePath - project root (cwd)
|
||||
* @param milestoneId - milestone just completed (e.g. "M001")
|
||||
*/
|
||||
export function compoundLearningsIntoKnowledge(
|
||||
basePath: string,
|
||||
milestoneId: string,
|
||||
): CompoundLearningsResult {
|
||||
const sfDir = sfRoot(basePath);
|
||||
const knowledgePath = join(sfDir, "KNOWLEDGE.md");
|
||||
const sectionHeading = `## Learned during ${milestoneId}`;
|
||||
|
||||
// Read high-confidence entries for this milestone
|
||||
const entries = readJudgmentLog(basePath, milestoneId).filter(
|
||||
(e) => e.confidence === "high",
|
||||
);
|
||||
|
||||
if (entries.length === 0) return { added: 0, skipped: 0 };
|
||||
|
||||
// Load or initialise KNOWLEDGE.md
|
||||
let existing = "";
|
||||
if (existsSync(knowledgePath)) {
|
||||
try {
|
||||
existing = readFileSync(knowledgePath, "utf-8");
|
||||
} catch {
|
||||
return { added: 0, skipped: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate: collect existing learned lines under our section (or globally)
|
||||
const existingLines = new Set(
|
||||
existing
|
||||
.split("\n")
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l.startsWith("- [")),
|
||||
);
|
||||
|
||||
let added = 0;
|
||||
let skipped = 0;
|
||||
const newLines: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const line = `- [${milestoneId}] ${entry.decision}: ${entry.reasoning}`;
|
||||
if (existingLines.has(line)) {
|
||||
skipped++;
|
||||
} else {
|
||||
newLines.push(line);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (added === 0) return { added: 0, skipped };
|
||||
|
||||
// Append or create section
|
||||
let updated: string;
|
||||
if (existing.includes(sectionHeading)) {
|
||||
// Section already exists — append after the heading line
|
||||
const idx = existing.indexOf(sectionHeading);
|
||||
const afterHeading = existing.indexOf("\n", idx + sectionHeading.length);
|
||||
const insertPos = afterHeading !== -1 ? afterHeading + 1 : existing.length;
|
||||
updated =
|
||||
existing.slice(0, insertPos) +
|
||||
newLines.join("\n") +
|
||||
"\n" +
|
||||
existing.slice(insertPos);
|
||||
} else if (!existing.trim()) {
|
||||
// New file
|
||||
updated = `# Project Knowledge\n\n${sectionHeading}\n\n${newLines.join("\n")}\n`;
|
||||
} else {
|
||||
// Append new section at end
|
||||
updated =
|
||||
existing.trimEnd() + `\n\n${sectionHeading}\n\n${newLines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
try {
|
||||
mkdirSync(dirname(knowledgePath), { recursive: true });
|
||||
writeFileSync(knowledgePath, updated, "utf-8");
|
||||
} catch {
|
||||
return { added: 0, skipped };
|
||||
}
|
||||
|
||||
return { added, skipped };
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue