@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
package/lib/indexing.js
ADDED
|
@@ -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"}
|
package/lib/notice.d.ts
ADDED
|
@@ -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;
|
package/lib/provider.js
ADDED
|
@@ -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
|