@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,73 @@
1
+ /**
2
+ * P2 S1 — the enrichment pass, pure core (record §6.1).
3
+ *
4
+ * One structured call per ARCHIVED chapter, input = the cheap slice the user
5
+ * specified (user requests + first/last assistant message, capped, plus the
6
+ * first ~200 tokens of the chapter body), output = validated JSON:
7
+ * {"title": "...", "summary": "...", "topics": ["...", ...]}
8
+ *
9
+ * No fs, no harness, no model here — the queue (S2) wires the fetcher. This
10
+ * module guarantees the properties the record stakes:
11
+ * - VALIDATION BEFORE COMMIT: parseEnrichResult accepts nothing malformed;
12
+ * - NEVER BLOCKS OR BLANKS: applyEnrich(null) is a pure no-op — the
13
+ * deterministic title/summary/topics from P1 survive any model failure.
14
+ */
15
+ export interface EnrichSlice {
16
+ requests: string[];
17
+ firstAssistant?: string;
18
+ lastAssistant?: string;
19
+ }
20
+ export interface EnrichResult {
21
+ title: string;
22
+ summary: string;
23
+ topics: string[];
24
+ }
25
+ export type GeneratedMark = {
26
+ by: 'deterministic';
27
+ } | {
28
+ by: 'model';
29
+ model: string;
30
+ at: string;
31
+ };
32
+ export interface GeneratedProvenance {
33
+ title: GeneratedMark[];
34
+ summary: GeneratedMark[];
35
+ topics: GeneratedMark[];
36
+ }
37
+ export type EnrichableFields = 'title' | 'summary' | 'topics';
38
+ export declare const ENRICH_MAX_TOKENS = 350;
39
+ /**
40
+ * Assemble the model input: capped slice, stable order, hard total budget.
41
+ * Deterministic — same chapter material always produces the same prompt,
42
+ * which is what makes the queue's chapter+model idempotency key sound.
43
+ */
44
+ export declare function buildEnrichInput(meta: {
45
+ title: string;
46
+ }, slice: EnrichSlice, bodyHead: string): string;
47
+ /**
48
+ * The validator. Tolerates code fences and surrounding prose (extracts the
49
+ * first JSON object), then requires every field well-shaped. Returns null —
50
+ * never throws — on ANY failure; the retry-then-keep-deterministic ladder is
51
+ * the caller's loop by design.
52
+ */
53
+ export declare function parseEnrichResult(text: string): EnrichResult | null;
54
+ export interface CurrentValues {
55
+ title: string;
56
+ summary: string;
57
+ topics: string[];
58
+ generated?: Partial<GeneratedProvenance>;
59
+ }
60
+ /**
61
+ * Merge an enrichment result into the chapter's model-owned fields.
62
+ * - result === null (ladder bottomed out) → PURE no-op, values + provenance
63
+ * untouched (deterministic P1 values survive forever).
64
+ * - same values AND same newest model author → no-op (idempotent re-run).
65
+ * - otherwise: fields update; each provenance chain grows newest-first, one
66
+ * entry per model (re-annotation by a newer model never erases history).
67
+ * The verbatim BODY is not in this module's universe — callers may only
68
+ * rewrite frontmatter fields (record §6.2), body hash-verified elsewhere.
69
+ */
70
+ export declare function applyEnrich(current: CurrentValues, result: EnrichResult | null, model: string, at: string): {
71
+ values: CurrentValues;
72
+ changed: boolean;
73
+ };
package/lib/enrich.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * P2 S1 — the enrichment pass, pure core (record §6.1).
3
+ *
4
+ * One structured call per ARCHIVED chapter, input = the cheap slice the user
5
+ * specified (user requests + first/last assistant message, capped, plus the
6
+ * first ~200 tokens of the chapter body), output = validated JSON:
7
+ * {"title": "...", "summary": "...", "topics": ["...", ...]}
8
+ *
9
+ * No fs, no harness, no model here — the queue (S2) wires the fetcher. This
10
+ * module guarantees the properties the record stakes:
11
+ * - VALIDATION BEFORE COMMIT: parseEnrichResult accepts nothing malformed;
12
+ * - NEVER BLOCKS OR BLANKS: applyEnrich(null) is a pure no-op — the
13
+ * deterministic title/summary/topics from P1 survive any model failure.
14
+ */
15
+ const TITLE_CAP = 160;
16
+ const SUMMARY_CAP = 600;
17
+ const TOPIC_MAX = 10;
18
+ const TOPIC_LEN = 48;
19
+ const PART_CAP = 1200;
20
+ const TOTAL_IN_CAP = 8000;
21
+ const BODY_HEAD_TOKENS = 200;
22
+ export const ENRICH_MAX_TOKENS = 350;
23
+ const INSTRUCTION = 'You annotate one archived chapter of a coding-agent conversation for retrieval. '
24
+ + 'Reply with ONLY one JSON object, no prose, no code fences, shaped exactly: '
25
+ + '{"title": "<one descriptive sentence, <=120 chars>", "summary": "<2-3 informative sentences>", "topics": ["<lowercase short labels, 3 to 10>"]}. '
26
+ + 'Topics are semantic labels (concepts, not file paths). Base everything strictly on the material below.';
27
+ /** cap a piece of the slice; truncation is EXPLICIT, never silent. */
28
+ function cap(text, n) {
29
+ const t = defuseInjections(text).trim();
30
+ if (t.length <= n)
31
+ return t;
32
+ return t.slice(0, n) + ' …[truncated]';
33
+ }
34
+ /**
35
+ * Host-injected spans enter the chapter as ⟦omitted:host-injected <label>,
36
+ * N chars…⟧ markers whose COUNT and length are machine state (a dev box
37
+ * carries a skills catalog message; a runner does not). The ladder annotates
38
+ * CONVERSATION — record §"host-injected context is not conversation": in the
39
+ * enricher's input every marker collapses to a fixed token. Without this the
40
+ * 8000-char input cap lands at different offsets per machine (measured on
41
+ * hosted CI 2026-09-25: recorded prompts cut ' …[truncated]' at offset 1771
42
+ * while the runner's shorter material sent the full tail — the exchange
43
+ * missed, the ladder starved).
44
+ */
45
+ function defuseInjections(text) {
46
+ return text
47
+ .replace(/⟦omitted:host-injected[^⟧]*⟧/g, '⟦injected-context⟧')
48
+ .replace(/(?:⟦injected-context⟧[ \t\n]*){2,}/g, '⟦injected-context⟧ ');
49
+ }
50
+ /**
51
+ * Assemble the model input: capped slice, stable order, hard total budget.
52
+ * Deterministic — same chapter material always produces the same prompt,
53
+ * which is what makes the queue's chapter+model idempotency key sound.
54
+ */
55
+ export function buildEnrichInput(meta, slice, bodyHead) {
56
+ const parts = [INSTRUCTION, '', `CURRENT TITLE: ${cap(meta.title, TITLE_CAP)}`];
57
+ if (slice.firstAssistant !== undefined)
58
+ parts.push(`FIRST ASSISTANT: ${cap(slice.firstAssistant, PART_CAP)}`);
59
+ if (slice.lastAssistant !== undefined)
60
+ parts.push(`LAST ASSISTANT: ${cap(slice.lastAssistant, PART_CAP)}`);
61
+ for (const [i, r] of slice.requests.slice(-3).entries())
62
+ parts.push(`REQUEST ${i + 1}: ${cap(r, PART_CAP)}`);
63
+ parts.push('', 'CHAPTER BODY (opening):', cap(bodyHead, BODY_HEAD_TOKENS * 4));
64
+ let text = parts.join('\n');
65
+ if (text.length > TOTAL_IN_CAP)
66
+ text = text.slice(0, TOTAL_IN_CAP) + '\n…[input truncated]';
67
+ return text;
68
+ }
69
+ /** label sanitizer: lowercase, bracket/quote-free, bounded; null if unusable. */
70
+ function cleanTopic(raw) {
71
+ if (typeof raw !== 'string')
72
+ return null;
73
+ const t = raw.trim().toLowerCase().replace(/["'`[\]{}()<>|\\]/g, '').replace(/\s+/g, ' ').trim();
74
+ if (t.length === 0 || t.length > TOPIC_LEN)
75
+ return null;
76
+ return t;
77
+ }
78
+ /**
79
+ * The validator. Tolerates code fences and surrounding prose (extracts the
80
+ * first JSON object), then requires every field well-shaped. Returns null —
81
+ * never throws — on ANY failure; the retry-then-keep-deterministic ladder is
82
+ * the caller's loop by design.
83
+ */
84
+ export function parseEnrichResult(text) {
85
+ const m = /\{[\s\S]*\}/.exec(text);
86
+ if (m === null)
87
+ return null;
88
+ let obj;
89
+ try {
90
+ obj = JSON.parse(m[0]);
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ if (typeof obj !== 'object' || obj === null)
96
+ return null;
97
+ const o = obj;
98
+ if (typeof o.title !== 'string' || typeof o.summary !== 'string' || !Array.isArray(o.topics))
99
+ return null;
100
+ const title = o.title.trim();
101
+ const summary = o.summary.trim();
102
+ if (title.length === 0 || summary.length === 0)
103
+ return null;
104
+ const topics = [];
105
+ for (const t of o.topics) {
106
+ const c = cleanTopic(t);
107
+ if (c !== null && !topics.includes(c))
108
+ topics.push(c);
109
+ if (topics.length >= TOPIC_MAX)
110
+ break;
111
+ }
112
+ if (topics.length === 0)
113
+ return null;
114
+ return {
115
+ title: title.slice(0, TITLE_CAP),
116
+ summary: summary.slice(0, SUMMARY_CAP),
117
+ topics,
118
+ };
119
+ }
120
+ /**
121
+ * Merge an enrichment result into the chapter's model-owned fields.
122
+ * - result === null (ladder bottomed out) → PURE no-op, values + provenance
123
+ * untouched (deterministic P1 values survive forever).
124
+ * - same values AND same newest model author → no-op (idempotent re-run).
125
+ * - otherwise: fields update; each provenance chain grows newest-first, one
126
+ * entry per model (re-annotation by a newer model never erases history).
127
+ * The verbatim BODY is not in this module's universe — callers may only
128
+ * rewrite frontmatter fields (record §6.2), body hash-verified elsewhere.
129
+ */
130
+ export function applyEnrich(current, result, model, at) {
131
+ if (result === null)
132
+ return { values: current, changed: false };
133
+ const sameValues = current.title === result.title
134
+ && current.summary === result.summary
135
+ && current.topics.join('\u0000') === result.topics.join('\u0000');
136
+ const authoredBySame = ['title', 'summary', 'topics'].every((f) => {
137
+ const top = current.generated?.[f]?.[0];
138
+ return top?.by === 'model' && top.model === model;
139
+ });
140
+ if (sameValues && authoredBySame)
141
+ return { values: current, changed: false };
142
+ const gen = { title: [], summary: [], topics: [] };
143
+ const mark = { by: 'model', model, at };
144
+ for (const f of ['title', 'summary', 'topics']) {
145
+ const chain = current.generated?.[f] ?? [];
146
+ gen[f] = [mark, ...chain.filter((c) => !(c.by === 'model' && c.model === model))];
147
+ }
148
+ return { values: { ...current, title: result.title, summary: result.summary, topics: result.topics, generated: gen }, changed: true };
149
+ }
150
+ //# sourceMappingURL=enrich.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrich.js","sourceRoot":"","sources":["../src/enrich.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ export interface RemoteSpec {
2
+ url: string;
3
+ /** Scoped per-harness token (record §2.1); absent for file:// remotes. */
4
+ token?: string;
5
+ }
6
+ export interface GitOpResult {
7
+ ok: boolean;
8
+ detail: string;
9
+ changed?: boolean;
10
+ /** Machine-readable failure class (r35 lesson: the caller must NOT regex
11
+ * prose — the network failure's wording once contained 'diverge'). */
12
+ code?: 'diverged' | 'network' | 'rejected' | 'origin-mismatch' | 'auth' | 'not_found';
13
+ }
14
+ export interface CommitAuthor {
15
+ name: string;
16
+ email: string;
17
+ }
18
+ /**
19
+ * Classify an isomorphic-git transport error. A rejected credential (HTTP
20
+ * 401/403, or the auth-phrase fallback) MUST come back as `auth`, not the
21
+ * generic `network` the catch blocks used to assume — otherwise /chapters-status
22
+ * tells the user "remote unreachable" when the truth is "your token is bad,
23
+ * re-link it". 404/401-on-repo maps to not_found (private-but-missing vs
24
+ * forbidden are genuinely ambiguous over smart HTTP; auth wins when a token
25
+ * was actually sent).
26
+ */
27
+ export declare function classifyGitTransportError(error: unknown): {
28
+ code: 'auth' | 'not_found' | 'network';
29
+ detail: string;
30
+ };
31
+ /**
32
+ * Ensure a clone exists at `dir` for `remote`. A non-repo dir is cloned
33
+ * (single branch, main); an existing repo is left alone — pull/ff is the
34
+ * caller's step. A non-empty non-repo dir is dead transport: moved aside
35
+ * (`<dir>.stale-<ts>`, contents preserved) and cloned fresh. The r39-era e2e
36
+ * failure class was exactly this residue making every later clone refuse;
37
+ * renaming aside is strictly weaker than what the diverged-rebuild path
38
+ * already does (removeMirror rm -rf) — the mirror is transport, the store
39
+ * is truth (§3.1).
40
+ */
41
+ export declare function ensureClone(dir: string, remote: RemoteSpec, opts?: {
42
+ defaultBranch?: string;
43
+ }): Promise<GitOpResult>;
44
+ /**
45
+ * Commit every workdir file whose content differs from HEAD. Does not trust
46
+ * `statusMatrix` — on isomorphic-git 1.42 it reports stale index state right
47
+ * after a commit (verified: a clean repo still reports [1,1,1]); the
48
+ * workdir-vs-HEAD content comparison is the honest diff. Untracked dotfiles
49
+ * and .git are out of scope (the mirror holds knowledge files only).
50
+ */
51
+ export declare function stageAllAndCommit(dir: string, message: string, author: CommitAuthor): Promise<GitOpResult>;
52
+ /** Fetch the remote and fast-forward the local branch. ff-only, always. */
53
+ export declare function pullFastForward(dir: string, remote: RemoteSpec, opts?: {
54
+ defaultBranch?: string;
55
+ }): Promise<GitOpResult>;
56
+ export declare const isLocalUpstreamUrl: (url: string) => boolean;
57
+ /** file:///a/b -> /a/b ; ~ -> homedir ; relative resolved against `cwd`. */
58
+ export declare function resolveLocalUpstreamPath(url: string, cwd: string): string;
59
+ /** local-upstream ops take a url that is a resolved absolute path (sync.ts
60
+ * resolves before calling; the driver dispatches on the shape). */
61
+ export declare function ensureCloneLocal(mirrorDir: string, upstreamPath: string, opts?: {
62
+ defaultBranch?: string;
63
+ }): Promise<GitOpResult>;
64
+ export declare function pullLocal(mirrorDir: string, upstreamPath: string, opts?: {
65
+ defaultBranch?: string;
66
+ }): Promise<GitOpResult>;
67
+ export declare function pushLocal(mirrorDir: string, upstreamPath: string, opts?: {
68
+ defaultBranch?: string;
69
+ }): Promise<GitOpResult>;
70
+ /** Mirror provenance: which upstream this mirror was last bound to (http OR
71
+ * local) — an origin change is detected on EVERY ensureClone, local or not. */
72
+ export declare const originSentinel: (mirrorDir: string) => string;
73
+ export declare function recordOrigin(mirrorDir: string, url: string): Promise<void>;
74
+ export declare function readOrigin(mirrorDir: string): string | null;
75
+ export interface GitDriver {
76
+ ensureClone(dir: string, remote: RemoteSpec, opts?: {
77
+ defaultBranch?: string;
78
+ }): Promise<GitOpResult>;
79
+ initLocal(dir: string, remote?: RemoteSpec, opts?: {
80
+ defaultBranch?: string;
81
+ }): Promise<GitOpResult>;
82
+ removeMirror(dir: string): Promise<GitOpResult>;
83
+ stageAllAndCommit(dir: string, message: string, author: CommitAuthor): Promise<GitOpResult>;
84
+ pullFastForward(dir: string, remote: RemoteSpec, opts?: {
85
+ defaultBranch?: string;
86
+ }): Promise<GitOpResult>;
87
+ push(dir: string, remote: RemoteSpec, opts?: {
88
+ defaultBranch?: string;
89
+ }): Promise<GitOpResult>;
90
+ }
91
+ /**
92
+ * Initialize a local-only mirror (offline mode, record §5.3): the remote is
93
+ * unreachable, but the mirror is transport over truth we own — publish,
94
+ * index, commit, and search keep working; push waits for the remote.
95
+ */
96
+ export declare function initLocal(dir: string, remote?: RemoteSpec, opts?: {
97
+ defaultBranch?: string;
98
+ }): Promise<GitOpResult>;
99
+ /** Delete the mirror working tree + git dir entirely. SAFE ONLY because the
100
+ * mirror is transport (§3.1): the workspace store holds the truth, and the
101
+ * rebuild republishes from it (the diverged-offline recovery, §5.3). */
102
+ export declare function removeMirror(dir: string): Promise<GitOpResult>;
103
+ /** Push the local branch; a rejected (non-ff) push comes back for the caller to ff-and-retry. */
104
+ export declare function push(dir: string, remote: RemoteSpec, opts?: {
105
+ defaultBranch?: string;
106
+ }): Promise<GitOpResult>;
107
+ export declare const isomorphicDriver: GitDriver;