@gordon.gan/specflow 1.7.0-beta → 1.8.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/commands/document-run.d.ts +84 -0
  3. package/dist/cli/commands/document-run.js +539 -0
  4. package/dist/cli/index.js +2 -2
  5. package/dist/core/artifact-language.js +11 -1
  6. package/dist/core/document/chapters.d.ts +6 -0
  7. package/dist/core/document/chapters.js +21 -0
  8. package/dist/core/document/coverage.d.ts +25 -0
  9. package/dist/core/document/coverage.js +49 -0
  10. package/dist/core/document/digests.d.ts +41 -0
  11. package/dist/core/document/digests.js +136 -0
  12. package/dist/core/document/engine.d.ts +92 -0
  13. package/dist/core/document/engine.js +487 -0
  14. package/dist/core/{approval → document}/forbidden-patterns.d.ts +4 -2
  15. package/dist/core/document/forbidden-patterns.js +33 -0
  16. package/dist/core/document/gates.d.ts +27 -0
  17. package/dist/core/document/gates.js +33 -0
  18. package/dist/core/document/index.d.ts +7 -0
  19. package/dist/core/document/index.js +7 -0
  20. package/dist/core/document/input-digest.d.ts +55 -0
  21. package/dist/core/document/input-digest.js +121 -0
  22. package/dist/core/document/input-features.d.ts +24 -0
  23. package/dist/core/document/input-features.js +112 -0
  24. package/dist/core/document/lint.d.ts +22 -0
  25. package/dist/core/document/lint.js +29 -0
  26. package/dist/core/document/llm.d.ts +35 -0
  27. package/dist/core/document/llm.js +27 -0
  28. package/dist/core/document/map.d.ts +46 -0
  29. package/dist/core/document/map.js +212 -0
  30. package/dist/core/document/outline.d.ts +26 -0
  31. package/dist/core/document/outline.js +128 -0
  32. package/dist/core/document/paths.d.ts +23 -0
  33. package/dist/core/document/paths.js +31 -0
  34. package/dist/core/document/profile-validator.d.ts +23 -0
  35. package/dist/core/document/profile-validator.js +127 -0
  36. package/dist/core/document/profiles.d.ts +23 -0
  37. package/dist/core/document/profiles.js +69 -0
  38. package/dist/core/document/render.d.ts +19 -0
  39. package/dist/core/document/render.js +97 -0
  40. package/dist/core/document/review.d.ts +42 -0
  41. package/dist/core/document/review.js +157 -0
  42. package/dist/core/document/scene-detect.d.ts +25 -0
  43. package/dist/core/document/scene-detect.js +121 -0
  44. package/dist/core/document/schemas.d.ts +949 -0
  45. package/dist/core/document/schemas.js +158 -0
  46. package/dist/integrations/shared/capability-evidence.js +4 -8
  47. package/dist/integrations/shared/command-catalog.js +1 -1
  48. package/dist/integrations/shared/parity-manifest.js +4 -8
  49. package/package.json +1 -1
  50. package/prompts/apply/phase-a-plan.md +1 -1
  51. package/prompts/document/map/acceptance.md +8 -0
  52. package/prompts/document/map/api-design.md +17 -0
  53. package/prompts/document/map/architecture.md +7 -0
  54. package/prompts/document/map/closed-loop.md +8 -0
  55. package/prompts/document/map/compat-migration.md +8 -0
  56. package/prompts/document/map/config-runtime.md +8 -0
  57. package/prompts/document/map/core-logic.md +8 -0
  58. package/prompts/document/map/data-model.md +17 -0
  59. package/prompts/document/map/deploy.md +8 -0
  60. package/prompts/document/map/fix.md +7 -0
  61. package/prompts/document/map/goal.md +7 -0
  62. package/prompts/document/map/impact.md +7 -0
  63. package/prompts/document/map/implementability.md +8 -0
  64. package/prompts/document/map/mvp-boundary.md +7 -0
  65. package/prompts/document/map/non-goals.md +7 -0
  66. package/prompts/document/map/regression.md +7 -0
  67. package/prompts/document/map/reproduce.md +7 -0
  68. package/prompts/document/map/requirement.md +7 -0
  69. package/prompts/document/map/root-cause.md +7 -0
  70. package/prompts/document/map/signoff.md +8 -0
  71. package/prompts/document/map/tech-selection.md +17 -0
  72. package/prompts/document/map/test-strategy.md +7 -0
  73. package/prompts/document/map/ui-design.md +18 -0
  74. package/prompts/document/outline/general.md +37 -0
  75. package/prompts/document/review/ai-review.md +56 -0
  76. package/prompts/explore/explore-session.md +1 -1
  77. package/prompts/propose/design-draft.md +1 -1
  78. package/prompts/propose/proposal.md +1 -1
  79. package/prompts/propose/specs.md +1 -1
  80. package/prompts/propose/tasks-draft.md +1 -1
  81. package/prompts/refine/design-output.md +1 -1
  82. package/prompts/refine/update-artifacts.md +2 -0
  83. package/prompts/shared/artifact-language.md +13 -0
  84. package/skills/specflow-document/SKILL.md +124 -0
  85. package/templates/document/chapters/acceptance.yaml +16 -0
  86. package/templates/document/chapters/api-design.yaml +24 -0
  87. package/templates/document/chapters/architecture.yaml +18 -0
  88. package/templates/document/chapters/closed-loop.yaml +22 -0
  89. package/templates/document/chapters/compat-migration.yaml +17 -0
  90. package/templates/document/chapters/config-runtime.yaml +16 -0
  91. package/templates/document/chapters/core-logic.yaml +16 -0
  92. package/templates/document/chapters/data-model.yaml +25 -0
  93. package/templates/document/chapters/deploy.yaml +18 -0
  94. package/templates/document/chapters/fix.yaml +18 -0
  95. package/templates/document/chapters/goal.yaml +17 -0
  96. package/templates/document/chapters/impact.yaml +16 -0
  97. package/templates/document/chapters/implementability.yaml +22 -0
  98. package/templates/document/chapters/mvp-boundary.yaml +20 -0
  99. package/templates/document/chapters/non-goals.yaml +17 -0
  100. package/templates/document/chapters/regression.yaml +16 -0
  101. package/templates/document/chapters/reproduce.yaml +16 -0
  102. package/templates/document/chapters/requirement.yaml +18 -0
  103. package/templates/document/chapters/root-cause.yaml +18 -0
  104. package/templates/document/chapters/signoff.yaml +15 -0
  105. package/templates/document/chapters/tech-selection.yaml +20 -0
  106. package/templates/document/chapters/test-strategy.yaml +20 -0
  107. package/templates/document/chapters/ui-design.yaml +22 -0
  108. package/templates/document/profiles/0to1.yaml +12 -0
  109. package/templates/document/profiles/approve.yaml +22 -0
  110. package/templates/document/profiles/bugfix.yaml +10 -0
  111. package/templates/document/profiles/feature.yaml +11 -0
  112. package/dist/cli/commands/approval-assemble.d.ts +0 -64
  113. package/dist/cli/commands/approval-assemble.js +0 -408
  114. package/dist/core/approval/assemble.d.ts +0 -10
  115. package/dist/core/approval/assemble.js +0 -384
  116. package/dist/core/approval/bundle.d.ts +0 -9
  117. package/dist/core/approval/bundle.js +0 -172
  118. package/dist/core/approval/forbidden-patterns.js +0 -37
  119. package/dist/core/approval/index-schema.d.ts +0 -676
  120. package/dist/core/approval/index-schema.js +0 -193
  121. package/dist/core/approval/index.d.ts +0 -12
  122. package/dist/core/approval/index.js +0 -9
  123. package/dist/core/approval/lint.d.ts +0 -10
  124. package/dist/core/approval/lint.js +0 -302
  125. package/dist/core/approval/paths.d.ts +0 -13
  126. package/dist/core/approval/paths.js +0 -43
  127. package/dist/core/approval/pipeline.d.ts +0 -28
  128. package/dist/core/approval/pipeline.js +0 -146
  129. package/dist/core/approval/playbook-schema.d.ts +0 -182
  130. package/dist/core/approval/playbook-schema.js +0 -51
  131. package/dist/core/approval/render.d.ts +0 -20
  132. package/dist/core/approval/render.js +0 -210
  133. package/dist/core/approval/review-pack.d.ts +0 -26
  134. package/dist/core/approval/review-pack.js +0 -205
  135. package/dist/core/approval/types.d.ts +0 -223
  136. package/dist/core/approval/types.js +0 -1
  137. package/prompts/approval/acp-pipeline.md +0 -104
  138. package/prompts/approval/ai-review.md +0 -145
  139. package/prompts/approval/api-guidance.md +0 -179
  140. package/prompts/approval/database-guidance.md +0 -105
  141. package/prompts/approval/frontend-guidance.md +0 -249
  142. package/prompts/approval/generate.md +0 -1583
  143. package/prompts/approval/multi-repo-guidance.md +0 -238
  144. package/prompts/approval/project-conventions-guidance.md +0 -171
  145. package/prompts/approval/runtime-guidance.md +0 -64
  146. package/prompts/approval/segmented-generation.md +0 -157
  147. package/skills/specflow-approval/SKILL.md +0 -663
  148. package/templates/approval-index.yaml +0 -93
  149. package/templates/approval-part.md +0 -15
  150. package/templates/approval-playbook.yaml +0 -28
  151. package/templates/approval.md +0 -261
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Document Engine — Pass 3 AI review (decision 9 · target + max_rounds + part_hashes).
3
+ *
4
+ * Fresh-context reviewer (separate from generator) produces findings with a `target`
5
+ * (entity|narrative|mixed). Findings apply by target; review-result is validated against
6
+ * part_hashes (post-review tamper detection) and max_rounds (infinite-loop guard).
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import { promises as fs } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { parseReviewResult, } from './schemas.js';
12
+ import { chapterPath } from './paths.js';
13
+ import { semanticCoverageChecklist } from './coverage.js';
14
+ export const AI_REVIEW_CHECKLIST = [
15
+ 'R1 跨章命名一致',
16
+ 'R2 决策落地',
17
+ 'R3 实体↔正文',
18
+ 'R4 深度均衡',
19
+ 'R5 交叉引用稳定',
20
+ 'R6 图文一致',
21
+ 'R7 契约完整',
22
+ 'R8 枚举闭合',
23
+ 'R9 分支闭合',
24
+ 'R10 UI↔API 对齐',
25
+ 'R11 场景一致',
26
+ 'R12 stub 清除',
27
+ 'R13 结论一致',
28
+ 'R14 大纲要点覆盖',
29
+ ];
30
+ // ============= Packet (T8.1) =============
31
+ export async function buildReviewPacket(workRoot, change, outline) {
32
+ const chapters = new Map();
33
+ for (const ch of outline.chapters) {
34
+ try {
35
+ chapters.set(ch.id, await fs.readFile(chapterPath(workRoot, ch.id), 'utf-8'));
36
+ }
37
+ catch {
38
+ chapters.set(ch.id, '');
39
+ }
40
+ }
41
+ return {
42
+ schema: 'specflow.document.review-packet/v1',
43
+ change,
44
+ outline,
45
+ chapters,
46
+ checklist: [...AI_REVIEW_CHECKLIST],
47
+ semanticChecklist: semanticCoverageChecklist(outline),
48
+ };
49
+ }
50
+ // ============= Review call (T8.2) =============
51
+ export async function reviewWithAgent(llm, packet, prompt) {
52
+ const checklist = packet.checklist.join('\n');
53
+ const semantic = packet.semanticChecklist.length > 0
54
+ ? packet.semanticChecklist.map((s) => `- ${s.chapter} · ${s.pointId}: ${s.text}`).join('\n')
55
+ : '(无 narrative/mixed 要点)';
56
+ const content = [
57
+ `# Review packet`,
58
+ `change: ${packet.change}`,
59
+ `checklist:\n${checklist}`,
60
+ `## R14 语义覆盖核对清单(逐要点核对:对应小节存在且有实质内容)`,
61
+ semantic,
62
+ `outline: ${JSON.stringify(packet.outline)}`,
63
+ `chapters:\n${[...packet.chapters.entries()].map(([id, c]) => `### ${id}\n${c}`).join('\n\n')}`,
64
+ ].join('\n\n');
65
+ const input = {
66
+ system: prompt ?? '你是独立长文档审查者。按 checklist 审查,输出 review-result JSON。R14 必须逐要点核对语义覆盖清单。',
67
+ messages: [{ role: 'user', content }],
68
+ };
69
+ let res;
70
+ try {
71
+ res = await llm.complete(input);
72
+ }
73
+ catch (e) {
74
+ throw new Error(`reviewWithAgent: LLM call failed: ${e instanceof Error ? e.message : String(e)}`);
75
+ }
76
+ try {
77
+ return parseReviewResult(JSON.parse(res.content));
78
+ }
79
+ catch (e) {
80
+ throw new Error(`reviewWithAgent: unparsable review result: ${e instanceof Error ? e.message : String(e)}`);
81
+ }
82
+ }
83
+ // ============= Apply findings by target (T8.3) =============
84
+ export async function applyFindings(workRoot, findings, apply) {
85
+ for (const f of findings) {
86
+ if (f.status === 'fixed') {
87
+ await apply(f);
88
+ }
89
+ }
90
+ }
91
+ // ============= Part hashes (T8.4) =============
92
+ export function sha256(content) {
93
+ return createHash('sha256').update(content, 'utf8').digest('hex');
94
+ }
95
+ export async function computePartHashes(workRoot, chapterIds) {
96
+ const out = [];
97
+ for (const id of chapterIds) {
98
+ try {
99
+ const content = await fs.readFile(chapterPath(workRoot, id), 'utf-8');
100
+ out.push({ id, sha256: sha256(content) });
101
+ }
102
+ catch {
103
+ // missing chapter skipped
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ export async function checkReviewResult(workRoot, review, outline) {
109
+ const diags = [];
110
+ if (review.verdict === 'fail') {
111
+ diags.push({ code: 'review_verdict_fail', severity: 'error', message: 'review verdict is fail' });
112
+ }
113
+ // open critical/important findings
114
+ for (const f of review.findings) {
115
+ if (f.status === 'open' && f.severity !== 'minor') {
116
+ diags.push({ code: 'review_finding_open', severity: 'error', message: `[${f.id}] ${f.message}` });
117
+ }
118
+ if (f.status === 'waived' && !f.waiver_reason?.trim()) {
119
+ diags.push({ code: 'review_waiver_missing_reason', severity: 'error', message: `[${f.id}] waived finding must include waiver_reason` });
120
+ }
121
+ }
122
+ // part_hashes tamper detection (decision 9): review-result MUST include a hash for every chapter
123
+ // that exists on disk, so post-review changes are detected. Missing coverage for a persisted
124
+ // chapter = incomplete review (no tamper verification).
125
+ const currentHashes = await computePartHashes(workRoot, outline.chapters.map((c) => c.id));
126
+ const current = new Map(currentHashes.map((h) => [h.id, h.sha256]));
127
+ const reported = new Set((review.part_hashes ?? []).map((ph) => ph.id));
128
+ for (const [chId] of current) {
129
+ if (!reported.has(chId)) {
130
+ diags.push({
131
+ code: 'review_part_hashes_missing',
132
+ severity: 'error',
133
+ message: `review-result missing part_hashes for persisted chapter "${chId}" — cannot verify no post-review tampering`,
134
+ });
135
+ }
136
+ }
137
+ for (const ph of review.part_hashes ?? []) {
138
+ const now = current.get(ph.id);
139
+ if (now !== undefined && now !== ph.sha256) {
140
+ diags.push({ code: 'review_part_stale', severity: 'error', message: `Chapter "${ph.id}" changed after review; re-run review` });
141
+ }
142
+ }
143
+ return { ok: diags.every((d) => d.severity !== 'error'), diagnostics: diags };
144
+ }
145
+ // ============= Persistence (T8.5) =============
146
+ export async function writeReviewResult(workRoot, review) {
147
+ await fs.writeFile(join(workRoot, 'review-result.json'), JSON.stringify(review, null, 2), 'utf-8');
148
+ }
149
+ export async function readReviewResult(workRoot) {
150
+ try {
151
+ const raw = JSON.parse(await fs.readFile(join(workRoot, 'review-result.json'), 'utf-8'));
152
+ return parseReviewResult(raw);
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Document Engine — scene detection (decision: auto-detect 0to1 / bugfix / feature from
3
+ * natural language; confirm with the user when uncertain).
4
+ *
5
+ * Deterministic keyword scoring as a first pass. The Agent (IDE model) may refine and confirm
6
+ * when confidence is low. Returns the best match + confidence + candidates.
7
+ */
8
+ export type SceneProfile = '0to1' | 'bugfix' | 'feature';
9
+ export interface SceneDetectResult {
10
+ profile: SceneProfile | null;
11
+ confidence: number;
12
+ candidates: Array<{
13
+ profile: SceneProfile;
14
+ score: number;
15
+ }>;
16
+ reasons: string[];
17
+ }
18
+ /**
19
+ * Detect the most likely scene profile from natural language.
20
+ * - Returns a profile when its score clearly leads (≥ 2× runner-up, or score ≥ threshold).
21
+ * - Returns null when signals are weak/conflicting → caller should confirm with the user.
22
+ */
23
+ export declare function detectScene(text: string): SceneDetectResult;
24
+ /** The user-facing prompt when detection is uncertain. */
25
+ export declare function confirmationPrompt(result: SceneDetectResult, text: string): string;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Document Engine — scene detection (decision: auto-detect 0to1 / bugfix / feature from
3
+ * natural language; confirm with the user when uncertain).
4
+ *
5
+ * Deterministic keyword scoring as a first pass. The Agent (IDE model) may refine and confirm
6
+ * when confidence is low. Returns the best match + confidence + candidates.
7
+ */
8
+ const KEYWORDS = {
9
+ // 从零做一个产品/项目
10
+ '0to1': [
11
+ { text: '想做', weight: 3 },
12
+ { text: '想做一个', weight: 4 },
13
+ { text: '打算做', weight: 3 },
14
+ { text: '做一个', weight: 2 },
15
+ { text: '从零', weight: 3 },
16
+ { text: '新产品', weight: 2 },
17
+ { text: 'startup', weight: 2 },
18
+ { text: '想法', weight: 2 },
19
+ { text: 'idea', weight: 2 },
20
+ { text: '开始做', weight: 2 },
21
+ { text: '立项', weight: 2 },
22
+ { text: '技术选型', weight: 1 },
23
+ { text: 'mvp', weight: 1 },
24
+ { text: 'MVP', weight: 1 },
25
+ ],
26
+ // 修 bug / 排查故障
27
+ bugfix: [
28
+ { text: '报错', weight: 3 },
29
+ { text: '500', weight: 3 },
30
+ { text: '崩溃', weight: 3 },
31
+ { text: '挂了', weight: 3 },
32
+ { text: '修复', weight: 2 },
33
+ { text: '修一下', weight: 3 },
34
+ { text: 'bug', weight: 2 },
35
+ { text: 'BUG', weight: 2 },
36
+ { text: '异常', weight: 2 },
37
+ { text: '失败', weight: 2 },
38
+ { text: '闪退', weight: 3 },
39
+ { text: '无法使用', weight: 2 },
40
+ { text: '不工作', weight: 2 },
41
+ { text: '错误', weight: 2 },
42
+ { text: '故障', weight: 3 },
43
+ { text: '排查', weight: 2 },
44
+ { text: '定位问题', weight: 2 },
45
+ { text: 'debug', weight: 2 },
46
+ ],
47
+ // 给已有系统加功能
48
+ feature: [
49
+ { text: '新增', weight: 3 },
50
+ { text: '增加', weight: 2 },
51
+ { text: '加一个', weight: 3 },
52
+ { text: '加个', weight: 3 },
53
+ { text: '扩展', weight: 2 },
54
+ { text: '添加', weight: 2 },
55
+ { text: '支持', weight: 1 },
56
+ { text: '功能', weight: 1 },
57
+ { text: '开发一个功能', weight: 3 },
58
+ { text: 'feat', weight: 2 },
59
+ ],
60
+ };
61
+ /** Score a text against one profile's keywords. */
62
+ function score(text, profile) {
63
+ let score = 0;
64
+ const hits = [];
65
+ for (const kw of KEYWORDS[profile]) {
66
+ if (text.includes(kw.text)) {
67
+ score += kw.weight;
68
+ hits.push(kw.text);
69
+ }
70
+ }
71
+ return { score, hits };
72
+ }
73
+ /**
74
+ * Detect the most likely scene profile from natural language.
75
+ * - Returns a profile when its score clearly leads (≥ 2× runner-up, or score ≥ threshold).
76
+ * - Returns null when signals are weak/conflicting → caller should confirm with the user.
77
+ */
78
+ export function detectScene(text) {
79
+ const lower = text.toLowerCase();
80
+ const scores = Object.keys(KEYWORDS).map((p) => ({
81
+ profile: p,
82
+ score: score(lower, p).score,
83
+ }));
84
+ scores.sort((a, b) => b.score - a.score);
85
+ const best = scores[0];
86
+ const runner = scores[1];
87
+ const total = scores.reduce((s, x) => s + x.score, 0);
88
+ const reasons = [];
89
+ // Strong signal: clear winner.
90
+ if (best.score >= 3 && (runner.score === 0 || best.score >= runner.score * 2)) {
91
+ const confidence = Math.min(1, best.score / (best.score + runner.score + 1));
92
+ reasons.push(`匹配「${best.profile}」关键词 ${best.score} 分(次优 ${runner.profile} ${runner.score} 分)`);
93
+ return { profile: best.profile, confidence, candidates: scores, reasons };
94
+ }
95
+ // Conflicting/weak → uncertain.
96
+ if (total === 0) {
97
+ reasons.push('未检测到明显场景信号,需向用户确认');
98
+ }
99
+ else {
100
+ reasons.push(`场景信号较弱或冲突(${scores.map((s) => `${s.profile}:${s.score}`).join(', ')}),需确认`);
101
+ }
102
+ return { profile: null, confidence: 0, candidates: scores, reasons };
103
+ }
104
+ /** The user-facing prompt when detection is uncertain. */
105
+ export function confirmationPrompt(result, text) {
106
+ const lines = [
107
+ `未能明确判断你的需求属于哪个场景:`,
108
+ `「${text.slice(0, 60)}${text.length > 60 ? '…' : ''}」`,
109
+ '',
110
+ `检测到的候选:`,
111
+ ...result.candidates
112
+ .filter((c) => c.score > 0)
113
+ .map((c) => `- ${c.profile}: ${c.score} 分`),
114
+ '',
115
+ `请选择场景(或直接输入新的需求描述):`,
116
+ `- 0to1 → 从零启动一个产品/项目`,
117
+ `- bugfix → 排查并修复一个问题`,
118
+ `- feature → 给已有系统新增功能`,
119
+ ];
120
+ return lines.join('\n');
121
+ }