@deftai/directive 0.107.0 → 0.108.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.
@@ -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", "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"];
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", "occupancy-heartbeat", "occupancy-grant", "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-pr-closeout-attestable", "verify-lifecycle-visible", "verify-completed-tracked", "verify-completed-write-guard", "verify-literal-ac", "verify-ac", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "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). */
package/dist/dispatch.js CHANGED
@@ -8,12 +8,11 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, readSync
8
8
  import { homedir, tmpdir } from "node:os";
9
9
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
10
10
  import { engineInfo, userConfig } from "@deftai/directive-core";
11
- import { assertWriteTargetSafe } from "@deftai/directive-core/dist/fs/projection-containment.js";
12
11
  import { parseInitArgv, runInitDepositCli } from "@deftai/directive-core/init-deposit";
13
12
  import { appendAuditLog, disclosureLine, migrateLegacyPolicyKey, PLAN_POLICY_KEY, POLICY_AUDIT_NOOP_STDOUT, policyColonInvocation, policySetInvocation, projectDefinitionPath, resolvePolicy, resolveWipCap, setPolicy, stampChangedToken, } from "@deftai/directive-core/policy";
14
13
  import { defaultWhich } from "@deftai/directive-core/scm";
15
14
  import { KNOWN_SUBAGENT_BACKEND_IDS, probeSubagentBackends, resolveSwarmSubagentBackend, } from "@deftai/directive-core/swarm";
