@deftai/directive-core 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 (271) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/cache/operations.js +1 -1
  3. package/dist/check/cached-orchestrator.js +28 -3
  4. package/dist/check/gate-lists.d.ts +14 -0
  5. package/dist/check/gate-lists.js +26 -3
  6. package/dist/check/named-cause.js +18 -3
  7. package/dist/check/session-completed-ac.d.ts +1 -1
  8. package/dist/check/session-completed-ac.js +1 -1
  9. package/dist/codebase/provider.js +7 -1
  10. package/dist/consumer-check-contract/evaluate.d.ts +47 -0
  11. package/dist/consumer-check-contract/evaluate.js +181 -15
  12. package/dist/delivery-attempt/evaluate.d.ts +9 -1
  13. package/dist/delivery-attempt/evaluate.js +69 -0
  14. package/dist/delivery-attempt/handoff.js +1 -1
  15. package/dist/delivery-attempt/index.d.ts +1 -1
  16. package/dist/delivery-attempt/index.js +1 -1
  17. package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
  18. package/dist/deposit/live-procedure-exclusions.js +110 -0
  19. package/dist/deposit/live-procedure-targets.d.ts +45 -0
  20. package/dist/deposit/live-procedure-targets.js +274 -0
  21. package/dist/deposit/python-free.d.ts +6 -0
  22. package/dist/deposit/python-free.js +15 -0
  23. package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
  24. package/dist/deposit/rewrite-deposit-links.js +148 -0
  25. package/dist/deposit/run-stage-content-pack.d.ts +2 -0
  26. package/dist/deposit/run-stage-content-pack.js +3 -0
  27. package/dist/deposit/stage-content-pack.d.ts +17 -0
  28. package/dist/deposit/stage-content-pack.js +91 -0
  29. package/dist/design-critique/citation-grammar.d.ts +60 -0
  30. package/dist/design-critique/citation-grammar.js +303 -0
  31. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  32. package/dist/design-critique/completed-arc-record.js +277 -0
  33. package/dist/freshness/bind.d.ts +32 -3
  34. package/dist/freshness/bind.js +58 -12
  35. package/dist/hooks/classify/host-session-identity.d.ts +89 -0
  36. package/dist/hooks/classify/host-session-identity.js +467 -0
  37. package/dist/hooks/classify/index.d.ts +3 -2
  38. package/dist/hooks/classify/index.js +3 -2
  39. package/dist/hooks/classify/paths.d.ts +9 -0
  40. package/dist/hooks/classify/paths.js +39 -0
  41. package/dist/hooks/classify/stdin.d.ts +5 -0
  42. package/dist/hooks/classify/stdin.js +94 -1
  43. package/dist/hooks/dest-form.d.ts +20 -1
  44. package/dist/hooks/dest-form.js +158 -21
  45. package/dist/hooks/dispatcher.d.ts +58 -3
  46. package/dist/hooks/dispatcher.js +762 -73
  47. package/dist/hooks/fixtures/cases.d.ts +22 -2
  48. package/dist/hooks/fixtures/cases.js +228 -0
  49. package/dist/hooks/git-destructive-log.d.ts +32 -0
  50. package/dist/hooks/git-destructive-log.js +46 -0
  51. package/dist/hooks/index.d.ts +2 -0
  52. package/dist/hooks/index.js +2 -0
  53. package/dist/hooks/scope.js +3 -1
  54. package/dist/hooks/shell-write-targets.d.ts +10 -0
  55. package/dist/hooks/shell-write-targets.js +274 -0
  56. package/dist/hooks/tools.d.ts +20 -2
  57. package/dist/hooks/tools.js +30 -1
  58. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  59. package/dist/init-deposit/agent-hooks.js +8 -1
  60. package/dist/init-deposit/gitignore.d.ts +7 -0
  61. package/dist/init-deposit/gitignore.js +24 -0
  62. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  63. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  64. package/dist/init-deposit/init-deposit.js +3 -0
  65. package/dist/init-deposit/refresh.js +7 -0
  66. package/dist/init-deposit/runtime-writers.d.ts +14 -0
  67. package/dist/init-deposit/runtime-writers.js +33 -0
  68. package/dist/intake/clause-derivation.js +9 -3
  69. package/dist/intake/issue-ingest.d.ts +6 -1
  70. package/dist/intake/issue-ingest.js +83 -4
  71. package/dist/intake/platform-capabilities.d.ts +9 -2
  72. package/dist/intake/platform-capabilities.js +38 -9
  73. package/dist/intake/reconcile-issues.js +13 -13
  74. package/dist/lifecycle/brief-envelope.d.ts +25 -0
  75. package/dist/lifecycle/brief-envelope.js +42 -0
  76. package/dist/lifecycle/index.d.ts +1 -0
  77. package/dist/lifecycle/index.js +1 -0
  78. package/dist/literal-acceptance/evaluate.js +14 -5
  79. package/dist/literal-acceptance/index.d.ts +1 -1
  80. package/dist/literal-acceptance/index.js +1 -1
  81. package/dist/literal-acceptance/run.d.ts +2 -0
  82. package/dist/literal-acceptance/run.js +19 -1
  83. package/dist/orphan-active/candidate-scope.d.ts +53 -0
  84. package/dist/orphan-active/candidate-scope.js +157 -0
  85. package/dist/orphan-active/evaluate.d.ts +52 -0
  86. package/dist/orphan-active/evaluate.js +250 -128
  87. package/dist/orphan-active/index.d.ts +2 -0
  88. package/dist/orphan-active/index.js +2 -0
  89. package/dist/orphan-active/issue-state.d.ts +121 -0
  90. package/dist/orphan-active/issue-state.js +278 -0
  91. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  92. package/dist/platform/cursor-managed-runtime.js +241 -0
  93. package/dist/platform/index.d.ts +1 -0
  94. package/dist/platform/index.js +1 -0
  95. package/dist/platform/platform-capabilities.d.ts +23 -1
  96. package/dist/platform/platform-capabilities.js +55 -15
  97. package/dist/policy/ceremony-dial.js +5 -9
  98. package/dist/policy/host-hooks.js +4 -9
  99. package/dist/policy/merge-approval-head.js +7 -6
  100. package/dist/policy/org-force-on-migration.js +5 -10
  101. package/dist/policy/plan-extensions.d.ts +14 -2
  102. package/dist/policy/plan-extensions.js +24 -3
  103. package/dist/policy/product-signal.js +5 -10
  104. package/dist/policy/require-human-merge.js +5 -9
  105. package/dist/policy/resolve.js +77 -16
  106. package/dist/policy/value-feedback.js +9 -18
  107. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  108. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  109. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  110. package/dist/pr-closeout-attestable/index.js +2 -0
  111. package/dist/pr-closing-keywords/gh.js +32 -9
  112. package/dist/pr-closing-keywords/main.d.ts +1 -0
  113. package/dist/pr-closing-keywords/main.js +68 -3
  114. package/dist/pr-closing-keywords/types.d.ts +2 -0
  115. package/dist/pr-merge-readiness/gh.js +32 -9
  116. package/dist/pr-protected-issues/gh.d.ts +6 -2
  117. package/dist/pr-protected-issues/gh.js +42 -11
  118. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  119. package/dist/pr-wait-mergeable/cascade.js +21 -1
  120. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  121. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  122. package/dist/pr-wait-mergeable/wrappers.js +14 -3
  123. package/dist/preflight/evaluate.d.ts +15 -0
  124. package/dist/preflight/evaluate.js +29 -3
  125. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  126. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  127. package/dist/product-first-done-gate/evaluate.js +23 -9
  128. package/dist/product-first-done-gate/types.js +2 -0
  129. package/dist/release/cli-drift-report.d.ts +68 -0
  130. package/dist/release/cli-drift-report.js +189 -0
  131. package/dist/release/consumer-hard-stops.d.ts +48 -0
  132. package/dist/release/consumer-hard-stops.js +140 -0
  133. package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
  134. package/dist/release/consumer-readiness-disclosure.js +51 -0
  135. package/dist/release/index.d.ts +3 -0
  136. package/dist/release/index.js +3 -0
  137. package/dist/release/issue-state-fetch.d.ts +5 -3
  138. package/dist/release/issue-state-fetch.js +86 -21
  139. package/dist/release/native-steps.js +1 -0
  140. package/dist/release/pipeline.js +74 -0
  141. package/dist/release/run-consumer-readiness.d.ts +15 -0
  142. package/dist/release/run-consumer-readiness.js +24 -0
  143. package/dist/release/types.d.ts +20 -0
  144. package/dist/render/constants.d.ts +4 -0
  145. package/dist/render/constants.js +11 -8
  146. package/dist/render/export-spec.js +31 -5
  147. package/dist/render/index.d.ts +1 -1
  148. package/dist/render/index.js +1 -1
  149. package/dist/render/prd-render.d.ts +4 -1
  150. package/dist/render/prd-render.js +82 -23
  151. package/dist/render/project-render.js +14 -7
  152. package/dist/render/scope-outlook.d.ts +2 -0
  153. package/dist/render/scope-outlook.js +3 -0
  154. package/dist/render/spec-render.js +2 -2
  155. package/dist/resolution/package-manager.d.ts +33 -4
  156. package/dist/resolution/package-manager.js +210 -9
  157. package/dist/review-monitor/constants.d.ts +7 -0
  158. package/dist/review-monitor/constants.js +7 -0
  159. package/dist/review-monitor/github-lease.d.ts +98 -1
  160. package/dist/review-monitor/github-lease.js +186 -4
  161. package/dist/review-monitor/lease-comment.d.ts +36 -0
  162. package/dist/review-monitor/lease-comment.js +103 -2
  163. package/dist/run-summary/types.d.ts +2 -2
  164. package/dist/scm/build-command.d.ts +2 -2
  165. package/dist/scm/build-command.js +2 -2
  166. package/dist/scm/call-shape.d.ts +25 -0
  167. package/dist/scm/call-shape.js +59 -0
  168. package/dist/scm/call.d.ts +7 -4
  169. package/dist/scm/call.js +48 -9
  170. package/dist/scm/design-critique-chip.d.ts +1 -0
  171. package/dist/scm/design-critique-chip.js +22 -6
  172. package/dist/scm/gh-rest.d.ts +34 -13
  173. package/dist/scm/gh-rest.js +168 -15
  174. package/dist/scm/index.d.ts +2 -0
  175. package/dist/scm/index.js +2 -0
  176. package/dist/scm/readiness.d.ts +2 -0
  177. package/dist/scm/readiness.js +28 -4
  178. package/dist/scm/spawn-status.d.ts +33 -0
  179. package/dist/scm/spawn-status.js +53 -0
  180. package/dist/scope/acceptance-evidence.d.ts +1 -1
  181. package/dist/scope/project-definition-sync.js +14 -7
  182. package/dist/scope/transition.js +4 -14
  183. package/dist/session/ac-pass-banking.d.ts +2 -2
  184. package/dist/session/ac-pass-banking.js +2 -2
  185. package/dist/session/git.d.ts +24 -0
  186. package/dist/session/git.js +138 -2
  187. package/dist/session/host-session-owner.d.ts +53 -0
  188. package/dist/session/host-session-owner.js +89 -0
  189. package/dist/session/index.d.ts +1 -0
  190. package/dist/session/index.js +1 -0
  191. package/dist/session/occupancy.d.ts +259 -13
  192. package/dist/session/occupancy.js +877 -33
  193. package/dist/session/ritual-sentinel.d.ts +5 -0
  194. package/dist/session/ritual-sentinel.js +5 -0
  195. package/dist/session/session-ready.d.ts +5 -1
  196. package/dist/session/session-ready.js +98 -8
  197. package/dist/session/session-start.d.ts +5 -1
  198. package/dist/session/session-start.js +32 -17
  199. package/dist/session/toolchain-preflight.d.ts +13 -3
  200. package/dist/session/toolchain-preflight.js +93 -18
  201. package/dist/session/verify-ac-session-cache.d.ts +2 -2
  202. package/dist/session/verify-ac-session-cache.js +2 -2
  203. package/dist/session/verify-session-ritual.d.ts +11 -0
  204. package/dist/session/verify-session-ritual.js +60 -15
  205. package/dist/slice/constants.d.ts +1 -1
  206. package/dist/spec-authority/constants.d.ts +10 -2
  207. package/dist/spec-authority/constants.js +53 -8
  208. package/dist/spec-authority/resolver.d.ts +3 -0
  209. package/dist/spec-authority/resolver.js +55 -7
  210. package/dist/subprocess/max-buffer.d.ts +14 -0
  211. package/dist/subprocess/max-buffer.js +15 -0
  212. package/dist/swarm/complete-cohort.d.ts +2 -0
  213. package/dist/swarm/complete-cohort.js +21 -13
  214. package/dist/swarm/index.d.ts +1 -0
  215. package/dist/swarm/index.js +1 -0
  216. package/dist/swarm/launch-cli.js +53 -33
  217. package/dist/swarm/launch.d.ts +8 -0
  218. package/dist/swarm/launch.js +63 -43
  219. package/dist/swarm/pre-dispatch-cli.js +2 -1
  220. package/dist/swarm/pre-dispatch.js +10 -1
  221. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  222. package/dist/swarm/subagent-status-dir.js +42 -0
  223. package/dist/swarm/worktrees.d.ts +17 -3
  224. package/dist/swarm/worktrees.js +48 -4
  225. package/dist/triage/evaluate/worktrees.js +153 -6
  226. package/dist/triage/scope/mutations-core.d.ts +2 -6
  227. package/dist/triage/scope/mutations-core.js +23 -55
  228. package/dist/triage/scope-drift/add-ignore.js +5 -39
  229. package/dist/triage/subscribe/index.d.ts +2 -6
  230. package/dist/triage/subscribe/index.js +15 -65
  231. package/dist/triage/welcome/writers.js +15 -74
  232. package/dist/umbrella-current-shape/index.d.ts +51 -3
  233. package/dist/umbrella-current-shape/index.js +106 -18
  234. package/dist/validate-content/deposit-required.d.ts +39 -0
  235. package/dist/validate-content/deposit-required.js +147 -0
  236. package/dist/validate-content/index.d.ts +1 -0
  237. package/dist/validate-content/index.js +1 -0
  238. package/dist/validate-content/validate-links.d.ts +2 -3
  239. package/dist/validate-content/validate-links.js +29 -2
  240. package/dist/vbrief-activate/activate.d.ts +7 -2
  241. package/dist/vbrief-activate/activate.js +26 -13
  242. package/dist/vbrief-build/index.d.ts +2 -1
  243. package/dist/vbrief-build/index.js +2 -1
  244. package/dist/vbrief-build/parity-scenarios.js +5 -5
  245. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  246. package/dist/vbrief-build/project-definition-io.js +357 -31
  247. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  248. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  249. package/dist/vbrief-validate/conformance.d.ts +1 -0
  250. package/dist/vbrief-validate/conformance.js +85 -8
  251. package/dist/vbrief-validate/main.js +2 -0
  252. package/dist/vbrief-validate/precutover.js +5 -10
  253. package/dist/verify-ac/clauses.d.ts +62 -2
  254. package/dist/verify-ac/clauses.js +183 -111
  255. package/dist/verify-ac/evaluate.d.ts +9 -0
  256. package/dist/verify-ac/evaluate.js +32 -9
  257. package/dist/verify-ac/index.d.ts +1 -1
  258. package/dist/verify-ac/index.js +1 -1
  259. package/dist/verify-env/node-runtime.d.ts +8 -4
  260. package/dist/verify-env/node-runtime.js +9 -6
  261. package/dist/verify-env/toolchain-check.d.ts +35 -3
  262. package/dist/verify-env/toolchain-check.js +155 -36
  263. package/dist/verify-source/deposit-closure.d.ts +23 -0
  264. package/dist/verify-source/deposit-closure.js +162 -0
  265. package/dist/verify-source/index.d.ts +2 -0
  266. package/dist/verify-source/index.js +2 -0
  267. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  268. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  269. package/dist/verify-source/semantic-single-source.d.ts +36 -0
  270. package/dist/verify-source/semantic-single-source.js +349 -0
  271. package/package.json +7 -3
