@deftai/directive-core 0.104.0 → 0.105.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 (225) hide show
  1. package/dist/authz/classify.js +526 -42
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +33 -3
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/fs/contained-write.d.ts +115 -1
  17. package/dist/fs/contained-write.js +241 -5
  18. package/dist/fs/mutation-ledger.d.ts +54 -0
  19. package/dist/fs/mutation-ledger.js +133 -0
  20. package/dist/fs/non-product-dirs.d.ts +79 -0
  21. package/dist/fs/non-product-dirs.js +96 -0
  22. package/dist/hooks/dispatcher.d.ts +3 -1
  23. package/dist/hooks/dispatcher.js +14 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/init-deposit/agent-hooks.js +17 -15
  27. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  28. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  29. package/dist/init-deposit/gitignore.js +9 -0
  30. package/dist/init-deposit/hygiene.d.ts +47 -11
  31. package/dist/init-deposit/hygiene.js +172 -37
  32. package/dist/init-deposit/legacy-detect.js +6 -2
  33. package/dist/init-deposit/refresh.d.ts +21 -1
  34. package/dist/init-deposit/refresh.js +106 -9
  35. package/dist/init-deposit/scaffold.js +28 -279
  36. package/dist/init-deposit/slash-deposit.js +13 -7
  37. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  38. package/dist/init-deposit/xbrief-projections.js +24 -11
  39. package/dist/intake/clause-derivation.d.ts +38 -1
  40. package/dist/intake/clause-derivation.js +211 -9
  41. package/dist/intake/issue-ingest.d.ts +3 -1
  42. package/dist/intake/issue-ingest.js +35 -6
  43. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  44. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  45. package/dist/literal-acceptance/capture.d.ts +10 -0
  46. package/dist/literal-acceptance/capture.js +224 -75
  47. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  48. package/dist/literal-acceptance/evaluate.js +70 -2
  49. package/dist/literal-acceptance/index.d.ts +3 -3
  50. package/dist/literal-acceptance/index.js +3 -3
  51. package/dist/literal-acceptance/safety.d.ts +41 -0
  52. package/dist/literal-acceptance/safety.js +126 -2
  53. package/dist/literal-acceptance/types.d.ts +8 -0
  54. package/dist/orphan-active/evaluate.d.ts +7 -1
  55. package/dist/orphan-active/evaluate.js +102 -23
  56. package/dist/platform/platform-capabilities.js +2 -3
  57. package/dist/policy/base-branch.d.ts +37 -0
  58. package/dist/policy/base-branch.js +114 -0
  59. package/dist/policy/branch-sync.d.ts +80 -0
  60. package/dist/policy/branch-sync.js +211 -0
  61. package/dist/policy/ceremony-dial-escalation.js +1 -0
  62. package/dist/policy/ceremony-dial.js +4 -14
  63. package/dist/policy/delivery-branch.d.ts +5 -3
  64. package/dist/policy/delivery-branch.js +7 -3
  65. package/dist/policy/file-size-thresholds.d.ts +24 -0
  66. package/dist/policy/file-size-thresholds.js +23 -0
  67. package/dist/policy/forge-outage-retry.d.ts +47 -0
  68. package/dist/policy/forge-outage-retry.js +157 -0
  69. package/dist/policy/index.d.ts +7 -0
  70. package/dist/policy/index.js +66 -7
  71. package/dist/policy/project-invariants.d.ts +50 -0
  72. package/dist/policy/project-invariants.js +205 -0
  73. package/dist/policy/sync-default.d.ts +129 -0
  74. package/dist/policy/sync-default.js +452 -0
  75. package/dist/policy/sync-max-files.d.ts +93 -0
  76. package/dist/policy/sync-max-files.js +192 -0
  77. package/dist/pr-monitor/absent-required.d.ts +11 -0
  78. package/dist/pr-monitor/absent-required.js +61 -0
  79. package/dist/pr-monitor/constants.d.ts +7 -0
  80. package/dist/pr-monitor/constants.js +7 -0
  81. package/dist/pr-monitor/index.d.ts +2 -1
  82. package/dist/pr-monitor/index.js +2 -1
  83. package/dist/pr-monitor/main.js +4 -0
  84. package/dist/pr-monitor/monitor.js +24 -1
  85. package/dist/pr-wait-mergeable/classify.js +6 -0
  86. package/dist/preflight/evaluate.d.ts +4 -0
  87. package/dist/preflight/evaluate.js +35 -1
  88. package/dist/preflight/index.d.ts +4 -0
  89. package/dist/preflight/index.js +2 -0
  90. package/dist/preflight/intended-placement.d.ts +62 -0
  91. package/dist/preflight/intended-placement.js +275 -0
  92. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  93. package/dist/preflight/project-invariants-gate.js +82 -0
  94. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  95. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  96. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  97. package/dist/product-first-done-gate/acceptance.js +79 -8
  98. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  99. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  100. package/dist/product-first-done-gate/evaluate.d.ts +12 -0
  101. package/dist/product-first-done-gate/evaluate.js +254 -31
  102. package/dist/product-first-done-gate/index.d.ts +2 -0
  103. package/dist/product-first-done-gate/index.js +1 -0
  104. package/dist/release/build-dist.d.ts +101 -0
  105. package/dist/release/build-dist.js +428 -46
  106. package/dist/run-summary/emit.d.ts +16 -10
  107. package/dist/run-summary/emit.js +26 -15
  108. package/dist/run-summary/index.d.ts +3 -2
  109. package/dist/run-summary/index.js +2 -1
  110. package/dist/run-summary/session-id.d.ts +21 -0
  111. package/dist/run-summary/session-id.js +92 -0
  112. package/dist/run-summary/types.d.ts +29 -4
  113. package/dist/run-summary/types.js +1 -0
  114. package/dist/scope/acceptance-evidence.d.ts +19 -7
  115. package/dist/scope/acceptance-evidence.js +38 -11
  116. package/dist/scope/delivery-evidence.d.ts +3 -3
  117. package/dist/scope/delivery-evidence.js +22 -27
  118. package/dist/scope/index.d.ts +1 -0
  119. package/dist/scope/index.js +1 -0
  120. package/dist/scope/parent-lineage.js +7 -6
  121. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  122. package/dist/scope/project-invariant-coverage.js +322 -0
  123. package/dist/scope/transition.js +1 -1
  124. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  125. package/dist/scope-provenance/compare-intent.js +233 -0
  126. package/dist/scope-provenance/digest.d.ts +18 -1
  127. package/dist/scope-provenance/digest.js +16 -11
  128. package/dist/scope-provenance/evaluate.d.ts +5 -1
  129. package/dist/scope-provenance/evaluate.js +64 -4
  130. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  131. package/dist/scope-provenance/extract-intent.js +262 -0
  132. package/dist/scope-provenance/index.d.ts +7 -1
  133. package/dist/scope-provenance/index.js +7 -1
  134. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  135. package/dist/scope-provenance/intent-digest.js +33 -0
  136. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  137. package/dist/scope-provenance/intent-evaluate.js +149 -0
  138. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  139. package/dist/scope-provenance/json-tokenizer.js +225 -0
  140. package/dist/scope-provenance/known-machine.d.ts +38 -0
  141. package/dist/scope-provenance/known-machine.js +157 -0
  142. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  143. package/dist/scope-provenance/mint-artifacts.js +425 -0
  144. package/dist/session/ac-pass-banking.d.ts +4 -36
  145. package/dist/session/ac-pass-banking.js +30 -77
  146. package/dist/session/ac-pass-reuse.d.ts +53 -0
  147. package/dist/session/ac-pass-reuse.js +95 -0
  148. package/dist/session/git.js +19 -4
  149. package/dist/session/index.d.ts +4 -0
  150. package/dist/session/index.js +4 -0
  151. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  152. package/dist/session/occupancy-stress-worker.js +36 -0
  153. package/dist/session/occupancy.d.ts +97 -0
  154. package/dist/session/occupancy.js +422 -0
  155. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  156. package/dist/session/product-state-hash.d.ts +26 -0
  157. package/dist/session/product-state-hash.js +683 -0
  158. package/dist/session/session-ready.d.ts +2 -0
  159. package/dist/session/session-ready.js +52 -30
  160. package/dist/session/session-start.d.ts +7 -0
  161. package/dist/session/session-start.js +71 -7
  162. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  163. package/dist/session/verify-ac-session-cache.js +126 -0
  164. package/dist/session/verify-session-ritual.d.ts +5 -0
  165. package/dist/session/verify-session-ritual.js +9 -0
  166. package/dist/slash/openclaw-deposit.js +13 -9
  167. package/dist/slice/constants.d.ts +2 -2
  168. package/dist/slice/constants.js +2 -2
  169. package/dist/slice/existing.js +9 -3
  170. package/dist/slice/lock.d.ts +16 -1
  171. package/dist/slice/lock.js +137 -11
  172. package/dist/story-ready/evaluate.d.ts +2 -0
  173. package/dist/story-ready/evaluate.js +13 -0
  174. package/dist/swarm/complete-cohort.d.ts +3 -0
  175. package/dist/swarm/complete-cohort.js +59 -11
  176. package/dist/swarm/finalize-cohort.js +33 -2
  177. package/dist/swarm/launch.d.ts +22 -0
  178. package/dist/swarm/launch.js +155 -0
  179. package/dist/swarm/routing-set-cli.d.ts +11 -1
  180. package/dist/swarm/routing-set-cli.js +13 -3
  181. package/dist/swarm/routing-verify.d.ts +12 -0
  182. package/dist/swarm/routing-verify.js +59 -20
  183. package/dist/swarm/routing.d.ts +14 -0
  184. package/dist/swarm/routing.js +53 -3
  185. package/dist/telemetry-coverage/fake-trial.js +10 -0
  186. package/dist/telemetry-coverage/kinds.js +2 -0
  187. package/dist/triage/actions/index.js +4 -1
  188. package/dist/ts-check-lane/index.d.ts +3 -0
  189. package/dist/ts-check-lane/index.js +2 -0
  190. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  191. package/dist/ts-check-lane/progress-reporter.js +38 -0
  192. package/dist/ts-check-lane/progress.d.ts +32 -0
  193. package/dist/ts-check-lane/progress.js +57 -0
  194. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  195. package/dist/ts-check-lane/run-lane.js +8 -6
  196. package/dist/validate-content/validate-links.d.ts +8 -0
  197. package/dist/validate-content/validate-links.js +9 -15
  198. package/dist/value/feedback-file.js +12 -2
  199. package/dist/vbrief-build/index.d.ts +1 -0
  200. package/dist/vbrief-build/index.js +1 -0
  201. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  202. package/dist/vbrief-build/project-invariants-io.js +41 -0
  203. package/dist/vbrief-reconcile/graph.js +18 -1
  204. package/dist/vbrief-reconcile/index.d.ts +1 -1
  205. package/dist/vbrief-reconcile/index.js +1 -1
  206. package/dist/vbrief-reconcile/types.d.ts +13 -0
  207. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  208. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  209. package/dist/vbrief-validate/index.d.ts +1 -1
  210. package/dist/vbrief-validate/index.js +1 -1
  211. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  212. package/dist/vbrief-validate/plan-hooks.js +68 -0
  213. package/dist/verify-ac/evaluate.d.ts +7 -1
  214. package/dist/verify-ac/evaluate.js +34 -26
  215. package/dist/verify-ac/flag.d.ts +4 -0
  216. package/dist/verify-ac/flag.js +16 -0
  217. package/dist/verify-ac/index.d.ts +2 -2
  218. package/dist/verify-ac/index.js +2 -2
  219. package/dist/verify-source/contained-writes.js +2 -0
  220. package/dist/verify-source/verify-stubs.d.ts +15 -1
  221. package/dist/verify-source/verify-stubs.js +20 -16
  222. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  223. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  224. package/dist/xbrief-migrate/migrate-project.js +2 -5
  225. package/package.json +3 -3