16
- import { atomicWriteProjectDefinition } from "@deftai/directive-core/vbrief-build";
15
+ import { withProjectDefinitionMutation, } from "@deftai/directive-core/vbrief-build";
17
16
  const HANDLER_KEYS = [
18
17
  "run",
19
18
  "main",
@@ -76,6 +75,8 @@ export const CLI_MODULE_VERBS = [
76
75
  "session-ready",
77
76
  "occupancy-steal",
78
77
  "occupancy-release",
78
+ "occupancy-heartbeat",
79
+ "occupancy-grant",
79
80
  "plan-sequence",
80
81
  "slice",
81
82
  "subagent-monitor",
@@ -142,6 +143,7 @@ export const CLI_MODULE_VERBS = [
142
143
  "verify-tools",
143
144
  "verify-wip-cap",
144
145
  "verify-orphan-active",
146
+ "verify-pr-closeout-attestable",
145
147
  "verify-lifecycle-visible",
146
148
  "verify-completed-tracked",
147
149
  "verify-completed-write-guard",
@@ -287,6 +289,7 @@ export const VERB_ALIASES = {
287
289
  "verify:vbrief-conformance": "vbrief-validate",
288
290
  "verify:wip-cap": "verify-wip-cap",
289
291
  "verify:orphan-active": "verify-orphan-active",
292
+ "verify:pr-closeout-attestable": "verify-pr-closeout-attestable",
290
293
  "verify:lifecycle-visible": "verify-lifecycle-visible",
291
294
  "verify:completed-tracked": "verify-completed-tracked",
292
295
  "verify:completed-write-guard": "verify-completed-write-guard",
@@ -369,6 +372,8 @@ export const VERB_ALIASES = {
369
372
  "session:ready": "session-ready",
370
373
  "occupancy:steal": "occupancy-steal",
371
374
  "occupancy:release": "occupancy-release",
375
+ "occupancy:heartbeat": "occupancy-heartbeat",
376
+ "occupancy:grant": "occupancy-grant",
372
377
  "session:end": "occupancy-release",
373
378
  ...FRESHNESS_COLON_ALIASES,
374
379
  "lifecycle:event": "lifecycle-event",
@@ -2104,25 +2109,26 @@ function parsePolicySetArgs(argv) {
2104
2109
  }
2105
2110
  return args;
2106
2111
  }
2107
- /** Load PROJECT-DEFINITION for an in-place typed-field write (mirrors the .setdefault chain). */
2108
- function loadProjectDefinitionForWrite(projectRoot) {
2109
- const path = projectDefinitionPath(projectRoot);
2112
+ /**
2113
+ * Load PROJECT-DEFINITION for an in-place typed-field write (mirrors the
2114
+ * .setdefault chain). Reads the artifact the mutation lock captured (#3796) so
2115
+ * these CLI writers can no longer resolve, read, and write a PROJECT-DEFINITION
2116
+ * outside the shared mutation lock.
2117
+ */
2118
+ function loadProjectDefinitionForWrite(mutation) {
2119
+ const path = mutation.artifactPath;
2110
2120
  if (!existsSync(path)) {
2111
- throw new PolicySetError(`PROJECT-DEFINITION not found at ${path}`, "not-found");
2121
+ throw new PolicySetError(`PROJECT-DEFINITION not found at ${mutation.artifactLabel}`, "not-found");
2112
2122
  }
2113
- let parsed;
2123
+ let data;
2114
2124
  try {
2115
- parsed = JSON.parse(readFileSync(path, "utf8"));
2125
+ // The shared loader rejects unreadable, malformed, and non-object top-level
2126
+ // payloads; every one of those is a config error for the CLI writers.
2127
+ data = mutation.load();
2116
2128
  }
2117
2129
  catch (err) {
2118
- throw new PolicySetError(`PROJECT-DEFINITION at ${path} is not valid JSON: ${String(err)}`, "config");
2130
+ throw new PolicySetError(err instanceof Error ? err.message : String(err), "config");
2119
2131
  }
2120
- // JSON.parse can yield a non-object top level (null / array / scalar) without
2121
- // throwing; reject it before the .plan/.policy property chain dereferences it.
2122
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2123
- throw new PolicySetError(`PROJECT-DEFINITION at ${path} is not a JSON object`, "config");
2124
- }
2125
- const data = parsed;
2126
2132
  let plan = data.plan;
2127
2133
  if (plan === undefined) {
2128
2134
  plan = {};
@@ -2141,45 +2147,47 @@ function loadProjectDefinitionForWrite(projectRoot) {
2141
2147
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
2142
2148
  throw new PolicySetError("plan.policy is not an object", "config");
2143
2149
  }
2144
- return { path, data, policyBlock: policy, legacyMigrated };
2150
+ return { data, policyBlock: policy, legacyMigrated };
2145
2151
  }
2146
2152
  /** Write plan.policy.wipCap in place + append the audit row (mirrors set_wip_cap). */
2147
2153
  function writeWipCap(projectRoot, cap, actor, note) {
2148
- const { path, data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(projectRoot);
2149
- const previous = policyBlock.wipCap;
2150
- policyBlock.wipCap = cap;
2151
- const changed = previous !== cap || legacyMigrated;
2152
- const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
2153
- if (note)
2154
- parts.push(`note=${sanitizeNote(note)}`);
2155
- const auditEntry = stampChangedToken(parts.join(" "), changed);
2156
- if (changed) {
2157
- assertWriteTargetSafe(projectRoot, path);
2158
- atomicWriteProjectDefinition(path, data);
2159
- }
2160
- appendAuditLog(projectRoot, auditEntry, changed);
2161
- return { changed, auditEntry };
2154
+ return withProjectDefinitionMutation(projectRoot, (mutation) => {
2155
+ const { data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(mutation);
2156
+ const previous = policyBlock.wipCap;
2157
+ policyBlock.wipCap = cap;
2158
+ const changed = previous !== cap || legacyMigrated;
2159
+ const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
2160
+ if (note)
2161
+ parts.push(`note=${sanitizeNote(note)}`);
2162
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2163
+ if (changed) {
2164
+ mutation.persist(data);
2165
+ }
2166
+ appendAuditLog(projectRoot, auditEntry, changed);
2167
+ return { changed, auditEntry };
2168
+ });
2162
2169
  }
2163
2170
  /** Write plan.policy.swarmSubagentBackend in place + append the audit row. */
2164
2171
  function writeSubagentBackend(projectRoot, backendId, actor, note) {
2165
- const { path, data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(projectRoot);
2166
- const previous = policyBlock.swarmSubagentBackend;
2167
- policyBlock.swarmSubagentBackend = backendId;
2168
- const changed = previous !== backendId || legacyMigrated;
2169
- const parts = [
2170
- `actor=${actor}`,
2171
- `swarmSubagentBackend=${backendId}`,
2172
- `previous=${pyRepr(previous)}`,
2173
- ];
2174
- if (note)
2175
- parts.push(`note=${sanitizeNote(note)}`);
2176
- const auditEntry = stampChangedToken(parts.join(" "), changed);
2177
- if (changed) {
2178
- assertWriteTargetSafe(projectRoot, path);
2179
- atomicWriteProjectDefinition(path, data);
2180
- }
2181
- appendAuditLog(projectRoot, auditEntry, changed);
2182
- return { changed, auditEntry };
2172
+ return withProjectDefinitionMutation(projectRoot, (mutation) => {
2173
+ const { data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(mutation);
2174
+ const previous = policyBlock.swarmSubagentBackend;
2175
+ policyBlock.swarmSubagentBackend = backendId;
2176
+ const changed = previous !== backendId || legacyMigrated;
2177
+ const parts = [
2178
+ `actor=${actor}`,
2179
+ `swarmSubagentBackend=${backendId}`,
2180
+ `previous=${pyRepr(previous)}`,
2181
+ ];
2182
+ if (note)
2183
+ parts.push(`note=${sanitizeNote(note)}`);
2184
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2185
+ if (changed) {
2186
+ mutation.persist(data);
2187
+ }
2188
+ appendAuditLog(projectRoot, auditEntry, changed);
2189
+ return { changed, auditEntry };
2190
+ });
2183
2191
  }
2184
2192
  /** Serialise the probe output for `subagent-backends --format json`. */
2185
2193
  function subagentBackendsToJson(backends) {
@@ -2617,19 +2625,30 @@ const CURATED_HELP_GROUPS = [
2617
2625
  {
2618
2626
  title: "Session & ritual",
2619
2627
  commands: [
2620
- { name: "session:start", summary: "Record session-start ritual state" },
2628
+ {
2629
+ name: "session:start",
2630
+ summary: "Claim occupancy and record ritual state for one session owner",
2631
+ },
2621
2632
  {
2622
2633
  name: "session:ready",
2623
2634
  summary: "One-shot recovery to gated write-ready (session + ritual + cache)",
2624
2635
  },
2625
2636
  {
2626
2637
  name: "occupancy:steal",
2627
- summary: "Supersede a live worktree occupancy lease (--confirm --occupant)",
2638
+ summary: "Replace only the live lease; use session:start --steal to align ritual",
2628
2639
  },
2629
2640
  {
2630
2641
  name: "occupancy:release",
2631
2642
  summary: "Release this worktree occupancy lease (owner live, or expired residue)",
2632
2643
  },
2644
+ {
2645
+ name: "occupancy:heartbeat",
2646
+ summary: "Refresh your own live lease during long quiet stretches (never claims)",
2647
+ },
2648
+ {
2649
+ name: "occupancy:grant",
2650
+ summary: "Admit a dispatched child to your lease for writes (--revoke withdraws)",
2651
+ },
2633
2652
  {
2634
2653
  name: "session:end",
2635
2654
  summary: "End this mutation session by releasing occupancy (same as occupancy:release)",
@@ -2795,6 +2814,9 @@ export async function dispatch(argv, io = defaultIo()) {
2795
2814
  const planSequenceSubcommand = verb !== undefined ? PLAN_SEQUENCE_ALIAS_SUBCOMMANDS[verb] : undefined;
2796
2815
  const productSignalSubcommand = verb !== undefined ? PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS[verb] : undefined;
2797
2816
  const freshnessSubcommand = verb !== undefined ? FRESHNESS_ALIAS_SUBCOMMANDS[verb] : undefined;
2817
+ const vbriefValidateSubcommand = verb === "verify:vbrief-conformance" && canonical === "vbrief-validate"
2818
+ ? "conformance"
2819
+ : undefined;
2798
2820
  const handlerArgv = canonical === "framework-commands" && verb !== undefined && verb !== canonical
2799
2821
  ? [verb, ...rest]
2800
2822
  : triageSubcommand !== undefined && canonical === "triage-actions"
@@ -2811,7 +2833,9 @@ export async function dispatch(argv, io = defaultIo()) {
2811
2833
  ? [productSignalSubcommand, ...rest]
2812
2834
  : freshnessSubcommand !== undefined && canonical === "freshness-report"
2813
2835
  ? [freshnessSubcommand, ...rest]
2814
- : rest;
2836
+ : vbriefValidateSubcommand !== undefined
2837
+ ? [vbriefValidateSubcommand, ...rest]
2838
+ : rest;
2815
2839
  return await invokeHandler(handler, handlerArgv);
2816
2840
  }
2817
2841
  catch (err) {
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { type OccupancyJoinProtocol } from "@deftai/directive-core/session";
3
+ export interface GrantArgs {
4
+ projectRoot: string;
5
+ sessionId?: string;
6
+ childSessionId?: string;
7
+ role?: string;
8
+ worktree?: string;
9
+ ttlMinutes?: number;
10
+ host?: string;
11
+ address?: string;
12
+ joinProtocol?: OccupancyJoinProtocol;
13
+ revoke: boolean;
14
+ error?: string;
15
+ }
16
+ export declare function parseArgs(argv: readonly string[]): GrantArgs;
17
+ export declare function run(argv: readonly string[]): number;
18
+ //# sourceMappingURL=occupancy-grant.d.ts.map
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { grantOccupancyMembership, OCCUPANCY_JOIN_PROTOCOLS, revokeOccupancyMembership, } from "@deftai/directive-core/session";
4
+ const VALUE_FLAGS = [
5
+ "--project-root",
6
+ "--session-id",
7
+ "--child-session-id",
8
+ "--role",
9
+ "--worktree",
10
+ "--ttl-minutes",
11
+ "--host",
12
+ "--address",
13
+ "--join-protocol",
14
+ ];
15
+ function readValue(argv, index, flag) {
16
+ const arg = argv[index] ?? "";
17
+ const inline = `${flag}=`;
18
+ const raw = arg.startsWith(inline) ? arg.slice(inline.length) : argv[index + 1];
19
+ if (raw === undefined || raw.startsWith("--")) {
20
+ return { error: `argument ${flag}: expected one argument` };
21
+ }
22
+ const value = raw.trim();
23
+ if (value.length === 0) {
24
+ return { error: `argument ${flag}: expected a non-empty value` };
25
+ }
26
+ return { value, next: arg.startsWith(inline) ? index : index + 1 };
27
+ }
28
+ export function parseArgs(argv) {
29
+ const parsed = { projectRoot: ".", revoke: false };
30
+ for (let i = 0; i < argv.length; i += 1) {
31
+ const arg = argv[i] ?? "";
32
+ if (arg === "--revoke")
33
+ continue;
34
+ const flag = VALUE_FLAGS.find((name) => arg === name || arg.startsWith(`${name}=`));
35
+ if (flag === undefined) {
36
+ return {
37
+ ...parsed,
38
+ revoke: argv.includes("--revoke"),
39
+ error: `unrecognized argument: ${arg}`,
40
+ };
41
+ }
42
+ const read = readValue(argv, i, flag);
43
+ if ("error" in read)
44
+ return { ...parsed, revoke: argv.includes("--revoke"), error: read.error };
45
+ i = read.next;
46
+ switch (flag) {
47
+ case "--project-root":
48
+ parsed.projectRoot = read.value;
49
+ break;
50
+ case "--session-id":
51
+ parsed.sessionId = read.value;
52
+ break;
53
+ case "--child-session-id":
54
+ parsed.childSessionId = read.value;
55
+ break;
56
+ case "--role":
57
+ parsed.role = read.value;
58
+ break;
59
+ case "--worktree":
60
+ parsed.worktree = read.value;
61
+ break;
62
+ case "--host":
63
+ parsed.host = read.value;
64
+ break;
65
+ case "--address":
66
+ parsed.address = read.value;
67
+ break;
68
+ case "--ttl-minutes": {
69
+ const minutes = Number(read.value);
70
+ if (!Number.isFinite(minutes) || minutes <= 0) {
71
+ return {
72
+ ...parsed,
73
+ revoke: argv.includes("--revoke"),
74
+ error: "argument --ttl-minutes: expected a positive number of minutes",
75
+ };
76
+ }
77
+ parsed.ttlMinutes = minutes;
78
+ break;
79
+ }
80
+ case "--join-protocol": {
81
+ if (!OCCUPANCY_JOIN_PROTOCOLS.includes(read.value)) {
82
+ return {
83
+ ...parsed,
84
+ revoke: argv.includes("--revoke"),
85
+ error: `argument --join-protocol: expected one of ${OCCUPANCY_JOIN_PROTOCOLS.join(", ")}`,
86
+ };
87
+ }
88
+ parsed.joinProtocol = read.value;
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ return { ...parsed, revoke: argv.includes("--revoke") };
94
+ }
95
+ export function run(argv) {
96
+ const args = parseArgs(argv);
97
+ if (args.error !== undefined) {
98
+ process.stderr.write(`occupancy:grant: ${args.error}\n`);
99
+ return 2;
100
+ }
101
+ const projectRoot = resolve(args.projectRoot);
102
+ const input = {
103
+ sessionId: args.sessionId,
104
+ childSessionId: args.childSessionId,
105
+ role: args.role,
106
+ worktreePath: args.worktree,
107
+ ttlMs: args.ttlMinutes === undefined ? undefined : args.ttlMinutes * 60 * 1000,
108
+ host: args.host,
109
+ address: args.address,
110
+ joinProtocol: args.joinProtocol,
111
+ env: process.env,
112
+ };
113
+ const result = args.revoke
114
+ ? revokeOccupancyMembership(projectRoot, input)
115
+ : grantOccupancyMembership(projectRoot, input);
116
+ const sink = result.code === 0 ? process.stdout : process.stderr;
117
+ sink.write(`${result.message}\n`);
118
+ return result.code;
119
+ }
120
+ //# sourceMappingURL=occupancy-grant.js.map
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ export declare function parseArgs(argv: readonly string[]): {
3
+ projectRoot: string;
4
+ sessionId?: string;
5
+ error?: string;
6
+ };
7
+ export declare function run(argv: readonly string[]): number;
8
+ //# sourceMappingURL=occupancy-heartbeat.d.ts.map
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { heartbeatOccupancy } 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 || value.startsWith("--")) {
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
+ const value = arg.slice("--project-root=".length);
18
+ if (value.length === 0 || value.startsWith("--")) {
19
+ return { ...parsed, error: "argument --project-root: expected one argument" };
20
+ }
21
+ parsed.projectRoot = value;
22
+ }
23
+ else if (arg === "--session-id") {
24
+ const value = argv[i + 1];
25
+ if (value === undefined || value.startsWith("--")) {
26
+ return { ...parsed, error: "argument --session-id: expected one argument" };
27
+ }
28
+ const sessionId = value.trim();
29
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
30
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
31
+ }
32
+ parsed.sessionId = sessionId;
33
+ i += 1;
34
+ }
35
+ else if (arg?.startsWith("--session-id=")) {
36
+ const sessionId = arg.slice("--session-id=".length).trim();
37
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
38
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
39
+ }
40
+ parsed.sessionId = sessionId;
41
+ }
42
+ else {
43
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
44
+ }
45
+ }
46
+ return parsed;
47
+ }
48
+ export function run(argv) {
49
+ const args = parseArgs(argv);
50
+ if (args.error !== undefined) {
51
+ process.stderr.write(`occupancy:heartbeat: ${args.error}\n`);
52
+ return 2;
53
+ }
54
+ const result = heartbeatOccupancy(resolve(args.projectRoot), {
55
+ sessionId: args.sessionId,
56
+ env: process.env,
57
+ });
58
+ const sink = result.code === 0 ? process.stdout : process.stderr;
59
+ sink.write(`${result.message}\n`);
60
+ return result.code;
61
+ }
62
+ //# sourceMappingURL=occupancy-heartbeat.js.map
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  export declare function parseArgs(argv: readonly string[]): {
3
3
  projectRoot: string;
4
+ sessionId?: string;
4
5
  error?: string;
5
6
  };
6
7
  export declare function run(argv: readonly string[]): number;
@@ -7,14 +7,37 @@ export function parseArgs(argv) {
7
7
  const arg = argv[i];
8
8
  if (arg === "--project-root") {
9
9
  const value = argv[i + 1];
10
- if (value === undefined) {
10
+ if (value === undefined || value.startsWith("--")) {
11
11
  return { ...parsed, error: "argument --project-root: expected one argument" };
12
12
  }
13
13
  parsed.projectRoot = value;
14
14
  i += 1;
15
15
  }
16
16
  else if (arg?.startsWith("--project-root=")) {
17
- parsed.projectRoot = arg.slice("--project-root=".length);
17
+ const value = arg.slice("--project-root=".length);
18
+ if (value.length === 0 || value.startsWith("--")) {
19
+ return { ...parsed, error: "argument --project-root: expected one argument" };
20
+ }
21
+ parsed.projectRoot = value;
22
+ }
23
+ else if (arg === "--session-id") {
24
+ const value = argv[i + 1];
25
+ if (value === undefined || value.startsWith("--")) {
26
+ return { ...parsed, error: "argument --session-id: expected one argument" };
27
+ }
28
+ const sessionId = value.trim();
29
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
30
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
31
+ }
32
+ parsed.sessionId = sessionId;
33
+ i += 1;
34
+ }
35
+ else if (arg?.startsWith("--session-id=")) {
36
+ const sessionId = arg.slice("--session-id=".length).trim();
37
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
38
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
39
+ }
40
+ parsed.sessionId = sessionId;
18
41
  }
19
42
  else {
20
43
  return { ...parsed, error: `unrecognized argument: ${arg}` };
@@ -29,6 +52,7 @@ export function run(argv) {
29
52
  return 2;
30
53
  }
31
54
  const result = releaseOccupancy(resolve(args.projectRoot), {
55
+ sessionId: args.sessionId,
32
56
  env: process.env,
33
57
  });
34
58
  const sink = result.code === 0 ? process.stdout : process.stderr;
@@ -3,6 +3,7 @@ export declare function parseArgs(argv: readonly string[]): {
3
3
  projectRoot: string;
4
4
  confirm: boolean;
5
5
  occupant: string | null;
6
+ sessionId?: string;
6
7
  error?: string;
7
8
  };
8
9
  export declare function run(argv: readonly string[]): number;
@@ -10,25 +10,52 @@ export function parseArgs(argv) {
10
10
  }
11
11
  else if (arg === "--occupant") {
12
12
  const value = argv[i + 1];
13
- if (value === undefined) {
13
+ if (value === undefined || value.startsWith("--")) {
14
14
  return { ...parsed, error: "argument --occupant: expected one argument" };
15
15
  }
16
16
  parsed.occupant = value;
17
17
  i += 1;
18
18
  }
19
19
  else if (arg?.startsWith("--occupant=")) {
20
- parsed.occupant = arg.slice("--occupant=".length);
20
+ const value = arg.slice("--occupant=".length);
21
+ if (value.length === 0 || value.startsWith("--")) {
22
+ return { ...parsed, error: "argument --occupant: expected one argument" };
23
+ }
24
+ parsed.occupant = value;
21
25
  }
22
26
  else if (arg === "--project-root") {
23
27
  const value = argv[i + 1];
24
- if (value === undefined) {
28
+ if (value === undefined || value.startsWith("--")) {
25
29
  return { ...parsed, error: "argument --project-root: expected one argument" };
26
30
  }
27
31
  parsed.projectRoot = value;
28
32
  i += 1;
29
33
  }
30
34
  else if (arg?.startsWith("--project-root=")) {
31
- parsed.projectRoot = arg.slice("--project-root=".length);
35
+ const value = arg.slice("--project-root=".length);
36
+ if (value.length === 0 || value.startsWith("--")) {
37
+ return { ...parsed, error: "argument --project-root: expected one argument" };
38
+ }
39
+ parsed.projectRoot = value;
40
+ }
41
+ else if (arg === "--session-id") {
42
+ const value = argv[i + 1];
43
+ if (value === undefined || value.startsWith("--")) {
44
+ return { ...parsed, error: "argument --session-id: expected one argument" };
45
+ }
46
+ const sessionId = value.trim();
47
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
48
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
49
+ }
50
+ parsed.sessionId = sessionId;
51
+ i += 1;
52
+ }
53
+ else if (arg?.startsWith("--session-id=")) {
54
+ const sessionId = arg.slice("--session-id=".length).trim();
55
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
56
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
57
+ }
58
+ parsed.sessionId = sessionId;
32
59
  }
33
60
  else {
34
61
  return { ...parsed, error: `unrecognized argument: ${arg}` };
@@ -43,6 +70,7 @@ export function run(argv) {
43
70
  return 2;
44
71
  }
45
72
  const result = stealOccupancy(resolve(args.projectRoot), {
73
+ sessionId: args.sessionId,
46
74
  confirm: args.confirm,
47
75
  occupant: args.occupant ?? undefined,
48
76
  env: process.env,
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * Thin CLI wrapper for prd-render (mirrors ``scripts/prd_render.py``).
3
3
  *
4
- * Supports `--project-root <dir>` to resolve the spec artifact via the layout
5
- * resolver (#2132), preferring `xbrief/specification.xbrief.json` on migrated
6
- * trees and falling back to `vbrief/specification.vbrief.json` on legacy trees.
4
+ * Supports `--project-root <dir>` to resolve full-spec or greenfield authority
5
+ * through the shared authority resolver (#2132 / #3598).
7
6
  * When `--spec` is explicitly provided it takes precedence over `--project-root`.
8
7
  * Direct `--spec` / `--output` / `--force` flags are still accepted unchanged.
9
8
  */
10
- import { layout } from "@deftai/directive-core";
11
9
  import { parsePrdArgv, prdRenderMain } from "@deftai/directive-core/render";
12
10
  function parsePrdRenderCliArgv(argv) {
13
11
  const remaining = [];
@@ -30,12 +28,10 @@ function parsePrdRenderCliArgv(argv) {
30
28
  export function runPrdRenderCli(argv) {
31
29
  const { projectRoot, remaining } = parsePrdRenderCliArgv(argv);
32
30
  const parsedArgs = parsePrdArgv(remaining);
33
- const spec = parsedArgs.spec !== undefined
34
- ? parsedArgs.spec
35
- : projectRoot !== undefined
36
- ? layout.resolveSpecArtifactPath(projectRoot)
37
- : undefined;
38
- prdRenderMain({ ...parsedArgs, spec });
31
+ prdRenderMain({
32
+ ...parsedArgs,
33
+ projectRoot: parsedArgs.spec === undefined ? projectRoot : undefined,
34
+ });
39
35
  return 0;
40
36
  }
41
37
  //# sourceMappingURL=prd-render-cli.js.map
@@ -4,6 +4,8 @@ export interface ParsedSessionReadyArgs {
4
4
  emitJson: boolean;
5
5
  withNetwork: boolean;
6
6
  repo: string | null;
7
+ /** #3611: explicit lifecycle owner injected by a host hook bridge. */
8
+ sessionId: string | null;
7
9
  error?: string;
8
10
  }
9
11
  /** Parse session:ready CLI args. */
@@ -15,6 +15,7 @@ export function parseArgs(argv) {
15
15
  emitJson: false,
16
16
  withNetwork: false,
17
17
  repo: null,
18
+ sessionId: null,
18
19
  };
19
20
  for (let i = 0; i < argv.length; i += 1) {
20
21
  const arg = argv[i];
@@ -33,13 +34,13 @@ export function parseArgs(argv) {
33
34
  if (arg === "--help" || arg === "-h") {
34
35
  return {
35
36
  ...parsed,
36
- error: "usage: session:ready [--project-root <path>] [--repo OWNER/NAME] [--with-network] [--json]\n" +
37
+ error: "usage: session:ready [--project-root <path>] [--repo OWNER/NAME] [--session-id <id>] [--with-network] [--json]\n" +
37
38
  " One-shot recovery: session:start (if needed) + gated ritual + cache recovery (#2993).",
38
39
  };
39
40
  }
40
41
  if (arg === "--project-root") {
41
42
  const value = argv[i + 1];
42
- if (value === undefined) {
43
+ if (value === undefined || value.startsWith("--")) {
43
44
  return { ...parsed, error: "argument --project-root: expected one argument" };
44
45
  }
45
46
  parsed.projectRoot = value;
@@ -47,12 +48,37 @@ export function parseArgs(argv) {
47
48
  continue;
48
49
  }
49
50
  if (arg.startsWith("--project-root=")) {
50
- parsed.projectRoot = arg.slice("--project-root=".length);
51
+ const value = arg.slice("--project-root=".length);
52
+ if (value.length === 0 || value.startsWith("--")) {
53
+ return { ...parsed, error: "argument --project-root: expected one argument" };
54
+ }
55
+ parsed.projectRoot = value;
56
+ continue;
57
+ }
58
+ if (arg === "--session-id") {
59
+ const value = argv[i + 1];
60
+ if (value === undefined || value.startsWith("--")) {
61
+ return { ...parsed, error: "argument --session-id: expected one argument" };
62
+ }
63
+ const sessionId = value.trim();
64
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
65
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
66
+ }
67
+ parsed.sessionId = sessionId;
68
+ i += 1;
69
+ continue;
70
+ }
71
+ if (arg.startsWith("--session-id=")) {
72
+ const sessionId = arg.slice("--session-id=".length).trim();
73
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
74
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
75
+ }
76
+ parsed.sessionId = sessionId;
51
77
  continue;
52
78
  }
53
79
  if (arg === "--repo") {
54
80
  const value = argv[i + 1];
55
- if (value === undefined) {
81
+ if (value === undefined || value.startsWith("--")) {
56
82
  return { ...parsed, error: "argument --repo: expected one argument" };
57
83
  }
58
84
  parsed.repo = value;
@@ -60,7 +86,11 @@ export function parseArgs(argv) {
60
86
  continue;
61
87
  }
62
88
  if (arg.startsWith("--repo=")) {
63
- parsed.repo = arg.slice("--repo=".length);
89
+ const value = arg.slice("--repo=".length);
90
+ if (value.length === 0 || value.startsWith("--")) {
91
+ return { ...parsed, error: "argument --repo: expected one argument" };
92
+ }
93
+ parsed.repo = value;
64
94
  continue;
65
95
  }
66
96
  if (arg.startsWith("-")) {
@@ -80,6 +110,7 @@ export function run(argv = process.argv.slice(2)) {
80
110
  const projectRoot = resolve(args.projectRoot);
81
111
  const result = runSessionReady(projectRoot, {
82
112
  repo: args.repo,
113
+ sessionId: args.sessionId ?? undefined,
83
114
  sessionStartOptions: {
84
115
  allowOptionalNetwork: args.withNetwork ? true : undefined,
85
116
  writeHistory: false,
@@ -89,6 +120,7 @@ export function run(argv = process.argv.slice(2)) {
89
120
  process.stdout.write(`${JSON.stringify({
90
121
  ready: result.code === 0,
91
122
  exit_code: result.code,
123
+ session_id: result.sessionId,
92
124
  path: result.path,
93
125
  message: result.message,
94
126
  steps: result.steps,
@@ -36,6 +36,8 @@ export interface ParsedSessionStartArgs {
36
36
  steal: boolean;
37
37
  confirm: boolean;
38
38
  occupant: string | null;
39
+ /** #3611: explicit lifecycle owner injected by a host hook bridge. */
40
+ sessionId: string | null;
39
41
  error?: string;
40
42
  }
41
43
  /** Parse session:start CLI args, mirroring scripts/session_start.py. */
@@ -34,6 +34,7 @@ export function parseArgs(argv) {
34
34
  steal: false,
35
35
  confirm: false,
36
36
  occupant: null,
37
+ sessionId: null,
37
38
  };
38
39
  const dialInputs = {};
39
40
  for (let i = 0; i < argv.length; i += 1) {
@@ -82,25 +83,33 @@ export function parseArgs(argv) {
82
83
  }
83
84
  else if (arg === "--project-root") {
84
85
  const value = argv[i + 1];
85
- if (value === undefined) {
86
+ if (value === undefined || value.startsWith("--")) {
86
87
  return { ...parsed, error: "argument --project-root: expected one argument" };
87
88
  }
88
89
  parsed.projectRoot = value;
89
90
  i += 1;
90
91
  }
91
92
  else if (arg?.startsWith("--project-root=")) {
92
- parsed.projectRoot = arg.slice("--project-root=".length);
93
+ const value = arg.slice("--project-root=".length);
94
+ if (value.length === 0 || value.startsWith("--")) {
95
+ return { ...parsed, error: "argument --project-root: expected one argument" };
96
+ }
97
+ parsed.projectRoot = value;
93
98
  }
94
99
  else if (arg === "--defer") {
95
100
  const value = argv[i + 1];
96
- if (value === undefined) {
101
+ if (value === undefined || value.startsWith("--")) {
97
102
  return { ...parsed, error: "argument --defer: expected one argument" };
98
103
  }
99
104
  parsed.deferValues.push(value);
100
105
  i += 1;
101
106
  }
102
107
  else if (arg?.startsWith("--defer=")) {
103
- parsed.deferValues.push(arg.slice("--defer=".length));
108
+ const value = arg.slice("--defer=".length);
109
+ if (value.length === 0 || value.startsWith("--")) {
110
+ return { ...parsed, error: "argument --defer: expected one argument" };
111
+ }
112
+ parsed.deferValues.push(value);
104
113
  }
105
114
  else if (arg === "--task-size" || arg === "--ceremony-task-size") {
106
115
  const value = argv[i + 1];
@@ -282,16 +291,39 @@ export function parseArgs(argv) {
282
291
  else if (arg === "--confirm") {
283
292
  parsed.confirm = true;
284
293
  }
294
+ else if (arg === "--session-id") {
295
+ const value = argv[i + 1];
296
+ if (value === undefined || value.startsWith("--")) {
297
+ return { ...parsed, error: "argument --session-id: expected one argument" };
298
+ }
299
+ const sessionId = value.trim();
300
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
301
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
302
+ }
303
+ parsed.sessionId = sessionId;
304
+ i += 1;
305
+ }
306
+ else if (arg?.startsWith("--session-id=")) {
307
+ const sessionId = arg.slice("--session-id=".length).trim();
308
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
309
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
310
+ }
311
+ parsed.sessionId = sessionId;
312
+ }
285
313
  else if (arg === "--occupant") {
286
314
  const value = argv[i + 1];
287
- if (value === undefined) {
315
+ if (value === undefined || value.startsWith("--")) {
288
316
  return { ...parsed, error: "argument --occupant: expected one argument" };
289
317
  }
290
318
  parsed.occupant = value;
291
319
  i += 1;
292
320
  }
293
321
  else if (arg?.startsWith("--occupant=")) {
294
- parsed.occupant = arg.slice("--occupant=".length);
322
+ const value = arg.slice("--occupant=".length);
323
+ if (value.length === 0 || value.startsWith("--")) {
324
+ return { ...parsed, error: "argument --occupant: expected one argument" };
325
+ }
326
+ parsed.occupant = value;
295
327
  }
296
328
  else {
297
329
  return { ...parsed, error: `unrecognized argument: ${arg}` };
@@ -358,6 +390,7 @@ export function run(argv) {
358
390
  steal: args.steal ? true : undefined,
359
391
  confirm: args.confirm ? true : undefined,
360
392
  occupant: args.occupant ?? undefined,
393
+ sessionId: args.sessionId ?? undefined,
361
394
  ...(args.ceremonyDepthOverride !== null
362
395
  ? {
363
396
  ceremonyDial: selectCeremonyDepth({
@@ -2,6 +2,7 @@
2
2
  import { type CommandRunner } from "@deftai/directive-core/verify-env";
3
3
  export interface ToolchainCheckRunOptions {
4
4
  readonly runner?: CommandRunner;
5
+ readonly env?: NodeJS.ProcessEnv;
5
6
  }
6
7
  export declare function run(argv?: readonly string[], options?: ToolchainCheckRunOptions): number;
7
8
  //# sourceMappingURL=toolchain-check.d.ts.map
@@ -1,9 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { runToolchainCheck } from "@deftai/directive-core/verify-env";
4
+ function parseArgs(argv) {
5
+ let consumer = false;
6
+ let projectRoot = process.cwd();
7
+ for (let index = 0; index < argv.length; index += 1) {
8
+ const arg = argv[index] ?? "";
9
+ if (arg === "--consumer") {
10
+ consumer = true;
11
+ continue;
12
+ }
13
+ if (arg === "--project-root") {
14
+ const value = argv[index + 1];
15
+ if (value === undefined || value.trim() === "" || value.startsWith("-")) {
16
+ return { consumer, projectRoot, error: "--project-root requires a path" };
17
+ }
18
+ projectRoot = value;
19
+ index += 1;
20
+ continue;
21
+ }
22
+ if (arg.startsWith("--project-root=")) {
23
+ const value = arg.slice("--project-root=".length);
24
+ if (value.trim() === "") {
25
+ return { consumer, projectRoot, error: "--project-root requires a path" };
26
+ }
27
+ projectRoot = value;
28
+ continue;
29
+ }
30
+ return { consumer, projectRoot, error: `unrecognized argument: ${arg}` };
31
+ }
32
+ return { consumer, projectRoot };
33
+ }
4
34
  export function run(argv = process.argv.slice(2), options = {}) {
5
- const consumer = argv.includes("--consumer");
6
- const result = runToolchainCheck(options.runner, { consumer });
35
+ const parsed = parseArgs(argv);
36
+ if (parsed.error !== undefined) {
37
+ process.stderr.write(`toolchain-check: ${parsed.error}\n`);
38
+ process.stderr.write("Usage: deft toolchain-check [--consumer] [--project-root <path>]\n");
39
+ return 2;
40
+ }
41
+ const result = runToolchainCheck(options.runner, {
42
+ consumer: parsed.consumer,
43
+ projectRoot: parsed.projectRoot,
44
+ env: options.env ?? process.env,
45
+ });
7
46
  for (const line of result.lines) {
8
47
  process.stdout.write(`${line}\n`);
9
48
  }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ repo: string | null;
5
+ pr: number | null;
6
+ quiet: boolean;
7
+ error?: string;
8
+ }
9
+ /** Parse verify-pr-closeout-attestable CLI args. */
10
+ export declare function parseArgs(argv: string[]): ParsedArgs;
11
+ /** Run the gate and return the process exit code. */
12
+ export declare function run(argv: string[]): number;
13
+ export {};
14
+ //# sourceMappingURL=verify-pr-closeout-attestable.d.ts.map
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluate } from "@deftai/directive-core/pr-closeout-attestable";
5
+ function parsePrNumber(raw) {
6
+ const trimmed = raw.startsWith("#") ? raw.slice(1) : raw;
7
+ if (!/^\d+$/.test(trimmed)) {
8
+ return null;
9
+ }
10
+ const value = Number(trimmed);
11
+ return Number.isInteger(value) && value > 0 ? value : null;
12
+ }
13
+ /** Parse verify-pr-closeout-attestable CLI args. */
14
+ export function parseArgs(argv) {
15
+ const parsed = { projectRoot: ".", repo: null, pr: null, quiet: false };
16
+ for (let i = 0; i < argv.length; i += 1) {
17
+ const arg = argv[i];
18
+ if (arg === "--quiet") {
19
+ parsed.quiet = true;
20
+ }
21
+ else if (arg === "--project-root") {
22
+ const value = argv[i + 1];
23
+ if (value === undefined) {
24
+ return { ...parsed, error: "argument --project-root: expected one argument" };
25
+ }
26
+ parsed.projectRoot = value;
27
+ i += 1;
28
+ }
29
+ else if (arg?.startsWith("--project-root=")) {
30
+ parsed.projectRoot = arg.slice("--project-root=".length);
31
+ }
32
+ else if (arg === "--repo") {
33
+ const value = argv[i + 1];
34
+ if (value === undefined) {
35
+ return { ...parsed, error: "argument --repo: expected one argument" };
36
+ }
37
+ parsed.repo = value;
38
+ i += 1;
39
+ }
40
+ else if (arg?.startsWith("--repo=")) {
41
+ parsed.repo = arg.slice("--repo=".length);
42
+ }
43
+ else if (arg === "--pr") {
44
+ const value = argv[i + 1];
45
+ if (value === undefined) {
46
+ return { ...parsed, error: "argument --pr: expected one argument" };
47
+ }
48
+ const pr = parsePrNumber(value);
49
+ if (pr === null) {
50
+ return { ...parsed, error: `argument --pr: expected a positive integer, got ${value}` };
51
+ }
52
+ parsed.pr = pr;
53
+ i += 1;
54
+ }
55
+ else if (arg?.startsWith("--pr=")) {
56
+ const value = arg.slice("--pr=".length);
57
+ const pr = parsePrNumber(value);
58
+ if (pr === null) {
59
+ return { ...parsed, error: `argument --pr: expected a positive integer, got ${value}` };
60
+ }
61
+ parsed.pr = pr;
62
+ }
63
+ else {
64
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
65
+ }
66
+ }
67
+ if (parsed.pr === null) {
68
+ return { ...parsed, error: "argument --pr is required" };
69
+ }
70
+ return parsed;
71
+ }
72
+ /** Run the gate and return the process exit code. */
73
+ export function run(argv) {
74
+ const args = parseArgs(argv);
75
+ if (args.error !== undefined) {
76
+ process.stderr.write(`verify_pr_closeout_attestable: ${args.error}\n`);
77
+ return 2;
78
+ }
79
+ const result = evaluate(resolve(args.projectRoot), args.pr, {
80
+ repo: args.repo,
81
+ quiet: args.quiet,
82
+ });
83
+ if (result.message.length > 0) {
84
+ if (result.stream === "stdout") {
85
+ process.stdout.write(`${result.message}\n`);
86
+ }
87
+ else if (result.stream === "stderr") {
88
+ process.stderr.write(`${result.message}\n`);
89
+ }
90
+ }
91
+ return result.code;
92
+ }
93
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
94
+ process.exit(run(process.argv.slice(2)));
95
+ }
96
+ //# sourceMappingURL=verify-pr-closeout-attestable.js.map
@@ -166,7 +166,11 @@ export function evaluateSubagentAliveGate(args, cwd = process.cwd()) {
166
166
  }
167
167
  lines.push("");
168
168
  lines.push("REDISPATCH_OK: host-reported running + missing/STALE heartbeat authorizes takeover.");
169
- lines.push("Re-dispatch a replacement worker; do not wait on Cursor false-alive state.");
169
+ lines.push("A killed worker's delivery attempt stays running until cancelled.");
170
+ lines.push("Takeover: task swarm:pre-dispatch -- --scope-id <id> --target-id <worktree> --action cancel");
171
+ lines.push("then: task swarm:pre-dispatch -- --scope-id <id> --target-id <worktree>");
172
+ lines.push("If verify:session-ritual --tier=gated fails, run session:start --rearm --session-id=<same> first.");
173
+ lines.push("Do not wait on Cursor false-alive state. Do not spawn while DENY_DUPLICATE_ACTIVE.");
170
174
  return {
171
175
  exitCode: EXIT_STALE,
172
176
  redispatchOk: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.107.0",
3
+ "version": "0.108.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.107.0",
38
- "@deftai/directive-content": "^0.107.0"
37
+ "@deftai/directive-core": "^0.108.0",
38
+ "@deftai/directive-content": "^0.108.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"