@wildorder/nightshift 0.6.0 → 0.7.1
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 +21 -21
- package/dist/author.d.ts +13 -0
- package/dist/author.d.ts.map +1 -1
- package/dist/author.js +46 -13
- package/dist/author.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/decide.d.ts +13 -5
- package/dist/decide.d.ts.map +1 -1
- package/dist/decide.js +50 -35
- package/dist/decide.js.map +1 -1
- package/dist/decider-review.d.ts +68 -1
- package/dist/decider-review.d.ts.map +1 -1
- package/dist/decider-review.js +211 -3
- package/dist/decider-review.js.map +1 -1
- package/dist/decision-ledger.d.ts +79 -1
- package/dist/decision-ledger.d.ts.map +1 -1
- package/dist/decision-ledger.js +118 -11
- package/dist/decision-ledger.js.map +1 -1
- package/dist/decision-view.d.ts +41 -0
- package/dist/decision-view.d.ts.map +1 -0
- package/dist/decision-view.js +264 -0
- package/dist/decision-view.js.map +1 -0
- package/dist/findings.d.ts +3 -0
- package/dist/findings.d.ts.map +1 -1
- package/dist/findings.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/programs-dir.d.ts +27 -0
- package/dist/programs-dir.d.ts.map +1 -0
- package/dist/programs-dir.js +52 -0
- package/dist/programs-dir.js.map +1 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +21 -32
- package/dist/publish.js.map +1 -1
- package/dist/review-pass.d.ts +33 -1
- package/dist/review-pass.d.ts.map +1 -1
- package/dist/review-pass.js +94 -42
- package/dist/review-pass.js.map +1 -1
- package/dist/run-program.d.ts +25 -3
- package/dist/run-program.d.ts.map +1 -1
- package/dist/run-program.js +382 -42
- package/dist/run-program.js.map +1 -1
- package/package.json +3 -3
- package/templates/AGENTS.md +29 -29
- package/templates/CLAUDE.md +7 -7
- package/templates/vision.md +46 -46
package/dist/decider-review.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AgentRunner } from "./agent-runner.js";
|
|
2
2
|
import type { AgentConfig } from "./config.js";
|
|
3
|
-
import { type DecisionBlock } from "./decision.js";
|
|
3
|
+
import { type DecisionBlock, type DecisionOption } from "./decision.js";
|
|
4
|
+
import { type LedgerEvent } from "./decision-ledger.js";
|
|
4
5
|
import type { ProgramManifest } from "./manifest.js";
|
|
5
6
|
import type { GitOps } from "./run-program.js";
|
|
6
7
|
export interface ReviewDecisionsOptions {
|
|
@@ -14,8 +15,74 @@ export interface ReviewDecisionsOptions {
|
|
|
14
15
|
agentRunner: AgentRunner;
|
|
15
16
|
git: GitOps;
|
|
16
17
|
isRepository: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Subject ids already sent to the decider this run, shared across the
|
|
20
|
+
* authoring and build stages and across both the decision and finding
|
|
21
|
+
* paths. Consulted, then added, immediately before each invocation, so a
|
|
22
|
+
* subject that re-surfaces in the same run is never reviewed twice while a
|
|
23
|
+
* later run still reviews it correctly (SC-10). This is a "sent" set, not
|
|
24
|
+
* a "ruled on" set — see `triaged`.
|
|
25
|
+
*/
|
|
26
|
+
reviewed: Set<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Subject ids the decider actually ruled on this run — added only after a
|
|
29
|
+
* successful invocation produced a valid verdict that was appended to the
|
|
30
|
+
* ledger. Distinct from `reviewed` (which marks a subject as sent, even
|
|
31
|
+
* when the invocation fails or the decider returns no parseable verdict):
|
|
32
|
+
* this is the run-local basis the report's triage ratio (SC-07) needs, so
|
|
33
|
+
* a failed or protocol-invalid decider run is never counted as having
|
|
34
|
+
* triaged the subject.
|
|
35
|
+
*/
|
|
36
|
+
triaged: Set<string>;
|
|
17
37
|
now: () => Date;
|
|
18
38
|
log: (line: string) => void;
|
|
19
39
|
}
|
|
20
40
|
export declare function reviewDecisions(options: ReviewDecisionsOptions): Promise<void>;
|
|
41
|
+
export type FindingRecordedEvent = Extract<LedgerEvent, {
|
|
42
|
+
kind: "finding-recorded";
|
|
43
|
+
}>;
|
|
44
|
+
export type TriageVerdict = {
|
|
45
|
+
disposition: "fix-now" | "accept";
|
|
46
|
+
rationale: string;
|
|
47
|
+
} | {
|
|
48
|
+
disposition: "escalate";
|
|
49
|
+
rationale: string;
|
|
50
|
+
question: string;
|
|
51
|
+
alternatives: DecisionOption[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Parses the finding-triage verdict block, last-block-wins, fail-closed to
|
|
55
|
+
* `undefined` — mirroring `extractVerdict`. An `escalate` verdict missing a
|
|
56
|
+
* real, non-blank question or a non-empty array of well-formed alternatives
|
|
57
|
+
* is rejected here rather than written: the invariant that a ledgered
|
|
58
|
+
* `escalate` always carries both holds at this boundary (SC-12).
|
|
59
|
+
*/
|
|
60
|
+
export declare function extractTriageVerdict(output: string): TriageVerdict | undefined;
|
|
61
|
+
export interface TriageFindingsOptions {
|
|
62
|
+
root: string;
|
|
63
|
+
programId: string;
|
|
64
|
+
manifest: ProgramManifest;
|
|
65
|
+
workstreamId: string;
|
|
66
|
+
/** The routed `finding-recorded` events for this workstream — already appended to the ledger. */
|
|
67
|
+
findings: FindingRecordedEvent[];
|
|
68
|
+
/** The pre-workstream diff base, same as `reviewDecisions` uses. */
|
|
69
|
+
baseCommit: string | undefined;
|
|
70
|
+
decider: AgentConfig | undefined;
|
|
71
|
+
agentRunner: AgentRunner;
|
|
72
|
+
git: GitOps;
|
|
73
|
+
isRepository: boolean;
|
|
74
|
+
/** Shared with `reviewDecisions` — see that option's doc comment. */
|
|
75
|
+
reviewed: Set<string>;
|
|
76
|
+
/** Shared with `reviewDecisions` — see that option's doc comment. */
|
|
77
|
+
triaged: Set<string>;
|
|
78
|
+
now: () => Date;
|
|
79
|
+
log: (line: string) => void;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Triages each routed finding with the charter's three dispositions. This
|
|
83
|
+
* only records a `fix-now` triage — WS-06 acts on it. Fails open throughout:
|
|
84
|
+
* no decider, a non-zero exit, an unparseable verdict, or a hollow
|
|
85
|
+
* escalation leaves the finding `open` and never blocks the run.
|
|
86
|
+
*/
|
|
87
|
+
export declare function triageFindings(options: TriageFindingsOptions): Promise<void>;
|
|
21
88
|
//# sourceMappingURL=decider-review.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decider-review.d.ts","sourceRoot":"","sources":["../src/decider-review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"decider-review.d.ts","sourceRoot":"","sources":["../src/decider-review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAuB,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA4F/C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgDf;AAMD,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAC;AAkHtF,MAAM,MAAM,aAAa,GACrB;IAAE,WAAW,EAAE,SAAS,GAAG,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACxD;IACE,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,cAAc,EAAE,CAAC;CAChC,CAAC;AAEN;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAyC9E;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,qEAAqE;IACrE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2DlF"}
|
package/dist/decider-review.js
CHANGED
|
@@ -7,12 +7,22 @@ import { appendLedgerEvents } from "./decision-ledger.js";
|
|
|
7
7
|
* decision, one invocation, one verdict — a judgment, not a dialogue. No
|
|
8
8
|
* decider configured means defaults stand unratified, which the run report
|
|
9
9
|
* says plainly.
|
|
10
|
+
*
|
|
11
|
+
* A routed finding is a different kind of subject — raised by an
|
|
12
|
+
* independent reviewer, not a judgment call an agent stands behind — and
|
|
13
|
+
* gets its own triage path (`triageFindings`) with its own three-disposition
|
|
14
|
+
* vocabulary. The two paths are kept physically separate on purpose: an
|
|
15
|
+
* agent's own decision must stay on its untouched `ratify`/`escalate`
|
|
16
|
+
* contract.
|
|
10
17
|
*/
|
|
11
18
|
const DIFF_LIMIT = 20_000;
|
|
12
|
-
function
|
|
13
|
-
|
|
19
|
+
function clipDiff(diff) {
|
|
20
|
+
return diff.length > DIFF_LIMIT
|
|
14
21
|
? `${diff.slice(0, DIFF_LIMIT)}\n… (diff clipped at ${DIFF_LIMIT} characters)`
|
|
15
22
|
: diff;
|
|
23
|
+
}
|
|
24
|
+
function deciderBrief(manifest, record, diff) {
|
|
25
|
+
const clippedDiff = clipDiff(diff);
|
|
16
26
|
return [
|
|
17
27
|
"# Review one decision",
|
|
18
28
|
"",
|
|
@@ -66,7 +76,7 @@ function extractVerdict(output) {
|
|
|
66
76
|
return undefined;
|
|
67
77
|
}
|
|
68
78
|
export async function reviewDecisions(options) {
|
|
69
|
-
const { root, programId, manifest, workstreamId, decisions, baseCommit, decider, agentRunner, git, isRepository, now, log, } = options;
|
|
79
|
+
const { root, programId, manifest, workstreamId, decisions, baseCommit, decider, agentRunner, git, isRepository, reviewed, triaged, now, log, } = options;
|
|
70
80
|
if (!decider || decisions.length === 0)
|
|
71
81
|
return;
|
|
72
82
|
const diff = isRepository && baseCommit !== undefined
|
|
@@ -74,6 +84,9 @@ export async function reviewDecisions(options) {
|
|
|
74
84
|
: "";
|
|
75
85
|
for (const decision of decisions) {
|
|
76
86
|
const id = decisionFingerprint(workstreamId, decision);
|
|
87
|
+
if (reviewed.has(id))
|
|
88
|
+
continue;
|
|
89
|
+
reviewed.add(id);
|
|
77
90
|
const brief = deciderBrief(manifest, { workstream: workstreamId, decision }, diff);
|
|
78
91
|
const invocation = await invokeAgent(agentRunner, decider, brief, root);
|
|
79
92
|
const verdict = extractVerdict(invocation.output);
|
|
@@ -92,7 +105,202 @@ export async function reviewDecisions(options) {
|
|
|
92
105
|
reviewedBy: "decider",
|
|
93
106
|
};
|
|
94
107
|
await appendLedgerEvents(root, programId, [event]);
|
|
108
|
+
triaged.add(id);
|
|
95
109
|
log(`${workstreamId} decision ${id}: ${verdict.verdict}`);
|
|
96
110
|
}
|
|
97
111
|
}
|
|
112
|
+
function findingEvidenceLines(evidence) {
|
|
113
|
+
return evidence.map((item) => {
|
|
114
|
+
if (item.kind === "location") {
|
|
115
|
+
const range = item.endLine === undefined ? `${item.startLine}` : `${item.startLine}-${item.endLine}`;
|
|
116
|
+
const location = `${item.file}:${range}${item.unverifiable ? " (unverifiable path)" : ""}`;
|
|
117
|
+
return item.excerpt ? `- ${location} — ${item.excerpt}` : `- ${location}`;
|
|
118
|
+
}
|
|
119
|
+
if (item.kind === "concern") {
|
|
120
|
+
return item.detail ? `- ${item.named} — ${item.detail}` : `- ${item.named}`;
|
|
121
|
+
}
|
|
122
|
+
return `- ${item.metric}: ${item.value}`;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The origin-aware finding brief (SC-11): states plainly that the subject is
|
|
127
|
+
* a finding routed from a review pass, never framed as an agent's own
|
|
128
|
+
* judgment call, renders the finding as the structured data it is, offers
|
|
129
|
+
* the three dispositions with honest definitions, and — for a finding the
|
|
130
|
+
* reviewer itself declined to rate a blocker — states in prose (never a
|
|
131
|
+
* numeric cap) that escalation is reserved for the genuinely unsettleable.
|
|
132
|
+
*/
|
|
133
|
+
function findingTriageBrief(manifest, workstreamId, event, diff) {
|
|
134
|
+
const clippedDiff = clipDiff(diff);
|
|
135
|
+
const evidenceLines = findingEvidenceLines(event.evidence);
|
|
136
|
+
const higherBar = event.severity === "major"
|
|
137
|
+
? [
|
|
138
|
+
"This finding's severity is **major** — the reviewer itself",
|
|
139
|
+
"declined to rate it a blocker. That is itself evidence: reserve",
|
|
140
|
+
"`escalate` for what is genuinely unsettleable here, and lean on",
|
|
141
|
+
"`fix-now` or `accept` — a reviewer that stopped short of blocker",
|
|
142
|
+
"is usually right that the crew can settle this on its own.",
|
|
143
|
+
"",
|
|
144
|
+
]
|
|
145
|
+
: [];
|
|
146
|
+
return [
|
|
147
|
+
"# Triage a finding",
|
|
148
|
+
"",
|
|
149
|
+
`Program: ${manifest.program.id} — ${manifest.program.name}`,
|
|
150
|
+
`Workstream: ${workstreamId}`,
|
|
151
|
+
"",
|
|
152
|
+
"This is a **finding routed from a review pass** — raised by an",
|
|
153
|
+
"independent reviewer, not a judgment call an implementing agent made",
|
|
154
|
+
"and stands behind. Your job is to triage it: fix it now, accept it as",
|
|
155
|
+
"a known limitation, or escalate it to the human. You never edit",
|
|
156
|
+
"anything.",
|
|
157
|
+
"",
|
|
158
|
+
`- **Severity:** ${event.severity}`,
|
|
159
|
+
`- **Category:** ${event.category}`,
|
|
160
|
+
`- **Subject:** ${event.subject}`,
|
|
161
|
+
`- **Message:** ${event.message}`,
|
|
162
|
+
...(evidenceLines.length > 0 ? ["- **Evidence:**", ...evidenceLines] : []),
|
|
163
|
+
"",
|
|
164
|
+
...higherBar,
|
|
165
|
+
"The work this finding concerns:",
|
|
166
|
+
"",
|
|
167
|
+
"```diff",
|
|
168
|
+
clippedDiff.trim() === "" ? "(no diff available)" : clippedDiff,
|
|
169
|
+
"```",
|
|
170
|
+
"",
|
|
171
|
+
"Reply with exactly one verdict block:",
|
|
172
|
+
"",
|
|
173
|
+
"```verdict",
|
|
174
|
+
"{",
|
|
175
|
+
' "disposition": "fix-now" | "accept" | "escalate",',
|
|
176
|
+
' "rationale": "One or two sentences, written for the human who reads the run report.",',
|
|
177
|
+
' "question": "…",',
|
|
178
|
+
' "alternatives": [',
|
|
179
|
+
' { "label": "…", "description": "…" }',
|
|
180
|
+
" ]",
|
|
181
|
+
"}",
|
|
182
|
+
"```",
|
|
183
|
+
"",
|
|
184
|
+
"`question` and `alternatives` apply only to `escalate` — omit them for",
|
|
185
|
+
"`fix-now` or `accept`.",
|
|
186
|
+
"",
|
|
187
|
+
"`fix-now`: the finding is worth one bounded fix attempt in this run",
|
|
188
|
+
"before proceeding. `accept`: a real but tolerable limitation, recorded",
|
|
189
|
+
"for the human and not worth stopping for. `escalate`: the crew cannot",
|
|
190
|
+
"settle this and the human should rule — compose the question and the",
|
|
191
|
+
"real alternatives yourself, in your own words. One triage, one",
|
|
192
|
+
"verdict: this is a judgment, not a dialogue.",
|
|
193
|
+
].join("\n");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* A well-formed escalation alternative, per SC-12 and the verdict contract:
|
|
197
|
+
* a *real* `{ label, description }` composed by the decider, not a bare
|
|
198
|
+
* label. Unlike `DecisionOption` (where `description` is optional for an
|
|
199
|
+
* agent's own decision options), an escalation alternative requires a
|
|
200
|
+
* non-blank description — a hollow `{ "label": "fix" }` is not an
|
|
201
|
+
* alternative the human can rule on.
|
|
202
|
+
*/
|
|
203
|
+
function isWellFormedAlternative(value) {
|
|
204
|
+
if (typeof value !== "object" || value === null)
|
|
205
|
+
return false;
|
|
206
|
+
const candidate = value;
|
|
207
|
+
if (typeof candidate.label !== "string" || candidate.label.trim() === "")
|
|
208
|
+
return false;
|
|
209
|
+
if (typeof candidate.description !== "string" || candidate.description.trim() === "") {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Parses the finding-triage verdict block, last-block-wins, fail-closed to
|
|
216
|
+
* `undefined` — mirroring `extractVerdict`. An `escalate` verdict missing a
|
|
217
|
+
* real, non-blank question or a non-empty array of well-formed alternatives
|
|
218
|
+
* is rejected here rather than written: the invariant that a ledgered
|
|
219
|
+
* `escalate` always carries both holds at this boundary (SC-12).
|
|
220
|
+
*/
|
|
221
|
+
export function extractTriageVerdict(output) {
|
|
222
|
+
const matches = [...output.matchAll(/```verdict[^\S\r\n]*\r?\n([\s\S]*?)```/gu)];
|
|
223
|
+
for (const match of matches.reverse()) {
|
|
224
|
+
try {
|
|
225
|
+
const parsed = JSON.parse(match[1] ?? "");
|
|
226
|
+
if (typeof parsed.rationale !== "string")
|
|
227
|
+
continue;
|
|
228
|
+
if (parsed.disposition === "fix-now" || parsed.disposition === "accept") {
|
|
229
|
+
return { disposition: parsed.disposition, rationale: parsed.rationale };
|
|
230
|
+
}
|
|
231
|
+
if (parsed.disposition === "escalate") {
|
|
232
|
+
const question = parsed.question;
|
|
233
|
+
const alternatives = parsed.alternatives;
|
|
234
|
+
if (typeof question === "string" &&
|
|
235
|
+
question.trim() !== "" &&
|
|
236
|
+
Array.isArray(alternatives) &&
|
|
237
|
+
alternatives.length > 0 &&
|
|
238
|
+
alternatives.every(isWellFormedAlternative)) {
|
|
239
|
+
return {
|
|
240
|
+
disposition: "escalate",
|
|
241
|
+
rationale: parsed.rationale,
|
|
242
|
+
question,
|
|
243
|
+
alternatives,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// A hollow escalation (missing question/alternatives) is rejected at
|
|
247
|
+
// this boundary — try an earlier block rather than writing it.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Try an earlier block.
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Triages each routed finding with the charter's three dispositions. This
|
|
258
|
+
* only records a `fix-now` triage — WS-06 acts on it. Fails open throughout:
|
|
259
|
+
* no decider, a non-zero exit, an unparseable verdict, or a hollow
|
|
260
|
+
* escalation leaves the finding `open` and never blocks the run.
|
|
261
|
+
*/
|
|
262
|
+
export async function triageFindings(options) {
|
|
263
|
+
const { root, programId, manifest, workstreamId, findings, baseCommit, decider, agentRunner, git, isRepository, reviewed, triaged, now, log, } = options;
|
|
264
|
+
if (!decider || findings.length === 0)
|
|
265
|
+
return;
|
|
266
|
+
const diff = isRepository && baseCommit !== undefined
|
|
267
|
+
? await git.diffSince(root, baseCommit)
|
|
268
|
+
: "";
|
|
269
|
+
for (const event of findings) {
|
|
270
|
+
if (reviewed.has(event.id))
|
|
271
|
+
continue;
|
|
272
|
+
reviewed.add(event.id);
|
|
273
|
+
const brief = findingTriageBrief(manifest, workstreamId, event, diff);
|
|
274
|
+
const invocation = await invokeAgent(agentRunner, decider, brief, root);
|
|
275
|
+
const verdict = extractTriageVerdict(invocation.output);
|
|
276
|
+
if (invocation.exitCode !== 0 || verdict === undefined) {
|
|
277
|
+
// Fail open: an untriaged finding stays open and the report says so;
|
|
278
|
+
// it never blocks the run.
|
|
279
|
+
log(`${workstreamId} finding ${event.id}: decider produced no verdict; left untriaged`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const triageEvent = verdict.disposition === "escalate"
|
|
283
|
+
? {
|
|
284
|
+
kind: "finding-triaged",
|
|
285
|
+
at: now().toISOString(),
|
|
286
|
+
id: event.id,
|
|
287
|
+
disposition: "escalate",
|
|
288
|
+
rationale: verdict.rationale,
|
|
289
|
+
question: verdict.question,
|
|
290
|
+
alternatives: verdict.alternatives,
|
|
291
|
+
reviewedBy: "decider",
|
|
292
|
+
}
|
|
293
|
+
: {
|
|
294
|
+
kind: "finding-triaged",
|
|
295
|
+
at: now().toISOString(),
|
|
296
|
+
id: event.id,
|
|
297
|
+
disposition: verdict.disposition,
|
|
298
|
+
rationale: verdict.rationale,
|
|
299
|
+
reviewedBy: "decider",
|
|
300
|
+
};
|
|
301
|
+
await appendLedgerEvents(root, programId, [triageEvent]);
|
|
302
|
+
triaged.add(event.id);
|
|
303
|
+
log(`${workstreamId} finding ${event.id}: ${verdict.disposition}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
98
306
|
//# sourceMappingURL=decider-review.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decider-review.js","sourceRoot":"","sources":["../src/decider-review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"decider-review.js","sourceRoot":"","sources":["../src/decider-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { DecisionBlock } from "./decision.js";
|
|
1
|
+
import type { DecisionBlock, DecisionOption } from "./decision.js";
|
|
2
|
+
import type { Evidence, FindingCategory, Severity } from "./findings.js";
|
|
2
3
|
/**
|
|
3
4
|
* The decision ledger: an append-only JSONL journal per program, the durable
|
|
4
5
|
* record of every judgment call a run made, who reviewed it, and what a
|
|
@@ -34,6 +35,46 @@ export type LedgerEvent = (EventBase & {
|
|
|
34
35
|
/** The option label the human settled on. */
|
|
35
36
|
chosen: string;
|
|
36
37
|
reason: string;
|
|
38
|
+
}) | (EventBase & {
|
|
39
|
+
kind: "finding-recorded";
|
|
40
|
+
workstream: string;
|
|
41
|
+
severity: Severity;
|
|
42
|
+
category: FindingCategory;
|
|
43
|
+
subject: string;
|
|
44
|
+
message: string;
|
|
45
|
+
evidence: Evidence[];
|
|
46
|
+
/** The finding's optional machine code. */
|
|
47
|
+
code?: string;
|
|
48
|
+
/** The green pre-critique commit — the anchor. */
|
|
49
|
+
baseCommit?: string;
|
|
50
|
+
/** Attribution — never "implementer": a finding is the reviewer's. */
|
|
51
|
+
foundBy: "reviewer";
|
|
52
|
+
}) | (EventBase & ({
|
|
53
|
+
kind: "finding-triaged";
|
|
54
|
+
disposition: "fix-now" | "accept";
|
|
55
|
+
rationale: string;
|
|
56
|
+
reviewedBy: "decider";
|
|
57
|
+
} | {
|
|
58
|
+
kind: "finding-triaged";
|
|
59
|
+
disposition: "escalate";
|
|
60
|
+
rationale: string;
|
|
61
|
+
/** Composed by the decider. */
|
|
62
|
+
question: string;
|
|
63
|
+
/** The real alternatives, in the decider's words. */
|
|
64
|
+
alternatives: DecisionOption[];
|
|
65
|
+
reviewedBy: "decider";
|
|
66
|
+
})) | (EventBase & {
|
|
67
|
+
kind: "finding-fix-attempted";
|
|
68
|
+
/** "kept" only when a fix verified clean AND landed a commit; "failed"
|
|
69
|
+
* for a failed verification or a clean attempt that produced no
|
|
70
|
+
* commit. */
|
|
71
|
+
outcome: "kept" | "failed";
|
|
72
|
+
/** The implementer's verbatim summary, plus (on failure) the
|
|
73
|
+
* verification diagnosis or the no-change fact. */
|
|
74
|
+
note: string;
|
|
75
|
+
/** The fix commit, present only on a kept fix that landed one. */
|
|
76
|
+
commit?: string;
|
|
77
|
+
attemptedBy: "implementer";
|
|
37
78
|
});
|
|
38
79
|
export type DecisionStatus = "unratified" | "ratified" | "escalated" | "human-decided";
|
|
39
80
|
export interface DecisionRecord {
|
|
@@ -50,8 +91,45 @@ export interface DecisionRecord {
|
|
|
50
91
|
/** True when the agent claimed the choice is costly to undo. */
|
|
51
92
|
oneWay: boolean;
|
|
52
93
|
}
|
|
94
|
+
export type FindingStatus = "open" | "fix-now" | "accepted" | "escalated" | "human-decided";
|
|
95
|
+
export interface FindingRecord {
|
|
96
|
+
id: string;
|
|
97
|
+
workstream: string;
|
|
98
|
+
severity: Severity;
|
|
99
|
+
category: FindingCategory;
|
|
100
|
+
subject: string;
|
|
101
|
+
message: string;
|
|
102
|
+
evidence: Evidence[];
|
|
103
|
+
code?: string;
|
|
104
|
+
baseCommit?: string;
|
|
105
|
+
status: FindingStatus;
|
|
106
|
+
/** Present when a decider triaged it. */
|
|
107
|
+
triageRationale?: string;
|
|
108
|
+
/** Present when the decider escalated; the composed question. */
|
|
109
|
+
escalationQuestion?: string;
|
|
110
|
+
escalationAlternatives?: DecisionOption[];
|
|
111
|
+
/** Present when a human decided; overrides the decider's triage. */
|
|
112
|
+
humanChosen?: string;
|
|
113
|
+
humanReason?: string;
|
|
114
|
+
foundBy: "reviewer";
|
|
115
|
+
/** Discriminates from a DecisionRecord where a union of both is handled. */
|
|
116
|
+
origin: "finding";
|
|
117
|
+
/**
|
|
118
|
+
* Present once a `fix-now` triage has driven a bounded fix attempt
|
|
119
|
+
* (WS-06). "kept" leaves `status` at `"fix-now"` — a fix-attempt-and-verify
|
|
120
|
+
* fact, not a per-finding proof of correction; "failed" moves `status` to
|
|
121
|
+
* `"escalated"` while `triageRationale` (the decider's original reason)
|
|
122
|
+
* stays untouched.
|
|
123
|
+
*/
|
|
124
|
+
fixAttempt?: {
|
|
125
|
+
outcome: "kept" | "failed";
|
|
126
|
+
note: string;
|
|
127
|
+
commit?: string;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
53
130
|
export interface DecisionLedgerView {
|
|
54
131
|
decisions: DecisionRecord[];
|
|
132
|
+
findings: FindingRecord[];
|
|
55
133
|
/** Journal lines that could not be read; reported, never fatal. */
|
|
56
134
|
corrupt: number;
|
|
57
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-ledger.d.ts","sourceRoot":"","sources":["../src/decision-ledger.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"decision-ledger.d.ts","sourceRoot":"","sources":["../src/decision-ledger.ts"],"names":[],"mappings":"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"}
|
package/dist/decision-ledger.js
CHANGED
|
@@ -13,6 +13,11 @@ import { dirname, join, resolve } from "node:path";
|
|
|
13
13
|
* blocked run — because the ledger explains work, it does not gate it.
|
|
14
14
|
*/
|
|
15
15
|
export const LEDGER_SCHEMA_VERSION = 1;
|
|
16
|
+
const FINDING_STATUS_BY_DISPOSITION = {
|
|
17
|
+
"fix-now": "fix-now",
|
|
18
|
+
accept: "accepted",
|
|
19
|
+
escalate: "escalated",
|
|
20
|
+
};
|
|
16
21
|
export function ledgerPath(root, programId) {
|
|
17
22
|
return join(resolve(root), "docs", "programs", `${programId}-decisions.jsonl`);
|
|
18
23
|
}
|
|
@@ -27,13 +32,14 @@ export async function appendLedgerEvents(root, programId, events) {
|
|
|
27
32
|
await appendFile(path, `${lines}\n`, "utf8");
|
|
28
33
|
}
|
|
29
34
|
export function reduceLedgerEvents(events) {
|
|
30
|
-
const
|
|
35
|
+
const decisionsById = new Map();
|
|
36
|
+
const findingsById = new Map();
|
|
31
37
|
for (const event of events) {
|
|
32
38
|
if (event.kind === "decision-recorded") {
|
|
33
|
-
const existing =
|
|
39
|
+
const existing = decisionsById.get(event.id);
|
|
34
40
|
// A re-recorded decision (retry, resumed run) refreshes the block but
|
|
35
41
|
// keeps any higher-authority outcome already on the record.
|
|
36
|
-
|
|
42
|
+
decisionsById.set(event.id, {
|
|
37
43
|
id: event.id,
|
|
38
44
|
workstream: event.workstream,
|
|
39
45
|
decision: event.decision,
|
|
@@ -54,23 +60,124 @@ export function reduceLedgerEvents(events) {
|
|
|
54
60
|
});
|
|
55
61
|
continue;
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
if (event.kind === "finding-recorded") {
|
|
64
|
+
const existing = findingsById.get(event.id);
|
|
65
|
+
// Mirrors the decision-recorded re-record rule: refresh the finding's
|
|
66
|
+
// own fields, keep any higher-authority outcome already on it.
|
|
67
|
+
findingsById.set(event.id, {
|
|
68
|
+
id: event.id,
|
|
69
|
+
workstream: event.workstream,
|
|
70
|
+
severity: event.severity,
|
|
71
|
+
category: event.category,
|
|
72
|
+
subject: event.subject,
|
|
73
|
+
message: event.message,
|
|
74
|
+
evidence: event.evidence,
|
|
75
|
+
...(event.code === undefined ? {} : { code: event.code }),
|
|
76
|
+
...(event.baseCommit === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { baseCommit: event.baseCommit }),
|
|
79
|
+
status: existing?.status ?? "open",
|
|
80
|
+
...(existing?.triageRationale === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { triageRationale: existing.triageRationale }),
|
|
83
|
+
...(existing?.escalationQuestion === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: { escalationQuestion: existing.escalationQuestion }),
|
|
86
|
+
...(existing?.escalationAlternatives === undefined
|
|
87
|
+
? {}
|
|
88
|
+
: { escalationAlternatives: existing.escalationAlternatives }),
|
|
89
|
+
...(existing?.humanChosen === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: { humanChosen: existing.humanChosen }),
|
|
92
|
+
...(existing?.humanReason === undefined
|
|
93
|
+
? {}
|
|
94
|
+
: { humanReason: existing.humanReason }),
|
|
95
|
+
...(existing?.fixAttempt === undefined
|
|
96
|
+
? {}
|
|
97
|
+
: { fixAttempt: existing.fixAttempt }),
|
|
98
|
+
foundBy: "reviewer",
|
|
99
|
+
origin: "finding",
|
|
100
|
+
});
|
|
59
101
|
continue;
|
|
102
|
+
}
|
|
103
|
+
if (event.kind === "finding-triaged") {
|
|
104
|
+
const record = findingsById.get(event.id);
|
|
105
|
+
if (!record)
|
|
106
|
+
continue;
|
|
107
|
+
// Human authority already on the record outranks a decider triage.
|
|
108
|
+
if (record.status === "human-decided")
|
|
109
|
+
continue;
|
|
110
|
+
record.status = FINDING_STATUS_BY_DISPOSITION[event.disposition];
|
|
111
|
+
record.triageRationale = event.rationale;
|
|
112
|
+
if (event.disposition === "escalate") {
|
|
113
|
+
record.escalationQuestion = event.question;
|
|
114
|
+
record.escalationAlternatives = event.alternatives;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (event.kind === "finding-fix-attempted") {
|
|
119
|
+
const record = findingsById.get(event.id);
|
|
120
|
+
if (!record)
|
|
121
|
+
continue;
|
|
122
|
+
// Human authority already on the record outranks a fix attempt — the
|
|
123
|
+
// fix-now driver never selects a human-decided finding in the first
|
|
124
|
+
// place (it reads the same projection), but an older run's ledger can
|
|
125
|
+
// still contain the event, so the reducer stays harmless either way.
|
|
126
|
+
if (record.status === "human-decided")
|
|
127
|
+
continue;
|
|
128
|
+
record.fixAttempt = {
|
|
129
|
+
outcome: event.outcome,
|
|
130
|
+
note: event.note,
|
|
131
|
+
...(event.commit === undefined ? {} : { commit: event.commit }),
|
|
132
|
+
};
|
|
133
|
+
// A kept fix leaves status at "fix-now" — the presence of a "kept"
|
|
134
|
+
// fixAttempt is what distinguishes it from a still-pending fix-now
|
|
135
|
+
// finding. A failed fix — a failed verification or a clean attempt
|
|
136
|
+
// that landed no commit — is promoted to escalation: this is the
|
|
137
|
+
// *only* place that promotion happens, a projection, not a re-triage.
|
|
138
|
+
if (event.outcome === "failed") {
|
|
139
|
+
record.status = "escalated";
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
60
143
|
if (event.kind === "decision-reviewed") {
|
|
144
|
+
const record = decisionsById.get(event.id);
|
|
145
|
+
if (!record)
|
|
146
|
+
continue;
|
|
61
147
|
// Human authority already on the record outranks a decider review.
|
|
62
148
|
if (record.status === "human-decided")
|
|
63
149
|
continue;
|
|
64
150
|
record.status = event.verdict === "ratify" ? "ratified" : "escalated";
|
|
65
151
|
record.reviewRationale = event.rationale;
|
|
152
|
+
continue;
|
|
66
153
|
}
|
|
67
|
-
|
|
68
|
-
record
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
if (event.kind === "decision-human") {
|
|
155
|
+
// A human ruling settles whichever record matches the id — a decision
|
|
156
|
+
// or a finding — never both, since ids never collide across the two.
|
|
157
|
+
const decision = decisionsById.get(event.id);
|
|
158
|
+
if (decision) {
|
|
159
|
+
decision.status = "human-decided";
|
|
160
|
+
decision.humanChosen = event.chosen;
|
|
161
|
+
decision.humanReason = event.reason;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const finding = findingsById.get(event.id);
|
|
165
|
+
if (finding) {
|
|
166
|
+
finding.status = "human-decided";
|
|
167
|
+
finding.humanChosen = event.chosen;
|
|
168
|
+
finding.humanReason = event.reason;
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
71
171
|
}
|
|
172
|
+
// A genuinely unknown or future event kind is ignored, never
|
|
173
|
+
// mis-handled — reads stay fail-open regardless of what a hand-edited
|
|
174
|
+
// or newer ledger presents.
|
|
72
175
|
}
|
|
73
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
decisions: [...decisionsById.values()],
|
|
178
|
+
findings: [...findingsById.values()],
|
|
179
|
+
corrupt: 0,
|
|
180
|
+
};
|
|
74
181
|
}
|
|
75
182
|
/**
|
|
76
183
|
* Read and reduce the journal. Fail-open: a missing file is an empty ledger,
|
|
@@ -83,7 +190,7 @@ export async function readDecisionLedger(root, programId) {
|
|
|
83
190
|
raw = await readFile(ledgerPath(root, programId), "utf8");
|
|
84
191
|
}
|
|
85
192
|
catch {
|
|
86
|
-
return { decisions: [], corrupt: 0 };
|
|
193
|
+
return { decisions: [], findings: [], corrupt: 0 };
|
|
87
194
|
}
|
|
88
195
|
const events = [];
|
|
89
196
|
let corrupt = 0;
|