@mjasnikovs/pi-task 0.40.50 → 0.42.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/README.md +7 -4
- package/dist/config/config.d.ts +61 -18
- package/dist/config/config.js +50 -6
- package/dist/config/register.js +18 -5
- package/dist/shared/content-tokens.d.ts +16 -0
- package/dist/shared/content-tokens.js +137 -0
- package/dist/task/accept-debt.d.ts +31 -1
- package/dist/task/accept-debt.js +49 -3
- package/dist/task/artifact-closure.js +7 -1
- package/dist/task/auto-io.d.ts +66 -7
- package/dist/task/auto-io.js +152 -47
- package/dist/task/auto-orchestrator.d.ts +22 -19
- package/dist/task/auto-orchestrator.js +184 -112
- package/dist/task/child-runner.d.ts +18 -0
- package/dist/task/child-runner.js +7 -8
- package/dist/task/command-run.d.ts +4 -0
- package/dist/task/command-run.js +4 -2
- package/dist/task/constraint-policy.d.ts +52 -0
- package/dist/task/constraint-policy.js +66 -0
- package/dist/task/context-silence.d.ts +5 -4
- package/dist/task/context-silence.js +13 -5
- package/dist/task/coverage-loop.d.ts +7 -4
- package/dist/task/coverage-loop.js +16 -138
- package/dist/task/debug-log.d.ts +25 -2
- package/dist/task/debug-log.js +64 -10
- package/dist/task/decompose-fidelity.d.ts +17 -4
- package/dist/task/decompose-fidelity.js +95 -55
- package/dist/task/decompose-granularity.d.ts +7 -7
- package/dist/task/decompose-granularity.js +8 -8
- package/dist/task/env-notes.d.ts +65 -19
- package/dist/task/env-notes.js +152 -50
- package/dist/task/external-context.js +25 -5
- package/dist/task/fix-context.d.ts +39 -0
- package/dist/task/fix-context.js +58 -0
- package/dist/task/gate-deps.d.ts +49 -0
- package/dist/task/gate-deps.js +201 -39
- package/dist/task/gate-evidence.d.ts +51 -0
- package/dist/task/gate-evidence.js +90 -0
- package/dist/task/gate-resolution.d.ts +119 -0
- package/dist/task/gate-resolution.js +120 -0
- package/dist/task/git-state-guard.js +3 -26
- package/dist/task/handoff.d.ts +35 -0
- package/dist/task/handoff.js +100 -0
- package/dist/task/health-baseline.d.ts +87 -0
- package/dist/task/health-baseline.js +158 -0
- package/dist/task/health-repair.d.ts +68 -0
- package/dist/task/health-repair.js +124 -0
- package/dist/task/ledger.d.ts +10 -0
- package/dist/task/ledger.js +22 -11
- package/dist/task/lint-fix.d.ts +11 -0
- package/dist/task/lint-fix.js +14 -8
- package/dist/task/loop-detector.d.ts +52 -3
- package/dist/task/loop-detector.js +81 -5
- package/dist/task/mentions.d.ts +17 -0
- package/dist/task/mentions.js +86 -0
- package/dist/task/orchestrator.d.ts +53 -9
- package/dist/task/orchestrator.js +104 -28
- package/dist/task/orientation.d.ts +135 -29
- package/dist/task/orientation.js +259 -67
- package/dist/task/owned-freeze-reassign.d.ts +7 -3
- package/dist/task/owned-freeze-reassign.js +13 -5
- package/dist/task/parsers.d.ts +6 -1
- package/dist/task/parsers.js +21 -4
- package/dist/task/phases.d.ts +1 -1
- package/dist/task/phases.js +125 -153
- package/dist/task/plan-orchestrator.js +5 -6
- package/dist/task/plan-rounds.d.ts +1 -0
- package/dist/task/plan-rounds.js +8 -1
- package/dist/task/prohibition-probe.d.ts +6 -1
- package/dist/task/prohibition-probe.js +26 -6
- package/dist/task/prompts.js +13 -2
- package/dist/task/qa-transcript.d.ts +17 -0
- package/dist/task/qa-transcript.js +28 -0
- package/dist/task/question-source.d.ts +16 -2
- package/dist/task/question-source.js +21 -8
- package/dist/task/repo-health-check.d.ts +27 -0
- package/dist/task/repo-health-check.js +39 -5
- package/dist/task/requirements.d.ts +67 -13
- package/dist/task/requirements.js +129 -67
- package/dist/task/research-worker.d.ts +43 -2
- package/dist/task/research-worker.js +103 -8
- package/dist/task/run-bracket.d.ts +9 -4
- package/dist/task/run-bracket.js +9 -0
- package/dist/task/run-context.d.ts +152 -0
- package/dist/task/run-context.js +277 -0
- package/dist/task/spec-doc.d.ts +78 -0
- package/dist/task/spec-doc.js +205 -0
- package/dist/task/spec-model.d.ts +70 -0
- package/dist/task/spec-model.js +139 -0
- package/dist/task/stall-detector.d.ts +1 -1
- package/dist/task/stall-detector.js +9 -5
- package/dist/task/state-dir.d.ts +29 -0
- package/dist/task/state-dir.js +80 -0
- package/dist/task/suppression-probe.d.ts +67 -0
- package/dist/task/suppression-probe.js +78 -0
- package/dist/task/task-gates.d.ts +52 -58
- package/dist/task/task-gates.js +161 -199
- package/dist/task/task-io.d.ts +10 -0
- package/dist/task/task-io.js +69 -18
- package/dist/task/task-parsers.js +6 -6
- package/dist/task/task-types.d.ts +7 -0
- package/dist/task/timings.d.ts +11 -0
- package/dist/task/timings.js +21 -0
- package/dist/task/tree-hash.d.ts +8 -0
- package/dist/task/tree-hash.js +55 -0
- package/dist/task/verify-reconcile.d.ts +4 -3
- package/dist/task/verify-reconcile.js +7 -8
- package/dist/task/verify-resolution.d.ts +32 -6
- package/dist/task/verify-resolution.js +78 -15
- package/dist/task/verify-work.d.ts +105 -35
- package/dist/task/verify-work.js +232 -60
- package/dist/task/yolo.d.ts +0 -13
- package/dist/task/yolo.js +9 -21
- package/dist/workers/pi-worker-core.d.ts +20 -0
- package/dist/workers/pi-worker-core.js +16 -11
- package/dist/workers/pi-worker-docs.d.ts +6 -5
- package/dist/workers/pi-worker-docs.js +8 -7
- package/dist/workers/pi-worker-fetch.d.ts +3 -2
- package/dist/workers/pi-worker-fetch.js +5 -4
- package/dist/workers/pi-worker-search.js +5 -4
- package/dist/workers/research-cache.d.ts +23 -1
- package/dist/workers/research-cache.js +63 -3
- package/dist/workers/shared.d.ts +18 -0
- package/dist/workers/shared.js +38 -22
- package/package.json +1 -1
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
* Mechanism (contracts.ts pattern, applied to decompose itself): the decompose
|
|
13
13
|
* prompt asks each task line to cite its origin as a trailing
|
|
14
14
|
* `[source: "<verbatim spec line>"]`. The host then:
|
|
15
|
-
* 1. GROUNDS the quote — a citation that is
|
|
16
|
-
*
|
|
15
|
+
* 1. GROUNDS the quote in a BLOCK of the parsed spec doc — a citation that is
|
|
16
|
+
* not a (whitespace/case-normalised) substring is fabricated and is
|
|
17
|
+
* stripped, never trusted — and remembers which block it landed in;
|
|
17
18
|
* 2. deterministically detects DROPPED ADDITIVE FRAGMENTS: the `+`-joined
|
|
18
|
-
* trailing constraints of the cited
|
|
19
|
+
* trailing constraints of the cited block ("… + tests") whose words are
|
|
19
20
|
* absent from the title;
|
|
20
21
|
* 3. RE-ATTACHES the missing fragments to the title verbatim.
|
|
21
22
|
*
|
|
@@ -30,41 +31,9 @@
|
|
|
30
31
|
* presence is exact word membership (with a singular/plural `s` allowance).
|
|
31
32
|
*/
|
|
32
33
|
import { normalise } from './contracts.js';
|
|
34
|
+
import { demark, groundIn, parseSpecDoc, specPlain } from './spec-doc.js';
|
|
33
35
|
/** One trailing `[source: "…"]` clause, anchored so it is the WHOLE remainder. */
|
|
34
36
|
const SOURCE_RE = /^\[source:\s*"([\s\S]*)"\s*\]$/i;
|
|
35
|
-
/**
|
|
36
|
-
* Markdown MARKUP dropped before grounding — emphasis runs, list and heading
|
|
37
|
-
* markers, table pipes and CODE BACKTICKS. Not content: no word, number or
|
|
38
|
-
* punctuation inside a sentence is touched, so this cannot make an invented
|
|
39
|
-
* quote match.
|
|
40
|
-
*
|
|
41
|
-
* WHY. A model copies a spec line as it READS, and what it reads is rendered:
|
|
42
|
-
* `2. **Auth** — sessions, login/logout/me, guards + tests.` comes back as
|
|
43
|
-
* `Auth — sessions, login/logout/me, guards + tests.` That is a verbatim copy of
|
|
44
|
-
* the line's TEXT, and the exact-substring test called it fabricated and threw
|
|
45
|
-
* it away — including, as here, the "+ tests" line that is this module's own
|
|
46
|
-
* worked example.
|
|
47
|
-
*
|
|
48
|
-
* BACKTICKS ARE THE SAME CLASS and were the larger half. A code span renders as
|
|
49
|
-
* bare text, so `3. **Invites** — create/validate/redeem, \`/join/:token\` page.`
|
|
50
|
-
* comes back as `Invites — create/validate/redeem, /join/:token page.` Screening
|
|
51
|
-
* every spec line in its RENDERED form is what makes those quotes match at all.
|
|
52
|
-
*
|
|
53
|
-
* The two directions this has to hold in, both run:
|
|
54
|
-
* FLOOR a real spec line with ONE content word altered must NOT be grounded —
|
|
55
|
-
* changing `sessions` to `tokens`, or `redeem` to `revoke`, drops it.
|
|
56
|
-
* CEILING a real spec line quoted without its markup MUST be grounded — both the
|
|
57
|
-
* `2. **Auth** —` numbering-and-bold case and the backticked
|
|
58
|
-
* `` `/join/:token` `` case still match.
|
|
59
|
-
*/
|
|
60
|
-
function demark(s) {
|
|
61
|
-
return s
|
|
62
|
-
.replace(/\*\*|__/g, '')
|
|
63
|
-
.replace(/^\s*(?:[-*+]|\d+\.)\s+/gm, '')
|
|
64
|
-
.replace(/^#+\s*/gm, '')
|
|
65
|
-
.replace(/`/g, '')
|
|
66
|
-
.replace(/\|/g, ' ');
|
|
67
|
-
}
|
|
68
37
|
/**
|
|
69
38
|
* Undo the backslash-escaping a model applies to a quote it is putting INSIDE a
|
|
70
39
|
* double-quoted clause. `[source: "… \`import { sql } from \\"bun\\"\` gotcha …"]`
|
|
@@ -99,7 +68,8 @@ function unescapeQuotes(s) {
|
|
|
99
68
|
* — exactly like keepGroundedContracts rejects a paraphrased quote.
|
|
100
69
|
*/
|
|
101
70
|
export function extractTitleSource(title, sourceDoc) {
|
|
102
|
-
const
|
|
71
|
+
const doc = typeof sourceDoc === 'string' ? parseSpecDoc(sourceDoc) : sourceDoc;
|
|
72
|
+
const flat = normalise(specPlain(doc));
|
|
103
73
|
let base = title.trim();
|
|
104
74
|
const sources = [];
|
|
105
75
|
for (;;) {
|
|
@@ -111,8 +81,16 @@ export function extractTitleSource(title, sourceDoc) {
|
|
|
111
81
|
break;
|
|
112
82
|
const quote = m[1].trim();
|
|
113
83
|
base = base.slice(0, at).trim();
|
|
114
|
-
if (quote.length
|
|
115
|
-
|
|
84
|
+
if (quote.length === 0)
|
|
85
|
+
continue;
|
|
86
|
+
const unescaped = unescapeQuotes(quote);
|
|
87
|
+
const block = groundIn(doc, unescaped);
|
|
88
|
+
if (block !== null)
|
|
89
|
+
sources.unshift({ quote, block });
|
|
90
|
+
// A citation that spans two blocks is still a faithful copy of the doc, so
|
|
91
|
+
// it stays grounded; it just has no single block to restore fragments from.
|
|
92
|
+
else if (flat.includes(normalise(demark(unescaped))))
|
|
93
|
+
sources.unshift({ quote, block: null });
|
|
116
94
|
}
|
|
117
95
|
return { base, sources };
|
|
118
96
|
}
|
|
@@ -132,7 +110,57 @@ function fragmentPresent(fragment, titleWords) {
|
|
|
132
110
|
});
|
|
133
111
|
}
|
|
134
112
|
/**
|
|
135
|
-
*
|
|
113
|
+
* An additive suffix is a PROSE construct. Inside a table row a `+` joins one
|
|
114
|
+
* cell's own words while the row's other cells are unrelated columns; inside a
|
|
115
|
+
* code fence it is code. Live, those two shapes restored `password) | public |`
|
|
116
|
+
* (a router table's path, guard and its pipes) and `check` (the tail of a DDL
|
|
117
|
+
* `check (…)` constraint) into task titles as spec obligations.
|
|
118
|
+
*/
|
|
119
|
+
const RESTORABLE_KINDS = new Set(['para', 'list-item']);
|
|
120
|
+
const BRACKET_PAIRS = [
|
|
121
|
+
['(', ')'],
|
|
122
|
+
['[', ']'],
|
|
123
|
+
['{', '}']
|
|
124
|
+
];
|
|
125
|
+
function occurrences(s, ch) {
|
|
126
|
+
let n = 0;
|
|
127
|
+
for (const c of s)
|
|
128
|
+
if (c === ch)
|
|
129
|
+
n++;
|
|
130
|
+
return n;
|
|
131
|
+
}
|
|
132
|
+
/** A fragment cut mid-expression: the cut is the defect, not the content. */
|
|
133
|
+
function isBalanced(s) {
|
|
134
|
+
return BRACKET_PAIRS.every(([open, close]) => occurrences(s, open) === occurrences(s, close));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Split on the separators that sit at bracket depth ZERO. A `+` or a comma inside
|
|
138
|
+
* a parenthetical belongs to the parenthetical — splitting there is what cut
|
|
139
|
+
* `(unused + not expired)` into a fragment with a dangling `)`.
|
|
140
|
+
*
|
|
141
|
+
* A stray closer floors the depth at zero rather than driving it negative, so a
|
|
142
|
+
* line whose brackets are already unbalanced still splits on its later separators.
|
|
143
|
+
*/
|
|
144
|
+
function splitAtDepth(s, separators) {
|
|
145
|
+
const out = [];
|
|
146
|
+
let depth = 0;
|
|
147
|
+
let start = 0;
|
|
148
|
+
for (let i = 0; i < s.length; i++) {
|
|
149
|
+
const c = s[i];
|
|
150
|
+
if (BRACKET_PAIRS.some(([open]) => open === c))
|
|
151
|
+
depth++;
|
|
152
|
+
else if (BRACKET_PAIRS.some(([, close]) => close === c))
|
|
153
|
+
depth = Math.max(0, depth - 1);
|
|
154
|
+
else if (depth === 0 && separators.includes(c)) {
|
|
155
|
+
out.push(s.slice(start, i));
|
|
156
|
+
start = i + 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
out.push(s.slice(start));
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The `+`-joined trailing constraint fragments of a spec block whose words are
|
|
136
164
|
* absent from `title`. "2. **Auth** — sessions, login/logout/me, guards + tests."
|
|
137
165
|
* yields the fragment "tests"; a title that never mentions tests gets it back.
|
|
138
166
|
* Fragments before the first `+` are the task's body — a title paraphrases those
|
|
@@ -140,31 +168,40 @@ function fragmentPresent(fragment, titleWords) {
|
|
|
140
168
|
* ("+ Tailwind v4 tokens, nav, router" is three constraints), so a title missing
|
|
141
169
|
* one of them gets ONLY that one restored, not the whole phrase (measured live:
|
|
142
170
|
* whole-phrase restoration re-attached text the title already carried).
|
|
171
|
+
*
|
|
172
|
+
* A bare string is read as prose — the shape a caller that has no document has.
|
|
143
173
|
*/
|
|
144
|
-
export function findDroppedPlusFragments(
|
|
145
|
-
const
|
|
174
|
+
export function findDroppedPlusFragments(source, title) {
|
|
175
|
+
const block = typeof source === 'string' ?
|
|
176
|
+
{ kind: 'para', text: source, plain: demark(source), line: 0 }
|
|
177
|
+
: source;
|
|
178
|
+
if (!RESTORABLE_KINDS.has(block.kind))
|
|
179
|
+
return [];
|
|
180
|
+
const parts = splitAtDepth(block.plain, '+');
|
|
146
181
|
if (parts.length < 2)
|
|
147
182
|
return [];
|
|
148
183
|
const titleWords = new Set(words(title));
|
|
149
184
|
const missing = [];
|
|
150
185
|
for (const raw of parts.slice(1)) {
|
|
151
|
-
for (const sub of raw
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
const fragment = sub
|
|
155
|
-
.replace(/[*_`]/g, '')
|
|
156
|
-
.replace(/[.,;:!?)\]]+\s*$/, '')
|
|
157
|
-
.trim();
|
|
158
|
-
if (fragment.length === 0)
|
|
159
|
-
continue;
|
|
186
|
+
for (const sub of splitAtDepth(raw, ',')) {
|
|
187
|
+
// Trailing sentence punctuation is the line's, not the fragment's;
|
|
188
|
+
// brackets are left alone because they are what the balance test reads.
|
|
189
|
+
const fragment = sub.replace(/[.,;:!?]+\s*$/, '').trim();
|
|
160
190
|
if (words(fragment).length === 0)
|
|
161
191
|
continue;
|
|
192
|
+
if (!isBalanced(fragment))
|
|
193
|
+
continue;
|
|
162
194
|
if (!fragmentPresent(fragment, titleWords))
|
|
163
195
|
missing.push(fragment);
|
|
164
196
|
}
|
|
165
197
|
}
|
|
166
198
|
return missing;
|
|
167
199
|
}
|
|
200
|
+
/** Restored fragments as a DELIMITED list. A bare `join(', ')` is ambiguous the
|
|
201
|
+
* moment a fragment contains a comma of its own — and they routinely do. */
|
|
202
|
+
export function renderFragments(fragments) {
|
|
203
|
+
return fragments.map(f => `"${f}"`).join('; ');
|
|
204
|
+
}
|
|
168
205
|
/**
|
|
169
206
|
* Reconcile decompose output against the source doc: ground each citation, strip
|
|
170
207
|
* the clause (its job ends here), and re-attach any dropped `+`-fragments to the
|
|
@@ -173,11 +210,12 @@ export function findDroppedPlusFragments(sourceLine, title) {
|
|
|
173
210
|
* model that never cites degrades to exactly the old behavior.
|
|
174
211
|
*/
|
|
175
212
|
export function reconcileTitleSources(titles, sourceDoc) {
|
|
213
|
+
const doc = parseSpecDoc(sourceDoc);
|
|
176
214
|
const out = [];
|
|
177
215
|
const restored = [];
|
|
178
216
|
let sourced = 0;
|
|
179
217
|
for (let i = 0; i < titles.length; i++) {
|
|
180
|
-
const { base, sources } = extractTitleSource(titles[i],
|
|
218
|
+
const { base, sources } = extractTitleSource(titles[i], doc);
|
|
181
219
|
if (sources.length === 0) {
|
|
182
220
|
out.push(base);
|
|
183
221
|
continue;
|
|
@@ -190,7 +228,9 @@ export function reconcileTitleSources(titles, sourceDoc) {
|
|
|
190
228
|
const seen = new Set();
|
|
191
229
|
const missing = [];
|
|
192
230
|
for (const src of sources) {
|
|
193
|
-
|
|
231
|
+
if (src.block === null)
|
|
232
|
+
continue;
|
|
233
|
+
for (const f of findDroppedPlusFragments(src.block, base)) {
|
|
194
234
|
const k = f.toLowerCase();
|
|
195
235
|
if (seen.has(k))
|
|
196
236
|
continue;
|
|
@@ -202,8 +242,8 @@ export function reconcileTitleSources(titles, sourceDoc) {
|
|
|
202
242
|
out.push(base);
|
|
203
243
|
continue;
|
|
204
244
|
}
|
|
205
|
-
restored.push({ index: i, fragments: missing, sources });
|
|
206
|
-
out.push(`${base} — MUST also cover (restored from its spec line): ${missing
|
|
245
|
+
restored.push({ index: i, fragments: missing, sources: sources.map(s => s.quote) });
|
|
246
|
+
out.push(`${base} — MUST also cover (restored from its spec line): ${renderFragments(missing)}`);
|
|
207
247
|
}
|
|
208
248
|
return { titles: out, restored, sourced };
|
|
209
249
|
}
|
|
@@ -70,6 +70,8 @@ export declare function isTooCoarse(titles: number, floor: number): boolean;
|
|
|
70
70
|
export declare const MIN_REQUIREMENTS_FOR_PLAN_SHAPE = 5;
|
|
71
71
|
/** Does this feature have enough distinct deliverables for granularity to matter? */
|
|
72
72
|
export declare function planShapeIsHostsToAnswer(ownable: number): boolean;
|
|
73
|
+
/** The clarify dialog's name for this fork — see `QuestionSource.settle`. */
|
|
74
|
+
export declare const PLAN_SHAPE_TOPIC = "plan-shape";
|
|
73
75
|
/**
|
|
74
76
|
* Does this clarify question decide how finely the feature is CUT into tasks?
|
|
75
77
|
*
|
|
@@ -78,13 +80,11 @@ export declare function planShapeIsHostsToAnswer(ownable: number): boolean;
|
|
|
78
80
|
* over-eager classifier would replace a real user decision with the host's.
|
|
79
81
|
* Matched against the plain-text question.
|
|
80
82
|
*
|
|
81
|
-
* BOTH halves must hold — a breakdown phrase AND a plan-unit noun
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* phrased "follow the milestones as-is, or split more granularly?" does not —
|
|
87
|
-
* the breakdown half matches, the plural unit does not.
|
|
83
|
+
* BOTH halves must hold — a breakdown phrase AND a plan-unit noun, singular or
|
|
84
|
+
* plural. The plural used to be excluded, which left "follow the milestones
|
|
85
|
+
* as-is, or split more granularly?" unmatched: the same fork, redrawn in the
|
|
86
|
+
* plural, went to the user (or the triage) after the host had already settled it.
|
|
87
|
+
* Number is not a signal about what the question decides.
|
|
88
88
|
*/
|
|
89
89
|
export declare function isPlanShapeQuestion(question: string): boolean;
|
|
90
90
|
/**
|
|
@@ -78,6 +78,8 @@ export const MIN_REQUIREMENTS_FOR_PLAN_SHAPE = 5;
|
|
|
78
78
|
export function planShapeIsHostsToAnswer(ownable) {
|
|
79
79
|
return ownable >= MIN_REQUIREMENTS_FOR_PLAN_SHAPE;
|
|
80
80
|
}
|
|
81
|
+
/** The clarify dialog's name for this fork — see `QuestionSource.settle`. */
|
|
82
|
+
export const PLAN_SHAPE_TOPIC = 'plan-shape';
|
|
81
83
|
/**
|
|
82
84
|
* Does this clarify question decide how finely the feature is CUT into tasks?
|
|
83
85
|
*
|
|
@@ -86,13 +88,11 @@ export function planShapeIsHostsToAnswer(ownable) {
|
|
|
86
88
|
* over-eager classifier would replace a real user decision with the host's.
|
|
87
89
|
* Matched against the plain-text question.
|
|
88
90
|
*
|
|
89
|
-
* BOTH halves must hold — a breakdown phrase AND a plan-unit noun
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* phrased "follow the milestones as-is, or split more granularly?" does not —
|
|
95
|
-
* the breakdown half matches, the plural unit does not.
|
|
91
|
+
* BOTH halves must hold — a breakdown phrase AND a plan-unit noun, singular or
|
|
92
|
+
* plural. The plural used to be excluded, which left "follow the milestones
|
|
93
|
+
* as-is, or split more granularly?" unmatched: the same fork, redrawn in the
|
|
94
|
+
* plural, went to the user (or the triage) after the host had already settled it.
|
|
95
|
+
* Number is not a signal about what the question decides.
|
|
96
96
|
*/
|
|
97
97
|
export function isPlanShapeQuestion(question) {
|
|
98
98
|
const q = question.toLowerCase();
|
|
@@ -101,7 +101,7 @@ export function isPlanShapeQuestion(question) {
|
|
|
101
101
|
if (!aboutBreakdown)
|
|
102
102
|
return false;
|
|
103
103
|
// …and offer a coarse/fine choice over the plan's own units.
|
|
104
|
-
return /\b(
|
|
104
|
+
return /\b(milestones?|sections?|steps?|phases?|tasks?)\b/.test(q);
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* BELT — the host's own answer to that fork, recorded in the clarify transcript in
|
package/dist/task/env-notes.d.ts
CHANGED
|
@@ -1,48 +1,94 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* One recorded fact, the origin task that established it, the run it belongs to,
|
|
3
|
+
* and the SUBJECT it is about.
|
|
4
|
+
*
|
|
5
|
+
* The subject is the ledger key, and that is the whole point: twenty children
|
|
6
|
+
* re-measuring one database's reachability wrote twenty slots of one fact, and the
|
|
7
|
+
* oldest, least true of them was as loud as the newest. Under a subject key the
|
|
8
|
+
* latest measurement REPLACES its predecessor, which is also the only retraction
|
|
9
|
+
* mechanism a fact cache has.
|
|
10
|
+
*/
|
|
2
11
|
export interface EnvNote {
|
|
3
12
|
fact: string;
|
|
4
13
|
origin: string;
|
|
14
|
+
/** The run that recorded it; '' for a file written before runs were stamped. */
|
|
15
|
+
runId: string;
|
|
16
|
+
subject: string;
|
|
17
|
+
/** The task that retracted this claim. A resolved note stays as an audit line
|
|
18
|
+
* and is never carried into another child's prompt. */
|
|
19
|
+
resolvedBy?: string;
|
|
5
20
|
}
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* provenance existed still reads — and `buildEnvNotesBlock` renders those as
|
|
10
|
-
* "origin unrecorded" instead of silently claiming an author.
|
|
11
|
-
*/
|
|
21
|
+
/** Stored line: `fact TAB origin TAB runId TAB subject TAB resolvedBy`, where every
|
|
22
|
+
* field after the fact may be absent — a one- or two-field line is what earlier
|
|
23
|
+
* versions wrote, and it still reads. */
|
|
12
24
|
export declare function parseEnvNotes(raw: string): EnvNote[];
|
|
13
25
|
export declare function envNotesFile(cwd: string): string;
|
|
14
26
|
/** The raw stored file ('' when none were recorded yet). Parse with parseEnvNotes. */
|
|
15
27
|
export declare function readEnvNotes(cwd: string): Promise<string>;
|
|
16
28
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
29
|
+
* The subject of a fact the child did not name one for.
|
|
30
|
+
*
|
|
31
|
+
* A fact identifies what it is ABOUT by naming it first, and three rungs cover
|
|
32
|
+
* what an environment fact names: the SERVICE it reached (a URL authority), the
|
|
33
|
+
* COMMAND it ran (the first code span, by its basename so a tool named by path is
|
|
34
|
+
* the same tool), or — for the rest — its leading words.
|
|
35
|
+
*
|
|
36
|
+
* Measured against the 40-note cache a real 21-task run left behind, where 34
|
|
37
|
+
* slots restate two facts: it resolves them to nine subjects, so the twelfth
|
|
38
|
+
* re-measurement of one database replaces the eleventh instead of joining it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deriveSubject(fact: string): string;
|
|
41
|
+
/** One thing a child shared: a fact, or the retraction of one. */
|
|
42
|
+
export interface EmittedNote {
|
|
43
|
+
subject: string;
|
|
44
|
+
fact: string;
|
|
45
|
+
/** The child observed this subject's recorded claim to be no longer true. */
|
|
46
|
+
resolved?: true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pull the facts out of a child's answer text. Three forms are accepted:
|
|
50
|
+
* `ENV-NOTE[<subject>]: <fact>` (what the instruction asks for), the bare
|
|
51
|
+
* `ENV-NOTE: <fact>` whose subject the host derives, and
|
|
52
|
+
* `ENV-NOTE-RESOLVED[<subject>]: <what was observed>`, which retracts a recorded
|
|
53
|
+
* claim. Facts are trimmed, deduped case-insensitively, and dropped when empty or
|
|
54
|
+
* over MAX_NOTE_LENGTH. A `VERDICT:` line and even a near-miss `ENV-NOTES:` line
|
|
20
55
|
* match nothing, so a verdict cannot leak into the fact cache.
|
|
21
56
|
*/
|
|
22
|
-
export declare function extractEnvNotes(text: string):
|
|
57
|
+
export declare function extractEnvNotes(text: string): EmittedNote[];
|
|
23
58
|
/** True when a fact reads like a standing excuse (see EXCUSE_PATTERNS). */
|
|
24
59
|
export declare function isExcuseNote(fact: string): boolean;
|
|
25
60
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
61
|
+
* Store what a child shared, keyed by SUBJECT: a fresh statement about a subject
|
|
62
|
+
* REPLACES the one the cache held, so the newest measurement is the one later
|
|
63
|
+
* children read and a stale fact cannot outlive its correction. Each note is
|
|
64
|
+
* stamped with the `origin` task and the `runId` that recorded it — the run is
|
|
65
|
+
* what the prompt block scopes on, the task is the provenance a reader is told to
|
|
66
|
+
* re-validate against.
|
|
67
|
+
*
|
|
68
|
+
* A retraction stores the observation that closed the subject and marks it
|
|
69
|
+
* RESOLVED, so it stays as an audit line without being carried into another
|
|
70
|
+
* child's prompt.
|
|
32
71
|
*
|
|
33
72
|
* Tabs in an emitted fact are normalised to spaces before storage, which is what
|
|
34
73
|
* keeps the separator unambiguous. The cap holds too — sixty further facts leave
|
|
35
74
|
* MAX_NOTES stored. Failures are swallowed: the cache is a sharpener, never a
|
|
36
75
|
* blocker.
|
|
37
76
|
*/
|
|
38
|
-
export declare function appendEnvNotes(cwd: string, notes:
|
|
77
|
+
export declare function appendEnvNotes(cwd: string, notes: readonly EmittedNote[], origin?: string, runId?: string): Promise<void>;
|
|
39
78
|
/**
|
|
40
79
|
* The prompt block a gate child receives when notes exist. Two things are
|
|
41
80
|
* load-bearing: the no-waiver caveat (facts save re-discovery time but grant no
|
|
42
81
|
* license to prepare/repair) and the trust discipline (a note is second-hand
|
|
43
82
|
* until re-validated; an EXCUSE-CLASS note may not wave off a failure without a
|
|
44
83
|
* live re-check; a grep of a generated artifact is not evidence of absence).
|
|
84
|
+
*
|
|
85
|
+
* THIS RUN leads. A fact measured by a sibling task minutes ago describes the tree
|
|
86
|
+
* the child is standing in; one from a run last week describes a tree that has
|
|
87
|
+
* moved since, and reading the two as one list is how a stale fact gets cited as
|
|
88
|
+
* current. Resolved subjects are dropped from both lists: a retracted claim is
|
|
89
|
+
* exactly the thing that must not be carried forward. `runId` '' (a cache written
|
|
90
|
+
* before runs were stamped) reads as an earlier run.
|
|
45
91
|
*/
|
|
46
|
-
export declare function buildEnvNotesBlock(raw: string): string;
|
|
92
|
+
export declare function buildEnvNotesBlock(raw: string, runId?: string): string;
|
|
47
93
|
/** The emit instruction appended to bash-capable gate-child prompts. */
|
|
48
94
|
export declare const ENV_NOTE_EMIT_INSTRUCTION: string;
|