@@ -1,17 +1,21 @@
1
1
  import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { assertProjectionContained } from "../fs/projection-containment.js";
4
+ import { restIssueListOpenInventory } from "../scm/gh-rest.js";
4
5
  import { readCoverageTotalsFromReport } from "../vitest-runner/coverage-debt.js";
5
6
  import { buildCoverageDebtIssueDraft, classifyStep5FailureWithFreshness, evaluateAutoHatch, formatAutoHatchBanner, parseExitCodeFromReason, reasonLooksLikeTimeout, } from "./auto-hatch.js";
6
7
  import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./changelog.js";
8
+ import { emitCliDriftReportBestEffort, shouldSkipRegistryPoll, } from "./cli-drift-report.js";
7
9
  import { assertTagPushClosedVerb } from "./closed-verb-gate.js";
8
10
  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";
11
+ import { formatConsumerReadinessDisclosure } from "./consumer-readiness-disclosure.js";
9
12
  import { createCoverageDebtIssue, probeOpenCoverageDebtLedger } from "./coverage-debt-ledger.js";
10
13
  import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
11
14
  import { checkGitClean, commitReleaseArtifacts, createTag, currentBranch, pushRelease, releaseCommitSubject, runGit, } from "./git.js";
12
15
  import { checkVbriefLifecycleSyncNative, refreshRoadmapNative, runBuildNative, } from "./native-steps.js";