@@ -1,12 +1,127 @@
1
- import { closeSync, existsSync, mkdirSync, openSync, unlinkSync, writeSync } from "node:fs";
1
+ import { randomUUID } from "node:crypto";
2
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from "node:fs";
2
3
  import { dirname } from "node:path";
3
4
  const threadLocked = { held: false };
5
+ /** 10× occupancy heartbeat TTL. Off-Linux PID reuse / stalled holder reclaim. */
6
+ export const STALE_LOCK_HARD_CAP_MS = 20 * 60 * 1000 * 10;
4
7
  function defaultSleep(ms) {
5
8
  const end = Date.now() + ms;
6
9
  while (Date.now() < end) {
7
10
  /* spin */
8
11
  }
9
12
  }
13
+ export function parseLockRecord(lockPath) {
14
+ try {
15
+ const lines = readFileSync(lockPath, { encoding: "utf8" }).trim().split(/\r?\n/);
16
+ const pidRaw = Number(lines[0]);
17
+ const pid = Number.isInteger(pidRaw) && pidRaw > 0 ? pidRaw : null;
18
+ if (lines.length >= 3) {
19
+ const acquiredRaw = Number(lines[2]);
20
+ return {
21
+ pid,
22
+ token: lines[1] ?? "",
23
+ acquiredAt: Number.isInteger(acquiredRaw) && acquiredRaw > 0 ? acquiredRaw : null,
24
+ };
25
+ }
26
+ const acquiredRaw = Number(lines[1]);
27
+ return {
28
+ pid,
29
+ token: "",
30
+ acquiredAt: Number.isInteger(acquiredRaw) && acquiredRaw > 0 ? acquiredRaw : null,
31
+ };
32
+ }
33
+ catch {
34
+ return { pid: null, token: "", acquiredAt: null };
35
+ }
36
+ }
37
+ export function assertAppendLockOwned(held) {
38
+ const rec = parseLockRecord(held.lockPath);
39
+ if (rec.pid !== held.pid || rec.token !== held.token || rec.token.length === 0) {
40
+ throw new Error(`lock compromised: ${held.lockPath}`);
41
+ }
42
+ }
43
+ function processExists(pid) {
44
+ try {
45
+ process.kill(pid, 0);
46
+ return true;
47
+ }
48
+ catch (err) {
49
+ return err.code !== "ESRCH";
50
+ }
51
+ }
52
+ function linuxPidStartedAfter(pid, acquiredAt) {
53
+ try {
54
+ const stat = readFileSync(`/proc/${pid}/stat`, { encoding: "utf8" });
55
+ const closeParen = stat.lastIndexOf(")");
56
+ if (closeParen < 0)
57
+ return false;
58
+ const startTicks = Number(stat.slice(closeParen + 2).split(" ")[19]);
59
+ if (!Number.isFinite(startTicks))
60
+ return false;
61
+ const btime = /(?:^|\n)btime (\d+)/.exec(readFileSync("/proc/stat", { encoding: "utf8" }));
62
+ if (btime === null)
63
+ return false;
64
+ const startMs = Number(btime[1]) * 1000 + (startTicks * 1000) / 100;
65
+ return startMs > acquiredAt + 1000;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ function lockMtimeMs(lockPath) {
72
+ try {
73
+ return statSync(lockPath).mtimeMs;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ function isAbandoned(rec, now, lockPath) {
80
+ if (rec.pid === null) {
81
+ const mtime = lockMtimeMs(lockPath);
82
+ return mtime !== null && now - mtime > STALE_LOCK_HARD_CAP_MS;
83
+ }
84
+ if (!processExists(rec.pid))
85
+ return true;
86
+ if (rec.acquiredAt !== null && linuxPidStartedAfter(rec.pid, rec.acquiredAt))
87
+ return true;
88
+ if (rec.acquiredAt !== null && now - rec.acquiredAt > STALE_LOCK_HARD_CAP_MS)
89
+ return true;
90
+ return false;
91
+ }
92
+ function recordsMatch(a, b) {
93
+ return a.pid === b.pid && a.token === b.token && a.acquiredAt === b.acquiredAt;
94
+ }
95
+ /** Atomic rename reclaim: one waiter wins; losers see ENOENT. */
96
+ function tryReclaimAbandonedOwner(lockPath, now) {
97
+ const observed = parseLockRecord(lockPath);
98
+ if (!isAbandoned(observed, now, lockPath))
99
+ return false;
100
+ const quarantine = `${lockPath}.reclaim.${process.pid}.${randomUUID()}`;
101
+ try {
102
+ renameSync(lockPath, quarantine);
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ const quarantined = parseLockRecord(quarantine);
108
+ if (recordsMatch(observed, quarantined)) {
109
+ try {
110
+ unlinkSync(quarantine);
111
+ }
112
+ catch {
113
+ /* already gone */
114
+ }
115
+ return true;
116
+ }
117
+ try {
118
+ renameSync(quarantine, lockPath);
119
+ }
120
+ catch {
121
+ /* best-effort restore */
122
+ }
123
+ return false;
124
+ }
10
125
  /** Serialise appenders across threads AND processes (sidecar lock file). */
11
126
  export function withAppendLock(logPath, fn, deps = {}) {
12
127
  const sleepMs = deps.sleepMs ?? defaultSleep;
@@ -18,13 +133,18 @@ export function withAppendLock(logPath, fn, deps = {}) {
18
133
  }
19
134
  threadLocked.held = true;
20
135
  let fd;
136
+ let held;
137
+ let reclaimedStale = false;
21
138
  try {
22
139
  const deadline = now() + 30_000;
23
140
  while (true) {
24
141
  try {
25
142
  fd = openSync(lockPath, "wx");
26
- writeSync(fd, Buffer.from("\0"));
27
- break;
143
+ const token = randomUUID();
144
+ const acquiredAt = now();
145
+ writeSync(fd, Buffer.from(`${process.pid}\n${token}\n${acquiredAt}\n`));
146
+ held = { lockPath, pid: process.pid, token, acquiredAt };
147
+ return fn(held);
28
148
  }
29
149
  catch (err) {
30
150
  const code = err.code;
@@ -32,26 +152,32 @@ export function withAppendLock(logPath, fn, deps = {}) {
32
152
  throw err;
33
153
  }
34
154
  if (now() > deadline) {
155
+ if (!reclaimedStale && tryReclaimAbandonedOwner(lockPath, now())) {
156
+ reclaimedStale = true;
157
+ continue;
158
+ }
35
159
  throw new Error(`timed out acquiring lock for ${logPath}`);
36
160
  }
37
161
  sleepMs(20);
38
162
  }
39
163
  }
40
- return fn();
41
164
  }
42
165
  finally {
43
166
  if (fd !== undefined) {
44
167
  closeSync(fd);
45
168
  }
46
- threadLocked.held = false;
47
- try {
48
- if (existsSync(lockPath)) {
49
- unlinkSync(lockPath);
169
+ if (held !== undefined) {
170
+ try {
171
+ assertAppendLockOwned(held);
172
+ if (existsSync(lockPath)) {
173
+ unlinkSync(lockPath);
174
+ }
175
+ }
176
+ catch {
177
+ /* not ours anymore */
50
178
  }
51
179
  }
52
- catch {
53
- /* best-effort */
54
- }
180
+ threadLocked.held = false;
55
181
  }
56
182
  }
57
183
  /** Public alias mirroring Python append_lock. */
@@ -20,6 +20,8 @@ export interface EvaluateOptions {
20
20
  readonly projectRoot?: string;
21
21
  /** Skip parent-lineage check (tests / opt-out). Default false. */
22
22
  readonly skipParentLineage?: boolean;
23
+ /** Skip project-invariant coverage (#3425). Default false. */
24
+ readonly skipProjectInvariants?: boolean;
23
25
  }
24
26
  /**
25
27
  * Pure evaluator — returns exit code + human message. Faithful to
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { evaluateProjectInvariantsGate, resolveProjectRootForInvariants, } from "../preflight/project-invariants-gate.js";
3
4
  import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
4
5
  import { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND, VALID_DISPATCH_KINDS, } from "./allocation.js";
5
6
  export const ACTIVE_FOLDER = "active";
@@ -168,6 +169,18 @@ export function evaluate(vbriefPath, options = {}) {
168
169
  `\n${formatParentLineageLine(lineage)}`,
169
170
  };
170
171
  }
172
+ const invariants = evaluateProjectInvariantsGate(vbriefCheck.payload, {
173
+ projectRoot: resolveProjectRootForInvariants(vbriefCheck.path, options.projectRoot),
174
+ skip: options.skipProjectInvariants === true,
175
+ });
176
+ if (!invariants.ok) {
177
+ return {
178
+ exitCode: 1,
179
+ dispatchKind: null,
180
+ parentLineage: lineage,
181
+ message: `not ready: ${invariants.message}`,
182
+ };
183
+ }
171
184
  const lineageNote = lineage.applicable
172
185
  ? `parent lineage OK (${lineage.parent_requirement_ids.length} req IDs` +
173
186
  (lineage.negative_invariant_ids.length > 0
@@ -45,9 +45,12 @@ export declare function completeCohort(args: {
45
45
  emitJson?: boolean;
46
46
  /** Per-story delivery evidence; without it code-bearing stories fail closed (#3041). */
47
47
  delivery?: CohortDeliveryContext | null;
48
+ /** Optional launch allocation plan id so close-out reads this cohort's occupancy slot. */
49
+ allocationPlanId?: string | null;
48
50
  }): {
49
51
  exitCode: number;
50
52
  stdout: string;
51
53
  stderr: string;
54
+ sweep: SweepResult | null;
52
55
  };
53
56
  //# sourceMappingURL=complete-cohort.d.ts.map
@@ -1,11 +1,13 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
- import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
3
+ import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
4
4
  import { detectLifecycleFolder } from "../scope/decomposed-refs.js";
5
5
  import { classifyStoredDeliveryDisposition, } from "../scope/delivery-evidence.js";
6
6
  import { runTransition } from "../scope/transition.js";
7
7
  import { collectChildUris, collectPlanRefs, resolveVbriefRef } from "../scope/vbrief-ref.js";
8
+ import { readOccupancy, releaseSwarmOccupancy } from "../session/occupancy.js";
8
9
  import { MAX_FIXPOINT_PASSES, TERMINAL_FOLDERS } from "./constants.js";
10
+ import { resolveLaunchOccupancySessionId } from "./launch.js";
9
11
  function loadPlan(path) {
10
12
  try {
11
13
  const data = JSON.parse(readFileSync(path, "utf8"));
@@ -21,6 +23,14 @@ function loadPlan(path) {
21
23
  return null;
22
24
  }
23
25
  }
26
+ function storyIdFromPath(path) {
27
+ const plan = loadPlan(path);
28
+ const id = plan !== null && typeof plan.id === "string" ? plan.id.trim() : "";
29
+ if (id.length > 0)
30
+ return id;
31
+ const name = basename(path);
32
+ return hasArtifactSuffix(name) ? stripArtifactSuffix(name) : name.replace(/\.[^.]+$/, "");
33
+ }
24
34
  function rel(path, projectRoot) {
25
35
  try {
26
36
  return resolve(path)
@@ -226,6 +236,7 @@ function completeParent(args) {
226
236
  }
227
237
  let current = parentPath;
228
238
  let action = "complete";
239
+ let activateNotice = "";
229
240
  if (folder === "pending") {
230
241
  action = "activate+complete";
231
242
  const activateResult = runTransition("activate", current);
@@ -238,18 +249,22 @@ function completeParent(args) {
238
249
  detail: `activate failed: ${activateResult.message}`,
239
250
  };
240
251
  }
252
+ activateNotice = activateResult.message.trim();
241
253
  current = join(vbriefDir, "active", parentPath.split(/[/\\]/).pop() ?? "");
242
254
  }
243
255
  const completeResult = runTransition("complete", current, new Date(), transitionOptionsFor(current, delivery));
244
256
  if (completeResult.ok) {
245
257
  settled.add(resolve(join(vbriefDir, "completed", parentPath.split(/[/\\]/).pop() ?? "")));
246
258
  }
259
+ const detail = activateNotice.length > 0
260
+ ? `${activateNotice}\n${completeResult.message}`
261
+ : completeResult.message;
247
262
  return {
248
263
  kind: "epic",
249
264
  path: relpath,
250
265
  action: completeResult.ok ? action : "failed",
251
266
  ok: completeResult.ok,
252
- detail: completeResult.message,
267
+ detail,
253
268
  };
254
269
  }
255
270
  export function sweepCohort(storyPaths, projectRoot, dryRun, delivery) {
@@ -412,6 +427,7 @@ export function completeCohort(args) {
412
427
  exitCode: 2,
413
428
  stdout: "",
414
429
  stderr: `Error: project root does not exist: ${projectRoot}\n`,
430
+ sweep: null,
415
431
  };
416
432
  }
417
433
  if (!existsSync(resolveLifecycleRoot(projectRoot))) {
@@ -419,31 +435,33 @@ export function completeCohort(args) {
419
435
  exitCode: 2,
420
436
  stdout: "",
421
437
  stderr: `Error: no vbrief/ directory under project root: ${projectRoot}\n`,
438
+ sweep: null,
422
439
  };
423
440
  }
424
441
  const { paths, errors } = resolveCohortPaths(args.stories ?? [], args.cohortGlobs ?? [], projectRoot);
425
442
  if (paths.length === 0) {
426
443
  const msg = "Error: empty cohort. Pass one or more story vBRIEF paths as positional arguments and/or --cohort <glob>.";
444
+ const empty = {
445
+ project_root: projectRoot,
446
+ dry_run: args.dryRun ?? false,
447
+ stories: [],
448
+ parents: [],
449
+ errors: errors.length > 0 ? errors : [msg],
450
+ ok: false,
451
+ };
427
452
  if (args.emitJson) {
428
- const empty = {
429
- project_root: projectRoot,
430
- dry_run: args.dryRun ?? false,
431
- stories: [],
432
- parents: [],
433
- errors: errors.length > 0 ? errors : [msg],
434
- ok: false,
435
- };
436
453
  return {
437
454
  exitCode: 2,
438
455
  stdout: `${JSON.stringify(sweepResultToDict(empty), null, 2)}\n`,
439
456
  stderr: "",
457
+ sweep: empty,
440
458
  };
441
459
  }
442
460
  let stderr = `${msg}\n`;
443
461
  for (const err of errors) {
444
462
  stderr += ` - ${err}\n`;
445
463
  }
446
- return { exitCode: 2, stdout: "", stderr };
464
+ return { exitCode: 2, stdout: "", stderr, sweep: empty };
447
465
  }
448
466
  const result = sweepCohortWithArgs({
449
467
  storyPaths: paths,
@@ -452,17 +470,47 @@ export function completeCohort(args) {
452
470
  delivery: args.delivery ?? null,
453
471
  });
454
472
  result.errors.push(...errors);
473
+ if (result.ok && args.dryRun !== true) {
474
+ const envSession = process.env.DEFT_SESSION_ID?.trim() ?? "";
475
+ let sessionId = envSession;
476
+ if (sessionId.length === 0) {
477
+ const resolved = resolveLaunchOccupancySessionId(projectRoot, {
478
+ allocationPlanId: args.allocationPlanId ?? null,
479
+ storyIds: paths.map(storyIdFromPath),
480
+ });
481
+ if (resolved.reason === "ok" && resolved.sessionId.length > 0) {
482
+ sessionId = resolved.sessionId;
483
+ }
484
+ else if (readOccupancy(projectRoot) !== null) {
485
+ result.ok = false;
486
+ result.errors.push("swarm close-out occupancy record is missing or belongs to a different cohort. " +
487
+ "Steal with occupancy:steal --confirm --occupant <id>.");
488
+ }
489
+ }
490
+ if (result.ok && sessionId.length > 0) {
491
+ const released = releaseSwarmOccupancy(projectRoot, {
492
+ env: process.env,
493
+ sessionId,
494
+ });
495
+ if (released.code !== 0) {
496
+ result.ok = false;
497
+ result.errors.push(released.message);
498
+ }
499
+ }
500
+ }
455
501
  if (args.emitJson) {
456
502
  return {
457
503
  exitCode: result.ok ? 0 : 1,
458
504
  stdout: `${JSON.stringify(sweepResultToDict(result), null, 2)}\n`,
459
505
  stderr: "",
506
+ sweep: result,
460
507
  };
461
508
  }
462
509
  return {
463
510
  exitCode: result.ok ? 0 : 1,
464
511
  stdout: `${renderSweepText(result)}\n`,
465
512
  stderr: "",
513
+ sweep: result,
466
514
  };
467
515
  }
468
516
  //# sourceMappingURL=complete-cohort.js.map
@@ -51,6 +51,18 @@ function safeSegment(text) {
51
51
  function oneLine(text) {
52
52
  return text.replace(/\r?\n/g, " ");
53
53
  }
54
+ /** Existing sweep records already carry refused-stamp remediations (#3398). */
55
+ function sweepDetailLines(sweep) {
56
+ const lines = [];
57
+ for (const rec of [...sweep.stories, ...sweep.parents]) {
58
+ const detail = rec.detail.trim();
59
+ if (detail.length > 0) {
60
+ lines.push(`${rec.path}: ${detail}`);
61
+ }
62
+ }
63
+ lines.push(...sweep.errors);
64
+ return lines;
65
+ }
54
66
  function parseRepo(repo) {
55
67
  const slash = repo.indexOf("/");
56
68
  if (slash <= 0 || slash >= repo.length - 1) {
@@ -640,8 +652,15 @@ export function finalizeCohort(args) {
640
652
  }
641
653
  : null,
642
654
  });
655
+ const sweep = sweepResult.sweep;
643
656
  if (sweepResult.exitCode !== 0) {
644
657
  errors.push("cohort completion sweep failed.");
658
+ if (sweep !== null) {
659
+ errors.push(...sweepDetailLines(sweep));
660
+ }
661
+ else if (sweepResult.stdout.trim().length > 0) {
662
+ errors.push(sweepResult.stdout.trim());
663
+ }
645
664
  return buildResponse({
646
665
  projectRoot,
647
666
  dryRun,
@@ -649,7 +668,7 @@ export function finalizeCohort(args) {
649
668
  prNumbers,
650
669
  storyPaths,
651
670
  closingIssues: [...closingIssues],
652
- sweep: null,
671
+ sweep,
653
672
  commitSha: null,
654
673
  branch: null,
655
674
  prUrl: null,
@@ -688,7 +707,7 @@ export function finalizeCohort(args) {
688
707
  prNumbers,
689
708
  storyPaths,
690
709
  closingIssues: [...closingIssues],
691
- sweep: null,
710
+ sweep,
692
711
  commitSha,
693
712
  branch,
694
713
  prUrl,
@@ -747,6 +766,18 @@ function buildResponse(input) {
747
766
  lines.push(` - ${oneLine(path)}`);
748
767
  }
749
768
  }
769
+ if (input.sweep !== null) {
770
+ const records = [...input.sweep.stories, ...input.sweep.parents];
771
+ if (records.length > 0 || input.sweep.errors.length > 0) {
772
+ lines.push(" Sweep:");
773
+ for (const rec of records) {
774
+ lines.push(` [${rec.ok ? "ok" : "FAILED"}] ${rec.action} ${rec.path} -- ${oneLine(rec.detail)}`);
775
+ }
776
+ for (const err of input.sweep.errors) {
777
+ lines.push(` - ${oneLine(err)}`);
778
+ }
779
+ }
780
+ }
750
781
  if (input.branch !== null) {
751
782
  lines.push(` Branch: ${input.branch}`);
752
783
  }
@@ -37,6 +37,27 @@ export declare function enforceGates(resolved: readonly ResolvedStory[], project
37
37
  story: ResolvedStory;
38
38
  reason: string;
39
39
  } | null;
40
+ export declare const SWARM_LAUNCH_MANIFEST_RELPATH: readonly [".deft", "swarm-launch-manifest.json"];
41
+ export declare const SWARM_LAUNCH_OCCUPANCY_DIR: readonly [".deft", "swarm-launch-occupancy"];
42
+ export declare function swarmLaunchManifestPath(projectRoot: string): string;
43
+ export interface LaunchOccupancyRecord {
44
+ readonly allocation_plan_id: string | null;
45
+ readonly occupancy_session_id: string;
46
+ readonly story_ids: readonly string[];
47
+ readonly cohort_key: string;
48
+ }
49
+ export interface LaunchOccupancyQuery {
50
+ readonly allocationPlanId?: string | null;
51
+ readonly storyIds?: readonly string[];
52
+ }
53
+ export type LaunchOccupancyLookupReason = "ok" | "missing" | "wrong-cohort";
54
+ export declare function occupancyCohortKey(allocationPlanId: string | null | undefined, storyIds?: readonly string[]): string;
55
+ export declare function launchOccupancyRecordRelpath(cohortKey: string): string[];
56
+ export declare function persistLaunchOccupancyRecord(projectRoot: string, record: LaunchOccupancyRecord): void;
57
+ export declare function resolveLaunchOccupancySessionId(projectRoot: string, query?: LaunchOccupancyQuery): {
58
+ sessionId: string;
59
+ reason: LaunchOccupancyLookupReason;
60
+ };
40
61
  export declare function buildManifest(resolved: readonly ResolvedStory[], options: {
41
62
  projectRoot: string;
42
63
  group?: string | null;
@@ -53,6 +74,7 @@ export declare function buildManifest(resolved: readonly ResolvedStory[], option
53
74
  modelSource?: string | null;
54
75
  runtimeMode?: string | null;
55
76
  githubAuthMode?: string | null;
77
+ occupancySessionId?: string | null;
56
78
  }): Record<string, unknown>[];
57
79
  export declare function orderCohort(resolved: readonly ResolvedStory[], _projectRoot: string): ResolvedStory[];
58
80
  export interface LaunchArgs {