@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
package/dist/hooks/readonly.js
CHANGED
|
@@ -123,8 +123,8 @@ export function isAssistPosture(payload, environ = process.env) {
|
|
|
123
123
|
if (posture !== null && ASSIST_POSTURE_MARKERS.has(normalizePostureToken(posture))) {
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
|
-
// Shared taxonomy with #3080 ephemeral spawn markers.
|
|
127
|
-
if (isEphemeralSpawn(payload))
|
|
126
|
+
// Shared taxonomy with #3080 / #3259 ephemeral spawn markers.
|
|
127
|
+
if (isEphemeralSpawn(payload, environ))
|
|
128
128
|
return true;
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
@@ -176,34 +176,53 @@ function hasImplementConflictSignal(toolInput, input) {
|
|
|
176
176
|
return false;
|
|
177
177
|
}
|
|
178
178
|
/**
|
|
179
|
-
*
|
|
179
|
+
* Session assist env tokens that count as structural ephemeral markers for spawn (#3259).
|
|
180
|
+
* Reuses #1802 ASSIST_POSTURE_MARKERS / DEFT_HOOK_ASSIST — not free-text NLP.
|
|
181
|
+
*/
|
|
182
|
+
function hasSessionAssistEphemeralEnv(environ) {
|
|
183
|
+
const envPosture = (environ[ASSIST_SESSION_POSTURE_ENV] ?? "").trim();
|
|
184
|
+
if (envPosture.length > 0 && ASSIST_POSTURE_MARKERS.has(normalizePostureToken(envPosture))) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return envTruthy(environ, "DEFT_HOOK_ASSIST");
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Ephemeral / assist / docs spawns skip active-xBRIEF implementation gates (#3080 / #3259).
|
|
180
191
|
* True only with an explicit allowlisted marker. Absent marker → false (fail closed).
|
|
181
|
-
*
|
|
192
|
+
* Markers: structural `worker_role`/`subagent_type` ∈ {ephemeral,docs,assist}, OR
|
|
193
|
+
* session assist env (`DEFT_SESSION_POSTURE` assist-set / `DEFT_HOOK_ASSIST=1`) for spawn.
|
|
194
|
+
* Free-text prompt strings are never classified. When a marker conflicts with implement
|
|
195
|
+
* envelope signals, implement wins.
|
|
182
196
|
*/
|
|
183
|
-
export function isEphemeralSpawn(payload) {
|
|
197
|
+
export function isEphemeralSpawn(payload, environ = process.env) {
|
|
184
198
|
const input = record(payload);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
const toolInput = input !== null ? (toolInputRecord(input) ?? input) : null;
|
|
200
|
+
let hasMarker = false;
|
|
201
|
+
if (input !== null && toolInput !== null) {
|
|
202
|
+
const subagentType = fieldString(toolInput, "subagent_type") ??
|
|
203
|
+
fieldString(toolInput, "subagentType") ??
|
|
204
|
+
fieldString(input, "subagent_type") ??
|
|
205
|
+
fieldString(input, "subagentType");
|
|
206
|
+
const workerRole = fieldString(toolInput, "worker_role") ??
|
|
207
|
+
fieldString(toolInput, "workerRole") ??
|
|
208
|
+
fieldString(input, "worker_role") ??
|
|
209
|
+
fieldString(input, "workerRole");
|
|
210
|
+
if ((workerRole !== null && EPHEMERAL_ROLE_MARKERS.has(workerRole.toLowerCase())) ||
|
|
211
|
+
(subagentType !== null && EPHEMERAL_ROLE_MARKERS.has(subagentType.toLowerCase()))) {
|
|
212
|
+
hasMarker = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// #3259 D1-B: session assist env is a structural ephemeral marker for spawn tools.
|
|
216
|
+
// Call sites gate "spawn only"; this helper stays pure classification.
|
|
217
|
+
if (!hasMarker && hasSessionAssistEphemeralEnv(environ)) {
|
|
218
|
+
hasMarker = true;
|
|
219
|
+
}
|
|
220
|
+
if (!hasMarker)
|
|
203
221
|
return false;
|
|
204
|
-
// Implement signals win over ephemeral markers (fail closed).
|
|
205
|
-
if (hasImplementConflictSignal(toolInput, input))
|
|
222
|
+
// Implement signals win over ephemeral markers including session assist env (fail closed).
|
|
223
|
+
if (input !== null && toolInput !== null && hasImplementConflictSignal(toolInput, input)) {
|
|
206
224
|
return false;
|
|
225
|
+
}
|
|
207
226
|
return true;
|
|
208
227
|
}
|
|
209
228
|
//# sourceMappingURL=readonly.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export * as intake from "./intake/index.js";
|
|
|
35
35
|
export * as layout from "./layout/index.js";
|
|
36
36
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
37
37
|
export * as lifecycle from "./lifecycle/index.js";
|
|
38
|
+
export * as literalAcceptance from "./literal-acceptance/index.js";
|
|
38
39
|
export * as metrics from "./metrics/index.js";
|
|
39
40
|
export * as orchestration from "./orchestration/index.js";
|
|
40
41
|
export * as packs from "./packs/index.js";
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ export * as intake from "./intake/index.js";
|
|
|
36
36
|
export * as layout from "./layout/index.js";
|
|
37
37
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
38
38
|
export * as lifecycle from "./lifecycle/index.js";
|
|
39
|
+
export * as literalAcceptance from "./literal-acceptance/index.js";
|
|
39
40
|
export * as metrics from "./metrics/index.js";
|
|
40
41
|
export * as orchestration from "./orchestration/index.js";
|
|
41
42
|
export * as packs from "./packs/index.js";
|
|
@@ -5,6 +5,26 @@ export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_
|
|
|
5
5
|
export declare const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
|
|
6
6
|
export declare const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
7
7
|
export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json", ".codex/hooks.json"];
|
|
8
|
+
/**
|
|
9
|
+
* Cursor session.start / session.compact deposit timeout (seconds).
|
|
10
|
+
* Lightweight ceremony paths — keep tight so stalled hooks fail fast.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
|
|
13
|
+
/**
|
|
14
|
+
* Cursor preToolUse (tool.before) deposit timeout (seconds).
|
|
15
|
+
*
|
|
16
|
+
* Mutation tool.before runs `inspectMutationGates` → gated
|
|
17
|
+
* `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
|
|
18
|
+
* every boundary. Live readiness alone has a multi-host fixture ceiling of
|
|
19
|
+
* ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
|
|
20
|
+
* default of 5s was below that budget. Under Cursor `failClosed: true`, a
|
|
21
|
+
* host timeout kill surfaces as opaque exit-1 with no Directive decision
|
|
22
|
+
* code (#3246 / related #2864). Keep this above the readiness ceiling plus
|
|
23
|
+
* dispatch overhead so allow/deny can render within the host budget.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS = 30;
|
|
26
|
+
/** Nested Claude/Grok/Codex command-hook default timeout (seconds). */
|
|
27
|
+
export declare const NESTED_HOOK_TIMEOUT_SECONDS = 5;
|
|
8
28
|
export type AgentHookPath = (typeof AGENT_HOOK_PATHS)[number];
|
|
9
29
|
export type AgentHookRegistrationStatus = "healthy" | "disabled" | "missing" | "drifted";
|
|
10
30
|
/** Whether the host receives a compact/resume hook deposit (#2113). */
|
|
@@ -13,6 +13,26 @@ export const AGENT_HOOK_PATHS = [
|
|
|
13
13
|
".cursor/hooks.json",
|
|
14
14
|
".codex/hooks.json",
|
|
15
15
|
];
|
|
16
|
+
/**
|
|
17
|
+
* Cursor session.start / session.compact deposit timeout (seconds).
|
|
18
|
+
* Lightweight ceremony paths — keep tight so stalled hooks fail fast.
|
|
19
|
+
*/
|
|
20
|
+
export const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
|
|
21
|
+
/**
|
|
22
|
+
* Cursor preToolUse (tool.before) deposit timeout (seconds).
|
|
23
|
+
*
|
|
24
|
+
* Mutation tool.before runs `inspectMutationGates` → gated
|
|
25
|
+
* `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
|
|
26
|
+
* every boundary. Live readiness alone has a multi-host fixture ceiling of
|
|
27
|
+
* ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
|
|
28
|
+
* default of 5s was below that budget. Under Cursor `failClosed: true`, a
|
|
29
|
+
* host timeout kill surfaces as opaque exit-1 with no Directive decision
|
|
30
|
+
* code (#3246 / related #2864). Keep this above the readiness ceiling plus
|
|
31
|
+
* dispatch overhead so allow/deny can render within the host budget.
|
|
32
|
+
*/
|
|
33
|
+
export const CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS = 30;
|
|
34
|
+
/** Nested Claude/Grok/Codex command-hook default timeout (seconds). */
|
|
35
|
+
export const NESTED_HOOK_TIMEOUT_SECONDS = 5;
|
|
16
36
|
function object(value) {
|
|
17
37
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
18
38
|
? value
|
|
@@ -90,7 +110,7 @@ function nestedGroup(host, event, matcher) {
|
|
|
90
110
|
{
|
|
91
111
|
type: "command",
|
|
92
112
|
command: command(host, event),
|
|
93
|
-
timeout:
|
|
113
|
+
timeout: NESTED_HOOK_TIMEOUT_SECONDS,
|
|
94
114
|
},
|
|
95
115
|
],
|
|
96
116
|
};
|
|
@@ -154,35 +174,47 @@ function mergeCursorConfig(config, path) {
|
|
|
154
174
|
const session = eventArray(hooks, "sessionStart", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
155
175
|
const preTool = eventArray(hooks, "preToolUse", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
156
176
|
const preCompact = eventArray(hooks, "preCompact", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
157
|
-
hooks.sessionStart = [
|
|
177
|
+
hooks.sessionStart = [
|
|
178
|
+
...session,
|
|
179
|
+
{
|
|
180
|
+
command: command("cursor", "session.start"),
|
|
181
|
+
timeout: CURSOR_SESSION_HOOK_TIMEOUT_SECONDS,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
158
184
|
hooks.preToolUse = [
|
|
159
185
|
...preTool,
|
|
160
186
|
{
|
|
161
187
|
command: command("cursor", "tool.before"),
|
|
162
188
|
matcher: DIRECT_WRITE_HOOK_MATCHER,
|
|
163
189
|
failClosed: true,
|
|
164
|
-
timeout:
|
|
190
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
165
191
|
},
|
|
166
192
|
{
|
|
167
193
|
command: command("cursor", "tool.before"),
|
|
168
194
|
matcher: SPAWN_HOOK_MATCHER,
|
|
169
195
|
failClosed: true,
|
|
170
|
-
timeout:
|
|
196
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
171
197
|
},
|
|
172
198
|
{
|
|
173
199
|
command: command("cursor", "tool.before"),
|
|
174
200
|
matcher: SHELL_HOOK_MATCHER,
|
|
175
201
|
failClosed: true,
|
|
176
|
-
timeout:
|
|
202
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
177
203
|
},
|
|
178
204
|
{
|
|
179
205
|
command: command("cursor", "tool.before"),
|
|
180
206
|
matcher: MCP_HOOK_MATCHER,
|
|
181
207
|
failClosed: true,
|
|
182
|
-
timeout:
|
|
208
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
hooks.preCompact = [
|
|
212
|
+
...preCompact,
|
|
213
|
+
{
|
|
214
|
+
command: command("cursor", "session.compact"),
|
|
215
|
+
timeout: CURSOR_SESSION_HOOK_TIMEOUT_SECONDS,
|
|
183
216
|
},
|
|
184
217
|
];
|
|
185
|
-
hooks.preCompact = [...preCompact, { command: command("cursor", "session.compact"), timeout: 5 }];
|
|
186
218
|
return { ...config, version: 1, hooks };
|
|
187
219
|
}
|
|
188
220
|
function writeJsonIfChanged(projectRoot, path, payload) {
|
|
@@ -341,6 +373,14 @@ function hasNestedRegistration(config, host, options = {}) {
|
|
|
341
373
|
return (preCompact.some((entry) => nestedCommands(entry).includes(compactCommand)) &&
|
|
342
374
|
postCompact.some((entry) => nestedCommands(entry).includes(compactCommand)));
|
|
343
375
|
}
|
|
376
|
+
function isCursorToolBeforeEntry(value, matcher) {
|
|
377
|
+
const hook = object(value);
|
|
378
|
+
return (hook?.command === command("cursor", "tool.before") &&
|
|
379
|
+
hook.matcher === matcher &&
|
|
380
|
+
hook.failClosed === true &&
|
|
381
|
+
// #3246: budget must cover gated ritual + live agent-hook readiness.
|
|
382
|
+
hook.timeout === CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS);
|
|
383
|
+
}
|
|
344
384
|
function hasCursorRegistration(config) {
|
|
345
385
|
const hooks = object(config.hooks);
|
|
346
386
|
if (hooks === null || config.version !== 1)
|
|
@@ -349,30 +389,10 @@ function hasCursorRegistration(config) {
|
|
|
349
389
|
const preTool = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
|
|
350
390
|
const preCompact = Array.isArray(hooks.preCompact) ? hooks.preCompact : [];
|
|
351
391
|
return (session.some((entry) => object(entry)?.command === command("cursor", "session.start")) &&
|
|
352
|
-
preTool.some((entry) =>
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
hook.failClosed === true);
|
|
357
|
-
}) &&
|
|
358
|
-
preTool.some((entry) => {
|
|
359
|
-
const hook = object(entry);
|
|
360
|
-
return (hook?.command === command("cursor", "tool.before") &&
|
|
361
|
-
hook.matcher === SPAWN_HOOK_MATCHER &&
|
|
362
|
-
hook.failClosed === true);
|
|
363
|
-
}) &&
|
|
364
|
-
preTool.some((entry) => {
|
|
365
|
-
const hook = object(entry);
|
|
366
|
-
return (hook?.command === command("cursor", "tool.before") &&
|
|
367
|
-
hook.matcher === SHELL_HOOK_MATCHER &&
|
|
368
|
-
hook.failClosed === true);
|
|
369
|
-
}) &&
|
|
370
|
-
preTool.some((entry) => {
|
|
371
|
-
const hook = object(entry);
|
|
372
|
-
return (hook?.command === command("cursor", "tool.before") &&
|
|
373
|
-
hook.matcher === MCP_HOOK_MATCHER &&
|
|
374
|
-
hook.failClosed === true);
|
|
375
|
-
}) &&
|
|
392
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, DIRECT_WRITE_HOOK_MATCHER)) &&
|
|
393
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, SPAWN_HOOK_MATCHER)) &&
|
|
394
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, SHELL_HOOK_MATCHER)) &&
|
|
395
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, MCP_HOOK_MATCHER)) &&
|
|
376
396
|
preCompact.some((entry) => object(entry)?.command === command("cursor", "session.compact")));
|
|
377
397
|
}
|
|
378
398
|
/** Read-only registration probe shared by verify and doctor. */
|
|
@@ -4,6 +4,7 @@ import { cacheGet } from "../cache/operations.js";
|
|
|
4
4
|
import { scan } from "../cache/scanner.js";
|
|
5
5
|
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
6
6
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
7
|
+
import { captureAndAttachLiteralAcceptance } from "../literal-acceptance/index.js";
|
|
7
8
|
import { call } from "../scm/call.js";
|
|
8
9
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
9
10
|
import { resolveProjectRepo } from "../slice/project-context.js";
|
|
@@ -444,6 +445,34 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
444
445
|
}
|
|
445
446
|
plan.references = references;
|
|
446
447
|
}
|
|
448
|
+
// #3267: capture exact stated acceptance commands from the issue body at intake
|
|
449
|
+
// into plan.metadata.literal_acceptance_commands (source=task_statement, capture-only).
|
|
450
|
+
// Agents MUST promote exact strings into swarm.verify_commands before shell execution
|
|
451
|
+
// (Greptile P1: raw issue text must not auto-spawn). Not paraphrased.
|
|
452
|
+
if (bodyStr.length > 0) {
|
|
453
|
+
const intakeText = [title, bodyStr].filter((s) => s.length > 0).join("\n\n");
|
|
454
|
+
const attached = captureAndAttachLiteralAcceptance(plan, intakeText);
|
|
455
|
+
// Re-tag stored capture as task_statement so run refuses until promote.
|
|
456
|
+
const meta = attached.plan.metadata;
|
|
457
|
+
if (meta !== undefined && Array.isArray(meta.literal_acceptance_commands)) {
|
|
458
|
+
meta.literal_acceptance_commands = meta.literal_acceptance_commands.map((row) => ({ ...row, source: "task_statement" }));
|
|
459
|
+
// Do not copy untrusted issue text into swarm.verify_commands (executable).
|
|
460
|
+
const swarm = meta.swarm;
|
|
461
|
+
if (swarm !== undefined && Array.isArray(swarm.verify_commands)) {
|
|
462
|
+
// Keep only pre-existing verify_commands that were not just attached from capture.
|
|
463
|
+
// captureAndAttach merges captured into verify_commands — strip those that match
|
|
464
|
+
// task_statement captures so issue text cannot auto-execute.
|
|
465
|
+
const capturedSet = new Set(meta.literal_acceptance_commands
|
|
466
|
+
.map((r) => (typeof r.command === "string" ? r.command : ""))
|
|
467
|
+
.filter((s) => s.length > 0));
|
|
468
|
+
swarm.verify_commands = swarm.verify_commands.filter((c) => typeof c === "string" && !capturedSet.has(c));
|
|
469
|
+
if (swarm.verify_commands.length === 0) {
|
|
470
|
+
delete swarm.verify_commands;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
Object.assign(plan, attached.plan);
|
|
475
|
+
}
|
|
447
476
|
const infoRootKey = options.infoRootKey ?? LEGACY_INFO_ROOT_KEY;
|
|
448
477
|
const infoVersion = options.infoVersion ?? EMITTED_VBRIEF_VERSION;
|
|
449
478
|
const briefLabel = infoRootKey === MIGRATED_INFO_ROOT_KEY ? "xBRIEF" : "vBRIEF";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Item statuses that still represent unfinished checklist work (#2862 / #3240 / #3242). */
|
|
2
|
+
export declare const NON_TERMINAL_ITEM_STATUSES: Set<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Terminal checklist statuses for completed-folder fail-closed (#3242).
|
|
5
|
+
* Align with #3240 non-terminal set: any valid item status that is not
|
|
6
|
+
* pending|proposed|running is terminal; unknown/invalid statuses fail closed.
|
|
7
|
+
*/
|
|
8
|
+
export declare const TERMINAL_ITEM_STATUSES: Set<string>;
|
|
9
|
+
/** Statuses allowed under completed/ (D2/D13). */
|
|
10
|
+
export declare const COMPLETED_FOLDER_PLAN_STATUSES: ReadonlySet<string>;
|
|
11
|
+
export type CompletedConsistencyKind = "status_mismatch" | "open_items" | "unreadable";
|
|
12
|
+
export interface OpenItemHit {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly status: string;
|
|
15
|
+
readonly title: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CompletedConsistencyFinding {
|
|
18
|
+
/** Lifecycle-relative path, e.g. `completed/foo.xbrief.json`. */
|
|
19
|
+
readonly relPath: string;
|
|
20
|
+
readonly planStatus: string;
|
|
21
|
+
readonly folder: "completed";
|
|
22
|
+
readonly kind: CompletedConsistencyKind;
|
|
23
|
+
/** Human detail including exact path + statuses. */
|
|
24
|
+
readonly detail: string;
|
|
25
|
+
readonly openItems?: readonly OpenItemHit[];
|
|
26
|
+
}
|
|
27
|
+
export interface CompletedConsistencyResult {
|
|
28
|
+
readonly ok: boolean;
|
|
29
|
+
readonly findings: readonly CompletedConsistencyFinding[];
|
|
30
|
+
readonly message: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Collect non-terminal plan.items / subItems / nested items.
|
|
34
|
+
* Fail-closed on malformed slots: non-object entries and present non-array
|
|
35
|
+
* nested collections are reported so completed corpus cannot green-skip them.
|
|
36
|
+
*/
|
|
37
|
+
export declare function collectOpenPlanItems(items: unknown, pathPrefix?: string): OpenItemHit[];
|
|
38
|
+
/**
|
|
39
|
+
* Evaluate Q4 consistency for a single plan that claims completed lifecycle.
|
|
40
|
+
*
|
|
41
|
+
* @param plan - plan object
|
|
42
|
+
* @param options.relPath - artifact path for messages (default `plan`)
|
|
43
|
+
* @param options.requireStatus - when set, plan.status must equal this exactly
|
|
44
|
+
* (scope:complete uses `completed`). When omitted, any completed-folder
|
|
45
|
+
* terminal status (completed|failed) is accepted.
|
|
46
|
+
*/
|
|
47
|
+
export declare function evaluateCompletedPlanConsistency(plan: Record<string, unknown>, options?: {
|
|
48
|
+
relPath?: string;
|
|
49
|
+
requireStatus?: string;
|
|
50
|
+
}): CompletedConsistencyResult;
|
|
51
|
+
/** Format fail-closed doctor/complete message with exact paths and statuses. */
|
|
52
|
+
export declare function formatCompletedConsistencyFailure(findings: readonly CompletedConsistencyFinding[]): string;
|
|
53
|
+
/**
|
|
54
|
+
* Scan `xbrief/completed/` and/or legacy `vbrief/completed/` for Q4 mismatches.
|
|
55
|
+
* Offline / filesystem only. Missing completed/ is green (nothing to check).
|
|
56
|
+
* Malformed completed artifacts and unreadable roots fail closed (no silent skip).
|
|
57
|
+
* Mixed roots scan both.
|
|
58
|
+
*/
|
|
59
|
+
export declare function scanCompletedLifecycleConsistency(projectRoot: string): CompletedConsistencyResult;
|
|
60
|
+
//# sourceMappingURL=completed-consistency.d.ts.map
|