@gordon.gan/specflow 1.8.0-beta → 1.8.2-beta

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 (181) hide show
  1. package/README.md +7 -5
  2. package/dist/cli/commands/document-run.d.ts +98 -0
  3. package/dist/cli/commands/document-run.js +1064 -0
  4. package/dist/cli/index.js +2 -2
  5. package/dist/core/artifact-language.js +11 -1
  6. package/dist/core/document/asset-paths.d.ts +15 -0
  7. package/dist/core/document/asset-paths.js +38 -0
  8. package/dist/core/document/chapters.d.ts +6 -0
  9. package/dist/core/document/chapters.js +38 -0
  10. package/dist/core/document/coverage.d.ts +25 -0
  11. package/dist/core/document/coverage.js +49 -0
  12. package/dist/core/document/digests.d.ts +40 -0
  13. package/dist/core/document/digests.js +144 -0
  14. package/dist/core/document/engine.d.ts +101 -0
  15. package/dist/core/document/engine.js +722 -0
  16. package/dist/core/document/extract.d.ts +61 -0
  17. package/dist/core/document/extract.js +437 -0
  18. package/dist/core/{approval → document}/forbidden-patterns.d.ts +4 -2
  19. package/dist/core/document/forbidden-patterns.js +36 -0
  20. package/dist/core/document/gates.d.ts +39 -0
  21. package/dist/core/document/gates.js +75 -0
  22. package/dist/core/document/input-digest.d.ts +63 -0
  23. package/dist/core/document/input-digest.js +200 -0
  24. package/dist/core/document/input-features.d.ts +43 -0
  25. package/dist/core/document/input-features.js +161 -0
  26. package/dist/core/document/lint.d.ts +39 -0
  27. package/dist/core/document/lint.js +147 -0
  28. package/dist/core/document/llm.d.ts +28 -0
  29. package/dist/core/document/llm.js +22 -0
  30. package/dist/core/document/map.d.ts +52 -0
  31. package/dist/core/document/map.js +296 -0
  32. package/dist/core/document/outline.d.ts +29 -0
  33. package/dist/core/document/outline.js +171 -0
  34. package/dist/core/document/paths.d.ts +23 -0
  35. package/dist/core/document/paths.js +36 -0
  36. package/dist/core/document/profile-validator.d.ts +23 -0
  37. package/dist/core/document/profile-validator.js +155 -0
  38. package/dist/core/document/profiles.d.ts +23 -0
  39. package/dist/core/document/profiles.js +76 -0
  40. package/dist/core/document/render.d.ts +61 -0
  41. package/dist/core/document/render.js +243 -0
  42. package/dist/core/document/review.d.ts +57 -0
  43. package/dist/core/document/review.js +202 -0
  44. package/dist/core/document/scene-detect.d.ts +32 -0
  45. package/dist/core/document/scene-detect.js +237 -0
  46. package/dist/core/document/schemas.d.ts +1111 -0
  47. package/dist/core/document/schemas.js +191 -0
  48. package/dist/integrations/shared/capability-evidence.js +4 -9
  49. package/dist/integrations/shared/command-catalog.js +2 -1
  50. package/dist/integrations/shared/parity-manifest.js +4 -9
  51. package/package.json +2 -1
  52. package/prompts/apply/phase-a-plan.md +1 -1
  53. package/prompts/document/map/acceptance.md +9 -0
  54. package/prompts/document/map/anti-ai.md +29 -0
  55. package/prompts/document/map/api-design.md +26 -0
  56. package/prompts/document/map/architecture.md +27 -0
  57. package/prompts/document/map/benchmark.md +26 -0
  58. package/prompts/document/map/closed-loop.md +9 -0
  59. package/prompts/document/map/compat-migration.md +31 -0
  60. package/prompts/document/map/component-design.md +30 -0
  61. package/prompts/document/map/config-runtime.md +9 -0
  62. package/prompts/document/map/core-flow.md +62 -0
  63. package/prompts/document/map/core-logic.md +9 -0
  64. package/prompts/document/map/data-model.md +18 -0
  65. package/prompts/document/map/deploy.md +26 -0
  66. package/prompts/document/map/fix.md +8 -0
  67. package/prompts/document/map/frontend-architecture.md +35 -0
  68. package/prompts/document/map/goal.md +8 -0
  69. package/prompts/document/map/impact.md +8 -0
  70. package/prompts/document/map/implementability.md +9 -0
  71. package/prompts/document/map/migration-guide.md +36 -0
  72. package/prompts/document/map/mvp-boundary.md +8 -0
  73. package/prompts/document/map/non-goals.md +8 -0
  74. package/prompts/document/map/ops.md +33 -0
  75. package/prompts/document/map/performance.md +32 -0
  76. package/prompts/document/map/poc-demo.md +25 -0
  77. package/prompts/document/map/regression.md +8 -0
  78. package/prompts/document/map/reproduce.md +8 -0
  79. package/prompts/document/map/requirement.md +8 -0
  80. package/prompts/document/map/research.md +25 -0
  81. package/prompts/document/map/root-cause.md +8 -0
  82. package/prompts/document/map/signoff.md +9 -0
  83. package/prompts/document/map/state-management.md +23 -0
  84. package/prompts/document/map/tech-selection.md +29 -0
  85. package/prompts/document/map/test-strategy.md +24 -0
  86. package/prompts/document/map/ui-design.md +21 -0
  87. package/prompts/document/outline/general.md +46 -0
  88. package/prompts/document/review/ai-review.md +57 -0
  89. package/prompts/document/shared/grounding.md +84 -0
  90. package/prompts/explore/explore-session.md +1 -1
  91. package/prompts/propose/design-draft.md +1 -1
  92. package/prompts/propose/proposal.md +1 -1
  93. package/prompts/propose/specs.md +1 -1
  94. package/prompts/propose/tasks-draft.md +1 -1
  95. package/prompts/refine/design-output.md +1 -1
  96. package/prompts/refine/update-artifacts.md +2 -0
  97. package/prompts/shared/artifact-language.md +13 -0
  98. package/skills/specflow-techdoc/SKILL.md +143 -0
  99. package/skills/specflow-techdoc-synth/SKILL.md +115 -0
  100. package/templates/document/chapters/acceptance.yaml +16 -0
  101. package/templates/document/chapters/api-design.yaml +29 -0
  102. package/templates/document/chapters/architecture.yaml +21 -0
  103. package/templates/document/chapters/benchmark.yaml +20 -0
  104. package/templates/document/chapters/closed-loop.yaml +22 -0
  105. package/templates/document/chapters/compat-migration.yaml +21 -0
  106. package/templates/document/chapters/component-design.yaml +22 -0
  107. package/templates/document/chapters/config-runtime.yaml +16 -0
  108. package/templates/document/chapters/core-flow.yaml +27 -0
  109. package/templates/document/chapters/core-logic.yaml +16 -0
  110. package/templates/document/chapters/data-model.yaml +25 -0
  111. package/templates/document/chapters/deploy.yaml +22 -0
  112. package/templates/document/chapters/fix.yaml +18 -0
  113. package/templates/document/chapters/frontend-architecture.yaml +22 -0
  114. package/templates/document/chapters/goal.yaml +17 -0
  115. package/templates/document/chapters/impact.yaml +16 -0
  116. package/templates/document/chapters/implementability.yaml +22 -0
  117. package/templates/document/chapters/migration-guide.yaml +21 -0
  118. package/templates/document/chapters/mvp-boundary.yaml +20 -0
  119. package/templates/document/chapters/non-goals.yaml +17 -0
  120. package/templates/document/chapters/ops.yaml +25 -0
  121. package/templates/document/chapters/performance.yaml +21 -0
  122. package/templates/document/chapters/poc-demo.yaml +22 -0
  123. package/templates/document/chapters/regression.yaml +16 -0
  124. package/templates/document/chapters/reproduce.yaml +16 -0
  125. package/templates/document/chapters/requirement.yaml +18 -0
  126. package/templates/document/chapters/research.yaml +22 -0
  127. package/templates/document/chapters/root-cause.yaml +18 -0
  128. package/templates/document/chapters/signoff.yaml +15 -0
  129. package/templates/document/chapters/state-management.yaml +22 -0
  130. package/templates/document/chapters/tech-selection.yaml +23 -0
  131. package/templates/document/chapters/test-strategy.yaml +23 -0
  132. package/templates/document/chapters/ui-design.yaml +28 -0
  133. package/templates/document/profiles/0to1.yaml +44 -0
  134. package/templates/document/profiles/approve.yaml +22 -0
  135. package/templates/document/profiles/bugfix.yaml +15 -0
  136. package/templates/document/profiles/feature.yaml +23 -0
  137. package/templates/document/profiles/frontend-0to1.yaml +47 -0
  138. package/templates/document/profiles/migration.yaml +42 -0
  139. package/templates/document/profiles/poc.yaml +46 -0
  140. package/dist/cli/commands/approval-assemble.d.ts +0 -64
  141. package/dist/cli/commands/approval-assemble.js +0 -408
  142. package/dist/core/approval/assemble.d.ts +0 -10
  143. package/dist/core/approval/assemble.js +0 -384
  144. package/dist/core/approval/bundle.d.ts +0 -9
  145. package/dist/core/approval/bundle.js +0 -172
  146. package/dist/core/approval/forbidden-patterns.js +0 -40
  147. package/dist/core/approval/index-schema.d.ts +0 -676
  148. package/dist/core/approval/index-schema.js +0 -193
  149. package/dist/core/approval/index.d.ts +0 -12
  150. package/dist/core/approval/index.js +0 -9
  151. package/dist/core/approval/lint.d.ts +0 -10
  152. package/dist/core/approval/lint.js +0 -302
  153. package/dist/core/approval/paths.d.ts +0 -13
  154. package/dist/core/approval/paths.js +0 -43
  155. package/dist/core/approval/pipeline.d.ts +0 -28
  156. package/dist/core/approval/pipeline.js +0 -146
  157. package/dist/core/approval/playbook-schema.d.ts +0 -182
  158. package/dist/core/approval/playbook-schema.js +0 -51
  159. package/dist/core/approval/render.d.ts +0 -20
  160. package/dist/core/approval/render.js +0 -210
  161. package/dist/core/approval/review-pack.d.ts +0 -26
  162. package/dist/core/approval/review-pack.js +0 -205
  163. package/dist/core/approval/types.d.ts +0 -223
  164. package/dist/core/approval/types.js +0 -1
  165. package/prompts/approval/acp-pipeline.md +0 -106
  166. package/prompts/approval/ai-review.md +0 -145
  167. package/prompts/approval/api-guidance.md +0 -179
  168. package/prompts/approval/database-guidance.md +0 -105
  169. package/prompts/approval/frontend-guidance.md +0 -249
  170. package/prompts/approval/generate.md +0 -1596
  171. package/prompts/approval/multi-repo-guidance.md +0 -263
  172. package/prompts/approval/multi-repo-spoke-subagent.md +0 -94
  173. package/prompts/approval/project-conventions-guidance.md +0 -171
  174. package/prompts/approval/runtime-guidance.md +0 -64
  175. package/prompts/approval/segmented-generation.md +0 -157
  176. package/skills/specflow-approval/SKILL.md +0 -778
  177. package/templates/approval-index.yaml +0 -93
  178. package/templates/approval-part.md +0 -15
  179. package/templates/approval-playbook-talos-scenario-job-compile.yaml +0 -29
  180. package/templates/approval-playbook.yaml +0 -28
  181. package/templates/approval.md +0 -261
