@deftai/directive 0.119.8 → 0.119.10

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.
@@ -29,6 +29,7 @@ export const SCOPE_LIFECYCLE_VERBS = new Set([
29
29
  "block",
30
30
  "unblock",
31
31
  "stamp-evidence",
32
+ "bind-clause",
32
33
  ]);
33
34
  /** pr:* task names that do not hyphenate 1:1 to handler stems. */
34
35
  export const PR_VERB_MAP = {
@@ -82,6 +83,8 @@ export const SUBCOMMAND_ROUTES = {
82
83
  "policy:enable-directive": ["policy", "enable-directive"],
83
84
  "policy:enforce-branches": ["policy", "enforce-branches"],
84
85
  "policy:allow-direct-commits": ["policy", "allow-direct-commits"],
86
+ "policy:allow-destructive-gh-verbs": ["policy", "allow-destructive-gh-verbs"],
87
+ "policy:enforce-destructive-gh-verbs": ["policy", "enforce-destructive-gh-verbs"],
85
88
  "policy:allow-bot-merge": ["policy", "allow-bot-merge"],
86
89
  "policy:enable-value-feedback": ["policy", "enable-value-feedback"],
87
90
  "policy:disable-host-hooks": ["policy", "disable-host-hooks"],
@@ -207,6 +210,9 @@ function routeNamespaceVerb(ns, verb, rest) {
207
210
  if (ns === "scm" && verb === "issue:design-critique-chip") {
208
211
  return { kind: "dispatch", argv: ["scm", "issue", "design-critique-chip", ...rest] };
209
212
  }
213
+ if (ns === "scm" && verb === "issue:design-critique-stale-ready") {
214
+ return { kind: "dispatch", argv: ["scm", "issue", "design-critique-stale-ready", ...rest] };
215
+ }
210
216
  if (ns === "scm" && verb === "issue:work-claim") {
211
217
  return { kind: "dispatch", argv: ["scm", "issue", "work-claim", ...rest] };
212
218
  }
@@ -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", "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", "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", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-test-boundary", "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-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "verify-evaluator-surface", "verify-observable-scope", "verify-intent-constraint", "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", "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", "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-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-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "verify-evaluator-surface", "verify-observable-scope", "verify-operator-scope-limit", "verify-intent-constraint", "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-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"];
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"];
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
@@ -111,10 +111,12 @@ export const CLI_MODULE_VERBS = [
111
111
  "vbrief-validation",
112
112
  "xbrief-create",
113
113
  "xbrief-verify",
114
+ "xbrief-adopt-stored-plan-id",
114
115
  "verify-branch",
115
116
  "verify-encoding",
116
117
  "verify-forward-coverage",
117
118
  "verify-test-boundary",
119
+ "verify-class-checks",
118
120
  "verify-scope-provenance",
119
121
  "verify-consumer-check-contract",
120
122
  "verify-hooks-installed",
@@ -167,6 +169,7 @@ export const CLI_MODULE_VERBS = [
167
169
  "verify-eval-triggers-relocation",
168
170
  "verify-evaluator-surface",
169
171
  "verify-observable-scope",
172
+ "verify-operator-scope-limit",
170
173
  "verify-intent-constraint",
171
174
  "verify-consumer-test-lane",
172
175
  "eval-health",
@@ -187,6 +190,7 @@ export const CORE_MODULE_VERBS = [
187
190
  "swarm-launch",
188
191
  "swarm-complete-cohort",
189
192
  "swarm-finalize-cohort",
193
+ "swarm-finalize-owed",
190
194
  "swarm-pre-dispatch",
191
195
  "swarm-readiness",
192
196
  "swarm-routing-verify",
@@ -242,6 +246,8 @@ export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
242
246
  "policy:show": "show",
243
247
  "policy:enforce-branches": "enforce-branches",
244
248
  "policy:allow-direct-commits": "allow-direct-commits",
249
+ "policy:allow-destructive-gh-verbs": "allow-destructive-gh-verbs",
250
+ "policy:enforce-destructive-gh-verbs": "enforce-destructive-gh-verbs",
245
251
  "policy:allow-bot-merge": "allow-bot-merge",
246
252
  "policy:enable-value-feedback": "enable-value-feedback",
247
253
  "policy:disable-host-hooks": "disable-host-hooks",
@@ -299,6 +305,7 @@ export const VERB_ALIASES = {
299
305
  "verify:encoding": "verify-encoding",
300
306
  "verify:forward-coverage": "verify-forward-coverage",
301
307
  "verify:test-boundary": "verify-test-boundary",
308
+ "verify:class-checks": "verify-class-checks",
302
309
  "verify:scope-provenance": "verify-scope-provenance",
303
310
  "scope:record-approved-scope": "scope-record-approved-scope",
304
311
  "scope:record-observable-scope": "scope-record-observable-scope",
@@ -323,6 +330,7 @@ export const VERB_ALIASES = {
323
330
  "verify:eval-triggers-relocation": "verify-eval-triggers-relocation",
324
331
  "verify:evaluator-surface": "verify-evaluator-surface",
325
332
  "verify:observable-scope": "verify-observable-scope",
333
+ "verify:operator-scope-limit": "verify-operator-scope-limit",
326
334
  "verify:intent-constraint": "verify-intent-constraint",
327
335
  "verify:consumer-test-lane": "verify-consumer-test-lane",
328
336
  "verify:hooks-installed": "verify-hooks-installed",
@@ -337,6 +345,7 @@ export const VERB_ALIASES = {
337
345
  "xbrief:preflight": "vbrief-preflight",
338
346
  "xbrief:create": "xbrief-create",
339
347
  "xbrief:verify": "xbrief-verify",
348
+ "xbrief:adopt-stored-plan-id": "xbrief-adopt-stored-plan-id",
340
349
  "vbrief:activate": "vbrief-activate",
341
350
  "verify:story-ready": "verify-story-ready",
342
351
  "verify:review-monitor": "verify-review-monitor",
@@ -478,6 +487,7 @@ const WRAPPER_CLI_STEMS = new Set([
478
487
  "vbrief-validation",
479
488
  "xbrief-create",
480
489
  "xbrief-verify",
490
+ "xbrief-adopt-stored-plan-id",
481
491
  ]);
482
492
  function emitCliResult(result, io) {
483
493
  if (result.stdout)
@@ -553,6 +563,10 @@ async function loadWrapperCliHandler(stem, io) {
553
563
  const { runXbriefVerifyCli } = await import("@deftai/directive-core/xbrief");
554
564
  return (argv) => emitCliResult(runXbriefVerifyCli(argv), io);
555
565
  }
566
+ case "xbrief-adopt-stored-plan-id": {
567
+ const { runAdoptStoredPlanIdCli } = await import("@deftai/directive-core/xbrief");
568
+ return (argv) => emitCliResult(runAdoptStoredPlanIdCli(argv), io);
569
+ }
556
570
  default:
557
571
  throw new Error(`no wrapper handler for ${stem}`);
558
572
  }
@@ -2444,6 +2458,10 @@ async function loadCoreModuleHandler(verb, io) {
2444
2458
  const { finalizeCohortMain } = await import("@deftai/directive-core/dist/swarm/finalize-cohort-cli.js");
2445
2459
  return finalizeCohortMain;
2446
2460
  }
2461
+ case "swarm-finalize-owed": {
2462
+ const { finalizeOwedMain } = await import("@deftai/directive-core/dist/swarm/finalize-owed-cli.js");
2463
+ return finalizeOwedMain;
2464
+ }
2447
2465
  case "swarm-pre-dispatch": {
2448
2466
  const { preDispatchMain } = await import("@deftai/directive-core/dist/swarm/pre-dispatch-cli.js");
2449
2467
  return preDispatchMain;
@@ -2771,6 +2789,10 @@ const CURATED_HELP_GROUPS = [
2771
2789
  name: "xbrief:verify",
2772
2790
  summary: "Verify a dense xBRIEF artifact at --out (requires --format); not scope:*",
2773
2791
  },
2792
+ {
2793
+ name: "xbrief:adopt-stored-plan-id",
2794
+ summary: "Set plan.id from the stored plan-id binding; refuse if another artifact has it",
2795
+ },
2774
2796
  ],
2775
2797
  },
2776
2798
  {
package/dist/policy.d.ts CHANGED
@@ -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-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" | "disable-host-hooks" | "disable-directive" | "enable-directive" | "resolve";
12
12
  confirm: boolean;
13
13
  actor: string;
14
14
  note: string;
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, 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, 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, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, 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,7 +114,7 @@ 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-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|disable-host-hooks|disable-directive|enable-directive|resolve] ...";
118
118
  return makeSetError(usage);
119
119
  }
120
120
  const cmd = argv[0];
@@ -148,6 +148,8 @@ export function parseArgs(argv) {
148
148
  }
149
149
  if (cmd === "enforce-branches" ||
150
150
  cmd === "allow-direct-commits" ||
151
+ cmd === "allow-destructive-gh-verbs" ||
152
+ cmd === "enforce-destructive-gh-verbs" ||
151
153
  cmd === "allow-bot-merge" ||
152
154
  cmd === "enable-value-feedback" ||
153
155
  cmd === "clear-value-feedback" ||
@@ -160,19 +162,23 @@ export function parseArgs(argv) {
160
162
  ? policyColonInvocation("enforce-branches")
161
163
  : cmd === "allow-direct-commits"
162
164
  ? policyColonInvocation("allow-direct-commits")
163
- : cmd === "allow-bot-merge"
164
- ? policyColonInvocation("allow-bot-merge")
165
- : cmd === "enable-value-feedback"
166
- ? policyColonInvocation("enable-value-feedback")
167
- : cmd === "clear-value-feedback"
168
- ? policyColonInvocation("clear-value-feedback")
169
- : cmd === "set-ceremony-dial"
170
- ? policyColonInvocation("set-ceremony-dial")
171
- : cmd === "disable-host-hooks"
172
- ? policyColonInvocation("disable-host-hooks")
173
- : cmd === "disable-directive"
174
- ? policyColonInvocation("disable-directive")
175
- : policyColonInvocation("enable-directive");
165
+ : cmd === "allow-destructive-gh-verbs"
166
+ ? policyColonInvocation("allow-destructive-gh-verbs")
167
+ : cmd === "enforce-destructive-gh-verbs"
168
+ ? policyColonInvocation("enforce-destructive-gh-verbs")
169
+ : cmd === "allow-bot-merge"
170
+ ? policyColonInvocation("allow-bot-merge")
171
+ : cmd === "enable-value-feedback"
172
+ ? policyColonInvocation("enable-value-feedback")
173
+ : cmd === "clear-value-feedback"
174
+ ? policyColonInvocation("clear-value-feedback")
175
+ : cmd === "set-ceremony-dial"
176
+ ? policyColonInvocation("set-ceremony-dial")
177
+ : cmd === "disable-host-hooks"
178
+ ? policyColonInvocation("disable-host-hooks")
179
+ : cmd === "disable-directive"
180
+ ? policyColonInvocation("disable-directive")
181
+ : policyColonInvocation("enable-directive");
176
182
  let note = "";
177
183
  let projectRoot = ".";
178
184
  let host;
@@ -476,6 +482,51 @@ function runEnableDirective(args) {
476
482
  process.stdout.write("Directive opt-out cleared. Run `directive init` or `directive update` to ensure install.\n");
477
483
  return 0;
478
484
  }
485
+ /** Allow or re-enforce the #1019 destructive-gh-verb gate. */
486
+ function runDestructiveGhVerbs(args) {
487
+ const projectRoot = pathResolve(args.projectRoot);
488
+ const allow = args.cmd === "allow-destructive-gh-verbs";
489
+ if (allow && !args.confirm) {
490
+ process.stdout.write(`${ALLOW_DESTRUCTIVE_GH_VERBS_CAPABILITY_COST}\n\n`);
491
+ process.stdout.write(`Re-run with --confirm to apply: ${policyColonInvocation("allow-destructive-gh-verbs", " -- --confirm")}\n`);
492
+ return 1;
493
+ }
494
+ try {
495
+ const result = setAllowDestructiveGhVerbs(projectRoot, {
496
+ allowDestructiveGhVerbs: allow,
497
+ actor: args.actor,
498
+ note: args.note,
499
+ });
500
+ if (!result.ok) {
501
+ process.stderr.write(`\u274c ${result.message}\n`);
502
+ if (result.message.includes("PROJECT-DEFINITION not found")) {
503
+ const pdRel = relative(projectRoot, projectDefinitionPath(projectRoot));
504
+ process.stderr.write(` Recovery: run \`task setup\` to generate ${pdRel}.\n`);
505
+ }
506
+ return 2;
507
+ }
508
+ process.stdout.write(`\u2713 plan.policy.allowDestructiveGhVerbs=${allow ? "true" : "false"} ` +
509
+ `(#1019 gate ${allow ? "OFF" : "ON"}).\n`);
510
+ if (result.changed) {
511
+ process.stdout.write(` audit: meta/policy-changes.log :: ${result.auditEntry}\n`);
512
+ }
513
+ else {
514
+ process.stdout.write(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
515
+ }
516
+ return 0;
517
+ }
518
+ catch (err) {
519
+ const message = err instanceof Error ? err.message : String(err);
520
+ if (message.includes("PROJECT-DEFINITION not found")) {
521
+ process.stderr.write(`\u274c ${message}\n`);
522
+ const pdRel = relative(projectRoot, projectDefinitionPath(projectRoot));
523
+ process.stderr.write(` Recovery: run \`task setup\` to generate ${pdRel}.\n`);
524
+ return 2;
525
+ }
526
+ process.stderr.write(`\u274c Config error: ${message}\n`);
527
+ return 2;
528
+ }
529
+ }
479
530
  /** Allow agent/bot merge by writing requireHumanMerge=false (#1193). */
480
531
  function runAllowBotMerge(args) {
481
532
  const projectRoot = pathResolve(args.projectRoot);
@@ -539,6 +590,9 @@ export function run(argv) {
539
590
  if (args.cmd === "enforce-branches" || args.cmd === "allow-direct-commits") {
540
591
  return runSet(args);
541
592
  }
593
+ if (args.cmd === "allow-destructive-gh-verbs" || args.cmd === "enforce-destructive-gh-verbs") {
594
+ return runDestructiveGhVerbs(args);
595
+ }
542
596
  if (args.cmd === "allow-bot-merge") {
543
597
  return runAllowBotMerge(args);
544
598
  }
@@ -3,6 +3,7 @@ interface ParsedArgs {
3
3
  mode?: "self-test" | "command" | "pre-push-stdin";
4
4
  command?: string;
5
5
  defaultBranches?: Set<string>;
6
+ projectRoot?: string;
6
7
  quiet?: boolean;
7
8
  error?: string;
8
9
  }
@@ -7,11 +7,13 @@
7
7
  * deft-ts preflight-gh --command "<gh ...>"
8
8
  * deft-ts preflight-gh --pre-push-stdin (reads from stdin)
9
9
  *
10
- * Thin shim -- delegates to @deftai/directive-core/preflight-gh.
10
+ * Thin shim -- delegates through @deftai/directive-core/preflight
11
+ * (re-exports the preflight-gh API; vitest already aliases that subpath).
12
+ * --project-root is consulted by evaluatePrePush / evaluateCommand (#4384).
11
13
  */
12
14
  import { createInterface } from "node:readline";
13
15
  import { fileURLToPath } from "node:url";
14
- import { DEFAULT_BRANCHES, ENV_BYPASS, evaluateCommand, runSelfTest, } from "@deftai/directive-core/preflight-gh";
16
+ import { DEFAULT_BRANCHES, evaluateCommand, evaluatePrePush, parsePrePushStdin, runSelfTest, } from "@deftai/directive-core/preflight";
15
17
  export function parseArgs(argv) {
16
18
  const parsed = { defaultBranches: new Set(DEFAULT_BRANCHES) };
17
19
  for (let i = 0; i < argv.length; i++) {
@@ -44,7 +46,11 @@ export function parseArgs(argv) {
44
46
  i++;
45
47
  }
46
48
  else if (arg === "--project-root") {
47
- // accepted for parity with preflight_branch.py; ignored here
49
+ const next = argv[i + 1];
50
+ if (next === undefined) {
51
+ return { ...parsed, error: "argument --project-root: expected one argument" };
52
+ }
53
+ parsed.projectRoot = next;
48
54
  i++;
49
55
  }
50
56
  else {
@@ -73,7 +79,9 @@ export function run(argv) {
73
79
  return code;
74
80
  }
75
81
  if (args.mode === "command" && args.command !== undefined) {
76
- const [code, msg] = evaluateCommand(args.command, branches);
82
+ const [code, msg] = evaluateCommand(args.command, branches, {
83
+ projectRoot: args.projectRoot,
84
+ });
77
85
  if (code === 0) {
78
86
  if (!quiet)
79
87
  process.stdout.write(`${msg}\n`);
@@ -84,85 +92,19 @@ export function run(argv) {
84
92
  return code;
85
93
  }
86
94
  if (args.mode === "pre-push-stdin") {
87
- return runPrePushStdin(branches, quiet);
95
+ return runPrePushStdin(branches, quiet, args.projectRoot);
88
96
  }
89
97
  process.stderr.write("preflight-gh: one of --self-test / --command / --pre-push-stdin required\n");
90
98
  return 2;
91
99
  }
92
- function parsePrePushLines(text) {
93
- return text
94
- .split("\n")
95
- .map((l) => l.trim())
96
- .filter((l) => l.length > 0)
97
- .flatMap((l) => {
98
- const parts = l.split(/\s+/);
99
- if (parts.length !== 4)
100
- return [];
101
- return [
102
- {
103
- localRef: parts[0] ?? "",
104
- localOid: parts[1] ?? "",
105
- remoteRef: parts[2] ?? "",
106
- remoteOid: parts[3] ?? "",
107
- },
108
- ];
109
- });
110
- }
111
- const ZERO_OID_RE = /^0+$/;
112
- function evaluatePrePush(refs, branches) {
113
- if (refs.length === 0) {
114
- return [0, "✓ deft destructive-gh-verb gate (pre-push): no refs in stdin -- nothing to gate."];
115
- }
116
- const branchesLower = new Set([...branches].map((b) => b.toLowerCase()));
117
- const blocked = [];
118
- for (const { localRef, localOid, remoteRef, remoteOid } of refs) {
119
- const branch = remoteRef.replace(/^refs\/heads\//, "");
120
- if (!branchesLower.has(branch.toLowerCase()))
121
- continue;
122
- if (ZERO_OID_RE.test(remoteOid)) {
123
- blocked.push(`create ${branch} (local=${localRef})`);
124
- }
125
- else if (ZERO_OID_RE.test(localOid)) {
126
- blocked.push(`delete ${branch}`);
127
- }
128
- else {
129
- blocked.push(`update ${branch} (local=${localRef})`);
130
- }
131
- }
132
- if (blocked.length === 0) {
133
- return [
134
- 0,
135
- "✓ deft destructive-gh-verb gate (pre-push): no pushes to default branches detected -- proceeding.",
136
- ];
137
- }
138
- const bypass = (process.env[ENV_BYPASS] ?? "").trim().toLowerCase();
139
- if (["1", "true", "yes", "on"].includes(bypass)) {
140
- return [
141
- 0,
142
- `⚠ deft destructive-gh-verb gate (pre-push): default-branch push detected (${blocked.join("; ")}) but ${ENV_BYPASS}=1 is set -- policy bypassed for this invocation.`,
143
- ];
144
- }
145
- return [
146
- 1,
147
- [
148
- "❌ deft destructive-gh-verb gate (pre-push): refusing to push directly to the default branch.",
149
- ` Detail: ${blocked.join("; ")}`,
150
- "",
151
- " How to proceed:",
152
- " • push to a feature branch and open a PR",
153
- ` • or set the env-var bypass for this shell: ${ENV_BYPASS}=1`,
154
- " See scm/github.md (## Destructive gh verbs (#1019)).",
155
- ].join("\n"),
156
- ];
157
- }
158
- function runPrePushStdin(branches, quiet) {
100
+ function runPrePushStdin(branches, quiet, projectRoot) {
159
101
  return new Promise((resolve) => {
160
102
  const lines = [];
161
103
  const rl = createInterface({ input: process.stdin });
162
104
  rl.on("line", (l) => lines.push(l));
163
105
  rl.on("close", () => {
164
- const refs = parsePrePushLines(lines.join("\n"));
165
- const [code, msg] = evaluatePrePush(refs, branches);
106
+ const refs = parsePrePushStdin(lines.join("\n"));
107
+ const [code, msg] = evaluatePrePush(refs, { branches, projectRoot });
166
108
  if (code === 0) {
167
109
  if (!quiet)
168
110
  process.stdout.write(`${msg}\n`);
@@ -5,6 +5,7 @@ export interface ParsedArgs {
5
5
  xbriefPath: string;
6
6
  actor: string;
7
7
  kind: string;
8
+ mintedVia: string;
8
9
  quiet: boolean;
9
10
  confirm: boolean;
10
11
  help?: boolean;
@@ -11,12 +11,18 @@ import { buildIntentConstraintRecord, extractIntentConstraintFromPlan, parseInte
11
11
  import { isDirectEntrypoint } from "./entrypoint.js";
12
12
  import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
13
13
  import { isPathInsideRoot } from "./scope-record-approved-scope.js";
14
+ const DEFAULT_MINTED_VIA = "scope:record-intent-constraint";
15
+ const ALLOWED_MINTED_VIA = new Set([DEFAULT_MINTED_VIA, "in-harness-ask"]);
14
16
  function usage() {
15
17
  return ("usage: scope:record-intent-constraint -- <xbrief-path> --actor <name> --confirm " +
16
- "[--kind operator] [--project-root <dir>] [--quiet]\n" +
18
+ "[--kind operator] [--minted-via in-harness-ask|scope:record-intent-constraint] " +
19
+ "[--project-root <dir>] [--quiet]\n" +
17
20
  ' Writes .deft/intent-constraint/<plan-id>.json from plan["x-directive/intentConstraint"].\n' +
18
- " --actor is display only and never authorizes mint. Mint requires a real TTY, " +
19
- "controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
21
+ " Attended mid-build (#5010): ask in parent chat; on yes the human runs this verb on a real TTY " +
22
+ "with --minted-via=in-harness-ask (attestation is not an agent-shell bypass). Commit the record on " +
23
+ "the delivery-branch merge base, then rebase the implementation branch — same-PR working-tree-only " +
24
+ "records are not authoritative. Unattended/C1: rewrite or park. --actor is display only. Mint still " +
25
+ "requires a real TTY, controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
20
26
  }
21
27
  function extractPlanId(payload) {
22
28
  if (payload === null || typeof payload !== "object" || Array.isArray(payload))
@@ -33,6 +39,7 @@ export function parseArgs(argv) {
33
39
  xbriefPath: "",
34
40
  actor: "",
35
41
  kind: "operator",
42
+ mintedVia: DEFAULT_MINTED_VIA,
36
43
  quiet: false,
37
44
  confirm: false,
38
45
  };
@@ -77,6 +84,16 @@ export function parseArgs(argv) {
77
84
  else if (arg?.startsWith("--kind=")) {
78
85
  parsed.kind = arg.slice("--kind=".length);
79
86
  }
87
+ else if (arg === "--minted-via") {
88
+ const value = argv[i + 1];
89
+ if (value === undefined)
90
+ return { ...parsed, error: "argument --minted-via: expected one argument" };
91
+ parsed.mintedVia = value;
92
+ i += 1;
93
+ }
94
+ else if (arg?.startsWith("--minted-via=")) {
95
+ parsed.mintedVia = arg.slice("--minted-via=".length);
96
+ }
80
97
  else if (arg?.startsWith("-")) {
81
98
  return { ...parsed, error: `unrecognized argument: ${arg}` };
82
99
  }
@@ -98,6 +115,13 @@ export function parseArgs(argv) {
98
115
  error: `argument --actor is required (human operator identity)\n${usage()}`,
99
116
  };
100
117
  }
118
+ if (!ALLOWED_MINTED_VIA.has(parsed.mintedVia.trim())) {
119
+ return {
120
+ ...parsed,
121
+ error: `argument --minted-via must be one of: ${[...ALLOWED_MINTED_VIA].join(", ")}\n${usage()}`,
122
+ };
123
+ }
124
+ parsed.mintedVia = parsed.mintedVia.trim();
101
125
  return parsed;
102
126
  }
103
127
  export function run(argv, seams = {}) {
@@ -159,7 +183,7 @@ export function run(argv, seams = {}) {
159
183
  kind: args.kind,
160
184
  actor: args.actor,
161
185
  mintedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
162
- mintedVia: "scope:record-intent-constraint",
186
+ mintedVia: args.mintedVia,
163
187
  },
164
188
  });
165
189
  if ("error" in rec) {
@@ -5,6 +5,7 @@ export interface ParsedArgs {
5
5
  xbriefPath: string;
6
6
  actor: string;
7
7
  kind: string;
8
+ mintedVia: string;
8
9
  quiet: boolean;
9
10
  confirm: boolean;
10
11
  help?: boolean;
@@ -11,12 +11,18 @@ import { buildObservableScopeRecord, extractObservableChangeFromPlan, parseObser
11
11
  import { isDirectEntrypoint } from "./entrypoint.js";
12
12
  import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
13
13
  import { isPathInsideRoot } from "./scope-record-approved-scope.js";
14
+ const DEFAULT_MINTED_VIA = "scope:record-observable-scope";
15
+ const ALLOWED_MINTED_VIA = new Set([DEFAULT_MINTED_VIA, "in-harness-ask"]);
14
16
  function usage() {
15
17
  return ("usage: scope:record-observable-scope -- <xbrief-path> --actor <name> --confirm " +
16
- "[--kind operator] [--project-root <dir>] [--quiet]\n" +
18
+ "[--kind operator] [--minted-via in-harness-ask|scope:record-observable-scope] " +
19
+ "[--project-root <dir>] [--quiet]\n" +
17
20
  ' Writes .deft/observable-scope/<plan-id>.json from plan["x-directive/observableChange"].\n' +
18
- " --actor is display only and never authorizes mint. Mint requires a real TTY, " +
19
- "controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
21
+ " Attended mid-build (#5010): ask in parent chat; on yes the human runs this verb on a real TTY " +
22
+ "with --minted-via=in-harness-ask (attestation is not an agent-shell bypass). Commit the record on " +
23
+ "the delivery-branch merge base, then rebase the implementation branch — same-PR working-tree-only " +
24
+ "records are not authoritative. Unattended/C1: rewrite or park. --actor is display only. Mint still " +
25
+ "requires a real TTY, controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
20
26
  }
21
27
  function extractPlanId(payload) {
22
28
  if (payload === null || typeof payload !== "object" || Array.isArray(payload))
@@ -33,6 +39,7 @@ export function parseArgs(argv) {
33
39
  xbriefPath: "",
34
40
  actor: "",
35
41
  kind: "operator",
42
+ mintedVia: DEFAULT_MINTED_VIA,
36
43
  quiet: false,
37
44
  confirm: false,
38
45
  };
@@ -77,6 +84,16 @@ export function parseArgs(argv) {
77
84
  else if (arg?.startsWith("--kind=")) {
78
85
  parsed.kind = arg.slice("--kind=".length);
79
86
  }
87
+ else if (arg === "--minted-via") {
88
+ const value = argv[i + 1];
89
+ if (value === undefined)
90
+ return { ...parsed, error: "argument --minted-via: expected one argument" };
91
+ parsed.mintedVia = value;
92
+ i += 1;
93
+ }
94
+ else if (arg?.startsWith("--minted-via=")) {
95
+ parsed.mintedVia = arg.slice("--minted-via=".length);
96
+ }
80
97
  else if (arg?.startsWith("-")) {
81
98
  return { ...parsed, error: `unrecognized argument: ${arg}` };
82
99
  }
@@ -98,6 +115,13 @@ export function parseArgs(argv) {
98
115
  error: `argument --actor is required (human operator identity)\n${usage()}`,
99
116
  };
100
117
  }
118
+ if (!ALLOWED_MINTED_VIA.has(parsed.mintedVia.trim())) {
119
+ return {
120
+ ...parsed,
121
+ error: `argument --minted-via must be one of: ${[...ALLOWED_MINTED_VIA].join(", ")}\n${usage()}`,
122
+ };
123
+ }
124
+ parsed.mintedVia = parsed.mintedVia.trim();
101
125
  return parsed;
102
126
  }
103
127
  export function run(argv, seams = {}) {
@@ -161,7 +185,7 @@ export function run(argv, seams = {}) {
161
185
  kind: args.kind,
162
186
  actor: args.actor,
163
187
  mintedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
164
- mintedVia: "scope:record-observable-scope",
188
+ mintedVia: args.mintedVia,
165
189
  },
166
190
  });
167
191
  if ("error" in rec) {
@@ -4,6 +4,8 @@ import { type PrimaryClaimException, type SessionCeremonyTier, type SessionPostu
4
4
  export interface ParsedSessionStartArgs {
5
5
  projectRoot: string;
6
6
  deferValues: string[];
7
+ /** #4919: defer finalize-owed session gate with a recorded reason. */
8
+ deferOwedReason: string | null;
7
9
  emitJson: boolean;
8
10
  noHistory: boolean;
9
11
  readOnly: boolean;
@@ -30,6 +30,7 @@ export function parseArgs(argv) {
30
30
  const parsed = {
31
31
  projectRoot: ".",
32
32
  deferValues: [],
33
+ deferOwedReason: null,
33
34
  emitJson: false,
34
35
  noHistory: false,
35
36
  readOnly: false,
@@ -124,6 +125,21 @@ export function parseArgs(argv) {
124
125
  }
125
126
  parsed.deferValues.push(value);
126
127
  }
128
+ else if (arg === "--defer-owed") {
129
+ const value = argv[i + 1];
130
+ if (value === undefined || value.startsWith("--")) {
131
+ return { ...parsed, error: "argument --defer-owed: expected one argument" };
132
+ }
133
+ parsed.deferOwedReason = value;
134
+ i += 1;
135
+ }
136
+ else if (arg?.startsWith("--defer-owed=")) {
137
+ const value = arg.slice("--defer-owed=".length);
138
+ if (value.length === 0 || value.startsWith("--")) {
139
+ return { ...parsed, error: "argument --defer-owed: expected one argument" };
140
+ }
141
+ parsed.deferOwedReason = value;
142
+ }
127
143
  else if (arg === "--task-size" || arg === "--ceremony-task-size") {
128
144
  const value = argv[i + 1];
129
145
  if (value === undefined) {
@@ -487,6 +503,7 @@ export function run(argv) {
487
503
  occupant: args.occupant ?? undefined,
488
504
  primaryClaimException: args.primaryClaimException ?? undefined,
489
505
  sessionId: args.sessionId ?? undefined,
506
+ deferOwedReason: args.deferOwedReason,
490
507
  ...(args.ceremonyDepthOverride !== null
491
508
  ? {
492
509
  ceremonyDial: selectCeremonyDepth({