@deftai/directive 0.107.0 → 0.109.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.
Files changed (32) hide show
  1. package/dist/dispatch.d.ts +1 -1
  2. package/dist/dispatch.js +87 -51
  3. package/dist/doctor.js +6 -0
  4. package/dist/occupancy-grant.d.ts +18 -0
  5. package/dist/occupancy-grant.js +120 -0
  6. package/dist/occupancy-heartbeat.d.ts +8 -0
  7. package/dist/occupancy-heartbeat.js +62 -0
  8. package/dist/occupancy-release.d.ts +1 -0
  9. package/dist/occupancy-release.js +26 -2
  10. package/dist/occupancy-steal.d.ts +1 -0
  11. package/dist/occupancy-steal.js +32 -4
  12. package/dist/render-cli/prd-render-cli.js +6 -10
  13. package/dist/session-ready.d.ts +2 -0
  14. package/dist/session-ready.js +37 -5
  15. package/dist/session-start.d.ts +2 -0
  16. package/dist/session-start.js +39 -6
  17. package/dist/toolchain-check.d.ts +1 -0
  18. package/dist/toolchain-check.js +41 -2
  19. package/dist/verify-orphan-active.d.ts +2 -0
  20. package/dist/verify-orphan-active.js +30 -0
  21. package/dist/verify-pr-closeout-attestable.d.ts +14 -0
  22. package/dist/verify-pr-closeout-attestable.js +96 -0
  23. package/dist/verify-source-cli/verify-closing-keywords.d.ts +35 -0
  24. package/dist/verify-source-cli/verify-closing-keywords.js +139 -0
  25. package/dist/verify-source-cli/verify-consumer-hard-stops.d.ts +16 -0
  26. package/dist/verify-source-cli/verify-consumer-hard-stops.js +99 -0
  27. package/dist/verify-source-cli/verify-deposit-closure.d.ts +12 -0
  28. package/dist/verify-source-cli/verify-deposit-closure.js +52 -0
  29. package/dist/verify-source-cli/verify-semantic-single-source.d.ts +11 -0
  30. package/dist/verify-source-cli/verify-semantic-single-source.js +49 -0
  31. package/dist/verify-subagent-alive.js +5 -1
  32. package/package.json +3 -3