13
16
  import { todayIso } from "./paths.js";
14
17
  import { runReleaseCheck } from "./preflight.js";
18
+ import { evaluateReleaseConsumerReadiness, issuesFromInventory } from "./run-consumer-readiness.js";
15
19
  import { formatSkipCiIncidentWarning } from "./skip-ci-incident.js";
16
20
  import { evaluateSuiteStamp, writeSuiteStamp } from "./suite-stamp.js";
17
21
  import { isPrereleaseTag } from "./version.js";
@@ -289,6 +293,58 @@ export function runPipeline(config, seams = {}) {
289
293
  }
290
294
  }
291
295
  }
296
+ // #3900: check 4 fail-closed (title/label census); checks 5-6 disclosure never block.
297
+ {
298
+ const crLabel = "Consumer-readiness census";
299
+ let changelogText = "";
300
+ try {
301
+ changelogText = readFile(changelogPath);
302
+ }
303
+ catch {
304
+ changelogText = "";
305
+ }
306
+ const disclosure = formatConsumerReadinessDisclosure(changelogText);
307
+ if (config.dryRun) {
308
+ emit(5, crLabel, "DRYRUN (would enumerate open privileged hard-stop labels; disclosure does not block)");
309
+ process.stderr.write(disclosure.text + "\n");
310
+ }
311
+ else if (seams.consumerHardStops) {
312
+ const [ok, msg] = seams.consumerHardStops(config.repo, projectRoot);
313
+ if (!ok) {
314
+ emit(5, crLabel, "FAIL (" + msg + ")");
315
+ process.stderr.write(disclosure.text + "\n");
316
+ return EXIT_VIOLATION;
317
+ }
318
+ emit(5, crLabel, "OK (" + msg + ")");
319
+ process.stderr.write(disclosure.text + "\n");
320
+ }
321
+ else if (seams.runCi !== undefined || seams.spawnText !== undefined) {
322
+ emit(5, crLabel, "SKIP (injected seam; run task verify:consumer-hard-stops at a real cut)");
323
+ process.stderr.write(disclosure.text + "\n");
324
+ }
325
+ else {
326
+ try {
327
+ const rows = restIssueListOpenInventory(config.repo);
328
+ const result = evaluateReleaseConsumerReadiness({
329
+ changelogText,
330
+ issues: issuesFromInventory(rows),
331
+ });
332
+ process.stderr.write(result.disclosure.text + "\n");
333
+ if (result.hardStops.code !== 0) {
334
+ emit(5, crLabel, "FAIL (" + result.hardStops.message + ")");
335
+ return result.hardStops.code === 2 ? EXIT_CONFIG_ERROR : EXIT_VIOLATION;
336
+ }
337
+ emit(5, crLabel, "OK (" + result.hardStops.message + ")");
338
+ }
339
+ catch (err) {
340
+ const reason = err instanceof Error ? err.message : String(err);
341
+ emit(5, crLabel, "FAIL (could not list open issues. Recovery: check gh auth and REST quota. " +
342
+ reason +
343
+ ")");
344
+ return EXIT_CONFIG_ERROR;
345
+ }
346
+ }
347
+ }
292
348
  // Step 6: CHANGELOG promotion.
