@deftai/directive-core 0.106.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.
Files changed (249) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/check/cached-orchestrator.js +28 -3
  3. package/dist/check/gate-lists.js +2 -0
  4. package/dist/check/named-cause.js +18 -3
  5. package/dist/codebase/provider.js +7 -1
  6. package/dist/delivery-attempt/handoff.js +1 -1
  7. package/dist/design-critique/citation-grammar.d.ts +53 -0
  8. package/dist/design-critique/citation-grammar.js +303 -0
  9. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  10. package/dist/design-critique/completed-arc-record.js +232 -0
  11. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  12. package/dist/design-critique/exclusive-chip.js +68 -0
  13. package/dist/design-critique/parent-audit.d.ts +59 -0
  14. package/dist/design-critique/parent-audit.js +121 -0
  15. package/dist/doctor/checks.d.ts +6 -0
  16. package/dist/doctor/checks.js +35 -0
  17. package/dist/freshness/bind.d.ts +32 -3
  18. package/dist/freshness/bind.js +58 -12
  19. package/dist/hooks/classify/host-session-identity.d.ts +76 -0
  20. package/dist/hooks/classify/host-session-identity.js +470 -0
  21. package/dist/hooks/classify/index.d.ts +3 -2
  22. package/dist/hooks/classify/index.js +3 -2
  23. package/dist/hooks/classify/paths.d.ts +7 -0
  24. package/dist/hooks/classify/paths.js +35 -0
  25. package/dist/hooks/classify/stdin.d.ts +5 -0
  26. package/dist/hooks/classify/stdin.js +27 -0
  27. package/dist/hooks/dest-form.d.ts +46 -0
  28. package/dist/hooks/dest-form.js +575 -0
  29. package/dist/hooks/dispatcher.d.ts +69 -2
  30. package/dist/hooks/dispatcher.js +609 -45
  31. package/dist/hooks/fixtures/cases.d.ts +16 -2
  32. package/dist/hooks/fixtures/cases.js +175 -0
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/index.js +1 -0
  35. package/dist/hooks/scope.js +3 -1
  36. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  37. package/dist/init-deposit/agent-hooks.js +8 -1
  38. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  39. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  40. package/dist/intake/clause-derivation.js +9 -3
  41. package/dist/intake/github-auth-modes-cli.js +14 -2
  42. package/dist/intake/github-auth-modes.d.ts +54 -9
  43. package/dist/intake/github-auth-modes.js +326 -123
  44. package/dist/intake/issue-ingest.js +62 -2
  45. package/dist/intake/platform-capabilities.d.ts +9 -2
  46. package/dist/intake/platform-capabilities.js +38 -9
  47. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  48. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  49. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  50. package/dist/lifecycle/completed-write-guard.js +373 -0
  51. package/dist/lifecycle/index.d.ts +1 -0
  52. package/dist/lifecycle/index.js +1 -0
  53. package/dist/literal-acceptance/capture.d.ts +16 -0
  54. package/dist/literal-acceptance/capture.js +75 -7
  55. package/dist/literal-acceptance/evaluate.js +25 -3
  56. package/dist/literal-acceptance/index.d.ts +2 -2
  57. package/dist/literal-acceptance/index.js +2 -2
  58. package/dist/literal-acceptance/run.js +9 -3
  59. package/dist/literal-acceptance/types.d.ts +6 -0
  60. package/dist/literal-acceptance/types.js +6 -0
  61. package/dist/orphan-active/evaluate.d.ts +25 -0
  62. package/dist/orphan-active/evaluate.js +193 -128
  63. package/dist/orphan-active/index.d.ts +1 -0
  64. package/dist/orphan-active/index.js +1 -0
  65. package/dist/orphan-active/issue-state.d.ts +121 -0
  66. package/dist/orphan-active/issue-state.js +278 -0
  67. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  68. package/dist/platform/cursor-managed-runtime.js +241 -0
  69. package/dist/platform/index.d.ts +1 -0
  70. package/dist/platform/index.js +1 -0
  71. package/dist/platform/platform-capabilities.d.ts +23 -1
  72. package/dist/platform/platform-capabilities.js +55 -15
  73. package/dist/policy/ceremony-dial.js +5 -9
  74. package/dist/policy/host-hooks.js +4 -9
  75. package/dist/policy/org-force-on-migration.js +5 -10
  76. package/dist/policy/plan-extensions.d.ts +14 -2
  77. package/dist/policy/plan-extensions.js +24 -3
  78. package/dist/policy/product-signal.js +5 -10
  79. package/dist/policy/require-human-merge.js +5 -9
  80. package/dist/policy/resolve.js +77 -16
  81. package/dist/policy/runtime-authority.d.ts +18 -0
  82. package/dist/policy/runtime-authority.js +11 -0
  83. package/dist/policy/value-feedback.js +9 -18
  84. package/dist/policy/write-fence.js +3 -0
  85. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  86. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  87. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  88. package/dist/pr-closeout-attestable/index.js +2 -0
  89. package/dist/pr-protected-issues/gh.d.ts +6 -2
  90. package/dist/pr-protected-issues/gh.js +7 -3
  91. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  92. package/dist/pr-wait-mergeable/cascade.js +21 -1
  93. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  94. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/wrappers.js +11 -0
  96. package/dist/preflight/evaluate.d.ts +15 -0
  97. package/dist/preflight/evaluate.js +29 -3
  98. package/dist/preflight-cache/evaluate.js +9 -3
  99. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  100. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  101. package/dist/product-first-done-gate/evaluate.js +53 -12
  102. package/dist/release/cli-drift-report.d.ts +68 -0
  103. package/dist/release/cli-drift-report.js +189 -0
  104. package/dist/release/index.d.ts +1 -0
  105. package/dist/release/index.js +1 -0
  106. package/dist/release/issue-state-fetch.d.ts +5 -3
  107. package/dist/release/issue-state-fetch.js +86 -21
  108. package/dist/release/native-steps.js +1 -0
  109. package/dist/release/pipeline.js +19 -0
  110. package/dist/release/types.d.ts +14 -0
  111. package/dist/render/constants.d.ts +4 -0
  112. package/dist/render/constants.js +11 -8
  113. package/dist/render/export-spec.js +31 -5
  114. package/dist/render/index.d.ts +1 -1
  115. package/dist/render/index.js +1 -1
  116. package/dist/render/prd-render.d.ts +4 -1
  117. package/dist/render/prd-render.js +82 -23
  118. package/dist/render/project-render.js +14 -7
  119. package/dist/render/scope-outlook.d.ts +2 -0
  120. package/dist/render/scope-outlook.js +3 -0
  121. package/dist/render/spec-render.js +2 -2
  122. package/dist/resolution/package-manager.d.ts +33 -4
  123. package/dist/resolution/package-manager.js +210 -9
  124. package/dist/review-monitor/constants.d.ts +7 -0
  125. package/dist/review-monitor/constants.js +7 -0
  126. package/dist/review-monitor/github-lease.d.ts +98 -1
  127. package/dist/review-monitor/github-lease.js +182 -1
  128. package/dist/review-monitor/lease-comment.d.ts +36 -0
  129. package/dist/review-monitor/lease-comment.js +103 -2
  130. package/dist/scm/call.js +12 -3
  131. package/dist/scm/design-critique-chip.d.ts +38 -0
  132. package/dist/scm/design-critique-chip.js +187 -0
  133. package/dist/scm/gh-rest.d.ts +9 -1
  134. package/dist/scm/gh-rest.js +103 -0
  135. package/dist/scm/index.d.ts +1 -0
  136. package/dist/scm/index.js +1 -0
  137. package/dist/scm/main.d.ts +3 -0
  138. package/dist/scm/main.js +15 -1
  139. package/dist/scm/readiness-cli.d.ts +2 -0
  140. package/dist/scm/readiness-cli.js +42 -1
  141. package/dist/scm/readiness.d.ts +4 -1
  142. package/dist/scm/readiness.js +35 -5
  143. package/dist/scope/decompose.js +2 -1
  144. package/dist/scope/delivery-evidence.d.ts +27 -1
  145. package/dist/scope/index.d.ts +1 -0
  146. package/dist/scope/index.js +1 -0
  147. package/dist/scope/lifecycle-write.d.ts +24 -0
  148. package/dist/scope/lifecycle-write.js +64 -0
  149. package/dist/scope/main.js +3 -1
  150. package/dist/scope/project-definition-sync.js +14 -7
  151. package/dist/scope/transition.js +90 -11
  152. package/dist/scope-provenance/evaluate.js +5 -1
  153. package/dist/session/git.d.ts +42 -0
  154. package/dist/session/git.js +219 -3
  155. package/dist/session/occupancy.d.ts +239 -12
  156. package/dist/session/occupancy.js +820 -27
  157. package/dist/session/ritual-sentinel.d.ts +5 -0
  158. package/dist/session/ritual-sentinel.js +5 -0
  159. package/dist/session/session-ready.d.ts +5 -1
  160. package/dist/session/session-ready.js +98 -8
  161. package/dist/session/session-start.d.ts +17 -1
  162. package/dist/session/session-start.js +49 -17
  163. package/dist/session/toolchain-preflight.d.ts +13 -3
  164. package/dist/session/toolchain-preflight.js +93 -18
  165. package/dist/session/verify-session-ritual.d.ts +33 -3
  166. package/dist/session/verify-session-ritual.js +89 -19
  167. package/dist/slice/constants.d.ts +1 -1
  168. package/dist/spec-authority/constants.d.ts +10 -2
  169. package/dist/spec-authority/constants.js +53 -8
  170. package/dist/spec-authority/resolver.d.ts +3 -0
  171. package/dist/spec-authority/resolver.js +55 -7
  172. package/dist/subprocess/max-buffer.d.ts +14 -0
  173. package/dist/subprocess/max-buffer.js +15 -0
  174. package/dist/swarm/complete-cohort.d.ts +2 -0
  175. package/dist/swarm/complete-cohort.js +21 -13
  176. package/dist/swarm/index.d.ts +1 -0
  177. package/dist/swarm/index.js +1 -0
  178. package/dist/swarm/launch-cli.js +53 -33
  179. package/dist/swarm/launch.d.ts +77 -0
  180. package/dist/swarm/launch.js +248 -38
  181. package/dist/swarm/pre-dispatch-cli.js +2 -1
  182. package/dist/swarm/pre-dispatch.js +10 -1
  183. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  184. package/dist/swarm/subagent-status-dir.js +42 -0
  185. package/dist/swarm/worktrees.d.ts +17 -3
  186. package/dist/swarm/worktrees.js +48 -4
  187. package/dist/triage/actions/index.d.ts +4 -0
  188. package/dist/triage/actions/index.js +6 -0
  189. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  190. package/dist/triage/evaluate/evaluate.js +169 -0
  191. package/dist/triage/evaluate/github.d.ts +16 -0
  192. package/dist/triage/evaluate/github.js +153 -0
  193. package/dist/triage/evaluate/index.d.ts +9 -0
  194. package/dist/triage/evaluate/index.js +8 -0
  195. package/dist/triage/evaluate/paths.d.ts +7 -0
  196. package/dist/triage/evaluate/paths.js +25 -0
  197. package/dist/triage/evaluate/sink.d.ts +5 -0
  198. package/dist/triage/evaluate/sink.js +52 -0
  199. package/dist/triage/evaluate/types.d.ts +93 -0
  200. package/dist/triage/evaluate/types.js +13 -0
  201. package/dist/triage/evaluate/validity.d.ts +8 -0
  202. package/dist/triage/evaluate/validity.js +87 -0
  203. package/dist/triage/evaluate/value.d.ts +12 -0
  204. package/dist/triage/evaluate/value.js +33 -0
  205. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  206. package/dist/triage/evaluate/wip-census.js +39 -0
  207. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  208. package/dist/triage/evaluate/worktrees.js +68 -0
  209. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  210. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  211. package/dist/triage/help/registry-data.d.ts +21 -7
  212. package/dist/triage/help/registry-data.js +53 -5
  213. package/dist/triage/index.d.ts +1 -0
  214. package/dist/triage/index.js +1 -0
  215. package/dist/triage/scope/mutations-core.d.ts +2 -6
  216. package/dist/triage/scope/mutations-core.js +23 -55
  217. package/dist/triage/scope-drift/add-ignore.js +5 -39
  218. package/dist/triage/subscribe/index.d.ts +2 -6
  219. package/dist/triage/subscribe/index.js +15 -65
  220. package/dist/triage/welcome/writers.js +15 -74
  221. package/dist/value/feedback-file.d.ts +17 -2
  222. package/dist/value/feedback-file.js +80 -6
  223. package/dist/vbrief-build/index.d.ts +2 -1
  224. package/dist/vbrief-build/index.js +2 -1
  225. package/dist/vbrief-build/parity-scenarios.js +5 -5
  226. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  227. package/dist/vbrief-build/project-definition-io.js +357 -31
  228. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  229. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  230. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  231. package/dist/vbrief-reconcile/labels.js +30 -0
  232. package/dist/vbrief-validate/conformance.d.ts +1 -0
  233. package/dist/vbrief-validate/conformance.js +85 -8
  234. package/dist/vbrief-validate/main.js +2 -0
  235. package/dist/vbrief-validate/precutover.js +5 -10
  236. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  237. package/dist/vbrief-validation/story-quality.js +8 -1
  238. package/dist/verify-ac/clauses.d.ts +62 -2
  239. package/dist/verify-ac/clauses.js +183 -111
  240. package/dist/verify-ac/index.d.ts +1 -1
  241. package/dist/verify-ac/index.js +1 -1
  242. package/dist/verify-env/node-runtime.d.ts +8 -4
  243. package/dist/verify-env/node-runtime.js +9 -6
  244. package/dist/verify-env/toolchain-check.d.ts +35 -3
  245. package/dist/verify-env/toolchain-check.js +155 -36
  246. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  247. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  248. package/dist/xbrief-migrate/agents-header.js +69 -7
  249. package/package.json +7 -3
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Release Step 3 issue-state fetch with capped rate-limit retry (#2577).
3
+ * Open-issue inventory via one paginated REST subprocess (#3752).
3
4
  */
4
5
  import { detectRateLimit } from "../cache/fetch.js";
5
- import { fetchIssueStates } from "../intake/reconcile-issues.js";
6
+ import { IssueState } from "../intake/reconcile-issues.js";
6
7
  import { call } from "../scm/call.js";
8
+ import { GhRestError, restIssueListOpenInventory } from "../scm/gh-rest.js";
7
9
  /** Max seconds to sleep before the single rate-limit retry (operator-locked cap). */
8
10
  export const MAX_RATE_LIMIT_RETRY_SLEEP_S = 120;
9
11
  const X_RATE_LIMIT_RESET_RE = /X-RateLimit-Reset:\s*(\d+)/i;
@@ -15,6 +17,71 @@ function defaultSleep(seconds) {
15
17
  const shared = new Int32Array(new SharedArrayBuffer(4));
16
18
  Atomics.wait(shared, 0, 0, ms);
17
19
  }
20
+ function scmCallToRunGhApi(scmCall, cwd) {
21
+ return (args, options) => {
22
+ const result = scmCall("github-issue", "api", args, {
23
+ timeout: options?.timeout ?? 120,
24
+ cwd,
25
+ });
26
+ return {
27
+ returncode: result.returncode,
28
+ stdout: result.stdout,
29
+ stderr: result.stderr,
30
+ };
31
+ };
32
+ }
33
+ function buildStatesFromOpenInventory(issueNumbers, openInventory) {
34
+ const openNumbers = new Set();
35
+ for (const row of openInventory) {
36
+ const n = row.number;
37
+ if (typeof n === "number" && Number.isInteger(n)) {
38
+ openNumbers.add(n);
39
+ }
40
+ }
41
+ const states = new Map();
42
+ for (const n of [...issueNumbers].sort((a, b) => a - b)) {
43
+ states.set(n, openNumbers.has(n) ? new IssueState("OPEN") : new IssueState("NOT_FOUND"));
44
+ }
45
+ return states;
46
+ }
47
+ function fetchOpenInventoryAttempt(repo, runGhApiFn) {
48
+ let sawRateLimit = false;
49
+ let rateLimitStderr = "";
50
+ const trackingFn = (args, options) => {
51
+ const result = runGhApiFn(args, options);
52
+ if (result.returncode !== 0 && detectRateLimit(result.stderr)[0]) {
53
+ sawRateLimit = true;
54
+ rateLimitStderr = result.stderr;
55
+ }
56
+ return result;
57
+ };
58
+ try {
59
+ return {
60
+ inventory: restIssueListOpenInventory(repo, { runGhApiFn: trackingFn }),
61
+ sawRateLimit,
62
+ rateLimitStderr,
63
+ failureReason: null,
64
+ };
65
+ }
66
+ catch (err) {
67
+ const stderr = err instanceof GhRestError ? err.stderr : "";
68
+ if (stderr.length > 0 && detectRateLimit(stderr)[0]) {
69
+ sawRateLimit = true;
70
+ rateLimitStderr = stderr;
71
+ }
72
+ const reason = err instanceof GhRestError
73
+ ? `open-issue inventory fetch failed: ${err.message}`
74
+ : err instanceof Error
75
+ ? err.message
76
+ : String(err);
77
+ return {
78
+ inventory: null,
79
+ sawRateLimit,
80
+ rateLimitStderr,
81
+ failureReason: reason,
82
+ };
83
+ }
84
+ }
18
85
  function parseRateLimitProbe(stdout) {
19
86
  try {
20
87
  const body = JSON.parse(stdout);
@@ -101,37 +168,34 @@ export function formatRateLimitFailureDetails(probe, rateLimitRelated) {
101
168
  return lines.join("\n");
102
169
  }
103
170
  export function fetchIssueStatesForRelease(repo, issueNumbers, options = {}) {
171
+ if (issueNumbers.size === 0) {
172
+ return { ok: true, states: new Map() };
173
+ }
174
+ process.stderr.write(`[release Step 3] checking ${issueNumbers.size} unique anchored issue(s) via open-issue inventory\n`);
104
175
  const sleepFn = options.sleep ?? defaultSleep;
105
176
  const nowFn = options.now ?? (() => Math.floor(Date.now() / 1000));
106
177
  const baseScmCall = options.scmCall ?? call;
107
178
  const cwd = options.cwd ?? undefined;
108
- let sawRateLimit = false;
109
- let lastRateLimitStderr = "";
110
- const trackingScmCall = (source, verb, args, callOptions) => {
111
- const result = baseScmCall(source, verb, args, callOptions);
112
- if (result.returncode !== 0 && detectRateLimit(result.stderr)[0]) {
113
- sawRateLimit = true;
114
- lastRateLimitStderr = result.stderr;
115
- }
116
- return result;
179
+ const runGhApiFn = scmCallToRunGhApi(baseScmCall, cwd);
180
+ const finishSuccess = (inventory) => {
181
+ process.stderr.write(`[release Step 3] open-issue inventory: ${inventory.length} open issue(s)\n`);
182
+ return { ok: true, states: buildStatesFromOpenInventory(issueNumbers, inventory) };
117
183
  };
118
- let states = fetchIssueStates(repo, issueNumbers, { ...options, scmCall: trackingScmCall });
119
- if (states !== null) {
120
- return { ok: true, states };
184
+ let attempt = fetchOpenInventoryAttempt(repo, runGhApiFn);
185
+ if (attempt.inventory !== null) {
186
+ return finishSuccess(attempt.inventory);
121
187
  }
122
- if (sawRateLimit) {
188
+ if (attempt.sawRateLimit) {
123
189
  const probe = probeGithubRateLimit(baseScmCall, cwd);
124
- const sleepS = computeRateLimitSleepSeconds(lastRateLimitStderr, probe, nowFn());
190
+ const sleepS = computeRateLimitSleepSeconds(attempt.rateLimitStderr, probe, nowFn());
125
191
  process.stderr.write(`[release Step 3] GitHub REST rate limit hit; sleeping ${sleepS}s before one retry\n`);
126
192
  sleepFn(sleepS);
127
- sawRateLimit = false;
128
- lastRateLimitStderr = "";
129
- states = fetchIssueStates(repo, issueNumbers, { ...options, scmCall: trackingScmCall });
130
- if (states !== null) {
131
- return { ok: true, states };
193
+ attempt = fetchOpenInventoryAttempt(repo, runGhApiFn);
194
+ if (attempt.inventory !== null) {
195
+ return finishSuccess(attempt.inventory);
132
196
  }
133
197
  }
134
- const rateLimitRelated = sawRateLimit || lastRateLimitStderr.length > 0;
198
+ const rateLimitRelated = attempt.sawRateLimit || attempt.rateLimitStderr.length > 0;
135
199
  if (rateLimitRelated) {
136
200
  const probe = probeGithubRateLimit(baseScmCall, cwd);
137
201
  const details = formatRateLimitFailureDetails(probe, true);
@@ -141,6 +205,7 @@ export function fetchIssueStatesForRelease(repo, issueNumbers, options = {}) {
141
205
  reason: "GitHub REST rate limit exhausted (see stderr for recovery steps)",
142
206
  };
143
207
  }
208
+ process.stderr.write(`${attempt.failureReason ?? "open-issue inventory fetch failed"}\n`);
144
209
  return { ok: false, reason: "failed to fetch issue states from gh" };
145
210
  }
146
211
  //# sourceMappingURL=issue-state-fetch.js.map
@@ -43,6 +43,7 @@ export function checkVbriefLifecycleSyncNative(projectRoot, repo) {
43
43
  anchorIssueNumbers.add(num);
44
44
  }
45
45
  }
46
+ process.stderr.write(`[release Step 3] ${anchors.length} lifecycle anchor(s), ${anchorIssueNumbers.size} unique issue number(s) to evaluate\n`);
46
47
  const fetchResult = fetchIssueStatesForRelease(repo, anchorIssueNumbers, {
47
48
  cwd: projectRoot,
48
49
  });
@@ -4,6 +4,7 @@ import { assertProjectionContained } from "../fs/projection-containment.js";
4
4
  import { readCoverageTotalsFromReport } from "../vitest-runner/coverage-debt.js";
5
5
  import { buildCoverageDebtIssueDraft, classifyStep5FailureWithFreshness, evaluateAutoHatch, formatAutoHatchBanner, parseExitCodeFromReason, reasonLooksLikeTimeout, } from "./auto-hatch.js";
6
6
  import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./changelog.js";
7
+ import { emitCliDriftReportBestEffort, shouldSkipRegistryPoll, } from "./cli-drift-report.js";
7
8
  import { assertTagPushClosedVerb } from "./closed-verb-gate.js";
8
9
  import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, RELEASE_CHECK_TIMEOUT_MINUTES, TOTAL_STEPS, VERIFY_DRAFT_INTERVAL_SECONDS, VERIFY_DRAFT_MAX_ATTEMPTS, } from "./constants.js";
9
10
  import { createCoverageDebtIssue, probeOpenCoverageDebtLedger } from "./coverage-debt-ledger.js";
@@ -472,6 +473,24 @@ export function runPipeline(config, seams = {}) {
472
473
  }
473
474
  }
474
475
  process.stderr.write(`Release v${version} pipeline complete (dry_run=${config.dryRun ? "True" : "False"}, skip_tag=${config.skipTag ? "True" : "False"}, skip_release=${config.skipRelease ? "True" : "False"}).\n`);
476
+ // #3753 — report-only. Never runs npm i -g. Never fails the cut.
477
+ // Single-shot registry probe (no wait): Step 13 returns before npm-publish.yml
478
+ // is green; Phase 7 of the release skill owns the bounded wait.
479
+ emitCliDriftReportBestEffort(version, {
480
+ skipRegistryPoll: shouldSkipRegistryPoll(config),
481
+ pollTimeoutMs: 0,
482
+ checkActiveCli: seams.checkActiveCli,
483
+ viewPackage: seams.viewWorkspacePackage
484
+ ? (name, ver) => {
485
+ const probe = seams.viewWorkspacePackage?.(name, ver);
486
+ return {
487
+ name: name,
488
+ visible: probe?.visible ?? false,
489
+ version: probe?.version ?? null,
490
+ };
491
+ }
492
+ : undefined,
493
+ });
475
494
  return EXIT_OK;
476
495
  }
477
496
  //# sourceMappingURL=pipeline.js.map
@@ -89,5 +89,19 @@ export interface ReleaseSeams {
89
89
  * Tests inject `{}` so a host env bypass cannot leak into fail-closed cases.
90
90
  */
91
91
  readonly closedVerbEnv?: EnvMap;
92
+ /**
93
+ * #3753 — override the active-CLI probe used by the local-vs-released report.
94
+ * When omitted, production calls `checkActiveCliAgainstTarget`.
95
+ */
96
+ readonly checkActiveCli?: (targetVersion: string) => import("../session/active-cli.js").ActiveCliCheckResult;
97
+ /**
98
+ * #3753 — override one `npm view <pkg>@<ver> --prefer-online` (tests).
99
+ * When omitted, production spawns npm. Never used to install.
100
+ */
101
+ readonly viewWorkspacePackage?: (name: string, version: string) => {
102
+ readonly name: string;
103
+ readonly visible: boolean;
104
+ readonly version: string | null;
105
+ };
92
106
  }
93
107
  //# sourceMappingURL=types.d.ts.map
@@ -13,8 +13,12 @@ export declare const DEFAULT_INCLUDE_SCOPES_MODE: "off";
13
13
  /** Lifecycle scope aggregation modes for rendered specs (#1566). */
14
14
  export type IncludeScopesMode = "off" | "current" | "all";
15
15
  export declare const LIFECYCLE_BUCKETS: ReadonlyArray<readonly [string, string]>;
16
+ /** Build a specification banner from the actual specification artifact path. */
17
+ export declare function buildSpecRenderBanner(specPath: string): string;
16
18
  export declare const SPEC_RENDER_BANNER: string;
17
19
  export declare const PRD_NARRATIVE_KEY_ORDER: readonly ["Overview", "ProblemStatement", "Goals", "UserStories", "Requirements", "SuccessMetrics", "Architecture"];
20
+ /** Build a PRD banner from the actual resolved authority path. */
21
+ export declare function buildPrdBanner(sourcePath: string): string;
18
22
  export declare const PRD_BANNER: string;
19
23
  export declare const PRD_GENERATED_SENTINEL = "AUTO-GENERATED by task prd:render";
20
24
  /** Max completed scopes listed under ## Completed in ROADMAP.md (#2653). */
@@ -12,6 +12,7 @@ export const VALID_STATUSES = new Set([
12
12
  ]);
13
13
  export { VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
14
14
  export { resolveInfoBlock } from "../vbrief-validate/schema.js";
15
+ import { buildGeneratedArtifactBanner } from "../spec-authority/constants.js";
15
16
  export const SPECIFICATION_NARRATIVE_KEY_ORDER = [
16
17
  "Overview",
17
18
  "ProblemStatement",
@@ -37,10 +38,11 @@ export const LIFECYCLE_BUCKETS = [
37
38
  ["active", "Active"],
38
39
  ["completed", "Completed"],
39
40
  ];
40
- export const SPEC_RENDER_BANNER = "<!-- AUTO-GENERATED by task spec:render -- DO NOT EDIT MANUALLY -->\n" +
41
- "<!-- Purpose: rendered specification -->\n" +
42
- "<!-- Source of truth: vbrief/specification.vbrief.json -->\n" +
43
- "<!-- Regenerate with: task spec:render -->\n";
41
+ /** Build a specification banner from the actual specification artifact path. */
42
+ export function buildSpecRenderBanner(specPath) {
43
+ return buildGeneratedArtifactBanner("task spec:render", "rendered specification", specPath);
44
+ }
45
+ export const SPEC_RENDER_BANNER = buildSpecRenderBanner("xbrief/specification.xbrief.json");
44
46
  export const PRD_NARRATIVE_KEY_ORDER = [
45
47
  "Overview",
46
48
  "ProblemStatement",
@@ -50,10 +52,11 @@ export const PRD_NARRATIVE_KEY_ORDER = [
50
52
  "SuccessMetrics",
51
53
  "Architecture",
52
54
  ];
53
- export const PRD_BANNER = "<!-- AUTO-GENERATED by task prd:render -- DO NOT EDIT MANUALLY -->\n" +
54
- "<!-- Purpose: rendered PRD -->\n" +
55
- "<!-- Source of truth: vbrief/specification.vbrief.json -->\n" +
56
- "<!-- Regenerate with: task prd:render -->\n";
55
+ /** Build a PRD banner from the actual resolved authority path. */
56
+ export function buildPrdBanner(sourcePath) {
57
+ return buildGeneratedArtifactBanner("task prd:render", "rendered PRD", sourcePath);
58
+ }
59
+ export const PRD_BANNER = buildPrdBanner("xbrief/specification.xbrief.json");
57
60
  export const PRD_GENERATED_SENTINEL = "AUTO-GENERATED by task prd:render";
58
61
  /** Max completed scopes listed under ## Completed in ROADMAP.md (#2653). */
59
62
  export const ROADMAP_COMPLETED_CAP = 25;
@@ -7,6 +7,32 @@ import { buildScopeOutlookSection } from "./scope-outlook.js";
7
7
  import { normalizeIncludeScopesMode, tryParseIncludeScopesMode, tryParseOnOffFlag, } from "./spec-render.js";
8
8
  import { validateSpec } from "./spec-validate.js";
9
9
  import { stripTrailingWhitespace } from "./text-utils.js";
10
+ function resolveExportScopePolicy(audience, includeScopes) {
11
+ const includeProposed = audience === "internal";
12
+ if (includeScopes === undefined) {
13
+ return {
14
+ render: includeProposed,
15
+ includeProposed,
16
+ includeCurrent: false,
17
+ includeCompleted: false,
18
+ };
19
+ }
20
+ const mode = normalizeIncludeScopesMode(includeScopes);
21
+ if (mode === "off") {
22
+ return {
23
+ render: false,
24
+ includeProposed: false,
25
+ includeCurrent: false,
26
+ includeCompleted: false,
27
+ };
28
+ }
29
+ return {
30
+ render: true,
31
+ includeProposed,
32
+ includeCurrent: true,
33
+ includeCompleted: mode === "all",
34
+ };
35
+ }
10
36
  function loadPlanTitle(path, fallback) {
11
37
  try {
12
38
  const doc = JSON.parse(readFileSync(path, "utf8"));
@@ -36,9 +62,8 @@ export function exportSpec(options = {}) {
36
62
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
37
63
  const outPath = options.outPath ?? join(projectRoot, "SPECIFICATION.md");
38
64
  const audience = options.audience ?? "stakeholder";
39
- const includeScopesMode = normalizeIncludeScopesMode(options.includeScopes);
65
+ const scopePolicy = resolveExportScopePolicy(audience, options.includeScopes);
40
66
  const includeLegacyArtifacts = options.includeLegacyArtifacts ?? false;
41
- const includeProposed = audience === "internal";
42
67
  const authority = resolveSpecAuthority(projectRoot);
43
68
  if (!authority) {
44
69
  return [false, "✗ Missing xbrief/PROJECT-DEFINITION.xbrief.json — cannot export spec."];
@@ -63,11 +88,12 @@ export function exportSpec(options = {}) {
63
88
  `# ${title}\n`,
64
89
  ...renderNarrativeSections(narratives),
65
90
  ];
66
- if (includeScopesMode !== "off") {
91
+ if (scopePolicy.render) {
67
92
  const scopeLines = buildScopeOutlookSection(authority.vbriefDir, {
68
- includeProposed,
93
+ includeProposed: scopePolicy.includeProposed,
69
94
  proposedLimit: options.proposedLimit,
70
- includeCompleted: includeScopesMode === "all",
95
+ includeCurrent: scopePolicy.includeCurrent,
96
+ includeCompleted: scopePolicy.includeCompleted,
71
97
  });
72
98
  if (scopeLines.length > 0)
73
99
  lines.push(...scopeLines);
@@ -3,7 +3,7 @@ export { type ExportAudience, type ExportSpecOptions, exportSpec, exportSpecMain
3
3
  export * as frameworkCommands from "./framework-commands.js";
4
4
  export { availableCommands, cmdCoreValidate, formatFrameworkCommand, hasCommand, main as frameworkCommandsMain, normalizeTaskSeparator, runFrameworkCommand, } from "./framework-commands.js";
5
5
  export * as prdRender from "./prd-render.js";
6
- export { main as prdRenderMain, parsePrdArgv, renderPrd } from "./prd-render.js";
6
+ export { main as prdRenderMain, parsePrdArgv, renderPrd, renderProjectPrd, } from "./prd-render.js";
7
7
  export * as projectRender from "./project-render.js";
8
8
  export { acknowledgeProjectDefinitionStaleness, buildStalenessAcknowledgement, computeStalenessFlags, flagStaleNarratives, main as projectRenderMain, parseStalenessReview, renderProjectDefinition, scanLifecycleFolders, unacknowledgedCompletedItems, } from "./project-render.js";
9
9
  export * as roadmapRender from "./roadmap-render.js";
@@ -3,7 +3,7 @@ export { exportSpec, exportSpecMain, parseExportSpecArgv, } from "./export-spec.
3
3
  export * as frameworkCommands from "./framework-commands.js";
4
4
  export { availableCommands, cmdCoreValidate, formatFrameworkCommand, hasCommand, main as frameworkCommandsMain, normalizeTaskSeparator, runFrameworkCommand, } from "./framework-commands.js";
5
5
  export * as prdRender from "./prd-render.js";
6
- export { main as prdRenderMain, parsePrdArgv, renderPrd } from "./prd-render.js";
6
+ export { main as prdRenderMain, parsePrdArgv, renderPrd, renderProjectPrd, } from "./prd-render.js";
7
7
  export * as projectRender from "./project-render.js";
8
8
  export { acknowledgeProjectDefinitionStaleness, buildStalenessAcknowledgement, computeStalenessFlags, flagStaleNarratives, main as projectRenderMain, parseStalenessReview, renderProjectDefinition, scanLifecycleFolders, unacknowledgedCompletedItems, } from "./project-render.js";
9
9
  export * as roadmapRender from "./roadmap-render.js";
@@ -1,10 +1,13 @@
1
1
  export interface RenderPrdOptions {
2
2
  readonly force?: boolean;
3
3
  }
4
- /** Read specification.vbrief.json and write PRD.md (mirrors ``scripts/prd_render.render_prd``). */
4
+ /** Read an explicit specification artifact and write PRD.md. */
5
5
  export declare function renderPrd(specPath: string, outputPath: string, options?: RenderPrdOptions): void;
6
+ /** Resolve full-spec or greenfield project authority and write a stakeholder-safe PRD. */
7
+ export declare function renderProjectPrd(projectRoot: string, outputPath: string, options?: RenderPrdOptions): void;
6
8
  export interface PrdCliArgs {
7
9
  readonly spec?: string;
10
+ readonly projectRoot?: string;
8
11
  readonly output?: string;
9
12
  readonly force?: boolean;
10
13
  }
@@ -1,5 +1,11 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { PRD_BANNER, PRD_GENERATED_SENTINEL, PRD_NARRATIVE_KEY_ORDER } from "./constants.js";
2
+ import { resolve } from "node:path";
3
+ import { resolveSpecArtifactPath } from "../layout/resolve.js";
4
+ import { generatedSourcePath } from "../spec-authority/constants.js";
5
+ import { greenfieldOverviewNonEmpty, resolveExportNarratives, } from "../spec-authority/narratives.js";
6
+ import { resolveSpecAuthority } from "../spec-authority/resolver.js";
7
+ import { buildPrdBanner, PRD_GENERATED_SENTINEL, PRD_NARRATIVE_KEY_ORDER } from "./constants.js";
8
+ import { validateSpec } from "./spec-validate.js";
3
9
  function isDeftGenerated(path) {
4
10
  if (!existsSync(path))
5
11
  return true;
@@ -12,31 +18,20 @@ function isDeftGenerated(path) {
12
18
  return false;
13
19
  }
14
20
  }
15
- /** Read specification.vbrief.json and write PRD.md (mirrors ``scripts/prd_render.render_prd``). */
16
- export function renderPrd(specPath, outputPath, options = {}) {
17
- const force = options.force ?? false;
18
- if (!existsSync(specPath)) {
19
- process.stderr.write(`Error: specification file not found: ${specPath}\n`);
20
- process.exit(1);
21
- }
21
+ function writePrd(title, narratives, sourcePath, outputPath, force) {
22
22
  if (!force && !isDeftGenerated(outputPath)) {
23
23
  process.stderr.write(`Error: refusing to overwrite non-generated PRD at ${outputPath}. ` +
24
24
  `This file lacks the "${PRD_GENERATED_SENTINEL}" banner -- it was likely hand-authored. ` +
25
25
  "Re-run with --force to overwrite, or point --output at a different file (#539).\n");
26
26
  process.exit(2);
27
27
  }
28
- const data = JSON.parse(readFileSync(specPath, "utf8"));
29
- const plan = (data.plan ?? {});
30
- const title = String(plan.title ?? "Project");
31
- const narratives = typeof plan.narratives === "object" &&
32
- plan.narratives !== null &&
33
- !Array.isArray(plan.narratives)
34
- ? plan.narratives
35
- : {};
36
28
  if (Object.keys(narratives).length === 0) {
37
- process.stderr.write(`Warning: no narratives found in ${specPath}\n`);
29
+ process.stderr.write(`Warning: no narratives found in ${sourcePath}\n`);
38
30
  }
39
- const lines = [PRD_BANNER, `# ${title} -- Product Requirements Document\n`];
31
+ const lines = [
32
+ buildPrdBanner(sourcePath),
33
+ `# ${title} -- Product Requirements Document\n`,
34
+ ];
40
35
  const renderedKeys = new Set();
41
36
  for (const key of PRD_NARRATIVE_KEY_ORDER) {
42
37
  if (key in narratives) {
@@ -52,16 +47,80 @@ export function renderPrd(specPath, outputPath, options = {}) {
52
47
  }
53
48
  }
54
49
  lines.push("---\n" +
55
- "*This document is auto-generated from the specification artifact " +
56
- "(e.g. `xbrief/specification.xbrief.json`) via `task prd:render`. Do not edit directly.*\n");
50
+ `*This document is auto-generated from \`${generatedSourcePath(sourcePath)}\` ` +
51
+ "via `task prd:render`. Do not edit directly.*\n");
57
52
  writeFileSync(outputPath, lines.join("\n"), "utf8");
58
53
  process.stdout.write(`PRD.md written to ${outputPath}\n`);
59
54
  }
55
+ function loadTitleAndNarratives(sourcePath) {
56
+ const payload = JSON.parse(readFileSync(sourcePath, "utf8"));
57
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
58
+ process.stderr.write(`Error: ${sourcePath} root must be a JSON object\n`);
59
+ process.exit(1);
60
+ }
61
+ const data = payload;
62
+ const plan = (data.plan ?? {});
63
+ const narratives = typeof plan.narratives === "object" &&
64
+ plan.narratives !== null &&
65
+ !Array.isArray(plan.narratives)
66
+ ? plan.narratives
67
+ : {};
68
+ return { title: String(plan.title ?? "Project"), narratives };
69
+ }
70
+ /** Read an explicit specification artifact and write PRD.md. */
71
+ export function renderPrd(specPath, outputPath, options = {}) {
72
+ if (!existsSync(specPath)) {
73
+ process.stderr.write(`Error: specification file not found: ${specPath}\n`);
74
+ process.exit(1);
75
+ }
76
+ const { title, narratives } = loadTitleAndNarratives(specPath);
77
+ writePrd(title, narratives, specPath, outputPath, options.force ?? false);
78
+ }
79
+ /** Resolve full-spec or greenfield project authority and write a stakeholder-safe PRD. */
80
+ export function renderProjectPrd(projectRoot, outputPath, options = {}) {
81
+ const root = resolve(projectRoot);
82
+ const authority = resolveSpecAuthority(root);
83
+ if (!authority) {
84
+ try {
85
+ const compatibilitySpecPath = resolveSpecArtifactPath(root);
86
+ if (existsSync(compatibilitySpecPath)) {
87
+ renderPrd(compatibilitySpecPath, outputPath, options);
88
+ return;
89
+ }
90
+ }
91
+ catch {
92
+ // The stable error below covers projects without a resolvable lifecycle layout.
93
+ }
94
+ process.stderr.write("Error: xbrief/PROJECT-DEFINITION.xbrief.json not found; PRD authority is unavailable.\n");
95
+ process.exit(1);
96
+ }
97
+ if (authority.kind === "full-spec" && authority.specPath) {
98
+ const [ok, message] = validateSpec(authority.specPath);
99
+ if (!ok) {
100
+ process.stderr.write(`${message}\n`);
101
+ process.exit(1);
102
+ }
103
+ }
104
+ else if (!greenfieldOverviewNonEmpty(authority)) {
105
+ process.stderr.write("Error: PROJECT-DEFINITION.xbrief.json Overview narrative is empty; cannot render PRD.\n");
106
+ process.exit(1);
107
+ }
108
+ const { title } = loadTitleAndNarratives(authority.sourcePath);
109
+ writePrd(title, resolveExportNarratives(authority), authority.sourcePath, outputPath, options.force ?? false);
110
+ }
60
111
  /** CLI entry (mirrors ``scripts/prd_render.main``). */
61
112
  export function main(args = {}) {
62
- renderPrd(args.spec ?? "vbrief/specification.vbrief.json", args.output ?? "PRD.md", {
63
- force: args.force ?? false,
64
- });
113
+ const outputPath = args.output ?? "PRD.md";
114
+ const options = { force: args.force ?? false };
115
+ if (args.spec !== undefined) {
116
+ renderPrd(args.spec, outputPath, options);
117
+ }
118
+ else if (args.projectRoot !== undefined) {
119
+ renderProjectPrd(args.projectRoot, outputPath, options);
120
+ }
121
+ else {
122
+ renderPrd("vbrief/specification.vbrief.json", outputPath, options);
123
+ }
65
124
  }
66
125
  export function parsePrdArgv(argv) {
67
126
  const out = {};
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import { hasArtifactSuffix, resolveLayoutRootOrCanonical, stripArtifactSuffix, } from "../layout/resolve.js";
4
4
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
5
- import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
5
+ import { withProjectDefinitionMutation } from "../vbrief-build/project-definition-mutation.js";
6
6
  import { deriveRegistryItemStatus, registryMetadataReferencesFromScope, } from "../vbrief-validate/registry-status.js";
7
7
  import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
8
8
  import { PROJECT_LIFECYCLE_FOLDERS, SKELETON_NARRATIVES } from "./constants.js";
@@ -192,11 +192,15 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
192
192
  // Serialise the whole read-modify-write of PROJECT-DEFINITION under the shared
193
193
  // mutation lock so a concurrent policy/triage mutator cannot be clobbered by the
194
194
  // materialised items/metadata write (or vice versa) (#1260).
195
- return projectDefinitionMutationLock(resolve(vbriefDir, ".."), () => {
195
+ const projectRoot = resolve(vbriefDir, "..");
196
+ return withProjectDefinitionMutation(projectRoot, (mutation) => {
196
197
  const nowDate = options.now ?? new Date();
197
198
  const now = nowDate.toISOString().replace(/\.\d{3}Z$/, "Z");
198
199
  const layout = resolveProjectDefinitionLayout(vbriefDir);
199
- const projectDefPath = join(vbriefDir, layout.filename);
200
+ // Read and write the artifact the lock captured, not a second independent
201
+ // resolution of the same file (#3796). Resolving it again here is how a
202
+ // render could lock one identity and materialise items into another.
203
+ const projectDefPath = mutation.artifactPath;
200
204
  const items = scanLifecycleFolders(vbriefDir);
201
205
  const createdNew = !existsSync(projectDefPath);
202
206
  let projectDef;
@@ -242,7 +246,7 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
242
246
  mkdirSync(vbriefDir, { recursive: true });
243
247
  // Atomic temp+rename write under the lock so external readers never observe
244
248
  // a partially-written PROJECT-DEFINITION (#1260).
245
- atomicWriteProjectDefinition(projectDefPath, projectDef);
249
+ mutation.persist(projectDef);
246
250
  const itemCount = items.length;
247
251
  const planMeta = (projectDef.plan?.metadata ?? {});
248
252
  const flagCount = Array.isArray(planMeta.staleness_flags) ? planMeta.staleness_flags.length : 0;
@@ -262,11 +266,14 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
262
266
  export function acknowledgeProjectDefinitionStaleness(vbriefDir, options = {}) {
263
267
  // Serialise the read-modify-write of PROJECT-DEFINITION under the shared
264
268
  // mutation lock so a concurrent policy/triage mutator is not clobbered (#1260).
265
- return projectDefinitionMutationLock(resolve(vbriefDir, ".."), () => {
269
+ const projectRoot = resolve(vbriefDir, "..");
270
+ return withProjectDefinitionMutation(projectRoot, (mutation) => {
266
271
  const nowDate = options.now ?? new Date();
267
272
  const now = isoTimestamp(nowDate);
268
273
  const layout = resolveProjectDefinitionLayout(vbriefDir);
269
- const projectDefPath = join(vbriefDir, layout.filename);
274
+ // Read and write the artifact the lock captured (#3796) -- see the note in
275
+ // `renderProjectDefinition`.
276
+ const projectDefPath = mutation.artifactPath;
270
277
  if (!existsSync(projectDefPath)) {
271
278
  return [false, `✗ ${projectDefPath} not found — run project:render first`];
272
279
  }
@@ -309,7 +316,7 @@ export function acknowledgeProjectDefinitionStaleness(vbriefDir, options = {}) {
309
316
  projectDef.plan = plan;
310
317
  // Atomic temp+rename write under the lock so external readers never observe
311
318
  // a partially-written PROJECT-DEFINITION (#1260).
312
- atomicWriteProjectDefinition(projectDefPath, projectDef);
319
+ mutation.persist(projectDef);
313
320
  const ackCount = completedItems.length;
314
321
  return [
315
322
  true,
@@ -1,6 +1,8 @@
1
1
  export interface ScopeOutlookOptions {
2
2
  readonly includeProposed?: boolean;
3
3
  readonly proposedLimit?: number;
4
+ /** When false, omit pending and active lifecycle buckets. Default true. */
5
+ readonly includeCurrent?: boolean;
4
6
  /**
5
7
  * When false, omit the completed lifecycle bucket (#1566 compact render).
6
8
  * Default true so callers that opt into full aggregation keep prior behavior.
@@ -203,6 +203,7 @@ function filterProposed(vb) {
203
203
  export function buildScopeOutlookSection(vbriefDir, options = {}) {
204
204
  const includeProposed = options.includeProposed ?? false;
205
205
  const proposedLimit = options.proposedLimit ?? 0;
206
+ const includeCurrent = options.includeCurrent ?? true;
206
207
  const includeCompleted = options.includeCompleted ?? true;
207
208
  const buckets = [];
208
209
  if (includeProposed) {
@@ -221,6 +222,8 @@ export function buildScopeOutlookSection(vbriefDir, options = {}) {
221
222
  }
222
223
  }
223
224
  for (const [folder, heading, filter] of COMMITTED_BUCKETS) {
225
+ if ((folder === "pending" || folder === "active") && !includeCurrent)
226
+ continue;
224
227
  if (folder === "completed" && !includeCompleted)
225
228
  continue;
226
229
  let scopes = loadScopeVbriefs(join(vbriefDir, folder));
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { DEFAULT_INCLUDE_SCOPES_MODE, LEGACY_ARTIFACTS_NARRATIVE_KEY, RENDERABLE_SPEC_STATUSES, SPEC_RENDER_BANNER, SPECIFICATION_NARRATIVE_KEY_ORDER, } from "./constants.js";
3
+ import { buildSpecRenderBanner, DEFAULT_INCLUDE_SCOPES_MODE, LEGACY_ARTIFACTS_NARRATIVE_KEY, RENDERABLE_SPEC_STATUSES, SPECIFICATION_NARRATIVE_KEY_ORDER, } from "./constants.js";
4
4
  import { buildScopeOutlookSection } from "./scope-outlook.js";
5
5
  import { validateSpec } from "./spec-validate.js";
6
6
  import { stripTrailingWhitespace } from "./text-utils.js";
@@ -100,7 +100,7 @@ export function renderSpec(specPath, outPath, options = {}) {
100
100
  " Have the user review and set status to one of the renderable statuses before rendering.",
101
101
  ];
102
102
  }
103
- const lines = [SPEC_RENDER_BANNER];
103
+ const lines = [buildSpecRenderBanner(specPath)];
104
104
  let title = "Specification";
105
105
  if (typeof plan === "object" && plan !== null && !Array.isArray(plan)) {
106
106
  title = String(plan.title ?? "Specification");