@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.
- package/dist/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +69 -2
- package/dist/hooks/dispatcher.js +609 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +9 -18
- package/dist/policy/write-fence.js +3 -0
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +53 -12
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +38 -0
- package/dist/scm/design-critique-chip.js +187 -0
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +4 -1
- package/dist/scm/readiness.js +35 -5
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +42 -0
- package/dist/session/git.js +219 -3
- package/dist/session/occupancy.d.ts +239 -12
- package/dist/session/occupancy.js +820 -27
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +17 -1
- package/dist/session/session-start.js +49 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +33 -3
- package/dist/session/verify-session-ritual.js +89 -19
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +77 -0
- package/dist/swarm/launch.js +248 -38
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/xbrief-migrate/agents-header.js +69 -7
- 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 {
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
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(
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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 ||
|
|
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
|
});
|
package/dist/release/pipeline.js
CHANGED
|
@@ -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
|
package/dist/release/types.d.ts
CHANGED
|
@@ -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). */
|
package/dist/render/constants.js
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
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
|
|
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 (
|
|
91
|
+
if (scopePolicy.render) {
|
|
67
92
|
const scopeLines = buildScopeOutlookSection(authority.vbriefDir, {
|
|
68
|
-
includeProposed,
|
|
93
|
+
includeProposed: scopePolicy.includeProposed,
|
|
69
94
|
proposedLimit: options.proposedLimit,
|
|
70
|
-
|
|
95
|
+
includeCurrent: scopePolicy.includeCurrent,
|
|
96
|
+
includeCompleted: scopePolicy.includeCompleted,
|
|
71
97
|
});
|
|
72
98
|
if (scopeLines.length > 0)
|
|
73
99
|
lines.push(...scopeLines);
|
package/dist/render/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/render/index.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
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 ${
|
|
29
|
+
process.stderr.write(`Warning: no narratives found in ${sourcePath}\n`);
|
|
38
30
|
}
|
|
39
|
-
const lines = [
|
|
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
|
-
|
|
56
|
-
"
|
|
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
|
-
|
|
63
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 = [
|
|
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");
|