@deftai/directive 0.105.0 → 0.107.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 CHANGED
@@ -225,7 +225,7 @@ function helpText() {
225
225
  " (grant / uat-start / uat-suspend / revoke) — no TTY, --confirm, or phrase path.",
226
226
  " Self-approval under UAT is impossible by construction. Mint grants BEFORE uat-start.",
227
227
  "Outside UAT, mutating verbs require multi-factor human presence (#3110):",
228
- " - Interactive TTY (stdin+stdout) + controlling terminal (/dev/tty|CONIN$)",
228
+ " - Interactive TTY (stdin+stdout) + controlling terminal (/dev/tty|\\\\.\\CONIN$)",
229
229
  " - Explicit --confirm (argv flag alone never enough)",
230
230
  " - Typed phrase 'mint' on the controlling TTY (PTY+--confirm alone never enough)",
231
231
  " - Known agent/CI env markers always refuse (fail-closed).",
@@ -83,6 +83,7 @@ export const SUBCOMMAND_ROUTES = {
83
83
  "policy:allow-direct-commits": ["policy", "allow-direct-commits"],
84
84
  "policy:allow-bot-merge": ["policy", "allow-bot-merge"],
85
85
  "policy:enable-value-feedback": ["policy", "enable-value-feedback"],
86
+ "policy:disable-host-hooks": ["policy", "disable-host-hooks"],
86
87
  "policy:set-ceremony-dial": ["policy", "set-ceremony-dial"],
87
88
  "policy:clear-value-feedback": ["policy", "clear-value-feedback"],
88
89
  "authz:show": ["authz", "show"],
@@ -196,6 +197,9 @@ function routeNamespaceVerb(ns, verb, rest) {
196
197
  return { kind: "dispatch", argv: [verifyStem, ...rest] };
197
198
  }
198
199
  }
200
+ if (ns === "scm" && verb === "issue:design-critique-chip") {
201
+ return { kind: "dispatch", argv: ["scm", "issue", "design-critique-chip", ...rest] };
202
+ }
199
203
  if (ns === "issue" && verb === "ingest") {
200
204
  return { kind: "dispatch", argv: ["issue-ingest", ...rest] };
201
205
  }
@@ -10,7 +10,7 @@ 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", "scope-lifecycle", "scope-record-approved-scope", "scm-sync-default", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "occupancy-steal", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "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-skill-external-fetch-gate", "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", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-completed-tracked", "verify-literal-ac", "verify-ac", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "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", "scope-lifecycle", "scope-record-approved-scope", "scm-sync-default", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "occupancy-steal", "occupancy-release", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "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-skill-external-fetch-gate", "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", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "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", "eval-health", "eval-run", "eval-report", "eval-triggers"];
14
14
  /** Core-only CLI entrypoints without a packages/cli wrapper. */
15
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", "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"];
16
16
  /** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
@@ -177,6 +177,12 @@ export declare function printCommandsList(io?: DispatchIo): void;
177
177
  * (#2172). Preserves the init/update/doctor first-run guidance from #2273.
178
178
  */
179
179
  export declare function printHelp(io?: DispatchIo): void;
180
+ /**
181
+ * Unknown-colon-verb remediation (#3439 / #2652).
182
+ * Agent-facing spelling is `deft <verb>` (works on consumer CLI and source).
183
+ * Bare `task <verb>` is not consumer-runnable on include-only Taskfiles.
184
+ */
185
+ export declare function unknownColonVerbHint(verb: string): string;
180
186
  /** Dispatch argv to a registered verb; returns the handler exit code. */
181
187
  export declare function dispatch(argv: string[], io?: DispatchIo): Promise<number>;
182
188
  /** Test seam: reset lazy handler cache between cases. */
package/dist/dispatch.js CHANGED
@@ -10,7 +10,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:pat
10
10
  import { engineInfo, userConfig } from "@deftai/directive-core";
11
11
  import { assertWriteTargetSafe } from "@deftai/directive-core/dist/fs/projection-containment.js";
12
12
  import { parseInitArgv, runInitDepositCli } from "@deftai/directive-core/init-deposit";
13
- import { appendAuditLog, disclosureLine, migrateLegacyPolicyKey, PLAN_POLICY_KEY, policyColonInvocation, policySetInvocation, projectDefinitionPath, resolvePolicy, resolveWipCap, setPolicy, } from "@deftai/directive-core/policy";
13
+ import { appendAuditLog, disclosureLine, migrateLegacyPolicyKey, PLAN_POLICY_KEY, POLICY_AUDIT_NOOP_STDOUT, policyColonInvocation, policySetInvocation, projectDefinitionPath, resolvePolicy, resolveWipCap, setPolicy, stampChangedToken, } from "@deftai/directive-core/policy";
14
14
  import { defaultWhich } from "@deftai/directive-core/scm";
15
15
  import { KNOWN_SUBAGENT_BACKEND_IDS, probeSubagentBackends, resolveSwarmSubagentBackend, } from "@deftai/directive-core/swarm";
16
16
  import { atomicWriteProjectDefinition } from "@deftai/directive-core/vbrief-build";
