@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.
Files changed (119) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +644 -0
  3. package/cordis.patch.yml +88 -0
  4. package/docs/architecture.md +460 -0
  5. package/docs/contract.md +375 -0
  6. package/docs/development.md +437 -0
  7. package/docs/host-compaction-seam.md +117 -0
  8. package/docs/knowledge-repo.md +730 -0
  9. package/docs/provider.md +98 -0
  10. package/docs/verify.md +200 -0
  11. package/lib/archive.d.ts +88 -0
  12. package/lib/archive.js +114 -0
  13. package/lib/archive.js.map +1 -0
  14. package/lib/arrival.d.ts +23 -0
  15. package/lib/arrival.js +86 -0
  16. package/lib/arrival.js.map +1 -0
  17. package/lib/artifact-query.d.ts +53 -0
  18. package/lib/artifact-query.js +130 -0
  19. package/lib/artifact-query.js.map +1 -0
  20. package/lib/client.js +234 -0
  21. package/lib/client.js.map +1 -0
  22. package/lib/commands.d.ts +28 -0
  23. package/lib/commands.js +305 -0
  24. package/lib/commands.js.map +1 -0
  25. package/lib/compose.d.ts +30 -0
  26. package/lib/compose.js +131 -0
  27. package/lib/compose.js.map +1 -0
  28. package/lib/continue-core.d.ts +153 -0
  29. package/lib/continue-core.js +252 -0
  30. package/lib/continue-core.js.map +1 -0
  31. package/lib/engine-core.d.ts +219 -0
  32. package/lib/engine-core.js +405 -0
  33. package/lib/engine-core.js.map +1 -0
  34. package/lib/engine.d.ts +211 -0
  35. package/lib/engine.js +571 -0
  36. package/lib/engine.js.map +1 -0
  37. package/lib/enrich-queue.d.ts +66 -0
  38. package/lib/enrich-queue.js +100 -0
  39. package/lib/enrich-queue.js.map +1 -0
  40. package/lib/enrich-store.d.ts +31 -0
  41. package/lib/enrich-store.js +103 -0
  42. package/lib/enrich-store.js.map +1 -0
  43. package/lib/enrich-wire.d.ts +73 -0
  44. package/lib/enrich-wire.js +209 -0
  45. package/lib/enrich-wire.js.map +1 -0
  46. package/lib/enrich.d.ts +73 -0
  47. package/lib/enrich.js +150 -0
  48. package/lib/enrich.js.map +1 -0
  49. package/lib/gitops.d.ts +107 -0
  50. package/lib/gitops.js +539 -0
  51. package/lib/gitops.js.map +1 -0
  52. package/lib/index.d.ts +81 -0
  53. package/lib/index.js +305 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/indexing.d.ts +85 -0
  56. package/lib/indexing.js +212 -0
  57. package/lib/indexing.js.map +1 -0
  58. package/lib/injections.d.ts +66 -0
  59. package/lib/injections.js +83 -0
  60. package/lib/injections.js.map +1 -0
  61. package/lib/notice.d.ts +60 -0
  62. package/lib/notice.js +84 -0
  63. package/lib/notice.js.map +1 -0
  64. package/lib/provider.d.ts +44 -0
  65. package/lib/provider.js +66 -0
  66. package/lib/provider.js.map +1 -0
  67. package/lib/redact.d.ts +24 -0
  68. package/lib/redact.js +85 -0
  69. package/lib/redact.js.map +1 -0
  70. package/lib/registry.d.ts +120 -0
  71. package/lib/registry.js +130 -0
  72. package/lib/registry.js.map +1 -0
  73. package/lib/render.d.ts +40 -0
  74. package/lib/render.js +280 -0
  75. package/lib/render.js.map +1 -0
  76. package/lib/repo.d.ts +78 -0
  77. package/lib/repo.js +184 -0
  78. package/lib/repo.js.map +1 -0
  79. package/lib/rules-commands.d.ts +24 -0
  80. package/lib/rules-commands.js +138 -0
  81. package/lib/rules-commands.js.map +1 -0
  82. package/lib/rules.d.ts +90 -0
  83. package/lib/rules.js +185 -0
  84. package/lib/rules.js.map +1 -0
  85. package/lib/search.d.ts +48 -0
  86. package/lib/search.js +152 -0
  87. package/lib/search.js.map +1 -0
  88. package/lib/signature.d.ts +28 -0
  89. package/lib/signature.js +149 -0
  90. package/lib/signature.js.map +1 -0
  91. package/lib/store.d.ts +130 -0
  92. package/lib/store.js +235 -0
  93. package/lib/store.js.map +1 -0
  94. package/lib/sync.d.ts +183 -0
  95. package/lib/sync.js +719 -0
  96. package/lib/sync.js.map +1 -0
  97. package/lib/tools.d.ts +84 -0
  98. package/lib/tools.js +581 -0
  99. package/lib/tools.js.map +1 -0
  100. package/lib/treedx/client.d.ts +57 -0
  101. package/lib/treedx/client.js +82 -0
  102. package/lib/treedx/client.js.map +1 -0
  103. package/lib/treedx/provider.d.ts +39 -0
  104. package/lib/treedx/provider.js +479 -0
  105. package/lib/treedx/provider.js.map +1 -0
  106. package/lib/treedx/state.d.ts +27 -0
  107. package/lib/treedx/state.js +65 -0
  108. package/lib/treedx/state.js.map +1 -0
  109. package/lib/types.d.ts +100 -0
  110. package/lib/types.js +19 -0
  111. package/lib/types.js.map +1 -0
  112. package/lib/vocabulary.d.ts +41 -0
  113. package/lib/vocabulary.js +118 -0
  114. package/lib/vocabulary.js.map +1 -0
  115. package/package.json +132 -0
  116. package/presets/chapters/agent.cordis.yml +272 -0
  117. package/presets/chapters/preset.yml +3 -0
  118. package/presets/probe-roots/agent.cordis.yml +11 -0
  119. package/presets/probe-roots/preset.yml +3 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Layer 2 of the two-layer index (knowledge-repo.md §10.2): the compressed,
