@treeseed/dsh-chapters 0.1.24 → 0.2.1
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/README.md +2 -1
- package/cordis.patch.yml +49 -1
- package/docs/architecture.md +1 -496
- package/docs/backlog.md +234 -4
- package/docs/contract.md +127 -1
- package/docs/development.md +248 -12
- package/docs/host-compaction-seam.md +108 -0
- package/docs/knowledge-repo.md +532 -16
- package/docs/project-state.md +323 -0
- package/docs/verify.md +266 -0
- package/lib/archive.d.ts +49 -1
- package/lib/archive.js +56 -2
- package/lib/archive.js.map +1 -1
- package/lib/arrival.js +24 -10
- package/lib/arrival.js.map +1 -1
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +74 -0
- package/lib/auto-save.js +175 -26
- package/lib/auto-save.js.map +1 -1
- package/lib/client.js +413 -11
- package/lib/client.js.map +1 -1
- package/lib/commands.d.ts +8 -1
- package/lib/commands.js +135 -7
- package/lib/commands.js.map +1 -1
- package/lib/compose.js +25 -6
- package/lib/compose.js.map +1 -1
- package/lib/continue-core.d.ts +24 -1
- package/lib/continue-core.js +41 -9
- package/lib/continue-core.js.map +1 -1
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +53 -1
- package/lib/engine-core.js +64 -7
- package/lib/engine-core.js.map +1 -1
- package/lib/engine.d.ts +99 -0
- package/lib/engine.js +488 -36
- package/lib/engine.js.map +1 -1
- package/lib/enrich-queue.js +28 -2
- package/lib/enrich-queue.js.map +1 -1
- package/lib/enrich-wire.d.ts +92 -2
- package/lib/enrich-wire.js +135 -17
- package/lib/enrich-wire.js.map +1 -1
- package/lib/enrich.d.ts +27 -2
- package/lib/enrich.js +114 -18
- package/lib/enrich.js.map +1 -1
- package/lib/index.d.ts +52 -0
- package/lib/index.js +182 -2
- package/lib/index.js.map +1 -1
- package/lib/indexing.d.ts +7 -0
- package/lib/indexing.js.map +1 -1
- package/lib/injections.d.ts +1 -0
- package/lib/injections.js +38 -0
- package/lib/injections.js.map +1 -1
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/prompt.d.ts +130 -0
- package/lib/prompt.js +255 -0
- package/lib/prompt.js.map +1 -0
- package/lib/registry.d.ts +39 -1
- package/lib/registry.js +76 -9
- package/lib/registry.js.map +1 -1
- package/lib/render.d.ts +31 -3
- package/lib/render.js +75 -3
- package/lib/render.js.map +1 -1
- package/lib/repair-run.d.ts +49 -0
- package/lib/repair-run.js +423 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +200 -0
- package/lib/repair.js +354 -0
- package/lib/repair.js.map +1 -0
- package/lib/rules-commands.d.ts +22 -0
- package/lib/rules-commands.js +177 -20
- package/lib/rules-commands.js.map +1 -1
- package/lib/rules.d.ts +184 -7
- package/lib/rules.js +312 -27
- package/lib/rules.js.map +1 -1
- package/lib/save-core.d.ts +31 -1
- package/lib/save-core.js +153 -17
- package/lib/save-core.js.map +1 -1
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/store.d.ts +22 -1
- package/lib/store.js +94 -17
- package/lib/store.js.map +1 -1
- package/lib/sync.js +13 -1
- package/lib/sync.js.map +1 -1
- package/lib/tools.d.ts +14 -0
- package/lib/tools.js +321 -32
- package/lib/tools.js.map +1 -1
- package/lib/types.d.ts +12 -1
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +9 -3
package/lib/save-core.js
CHANGED
|
@@ -14,26 +14,95 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { composeChapters } from "./compose.js";
|
|
16
16
|
import { deriveRanges } from "./continue-core.js";
|
|
17
|
-
import { renderChapter } from "./render.js";
|
|
17
|
+
import { estimateTokens, oversizeRanges, renderChapter } from "./render.js";
|
|
18
18
|
import { chapterTopics } from "./signature.js";
|
|
19
19
|
import { makeArchiveFs, commitChapters } from "./store.js";
|
|
20
|
-
import { reserve, reservePlan as reservePlanNumber } from "./registry.js";
|
|
20
|
+
import { releaseReservation, reserve, reservePlan as reservePlanNumber } from "./registry.js";
|
|
21
|
+
/** One serialized read-allocate-write for a door that cannot use makeAllocator (same rule, #59). */
|
|
22
|
+
async function reserveNumbers(store, sessionId, attemptId, count, plan = false) {
|
|
23
|
+
let numbers = [];
|
|
24
|
+
await store.mutate(sessionId, (state) => {
|
|
25
|
+
const r = plan ? reservePlanNumber(state, attemptId, count) : reserve(state, attemptId, count);
|
|
26
|
+
numbers = r.numbers;
|
|
27
|
+
return r.state;
|
|
28
|
+
});
|
|
29
|
+
return { numbers };
|
|
30
|
+
}
|
|
21
31
|
import { writeArchive, attemptKey } from "./archive.js";
|
|
32
|
+
import { supersedeChapter } from "./repair.js";
|
|
33
|
+
/**
|
|
34
|
+
* Split a range whose RENDERED size would blow past the oversize ceiling (#60, repair path only).
|
|
35
|
+
*
|
|
36
|
+
* The normal doors cut on `turn/end` and warn when a chapter is over `chapterTokenTarget`, because a
|
|
37
|
+
* boundary is around the corner and clipping is forbidden. A REPAIR span can be one gigantic turn — a
|
|
38
|
+
* delegated subagent whose single turn ran to 4 MB — where there IS no next boundary, and refusing it
|
|
39
|
+
* (the #59c gate) would leave the husk superseded-by-nothing. So the repair splits at EVENT boundaries
|
|
40
|
+
* inside the turn: nothing is truncated, every byte still lands in a chapter, and the TOC gains several
|
|
41
|
+
* consecutive ranges instead of one unreadable one. The rule that binds is completeness, not "one chapter
|
|
42
|
+
* per turn" — and a chapter nobody can `read` back is not an archive.
|
|
43
|
+
*/
|
|
44
|
+
function splitOversize(chapters, events, chapterTokenTarget, oversizeFactor) {
|
|
45
|
+
const ceiling = Math.floor(chapterTokenTarget * oversizeFactor);
|
|
46
|
+
const notes = [];
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const c of chapters) {
|
|
49
|
+
const inRange = events.filter((e) => e.seq >= c.startSeq && e.seq <= c.endSeq);
|
|
50
|
+
const total = inRange.reduce((m, e) => m + estimateTokens(JSON.stringify(e.data ?? {})), 0);
|
|
51
|
+
if (total <= ceiling) {
|
|
52
|
+
out.push(c);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const parts = Math.ceil(total / ceiling);
|
|
56
|
+
const perPart = Math.ceil(inRange.length / parts);
|
|
57
|
+
let title = c.title;
|
|
58
|
+
for (let i = 0, n = 0; i < inRange.length; i += perPart, n++) {
|
|
59
|
+
const from = inRange[i].seq;
|
|
60
|
+
const to = inRange[Math.min(i + perPart, inRange.length) - 1].seq;
|
|
61
|
+
out.push({ ...c, title: n === 0 ? title : `${title} (part ${n + 1})`, startSeq: from, endSeq: to });
|
|
62
|
+
}
|
|
63
|
+
notes.push(`"${c.title}" (seqs ${c.startSeq}-${c.endSeq}) is ${total} est tokens in a single unfinished turn — split into ${parts} chapters at event boundaries (repair path; nothing truncated)`);
|
|
64
|
+
}
|
|
65
|
+
return { ranges: out, notes };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Force a repaired archive to cover exactly the span the finding named (#60). Composition may still cut
|
|
69
|
+
* it into topics - a cut is a chapter boundary, not a loss - but nothing may start above `fromSeq` or
|
|
70
|
+
* stop before the last completed turn inside the span, or the hole the finding reported stays open while
|
|
71
|
+
* the repair reports success.
|
|
72
|
+
*/
|
|
73
|
+
function clampSpan(chapters, fromSeq, toSeq, events) {
|
|
74
|
+
// Nothing to clamp: the span held no completed turn at all. Returning EMPTY is the honest answer —
|
|
75
|
+
// inventing a title here would fabricate a chapter, and an empty `records` makes the caller skip the
|
|
76
|
+
// supersede, which is exactly right: a record is never marked superseded without its replacement (#60).
|
|
77
|
+
if (chapters.length === 0)
|
|
78
|
+
return [];
|
|
79
|
+
const lastCompleted = events.reduce((m, e) => (e.type === 'turn/end' && e.seq <= toSeq && e.seq > m ? e.seq : m), fromSeq);
|
|
80
|
+
return chapters.map((c, i) => ({
|
|
81
|
+
...c,
|
|
82
|
+
startSeq: i === 0 ? Math.min(c.startSeq, fromSeq) : Math.max(c.startSeq, chapters[i - 1].endSeq + 1),
|
|
83
|
+
endSeq: i === chapters.length - 1 ? lastCompleted : Math.min(c.endSeq, lastCompleted),
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
22
86
|
export async function saveSession(a) {
|
|
23
87
|
const { events, sessionId, cwd, store, config } = a;
|
|
24
88
|
if (cwd === '')
|
|
25
89
|
throw new Error('saveSession: session has no workspace cwd — nowhere reachable to write');
|
|
26
90
|
if (sessionId === '')
|
|
27
91
|
throw new Error('saveSession: no session id to own the archive');
|
|
28
|
-
|
|
92
|
+
// A repair targets a span it chose; every other door archives the watermark tail up to the last
|
|
93
|
+
// completed turn. One pipeline, one difference (#60).
|
|
94
|
+
const anchor = a.span !== undefined ? a.span.toSeq : [...events].reverse().find((e) => e.type === 'turn/end')?.seq;
|
|
29
95
|
if (anchor === undefined) {
|
|
30
96
|
return { skipped: 'no-completed-turn', fromSeq: 0, anchor: -1, lastArchived: 0, priorChapters: 0, records: [], warnings: [] };
|
|
31
97
|
}
|
|
32
98
|
// Watermark: only archive what is newer than the last saved chapter.
|
|
33
99
|
const state = await store.get(sessionId);
|
|
34
100
|
const lastArchived = state.chapters.reduce((m, c) => Math.max(m, c.endSeq), 0);
|
|
35
|
-
const fromSeq = lastArchived > 0 ? lastArchived + 1 : 0;
|
|
36
|
-
|
|
101
|
+
const fromSeq = a.span !== undefined ? a.span.fromSeq : (lastArchived > 0 ? lastArchived + 1 : 0);
|
|
102
|
+
// A REPAIR span is deliberate re-derivation of seqs the watermark has already passed (#60): the
|
|
103
|
+
// nothing-new guard is about not re-archiving a tail, and would otherwise refuse to fix the hole a
|
|
104
|
+
// hollow chapter sits exactly on top of. The span caller has already decided what needs re-rendering.
|
|
105
|
+
if (a.span === undefined && fromSeq > 0 && anchor <= lastArchived) {
|
|
37
106
|
return { skipped: 'nothing-new', fromSeq, anchor, lastArchived, priorChapters: state.chapters.length, records: [], warnings: [] };
|
|
38
107
|
}
|
|
39
108
|
// Topic-sequential composition — same pipeline as fork/continue.
|
|
@@ -41,9 +110,21 @@ export async function saveSession(a) {
|
|
|
41
110
|
mergeThreshold: config.mergeThreshold,
|
|
42
111
|
chapterLimit: config.chapterLimit,
|
|
43
112
|
});
|
|
44
|
-
|
|
113
|
+
let chapters = composed.chapters.length > 0
|
|
45
114
|
? composed.chapters
|
|
46
115
|
: deriveRanges(events, anchor, config.chapterTokenTarget, fromSeq).chapters;
|
|
116
|
+
const repairNotes = [];
|
|
117
|
+
if (a.span !== undefined) {
|
|
118
|
+
// A repair's span is bounded by the finding, so force exactly that range: composition inside a hole
|
|
119
|
+
// may cut it into topics, and a cut is fine, but nothing may start above `fromSeq` or end below the
|
|
120
|
+
// last completed turn inside the span.
|
|
121
|
+
chapters = clampSpan(chapters, a.span.fromSeq, a.span.toSeq, events);
|
|
122
|
+
// A repair span may be one enormous turn; the oversize gate would refuse it and leave the husk
|
|
123
|
+
// un-replaced. Split inside it at event boundaries — see `splitOversize`.
|
|
124
|
+
const split = splitOversize(chapters, events, config.chapterTokenTarget, config.chapterOversizeFactor ?? 2);
|
|
125
|
+
chapters = split.ranges;
|
|
126
|
+
repairNotes.push(...split.notes);
|
|
127
|
+
}
|
|
47
128
|
// Render + write archive (same as runContinue steps 2-3, minus child creation).
|
|
48
129
|
const below = events.filter((e) => e.seq <= anchor);
|
|
49
130
|
const renderConfig = {
|
|
@@ -54,31 +135,86 @@ export async function saveSession(a) {
|
|
|
54
135
|
...(a.subagentOf !== undefined && a.subagentOf !== '' ? { subagentOf: a.subagentOf } : {}),
|
|
55
136
|
};
|
|
56
137
|
const rendered = chapters.map((range) => renderChapter(below, range, renderConfig, [], chapterTopics(below, range.startSeq, range.endSeq)));
|
|
138
|
+
// #58 — NEVER commit an archive that rendered nothing. A `tool/call` is always preceded by an
|
|
139
|
+
// `assistant/message`, so a non-empty range with zero rendered messages is not a short
|
|
140
|
+
// conversation: it is a read that carried no payloads. The real instance was
|
|
141
|
+
// `sessionQuery.listEvents`, whose `SessionEventRecord` rows have `seq`/`type`/`time`/`surface`
|
|
142
|
+
// and no `data` — 12 children on this machine archived as `**unknown**` / `{}` / `0 B` shells.
|
|
143
|
+
// The refusal is BEFORE `writeArchive`, so no number is reserved and no file is written: a
|
|
144
|
+
// committed husk would poison the watermark (`max(endSeq)`), and the child could then never be
|
|
145
|
+
// re-archived without a repair (#60). Refuse with numbers, never truncate — AGENTS.md.
|
|
146
|
+
//
|
|
147
|
+
// AMENDED #60: the test is on the ARCHIVE, not on each chapter. A repair may split one enormous turn
|
|
148
|
+
// into consecutive parts, and a part holding only tool calls legitimately renders zero messages —
|
|
149
|
+
// refusing the whole re-read over that stranded five SAIP children's knowledge again, which is the
|
|
150
|
+
// opposite of what the gate protects. "No payloads anywhere" is the anomaly; `describeHollow` keeps
|
|
151
|
+
// naming every message-free part so it is never silent, and the single-chapter case (#58's instance)
|
|
152
|
+
// still refuses exactly as before.
|
|
153
|
+
const describeHollow = (r) => {
|
|
154
|
+
const un = r.stats.unrenderedSeqs;
|
|
155
|
+
return `"${r.range.title}" spans seqs ${r.range.startSeq}..${r.range.endSeq}: ${r.stats.events} events, `
|
|
156
|
+
+ `${r.stats.toolCalls} tool calls, 0 rendered messages, ${un.length} unrendered`
|
|
157
|
+
+ `${un.length > 0 ? ` (first: ${un.slice(0, 5).join(', ')})` : ''}`;
|
|
158
|
+
};
|
|
159
|
+
const hollow = rendered.filter((r) => r.stats.events > 0 && r.stats.messages === 0);
|
|
160
|
+
if (rendered.length > 0 && hollow.length === rendered.length) {
|
|
161
|
+
return {
|
|
162
|
+
skipped: 'payloadless',
|
|
163
|
+
fromSeq,
|
|
164
|
+
anchor,
|
|
165
|
+
lastArchived,
|
|
166
|
+
priorChapters: state.chapters.length,
|
|
167
|
+
records: [],
|
|
168
|
+
warnings: hollow.map((r) => `${describeHollow(r)} — the read carried event SHAPES but no payloads; nothing was reserved or written`),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Parts that carry tool calls but no prose are reported and STILL archived (#60): "what was
|
|
172
|
+
// attempted" is half the record, and silence about it is the defect class this issue is named for.
|
|
173
|
+
const hollowNotes = hollow.map((r) => `${describeHollow(r)} — archived for its tool calls, not a payload failure`);
|
|
174
|
+
// #59: refuse before reserving anything, exactly like the payloadless gate — a spent number with no
|
|
175
|
+
// file behind it is the burned-reservation state this whole round is eliminating. The factor is
|
|
176
|
+
// config, never a literal: over `chapterTokenTarget` warns, over `target × factor` refuses.
|
|
177
|
+
const tooBig = oversizeRanges(rendered, config.chapterTokenTarget, config.chapterOversizeFactor ?? 2);
|
|
178
|
+
if (tooBig.length > 0) {
|
|
179
|
+
return {
|
|
180
|
+
skipped: 'oversize', fromSeq, anchor, lastArchived,
|
|
181
|
+
priorChapters: state.chapters.length, records: [], warnings: tooBig,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
57
184
|
const wrote = await writeArchive({
|
|
58
185
|
fs: makeArchiveFs(cwd),
|
|
59
186
|
allocator: {
|
|
187
|
+
// One section per allocation (#59) — identical semantics to makeAllocator, which is why the
|
|
188
|
+
// shared helper is used by every other door; this door had its own copy of the race.
|
|
60
189
|
async reserve(attemptId, count) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
await store.put(sessionId, r.state);
|
|
64
|
-
return r.numbers;
|
|
190
|
+
const { numbers } = await reserveNumbers(store, sessionId, attemptId, count);
|
|
191
|
+
return numbers;
|
|
65
192
|
},
|
|
66
193
|
async reservePlan(attemptId, count) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
194
|
+
const { numbers } = await reserveNumbers(store, sessionId, attemptId, count, true);
|
|
195
|
+
return numbers;
|
|
196
|
+
},
|
|
197
|
+
async release(attemptId) {
|
|
198
|
+
await store.mutate(sessionId, (state) => releaseReservation(state, attemptId));
|
|
71
199
|
},
|
|
72
200
|
},
|
|
73
201
|
// raw value: makeArchiveFs roots RELATIVE paths at the session cwd and
|
|
74
202
|
// passes absolute ones through — resolving here would double-root relative stores
|
|
75
203
|
storeRoot: config.artifactStoreRoot,
|
|
204
|
+
cwd, // #69: records are stored workspace-relative
|
|
76
205
|
rootSessionId: state.rootSession,
|
|
77
206
|
chapters: rendered,
|
|
78
|
-
attemptId: attemptKey(sessionId, anchor),
|
|
207
|
+
attemptId: attemptKey(sessionId, anchor, a.attemptSalt),
|
|
79
208
|
});
|
|
80
209
|
// Commit chapter records to session state so search index picks them up.
|
|
81
|
-
|
|
82
|
-
|
|
210
|
+
if (a.supersede !== undefined && wrote.records.length > 0) {
|
|
211
|
+
// Commit AND mark in one section: no reader can ever see the replacement without the old record
|
|
212
|
+
// marked, or the mark without its replacement (#60).
|
|
213
|
+
await store.mutate(sessionId, (fresh) => supersedeChapter(fresh, a.supersede.over, wrote.records, a.supersede.reason));
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
await commitChapters(store, sessionId, wrote.records);
|
|
217
|
+
}
|
|
218
|
+
return { skipped: null, fromSeq, anchor, lastArchived, priorChapters: state.chapters.length, records: wrote.records, warnings: [...repairNotes, ...hollowNotes, ...wrote.warnings] };
|
|
83
219
|
}
|
|
84
220
|
//# sourceMappingURL=save-core.js.map
|
package/lib/save-core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save-core.js","sourceRoot":"","sources":["../src/save-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"save-core.js","sourceRoot":"","sources":["../src/save-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { type RuleRecord, type RulesSectionResult } from './rules.ts';
|
|
2
|
+
import type { SessionEventLike } from './types.ts';
|
|
3
|
+
/** One captured rule: the human's body, anchored to the seq it came from. */
|
|
4
|
+
export interface CapturedRule {
|
|
5
|
+
body: string;
|
|
6
|
+
seq: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The markers a turn's HUMAN messages contain. Two screens, both load-bearing:
|
|
10
|
+
*
|
|
11
|
+
* - `isHumanAuthored` — the same shared predicate signatures use (r28: injected
|
|
12
|
+
* context moves chapter boundaries and would here too). A host-injected
|
|
13
|
+
* instruction file that happens to contain a line starting with the marker is
|
|
14
|
+
* NOT a rule request.
|
|
15
|
+
* - `stripReminderSpans` — `<system-reminder>` spans ride INSIDE human
|
|
16
|
+
* messages. The human's own lines survive; the injected span does not (§15
|
|
17
|
+
* amendment, "host-injected context is not conversation").
|
|
18
|
+
*/
|
|
19
|
+
export declare function humanRuleMarkers(events: readonly SessionEventLike[], prefix: string): CapturedRule[];
|
|
20
|
+
/**
|
|
21
|
+
* The next free number in the SHARED per-machine sequence. Both tiers author
|
|
22
|
+
* into `rules/<harness>/`, and ids are `<harness>/<NNN>`, so a session rule must
|
|
23
|
+
* not take a number a project rule already holds — neither the registry's (pre-
|
|
24
|
+
* sync) records' nor the disk's (a killed write leaves a file without a record;
|
|
25
|
+
* the r39 walk-past rule). Numbers are allocated, never scanned-for-content.
|
|
26
|
+
*/
|
|
27
|
+
export declare function nextRuleNumber(opts: {
|
|
28
|
+
records: Iterable<[string, RuleRecord]>;
|
|
29
|
+
harnessId: string;
|
|
30
|
+
rulesDir: string;
|
|
31
|
+
}): number;
|
|
32
|
+
export interface CaptureDeps {
|
|
33
|
+
/** workspace store root, e.g. `<cwd>/.dsh-chapters` */
|
|
34
|
+
storeDir: string;
|
|
35
|
+
harnessId: string;
|
|
36
|
+
/** the session whose tree these rules bind (§7.5 activation) */
|
|
37
|
+
sessionId: string;
|
|
38
|
+
/** for id-collision avoidance with the shared tier's pre-sync records */
|
|
39
|
+
records: Iterable<[string, RuleRecord]>;
|
|
40
|
+
prefix: string;
|
|
41
|
+
/** the completed turn's events */
|
|
42
|
+
events: readonly SessionEventLike[];
|
|
43
|
+
now?: () => string;
|
|
44
|
+
}
|
|
45
|
+
export interface CaptureResult {
|
|
46
|
+
written: string[];
|
|
47
|
+
skipped: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Capture this turn's markers. Returns the rule ids written (empty when nothing
|
|
51
|
+
* matched), and never throws into the session — the caller contains failures the
|
|
52
|
+
* way the signature listener does.
|
|
53
|
+
*/
|
|
54
|
+
export declare function captureSessionRules(deps: CaptureDeps): CaptureResult;
|
|
55
|
+
/**
|
|
56
|
+
* The current session's ancestor path, root-first — the async mirror of
|
|
57
|
+
* `registry.ancestorPath`, which takes a synchronous getter the realm does not
|
|
58
|
+
* have. §7.5 activation is "this rule's session is on MY path", so every render
|
|
59
|
+
* site needs this walk; a missing parent stops the walk instead of throwing,
|
|
60
|
+
* because a rules render must never break a compaction (the same containment
|
|
61
|
+
* the notice path uses).
|
|
62
|
+
*/
|
|
63
|
+
export declare function ancestorSessionIds(get: (id: string) => Promise<{
|
|
64
|
+
parentSession: string | null;
|
|
65
|
+
} | undefined>, sessionId: string, maxDepth?: number): Promise<string[]>;
|
|
66
|
+
/** One mined rule candidate, as the enrichment pass proposes it (§7.5). */
|
|
67
|
+
/**
|
|
68
|
+
* Rule ancestry crosses BOTH lineage edges (#52, user decision 2026-10-21).
|
|
69
|
+
*
|
|
70
|
+
* Our registry is the sole authority on CONTINUATION lineage and deliberately
|
|
71
|
+
* ignores the kernel header's `parentSession` — right for chapter numbering and
|
|
72
|
+
* the TOC, and it left a whole session shape uncovered: a delegated subagent is an
|
|
73
|
+
* unrelated root by that view, so it inherited no activated rules from ANY door.
|
|
74
|
+
* The rule tier now walks both edges, READ-ONLY, writing nothing:
|
|
75
|
+
*
|
|
76
|
+
* registry parentSession (ours) ∪ header parentSession (the host's)
|
|
77
|
+
*
|
|
78
|
+
* transitively, so a subagent of a continuation child of a delegating parent sees
|
|
79
|
+
* the whole chain — and still walks UP only, which keeps the direction one-way: a
|
|
80
|
+
* child's inferences never govern the session that delegated them. Two bounds,
|
|
81
|
+
* because this runs inside a head rebuild: an expansion cap, and a reader that
|
|
82
|
+
* throws contributes no edge — a rule walk must never break a turn.
|
|
83
|
+
*/
|
|
84
|
+
export declare const RULE_LINEAGE_MAX_EXPANSIONS = 64;
|
|
85
|
+
export interface RuleAncestryWalk {
|
|
86
|
+
ancestors: Set<string>;
|
|
87
|
+
truncated: boolean;
|
|
88
|
+
}
|
|
89
|
+
export declare function ruleAncestorSet(opts: {
|
|
90
|
+
/** Usually [sessionId]; the session always belongs to its own ancestry. */
|
|
91
|
+
seeds: readonly string[];
|
|
92
|
+
/** Our registry chain (continuation lineage). */
|
|
93
|
+
registryAncestors?: ((id: string) => Promise<readonly string[]>) | undefined;
|
|
94
|
+
/** The host header's delegation edge, for any session id. */
|
|
95
|
+
parentOf?: ((id: string) => Promise<string | null | undefined>) | undefined;
|
|
96
|
+
maxExpansions?: number;
|
|
97
|
+
}): Promise<RuleAncestryWalk>;
|
|
98
|
+
/**
|
|
99
|
+
* A `parentOf` from whatever session views this plane can reach: a live agent
|
|
100
|
+
* handle first, then `sessionQuery`'s durable headers. undefined when NEITHER
|
|
101
|
+
* exists — which degrades the walk to registry edges only.
|
|
102
|
+
*
|
|
103
|
+
* Cordis `ctx.get` THROWS for an unregistered service and a plugin plane may have
|
|
104
|
+
* neither, so every lookup is guarded (the #43 lesson: a strict host makes a
|
|
105
|
+
* plausible service name throw).
|
|
106
|
+
*/
|
|
107
|
+
export declare function makeHeaderParentReader(get?: (name: string) => unknown): ((id: string) => Promise<string | null | undefined>) | undefined;
|
|
108
|
+
export interface MinedRule {
|
|
109
|
+
text: string;
|
|
110
|
+
category: string;
|
|
111
|
+
/** the human's own words this rule came from — the quote IS the gate */
|
|
112
|
+
quote: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* THE verbatim-quote gate (§7.5). A mined rule activates only when its quote is
|
|
116
|
+
* the human's own words, present verbatim (whitespace-normalized) in the
|
|
117
|
+
* chapter's USER text. Assistant prose is deliberately NOT admissible evidence:
|
|
118
|
+
* the model restating an instruction it was given is the amplification path, and
|
|
119
|
+
* a quote it invented is a guess wearing a citation.
|
|
120
|
+
*/
|
|
121
|
+
export declare function quoteIsHuman(humanText: string, quote: string): boolean;
|
|
122
|
+
export interface MineDeps {
|
|
123
|
+
storeDir: string;
|
|
124
|
+
harnessId: string;
|
|
125
|
+
/** the session whose tree the mined rules bind — the chapter's OWN session */
|
|
126
|
+
sessionId: string;
|
|
127
|
+
records: Iterable<[string, RuleRecord]>;
|
|
128
|
+
/** the chapter's human-authored bytes (its `**User…:**` chunks joined) */
|
|
129
|
+
humanText: string;
|
|
130
|
+
rules: readonly MinedRule[];
|
|
131
|
+
/** per-chapter cap: one conversation must not mint a dozen standing orders */
|
|
132
|
+
maxPerChapter: number;
|
|
133
|
+
/** the chapter these came from (provenance rendered into the head) */
|
|
134
|
+
chapterNumber: number;
|
|
135
|
+
/**
|
|
136
|
+
* §7.5 policy (amended 2026-10-21): may a mined rule govern on arrival? TRUE by
|
|
137
|
+
* default — a rule the model inferred from context is the reason this tier
|
|
138
|
+
* exists; the human may not have known to say it. When false every mined rule is
|
|
139
|
+
* written `reviewOnly: true` and stays inert until this machine approves it, the
|
|
140
|
+
* conservative posture a shared box may want.
|
|
141
|
+
*/
|
|
142
|
+
autoActivate: boolean;
|
|
143
|
+
now?: () => string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Write the mined candidates. Each records its warrant (`stated` / `inferred`) and
|
|
147
|
+
* the chapter it came from; whether it governs on arrival is the `autoActivate`
|
|
148
|
+
* POLICY, not an evidence test (§7.5, amended 2026-10-21 — the tier's premise is
|
|
149
|
+
* that the human may not have known to say the rule). Deduped by normalized body
|
|
150
|
+
* against what the machine already holds, so re-running the ladder (or a
|
|
151
|
+
* chapter annotated by a newer model) fills gaps and never duplicates.
|
|
152
|
+
*/
|
|
153
|
+
export declare function captureMinedRules(deps: MineDeps): {
|
|
154
|
+
ids: string[];
|
|
155
|
+
stated: number;
|
|
156
|
+
inferred: number;
|
|
157
|
+
pending: number;
|
|
158
|
+
};
|
|
159
|
+
/** Turn a task description into the relevance term set §7.4 selects on — the same
|
|
160
|
+
* normalization the rule-term index uses, so a query and a rule body meet in one
|
|
161
|
+
* vocabulary instead of two. */
|
|
162
|
+
export declare function queryTopics(query: string): string[];
|
|
163
|
+
/** The registry view the renderer needs — kept structural so tests can hand it a map. */
|
|
164
|
+
export interface RulesStoreLike {
|
|
165
|
+
get(id: string): Promise<{
|
|
166
|
+
parentSession: string | null;
|
|
167
|
+
collections: {
|
|
168
|
+
terms: string[];
|
|
169
|
+
}[];
|
|
170
|
+
} | undefined>;
|
|
171
|
+
}
|
|
172
|
+
export interface RenderRulesOpts {
|
|
173
|
+
mirrorDir: string;
|
|
174
|
+
storeDir: string;
|
|
175
|
+
projectKey: string;
|
|
176
|
+
harnessId: string;
|
|
177
|
+
budgetTokens: number;
|
|
178
|
+
projectCap?: number | undefined;
|
|
179
|
+
sessionCap?: number | undefined;
|
|
180
|
+
/** §7.4 relevance on; off ⇒ deterministic category→id order (the tape order). */
|
|
181
|
+
relevance: boolean;
|
|
182
|
+
/** §7.6: a BRANCH head carries the project tier only — zero session bytes. */
|
|
183
|
+
includeSession: boolean;
|
|
184
|
+
/** When supplied, selection uses THESE terms (the assembly door passes the
|
|
185
|
+
* task query) instead of the session's own recent signatures. */
|
|
186
|
+
topicsOverride?: readonly string[] | undefined;
|
|
187
|
+
store: RulesStoreLike;
|
|
188
|
+
sessionId: string;
|
|
189
|
+
/** #52: reads the host header's parentSession for any id in the chain. Absent
|
|
190
|
+
* ⇒ the walk follows registry edges only, so delegation contributes nothing. */
|
|
191
|
+
parentOf?: ((id: string) => Promise<string | null | undefined>) | undefined;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* THE one rules renderer for a rebuilt head. Both doors (continue / fork) and the
|
|
195
|
+
* compaction checkpoint call this and nothing else, so the same session can never
|
|
196
|
+
* see two different rule sets depending on which path rebuilt its head — the same
|
|
197
|
+
* one-builder discipline as `search.rankEntries` and `deterministicCommand`.
|
|
198
|
+
*
|
|
199
|
+
* Relevance topics come from the session’s OWN stored turn signatures:
|
|
200
|
+
* deterministic, already on disk, zero model tokens (§1 invariant 1).
|
|
201
|
+
*/
|
|
202
|
+
export declare function renderRulesBlock(opts: RenderRulesOpts): Promise<RulesSectionResult>;
|
|
203
|
+
/**
|
|
204
|
+
* The `turn/end` listener (the same boundary the topic signatures collect on —
|
|
205
|
+
* §7.5: this extends the existing door, it is not a sixth archive moment).
|
|
206
|
+
*
|
|
207
|
+
* Never-break containment: every failure lands in `warn`, because a rule that
|
|
208
|
+
* could not be written must not cost a user their turn.
|
|
209
|
+
*/
|
|
210
|
+
export declare function makeRuleCaptureListener(deps: {
|
|
211
|
+
enabled: () => boolean;
|
|
212
|
+
prefix: () => string;
|
|
213
|
+
harnessId: () => string;
|
|
214
|
+
storeRoot: (cwd: string) => string;
|
|
215
|
+
store: () => Promise<{
|
|
216
|
+
store: {
|
|
217
|
+
rules(): IterableIterator<[string, RuleRecord]>;
|
|
218
|
+
};
|
|
219
|
+
}>;
|
|
220
|
+
warn: (error: unknown) => void;
|
|
221
|
+
onCaptured?: (sessionId: string, ids: string[]) => void;
|
|
222
|
+
}): (session: unknown, event: unknown) => void;
|