293
349
  label = "CHANGELOG promotion";
294
350
  if (!fileExists(changelogPath)) {
@@ -472,6 +528,24 @@ export function runPipeline(config, seams = {}) {
472
528
  }
473
529
  }
474
530
  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`);
531
+ // #3753 — report-only. Never runs npm i -g. Never fails the cut.
532
+ // Single-shot registry probe (no wait): Step 13 returns before npm-publish.yml
533
+ // is green; Phase 7 of the release skill owns the bounded wait.
534
+ emitCliDriftReportBestEffort(version, {
535
+ skipRegistryPoll: shouldSkipRegistryPoll(config),
536
+ pollTimeoutMs: 0,
537
+ checkActiveCli: seams.checkActiveCli,
538
+ viewPackage: seams.viewWorkspacePackage
539
+ ? (name, ver) => {
540
+ const probe = seams.viewWorkspacePackage?.(name, ver);
541
+ return {
542
+ name: name,
543
+ visible: probe?.visible ?? false,
544
+ version: probe?.version ?? null,
545
+ };
546
+ }
547
+ : undefined,
548
+ });
475
549
  return EXIT_OK;
476
550
  }
477
551
  //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Release pre-flight consumer-readiness: check 4 fail-closed, checks 5-6 disclosure (#3900).
3
+ */
4
+ import { evaluateConsumerHardStopCensus, type HardStopIssue } from "./consumer-hard-stops.js";
5
+ import { formatConsumerReadinessDisclosure } from "./consumer-readiness-disclosure.js";
6
+ export interface ConsumerReadinessInput {
7
+ readonly changelogText: string;
8
+ readonly issues: readonly HardStopIssue[];
9
+ }
10
+ export declare function evaluateReleaseConsumerReadiness(input: ConsumerReadinessInput): {
11
+ readonly hardStops: ReturnType<typeof evaluateConsumerHardStopCensus>;
12
+ readonly disclosure: ReturnType<typeof formatConsumerReadinessDisclosure>;
13
+ };
14
+ export declare function issuesFromInventory(rows: readonly Record<string, unknown>[]): HardStopIssue[];
15
+ //# sourceMappingURL=run-consumer-readiness.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Release pre-flight consumer-readiness: check 4 fail-closed, checks 5-6 disclosure (#3900).
3
+ */
4
+ import { evaluateConsumerHardStopCensus, issueFromInventoryRow, parseClosesSet, } from "./consumer-hard-stops.js";
5
+ import { formatConsumerReadinessDisclosure } from "./consumer-readiness-disclosure.js";
6
+ export function evaluateReleaseConsumerReadiness(input) {
7
+ return {
8
+ hardStops: evaluateConsumerHardStopCensus({
9
+ issues: input.issues,
10
+ closesSet: parseClosesSet(input.changelogText),
11
+ }),
12
+ disclosure: formatConsumerReadinessDisclosure(input.changelogText),
13
+ };
14
+ }
15
+ export function issuesFromInventory(rows) {
16
+ const issues = [];
17
+ for (const row of rows) {
18
+ const issue = issueFromInventoryRow(row);
19
+ if (issue !== null)
20
+ issues.push(issue);
21
+ }
22
+ return issues;
23
+ }
24
+ //# sourceMappingURL=run-consumer-readiness.js.map
@@ -54,6 +54,12 @@ export interface ReleaseSeams {
54
54
  readonly writeFile?: (path: string, content: string) => void;
55
55
  readonly readFile?: (path: string) => string;
56
56
  readonly fileExists?: (path: string) => boolean;
57
+ /**
58
+ * #3900 check 4 -- consumer hard-stop census. When omitted, production lists
59
+ * open issues via REST. Tests inject a [ok, message] tuple so they do not
60
+ * hit GitHub.
61
+ */
62
+ readonly consumerHardStops?: (repo: string, projectRoot: string) => [boolean, string];
57
63
  readonly runCi?: (projectRoot: string, allowCoverageDebtIssue: number | null) => [boolean, string];
58
64
  readonly refreshRoadmap?: (projectRoot: string) => [boolean, string];
59
65
  readonly checkVbriefLifecycleSync?: (projectRoot: string, repo: string) => [boolean, number, string];
@@ -89,5 +95,19 @@ export interface ReleaseSeams {
89
95
  * Tests inject `{}` so a host env bypass cannot leak into fail-closed cases.
90
96
  */
91
97
  readonly closedVerbEnv?: EnvMap;
98
+ /**
99
+ * #3753 — override the active-CLI probe used by the local-vs-released report.
100
+ * When omitted, production calls `checkActiveCliAgainstTarget`.
101
+ */
102
+ readonly checkActiveCli?: (targetVersion: string) => import("../session/active-cli.js").ActiveCliCheckResult;
103
+ /**
104
+ * #3753 — override one `npm view <pkg>@<ver> --prefer-online` (tests).
105
+ * When omitted, production spawns npm. Never used to install.
106
+ */
107
+ readonly viewWorkspacePackage?: (name: string, version: string) => {
108
+ readonly name: string;
109
+ readonly visible: boolean;
110
+ readonly version: string | null;
111
+ };
92
112
  }
93
113
  //# 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");
@@ -16,8 +16,9 @@
16
16
  * gitignored / package-manager-invisible). Only the global, project-local,
17
17
  * ephemeral, and upgrade command forms vary by package manager.
18
18
  *
19
- * Every function here is PURE (no I/O): detection consumes an injected fact-set
20
- * so the whole surface is unit-testable without touching the filesystem or env.
19
+ * Detection from an injected fact-set remains pure. The project resolver is the
20
+ * single filesystem boundary used by consumer gates so package.json and lockfile
21
+ * precedence cannot drift across callers (#3610).
21
22
  */
22
23
  export type PackageManager = "npm" | "pnpm";
23
24
  export declare const PACKAGE_MANAGERS: readonly PackageManager[];
@@ -32,10 +33,38 @@ export interface DetectPackageManagerInput {
32
33
  /** Whether a `pnpm-lock.yaml` is present at the project root. */
33
34
  readonly pnpmLockPresent?: boolean;
34
35
  }
36
+ export type PackageManagerResolutionSource = "env-override" | "package-manager-field" | "pnpm-lock" | "user-agent" | "default";
37
+ export type PackageManagerResolutionErrorSource = "env-override" | "package-manager-field" | "package-json" | "project-filesystem";
38
+ export type PackageManagerResolution = {
39
+ readonly ok: true;
40
+ readonly packageManager: PackageManager;
41
+ readonly source: PackageManagerResolutionSource;
42
+ } | {
43
+ readonly ok: false;
44
+ readonly source: PackageManagerResolutionErrorSource;
45
+ readonly message: string;
46
+ };
47
+ export interface ResolveProjectPackageManagerOptions {
48
+ /** Consumer project root. Defaults to the current working directory. */
49
+ readonly projectRoot?: string;
50
+ /** Environment map used for override and user-agent precedence. */
51
+ readonly env?: NodeJS.ProcessEnv;
52
+ /** Text-read seam. `null` means the file is absent. */
53
+ readonly readText?: (path: string) => string | null;
54
+ /** File-existence seam used for pnpm-lock.yaml. */
55
+ readonly isFile?: (path: string) => boolean;
56
+ }
57
+ /** Human-readable, non-executable label for a resolution source. */
58
+ export declare function packageManagerSourceLabel(source: PackageManagerResolutionSource): string;
59
+ export declare function resolvePackageManager(input?: DetectPackageManagerInput): PackageManagerResolution;
60
+ /** Render untrusted diagnostic text as one bounded terminal-safe line. */
61
+ export declare function sanitizeSingleLineDiagnostic(text: string): string;
62
+ /** Resolve a consumer project's package manager from package.json and lockfile facts. */
63
+ export declare function resolveProjectPackageManager(options?: ResolveProjectPackageManagerOptions): PackageManagerResolution;
35
64
  /**
36
65
  * Detect the active package manager. Precedence (first match wins):
37
- * 1. `DEFT_PACKAGE_MANAGER` env override
38
- * 2. `packageManager` field / Corepack shim
66
+ * 1. `packageManager` field / Corepack shim (authoritative when present)
67
+ * 2. `DEFT_PACKAGE_MANAGER` env override
39
68
  * 3. `pnpm-lock.yaml` present
40
69
  * 4. `npm_config_user_agent` (set by the manager that spawned the process)
41
70
  * 5. default: npm