@deftai/directive-core 0.100.0 → 0.101.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/dist/authz/classify.js +100 -23
- package/dist/authz/decompose-apply.d.ts +14 -0
- package/dist/authz/decompose-apply.js +93 -18
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.js +58 -0
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +7 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +29 -0
- package/dist/policy/ceremony-dial.js +76 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +15 -1
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +34 -0
- package/dist/scope/acceptance-evidence.js +84 -10
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +11 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +6 -0
- package/dist/session/effort-budget.js +21 -3
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +25 -0
- package/dist/session/session-start.js +175 -0
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/package.json +11 -3
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evaluate verify:ac — product-first acceptance gate (#3284).
|
|
3
|
+
*
|
|
4
|
+
* Runs plan.acceptance.commands (or #3267 literal ledger) via the shared
|
|
5
|
+
* literal-acceptance runner. Records source_rung in the result message.
|
|
6
|
+
* Project floor with empty commands is a soft pass (suite gates own the floor
|
|
7
|
+
* inside full `task check`); standalone done paths still surface the rung.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { basename, resolve } from "node:path";
|
|
11
|
+
import { evaluateLiteralAcceptanceFromPlan, runLiteralAcceptanceCommands, } from "../literal-acceptance/index.js";
|
|
12
|
+
import { maybeBankOnAcPass } from "../session/ac-pass-banking.js";
|
|
13
|
+
import { readPlanAcceptance, validatePlanAcceptance } from "./acceptance.js";
|
|
14
|
+
function asRecord(value) {
|
|
15
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate product AC from an in-memory plan.
|
|
22
|
+
*/
|
|
23
|
+
export function evaluateVerifyAcFromPlan(plan, options = {}) {
|
|
24
|
+
const acceptance = readPlanAcceptance(plan);
|
|
25
|
+
const schemaErrors = validatePlanAcceptance(plan.acceptance ?? acceptance);
|
|
26
|
+
// Only hard-fail schema when an explicit plan.acceptance object exists.
|
|
27
|
+
if (plan.acceptance !== undefined && schemaErrors.length > 0) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
code: 2,
|
|
31
|
+
message: `verify:ac config error (#3284): ${schemaErrors.join("; ")}`,
|
|
32
|
+
commands: [],
|
|
33
|
+
runs: [],
|
|
34
|
+
sourceRung: acceptance.source_rung,
|
|
35
|
+
noneStated: acceptance.none_stated,
|
|
36
|
+
acceptance,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
40
|
+
// Prefer shared literal-acceptance path so safety / promotion rules stay one place.
|
|
41
|
+
// Empty plan.acceptance.commands still consults the #3267 rejected ledger
|
|
42
|
+
// (Greptile P1: rejected stated AC must never soft-pass).
|
|
43
|
+
// For derived/floor commands already on plan.acceptance, inject as explicit metadata
|
|
44
|
+
// if the literal ledger is empty of executables.
|
|
45
|
+
const base = evaluateLiteralAcceptanceFromPlan(plan, {
|
|
46
|
+
projectRoot,
|
|
47
|
+
runner: options.runner,
|
|
48
|
+
captureFromNarratives: options.captureFromNarratives,
|
|
49
|
+
quiet: options.quiet,
|
|
50
|
+
});
|
|
51
|
+
// When literal path had nothing executable but plan.acceptance has derived commands,
|
|
52
|
+
// run them directly with source=explicit semantics.
|
|
53
|
+
if (base.ok &&
|
|
54
|
+
base.runs.length === 0 &&
|
|
55
|
+
acceptance.commands.length > 0 &&
|
|
56
|
+
(acceptance.source_rung === "derived" || acceptance.source_rung === "project_floor")) {
|
|
57
|
+
const runner = options.runner;
|
|
58
|
+
const direct = runLiteralAcceptanceCommands(acceptance.commands.map((c) => ({
|
|
59
|
+
command: c.command,
|
|
60
|
+
cwd: c.cwd ?? null,
|
|
61
|
+
expectedStdout: c.expectedStdout ?? null,
|
|
62
|
+
expectedExitCode: c.expectedExitCode ?? 0,
|
|
63
|
+
source: "explicit",
|
|
64
|
+
sourceSpan: "plan.acceptance.commands",
|
|
65
|
+
})), { projectRoot, runner, allowTaskStatement: options.allowTaskStatement });
|
|
66
|
+
return annotate(direct, acceptance, options.quiet);
|
|
67
|
+
}
|
|
68
|
+
// Check composition: mid-story unpromoted capture-only may soft-pass so the
|
|
69
|
+
// framework graph is not deadlocked before agents promote peers.
|
|
70
|
+
// Greptile P1 #3284: safety-rejected stated commands NEVER soft-pass — they
|
|
71
|
+
// block product verification until a safe alternative is promoted.
|
|
72
|
+
if (options.checkIntegrated === true && !base.ok && base.runs.length === 0) {
|
|
73
|
+
const hasRejected = (base.rejected?.length ?? 0) > 0;
|
|
74
|
+
if (hasRejected) {
|
|
75
|
+
return annotate(base, acceptance, options.quiet);
|
|
76
|
+
}
|
|
77
|
+
const unpromoted = /capture-only|task_statement|no matching agent-promoted/i.test(base.message) ||
|
|
78
|
+
(base.commands.length > 0 && base.commands.every((c) => c.source === "task_statement"));
|
|
79
|
+
if (unpromoted || base.commands.length === 0) {
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
code: 0,
|
|
83
|
+
message: options.quiet
|
|
84
|
+
? ""
|
|
85
|
+
: `verify:ac advisory (#3284 check-integrated): no executable AC peers yet ` +
|
|
86
|
+
`(capture-only / empty). Done-gate standalone verify:ac still requires promotion. ` +
|
|
87
|
+
`[rung=${acceptance.source_rung}]\n` +
|
|
88
|
+
base.message,
|
|
89
|
+
commands: base.commands,
|
|
90
|
+
runs: [],
|
|
91
|
+
rejected: base.rejected,
|
|
92
|
+
sourceRung: acceptance.source_rung,
|
|
93
|
+
noneStated: acceptance.none_stated,
|
|
94
|
+
acceptance,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return annotate(base, acceptance, options.quiet);
|
|
99
|
+
}
|
|
100
|
+
function annotate(result, acceptance, quiet) {
|
|
101
|
+
const prefix = result.ok
|
|
102
|
+
? `verify:ac passed (#3284) [rung=${acceptance.source_rung}]`
|
|
103
|
+
: `verify:ac FAILED (#3284) [rung=${acceptance.source_rung}]`;
|
|
104
|
+
let message = result.message;
|
|
105
|
+
if (!quiet) {
|
|
106
|
+
if (message.includes("#3267")) {
|
|
107
|
+
message = message.replace(/#3267/g, "#3284/#3267");
|
|
108
|
+
}
|
|
109
|
+
if (!message.startsWith("verify:ac")) {
|
|
110
|
+
message = `${prefix}\n${message}`;
|
|
111
|
+
}
|
|
112
|
+
else if (!message.includes(`rung=${acceptance.source_rung}`)) {
|
|
113
|
+
message = `${message} [rung=${acceptance.source_rung}]`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (result.ok) {
|
|
117
|
+
message = "";
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...result,
|
|
121
|
+
message,
|
|
122
|
+
sourceRung: acceptance.source_rung,
|
|
123
|
+
noneStated: acceptance.none_stated,
|
|
124
|
+
acceptance,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Evaluate from xBRIEF path.
|
|
129
|
+
*/
|
|
130
|
+
export function evaluateVerifyAcFromPath(xbriefPath, options = {}) {
|
|
131
|
+
const abs = resolve(xbriefPath);
|
|
132
|
+
if (!existsSync(abs)) {
|
|
133
|
+
if (options.softMissingXbrief) {
|
|
134
|
+
return softSkip(`xBRIEF not found: ${abs}`, options.quiet);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
code: 2,
|
|
139
|
+
message: `verify:ac: xBRIEF not found: ${abs}`,
|
|
140
|
+
commands: [],
|
|
141
|
+
runs: [],
|
|
142
|
+
sourceRung: "project_floor",
|
|
143
|
+
noneStated: true,
|
|
144
|
+
acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(readFileSync(abs, "utf8"));
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
code: 2,
|
|
156
|
+
message: `verify:ac: unreadable xBRIEF (${msg}): ${abs}`,
|
|
157
|
+
commands: [],
|
|
158
|
+
runs: [],
|
|
159
|
+
sourceRung: "project_floor",
|
|
160
|
+
noneStated: true,
|
|
161
|
+
acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const data = asRecord(parsed);
|
|
165
|
+
if (data === null) {
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
code: 2,
|
|
169
|
+
message: `verify:ac: xBRIEF top-level is not an object: ${abs}`,
|
|
170
|
+
commands: [],
|
|
171
|
+
runs: [],
|
|
172
|
+
sourceRung: "project_floor",
|
|
173
|
+
noneStated: true,
|
|
174
|
+
acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const plan = asRecord(data.plan);
|
|
178
|
+
if (plan === null) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
code: 2,
|
|
182
|
+
message: `verify:ac: xBRIEF missing plan object: ${abs}`,
|
|
183
|
+
commands: [],
|
|
184
|
+
runs: [],
|
|
185
|
+
sourceRung: "project_floor",
|
|
186
|
+
noneStated: true,
|
|
187
|
+
acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const result = evaluateVerifyAcFromPlan(plan, options);
|
|
191
|
+
return maybeAttachAcPassBank(result, plan, abs, options);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* After executable AC pass, FINALIZE the banking checkpoint (#3285).
|
|
195
|
+
* Soft/advisory passes with zero runs do not bank. Fail-open on ledger errors.
|
|
196
|
+
*/
|
|
197
|
+
function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
|
|
198
|
+
if (options.bankOnPass === false) {
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
if (!result.ok || result.runs.length === 0) {
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
205
|
+
const planId = typeof plan.id === "string" && plan.id.trim() ? plan.id.trim() : null;
|
|
206
|
+
const scopeId = options.bankScopeId?.trim() ||
|
|
207
|
+
planId ||
|
|
208
|
+
basename(xbriefPath)
|
|
209
|
+
.replace(/\.xbrief\.json$/i, "")
|
|
210
|
+
.replace(/\.vbrief\.json$/i, "");
|
|
211
|
+
try {
|
|
212
|
+
const banked = maybeBankOnAcPass({
|
|
213
|
+
projectRoot,
|
|
214
|
+
scopeId,
|
|
215
|
+
executableRuns: result.runs.length,
|
|
216
|
+
quiet: options.quiet,
|
|
217
|
+
});
|
|
218
|
+
if (options.quiet || banked.notes.length === 0) {
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
const extra = banked.notes.join("\n");
|
|
222
|
+
return {
|
|
223
|
+
...result,
|
|
224
|
+
message: result.message ? `${result.message}\n${extra}` : extra,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
// Checkpoint is mandatory after executable AC green (#3285 Greptile residual).
|
|
229
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
230
|
+
return {
|
|
231
|
+
...result,
|
|
232
|
+
ok: false,
|
|
233
|
+
code: 1,
|
|
234
|
+
message: `verify:ac bank checkpoint failed (#3285): ${msg}` +
|
|
235
|
+
(result.message ? `\n${result.message}` : ""),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function softSkip(detail, quiet) {
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
code: 0,
|
|
243
|
+
message: quiet ? "" : `verify:ac skipped (#3284 soft-missing): ${detail}`,
|
|
244
|
+
commands: [],
|
|
245
|
+
runs: [],
|
|
246
|
+
sourceRung: "project_floor",
|
|
247
|
+
noneStated: true,
|
|
248
|
+
acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** Pure: product AC is required at every ceremony depth (#3284 / #3267 / #3156). */
|
|
252
|
+
export function isVerifyAcRequiredAtCeremonyDepth(_depth) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-first done-gate (#3284).
|
|
3
|
+
*
|
|
4
|
+
* acceptance.commands schema, verify:ac evaluation, check-mode composition
|
|
5
|
+
* (AC-first, hygiene advisory under pressure, rapid=AC-only).
|
|
6
|
+
*/
|
|
7
|
+
export { attachPlanAcceptance, buildAcceptanceFromIntakeCapture, readPlanAcceptance, stampAcceptanceFromLiteralCapture, validatePlanAcceptance, } from "./acceptance.js";
|
|
8
|
+
export { applyProductFirstGateMode, isHygieneGate, isProductAcGate, type ProductFirstCheckModeResolution, type ResolveProductFirstCheckModeInput, resolveProductFirstCheckMode, } from "./check-mode.js";
|
|
9
|
+
export { type EvaluateVerifyAcOptions, evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, type VerifyAcResult, } from "./evaluate.js";
|
|
10
|
+
export { type AcceptanceCommand, type AcSourceRung, ENV_CHECK_AC_ONLY, ENV_CHECK_MODE, ENV_HYGIENE_ADVISORY, HYGIENE_GATE_ID_PREFIXES, PLAN_ACCEPTANCE_KEY, type PlanAcceptance, PRODUCT_AC_GATE_ID, type ProductFirstCheckMode, } from "./types.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-first done-gate (#3284).
|
|
3
|
+
*
|
|
4
|
+
* acceptance.commands schema, verify:ac evaluation, check-mode composition
|
|
5
|
+
* (AC-first, hygiene advisory under pressure, rapid=AC-only).
|
|
6
|
+
*/
|
|
7
|
+
export { attachPlanAcceptance, buildAcceptanceFromIntakeCapture, readPlanAcceptance, stampAcceptanceFromLiteralCapture, validatePlanAcceptance, } from "./acceptance.js";
|
|
8
|
+
export { applyProductFirstGateMode, isHygieneGate, isProductAcGate, resolveProductFirstCheckMode, } from "./check-mode.js";
|
|
9
|
+
export { evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, } from "./evaluate.js";
|
|
10
|
+
export { ENV_CHECK_AC_ONLY, ENV_CHECK_MODE, ENV_HYGIENE_ADVISORY, HYGIENE_GATE_ID_PREFIXES, PLAN_ACCEPTANCE_KEY, PRODUCT_AC_GATE_ID, } from "./types.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-first done-gate (#3284): stated acceptance criteria run FIRST and are
|
|
3
|
+
* never skippable; hygiene gates run second and may degrade under pressure.
|
|
4
|
+
*
|
|
5
|
+
* Architecture for #3267 (literal acceptance-command mechanism). Composes
|
|
6
|
+
* #3214 ceremony dial (rapid = AC-only) and #3266 bank-the-pass.
|
|
7
|
+
*/
|
|
8
|
+
/** Three-rung AC-source ladder (probe lock on #3284). */
|
|
9
|
+
export type AcSourceRung = "stated" | "derived" | "project_floor";
|
|
10
|
+
/**
|
|
11
|
+
* One executable acceptance command (verbatim when source_rung=stated).
|
|
12
|
+
* Mirrors literal-acceptance command shape for interoperability.
|
|
13
|
+
*/
|
|
14
|
+
export interface AcceptanceCommand {
|
|
15
|
+
readonly command: string;
|
|
16
|
+
readonly cwd?: string | null;
|
|
17
|
+
readonly expectedStdout?: string | null;
|
|
18
|
+
readonly expectedExitCode?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Canonical plan.acceptance block (#3284).
|
|
22
|
+
*
|
|
23
|
+
* - Empty `commands` is allowed only when `none_stated: true` (absence is a decision).
|
|
24
|
+
* - `none_stated: true` never means "no AC" — it means AC is derived/floor and flagged.
|
|
25
|
+
* - `source_rung` records which ladder rung was locked at intake / promote.
|
|
26
|
+
*/
|
|
27
|
+
export interface PlanAcceptance {
|
|
28
|
+
readonly commands: readonly AcceptanceCommand[];
|
|
29
|
+
/** Explicit marker when the task statement stated no shell commands. */
|
|
30
|
+
readonly none_stated: boolean;
|
|
31
|
+
readonly source_rung: AcSourceRung;
|
|
32
|
+
/** Why derived/floor was chosen (required for derived when recorded). */
|
|
33
|
+
readonly derived_reason?: string | null;
|
|
34
|
+
}
|
|
35
|
+
/** How `task check` composes product vs hygiene gates under pressure (#3284). */
|
|
36
|
+
export type ProductFirstCheckMode =
|
|
37
|
+
/** Full graph: AC first (fail-fast), then hygiene (hard), then suite. */
|
|
38
|
+
"full"
|
|
39
|
+
/**
|
|
40
|
+
* Budget pressure / degraded env: AC still mandatory; hygiene runs but is
|
|
41
|
+
* advisory (non-zero does not fail check). Suite still runs after AC when present.
|
|
42
|
+
*/
|
|
43
|
+
| "pressure"
|
|
44
|
+
/**
|
|
45
|
+
* Ceremony dial rapid/minimal positive content: only AC verification
|
|
46
|
+
* (no hygiene, no suite) before declaring done via check.
|
|
47
|
+
*/
|
|
48
|
+
| "rapid";
|
|
49
|
+
/** Canonical product AC gate id used in check gate lists. */
|
|
50
|
+
export declare const PRODUCT_AC_GATE_ID: "verify:ac";
|
|
51
|
+
/** plan.acceptance object key on the plan root. */
|
|
52
|
+
export declare const PLAN_ACCEPTANCE_KEY: "acceptance";
|
|
53
|
+
/** Env override for check mode (full|pressure|rapid|degraded→pressure). */
|
|
54
|
+
export declare const ENV_CHECK_MODE = "DEFT_CHECK_MODE";
|
|
55
|
+
/** Explicit hygiene-advisory flag (truthy → pressure mode floor). */
|
|
56
|
+
export declare const ENV_HYGIENE_ADVISORY = "DEFT_HYGIENE_ADVISORY";
|
|
57
|
+
/** Explicit rapid AC-only flag (truthy → rapid mode). */
|
|
58
|
+
export declare const ENV_CHECK_AC_ONLY = "DEFT_CHECK_AC_ONLY";
|
|
59
|
+
/**
|
|
60
|
+
* Hygiene gate ids (framework + consumer). Failures may become advisory under
|
|
61
|
+
* pressure; AC never does. Suite gates are separate (#3188).
|
|
62
|
+
*/
|
|
63
|
+
export declare const HYGIENE_GATE_ID_PREFIXES: readonly string[];
|
|
64
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-first done-gate (#3284): stated acceptance criteria run FIRST and are
|
|
3
|
+
* never skippable; hygiene gates run second and may degrade under pressure.
|
|
4
|
+
*
|
|
5
|
+
* Architecture for #3267 (literal acceptance-command mechanism). Composes
|
|
6
|
+
* #3214 ceremony dial (rapid = AC-only) and #3266 bank-the-pass.
|
|
7
|
+
*/
|
|
8
|
+
/** Canonical product AC gate id used in check gate lists. */
|
|
9
|
+
export const PRODUCT_AC_GATE_ID = "verify:ac";
|
|
10
|
+
/** plan.acceptance object key on the plan root. */
|
|
11
|
+
export const PLAN_ACCEPTANCE_KEY = "acceptance";
|
|
12
|
+
/** Env override for check mode (full|pressure|rapid|degraded→pressure). */
|
|
13
|
+
export const ENV_CHECK_MODE = "DEFT_CHECK_MODE";
|
|
14
|
+
/** Explicit hygiene-advisory flag (truthy → pressure mode floor). */
|
|
15
|
+
export const ENV_HYGIENE_ADVISORY = "DEFT_HYGIENE_ADVISORY";
|
|
16
|
+
/** Explicit rapid AC-only flag (truthy → rapid mode). */
|
|
17
|
+
export const ENV_CHECK_AC_ONLY = "DEFT_CHECK_AC_ONLY";
|
|
18
|
+
/**
|
|
19
|
+
* Hygiene gate ids (framework + consumer). Failures may become advisory under
|
|
20
|
+
* pressure; AC never does. Suite gates are separate (#3188).
|
|
21
|
+
*/
|
|
22
|
+
export const HYGIENE_GATE_ID_PREFIXES = [
|
|
23
|
+
"verify:branch",
|
|
24
|
+
"verify:encoding",
|
|
25
|
+
"verify:cache-fresh",
|
|
26
|
+
"verify:orphan-active",
|
|
27
|
+
"verify:license-sync",
|
|
28
|
+
"verify:contract-drift",
|
|
29
|
+
"toolchain:check",
|
|
30
|
+
"toolchain:check-consumer",
|
|
31
|
+
"verify:stubs",
|
|
32
|
+
"verify:links",
|
|
33
|
+
"verify:rule-ownership",
|
|
34
|
+
"verify:biome-config",
|
|
35
|
+
"verify:content-manifest",
|
|
36
|
+
"verify:skill-external-fetch-gate",
|
|
37
|
+
"verify:cursor-tier1",
|
|
38
|
+
"verify:openclaw-tier1",
|
|
39
|
+
"verify:go-freeze",
|
|
40
|
+
"verify:bridge-drift",
|
|
41
|
+
"verify:forward-coverage",
|
|
42
|
+
"verify:test-boundary",
|
|
43
|
+
"verify:scope-provenance",
|
|
44
|
+
"verify:consumer-check-contract",
|
|
45
|
+
"verify:vbrief-conformance",
|
|
46
|
+
"verify:destructive-gh-verbs",
|
|
47
|
+
"verify:scm-boundary",
|
|
48
|
+
"verify:xbrief-drift",
|
|
49
|
+
"verify:no-task-runtime",
|
|
50
|
+
"verify:pack-drift",
|
|
51
|
+
"verify:wip-cap",
|
|
52
|
+
"verify:agents-md-budget",
|
|
53
|
+
"verify:eval-health-relocation",
|
|
54
|
+
"verify:eval-triggers-relocation",
|
|
55
|
+
"vbrief:validate",
|
|
56
|
+
"codebase:validate-structure",
|
|
57
|
+
"verify:codebase-map-fresh",
|
|
58
|
+
"verify-strategy-output",
|
|
59
|
+
"doctor",
|
|
60
|
+
];
|
|
61
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-open run-summary JSONL emitter (#3282).
|
|
3
|
+
*
|
|
4
|
+
* Emission never changes exit codes, never throws to callers, and prints at
|
|
5
|
+
* most one warning when an *explicit* DEFT_RUN_SUMMARY_PATH write fails.
|
|
6
|
+
* Symlink destinations are refused (no-follow / containment) — fail-open.
|
|
7
|
+
*/
|
|
8
|
+
import { type ResolveRunSummaryDestinationOptions } from "./path.js";
|
|
9
|
+
import { type CheckInvocationRunSummaryPayload, type DialTransitionRunSummaryPayload, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload } from "./types.js";
|
|
10
|
+
export interface RunSummaryEmitterOptions extends ResolveRunSummaryDestinationOptions {
|
|
11
|
+
readonly projectRoot: string;
|
|
12
|
+
readonly sessionId: string;
|
|
13
|
+
readonly frameworkVersion?: string;
|
|
14
|
+
/** Clock seam for tests. */
|
|
15
|
+
readonly now?: () => Date;
|
|
16
|
+
/** Destination override (tests). */
|
|
17
|
+
readonly destination?: RunSummaryDestination;
|
|
18
|
+
/** stdout/stderr seams (tests). */
|
|
19
|
+
readonly writeStdout?: (line: string) => void;
|
|
20
|
+
readonly writeStderr?: (line: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface EmitRunSummaryResult {
|
|
23
|
+
readonly emitted: boolean;
|
|
24
|
+
readonly destination: RunSummaryDestination;
|
|
25
|
+
readonly line: RunSummaryLine | null;
|
|
26
|
+
readonly warning: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Stateful emitter: one instance per session tracks seq and write-warning once.
|
|
30
|
+
*/
|
|
31
|
+
export declare class RunSummaryEmitter {
|
|
32
|
+
private seq;
|
|
33
|
+
private warned;
|
|
34
|
+
private readonly projectRoot;
|
|
35
|
+
private readonly sessionId;
|
|
36
|
+
private readonly frameworkVersion;
|
|
37
|
+
private readonly now;
|
|
38
|
+
private readonly destination;
|
|
39
|
+
private readonly writeStdout;
|
|
40
|
+
private readonly writeStderr;
|
|
41
|
+
constructor(options: RunSummaryEmitterOptions);
|
|
42
|
+
getDestination(): RunSummaryDestination;
|
|
43
|
+
emit(event: RunSummaryEventKind, payload: RunSummaryPayload): EmitRunSummaryResult;
|
|
44
|
+
emitSessionStart(payload: SessionStartRunSummaryPayload): EmitRunSummaryResult;
|
|
45
|
+
emitDialTransition(payload: DialTransitionRunSummaryPayload): EmitRunSummaryResult;
|
|
46
|
+
emitCheckInvocation(payload: CheckInvocationRunSummaryPayload): EmitRunSummaryResult;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One-shot helper for call sites that do not hold an emitter (e.g. dial escalate).
|
|
50
|
+
* Still fail-open; creates a fresh seq=1 emitter unless `seqSeed` is passed via reuse.
|
|
51
|
+
*/
|
|
52
|
+
export declare function emitRunSummaryEvent(options: RunSummaryEmitterOptions & {
|
|
53
|
+
readonly event: RunSummaryEventKind;
|
|
54
|
+
readonly payload: RunSummaryPayload;
|
|
55
|
+
}): EmitRunSummaryResult;
|
|
56
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-open run-summary JSONL emitter (#3282).
|
|
3
|
+
*
|
|
4
|
+
* Emission never changes exit codes, never throws to callers, and prints at
|
|
5
|
+
* most one warning when an *explicit* DEFT_RUN_SUMMARY_PATH write fails.
|
|
6
|
+
* Symlink destinations are refused (no-follow / containment) — fail-open.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync, realpathSync } from "node:fs";
|
|
9
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
11
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
12
|
+
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
13
|
+
import { resolveRunSummaryDestination } from "./path.js";
|
|
14
|
+
import { RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
|
|
15
|
+
function sortKeysDeep(value) {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.map(sortKeysDeep);
|
|
18
|
+
}
|
|
19
|
+
if (value !== null && typeof value === "object") {
|
|
20
|
+
const sorted = {};
|
|
21
|
+
for (const key of Object.keys(value).sort()) {
|
|
22
|
+
sorted[key] = sortKeysDeep(value[key]);
|
|
23
|
+
}
|
|
24
|
+
return sorted;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function lineToJson(line) {
|
|
29
|
+
return JSON.stringify(sortKeysDeep(line));
|
|
30
|
+
}
|
|
31
|
+
function isNestedUnder(parent, child) {
|
|
32
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
33
|
+
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Post-write realpath check: refuse parent-component TOCTOU where a parent was
|
|
37
|
+
* replaced with a symlink between validation and open (#3282 Greptile residual).
|
|
38
|
+
*/
|
|
39
|
+
function assertRealpathStillContained(root, targetAbs) {
|
|
40
|
+
const realRoot = realpathSync(resolve(root));
|
|
41
|
+
const realTarget = realpathSync(targetAbs);
|
|
42
|
+
const prefix = realRoot.endsWith(sep) ? realRoot : `${realRoot}${sep}`;
|
|
43
|
+
if (realTarget !== realRoot && !realTarget.startsWith(prefix)) {
|
|
44
|
+
throw new Error(`run-summary write refused: realpath escaped containment (${realTarget} not under ${realRoot})`);
|
|
45
|
+
}
|
|
46
|
+
// Re-assert path components are not symlinks after the write.
|
|
47
|
+
assertWriteTargetSafe(realRoot, realTarget);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Append (or replace) a run-summary line with symlink refusal + containment.
|
|
51
|
+
* Mirrors lifecycle/events.ts appendEventLogLine (#2766 / #2951 / #3282 Greptile P1).
|
|
52
|
+
* Post-write realpath re-check closes parent-replacement TOCTOU window.
|
|
53
|
+
*/
|
|
54
|
+
function writeRunSummaryLine(projectRoot, targetPath, textLine, mode) {
|
|
55
|
+
const targetAbs = resolve(targetPath);
|
|
56
|
+
const data = `${textLine}\n`;
|
|
57
|
+
if (isNestedUnder(projectRoot, targetAbs)) {
|
|
58
|
+
const rootAbs = resolve(projectRoot);
|
|
59
|
+
assertWriteTargetSafe(rootAbs, targetAbs);
|
|
60
|
+
containedWrite({
|
|
61
|
+
root: rootAbs,
|
|
62
|
+
target: targetAbs,
|
|
63
|
+
data,
|
|
64
|
+
mode,
|
|
65
|
+
});
|
|
66
|
+
assertRealpathStillContained(rootAbs, targetAbs);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Explicit path outside project: contain under the log parent (no symlink follow).
|
|
70
|
+
const parent = dirname(targetAbs);
|
|
71
|
+
mkdirSync(parent, { recursive: true });
|
|
72
|
+
const parentAbs = resolve(parent);
|
|
73
|
+
assertWriteTargetSafe(parentAbs, targetAbs);
|
|
74
|
+
containedWrite({
|
|
75
|
+
root: parentAbs,
|
|
76
|
+
target: basename(targetAbs),
|
|
77
|
+
data,
|
|
78
|
+
mode,
|
|
79
|
+
});
|
|
80
|
+
assertRealpathStillContained(parentAbs, targetAbs);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Stateful emitter: one instance per session tracks seq and write-warning once.
|
|
84
|
+
*/
|
|
85
|
+
export class RunSummaryEmitter {
|
|
86
|
+
seq = 0;
|
|
87
|
+
warned = false;
|
|
88
|
+
projectRoot;
|
|
89
|
+
sessionId;
|
|
90
|
+
frameworkVersion;
|
|
91
|
+
now;
|
|
92
|
+
destination;
|
|
93
|
+
writeStdout;
|
|
94
|
+
writeStderr;
|
|
95
|
+
constructor(options) {
|
|
96
|
+
this.projectRoot = options.projectRoot;
|
|
97
|
+
this.sessionId = options.sessionId;
|
|
98
|
+
this.frameworkVersion = options.frameworkVersion ?? readCorePackageVersion();
|
|
99
|
+
this.now = options.now ?? (() => new Date());
|
|
100
|
+
this.destination =
|
|
101
|
+
options.destination ??
|
|
102
|
+
resolveRunSummaryDestination(options.projectRoot, {
|
|
103
|
+
env: options.env,
|
|
104
|
+
gitignoreCovers: options.gitignoreCovers,
|
|
105
|
+
});
|
|
106
|
+
this.writeStdout = options.writeStdout ?? ((line) => process.stdout.write(`${line}\n`));
|
|
107
|
+
this.writeStderr = options.writeStderr ?? ((line) => process.stderr.write(`${line}\n`));
|
|
108
|
+
}
|
|
109
|
+
getDestination() {
|
|
110
|
+
return this.destination;
|
|
111
|
+
}
|
|
112
|
+
emit(event, payload) {
|
|
113
|
+
try {
|
|
114
|
+
if (this.destination.kind === "silent") {
|
|
115
|
+
return { emitted: false, destination: this.destination, line: null, warning: false };
|
|
116
|
+
}
|
|
117
|
+
this.seq += 1;
|
|
118
|
+
const line = {
|
|
119
|
+
schema_version: RUN_SUMMARY_SCHEMA_VERSION,
|
|
120
|
+
session_id: this.sessionId,
|
|
121
|
+
framework_version: this.frameworkVersion,
|
|
122
|
+
seq: this.seq,
|
|
123
|
+
ts: this.now().toISOString(),
|
|
124
|
+
event,
|
|
125
|
+
payload,
|
|
126
|
+
};
|
|
127
|
+
const text = lineToJson(line);
|
|
128
|
+
if (this.destination.kind === "stdout") {
|
|
129
|
+
this.writeStdout(`${RUN_SUMMARY_STDOUT_PREFIX}${text}`);
|
|
130
|
+
return { emitted: true, destination: this.destination, line, warning: false };
|
|
131
|
+
}
|
|
132
|
+
// file destination — no-follow containment (#3282 Greptile P1 security)
|
|
133
|
+
const { path, truncateOnSessionStart, explicit } = this.destination;
|
|
134
|
+
try {
|
|
135
|
+
const mode = event === "session_start" && truncateOnSessionStart ? "replace" : "append";
|
|
136
|
+
writeRunSummaryLine(this.projectRoot, path, text, mode);
|
|
137
|
+
return { emitted: true, destination: this.destination, line, warning: false };
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Symlink / containment refusal and I/O both fail-open.
|
|
141
|
+
if (explicit && !this.warned) {
|
|
142
|
+
this.warned = true;
|
|
143
|
+
this.writeStderr(RUN_SUMMARY_WRITE_WARNING);
|
|
144
|
+
return { emitted: false, destination: this.destination, line, warning: true };
|
|
145
|
+
}
|
|
146
|
+
return { emitted: false, destination: this.destination, line, warning: false };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { emitted: false, destination: this.destination, line: null, warning: false };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
emitSessionStart(payload) {
|
|
154
|
+
return this.emit("session_start", payload);
|
|
155
|
+
}
|
|
156
|
+
emitDialTransition(payload) {
|
|
157
|
+
return this.emit("dial_transition", payload);
|
|
158
|
+
}
|
|
159
|
+
emitCheckInvocation(payload) {
|
|
160
|
+
return this.emit("check_invocation", payload);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* One-shot helper for call sites that do not hold an emitter (e.g. dial escalate).
|
|
165
|
+
* Still fail-open; creates a fresh seq=1 emitter unless `seqSeed` is passed via reuse.
|
|
166
|
+
*/
|
|
167
|
+
export function emitRunSummaryEvent(options) {
|
|
168
|
+
const emitter = new RunSummaryEmitter(options);
|
|
169
|
+
return emitter.emit(options.event, options.payload);
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=emit.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { type EmitRunSummaryResult, emitRunSummaryEvent, RunSummaryEmitter, type RunSummaryEmitterOptions, } from "./emit.js";
|
|
2
|
+
export { gitignoreCoversRunSummary, type ResolveRunSummaryDestinationOptions, resolveRunSummaryDestination, } from "./path.js";
|
|
3
|
+
export { type CheckGateOutcome, type CheckInvocationRunSummaryPayload, DEFAULT_RUN_SUMMARY_BASENAME, type DialTransitionRunSummaryPayload, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, type RunSummaryBaseFields, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, } from "./types.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { emitRunSummaryEvent, RunSummaryEmitter, } from "./emit.js";
|
|
2
|
+
export { gitignoreCoversRunSummary, resolveRunSummaryDestination, } from "./path.js";
|
|
3
|
+
export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve where run-summary JSONL should go (#3282 emission matrix).
|
|
3
|
+
*
|
|
4
|
+
* - `DEFT_RUN_SUMMARY_PATH=<path>` → append JSONL there (explicit; warn once on write fail)
|
|
5
|
+
* - `DEFT_RUN_SUMMARY_PATH=-` → stdout lines prefixed `DEFT-TLM:`
|
|
6
|
+
* - unset → repo-root `.deft-run-summary.json` only when .gitignore covers it; else silent
|
|
7
|
+
*/
|
|
8
|
+
import { type RunSummaryDestination } from "./types.js";
|
|
9
|
+
export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* True when a .gitignore line covers the default run-summary basename.
|
|
12
|
+
* Matches exact line or trailing slash variants; ignores comments.
|
|
13
|
+
*/
|
|
14
|
+
export declare function gitignoreCoversRunSummary(projectRoot: string, basename?: string): boolean;
|
|
15
|
+
export interface ResolveRunSummaryDestinationOptions {
|
|
16
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
17
|
+
/** Test seam for gitignore coverage. */
|
|
18
|
+
readonly gitignoreCovers?: (projectRoot: string) => boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve emission destination for the run-summary matrix (#3282).
|
|
22
|
+
* Never throws — fail-open consumers treat resolution errors as silent.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveRunSummaryDestination(projectRoot: string, options?: ResolveRunSummaryDestinationOptions): RunSummaryDestination;
|
|
25
|
+
//# sourceMappingURL=path.d.ts.map
|