@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,128 @@
1
+ /**
2
+ * Document Engine — Pass 1 outline generation + lint + freeze (decisions 6/8/9).
3
+ *
4
+ * Generates the Outline from the global input digest + profile constraints,
5
+ * lints it (required/forbidden, references forward + reverse, kind consistency),
6
+ * then freezes to work/outline.yaml.
7
+ */
8
+ import { promises as fs } from 'node:fs';
9
+ import yaml from 'js-yaml';
10
+ import { parseOutline } from './schemas.js';
11
+ const DEFAULT_PROMPT = '你是文档大纲设计专家。基于全局输入摘要与场景 Profile 约束,生成结构化文档大纲。见 prompts/document/outline/general.md。';
12
+ const OUTLINE_SYSTEM = '你是文档大纲设计专家。严格按输入要求输出 JSON 大纲,满足所有硬规则(章节约束/要点 kind/references 完整性/source_segments)。';
13
+ // ============= Generation (T5.2) =============
14
+ export async function generateOutline(llm, options) {
15
+ const { profile, inputDigest } = options;
16
+ const prompt = options.prompt ?? DEFAULT_PROMPT;
17
+ const input = {
18
+ system: OUTLINE_SYSTEM,
19
+ messages: [
20
+ { role: 'user', content: prompt },
21
+ {
22
+ role: 'user',
23
+ content: [
24
+ `Profile: ${JSON.stringify(profile)}`,
25
+ `全局输入摘要: ${inputDigest.summary}`,
26
+ `段摘要: ${inputDigest.segments.map((s) => `[${s.id}] ${s.summary}`).join('\n')}`,
27
+ ].join('\n\n'),
28
+ },
29
+ ],
30
+ };
31
+ // Retry up to 2 times on parse failure.
32
+ let lastErr;
33
+ for (let attempt = 0; attempt < 3; attempt++) {
34
+ try {
35
+ const res = await llm.complete(input);
36
+ const outline = parseOutline(JSON.parse(res.content));
37
+ return outline;
38
+ }
39
+ catch (e) {
40
+ lastErr = e;
41
+ }
42
+ }
43
+ throw new Error(`generateOutline: failed to parse outline after retries: ${lastErr}`);
44
+ }
45
+ // ============= Lint (T5.3) =============
46
+ export function lintOutline(outline, profile) {
47
+ const diags = [];
48
+ // 1. Required chapters all present.
49
+ for (const id of profile.required) {
50
+ if (!outline.chapters.some((c) => c.id === id)) {
51
+ diags.push({ code: 'required_missing', severity: 'error', message: `Required chapter "${id}" missing from outline` });
52
+ }
53
+ }
54
+ // 2. Forbidden chapters absent.
55
+ for (const id of profile.forbidden) {
56
+ if (outline.chapters.some((c) => c.id === id)) {
57
+ diags.push({ code: 'forbidden_present', severity: 'error', chapter: id, message: `Forbidden chapter "${id}" present in outline` });
58
+ }
59
+ }
60
+ // 3. Duplicate chapter ids.
61
+ const seenCh = new Set();
62
+ for (const ch of outline.chapters) {
63
+ if (seenCh.has(ch.id))
64
+ diags.push({ code: 'duplicate_chapter', severity: 'error', chapter: ch.id, message: `Duplicate chapter "${ch.id}"` });
65
+ seenCh.add(ch.id);
66
+ }
67
+ // 4. Duplicate point ids within a chapter.
68
+ for (const ch of outline.chapters) {
69
+ const seenP = new Set();
70
+ for (const p of ch.points) {
71
+ if (seenP.has(p.id))
72
+ diags.push({ code: 'duplicate_point', severity: 'error', chapter: ch.id, message: `Duplicate point "${p.id}"` });
73
+ seenP.add(p.id);
74
+ }
75
+ }
76
+ const entityIds = new Set(outline.entities.map((e) => e.id));
77
+ // 5. References forward: point references must exist in entity registry.
78
+ // 6. Kind consistency: references present → kind must not be narrative; ≤3 refs per point.
79
+ for (const ch of outline.chapters) {
80
+ for (const p of ch.points) {
81
+ const refs = p.references ?? [];
82
+ if (refs.length > 3) {
83
+ diags.push({ code: 'too_many_references', severity: 'error', chapter: ch.id, message: `Point "${p.id}" references >3 entities` });
84
+ }
85
+ for (const ref of refs) {
86
+ if (!entityIds.has(ref)) {
87
+ diags.push({ code: 'reference_unknown', severity: 'error', chapter: ch.id, message: `Point "${p.id}" references unknown entity "${ref}"` });
88
+ }
89
+ }
90
+ if (refs.length > 0 && p.kind === 'narrative') {
91
+ diags.push({ code: 'kind_inconsistent', severity: 'error', chapter: ch.id, message: `Point "${p.id}" has references but kind=narrative` });
92
+ }
93
+ }
94
+ }
95
+ // 7. References reverse (decision 9): every non-standalone entity must be referenced by ≥1 point.
96
+ const referenced = new Set();
97
+ for (const ch of outline.chapters) {
98
+ for (const p of ch.points) {
99
+ for (const ref of p.references ?? [])
100
+ referenced.add(ref);
101
+ }
102
+ }
103
+ for (const ent of outline.entities) {
104
+ if (!ent.standalone && !referenced.has(ent.id)) {
105
+ diags.push({ code: 'entity_unreferenced', severity: 'error', message: `Entity "${ent.id}" not referenced by any point (mark standalone to exempt)` });
106
+ }
107
+ }
108
+ // 8. Every chapter must have ≥1 point.
109
+ for (const ch of outline.chapters) {
110
+ if (ch.points.length === 0) {
111
+ diags.push({ code: 'chapter_no_points', severity: 'error', chapter: ch.id, message: `Chapter "${ch.id}" has no points` });
112
+ }
113
+ }
114
+ return diags;
115
+ }
116
+ export function outlineHasErrors(diags) {
117
+ return diags.some((d) => d.severity === 'error');
118
+ }
119
+ // ============= Freeze (T5.4/T5.5) =============
120
+ export async function freezeOutline(workRoot, outline) {
121
+ const path = `${workRoot}/outline.yaml`;
122
+ await fs.writeFile(path, yaml.dump(outline, { noRefs: true }), 'utf-8');
123
+ return path;
124
+ }
125
+ export async function readOutline(workRoot) {
126
+ const raw = yaml.load(await fs.readFile(`${workRoot}/outline.yaml`, 'utf-8'));
127
+ return parseOutline(raw);
128
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Document Engine — injectable path resolution.
3
+ *
4
+ * Replaces ACP's hardcoded `specflow/changes` + `validateChangeName`. The
5
+ * caller supplies a `workRoot`; all artifact paths derive from it.
6
+ */
7
+ export interface DocumentPaths {
8
+ workRoot: string;
9
+ outline: string;
10
+ entities: string;
11
+ chaptersDir: string;
12
+ segmentsDir: string;
13
+ digestsDir: string;
14
+ hashes: string;
15
+ reviewResult: string;
16
+ documentMd: string;
17
+ documentHtml: string;
18
+ }
19
+ /** Resolve all engine artifact paths under a caller-provided work root. */
20
+ export declare function resolvePaths(workRoot: string): DocumentPaths;
21
+ export declare function chapterPath(workRoot: string, chapterId: string): string;
22
+ export declare function digestPath(workRoot: string, chapterId: string): string;
23
+ export declare function segmentIndexPath(workRoot: string): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Document Engine — injectable path resolution.
3
+ *
4
+ * Replaces ACP's hardcoded `specflow/changes` + `validateChangeName`. The
5
+ * caller supplies a `workRoot`; all artifact paths derive from it.
6
+ */
7
+ import { join } from 'node:path';
8
+ /** Resolve all engine artifact paths under a caller-provided work root. */
9
+ export function resolvePaths(workRoot) {
10
+ return {
11
+ workRoot,
12
+ outline: join(workRoot, 'outline.yaml'),
13
+ entities: join(workRoot, 'entities.json'),
14
+ chaptersDir: join(workRoot, 'chapters'),
15
+ segmentsDir: join(workRoot, 'segments'),
16
+ digestsDir: join(workRoot, 'digests'),
17
+ hashes: join(workRoot, 'hashes.json'),
18
+ reviewResult: join(workRoot, 'review-result.json'),
19
+ documentMd: join(workRoot, 'document.md'),
20
+ documentHtml: join(workRoot, 'document.html'),
21
+ };
22
+ }
23
+ export function chapterPath(workRoot, chapterId) {
24
+ return join(workRoot, 'chapters', `${chapterId}.md`);
25
+ }
26
+ export function digestPath(workRoot, chapterId) {
27
+ return join(workRoot, 'digests', `${chapterId}.md`);
28
+ }
29
+ export function segmentIndexPath(workRoot) {
30
+ return join(workRoot, 'segments', 'index.json');
31
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Document Engine — Profile static validator (decision 8 · P1-H).
3
+ *
4
+ * Validates a ScenarioProfile at load time (fail-fast) before any generation:
5
+ * 1. Existence — every required/optional chapter id exists in the library
6
+ * 2. Required met — required is non-empty and fully present
7
+ * 3. Closure — every depends_on target is in the profile (or declared shared)
8
+ * 4. Acyclic — depends_on forms a DAG (topological sort, cycle → error)
9
+ * 5. When-usable — when expressions reference known input features
10
+ */
11
+ import { ScenarioProfile, ChapterComponent } from './schemas.js';
12
+ export interface ValidationDiagnostic {
13
+ code: string;
14
+ severity: 'error' | 'warning';
15
+ message: string;
16
+ chapter?: string;
17
+ }
18
+ export interface ProfileValidationOptions {
19
+ profile: ScenarioProfile;
20
+ library: Map<string, ChapterComponent>;
21
+ }
22
+ export declare function validateProfile(options: ProfileValidationOptions): ValidationDiagnostic[];
23
+ export declare function hasErrors(diags: ValidationDiagnostic[]): boolean;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Document Engine — Profile static validator (decision 8 · P1-H).
3
+ *
4
+ * Validates a ScenarioProfile at load time (fail-fast) before any generation:
5
+ * 1. Existence — every required/optional chapter id exists in the library
6
+ * 2. Required met — required is non-empty and fully present
7
+ * 3. Closure — every depends_on target is in the profile (or declared shared)
8
+ * 4. Acyclic — depends_on forms a DAG (topological sort, cycle → error)
9
+ * 5. When-usable — when expressions reference known input features
10
+ */
11
+ import { isKnownFeature, parseWhen, featuresInWhen } from './input-features.js';
12
+ export function validateProfile(options) {
13
+ const { profile, library } = options;
14
+ const diags = [];
15
+ const declared = new Set([
16
+ ...profile.required,
17
+ ...profile.optional_candidates,
18
+ ...profile.forbidden,
19
+ ...profile.shared,
20
+ ]);
21
+ // 1. Existence: every referenced chapter id exists in the library.
22
+ for (const id of declared) {
23
+ if (!library.has(id)) {
24
+ diags.push({
25
+ code: 'chapter_not_found',
26
+ severity: 'error',
27
+ message: `Chapter "${id}" referenced by profile "${profile.id}" does not exist in the library`,
28
+ });
29
+ }
30
+ }
31
+ // 2. Required non-empty + all present.
32
+ if (profile.required.length === 0) {
33
+ diags.push({
34
+ code: 'required_empty',
35
+ severity: 'error',
36
+ message: `Profile "${profile.id}" must declare at least one required chapter`,
37
+ });
38
+ }
39
+ // 3+4. Dependency closure + acyclic over the selected chapters.
40
+ // A chapter may depend on: another selected chapter, a shared chapter, or a chapter in
41
+ // required/optional that we include in the closure graph.
42
+ const closureNodes = new Set([...profile.required, ...profile.optional_candidates, ...profile.shared]);
43
+ for (const id of profile.required) {
44
+ const ch = library.get(id);
45
+ if (!ch)
46
+ continue;
47
+ for (const dep of ch.depends_on) {
48
+ if (!closureNodes.has(dep)) {
49
+ diags.push({
50
+ code: 'dependency_unclosed',
51
+ severity: 'error',
52
+ chapter: id,
53
+ message: `Chapter "${id}" depends on "${dep}" which is not in the profile's required/optional/shared set`,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ // Acyclic check via Kahn's algorithm on selected + shared nodes.
59
+ const nodes = [...closureNodes].filter((n) => library.has(n));
60
+ const indegree = new Map();
61
+ const adj = new Map();
62
+ for (const n of nodes) {
63
+ indegree.set(n, 0);
64
+ adj.set(n, []);
65
+ }
66
+ for (const n of nodes) {
67
+ const ch = library.get(n);
68
+ for (const dep of ch.depends_on) {
69
+ if (closureNodes.has(dep) && library.has(dep) && dep !== n) {
70
+ adj.get(dep).push(n);
71
+ indegree.set(n, (indegree.get(n) ?? 0) + 1);
72
+ }
73
+ }
74
+ }
75
+ const queue = nodes.filter((n) => indegree.get(n) === 0);
76
+ const seen = new Set();
77
+ while (queue.length > 0) {
78
+ const cur = queue.shift();
79
+ seen.add(cur);
80
+ for (const next of adj.get(cur) ?? []) {
81
+ indegree.set(next, (indegree.get(next) ?? 1) - 1);
82
+ if (indegree.get(next) === 0)
83
+ queue.push(next);
84
+ }
85
+ }
86
+ for (const n of nodes) {
87
+ if (!seen.has(n)) {
88
+ diags.push({
89
+ code: 'dependency_cycle',
90
+ severity: 'error',
91
+ chapter: n,
92
+ message: `Dependency cycle detected involving chapter "${n}"`,
93
+ });
94
+ }
95
+ }
96
+ // 5. When-usable: every when expression must parse and reference known features.
97
+ for (const id of [...profile.required, ...profile.optional_candidates]) {
98
+ const ch = library.get(id);
99
+ if (!ch?.when)
100
+ continue;
101
+ try {
102
+ parseWhen(ch.when);
103
+ for (const f of featuresInWhen(ch.when)) {
104
+ if (!isKnownFeature(f)) {
105
+ diags.push({
106
+ code: 'when_unknown_feature',
107
+ severity: 'warning',
108
+ chapter: id,
109
+ message: `when expression of chapter "${id}" references unknown input feature "${f}"`,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ catch (e) {
115
+ diags.push({
116
+ code: 'when_invalid',
117
+ severity: 'error',
118
+ chapter: id,
119
+ message: `when expression of chapter "${id}" is invalid: ${e instanceof Error ? e.message : String(e)}`,
120
+ });
121
+ }
122
+ }
123
+ return diags;
124
+ }
125
+ export function hasErrors(diags) {
126
+ return diags.some((d) => d.severity === 'error');
127
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Document Engine — scenario profile loader + expansion.
3
+ * Loads templates/document/profiles/*.yaml, validates via profile-validator,
4
+ * and expands a profile into the concrete chapter selection given input features.
5
+ */
6
+ import { ScenarioProfile } from './schemas.js';
7
+ import { ValidationDiagnostic } from './profile-validator.js';
8
+ import { ChapterComponent } from './schemas.js';
9
+ export interface ExpandedProfile {
10
+ profile: ScenarioProfile;
11
+ /** Chapters in render order: required first (in order), then selected optional candidates. */
12
+ chapters: string[];
13
+ diagnostics: ValidationDiagnostic[];
14
+ }
15
+ export declare function loadProfile(profileId: string, dir?: string): Promise<ScenarioProfile>;
16
+ /**
17
+ * Expand a profile against input features:
18
+ * - required chapters always included (in declared order)
19
+ * - optional candidates included when their `when` (if any) evaluates true, or when no `when`
20
+ * - forbidden chapters never included
21
+ * - output respects dependency order (topological) so dependents come after dependencies
22
+ */
23
+ export declare function expandProfile(profile: ScenarioProfile, library: Map<string, ChapterComponent>, features: Record<string, boolean>): ExpandedProfile;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Document Engine — scenario profile loader + expansion.
3
+ * Loads templates/document/profiles/*.yaml, validates via profile-validator,
4
+ * and expands a profile into the concrete chapter selection given input features.
5
+ */
6
+ import { promises as fs } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import yaml from 'js-yaml';
9
+ import { parseScenarioProfile } from './schemas.js';
10
+ import { validateProfile, hasErrors } from './profile-validator.js';
11
+ import { evaluateWhen } from './input-features.js';
12
+ const DEFAULT_PROFILES_DIR = join(process.cwd(), 'templates', 'document', 'profiles');
13
+ export async function loadProfile(profileId, dir = DEFAULT_PROFILES_DIR) {
14
+ const raw = yaml.load(await fs.readFile(join(dir, `${profileId}.yaml`), 'utf-8'));
15
+ return parseScenarioProfile(raw);
16
+ }
17
+ /**
18
+ * Expand a profile against input features:
19
+ * - required chapters always included (in declared order)
20
+ * - optional candidates included when their `when` (if any) evaluates true, or when no `when`
21
+ * - forbidden chapters never included
22
+ * - output respects dependency order (topological) so dependents come after dependencies
23
+ */
24
+ export function expandProfile(profile, library, features) {
25
+ const diags = validateProfile({ profile, library });
26
+ if (hasErrors(diags)) {
27
+ return { profile, chapters: [], diagnostics: diags };
28
+ }
29
+ const selected = new Set(profile.required);
30
+ for (const id of profile.optional_candidates) {
31
+ const ch = library.get(id);
32
+ if (!ch)
33
+ continue;
34
+ if (ch.when && !evaluateWhen(ch.when, features))
35
+ continue;
36
+ selected.add(id);
37
+ }
38
+ for (const id of profile.forbidden)
39
+ selected.delete(id);
40
+ // Topological order over selected chapters (dependencies first).
41
+ const ordered = [];
42
+ const visited = new Set();
43
+ const stack = new Set();
44
+ const visit = (id) => {
45
+ if (visited.has(id))
46
+ return;
47
+ if (stack.has(id))
48
+ return; // cycle — validator should have caught it
49
+ stack.add(id);
50
+ const ch = library.get(id);
51
+ if (ch) {
52
+ for (const dep of ch.depends_on) {
53
+ if (selected.has(dep))
54
+ visit(dep);
55
+ }
56
+ }
57
+ stack.delete(id);
58
+ visited.add(id);
59
+ if (selected.has(id))
60
+ ordered.push(id);
61
+ };
62
+ for (const id of profile.required)
63
+ visit(id);
64
+ for (const id of profile.optional_candidates) {
65
+ if (selected.has(id))
66
+ visit(id);
67
+ }
68
+ return { profile, chapters: ordered, diagnostics: diags };
69
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Document Engine — Pass 4 deterministic rendering (decisions 7/9).
3
+ *
4
+ * Renders the final document by concatenating chapters in outline order (NOT assemble):
5
+ * contract entities → Markdown tables/code blocks; narrative → verbatim into its chapter.
6
+ * Optional HTML render reuses `marked`.
7
+ */
8
+ import { Outline, Entities, InterfaceEntity, TableEntity } from './schemas.js';
9
+ export declare function renderInterface(i: InterfaceEntity): string;
10
+ export declare function renderTable(t: TableEntity): string;
11
+ export declare function renderEntities(entities: Entities): string;
12
+ export interface RenderDocumentInput {
13
+ outline: Outline;
14
+ entities: Entities;
15
+ /** chapterId → narrative markdown */
16
+ narratives: Map<string, string>;
17
+ }
18
+ export declare function renderDocument(input: RenderDocumentInput): string;
19
+ export declare function renderHtml(markdown: string, title?: string): Promise<string>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Document Engine — Pass 4 deterministic rendering (decisions 7/9).
3
+ *
4
+ * Renders the final document by concatenating chapters in outline order (NOT assemble):
5
+ * contract entities → Markdown tables/code blocks; narrative → verbatim into its chapter.
6
+ * Optional HTML render reuses `marked`.
7
+ */
8
+ import { marked } from 'marked';
9
+ // ============= Contract entity → Markdown (T9.1) =============
10
+ export function renderInterface(i) {
11
+ const rows = [
12
+ `##### ${i.id} · ${i.name}(${i.method} ${i.path})`,
13
+ '',
14
+ '**字段表**',
15
+ '',
16
+ '| 字段 | 类型 | 必填 | 说明 |',
17
+ '|------|------|------|------|',
18
+ ...[...i.request_fields ?? [], ...i.response_fields ?? []].map((f) => `| ${f.name} | ${f.type} | ${f.required ? '是' : '否'} | ${f.desc ?? ''} |`),
19
+ '',
20
+ '**成功示例**',
21
+ '',
22
+ '```http',
23
+ `${i.method} ${i.path}`,
24
+ '```',
25
+ '',
26
+ '**失败示例**',
27
+ '',
28
+ ...i.failure_examples.map((ex) => `- ${ex}`),
29
+ '',
30
+ '**错误**',
31
+ '',
32
+ '| 条件 | 状态 | 说明 |',
33
+ '|------|------|------|',
34
+ ...i.errors.map((e) => `| ${e.condition} | ${e.status} | ${e.note ?? ''} |`),
35
+ ];
36
+ return rows.join('\n');
37
+ }
38
+ export function renderTable(t) {
39
+ return [
40
+ `##### ${t.id} · ${t.name}`,
41
+ '',
42
+ '```sql',
43
+ t.ddl,
44
+ '```',
45
+ '',
46
+ ...(t.migration ? ['**存量填充策略**', '', t.migration, ''] : []),
47
+ ...(t.rollback ? ['**回滚兼容**', '', t.rollback, ''] : []),
48
+ ].join('\n');
49
+ }
50
+ export function renderEntities(entities) {
51
+ const parts = [];
52
+ for (const i of entities.interfaces)
53
+ parts.push(renderInterface(i));
54
+ for (const t of entities.tables)
55
+ parts.push(renderTable(t));
56
+ return parts.join('\n\n');
57
+ }
58
+ export function renderDocument(input) {
59
+ const { outline, entities, narratives } = input;
60
+ const body = [`# 方案文档:${outline.profile}`, ''];
61
+ for (const ch of outline.chapters) {
62
+ body.push(`## ${ch.title}`, '');
63
+ const narrative = narratives.get(ch.id) ?? '';
64
+ if (narrative.trim())
65
+ body.push(narrative.trim(), '');
66
+ }
67
+ // Contract entities: append to the chapters that reference them (simplified: append in a
68
+ // final "契约" section, since entities are cross-chapter).
69
+ const contractMd = renderEntities(entities);
70
+ if (contractMd.trim()) {
71
+ body.push('---', '', '## 契约实体', '', contractMd, '');
72
+ }
73
+ // 产物溯源附录 (borrowed from approval · appendix A): each chapter's data source.
74
+ body.push('---', '', '## 附录 A: 产物溯源', '');
75
+ body.push('| 章节 | 数据来源 | 处理方式 |');
76
+ body.push('|------|---------|---------|');
77
+ for (const ch of outline.chapters) {
78
+ body.push(`| ${ch.title} | 输入素材 / 项目规约 / 大纲要点 | LLM 生成 + 引擎校验 |`);
79
+ }
80
+ body.push('', '');
81
+ return body.join('\n').replace(/\n{3,}/g, '\n\n').trim() + '\n';
82
+ }
83
+ // ============= HTML (T9.3) =============
84
+ export async function renderHtml(markdown, title = '方案文档') {
85
+ const bodyHtml = await marked.parse(markdown);
86
+ const hasMermaid = bodyHtml.includes('class="language-mermaid"') || bodyHtml.includes('mermaid');
87
+ const mermaidScript = hasMermaid
88
+ ? '<script type="module">\nimport mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";\nmermaid.initialize({ startOnLoad: true });\n</script>'
89
+ : '';
90
+ return `<!DOCTYPE html>
91
+ <html lang="zh-CN">
92
+ <head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
93
+ <title>${title}</title>
94
+ <style>body{max-width:920px;margin:0 auto;padding:2rem;font-family:system-ui,sans-serif;line-height:1.65}table{border-collapse:collapse;width:100%}th,td{border:1px solid #ddd;padding:.4rem}pre{background:#f6f8fa;padding:1rem;overflow-x:auto}</style>
95
+ </head>
96
+ <body><h1>${title}</h1><article>${bodyHtml}</article>${mermaidScript}</body></html>`;
97
+ }
@@ -0,0 +1,42 @@
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 { ReviewResult, ReviewFinding, Outline } from './schemas.js';
9
+ import type { DocumentLLM } from './llm.js';
10
+ export declare const AI_REVIEW_CHECKLIST: readonly ["R1 跨章命名一致", "R2 决策落地", "R3 实体↔正文", "R4 深度均衡", "R5 交叉引用稳定", "R6 图文一致", "R7 契约完整", "R8 枚举闭合", "R9 分支闭合", "R10 UI↔API 对齐", "R11 场景一致", "R12 stub 清除", "R13 结论一致", "R14 大纲要点覆盖"];
11
+ export interface ReviewPacket {
12
+ schema: 'specflow.document.review-packet/v1';
13
+ change: string;
14
+ outline: Outline;
15
+ chapters: Map<string, string>;
16
+ checklist: readonly string[];
17
+ /** decision 8/9 · P1-B: concrete narrative/mixed points the reviewer must verify (semantic channel). */
18
+ semanticChecklist: Array<{
19
+ chapter: string;
20
+ pointId: string;
21
+ text: string;
22
+ }>;
23
+ }
24
+ export declare function buildReviewPacket(workRoot: string, change: string, outline: Outline): Promise<ReviewPacket>;
25
+ export declare function reviewWithAgent(llm: DocumentLLM, packet: ReviewPacket, prompt?: string): Promise<ReviewResult>;
26
+ export declare function applyFindings(workRoot: string, findings: ReviewFinding[], apply: (finding: ReviewFinding) => Promise<void>): Promise<void>;
27
+ export declare function sha256(content: string): string;
28
+ export declare function computePartHashes(workRoot: string, chapterIds: string[]): Promise<{
29
+ id: string;
30
+ sha256: string;
31
+ }[]>;
32
+ export interface ReviewCheckResult {
33
+ ok: boolean;
34
+ diagnostics: Array<{
35
+ code: string;
36
+ severity: 'error' | 'warning';
37
+ message: string;
38
+ }>;
39
+ }
40
+ export declare function checkReviewResult(workRoot: string, review: ReviewResult, outline: Outline): Promise<ReviewCheckResult>;
41
+ export declare function writeReviewResult(workRoot: string, review: ReviewResult): Promise<void>;
42
+ export declare function readReviewResult(workRoot: string): Promise<ReviewResult | null>;