@deftai/directive-core 0.99.0 → 0.100.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/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +157 -23
- package/dist/authz/decompose-apply.d.ts +67 -0
- package/dist/authz/decompose-apply.js +302 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +72 -79
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/intake/issue-ingest.js +29 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ceremony-dial.d.ts +67 -11
- package/dist/policy/ceremony-dial.js +174 -23
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/render/framework-commands.js +4 -0
- package/dist/scope/acceptance-evidence.d.ts +76 -0
- package/dist/scope/acceptance-evidence.js +348 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.js +125 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/effort-budget.d.ts +130 -0
- package/dist/session/effort-budget.js +334 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/session-start.d.ts +6 -0
- package/dist/session/session-start.js +53 -2
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +7 -3
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate literal acceptance-command gate from an xBRIEF plan or path (#3267).
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
|
|
7
|
+
import { runLiteralAcceptanceCommands } from "./run.js";
|
|
8
|
+
function asRecord(value) {
|
|
9
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function planOf(data) {
|
|
15
|
+
return asRecord(data.plan);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve commands + rejected ledger.
|
|
19
|
+
* Stored first; when captureFromNarratives is true (default), also re-scan narratives
|
|
20
|
+
* and merge any newly stated commands so stored entries cannot hide AC updates
|
|
21
|
+
* (Greptile conf residual).
|
|
22
|
+
*/
|
|
23
|
+
export function resolveLiteralAcceptanceDetailed(plan, options = {}) {
|
|
24
|
+
const stored = readStoredLiteralAcceptanceDetailed(plan);
|
|
25
|
+
if (options.captureFromNarratives === false) {
|
|
26
|
+
return stored;
|
|
27
|
+
}
|
|
28
|
+
const narratives = asRecord(plan.narratives);
|
|
29
|
+
if (narratives === null) {
|
|
30
|
+
return stored;
|
|
31
|
+
}
|
|
32
|
+
const parts = [];
|
|
33
|
+
for (const key of [
|
|
34
|
+
"Overview",
|
|
35
|
+
"Description",
|
|
36
|
+
"Acceptance",
|
|
37
|
+
"UserStory",
|
|
38
|
+
"ImplementationPlan",
|
|
39
|
+
"AcceptanceJustification",
|
|
40
|
+
]) {
|
|
41
|
+
if (typeof narratives[key] === "string") {
|
|
42
|
+
parts.push(narratives[key]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (parts.length === 0)
|
|
46
|
+
return stored;
|
|
47
|
+
const captured = captureLiteralAcceptanceCommandsDetailed(parts.join("\n\n"));
|
|
48
|
+
// Merge commands: keep stored (richer context) first; add narrative captures by
|
|
49
|
+
// composite key (command+cwd+exit) so new stated lines are not suppressed.
|
|
50
|
+
const cmdKey = (c) => {
|
|
51
|
+
const cwd = c.cwd !== null && c.cwd !== undefined && c.cwd.trim().length > 0 ? c.cwd.trim() : "";
|
|
52
|
+
const exit = typeof c.expectedExitCode === "number" ? c.expectedExitCode : 0;
|
|
53
|
+
return `${c.command}\0${cwd}\0${exit}`;
|
|
54
|
+
};
|
|
55
|
+
const seen = new Set(stored.commands.map(cmdKey));
|
|
56
|
+
const commands = [...stored.commands];
|
|
57
|
+
for (const c of captured.commands) {
|
|
58
|
+
const k = cmdKey(c);
|
|
59
|
+
if (seen.has(k))
|
|
60
|
+
continue;
|
|
61
|
+
// Also skip pure command-string dupes already stored (any cwd) so narrative
|
|
62
|
+
// re-capture does not invent a second null-cwd twin of an explicit row.
|
|
63
|
+
if (commands.some((s) => s.command === c.command))
|
|
64
|
+
continue;
|
|
65
|
+
seen.add(k);
|
|
66
|
+
commands.push(c);
|
|
67
|
+
}
|
|
68
|
+
const rejectedSeen = new Set(stored.rejected.map((r) => `${r.command}\0${r.reason}`));
|
|
69
|
+
const rejected = [...stored.rejected];
|
|
70
|
+
for (const r of captured.rejected) {
|
|
71
|
+
const k = `${r.command}\0${r.reason}`;
|
|
72
|
+
if (rejectedSeen.has(k))
|
|
73
|
+
continue;
|
|
74
|
+
rejectedSeen.add(k);
|
|
75
|
+
rejected.push(r);
|
|
76
|
+
}
|
|
77
|
+
return { commands, rejected };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolve commands: stored first; optional narrative re-capture when empty.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveLiteralAcceptanceCommands(plan, options = {}) {
|
|
83
|
+
return [...resolveLiteralAcceptanceDetailed(plan, options).commands];
|
|
84
|
+
}
|
|
85
|
+
function appendRejectedNote(message, rejected) {
|
|
86
|
+
const ledger = formatRejectedLedger(rejected);
|
|
87
|
+
if (ledger.length === 0)
|
|
88
|
+
return message;
|
|
89
|
+
if (message.length === 0)
|
|
90
|
+
return ledger;
|
|
91
|
+
return `${message}\n${ledger}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Evaluate from an in-memory plan object.
|
|
95
|
+
*/
|
|
96
|
+
export function evaluateLiteralAcceptanceFromPlan(plan, options = {}) {
|
|
97
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
98
|
+
const resolved = resolveLiteralAcceptanceDetailed(plan, {
|
|
99
|
+
captureFromNarratives: options.captureFromNarratives,
|
|
100
|
+
});
|
|
101
|
+
const result = runLiteralAcceptanceCommands(resolved.commands, {
|
|
102
|
+
projectRoot,
|
|
103
|
+
runner: options.runner,
|
|
104
|
+
});
|
|
105
|
+
// Rejected stated commands are fail-closed for completion (#3267 Greptile conf residual).
|
|
106
|
+
// A safety-rejected shell-shaped acceptance line is not "optional diagnostic" — operators
|
|
107
|
+
// must promote a safe alternative or remove the stated command from the task statement.
|
|
108
|
+
let ok = result.ok;
|
|
109
|
+
let code = result.code;
|
|
110
|
+
let message = appendRejectedNote(result.message, resolved.rejected);
|
|
111
|
+
if (resolved.rejected.length > 0) {
|
|
112
|
+
ok = false;
|
|
113
|
+
if (code === 0)
|
|
114
|
+
code = 1;
|
|
115
|
+
const ledger = formatRejectedLedger(resolved.rejected);
|
|
116
|
+
message =
|
|
117
|
+
`Literal acceptance-command gate FAILED (#3267): ${resolved.rejected.length} ` +
|
|
118
|
+
`safety-rejected stated command(s) block completion until resolved ` +
|
|
119
|
+
`(promote a safe alternative or remove from the task statement).\n` +
|
|
120
|
+
ledger +
|
|
121
|
+
(result.message.length > 0 ? `\n${result.message}` : "");
|
|
122
|
+
}
|
|
123
|
+
const withRejected = {
|
|
124
|
+
...result,
|
|
125
|
+
ok,
|
|
126
|
+
code,
|
|
127
|
+
rejected: resolved.rejected,
|
|
128
|
+
message,
|
|
129
|
+
};
|
|
130
|
+
if (options.quiet === true && withRejected.ok) {
|
|
131
|
+
return { ...withRejected, message: "" };
|
|
132
|
+
}
|
|
133
|
+
return withRejected;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Evaluate from an xBRIEF JSON file path.
|
|
137
|
+
*/
|
|
138
|
+
export function evaluateLiteralAcceptanceFromPath(xbriefPath, options = {}) {
|
|
139
|
+
const abs = resolve(xbriefPath);
|
|
140
|
+
if (!existsSync(abs)) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
code: 2,
|
|
144
|
+
message: `Literal acceptance-command gate: xBRIEF not found: ${abs}`,
|
|
145
|
+
commands: [],
|
|
146
|
+
runs: [],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(readFileSync(abs, "utf8"));
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
code: 2,
|
|
158
|
+
message: `Literal acceptance-command gate: unreadable xBRIEF (${msg}): ${abs}`,
|
|
159
|
+
commands: [],
|
|
160
|
+
runs: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const data = asRecord(parsed);
|
|
164
|
+
if (data === null) {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
code: 2,
|
|
168
|
+
message: `Literal acceptance-command gate: xBRIEF top-level is not an object: ${abs}`,
|
|
169
|
+
commands: [],
|
|
170
|
+
runs: [],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const plan = planOf(data);
|
|
174
|
+
if (plan === null) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
code: 2,
|
|
178
|
+
message: `Literal acceptance-command gate: xBRIEF missing plan object: ${abs}`,
|
|
179
|
+
commands: [],
|
|
180
|
+
runs: [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return evaluateLiteralAcceptanceFromPlan(plan, options);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Pure ceremony-dial contract helper: literal AC run is required at every depth
|
|
187
|
+
* including rapid/minimal (#3267 / #3214 / #3156).
|
|
188
|
+
*/
|
|
189
|
+
export function isLiteralAcceptanceRequiredAtCeremonyDepth(_depth) {
|
|
190
|
+
// Verification depth is constant — dial never skips this check.
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Literal acceptance-command verification (#3267).
|
|
3
|
+
*
|
|
4
|
+
* Capture exact stated shell commands at intake; run them verbatim before done.
|
|
5
|
+
* Extends #973; survives ceremony dial rapid/minimal (#3214).
|
|
6
|
+
*/
|
|
7
|
+
export { attachLiteralAcceptanceCommands, type CaptureLiteralAcceptanceResult, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
|
|
8
|
+
export { type EvaluateLiteralAcceptanceOptions, evaluateLiteralAcceptanceFromPath, evaluateLiteralAcceptanceFromPlan, isLiteralAcceptanceRequiredAtCeremonyDepth, resolveLiteralAcceptanceCommands, resolveLiteralAcceptanceDetailed, } from "./evaluate.js";
|
|
9
|
+
export { defaultLiteralAcceptanceRunner, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
|
|
10
|
+
export { type CommandSafetyResult, evaluateCommandSafety, isExecutableLiteralSource, } from "./safety.js";
|
|
11
|
+
export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, type LiteralAcceptanceCommand, type LiteralAcceptanceGateResult, type LiteralAcceptanceRunner, type LiteralAcceptanceRunResult, type LiteralAcceptanceSource, type RejectedLiteralCommand, } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Literal acceptance-command verification (#3267).
|
|
3
|
+
*
|
|
4
|
+
* Capture exact stated shell commands at intake; run them verbatim before done.
|
|
5
|
+
* Extends #973; survives ceremony dial rapid/minimal (#3214).
|
|
6
|
+
*/
|
|
7
|
+
export { attachLiteralAcceptanceCommands, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
|
|
8
|
+
export { evaluateLiteralAcceptanceFromPath, evaluateLiteralAcceptanceFromPlan, isLiteralAcceptanceRequiredAtCeremonyDepth, resolveLiteralAcceptanceCommands, resolveLiteralAcceptanceDetailed, } from "./evaluate.js";
|
|
9
|
+
export { defaultLiteralAcceptanceRunner, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
|
|
10
|
+
export { evaluateCommandSafety, isExecutableLiteralSource, } from "./safety.js";
|
|
11
|
+
export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run stored literal acceptance commands verbatim (#3267).
|
|
3
|
+
*
|
|
4
|
+
* Same flags, same cwd (project root unless the command record states otherwise).
|
|
5
|
+
* Fail closed on non-zero exit or missing expected stdout.
|
|
6
|
+
* Self-chosen verification is supplementary — this gate only runs stored literals.
|
|
7
|
+
*/
|
|
8
|
+
import type { LiteralAcceptanceCommand, LiteralAcceptanceGateResult, LiteralAcceptanceRunner, LiteralAcceptanceRunResult } from "./types.js";
|
|
9
|
+
/** Default shell runner: exact command string via platform shell. */
|
|
10
|
+
export declare function defaultLiteralAcceptanceRunner(input: {
|
|
11
|
+
readonly command: string;
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
}): {
|
|
14
|
+
exitCode: number;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Run one command record verbatim. Does not re-tokenize or rewrite the command string.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runLiteralAcceptanceCommand(cmd: LiteralAcceptanceCommand, options: {
|
|
22
|
+
readonly projectRoot: string;
|
|
23
|
+
readonly runner?: LiteralAcceptanceRunner;
|
|
24
|
+
}): LiteralAcceptanceRunResult;
|
|
25
|
+
/**
|
|
26
|
+
* Run all stored commands in order. Fail closed if any fail.
|
|
27
|
+
* Empty command list → ok (nothing stated to verify).
|
|
28
|
+
*/
|
|
29
|
+
export declare function runLiteralAcceptanceCommands(commands: readonly LiteralAcceptanceCommand[], options: {
|
|
30
|
+
readonly projectRoot: string;
|
|
31
|
+
readonly runner?: LiteralAcceptanceRunner;
|
|
32
|
+
/**
|
|
33
|
+
* When true, allow source=task_statement (raw issue text) to spawn.
|
|
34
|
+
* Default false — issue text is capture-only until promoted (#3267 Greptile P1).
|
|
35
|
+
*/
|
|
36
|
+
readonly allowTaskStatement?: boolean;
|
|
37
|
+
}): LiteralAcceptanceGateResult;
|
|
38
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run stored literal acceptance commands verbatim (#3267).
|
|
3
|
+
*
|
|
4
|
+
* Same flags, same cwd (project root unless the command record states otherwise).
|
|
5
|
+
* Fail closed on non-zero exit or missing expected stdout.
|
|
6
|
+
* Self-chosen verification is supplementary — this gate only runs stored literals.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { isAbsolute, resolve } from "node:path";
|
|
10
|
+
import { evaluateCommandSafety, isExecutableLiteralSource } from "./safety.js";
|
|
11
|
+
/** Default shell runner: exact command string via platform shell. */
|
|
12
|
+
export function defaultLiteralAcceptanceRunner(input) {
|
|
13
|
+
// Windows: cmd.exe /c; POSIX: shell true uses sh -c. Exact string preserved.
|
|
14
|
+
const result = spawnSync(input.command, {
|
|
15
|
+
cwd: input.cwd,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
shell: true,
|
|
18
|
+
env: process.env,
|
|
19
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
20
|
+
});
|
|
21
|
+
const exitCode = typeof result.status === "number" ? result.status : result.error !== undefined ? 1 : 1;
|
|
22
|
+
return {
|
|
23
|
+
exitCode,
|
|
24
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
25
|
+
stderr: typeof result.stderr === "string"
|
|
26
|
+
? result.stderr
|
|
27
|
+
: result.error instanceof Error
|
|
28
|
+
? result.error.message
|
|
29
|
+
: "",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function resolveCwd(projectRoot, cmd) {
|
|
33
|
+
if (cmd.cwd === null || cmd.cwd === undefined || cmd.cwd.trim().length === 0) {
|
|
34
|
+
return resolve(projectRoot);
|
|
35
|
+
}
|
|
36
|
+
const raw = cmd.cwd.trim();
|
|
37
|
+
if (isAbsolute(raw))
|
|
38
|
+
return raw;
|
|
39
|
+
return resolve(projectRoot, raw);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run one command record verbatim. Does not re-tokenize or rewrite the command string.
|
|
43
|
+
*/
|
|
44
|
+
export function runLiteralAcceptanceCommand(cmd, options) {
|
|
45
|
+
const cwd = resolveCwd(options.projectRoot, cmd);
|
|
46
|
+
const safety = evaluateCommandSafety(cmd.command);
|
|
47
|
+
if (!safety.ok) {
|
|
48
|
+
return {
|
|
49
|
+
command: cmd.command,
|
|
50
|
+
cwd,
|
|
51
|
+
exitCode: 2,
|
|
52
|
+
stdout: "",
|
|
53
|
+
stderr: safety.reason ?? "unsafe command",
|
|
54
|
+
ok: false,
|
|
55
|
+
detail: `refused: ${safety.reason ?? "unsafe command"}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const runner = options.runner ?? defaultLiteralAcceptanceRunner;
|
|
59
|
+
const result = runner({ command: cmd.command, cwd });
|
|
60
|
+
const expectedExit = cmd.expectedExitCode ?? 0;
|
|
61
|
+
let ok = result.exitCode === expectedExit;
|
|
62
|
+
let detail = ok
|
|
63
|
+
? `exit ${result.exitCode} (expected ${expectedExit})`
|
|
64
|
+
: `exit ${result.exitCode} (expected ${expectedExit})`;
|
|
65
|
+
if (ok &&
|
|
66
|
+
cmd.expectedStdout !== null &&
|
|
67
|
+
cmd.expectedStdout !== undefined &&
|
|
68
|
+
cmd.expectedStdout.length > 0) {
|
|
69
|
+
if (!result.stdout.includes(cmd.expectedStdout)) {
|
|
70
|
+
ok = false;
|
|
71
|
+
detail = `stdout missing expected substring ${JSON.stringify(cmd.expectedStdout)}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
command: cmd.command,
|
|
76
|
+
cwd,
|
|
77
|
+
exitCode: result.exitCode,
|
|
78
|
+
stdout: result.stdout,
|
|
79
|
+
stderr: result.stderr,
|
|
80
|
+
ok,
|
|
81
|
+
detail,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run all stored commands in order. Fail closed if any fail.
|
|
86
|
+
* Empty command list → ok (nothing stated to verify).
|
|
87
|
+
*/
|
|
88
|
+
export function runLiteralAcceptanceCommands(commands, options) {
|
|
89
|
+
if (commands.length === 0) {
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
code: 0,
|
|
93
|
+
message: "Literal acceptance-command gate: no stated commands (nothing to run) (#3267)",
|
|
94
|
+
commands: [],
|
|
95
|
+
runs: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const untrusted = commands.filter((c) => c.source === "task_statement" && options.allowTaskStatement !== true);
|
|
99
|
+
const executable = commands.filter((c) => isExecutableLiteralSource(c.source) ||
|
|
100
|
+
(c.source === "task_statement" && options.allowTaskStatement === true));
|
|
101
|
+
// Fail closed: every capture-only stated command must have a matching agent-promoted
|
|
102
|
+
// peer with the same execution constraints (command + cwd + expectedStdout + exit).
|
|
103
|
+
// Command-text-only promotion must not drop cwd/expected-result (Greptile conf residual).
|
|
104
|
+
const promotionKey = (c) => {
|
|
105
|
+
const cwd = c.cwd !== null && c.cwd !== undefined && String(c.cwd).trim().length > 0
|
|
106
|
+
? String(c.cwd).trim()
|
|
107
|
+
: "";
|
|
108
|
+
const stdout = c.expectedStdout !== null &&
|
|
109
|
+
c.expectedStdout !== undefined &&
|
|
110
|
+
String(c.expectedStdout).length > 0
|
|
111
|
+
? String(c.expectedStdout)
|
|
112
|
+
: "";
|
|
113
|
+
const exit = typeof c.expectedExitCode === "number" ? c.expectedExitCode : 0;
|
|
114
|
+
return `${c.command}\0${cwd}\0${stdout}\0${exit}`;
|
|
115
|
+
};
|
|
116
|
+
const executableKeySet = new Set(executable.map(promotionKey));
|
|
117
|
+
// Also accept command-text match when both sides have default context (null cwd / exit 0),
|
|
118
|
+
// but never when the stated row carries a non-default constraint the peer lacks.
|
|
119
|
+
const unpromoted = untrusted.filter((c) => {
|
|
120
|
+
if (executableKeySet.has(promotionKey(c)))
|
|
121
|
+
return false;
|
|
122
|
+
// Strict: if stated has non-default context, require exact peer key only.
|
|
123
|
+
const hasContext = (c.cwd !== null && c.cwd !== undefined && String(c.cwd).trim().length > 0) ||
|
|
124
|
+
(c.expectedStdout !== null &&
|
|
125
|
+
c.expectedStdout !== undefined &&
|
|
126
|
+
String(c.expectedStdout).length > 0) ||
|
|
127
|
+
(typeof c.expectedExitCode === "number" && c.expectedExitCode !== 0);
|
|
128
|
+
if (hasContext)
|
|
129
|
+
return true;
|
|
130
|
+
// Default-context stated: require at least same command text among executables.
|
|
131
|
+
return !executable.some((e) => e.command === c.command);
|
|
132
|
+
});
|
|
133
|
+
if (unpromoted.length > 0) {
|
|
134
|
+
const listed = unpromoted
|
|
135
|
+
.map((c) => {
|
|
136
|
+
const bits = [`command=${JSON.stringify(c.command)}`];
|
|
137
|
+
if (c.cwd)
|
|
138
|
+
bits.push(`cwd=${JSON.stringify(c.cwd)}`);
|
|
139
|
+
if (c.expectedStdout)
|
|
140
|
+
bits.push(`expectedStdout=${JSON.stringify(c.expectedStdout)}`);
|
|
141
|
+
if (typeof c.expectedExitCode === "number" && c.expectedExitCode !== 0) {
|
|
142
|
+
bits.push(`expectedExitCode=${c.expectedExitCode}`);
|
|
143
|
+
}
|
|
144
|
+
return ` - ${bits.join(" ")} (source=${c.source})`;
|
|
145
|
+
})
|
|
146
|
+
.join("\n");
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
code: 1,
|
|
150
|
+
message: `Literal acceptance-command gate FAILED (#3267): ${unpromoted.length} stated command(s) ` +
|
|
151
|
+
`are capture-only (source=task_statement) and have no matching agent-promoted peer.\n` +
|
|
152
|
+
`Promote the exact command strings (and cwd/expectedStdout/expectedExitCode when stated) ` +
|
|
153
|
+
`into plan.metadata.swarm.verify_commands (or plan item / explicit metadata), then re-run.\n` +
|
|
154
|
+
`Note: an unrelated executable peer or a same-text peer with different context does not waive.\n` +
|
|
155
|
+
listed,
|
|
156
|
+
commands,
|
|
157
|
+
runs: [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (executable.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
code: 0,
|
|
164
|
+
message: "Literal acceptance-command gate: no executable (agent-authored) commands (#3267)",
|
|
165
|
+
commands,
|
|
166
|
+
runs: [],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const runs = [];
|
|
170
|
+
for (const cmd of executable) {
|
|
171
|
+
if (typeof cmd.command !== "string" || cmd.command.trim().length === 0) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
code: 2,
|
|
175
|
+
message: "Literal acceptance-command gate config error: empty command entry (#3267)",
|
|
176
|
+
commands,
|
|
177
|
+
runs,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
runs.push(runLiteralAcceptanceCommand(cmd, options));
|
|
181
|
+
}
|
|
182
|
+
const failed = runs.filter((r) => !r.ok);
|
|
183
|
+
if (failed.length === 0) {
|
|
184
|
+
const lines = runs.map((r) => ` ✓ ${r.command} — ${r.detail}`);
|
|
185
|
+
// Untrusted that matched an executable peer were promoted for run; no skip note needed.
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
code: 0,
|
|
189
|
+
message: `Literal acceptance-command gate passed (#3267): ${runs.length} command(s) run verbatim\n` +
|
|
190
|
+
lines.join("\n"),
|
|
191
|
+
commands,
|
|
192
|
+
runs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const lines = runs.map((r) => {
|
|
196
|
+
const mark = r.ok ? "✓" : "✗";
|
|
197
|
+
const err = r.ok
|
|
198
|
+
? r.detail
|
|
199
|
+
: `${r.detail}${r.stderr.trim().length > 0 ? `; stderr=${truncate(r.stderr, 200)}` : ""}`;
|
|
200
|
+
return ` ${mark} ${r.command} — ${err}`;
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
code: 1,
|
|
205
|
+
message: `Literal acceptance-command gate FAILED (#3267): ${failed.length}/${runs.length} command(s) did not pass.\n` +
|
|
206
|
+
`Commands must run verbatim (same flags/cwd) — self-chosen verification is not a substitute.\n` +
|
|
207
|
+
lines.join("\n"),
|
|
208
|
+
commands,
|
|
209
|
+
runs,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function truncate(s, max) {
|
|
213
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
214
|
+
return t.length <= max ? t : `${t.slice(0, max)}…`;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety filters for literal acceptance commands (#3267 Greptile P1).
|
|
3
|
+
*
|
|
4
|
+
* Stored commands may originate from issue text. Before shell execution they
|
|
5
|
+
* MUST pass an allowlist of argv-shaped CLI invocations without shell metacharacters.
|
|
6
|
+
* source=task_statement is capture-only until agent promotes to verify_commands.
|
|
7
|
+
*
|
|
8
|
+
* Wrapper verbs (task/deft/directive) and package managers are further restricted
|
|
9
|
+
* to read-only / verification subcommands so ambient credentials and lifecycle
|
|
10
|
+
* mutations cannot ride an acceptance command (#3267 residual).
|
|
11
|
+
*/
|
|
12
|
+
import type { LiteralAcceptanceSource } from "./types.js";
|
|
13
|
+
export interface CommandSafetyResult {
|
|
14
|
+
readonly ok: boolean;
|
|
15
|
+
readonly reason: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** Whether this provenance is allowed to spawn a shell (#3267). */
|
|
18
|
+
export declare function isExecutableLiteralSource(source: LiteralAcceptanceSource | string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Linear-time scan: refuse shell metacharacters and non-allowlisted first tokens.
|
|
21
|
+
* Intentionally does not parse full shell grammar — fail closed on ambiguity.
|
|
22
|
+
*/
|
|
23
|
+
export declare function evaluateCommandSafety(command: string): CommandSafetyResult;
|
|
24
|
+
//# sourceMappingURL=safety.d.ts.map
|