feat(ms-ai-architect): Sesjon 3 - decision-ledger (lag 2) + discovery-dedup
Lukker discovery-løkken gjennom operatør-gaten. data/decisions.json er eneste skrive-autoriserte bro mellom deteksjon og KB/registry; discovery LESER den og utelater alt operatøren har tatt stilling til. Dedup-policy = A (operatør-valg): isDecided = enhver ledger-entry (approved/ rejected/pending). Kun helt fraværende URLer re-foreslås. - lib/decisions-io.mjs: createLedger/load/save(atomisk)/isDecided/recordDecision (ren)/filterUndecided. TDD: 10 tester før kode. - discover-new-urls.mjs leser ledger, filtrerer, rapporterer deduped_by_ledger. Importerer ALDRI write-utils (invariant verifisert, 6 guard-tester). - Gate dokumentert i kb-update.md §3b (eneste skrivevei). - decisions.json tracket via gitignore-negasjon (som domain-taxonomy.json). Kriterium møtt: dedup-diff (rejected re-foreslås ikke runde 2). Tester: validate 239 · kb-update 82 (+16) · kb-eval 13 · kb-integrity 115/115. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REiKFhP4w6xGXXqWKpPCJJ
This commit is contained in:
parent
444b9a375a
commit
fe484ec323
7 changed files with 323 additions and 2 deletions
|
|
@ -9,6 +9,9 @@ import { normalizeUrl } from './lib/url-normalize.mjs';
|
|||
import { loadRegistry, saveReport } from './lib/registry-io.mjs';
|
||||
import { streamSitemap, fetchSitemapIndex } from './lib/sitemap-stream.mjs';
|
||||
import { loadTaxonomy, getSitemapPrefixes, makeClassifier } from './lib/taxonomy.mjs';
|
||||
// Read-only: detection filters against the ledger but NEVER writes it (arkitektur-
|
||||
// invariant — no saveDecisions / registry / atomic-write / backup imports here).
|
||||
import { loadDecisions, isDecided } from './lib/decisions-io.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const DATA_DIR = join(__dirname, 'data');
|
||||
|
|
@ -33,6 +36,13 @@ async function main() {
|
|||
const knownUrls = new Set(Object.keys(registry.urls));
|
||||
console.log(`Registry: ${knownUrls.size} known URLs`);
|
||||
|
||||
// Decision ledger (lag 2): skip anything the operator already decided on
|
||||
// (policy A — approved | rejected | pending all dedup). Read-only here.
|
||||
const decisions = loadDecisions(DATA_DIR);
|
||||
const decidedCount = Object.keys(decisions.decisions || {}).length;
|
||||
console.log(`Decision ledger: ${decidedCount} decided URLs (excluded from re-proposal)`);
|
||||
let dedupedByLedger = 0;
|
||||
|
||||
console.log('Fetching sitemap index...');
|
||||
const indexEntries = await fetchSitemapIndex();
|
||||
|
||||
|
|
@ -55,6 +65,7 @@ async function main() {
|
|||
for await (const entry of streamSitemap(child.loc)) {
|
||||
const normalized = normalizeUrl(entry.loc);
|
||||
if (!normalized || knownUrls.has(normalized)) continue;
|
||||
if (isDecided(decisions, normalized)) { dedupedByLedger++; continue; }
|
||||
|
||||
const classification = classifyUrl(normalized);
|
||||
if (!classification) continue;
|
||||
|
|
@ -89,6 +100,7 @@ async function main() {
|
|||
const report = {
|
||||
generated_at: new Date().toISOString().split('T')[0],
|
||||
new_candidates: candidates.length,
|
||||
deduped_by_ledger: dedupedByLedger,
|
||||
by_suggested_skill: bySkill,
|
||||
candidates,
|
||||
};
|
||||
|
|
@ -97,6 +109,7 @@ async function main() {
|
|||
|
||||
console.log(`\n=== Discovery Report ===`);
|
||||
console.log(`New relevant URLs found: ${candidates.length}`);
|
||||
console.log(`Excluded by decision ledger: ${dedupedByLedger}`);
|
||||
console.log('By skill:', JSON.stringify(bySkill, null, 2));
|
||||
if (candidates.length > 0) {
|
||||
console.log('\nNewest 10:');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue