@treeseed/dsh-chapters 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +14 -0
- package/README.md +644 -0
- package/cordis.patch.yml +88 -0
- package/docs/architecture.md +460 -0
- package/docs/contract.md +375 -0
- package/docs/development.md +437 -0
- package/docs/host-compaction-seam.md +117 -0
- package/docs/knowledge-repo.md +730 -0
- package/docs/provider.md +98 -0
- package/docs/verify.md +200 -0
- package/lib/archive.d.ts +88 -0
- package/lib/archive.js +114 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +86 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +53 -0
- package/lib/artifact-query.js +130 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/client.js +234 -0
- package/lib/client.js.map +1 -0
- package/lib/commands.d.ts +28 -0
- package/lib/commands.js +305 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +131 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +153 -0
- package/lib/continue-core.js +252 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/engine-core.d.ts +219 -0
- package/lib/engine-core.js +405 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine.d.ts +211 -0
- package/lib/engine.js +571 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +100 -0
- package/lib/enrich-queue.js.map +1 -0
- package/lib/enrich-store.d.ts +31 -0
- package/lib/enrich-store.js +103 -0
- package/lib/enrich-store.js.map +1 -0
- package/lib/enrich-wire.d.ts +73 -0
- package/lib/enrich-wire.js +209 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +73 -0
- package/lib/enrich.js +150 -0
- package/lib/enrich.js.map +1 -0
- package/lib/gitops.d.ts +107 -0
- package/lib/gitops.js +539 -0
- package/lib/gitops.js.map +1 -0
- package/lib/index.d.ts +81 -0
- package/lib/index.js +305 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +85 -0
- package/lib/indexing.js +212 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +66 -0
- package/lib/injections.js +83 -0
- package/lib/injections.js.map +1 -0
- package/lib/notice.d.ts +60 -0
- package/lib/notice.js +84 -0
- package/lib/notice.js.map +1 -0
- package/lib/provider.d.ts +44 -0
- package/lib/provider.js +66 -0
- package/lib/provider.js.map +1 -0
- package/lib/redact.d.ts +24 -0
- package/lib/redact.js +85 -0
- package/lib/redact.js.map +1 -0
- package/lib/registry.d.ts +120 -0
- package/lib/registry.js +130 -0
- package/lib/registry.js.map +1 -0
- package/lib/render.d.ts +40 -0
- package/lib/render.js +280 -0
- package/lib/render.js.map +1 -0
- package/lib/repo.d.ts +78 -0
- package/lib/repo.js +184 -0
- package/lib/repo.js.map +1 -0
- package/lib/rules-commands.d.ts +24 -0
- package/lib/rules-commands.js +138 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +90 -0
- package/lib/rules.js +185 -0
- package/lib/rules.js.map +1 -0
- package/lib/search.d.ts +48 -0
- package/lib/search.js +152 -0
- package/lib/search.js.map +1 -0
- package/lib/signature.d.ts +28 -0
- package/lib/signature.js +149 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +130 -0
- package/lib/store.js +235 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +183 -0
- package/lib/sync.js +719 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +84 -0
- package/lib/tools.js +581 -0
- package/lib/tools.js.map +1 -0
- package/lib/treedx/client.d.ts +57 -0
- package/lib/treedx/client.js +82 -0
- package/lib/treedx/client.js.map +1 -0
- package/lib/treedx/provider.d.ts +39 -0
- package/lib/treedx/provider.js +479 -0
- package/lib/treedx/provider.js.map +1 -0
- package/lib/treedx/state.d.ts +27 -0
- package/lib/treedx/state.js +65 -0
- package/lib/treedx/state.js.map +1 -0
- package/lib/types.d.ts +100 -0
- package/lib/types.js +19 -0
- package/lib/types.js.map +1 -0
- package/lib/vocabulary.d.ts +41 -0
- package/lib/vocabulary.js +118 -0
- package/lib/vocabulary.js.map +1 -0
- package/package.json +132 -0
- package/presets/chapters/agent.cordis.yml +272 -0
- package/presets/chapters/preset.yml +3 -0
- package/presets/probe-roots/agent.cordis.yml +11 -0
- package/presets/probe-roots/preset.yml +3 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2 S2 — the enrichment queue: one queue, pluggable triggers, hard idle
|
|
3
|
+
* guarantee (record §6.2, plan decisions).
|
|
4
|
+
*
|
|
5
|
+
* Isomorphic factory (host plane + realm engine each own an instance, like
|
|
6
|
+
* createSyncScheduler). Providers are the ONLY variance:
|
|
7
|
+
* afterPush — schedule(reason) called after each completed sync pass
|
|
8
|
+
* idle — a resettable debounce timer (enrichment.idleMs)
|
|
9
|
+
* both — the default
|
|
10
|
+
* manual — no auto triggers; the /chapters-enrich run command drains
|
|
11
|
+
*
|
|
12
|
+
* Invariants this module owns:
|
|
13
|
+
* - NEVER in a hot path: enrichOne is only reached from drain(), and
|
|
14
|
+
* drain() is only fired by trigger events that already completed the
|
|
15
|
+
* user's work (post-push) or by silence (idle) or a command.
|
|
16
|
+
* - Idempotency key = chapter number + rootSession + model. Re-enqueue
|
|
17
|
+
* after vocab change is NOT needed (vocab affects canonicalization at
|
|
18
|
+
* index build, not the model call); after a MODEL change it IS (key
|
|
19
|
+
* includes model, so the key naturally changes).
|
|
20
|
+
* - enabled:false ⇒ every entry point is a pure no-op (kill-switch exit
|
|
21
|
+
* criterion: the corpus stays fully functional, signatures-only).
|
|
22
|
+
* - Cross-plane double-run: the caller-supplied lock (store-dir, same
|
|
23
|
+
* pattern as sync's acquireLock) makes the realm + host queues safe on
|
|
24
|
+
* one workspace.
|
|
25
|
+
*/
|
|
26
|
+
export interface EnrichQueueDeps {
|
|
27
|
+
/** enumerate chapters lacking current-model enrichment (cheap, durable-read only) */
|
|
28
|
+
listPending: (model: string, cap: number) => Promise<{
|
|
29
|
+
key: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}[]>;
|
|
32
|
+
/** run the ladder for one chapter; must never throw (S1 core + persistence) */
|
|
33
|
+
enrichOne: (key: string) => Promise<{
|
|
34
|
+
ok: boolean;
|
|
35
|
+
skipped?: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** current enrichment model id ('' = resolver said: conversation model — queue still needs a concrete string for keys) */
|
|
38
|
+
resolveModel: () => Promise<string>;
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
trigger: 'afterPush' | 'idle' | 'both' | 'manual';
|
|
41
|
+
idleMs: number;
|
|
42
|
+
batchCap: number;
|
|
43
|
+
log: (msg: string) => void;
|
|
44
|
+
/** injectable timer for tests */
|
|
45
|
+
setTimer?: (fn: () => void, ms: number) => {
|
|
46
|
+
cancel: () => void;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface EnrichQueue {
|
|
50
|
+
/** a sync pass just completed (or any natural idle point) */
|
|
51
|
+
onSyncDone: (reason: string) => void;
|
|
52
|
+
/** archive activity happened — resets the idle timer */
|
|
53
|
+
noteActivity: () => void;
|
|
54
|
+
/** manual drain (command surface); returns processed count */
|
|
55
|
+
drainNow: () => Promise<number>;
|
|
56
|
+
/** queue state for /chapters-status + reports */
|
|
57
|
+
state: () => {
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
trigger: string;
|
|
60
|
+
pendingEstimate: number;
|
|
61
|
+
lastBatch: number;
|
|
62
|
+
model: string | null;
|
|
63
|
+
};
|
|
64
|
+
dispose: () => void;
|
|
65
|
+
}
|
|
66
|
+
export declare function createEnrichQueue(deps: EnrichQueueDeps): EnrichQueue;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2 S2 — the enrichment queue: one queue, pluggable triggers, hard idle
|
|
3
|
+
* guarantee (record §6.2, plan decisions).
|
|
4
|
+
*
|
|
5
|
+
* Isomorphic factory (host plane + realm engine each own an instance, like
|
|
6
|
+
* createSyncScheduler). Providers are the ONLY variance:
|
|
7
|
+
* afterPush — schedule(reason) called after each completed sync pass
|
|
8
|
+
* idle — a resettable debounce timer (enrichment.idleMs)
|
|
9
|
+
* both — the default
|
|
10
|
+
* manual — no auto triggers; the /chapters-enrich run command drains
|
|
11
|
+
*
|
|
12
|
+
* Invariants this module owns:
|
|
13
|
+
* - NEVER in a hot path: enrichOne is only reached from drain(), and
|
|
14
|
+
* drain() is only fired by trigger events that already completed the
|
|
15
|
+
* user's work (post-push) or by silence (idle) or a command.
|
|
16
|
+
* - Idempotency key = chapter number + rootSession + model. Re-enqueue
|
|
17
|
+
* after vocab change is NOT needed (vocab affects canonicalization at
|
|
18
|
+
* index build, not the model call); after a MODEL change it IS (key
|
|
19
|
+
* includes model, so the key naturally changes).
|
|
20
|
+
* - enabled:false ⇒ every entry point is a pure no-op (kill-switch exit
|
|
21
|
+
* criterion: the corpus stays fully functional, signatures-only).
|
|
22
|
+
* - Cross-plane double-run: the caller-supplied lock (store-dir, same
|
|
23
|
+
* pattern as sync's acquireLock) makes the realm + host queues safe on
|
|
24
|
+
* one workspace.
|
|
25
|
+
*/
|
|
26
|
+
export function createEnrichQueue(deps) {
|
|
27
|
+
let idleTimer = null;
|
|
28
|
+
let draining = null;
|
|
29
|
+
let lastBatch = 0;
|
|
30
|
+
let modelCache = null;
|
|
31
|
+
let pendingEstimate = 0;
|
|
32
|
+
const setTimer = deps.setTimer ?? ((fn, ms) => {
|
|
33
|
+
const t = setTimeout(fn, ms);
|
|
34
|
+
return { cancel: () => clearTimeout(t) };
|
|
35
|
+
});
|
|
36
|
+
const drain = (forced = false) => {
|
|
37
|
+
// auto drains honor the trigger choice; a forced drain (the command /
|
|
38
|
+
// manual surface) only honors the kill switch
|
|
39
|
+
if (!deps.enabled || (!forced && deps.trigger === 'manual'))
|
|
40
|
+
return Promise.resolve(0);
|
|
41
|
+
if (draining !== null)
|
|
42
|
+
return draining; // one batch at a time; requests collapse
|
|
43
|
+
draining = (async () => {
|
|
44
|
+
try {
|
|
45
|
+
const model = await deps.resolveModel();
|
|
46
|
+
modelCache = model;
|
|
47
|
+
const pending = await deps.listPending(model, deps.batchCap);
|
|
48
|
+
pendingEstimate = pending.length;
|
|
49
|
+
let done = 0;
|
|
50
|
+
for (const item of pending) {
|
|
51
|
+
const r = await deps.enrichOne(item.key);
|
|
52
|
+
if (r.ok)
|
|
53
|
+
done++;
|
|
54
|
+
}
|
|
55
|
+
lastBatch = done;
|
|
56
|
+
if (done > 0)
|
|
57
|
+
deps.log(`enrichment batch: ${done}/${pending.length} chapter(s) via ${model}`);
|
|
58
|
+
return done;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
deps.log(`enrichment drain failed (kept silent for the user): ${String(error)}`);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
draining = null;
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
return draining;
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
onSyncDone: (reason) => {
|
|
72
|
+
if (!deps.enabled)
|
|
73
|
+
return;
|
|
74
|
+
if (deps.trigger === 'afterPush' || deps.trigger === 'both') {
|
|
75
|
+
deps.log(`enrichment scheduled after sync (${reason})`);
|
|
76
|
+
void drain();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
noteActivity: () => {
|
|
80
|
+
if (!deps.enabled || (deps.trigger !== 'idle' && deps.trigger !== 'both'))
|
|
81
|
+
return;
|
|
82
|
+
idleTimer?.cancel();
|
|
83
|
+
idleTimer = setTimer(() => { void drain(); }, deps.idleMs);
|
|
84
|
+
},
|
|
85
|
+
drainNow: async () => {
|
|
86
|
+
if (!deps.enabled)
|
|
87
|
+
return 0;
|
|
88
|
+
return await drain(true);
|
|
89
|
+
},
|
|
90
|
+
state: () => ({
|
|
91
|
+
enabled: deps.enabled,
|
|
92
|
+
trigger: deps.trigger,
|
|
93
|
+
pendingEstimate,
|
|
94
|
+
lastBatch,
|
|
95
|
+
model: modelCache,
|
|
96
|
+
}),
|
|
97
|
+
dispose: () => { idleTimer?.cancel(); idleTimer = null; },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=enrich-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich-queue.js","sourceRoot":"","sources":["../src/enrich-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ChapterDoc {
|
|
2
|
+
/** raw frontmatter lines (between the --- fences), verbatim */
|
|
3
|
+
fmLines: string[];
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseChapterFile(text: string): ChapterDoc;
|
|
7
|
+
export declare const bodyHash: (body: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Apply minimal frontmatter updates, body-guaranteed. expectedFileSha256 is
|
|
10
|
+
* the registry record's hash — which archive.ts computes over the WHOLE FILE
|
|
11
|
+
* (sha256(chapter.markdown)), the same value the tamper-check compares on-disk
|
|
12
|
+
* bytes against (archive.ts verify). The verbatim-BODY guarantee is the
|
|
13
|
+
* declared frontmatter anchor: a body that no longer matches its declared
|
|
14
|
+
* hash is corruption and refuses any write. Convention note: this guard once
|
|
15
|
+
* compared the registry's whole-file hash against the BODY hash — a mismatch
|
|
16
|
+
* on every real chapter, so enrichment silently never wrote (found by the
|
|
17
|
+
* enrich e2e 2026-09-22; the error was invisible behind the host logger).
|
|
18
|
+
* Callers MUST re-anchor the registry record with the returned fileSha256
|
|
19
|
+
* after a changed=true write (the ladder does).
|
|
20
|
+
*/
|
|
21
|
+
export declare function updateChapterFrontmatter(filePath: string, updates: Record<string, string>, expectedFileSha256?: string): {
|
|
22
|
+
changed: boolean;
|
|
23
|
+
body: string;
|
|
24
|
+
fileSha256: string;
|
|
25
|
+
};
|
|
26
|
+
/** YAML-ish serializer for the provenance chain (nested block, newest first) */
|
|
27
|
+
export declare function renderGeneratedBlock(chain: {
|
|
28
|
+
by: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
at?: string;
|
|
31
|
+
}[]): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2 S3 — provenance persistence (record §2.4/§6.2): the ONLY sanctioned
|
|
3
|
+
* chapter-file mutation in the system.
|
|
4
|
+
*
|
|
5
|
+
* A chapter file = YAML frontmatter + verbatim body. Enrichment may rewrite
|
|
6
|
+
* frontmatter fields it owns (title/summary/topics/generated); the body is
|
|
7
|
+
* untouchable, so EVERY write re-verifies the body hash before committing
|
|
8
|
+
* and refuses, loudly, on any mismatch. Frontmatter edits are LINE-BASED
|
|
9
|
+
* minimal replacements (order and unknown lines preserved — the file's own
|
|
10
|
+
* renderer stays the authority on shape); the write is atomic (tmp+rename);
|
|
11
|
+
* provenance chains grow, never truncate.
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
const FM_DELIM = '---';
|
|
17
|
+
export function parseChapterFile(text) {
|
|
18
|
+
if (!text.startsWith(`${FM_DELIM}\n`))
|
|
19
|
+
throw new Error('chapter file lacks frontmatter');
|
|
20
|
+
let endLine = -1;
|
|
21
|
+
const lines = text.split('\n');
|
|
22
|
+
for (let i = 1; i < lines.length; i++) {
|
|
23
|
+
if (lines[i] === FM_DELIM) {
|
|
24
|
+
endLine = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (endLine === -1)
|
|
29
|
+
throw new Error('unterminated frontmatter');
|
|
30
|
+
return { fmLines: lines.slice(1, endLine), body: lines.slice(endLine + 1).join('\n') };
|
|
31
|
+
}
|
|
32
|
+
export const bodyHash = (body) => createHash('sha256').update(body, 'utf8').digest('hex');
|
|
33
|
+
/** replace-or-append one top-level key; block values arrive as multi-line strings */
|
|
34
|
+
function setFmLine(lines, key, value) {
|
|
35
|
+
const start = lines.findIndex((l) => new RegExp(`^${key}:`).test(l));
|
|
36
|
+
const replacement = value.split('\n').map((l, i) => (i === 0 ? `${key}: ${l}` : l));
|
|
37
|
+
if (start === -1)
|
|
38
|
+
return [...lines, ...replacement];
|
|
39
|
+
let end = start + 1;
|
|
40
|
+
while (end < lines.length && /^\s/.test(lines[end]))
|
|
41
|
+
end++;
|
|
42
|
+
return [...lines.slice(0, start), ...replacement, ...lines.slice(end)];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Apply minimal frontmatter updates, body-guaranteed. expectedFileSha256 is
|
|
46
|
+
* the registry record's hash — which archive.ts computes over the WHOLE FILE
|
|
47
|
+
* (sha256(chapter.markdown)), the same value the tamper-check compares on-disk
|
|
48
|
+
* bytes against (archive.ts verify). The verbatim-BODY guarantee is the
|
|
49
|
+
* declared frontmatter anchor: a body that no longer matches its declared
|
|
50
|
+
* hash is corruption and refuses any write. Convention note: this guard once
|
|
51
|
+
* compared the registry's whole-file hash against the BODY hash — a mismatch
|
|
52
|
+
* on every real chapter, so enrichment silently never wrote (found by the
|
|
53
|
+
* enrich e2e 2026-09-22; the error was invisible behind the host logger).
|
|
54
|
+
* Callers MUST re-anchor the registry record with the returned fileSha256
|
|
55
|
+
* after a changed=true write (the ladder does).
|
|
56
|
+
*/
|
|
57
|
+
export function updateChapterFrontmatter(filePath, updates, expectedFileSha256) {
|
|
58
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
59
|
+
const doc = parseChapterFile(text);
|
|
60
|
+
const hash = bodyHash(doc.body);
|
|
61
|
+
const fileHash = bodyHash(text);
|
|
62
|
+
// self-consistency: a declared bodySha256 that no longer matches the body
|
|
63
|
+
// means on-disk corruption — refuse before anything else
|
|
64
|
+
// render.ts anchors the body hash under 'sha256:' — accept either name as
|
|
65
|
+
// the declared guard value
|
|
66
|
+
const declared = doc.fmLines.find((l) => /^(?:bodySha256|sha256):/.test(l))?.split(': ')[1]?.trim();
|
|
67
|
+
// Two legitimate declared forms exist: current (hash of the parsed body,
|
|
68
|
+
// trailing newline included) and legacy (hash of bodyText before the
|
|
69
|
+
// writer appended '\n' — every chapter archived before the 2026-09-23
|
|
70
|
+
// convention fix). Either matches; anything else is corruption. Accepting
|
|
71
|
+
// the legacy form keeps already-archived chapters enrichable.
|
|
72
|
+
if (declared !== undefined && declared !== hash && declared !== bodyHash(doc.body.replace(/\n$/, ''))) {
|
|
73
|
+
throw new Error(`refusing to touch ${filePath}: declared body hash ${declared.slice(0, 12)}… != computed ${hash.slice(0, 12)}… — body altered on disk`);
|
|
74
|
+
}
|
|
75
|
+
if (expectedFileSha256 !== undefined && expectedFileSha256 !== fileHash) {
|
|
76
|
+
throw new Error(`refusing to rewrite ${filePath}: file hash ${fileHash.slice(0, 12)}… != registry ${expectedFileSha256.slice(0, 12)}…`);
|
|
77
|
+
}
|
|
78
|
+
let fm = doc.fmLines;
|
|
79
|
+
let changed = false;
|
|
80
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
81
|
+
const next = setFmLine(fm, k, v);
|
|
82
|
+
if (next.join('\n') !== fm.join('\n')) {
|
|
83
|
+
fm = next;
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!changed)
|
|
88
|
+
return { changed: false, body: doc.body, fileSha256: fileHash };
|
|
89
|
+
// every sanctioned write carries a body anchor; render.ts names it sha256
|
|
90
|
+
if (!fm.some((l) => /^(?:bodySha256|sha256):/.test(l)))
|
|
91
|
+
fm = setFmLine(fm, 'sha256', hash);
|
|
92
|
+
const nextText = `${FM_DELIM}\n${fm.join('\n')}\n${FM_DELIM}\n${doc.body}`;
|
|
93
|
+
const tmp = path.join(path.dirname(filePath), `.${path.basename(filePath)}.tmp-${process.pid}`);
|
|
94
|
+
fs.writeFileSync(tmp, nextText);
|
|
95
|
+
fs.renameSync(tmp, filePath);
|
|
96
|
+
return { changed: true, body: doc.body, fileSha256: bodyHash(nextText) };
|
|
97
|
+
}
|
|
98
|
+
/** YAML-ish serializer for the provenance chain (nested block, newest first) */
|
|
99
|
+
export function renderGeneratedBlock(chain) {
|
|
100
|
+
const inner = chain.map((m) => m.by === 'model' ? `\n - by: model\n model: ${m.model}\n at: ${m.at ?? ''}` : '\n - by: deterministic');
|
|
101
|
+
return inner.join('');
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=enrich-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich-store.js","sourceRoot":"","sources":["../src/enrich-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type EnrichQueue } from './enrich-queue.ts';
|
|
2
|
+
import { type GeneratedProvenance, type GeneratedMark } from './enrich.ts';
|
|
3
|
+
export interface EnrichConfig {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
/** 'provider/model' or '' = follow the conversation (realm) / stored route (host) */
|
|
6
|
+
model: string;
|
|
7
|
+
trigger: 'afterPush' | 'idle' | 'both' | 'manual';
|
|
8
|
+
idleMs: number;
|
|
9
|
+
batchCap: number;
|
|
10
|
+
}
|
|
11
|
+
export interface EnrichWiringDeps {
|
|
12
|
+
store: {
|
|
13
|
+
get(id: string): Promise<{
|
|
14
|
+
chapters: {
|
|
15
|
+
number: number;
|
|
16
|
+
path: string;
|
|
17
|
+
title: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
topics: string[];
|
|
20
|
+
sha256: string;
|
|
21
|
+
generated?: Record<string, GeneratedMark[]>;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
24
|
+
put(id: string, state: unknown): Promise<void>;
|
|
25
|
+
sessions(): IterableIterator<[string, unknown]>;
|
|
26
|
+
getSetting(key: string): string | undefined;
|
|
27
|
+
putSetting(key: string, value: string): Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
cwd(): string | undefined;
|
|
30
|
+
config: EnrichConfig;
|
|
31
|
+
/** one completion; may reject — the ladder catches */
|
|
32
|
+
fetch(prompt: string, route: {
|
|
33
|
+
provider: string;
|
|
34
|
+
model: string;
|
|
35
|
+
} | null): Promise<string>;
|
|
36
|
+
/** route resolution when neither override nor config names one (realm: live agent; host: persisted) */
|
|
37
|
+
conversationRoute(): {
|
|
38
|
+
provider: string;
|
|
39
|
+
model: string;
|
|
40
|
+
} | null;
|
|
41
|
+
scheduler?: {
|
|
42
|
+
schedule(cwd: string, why: string): void;
|
|
43
|
+
};
|
|
44
|
+
log(m: string): void;
|
|
45
|
+
warn(m: string): void;
|
|
46
|
+
setTimer?: (fn: () => void, ms: number) => {
|
|
47
|
+
cancel(): void;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface EnrichWiring {
|
|
51
|
+
queue: EnrichQueue;
|
|
52
|
+
/** manual drain for the command surface (cwd arms the workspace for files) */
|
|
53
|
+
runNow(cwd?: string): Promise<{
|
|
54
|
+
processed: number;
|
|
55
|
+
remaining: number;
|
|
56
|
+
}>;
|
|
57
|
+
setModelOverride(v: string | null): Promise<void>;
|
|
58
|
+
modelOverride(): string | null;
|
|
59
|
+
statusLine(): string;
|
|
60
|
+
/** pending chapter count for reports (full scan, un-capped) */
|
|
61
|
+
pendingCount(): Promise<number>;
|
|
62
|
+
/** the resolved auxiliary route (enrichment AND plot share it, P2 decision) */
|
|
63
|
+
auxRoute(): {
|
|
64
|
+
provider: string;
|
|
65
|
+
model: string;
|
|
66
|
+
} | null;
|
|
67
|
+
/** planes remember the workspace seen at sync/command time */
|
|
68
|
+
rememberCwd(cwd: string): void;
|
|
69
|
+
dispose(): void;
|
|
70
|
+
}
|
|
71
|
+
/** YAML block for the generated: frontmatter field (newest-first chains) */
|
|
72
|
+
export declare function renderGeneratedBlock(gen: GeneratedProvenance): string;
|
|
73
|
+
export declare function createEnrichWiring(deps: EnrichWiringDeps): EnrichWiring;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2 S6 — enrichment wiring: binds the pure ladder (enrich.ts) and the
|
|
3
|
+
* guarded persistence (enrich-store.ts) to the registry domain and the sync
|
|
4
|
+
* scheduler, per plane.
|
|
5
|
+
*
|
|
6
|
+
* Division of labor (deliberate):
|
|
7
|
+
* - REALM engine owns the AUTO triggers (afterPush/idle/both): it sees the
|
|
8
|
+
* conversation's routed model (requestHeader) and fires after its own
|
|
9
|
+
* pushes/finalizes; it persists the resolved provider/model route and a
|
|
10
|
+
* state heartbeat into the shared settings table.
|
|
11
|
+
* - HOST plane runs the command surface (manual drains, model override,
|
|
12
|
+
* report/status) and reads the same tables; its queue is trigger-manual
|
|
13
|
+
* so auto work never double-runs.
|
|
14
|
+
*
|
|
15
|
+
* The ladder per chapter: build slice -> fetch -> validate; invalid -> ONE
|
|
16
|
+
* retry with a nudge; still invalid -> keep deterministic values (never
|
|
17
|
+
* blocks, never blanks). Frontmatter write goes through the body-hash guard;
|
|
18
|
+
* the registry record mirrors the new fields; a modified file is republished
|
|
19
|
+
* via the scheduler (debounced).
|
|
20
|
+
*/
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { createEnrichQueue } from "./enrich-queue.js";
|
|
24
|
+
import { buildEnrichInput, parseEnrichResult, applyEnrich } from "./enrich.js";
|
|
25
|
+
import { parseChapterFile, updateChapterFrontmatter } from "./enrich-store.js";
|
|
26
|
+
const ROUTE_KEY = 'enrichment.route';
|
|
27
|
+
const STATE_KEY = 'enrichment.state';
|
|
28
|
+
const stripFence = (chunk) => chunk.trim().replace(/^```[a-z]*\n?/, '').replace(/\n?```\s*$/, '').trim();
|
|
29
|
+
/** YAML block for the generated: frontmatter field (newest-first chains) */
|
|
30
|
+
export function renderGeneratedBlock(gen) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
for (const key of ['title', 'summary', 'topics']) {
|
|
33
|
+
const chain = gen[key];
|
|
34
|
+
if (chain === undefined || chain.length === 0)
|
|
35
|
+
continue;
|
|
36
|
+
lines.push(` ${key}:`);
|
|
37
|
+
for (const m of chain) {
|
|
38
|
+
if (m.by === 'model')
|
|
39
|
+
lines.push(` - by: model`, ` model: ${m.model}`, ` at: ${m.at ?? ''}`);
|
|
40
|
+
else
|
|
41
|
+
lines.push(` - by: deterministic`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return '\n' + lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
function chaptersOf(state) {
|
|
47
|
+
const c = state?.chapters;
|
|
48
|
+
return Array.isArray(c) ? c : [];
|
|
49
|
+
}
|
|
50
|
+
export function createEnrichWiring(deps) {
|
|
51
|
+
const resolveRoute = () => {
|
|
52
|
+
const override = deps.store.getSetting('enrichment.model') ?? '';
|
|
53
|
+
const spec = (override !== '' ? override : deps.config.model).trim();
|
|
54
|
+
if (spec !== '' && spec !== 'conversation') {
|
|
55
|
+
const i = spec.indexOf('/');
|
|
56
|
+
if (i > 0)
|
|
57
|
+
return { provider: spec.slice(0, i), model: spec.slice(i + 1) };
|
|
58
|
+
return { provider: deps.conversationRoute()?.provider ?? '', model: spec };
|
|
59
|
+
}
|
|
60
|
+
const persisted = deps.store.getSetting(ROUTE_KEY);
|
|
61
|
+
if (persisted !== undefined) {
|
|
62
|
+
const i = persisted.indexOf('/');
|
|
63
|
+
if (i > 0)
|
|
64
|
+
return { provider: persisted.slice(0, i), model: persisted.slice(i + 1) };
|
|
65
|
+
}
|
|
66
|
+
return deps.conversationRoute();
|
|
67
|
+
};
|
|
68
|
+
const modelKey = () => {
|
|
69
|
+
const r = resolveRoute();
|
|
70
|
+
return r !== null && r.model !== '' ? `${r.provider}/${r.model}` : 'conversation';
|
|
71
|
+
};
|
|
72
|
+
let rememberedCwd;
|
|
73
|
+
const ladder = async (key) => {
|
|
74
|
+
const sep = key.lastIndexOf('#');
|
|
75
|
+
const sid = key.slice(0, sep);
|
|
76
|
+
const num = Number(key.slice(sep + 1));
|
|
77
|
+
const cwd = deps.cwd() ?? rememberedCwd;
|
|
78
|
+
if (cwd === undefined)
|
|
79
|
+
return { ok: false, skipped: 'no workspace cwd yet (a sync or command arms it)' };
|
|
80
|
+
try {
|
|
81
|
+
const state = await deps.store.get(sid);
|
|
82
|
+
const rec = state.chapters.find((c) => c.number === num);
|
|
83
|
+
if (rec === undefined)
|
|
84
|
+
return { ok: false, skipped: 'record gone' };
|
|
85
|
+
const file = path.join(cwd, rec.path);
|
|
86
|
+
if (!fs.existsSync(file))
|
|
87
|
+
return { ok: false, skipped: 'chapter file absent' };
|
|
88
|
+
const doc = parseChapterFile(fs.readFileSync(file, 'utf8'));
|
|
89
|
+
const body = doc.body;
|
|
90
|
+
const userChunks = body.split(/\*\*User[^*\n]*:\*\*/g).slice(1);
|
|
91
|
+
const asstChunks = body.split(/\*\*Assistant[^*\n]*:\*\*/g).slice(1);
|
|
92
|
+
const input = buildEnrichInput({ title: rec.title }, {
|
|
93
|
+
requests: userChunks.slice(-3).map(stripFence),
|
|
94
|
+
...(asstChunks.length > 0 ? { firstAssistant: stripFence(asstChunks[0]) } : {}),
|
|
95
|
+
...(asstChunks.length > 1 ? { lastAssistant: stripFence(asstChunks[asstChunks.length - 1]) } : asstChunks.length === 1 && userChunks.length === 0 ? { lastAssistant: stripFence(asstChunks[0]) } : {}),
|
|
96
|
+
}, body.slice(0, 8000));
|
|
97
|
+
const route = resolveRoute();
|
|
98
|
+
const key2 = modelKey();
|
|
99
|
+
let result = parseEnrichResult(await deps.fetch(input, route));
|
|
100
|
+
if (result === null) {
|
|
101
|
+
// one retry with a nudge; garbage stays non-fatal (record §6.1)
|
|
102
|
+
result = parseEnrichResult(await deps.fetch(input + '\nRemember: reply with ONLY the JSON object, no other text.', route));
|
|
103
|
+
}
|
|
104
|
+
const current = { title: rec.title, summary: rec.summary, topics: rec.topics, ...(rec.generated !== undefined ? { generated: rec.generated } : {}) };
|
|
105
|
+
const applied = applyEnrich(current, result, key2, new Date().toISOString());
|
|
106
|
+
if (!applied.changed)
|
|
107
|
+
return { ok: false, skipped: result === null ? 'model output invalid; deterministic kept' : 'already enriched' };
|
|
108
|
+
const v = applied.values;
|
|
109
|
+
const written = updateChapterFrontmatter(file, {
|
|
110
|
+
title: JSON.stringify(v.title),
|
|
111
|
+
summary: JSON.stringify(v.summary),
|
|
112
|
+
topics: `[${v.topics.map((t) => JSON.stringify(t)).join(', ')}]`,
|
|
113
|
+
generated: renderGeneratedBlock(v.generated),
|
|
114
|
+
}, typeof rec.sha256 === 'string' ? rec.sha256 : undefined);
|
|
115
|
+
await deps.store.put(sid, {
|
|
116
|
+
...state,
|
|
117
|
+
chapters: state.chapters.map((c) => c.number === num
|
|
118
|
+
// re-anchor the registry's whole-file hash after the sanctioned write
|
|
119
|
+
// so the cite-time tamper check stays truthful for every descendant
|
|
120
|
+
? { ...c, title: v.title, summary: v.summary, topics: v.topics, generated: v.generated, ...(written.changed ? { sha256: written.fileSha256 } : {}) }
|
|
121
|
+
: c),
|
|
122
|
+
});
|
|
123
|
+
deps.scheduler?.schedule(cwd, 'archive:enrichment');
|
|
124
|
+
return { ok: true };
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
deps.warn(`enrichment failed for ${key}: ${String(error?.message ?? error).slice(0, 120)}`);
|
|
128
|
+
return { ok: false, skipped: 'error' };
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const queue = createEnrichQueue({
|
|
132
|
+
listPending: async (modelKey2, cap) => {
|
|
133
|
+
const out = [];
|
|
134
|
+
for (const [sid, st] of deps.store.sessions()) {
|
|
135
|
+
for (const c of chaptersOf(st)) {
|
|
136
|
+
const top = c.generated?.title?.[0];
|
|
137
|
+
if (top?.by === 'model' && top.model === modelKey2)
|
|
138
|
+
continue;
|
|
139
|
+
out.push({ key: `${sid}#${c.number}`, label: c.title.slice(0, 48) });
|
|
140
|
+
if (out.length >= cap)
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
},
|
|
146
|
+
enrichOne: ladder,
|
|
147
|
+
resolveModel: async () => modelKey(),
|
|
148
|
+
enabled: deps.config.enabled,
|
|
149
|
+
trigger: deps.config.trigger,
|
|
150
|
+
idleMs: deps.config.idleMs,
|
|
151
|
+
batchCap: deps.config.batchCap,
|
|
152
|
+
log: (m) => {
|
|
153
|
+
deps.log(m);
|
|
154
|
+
const r = resolveRoute();
|
|
155
|
+
if (r !== null)
|
|
156
|
+
void deps.store.putSetting(ROUTE_KEY, `${r.provider}/${r.model}`).catch(() => undefined);
|
|
157
|
+
void deps.store.putSetting(STATE_KEY, JSON.stringify({ at: new Date().toISOString(), line: m })).catch(() => undefined);
|
|
158
|
+
},
|
|
159
|
+
...(deps.setTimer !== undefined ? { setTimer: deps.setTimer } : {}),
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
queue,
|
|
163
|
+
auxRoute: () => resolveRoute(),
|
|
164
|
+
rememberCwd(c) { rememberedCwd = c; },
|
|
165
|
+
async pendingCount() {
|
|
166
|
+
let n = 0;
|
|
167
|
+
const key2 = modelKey();
|
|
168
|
+
for (const [, st] of deps.store.sessions()) {
|
|
169
|
+
for (const c of chaptersOf(st)) {
|
|
170
|
+
const top = c.generated?.title?.[0];
|
|
171
|
+
if (!(top?.by === 'model' && top.model === key2))
|
|
172
|
+
n += 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return n;
|
|
176
|
+
},
|
|
177
|
+
async runNow(cwd) {
|
|
178
|
+
if (cwd !== undefined)
|
|
179
|
+
rememberedCwd = cwd;
|
|
180
|
+
const processed = await queue.drainNow();
|
|
181
|
+
return { processed, remaining: await this.pendingCount() };
|
|
182
|
+
},
|
|
183
|
+
async setModelOverride(v) {
|
|
184
|
+
if (v === null)
|
|
185
|
+
await deps.store.putSetting('enrichment.model', '');
|
|
186
|
+
else
|
|
187
|
+
await deps.store.putSetting('enrichment.model', v);
|
|
188
|
+
},
|
|
189
|
+
modelOverride() {
|
|
190
|
+
const o = deps.store.getSetting('enrichment.model') ?? '';
|
|
191
|
+
return o === '' ? null : o;
|
|
192
|
+
},
|
|
193
|
+
statusLine() {
|
|
194
|
+
if (!deps.config.enabled)
|
|
195
|
+
return 'enrichment: disabled (signatures-only corpus, fully functional)';
|
|
196
|
+
const override = this.modelOverride();
|
|
197
|
+
const model = override ?? (deps.config.model !== '' ? deps.config.model : 'conversation model');
|
|
198
|
+
const heartbeat = deps.store.getSetting(STATE_KEY);
|
|
199
|
+
let last = '';
|
|
200
|
+
try {
|
|
201
|
+
last = ` · last: ${JSON.parse(heartbeat ?? '{}').line ?? 'idle'}`;
|
|
202
|
+
}
|
|
203
|
+
catch { /* cosmetic */ }
|
|
204
|
+
return `enrichment: ${deps.config.trigger}, model ${model}${last}`;
|
|
205
|
+
},
|
|
206
|
+
dispose() { queue.dispose(); },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=enrich-wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich-wire.js","sourceRoot":"","sources":["../src/enrich-wire.ts"],"names":[],"mappings":"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"}
|