@mjasnikovs/pi-task 0.40.50 → 0.41.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 +15 -1
- package/dist/task/accept-debt.js +4 -0
- package/dist/task/artifact-closure.js +7 -1
- package/dist/task/auto-io.d.ts +59 -7
- package/dist/task/auto-io.js +140 -47
- package/dist/task/auto-orchestrator.d.ts +22 -19
- package/dist/task/auto-orchestrator.js +126 -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 +199 -38
- 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/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 +39 -57
- package/dist/task/task-gates.js +115 -195
- 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 +28 -5
- package/dist/task/verify-resolution.js +70 -13
- package/dist/task/verify-work.d.ts +98 -35
- package/dist/task/verify-work.js +227 -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
package/dist/task/prompts.js
CHANGED
|
@@ -292,13 +292,16 @@ GOAL
|
|
|
292
292
|
<one paragraph>
|
|
293
293
|
|
|
294
294
|
CONSTRAINTS
|
|
295
|
-
- <bullet>
|
|
295
|
+
- <bullet> [from: Q<n>] ← when the constraint comes from the Q&A answer numbered <n>
|
|
296
|
+
- <bullet> [from: spec] ← when it comes from the refined task itself
|
|
296
297
|
- …
|
|
297
298
|
|
|
298
299
|
ACCEPTANCE
|
|
299
300
|
- <human-readable success criterion>
|
|
300
301
|
- …
|
|
301
302
|
|
|
303
|
+
Every CONSTRAINTS bullet ends with a \`[from: …]\` tag naming where it came from: \`[from: Q3]\` if the Q&A's answer 3 is what states it, or \`[from: spec]\` if the refined task states it. Tag a bullet ONLY when you can point at that source — a constraint you inferred yourself carries no tag, and that is the correct answer, not a gap. The tag decides how much weight the verification gates give the constraint, so a tag that names a source which does not state it turns your inference into an unwaivable rule.
|
|
304
|
+
|
|
302
305
|
VERIFY:
|
|
303
306
|
\`\`\`sh
|
|
304
307
|
<runnable shell command 1>
|
|
@@ -373,6 +376,7 @@ SCOPE RULES (equally critical — do not break these):
|
|
|
373
376
|
- Do NOT introduce new requirements, deliverables, files, scripts, hooks, configs, or acceptance criteria that are not explicitly implied by the refined task or the Q&A.
|
|
374
377
|
- Do NOT broaden scope. If the refined task says "run X and report", do not turn it into "build a toolchain around X with hooks, docs, and reports".
|
|
375
378
|
- CONSTRAINTS from the refined task MUST be preserved in spirit. Do not silently drop or weaken them.
|
|
379
|
+
- Keep each CONSTRAINTS bullet's trailing \`[from: …]\` tag exactly as it stands, and do not add one to a bullet that has none. The tag is the constraint's provenance, and the verification gates weigh it; inventing or dropping one rewrites how binding the constraint is.
|
|
376
380
|
- If the spec below is malformed, empty, or wrapped in a heredoc, reconstruct it from the refined task and Q&A — not from your own invention.
|
|
377
381
|
- Your job is to tighten language, sharpen acceptance criteria, and ensure VERIFY is runnable. Not to redesign the task.
|
|
378
382
|
- WIRING vs pinned facts: if the spec states interface wiring (a mount/route table, a module→path mapping, an exported signature, a file layout), reconcile EACH wiring specific against the design's pinned interface facts (the CROSS-SLICE CONTRACTS below, if present, are those facts quoted verbatim). Keep every wiring specific that reproduces the pinned facts exactly; CORRECT any that do not; and do NOT invent wiring the design leaves unspecified. Watch specifically for a "uniform" pattern (one module → one mount prefix, one naming scheme) applied to an interface whose pinned facts are NOT uniform — that is a seam bug, fix only the entry that breaks, and leave the conforming entries unchanged.
|
|
@@ -412,13 +416,20 @@ YOU MAY ONLY READ. Do NOT execute any of the listed commands, not even with --he
|
|
|
412
416
|
Output exactly two sections:
|
|
413
417
|
|
|
414
418
|
VERIFIED
|
|
415
|
-
<command> <one-line evidence: where it was found>
|
|
419
|
+
<command> <class> <one-line evidence: where it was found>
|
|
416
420
|
...
|
|
417
421
|
|
|
418
422
|
REJECTED
|
|
419
423
|
<command> <one-line reason it can't be confirmed>
|
|
420
424
|
...
|
|
421
425
|
|
|
426
|
+
<class> is exactly one of:
|
|
427
|
+
check — analyses and EXITS on its own (lint, typecheck, test, format --check)
|
|
428
|
+
build — produces artifacts and EXITS on its own (build, compile, bundle)
|
|
429
|
+
serve — starts a process that KEEPS RUNNING until killed (dev server, watch mode, repl)
|
|
430
|
+
Any command that does not terminate by itself MUST be classed serve.
|
|
431
|
+
Separate every column with TWO OR MORE spaces.
|
|
432
|
+
|
|
422
433
|
Do not add other sections, preamble, or commentary.
|
|
423
434
|
|
|
424
435
|
TOOLING (one command per line):
|
|
@@ -38,6 +38,23 @@ export type QaKind =
|
|
|
38
38
|
* gap — a human's own words are the baseline everything else is marked against.
|
|
39
39
|
*/
|
|
40
40
|
export declare const QA_PROVENANCE: Record<QaKind, string>;
|
|
41
|
+
/**
|
|
42
|
+
* Recover each answer's KIND from a rendered record, so a spec's `[from: Q3]`
|
|
43
|
+
* tag resolves to a provenance long after the dialog's own object is gone — at
|
|
44
|
+
* verify time the transcript exists only as the `## grill Q&A` text in the task
|
|
45
|
+
* file.
|
|
46
|
+
*
|
|
47
|
+
* Index IS the question number minus one, the same statement `_render` makes.
|
|
48
|
+
* An answer with no suffix is `typed`: that is what the empty-string row above
|
|
49
|
+
* declares, and it is also the honest reading of a record whose policy stamped
|
|
50
|
+
* nothing — the host cannot tell a human's words from an unstamped kind, and
|
|
51
|
+
* `typed` is the reading that does not invent a provenance.
|
|
52
|
+
*
|
|
53
|
+
* YOLO's two kinds share one stamp, so a YOLO answer resolves to `yolo`. They
|
|
54
|
+
* carry the same weight in `constraint-policy.ts`, which is the only question
|
|
55
|
+
* this resolution is asked.
|
|
56
|
+
*/
|
|
57
|
+
export declare function qaKindsFromRecord(record: string): Array<QaKind | undefined>;
|
|
41
58
|
export interface QaPolicy {
|
|
42
59
|
/** Kinds whose provenance appears in the RECORD — persisted, and handed on. */
|
|
43
60
|
record: ReadonlySet<QaKind>;
|
|
@@ -31,6 +31,34 @@ export const QA_PROVENANCE = {
|
|
|
31
31
|
accepted: '(accepted recommendation)',
|
|
32
32
|
typed: ''
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Recover each answer's KIND from a rendered record, so a spec's `[from: Q3]`
|
|
36
|
+
* tag resolves to a provenance long after the dialog's own object is gone — at
|
|
37
|
+
* verify time the transcript exists only as the `## grill Q&A` text in the task
|
|
38
|
+
* file.
|
|
39
|
+
*
|
|
40
|
+
* Index IS the question number minus one, the same statement `_render` makes.
|
|
41
|
+
* An answer with no suffix is `typed`: that is what the empty-string row above
|
|
42
|
+
* declares, and it is also the honest reading of a record whose policy stamped
|
|
43
|
+
* nothing — the host cannot tell a human's words from an unstamped kind, and
|
|
44
|
+
* `typed` is the reading that does not invent a provenance.
|
|
45
|
+
*
|
|
46
|
+
* YOLO's two kinds share one stamp, so a YOLO answer resolves to `yolo`. They
|
|
47
|
+
* carry the same weight in `constraint-policy.ts`, which is the only question
|
|
48
|
+
* this resolution is asked.
|
|
49
|
+
*/
|
|
50
|
+
export function qaKindsFromRecord(record) {
|
|
51
|
+
const kinds = [];
|
|
52
|
+
for (const line of record.split('\n')) {
|
|
53
|
+
const answer = /^A(\d+):\s*(.*)$/.exec(line);
|
|
54
|
+
if (!answer)
|
|
55
|
+
continue;
|
|
56
|
+
const text = answer[2].trimEnd();
|
|
57
|
+
const hit = Object.entries(QA_PROVENANCE).find(([, suffix]) => suffix.length > 0 && text.endsWith(suffix));
|
|
58
|
+
kinds[Number(answer[1]) - 1] = hit?.[0] ?? 'typed';
|
|
59
|
+
}
|
|
60
|
+
return kinds;
|
|
61
|
+
}
|
|
34
62
|
/**
|
|
35
63
|
* GRILL: stamps `auto` and both YOLO kinds in the record, and shows the generator
|
|
36
64
|
* nothing.
|
|
@@ -52,6 +52,17 @@ export interface QuestionRule {
|
|
|
52
52
|
/** Applied only when the defect survived its one re-prompt. */
|
|
53
53
|
repair?: (q: ClarifyQuestion) => ClarifyQuestion;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* A decision the caller may SETTLE outside the dialog. The generator is
|
|
57
|
+
* stateless, so being told "already answered" in the transcript does not stop it
|
|
58
|
+
* re-drawing the same fork reworded; `match` is how the source recognises the
|
|
59
|
+
* re-draw and drops it.
|
|
60
|
+
*/
|
|
61
|
+
export interface QuestionTopic {
|
|
62
|
+
id: string;
|
|
63
|
+
/** Matched against the plain-text question. */
|
|
64
|
+
match: (plain: string) => boolean;
|
|
65
|
+
}
|
|
55
66
|
export interface QuestionSourceDeps {
|
|
56
67
|
/**
|
|
57
68
|
* Ask the model for the next question. `hint` is the corrective re-prompt to
|
|
@@ -64,6 +75,8 @@ export interface QuestionSourceDeps {
|
|
|
64
75
|
/** Ordered quality rules. At most ONE fires per question: they share a single
|
|
65
76
|
* corrective re-prompt, spent by the first rule that detects a defect. */
|
|
66
77
|
rules?: ReadonlyArray<QuestionRule>;
|
|
78
|
+
/** Topics `settle` can close. A topic the caller never settles costs nothing. */
|
|
79
|
+
topics?: ReadonlyArray<QuestionTopic>;
|
|
67
80
|
cap?: number;
|
|
68
81
|
log?: (msg: string) => void;
|
|
69
82
|
}
|
|
@@ -81,11 +94,12 @@ export type NextQuestion = {
|
|
|
81
94
|
*
|
|
82
95
|
* The interface is one method. Behind it: the cap, the duplicate backstop and its
|
|
83
96
|
* strike budget, the NONE-vs-unparseable distinction, `pickQuestion`, the one-shot
|
|
84
|
-
* budget shared by every quality rule,
|
|
85
|
-
* re-prompt and a duplicate re-prompt.
|
|
97
|
+
* budget shared by every quality rule, the hint precedence between a format
|
|
98
|
+
* re-prompt and a duplicate re-prompt, and the settled-topic filter.
|
|
86
99
|
*/
|
|
87
100
|
export declare function makeQuestionSource(deps: QuestionSourceDeps): {
|
|
88
101
|
next: () => Promise<NextQuestion>;
|
|
89
102
|
asked: () => ReadonlyArray<string>;
|
|
90
103
|
reopen: () => void;
|
|
104
|
+
settle: (topic: string) => void;
|
|
91
105
|
};
|
|
@@ -47,12 +47,14 @@ export function pickQuestion(parsed) {
|
|
|
47
47
|
*
|
|
48
48
|
* The interface is one method. Behind it: the cap, the duplicate backstop and its
|
|
49
49
|
* strike budget, the NONE-vs-unparseable distinction, `pickQuestion`, the one-shot
|
|
50
|
-
* budget shared by every quality rule,
|
|
51
|
-
* re-prompt and a duplicate re-prompt.
|
|
50
|
+
* budget shared by every quality rule, the hint precedence between a format
|
|
51
|
+
* re-prompt and a duplicate re-prompt, and the settled-topic filter.
|
|
52
52
|
*/
|
|
53
53
|
export function makeQuestionSource(deps) {
|
|
54
54
|
const cap = deps.cap ?? MAX_DIALOG_QUESTIONS;
|
|
55
55
|
const rules = deps.rules ?? [];
|
|
56
|
+
const topics = deps.topics ?? [];
|
|
57
|
+
const settled = new Set();
|
|
56
58
|
const asked = [];
|
|
57
59
|
let dupStrikes = 0;
|
|
58
60
|
let dupHint = null;
|
|
@@ -92,12 +94,18 @@ export function makeQuestionSource(deps) {
|
|
|
92
94
|
}
|
|
93
95
|
let picked = pickQuestion(parsed);
|
|
94
96
|
const plain = stripInlineMarkdown(picked.question);
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
97
|
+
// A SETTLED topic is filtered here, ahead of both the dedupe backstop
|
|
98
|
+
// and any quality re-prompt: the caller has already recorded an answer,
|
|
99
|
+
// so this draw must not be shown, polished, or charged to the cap. It
|
|
100
|
+
// does spend a duplicate strike, because it IS one — a re-ask of a
|
|
101
|
+
// decision already in the transcript — and that budget is what stops a
|
|
102
|
+
// generator that can only redraw this fork from looping forever.
|
|
103
|
+
const settledTopic = topics.find(t => settled.has(t.id) && t.match(plain));
|
|
104
|
+
if (settledTopic || isDuplicateQuestion(asked, plain)) {
|
|
99
105
|
dupStrikes++;
|
|
100
|
-
deps.log?.(
|
|
106
|
+
deps.log?.(settledTopic ?
|
|
107
|
+
`settled topic "${settledTopic.id}" re-asked, strike ${dupStrikes}/${MAX_DUP_STRIKES}`
|
|
108
|
+
: `duplicate question, strike ${dupStrikes}/${MAX_DUP_STRIKES}`);
|
|
101
109
|
hint = null;
|
|
102
110
|
if (dupStrikes >= MAX_DUP_STRIKES)
|
|
103
111
|
return { kind: 'exhausted', why: 'dups' };
|
|
@@ -141,5 +149,10 @@ export function makeQuestionSource(deps) {
|
|
|
141
149
|
dupStrikes = 0;
|
|
142
150
|
dupHint = null;
|
|
143
151
|
}
|
|
144
|
-
|
|
152
|
+
/** Close a topic the caller answered itself. Idempotent; an unknown id is a
|
|
153
|
+
* no-op, so a caller may settle a topic it declared nothing for. */
|
|
154
|
+
function settle(topic) {
|
|
155
|
+
settled.add(topic);
|
|
156
|
+
}
|
|
157
|
+
return { next, asked: () => asked, reopen, settle };
|
|
145
158
|
}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { type CommandRunner } from './command-run.js';
|
|
2
|
+
/**
|
|
3
|
+
* What ONE discovered command did. `outcome` is `classifyCommandRun`'s verdict, so
|
|
4
|
+
* a tool that could not run at all is `skip` rather than a zero-exit pass.
|
|
5
|
+
*
|
|
6
|
+
* This exists for the DIFFERENTIAL (health-baseline.ts): "was the repo already
|
|
7
|
+
* failing?" is per command, not per overall verdict. Two runs can both be `ok:
|
|
8
|
+
* false` while a different command failed in each — a task that broke typecheck in
|
|
9
|
+
* a repo whose lint was already red — and the overall boolean calls that
|
|
10
|
+
* pre-existing.
|
|
11
|
+
*/
|
|
12
|
+
export interface HealthCommandResult {
|
|
13
|
+
/** The command line as run, e.g. `bun run lint`. The differential's join key. */
|
|
14
|
+
cmd: string;
|
|
15
|
+
outcome: 'pass' | 'fail' | 'skip';
|
|
16
|
+
/** Real exit status on a `fail`; null when nothing conclusive ran. */
|
|
17
|
+
exitCode: number | null;
|
|
18
|
+
}
|
|
2
19
|
export interface HealthOutcome {
|
|
3
20
|
/** true → every discovered static check passed, or there was nothing to run.
|
|
4
21
|
* false → a discovered command actually ran and exited non-zero. */
|
|
@@ -7,6 +24,9 @@ export interface HealthOutcome {
|
|
|
7
24
|
reason: string;
|
|
8
25
|
/** Which manifest drove discovery, or null when none was found. */
|
|
9
26
|
ecosystem: string | null;
|
|
27
|
+
/** Every command that was REACHED, in run order. The run short-circuits on the
|
|
28
|
+
* first failure, so commands after it are absent rather than passing. */
|
|
29
|
+
commands: HealthCommandResult[];
|
|
10
30
|
/**
|
|
11
31
|
* First lines of the failing command's combined stderr+stdout — captured so a
|
|
12
32
|
* FAIL is explainable from artifacts alone. The exit code alone does not say
|
|
@@ -20,6 +40,13 @@ export interface HealthOutcome {
|
|
|
20
40
|
export declare function captureHealthOutput(stdout: string, stderr: string): string;
|
|
21
41
|
/** One discovered command: the binary and its args, run from the repo root. */
|
|
22
42
|
export type HealthCommand = [bin: string, args: string[]];
|
|
43
|
+
/**
|
|
44
|
+
* Every file `discoverHealthCommands` consults. Exported because a verified
|
|
45
|
+
* command is only as good as the manifest that vouched for it: `manifestHash`
|
|
46
|
+
* (run-context.ts) hashes exactly this set, so a project that gains a `lint`
|
|
47
|
+
* script invalidates the run's tooling verdicts and nothing else does.
|
|
48
|
+
*/
|
|
49
|
+
export declare const HEALTH_MANIFEST_FILES: readonly ["package.json", "Makefile", "Cargo.toml", "pyproject.toml", "deno.json", "deno.jsonc", "go.mod"];
|
|
23
50
|
/**
|
|
24
51
|
* Discover the project's OWN whole-repo static-analysis commands. First manifest
|
|
25
52
|
* that exists wins; returns only the STATIC commands actually available for that
|
|
@@ -66,6 +66,21 @@ function makeHasTarget(cwd, target) {
|
|
|
66
66
|
return false;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Every file `discoverHealthCommands` consults. Exported because a verified
|
|
71
|
+
* command is only as good as the manifest that vouched for it: `manifestHash`
|
|
72
|
+
* (run-context.ts) hashes exactly this set, so a project that gains a `lint`
|
|
73
|
+
* script invalidates the run's tooling verdicts and nothing else does.
|
|
74
|
+
*/
|
|
75
|
+
export const HEALTH_MANIFEST_FILES = [
|
|
76
|
+
'package.json',
|
|
77
|
+
'Makefile',
|
|
78
|
+
'Cargo.toml',
|
|
79
|
+
'pyproject.toml',
|
|
80
|
+
'deno.json',
|
|
81
|
+
'deno.jsonc',
|
|
82
|
+
'go.mod'
|
|
83
|
+
];
|
|
69
84
|
/**
|
|
70
85
|
* Discover the project's OWN whole-repo static-analysis commands. First manifest
|
|
71
86
|
* that exists wins; returns only the STATIC commands actually available for that
|
|
@@ -105,7 +120,13 @@ export function discoverHealthCommands(cwd) {
|
|
|
105
120
|
}
|
|
106
121
|
/** The nothing-to-run outcome, shared by both runners. */
|
|
107
122
|
function noCommandOutcome(ecosystem) {
|
|
108
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
reason: 'no repo-wide static-analysis command found',
|
|
126
|
+
ecosystem,
|
|
127
|
+
commands: [],
|
|
128
|
+
output: ''
|
|
129
|
+
};
|
|
109
130
|
}
|
|
110
131
|
/**
|
|
111
132
|
* Run the discovered static checks whole-repo and let the real exit codes decide.
|
|
@@ -133,8 +154,10 @@ export async function runRepoHealthCheck(cwd, opts = {}) {
|
|
|
133
154
|
if (!ecosystem || cmds.length === 0)
|
|
134
155
|
return noCommandOutcome(ecosystem);
|
|
135
156
|
const run = opts.run ?? spawnCommand;
|
|
157
|
+
const commands = [];
|
|
136
158
|
for (const [bin, args] of cmds) {
|
|
137
|
-
|
|
159
|
+
const cmd = `${bin} ${args.join(' ')}`;
|
|
160
|
+
opts.onCommand?.(cmd);
|
|
138
161
|
// Runner resolution: a PATH-stripped environment must not
|
|
139
162
|
// silently skip the statics when the runner sits at a known install
|
|
140
163
|
// location; the resolved dir also rides on PATH for the script chain.
|
|
@@ -158,14 +181,25 @@ export async function runRepoHealthCheck(cwd, opts = {}) {
|
|
|
158
181
|
// English, so a genuine report quoting "browsers are not installed" would
|
|
159
182
|
// skip the static check and certify the repo healthy.
|
|
160
183
|
const verdict = classifyCommandRun(r, [], { runtimeGap: false });
|
|
161
|
-
if (verdict.outcome !== 'fail')
|
|
184
|
+
if (verdict.outcome !== 'fail') {
|
|
185
|
+
const passed = verdict.outcome === 'pass';
|
|
186
|
+
commands.push({ cmd, outcome: passed ? 'pass' : 'skip', exitCode: passed ? 0 : null });
|
|
162
187
|
continue;
|
|
188
|
+
}
|
|
189
|
+
commands.push({ cmd, outcome: 'fail', exitCode: verdict.status });
|
|
163
190
|
return {
|
|
164
191
|
ok: false,
|
|
165
|
-
reason: `\`${
|
|
192
|
+
reason: `\`${cmd}\` exited ${verdict.status}`,
|
|
166
193
|
ecosystem,
|
|
194
|
+
commands,
|
|
167
195
|
output: captureHealthOutput(r.stdout, r.stderr)
|
|
168
196
|
};
|
|
169
197
|
}
|
|
170
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
reason: `${ecosystem}: static checks passed`,
|
|
201
|
+
ecosystem,
|
|
202
|
+
commands,
|
|
203
|
+
output: ''
|
|
204
|
+
};
|
|
171
205
|
}
|
|
@@ -1,17 +1,38 @@
|
|
|
1
|
+
import { type BlockKind, type SpecDoc } from './spec-doc.js';
|
|
1
2
|
export interface RequirementEntry {
|
|
2
3
|
/** The verbatim quote from the source doc — the obligation. */
|
|
3
4
|
quote: string;
|
|
4
|
-
/** Where it came from (heading/section, or 'prose'). */
|
|
5
|
+
/** Where it came from (heading/section, or 'prose'). Model-authored. */
|
|
5
6
|
anchor: string;
|
|
7
|
+
/** 1-based source line of the block it grounded in — the HOST's anchor. */
|
|
8
|
+
line?: number;
|
|
9
|
+
/** Grounded above the first heading, where a doc says what the thing IS. */
|
|
10
|
+
preamble?: boolean;
|
|
6
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Which blocks may carry a requirement.
|
|
14
|
+
*
|
|
15
|
+
* The model still proposes whatever it likes; this decides what GROUNDING will
|
|
16
|
+
* accept. A fenced block is code or a file tree, and a table row is a grid of
|
|
17
|
+
* columns whose cells only read as a sentence by accident — both produced
|
|
18
|
+
* "requirements" like a DDL column definition or a router-table guard cell,
|
|
19
|
+
* which no task can own and which then held the coverage verdict INCOMPLETE.
|
|
20
|
+
*/
|
|
21
|
+
export interface RequirementPolicy {
|
|
22
|
+
candidateKinds: ReadonlySet<BlockKind>;
|
|
23
|
+
excludePreamble: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const REQUIREMENT_POLICY: RequirementPolicy;
|
|
7
26
|
export declare function requirementsFile(cwd: string): string;
|
|
8
27
|
/** Parse `REQUIREMENT: "<quote>" [anchor: …]` lines (mirrors parseContractLines). */
|
|
9
28
|
export declare function parseRequirementLines(text: string): RequirementEntry[];
|
|
10
|
-
/** THE ANTI-SYNTHESIS GUARD: keep only entries whose quote
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
29
|
+
/** THE ANTI-SYNTHESIS GUARD: keep only entries whose quote grounds in a CANDIDATE
|
|
30
|
+
* block of the source doc (same rule as keepGroundedContracts, narrowed by the
|
|
31
|
+
* policy). A quote whose only match lies in an excluded block is dropped — the
|
|
32
|
+
* model is free to propose it, the document decides. Does NOT cap — capping is
|
|
33
|
+
* capRequirements' job, which protects obligation-marked passages from doc-order
|
|
34
|
+
* truncation. */
|
|
35
|
+
export declare function keepGroundedRequirements(entries: RequirementEntry[], sourceDoc: string | SpecDoc, policy?: RequirementPolicy): RequirementEntry[];
|
|
15
36
|
/**
|
|
16
37
|
* Bound the list WITHOUT doc-order truncation. An extractor that works top-down
|
|
17
38
|
* yields more entries than the cap from the doc's early sections alone, so a
|
|
@@ -48,7 +69,7 @@ export declare function isLowValueQuote(quote: string): boolean;
|
|
|
48
69
|
* uncoveredPassages() below turns "a marked passage produced no quote" into hard
|
|
49
70
|
* evidence for one forced re-extraction.
|
|
50
71
|
*/
|
|
51
|
-
export declare function enumerateObligationPassages(doc: string): string[];
|
|
72
|
+
export declare function enumerateObligationPassages(doc: string | SpecDoc, policy?: RequirementPolicy): string[];
|
|
52
73
|
/** Marked passages none of the kept quotes came from — the hard evidence that
|
|
53
74
|
* extraction recall failed there (a kept quote "covers" a passage when the
|
|
54
75
|
* passage contains it, normalised). */
|
|
@@ -67,6 +88,23 @@ export type ReqMapping = {
|
|
|
67
88
|
} | {
|
|
68
89
|
kind: 'none';
|
|
69
90
|
};
|
|
91
|
+
export type RequirementClass = 'prohibition' | 'global-policy' | 'descriptive' | 'ownable';
|
|
92
|
+
/**
|
|
93
|
+
* `descriptive` is the class the coverage loop must not chase: a line that says
|
|
94
|
+
* what the product IS rather than what the work must do. It cost two whole
|
|
95
|
+
* rejected decompose rounds live — "Invite-only used-parts marketplace for a
|
|
96
|
+
* local Mazda MX-5 club." was extracted as a requirement, mapped NONE by every
|
|
97
|
+
* round because no task delivers a sentence, and held the verdict INCOMPLETE.
|
|
98
|
+
*
|
|
99
|
+
* Both marks are required, not either: preamble POSITION (above every heading,
|
|
100
|
+
* where a spec states its subject) and the absence of any modal. A preamble
|
|
101
|
+
* sentence that does carry a modal is a real obligation stated up front, and a
|
|
102
|
+
* modal-free sentence anywhere else is the ordinary shape of a feature statement
|
|
103
|
+
* — treating either alone as descriptive would empty the coverage gate.
|
|
104
|
+
*/
|
|
105
|
+
export declare function classifyRequirement(quote: string, fromPreamble?: boolean): RequirementClass;
|
|
106
|
+
/** A requirement no single task can ever OWN, judged on the quote alone — the
|
|
107
|
+
* shape `groundedCoverage` and the granularity floor get. */
|
|
70
108
|
export declare function isCrossCuttingRequirement(quote: string): boolean;
|
|
71
109
|
/** Requirement INDICES a task owns (a `TASK n` verdict), the monotonic-replacement
|
|
72
110
|
* signal (coverage-loop.ts). Index-aligned with the requirements list. */
|
|
@@ -125,9 +163,15 @@ export interface OwnedRequirement {
|
|
|
125
163
|
/** The verbatim design quote (the obligation). */
|
|
126
164
|
quote: string;
|
|
127
165
|
anchor: string;
|
|
128
|
-
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
166
|
+
/**
|
|
167
|
+
* The plan key (`TaskEntry.key`) of the task the coverage map assigned it to —
|
|
168
|
+
* the join. Allocated at plan time and stable under every later rewrite of the
|
|
169
|
+
* title. Absent on a ledger written before keys existed, which still joins on
|
|
170
|
+
* the title.
|
|
171
|
+
*/
|
|
172
|
+
key?: string;
|
|
173
|
+
/** The plan title of the owning task. Display and legacy join only — a title
|
|
174
|
+
* is prose, and a task that rewords its own is still the same task. */
|
|
131
175
|
title: string;
|
|
132
176
|
/**
|
|
133
177
|
* DETACHED: the files this obligation
|
|
@@ -149,10 +193,20 @@ export declare function readOwnedRequirements(cwd: string): Promise<OwnedRequire
|
|
|
149
193
|
export declare function parseOwnedRequirements(text: string): OwnedRequirement[];
|
|
150
194
|
/** The owned entries whose plan title matches THIS task's title (normalised
|
|
151
195
|
* equality — titles travel verbatim from the plan list into task creation;
|
|
152
|
-
* spliced repair tasks simply match nothing).
|
|
153
|
-
* owned by nobody until a task claims it, so it is never returned here — its
|
|
154
|
-
* `title` is provenance, not ownership. */
|
|
196
|
+
* spliced repair tasks simply match nothing). */
|
|
155
197
|
export declare function ownedForTitle(owned: OwnedRequirement[], title: string): OwnedRequirement[];
|
|
198
|
+
/** The owned entries assigned to THIS task's plan entry. */
|
|
199
|
+
export declare function ownedForKey(owned: OwnedRequirement[], key: string): OwnedRequirement[];
|
|
200
|
+
/**
|
|
201
|
+
* THIS task's owned entries. The key is the join; the title is the fallback for a
|
|
202
|
+
* ledger written before keys existed, and only then — once ANY entry carries a
|
|
203
|
+
* key the ledger is a keyed one, and a task the plan did not key (a spliced
|
|
204
|
+
* repair step) owns nothing, which is the same answer the title join gave it.
|
|
205
|
+
*/
|
|
206
|
+
export declare function ownedForTask(owned: OwnedRequirement[], join: {
|
|
207
|
+
key?: string;
|
|
208
|
+
title: string;
|
|
209
|
+
}): OwnedRequirement[];
|
|
156
210
|
/** The injection block for a task's OWN mapped obligations. Mirrors
|
|
157
211
|
* buildRequirementsBlock (the directive pattern that measurably works) but is
|
|
158
212
|
* singular in address: these are not "wherever they touch", they ARE this
|