package/dist/cli/index.js CHANGED
@@ -16,7 +16,7 @@ import { registerStoreCommand } from './commands/store.js';
16
16
  import { registerShowCommand } from './commands/show.js';
17
17
  import { registerContextCommand } from './commands/context.js';
18
18
  import { registerWorksetCommand } from './commands/workset.js';
19
- import { registerApprovalAssembleCommand } from './commands/approval-assemble.js';
19
+ import { registerDocumentRunCommand } from './commands/document-run.js';
20
20
  const __filename = fileURLToPath(import.meta.url);
21
21
  const __dirname = dirname(__filename);
22
22
  const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
@@ -44,7 +44,7 @@ registerStoreCommand(program);
44
44
  registerShowCommand(program);
45
45
  registerContextCommand(program);
46
46
  registerWorksetCommand(program);
47
- registerApprovalAssembleCommand(program);
47
+ registerDocumentRunCommand(program);
48
48
  program.exitOverride();
49
49
  try {
50
50
  await program.parseAsync();
@@ -18,13 +18,23 @@ export function requireArtifactLanguage(value) {
18
18
  }
19
19
  export function renderArtifactLanguageGuidance(language) {
20
20
  const narrativeLanguage = language === 'zh-CN' ? 'Simplified Chinese' : 'English';
21
+ const styleLines = language === 'zh-CN'
22
+ ? [
23
+ 'Chinese narrative must be brief: one idea per sentence; no filler.',
24
+ 'Write how-to / process / handling as numbered 1. 2. 3. steps (one sentence each).',
25
+ 'Do not collapse multi-step flows into a paragraph.',
26
+ 'Leave tables, Mermaid, DDL, HTTP examples, and protocol markers unchanged.',
27
+ ]
28
+ : ['Keep narrative concise. Use numbered steps for how-to. Leave tables and diagrams unchanged.'];
21
29
  return [
22
30
  '## Artifact language policy',
23
31
  '',
24
32
  `Write all non-protocol business content in ${narrativeLanguage}.`,
25
- 'This includes proposal narrative, requirement and scenario descriptions, text after WHEN/THEN, design rationale, and task descriptions.',
33
+ 'This includes proposal narrative, requirement and scenario descriptions, text after WHEN/THEN, design rationale, task descriptions, explore, and approval.',
26
34
  'Do not mix narrative languages in the same artifact.',
27
35
  '',
36
+ ...styleLines,
37
+ '',
28
38
  'Keep SpecFlow protocol markers unchanged, including:',
29
39
  '- `ADDED Requirements`, `MODIFIED Requirements`, `REMOVED Requirements`, and `RENAMED Requirements`',
30
40
  '- `Requirement:`, `Scenario:`, `WHEN`, and `THEN`',
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Document Engine — template asset path resolution.
3
+ *
4
+ * Templates (templates/document/chapters, templates/document/profiles) are loaded from two
5
+ * places, in priority order:
6
+ * 1. `<project-cwd>/templates` — user-overridable templates (e.g. a repo ships its own).
7
+ * 2. The specflow package's own `templates/` dir — so the CLI works from ANY directory
8
+ * (multi-repo workspaces, repos initialized elsewhere) instead of failing when cwd
9
+ * has no templates. This fixes "synthesize 因模板路径失败" when running in a workspace
10
+ * that is not the specflow checkout.
11
+ */
12
+ /** 候选模板根目录(含用户 cwd 覆盖与包内 fallback)。 */
13
+ export declare function templateRoots(projectDir?: string): string[];
14
+ /** 在模板根目录候选里找第一个存在的目录;都不存在则返回包内默认(让上层报可读错误)。 */
15
+ export declare function resolveTemplateDir(subpath: string, projectDir?: string): Promise<string>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Document Engine — template asset path resolution.
3
+ *
4
+ * Templates (templates/document/chapters, templates/document/profiles) are loaded from two
5
+ * places, in priority order:
6
+ * 1. `<project-cwd>/templates` — user-overridable templates (e.g. a repo ships its own).
7
+ * 2. The specflow package's own `templates/` dir — so the CLI works from ANY directory
8
+ * (multi-repo workspaces, repos initialized elsewhere) instead of failing when cwd
9
+ * has no templates. This fixes "synthesize 因模板路径失败" when running in a workspace
10
+ * that is not the specflow checkout.
11
+ */
12
+ import { promises as fs } from 'node:fs';
13
+ import { join, dirname } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ /** specflow 包根:从当前模块(dist/core/document/*.js)上溯到包根(templates 所在层)。 */
16
+ function packageRoot() {
17
+ const here = dirname(fileURLToPath(import.meta.url)); // <pkg>/dist/core/document
18
+ return dirname(dirname(dirname(here))); // <pkg>/dist/core → <pkg>/dist → <pkg>
19
+ }
20
+ /** 候选模板根目录(含用户 cwd 覆盖与包内 fallback)。 */
21
+ export function templateRoots(projectDir = process.cwd()) {
22
+ return [join(projectDir, 'templates'), join(packageRoot(), 'templates')];
23
+ }
24
+ /** 在模板根目录候选里找第一个存在的目录;都不存在则返回包内默认(让上层报可读错误)。 */
25
+ export async function resolveTemplateDir(subpath, projectDir) {
26
+ for (const root of templateRoots(projectDir)) {
27
+ const candidate = join(root, subpath);
28
+ try {
29
+ const st = await fs.stat(candidate);
30
+ if (st.isDirectory())
31
+ return candidate;
32
+ }
33
+ catch {
34
+ // not present → try next
35
+ }
36
+ }
37
+ return join(packageRoot(), 'templates', subpath);
38
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Document Engine — chapter component library loader.
3
+ * Loads templates/document/chapters/*.yaml, validates each parses, returns a Map.
4
+ */
5
+ import { ChapterComponent } from './schemas.js';
6
+ export declare function loadChapterLibrary(dir?: string): Promise<Map<string, ChapterComponent>>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Document Engine — chapter component library loader.
3
+ * Loads templates/document/chapters/*.yaml, validates each parses, returns a Map.
4
+ */
5
+ import { promises as fs } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import yaml from 'js-yaml';
8
+ import { parseChapterComponent } from './schemas.js';
9
+ import { resolveTemplateDir } from './asset-paths.js';
10
+ export async function loadChapterLibrary(dir) {
11
+ // 默认从「用户 cwd/templates 优先、包内 templates fallback」解析(多仓工作区也能跑)。
12
+ const resolvedDir = dir ?? (await resolveTemplateDir(join('document', 'chapters')));
13
+ const entries = await fs.readdir(resolvedDir, { withFileTypes: true });
14
+ const lib = new Map();
15
+ const seen = new Set();
16
+ for (const entry of entries) {
17
+ if (!entry.isFile())
18
+ continue; // 跳过目录(P2:避免 EISDIR)
19
+ const file = entry.name;
20
+ if (!file.endsWith('.yaml') && !file.endsWith('.yml'))
21
+ continue;
22
+ let raw;
23
+ try {
24
+ raw = yaml.load(await fs.readFile(join(resolvedDir, file), 'utf-8'));
25
+ }
26
+ catch (e) {
27
+ throw new Error(`Failed to parse chapter file ${file}: ${e instanceof Error ? e.message : String(e)}`);
28
+ }
29
+ const ch = parseChapterComponent(raw);
30
+ // 重复 id 检测(P2):Map.set 静默覆盖且覆盖顺序依赖目录枚举,id 碰撞会静默改变整条流水线。
31
+ if (seen.has(ch.id)) {
32
+ throw new Error(`Duplicate chapter id "${ch.id}" (in ${file}); chapter ids must be unique across templates/document/chapters/`);
33
+ }
34
+ seen.add(ch.id);
35
+ lib.set(ch.id, ch);
36
+ }
37
+ return lib;
38
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Document Engine — coverage completeness (decision 8·P1-B + decision 9·kind).
3
+ *
4
+ * Two channels:
5
+ * - Structured (at generation): for kind=entity|mixed points, referenced entity ids must appear
6
+ * in the chapter's structured entity contribution.
7
+ * - Semantic (at review R14): for kind=narrative|mixed points, produces a checklist consumed by
8
+ * the reviewer to verify the corresponding section exists and is non-stub.
9
+ */
10
+ import { Outline, Entities } from './schemas.js';
11
+ export interface CoverageDiagnostic {
12
+ code: string;
13
+ severity: 'error' | 'warning';
14
+ message: string;
15
+ chapter?: string;
16
+ point?: string;
17
+ }
18
+ /** Structured channel: verify referenced entities are present in the chapter's entity contribution. */
19
+ export declare function checkStructuredCoverage(outline: Outline, chapterEntities: Map<string, Entities>): CoverageDiagnostic[];
20
+ /** Semantic checklist for reviewer (R14): narrative|mixed points → must have a non-stub section. */
21
+ export declare function semanticCoverageChecklist(outline: Outline): Array<{
22
+ chapter: string;
23
+ pointId: string;
24
+ text: string;
25
+ }>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Document Engine — coverage completeness (decision 8·P1-B + decision 9·kind).
3
+ *
4
+ * Two channels:
5
+ * - Structured (at generation): for kind=entity|mixed points, referenced entity ids must appear
6
+ * in the chapter's structured entity contribution.
7
+ * - Semantic (at review R14): for kind=narrative|mixed points, produces a checklist consumed by
8
+ * the reviewer to verify the corresponding section exists and is non-stub.
9
+ */
10
+ /** Structured channel: verify referenced entities are present in the chapter's entity contribution. */
11
+ export function checkStructuredCoverage(outline, chapterEntities) {
12
+ const diags = [];
13
+ for (const ch of outline.chapters) {
14
+ const ents = chapterEntities.get(ch.id) ?? { interfaces: [], tables: [], decisions: [] };
15
+ const present = new Set([
16
+ ...ents.interfaces.map((i) => i.id),
17
+ ...ents.tables.map((t) => t.id),
18
+ ...ents.decisions.map((d) => d.id),
19
+ ]);
20
+ for (const p of ch.points) {
21
+ if (p.kind === 'narrative')
22
+ continue; // narrative → semantic channel only
23
+ for (const ref of p.references ?? []) {
24
+ if (!present.has(ref)) {
25
+ diags.push({
26
+ code: 'entity_not_filled',
27
+ severity: 'error',
28
+ chapter: ch.id,
29
+ point: p.id,
30
+ message: `Point "${p.id}" references entity "${ref}" but chapter "${ch.id}" has no such entity`,
31
+ });
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return diags;
37
+ }
38
+ /** Semantic checklist for reviewer (R14): narrative|mixed points → must have a non-stub section. */
39
+ export function semanticCoverageChecklist(outline) {
40
+ const list = [];
41
+ for (const ch of outline.chapters) {
42
+ for (const p of ch.points) {
43
+ if (p.kind === 'narrative' || p.kind === 'mixed') {
44
+ list.push({ chapter: ch.id, pointId: p.id, text: p.text });
45
+ }
46
+ }
47
+ }
48
+ return list;
49
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Document Engine — dependency digests (decisions 2 + 8·P1-D + 9).
3
+ *
4
+ * - extractConstraintList: deterministic constraint items (entity/decision/point ids) that a
5
+ * chapter's digest MUST preserve (decision 8).
6
+ * - generateDigest: bounded LLM summary carrying the constraint list; deterministic fallback.
7
+ * - lintDigest: bidirectional lint — forward (digest ids exist in source) + reverse (constraint
8
+ * items present in digest).
9
+ * - recordDigestSources / affectedDownstream: invalidation propagation (decision 9).
10
+ */
11
+ import { OutlineChapter, ConstraintItem, Entities } from './schemas.js';
12
+ import type { DocumentLLM } from './llm.js';
13
+ export declare function extractConstraintList(chapter: OutlineChapter, entities: Entities): ConstraintItem[];
14
+ export interface DigestInput {
15
+ chapterId: string;
16
+ chapterTitle: string;
17
+ source: string;
18
+ constraints: ConstraintItem[];
19
+ }
20
+ export declare function generateDigest(llm: DocumentLLM, input: DigestInput): Promise<string>;
21
+ /** Deterministic fallback: render entity registry as a one-line digest. */
22
+ export declare function deterministicDigest(entities: Entities, ids: string[]): string;
23
+ export interface DigestLintDiagnostic {
24
+ code: string;
25
+ severity: 'error' | 'warning';
26
+ message: string;
27
+ }
28
+ export declare function lintDigest(digest: string, constraints: ConstraintItem[], knownEntityIds: Set<string>): DigestLintDiagnostic[];
29
+ export interface DigestSource {
30
+ chapterId: string;
31
+ /** entity ids this digest depends on (for granular invalidation). */
32
+ entityIds: string[];
33
+ }
34
+ export declare function recordDigestSources(chapter: OutlineChapter, sourceEntities: Entities): DigestSource;
35
+ /**
36
+ * Given a changed chapter, return the set of downstream chapters whose digests are invalidated.
37
+ * (decision 9): only direct dependents whose digest references one of the changed entities are
38
+ * invalidated. Outline change → caller passes allDownstream (full invalidation).
39
+ */
40
+ export declare function affectedDownstream(changedChapterId: string, changedEntityIds: string[], downstreamSources: DigestSource[]): string[];
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Document Engine — dependency digests (decisions 2 + 8·P1-D + 9).
3
+ *
4
+ * - extractConstraintList: deterministic constraint items (entity/decision/point ids) that a
5
+ * chapter's digest MUST preserve (decision 8).
6
+ * - generateDigest: bounded LLM summary carrying the constraint list; deterministic fallback.
7
+ * - lintDigest: bidirectional lint — forward (digest ids exist in source) + reverse (constraint
8
+ * items present in digest).
9
+ * - recordDigestSources / affectedDownstream: invalidation propagation (decision 9).
10
+ */
11
+ // ============= Constraint extraction (T7.1) =============
12
+ export function extractConstraintList(chapter, entities) {
13
+ const items = [];
14
+ for (const p of chapter.points) {
15
+ items.push({ kind: 'point', id: p.id, label: p.text.slice(0, 40) });
16
+ for (const ref of p.references ?? []) {
17
+ items.push({ kind: 'entity', id: ref, label: ref });
18
+ }
19
+ }
20
+ for (const d of entities.decisions) {
21
+ // A decision is preserved in this chapter's digest when the chapter references it
22
+ // (deterministic signal) or its text is mentioned in a point (heuristic fallback).
23
+ // (Previously wrapped in an always-true `|| true` outer condition — dead code removed.)
24
+ const referenced = chapter.points.some((p) => (p.references ?? []).includes(d.id));
25
+ const mentioned = chapter.points.some((p) => p.text.includes(d.text.slice(0, 20)));
26
+ if (referenced || mentioned) {
27
+ items.push({ kind: 'decision', id: d.id, label: d.text.slice(0, 40) });
28
+ }
29
+ }
30
+ // dedupe by kind+id
31
+ const seen = new Set();
32
+ return items.filter((it) => {
33
+ const k = `${it.kind}:${it.id}`;
34
+ if (seen.has(k))
35
+ return false;
36
+ seen.add(k);
37
+ return true;
38
+ });
39
+ }
40
+ // ============= Digest generation (T7.2) =============
41
+ const DIGEST_SYSTEM = '你是依赖摘要生成器。为下游章节生成上游章节的紧凑摘要,必须逐项保留约束清单中的每一项(实体 id / 决策 id / 要点 id 及其一句话含义)。禁止引入上游没有的实体。';
42
+ export async function generateDigest(llm, input) {
43
+ const constraintText = input.constraints.map((c) => `- ${c.kind} ${c.id}: ${c.label}`).join('\n');
44
+ const llmInput = {
45
+ system: DIGEST_SYSTEM,
46
+ messages: [
47
+ {
48
+ role: 'user',
49
+ content: `上游章节 ${input.chapterId}(${input.chapterTitle}):\n${input.source.slice(0, 6000)}\n\n必须保留的约束清单:\n${constraintText}`,
50
+ },
51
+ ],
52
+ };
53
+ const res = await llm.complete(llmInput);
54
+ return res.content.trim();
55
+ }
56
+ /** Deterministic fallback: render entity registry as a one-line digest. */
57
+ export function deterministicDigest(entities, ids) {
58
+ const lines = [];
59
+ for (const it of entities.interfaces) {
60
+ if (ids.length === 0 || ids.includes(it.id)) {
61
+ lines.push(`I ${it.id}: ${it.name} ${it.method} ${it.path}`);
62
+ }
63
+ }
64
+ for (const t of entities.tables) {
65
+ if (ids.length === 0 || ids.includes(t.id)) {
66
+ lines.push(`T ${t.id}: ${t.name}`);
67
+ }
68
+ }
69
+ for (const d of entities.decisions) {
70
+ if (ids.length === 0 || ids.includes(d.id)) {
71
+ lines.push(`D ${d.id}: ${d.text}`);
72
+ }
73
+ }
74
+ return lines.join('\n');
75
+ }
76
+ export function lintDigest(digest, constraints, knownEntityIds) {
77
+ const diags = [];
78
+ // Reverse: every constraint item must appear in digest (decision 8).
79
+ // 用整词匹配(P2):短 id 子串匹配会把 T1 误判为出现(T10/ST1/GET /api/T1),
80
+ // 或把 t1/D-1 误判为缺失 → 触发确定性回退丢弃约束。
81
+ for (const c of constraints) {
82
+ const re = new RegExp(`(^|[^A-Za-z0-9_])${escapeRegExp(c.id)}($|[^A-Za-z0-9_])`);
83
+ if (!re.test(digest)) {
84
+ diags.push({
85
+ code: 'constraint_missing',
86
+ severity: 'error',
87
+ message: `Digest missing constraint ${c.kind} ${c.id} (${c.label})`,
88
+ });
89
+ }
90
+ }
91
+ // Forward: any entity id mentioned in digest that we can detect must exist in known set.
92
+ // (P2 修复:此前 I\d+/T\d+ 被豁免导致前向检查恒不触发;现加入 D\d+ 且不再豁免。)
93
+ const mentioned = new Set();
94
+ for (const m of digest.matchAll(/`([A-Za-z0-9_.]+)`/g))
95
+ mentioned.add(m[1]);
96
+ for (const m of digest.matchAll(/\b(I\d+|T\d+|D\d+)\b/g))
97
+ mentioned.add(m[1]);
98
+ for (const id of mentioned) {
99
+ if (knownEntityIds.size > 0 && !knownEntityIds.has(id)) {
100
+ diags.push({
101
+ code: 'digest_hallucinated_entity',
102
+ severity: 'warning',
103
+ message: `Digest mentions unknown entity "${id}"`,
104
+ });
105
+ }
106
+ }
107
+ return diags;
108
+ }
109
+ /** 转义正则特殊字符(供整词匹配使用)。 */
110
+ function escapeRegExp(s) {
111
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
112
+ }
113
+ export function recordDigestSources(chapter, sourceEntities) {
114
+ const entityIds = new Set();
115
+ for (const p of chapter.points) {
116
+ for (const ref of p.references ?? [])
117
+ entityIds.add(ref);
118
+ }
119
+ // Include referenced entity ids that actually exist in source entities.
120
+ const known = new Set([
121
+ ...sourceEntities.interfaces.map((i) => i.id),
122
+ ...sourceEntities.tables.map((t) => t.id),
123
+ ]);
124
+ return {
125
+ chapterId: chapter.id,
126
+ entityIds: [...entityIds].filter((id) => known.has(id)),
127
+ };
128
+ }
129
+ /**
130
+ * Given a changed chapter, return the set of downstream chapters whose digests are invalidated.
131
+ * (decision 9): only direct dependents whose digest references one of the changed entities are
132
+ * invalidated. Outline change → caller passes allDownstream (full invalidation).
133
+ */
134
+ export function affectedDownstream(changedChapterId, changedEntityIds, downstreamSources) {
135
+ const changed = new Set(changedEntityIds);
136
+ const affected = new Set();
137
+ for (const ds of downstreamSources) {
138
+ if (ds.chapterId === changedChapterId)
139
+ continue;
140
+ if (ds.entityIds.some((id) => changed.has(id)))
141
+ affected.add(ds.chapterId);
142
+ }
143
+ return [...affected];
144
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Document Engine — unified orchestration entry (T11.2).
3
+ *
4
+ * runDocument wires Pass 0.5 → 1 → 2 → 3 → 4 + gates. Consumers inject the LLM,
5
+ * the work root, and the input. An `outlineOnly` mode stops after Pass 1 (for user gate).
6
+ */
7
+ import { ScenarioProfile, ChapterComponent, Outline } from './schemas.js';
8
+ import type { DocumentLLM } from './llm.js';
9
+ import { SegmentableInput } from './input-digest.js';
10
+ import { expandProfile } from './profiles.js';
11
+ import { GateDiagnostic } from './gates.js';
12
+ import { ExtractedContracts } from './extract.js';
13
+ export interface RunDocumentOptions {
14
+ llm: DocumentLLM;
15
+ workRoot: string;
16
+ profile: ScenarioProfile;
17
+ library: Map<string, ChapterComponent>;
18
+ inputs: SegmentableInput[];
19
+ inputFeatures: Record<string, boolean>;
20
+ change: string;
21
+ outlineOnly?: boolean;
22
+ /** Skip Pass 3 AI review entirely (fast path; no findings applied, no review-result written). */
23
+ skipReview?: boolean;
24
+ /** User confirmation gate for the generated outline (T5.4). If absent, outline is auto-accepted
25
+ * (non-interactive / test path). Return true to accept, false to reject (→ regenerate). */
26
+ outlineConfirmer?: (outline: Outline) => Promise<boolean>;
27
+ /** Incremental refill (decision 9 · invalidation propagation). When a chapter/entity changed,
28
+ * only downstream chapters whose digests reference the changed entities are refilled; unaffected
29
+ * chapters are reused from persisted `chapters/<id>.md` when present. Default: full refill. */
30
+ incremental?: {
31
+ changedChapterId?: string;
32
+ changedEntityIds?: string[];
33
+ };
34
+ maxRounds?: number;
35
+ renderHtmlOutput?: boolean;
36
+ outlinePrompt?: string;
37
+ reviewPrompt?: string;
38
+ /** Project root for deterministic contract extraction (OpenAPI/proto/DDL). Optional —
39
+ * when set, engine extracts real contract entities and injects them into outline
40
+ * generation as frozen candidates (grounding P0). */
41
+ projectRoot?: string;
42
+ /** Pre-extracted contracts (skip file scan). Useful for tests/hosts that already know them. */
43
+ extractedContracts?: ExtractedContracts;
44
+ /** Persist per-chapter files/entities to workRoot (default true). */
45
+ persist?: boolean;
46
+ }
47
+ export interface RunDocumentResult {
48
+ ok: boolean;
49
+ stage: string;
50
+ outline?: Outline;
51
+ documentMd?: string;
52
+ documentHtml?: string;
53
+ diagnostics: GateDiagnostic[];
54
+ tokenEstimate?: number;
55
+ }
56
+ export declare function runDocument(options: RunDocumentOptions): Promise<RunDocumentResult>;
57
+ /** 附录覆盖校验(README §7.3):profile.appendices 含 anti-ai 但自检条目为空/不足 → warning。 */
58
+ export declare function appendixDiagnostics(profile: ScenarioProfile, antiAiContent: string): GateDiagnostic[];
59
+ export interface AgentStep {
60
+ /** stable id: 'input-summary' | 'outline' | 'chapter:<id>' | 'review' */
61
+ id: string;
62
+ phase: 'prep' | 'outline' | 'fill' | 'review';
63
+ title: string;
64
+ description: string;
65
+ prompt: string;
66
+ inputFiles: string[];
67
+ outputFile: string;
68
+ required: boolean;
69
+ }
70
+ export interface PlanStepsInput {
71
+ profile: ScenarioProfile;
72
+ library: Map<string, ChapterComponent>;
73
+ inputFeatures: Record<string, boolean>;
74
+ inputs: SegmentableInput[];
75
+ workRoot: string;
76
+ /** Project root to resolve project conventions / IDE rules & skills (approval-era behaviour). */
77
+ projectRoot?: string;
78
+ }
79
+ /** Map a scene profile to the project-convention topics it should read (approval-era: 项目约定 > guidance > LLM). */
80
+ export declare function sceneTopics(profileId: string): string[];
81
+ export interface PlanStepsResult {
82
+ steps: AgentStep[];
83
+ chapters: string[];
84
+ }
85
+ /** Emit the Agent-executable step list (no LLM call — the Agent's IDE model does the work). */
86
+ export declare function planSteps(options: PlanStepsInput): PlanStepsResult;
87
+ export interface ValidateWorkInput {
88
+ profile: ScenarioProfile;
89
+ library: Map<string, ChapterComponent>;
90
+ workRoot: string;
91
+ renderHtmlOutput?: boolean;
92
+ }
93
+ export interface ValidateWorkResult {
94
+ ok: boolean;
95
+ diagnostics: GateDiagnostic[];
96
+ documentMd?: string;
97
+ documentHtml?: string;
98
+ }
99
+ /** Read Agent-written artifacts from workRoot, validate (lint/coverage/zod) and render. No LLM. */
100
+ export declare function validateWork(options: ValidateWorkInput): Promise<ValidateWorkResult>;
101
+ export { expandProfile };