3
+ * searchable, sharded index derived from chapter frontmatter + Layer-1
4
+ * curation facts. PURE + DETERMINISTIC: same inputs → same shards, and an
5
+ * empty manifest rebuilds the whole index identically (the recovery path).
6
+ *
7
+ * Shards are one file per canonical topic (alphabetical — bounded files,
8
+ * writes from different chapters usually touch different shards). Layer 1
9
+ * (`edits/<harness>/curation.jsonl`) is where humans and the plugin inject
10
+ * aliases, weights, pins, and group facts; this module applies them.
11
+ */
12
+ /**
13
+ * S5 (record §4.3 boundary): fragment coherence at the INDEX level only.
14
+ * Legacy compaction fragments (turn-blind slices titled 'Earlier history'/
15
+ * 'Conversation span') that sit adjacent by chapter number within one
16
+ * session tree cohere into a single search entry citing every member file.
17
+ * Nothing on disk merges, no chapter is rewritten, the split rule is
18
+ * untouched — a pure function of what is already stored, so it is idempotent
19
+ * and vanishes if any member is reverted.
20
+ */
21
+ const FRAGMENT_TITLE = /^(?:Earlier history|Conversation span)/;
22
+ function chapterNumber(path) {
23
+ const base = path.split('/').pop() ?? '';
24
+ const n = /^(\d+)-/.exec(base);
25
+ return n !== null ? Number(n[1]) : null;
26
+ }
27
+ export function stitchFragments(entries) {
28
+ // group by session directory, keep input order
29
+ const byDir = new Map();
30
+ const loose = [];
31
+ for (const e of entries) {
32
+ const dir = e.path.split('/').slice(0, -1).join('/');
33
+ const isFrag = FRAGMENT_TITLE.test(e.title);
34
+ if (!isFrag || chapterNumber(e.path) === null) {
35
+ loose.push(e);
36
+ continue;
37
+ }
38
+ const list = byDir.get(dir);
39
+ if (list === undefined)
40
+ byDir.set(dir, [e]);
41
+ else
42
+ list.push(e);
43
+ }
44
+ const out = [...loose];
45
+ for (const [dir, frags] of byDir) {
46
+ const sorted = [...frags].sort((a, b) => (chapterNumber(a.path) ?? 0) - (chapterNumber(b.path) ?? 0));
47
+ let i = 0;
48
+ while (i < sorted.length) {
49
+ let j = i;
50
+ while (j + 1 < sorted.length &&
51
+ (chapterNumber(sorted[j + 1].path) ?? -1) === (chapterNumber(sorted[j].path) ?? -1) + 1)
52
+ j++;
53
+ const run = sorted.slice(i, j + 1);
54
+ if (run.length > 1) {
55
+ const head = run[0];
56
+ out.push({
57
+ ...head,
58
+ title: `${head.title} — ${run.length} consecutive parts (stitched)`,
59
+ topics: [...new Set(run.flatMap((r) => r.topics))],
60
+ paths: run.map((r) => r.path),
61
+ });
62
+ }
63
+ else if (run.length === 1)
64
+ out.push(run[0]);
65
+ i = j + 1;
66
+ }
67
+ void dir;
68
+ }
69
+ // stable, deterministic: original entry order by path
70
+ out.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
71
+ return out;
72
+ }
73
+ const hashOf = (text) => {
74
+ // FNV-1a 32-bit — deterministic, no crypto import needed in the pure core;
75
+ // collision-resistance is not a security property here, only change-detection.
76
+ let h = 0x811c9dc5;
77
+ for (let i = 0; i < text.length; i += 1) {
78
+ h ^= text.charCodeAt(i);
79
+ h = Math.imul(h, 0x01000193) >>> 0;
80
+ }
81
+ return h.toString(16).padStart(8, '0');
82
+ };
83
+ export const hashEntry = (e) => hashOf(`${e.path}|${e.title}|${[...e.topics].sort().join(',')}|${e.kind}|${e.mtime ?? 0}`);
84
+ /** Minimal frontmatter reader for the `key: value` lines render.ts writes. */
85
+ export function parseFrontmatter(text) {
86
+ const out = {};
87
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
88
+ if (m === null)
89
+ return out;
90
+ const body = m[1] ?? '';
91
+ for (const line of body.split(/\r?\n/)) {
92
+ const kv = /^([A-Za-z][A-Za-z0-9]*):\s*(.*)$/.exec(line);
93
+ if (kv === null)
94
+ continue;
95
+ const key = kv[1];
96
+ let value = kv[2].trim();
97
+ if (value.startsWith('[') && value.endsWith(']')) {
98
+ out[key] = value.slice(1, -1).split(',').map((v) => v.trim().replace(/^"|"$/g, '')).filter((v) => v.length > 0);
99
+ }
100
+ else {
101
+ out[key] = value.replace(/^"|"$/g, '');
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ /** A chapter file's text → IndexEntry (topics from frontmatter, kind from `kind:`). */
107
+ export function entryFromChapter(path, text, mtime) {
108
+ const fm = parseFrontmatter(text);
109
+ const topics = fm.topics;
110
+ return {
111
+ path,
112
+ title: typeof fm.title === 'string' ? fm.title : path,
113
+ topics: Array.isArray(topics) ? topics : [],
114
+ kind: fm.kind === 'rule' ? 'rule' : 'chapter',
115
+ ...(typeof fm.category === 'string' && fm.category !== '' ? { category: fm.category } : {}),
116
+ ...(mtime !== undefined ? { mtime } : {}),
117
+ };
118
+ }
119
+ /** Layer-1 parse: one JSON fact per line; malformed lines are skipped (logged by the caller). */
120
+ export function parseCuration(text) {
121
+ const facts = [];
122
+ for (const line of text.split(/\r?\n/)) {
123
+ const trimmed = line.trim();
124
+ if (trimmed === '')
125
+ continue;
126
+ try {
127
+ const fact = JSON.parse(trimmed);
128
+ if (typeof fact?.type === 'string')
129
+ facts.push(fact);
130
+ }
131
+ catch {
132
+ /* malformed line: skipped, not fatal (append-only appenders may race) */
133
+ }
134
+ }
135
+ return facts;
136
+ }
137
+ /**
138
+ * Build the sharded index. Aliases canonicalize topics (from→to, transitive),
139
+ * pinned topics sort first inside their position, weights scale the sort
140
+ * within a topic, and the manifest tracks per-entry hashes for the
141
+ * incremental fast path (an empty manifest = full rebuild = identical output).
142
+ */
143
+ export function buildIndexShards(entries, curation, manifest = {}) {
144
+ const alias = new Map();
145
+ for (const f of curation) {
146
+ if (f.type === 'topic-alias')
147
+ alias.set(f.from, f.to);
148
+ }
149
+ const canonical = (topic) => {
150
+ let t = topic;
151
+ let guard = 0;
152
+ while (alias.has(t) && guard < 16) {
153
+ t = alias.get(t);
154
+ guard += 1;
155
+ }
156
+ return t;
157
+ };
158
+ const pins = new Set(curation.filter((f) => f.type === 'topic-pin').map((f) => f.topic));
159
+ const weights = new Map();
160
+ for (const f of curation) {
161
+ if (f.type === 'topic-weight')
162
+ weights.set(canonical(f.topic), f.weight);
163
+ }
164
+ const byTopic = new Map();
165
+ const manifestNext = { ...manifest };
166
+ let changed = false;
167
+ for (const e of entries) {
168
+ const hash = hashEntry(e);
169
+ const prev = manifest[e.path];
170
+ if (prev !== undefined && prev.hash === hash) {
171
+ // unchanged entry: keep its shard content contribution by still
172
+ // including it (shards are rebuilt from entries, not diffs — the
173
+ // manifest gates the COMMIT, shard content is always whole)
174
+ }
175
+ else {
176
+ changed = true;
177
+ }
178
+ manifestNext[e.path] = { hash, at: new Date().toISOString() };
179
+ for (const rawTopic of e.topics) {
180
+ const topic = canonical(rawTopic);
181
+ if (topic === '')
182
+ continue;
183
+ const list = byTopic.get(topic) ?? [];
184
+ list.push(e);
185
+ byTopic.set(topic, list);
186
+ }
187
+ }
188
+ // entries gone from the input drop out of the shards but stay in the
189
+ // manifest? No — the index is a pure function of current entries: prune.
190
+ for (const path of Object.keys(manifestNext)) {
191
+ if (!entries.some((e) => e.path === path))
192
+ delete manifestNext[path];
193
+ }
194
+ const shards = new Map();
195
+ const topicNames = [...byTopic.keys()].sort();
196
+ for (const topic of topicNames) {
197
+ const entriesForTopic = byTopic.get(topic);
198
+ const weight = weights.get(topic) ?? 1;
199
+ entriesForTopic.sort((a, b) => (b.mtime ?? 0) - (a.mtime ?? 0));
200
+ const lines = entriesForTopic.map((e) => `${e.mtime !== undefined ? new Date(e.mtime).toISOString().slice(0, 10) : '----'} | ${e.kind} | ${e.title} | ${e.path}`);
201
+ const header = [
202
+ `# topic: ${topic}`,
203
+ pins.has(topic) ? '(pinned)' : '',
204
+ `entries: ${entriesForTopic.length} · weight ${weight}`,
205
+ '',
206
+ ].filter((l) => l !== '').join('\n');
207
+ shards.set(topic, `${header}\n${lines.join('\n')}\n`);
208
+ }
209
+ void canonical;
210
+ return { shards, manifest: manifestNext, changed, topics: topicNames };
211
+ }
212
+ //# sourceMappingURL=indexing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The one boundary that decides which bytes of the durable log are CONVERSATION
3
+ * and which are host-injected context (knowledge-repo.md §15 amendment,
4
+ * "host-injected context is not conversation").
5
+ *
6
+ * Why this exists: the harness injects project context — workspace instruction
7
+ * files (whatever a given project's AGENTS.md holds), the runtime-context
8
+ * snapshot, the skill catalog — and injected events arrive in the log either as
9
+ * their own `user/message` (source.kind `agent-instructions`, `plugin`, …) or
10
+ * riding INSIDE human messages as `<system-reminder>` spans. A target project's
11
+ * instruction text is that project's business: it must not be archived into the
12
+ * shared knowledge corpus, searched by other workspaces, re-quoted into
13
+ * enrichment input, or bloating a child's re-archive of its own TOC notice
14
+ * generation after generation.
15
+ *
16
+ * Precedents this rule follows, not invents:
17
+ * - `signature.ts` (r28) already excluded these events from per-turn topic
18
+ * signatures after measuring their poison: every signature gained the
19
+ * injected filenames and drowned the real topic signal.
20
+ * - `tests/e2e/model-proxy.ts` (user decision 2026-09-20, AGENTS-BLIND) already
21
+ * masks the same classes for tape matching, on the same reasoning:
22
+ * development artifacts, every real project ships its own.
23
+ *
24
+ * What is NOT touched: the durable session log (invariant: append-only, always —
25
+ * the originals live on in the parent session), and genuine conversation bytes
26
+ * (a human typing the words "AGENTS.md" in prose is conversation and stays
27
+ * verbatim; only the structural injected forms are removed).
28
+ *
29
+ * Pure module: no cordis, no fs, no network.
30
+ */
31
+ /** A user/message is human-authored when it carries no source, or source.kind
32
+ * 'user' (unlabelled keeps working for seeded fixtures — same rule the
33
+ * signature layer has used since r28). Everything else — 'agent-instructions',
34
+ * 'plugin' (the TOC notice, runtime snapshots), 'tool', 'system' — is injected
35
+ * context, not conversation. Exported so signature.ts imports the SAME
36
+ * predicate; the two layers must never drift on what counts as a turn. */
37
+ export declare function isHumanAuthored(data: Record<string, unknown>): boolean;
38
+ /** Label for the omission marker: kind, plus plugin name when the source
39
+ * carries one (so `plugin:dsh-chapters` is recognizable in a chapter). */
40
+ export declare function sourceLabel(data: Record<string, unknown>): string;
41
+ /** Strip every injected span from a message's text; deterministic, idempotent
42
+ * (the marker we emit below cannot re-trigger this rule). */
43
+ export declare function stripReminderSpans(text: string): {
44
+ text: string;
45
+ removedChars: number;
46
+ };
47
+ /** The visible, greppable, countable marker an omission leaves behind. One
48
+ * shape for every class so downstream assertions (and the sentinel integration
49
+ * test) pin a single prefix. */
50
+ export declare const omittedMarker: (what: string, chars: number) => string;
51
+ /**
52
+ * The single screen the renderer and anything else consuming conversation text
53
+ * should use: what survives as chapter text, and what was omitted (event-level
54
+ * for non-human sources, span-level inside otherwise-human messages).
55
+ */
56
+ export interface Screened {
57
+ /** Conversation text to render (human messages: span-stripped; non-human: ''). */
58
+ text: string;
59
+ /** True when the whole event is injected context (non-human source). */
60
+ omittedEvent: boolean;
61
+ /** Character count removed by this screen (event-level OR span-level). */
62
+ removedChars: number;
63
+ /** Source label for the event (for the marker + tests). */
64
+ sourceKind: string;
65
+ }
66
+ export declare function screenConversation(data: Record<string, unknown>, rawText: string, role: 'user' | 'assistant'): Screened;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The one boundary that decides which bytes of the durable log are CONVERSATION
3
+ * and which are host-injected context (knowledge-repo.md §15 amendment,
4
+ * "host-injected context is not conversation").
5
+ *
6
+ * Why this exists: the harness injects project context — workspace instruction
7
+ * files (whatever a given project's AGENTS.md holds), the runtime-context
8
+ * snapshot, the skill catalog — and injected events arrive in the log either as
9
+ * their own `user/message` (source.kind `agent-instructions`, `plugin`, …) or
10
+ * riding INSIDE human messages as `<system-reminder>` spans. A target project's
11
+ * instruction text is that project's business: it must not be archived into the
12
+ * shared knowledge corpus, searched by other workspaces, re-quoted into
13
+ * enrichment input, or bloating a child's re-archive of its own TOC notice
14
+ * generation after generation.
15
+ *
16
+ * Precedents this rule follows, not invents:
17
+ * - `signature.ts` (r28) already excluded these events from per-turn topic
18
+ * signatures after measuring their poison: every signature gained the
19
+ * injected filenames and drowned the real topic signal.
20
+ * - `tests/e2e/model-proxy.ts` (user decision 2026-09-20, AGENTS-BLIND) already
21
+ * masks the same classes for tape matching, on the same reasoning:
22
+ * development artifacts, every real project ships its own.
23
+ *
24
+ * What is NOT touched: the durable session log (invariant: append-only, always —
25
+ * the originals live on in the parent session), and genuine conversation bytes
26
+ * (a human typing the words "AGENTS.md" in prose is conversation and stays
27
+ * verbatim; only the structural injected forms are removed).
28
+ *
29
+ * Pure module: no cordis, no fs, no network.
30
+ */
31
+ /** A user/message is human-authored when it carries no source, or source.kind
32
+ * 'user' (unlabelled keeps working for seeded fixtures — same rule the
33
+ * signature layer has used since r28). Everything else — 'agent-instructions',
34
+ * 'plugin' (the TOC notice, runtime snapshots), 'tool', 'system' — is injected
35
+ * context, not conversation. Exported so signature.ts imports the SAME
36
+ * predicate; the two layers must never drift on what counts as a turn. */
37
+ export function isHumanAuthored(data) {
38
+ const source = data.source;
39
+ return source?.kind === undefined || source.kind === 'user';
40
+ }
41
+ /** Label for the omission marker: kind, plus plugin name when the source
42
+ * carries one (so `plugin:dsh-chapters` is recognizable in a chapter). */
43
+ export function sourceLabel(data) {
44
+ const source = data.source;
45
+ if (source === undefined)
46
+ return 'unlabelled';
47
+ return source.kind === 'plugin' && typeof source.plugin === 'string'
48
+ ? `plugin:${source.plugin}`
49
+ : String(source.kind ?? 'unknown');
50
+ }
51
+ /**
52
+ * The structural injection span. Anchored on the exact harness tag, case
53
+ * sensitive (the tape's own skill-catalog rule is case sensitive for the same
54
+ * reason: ordinary prose quoting the tag is vanishingly rare, and the cost of a
55
+ * false positive is a marked, countable omission — never a silent rewrite of
56
+ * history, the original bytes stay in the session log). The unclosed-tail form
57
+ * `[\s\S]*?(</system-reminder>|$)` mirrors the tape's masking of truncated
58
+ * injections.
59
+ */
60
+ const REMINDER_SPAN = /<system-reminder>[\s\S]*?(?:<\/system-reminder>|$)/g;
61
+ /** Strip every injected span from a message's text; deterministic, idempotent
62
+ * (the marker we emit below cannot re-trigger this rule). */
63
+ export function stripReminderSpans(text) {
64
+ const removed = text.match(REMINDER_SPAN)?.join('').length ?? 0;
65
+ if (removed === 0)
66
+ return { text, removedChars: 0 };
67
+ return { text: text.replace(REMINDER_SPAN, '').trim(), removedChars: removed };
68
+ }
69
+ /** The visible, greppable, countable marker an omission leaves behind. One
70
+ * shape for every class so downstream assertions (and the sentinel integration
71
+ * test) pin a single prefix. */
72
+ export const omittedMarker = (what, chars) => `⟦omitted:host-injected ${what}, ${chars} chars — project state, not conversation⟧`;
73
+ export function screenConversation(data, rawText, role) {
74
+ if (role === 'user' && !isHumanAuthored(data)) {
75
+ return { text: '', omittedEvent: true, removedChars: rawText.length, sourceKind: sourceLabel(data) };
76
+ }
77
+ // Assistant text is model output, but a runtime snapshot has been observed
78
+ // appended to role user|system by the harness — screening assistant spans
79
+ // too costs one regex and can only ever mark, never invent, text.
80
+ const { text, removedChars } = stripReminderSpans(rawText);
81
+ return { text, omittedEvent: false, removedChars, sourceKind: 'user' };
82
+ }
83
+ //# sourceMappingURL=injections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injections.js","sourceRoot":"","sources":["../src/injections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;0EAK0E;AAC1E,MAAM,UAAU,eAAe,CAAC,IAA6B;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAuC,CAAA;IAC3D,OAAO,MAAM,EAAE,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAA;AAC7D,CAAC;AAED;0EAC0E;AAC1E,MAAM,UAAU,WAAW,CAAC,IAA6B;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAwD,CAAA;IAC5E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,YAAY,CAAA;IAC7C,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;QAClE,CAAC,CAAC,UAAU,MAAM,CAAC,MAAM,EAAE;QAC3B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,SAAS,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,qDAAqD,CAAA;AAE3E;6DAC6D;AAC7D,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAA;IAC/D,IAAI,OAAO,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,CAAA;IACnD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,CAAA;AAChF,CAAC;AAED;;gCAEgC;AAChC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CACnE,0BAA0B,IAAI,KAAK,KAAK,2CAA2C,CAAA;AAkBrF,MAAM,UAAU,kBAAkB,CAAC,IAA6B,EAAE,OAAe,EAAE,IAA0B;IAC3G,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAA;IACtG,CAAC;IACD,2EAA2E;IAC3E,0EAA0E;IAC1E,kEAAkE;IAClE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC1D,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,CAAA;AACxE,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The TOC notice: the single synthetic `user/message` that seeds a continuation.
3
+ *
4
+ * This module exists because of the 66-log lesson from `dsh-session-fork`
5
+ * (docs/development.md § L1): a message source outside the kernel's accepted
6
+ * vocabulary is not a cosmetic bug — stored logs that fail replay refuse to
7
+ * load, retroactively. The kernel's seed validator is not published as a
8
+ * callable, so the vocabulary here is pinned two ways and guarded by
9
+ * test/notice.test.ts:
10
+ *
11
+ * 1. Behaviorally, from the Phase 0 seed probes — every rejection and
12
+ * acceptance recorded in spikes/probe/FINDINGS.md § The seed contract
13
+ * (an object `source` with a non-empty string `kind`; `role: 'user'`; a
14
+ * non-empty string `id`; array `content`; contiguous seq from 0).
15
+ * 2. Structurally, from the installed host's published types —
16
+ * `@deepseek-ai/dsh-llm/lib/types/message.d.ts` `MessageSourceMap` (kinds
17
+ * `user | plugin | model | tool`, merge-extensible) and `ContextForm`
18
+ * (`instructions | catalog | notice | relay | recall | snapshot`), plus
19
+ * `@deepseek-ai/dsh-session`'s `assertMessageEventShape`
20
+ * (lib/index.js:927-957 in 0.1.5-rc.1-era builds) for the seed-time checks.
21
+ *
22
+ * `plugin: 'dsh-chapters'` + `form: 'snapshot'` mirrors how
23
+ * `@deepseek-ai/dsh-system-prompt` delivers assembled context in real logs:
24
+ * attributable to its producer, and rendered by whatever the UI already does
25
+ * for snapshots. INVARIANT 2: exactly one event, at seq 0. Never a slice of
26
+ * the parent's log.
27
+ */
28
+ import type { SessionEventLike } from './types.ts';
29
+ /** Our bundle id as it appears in `source.plugin`. Changing this orphans every already-seeded continuation. */
30
+ export declare const NOTICE_PLUGIN = "dsh-chapters";
31
+ /** The section name our TOC rides under inside the snapshot. */
32
+ export declare const NOTICE_SECTION = "chapters:toc";
33
+ /**
34
+ * Message source kinds the base vocabulary accepts (installed host,
35
+ * `dsh-llm/lib/types/message.d.ts` `MessageSourceMap`). The map is
36
+ * merge-extensible — other plugins add kinds (`agent-instructions`,
37
+ * `skill-catalog` were observed in production logs) — but WE may only emit
38
+ * kinds from this list, and only via the local validator.
39
+ */
40
+ export declare const ACCEPTED_SOURCE_KINDS: readonly ["user", "plugin", "model", "tool"];
41
+ /** `ContextForm` values, exactly as published (semantic, never visual). */
42
+ export declare const ACCEPTED_CONTEXT_FORMS: readonly ["instructions", "catalog", "snapshot", "notice", "relay", "recall"];
43
+ export interface TocNoticeOptions {
44
+ /** Fresh unique id per notice — the kernel rejects a seed message without one. */
45
+ newId: () => string;
46
+ time?: number;
47
+ }
48
+ /**
49
+ * Build the one seed event for a continuation. Throws rather than emit an
50
+ * event the kernel would reject at seed time — a refusal here is cheap, a
51
+ * bricked log is not.
52
+ */
53
+ export declare function buildTocNotice(tocText: string, opts: TocNoticeOptions): SessionEventLike;
54
+ /**
55
+ * Transcription of the kernel's seed-time checks (assertMessageEventShape +
56
+ * the recorded rejections). Kept as an exported predicate so tests pin our
57
+ * output against the same rules the host applies, and any future notice
58
+ * variant fails HERE, at the boundary of authorship.
59
+ */
60
+ export declare function assertKernelAcceptedUserMessage(event: SessionEventLike): void;
package/lib/notice.js ADDED
@@ -0,0 +1,84 @@
1
+ /** Our bundle id as it appears in `source.plugin`. Changing this orphans every already-seeded continuation. */
2
+ export const NOTICE_PLUGIN = 'dsh-chapters';
3
+ /** The section name our TOC rides under inside the snapshot. */
4
+ export const NOTICE_SECTION = 'chapters:toc';
5
+ /**
6
+ * Message source kinds the base vocabulary accepts (installed host,
7
+ * `dsh-llm/lib/types/message.d.ts` `MessageSourceMap`). The map is
8
+ * merge-extensible — other plugins add kinds (`agent-instructions`,
9
+ * `skill-catalog` were observed in production logs) — but WE may only emit
10
+ * kinds from this list, and only via the local validator.
11
+ */
12
+ export const ACCEPTED_SOURCE_KINDS = ['user', 'plugin', 'model', 'tool'];
13
+ /** `ContextForm` values, exactly as published (semantic, never visual). */
14
+ export const ACCEPTED_CONTEXT_FORMS = [
15
+ 'instructions', 'catalog', 'snapshot', 'notice', 'relay', 'recall',
16
+ ];
17
+ /**
18
+ * Build the one seed event for a continuation. Throws rather than emit an
19
+ * event the kernel would reject at seed time — a refusal here is cheap, a
20
+ * bricked log is not.
21
+ */
22
+ export function buildTocNotice(tocText, opts) {
23
+ if (tocText.trim().length === 0)
24
+ throw new Error('chapters: TOC notice text must not be empty');
25
+ const id = opts.newId();
26
+ if (id.length === 0)
27
+ throw new Error('chapters: notice message needs a non-empty id ("lacks an identified message")');
28
+ const event = {
29
+ type: 'user/message',
30
+ seq: 0, // seeds must be contiguous from 0 (FINDINGS: a seq-3 seed is rejected)
31
+ time: opts.time ?? Date.now(),
32
+ surfaceOp: 'append',
33
+ data: {
34
+ id,
35
+ role: 'user',
36
+ source: {
37
+ kind: 'plugin',
38
+ plugin: NOTICE_PLUGIN,
39
+ form: 'snapshot',
40
+ sections: [{ name: NOTICE_SECTION, text: tocText }],
41
+ },
42
+ // content mirrors sections[0].text byte-identically: the model reads
43
+ // `content`; the UI and attribution read `source`. One text, two faces.
44
+ content: [{ type: 'text', text: tocText }],
45
+ },
46
+ };
47
+ assertKernelAcceptedUserMessage(event);
48
+ return event;
49
+ }
50
+ /**
51
+ * Transcription of the kernel's seed-time checks (assertMessageEventShape +
52
+ * the recorded rejections). Kept as an exported predicate so tests pin our
53
+ * output against the same rules the host applies, and any future notice
54
+ * variant fails HERE, at the boundary of authorship.
55
+ */
56
+ export function assertKernelAcceptedUserMessage(event) {
57
+ if (event.type !== 'user/message')
58
+ throw new Error(`chapters: unexpected notice event type ${event.type}`);
59
+ const data = event.data;
60
+ if (data === undefined || typeof data !== 'object')
61
+ throw new Error('chapters: notice event needs data');
62
+ if (typeof data.id !== 'string' || data.id.length === 0) {
63
+ throw new Error('chapters: seed user/message lacks an identified message');
64
+ }
65
+ if (data.role !== 'user')
66
+ throw new Error('chapters: seed message must have role "user"');
67
+ const source = data.source;
68
+ if (source === undefined || typeof source !== 'object' || typeof source.kind !== 'string' || source.kind.length === 0) {
69
+ throw new Error('chapters: seed message has invalid source (object with non-empty string kind required)');
70
+ }
71
+ if (!ACCEPTED_SOURCE_KINDS.includes(source.kind)) {
72
+ throw new Error(`chapters: source kind "${source.kind}" is outside the pinned vocabulary`);
73
+ }
74
+ if (source.kind === 'plugin' && (typeof source.plugin !== 'string' || source.plugin.length === 0)) {
75
+ throw new Error('chapters: plugin source must name its producer');
76
+ }
77
+ if (source.form !== undefined && !ACCEPTED_CONTEXT_FORMS.includes(source.form)) {
78
+ throw new Error(`chapters: context form "${String(source.form)}" is outside the published ContextForm union`);
79
+ }
80
+ if (!Array.isArray(data.content) || data.content.length === 0) {
81
+ throw new Error('chapters: seed message has invalid content');
82
+ }
83
+ }
84
+ //# sourceMappingURL=notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notice.js","sourceRoot":"","sources":["../src/notice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Knowledge-transport providers (knowledge-repo.md §3.2, extended by the §15
3
+ * amendment "transport is pluggable; git is one provider").
4
+ *
5
+ * The sync loop's transport surface is exactly six verbs — ensureClone,
6
+ * initLocal, removeMirror, stageAllAndCommit, pullFastForward, push — every one
7
+ * returning `{ok, detail, code?}` because §5.3 makes transport best-effort by
8
+ * rule: a transport failure degrades to local-only, it never throws into a
9
+ * conversation. A `SyncProvider` is those verbs plus an identity: `kind` (what
10
+ * the project record says it is, and what status lines print) and `describe`
11
+ * (one honest line for /chapters-status — e.g. which remote, which head).
12
+ *
13
+ * What a provider is NOT: storage. The workspace store stays the truth in
14
+ * every mode (§3.1); the mirror directory is materialized the same way whether
15
+ * a git clone or a remote API fills it, so search, rules, index build, and the
16
+ * read path never learn who transported the bytes. Adding a backend means
17
+ * implementing these six verbs and registering the kind — nothing else may
18
+ * grow provider-specific branches.
19
+ */
20
+ import { type GitDriver } from './gitops.ts';
21
+ import type { ProjectRecord } from './sync.ts';
22
+ export type ProviderKind = 'git' | 'treedx';
23
+ export interface SyncProvider extends GitDriver {
24
+ readonly kind: ProviderKind;
25
+ /** One human line describing where this project's transport points. */
26
+ describe(project: ProjectRecord): string;
27
+ }
28
+ /** The default provider: pure-JS git over HTTPS / local paths (record §3.2). */
29
+ export declare const gitProvider: SyncProvider;
30
+ export declare function registerProvider(provider: SyncProvider): void;
31
+ /**
32
+ * Resolve the transport for a project record. `kind` absent = 'git' (every
33
+ * record written before providers existed parses to this default).
34
+ * Throws for an unknown/unregistered kind — callers degrade loudly (§5.3):
35
+ * the sync pass reports local-only with the reason; nothing silently reroutes.
36
+ */
37
+ export declare function selectProvider(project: Pick<ProjectRecord, 'kind' | 'remote'>): SyncProvider;
38
+ /**
39
+ * Reconcile a remote target's scheme with the configured `knowledgeProvider`
40
+ * policy ('auto' | 'git' | 'treedx'). A contradiction throws — NOTHING ever
41
+ * silently falls back into a different pool; the caller degrades loudly
42
+ * (auto-link skips + warns; /chapters-link refuses with the message).
43
+ */
44
+ export declare function providerKindFor(targetKind: ProviderKind, policy: string): ProviderKind;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Knowledge-transport providers (knowledge-repo.md §3.2, extended by the §15
3
+ * amendment "transport is pluggable; git is one provider").
4
+ *
5
+ * The sync loop's transport surface is exactly six verbs — ensureClone,
6
+ * initLocal, removeMirror, stageAllAndCommit, pullFastForward, push — every one
7
+ * returning `{ok, detail, code?}` because §5.3 makes transport best-effort by
8
+ * rule: a transport failure degrades to local-only, it never throws into a
9
+ * conversation. A `SyncProvider` is those verbs plus an identity: `kind` (what
10
+ * the project record says it is, and what status lines print) and `describe`
11
+ * (one honest line for /chapters-status — e.g. which remote, which head).
12
+ *
13
+ * What a provider is NOT: storage. The workspace store stays the truth in
14
+ * every mode (§3.1); the mirror directory is materialized the same way whether
15
+ * a git clone or a remote API fills it, so search, rules, index build, and the
16
+ * read path never learn who transported the bytes. Adding a backend means
17
+ * implementing these six verbs and registering the kind — nothing else may
18
+ * grow provider-specific branches.
19
+ */
20
+ import { isomorphicDriver } from "./gitops.js";
21
+ /** The default provider: pure-JS git over HTTPS / local paths (record §3.2). */
22
+ export const gitProvider = {
23
+ ...isomorphicDriver,
24
+ kind: 'git',
25
+ describe: (project) => `git · ${project.remote}`,
26
+ };
27
+ // Registration is a map keyed by kind; Phase C's treedx module adds its entry.
28
+ // Kept as a registry (not a switch) so provider selection stays open while
29
+ // `kind` stays closed: an unknown kind is a LOUD refusal, never a silent
30
+ // fallback to git that would sync someone's chapters into the wrong pool.
31
+ const registry = new Map([['git', gitProvider]]);
32
+ export function registerProvider(provider) {
33
+ registry.set(provider.kind, provider);
34
+ }
35
+ /**
36
+ * Resolve the transport for a project record. `kind` absent = 'git' (every
37
+ * record written before providers existed parses to this default).
38
+ * Throws for an unknown/unregistered kind — callers degrade loudly (§5.3):
39
+ * the sync pass reports local-only with the reason; nothing silently reroutes.
40
+ */
41
+ export function selectProvider(project) {
42
+ const kind = project.kind ?? 'git';
43
+ const provider = registry.get(kind);
44
+ if (provider === undefined) {
45
+ throw new Error(`knowledge provider '${kind}' is not registered (remote ${project.remote})`);
46
+ }
47
+ return provider;
48
+ }
49
+ /**
50
+ * Reconcile a remote target's scheme with the configured `knowledgeProvider`
51
+ * policy ('auto' | 'git' | 'treedx'). A contradiction throws — NOTHING ever
52
+ * silently falls back into a different pool; the caller degrades loudly
53
+ * (auto-link skips + warns; /chapters-link refuses with the message).
54
+ */
55
+ export function providerKindFor(targetKind, policy) {
56
+ if (policy === 'auto')
57
+ return targetKind;
58
+ if (policy !== 'git' && policy !== 'treedx') {
59
+ throw new Error(`knowledgeProvider policy '${policy}' is not one of auto | git | treedx`);
60
+ }
61
+ if (policy !== targetKind) {
62
+ throw new Error(`knowledgeProvider policy '${policy}' contradicts the remote target (a ${targetKind} remote) — fix one; there is no automatic fallback`);
63
+ }
64
+ return policy;
65
+ }
66
+ //# sourceMappingURL=provider.js.map