@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
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Local-vs-released CLI drift report after a cut (#3753).
3
+ *
4
+ * Report-only: never runs `npm i -g`. Never fails the release. Polls all four
5
+ * workspace packages with `--prefer-online` so a lag-poisoned npm cache cannot
6
+ * masquerade as a missing publish.
7
+ */
8
+ import { spawnSync } from "node:child_process";
9
+ import { PUBLIC_NPM_REGISTRY } from "../doctor/constants.js";
10
+ import { checkActiveCliAgainstTarget } from "../session/active-cli.js";
11
+ import { RELEASE_E2E_ENV } from "./skip-ci-incident.js";
12
+ /** Same sentinel as release-e2e `REHEARSAL_VERSION` — do not poll npm for 0.0.1. */
13
+ const REHEARSAL_VERSION = "0.0.1";
14
+ /** Workspace packages published by npm-publish.yml (sibling propagate is not atomic). */
15
+ export const WORKSPACE_PACKAGES = [
16
+ "@deftai/directive-types",
17
+ "@deftai/directive-core",
18
+ "@deftai/directive-content",
19
+ "@deftai/directive",
20
+ ];
21
+ /** Observed sibling lag was ~7 minutes; Phase 7 wait sits under this ceiling. */
22
+ export const CLI_DRIFT_POLL_TIMEOUT_MS = 10 * 60 * 1000;
23
+ export const CLI_DRIFT_POLL_INTERVAL_MS = 30_000;
24
+ /** Pipeline completion is a single probe — Step 13 returns before npm-publish.yml is green. */
25
+ export const CLI_DRIFT_PIPELINE_POLL_TIMEOUT_MS = 0;
26
+ export function remediationCommand(version) {
27
+ return `npm i -g @deftai/directive@${version} --prefer-online`;
28
+ }
29
+ export function npmViewArgs(name, version) {
30
+ return [
31
+ "view",
32
+ `${name}@${version}`,
33
+ "version",
34
+ "--prefer-online",
35
+ "--ignore-scripts",
36
+ `--registry=${PUBLIC_NPM_REGISTRY}`,
37
+ ];
38
+ }
39
+ export function defaultViewWorkspacePackage(name, version) {
40
+ try {
41
+ const result = spawnSync("npm", npmViewArgs(name, version), {
42
+ encoding: "utf8",
43
+ timeout: 15_000,
44
+ stdio: ["ignore", "pipe", "pipe"],
45
+ windowsHide: true,
46
+ });
47
+ const raw = typeof result.stdout === "string" ? result.stdout : "";
48
+ const seen = raw.trim().split(/\r?\n/)[0]?.trim() ?? "";
49
+ const visible = result.status === 0 && seen === version;
50
+ return { name, visible, version: seen.length > 0 ? seen : null };
51
+ }
52
+ catch {
53
+ return { name, visible: false, version: null };
54
+ }
55
+ }
56
+ export function classifyRegistryVisibility(opts) {
57
+ if (opts.skipped)
58
+ return "skipped";
59
+ const visibleCount = opts.probes.filter((p) => p.visible).length;
60
+ if (opts.probes.length > 0 && visibleCount === opts.probes.length)
61
+ return "all-visible";
62
+ if (visibleCount === 0 && opts.waitExhausted)
63
+ return "publish-incomplete";
64
+ return "still-propagating";
65
+ }
66
+ export function pollWorkspacePackages(version, opts) {
67
+ const now = opts.nowMs ?? Date.now;
68
+ const sleep = opts.sleepMs ?? (() => undefined);
69
+ const interval = opts.intervalMs ?? CLI_DRIFT_POLL_INTERVAL_MS;
70
+ const start = now();
71
+ let probes = WORKSPACE_PACKAGES.map((name) => opts.viewPackage(name, version));
72
+ if (probes.every((p) => p.visible)) {
73
+ return { probes, waitExhausted: false };
74
+ }
75
+ if (opts.timeoutMs <= 0) {
76
+ return { probes, waitExhausted: false };
77
+ }
78
+ while (now() - start < opts.timeoutMs) {
79
+ const remaining = opts.timeoutMs - (now() - start);
80
+ if (remaining <= 0)
81
+ break;
82
+ sleep(Math.min(interval, remaining));
83
+ probes = WORKSPACE_PACKAGES.map((name) => opts.viewPackage(name, version));
84
+ if (probes.every((p) => p.visible)) {
85
+ return { probes, waitExhausted: false };
86
+ }
87
+ }
88
+ return { probes, waitExhausted: true };
89
+ }
90
+ export function shouldSkipRegistryPoll(config, env = process.env) {
91
+ if (config.dryRun || config.skipTag)
92
+ return true;
93
+ if (config.version === REHEARSAL_VERSION)
94
+ return true;
95
+ if (env[RELEASE_E2E_ENV])
96
+ return true;
97
+ if (env.CI === "true" || env.GITHUB_ACTIONS === "true")
98
+ return true;
99
+ // Unit tests never probe the live registry; production `task release` does not set VITEST.
100
+ if (env.VITEST)
101
+ return true;
102
+ return false;
103
+ }
104
+ function formatRegistryLine(registry, packages) {
105
+ if (registry === "skipped") {
106
+ return " registry: skipped (dry-run / skip-tag / rehearsal / CI — no publish to poll)";
107
+ }
108
+ const visible = packages.filter((p) => p.visible).map((p) => p.name);
109
+ const missing = packages.filter((p) => !p.visible).map((p) => p.name);
110
+ const counts = `visible ${visible.length}/${packages.length}`;
111
+ if (registry === "all-visible") {
112
+ return ` registry: all-visible (${counts})`;
113
+ }
114
+ if (registry === "publish-incomplete") {
115
+ return ` registry: publish-incomplete (${counts}; none of the four packages resolved after the wait)`;
116
+ }
117
+ const missingText = missing.length > 0 ? `; missing: ${missing.join(", ")}` : "";
118
+ return ` registry: still-propagating (${counts}${missingText})`;
119
+ }
120
+ export function buildCliDriftReport(releasedVersion, opts = { skipRegistryPoll: false }) {
121
+ const check = opts.checkActiveCli ?? ((target) => checkActiveCliAgainstTarget(target));
122
+ const cli = check(releasedVersion);
123
+ const localVersion = cli.active?.version ?? null;
124
+ const localPath = cli.active?.path ?? null;
125
+ const match = localVersion !== null && localVersion === releasedVersion && cli.ok;
126
+ const shadowed = !cli.ok && cli.candidates.length > 0;
127
+ let packages = [];
128
+ let waitExhausted = false;
129
+ if (!opts.skipRegistryPoll) {
130
+ const view = opts.viewPackage ?? defaultViewWorkspacePackage;
131
+ const polled = pollWorkspacePackages(releasedVersion, {
132
+ timeoutMs: opts.pollTimeoutMs ?? CLI_DRIFT_PIPELINE_POLL_TIMEOUT_MS,
133
+ intervalMs: opts.pollIntervalMs,
134
+ viewPackage: view,
135
+ nowMs: opts.nowMs,
136
+ sleepMs: opts.sleepMs,
137
+ });
138
+ packages = polled.probes;
139
+ waitExhausted = polled.waitExhausted;
140
+ }
141
+ const registry = classifyRegistryVisibility({
142
+ probes: packages,
143
+ waitExhausted,
144
+ skipped: opts.skipRegistryPoll,
145
+ });
146
+ const remediation = remediationCommand(releasedVersion);
147
+ const localLabel = localVersion === null
148
+ ? "none on PATH"
149
+ : `${localVersion}${localPath !== null ? ` (${localPath})` : ""}`;
150
+ const lines = [
151
+ "CLI drift report (#3753):",
152
+ ` released: ${releasedVersion}`,
153
+ ` local global CLI: ${localLabel}`,
154
+ ` match: ${match ? "yes" : "no"}`,
155
+ ...(shadowed
156
+ ? [" note: PATH-shadowed install — bare deft --version is not sufficient (#3233)"]
157
+ : []),
158
+ formatRegistryLine(registry, packages),
159
+ ` remediation: ${remediation}`,
160
+ " note: report-only — this pipeline does not run npm i -g",
161
+ ];
162
+ return {
163
+ releasedVersion,
164
+ localVersion,
165
+ localPath,
166
+ match,
167
+ shadowed,
168
+ registry,
169
+ packages,
170
+ remediation,
171
+ lines,
172
+ };
173
+ }
174
+ export function formatCliDriftReport(report) {
175
+ return `${report.lines.join("\n")}\n`;
176
+ }
177
+ /** Best-effort wrapper — a report failure must never fail the cut. */
178
+ export function emitCliDriftReportBestEffort(releasedVersion, opts, write = (text) => {
179
+ process.stderr.write(text);
180
+ }) {
181
+ try {
182
+ write(formatCliDriftReport(buildCliDriftReport(releasedVersion, opts)));
183
+ }
184
+ catch (err) {
185
+ const reason = err instanceof Error ? err.message : String(err);
186
+ write(`CLI drift report (#3753): skipped (${reason.replace(/\r?\n/g, " ")})\n`);
187
+ }
188
+ }
189
+ //# sourceMappingURL=cli-drift-report.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Check 4 -- consumer hard-stop census (#3900 / #3713 / #3969).
3
+ *
4
+ * Enumerates open issues by privileged label only.
5
+ * BLOCKER is the sole permitted consumer title classification (inbound flare).
6
+ * The adoption-blocker label is never derived from a title.
7
+ * Issue bodies are not read into the verdict.
8
+ *
9
+ * parseClosesSet composes citation-grammar classifyPosition; do not add a
10
+ * third position classifier (#3969).
11
+ */
12
+ export declare const CONSUMER_HARD_STOP_TITLE_RE: RegExp;
13
+ export declare const ADOPTION_BLOCKER_LABEL = "adoption-blocker";
14
+ export declare const BLOCKS_RELEASE_TAG_LABEL = "blocks-release-tag";
15
+ /** Maintainer-applied labels that can block a cut. Titles cannot. */
16
+ export declare const PRIVILEGED_HARD_STOP_LABELS: readonly string[];
17
+ export interface HardStopIssue {
18
+ readonly number: number;
19
+ readonly title: string;
20
+ readonly labels: readonly string[];
21
+ }
22
+ export interface HardStopCensusEntry {
23
+ readonly number: number;
24
+ readonly title: string;
25
+ readonly viaTitle: boolean;
26
+ readonly viaLabel: boolean;
27
+ }
28
+ export interface HardStopCensusResult {
29
+ readonly code: 0 | 1 | 2;
30
+ readonly message: string;
31
+ readonly stream: "stdout" | "stderr";
32
+ readonly matches: readonly HardStopCensusEntry[];
33
+ readonly shipsPast: readonly HardStopCensusEntry[];
34
+ }
35
+ /** Privileged labels only. Title is recorded as a flare, never as a match. Never inspect a body field. */
36
+ export declare function classifyHardStop(issue: HardStopIssue): HardStopCensusEntry | null;
37
+ export declare function enumerateConsumerHardStops(issues: readonly HardStopIssue[]): HardStopCensusEntry[];
38
+ /**
39
+ * Extract Closes/Fixes/Resolves #N from changelog Unreleased text.
40
+ * Never from issue bodies. Hits in refused markdown positions are not claims.
41
+ */
42
+ export declare function parseClosesSet(changelogText: string): Set<number>;
43
+ export declare function evaluateConsumerHardStopCensus(options: {
44
+ readonly issues: readonly HardStopIssue[];
45
+ readonly closesSet: ReadonlySet<number>;
46
+ }): HardStopCensusResult;
47
+ export declare function issueFromInventoryRow(row: Record<string, unknown>): HardStopIssue | null;
48
+ //# sourceMappingURL=consumer-hard-stops.d.ts.map
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Check 4 -- consumer hard-stop census (#3900 / #3713 / #3969).
3
+ *
4
+ * Enumerates open issues by privileged label only.
5
+ * BLOCKER is the sole permitted consumer title classification (inbound flare).
6
+ * The adoption-blocker label is never derived from a title.
7
+ * Issue bodies are not read into the verdict.
8
+ *
9
+ * parseClosesSet composes citation-grammar classifyPosition; do not add a
10
+ * third position classifier (#3969).
11
+ */
12
+ import { classifyPosition } from "../design-critique/citation-grammar.js";
13
+ export const CONSUMER_HARD_STOP_TITLE_RE = /^BLOCKER\b/i;
14
+ export const ADOPTION_BLOCKER_LABEL = "adoption-blocker";
15
+ export const BLOCKS_RELEASE_TAG_LABEL = "blocks-release-tag";
16
+ /** Maintainer-applied labels that can block a cut. Titles cannot. */
17
+ export const PRIVILEGED_HARD_STOP_LABELS = [
18
+ ADOPTION_BLOCKER_LABEL,
19
+ BLOCKS_RELEASE_TAG_LABEL,
20
+ ];
21
+ const REMEDIATION = "Recovery: close those issues in this cut (or list them in the Unreleased Closes set). " +
22
+ "Census blocking classification is privileged labels (" +
23
+ PRIVILEGED_HARD_STOP_LABELS.join(", ") +
24
+ ") only; BLOCKER in a title is an inbound flare and does not by itself block a cut (#3713 / #3969).";
25
+ function labelsOf(issue) {
26
+ return issue.labels.map((label) => label.trim()).filter((label) => label.length > 0);
27
+ }
28
+ function hasPrivilegedLabel(labels) {
29
+ return labels.some((label) => PRIVILEGED_HARD_STOP_LABELS.includes(label));
30
+ }
31
+ /** Privileged labels only. Title is recorded as a flare, never as a match. Never inspect a body field. */
32
+ export function classifyHardStop(issue) {
33
+ const viaTitle = CONSUMER_HARD_STOP_TITLE_RE.test(issue.title.trim());
34
+ const viaLabel = hasPrivilegedLabel(labelsOf(issue));
35
+ if (!viaLabel) {
36
+ return null;
37
+ }
38
+ return { number: issue.number, title: issue.title, viaTitle, viaLabel };
39
+ }
40
+ export function enumerateConsumerHardStops(issues) {
41
+ const out = [];
42
+ for (const issue of issues) {
43
+ const match = classifyHardStop(issue);
44
+ if (match !== null) {
45
+ out.push(match);
46
+ }
47
+ }
48
+ return out.sort((a, b) => a.number - b.number);
49
+ }
50
+ /**
51
+ * Extract Closes/Fixes/Resolves #N from changelog Unreleased text.
52
+ * Never from issue bodies. Hits in refused markdown positions are not claims.
53
+ */
54
+ export function parseClosesSet(changelogText) {
55
+ const after = changelogText.split(/^## \[Unreleased\]\s*$/m)[1] ?? "";
56
+ const unreleased = after.split(/^## \[/m)[0] ?? after;
57
+ const found = new Set();
58
+ const re = /\b(?:Closes|Fixes|Resolves)\s+#(\d+)/gi;
59
+ let match = re.exec(unreleased);
60
+ while (match !== null) {
61
+ const reason = classifyPosition(unreleased, match.index ?? 0);
62
+ if (reason === null) {
63
+ const n = Number(match[1]);
64
+ if (Number.isInteger(n) && n > 0) {
65
+ found.add(n);
66
+ }
67
+ }
68
+ match = re.exec(unreleased);
69
+ }
70
+ return found;
71
+ }
72
+ export function evaluateConsumerHardStopCensus(options) {
73
+ const matches = enumerateConsumerHardStops(options.issues);
74
+ const shipsPast = matches.filter((entry) => !options.closesSet.has(entry.number));
75
+ if (matches.length === 0) {
76
+ return {
77
+ code: 0,
78
+ message: "Consumer hard-stops: ok -- no open privileged hard-stop labels (" +
79
+ PRIVILEGED_HARD_STOP_LABELS.join(", ") +
80
+ ").",
81
+ stream: "stdout",
82
+ matches,
83
+ shipsPast,
84
+ };
85
+ }
86
+ if (shipsPast.length === 0) {
87
+ return {
88
+ code: 0,
89
+ message: "Consumer hard-stops: ok -- " +
90
+ String(matches.length) +
91
+ " open hard-stop(s) are in this cut Closes set.",
92
+ stream: "stdout",
93
+ matches,
94
+ shipsPast,
95
+ };
96
+ }
97
+ const sample = shipsPast
98
+ .slice(0, 8)
99
+ .map((entry) => "#" + String(entry.number))
100
+ .join(", ");
101
+ return {
102
+ code: 1,
103
+ message: "Consumer hard-stops: fail -- " +
104
+ String(shipsPast.length) +
105
+ " open hard-stop(s) not in this cut Closes set: " +
106
+ sample +
107
+ ". " +
108
+ REMEDIATION,
109
+ stream: "stderr",
110
+ matches,
111
+ shipsPast,
112
+ };
113
+ }
114
+ export function issueFromInventoryRow(row) {
115
+ const number = row.number;
116
+ const title = row.title;
117
+ if (typeof number !== "number" || !Number.isInteger(number) || typeof title !== "string") {
118
+ return null;
119
+ }
120
+ if ("pull_request" in row) {
121
+ return null;
122
+ }
123
+ const labelsRaw = row.labels;
124
+ const labels = [];
125
+ if (Array.isArray(labelsRaw)) {
126
+ for (const item of labelsRaw) {
127
+ if (typeof item === "string") {
128
+ labels.push(item);
129
+ }
130
+ else if (item !== null && typeof item === "object" && "name" in item) {
131
+ const name = item.name;
132
+ if (typeof name === "string") {
133
+ labels.push(name);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return { number, title, labels };
139
+ }
140
+ //# sourceMappingURL=consumer-hard-stops.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Checks 5 and 6 -- operator disclosure at the release decision point (#3900).
3
+ *
4
+ * New-refusal census and net-posture are judgments. They are printed for the
5
+ * operator and never fail-closed, never dressed as a computed verdict.
6
+ */
7
+ export interface RefusalHint {
8
+ readonly line: number;
9
+ readonly excerpt: string;
10
+ }
11
+ export interface ConsumerReadinessDisclosure {
12
+ readonly blocking: false;
13
+ readonly newRefusalHints: readonly RefusalHint[];
14
+ readonly text: string;
15
+ }
16
+ export declare function collectNewRefusalHints(changelogText: string): RefusalHint[];
17
+ export declare function formatConsumerReadinessDisclosure(changelogText: string): ConsumerReadinessDisclosure;
18
+ //# sourceMappingURL=consumer-readiness-disclosure.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Checks 5 and 6 -- operator disclosure at the release decision point (#3900).
3
+ *
4
+ * New-refusal census and net-posture are judgments. They are printed for the
5
+ * operator and never fail-closed, never dressed as a computed verdict.
6
+ */
7
+ const FAIL_CLOSED_HINT = /fail-closed|fail closed|refuses? to|MUST NOT|must not|blocks? (?:the )?(?:tag|cut|release)|new refusal/i;
8
+ function unreleasedSection(changelogText) {
9
+ const rest = changelogText.split(/^## \[Unreleased\]\s*$/m)[1];
10
+ if (rest === undefined) {
11
+ return "";
12
+ }
13
+ const next = rest.split(/^## \[/m)[0] ?? rest;
14
+ return next;
15
+ }
16
+ export function collectNewRefusalHints(changelogText) {
17
+ const section = unreleasedSection(changelogText);
18
+ const hints = [];
19
+ const lines = section.split("\n");
20
+ for (let i = 0; i < lines.length; i += 1) {
21
+ const line = lines[i] ?? "";
22
+ if (FAIL_CLOSED_HINT.test(line)) {
23
+ hints.push({ line: i + 1, excerpt: line.trim().slice(0, 160) });
24
+ }
25
+ }
26
+ return hints;
27
+ }
28
+ export function formatConsumerReadinessDisclosure(changelogText) {
29
+ const hints = collectNewRefusalHints(changelogText);
30
+ const lines = [
31
+ "Consumer-readiness disclosure (does not block the tag) -- #3900 checks 5 and 6.",
32
+ "",
33
+ "Check 5 -- new-refusal census (operator judgment):",
34
+ ];
35
+ if (hints.length === 0) {
36
+ lines.push(" No Unreleased lines matched fail-closed / new-refusal hints.");
37
+ }
38
+ else {
39
+ lines.push(" Unreleased lines that may introduce a consumer-visible refusal:");
40
+ for (const hint of hints.slice(0, 20)) {
41
+ lines.push(" - " + hint.excerpt);
42
+ }
43
+ }
44
+ lines.push(" Each new refusal needs remediation text and an escape hatch. This is not a computed verdict.");
45
+ lines.push("");
46
+ lines.push("Check 6 -- net-posture (operator judgment):");
47
+ lines.push(" Does this candidate fix more consumer-visible breakage than it introduces, and on which surfaces?");
48
+ lines.push(" Holding a release is only correct when that answer is negative. The operator decides.");
49
+ return { blocking: false, newRefusalHints: hints, text: lines.join("\n") };
50
+ }
51
+ //# sourceMappingURL=consumer-readiness-disclosure.js.map
@@ -1,7 +1,10 @@
1
1
  export * from "./auto-hatch.js";
2
2
  export * from "./build-dist.js";
3
+ export * from "./cli-drift-report.js";
3
4
  export * from "./closed-verb-gate.js";
4
5
  export * from "./constants.js";
6
+ export * from "./consumer-hard-stops.js";
7
+ export * from "./consumer-readiness-disclosure.js";
5
8
  export * from "./coverage-debt-ledger.js";
6
9
  export * from "./flags.js";
7
10
  export * from "./gh.js";
@@ -1,8 +1,11 @@
1
1
  /* v8 ignore file -- re-export barrel; covered via main.ts */
2
2
  export * from "./auto-hatch.js";
3
3
  export * from "./build-dist.js";
4
+ export * from "./cli-drift-report.js";
4
5
  export * from "./closed-verb-gate.js";
5
6
  export * from "./constants.js";
7
+ export * from "./consumer-hard-stops.js";
8
+ export * from "./consumer-readiness-disclosure.js";
6
9
  export * from "./coverage-debt-ledger.js";
7
10
  export * from "./flags.js";
8
11
  export * from "./gh.js";
@@ -1,4 +1,4 @@
1
- import { type FetchIssueStatesOptions } from "../intake/reconcile-issues.js";
1
+ import { IssueState } from "../intake/reconcile-issues.js";
2
2
  import { type CallOptions, type CompletedProcess } from "../scm/call.js";
3
3
  export type ScmCallFn = (source: string, verb: string, args: readonly string[] | null, options?: CallOptions) => CompletedProcess;
4
4
  /** Max seconds to sleep before the single rate-limit retry (operator-locked cap). */
@@ -9,13 +9,15 @@ export interface RateLimitProbe {
9
9
  readonly coreResetUnix: number | null;
10
10
  readonly graphqlRemaining: number | null;
11
11
  }
12
- export interface FetchIssueStatesForReleaseOptions extends FetchIssueStatesOptions {
12
+ export interface FetchIssueStatesForReleaseOptions {
13
+ readonly cwd?: string;
14
+ readonly scmCall?: ScmCallFn;
13
15
  readonly sleep?: SleepFn;
14
16
  readonly now?: () => number;
15
17
  }
16
18
  export type FetchIssueStatesForReleaseResult = {
17
19
  readonly ok: true;
18
- readonly states: Map<number, import("../intake/reconcile-issues.js").IssueState>;
20
+ readonly states: Map<number, IssueState>;
19
21
  } | {
20
22
  readonly ok: false;
21
23
  readonly reason: string;
@@ -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
  });