@deftai/directive-core 0.90.0 → 0.92.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/cache/fetch.js +3 -2
- package/dist/cache/io.d.ts +13 -2
- package/dist/cache/io.js +36 -7
- package/dist/cache/operations.js +5 -3
- package/dist/doctor/main.js +37 -0
- package/dist/doctor/openclaw-skills.d.ts +42 -8
- package/dist/doctor/openclaw-skills.js +281 -38
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- package/dist/hooks/dispatcher.d.ts +6 -1
- package/dist/hooks/dispatcher.js +30 -0
- package/dist/init-deposit/gitignore.js +3 -0
- package/dist/init-deposit/hygiene.d.ts +43 -1
- package/dist/init-deposit/hygiene.js +112 -7
- package/dist/init-deposit/init-deposit.d.ts +5 -0
- package/dist/init-deposit/init-deposit.js +37 -2
- package/dist/init-deposit/scaffold.js +3 -1
- package/dist/intake/github-auth-modes.d.ts +1 -1
- package/dist/intake/github-auth-modes.js +25 -3
- package/dist/intake/issue-ingest-cli.js +13 -0
- package/dist/intake/reconcile-issues-cli.js +13 -0
- package/dist/orchestration/probe-session.d.ts +5 -1
- package/dist/orchestration/probe-session.js +24 -13
- package/dist/platform/agents-md.js +1 -1
- package/dist/policy/deft-directive-disable.d.ts +86 -0
- package/dist/policy/deft-directive-disable.js +167 -0
- package/dist/policy/delivery-branch.d.ts +33 -0
- package/dist/policy/delivery-branch.js +124 -0
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +17 -1
- package/dist/policy/plan-extensions.d.ts +10 -0
- package/dist/policy/plan-extensions.js +16 -0
- package/dist/pr-closing-keywords/detect.d.ts +13 -0
- package/dist/pr-closing-keywords/detect.js +73 -8
- package/dist/pr-closing-keywords/index.d.ts +2 -2
- package/dist/pr-closing-keywords/index.js +1 -1
- package/dist/pr-closing-keywords/main.js +119 -90
- package/dist/pr-closing-keywords/types.d.ts +10 -0
- package/dist/render/constants.d.ts +16 -0
- package/dist/render/constants.js +16 -1
- package/dist/render/export-spec.d.ts +7 -1
- package/dist/render/export-spec.js +51 -5
- package/dist/render/index.d.ts +3 -2
- package/dist/render/index.js +2 -2
- package/dist/render/roadmap-render.js +164 -60
- package/dist/render/scope-outlook.d.ts +5 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.d.ts +28 -2
- package/dist/render/spec-render.js +111 -14
- package/dist/render/text-utils.d.ts +6 -0
- package/dist/render/text-utils.js +23 -0
- package/dist/scm/binary.d.ts +3 -0
- package/dist/scm/binary.js +10 -2
- package/dist/scm/call.d.ts +5 -0
- package/dist/scm/call.js +5 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +5 -0
- package/dist/scm/main.js +27 -0
- package/dist/scm/readiness-cli.d.ts +24 -0
- package/dist/scm/readiness-cli.js +85 -0
- package/dist/scm/readiness.d.ts +104 -0
- package/dist/scm/readiness.js +421 -0
- package/dist/scope/batch-promote.d.ts +26 -0
- package/dist/scope/batch-promote.js +167 -0
- package/dist/scope/brief-io.d.ts +3 -1
- package/dist/scope/brief-io.js +5 -3
- package/dist/scope/delivery-evidence.d.ts +112 -0
- package/dist/scope/delivery-evidence.js +419 -0
- package/dist/scope/index.d.ts +2 -0
- package/dist/scope/index.js +2 -0
- package/dist/scope/main.d.ts +7 -0
- package/dist/scope/main.js +155 -9
- package/dist/scope/registry-artifact-sync.js +4 -1
- package/dist/scope/transition.d.ts +11 -1
- package/dist/scope/transition.js +30 -4
- package/dist/scope/wip-cap-check.d.ts +6 -0
- package/dist/scope/wip-cap-check.js +14 -2
- package/dist/session/ritual-sentinel.js +21 -12
- package/dist/session/session-start-hook.d.ts +3 -0
- package/dist/session/session-start-hook.js +21 -0
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +85 -0
- package/dist/swarm/complete-cohort.d.ts +20 -1
- package/dist/swarm/complete-cohort.js +56 -9
- package/dist/swarm/finalize-cohort-cli.js +9 -0
- package/dist/swarm/finalize-cohort.d.ts +8 -0
- package/dist/swarm/finalize-cohort.js +217 -21
- package/dist/triage/welcome/index.d.ts +1 -1
- package/dist/triage/welcome/index.js +1 -1
- package/dist/triage/welcome/onboard.js +7 -1
- package/dist/triage/welcome/prior-state.d.ts +7 -0
- package/dist/triage/welcome/prior-state.js +16 -2
- package/dist/triage/welcome/writers.d.ts +10 -0
- package/dist/triage/welcome/writers.js +72 -2
- package/dist/user-config/experimental-rules.d.ts +43 -0
- package/dist/user-config/experimental-rules.js +162 -0
- package/dist/user-config/index.d.ts +1 -0
- package/dist/user-config/index.js +1 -0
- package/dist/verify-source/contained-writes.js +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EXIT_CONFIG_ERROR, EXIT_HITS_FOUND, EXIT_OK } from "./constants.js";
|
|
2
|
-
import { findHits, renderHit } from "./detect.js";
|
|
2
|
+
import { findAllClosingKeywordHits, findHits, renderHit } from "./detect.js";
|
|
3
3
|
import { defaultRunGh, fetchPrBody, fetchPrCommitMessages } from "./gh.js";
|
|
4
4
|
import { readCommitsFile, readTextFile } from "./io.js";
|
|
5
5
|
export function parseAllowList(values) {
|
|
@@ -11,43 +11,43 @@ export function parseAllowList(values) {
|
|
|
11
11
|
continue;
|
|
12
12
|
}
|
|
13
13
|
if (!/^\d+$/.test(tok)) {
|
|
14
|
-
throw new Error(`Invalid issue number in --allow-known-false-positives: ${JSON.stringify(tok)}`);
|
|
14
|
+
throw new Error(`Invalid issue number in --allow-known-false-positives / --allow-close: ${JSON.stringify(tok)}`);
|
|
15
15
|
}
|
|
16
16
|
out.add(Number(tok));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
return out;
|
|
20
20
|
}
|
|
21
|
+
function emptyParsed(error) {
|
|
22
|
+
return {
|
|
23
|
+
pr: null,
|
|
24
|
+
bodyFile: null,
|
|
25
|
+
commitsFile: null,
|
|
26
|
+
repo: null,
|
|
27
|
+
allowKnownFalsePositives: [],
|
|
28
|
+
allowClose: [],
|
|
29
|
+
mode: "both",
|
|
30
|
+
error,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
21
33
|
export function parseArgs(argv) {
|
|
22
34
|
let pr = null;
|
|
23
35
|
let bodyFile = null;
|
|
24
36
|
let commitsFile = null;
|
|
25
37
|
let repo = null;
|
|
38
|
+
let mode = "both";
|
|
26
39
|
const allowKnownFalsePositives = [];
|
|
40
|
+
const allowClose = [];
|
|
27
41
|
for (let i = 0; i < argv.length; i += 1) {
|
|
28
42
|
const arg = argv[i];
|
|
29
43
|
if (arg === "--pr") {
|
|
30
44
|
const value = argv[i + 1];
|
|
31
45
|
if (value === undefined) {
|
|
32
|
-
return
|
|
33
|
-
pr,
|
|
34
|
-
bodyFile,
|
|
35
|
-
commitsFile,
|
|
36
|
-
repo,
|
|
37
|
-
allowKnownFalsePositives,
|
|
38
|
-
error: "argument --pr: expected one argument",
|
|
39
|
-
};
|
|
46
|
+
return emptyParsed("argument --pr: expected one argument");
|
|
40
47
|
}
|
|
41
48
|
const n = Number(value);
|
|
42
49
|
if (!Number.isInteger(n)) {
|
|
43
|
-
return {
|
|
44
|
-
pr,
|
|
45
|
-
bodyFile,
|
|
46
|
-
commitsFile,
|
|
47
|
-
repo,
|
|
48
|
-
allowKnownFalsePositives,
|
|
49
|
-
error: `invalid int value: ${JSON.stringify(value)}`,
|
|
50
|
-
};
|
|
50
|
+
return emptyParsed(`invalid int value: ${JSON.stringify(value)}`);
|
|
51
51
|
}
|
|
52
52
|
pr = n;
|
|
53
53
|
i += 1;
|
|
@@ -56,28 +56,14 @@ export function parseArgs(argv) {
|
|
|
56
56
|
const value = arg.slice("--pr=".length);
|
|
57
57
|
const n = Number(value);
|
|
58
58
|
if (!Number.isInteger(n)) {
|
|
59
|
-
return {
|
|
60
|
-
pr,
|
|
61
|
-
bodyFile,
|
|
62
|
-
commitsFile,
|
|
63
|
-
repo,
|
|
64
|
-
allowKnownFalsePositives,
|
|
65
|
-
error: `invalid int value: ${JSON.stringify(value)}`,
|
|
66
|
-
};
|
|
59
|
+
return emptyParsed(`invalid int value: ${JSON.stringify(value)}`);
|
|
67
60
|
}
|
|
68
61
|
pr = n;
|
|
69
62
|
}
|
|
70
63
|
else if (arg === "--body-file") {
|
|
71
64
|
const value = argv[i + 1];
|
|
72
65
|
if (value === undefined) {
|
|
73
|
-
return
|
|
74
|
-
pr,
|
|
75
|
-
bodyFile,
|
|
76
|
-
commitsFile,
|
|
77
|
-
repo,
|
|
78
|
-
allowKnownFalsePositives,
|
|
79
|
-
error: "argument --body-file: expected one argument",
|
|
80
|
-
};
|
|
66
|
+
return emptyParsed("argument --body-file: expected one argument");
|
|
81
67
|
}
|
|
82
68
|
bodyFile = value;
|
|
83
69
|
i += 1;
|
|
@@ -88,14 +74,7 @@ export function parseArgs(argv) {
|
|
|
88
74
|
else if (arg === "--commits-file") {
|
|
89
75
|
const value = argv[i + 1];
|
|
90
76
|
if (value === undefined) {
|
|
91
|
-
return
|
|
92
|
-
pr,
|
|
93
|
-
bodyFile,
|
|
94
|
-
commitsFile,
|
|
95
|
-
repo,
|
|
96
|
-
allowKnownFalsePositives,
|
|
97
|
-
error: "argument --commits-file: expected one argument",
|
|
98
|
-
};
|
|
77
|
+
return emptyParsed("argument --commits-file: expected one argument");
|
|
99
78
|
}
|
|
100
79
|
commitsFile = value;
|
|
101
80
|
i += 1;
|
|
@@ -106,14 +85,7 @@ export function parseArgs(argv) {
|
|
|
106
85
|
else if (arg === "--repo") {
|
|
107
86
|
const value = argv[i + 1];
|
|
108
87
|
if (value === undefined) {
|
|
109
|
-
return
|
|
110
|
-
pr,
|
|
111
|
-
bodyFile,
|
|
112
|
-
commitsFile,
|
|
113
|
-
repo,
|
|
114
|
-
allowKnownFalsePositives,
|
|
115
|
-
error: "argument --repo: expected one argument",
|
|
116
|
-
};
|
|
88
|
+
return emptyParsed("argument --repo: expected one argument");
|
|
117
89
|
}
|
|
118
90
|
repo = value;
|
|
119
91
|
i += 1;
|
|
@@ -121,17 +93,28 @@ export function parseArgs(argv) {
|
|
|
121
93
|
else if (arg?.startsWith("--repo=")) {
|
|
122
94
|
repo = arg.slice("--repo=".length);
|
|
123
95
|
}
|
|
96
|
+
else if (arg === "--mode") {
|
|
97
|
+
const value = argv[i + 1];
|
|
98
|
+
if (value === undefined) {
|
|
99
|
+
return emptyParsed("argument --mode: expected one argument (fp|intent|both)");
|
|
100
|
+
}
|
|
101
|
+
if (value !== "fp" && value !== "intent" && value !== "both") {
|
|
102
|
+
return emptyParsed(`invalid --mode: ${JSON.stringify(value)} (expected fp|intent|both)`);
|
|
103
|
+
}
|
|
104
|
+
mode = value;
|
|
105
|
+
i += 1;
|
|
106
|
+
}
|
|
107
|
+
else if (arg?.startsWith("--mode=")) {
|
|
108
|
+
const value = arg.slice("--mode=".length);
|
|
109
|
+
if (value !== "fp" && value !== "intent" && value !== "both") {
|
|
110
|
+
return emptyParsed(`invalid --mode: ${JSON.stringify(value)} (expected fp|intent|both)`);
|
|
111
|
+
}
|
|
112
|
+
mode = value;
|
|
113
|
+
}
|
|
124
114
|
else if (arg === "--allow-known-false-positives") {
|
|
125
115
|
const value = argv[i + 1];
|
|
126
116
|
if (value === undefined) {
|
|
127
|
-
return
|
|
128
|
-
pr,
|
|
129
|
-
bodyFile,
|
|
130
|
-
commitsFile,
|
|
131
|
-
repo,
|
|
132
|
-
allowKnownFalsePositives,
|
|
133
|
-
error: "argument --allow-known-false-positives: expected one argument",
|
|
134
|
-
};
|
|
117
|
+
return emptyParsed("argument --allow-known-false-positives: expected one argument");
|
|
135
118
|
}
|
|
136
119
|
allowKnownFalsePositives.push(value);
|
|
137
120
|
i += 1;
|
|
@@ -139,45 +122,69 @@ export function parseArgs(argv) {
|
|
|
139
122
|
else if (arg?.startsWith("--allow-known-false-positives=")) {
|
|
140
123
|
allowKnownFalsePositives.push(arg.slice("--allow-known-false-positives=".length));
|
|
141
124
|
}
|
|
125
|
+
else if (arg === "--allow-close") {
|
|
126
|
+
const value = argv[i + 1];
|
|
127
|
+
if (value === undefined) {
|
|
128
|
+
return emptyParsed("argument --allow-close: expected one argument");
|
|
129
|
+
}
|
|
130
|
+
allowClose.push(value);
|
|
131
|
+
i += 1;
|
|
132
|
+
}
|
|
133
|
+
else if (arg?.startsWith("--allow-close=")) {
|
|
134
|
+
allowClose.push(arg.slice("--allow-close=".length));
|
|
135
|
+
}
|
|
142
136
|
else if (arg?.startsWith("-")) {
|
|
143
|
-
return {
|
|
144
|
-
pr,
|
|
145
|
-
bodyFile,
|
|
146
|
-
commitsFile,
|
|
147
|
-
repo,
|
|
148
|
-
allowKnownFalsePositives,
|
|
149
|
-
error: `unrecognized arguments: ${arg}`,
|
|
150
|
-
};
|
|
137
|
+
return emptyParsed(`unrecognized arguments: ${arg}`);
|
|
151
138
|
}
|
|
152
139
|
else {
|
|
153
|
-
return {
|
|
154
|
-
pr,
|
|
155
|
-
bodyFile,
|
|
156
|
-
commitsFile,
|
|
157
|
-
repo,
|
|
158
|
-
allowKnownFalsePositives,
|
|
159
|
-
error: `unrecognized arguments: ${arg}`,
|
|
160
|
-
};
|
|
140
|
+
return emptyParsed(`unrecognized arguments: ${arg}`);
|
|
161
141
|
}
|
|
162
142
|
}
|
|
163
|
-
return { pr, bodyFile, commitsFile, repo, allowKnownFalsePositives };
|
|
143
|
+
return { pr, bodyFile, commitsFile, repo, allowKnownFalsePositives, allowClose, mode };
|
|
164
144
|
}
|
|
165
145
|
function filterHits(hits, allowList) {
|
|
166
146
|
return hits.filter((h) => !allowList.has(h.issueNumber));
|
|
167
147
|
}
|
|
168
|
-
function emitResult(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
148
|
+
function emitResult(mode, fpFiltered, intentFiltered, fpSuppressed, intentSuppressed) {
|
|
149
|
+
const totalFail = fpFiltered.length + intentFiltered.length;
|
|
150
|
+
if (totalFail === 0) {
|
|
151
|
+
const notes = [];
|
|
152
|
+
if (fpSuppressed > 0) {
|
|
153
|
+
notes.push(`${fpSuppressed} FP hit(s) suppressed by --allow-known-false-positives`);
|
|
172
154
|
}
|
|
173
|
-
|
|
155
|
+
if (intentSuppressed > 0) {
|
|
156
|
+
notes.push(`${intentSuppressed} intent hit(s) suppressed by --allow-close`);
|
|
157
|
+
}
|
|
158
|
+
if (notes.length > 0) {
|
|
159
|
+
process.stderr.write(`OK: ${notes.join("; ")}.\n`);
|
|
160
|
+
}
|
|
161
|
+
else if (mode === "fp") {
|
|
174
162
|
process.stderr.write("OK: no closing-keyword negation/quotation/example/code-block hits found.\n");
|
|
175
163
|
}
|
|
164
|
+
else if (mode === "intent") {
|
|
165
|
+
process.stderr.write("OK: no unallowlisted closing-keyword hits (intent mode; #3015).\n");
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
process.stderr.write("OK: no closing-keyword FP or unallowlisted intent hits found (#737 / #3015).\n");
|
|
169
|
+
}
|
|
176
170
|
return EXIT_OK;
|
|
177
171
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
172
|
+
if (fpFiltered.length > 0) {
|
|
173
|
+
process.stderr.write(`FAIL: ${fpFiltered.length} closing-keyword negation-context hit(s) found (FP mode / #737). ` +
|
|
174
|
+
"Rewrite the PR body / commit messages to avoid the trigger token, or pass " +
|
|
175
|
+
"--allow-known-false-positives to suppress known-safe quotes.\n");
|
|
176
|
+
for (const h of fpFiltered) {
|
|
177
|
+
process.stderr.write(`${renderHit(h)}\n`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (intentFiltered.length > 0) {
|
|
181
|
+
process.stderr.write(`FAIL: ${intentFiltered.length} real closing-keyword hit(s) without allowlist (intent mode / #3015 class D). ` +
|
|
182
|
+
"Default PR bodies use Tracking: #N / Related: #N / Refs #N. Use Closes/Fixes/Resolves only when full " +
|
|
183
|
+
"issue DoD is met, then pass --allow-close <N,M> on the lint (body trailers are not an authorization path). " +
|
|
184
|
+
"Conditional prose (Phase A / only if / partial) does NOT prevent GitHub auto-close.\n");
|
|
185
|
+
for (const h of intentFiltered) {
|
|
186
|
+
process.stderr.write(`${renderHit(h)}\n`);
|
|
187
|
+
}
|
|
181
188
|
}
|
|
182
189
|
return EXIT_HITS_FOUND;
|
|
183
190
|
}
|
|
@@ -187,9 +194,11 @@ export function run(argv, options = {}) {
|
|
|
187
194
|
process.stderr.write(`Error: ${args.error}\n`);
|
|
188
195
|
return EXIT_CONFIG_ERROR;
|
|
189
196
|
}
|
|
190
|
-
let
|
|
197
|
+
let fpAllow;
|
|
198
|
+
let closeAllow;
|
|
191
199
|
try {
|
|
192
|
-
|
|
200
|
+
fpAllow = parseAllowList(args.allowKnownFalsePositives);
|
|
201
|
+
closeAllow = parseAllowList(args.allowClose);
|
|
193
202
|
}
|
|
194
203
|
catch (exc) {
|
|
195
204
|
const message = exc instanceof Error ? exc.message : String(exc);
|
|
@@ -230,15 +239,35 @@ export function run(argv, options = {}) {
|
|
|
230
239
|
commitMessages = msgs;
|
|
231
240
|
}
|
|
232
241
|
}
|
|
233
|
-
const
|
|
242
|
+
const runFp = args.mode === "fp" || args.mode === "both";
|
|
243
|
+
const runIntent = args.mode === "intent" || args.mode === "both";
|
|
244
|
+
const fpHits = [];
|
|
245
|
+
const intentHits = [];
|
|
234
246
|
if (bodyText !== null) {
|
|
235
|
-
|
|
247
|
+
if (runFp) {
|
|
248
|
+
fpHits.push(...findHits(bodyText, "pr-body"));
|
|
249
|
+
}
|
|
250
|
+
if (runIntent) {
|
|
251
|
+
// Intent = class D only (bare/conditional real closes). Class A FP-context
|
|
252
|
+
// hits (negation/quote/example/code) stay exclusive to FP mode so operators
|
|
253
|
+
// do not need --allow-close for quoted fixtures (#3015 / SLizard).
|
|
254
|
+
intentHits.push(...findAllClosingKeywordHits(bodyText, "pr-body").filter((h) => h.reason === "intent"));
|
|
255
|
+
}
|
|
236
256
|
}
|
|
237
257
|
for (let idx = 0; idx < commitMessages.length; idx += 1) {
|
|
238
|
-
|
|
258
|
+
const msg = commitMessages[idx] ?? "";
|
|
259
|
+
if (runFp) {
|
|
260
|
+
fpHits.push(...findHits(msg, `commit:${idx}`));
|
|
261
|
+
}
|
|
262
|
+
if (runIntent) {
|
|
263
|
+
intentHits.push(...findAllClosingKeywordHits(msg, `commit:${idx}`).filter((h) => h.reason === "intent"));
|
|
264
|
+
}
|
|
239
265
|
}
|
|
240
|
-
|
|
241
|
-
|
|
266
|
+
// Intent allowlist is CLI --allow-close only (#3015). Body trailers are not
|
|
267
|
+
// an authorization path (Markdown example/fence false-authorization class).
|
|
268
|
+
const fpFiltered = filterHits(fpHits, fpAllow);
|
|
269
|
+
const intentFiltered = filterHits(intentHits, closeAllow);
|
|
270
|
+
return emitResult(args.mode, fpFiltered, intentFiltered, fpHits.length - fpFiltered.length, intentHits.length - intentFiltered.length);
|
|
242
271
|
}
|
|
243
272
|
export function cmdPrCheckClosingKeywords(argv, options = {}) {
|
|
244
273
|
return run(argv, options);
|
|
@@ -12,12 +12,22 @@ export interface RunGhResult {
|
|
|
12
12
|
}
|
|
13
13
|
/** Injectable gh subprocess seam (#1366 / parity harness). */
|
|
14
14
|
export type RunGhFn = (cmd: readonly string[]) => RunGhResult;
|
|
15
|
+
/** Lint mode for closing-keyword checks (#3015). */
|
|
16
|
+
export type ClosingKeywordMode = "fp" | "intent" | "both";
|
|
15
17
|
export interface ParsedArgs {
|
|
16
18
|
readonly pr: number | null;
|
|
17
19
|
readonly bodyFile: string | null;
|
|
18
20
|
readonly commitsFile: string | null;
|
|
19
21
|
readonly repo: string | null;
|
|
20
22
|
readonly allowKnownFalsePositives: readonly string[];
|
|
23
|
+
/** Intent-mode allowlist of issue numbers permitted to use real closing keywords (#3015). */
|
|
24
|
+
readonly allowClose: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* fp = Layer 0 #737 (negation/quote/example/code only);
|
|
27
|
+
* intent = any closing keyword unless allowlisted (#3015 class D);
|
|
28
|
+
* both = default (FP + intent).
|
|
29
|
+
*/
|
|
30
|
+
readonly mode: ClosingKeywordMode;
|
|
21
31
|
readonly error?: string;
|
|
22
32
|
}
|
|
23
33
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -2,11 +2,27 @@
|
|
|
2
2
|
export declare const VALID_STATUSES: Set<string>;
|
|
3
3
|
export declare const VALID_VBRIEF_VERSIONS: Set<string>;
|
|
4
4
|
export declare const SPECIFICATION_NARRATIVE_KEY_ORDER: readonly ["Overview", "ProblemStatement", "Goals", "UserStories", "Requirements", "SuccessMetrics", "EdgeCases", "Architecture", "TechDecisions", "ImplementationPhases", "PreImplementationGates"];
|
|
5
|
+
/** Migration capture narrative; omitted from default compact SPECIFICATION.md (#1566). */
|
|
6
|
+
export declare const LEGACY_ARTIFACTS_NARRATIVE_KEY = "LegacyArtifacts";
|
|
7
|
+
/**
|
|
8
|
+
* Default lifecycle scope aggregation for `task spec:render` / `renderSpec` (#1566).
|
|
9
|
+
* `off` = product narratives only; opt in with `current` (pending+active) or `all`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_INCLUDE_SCOPES_MODE: "off";
|
|
12
|
+
/** Lifecycle scope aggregation modes for rendered specs (#1566). */
|
|
13
|
+
export type IncludeScopesMode = "off" | "current" | "all";
|
|
5
14
|
export declare const LIFECYCLE_BUCKETS: ReadonlyArray<readonly [string, string]>;
|
|
6
15
|
export declare const SPEC_RENDER_BANNER: string;
|
|
7
16
|
export declare const PRD_NARRATIVE_KEY_ORDER: readonly ["Overview", "ProblemStatement", "Goals", "UserStories", "Requirements", "SuccessMetrics", "Architecture"];
|
|
8
17
|
export declare const PRD_BANNER: string;
|
|
9
18
|
export declare const PRD_GENERATED_SENTINEL = "AUTO-GENERATED by task prd:render";
|
|
19
|
+
/** Max completed scopes listed under ## Completed in ROADMAP.md (#2653). */
|
|
20
|
+
export declare const ROADMAP_COMPLETED_CAP = 25;
|
|
21
|
+
/**
|
|
22
|
+
* Shown when completed/ has history but pending/, active/, and proposed/ are empty.
|
|
23
|
+
* Prevents silent Completed-only output without documenting empty-forward (#2653 D2.2).
|
|
24
|
+
*/
|
|
25
|
+
export declare const ROADMAP_EMPTY_FORWARD_MARKER: string;
|
|
10
26
|
export declare const ROADMAP_BANNER: string;
|
|
11
27
|
export declare const MIGRATOR_METADATA_KEY = "x-migrator";
|
|
12
28
|
export declare const PROJECT_LIFECYCLE_FOLDERS: readonly ["proposed", "pending", "active", "completed", "cancelled"];
|
package/dist/render/constants.js
CHANGED
|
@@ -24,6 +24,13 @@ export const SPECIFICATION_NARRATIVE_KEY_ORDER = [
|
|
|
24
24
|
"ImplementationPhases",
|
|
25
25
|
"PreImplementationGates",
|
|
26
26
|
];
|
|
27
|
+
/** Migration capture narrative; omitted from default compact SPECIFICATION.md (#1566). */
|
|
28
|
+
export const LEGACY_ARTIFACTS_NARRATIVE_KEY = "LegacyArtifacts";
|
|
29
|
+
/**
|
|
30
|
+
* Default lifecycle scope aggregation for `task spec:render` / `renderSpec` (#1566).
|
|
31
|
+
* `off` = product narratives only; opt in with `current` (pending+active) or `all`.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_INCLUDE_SCOPES_MODE = "off";
|
|
27
34
|
export const LIFECYCLE_BUCKETS = [
|
|
28
35
|
["pending", "Pending"],
|
|
29
36
|
["active", "Active"],
|
|
@@ -47,9 +54,17 @@ export const PRD_BANNER = "<!-- AUTO-GENERATED by task prd:render -- DO NOT EDIT
|
|
|
47
54
|
"<!-- Source of truth: vbrief/specification.vbrief.json -->\n" +
|
|
48
55
|
"<!-- Regenerate with: task prd:render -->\n";
|
|
49
56
|
export const PRD_GENERATED_SENTINEL = "AUTO-GENERATED by task prd:render";
|
|
57
|
+
/** Max completed scopes listed under ## Completed in ROADMAP.md (#2653). */
|
|
58
|
+
export const ROADMAP_COMPLETED_CAP = 25;
|
|
59
|
+
/**
|
|
60
|
+
* Shown when completed/ has history but pending/, active/, and proposed/ are empty.
|
|
61
|
+
* Prevents silent Completed-only output without documenting empty-forward (#2653 D2.2).
|
|
62
|
+
*/
|
|
63
|
+
export const ROADMAP_EMPTY_FORWARD_MARKER = "_No open work in `pending/`, `active/`, or `proposed/`. " +
|
|
64
|
+
"ROADMAP is an orientation view — use `task triage:queue` or plan-sequence for work selection._\n";
|
|
50
65
|
export const ROADMAP_BANNER = "<!-- AUTO-GENERATED by task roadmap:render -- DO NOT EDIT MANUALLY -->\n" +
|
|
51
66
|
"<!-- Purpose: rendered roadmap -->\n" +
|
|
52
|
-
"<!-- Source of truth: vbrief
|
|
67
|
+
"<!-- Source of truth: xbrief|vbrief pending/ + proposed/ + active/ (forward); completed/ capped -->\n" +
|
|
53
68
|
"<!-- Regenerate with: task roadmap:render -->\n";
|
|
54
69
|
export const MIGRATOR_METADATA_KEY = "x-migrator";
|
|
55
70
|
export const PROJECT_LIFECYCLE_FOLDERS = [
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { type IncludeScopesMode } from "./constants.js";
|
|
1
2
|
export type ExportAudience = "stakeholder" | "internal";
|
|
2
3
|
export interface ExportSpecOptions {
|
|
3
4
|
readonly projectRoot?: string;
|
|
4
5
|
readonly outPath?: string;
|
|
5
6
|
readonly audience?: ExportAudience;
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Lifecycle scope aggregation (#1566). Default `off` (compact).
|
|
9
|
+
* `current` = pending+active; `all` / true = include completed archive.
|
|
10
|
+
*/
|
|
11
|
+
readonly includeScopes?: boolean | IncludeScopesMode;
|
|
12
|
+
readonly includeLegacyArtifacts?: boolean;
|
|
7
13
|
readonly proposedLimit?: number;
|
|
8
14
|
}
|
|
9
15
|
export type ExportSpecResult = readonly [boolean, string];
|
|
@@ -2,8 +2,11 @@ import { readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { greenfieldOverviewNonEmpty, renderNarrativeSections, resolveExportNarratives, } from "../spec-authority/narratives.js";
|
|
4
4
|
import { resolveSpecAuthority } from "../spec-authority/resolver.js";
|
|
5
|
+
import { LEGACY_ARTIFACTS_NARRATIVE_KEY } from "./constants.js";
|
|
5
6
|
import { buildScopeOutlookSection } from "./scope-outlook.js";
|
|
7
|
+
import { normalizeIncludeScopesMode, tryParseIncludeScopesMode, tryParseOnOffFlag, } from "./spec-render.js";
|
|
6
8
|
import { validateSpec } from "./spec-validate.js";
|
|
9
|
+
import { stripTrailingWhitespace } from "./text-utils.js";
|
|
7
10
|
function loadPlanTitle(path, fallback) {
|
|
8
11
|
try {
|
|
9
12
|
const doc = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -17,12 +20,24 @@ function loadPlanTitle(path, fallback) {
|
|
|
17
20
|
}
|
|
18
21
|
return fallback;
|
|
19
22
|
}
|
|
23
|
+
function filterLegacyArtifacts(narratives, includeLegacyArtifacts) {
|
|
24
|
+
if (includeLegacyArtifacts)
|
|
25
|
+
return narratives;
|
|
26
|
+
const filtered = {};
|
|
27
|
+
for (const [key, val] of Object.entries(narratives)) {
|
|
28
|
+
if (key === LEGACY_ARTIFACTS_NARRATIVE_KEY)
|
|
29
|
+
continue;
|
|
30
|
+
filtered[key] = val;
|
|
31
|
+
}
|
|
32
|
+
return filtered;
|
|
33
|
+
}
|
|
20
34
|
/** Unified spec export (#2013 / #1502). */
|
|
21
35
|
export function exportSpec(options = {}) {
|
|
22
36
|
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
23
37
|
const outPath = options.outPath ?? join(projectRoot, "SPECIFICATION.md");
|
|
24
38
|
const audience = options.audience ?? "stakeholder";
|
|
25
|
-
const
|
|
39
|
+
const includeScopesMode = normalizeIncludeScopesMode(options.includeScopes);
|
|
40
|
+
const includeLegacyArtifacts = options.includeLegacyArtifacts ?? false;
|
|
26
41
|
const includeProposed = audience === "internal";
|
|
27
42
|
const authority = resolveSpecAuthority(projectRoot);
|
|
28
43
|
if (!authority) {
|
|
@@ -39,7 +54,7 @@ export function exportSpec(options = {}) {
|
|
|
39
54
|
"⚠ PROJECT-DEFINITION.xbrief.json Overview narrative is empty (D3). Populate Overview before export.",
|
|
40
55
|
];
|
|
41
56
|
}
|
|
42
|
-
const narratives = resolveExportNarratives(authority);
|
|
57
|
+
const narratives = filterLegacyArtifacts(resolveExportNarratives(authority), includeLegacyArtifacts);
|
|
43
58
|
const title = authority.kind === "full-spec" && authority.specPath
|
|
44
59
|
? loadPlanTitle(authority.specPath, "Specification")
|
|
45
60
|
: loadPlanTitle(authority.projectDefPath, "Specification");
|
|
@@ -48,15 +63,16 @@ export function exportSpec(options = {}) {
|
|
|
48
63
|
`# ${title}\n`,
|
|
49
64
|
...renderNarrativeSections(narratives),
|
|
50
65
|
];
|
|
51
|
-
if (
|
|
66
|
+
if (includeScopesMode !== "off") {
|
|
52
67
|
const scopeLines = buildScopeOutlookSection(authority.vbriefDir, {
|
|
53
68
|
includeProposed,
|
|
54
69
|
proposedLimit: options.proposedLimit,
|
|
70
|
+
includeCompleted: includeScopesMode === "all",
|
|
55
71
|
});
|
|
56
72
|
if (scopeLines.length > 0)
|
|
57
73
|
lines.push(...scopeLines);
|
|
58
74
|
}
|
|
59
|
-
writeFileSync(outPath, lines.join("\n"), "utf8");
|
|
75
|
+
writeFileSync(outPath, stripTrailingWhitespace(lines.join("\n")), "utf8");
|
|
60
76
|
return [true, `✓ Exported spec to ${outPath}`];
|
|
61
77
|
}
|
|
62
78
|
export function parseExportSpecArgv(argv) {
|
|
@@ -75,7 +91,37 @@ export function parseExportSpecArgv(argv) {
|
|
|
75
91
|
continue;
|
|
76
92
|
}
|
|
77
93
|
if (arg === "--no-scopes") {
|
|
78
|
-
options.includeScopes =
|
|
94
|
+
options.includeScopes = "off";
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg === "--include-scopes") {
|
|
98
|
+
options.includeScopes = "all";
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (arg.startsWith("--include-scopes=")) {
|
|
102
|
+
const value = arg.split("=", 2)[1] ?? "";
|
|
103
|
+
const parsed = tryParseIncludeScopesMode(value);
|
|
104
|
+
if (parsed === undefined) {
|
|
105
|
+
errors.push(`Invalid --include-scopes=${value} (expected off|current|all|active|on|true|1|yes|false|0|no)`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
options.includeScopes = parsed;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (arg === "--include-legacy-artifacts") {
|
|
113
|
+
options.includeLegacyArtifacts = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (arg.startsWith("--include-legacy-artifacts=")) {
|
|
117
|
+
const value = arg.split("=", 2)[1] ?? "";
|
|
118
|
+
const parsed = tryParseOnOffFlag(value);
|
|
119
|
+
if (parsed === undefined) {
|
|
120
|
+
errors.push(`Invalid --include-legacy-artifacts=${value} (expected on|off|true|false|1|0|yes|no)`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
options.includeLegacyArtifacts = parsed;
|
|
124
|
+
}
|
|
79
125
|
continue;
|
|
80
126
|
}
|
|
81
127
|
if (arg.startsWith("--")) {
|
package/dist/render/index.d.ts
CHANGED
|
@@ -10,9 +10,10 @@ export * as roadmapRender from "./roadmap-render.js";
|
|
|
10
10
|
export { checkDrift, generateRoadmapContent, main as roadmapRenderMain, renderRoadmap, renderRoadmapToBuffer, } from "./roadmap-render.js";
|
|
11
11
|
export { main as ruleMapMain } from "./rule-map.js";
|
|
12
12
|
export { aggregateScopeSection, buildScopeOutlookSection } from "./scope-outlook.js";
|
|
13
|
+
export type { RenderSpecOptions } from "./spec-render.js";
|
|
13
14
|
export * as specRender from "./spec-render.js";
|
|
14
|
-
export { main as specRenderMain, parseIncludeScopesFlag, renderSpec } from "./spec-render.js";
|
|
15
|
+
export { main as specRenderMain, normalizeIncludeScopesMode, parseIncludeScopesFlag, renderSpec, tryParseIncludeScopesMode, tryParseOnOffFlag, } from "./spec-render.js";
|
|
15
16
|
export * as specValidate from "./spec-validate.js";
|
|
16
17
|
export { main as specValidateMain, validateSpec } from "./spec-validate.js";
|
|
17
|
-
export { parsePhaseNumber, phaseSortKey, splitCamel, splitWords } from "./text-utils.js";
|
|
18
|
+
export { parsePhaseNumber, phaseSortKey, splitCamel, splitWords, stripTrailingWhitespace, } from "./text-utils.js";
|
|
18
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/render/index.js
CHANGED
|
@@ -11,8 +11,8 @@ export { checkDrift, generateRoadmapContent, main as roadmapRenderMain, renderRo
|
|
|
11
11
|
export { main as ruleMapMain } from "./rule-map.js";
|
|
12
12
|
export { aggregateScopeSection, buildScopeOutlookSection } from "./scope-outlook.js";
|
|
13
13
|
export * as specRender from "./spec-render.js";
|
|
14
|
-
export { main as specRenderMain, parseIncludeScopesFlag, renderSpec } from "./spec-render.js";
|
|
14
|
+
export { main as specRenderMain, normalizeIncludeScopesMode, parseIncludeScopesFlag, renderSpec, tryParseIncludeScopesMode, tryParseOnOffFlag, } from "./spec-render.js";
|
|
15
15
|
export * as specValidate from "./spec-validate.js";
|
|
16
16
|
export { main as specValidateMain, validateSpec } from "./spec-validate.js";
|
|
17
|
-
export { parsePhaseNumber, phaseSortKey, splitCamel, splitWords } from "./text-utils.js";
|
|
17
|
+
export { parsePhaseNumber, phaseSortKey, splitCamel, splitWords, stripTrailingWhitespace, } from "./text-utils.js";
|
|
18
18
|
//# sourceMappingURL=index.js.map
|