specdo 1.0.2
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/CHANGELOG.md +139 -0
- package/README.md +308 -0
- package/README.zh-CN.md +306 -0
- package/bin/specdo.js +3 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +297 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/_shared.d.ts +45 -0
- package/dist/commands/_shared.d.ts.map +1 -0
- package/dist/commands/_shared.js +124 -0
- package/dist/commands/_shared.js.map +1 -0
- package/dist/commands/apply.d.ts +30 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +393 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/archive.d.ts +25 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +362 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/doctor.d.ts +21 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +180 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/domains.d.ts +14 -0
- package/dist/commands/domains.d.ts.map +1 -0
- package/dist/commands/domains.js +107 -0
- package/dist/commands/domains.js.map +1 -0
- package/dist/commands/explore.d.ts +48 -0
- package/dist/commands/explore.d.ts.map +1 -0
- package/dist/commands/explore.js +378 -0
- package/dist/commands/explore.js.map +1 -0
- package/dist/commands/init.d.ts +45 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +243 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +23 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +135 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/propose.d.ts +22 -0
- package/dist/commands/propose.d.ts.map +1 -0
- package/dist/commands/propose.js +316 -0
- package/dist/commands/propose.js.map +1 -0
- package/dist/commands/show.d.ts +15 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +214 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +17 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +146 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +21 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +113 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/validate.d.ts +117 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +446 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/core/apply-brief-renderer.d.ts +35 -0
- package/dist/core/apply-brief-renderer.d.ts.map +1 -0
- package/dist/core/apply-brief-renderer.js +242 -0
- package/dist/core/apply-brief-renderer.js.map +1 -0
- package/dist/core/config-store.d.ts +190 -0
- package/dist/core/config-store.d.ts.map +1 -0
- package/dist/core/config-store.js +280 -0
- package/dist/core/config-store.js.map +1 -0
- package/dist/core/context-store.d.ts +96 -0
- package/dist/core/context-store.d.ts.map +1 -0
- package/dist/core/context-store.js +426 -0
- package/dist/core/context-store.js.map +1 -0
- package/dist/core/json-schemas.d.ts +349 -0
- package/dist/core/json-schemas.d.ts.map +1 -0
- package/dist/core/json-schemas.js +125 -0
- package/dist/core/json-schemas.js.map +1 -0
- package/dist/core/skill-content/cross-domain.d.ts +12 -0
- package/dist/core/skill-content/cross-domain.d.ts.map +1 -0
- package/dist/core/skill-content/cross-domain.js +291 -0
- package/dist/core/skill-content/cross-domain.js.map +1 -0
- package/dist/core/skill-content/protocol-examples.d.ts +13 -0
- package/dist/core/skill-content/protocol-examples.d.ts.map +1 -0
- package/dist/core/skill-content/protocol-examples.js +190 -0
- package/dist/core/skill-content/protocol-examples.js.map +1 -0
- package/dist/core/skill-content/workflow-content.d.ts +25 -0
- package/dist/core/skill-content/workflow-content.d.ts.map +1 -0
- package/dist/core/skill-content/workflow-content.js +1572 -0
- package/dist/core/skill-content/workflow-content.js.map +1 -0
- package/dist/core/skill-exporter.d.ts +186 -0
- package/dist/core/skill-exporter.d.ts.map +1 -0
- package/dist/core/skill-exporter.js +922 -0
- package/dist/core/skill-exporter.js.map +1 -0
- package/dist/core/spec-sync.d.ts +65 -0
- package/dist/core/spec-sync.d.ts.map +1 -0
- package/dist/core/spec-sync.js +226 -0
- package/dist/core/spec-sync.js.map +1 -0
- package/dist/core/task-parser.d.ts +58 -0
- package/dist/core/task-parser.d.ts.map +1 -0
- package/dist/core/task-parser.js +244 -0
- package/dist/core/task-parser.js.map +1 -0
- package/dist/core/template-renderer.d.ts +51 -0
- package/dist/core/template-renderer.d.ts.map +1 -0
- package/dist/core/template-renderer.js +362 -0
- package/dist/core/template-renderer.js.map +1 -0
- package/dist/domains/architecture.d.ts +34 -0
- package/dist/domains/architecture.d.ts.map +1 -0
- package/dist/domains/architecture.js +341 -0
- package/dist/domains/architecture.js.map +1 -0
- package/dist/domains/backend.d.ts +35 -0
- package/dist/domains/backend.d.ts.map +1 -0
- package/dist/domains/backend.js +367 -0
- package/dist/domains/backend.js.map +1 -0
- package/dist/domains/frontend.d.ts +36 -0
- package/dist/domains/frontend.d.ts.map +1 -0
- package/dist/domains/frontend.js +373 -0
- package/dist/domains/frontend.js.map +1 -0
- package/dist/domains/index.d.ts +49 -0
- package/dist/domains/index.d.ts.map +1 -0
- package/dist/domains/index.js +255 -0
- package/dist/domains/index.js.map +1 -0
- package/dist/domains/operations.d.ts +37 -0
- package/dist/domains/operations.d.ts.map +1 -0
- package/dist/domains/operations.js +344 -0
- package/dist/domains/operations.js.map +1 -0
- package/dist/domains/pool-ranking.d.ts +43 -0
- package/dist/domains/pool-ranking.d.ts.map +1 -0
- package/dist/domains/pool-ranking.js +153 -0
- package/dist/domains/pool-ranking.js.map +1 -0
- package/dist/domains/quality.d.ts +45 -0
- package/dist/domains/quality.d.ts.map +1 -0
- package/dist/domains/quality.js +368 -0
- package/dist/domains/quality.js.map +1 -0
- package/dist/domains/security.d.ts +19 -0
- package/dist/domains/security.d.ts.map +1 -0
- package/dist/domains/security.js +364 -0
- package/dist/domains/security.js.map +1 -0
- package/dist/domains/signal-match.d.ts +25 -0
- package/dist/domains/signal-match.d.ts.map +1 -0
- package/dist/domains/signal-match.js +67 -0
- package/dist/domains/signal-match.js.map +1 -0
- package/dist/domains/types.d.ts +354 -0
- package/dist/domains/types.d.ts.map +1 -0
- package/dist/domains/types.js +12 -0
- package/dist/domains/types.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/protocols/index.d.ts +36 -0
- package/dist/protocols/index.d.ts.map +1 -0
- package/dist/protocols/index.js +85 -0
- package/dist/protocols/index.js.map +1 -0
- package/dist/protocols/review-to-solid.d.ts +32 -0
- package/dist/protocols/review-to-solid.d.ts.map +1 -0
- package/dist/protocols/review-to-solid.js +309 -0
- package/dist/protocols/review-to-solid.js.map +1 -0
- package/dist/utils/prompt.d.ts +37 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +81 -0
- package/dist/utils/prompt.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-parser — tasks.md 唯一事实来源解析器
|
|
3
|
+
*
|
|
4
|
+
* 输入:tasks.md 全文文本
|
|
5
|
+
* 输出:结构化 ParsedTasks { tasks, warnings }
|
|
6
|
+
*
|
|
7
|
+
* 解析规则(与 phases/03-phase2-commands.md §task-parser 完全对齐):
|
|
8
|
+
*
|
|
9
|
+
* 1. 仅识别零缩进的顶层 checkbox:`- [ ] N. <title>`、`- [ ] N.M <title>` 或 `- [x] ...`
|
|
10
|
+
* (大小写 x 均视为已完成)
|
|
11
|
+
* 2. 编号必须是正整数路径(如 `1`、`2`、`1.1`、`2.3`),编号后空格分隔
|
|
12
|
+
* 3. 缩进 checkbox(子任务)忽略
|
|
13
|
+
* 4. 三反引号代码块内任何内容忽略
|
|
14
|
+
* 5. 无编号 checkbox `- [ ] Title` 加 warning 并跳过
|
|
15
|
+
* 6. 重复编号 → throw(exit code 1)
|
|
16
|
+
* 7. 编号缺口(1,2,4 缺 3)→ warning 但允许
|
|
17
|
+
* 8. 编号不从 1 开始 → warning 但允许
|
|
18
|
+
* 9. evidence:紧随任务行下方、缩进对齐的 `Evidence:` 行(含跨行延续)
|
|
19
|
+
*
|
|
20
|
+
* 该模块**只读**,不写回 tasks.md;写回由 commands/apply.ts 负责。
|
|
21
|
+
*/
|
|
22
|
+
const TOP_LEVEL_CHECKBOX_RE = /^- \[(?<mark>[ xX])\]\s+(?<rest>.+)$/;
|
|
23
|
+
const UNNUMBERED_HINT_RE = /^- \[[ xX]\]\s+/;
|
|
24
|
+
const NON_CANONICAL_NUMBERED_HINT_RE = /^(?<candidate>\d+(?:\.\d+)*)(?:\.)?\s+/;
|
|
25
|
+
const EVIDENCE_RE = /^(?<indent>\s+)Evidence:\s*(?<text>.*)$/;
|
|
26
|
+
const FENCE_RE = /^\s*```/;
|
|
27
|
+
const ROOT_TASK_RE = /^(?<num>[1-9]\d*)\.\s+(?<title>.+)$/;
|
|
28
|
+
const GROUPED_TASK_RE = /^(?<num>[1-9]\d*\.[1-9]\d*)\s+(?<title>.+)$/;
|
|
29
|
+
export function isCanonicalTaskReference(value) {
|
|
30
|
+
return /^(?:[1-9]\d*)(?:\.[1-9]\d*)?$/.test(value);
|
|
31
|
+
}
|
|
32
|
+
export function parseTasks(content) {
|
|
33
|
+
const lines = content.split(/\r?\n/);
|
|
34
|
+
const tasks = [];
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
let inFence = false;
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < lines.length) {
|
|
40
|
+
const line = lines[i] ?? '';
|
|
41
|
+
if (FENCE_RE.test(line)) {
|
|
42
|
+
inFence = !inFence;
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inFence) {
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const checkboxMatch = TOP_LEVEL_CHECKBOX_RE.exec(line);
|
|
51
|
+
if (!checkboxMatch || checkboxMatch.groups === undefined) {
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const restRaw = checkboxMatch.groups.rest ?? '';
|
|
56
|
+
const completed = (checkboxMatch.groups.mark ?? ' ').toLowerCase() === 'x';
|
|
57
|
+
const numberedMatch = parseTaskHeader(restRaw);
|
|
58
|
+
if (!numberedMatch || numberedMatch.groups === undefined) {
|
|
59
|
+
const nonCanonicalMatch = NON_CANONICAL_NUMBERED_HINT_RE.exec(restRaw);
|
|
60
|
+
if (nonCanonicalMatch?.groups?.candidate) {
|
|
61
|
+
warnings.push(`Line ${i + 1}: non-canonical task reference skipped (${nonCanonicalMatch.groups.candidate}); use "1" or "1.1" style numbering`);
|
|
62
|
+
}
|
|
63
|
+
else if (UNNUMBERED_HINT_RE.test(line)) {
|
|
64
|
+
// 无编号 checkbox:跳过并 warn
|
|
65
|
+
warnings.push(`Line ${i + 1}: unnumbered checkbox skipped (only "- [ ] N. <title>" is recognized)`);
|
|
66
|
+
}
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const number = numberedMatch.groups.num ?? '0';
|
|
71
|
+
const title = (numberedMatch.groups.title ?? '').trim();
|
|
72
|
+
if (seen.has(number)) {
|
|
73
|
+
throw new Error(`Duplicate task number ${number} in tasks.md`);
|
|
74
|
+
}
|
|
75
|
+
seen.add(number);
|
|
76
|
+
const taskLineIndex = i;
|
|
77
|
+
let evidence = null;
|
|
78
|
+
let evidenceLineIndex = null;
|
|
79
|
+
let j = i + 1;
|
|
80
|
+
const immediateNext = lines[j] ?? '';
|
|
81
|
+
const evidenceMatch = EVIDENCE_RE.exec(immediateNext);
|
|
82
|
+
if (evidenceMatch?.groups) {
|
|
83
|
+
evidence = (evidenceMatch.groups.text ?? '').trim();
|
|
84
|
+
evidenceLineIndex = j;
|
|
85
|
+
const evidenceIndent = evidenceMatch.groups.indent?.length ?? 0;
|
|
86
|
+
let k = j + 1;
|
|
87
|
+
while (k < lines.length) {
|
|
88
|
+
const cont = lines[k] ?? '';
|
|
89
|
+
if (TOP_LEVEL_CHECKBOX_RE.test(cont))
|
|
90
|
+
break;
|
|
91
|
+
if (FENCE_RE.test(cont))
|
|
92
|
+
break;
|
|
93
|
+
if (cont.trim().length === 0)
|
|
94
|
+
break;
|
|
95
|
+
const contIndent = leadingWhitespaceWidth(cont);
|
|
96
|
+
if (contIndent <= evidenceIndent)
|
|
97
|
+
break;
|
|
98
|
+
evidence = `${evidence ?? ''}\n${cont.trim()}`.trim();
|
|
99
|
+
k++;
|
|
100
|
+
}
|
|
101
|
+
j = k;
|
|
102
|
+
}
|
|
103
|
+
tasks.push({ number, title, completed, lineIndex: taskLineIndex, evidence, evidenceLineIndex });
|
|
104
|
+
i = j > i ? j : i + 1;
|
|
105
|
+
}
|
|
106
|
+
// 编号合规性 warning
|
|
107
|
+
if (tasks.length > 0) {
|
|
108
|
+
const sorted = [...tasks].sort((a, b) => compareTaskNumberParts(parseTaskNumber(a.number), parseTaskNumber(b.number)));
|
|
109
|
+
warnings.push(...buildNumberingWarnings(sorted.map((task) => task.number)));
|
|
110
|
+
}
|
|
111
|
+
return { tasks, warnings };
|
|
112
|
+
}
|
|
113
|
+
/** 判断给定 N 是否为合法任务编号(用于 --done/--undo 越界检查) */
|
|
114
|
+
export function findTaskByNumber(parsed, n) {
|
|
115
|
+
return parsed.tasks.find((t) => t.number === n);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 将一个任务在原文件中标记为 完成 / 未完成,并同步写入或删除 evidence。
|
|
119
|
+
*
|
|
120
|
+
* 返回新内容字符串;调用方负责写盘。
|
|
121
|
+
*
|
|
122
|
+
* 行为:
|
|
123
|
+
* - completed = true 且提供 evidence:勾选 [x] 并在任务行下插入 ` Evidence: ...`
|
|
124
|
+
* - completed = false:取消勾选 [x] 改回 [ ],并删除已存在的 evidence 行
|
|
125
|
+
* - 修改时尽量保留原文件其他行(缩进、空行、注释)不变
|
|
126
|
+
*/
|
|
127
|
+
export function applyTaskMutation(content, task, next) {
|
|
128
|
+
const lines = content.split(/\r?\n/);
|
|
129
|
+
const lineEnding = content.includes('\r\n') ? '\r\n' : '\n';
|
|
130
|
+
// 1. 改写 checkbox
|
|
131
|
+
const original = lines[task.lineIndex] ?? '';
|
|
132
|
+
const updated = next.completed
|
|
133
|
+
? original.replace(/^- \[[ xX]\]/, '- [x]')
|
|
134
|
+
: original.replace(/^- \[[ xX]\]/, '- [ ]');
|
|
135
|
+
lines[task.lineIndex] = updated;
|
|
136
|
+
// 2. 删除已有 evidence(如有)
|
|
137
|
+
if (task.evidenceLineIndex !== null) {
|
|
138
|
+
const removeStart = task.evidenceLineIndex;
|
|
139
|
+
const evidenceLine = lines[removeStart] ?? '';
|
|
140
|
+
const evidenceIndent = leadingWhitespaceWidth(evidenceLine);
|
|
141
|
+
let removeEnd = removeStart + 1;
|
|
142
|
+
while (removeEnd < lines.length) {
|
|
143
|
+
const cand = lines[removeEnd] ?? '';
|
|
144
|
+
if (TOP_LEVEL_CHECKBOX_RE.test(cand))
|
|
145
|
+
break;
|
|
146
|
+
if (FENCE_RE.test(cand))
|
|
147
|
+
break;
|
|
148
|
+
if (cand.trim().length === 0)
|
|
149
|
+
break;
|
|
150
|
+
if (leadingWhitespaceWidth(cand) <= evidenceIndent)
|
|
151
|
+
break;
|
|
152
|
+
removeEnd++;
|
|
153
|
+
}
|
|
154
|
+
lines.splice(removeStart, removeEnd - removeStart);
|
|
155
|
+
}
|
|
156
|
+
// 3. 写入新 evidence(仅当 completed 且 evidence 非空)
|
|
157
|
+
if (next.completed && next.evidence !== undefined && next.evidence !== null && next.evidence.trim().length > 0) {
|
|
158
|
+
const evidenceLines = next.evidence
|
|
159
|
+
.split(/\r?\n/)
|
|
160
|
+
.map((seg, idx) => (idx === 0 ? ` Evidence: ${seg}` : ` ${seg}`));
|
|
161
|
+
const insertAt = task.lineIndex + 1;
|
|
162
|
+
lines.splice(insertAt, 0, ...evidenceLines);
|
|
163
|
+
}
|
|
164
|
+
return lines.join(lineEnding);
|
|
165
|
+
}
|
|
166
|
+
function leadingWhitespaceWidth(line) {
|
|
167
|
+
const match = /^\s*/.exec(line);
|
|
168
|
+
return match?.[0].length ?? 0;
|
|
169
|
+
}
|
|
170
|
+
function buildNumberingWarnings(numbers) {
|
|
171
|
+
if (numbers.length === 0)
|
|
172
|
+
return [];
|
|
173
|
+
const warnings = [];
|
|
174
|
+
const parsed = [...numbers]
|
|
175
|
+
.map((value) => ({ value, parts: parseTaskNumber(value) }))
|
|
176
|
+
.sort((a, b) => compareTaskNumberParts(a.parts, b.parts));
|
|
177
|
+
const hasGrouped = parsed.some((entry) => entry.parts.length > 1);
|
|
178
|
+
if (!hasGrouped) {
|
|
179
|
+
const ints = parsed.map((entry) => entry.parts[0] ?? 0);
|
|
180
|
+
if ((ints[0] ?? 0) !== 1) {
|
|
181
|
+
warnings.push(`Task numbering does not start at 1 (first number = ${parsed[0]?.value ?? 'n/a'})`);
|
|
182
|
+
}
|
|
183
|
+
for (let index = 0; index < ints.length - 1; index++) {
|
|
184
|
+
const cur = ints[index] ?? 0;
|
|
185
|
+
const nxt = ints[index + 1] ?? 0;
|
|
186
|
+
if (nxt - cur > 1) {
|
|
187
|
+
warnings.push(`Task numbering has a gap between ${cur} and ${nxt}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return warnings;
|
|
191
|
+
}
|
|
192
|
+
const roots = [...new Set(parsed.map((entry) => entry.parts[0] ?? 0))].sort((a, b) => a - b);
|
|
193
|
+
if ((roots[0] ?? 0) !== 1) {
|
|
194
|
+
warnings.push(`Task numbering does not start at 1 (first group = ${roots[0] ?? 'n/a'})`);
|
|
195
|
+
}
|
|
196
|
+
for (let index = 0; index < roots.length - 1; index++) {
|
|
197
|
+
const cur = roots[index] ?? 0;
|
|
198
|
+
const nxt = roots[index + 1] ?? 0;
|
|
199
|
+
if (nxt - cur > 1) {
|
|
200
|
+
warnings.push(`Task group numbering has a gap between ${cur} and ${nxt}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const childrenByRoot = new Map();
|
|
204
|
+
for (const entry of parsed) {
|
|
205
|
+
if (entry.parts.length < 2)
|
|
206
|
+
continue;
|
|
207
|
+
const root = entry.parts[0] ?? 0;
|
|
208
|
+
const child = entry.parts[1] ?? 0;
|
|
209
|
+
const current = childrenByRoot.get(root) ?? [];
|
|
210
|
+
current.push(child);
|
|
211
|
+
childrenByRoot.set(root, current);
|
|
212
|
+
}
|
|
213
|
+
for (const [root, childNumbers] of childrenByRoot.entries()) {
|
|
214
|
+
const sortedChildren = [...new Set(childNumbers)].sort((a, b) => a - b);
|
|
215
|
+
if ((sortedChildren[0] ?? 0) !== 1) {
|
|
216
|
+
warnings.push(`Task numbering inside group ${root} does not start at 1 (first task = ${root}.${sortedChildren[0] ?? 'n/a'})`);
|
|
217
|
+
}
|
|
218
|
+
for (let index = 0; index < sortedChildren.length - 1; index++) {
|
|
219
|
+
const cur = sortedChildren[index] ?? 0;
|
|
220
|
+
const nxt = sortedChildren[index + 1] ?? 0;
|
|
221
|
+
if (nxt - cur > 1) {
|
|
222
|
+
warnings.push(`Task numbering has a gap between ${root}.${cur} and ${root}.${nxt}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return warnings;
|
|
227
|
+
}
|
|
228
|
+
function parseTaskNumber(value) {
|
|
229
|
+
return value.split('.').map((part) => Number.parseInt(part, 10));
|
|
230
|
+
}
|
|
231
|
+
function compareTaskNumberParts(left, right) {
|
|
232
|
+
const max = Math.max(left.length, right.length);
|
|
233
|
+
for (let index = 0; index < max; index++) {
|
|
234
|
+
const l = left[index] ?? 0;
|
|
235
|
+
const r = right[index] ?? 0;
|
|
236
|
+
if (l !== r)
|
|
237
|
+
return l - r;
|
|
238
|
+
}
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
function parseTaskHeader(rest) {
|
|
242
|
+
return GROUPED_TASK_RE.exec(rest) ?? ROOT_TASK_RE.exec(rest);
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=task-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-parser.js","sourceRoot":"","sources":["../../src/core/task-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* template-renderer — propose 阶段 4 个 artifact 模板生成
|
|
3
|
+
*
|
|
4
|
+
* 当前版本对齐 OpenSpec core artifact 语义:
|
|
5
|
+
* - proposal.md: Why / What Changes / Capabilities / Impact
|
|
6
|
+
* - design.md: Context / Goals / Non-Goals / Decisions / Risks / Trade-offs
|
|
7
|
+
* - tasks.md: grouped task sections + execution-ready task refs (1.1 / 1.2)
|
|
8
|
+
* - specs/<capability>/spec.md: ADDED Requirements / Requirement / Scenario
|
|
9
|
+
*
|
|
10
|
+
* SpecDo 的增强内容仍然存在,但只能注入到这些标准骨架内。
|
|
11
|
+
*/
|
|
12
|
+
import type { DomainModule } from '../domains/types.js';
|
|
13
|
+
export interface ProposedCapability {
|
|
14
|
+
name: string;
|
|
15
|
+
kind: 'new' | 'modified';
|
|
16
|
+
summary: string;
|
|
17
|
+
rationale?: string;
|
|
18
|
+
domains: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface RenderInput {
|
|
21
|
+
changeName: string;
|
|
22
|
+
idea: string;
|
|
23
|
+
domains: DomainModule[];
|
|
24
|
+
answers: Record<string, string>;
|
|
25
|
+
flaggedConcerns: string[];
|
|
26
|
+
generatedAt: string;
|
|
27
|
+
capabilities: ProposedCapability[];
|
|
28
|
+
}
|
|
29
|
+
export interface RenderedArtifacts {
|
|
30
|
+
proposal: string;
|
|
31
|
+
specs: {
|
|
32
|
+
relativePath: string;
|
|
33
|
+
content: string;
|
|
34
|
+
}[];
|
|
35
|
+
design: string;
|
|
36
|
+
tasks: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function renderArtifacts(input: RenderInput): RenderedArtifacts;
|
|
39
|
+
export declare function renderProposal(input: RenderInput): string;
|
|
40
|
+
export declare function renderSpecs(input: RenderInput): {
|
|
41
|
+
relativePath: string;
|
|
42
|
+
content: string;
|
|
43
|
+
}[];
|
|
44
|
+
export declare function renderDesign(input: RenderInput): string;
|
|
45
|
+
export declare function renderTasks(input: RenderInput): string;
|
|
46
|
+
export declare function inferCapabilitiesFromChange(input: {
|
|
47
|
+
changeName: string;
|
|
48
|
+
idea: string;
|
|
49
|
+
domains: DomainModule[];
|
|
50
|
+
}): ProposedCapability[];
|
|
51
|
+
//# sourceMappingURL=template-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/core/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,GAAG,UAAU,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,iBAAiB,CAOrE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAsDzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CA+B3F;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA+DvD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAsBtD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB,GAAG,kBAAkB,EAAE,CAcvB"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* template-renderer — propose 阶段 4 个 artifact 模板生成
|
|
3
|
+
*
|
|
4
|
+
* 当前版本对齐 OpenSpec core artifact 语义:
|
|
5
|
+
* - proposal.md: Why / What Changes / Capabilities / Impact
|
|
6
|
+
* - design.md: Context / Goals / Non-Goals / Decisions / Risks / Trade-offs
|
|
7
|
+
* - tasks.md: grouped task sections + execution-ready task refs (1.1 / 1.2)
|
|
8
|
+
* - specs/<capability>/spec.md: ADDED Requirements / Requirement / Scenario
|
|
9
|
+
*
|
|
10
|
+
* SpecDo 的增强内容仍然存在,但只能注入到这些标准骨架内。
|
|
11
|
+
*/
|
|
12
|
+
export function renderArtifacts(input) {
|
|
13
|
+
return {
|
|
14
|
+
proposal: renderProposal(input),
|
|
15
|
+
specs: renderSpecs(input),
|
|
16
|
+
design: renderDesign(input),
|
|
17
|
+
tasks: renderTasks(input),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function renderProposal(input) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`# Proposal: ${input.changeName}`);
|
|
23
|
+
lines.push('');
|
|
24
|
+
lines.push(frontmatter(input, 'proposal'));
|
|
25
|
+
lines.push('');
|
|
26
|
+
lines.push('## Why');
|
|
27
|
+
lines.push('');
|
|
28
|
+
if (input.idea.trim().length > 0) {
|
|
29
|
+
lines.push(`- This change starts from the explored request: ${input.idea}`);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
lines.push('- <!-- Describe the user-visible or business motivation for this change. -->');
|
|
33
|
+
}
|
|
34
|
+
if (input.flaggedConcerns.length > 0) {
|
|
35
|
+
lines.push(`- Resolve the currently known concern(s): ${input.flaggedConcerns.join('; ')}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
lines.push('- <!-- Explain why the change is needed now and what problem it solves. -->');
|
|
39
|
+
}
|
|
40
|
+
lines.push('');
|
|
41
|
+
lines.push('## What Changes');
|
|
42
|
+
lines.push('');
|
|
43
|
+
for (const capability of input.capabilities) {
|
|
44
|
+
lines.push(`- ${capitalize(capability.kind)} capability \`${capability.name}\`: ${capability.summary}`);
|
|
45
|
+
}
|
|
46
|
+
lines.push('');
|
|
47
|
+
lines.push('## Capabilities');
|
|
48
|
+
lines.push('');
|
|
49
|
+
const newCapabilities = input.capabilities.filter((capability) => capability.kind === 'new');
|
|
50
|
+
const modifiedCapabilities = input.capabilities.filter((capability) => capability.kind === 'modified');
|
|
51
|
+
lines.push('### New Capabilities');
|
|
52
|
+
lines.push(...renderCapabilitiesSection(newCapabilities, 'new'));
|
|
53
|
+
lines.push('');
|
|
54
|
+
lines.push('### Modified Capabilities');
|
|
55
|
+
lines.push(...renderCapabilitiesSection(modifiedCapabilities, 'modified'));
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push('## Impact');
|
|
58
|
+
lines.push('');
|
|
59
|
+
lines.push(`- Affected artifacts: ${input.capabilities.map((capability) => `specs/${capability.name}/spec.md`).join(', ')}`);
|
|
60
|
+
lines.push(`- Matched domains: ${input.domains.length > 0 ? input.domains.map((domain) => domain.name).join(', ') : '(none)'}`);
|
|
61
|
+
if (input.flaggedConcerns.length > 0) {
|
|
62
|
+
for (const concern of input.flaggedConcerns) {
|
|
63
|
+
lines.push(`- Risk to resolve: ${concern}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (Object.keys(input.answers).length > 0) {
|
|
67
|
+
lines.push('- Explore answers that constrain implementation:');
|
|
68
|
+
for (const [key, value] of Object.entries(input.answers)) {
|
|
69
|
+
lines.push(` - ${key}: ${value || '_(empty)_'}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
lines.push('- <!-- Record affected systems, APIs, data, rollout, or user-surface impact. -->');
|
|
74
|
+
}
|
|
75
|
+
lines.push('');
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
export function renderSpecs(input) {
|
|
79
|
+
return input.capabilities.map((capability) => {
|
|
80
|
+
const lines = [];
|
|
81
|
+
lines.push(`# Delta for ${capability.name}`);
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push(frontmatter(input, 'spec', capability.name));
|
|
84
|
+
lines.push('');
|
|
85
|
+
lines.push(capability.kind === 'modified' ? '## MODIFIED Requirements' : '## ADDED Requirements');
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push(`### Requirement: ${requirementTitleFromCapability(capability)}`);
|
|
88
|
+
lines.push(`The system SHALL ${capability.summary.toLowerCase()}.`);
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push(`#### Scenario: ${scenarioTitleFromCapability(capability)}`);
|
|
91
|
+
lines.push('- **WHEN** the relevant user or system flow reaches this capability');
|
|
92
|
+
lines.push('- **THEN** the expected behavior SHALL match the approved proposal and design');
|
|
93
|
+
if (capability.rationale) {
|
|
94
|
+
lines.push(`- **AND** the implementation SHALL respect this rationale: ${capability.rationale}`);
|
|
95
|
+
}
|
|
96
|
+
const verifyItems = collectCapabilityVerificationItems(input.domains, capability.domains);
|
|
97
|
+
if (verifyItems.length > 0) {
|
|
98
|
+
lines.push('- **AND** the following verification concerns SHALL be covered:');
|
|
99
|
+
for (const item of verifyItems.slice(0, 4)) {
|
|
100
|
+
lines.push(` - ${item}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
lines.push('');
|
|
104
|
+
return {
|
|
105
|
+
relativePath: `specs/${capability.name}/spec.md`,
|
|
106
|
+
content: lines.join('\n'),
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export function renderDesign(input) {
|
|
111
|
+
const lines = [];
|
|
112
|
+
lines.push(`# Design: ${input.changeName}`);
|
|
113
|
+
lines.push('');
|
|
114
|
+
lines.push(frontmatter(input, 'design'));
|
|
115
|
+
lines.push('');
|
|
116
|
+
lines.push('## Context');
|
|
117
|
+
lines.push('');
|
|
118
|
+
if (input.idea.trim().length > 0) {
|
|
119
|
+
lines.push(`- Explored change request: ${input.idea}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push(`- Capability scope: ${input.capabilities.map((capability) => capability.name).join(', ')}`);
|
|
122
|
+
lines.push(`- Domain context: ${input.domains.length > 0 ? input.domains.map((domain) => domain.name).join(', ') : '(none)'}`);
|
|
123
|
+
if (Object.keys(input.answers).length > 0) {
|
|
124
|
+
lines.push('- Known design constraints from explore answers:');
|
|
125
|
+
for (const [key, value] of Object.entries(input.answers)) {
|
|
126
|
+
lines.push(` - ${key}: ${value || '_(empty)_'}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
lines.push('- <!-- Capture existing system shape, constraints, and relevant baseline behavior. -->');
|
|
131
|
+
}
|
|
132
|
+
lines.push('');
|
|
133
|
+
lines.push('## Goals / Non-Goals');
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push('**Goals:**');
|
|
136
|
+
lines.push(...input.capabilities.map((capability) => `- Deliver capability \`${capability.name}\` with clear requirement coverage.`));
|
|
137
|
+
lines.push('');
|
|
138
|
+
lines.push('**Non-Goals:**');
|
|
139
|
+
lines.push('- <!-- Record what this change intentionally does not solve. -->');
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push('## Decisions');
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push('### Capability Structure');
|
|
144
|
+
lines.push('');
|
|
145
|
+
for (const capability of input.capabilities) {
|
|
146
|
+
lines.push(`- \`${capability.name}\` is treated as a ${capability.kind} capability and renders to \`specs/${capability.name}/spec.md\`.`);
|
|
147
|
+
}
|
|
148
|
+
const decisionBlocks = collectDecisionBlocks(input.domains);
|
|
149
|
+
if (decisionBlocks.length > 0) {
|
|
150
|
+
for (const block of decisionBlocks) {
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push(`### ${block.title}`);
|
|
153
|
+
lines.push('');
|
|
154
|
+
for (const item of block.items) {
|
|
155
|
+
lines.push(`- ${item}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
lines.push('- <!-- Document technical decisions, interfaces, data flow, and responsibilities. -->');
|
|
161
|
+
}
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push('## Risks / Trade-offs');
|
|
164
|
+
lines.push('');
|
|
165
|
+
const risks = collectRiskItems(input.domains, input.flaggedConcerns);
|
|
166
|
+
if (risks.length > 0) {
|
|
167
|
+
for (const item of risks) {
|
|
168
|
+
lines.push(`- ${item}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
lines.push('- <!-- Capture open trade-offs, failure paths, rollout risks, and unknowns. -->');
|
|
173
|
+
}
|
|
174
|
+
lines.push('');
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
export function renderTasks(input) {
|
|
178
|
+
const lines = [];
|
|
179
|
+
lines.push(`# Tasks: ${input.changeName}`);
|
|
180
|
+
lines.push('');
|
|
181
|
+
lines.push(frontmatter(input, 'tasks'));
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push('> Task refs remain the execution contract for `specdo apply`.');
|
|
184
|
+
lines.push('> Use `specdo apply --change <name> --done 1.1 --evidence "..."` to complete a task.');
|
|
185
|
+
lines.push('');
|
|
186
|
+
const taskGroups = buildTaskGroups(input);
|
|
187
|
+
for (const [groupIndex, group] of taskGroups.entries()) {
|
|
188
|
+
const groupNumber = groupIndex + 1;
|
|
189
|
+
lines.push(`## ${groupNumber}. ${group.title}`);
|
|
190
|
+
lines.push('');
|
|
191
|
+
for (const [taskIndex, item] of group.items.entries()) {
|
|
192
|
+
lines.push(`- [ ] ${groupNumber}.${taskIndex + 1} ${item}`);
|
|
193
|
+
}
|
|
194
|
+
lines.push('');
|
|
195
|
+
}
|
|
196
|
+
return lines.join('\n');
|
|
197
|
+
}
|
|
198
|
+
export function inferCapabilitiesFromChange(input) {
|
|
199
|
+
const slug = slugify(input.changeName) || 'change';
|
|
200
|
+
const summary = input.idea.trim().length > 0
|
|
201
|
+
? normalizeSentence(input.idea)
|
|
202
|
+
: `deliver ${slug.replace(/-/g, ' ')} behavior changes`;
|
|
203
|
+
return [
|
|
204
|
+
{
|
|
205
|
+
name: slug,
|
|
206
|
+
kind: 'new',
|
|
207
|
+
summary,
|
|
208
|
+
rationale: input.idea.trim().length > 0 ? `Derived from explored request: ${normalizeSentence(input.idea)}` : undefined,
|
|
209
|
+
domains: input.domains.map((domain) => domain.name),
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
function renderCapabilitiesSection(capabilities, kind) {
|
|
214
|
+
if (capabilities.length === 0) {
|
|
215
|
+
return ['- _(none)_'];
|
|
216
|
+
}
|
|
217
|
+
return capabilities.flatMap((capability) => [
|
|
218
|
+
`- \`${capability.name}\`: ${capability.summary}`,
|
|
219
|
+
` - spec path: \`specs/${capability.name}/spec.md\``,
|
|
220
|
+
` - kind: ${kind}`,
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
function collectCapabilityVerificationItems(domains, selectedDomainNames) {
|
|
224
|
+
const selected = new Set(selectedDomainNames);
|
|
225
|
+
const out = [];
|
|
226
|
+
const seen = new Set();
|
|
227
|
+
for (const domain of domains) {
|
|
228
|
+
if (!selected.has(domain.name))
|
|
229
|
+
continue;
|
|
230
|
+
for (const item of domain.verify?.checklist ?? []) {
|
|
231
|
+
if (seen.has(item))
|
|
232
|
+
continue;
|
|
233
|
+
seen.add(item);
|
|
234
|
+
out.push(item);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
function collectDecisionBlocks(domains) {
|
|
240
|
+
const blocks = [];
|
|
241
|
+
for (const domain of domains) {
|
|
242
|
+
const items = [];
|
|
243
|
+
for (const item of domain.design?.checklist ?? []) {
|
|
244
|
+
items.push(item);
|
|
245
|
+
}
|
|
246
|
+
for (const [name, description] of Object.entries(domain.design?.patterns ?? {})) {
|
|
247
|
+
items.push(`${name}: ${description}`);
|
|
248
|
+
}
|
|
249
|
+
if (items.length > 0) {
|
|
250
|
+
blocks.push({ title: `${displayName(domain)} Design`, items });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return blocks;
|
|
254
|
+
}
|
|
255
|
+
function collectRiskItems(domains, concerns) {
|
|
256
|
+
const out = [];
|
|
257
|
+
const seen = new Set();
|
|
258
|
+
for (const concern of concerns) {
|
|
259
|
+
if (seen.has(concern))
|
|
260
|
+
continue;
|
|
261
|
+
seen.add(concern);
|
|
262
|
+
out.push(concern);
|
|
263
|
+
}
|
|
264
|
+
for (const domain of domains) {
|
|
265
|
+
for (const item of domain.design?.antiPatterns ?? []) {
|
|
266
|
+
if (seen.has(item))
|
|
267
|
+
continue;
|
|
268
|
+
seen.add(item);
|
|
269
|
+
out.push(`(${domain.name}) ${item}`);
|
|
270
|
+
}
|
|
271
|
+
for (const item of domain.implement?.antiPatterns ?? []) {
|
|
272
|
+
if (seen.has(item))
|
|
273
|
+
continue;
|
|
274
|
+
seen.add(item);
|
|
275
|
+
out.push(`(${domain.name}) ${item}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
function buildTaskGroups(input) {
|
|
281
|
+
const primaryCapability = input.capabilities[0];
|
|
282
|
+
const groups = [];
|
|
283
|
+
groups.push({
|
|
284
|
+
title: 'Spec Updates',
|
|
285
|
+
items: [
|
|
286
|
+
`Finalize requirement scenarios for ${primaryCapability?.name ?? input.changeName}`,
|
|
287
|
+
`Review proposal and design alignment for ${primaryCapability?.name ?? input.changeName}`,
|
|
288
|
+
],
|
|
289
|
+
});
|
|
290
|
+
const implementationItems = collectImplementationItems(input.domains, primaryCapability?.name ?? input.changeName);
|
|
291
|
+
groups.push({
|
|
292
|
+
title: 'Implementation',
|
|
293
|
+
items: implementationItems.length > 0
|
|
294
|
+
? implementationItems
|
|
295
|
+
: [`Implement the approved behavior for ${primaryCapability?.name ?? input.changeName}`],
|
|
296
|
+
});
|
|
297
|
+
const verificationItems = collectVerificationItems(input.domains, primaryCapability?.name ?? input.changeName);
|
|
298
|
+
groups.push({
|
|
299
|
+
title: 'Verification',
|
|
300
|
+
items: verificationItems.length > 0
|
|
301
|
+
? verificationItems
|
|
302
|
+
: [`Verify the ${primaryCapability?.name ?? input.changeName} flow end to end`],
|
|
303
|
+
});
|
|
304
|
+
return groups;
|
|
305
|
+
}
|
|
306
|
+
function collectImplementationItems(domains, fallbackName) {
|
|
307
|
+
const out = [];
|
|
308
|
+
for (const domain of domains) {
|
|
309
|
+
for (const item of domain.implement?.focusAreas?.slice(0, 1) ?? []) {
|
|
310
|
+
out.push(`${capitalize(domain.name)}: ${item}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return out.slice(0, 3).length > 0 ? out.slice(0, 3) : [`Implement core logic for ${fallbackName}`];
|
|
314
|
+
}
|
|
315
|
+
function collectVerificationItems(domains, fallbackName) {
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const domain of domains) {
|
|
318
|
+
for (const item of domain.verify?.checklist?.slice(0, 1) ?? []) {
|
|
319
|
+
out.push(`${capitalize(domain.name)}: ${item}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return out.slice(0, 3).length > 0 ? out.slice(0, 3) : [`Confirm acceptance behavior for ${fallbackName}`];
|
|
323
|
+
}
|
|
324
|
+
function frontmatter(input, artifact, capabilityName) {
|
|
325
|
+
return [
|
|
326
|
+
'<!--',
|
|
327
|
+
' generatedBy: specdo template-renderer',
|
|
328
|
+
` artifact: ${artifact}`,
|
|
329
|
+
` change: ${input.changeName}`,
|
|
330
|
+
capabilityName ? ` capability: ${capabilityName}` : '',
|
|
331
|
+
` domains: ${input.domains.map((d) => d.name).join(', ') || '(none)'}`,
|
|
332
|
+
` generatedAt: ${input.generatedAt}`,
|
|
333
|
+
'-->',
|
|
334
|
+
]
|
|
335
|
+
.filter(Boolean)
|
|
336
|
+
.join('\n');
|
|
337
|
+
}
|
|
338
|
+
function displayName(domain) {
|
|
339
|
+
return domain.name.charAt(0).toUpperCase() + domain.name.slice(1);
|
|
340
|
+
}
|
|
341
|
+
function slugify(value) {
|
|
342
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
343
|
+
}
|
|
344
|
+
function normalizeSentence(value) {
|
|
345
|
+
return value.trim().replace(/\s+/g, ' ');
|
|
346
|
+
}
|
|
347
|
+
function capitalize(value) {
|
|
348
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
349
|
+
}
|
|
350
|
+
function requirementTitleFromCapability(capability) {
|
|
351
|
+
return capability.summary
|
|
352
|
+
.replace(/\.$/, '')
|
|
353
|
+
.replace(/^deliver\s+/i, '')
|
|
354
|
+
.replace(/^add\s+/i, 'Add ');
|
|
355
|
+
}
|
|
356
|
+
function scenarioTitleFromCapability(capability) {
|
|
357
|
+
return capability.summary
|
|
358
|
+
.replace(/\.$/, '')
|
|
359
|
+
.replace(/^add\s+/i, 'Use ')
|
|
360
|
+
.replace(/^deliver\s+/i, 'Use ');
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=template-renderer.js.map
|