@@ -75,6 +75,7 @@ export const CLI_MODULE_VERBS = [
75
75
  "session-start",
76
76
  "session-ready",
77
77
  "occupancy-steal",
78
+ "occupancy-release",
78
79
  "plan-sequence",
79
80
  "slice",
80
81
  "subagent-monitor",
@@ -83,6 +84,7 @@ export const CLI_MODULE_VERBS = [
83
84
  "triage-bootstrap",
84
85
  "triage-bulk",
85
86
  "triage-classify",
87
+ "triage-evaluate",
86
88
  "triage-help",
87
89
  "triage-queue",
88
90
  "triage-reconcile",
@@ -140,7 +142,9 @@ export const CLI_MODULE_VERBS = [
140
142
  "verify-tools",
141
143
  "verify-wip-cap",
142
144
  "verify-orphan-active",
145
+ "verify-lifecycle-visible",
143
146
  "verify-completed-tracked",
147
+ "verify-completed-write-guard",
144
148
  "verify-literal-ac",
145
149
  "verify-ac",
146
150
  "verify-agents-md-budget",
@@ -220,6 +224,7 @@ export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
220
224
  "policy:allow-direct-commits": "allow-direct-commits",
221
225
  "policy:allow-bot-merge": "allow-bot-merge",
222
226
  "policy:enable-value-feedback": "enable-value-feedback",
227
+ "policy:disable-host-hooks": "disable-host-hooks",
223
228
  "policy:set-ceremony-dial": "set-ceremony-dial",
224
229
  "policy:clear-value-feedback": "clear-value-feedback",
225
230
  "policy:disable-directive": "disable-directive",
@@ -282,7 +287,9 @@ export const VERB_ALIASES = {
282
287
  "verify:vbrief-conformance": "vbrief-validate",
283
288
  "verify:wip-cap": "verify-wip-cap",
284
289
  "verify:orphan-active": "verify-orphan-active",
290
+ "verify:lifecycle-visible": "verify-lifecycle-visible",
285
291
  "verify:completed-tracked": "verify-completed-tracked",
292
+ "verify:completed-write-guard": "verify-completed-write-guard",
286
293
  "verify:literal-ac": "verify-literal-ac",
287
294
  "verify:literal-acceptance": "verify-literal-ac",
288
295
  "verify:ac": "verify-ac",
@@ -343,6 +350,7 @@ export const VERB_ALIASES = {
343
350
  "triage:bootstrap": "triage-bootstrap",
344
351
  "triage:summary": "triage-summary",
345
352
  "triage:queue": "triage-queue",
353
+ "triage:evaluate": "triage-evaluate",
346
354
  "triage:scope": "triage-scope",
347
355
  ...TRIAGE_ACTION_COLON_ALIASES,
348
356
  ...POLICY_ACTION_COLON_ALIASES,
@@ -360,6 +368,8 @@ export const VERB_ALIASES = {
360
368
  "session:start": "session-start",
361
369
  "session:ready": "session-ready",
362
370
  "occupancy:steal": "occupancy-steal",
371
+ "occupancy:release": "occupancy-release",
372
+ "session:end": "occupancy-release",
363
373
  ...FRESHNESS_COLON_ALIASES,
364
374
  "lifecycle:event": "lifecycle-event",
365
375
  "lifecycle:stats": "lifecycle-stats",
@@ -2122,7 +2132,7 @@ function loadProjectDefinitionForWrite(projectRoot) {
2122
2132
  throw new PolicySetError("PROJECT-DEFINITION 'plan' is not an object", "config");
2123
2133
  }
2124
2134
  const planObj = plan;
2125
- migrateLegacyPolicyKey(planObj);
2135
+ const legacyMigrated = migrateLegacyPolicyKey(planObj);
2126
2136
  let policy = planObj[PLAN_POLICY_KEY];
2127
2137
  if (policy === undefined) {
2128
2138
  policy = {};
@@ -2131,31 +2141,31 @@ function loadProjectDefinitionForWrite(projectRoot) {
2131
2141
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
2132
2142
  throw new PolicySetError("plan.policy is not an object", "config");
2133
2143
  }
2134
- return { path, data, policyBlock: policy };
2144
+ return { path, data, policyBlock: policy, legacyMigrated };
2135
2145
  }
2136
2146
  /** Write plan.policy.wipCap in place + append the audit row (mirrors set_wip_cap). */
2137
2147
  function writeWipCap(projectRoot, cap, actor, note) {
2138
- const { path, data, policyBlock } = loadProjectDefinitionForWrite(projectRoot);
2148
+ const { path, data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(projectRoot);
2139
2149
  const previous = policyBlock.wipCap;
2140
2150
  policyBlock.wipCap = cap;
2141
- assertWriteTargetSafe(projectRoot, path);
2142
- atomicWriteProjectDefinition(path, data);
2143
- const changed = previous !== cap;
2151
+ const changed = previous !== cap || legacyMigrated;
2144
2152
  const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
2145
2153
  if (note)
2146
2154
  parts.push(`note=${sanitizeNote(note)}`);
2147
- const auditEntry = parts.join(" ");
2148
- appendAuditLog(projectRoot, auditEntry);
2155
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2156
+ if (changed) {
2157
+ assertWriteTargetSafe(projectRoot, path);
2158
+ atomicWriteProjectDefinition(path, data);
2159
+ }
2160
+ appendAuditLog(projectRoot, auditEntry, changed);
2149
2161
  return { changed, auditEntry };
2150
2162
  }
2151
2163
  /** Write plan.policy.swarmSubagentBackend in place + append the audit row. */
2152
2164
  function writeSubagentBackend(projectRoot, backendId, actor, note) {
2153
- const { path, data, policyBlock } = loadProjectDefinitionForWrite(projectRoot);
2165
+ const { path, data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(projectRoot);
2154
2166
  const previous = policyBlock.swarmSubagentBackend;
2155
2167
  policyBlock.swarmSubagentBackend = backendId;
2156
- assertWriteTargetSafe(projectRoot, path);
2157
- atomicWriteProjectDefinition(path, data);
2158
- const changed = previous !== backendId;
2168
+ const changed = previous !== backendId || legacyMigrated;
2159
2169
  const parts = [
2160
2170
  `actor=${actor}`,
2161
2171
  `swarmSubagentBackend=${backendId}`,
@@ -2163,8 +2173,12 @@ function writeSubagentBackend(projectRoot, backendId, actor, note) {
2163
2173
  ];
2164
2174
  if (note)
2165
2175
  parts.push(`note=${sanitizeNote(note)}`);
2166
- const auditEntry = parts.join(" ");
2167
- appendAuditLog(projectRoot, auditEntry);
2176
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2177
+ if (changed) {
2178
+ assertWriteTargetSafe(projectRoot, path);
2179
+ atomicWriteProjectDefinition(path, data);
2180
+ }
2181
+ appendAuditLog(projectRoot, auditEntry, changed);
2168
2182
  return { changed, auditEntry };
2169
2183
  }
2170
2184
  /** Serialise the probe output for `subagent-backends --format json`. */
@@ -2229,7 +2243,7 @@ function applyBranchPolicy(args, io) {
2229
2243
  io.writeOut(` audit: meta/policy-changes.log :: ${result.auditEntry}\n`);
2230
2244
  }
2231
2245
  else {
2232
- io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
2246
+ io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
2233
2247
  }
2234
2248
  io.writeOut(`${disclosureLine(resolvePolicy(args.projectRoot))}\n`);
2235
2249
  return 0;
@@ -2258,7 +2272,7 @@ function applyWipCap(args, io) {
2258
2272
  io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
2259
2273
  }
2260
2274
  else {
2261
- io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
2275
+ io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
2262
2276
  }
2263
2277
  const result = resolveWipCap(args.projectRoot);
2264
2278
  io.writeOut(`[deft policy] plan.policy.wipCap=${result.cap} (source: ${result.source}).\n`);
@@ -2278,7 +2292,7 @@ function applySubagentBackend(args, io) {
2278
2292
  io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
2279
2293
  }
2280
2294
  else {
2281
- io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
2295
+ io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
2282
2296
  }
2283
2297
  const result = resolveSwarmSubagentBackend(args.projectRoot);
2284
2298
  io.writeOut(`[deft policy] plan.policy.swarmSubagentBackend=${pyRepr(result.backend_id)} ` +
@@ -2612,6 +2626,14 @@ const CURATED_HELP_GROUPS = [
2612
2626
  name: "occupancy:steal",
2613
2627
  summary: "Supersede a live worktree occupancy lease (--confirm --occupant)",
2614
2628
  },
2629
+ {
2630
+ name: "occupancy:release",
2631
+ summary: "Release this worktree occupancy lease (owner live, or expired residue)",
2632
+ },
2633
+ {
2634
+ name: "session:end",
2635
+ summary: "End this mutation session by releasing occupancy (same as occupancy:release)",
2636
+ },
2615
2637
  {
2616
2638
  name: "freshness:report",
2617
2639
  summary: "Bound vs live deposit generation (current|stale_soft|stale_hard)",
@@ -2720,6 +2742,23 @@ async function invokeHandler(handler, argv) {
2720
2742
  return typeof code === "number" ? code : 0;
2721
2743
  }
2722
2744
  const CLI_PACKAGE = "@deftai/directive";
2745
+ const PLAN_SEQUENCE_VERBS = "set|current|clear|advance";
2746
+ /**
2747
+ * Unknown-colon-verb remediation (#3439 / #2652).
2748
+ * Agent-facing spelling is `deft <verb>` (works on consumer CLI and source).
2749
+ * Bare `task <verb>` is not consumer-runnable on include-only Taskfiles.
2750
+ */
2751
+ export function unknownColonVerbHint(verb) {
2752
+ if (verb.startsWith("plan-sequence:") && !(verb in PLAN_SEQUENCE_ALIAS_SUBCOMMANDS)) {
2753
+ return (`hint: plan-sequence verbs are ${PLAN_SEQUENCE_VERBS} ` +
2754
+ `(e.g. \`deft plan-sequence:current\`). '${verb}' is not a verb. ` +
2755
+ `Do not run bare \`task ${verb}\` -- consumer include-only Taskfiles cannot resolve it.\n`);
2756
+ }
2757
+ const hyphen = verb.replaceAll(":", "-");
2758
+ return (`hint: try \`deft ${verb}\` (CLI) or \`task deft:${verb}\` (consumer Taskfile). ` +
2759
+ `Bare \`task ${verb}\` does not exist on include-only deposits. ` +
2760
+ `Hyphen stem: ${hyphen}.\n`);
2761
+ }
2723
2762
  function versionBanner() {
2724
2763
  const info = engineInfo();
2725
2764
  return `${CLI_PACKAGE} (engine: ${info.name}@${info.version})\n`;
@@ -2743,8 +2782,7 @@ export async function dispatch(argv, io = defaultIo()) {
2743
2782
  if (canonical === null) {
2744
2783
  io.writeErr(`directive: unknown verb '${verb}'\n`);
2745
2784
  if (verb?.includes(":")) {
2746
- io.writeErr(`hint: prefer \`task ${verb}\` (Taskfile) or the hyphen stem ` +
2747
- `(e.g. pr:watch → pr-watch / \`task pr:watch\`)\n`);
2785
+ io.writeErr(unknownColonVerbHint(verb));
2748
2786
  }
2749
2787
  return 1;
2750
2788
  }
@@ -16,7 +16,7 @@ export interface HumanPresenceMintSeams {
16
16
  /** Environ for agent-shell marker detection (default: process.env). */
17
17
  readonly environ?: NodeJS.ProcessEnv;
18
18
  /**
19
- * True when a controlling terminal device is available (`/dev/tty` or `CONIN$`).
19
+ * True when a controlling terminal device is available (`/dev/tty` or `\\.\CONIN$`).
20
20
  * Default: open/close the platform controlling terminal (fail-closed on error).
21
21
  */
22
22
  readonly hasControllingTerminal?: () => boolean;
@@ -27,6 +27,15 @@ export interface HumanPresenceMintSeams {
27
27
  readonly readInteractiveConfirm?: () => string | null;
28
28
  }
29
29
  export declare function looksLikeAgentShell(environ: NodeJS.ProcessEnv): boolean;
30
+ /** Platform controlling-terminal path (`\\.\CONIN$` on win32, `/dev/tty` elsewhere). */
31
+ export declare function controllingTerminalPath(platform?: NodeJS.Platform): string;
32
+ /**
33
+ * Open flag for the platform controlling-terminal device (#3596 leftover).
34
+ *
35
+ * Win32 stays `r+` so the probe and phrase-read share one flag. The leftover
36
+ * path is the Windows device-namespace CONIN$ string, not bare CONIN$.
37
+ */
38
+ export declare function controllingTerminalOpenFlag(platform?: NodeJS.Platform): "r" | "r+";
30
39
  export declare function resolveHumanPresenceMintSeams(seams?: HumanPresenceMintSeams): Required<HumanPresenceMintSeams>;
31
40
  /** Active UAT campaign id, or null when no lease is active. */
32
41
  export declare function activeUatCampaignId(projectRoot: string): string | null;
@@ -43,7 +52,7 @@ export declare function refuseMintWhileUatActive(verb: string, projectRoot: stri
43
52
  * Multi-factor human-presence gate (applies only when UAT lease is inactive):
44
53
  * 1. No known agent/CI env markers
45
54
  * 2. Interactive TTY (stdin + stdout isTTY)
46
- * 3. Controlling terminal device present (`/dev/tty` / `CONIN$`)
55
+ * 3. Controlling terminal device present (`/dev/tty` / `\\.\CONIN$`)
47
56
  * 4. Explicit argv `--confirm` (flag alone never enough)
48
57
  * 5. Interactive typed phrase `mint` (argv --confirm alone never enough even on PTY)
49
58
  *
@@ -67,10 +67,23 @@ export function looksLikeAgentShell(environ) {
67
67
  function defaultIsTty() {
68
68
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
69
69
  }
70
+ /** Platform controlling-terminal path (`\\.\CONIN$` on win32, `/dev/tty` elsewhere). */
71
+ export function controllingTerminalPath(platform = process.platform) {
72
+ // Real-console diagnostic on #3596: bare CONIN$ and CON ENOENT; device-namespace path opens.
73
+ return platform === "win32" ? "\\\\.\\CONIN$" : "/dev/tty";
74
+ }
75
+ /**
76
+ * Open flag for the platform controlling-terminal device (#3596 leftover).
77
+ *
78
+ * Win32 stays `r+` so the probe and phrase-read share one flag. The leftover
79
+ * path is the Windows device-namespace CONIN$ string, not bare CONIN$.
80
+ */
81
+ export function controllingTerminalOpenFlag(platform = process.platform) {
82
+ return platform === "win32" ? "r+" : "r";
83
+ }
70
84
  function defaultHasControllingTerminal() {
71
85
  try {
72
- const path = process.platform === "win32" ? "CONIN$" : "/dev/tty";
73
- const fd = openSync(path, "r");
86
+ const fd = openSync(controllingTerminalPath(), controllingTerminalOpenFlag());
74
87
  closeSync(fd);
75
88
  return true;
76
89
  }
@@ -83,8 +96,7 @@ function defaultReadInteractiveConfirm() {
83
96
  // so agent-controlled stdin alone cannot supply the confirm phrase (#3110).
84
97
  let fd = null;
85
98
  try {
86
- const path = process.platform === "win32" ? "CONIN$" : "/dev/tty";
87
- fd = openSync(path, "r");
99
+ fd = openSync(controllingTerminalPath(), controllingTerminalOpenFlag());
88
100
  const buf = Buffer.alloc(256);
89
101
  const n = readSync(fd, buf, 0, buf.length, null);
90
102
  if (n <= 0)
@@ -142,7 +154,7 @@ export function refuseMintWhileUatActive(verb, projectRoot) {
142
154
  * Multi-factor human-presence gate (applies only when UAT lease is inactive):
143
155
  * 1. No known agent/CI env markers
144
156
  * 2. Interactive TTY (stdin + stdout isTTY)
145
- * 3. Controlling terminal device present (`/dev/tty` / `CONIN$`)
157
+ * 3. Controlling terminal device present (`/dev/tty` / `\\.\CONIN$`)
146
158
  * 4. Explicit argv `--confirm` (flag alone never enough)
147
159
  * 5. Interactive typed phrase `mint` (argv --confirm alone never enough even on PTY)
148
160
  *
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ export declare function parseArgs(argv: readonly string[]): {
3
+ projectRoot: string;
4
+ error?: string;
5
+ };
6
+ export declare function run(argv: readonly string[]): number;
7
+ //# sourceMappingURL=occupancy-release.d.ts.map
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { releaseOccupancy } from "@deftai/directive-core/session";
4
+ export function parseArgs(argv) {
5
+ const parsed = { projectRoot: "." };
6
+ for (let i = 0; i < argv.length; i += 1) {
7
+ const arg = argv[i];
8
+ if (arg === "--project-root") {
9
+ const value = argv[i + 1];
10
+ if (value === undefined) {
11
+ return { ...parsed, error: "argument --project-root: expected one argument" };
12
+ }
13
+ parsed.projectRoot = value;
14
+ i += 1;
15
+ }
16
+ else if (arg?.startsWith("--project-root=")) {
17
+ parsed.projectRoot = arg.slice("--project-root=".length);
18
+ }
19
+ else {
20
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
21
+ }
22
+ }
23
+ return parsed;
24
+ }
25
+ export function run(argv) {
26
+ const args = parseArgs(argv);
27
+ if (args.error !== undefined) {
28
+ process.stderr.write(`occupancy:release: ${args.error}\n`);
29
+ return 2;
30
+ }
31
+ const result = releaseOccupancy(resolve(args.projectRoot), {
32
+ env: process.env,
33
+ });
34
+ const sink = result.code === 0 ? process.stdout : process.stderr;
35
+ sink.write(`${result.message}\n`);
36
+ return result.code;
37
+ }
38
+ //# sourceMappingURL=occupancy-release.js.map
@@ -1,3 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ interface Parsed {
3
+ projectRoot: string;
4
+ action: "set" | "current" | "clear" | "advance" | null;
5
+ file: string | null;
6
+ jsonInline: string | null;
7
+ emitJson: boolean;
8
+ error?: string;
9
+ }
10
+ export declare function parseArgs(argv: string[]): Parsed;
2
11
  export declare function main(argv?: string[]): number;
12
+ export {};
3
13
  //# sourceMappingURL=plan-sequence.d.ts.map
@@ -6,7 +6,7 @@ import { readFileSync } from "node:fs";
6
6
  import { resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { advancePlanSequence, clearPlanSequence, createPlanSequence, parsePlanSequence, readPlanSequence, writePlanSequence, } from "@deftai/directive-core/plan-sequence";
9
- function parseArgs(argv) {
9
+ export function parseArgs(argv) {
10
10
  const parsed = {
11
11
  projectRoot: ".",
12
12
  action: null,
@@ -19,6 +19,8 @@ function parseArgs(argv) {
19
19
  const arg = argv[i];
20
20
  if (arg === undefined)
21
21
  continue;
22
+ if (arg === "--")
23
+ continue;
22
24
  if (arg === "--json") {
23
25
  parsed.emitJson = true;
24
26
  }
package/dist/policy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { type CeremonyDepth } from "@deftai/directive-core/policy";
2
+ import { type CeremonyDepth, type HookHost } from "@deftai/directive-core/policy";
3
3
  interface ShowArgs {
4
4
  format: "text" | "json";
5
5
  changedOnly: boolean;
@@ -8,10 +8,11 @@ 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-directive" | "enable-directive" | "resolve";
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";
12
12
  confirm: boolean;
13
13
  actor: string;
14
14
  note: string;
15
+ host?: HookHost;
15
16
  projectRoot: string;
16
17
  format: "text" | "json";
17
18
  changedOnly: boolean;
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, 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, 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, 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";
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-directive|enable-directive|resolve] ...";
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] ...";
118
118
  return makeSetError(usage);
119
119
  }
120
120
  const cmd = argv[0];
@@ -152,6 +152,7 @@ export function parseArgs(argv) {
152
152
  cmd === "enable-value-feedback" ||
153
153
  cmd === "clear-value-feedback" ||
154
154
  cmd === "set-ceremony-dial" ||
155
+ cmd === "disable-host-hooks" ||
155
156
  cmd === "disable-directive" ||
156
157
  cmd === "enable-directive") {
157
158
  let confirm = false;
@@ -167,11 +168,14 @@ export function parseArgs(argv) {
167
168
  ? policyColonInvocation("clear-value-feedback")
168
169
  : cmd === "set-ceremony-dial"
169
170
  ? policyColonInvocation("set-ceremony-dial")
170
- : cmd === "disable-directive"
171
- ? policyColonInvocation("disable-directive")
172
- : policyColonInvocation("enable-directive");
171
+ : cmd === "disable-host-hooks"
172
+ ? policyColonInvocation("disable-host-hooks")
173
+ : cmd === "disable-directive"
174
+ ? policyColonInvocation("disable-directive")
175
+ : policyColonInvocation("enable-directive");
173
176
  let note = "";
174
177
  let projectRoot = ".";
178
+ let host;
175
179
  let ceremonyOverride;
176
180
  let ceremonyEnabled;
177
181
  for (let i = 1; i < argv.length; i += 1) {
@@ -253,10 +257,32 @@ export function parseArgs(argv) {
253
257
  }
254
258
  ceremonyEnabled = v === "true";
255
259
  }
260
+ else if (cmd === "disable-host-hooks" && arg === "--host") {
261
+ const v = argv[i + 1];
262
+ const parsedHost = parseHookHost(v);
263
+ if (parsedHost === null) {
264
+ return makeSetError("argument --host: expected claude|cursor|grok|codex");
265
+ }
266
+ host = parsedHost;
267
+ i += 1;
268
+ }
269
+ else if (cmd === "disable-host-hooks" && arg?.startsWith("--host=")) {
270
+ const parsedHost = parseHookHost(arg.slice("--host=".length));
271
+ if (parsedHost === null) {
272
+ return makeSetError("argument --host: expected claude|cursor|grok|codex");
273
+ }
274
+ host = parsedHost;
275
+ }
276
+ else if (arg === "--") {
277
+ continue;
278
+ }
256
279
  else {
257
280
  return makeSetError(`unrecognized argument: ${arg}`);
258
281
  }
259
282
  }
283
+ if (cmd === "disable-host-hooks" && host === undefined) {
284
+ return makeSetError("disable-host-hooks requires --host claude|cursor|grok|codex");
285
+ }
260
286
  return {
261
287
  cmd,
262
288
  confirm,
@@ -266,6 +292,7 @@ export function parseArgs(argv) {
266
292
  format: "text",
267
293
  changedOnly: false,
268
294
  field: null,
295
+ host,
269
296
  ceremonyOverride,
270
297
  ceremonyEnabled,
271
298
  };
@@ -352,6 +379,21 @@ function runEnableValueFeedback(args) {
352
379
  process.stdout.write(result.stdout);
353
380
  return result.exitCode;
354
381
  }
382
+ function runDisableHostHooks(args) {
383
+ const host = args.host;
384
+ if (host === undefined) {
385
+ process.stderr.write("policy: disable-host-hooks requires --host claude|cursor|grok|codex\n");
386
+ return 2;
387
+ }
388
+ const result = disableHostHooks(pathResolve(args.projectRoot), {
389
+ host,
390
+ confirm: args.confirm,
391
+ actor: args.actor,
392
+ note: args.note,
393
+ });
394
+ process.stdout.write(result.stdout);
395
+ return result.exitCode;
396
+ }
355
397
  function runSet(args) {
356
398
  const projectRoot = pathResolve(args.projectRoot);
357
399
  if (args.cmd === "allow-direct-commits" && !args.confirm) {
@@ -373,7 +415,7 @@ function runSet(args) {
373
415
  process.stdout.write(` audit: meta/policy-changes.log :: ${auditEntry}\n`);
374
416
  }
375
417
  else {
376
- process.stdout.write(" no-op: value already matched (audit entry still appended for trail).\n");
418
+ process.stdout.write(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
377
419
  }
378
420
  process.stdout.write(`${disclosureLine(resolvePolicy(projectRoot))}\n`);
379
421
  return 0;
@@ -454,7 +496,7 @@ function runAllowBotMerge(args) {
454
496
  process.stdout.write(` audit: meta/policy-changes.log :: ${auditEntry}\n`);
455
497
  }
456
498
  else {
457
- process.stdout.write(" no-op: value already matched (audit entry still appended for trail).\n");
499
+ process.stdout.write(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
458
500
  }
459
501
  const line = humanMergeDisclosureLine(resolveHumanMergePolicy(projectRoot));
460
502
  if (line !== null) {
@@ -504,6 +546,9 @@ export function run(argv) {
504
546
  if (args.cmd === "enable-value-feedback") {
505
547
  return runEnableValueFeedback(args);
506
548
  }
549
+ if (args.cmd === "disable-host-hooks") {
550
+ return runDisableHostHooks(args);
551
+ }
507
552
  if (args.cmd === "clear-value-feedback") {
508
553
  return runClearValueFeedback(args);
509
554
  }
@@ -7,6 +7,8 @@ interface ParsedArgs {
7
7
  forIssue?: number;
8
8
  allowStale?: boolean;
9
9
  allowMissingBootstrap?: boolean;
10
+ skipDriftProbe?: boolean;
11
+ workSelection?: boolean;
10
12
  quiet?: boolean;
11
13
  error?: string;
12
14
  }
@@ -58,6 +58,12 @@ export function parseArgs(argv) {
58
58
  else if (arg === "--allow-missing-bootstrap") {
59
59
  parsed.allowMissingBootstrap = true;
60
60
  }
61
+ else if (arg === "--skip-drift-probe") {
62
+ parsed.skipDriftProbe = true;
63
+ }
64
+ else if (arg === "--work-selection") {
65
+ parsed.workSelection = true;
66
+ }
61
67
  else if (arg === "--quiet") {
62
68
  parsed.quiet = true;
63
69
  }
@@ -81,6 +87,7 @@ export function run(argv) {
81
87
  forIssue: args.forIssue ?? null,
82
88
  allowStale: args.allowStale ?? false,
83
89
  allowMissingBootstrap: args.allowMissingBootstrap ?? false,
90
+ skipDriftProbe: (args.skipDriftProbe ?? false) && !args.workSelection,
84
91
  });
85
92
  const quiet = args.quiet ?? false;
86
93
  if (result.code === 0) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { accept, createDefaultDeps, deferAction, formatDecision, history, markDuplicate, needsAc, reject, reset, status, TriageError, UpstreamCloseError, } from "@deftai/directive-core/dist/triage/actions/index.js";
4
+ import { accept, createDefaultDeps, deferAction, formatAcceptOutput, formatDecision, history, markDuplicate, needsAc, reject, reset, status, TriageError, UpstreamCloseError, } from "@deftai/directive-core/dist/triage/actions/index.js";
5
5
  const VALID_CMDS = new Set([
6
6
  "accept",
7
7
  "reject",
@@ -21,6 +21,8 @@ export function parseArgs(argv) {
21
21
  parsed.cmd = argv[0];
22
22
  for (let i = 1; i < argv.length; i += 1) {
23
23
  const arg = argv[i];
24
+ if (arg === "--")
25
+ continue;
24
26
  if (arg === "--issue") {
25
27
  const value = argv[i + 1];
26
28
  if (value === undefined)
@@ -156,8 +158,7 @@ export function run(argv) {
156
158
  autoPromote: args.autoPromote === true,
157
159
  force: args.force === true,
158
160
  });
159
- const promoteNote = args.autoPromote === true ? " + auto-promote" : "";
160
- process.stdout.write(`accept #${n} (${repo}) -> ${decisionId}${promoteNote}\n`);
161
+ process.stdout.write(`${formatAcceptOutput(n, repo, decisionId, { autoPromote: args.autoPromote === true })}\n`);
161
162
  }
162
163
  else if (args.cmd === "reject") {
163
164
  const decisionId = reject(n, repo, args.reason ?? "", deps, {
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ interface Parsed {
3
+ projectRoot: string;
4
+ repo: string | null;
5
+ issues: number[];
6
+ concurrency: number;
7
+ json: boolean;
8
+ error?: string;
9
+ }
10
+ export declare function parseArgs(argv: string[]): Parsed;
11
+ export declare function run(argv: string[]): Promise<number>;
12
+ export declare function main(argv: string[]): Promise<number>;
13
+ export {};
14
+ //# sourceMappingURL=triage-evaluate.d.ts.map
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { DEFAULT_CONCURRENCY, EvaluateError, evaluateIssues, renderEvaluateText, } from "@deftai/directive-core/dist/triage/evaluate/index.js";
5
+ import { interceptHelp } from "@deftai/directive-core/dist/triage/help/index.js";
6
+ import { resolveRepo } from "@deftai/directive-core/dist/triage/queue/repo.js";
7
+ export function parseArgs(argv) {
8
+ const parsed = {
9
+ projectRoot: process.env.DEFT_PROJECT_ROOT ?? process.cwd(),
10
+ repo: process.env.DEFT_TRIAGE_REPO ?? null,
11
+ issues: [],
12
+ concurrency: DEFAULT_CONCURRENCY,
13
+ json: false,
14
+ };
15
+ for (let i = 0; i < argv.length; i += 1) {
16
+ const arg = argv[i];
17
+ if (arg === undefined || arg === "--") {
18
+ continue;
19
+ }
20
+ if (arg === "--project-root") {
21
+ if (i + 1 >= argv.length) {
22
+ return { ...parsed, error: "--project-root requires a path" };
23
+ }
24
+ i += 1;
25
+ const value = argv[i];
26
+ if (value === undefined) {
27
+ return { ...parsed, error: "--project-root requires a path" };
28
+ }
29
+ parsed.projectRoot = value;
30
+ }
31
+ else if (arg.startsWith("--project-root=")) {
32
+ parsed.projectRoot = arg.slice("--project-root=".length);
33
+ }
34
+ else if (arg === "--repo") {
35
+ if (i + 1 >= argv.length) {
36
+ return { ...parsed, error: "--repo requires owner/name" };
37
+ }
38
+ i += 1;
39
+ const value = argv[i];
40
+ if (value === undefined) {
41
+ return { ...parsed, error: "--repo requires owner/name" };
42
+ }
43
+ parsed.repo = value;
44
+ }
45
+ else if (arg.startsWith("--repo=")) {
46
+ parsed.repo = arg.slice("--repo=".length);
47
+ }
48
+ else if (arg === "--issue") {
49
+ if (i + 1 >= argv.length) {
50
+ return { ...parsed, error: "--issue requires a number" };
51
+ }
52
+ i += 1;
53
+ const value = argv[i];
54
+ if (value === undefined) {
55
+ return { ...parsed, error: "--issue requires a number" };
56
+ }
57
+ const n = Number.parseInt(value, 10);
58
+ if (!Number.isInteger(n) || n < 1) {
59
+ return { ...parsed, error: `--issue invalid: ${value}` };
60
+ }
61
+ parsed.issues.push(n);
62
+ }
63
+ else if (arg.startsWith("--issue=")) {
64
+ const n = Number.parseInt(arg.slice("--issue=".length), 10);
65
+ if (!Number.isInteger(n) || n < 1) {
66
+ return { ...parsed, error: `--issue invalid: ${arg}` };
67
+ }
68
+ parsed.issues.push(n);
69
+ }
70
+ else if (arg === "--concurrency") {
71
+ if (i + 1 >= argv.length) {
72
+ return { ...parsed, error: "--concurrency requires a positive integer" };
73
+ }
74
+ i += 1;
75
+ const value = argv[i];
76
+ if (value === undefined) {
77
+ return { ...parsed, error: "--concurrency requires a positive integer" };
78
+ }
79
+ const n = Number.parseInt(value, 10);
80
+ if (!Number.isInteger(n) || n < 1) {
81
+ return { ...parsed, error: `--concurrency invalid: ${value}` };
82
+ }
83
+ parsed.concurrency = n;
84
+ }
85
+ else if (arg.startsWith("--concurrency=")) {
86
+ const n = Number.parseInt(arg.slice("--concurrency=".length), 10);
87
+ if (!Number.isInteger(n) || n < 1) {
88
+ return { ...parsed, error: `--concurrency invalid: ${arg}` };
89
+ }
90
+ parsed.concurrency = n;
91
+ }
92
+ else if (arg === "--json") {
93
+ parsed.json = true;
94
+ }
95
+ else if (arg.startsWith("-")) {
96
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
97
+ }
98
+ else {
99
+ const n = Number.parseInt(arg.replace(/^#/u, ""), 10);
100
+ if (!Number.isInteger(n) || n < 1) {
101
+ return { ...parsed, error: `invalid issue number: ${arg}` };
102
+ }
103
+ parsed.issues.push(n);
104
+ }
105
+ }
106
+ return parsed;
107
+ }
108
+ export async function run(argv) {
109
+ const help = interceptHelp("triage_evaluate", argv);
110
+ if (help !== null) {
111
+ return help;
112
+ }
113
+ const parsed = parseArgs(argv);
114
+ if (parsed.error !== undefined) {
115
+ process.stderr.write(`triage:evaluate: ${parsed.error}\n`);
116
+ return 2;
117
+ }
118
+ const projectRoot = resolve(parsed.projectRoot);
119
+ const repo = resolveRepo(parsed.repo, projectRoot);
120
+ if (repo === null) {
121
+ process.stderr.write("triage:evaluate: --repo owner/name is required\n");
122
+ return 2;
123
+ }
124
+ try {
125
+ const result = await evaluateIssues({
126
+ projectRoot,
127
+ repo,
128
+ issues: parsed.issues,
129
+ concurrency: parsed.concurrency,
130
+ });
131
+ if (parsed.json) {
132
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
133
+ }
134
+ else {
135
+ process.stdout.write(renderEvaluateText(result));
136
+ }
137
+ return result.verdicts.some((v) => v.error !== null) ? 1 : 0;
138
+ }
139
+ catch (err) {
140
+ const message = err instanceof EvaluateError ? err.message : String(err);
141
+ process.stderr.write(`triage:evaluate: ${message}\n`);
142
+ return err instanceof EvaluateError ? 2 : 1;
143
+ }
144
+ }
145
+ export async function main(argv) {
146
+ return run(argv);
147
+ }
148
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
149
+ void run(process.argv.slice(2)).then((code) => {
150
+ process.exit(code);
151
+ });
152
+ }
153
+ //# sourceMappingURL=triage-evaluate.js.map
@@ -88,7 +88,7 @@ export function parseArgs(argv) {
88
88
  };
89
89
  for (let i = 0; i < argv.length; i += 1) {
90
90
  const arg = argv[i];
91
- if (arg === "queue") {
91
+ if (arg === "queue" || arg === "--") {
92
92
  continue;
93
93
  }
94
94
  if (arg === "--include-blocked") {
@@ -197,6 +197,9 @@ export function parseShowArgs(argv) {
197
197
  }
198
198
  for (let i = start; i < argv.length; i += 1) {
199
199
  const arg = argv[i];
200
+ if (arg === "--") {
201
+ continue;
202
+ }
200
203
  const commonHit = parseCommonFlag(arg, argv, i, parsed);
201
204
  if (commonHit !== null) {
202
205
  if (commonHit.error !== undefined) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { evaluateCompletedTracked } from "@deftai/directive-core/lifecycle";
4
+ import { COMPLETED_TRACKED_PROGRESS_MIN_BLOBS, COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS, evaluateCompletedTracked, shouldAnnounceProgress, shouldAnnounceUpFrontCount, } from "@deftai/directive-core/lifecycle";
5
5
  function parseIssueNumber(raw) {
6
6
  const trimmed = raw.startsWith("#") ? raw.slice(1) : raw;
7
7
  if (!/^\d+$/.test(trimmed)) {
@@ -95,12 +95,27 @@ export function run(argv) {
95
95
  return 2;
96
96
  }
97
97
  const projectRoot = resolve(args.projectRoot);
98
+ const thresholdMs = resolveProgressThresholdMs();
99
+ let announced = false;
98
100
  const result = evaluateCompletedTracked(projectRoot, {
99
101
  quiet: args.quiet,
100
102
  repo: args.repo,
101
103
  tip: args.tip,
102
104
  issue: args.issue,
103
105
  skipGh: args.skipGh,
106
+ onProgress: (event) => {
107
+ if (args.quiet || announced) {
108
+ return;
109
+ }
110
+ const minBlobs = resolveProgressMinBlobs();
111
+ const upFront = event.phase === "listed" &&
112
+ shouldAnnounceUpFrontCount(event.terminalCount, event.nonterminalCount, minBlobs);
113
+ if (!upFront && !shouldAnnounceProgress(event.elapsedMs, thresholdMs)) {
114
+ return;
115
+ }
116
+ announced = true;
117
+ writeProgress(event);
118
+ },
104
119
  });
105
120
  if (result.message.length > 0) {
106
121
  if (result.stream === "stdout") {
@@ -112,6 +127,24 @@ export function run(argv) {
112
127
  }
113
128
  return result.code;
114
129
  }
130
+ function resolveProgressThresholdMs() {
131
+ const raw = process.env.DEFT_COMPLETED_TRACKED_PROGRESS_MS;
132
+ if (raw !== undefined && /^\d+$/.test(raw)) {
133
+ return Number(raw);
134
+ }
135
+ return COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS;
136
+ }
137
+ function resolveProgressMinBlobs() {
138
+ const raw = process.env.DEFT_COMPLETED_TRACKED_PROGRESS_MIN_BLOBS;
139
+ if (raw !== undefined && /^\d+$/.test(raw)) {
140
+ return Number(raw);
141
+ }
142
+ return COMPLETED_TRACKED_PROGRESS_MIN_BLOBS;
143
+ }
144
+ function writeProgress(event) {
145
+ process.stderr.write(`verify:completed-tracked: still running (${event.elapsedMs}ms); ` +
146
+ `reading ${event.terminalCount} terminal and ${event.nonterminalCount} nonterminal blobs.\n`);
147
+ }
115
148
  if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
116
149
  process.exit(run(process.argv.slice(2)));
117
150
  }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ baseRef: string;
5
+ quiet: boolean;
6
+ error?: string;
7
+ }
8
+ /** Parse verify-completed-write-guard CLI args (#3679). */
9
+ export declare function parseArgs(argv: string[]): ParsedArgs;
10
+ /** Run the gate and return the process exit code. */
11
+ export declare function run(argv: string[]): number;
12
+ export {};
13
+ //# sourceMappingURL=verify-completed-write-guard.d.ts.map
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for verify:completed-write-guard (#3679).
4
+ */
5
+ import { resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { evaluateCompletedWriteGuard } from "@deftai/directive-core/lifecycle";
8
+ /** Parse verify-completed-write-guard CLI args (#3679). */
9
+ export function parseArgs(argv) {
10
+ const parsed = {
11
+ projectRoot: ".",
12
+ baseRef: "",
13
+ quiet: false,
14
+ };
15
+ for (let i = 0; i < argv.length; i += 1) {
16
+ const arg = argv[i];
17
+ if (arg === "--quiet") {
18
+ parsed.quiet = true;
19
+ }
20
+ else if (arg === "--project-root") {
21
+ const value = argv[i + 1];
22
+ if (value === undefined) {
23
+ return { ...parsed, error: "argument --project-root: expected one argument" };
24
+ }
25
+ parsed.projectRoot = value;
26
+ i += 1;
27
+ }
28
+ else if (arg?.startsWith("--project-root=")) {
29
+ parsed.projectRoot = arg.slice("--project-root=".length);
30
+ }
31
+ else if (arg === "--base-ref") {
32
+ const value = argv[i + 1];
33
+ if (value === undefined) {
34
+ return { ...parsed, error: "argument --base-ref: expected one argument" };
35
+ }
36
+ parsed.baseRef = value;
37
+ i += 1;
38
+ }
39
+ else if (arg?.startsWith("--base-ref=")) {
40
+ parsed.baseRef = arg.slice("--base-ref=".length);
41
+ }
42
+ else {
43
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
44
+ }
45
+ }
46
+ return parsed;
47
+ }
48
+ /** Run the gate and return the process exit code. */
49
+ export function run(argv) {
50
+ const args = parseArgs(argv);
51
+ if (args.error !== undefined) {
52
+ process.stderr.write(`verify_completed_write_guard: ${args.error}\n`);
53
+ return 2;
54
+ }
55
+ const projectRoot = resolve(args.projectRoot);
56
+ const result = evaluateCompletedWriteGuard(projectRoot, {
57
+ ...(args.baseRef.length > 0 ? { baseRef: args.baseRef } : {}),
58
+ });
59
+ if (result.message.length > 0 && !args.quiet) {
60
+ if (result.code === 0) {
61
+ process.stdout.write(`${result.message}\n`);
62
+ }
63
+ else {
64
+ process.stderr.write(`${result.message}\n`);
65
+ }
66
+ }
67
+ return result.code;
68
+ }
69
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
70
+ process.exit(run(process.argv.slice(2)));
71
+ }
72
+ //# sourceMappingURL=verify-completed-write-guard.js.map
@@ -5,6 +5,10 @@ interface ParsedArgs {
5
5
  projectRoot: string;
6
6
  allowList: string | null;
7
7
  quiet: boolean;
8
+ enforce: boolean;
9
+ coverageReport: string | null;
10
+ coverageDir: string | null;
11
+ help?: boolean;
8
12
  error?: string;
9
13
  }
10
14
  /** Parse the verify-forward-coverage CLI args, mirroring the verify-encoding surface. */
@@ -2,6 +2,20 @@
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { evaluateForwardCoverage } from "@deftai/directive-core";
5
+ const HELP_TEXT = "Usage: verify-forward-coverage [--project-root <path>] [--staged|--head] [--allow-list <path>]\n" +
6
+ " [--coverage-dir <path>] [--coverage-report <path>]\n" +
7
+ " [--enforce] [--quiet]\n" +
8
+ " Fail-closed new-source-file existence (#1310) plus warn-first diff coverage of\n" +
9
+ " added/modified branches (#3514). The 90% per-diff threshold is not the 75\n" +
10
+ " global floor. Missing coverage reports skip the diff half.\n" +
11
+ " --enforce: fail closed when uncovered changed branches are below 90%.\n";
12
+ function takeValue(argv, i, flag) {
13
+ const value = argv[i + 1];
14
+ if (value === undefined) {
15
+ return { error: `argument ${flag}: expected one argument` };
16
+ }
17
+ return { value, next: i + 1 };
18
+ }
5
19
  /** Parse the verify-forward-coverage CLI args, mirroring the verify-encoding surface. */
6
20
  export function parseArgs(argv) {
7
21
  const parsed = {
@@ -9,6 +23,9 @@ export function parseArgs(argv) {
9
23
  projectRoot: ".",
10
24
  allowList: null,
11
25
  quiet: false,
26
+ enforce: false,
27
+ coverageReport: null,
28
+ coverageDir: null,
12
29
  };
13
30
  for (let i = 0; i < argv.length; i += 1) {
14
31
  const arg = argv[i];
@@ -21,48 +38,97 @@ export function parseArgs(argv) {
21
38
  else if (arg === "--quiet") {
22
39
  parsed.quiet = true;
23
40
  }
41
+ else if (arg === "--enforce") {
42
+ parsed.enforce = true;
43
+ }
44
+ else if (arg === "--help" || arg === "-h") {
45
+ return { ...parsed, help: true };
46
+ }
24
47
  else if (arg === "--project-root") {
25
- const value = argv[i + 1];
26
- if (value === undefined) {
27
- return { ...parsed, error: "argument --project-root: expected one argument" };
48
+ const taken = takeValue(argv, i, "--project-root");
49
+ if ("error" in taken) {
50
+ return { ...parsed, error: taken.error };
28
51
  }
29
- parsed.projectRoot = value;
30
- i += 1;
52
+ parsed.projectRoot = taken.value;
53
+ i = taken.next;
31
54
  }
32
55
  else if (arg?.startsWith("--project-root=")) {
33
56
  parsed.projectRoot = arg.slice("--project-root=".length);
34
57
  }
35
58
  else if (arg === "--allow-list") {
36
- const value = argv[i + 1];
37
- if (value === undefined) {
38
- return { ...parsed, error: "argument --allow-list: expected one argument" };
59
+ const taken = takeValue(argv, i, "--allow-list");
60
+ if ("error" in taken) {
61
+ return { ...parsed, error: taken.error };
39
62
  }
40
- parsed.allowList = value;
41
- i += 1;
63
+ parsed.allowList = taken.value;
64
+ i = taken.next;
42
65
  }
43
66
  else if (arg?.startsWith("--allow-list=")) {
44
67
  parsed.allowList = arg.slice("--allow-list=".length);
45
68
  }
69
+ else if (arg === "--coverage-report") {
70
+ const taken = takeValue(argv, i, "--coverage-report");
71
+ if ("error" in taken) {
72
+ return { ...parsed, error: taken.error };
73
+ }
74
+ parsed.coverageReport = taken.value;
75
+ i = taken.next;
76
+ }
77
+ else if (arg?.startsWith("--coverage-report=")) {
78
+ parsed.coverageReport = arg.slice("--coverage-report=".length);
79
+ }
80
+ else if (arg === "--coverage-dir") {
81
+ const taken = takeValue(argv, i, "--coverage-dir");
82
+ if ("error" in taken) {
83
+ return { ...parsed, error: taken.error };
84
+ }
85
+ parsed.coverageDir = taken.value;
86
+ i = taken.next;
87
+ }
88
+ else if (arg?.startsWith("--coverage-dir=")) {
89
+ parsed.coverageDir = arg.slice("--coverage-dir=".length);
90
+ }
46
91
  else {
47
92
  return { ...parsed, error: `unrecognized argument: ${arg}` };
48
93
  }
49
94
  }
50
95
  return parsed;
51
96
  }
97
+ function resolveCoverageReport(args) {
98
+ if (args.coverageReport !== null) {
99
+ return resolve(args.coverageReport);
100
+ }
101
+ if (args.coverageDir !== null) {
102
+ return resolve(args.coverageDir, "coverage-final.json");
103
+ }
104
+ return undefined;
105
+ }
52
106
  /** Run the gate and return the process exit code (argv parse error -> 2). */
53
107
  export function run(argv) {
54
108
  const args = parseArgs(argv);
109
+ if (args.help === true) {
110
+ process.stdout.write(HELP_TEXT);
111
+ return 0;
112
+ }
55
113
  if (args.error !== undefined) {
56
114
  process.stderr.write(`verify_forward_coverage: ${args.error}\n`);
57
115
  return 2;
58
116
  }
59
117
  const projectRoot = resolve(args.projectRoot);
60
118
  const allowListPath = args.allowList !== null ? resolve(args.allowList) : null;
61
- const result = evaluateForwardCoverage(projectRoot, { mode: args.mode, allowListPath });
119
+ const result = evaluateForwardCoverage(projectRoot, {
120
+ mode: args.mode,
121
+ allowListPath,
122
+ enforceDiffCoverage: args.enforce,
123
+ coverageReportPath: resolveCoverageReport(args),
124
+ });
62
125
  if (result.exitCode === 0) {
63
126
  if (!args.quiet) {
64
127
  process.stdout.write(`${result.message}\n`);
65
128
  }
129
+ else if (result.message.includes("ADVISORY")) {
130
+ process.stdout.write(`${result.message}\n`);
131
+ }
66
132
  }
67
133
  else {
68
134
  process.stderr.write(`${result.message}\n`);
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ enforce: boolean;
5
+ help?: boolean;
6
+ error?: string;
7
+ }
8
+ /** Parse verify-lifecycle-visible CLI args (#3505). */
9
+ export declare function parseArgs(argv: string[]): ParsedArgs;
10
+ /** Run the gate and return the process exit code. */
11
+ export declare function run(argv: string[]): number;
12
+ export {};
13
+ //# sourceMappingURL=verify-lifecycle-visible.d.ts.map
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for task verify:lifecycle-visible (#3505).
4
+ * Warn-only by default; pass --enforce to fail closed.
5
+ */
6
+ import { resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { evaluateLifecycleVisible } from "@deftai/directive-core/lifecycle-visible";
9
+ /** Parse verify-lifecycle-visible CLI args (#3505). */
10
+ export function parseArgs(argv) {
11
+ const parsed = { projectRoot: ".", enforce: false };
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const arg = argv[i];
14
+ if (arg === "--project-root") {
15
+ const value = argv[i + 1];
16
+ if (value === undefined) {
17
+ return { ...parsed, error: "argument --project-root: expected one argument" };
18
+ }
19
+ parsed.projectRoot = value;
20
+ i += 1;
21
+ }
22
+ else if (arg?.startsWith("--project-root=")) {
23
+ parsed.projectRoot = arg.slice("--project-root=".length);
24
+ }
25
+ else if (arg === "--enforce") {
26
+ parsed.enforce = true;
27
+ }
28
+ else if (arg === "--help" || arg === "-h") {
29
+ return { ...parsed, help: true };
30
+ }
31
+ else {
32
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
33
+ }
34
+ }
35
+ return parsed;
36
+ }
37
+ const HELP_TEXT = "Usage: verify-lifecycle-visible [--project-root <path>] [--enforce]\n" +
38
+ " Detect ignore/exclude/skip-worktree flags that hide xbrief/vbrief lifecycle roots (#3505).\n" +
39
+ " Default: warn-only (exit 0 with advisory report).\n" +
40
+ " --enforce: fail closed (exit 1) when a lifecycle root is hidden.\n" +
41
+ " Not part of task check — per-clone property, wired from session:start.\n";
42
+ /** Run the gate and return the process exit code. */
43
+ export function run(argv) {
44
+ const args = parseArgs(argv);
45
+ if (args.help === true) {
46
+ process.stdout.write(HELP_TEXT);
47
+ return 0;
48
+ }
49
+ if (args.error !== undefined) {
50
+ process.stderr.write(`verify_lifecycle_visible: ${args.error}\n`);
51
+ return 2;
52
+ }
53
+ const result = evaluateLifecycleVisible({
54
+ projectRoot: resolve(args.projectRoot),
55
+ enforce: args.enforce,
56
+ });
57
+ if (result.stream === "stdout") {
58
+ process.stdout.write(`${result.message}\n`);
59
+ }
60
+ else {
61
+ process.stderr.write(`${result.message}\n`);
62
+ }
63
+ return result.code;
64
+ }
65
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
66
+ process.exit(run(process.argv.slice(2)));
67
+ }
68
+ //# sourceMappingURL=verify-lifecycle-visible.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.105.0",
3
+ "version": "0.107.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/",
@@ -34,8 +34,8 @@
34
34
  "provenance": true
35
35
  },
36
36
  "dependencies": {
37
- "@deftai/directive-core": "^0.105.0",
38
- "@deftai/directive-content": "^0.105.0"
37
+ "@deftai/directive-core": "^0.107.0",
38
+ "@deftai/directive-content": "^0.107.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"