@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.21
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 +76 -2
- package/cordis.patch.yml +363 -0
- package/docs/architecture.md +692 -0
- package/docs/autorun-guide.md +248 -0
- package/docs/backlog.md +2022 -0
- package/docs/contract.md +938 -0
- package/docs/development.md +990 -0
- package/docs/host-compaction-seam.md +225 -0
- package/docs/host-protected-head-ask.md +80 -0
- package/docs/knowledge-repo.md +1332 -0
- package/docs/plans-carriage.md +571 -0
- package/docs/project-state.md +642 -0
- package/docs/provider.md +98 -0
- package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
- package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
- package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
- package/docs/verify.md +561 -0
- package/lib/archive.d.ts +161 -0
- package/lib/archive.js +229 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +125 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +62 -0
- package/lib/artifact-query.js +187 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +145 -0
- package/lib/auto-save.js +334 -0
- package/lib/auto-save.js.map +1 -0
- package/lib/aux-message.d.ts +17 -0
- package/lib/aux-message.js +20 -0
- package/lib/aux-message.js.map +1 -0
- package/lib/client.js +1419 -0
- package/lib/client.js.map +1 -0
- package/lib/codegraph.d.ts +76 -0
- package/lib/codegraph.js +190 -0
- package/lib/codegraph.js.map +1 -0
- package/lib/commands.d.ts +52 -0
- package/lib/commands.js +946 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +185 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +245 -0
- package/lib/continue-core.js +405 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +301 -0
- package/lib/engine-core.js +531 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine-note.d.ts +35 -0
- package/lib/engine-note.js +32 -0
- package/lib/engine-note.js.map +1 -0
- package/lib/engine.d.ts +329 -0
- package/lib/engine.js +1179 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +126 -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 +163 -0
- package/lib/enrich-wire.js +353 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +102 -0
- package/lib/enrich.js +267 -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 +241 -0
- package/lib/index.js +1162 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +98 -0
- package/lib/indexing.js +223 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +90 -0
- package/lib/injections.js +165 -0
- package/lib/injections.js.map +1 -0
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/log-shape.d.ts +38 -0
- package/lib/log-shape.js +93 -0
- package/lib/log-shape.js.map +1 -0
- package/lib/models-command.d.ts +74 -0
- package/lib/models-command.js +100 -0
- package/lib/models-command.js.map +1 -0
- package/lib/notice.d.ts +74 -0
- package/lib/notice.js +92 -0
- package/lib/notice.js.map +1 -0
- package/lib/plans.d.ts +154 -0
- package/lib/plans.js +315 -0
- package/lib/plans.js.map +1 -0
- package/lib/preset.d.ts +186 -0
- package/lib/preset.js +212 -0
- package/lib/preset.js.map +1 -0
- package/lib/prompt-keys.d.ts +7 -0
- package/lib/prompt-keys.js +8 -0
- package/lib/prompt-keys.js.map +1 -0
- package/lib/prompt-loop.d.ts +73 -0
- package/lib/prompt-loop.js +147 -0
- package/lib/prompt-loop.js.map +1 -0
- package/lib/prompt-push.d.ts +47 -0
- package/lib/prompt-push.js +115 -0
- package/lib/prompt-push.js.map +1 -0
- package/lib/prompt-runner.d.ts +48 -0
- package/lib/prompt-runner.js +90 -0
- package/lib/prompt-runner.js.map +1 -0
- package/lib/prompt-state.d.ts +142 -0
- package/lib/prompt-state.js +101 -0
- package/lib/prompt-state.js.map +1 -0
- package/lib/prompt.d.ts +270 -0
- package/lib/prompt.js +408 -0
- package/lib/prompt.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 +187 -0
- package/lib/registry.js +235 -0
- package/lib/registry.js.map +1 -0
- package/lib/removal.d.ts +159 -0
- package/lib/removal.js +556 -0
- package/lib/removal.js.map +1 -0
- package/lib/render.d.ts +97 -0
- package/lib/render.js +530 -0
- package/lib/render.js.map +1 -0
- package/lib/repair-run.d.ts +76 -0
- package/lib/repair-run.js +506 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +208 -0
- package/lib/repair.js +357 -0
- package/lib/repair.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 +46 -0
- package/lib/rules-commands.js +379 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +267 -0
- package/lib/rules.js +478 -0
- package/lib/rules.js.map +1 -0
- package/lib/save-core.d.ts +72 -0
- package/lib/save-core.js +220 -0
- package/lib/save-core.js.map +1 -0
- package/lib/search.d.ts +112 -0
- package/lib/search.js +282 -0
- package/lib/search.js.map +1 -0
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/signature.d.ts +45 -0
- package/lib/signature.js +199 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +156 -0
- package/lib/store.js +368 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +220 -0
- package/lib/sync.js +810 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +113 -0
- package/lib/tools.js +1090 -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 +210 -0
- package/lib/types.js +31 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.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/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +151 -3
package/lib/enrich.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { PLUGIN_VERSION } from "./version.js";
|
|
2
|
+
const TITLE_CAP = 160;
|
|
3
|
+
// #188: 600 → 2400. The summary is no longer a retrieval blurb — it is the chapter's guidance
|
|
4
|
+
// value for the suggestion director, which reads title+summary+plots INSTEAD of the body. The cap
|
|
5
|
+
// stays (frontmatter hygiene) and its truncation stays LOUD (' …[truncated]'), but 2-3 sentences
|
|
6
|
+
// cannot carry a chapter's decisions, findings, and open threads.
|
|
7
|
+
const SUMMARY_CAP = 2400;
|
|
8
|
+
const TOPIC_MAX = 10;
|
|
9
|
+
const TOPIC_LEN = 48;
|
|
10
|
+
// §7.5 mining bounds: few, short, and traceable. One chapter is not allowed to
|
|
11
|
+
// mint a dozen standing orders for a session tree.
|
|
12
|
+
const RULE_MAX = 3;
|
|
13
|
+
const RULE_TEXT_MIN = 10;
|
|
14
|
+
const RULE_TEXT_CAP = 320;
|
|
15
|
+
const RULE_QUOTE_CAP = 200;
|
|
16
|
+
const PART_CAP = 1200;
|
|
17
|
+
const TOTAL_IN_CAP = 8000;
|
|
18
|
+
const BODY_HEAD_TOKENS = 200;
|
|
19
|
+
// The default ceiling for ONE auxiliary completion — the annotation pass (which now
|
|
20
|
+
// also carries mined rules) and the conflicts report.
|
|
21
|
+
//
|
|
22
|
+
// 900 was the pre-mining number. Measured 2026-10-21, mining real chapters at
|
|
23
|
+
// `qwen3.8-flash`: the provider reported completion usage of 969–3237 tokens against a
|
|
24
|
+
// 700-token request, because REASONING tokens are counted in completion usage. A ceiling
|
|
25
|
+
// with room only for the visible JSON truncates mid-object, and a truncated answer
|
|
26
|
+
// parses as "no rules" — a silent loss that reads like a model with nothing to say. So
|
|
27
|
+
// the budget covers the thinking, not just the answer: 4000, configurable as
|
|
28
|
+
// `enrichmentMaxTokens` so no call site carries a literal.
|
|
29
|
+
//
|
|
30
|
+
// Unchanged: the input caps and the one-retry ladder. Cost is bounded by chapters per
|
|
31
|
+
// drain, not by this number.
|
|
32
|
+
export const ENRICH_MAX_TOKENS_DEFAULT = 4000;
|
|
33
|
+
const INSTRUCTION = 'You annotate one archived chapter of a coding-agent conversation for retrieval. '
|
|
34
|
+
+ 'Reply with ONLY one JSON object, no prose, no code fences, shaped exactly: '
|
|
35
|
+
+ '{"title": "<one descriptive sentence, <=120 chars>", '
|
|
36
|
+
+ '"summary": "<a DETAILED summary — one to three paragraphs capturing the decisions made, findings established, state changed, and threads left open, written for a reader who must direct future work from this chapter alone>", '
|
|
37
|
+
+ '"topics": ["<lowercase short labels, 3 to 10>"], "rules": [{"text": "<one imperative standing instruction>", "category": "<short slug>", "quote": "<the user\u0027s own words verbatim>"}]}. '
|
|
38
|
+
+ 'Topics are semantic labels (concepts, not file paths). Base everything strictly on the material below.\n'
|
|
39
|
+
+ 'rules is OPTIONAL and for durable steering only: a convention, invariant or pitfall the USER stated '
|
|
40
|
+
+ 'that should keep applying to future work in this project (e.g. "run the tape replay before tagging"). '
|
|
41
|
+
+ 'Do NOT report one-off task details, preferences about this chapter only, or anything you inferred. '
|
|
42
|
+
+ 'Every rule MUST quote the user\u0027s own words verbatim in "quote"; if no rule is backed by a literal user '
|
|
43
|
+
+ 'sentence, omit "rules" or send []. At most 3. They are reviewed and usually invisible to the model that wrote them.';
|
|
44
|
+
/** cap a piece of the slice; truncation is EXPLICIT, never silent. */
|
|
45
|
+
function cap(text, n) {
|
|
46
|
+
const t = defuseInjections(text).trim();
|
|
47
|
+
if (t.length <= n)
|
|
48
|
+
return t;
|
|
49
|
+
return t.slice(0, n) + ' …[truncated]';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Host-injected spans enter the chapter as ⟦omitted:host-injected <label>,
|
|
53
|
+
* N chars…⟧ markers whose COUNT and length are machine state (a dev box
|
|
54
|
+
* carries a skills catalog message; a runner does not). The ladder annotates
|
|
55
|
+
* CONVERSATION — record §"host-injected context is not conversation": in the
|
|
56
|
+
* enricher's input every marker collapses to a fixed token. Without this the
|
|
57
|
+
* 8000-char input cap lands at different offsets per machine (measured on
|
|
58
|
+
* hosted CI 2026-09-25: recorded prompts cut ' …[truncated]' at offset 1771
|
|
59
|
+
* while the runner's shorter material sent the full tail — the exchange
|
|
60
|
+
* missed, the ladder starved).
|
|
61
|
+
*/
|
|
62
|
+
function defuseInjections(text) {
|
|
63
|
+
return text
|
|
64
|
+
.replace(/⟦omitted:host-injected[^⟧]*⟧/g, '⟦injected-context⟧')
|
|
65
|
+
.replace(/(?:⟦injected-context⟧[ \t\n]*){2,}/g, '⟦injected-context⟧ ');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Assemble the model input: capped slice, stable order, hard total budget.
|
|
69
|
+
* Deterministic — same chapter material always produces the same prompt,
|
|
70
|
+
* which is what makes the queue's chapter+model idempotency key sound.
|
|
71
|
+
*
|
|
72
|
+
* @param codegraphContext Optional structural context from CodeGraph (symbols,
|
|
73
|
+
* call paths). When present, appended as a bounded block so the enricher can
|
|
74
|
+
* produce more precise topics. Absent → identical output to before this feature.
|
|
75
|
+
*/
|
|
76
|
+
export function buildEnrichInput(meta, slice, bodyHead, codegraphContext) {
|
|
77
|
+
const parts = [INSTRUCTION, '', `CURRENT TITLE: ${cap(meta.title, TITLE_CAP)}`];
|
|
78
|
+
if (slice.firstAssistant !== undefined)
|
|
79
|
+
parts.push(`FIRST ASSISTANT: ${cap(slice.firstAssistant, PART_CAP)}`);
|
|
80
|
+
if (slice.lastAssistant !== undefined)
|
|
81
|
+
parts.push(`LAST ASSISTANT: ${cap(slice.lastAssistant, PART_CAP)}`);
|
|
82
|
+
for (const [i, r] of slice.requests.slice(-3).entries())
|
|
83
|
+
parts.push(`REQUEST ${i + 1}: ${cap(r, PART_CAP)}`);
|
|
84
|
+
parts.push('', 'CHAPTER BODY (opening):', cap(bodyHead, BODY_HEAD_TOKENS * 4));
|
|
85
|
+
// CodeGraph structural context: symbols and their immediate relationships,
|
|
86
|
+
// verified against the project index. Helps the enricher produce structurally
|
|
87
|
+
// precise topics (e.g. "auth-middleware" instead of just "auth"). Capped at
|
|
88
|
+
// 800 chars to stay within TOTAL_IN_CAP; absent → zero bytes added.
|
|
89
|
+
if (codegraphContext !== undefined && codegraphContext.trim().length > 0) {
|
|
90
|
+
parts.push('', 'CODE STRUCTURE (from CodeGraph index):', cap(codegraphContext, 800));
|
|
91
|
+
}
|
|
92
|
+
let text = parts.join('\n');
|
|
93
|
+
if (text.length > TOTAL_IN_CAP)
|
|
94
|
+
text = text.slice(0, TOTAL_IN_CAP) + '\n…[input truncated]';
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
/** label sanitizer: lowercase, bracket/quote-free, bounded; null if unusable. */
|
|
98
|
+
function cleanTopic(raw) {
|
|
99
|
+
if (typeof raw !== 'string')
|
|
100
|
+
return null;
|
|
101
|
+
const t = raw.trim().toLowerCase().replace(/["'`[\]{}()<>|\\]/g, '').replace(/\s+/g, ' ').trim();
|
|
102
|
+
if (t.length === 0 || t.length > TOPIC_LEN)
|
|
103
|
+
return null;
|
|
104
|
+
return t;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The validator. Tolerates code fences and surrounding prose (extracts the
|
|
108
|
+
* first JSON object), then requires every field well-shaped. Returns null —
|
|
109
|
+
* never throws — on ANY failure; the retry-then-keep-deterministic ladder is
|
|
110
|
+
* the caller's loop by design.
|
|
111
|
+
*/
|
|
112
|
+
export function parseEnrichResult(text) {
|
|
113
|
+
const m = /\{[\s\S]*\}/.exec(text);
|
|
114
|
+
if (m === null)
|
|
115
|
+
return null;
|
|
116
|
+
let obj;
|
|
117
|
+
try {
|
|
118
|
+
obj = JSON.parse(m[0]);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (typeof obj !== 'object' || obj === null)
|
|
124
|
+
return null;
|
|
125
|
+
const o = obj;
|
|
126
|
+
if (typeof o.title !== 'string' || typeof o.summary !== 'string' || !Array.isArray(o.topics))
|
|
127
|
+
return null;
|
|
128
|
+
const title = o.title.trim();
|
|
129
|
+
const summary = o.summary.trim();
|
|
130
|
+
if (title.length === 0 || summary.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
const topics = [];
|
|
133
|
+
for (const t of o.topics) {
|
|
134
|
+
const c = cleanTopic(t);
|
|
135
|
+
if (c !== null && !topics.includes(c))
|
|
136
|
+
topics.push(c);
|
|
137
|
+
if (topics.length >= TOPIC_MAX)
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
if (topics.length === 0)
|
|
141
|
+
return null;
|
|
142
|
+
// rules are additive: absent, malformed or empty ⇒ no mined rules, and the
|
|
143
|
+
// annotation itself still commits. A bad `rules` array must never cost a
|
|
144
|
+
// chapter its title/summary/topics (the ladder's never-blank guarantee).
|
|
145
|
+
const rules = [];
|
|
146
|
+
if (Array.isArray(o.rules)) {
|
|
147
|
+
for (const raw of o.rules) {
|
|
148
|
+
if (typeof raw !== 'object' || raw === null)
|
|
149
|
+
continue;
|
|
150
|
+
const r = raw;
|
|
151
|
+
if (typeof r.text !== 'string' || typeof r.quote !== 'string')
|
|
152
|
+
continue;
|
|
153
|
+
const text = r.text.trim();
|
|
154
|
+
const quote = r.quote.trim().replace(/^["'`]+|["'`]+$/g, '');
|
|
155
|
+
if (text.length < RULE_TEXT_MIN || text.length > RULE_TEXT_CAP * 3 || quote.length === 0)
|
|
156
|
+
continue;
|
|
157
|
+
const category = typeof r.category === 'string'
|
|
158
|
+
? r.category.trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 24)
|
|
159
|
+
: '';
|
|
160
|
+
rules.push({ text: text.slice(0, RULE_TEXT_CAP), category, quote: quote.slice(0, RULE_QUOTE_CAP) });
|
|
161
|
+
if (rules.length >= RULE_MAX)
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
title: title.slice(0, TITLE_CAP),
|
|
167
|
+
// #188: the summary clamp stays LOUD — the input-side cap() has always announced itself
|
|
168
|
+
// ("truncation is EXPLICIT, never silent"); the output side was a bare slice, which is the same
|
|
169
|
+
// silence wearing a different hat. The marker fits INSIDE the cap, so the bound is real.
|
|
170
|
+
summary: summary.length > SUMMARY_CAP
|
|
171
|
+
? `${summary.slice(0, SUMMARY_CAP - 13)} …[truncated]`
|
|
172
|
+
: summary,
|
|
173
|
+
topics,
|
|
174
|
+
rules,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const MINE_INSTRUCTION = 'You read one archived chapter of a coding-agent conversation and write the STANDING ORDERS that '
|
|
178
|
+
+ 'would keep this project focused. Reply with ONLY a JSON object: '
|
|
179
|
+
+ '{"rules": [{"text": "<one imperative, generalizable instruction>", "category": "<short slug>", "basis": "<stated|inferred>", "quote": "<a short verbatim phrase from the material, or empty>"}]}. '
|
|
180
|
+
+ 'Infer what the material implies even when nobody spelled it out — a convention, an invariant, a trap '
|
|
181
|
+
+ 'that bit this work and will bite again — but each rule must GENERALIZE beyond this chapter: a '
|
|
182
|
+
+ 'one-off task detail is not a rule. `basis` is stated when a human sentence in the material backs it, '
|
|
183
|
+
+ 'inferred when you judged it. At most 3; send {"rules":[]} when nothing durable applies. Do not '
|
|
184
|
+
+ 'restate the chapter\u2019s contents.';
|
|
185
|
+
/** The rules-only prompt for a dedicated mining model (§7.5, `rulesModel`). */
|
|
186
|
+
export function buildMineInput(slice, bodyHead) {
|
|
187
|
+
const parts = [MINE_INSTRUCTION, ''];
|
|
188
|
+
for (const [i, r] of slice.requests.slice(-4).entries())
|
|
189
|
+
parts.push(`REQUEST ${i + 1}: ${cap(r, PART_CAP)}`);
|
|
190
|
+
if (slice.lastAssistant !== undefined)
|
|
191
|
+
parts.push(`LAST ASSISTANT: ${cap(slice.lastAssistant, PART_CAP)}`);
|
|
192
|
+
parts.push('', 'CHAPTER BODY:', cap(bodyHead, 6000));
|
|
193
|
+
let text = parts.join('\n');
|
|
194
|
+
if (text.length > TOTAL_IN_CAP)
|
|
195
|
+
text = text.slice(0, TOTAL_IN_CAP) + '\n…[input truncated]';
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Validate a mining-only answer. Bounds are the same as the annotation path's
|
|
200
|
+
* `rules` field so the two routes cannot produce differently-shaped candidates;
|
|
201
|
+
* an unknown `basis` falls back to the conservative `inferred`.
|
|
202
|
+
*/
|
|
203
|
+
export function parseMinedRules(text) {
|
|
204
|
+
const m = /\{[\s\S]*\}/.exec(text);
|
|
205
|
+
if (m === null)
|
|
206
|
+
return [];
|
|
207
|
+
let obj;
|
|
208
|
+
try {
|
|
209
|
+
obj = JSON.parse(m[0]);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const o = obj;
|
|
215
|
+
if (typeof o !== 'object' || o === null || !Array.isArray(o.rules))
|
|
216
|
+
return [];
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const raw of o.rules) {
|
|
219
|
+
if (typeof raw !== 'object' || raw === null)
|
|
220
|
+
continue;
|
|
221
|
+
const r = raw;
|
|
222
|
+
if (typeof r.text !== 'string')
|
|
223
|
+
continue;
|
|
224
|
+
const textOk = r.text.trim();
|
|
225
|
+
if (textOk.length < RULE_TEXT_MIN || textOk.length > RULE_TEXT_CAP * 3)
|
|
226
|
+
continue;
|
|
227
|
+
const quote = typeof r.quote === 'string' ? r.quote.trim().slice(0, RULE_QUOTE_CAP) : '';
|
|
228
|
+
const category = typeof r.category === 'string' ? r.category.trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 24) : '';
|
|
229
|
+
out.push({ text: textOk.slice(0, RULE_TEXT_CAP), category, quote, basis: r.basis === 'stated' ? 'stated' : 'inferred' });
|
|
230
|
+
if (out.length >= RULE_MAX)
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Merge an enrichment result into the chapter's model-owned fields.
|
|
237
|
+
* - result === null (ladder bottomed out) → PURE no-op, values + provenance
|
|
238
|
+
* untouched (deterministic P1 values survive forever).
|
|
239
|
+
* - same values AND same newest model author → no-op (idempotent re-run).
|
|
240
|
+
* - otherwise: fields update; each provenance chain grows newest-first, one
|
|
241
|
+
* entry per model (re-annotation by a newer model never erases history).
|
|
242
|
+
* The verbatim BODY is not in this module's universe — callers may only
|
|
243
|
+
* rewrite frontmatter fields (record §6.2), body hash-verified elsewhere.
|
|
244
|
+
*/
|
|
245
|
+
export function applyEnrich(current, result, model, at) {
|
|
246
|
+
if (result === null)
|
|
247
|
+
return { values: current, changed: false };
|
|
248
|
+
const sameValues = current.title === result.title
|
|
249
|
+
&& current.summary === result.summary
|
|
250
|
+
&& current.topics.join('\u0000') === result.topics.join('\u0000');
|
|
251
|
+
const authoredBySame = ['title', 'summary', 'topics'].every((f) => {
|
|
252
|
+
const top = current.generated?.[f]?.[0];
|
|
253
|
+
return top?.by === 'model' && top.model === model;
|
|
254
|
+
});
|
|
255
|
+
if (sameValues && authoredBySame)
|
|
256
|
+
return { values: current, changed: false };
|
|
257
|
+
const gen = { title: [], summary: [], topics: [] };
|
|
258
|
+
// pluginVersion rides with the model name (#61): a row must say WHICH build annotated it, not just
|
|
259
|
+
// when, or attribution is mtime-archaeology for the next person.
|
|
260
|
+
const mark = { by: 'model', model, at, pluginVersion: PLUGIN_VERSION };
|
|
261
|
+
for (const f of ['title', 'summary', 'topics']) {
|
|
262
|
+
const chain = current.generated?.[f] ?? [];
|
|
263
|
+
gen[f] = [mark, ...chain.filter((c) => !(c.by === 'model' && c.model === model))];
|
|
264
|
+
}
|
|
265
|
+
return { values: { ...current, title: result.title, summary: result.summary, topics: result.topics, generated: gen }, changed: true };
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=enrich.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich.js","sourceRoot":"","sources":["../src/enrich.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AA0D7C,MAAM,SAAS,GAAG,GAAG,CAAA;AACrB,8FAA8F;AAC9F,kGAAkG;AAClG,iGAAiG;AACjG,kEAAkE;AAClE,MAAM,WAAW,GAAG,IAAI,CAAA;AACxB,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,+EAA+E;AAC/E,mDAAmD;AACnD,MAAM,QAAQ,GAAG,CAAC,CAAA;AAClB,MAAM,aAAa,GAAG,EAAE,CAAA;AACxB,MAAM,aAAa,GAAG,GAAG,CAAA;AACzB,MAAM,cAAc,GAAG,GAAG,CAAA;AAC1B,MAAM,QAAQ,GAAG,IAAI,CAAA;AACrB,MAAM,YAAY,GAAG,IAAI,CAAA;AACzB,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAE5B,oFAAoF;AACpF,sDAAsD;AACtD,EAAE;AACF,8EAA8E;AAC9E,uFAAuF;AACvF,yFAAyF;AACzF,mFAAmF;AACnF,uFAAuF;AACvF,6EAA6E;AAC7E,2DAA2D;AAC3D,EAAE;AACF,sFAAsF;AACtF,6BAA6B;AAC7B,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAA;AAE7C,MAAM,WAAW,GACf,kFAAkF;MAChF,6EAA6E;MAC7E,uDAAuD;MACvD,kOAAkO;MAClO,+LAA+L;MAC/L,0GAA0G;MAC1G,sGAAsG;MACtG,wGAAwG;MACxG,qGAAqG;MACrG,8GAA8G;MAC9G,qHAAqH,CAAA;AAEzH,sEAAsE;AACtE,SAAS,GAAG,CAAC,IAAY,EAAE,CAAS;IAClC,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;IACvC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,CAAC,CAAA;IAC3B,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,eAAe,CAAA;AACxC,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,IAAI;SACR,OAAO,CAAC,+BAA+B,EAAE,oBAAoB,CAAC;SAC9D,OAAO,CAAC,qCAAqC,EAAE,qBAAqB,CAAC,CAAA;AAC1E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAuB,EAAE,KAAkB,EAAE,QAAgB,EAAE,gBAAyB;IACvH,MAAM,KAAK,GAAa,CAAC,WAAW,EAAE,EAAE,EAAE,kBAAkB,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,CAAA;IACzF,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC7G,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC1G,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC5G,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,yBAAyB,EAAE,GAAG,CAAC,QAAQ,EAAE,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAA;IAC9E,2EAA2E;IAC3E,8EAA8E;IAC9E,4EAA4E;IAC5E,oEAAoE;IACpE,IAAI,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,wCAAwC,EAAE,GAAG,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAA;IACtF,CAAC;IACD,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,IAAI,CAAC,MAAM,GAAG,YAAY;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,sBAAsB,CAAA;IAC3F,OAAO,IAAI,CAAA;AACb,CAAC;AAED,iFAAiF;AACjF,SAAS,UAAU,CAAC,GAAY;IAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAChG,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,SAAS;QAAE,OAAO,IAAI,CAAA;IACvD,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC3B,IAAI,GAAY,CAAA;IAChB,IAAI,CAAC;QAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;IACpD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IACxD,MAAM,CAAC,GAAG,GAA8B,CAAA;IACxC,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACzG,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3D,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QACvB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACrD,IAAI,MAAM,CAAC,MAAM,IAAI,SAAS;YAAE,MAAK;IACvC,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACpC,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,KAAK,GAAmB,EAAE,CAAA;IAChC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YAC1B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;gBAAE,SAAQ;YACrD,MAAM,CAAC,GAAG,GAA8B,CAAA;YACxC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;gBAAE,SAAQ;YACvE,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;YAC1B,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAA;YAC5D,IAAI,IAAI,CAAC,MAAM,GAAG,aAAa,IAAI,IAAI,CAAC,MAAM,GAAG,aAAa,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAQ;YAClG,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;gBAC7C,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;gBACzE,CAAC,CAAC,EAAE,CAAA;YACN,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC,CAAA;YACnG,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;gBAAE,MAAK;QACrC,CAAC;IACH,CAAC;IACD,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;QAChC,wFAAwF;QACxF,gGAAgG;QAChG,yFAAyF;QACzF,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,WAAW;YACnC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,GAAG,EAAE,CAAC,eAAe;YACtD,CAAC,CAAC,OAAO;QACX,MAAM;QACN,KAAK;KACN,CAAA;AACH,CAAC;AAED,MAAM,gBAAgB,GACpB,kGAAkG;MAChG,kEAAkE;MAClE,oMAAoM;MACpM,uGAAuG;MACvG,gGAAgG;MAChG,uGAAuG;MACvG,iGAAiG;MACjG,sCAAsC,CAAA;AAE1C,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,QAAgB;IACjE,MAAM,KAAK,GAAa,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC5G,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC1G,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAA;IACpD,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,IAAI,CAAC,MAAM,GAAG,YAAY;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,sBAAsB,CAAA;IAC3F,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IACzB,IAAI,GAAY,CAAA;IAChB,IAAI,CAAC;QAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAA;IAAC,CAAC;IAClD,MAAM,CAAC,GAAG,GAA8B,CAAA;IACxC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAC7E,MAAM,GAAG,GAAmB,EAAE,CAAA;IAC9B,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;YAAE,SAAQ;QACrD,MAAM,CAAC,GAAG,GAA8B,CAAA;QACxC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAQ;QACxC,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QAC5B,IAAI,MAAM,CAAC,MAAM,GAAG,aAAa,IAAI,MAAM,CAAC,MAAM,GAAG,aAAa,GAAG,CAAC;YAAE,SAAQ;QAChF,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9H,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAA;QACxH,IAAI,GAAG,CAAC,MAAM,IAAI,QAAQ;YAAE,MAAK;IACnC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AASD;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,OAAsB,EACtB,MAA2B,EAC3B,KAAa,EACb,EAAU;IAEV,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK;WAC5C,OAAO,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO;WAClC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACnE,MAAM,cAAc,GAAI,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3E,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACvC,OAAO,GAAG,EAAE,EAAE,KAAK,OAAO,IAAK,GAA0B,CAAC,KAAK,KAAK,KAAK,CAAA;IAC3E,CAAC,CAAC,CAAA;IACF,IAAI,UAAU,IAAI,cAAc;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAC5E,MAAM,GAAG,GAAwB,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;IACvE,mGAAmG;IACnG,iEAAiE;IACjE,MAAM,IAAI,GAAkB,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,CAAA;IACrF,KAAK,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,IAAK,CAAwB,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CAAA;IAC3G,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AACvI,CAAC"}
|
package/lib/gitops.d.ts
ADDED
|
@@ -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;
|