@deftai/directive 0.120.0 → 0.122.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.js +45 -2
- package/dist/cli-router/route-argv.js +1 -0
- package/dist/dispatch.d.ts +2 -2
- package/dist/dispatch.js +16 -0
- package/dist/hook-dispatch.js +4 -0
- package/dist/policy.d.ts +4 -2
- package/dist/policy.js +100 -2
- package/dist/render-cli/spec-reconstruct-cli.d.ts +3 -0
- package/dist/render-cli/spec-reconstruct-cli.js +15 -0
- package/dist/subagent-pre-cancel.d.ts +21 -0
- package/dist/subagent-pre-cancel.js +240 -0
- package/dist/subagent-steer-write.d.ts +41 -0
- package/dist/subagent-steer-write.js +333 -0
- package/dist/verify-cohort-review-monitors.d.ts +5 -0
- package/dist/verify-cohort-review-monitors.js +11 -0
- package/dist/verify-review-monitor.d.ts +2 -0
- package/dist/verify-review-monitor.js +66 -1
- package/dist/verify-source-cli/verify-spec-drift.d.ts +3 -0
- package/dist/verify-source-cli/verify-spec-drift.js +15 -0
- package/dist/verify-story-ready.js +5 -2
- package/dist/verify-subagent-alive.js +17 -1
- package/package.json +3 -3
package/dist/authz.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* deft authz:grant -- --operations edit,push --surfaces 'src/**' --cohort <id> ... [--confirm]
|
|
11
11
|
* deft authz:grant -- --template release-publish --target 0.30.0 [--confirm]
|
|
12
12
|
* deft authz:grant -- --template finish-loop [--confirm]
|
|
13
|
+
* deft authz:grant -- --template spec-drift-override --target <baseline> --plan-ref <scopeId> --story-ids <ids> [--confirm]
|
|
13
14
|
* deft authz:grant -- --parent <parent.xbrief.json> --draft <draft.json> [--repo owner/name] [--confirm]
|
|
14
15
|
* deft authz:revoke -- <grant-id> [--confirm]
|
|
15
16
|
*
|
|
@@ -28,6 +29,7 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
28
29
|
import { onePrUnit } from "@deftai/directive-core";
|
|
29
30
|
import { AFK_TEMPLATE_NAMES, AUTHZ_OPERATIONS, CLOSED_VERB_TEMPLATE_NAMES, FINISH_LOOP_TEMPLATE_NAME, formatDecomposeStructuralMintCommand, isAfkTemplateName, isClosedVerbTemplateName, isFinishLoopTemplateName, mintAfkTemplateGrant, mintDecomposeStructuralApplyGrant, mintHumanOriginGrant, revokeGrant, showAuthzSnapshot, startUatLease, suspendUatLease, toProjectRelativePosix, } from "@deftai/directive-core/authz";
|
|
30
31
|
import { uatCampaignEndSeal } from "@deftai/directive-core/authz/campaign-end-seal";
|
|
32
|
+
import { isSpecDriftOverrideTemplateName, mintSpecDriftOverrideTemplateGrant, SPEC_DRIFT_OVERRIDE_TEMPLATE_NAME, } from "@deftai/directive-core/policy";
|
|
31
33
|
import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
|
|
32
34
|
export { AUTHZ_AGENT_SHELL_ENV_MARKERS, AUTHZ_INTERACTIVE_CONFIRM_PHRASE, } from "./human-presence-mint.js";
|
|
33
35
|
function parseOps(raw) {
|
|
@@ -479,10 +481,51 @@ export function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
479
481
|
process.stdout.write(` origins=${claim.origins.map((o) => `${o.repo}#${o.issueId}`).join(",")}\n`);
|
|
480
482
|
return 0;
|
|
481
483
|
}
|
|
484
|
+
// Spec-drift override hatch (#5350): policy module, not authz/** (class 4).
|
|
485
|
+
if (args.template !== null && isSpecDriftOverrideTemplateName(args.template)) {
|
|
486
|
+
if (args.target === null || args.target.trim().length === 0) {
|
|
487
|
+
process.stderr.write("authz:grant --template spec-drift-override requires --target <baselineRevision>\n");
|
|
488
|
+
return 2;
|
|
489
|
+
}
|
|
490
|
+
if (args.planRef === null || args.planRef.trim().length === 0) {
|
|
491
|
+
process.stderr.write("authz:grant --template spec-drift-override requires --plan-ref <scopeId>\n");
|
|
492
|
+
return 2;
|
|
493
|
+
}
|
|
494
|
+
if (args.storyIds.length === 0) {
|
|
495
|
+
process.stderr.write("authz:grant --template spec-drift-override requires --story-ids <covered-item-ids>\n");
|
|
496
|
+
return 2;
|
|
497
|
+
}
|
|
498
|
+
const blockedOverride = gateConfirm();
|
|
499
|
+
if (blockedOverride !== null)
|
|
500
|
+
return blockedOverride;
|
|
501
|
+
const mintedOverride = mintSpecDriftOverrideTemplateGrant({
|
|
502
|
+
projectRoot: args.projectRoot,
|
|
503
|
+
target: args.target,
|
|
504
|
+
planRef: args.planRef,
|
|
505
|
+
storyIds: args.storyIds,
|
|
506
|
+
actor: args.actor,
|
|
507
|
+
expiresAt: args.expiresAt,
|
|
508
|
+
singleUse: args.singleUse,
|
|
509
|
+
repo: args.repo,
|
|
510
|
+
branch: args.branch,
|
|
511
|
+
});
|
|
512
|
+
if (!mintedOverride.ok) {
|
|
513
|
+
process.stderr.write(`authz:grant: ${mintedOverride.reason}\n`);
|
|
514
|
+
return 2;
|
|
515
|
+
}
|
|
516
|
+
const overrideGrant = mintedOverride.grant;
|
|
517
|
+
process.stdout.write(`✓ human-origin grant minted id=${overrideGrant.id} origin=${overrideGrant.origin.kind} ` +
|
|
518
|
+
`template=${SPEC_DRIFT_OVERRIDE_TEMPLATE_NAME}\n`);
|
|
519
|
+
process.stdout.write(` ops=[${overrideGrant.scope.operations.join(",")}] scope=${overrideGrant.scope.planRef} ` +
|
|
520
|
+
`items=${overrideGrant.scope.storyIds.join(",")} surfaces=${overrideGrant.scope.surfaces.join(", ")}\n`);
|
|
521
|
+
process.stdout.write(" Hatch: discharges rewrite/coverage refuse for bound ids only under enforce; single-use preferred.\n");
|
|
522
|
+
process.stdout.write(" Authorization SoT: Wave 1 grant store (.deft/authz/grants) — not session-auth.\n");
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
482
525
|
// AFK template path (#1095 / #871): presets only — still mintHumanOriginGrant.
|
|
483
526
|
if (args.template !== null && args.template.trim().length > 0) {
|
|
484
527
|
if (!isAfkTemplateName(args.template)) {
|
|
485
|
-
process.stderr.write(`authz:grant unknown --template '${args.template}'; expected one of: ${AFK_TEMPLATE_NAMES.join(", ")}\n`);
|
|
528
|
+
process.stderr.write(`authz:grant unknown --template '${args.template}'; expected one of: ${AFK_TEMPLATE_NAMES.join(", ")}, ${SPEC_DRIFT_OVERRIDE_TEMPLATE_NAME}\n`);
|
|
486
529
|
return 2;
|
|
487
530
|
}
|
|
488
531
|
if (isClosedVerbTemplateName(args.template) &&
|
|
@@ -526,7 +569,7 @@ export function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
526
569
|
return 0;
|
|
527
570
|
}
|
|
528
571
|
if (args.operations.length === 0) {
|
|
529
|
-
process.stderr.write("authz:grant requires --operations <edit,push,...>, --template <finish-loop|release
|
|
572
|
+
process.stderr.write("authz:grant requires --operations <edit,push,...>, --template <finish-loop|release-*|spec-drift-override>, " +
|
|
530
573
|
"or --parent + --draft (structural scope:decompose apply)\n");
|
|
531
574
|
return 2;
|
|
532
575
|
}
|
|
@@ -89,6 +89,7 @@ export const SUBCOMMAND_ROUTES = {
|
|
|
89
89
|
"policy:enable-value-feedback": ["policy", "enable-value-feedback"],
|
|
90
90
|
"policy:disable-host-hooks": ["policy", "disable-host-hooks"],
|
|
91
91
|
"policy:set-ceremony-dial": ["policy", "set-ceremony-dial"],
|
|
92
|
+
"policy:set-spec-guard-enforcement": ["policy", "set-spec-guard-enforcement"],
|
|
92
93
|
"policy:clear-value-feedback": ["policy", "clear-value-feedback"],
|
|
93
94
|
"authz:show": ["authz", "show"],
|
|
94
95
|
"authz:uat-start": ["authz", "uat-start"],
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export interface DispatchIo {
|
|
|
10
10
|
writeErr: (text: string) => void;
|
|
11
11
|
}
|
|
12
12
|
/** CLI modules in packages/cli/src (excluding parity harnesses and bin/index). */
|
|
13
|
-
export declare const CLI_MODULE_VERBS: readonly ["agents-refresh", "cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "ownership-doctor", "ownership-fix", "verify-ownership", "install-upgrade", "install-uninstall", "migrate-preflight", "migrate-xbrief", "migrate-category-b", "framework-check-updates", "hook-dispatch", "umbrella-current-shape", "changelog-check", "change-init", "commit-lint", "coverage-hotspots", "policy", "authz", "escalation-cli", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "pr-watch", "pr-finish-loop", "directive-finish-loop", "pre-pr-run", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "release-wait-npm", "scope-lifecycle", "scope-record-approved-scope", "scope-record-observable-scope", "scope-record-intent-constraint", "scm-sync-default", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "occupancy-steal", "occupancy-release", "occupancy-heartbeat", "occupancy-grant", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-strip-withdrawn", "triage-evaluate", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "xbrief-create", "xbrief-verify", "xbrief-adopt-stored-plan-id", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-test-boundary", "verify-class-checks", "verify-presentation-coverage", "verify-scope-provenance", "verify-consumer-check-contract", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-biome-config", "verify-bridge-drift", "verify-capacity", "verify-contained-writes", "verify-telemetry-coverage", "verify-content-manifest", "verify-deposit-closure", "verify-consumer-hard-stops", "verify-closing-keywords", "verify-skill-external-fetch-gate", "verify-semantic-single-source", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "verify-spec-prd-fresh", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "verify-subagent-steer", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-pr-closeout-attestable", "verify-lifecycle-visible", "verify-completed-tracked", "verify-completed-write-guard", "verify-literal-ac", "verify-ac", "verify-agents-md-budget", "verify-consumer-header-placeholder", "verify-agents-md-advisory", "verify-persisted-planning-narratives", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "verify-evaluator-surface", "verify-observable-scope", "verify-operator-scope-limit", "verify-presentation-ceiling", "verify-intent-constraint", "verify-durable-effect-acquisition", "verify-consumer-test-lane", "eval-health", "eval-run", "eval-report", "eval-triggers"];
|
|
13
|
+
export declare const CLI_MODULE_VERBS: readonly ["agents-refresh", "cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "ownership-doctor", "ownership-fix", "verify-ownership", "install-upgrade", "install-uninstall", "migrate-preflight", "migrate-xbrief", "migrate-category-b", "framework-check-updates", "hook-dispatch", "umbrella-current-shape", "changelog-check", "change-init", "commit-lint", "coverage-hotspots", "policy", "authz", "escalation-cli", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "pr-watch", "pr-finish-loop", "directive-finish-loop", "pre-pr-run", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "release-wait-npm", "scope-lifecycle", "scope-record-approved-scope", "scope-record-observable-scope", "scope-record-intent-constraint", "scm-sync-default", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "occupancy-steal", "occupancy-release", "occupancy-heartbeat", "occupancy-grant", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-strip-withdrawn", "triage-evaluate", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "xbrief-create", "xbrief-verify", "xbrief-adopt-stored-plan-id", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-test-boundary", "verify-class-checks", "verify-presentation-coverage", "verify-scope-provenance", "verify-consumer-check-contract", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-biome-config", "verify-bridge-drift", "verify-capacity", "verify-contained-writes", "verify-telemetry-coverage", "verify-content-manifest", "verify-deposit-closure", "verify-consumer-hard-stops", "verify-closing-keywords", "verify-skill-external-fetch-gate", "verify-semantic-single-source", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "verify-spec-prd-fresh", "verify-spec-drift", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-cohort-review-monitors", "verify-l4-owner", "verify-subagent-alive", "verify-subagent-steer", "subagent-pre-cancel", "subagent-steer-write", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-pr-closeout-attestable", "verify-lifecycle-visible", "verify-completed-tracked", "verify-completed-write-guard", "verify-literal-ac", "verify-ac", "verify-agents-md-budget", "verify-consumer-header-placeholder", "verify-agents-md-advisory", "verify-persisted-planning-narratives", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "verify-evaluator-surface", "verify-observable-scope", "verify-operator-scope-limit", "verify-presentation-ceiling", "verify-intent-constraint", "verify-durable-effect-acquisition", "verify-consumer-test-lane", "eval-health", "eval-run", "eval-report", "eval-triggers"];
|
|
14
14
|
/** Core-only CLI entrypoints without a packages/cli wrapper. */
|
|
15
|
-
export declare const CORE_MODULE_VERBS: readonly ["scm", "scm-readiness", "github-auth-modes", "github-body", "issue-emit", "issue-ingest", "issue-sync-from-xbrief", "reconcile-issues", "swarm-launch", "swarm-complete-cohort", "swarm-finalize-cohort", "swarm-finalize-owed", "swarm-pre-dispatch", "swarm-readiness", "swarm-routing-verify", "swarm-routing-set", "swarm-verify-review-clean", "swarm-worktrees", "framework-commands", "pack-render", "packs-slice", "prd-render", "export-spec", "project-render", "project-write-narratives", "roadmap-render", "rule-map", "spec-render", "spec-validate", "code-structure-validate", "pack-migrate-skills", "pack-migrate-rules", "pack-migrate-strategies", "pack-migrate-patterns", "pack-migrate-swarm-spec", "policy-set", "setup-ghx", "scope-undo", "scope-demote", "scope-decompose", "changelog-resolve-unreleased", "architecture-preflight-sor", "feedback-file", "value-readback", "product-signal", "freshness-report", "decision-write", "decision-list", "docs-impact", "migrate-clause-ids"];
|
|
15
|
+
export declare const CORE_MODULE_VERBS: readonly ["scm", "scm-readiness", "github-auth-modes", "github-body", "issue-emit", "issue-ingest", "issue-sync-from-xbrief", "reconcile-issues", "swarm-launch", "swarm-complete-cohort", "swarm-finalize-cohort", "swarm-finalize-owed", "swarm-pre-dispatch", "swarm-readiness", "swarm-routing-verify", "swarm-routing-set", "swarm-verify-review-clean", "swarm-worktrees", "framework-commands", "pack-render", "packs-slice", "prd-render", "export-spec", "project-render", "project-write-narratives", "roadmap-render", "rule-map", "spec-render", "spec-reconstruct", "spec-validate", "code-structure-validate", "pack-migrate-skills", "pack-migrate-rules", "pack-migrate-strategies", "pack-migrate-patterns", "pack-migrate-swarm-spec", "policy-set", "setup-ghx", "scope-undo", "scope-demote", "scope-decompose", "changelog-resolve-unreleased", "architecture-preflight-sor", "feedback-file", "value-readback", "product-signal", "freshness-report", "decision-write", "decision-list", "docs-impact", "migrate-clause-ids"];
|
|
16
16
|
/** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
|
|
17
17
|
export declare const TRIAGE_ACTION_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
|
|
18
18
|
/** Colon aliases for policy subcommands (mirrors cli-router SUBCOMMAND_ROUTES). */
|
package/dist/dispatch.js
CHANGED
|
@@ -151,12 +151,16 @@ export const CLI_MODULE_VERBS = [
|
|
|
151
151
|
"verify-stubs",
|
|
152
152
|
"verify-xbrief-drift",
|
|
153
153
|
"verify-spec-prd-fresh",
|
|
154
|
+
"verify-spec-drift",
|
|
154
155
|
"rule-ownership-lint",
|
|
155
156
|
"verify-story-ready",
|
|
156
157
|
"verify-review-monitor",
|
|
158
|
+
"verify-cohort-review-monitors",
|
|
157
159
|
"verify-l4-owner",
|
|
158
160
|
"verify-subagent-alive",
|
|
159
161
|
"verify-subagent-steer",
|
|
162
|
+
"subagent-pre-cancel",
|
|
163
|
+
"subagent-steer-write",
|
|
160
164
|
"review-monitor-register",
|
|
161
165
|
"review-monitor-release",
|
|
162
166
|
"verify-tools",
|
|
@@ -216,6 +220,7 @@ export const CORE_MODULE_VERBS = [
|
|
|
216
220
|
"roadmap-render",
|
|
217
221
|
"rule-map",
|
|
218
222
|
"spec-render",
|
|
223
|
+
"spec-reconstruct",
|
|
219
224
|
"spec-validate",
|
|
220
225
|
"code-structure-validate",
|
|
221
226
|
"pack-migrate-skills",
|
|
@@ -260,6 +265,7 @@ export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
|
|
|
260
265
|
"policy:enforce-destructive-gh-verbs": "enforce-destructive-gh-verbs",
|
|
261
266
|
"policy:allow-bot-merge": "allow-bot-merge",
|
|
262
267
|
"policy:enable-value-feedback": "enable-value-feedback",
|
|
268
|
+
"policy:set-spec-guard-enforcement": "set-spec-guard-enforcement",
|
|
263
269
|
"policy:disable-host-hooks": "disable-host-hooks",
|
|
264
270
|
"policy:set-ceremony-dial": "set-ceremony-dial",
|
|
265
271
|
"policy:clear-value-feedback": "clear-value-feedback",
|
|
@@ -365,9 +371,12 @@ export const VERB_ALIASES = {
|
|
|
365
371
|
"vbrief:activate": "vbrief-activate",
|
|
366
372
|
"verify:story-ready": "verify-story-ready",
|
|
367
373
|
"verify:review-monitor": "verify-review-monitor",
|
|
374
|
+
"verify:cohort-review-monitors": "verify-cohort-review-monitors",
|
|
368
375
|
"verify:l4-owner": "verify-l4-owner",
|
|
369
376
|
"verify:subagent-alive": "verify-subagent-alive",
|
|
370
377
|
"verify:subagent-steer": "verify-subagent-steer",
|
|
378
|
+
"subagent:pre-cancel": "subagent-pre-cancel",
|
|
379
|
+
"subagent:steer": "subagent-steer-write",
|
|
371
380
|
"agent:monitor": "subagent-monitor",
|
|
372
381
|
"review-monitor:register": "review-monitor-register",
|
|
373
382
|
"review-monitor:release": "review-monitor-release",
|
|
@@ -397,6 +406,7 @@ export const VERB_ALIASES = {
|
|
|
397
406
|
"verify:scm-boundary": "verify-scm-boundary",
|
|
398
407
|
"verify:xbrief-drift": "verify-xbrief-drift",
|
|
399
408
|
"verify:spec-prd-fresh": "verify-spec-prd-fresh",
|
|
409
|
+
"verify:spec-drift": "verify-spec-drift",
|
|
400
410
|
"verify:capacity": "verify-capacity",
|
|
401
411
|
"verify:session-ritual": "verify-session-ritual",
|
|
402
412
|
"verify:plan-sequence": "verify-plan-sequence",
|
|
@@ -439,6 +449,7 @@ export const VERB_ALIASES = {
|
|
|
439
449
|
"ts:check-lane": "ts-check-lane",
|
|
440
450
|
"spec:validate": "spec-validate",
|
|
441
451
|
"spec:render": "spec-render",
|
|
452
|
+
"spec:reconstruct": "spec-reconstruct",
|
|
442
453
|
"prd:render": "prd-render",
|
|
443
454
|
"project:render": "project-render",
|
|
444
455
|
"project:write-narratives": "project-write-narratives",
|
|
@@ -489,6 +500,7 @@ const SUBDIR_CLI_STEMS = {
|
|
|
489
500
|
"verify-scm-boundary": "verify-source-cli/verify-scm-boundary",
|
|
490
501
|
"verify-xbrief-drift": "verify-source-cli/verify-xbrief-drift",
|
|
491
502
|
"verify-spec-prd-fresh": "verify-source-cli/verify-spec-prd-fresh",
|
|
503
|
+
"verify-spec-drift": "verify-source-cli/verify-spec-drift",
|
|
492
504
|
"verify-go-freeze": "gates-cli/verify-go-freeze",
|
|
493
505
|
"verify-bridge-drift": "gates-cli/verify-bridge-drift",
|
|
494
506
|
"validate-links": "content-validate-cli/validate-links",
|
|
@@ -2529,6 +2541,10 @@ async function loadCoreModuleHandler(verb, io) {
|
|
|
2529
2541
|
const { runSpecValidateCli } = await import("./render-cli/spec-validate-cli.js");
|
|
2530
2542
|
return (argv) => runSpecValidateCli(argv);
|
|
2531
2543
|
}
|
|
2544
|
+
case "spec-reconstruct": {
|
|
2545
|
+
const { runSpecReconstructCliEntry } = await import("./render-cli/spec-reconstruct-cli.js");
|
|
2546
|
+
return (argv) => runSpecReconstructCliEntry(argv);
|
|
2547
|
+
}
|
|
2532
2548
|
case "spec-render": {
|
|
2533
2549
|
const { runSpecRenderCli } = await import("./render-cli/spec-render-cli.js");
|
|
2534
2550
|
return (argv) => runSpecRenderCli(argv);
|
package/dist/hook-dispatch.js
CHANGED
|
@@ -156,6 +156,10 @@ export function run(argv, seams = {}) {
|
|
|
156
156
|
const rendered = renderHostDecision(args.host, decision);
|
|
157
157
|
if (rendered.length > 0)
|
|
158
158
|
writeOut(`${rendered}\n`);
|
|
159
|
+
// Force-kill allow must print the non-empty reason even when host allow wire is empty (#5281).
|
|
160
|
+
if (decision.code === "kill-force-ready" && decision.message.trim().length > 0) {
|
|
161
|
+
writeErr(`${decision.message}\n`);
|
|
162
|
+
}
|
|
159
163
|
if ((decision.code === "invalid-input" || decision.code === "stdin-empty") &&
|
|
160
164
|
args.host === "cursor") {
|
|
161
165
|
// Keys are already embedded in decision.message; stderr helps operators tailing logs.
|
package/dist/policy.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type CeremonyDepth, type HookHost } from "@deftai/directive-core/policy";
|
|
2
|
+
import { type CeremonyDepth, type HookHost, type SpecGuardEnforcement } from "@deftai/directive-core/policy";
|
|
3
3
|
interface ShowArgs {
|
|
4
4
|
format: "text" | "json";
|
|
5
5
|
changedOnly: boolean;
|
|
@@ -8,7 +8,7 @@ interface ShowArgs {
|
|
|
8
8
|
error?: string;
|
|
9
9
|
}
|
|
10
10
|
interface SetArgs {
|
|
11
|
-
cmd: "show" | "enforce-branches" | "allow-direct-commits" | "allow-destructive-gh-verbs" | "enforce-destructive-gh-verbs" | "allow-bot-merge" | "enable-value-feedback" | "clear-value-feedback" | "set-ceremony-dial" | "disable-host-hooks" | "disable-directive" | "enable-directive" | "resolve";
|
|
11
|
+
cmd: "show" | "enforce-branches" | "allow-direct-commits" | "allow-destructive-gh-verbs" | "enforce-destructive-gh-verbs" | "allow-bot-merge" | "enable-value-feedback" | "clear-value-feedback" | "set-ceremony-dial" | "set-spec-guard-enforcement" | "disable-host-hooks" | "disable-directive" | "enable-directive" | "resolve";
|
|
12
12
|
confirm: boolean;
|
|
13
13
|
actor: string;
|
|
14
14
|
note: string;
|
|
@@ -21,6 +21,8 @@ interface SetArgs {
|
|
|
21
21
|
ceremonyOverride?: CeremonyDepth | null;
|
|
22
22
|
/** #3214 ceremony dial enabled flag for set-ceremony-dial. */
|
|
23
23
|
ceremonyEnabled?: boolean;
|
|
24
|
+
/** #5350 driftGuard.enforcement target for set-spec-guard-enforcement. */
|
|
25
|
+
specGuardEnforcement?: SpecGuardEnforcement;
|
|
24
26
|
error?: string;
|
|
25
27
|
}
|
|
26
28
|
/** Parse policy:show flags (mirrors _policy_show_cli.py). */
|
package/dist/policy.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { resolve as pathResolve, relative } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { ALLOW_BOT_MERGE_CAPABILITY_COST, ALLOW_DESTRUCTIVE_GH_VERBS_CAPABILITY_COST, CEREMONY_DEPTHS, clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disableHostHooks, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, humanMergeDisclosureLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, POLICY_AUDIT_NOOP_STDOUT, parseHookHost, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, setAllowDestructiveGhVerbs, setCeremonyDial, setPolicy, setRequireHumanMerge, } from "@deftai/directive-core/policy";
|
|
9
|
+
import { ALLOW_BOT_MERGE_CAPABILITY_COST, ALLOW_DESTRUCTIVE_GH_VERBS_CAPABILITY_COST, CEREMONY_DEPTHS, clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disableHostHooks, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, humanMergeDisclosureLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, POLICY_AUDIT_NOOP_STDOUT, parseHookHost, policyColonInvocation, projectDefinitionPath, promoteSpecGuardDriftEnforcement, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, SPEC_GUARD_ENFORCEMENTS, setAllowDestructiveGhVerbs, setCeremonyDial, setPolicy, setRequireHumanMerge, } from "@deftai/directive-core/policy";
|
|
10
10
|
const CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
|
|
11
11
|
"branch turns OFF the deft branch-protection policy.\n" +
|
|
12
12
|
" \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
|
|
@@ -114,10 +114,87 @@ export function parseShowArgs(argv) {
|
|
|
114
114
|
/** Parse argv for the policy CLI (show + set subcommands). */
|
|
115
115
|
export function parseArgs(argv) {
|
|
116
116
|
if (argv.length === 0) {
|
|
117
|
-
const usage = "usage: policy [show|enforce-branches|allow-direct-commits|allow-destructive-gh-verbs|enforce-destructive-gh-verbs|allow-bot-merge|enable-value-feedback|clear-value-feedback|set-ceremony-dial|disable-host-hooks|disable-directive|enable-directive|resolve] ...";
|
|
117
|
+
const usage = "usage: policy [show|enforce-branches|allow-direct-commits|allow-destructive-gh-verbs|enforce-destructive-gh-verbs|allow-bot-merge|enable-value-feedback|clear-value-feedback|set-ceremony-dial|set-spec-guard-enforcement|disable-host-hooks|disable-directive|enable-directive|resolve] ...";
|
|
118
118
|
return makeSetError(usage);
|
|
119
119
|
}
|
|
120
120
|
const cmd = argv[0];
|
|
121
|
+
if (cmd === "set-spec-guard-enforcement") {
|
|
122
|
+
let confirm = false;
|
|
123
|
+
let actor = policyColonInvocation("set-spec-guard-enforcement");
|
|
124
|
+
let note = "";
|
|
125
|
+
let projectRoot = ".";
|
|
126
|
+
let specGuardEnforcement;
|
|
127
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
128
|
+
const arg = argv[i];
|
|
129
|
+
if (arg === "--confirm") {
|
|
130
|
+
confirm = true;
|
|
131
|
+
}
|
|
132
|
+
else if (arg === "--actor") {
|
|
133
|
+
const v = argv[i + 1];
|
|
134
|
+
if (v === undefined)
|
|
135
|
+
return makeSetError("argument --actor: expected one argument");
|
|
136
|
+
actor = v;
|
|
137
|
+
i += 1;
|
|
138
|
+
}
|
|
139
|
+
else if (arg?.startsWith("--actor=")) {
|
|
140
|
+
actor = arg.slice("--actor=".length);
|
|
141
|
+
}
|
|
142
|
+
else if (arg === "--note") {
|
|
143
|
+
const v = argv[i + 1];
|
|
144
|
+
if (v === undefined)
|
|
145
|
+
return makeSetError("argument --note: expected one argument");
|
|
146
|
+
note = v;
|
|
147
|
+
i += 1;
|
|
148
|
+
}
|
|
149
|
+
else if (arg?.startsWith("--note=")) {
|
|
150
|
+
note = arg.slice("--note=".length);
|
|
151
|
+
}
|
|
152
|
+
else if (arg === "--set") {
|
|
153
|
+
const v = argv[i + 1];
|
|
154
|
+
if (v === undefined) {
|
|
155
|
+
return makeSetError("argument --set: expected advise|shadow|enforce");
|
|
156
|
+
}
|
|
157
|
+
if (!SPEC_GUARD_ENFORCEMENTS.has(v)) {
|
|
158
|
+
return makeSetError(`argument --set: expected advise|shadow|enforce, got ${JSON.stringify(v)}`);
|
|
159
|
+
}
|
|
160
|
+
specGuardEnforcement = v;
|
|
161
|
+
i += 1;
|
|
162
|
+
}
|
|
163
|
+
else if (arg?.startsWith("--set=")) {
|
|
164
|
+
const v = arg.slice("--set=".length);
|
|
165
|
+
if (!SPEC_GUARD_ENFORCEMENTS.has(v)) {
|
|
166
|
+
return makeSetError(`argument --set: expected advise|shadow|enforce, got ${JSON.stringify(v)}`);
|
|
167
|
+
}
|
|
168
|
+
specGuardEnforcement = v;
|
|
169
|
+
}
|
|
170
|
+
else if (arg === "--project-root") {
|
|
171
|
+
const v = argv[i + 1];
|
|
172
|
+
if (v === undefined)
|
|
173
|
+
return makeSetError("argument --project-root: expected one argument");
|
|
174
|
+
projectRoot = v;
|
|
175
|
+
i += 1;
|
|
176
|
+
}
|
|
177
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
178
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
179
|
+
}
|
|
180
|
+
else if (arg === "--") {
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
return makeSetError(`unrecognized argument: ${arg}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
cmd: "set-spec-guard-enforcement",
|
|
188
|
+
confirm,
|
|
189
|
+
actor,
|
|
190
|
+
note,
|
|
191
|
+
projectRoot,
|
|
192
|
+
format: "text",
|
|
193
|
+
changedOnly: false,
|
|
194
|
+
field: null,
|
|
195
|
+
specGuardEnforcement,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
121
198
|
if (cmd === "show") {
|
|
122
199
|
const show = parseShowArgs(argv.slice(1));
|
|
123
200
|
return {
|
|
@@ -608,6 +685,9 @@ export function run(argv) {
|
|
|
608
685
|
if (args.cmd === "set-ceremony-dial") {
|
|
609
686
|
return runSetCeremonyDial(args);
|
|
610
687
|
}
|
|
688
|
+
if (args.cmd === "set-spec-guard-enforcement") {
|
|
689
|
+
return runSetSpecGuardEnforcement(args);
|
|
690
|
+
}
|
|
611
691
|
if (args.cmd === "disable-directive") {
|
|
612
692
|
return runDisableDirective(args);
|
|
613
693
|
}
|
|
@@ -616,6 +696,24 @@ export function run(argv) {
|
|
|
616
696
|
}
|
|
617
697
|
return 2;
|
|
618
698
|
}
|
|
699
|
+
/** Persist plan.policy.specGuard.driftGuard.enforcement (#5350). */
|
|
700
|
+
function runSetSpecGuardEnforcement(args) {
|
|
701
|
+
const root = pathResolve(args.projectRoot);
|
|
702
|
+
if (args.specGuardEnforcement === undefined) {
|
|
703
|
+
process.stdout.write("usage: policy set-spec-guard-enforcement -- --set advise|shadow|enforce [--confirm] [--project-root PATH]\n" +
|
|
704
|
+
" Promote ladder: advise → shadow → enforce (advise→enforce skip refused without shadow attestation).\n" +
|
|
705
|
+
` Inspect: ${policyColonInvocation("show", " --field=specGuard")}\n`);
|
|
706
|
+
return 1;
|
|
707
|
+
}
|
|
708
|
+
const result = promoteSpecGuardDriftEnforcement(root, {
|
|
709
|
+
to: args.specGuardEnforcement,
|
|
710
|
+
confirm: args.confirm,
|
|
711
|
+
actor: args.actor,
|
|
712
|
+
note: args.note,
|
|
713
|
+
});
|
|
714
|
+
process.stdout.write(result.stdout);
|
|
715
|
+
return result.exitCode;
|
|
716
|
+
}
|
|
619
717
|
/** Persist plan.policy.ceremonyDial override/enabled (#3214). */
|
|
620
718
|
function runSetCeremonyDial(args) {
|
|
621
719
|
const root = pathResolve(args.projectRoot);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { runSpecReconstructCli } from "@deftai/directive-core/spec-reconstruct";
|
|
4
|
+
export function runSpecReconstructCliEntry(argv) {
|
|
5
|
+
const result = runSpecReconstructCli(argv);
|
|
6
|
+
if (result.stdout.length > 0)
|
|
7
|
+
process.stdout.write(result.stdout);
|
|
8
|
+
if (result.stderr.length > 0)
|
|
9
|
+
process.stderr.write(result.stderr);
|
|
10
|
+
return result.exitCode;
|
|
11
|
+
}
|
|
12
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
13
|
+
process.exit(runSpecReconstructCliEntry(process.argv.slice(2)));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=spec-reconstruct-cli.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare const SUBAGENT_PRE_CANCEL_HELP = "subagent:pre-cancel \u2014 query-before-cancel gate (#5278)\n\nUsage:\n task subagent:pre-cancel -- --agent <id> --canceller-id <id> [options]\n\nExit 0 only when one ordered branch clears:\n (a) canceller-authored status steer (kind note|correction) + matching ack\n OR observed first-seen window (default 3 minutes; PA-18 forward skew only)\n (b) heartbeat STALE/missing under startup grace from --dispatch-started-at\n (c) --force --reason <text>\n\nOptions:\n --agent ID Target child agent_id (required)\n --canceller-id ID Canceller identity; must match steer writer_id for (a)\n --target-id PATH Worktree root (resolves steer/scratch/first-seen under it)\n --steer-dir PATH Steer inbox dir (default: <cwd|target>/.deft-scratch/subagent-steer)\n --scratch-dir PATH Heartbeat dir (default: <cwd|target>/.deft-scratch/subagent-status)\n --first-seen-dir PATH First-seen stamps (default: .../subagent-steer-firstseen)\n --dispatch-started-at ISO DeliveryAttemptRecord.startedAt for grace path (b)\n --expected-worker-id ID Refuse when agent does not match current attempt worker\n --observed-window-seconds N Default 180\n --force Explicit force clear (requires --reason)\n --reason TEXT Printed force reason\n --json Machine-readable verdict\n\nExit codes:\n 0 Pre-cancel green\n 1 Refused (gate red)\n 2 Config / usage error\n";
|
|
3
|
+
export interface SubagentPreCancelArgs {
|
|
4
|
+
agentId: string | null;
|
|
5
|
+
cancellerId: string | null;
|
|
6
|
+
targetId: string | null;
|
|
7
|
+
steerDir: string | null;
|
|
8
|
+
scratchDir: string | null;
|
|
9
|
+
firstSeenDir: string | null;
|
|
10
|
+
dispatchStartedAt: string | null;
|
|
11
|
+
expectedWorkerId: string | null;
|
|
12
|
+
observedWindowSeconds: number | null;
|
|
13
|
+
force: boolean;
|
|
14
|
+
forceReason: string | null;
|
|
15
|
+
emitJson: boolean;
|
|
16
|
+
help: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function parseSubagentPreCancelArgs(argv: readonly string[]): SubagentPreCancelArgs;
|
|
20
|
+
export declare function run(argv: readonly string[], cwd?: string): number;
|
|
21
|
+
//# sourceMappingURL=subagent-pre-cancel.d.ts.map
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fail-closed query-before-cancel gate (#5278 Prefer-A).
|
|
4
|
+
* Dest-capable args required for linked-worktree children (#4066).
|
|
5
|
+
*/
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { defaultFirstSeenDir, defaultScratchDir, defaultSteerDir, EXIT_PRE_CANCEL_CONFIG, EXIT_PRE_CANCEL_OK, evaluatePreCancel, } from "@deftai/directive-core/orchestration";
|
|
9
|
+
export const SUBAGENT_PRE_CANCEL_HELP = `subagent:pre-cancel — query-before-cancel gate (#5278)
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
task subagent:pre-cancel -- --agent <id> --canceller-id <id> [options]
|
|
13
|
+
|
|
14
|
+
Exit 0 only when one ordered branch clears:
|
|
15
|
+
(a) canceller-authored status steer (kind note|correction) + matching ack
|
|
16
|
+
OR observed first-seen window (default 3 minutes; PA-18 forward skew only)
|
|
17
|
+
(b) heartbeat STALE/missing under startup grace from --dispatch-started-at
|
|
18
|
+
(c) --force --reason <text>
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--agent ID Target child agent_id (required)
|
|
22
|
+
--canceller-id ID Canceller identity; must match steer writer_id for (a)
|
|
23
|
+
--target-id PATH Worktree root (resolves steer/scratch/first-seen under it)
|
|
24
|
+
--steer-dir PATH Steer inbox dir (default: <cwd|target>/.deft-scratch/subagent-steer)
|
|
25
|
+
--scratch-dir PATH Heartbeat dir (default: <cwd|target>/.deft-scratch/subagent-status)
|
|
26
|
+
--first-seen-dir PATH First-seen stamps (default: .../subagent-steer-firstseen)
|
|
27
|
+
--dispatch-started-at ISO DeliveryAttemptRecord.startedAt for grace path (b)
|
|
28
|
+
--expected-worker-id ID Refuse when agent does not match current attempt worker
|
|
29
|
+
--observed-window-seconds N Default 180
|
|
30
|
+
--force Explicit force clear (requires --reason)
|
|
31
|
+
--reason TEXT Printed force reason
|
|
32
|
+
--json Machine-readable verdict
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
0 Pre-cancel green
|
|
36
|
+
1 Refused (gate red)
|
|
37
|
+
2 Config / usage error
|
|
38
|
+
`;
|
|
39
|
+
export function parseSubagentPreCancelArgs(argv) {
|
|
40
|
+
const acc = {
|
|
41
|
+
agentId: null,
|
|
42
|
+
cancellerId: null,
|
|
43
|
+
targetId: null,
|
|
44
|
+
steerDir: null,
|
|
45
|
+
scratchDir: null,
|
|
46
|
+
firstSeenDir: null,
|
|
47
|
+
dispatchStartedAt: null,
|
|
48
|
+
expectedWorkerId: null,
|
|
49
|
+
observedWindowSeconds: null,
|
|
50
|
+
force: false,
|
|
51
|
+
forceReason: null,
|
|
52
|
+
emitJson: false,
|
|
53
|
+
help: false,
|
|
54
|
+
};
|
|
55
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
56
|
+
const arg = argv[i];
|
|
57
|
+
if (arg === "--help" || arg === "-h") {
|
|
58
|
+
return { ...acc, help: true };
|
|
59
|
+
}
|
|
60
|
+
if (arg === "--json") {
|
|
61
|
+
acc.emitJson = true;
|
|
62
|
+
}
|
|
63
|
+
else if (arg === "--force") {
|
|
64
|
+
acc.force = true;
|
|
65
|
+
}
|
|
66
|
+
else if (arg === "--agent" || arg === "--agent-id") {
|
|
67
|
+
const value = argv[i + 1];
|
|
68
|
+
if (value === undefined)
|
|
69
|
+
return { ...acc, error: `argument ${arg}: expected one argument` };
|
|
70
|
+
acc.agentId = value;
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
else if (arg?.startsWith("--agent=") || arg?.startsWith("--agent-id=")) {
|
|
74
|
+
acc.agentId = arg.slice(arg.indexOf("=") + 1);
|
|
75
|
+
}
|
|
76
|
+
else if (arg === "--canceller-id" || arg === "--canceller") {
|
|
77
|
+
const value = argv[i + 1];
|
|
78
|
+
if (value === undefined)
|
|
79
|
+
return { ...acc, error: `argument ${arg}: expected one argument` };
|
|
80
|
+
acc.cancellerId = value;
|
|
81
|
+
i += 1;
|
|
82
|
+
}
|
|
83
|
+
else if (arg?.startsWith("--canceller-id=") || arg?.startsWith("--canceller=")) {
|
|
84
|
+
acc.cancellerId = arg.slice(arg.indexOf("=") + 1);
|
|
85
|
+
}
|
|
86
|
+
else if (arg === "--target-id" || arg === "--target") {
|
|
87
|
+
const value = argv[i + 1];
|
|
88
|
+
if (value === undefined)
|
|
89
|
+
return { ...acc, error: `argument ${arg}: expected one argument` };
|
|
90
|
+
acc.targetId = value;
|
|
91
|
+
i += 1;
|
|
92
|
+
}
|
|
93
|
+
else if (arg?.startsWith("--target-id=") || arg?.startsWith("--target=")) {
|
|
94
|
+
acc.targetId = arg.slice(arg.indexOf("=") + 1);
|
|
95
|
+
}
|
|
96
|
+
else if (arg === "--steer-dir") {
|
|
97
|
+
const value = argv[i + 1];
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
return { ...acc, error: "argument --steer-dir: expected one argument" };
|
|
100
|
+
acc.steerDir = value;
|
|
101
|
+
i += 1;
|
|
102
|
+
}
|
|
103
|
+
else if (arg?.startsWith("--steer-dir=")) {
|
|
104
|
+
acc.steerDir = arg.slice("--steer-dir=".length);
|
|
105
|
+
}
|
|
106
|
+
else if (arg === "--scratch-dir") {
|
|
107
|
+
const value = argv[i + 1];
|
|
108
|
+
if (value === undefined)
|
|
109
|
+
return { ...acc, error: "argument --scratch-dir: expected one argument" };
|
|
110
|
+
acc.scratchDir = value;
|
|
111
|
+
i += 1;
|
|
112
|
+
}
|
|
113
|
+
else if (arg?.startsWith("--scratch-dir=")) {
|
|
114
|
+
acc.scratchDir = arg.slice("--scratch-dir=".length);
|
|
115
|
+
}
|
|
116
|
+
else if (arg === "--first-seen-dir") {
|
|
117
|
+
const value = argv[i + 1];
|
|
118
|
+
if (value === undefined) {
|
|
119
|
+
return { ...acc, error: "argument --first-seen-dir: expected one argument" };
|
|
120
|
+
}
|
|
121
|
+
acc.firstSeenDir = value;
|
|
122
|
+
i += 1;
|
|
123
|
+
}
|
|
124
|
+
else if (arg?.startsWith("--first-seen-dir=")) {
|
|
125
|
+
acc.firstSeenDir = arg.slice("--first-seen-dir=".length);
|
|
126
|
+
}
|
|
127
|
+
else if (arg === "--dispatch-started-at") {
|
|
128
|
+
const value = argv[i + 1];
|
|
129
|
+
if (value === undefined) {
|
|
130
|
+
return { ...acc, error: "argument --dispatch-started-at: expected one argument" };
|
|
131
|
+
}
|
|
132
|
+
acc.dispatchStartedAt = value;
|
|
133
|
+
i += 1;
|
|
134
|
+
}
|
|
135
|
+
else if (arg?.startsWith("--dispatch-started-at=")) {
|
|
136
|
+
acc.dispatchStartedAt = arg.slice("--dispatch-started-at=".length);
|
|
137
|
+
}
|
|
138
|
+
else if (arg === "--expected-worker-id") {
|
|
139
|
+
const value = argv[i + 1];
|
|
140
|
+
if (value === undefined) {
|
|
141
|
+
return { ...acc, error: "argument --expected-worker-id: expected one argument" };
|
|
142
|
+
}
|
|
143
|
+
acc.expectedWorkerId = value;
|
|
144
|
+
i += 1;
|
|
145
|
+
}
|
|
146
|
+
else if (arg?.startsWith("--expected-worker-id=")) {
|
|
147
|
+
acc.expectedWorkerId = arg.slice("--expected-worker-id=".length);
|
|
148
|
+
}
|
|
149
|
+
else if (arg === "--observed-window-seconds") {
|
|
150
|
+
const value = argv[i + 1];
|
|
151
|
+
if (value === undefined) {
|
|
152
|
+
return { ...acc, error: "argument --observed-window-seconds: expected one argument" };
|
|
153
|
+
}
|
|
154
|
+
acc.observedWindowSeconds = Number(value);
|
|
155
|
+
i += 1;
|
|
156
|
+
}
|
|
157
|
+
else if (arg?.startsWith("--observed-window-seconds=")) {
|
|
158
|
+
acc.observedWindowSeconds = Number(arg.slice("--observed-window-seconds=".length));
|
|
159
|
+
}
|
|
160
|
+
else if (arg === "--reason") {
|
|
161
|
+
const value = argv[i + 1];
|
|
162
|
+
if (value === undefined)
|
|
163
|
+
return { ...acc, error: "argument --reason: expected one argument" };
|
|
164
|
+
acc.forceReason = value;
|
|
165
|
+
i += 1;
|
|
166
|
+
}
|
|
167
|
+
else if (arg?.startsWith("--reason=")) {
|
|
168
|
+
acc.forceReason = arg.slice("--reason=".length);
|
|
169
|
+
}
|
|
170
|
+
else if (arg?.startsWith("-")) {
|
|
171
|
+
return { ...acc, error: `unrecognized argument: ${arg}` };
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
return { ...acc, error: `unrecognized argument: ${arg}` };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return acc;
|
|
178
|
+
}
|
|
179
|
+
export function run(argv, cwd = process.cwd()) {
|
|
180
|
+
const args = parseSubagentPreCancelArgs(argv);
|
|
181
|
+
if (args.help) {
|
|
182
|
+
process.stdout.write(SUBAGENT_PRE_CANCEL_HELP);
|
|
183
|
+
return EXIT_PRE_CANCEL_OK;
|
|
184
|
+
}
|
|
185
|
+
if (args.error !== undefined) {
|
|
186
|
+
process.stderr.write(`subagent:pre-cancel: ${args.error}\n`);
|
|
187
|
+
return EXIT_PRE_CANCEL_CONFIG;
|
|
188
|
+
}
|
|
189
|
+
const root = args.targetId !== null && args.targetId.trim().length > 0 ? resolve(cwd, args.targetId) : cwd;
|
|
190
|
+
const steerDir = resolve(root, args.steerDir ?? defaultSteerDir(root));
|
|
191
|
+
const scratchDir = resolve(root, args.scratchDir ?? defaultScratchDir(root));
|
|
192
|
+
const firstSeenDir = resolve(root, args.firstSeenDir ?? defaultFirstSeenDir(root));
|
|
193
|
+
// Linked-worktree children: refuse default-to-parent-cwd alone without dest args (#4066 / #5278).
|
|
194
|
+
// Partial dest (only --steer-dir or only --scratch-dir) mixes worktrees — require a pair or --target-id.
|
|
195
|
+
const hasTarget = args.targetId !== null && args.targetId.trim().length > 0;
|
|
196
|
+
const hasSteer = args.steerDir !== null;
|
|
197
|
+
const hasScratch = args.scratchDir !== null;
|
|
198
|
+
if (!hasTarget && hasSteer !== hasScratch) {
|
|
199
|
+
process.stderr.write("subagent:pre-cancel: --steer-dir and --scratch-dir must be paired when --target-id is omitted (partial dest mixes worktrees)\n");
|
|
200
|
+
return EXIT_PRE_CANCEL_CONFIG;
|
|
201
|
+
}
|
|
202
|
+
const hasDest = hasTarget || (hasSteer && hasScratch);
|
|
203
|
+
if (!hasDest) {
|
|
204
|
+
process.stderr.write("subagent:pre-cancel: dest-capable --target-id and/or paired --steer-dir/--scratch-dir required (default-to-cwd alone is refuse-closed for linked-worktree children)\n");
|
|
205
|
+
return EXIT_PRE_CANCEL_CONFIG;
|
|
206
|
+
}
|
|
207
|
+
if (args.observedWindowSeconds !== null &&
|
|
208
|
+
(!Number.isFinite(args.observedWindowSeconds) || args.observedWindowSeconds <= 0)) {
|
|
209
|
+
process.stderr.write("subagent:pre-cancel: --observed-window-seconds must be positive\n");
|
|
210
|
+
return EXIT_PRE_CANCEL_CONFIG;
|
|
211
|
+
}
|
|
212
|
+
const verdict = evaluatePreCancel({
|
|
213
|
+
agentId: args.agentId ?? "",
|
|
214
|
+
cancellerId: args.cancellerId ?? "",
|
|
215
|
+
steerDir,
|
|
216
|
+
firstSeenDir,
|
|
217
|
+
scratchDir,
|
|
218
|
+
force: args.force,
|
|
219
|
+
forceReason: args.forceReason ?? undefined,
|
|
220
|
+
dispatchStartedAt: args.dispatchStartedAt,
|
|
221
|
+
expectedWorkerId: args.expectedWorkerId,
|
|
222
|
+
observedWindowSeconds: args.observedWindowSeconds ?? undefined,
|
|
223
|
+
});
|
|
224
|
+
if (args.emitJson) {
|
|
225
|
+
process.stdout.write(`${JSON.stringify(verdict.json, null, 2)}\n`);
|
|
226
|
+
}
|
|
227
|
+
else if (verdict.message.length > 0) {
|
|
228
|
+
if (verdict.ok) {
|
|
229
|
+
process.stdout.write(`${verdict.message}\n`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
process.stderr.write(`${verdict.message}\n`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return verdict.exitCode;
|
|
236
|
+
}
|
|
237
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
238
|
+
process.exit(run(process.argv.slice(2)));
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=subagent-pre-cancel.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type SteerKind, type SteerWriterKind } from "@deftai/directive-core/orchestration";
|
|
3
|
+
export declare const EXIT_STEER_WRITE_OK: number;
|
|
4
|
+
export declare const EXIT_STEER_WRITE_CONFIG: number;
|
|
5
|
+
export declare const SUBAGENT_STEER_WRITE_HELP = "subagent:steer \u2014 write a closed parent-steer inbox (#5278 / #4286)\n\nUsage:\n task subagent:steer -- --agent <id> --writer-id <id> --kind <kind> --text <text> [options]\n\nOptions:\n --agent ID Child agent_id (inbox file stem)\n --writer-id ID Writer identity (must match independent parent/owner record)\n --writer-kind KIND occupancy-owner | dispatching-parent (default: dispatching-parent)\n --kind KIND constraint | correction | halt | note\n --text TEXT Steer body (\u22642000 chars)\n --steer-id ID Optional stable steer id (default: uuid)\n --ttl-seconds N Expiry TTL (default: 1800)\n --target-id PATH Worktree root for dest inbox\n --steer-dir PATH Inbox directory override\n --scratch-dir PATH Heartbeat dir (not used for writer authority)\n --parent-id ID Must match child occupancy lease parentId\n --occupancy-owner-id ID Must match live occupancy session under --target-id\n --json Emit written record\n\nExit codes:\n 0 Wrote inbox\n 2 Config / validation error\n";
|
|
6
|
+
export interface SubagentSteerWriteArgs {
|
|
7
|
+
agentId: string | null;
|
|
8
|
+
writerId: string | null;
|
|
9
|
+
writerKind: SteerWriterKind;
|
|
10
|
+
kind: SteerKind | null;
|
|
11
|
+
text: string | null;
|
|
12
|
+
steerId: string | null;
|
|
13
|
+
ttlSeconds: number | null;
|
|
14
|
+
targetId: string | null;
|
|
15
|
+
steerDir: string | null;
|
|
16
|
+
scratchDir: string | null;
|
|
17
|
+
parentId: string | null;
|
|
18
|
+
occupancyOwnerId: string | null;
|
|
19
|
+
emitJson: boolean;
|
|
20
|
+
help: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve parent/owner from dispatcher-recorded child occupancy + live occupancy.
|
|
25
|
+
* Heartbeat alone is never authority (caller-selected --scratch-dir cannot mint parent_id).
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveIndependentSteerAuthority(input: {
|
|
28
|
+
root: string;
|
|
29
|
+
agentId: string;
|
|
30
|
+
writerKind: SteerWriterKind;
|
|
31
|
+
writerId: string;
|
|
32
|
+
claimedParentId?: string | null;
|
|
33
|
+
claimedOccupancyOwnerId?: string | null;
|
|
34
|
+
}): {
|
|
35
|
+
parentId?: string;
|
|
36
|
+
occupancyOwnerId?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function parseSubagentSteerWriteArgs(argv: readonly string[]): SubagentSteerWriteArgs;
|
|
40
|
+
export declare function run(argv: readonly string[], cwd?: string): number;
|
|
41
|
+
//# sourceMappingURL=subagent-steer-write.d.ts.map
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Parent write surface for deft.subagent.steer.v1 (#5278 / #4286).
|
|
4
|
+
* Wraps writeSteer so status steers are reachable before pre-cancel.
|
|
5
|
+
* Writer authority is checked against child occupancy / heartbeat — not self-attested.
|
|
6
|
+
*/
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { defaultSteerDir, isSafeAgentId, STEER_KINDS, STEER_WRITER_KINDS, writeSteer, } from "@deftai/directive-core/orchestration";
|
|
10
|
+
import { liveOccupant, readChildOccupancyLease } from "@deftai/directive-core/session";
|
|
11
|
+
export const EXIT_STEER_WRITE_OK = 0 * 1;
|
|
12
|
+
export const EXIT_STEER_WRITE_CONFIG = 2 * 1;
|
|
13
|
+
export const SUBAGENT_STEER_WRITE_HELP = `subagent:steer — write a closed parent-steer inbox (#5278 / #4286)
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
task subagent:steer -- --agent <id> --writer-id <id> --kind <kind> --text <text> [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--agent ID Child agent_id (inbox file stem)
|
|
20
|
+
--writer-id ID Writer identity (must match independent parent/owner record)
|
|
21
|
+
--writer-kind KIND occupancy-owner | dispatching-parent (default: dispatching-parent)
|
|
22
|
+
--kind KIND constraint | correction | halt | note
|
|
23
|
+
--text TEXT Steer body (≤2000 chars)
|
|
24
|
+
--steer-id ID Optional stable steer id (default: uuid)
|
|
25
|
+
--ttl-seconds N Expiry TTL (default: 1800)
|
|
26
|
+
--target-id PATH Worktree root for dest inbox
|
|
27
|
+
--steer-dir PATH Inbox directory override
|
|
28
|
+
--scratch-dir PATH Heartbeat dir (not used for writer authority)
|
|
29
|
+
--parent-id ID Must match child occupancy lease parentId
|
|
30
|
+
--occupancy-owner-id ID Must match live occupancy session under --target-id
|
|
31
|
+
--json Emit written record
|
|
32
|
+
|
|
33
|
+
Exit codes:
|
|
34
|
+
0 Wrote inbox
|
|
35
|
+
2 Config / validation error
|
|
36
|
+
`;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve parent/owner from dispatcher-recorded child occupancy + live occupancy.
|
|
39
|
+
* Heartbeat alone is never authority (caller-selected --scratch-dir cannot mint parent_id).
|
|
40
|
+
*/
|
|
41
|
+
export function resolveIndependentSteerAuthority(input) {
|
|
42
|
+
const writerId = input.writerId.trim();
|
|
43
|
+
const lease = readChildOccupancyLease(input.root, input.agentId);
|
|
44
|
+
const occupancy = liveOccupant(input.root);
|
|
45
|
+
if (input.writerKind === "dispatching-parent") {
|
|
46
|
+
const expected = (lease?.parentId ?? "").trim();
|
|
47
|
+
if (expected.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
error: "dispatching-parent requires child occupancy lease parentId under --target-id (heartbeat alone is not authority)",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (writerId !== expected) {
|
|
53
|
+
return {
|
|
54
|
+
error: `dispatching-parent writer_id ${JSON.stringify(writerId)} does not match child occupancy parent ${JSON.stringify(expected)}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const claimed = input.claimedParentId?.trim() ?? "";
|
|
58
|
+
if (claimed.length > 0 && claimed !== expected) {
|
|
59
|
+
return {
|
|
60
|
+
error: `--parent-id ${JSON.stringify(claimed)} does not match child occupancy parent ${JSON.stringify(expected)}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { parentId: expected };
|
|
64
|
+
}
|
|
65
|
+
// occupancy-owner: liveOccupant (TTL/age-cap) is SoT; expired/stale leases refuse.
|
|
66
|
+
const liveOwner = (occupancy?.sessionId ?? "").trim();
|
|
67
|
+
if (liveOwner.length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
error: "occupancy-owner requires a live (non-expired) occupancy session under --target-id",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const leaseOwner = (lease?.occupancyOwner ?? "").trim();
|
|
73
|
+
if (leaseOwner.length > 0 && leaseOwner !== liveOwner) {
|
|
74
|
+
return {
|
|
75
|
+
error: `child occupancy owner ${JSON.stringify(leaseOwner)} is superseded by live occupancy ${JSON.stringify(liveOwner)}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (writerId !== liveOwner) {
|
|
79
|
+
return {
|
|
80
|
+
error: `occupancy-owner writer_id ${JSON.stringify(writerId)} does not match live occupancy ${JSON.stringify(liveOwner)}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const claimedOwner = input.claimedOccupancyOwnerId?.trim() ?? "";
|
|
84
|
+
if (claimedOwner.length > 0 && claimedOwner !== liveOwner) {
|
|
85
|
+
return {
|
|
86
|
+
error: `--occupancy-owner-id ${JSON.stringify(claimedOwner)} does not match live occupancy ${JSON.stringify(liveOwner)}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { occupancyOwnerId: liveOwner };
|
|
90
|
+
}
|
|
91
|
+
export function parseSubagentSteerWriteArgs(argv) {
|
|
92
|
+
const acc = {
|
|
93
|
+
agentId: null,
|
|
94
|
+
writerId: null,
|
|
95
|
+
writerKind: "dispatching-parent",
|
|
96
|
+
kind: null,
|
|
97
|
+
text: null,
|
|
98
|
+
steerId: null,
|
|
99
|
+
ttlSeconds: null,
|
|
100
|
+
targetId: null,
|
|
101
|
+
steerDir: null,
|
|
102
|
+
scratchDir: null,
|
|
103
|
+
parentId: null,
|
|
104
|
+
occupancyOwnerId: null,
|
|
105
|
+
emitJson: false,
|
|
106
|
+
help: false,
|
|
107
|
+
};
|
|
108
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
109
|
+
const arg = argv[i];
|
|
110
|
+
if (arg === "--help" || arg === "-h") {
|
|
111
|
+
return { ...acc, help: true };
|
|
112
|
+
}
|
|
113
|
+
if (arg === "--json") {
|
|
114
|
+
acc.emitJson = true;
|
|
115
|
+
}
|
|
116
|
+
else if (arg === "--agent" || arg === "--agent-id") {
|
|
117
|
+
const value = argv[i + 1];
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
return { ...acc, error: `argument ${arg}: expected one argument` };
|
|
120
|
+
acc.agentId = value;
|
|
121
|
+
i += 1;
|
|
122
|
+
}
|
|
123
|
+
else if (arg?.startsWith("--agent=") || arg?.startsWith("--agent-id=")) {
|
|
124
|
+
acc.agentId = arg.slice(arg.indexOf("=") + 1);
|
|
125
|
+
}
|
|
126
|
+
else if (arg === "--writer-id") {
|
|
127
|
+
const value = argv[i + 1];
|
|
128
|
+
if (value === undefined)
|
|
129
|
+
return { ...acc, error: "argument --writer-id: expected one argument" };
|
|
130
|
+
acc.writerId = value;
|
|
131
|
+
i += 1;
|
|
132
|
+
}
|
|
133
|
+
else if (arg?.startsWith("--writer-id=")) {
|
|
134
|
+
acc.writerId = arg.slice("--writer-id=".length);
|
|
135
|
+
}
|
|
136
|
+
else if (arg === "--writer-kind") {
|
|
137
|
+
const value = argv[i + 1];
|
|
138
|
+
if (value === undefined)
|
|
139
|
+
return { ...acc, error: "argument --writer-kind: expected one argument" };
|
|
140
|
+
if (!STEER_WRITER_KINDS.includes(value)) {
|
|
141
|
+
return { ...acc, error: `writer-kind must be one of ${STEER_WRITER_KINDS.join(", ")}` };
|
|
142
|
+
}
|
|
143
|
+
acc.writerKind = value;
|
|
144
|
+
i += 1;
|
|
145
|
+
}
|
|
146
|
+
else if (arg?.startsWith("--writer-kind=")) {
|
|
147
|
+
const value = arg.slice("--writer-kind=".length);
|
|
148
|
+
if (!STEER_WRITER_KINDS.includes(value)) {
|
|
149
|
+
return { ...acc, error: `writer-kind must be one of ${STEER_WRITER_KINDS.join(", ")}` };
|
|
150
|
+
}
|
|
151
|
+
acc.writerKind = value;
|
|
152
|
+
}
|
|
153
|
+
else if (arg === "--kind") {
|
|
154
|
+
const value = argv[i + 1];
|
|
155
|
+
if (value === undefined)
|
|
156
|
+
return { ...acc, error: "argument --kind: expected one argument" };
|
|
157
|
+
if (!STEER_KINDS.includes(value)) {
|
|
158
|
+
return { ...acc, error: `kind must be one of ${STEER_KINDS.join(", ")}` };
|
|
159
|
+
}
|
|
160
|
+
acc.kind = value;
|
|
161
|
+
i += 1;
|
|
162
|
+
}
|
|
163
|
+
else if (arg?.startsWith("--kind=")) {
|
|
164
|
+
const value = arg.slice("--kind=".length);
|
|
165
|
+
if (!STEER_KINDS.includes(value)) {
|
|
166
|
+
return { ...acc, error: `kind must be one of ${STEER_KINDS.join(", ")}` };
|
|
167
|
+
}
|
|
168
|
+
acc.kind = value;
|
|
169
|
+
}
|
|
170
|
+
else if (arg === "--text") {
|
|
171
|
+
const value = argv[i + 1];
|
|
172
|
+
if (value === undefined)
|
|
173
|
+
return { ...acc, error: "argument --text: expected one argument" };
|
|
174
|
+
acc.text = value;
|
|
175
|
+
i += 1;
|
|
176
|
+
}
|
|
177
|
+
else if (arg?.startsWith("--text=")) {
|
|
178
|
+
acc.text = arg.slice("--text=".length);
|
|
179
|
+
}
|
|
180
|
+
else if (arg === "--steer-id") {
|
|
181
|
+
const value = argv[i + 1];
|
|
182
|
+
if (value === undefined)
|
|
183
|
+
return { ...acc, error: "argument --steer-id: expected one argument" };
|
|
184
|
+
acc.steerId = value;
|
|
185
|
+
i += 1;
|
|
186
|
+
}
|
|
187
|
+
else if (arg?.startsWith("--steer-id=")) {
|
|
188
|
+
acc.steerId = arg.slice("--steer-id=".length);
|
|
189
|
+
}
|
|
190
|
+
else if (arg === "--ttl-seconds") {
|
|
191
|
+
const value = argv[i + 1];
|
|
192
|
+
if (value === undefined)
|
|
193
|
+
return { ...acc, error: "argument --ttl-seconds: expected one argument" };
|
|
194
|
+
acc.ttlSeconds = Number(value);
|
|
195
|
+
i += 1;
|
|
196
|
+
}
|
|
197
|
+
else if (arg?.startsWith("--ttl-seconds=")) {
|
|
198
|
+
acc.ttlSeconds = Number(arg.slice("--ttl-seconds=".length));
|
|
199
|
+
}
|
|
200
|
+
else if (arg === "--target-id" || arg === "--target") {
|
|
201
|
+
const value = argv[i + 1];
|
|
202
|
+
if (value === undefined)
|
|
203
|
+
return { ...acc, error: `argument ${arg}: expected one argument` };
|
|
204
|
+
acc.targetId = value;
|
|
205
|
+
i += 1;
|
|
206
|
+
}
|
|
207
|
+
else if (arg?.startsWith("--target-id=") || arg?.startsWith("--target=")) {
|
|
208
|
+
acc.targetId = arg.slice(arg.indexOf("=") + 1);
|
|
209
|
+
}
|
|
210
|
+
else if (arg === "--steer-dir") {
|
|
211
|
+
const value = argv[i + 1];
|
|
212
|
+
if (value === undefined)
|
|
213
|
+
return { ...acc, error: "argument --steer-dir: expected one argument" };
|
|
214
|
+
acc.steerDir = value;
|
|
215
|
+
i += 1;
|
|
216
|
+
}
|
|
217
|
+
else if (arg?.startsWith("--steer-dir=")) {
|
|
218
|
+
acc.steerDir = arg.slice("--steer-dir=".length);
|
|
219
|
+
}
|
|
220
|
+
else if (arg === "--scratch-dir") {
|
|
221
|
+
const value = argv[i + 1];
|
|
222
|
+
if (value === undefined)
|
|
223
|
+
return { ...acc, error: "argument --scratch-dir: expected one argument" };
|
|
224
|
+
acc.scratchDir = value;
|
|
225
|
+
i += 1;
|
|
226
|
+
}
|
|
227
|
+
else if (arg?.startsWith("--scratch-dir=")) {
|
|
228
|
+
acc.scratchDir = arg.slice("--scratch-dir=".length);
|
|
229
|
+
}
|
|
230
|
+
else if (arg === "--parent-id") {
|
|
231
|
+
const value = argv[i + 1];
|
|
232
|
+
if (value === undefined)
|
|
233
|
+
return { ...acc, error: "argument --parent-id: expected one argument" };
|
|
234
|
+
acc.parentId = value;
|
|
235
|
+
i += 1;
|
|
236
|
+
}
|
|
237
|
+
else if (arg?.startsWith("--parent-id=")) {
|
|
238
|
+
acc.parentId = arg.slice("--parent-id=".length);
|
|
239
|
+
}
|
|
240
|
+
else if (arg === "--occupancy-owner-id") {
|
|
241
|
+
const value = argv[i + 1];
|
|
242
|
+
if (value === undefined) {
|
|
243
|
+
return { ...acc, error: "argument --occupancy-owner-id: expected one argument" };
|
|
244
|
+
}
|
|
245
|
+
acc.occupancyOwnerId = value;
|
|
246
|
+
i += 1;
|
|
247
|
+
}
|
|
248
|
+
else if (arg?.startsWith("--occupancy-owner-id=")) {
|
|
249
|
+
acc.occupancyOwnerId = arg.slice("--occupancy-owner-id=".length);
|
|
250
|
+
}
|
|
251
|
+
else if (arg?.startsWith("-")) {
|
|
252
|
+
return { ...acc, error: `unrecognized argument: ${arg}` };
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
return { ...acc, error: `unrecognized argument: ${arg}` };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return acc;
|
|
259
|
+
}
|
|
260
|
+
export function run(argv, cwd = process.cwd()) {
|
|
261
|
+
const args = parseSubagentSteerWriteArgs(argv);
|
|
262
|
+
if (args.help) {
|
|
263
|
+
process.stdout.write(SUBAGENT_STEER_WRITE_HELP);
|
|
264
|
+
return EXIT_STEER_WRITE_OK;
|
|
265
|
+
}
|
|
266
|
+
if (args.error !== undefined) {
|
|
267
|
+
process.stderr.write(`subagent:steer: ${args.error}\n`);
|
|
268
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
269
|
+
}
|
|
270
|
+
if (args.agentId === null || !isSafeAgentId(args.agentId)) {
|
|
271
|
+
process.stderr.write("subagent:steer: --agent is required and must be a filesystem-safe slug\n");
|
|
272
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
273
|
+
}
|
|
274
|
+
if (args.writerId === null || args.writerId.trim().length === 0) {
|
|
275
|
+
process.stderr.write("subagent:steer: --writer-id is required\n");
|
|
276
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
277
|
+
}
|
|
278
|
+
if (args.kind === null) {
|
|
279
|
+
process.stderr.write(`subagent:steer: --kind is required (${STEER_KINDS.join("|")})\n`);
|
|
280
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
281
|
+
}
|
|
282
|
+
if (args.text === null || args.text.trim().length === 0) {
|
|
283
|
+
process.stderr.write("subagent:steer: --text is required\n");
|
|
284
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
285
|
+
}
|
|
286
|
+
if (args.ttlSeconds !== null && (!Number.isFinite(args.ttlSeconds) || args.ttlSeconds <= 0)) {
|
|
287
|
+
process.stderr.write("subagent:steer: --ttl-seconds must be positive\n");
|
|
288
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
289
|
+
}
|
|
290
|
+
const root = args.targetId !== null && args.targetId.trim().length > 0 ? resolve(cwd, args.targetId) : cwd;
|
|
291
|
+
const steerDir = resolve(root, args.steerDir ?? defaultSteerDir(root));
|
|
292
|
+
const writerId = args.writerId.trim();
|
|
293
|
+
const authority = resolveIndependentSteerAuthority({
|
|
294
|
+
root,
|
|
295
|
+
agentId: args.agentId,
|
|
296
|
+
writerKind: args.writerKind,
|
|
297
|
+
writerId,
|
|
298
|
+
claimedParentId: args.parentId,
|
|
299
|
+
claimedOccupancyOwnerId: args.occupancyOwnerId,
|
|
300
|
+
});
|
|
301
|
+
if (authority.error !== undefined) {
|
|
302
|
+
process.stderr.write(`subagent:steer: ${authority.error}\n`);
|
|
303
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const record = writeSteer(steerDir, {
|
|
307
|
+
agentId: args.agentId,
|
|
308
|
+
writerKind: args.writerKind,
|
|
309
|
+
writerId,
|
|
310
|
+
kind: args.kind,
|
|
311
|
+
text: args.text,
|
|
312
|
+
steerId: args.steerId ?? undefined,
|
|
313
|
+
ttlSeconds: args.ttlSeconds ?? undefined,
|
|
314
|
+
parentId: authority.parentId,
|
|
315
|
+
occupancyOwnerId: authority.occupancyOwnerId,
|
|
316
|
+
});
|
|
317
|
+
if (args.emitJson) {
|
|
318
|
+
process.stdout.write(`${JSON.stringify(record, null, 2)}\n`);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
process.stdout.write(`subagent:steer: wrote ${record.agent_id} steer_id=${record.steer_id} kind=${record.kind}\n`);
|
|
322
|
+
}
|
|
323
|
+
return EXIT_STEER_WRITE_OK;
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
process.stderr.write(`subagent:steer: ${String(err.message ?? err)}\n`);
|
|
327
|
+
return EXIT_STEER_WRITE_CONFIG;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
331
|
+
process.exit(run(process.argv.slice(2)));
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=subagent-steer-write.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseCohortReviewMonitorsArgv, verifyCohortReviewMonitorsMain } from "@deftai/directive-core/swarm";
|
|
3
|
+
export { parseCohortReviewMonitorsArgv, verifyCohortReviewMonitorsMain };
|
|
4
|
+
export declare function run(argv?: readonly string[]): number;
|
|
5
|
+
//# sourceMappingURL=verify-cohort-review-monitors.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { parseCohortReviewMonitorsArgv, verifyCohortReviewMonitorsMain, } from "@deftai/directive-core/swarm";
|
|
4
|
+
export { parseCohortReviewMonitorsArgv, verifyCohortReviewMonitorsMain };
|
|
5
|
+
export function run(argv = process.argv.slice(2)) {
|
|
6
|
+
return verifyCohortReviewMonitorsMain([...argv]);
|
|
7
|
+
}
|
|
8
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
9
|
+
process.exit(run(process.argv.slice(2)));
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=verify-cohort-review-monitors.js.map
|
|
@@ -14,6 +14,8 @@ interface ParsedArgs {
|
|
|
14
14
|
liveWait: boolean;
|
|
15
15
|
explicitFinish: boolean;
|
|
16
16
|
stickyLease: boolean;
|
|
17
|
+
/** DeliveryAttemptRecord.startedAt for Approach 1 arm-startup halt (#5278 P3). */
|
|
18
|
+
dispatchStartedAt: string | null;
|
|
17
19
|
help: boolean;
|
|
18
20
|
error?: string;
|
|
19
21
|
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { bindLivePhaseCorrectWait, evaluateMergePathArm, } from "@deftai/directive-core/dist/pr-watch/main.js";
|
|
5
|
+
import { evaluateApproach1ArmStartup } from "@deftai/directive-core/orchestration";
|
|
5
6
|
import { EXIT_CONFIG_ERROR, EXIT_NOT_READY, evaluateReviewMonitorGate, formatApproach1BabysitterOneLiner, isTier1, REVIEW_MONITOR_HELP, spawnRedirect, verifyResultToJson, } from "@deftai/directive-core/review-monitor";
|
|
7
|
+
import { writeMergePathExplicitFinishAttestation } from "@deftai/directive-core/swarm";
|
|
6
8
|
const CALL_SITES = new Set([
|
|
7
9
|
"solo",
|
|
8
10
|
"swarm-phase5-6",
|
|
@@ -23,6 +25,7 @@ export function parseVerifyReviewMonitorArgs(argv) {
|
|
|
23
25
|
liveWait: false,
|
|
24
26
|
explicitFinish: false,
|
|
25
27
|
stickyLease: false,
|
|
28
|
+
dispatchStartedAt: null,
|
|
26
29
|
help: false,
|
|
27
30
|
};
|
|
28
31
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -45,6 +48,17 @@ export function parseVerifyReviewMonitorArgs(argv) {
|
|
|
45
48
|
else if (arg === "--sticky-lease") {
|
|
46
49
|
acc.stickyLease = true;
|
|
47
50
|
}
|
|
51
|
+
else if (arg === "--dispatch-started-at") {
|
|
52
|
+
const value = argv[i + 1];
|
|
53
|
+
if (value === undefined) {
|
|
54
|
+
return { ...acc, error: "argument --dispatch-started-at: expected one argument" };
|
|
55
|
+
}
|
|
56
|
+
acc.dispatchStartedAt = value;
|
|
57
|
+
i += 1;
|
|
58
|
+
}
|
|
59
|
+
else if (arg?.startsWith("--dispatch-started-at=")) {
|
|
60
|
+
acc.dispatchStartedAt = arg.slice("--dispatch-started-at=".length);
|
|
61
|
+
}
|
|
48
62
|
else if (arg === "--approach3") {
|
|
49
63
|
acc.approach3 = true;
|
|
50
64
|
}
|
|
@@ -146,6 +160,9 @@ export function run(argv) {
|
|
|
146
160
|
" parent-shell pr:watch (parent_id=pr-watch) does not arm.\n" +
|
|
147
161
|
" --explicit-finish Attest option-C BLOCKED/FAILED finish for this PR\n" +
|
|
148
162
|
" --sticky-lease Attest a fresh sticky lease (not sufficient alone)\n" +
|
|
163
|
+
" --dispatch-started-at DeliveryAttemptRecord.startedAt ISO for Approach 1\n" +
|
|
164
|
+
" arm-startup halt (#5278); when merge-path-arm stays\n" +
|
|
165
|
+
" red past the 3m allowance, emit approach1-arm-startup.\n" +
|
|
149
166
|
" Prefer Approach 1 / native pr:watch --monitor-agent-id <id> or post-CLEAN\n" +
|
|
150
167
|
" pr:wait-mergeable-and-merge; homemade line-parsed --json is not an arm.\n");
|
|
151
168
|
return 0;
|
|
@@ -174,6 +191,26 @@ export function run(argv) {
|
|
|
174
191
|
});
|
|
175
192
|
let arm = null;
|
|
176
193
|
if (args.mergePathArm) {
|
|
194
|
+
// Durable option-C attestation for cohort inventory halted-explicit (#5318).
|
|
195
|
+
// Do not write (or arm from) --explicit-finish when the monitor gate is a config error,
|
|
196
|
+
// and fail closed if the durable write itself fails.
|
|
197
|
+
let explicitFinishDurable = false;
|
|
198
|
+
if (args.explicitFinish) {
|
|
199
|
+
if (result.exitCode === EXIT_CONFIG_ERROR) {
|
|
200
|
+
explicitFinishDurable = false;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const written = writeMergePathExplicitFinishAttestation(resolve(args.projectRoot), args.pr, {
|
|
204
|
+
source: "verify:review-monitor --explicit-finish",
|
|
205
|
+
});
|
|
206
|
+
if (!written.ok) {
|
|
207
|
+
process.stderr.write(`verify_review_monitor: ${written.reason}
|
|
208
|
+
`);
|
|
209
|
+
return EXIT_CONFIG_ERROR;
|
|
210
|
+
}
|
|
211
|
+
explicitFinishDurable = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
177
214
|
// Bind --live-wait to lease (#5018) + process-liveness heartbeat (#5020) on Tier 1.
|
|
178
215
|
// spawn_subagent identity join is already applied in evaluateReviewMonitorGate (#5219).
|
|
179
216
|
const leaseEvidence = result.monitorRecord !== null;
|
|
@@ -186,7 +223,7 @@ export function run(argv) {
|
|
|
186
223
|
});
|
|
187
224
|
arm = evaluateMergePathArm({
|
|
188
225
|
livePhaseCorrectWait: liveBind.livePhaseCorrectWait,
|
|
189
|
-
explicitFinish:
|
|
226
|
+
explicitFinish: explicitFinishDurable,
|
|
190
227
|
stickyLeaseActive: args.stickyLease || leaseEvidence,
|
|
191
228
|
});
|
|
192
229
|
if (args.liveWait && !liveBind.livePhaseCorrectWait && !args.explicitFinish && !arm.armed) {
|
|
@@ -224,6 +261,26 @@ export function run(argv) {
|
|
|
224
261
|
};
|
|
225
262
|
}
|
|
226
263
|
}
|
|
264
|
+
// Approach 1 arm-startup halt (#5278 P3): production caller for evaluateApproach1ArmStartup.
|
|
265
|
+
let armStartupHalt = null;
|
|
266
|
+
if (args.mergePathArm &&
|
|
267
|
+
args.dispatchStartedAt !== null &&
|
|
268
|
+
args.dispatchStartedAt.trim().length > 0 &&
|
|
269
|
+
arm !== null &&
|
|
270
|
+
!arm.armed &&
|
|
271
|
+
!args.explicitFinish) {
|
|
272
|
+
armStartupHalt = evaluateApproach1ArmStartup({
|
|
273
|
+
dispatchStartedAt: args.dispatchStartedAt,
|
|
274
|
+
probeReady: false,
|
|
275
|
+
});
|
|
276
|
+
if (armStartupHalt.halt && armStartupHalt.message.length > 0) {
|
|
277
|
+
arm = {
|
|
278
|
+
armed: false,
|
|
279
|
+
reason: "unarmed_stand_down",
|
|
280
|
+
message: `${arm.message}\n${armStartupHalt.message}`,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
227
284
|
if (args.emitJson) {
|
|
228
285
|
const payload = verifyResultToJson(result);
|
|
229
286
|
if (arm !== null) {
|
|
@@ -237,6 +294,14 @@ export function run(argv) {
|
|
|
237
294
|
lease_evidence: result.monitorRecord !== null,
|
|
238
295
|
heartbeat_active: result.heartbeatActive,
|
|
239
296
|
};
|
|
297
|
+
if (armStartupHalt !== null) {
|
|
298
|
+
payload.approach1_arm_startup = {
|
|
299
|
+
halt: armStartupHalt.halt,
|
|
300
|
+
halt_class: armStartupHalt.halt_class,
|
|
301
|
+
elapsed_seconds: armStartupHalt.elapsed_seconds,
|
|
302
|
+
message: armStartupHalt.message,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
240
305
|
// Combined gate+arm: unarmed fails closed even when the monitor gate is ready.
|
|
241
306
|
if (!arm.armed && result.exitCode !== EXIT_CONFIG_ERROR) {
|
|
242
307
|
payload.ready = false;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { runSpecDriftCli } from "@deftai/directive-core/verify-source";
|
|
4
|
+
export function run(argv) {
|
|
5
|
+
const result = runSpecDriftCli(argv);
|
|
6
|
+
if (result.stdout.length > 0)
|
|
7
|
+
process.stdout.write(result.stdout);
|
|
8
|
+
if (result.stderr.length > 0)
|
|
9
|
+
process.stderr.write(result.stderr);
|
|
10
|
+
return result.exitCode;
|
|
11
|
+
}
|
|
12
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
13
|
+
process.exit(run(process.argv.slice(2)));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=verify-spec-drift.js.map
|
|
@@ -95,8 +95,11 @@ const HELP_TEXT = `usage: verify-story-ready [--vbrief-path PATH] [--project-roo
|
|
|
95
95
|
[--skip-routing] [--roles ROLE[,ROLE...]]
|
|
96
96
|
|
|
97
97
|
Deterministic story-start Gate 0 (#1378). When the active dispatch provider is
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
one of ROUTING_GATED_DISPATCH_PROVIDERS (claude, cursor, grok, grok-bot,
|
|
99
|
+
openclaw), chains verify:routing (#1877 / #3703). --skip-routing records a
|
|
100
|
+
silent opt-out. Session-start --advise remains additive disclosure only and
|
|
101
|
+
does not relieve honor-at-dispatch. Three-state exit: 0 ready / 1 not ready /
|
|
102
|
+
2 config error.
|
|
100
103
|
`;
|
|
101
104
|
function emitJson(vbriefPath, exitCode, message, dispatchKind) {
|
|
102
105
|
const payload = {
|
|
@@ -129,6 +129,20 @@ export function evaluateSubagentAliveGate(args, cwd = process.cwd()) {
|
|
|
129
129
|
redispatch_ok: unhealthy && !configError,
|
|
130
130
|
required_agents: [...args.requireAgents],
|
|
131
131
|
missing_or_stale_agents: missingAgents,
|
|
132
|
+
// Flat per-agent records including wait_kind (#5278 P2).
|
|
133
|
+
records: sweep.records.map((r) => ({
|
|
134
|
+
agent_id: r.agent_id,
|
|
135
|
+
phase: r.phase,
|
|
136
|
+
wait_kind: r.wait_kind,
|
|
137
|
+
head_sha: r.head_sha,
|
|
138
|
+
pr_number: r.pr_number,
|
|
139
|
+
last_heartbeat_at: r.last_heartbeat_at_iso,
|
|
140
|
+
age_seconds: r.age_seconds,
|
|
141
|
+
is_stale: r.is_stale,
|
|
142
|
+
is_terminal: r.is_terminal,
|
|
143
|
+
failures: [...r.failures],
|
|
144
|
+
ok: recordOk(r),
|
|
145
|
+
})),
|
|
132
146
|
sweep: {
|
|
133
147
|
scratch_dirs: sweep.scratch_dirs,
|
|
134
148
|
threshold_minutes: sweep.threshold_minutes,
|
|
@@ -167,10 +181,12 @@ export function evaluateSubagentAliveGate(args, cwd = process.cwd()) {
|
|
|
167
181
|
lines.push("");
|
|
168
182
|
lines.push("REDISPATCH_OK: host-reported running + missing/STALE heartbeat authorizes takeover.");
|
|
169
183
|
lines.push("A killed worker's delivery attempt stays running until cancelled.");
|
|
170
|
-
lines.push("Takeover: task
|
|
184
|
+
lines.push("Takeover: task subagent:pre-cancel -- --agent <id> --canceller-id <id> --target-id <worktree>");
|
|
185
|
+
lines.push("then: task swarm:pre-dispatch -- --scope-id <id> --target-id <worktree> --action cancel --agent <id>");
|
|
171
186
|
lines.push("then: task swarm:pre-dispatch -- --scope-id <id> --target-id <worktree>");
|
|
172
187
|
lines.push("If verify:session-ritual --tier=gated fails, run session:start --rearm --session-id=<same> first.");
|
|
173
188
|
lines.push("Do not wait on Cursor false-alive state. Do not spawn while DENY_DUPLICATE_ACTIVE.");
|
|
189
|
+
lines.push("⊗ bare ledger cancel while pre-cancel is red (#5278).");
|
|
174
190
|
return {
|
|
175
191
|
exitCode: EXIT_STALE,
|
|
176
192
|
redispatchOk: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.122.0",
|
|
4
4
|
"description": "Directive CLI — npm install path for the Deft Directive framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://deftai.github.io/directive/",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"provenance": true
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@deftai/directive-core": "^0.
|
|
39
|
-
"@deftai/directive-content": "^0.
|
|
38
|
+
"@deftai/directive-core": "^0.122.0",
|
|
39
|
+
"@deftai/directive-content": "^0.122.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -b",
|