@@ -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-deposit-closure", "verify-consumer-hard-stops", "verify-closing-keywords", "verify-skill-external-fetch-gate", "verify-semantic-single-source", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "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",
@@ -122,7 +123,11 @@ export const CLI_MODULE_VERBS = [
122
123
  "verify-contained-writes",
123
124
  "verify-telemetry-coverage",
124
125
  "verify-content-manifest",
126
+ "verify-deposit-closure",
127
+ "verify-consumer-hard-stops",
128
+ "verify-closing-keywords",
125
129
  "verify-skill-external-fetch-gate",
130
+ "verify-semantic-single-source",
126
131
  "verify-contract-drift",
127
132
  "verify-cursor-tier1",
128
133
  "verify-openclaw-tier1",
@@ -142,6 +147,7 @@ export const CLI_MODULE_VERBS = [
142
147
  "verify-tools",
143
148
  "verify-wip-cap",
144
149
  "verify-orphan-active",
150
+ "verify-pr-closeout-attestable",
145
151
  "verify-lifecycle-visible",
146
152
  "verify-completed-tracked",
147
153
  "verify-completed-write-guard",
@@ -287,6 +293,7 @@ export const VERB_ALIASES = {
287
293
  "verify:vbrief-conformance": "vbrief-validate",
288
294
  "verify:wip-cap": "verify-wip-cap",
289
295
  "verify:orphan-active": "verify-orphan-active",
296
+ "verify:pr-closeout-attestable": "verify-pr-closeout-attestable",
290
297
  "verify:lifecycle-visible": "verify-lifecycle-visible",
291
298
  "verify:completed-tracked": "verify-completed-tracked",
292
299
  "verify:completed-write-guard": "verify-completed-write-guard",
@@ -330,7 +337,11 @@ export const VERB_ALIASES = {
330
337
  "verify:contained-writes": "verify-contained-writes",
331
338
  "verify:telemetry-coverage": "verify-telemetry-coverage",
332
339
  "verify:content-manifest": "verify-content-manifest",
340
+ "verify:deposit-closure": "verify-deposit-closure",
341
+ "verify:consumer-hard-stops": "verify-consumer-hard-stops",
342
+ "verify:closing-keywords": "verify-closing-keywords",
333
343
  "verify:skill-external-fetch-gate": "verify-skill-external-fetch-gate",
344
+ "verify:semantic-single-source": "verify-semantic-single-source",
334
345
  "verify:contract-drift": "verify-contract-drift",
335
346
  "verify:cursor-tier1": "verify-cursor-tier1",
336
347
  "verify:openclaw-tier1": "verify-openclaw-tier1",
@@ -369,6 +380,8 @@ export const VERB_ALIASES = {
369
380
  "session:ready": "session-ready",
370
381
  "occupancy:steal": "occupancy-steal",
371
382
  "occupancy:release": "occupancy-release",
383
+ "occupancy:heartbeat": "occupancy-heartbeat",
384
+ "occupancy:grant": "occupancy-grant",
372
385
  "session:end": "occupancy-release",
373
386
  ...FRESHNESS_COLON_ALIASES,
374
387
  "lifecycle:event": "lifecycle-event",
@@ -408,7 +421,11 @@ const SUBDIR_CLI_STEMS = {
408
421
  "verify-contained-writes": "verify-source-cli/verify-contained-writes",
409
422
  "verify-telemetry-coverage": "verify-source-cli/verify-telemetry-coverage",
410
423
  "verify-content-manifest": "verify-source-cli/verify-content-manifest",
424
+ "verify-deposit-closure": "verify-source-cli/verify-deposit-closure",
425
+ "verify-consumer-hard-stops": "verify-source-cli/verify-consumer-hard-stops",
426
+ "verify-closing-keywords": "verify-source-cli/verify-closing-keywords",
411
427
  "verify-skill-external-fetch-gate": "verify-source-cli/verify-skill-external-fetch-gate",
428
+ "verify-semantic-single-source": "verify-source-cli/verify-semantic-single-source",
412
429
  "verify-contract-drift": "verify-source-cli/verify-contract-drift",
413
430
  "verify-cursor-tier1": "verify-source-cli/verify-cursor-tier1",
414
431
  "verify-openclaw-tier1": "verify-source-cli/verify-openclaw-tier1",
@@ -2104,25 +2121,26 @@ function parsePolicySetArgs(argv) {
2104
2121
  }
2105
2122
  return args;
2106
2123
  }
2107
- /** Load PROJECT-DEFINITION for an in-place typed-field write (mirrors the .setdefault chain). */
2108
- function loadProjectDefinitionForWrite(projectRoot) {
2109
- const path = projectDefinitionPath(projectRoot);
2124
+ /**
2125
+ * Load PROJECT-DEFINITION for an in-place typed-field write (mirrors the
2126
+ * .setdefault chain). Reads the artifact the mutation lock captured (#3796) so
2127
+ * these CLI writers can no longer resolve, read, and write a PROJECT-DEFINITION
2128
+ * outside the shared mutation lock.
2129
+ */
2130
+ function loadProjectDefinitionForWrite(mutation) {
2131
+ const path = mutation.artifactPath;
2110
2132
  if (!existsSync(path)) {
2111
- throw new PolicySetError(`PROJECT-DEFINITION not found at ${path}`, "not-found");
2133
+ throw new PolicySetError(`PROJECT-DEFINITION not found at ${mutation.artifactLabel}`, "not-found");
2112
2134
  }
2113
- let parsed;
2135
+ let data;
2114
2136
  try {
2115
- parsed = JSON.parse(readFileSync(path, "utf8"));
2137
+ // The shared loader rejects unreadable, malformed, and non-object top-level
2138
+ // payloads; every one of those is a config error for the CLI writers.
2139
+ data = mutation.load();
2116
2140
  }
2117
2141
  catch (err) {
2118
- throw new PolicySetError(`PROJECT-DEFINITION at ${path} is not valid JSON: ${String(err)}`, "config");
2142
+ throw new PolicySetError(err instanceof Error ? err.message : String(err), "config");
2119
2143
  }
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
2144
  let plan = data.plan;
2127
2145
  if (plan === undefined) {
2128
2146
  plan = {};
@@ -2141,45 +2159,47 @@ function loadProjectDefinitionForWrite(projectRoot) {
2141
2159
  if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
2142
2160
  throw new PolicySetError("plan.policy is not an object", "config");
2143
2161
  }
2144
- return { path, data, policyBlock: policy, legacyMigrated };
2162
+ return { data, policyBlock: policy, legacyMigrated };
2145
2163
  }
2146
2164
  /** Write plan.policy.wipCap in place + append the audit row (mirrors set_wip_cap). */
2147
2165
  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 };
2166
+ return withProjectDefinitionMutation(projectRoot, (mutation) => {
2167
+ const { data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(mutation);
2168
+ const previous = policyBlock.wipCap;
2169
+ policyBlock.wipCap = cap;
2170
+ const changed = previous !== cap || legacyMigrated;
2171
+ const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
2172
+ if (note)
2173
+ parts.push(`note=${sanitizeNote(note)}`);
2174
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2175
+ if (changed) {
2176
+ mutation.persist(data);
2177
+ }
2178
+ appendAuditLog(projectRoot, auditEntry, changed);
2179
+ return { changed, auditEntry };
2180
+ });
2162
2181
  }
2163
2182
  /** Write plan.policy.swarmSubagentBackend in place + append the audit row. */
2164
2183
  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 };
2184
+ return withProjectDefinitionMutation(projectRoot, (mutation) => {
2185
+ const { data, policyBlock, legacyMigrated } = loadProjectDefinitionForWrite(mutation);
2186
+ const previous = policyBlock.swarmSubagentBackend;
2187
+ policyBlock.swarmSubagentBackend = backendId;
2188
+ const changed = previous !== backendId || legacyMigrated;
2189
+ const parts = [
2190
+ `actor=${actor}`,
2191
+ `swarmSubagentBackend=${backendId}`,
2192
+ `previous=${pyRepr(previous)}`,
2193
+ ];
2194
+ if (note)
2195
+ parts.push(`note=${sanitizeNote(note)}`);
2196
+ const auditEntry = stampChangedToken(parts.join(" "), changed);
2197
+ if (changed) {
2198
+ mutation.persist(data);
2199
+ }
2200
+ appendAuditLog(projectRoot, auditEntry, changed);
2201
+ return { changed, auditEntry };
2202
+ });
2183
2203
  }
2184
2204
  /** Serialise the probe output for `subagent-backends --format json`. */
2185
2205
  function subagentBackendsToJson(backends) {
@@ -2617,19 +2637,30 @@ const CURATED_HELP_GROUPS = [
2617
2637
  {
2618
2638
  title: "Session & ritual",
2619
2639
  commands: [
2620
- { name: "session:start", summary: "Record session-start ritual state" },
2640
+ {
2641
+ name: "session:start",
2642
+ summary: "Claim occupancy and record ritual state for one session owner",
2643
+ },
2621
2644
  {
2622
2645
  name: "session:ready",
2623
2646
  summary: "One-shot recovery to gated write-ready (session + ritual + cache)",
2624
2647
  },
2625
2648
  {
2626
2649
  name: "occupancy:steal",
2627
- summary: "Supersede a live worktree occupancy lease (--confirm --occupant)",
2650
+ summary: "Replace only the live lease; use session:start --steal to align ritual",
2628
2651
  },
2629
2652
  {
2630
2653
  name: "occupancy:release",
2631
2654
  summary: "Release this worktree occupancy lease (owner live, or expired residue)",
2632
2655
  },
2656
+ {
2657
+ name: "occupancy:heartbeat",
2658
+ summary: "Refresh your own live lease during long quiet stretches (never claims)",
2659
+ },
2660
+ {
2661
+ name: "occupancy:grant",
2662
+ summary: "Admit a dispatched child to your lease for writes (--revoke withdraws)",
2663
+ },
2633
2664
  {
2634
2665
  name: "session:end",
2635
2666
  summary: "End this mutation session by releasing occupancy (same as occupancy:release)",
@@ -2795,6 +2826,9 @@ export async function dispatch(argv, io = defaultIo()) {
2795
2826
  const planSequenceSubcommand = verb !== undefined ? PLAN_SEQUENCE_ALIAS_SUBCOMMANDS[verb] : undefined;
2796
2827
  const productSignalSubcommand = verb !== undefined ? PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS[verb] : undefined;
2797
2828
  const freshnessSubcommand = verb !== undefined ? FRESHNESS_ALIAS_SUBCOMMANDS[verb] : undefined;
2829
+ const vbriefValidateSubcommand = verb === "verify:vbrief-conformance" && canonical === "vbrief-validate"
2830
+ ? "conformance"
2831
+ : undefined;
2798
2832
  const handlerArgv = canonical === "framework-commands" && verb !== undefined && verb !== canonical
2799
2833
  ? [verb, ...rest]
2800
2834
  : triageSubcommand !== undefined && canonical === "triage-actions"
@@ -2811,7 +2845,9 @@ export async function dispatch(argv, io = defaultIo()) {
2811
2845
  ? [productSignalSubcommand, ...rest]
2812
2846
  : freshnessSubcommand !== undefined && canonical === "freshness-report"
2813
2847
  ? [freshnessSubcommand, ...rest]
2814
- : rest;
2848
+ : vbriefValidateSubcommand !== undefined
2849
+ ? [vbriefValidateSubcommand, ...rest]
2850
+ : rest;
2815
2851
  return await invokeHandler(handler, handlerArgv);
2816
2852
  }
2817
2853
  catch (err) {
package/dist/doctor.js CHANGED
@@ -6,6 +6,7 @@ import { resolveContentPackageRoot } from "@deftai/directive-core/dist/content-r
6
6
  import { parseDoctorFlags } from "@deftai/directive-core/dist/doctor/flags.js";
7
7
  import { cmdDoctor } from "@deftai/directive-core/dist/doctor/main.js";
8
8
  import { findPackageAbsentDepositPathsSync } from "@deftai/directive-core/dist/init-deposit/hygiene.js";
9
+ import { evaluateInstalledDepositClosure, renderDeclaredDepositClosureLine, } from "@deftai/directive-core/dist/validate-content/deposit-required.js";
9
10
  import { renderPrecutoverLine } from "@deftai/directive-core/dist/vbrief-validate/precutover.js";
10
11
  import { renderStaleHeaderLine, renderXbriefMigrationLine, } from "@deftai/directive-core/xbrief-migrate";
11
12
  /** Compare `.deft/core/` against `@deftai/directive-content` (#2804). */
@@ -56,9 +57,14 @@ export function run(argv) {
56
57
  process.stdout.write(`${renderXbriefMigrationLine(projectRoot)}\n`);
57
58
  process.stdout.write(`${renderStaleHeaderLine(projectRoot)}\n`);
58
59
  process.stdout.write(`${renderDepositFileSetHygieneLine(projectRoot, depositResult)}\n`);
60
+ const closure = evaluateInstalledDepositClosure(projectRoot);
61
+ process.stdout.write(`${renderDeclaredDepositClosureLine(closure)}\n`);
59
62
  if (flags.full && !depositResult.skipped && depositResult.absent.length > 0) {
60
63
  depositHygieneExit = 1;
61
64
  }
65
+ if (flags.full && !closure.skipped && (closure.missing.length > 0 || closure.error !== null)) {
66
+ depositHygieneExit = 1;
67
+ }
62
68
  }
63
69
  const doctorExit = cmdDoctor(argv);
64
70
  return Math.max(depositHygieneExit, doctorExit);
@@ -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,