@deftai/directive 0.105.0 → 0.106.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.
@@ -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"],
@@ -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", "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-lifecycle-visible", "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"];
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";
@@ -140,6 +140,7 @@ export const CLI_MODULE_VERBS = [
140
140
  "verify-tools",
141
141
  "verify-wip-cap",
142
142
  "verify-orphan-active",
143
+ "verify-lifecycle-visible",
143
144
  "verify-completed-tracked",
144
145
  "verify-literal-ac",
145
146
  "verify-ac",
@@ -220,6 +221,7 @@ export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
220
221
  "policy:allow-direct-commits": "allow-direct-commits",
221
222
  "policy:allow-bot-merge": "allow-bot-merge",
222
223
  "policy:enable-value-feedback": "enable-value-feedback",
224
+ "policy:disable-host-hooks": "disable-host-hooks",
223
225
  "policy:set-ceremony-dial": "set-ceremony-dial",
224
226
  "policy:clear-value-feedback": "clear-value-feedback",
225
227
  "policy:disable-directive": "disable-directive",
@@ -282,6 +284,7 @@ export const VERB_ALIASES = {
282
284
  "verify:vbrief-conformance": "vbrief-validate",
283
285
  "verify:wip-cap": "verify-wip-cap",
284
286
  "verify:orphan-active": "verify-orphan-active",
287
+ "verify:lifecycle-visible": "verify-lifecycle-visible",
285
288
  "verify:completed-tracked": "verify-completed-tracked",
286
289
  "verify:literal-ac": "verify-literal-ac",
287
290
  "verify:literal-acceptance": "verify-literal-ac",
@@ -2122,7 +2125,7 @@ function loadProjectDefinitionForWrite(projectRoot) {
2122
2125
  throw new PolicySetError("PROJECT-DEFINITION 'plan' is not an object", "config");
2123
2126
  }
2124
2127
  const planObj = plan;
2125
- migrateLegacyPolicyKey(planObj);
2128
+ const legacyMigrated = migrateLegacyPolicyKey(planObj);
2126
2129
  let policy = planObj[PLAN_POLICY_KEY];
2127
2130
  if (policy === undefined) {
2128
2131
  policy = {};
@@ -2131,31 +2134,31 @@ function loadProjectDefinitionForWrite(projectRoot) {
2131
2134
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
2132
2135
  throw new PolicySetError("plan.policy is not an object", "config");
2133
2136
  }
2134
- return { path, data, policyBlock: policy };
2137
+ return { path, data, policyBlock: policy, legacyMigrated };
2135
2138
  }
2136
2139
  /** Write plan.policy.wipCap in place + append the audit row (mirrors set_wip_cap). */
2137
2140
  function writeWipCap(projectRoot, cap, actor, note) {
2138
- const { path, data, policyBlock } = loadProjectDefinitionForWrite(projectRoot);
2141
+ const { path, data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(projectRoot);
2139
2142
  const previous = policyBlock.wipCap;
2140
2143
  policyBlock.wipCap = cap;
2141
- assertWriteTargetSafe(projectRoot, path);
2142
- atomicWriteProjectDefinition(path, data);
2143
- const changed = previous !== cap;
2144
+ const changed = previous !== cap || legacyMigrated;
2144
2145
  const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
2145
2146
  if (note)
2146
2147
  parts.push(`note=${sanitizeNote(note)}`);
2147
- const auditEntry = parts.join(" ");
2148
- appendAuditLog(projectRoot, auditEntry);
2148
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2149
+ if (changed) {
2150
+ assertWriteTargetSafe(projectRoot, path);
2151
+ atomicWriteProjectDefinition(path, data);
2152
+ }
2153
+ appendAuditLog(projectRoot, auditEntry, changed);
2149
2154
  return { changed, auditEntry };
2150
2155
  }
2151
2156
  /** Write plan.policy.swarmSubagentBackend in place + append the audit row. */
2152
2157
  function writeSubagentBackend(projectRoot, backendId, actor, note) {
2153
- const { path, data, policyBlock } = loadProjectDefinitionForWrite(projectRoot);
2158
+ const { path, data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(projectRoot);
2154
2159
  const previous = policyBlock.swarmSubagentBackend;
2155
2160
  policyBlock.swarmSubagentBackend = backendId;
2156
- assertWriteTargetSafe(projectRoot, path);
2157
- atomicWriteProjectDefinition(path, data);
2158
- const changed = previous !== backendId;
2161
+ const changed = previous !== backendId || legacyMigrated;
2159
2162
  const parts = [
2160
2163
  `actor=${actor}`,
2161
2164
  `swarmSubagentBackend=${backendId}`,
@@ -2163,8 +2166,12 @@ function writeSubagentBackend(projectRoot, backendId, actor, note) {
2163
2166
  ];
2164
2167
  if (note)
2165
2168
  parts.push(`note=${sanitizeNote(note)}`);
2166
- const auditEntry = parts.join(" ");
2167
- appendAuditLog(projectRoot, auditEntry);
2169
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2170
+ if (changed) {
2171
+ assertWriteTargetSafe(projectRoot, path);
2172
+ atomicWriteProjectDefinition(path, data);
2173
+ }
2174
+ appendAuditLog(projectRoot, auditEntry, changed);
2168
2175
  return { changed, auditEntry };
2169
2176
  }
2170
2177
  /** Serialise the probe output for `subagent-backends --format json`. */
@@ -2229,7 +2236,7 @@ function applyBranchPolicy(args, io) {
2229
2236
  io.writeOut(` audit: meta/policy-changes.log :: ${result.auditEntry}\n`);
2230
2237
  }
2231
2238
  else {
2232
- io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
2239
+ io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
2233
2240
  }
2234
2241
  io.writeOut(`${disclosureLine(resolvePolicy(args.projectRoot))}\n`);
2235
2242
  return 0;
@@ -2258,7 +2265,7 @@ function applyWipCap(args, io) {
2258
2265
  io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
2259
2266
  }
2260
2267
  else {
2261
- io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
2268
+ io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
2262
2269
  }
2263
2270
  const result = resolveWipCap(args.projectRoot);
2264
2271
  io.writeOut(`[deft policy] plan.policy.wipCap=${result.cap} (source: ${result.source}).\n`);
@@ -2278,7 +2285,7 @@ function applySubagentBackend(args, io) {
2278
2285
  io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
2279
2286
  }
2280
2287
  else {
2281
- io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
2288
+ io.writeOut(`${POLICY_AUDIT_NOOP_STDOUT}\n`);
2282
2289
  }
2283
2290
  const result = resolveSwarmSubagentBackend(args.projectRoot);
2284
2291
  io.writeOut(`[deft policy] plan.policy.swarmSubagentBackend=${pyRepr(result.backend_id)} ` +
@@ -2720,6 +2727,23 @@ async function invokeHandler(handler, argv) {
2720
2727
  return typeof code === "number" ? code : 0;
2721
2728
  }
2722
2729
  const CLI_PACKAGE = "@deftai/directive";
2730
+ const PLAN_SEQUENCE_VERBS = "set|current|clear|advance";
2731
+ /**
2732
+ * Unknown-colon-verb remediation (#3439 / #2652).
2733
+ * Agent-facing spelling is `deft <verb>` (works on consumer CLI and source).
2734
+ * Bare `task <verb>` is not consumer-runnable on include-only Taskfiles.
2735
+ */
2736
+ export function unknownColonVerbHint(verb) {
2737
+ if (verb.startsWith("plan-sequence:") && !(verb in PLAN_SEQUENCE_ALIAS_SUBCOMMANDS)) {
2738
+ return (`hint: plan-sequence verbs are ${PLAN_SEQUENCE_VERBS} ` +
2739
+ `(e.g. \`deft plan-sequence:current\`). '${verb}' is not a verb. ` +
2740
+ `Do not run bare \`task ${verb}\` -- consumer include-only Taskfiles cannot resolve it.\n`);
2741
+ }
2742
+ const hyphen = verb.replaceAll(":", "-");
2743
+ return (`hint: try \`deft ${verb}\` (CLI) or \`task deft:${verb}\` (consumer Taskfile). ` +
2744
+ `Bare \`task ${verb}\` does not exist on include-only deposits. ` +
2745
+ `Hyphen stem: ${hyphen}.\n`);
2746
+ }
2723
2747
  function versionBanner() {
2724
2748
  const info = engineInfo();
2725
2749
  return `${CLI_PACKAGE} (engine: ${info.name}@${info.version})\n`;
@@ -2743,8 +2767,7 @@ export async function dispatch(argv, io = defaultIo()) {
2743
2767
  if (canonical === null) {
2744
2768
  io.writeErr(`directive: unknown verb '${verb}'\n`);
2745
2769
  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`);
2770
+ io.writeErr(unknownColonVerbHint(verb));
2748
2771
  }
2749
2772
  return 1;
2750
2773
  }
@@ -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).
34
+ *
35
+ * Windows `CONIN$` generally requires read/write (`r+`); `r` (O_RDONLY) fails
36
+ * in a real interactive console and made operator mint unreachable on win32.
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;
@@ -67,10 +67,22 @@ 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
+ return platform === "win32" ? "CONIN$" : "/dev/tty";
73
+ }
74
+ /**
75
+ * Open flag for the platform controlling-terminal device (#3596).
76
+ *
77
+ * Windows `CONIN$` generally requires read/write (`r+`); `r` (O_RDONLY) fails
78
+ * in a real interactive console and made operator mint unreachable on win32.
79
+ */
80
+ export function controllingTerminalOpenFlag(platform = process.platform) {
81
+ return platform === "win32" ? "r+" : "r";
82
+ }
70
83
  function defaultHasControllingTerminal() {
71
84
  try {
72
- const path = process.platform === "win32" ? "CONIN$" : "/dev/tty";
73
- const fd = openSync(path, "r");
85
+ const fd = openSync(controllingTerminalPath(), controllingTerminalOpenFlag());
74
86
  closeSync(fd);
75
87
  return true;
76
88
  }
@@ -83,8 +95,7 @@ function defaultReadInteractiveConfirm() {
83
95
  // so agent-controlled stdin alone cannot supply the confirm phrase (#3110).
84
96
  let fd = null;
85
97
  try {
86
- const path = process.platform === "win32" ? "CONIN$" : "/dev/tty";
87
- fd = openSync(path, "r");
98
+ fd = openSync(controllingTerminalPath(), controllingTerminalOpenFlag());
88
99
  const buf = Buffer.alloc(256);
89
100
  const n = readSync(fd, buf, 0, buf.length, null);
90
101
  if (n <= 0)
@@ -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) {
@@ -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)
@@ -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) {
@@ -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.106.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.106.0",
38
+ "@deftai/directive-content": "^0.106.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"