@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.
- package/README.md +4 -4
- package/dist/cli/commands/document-run.d.ts +84 -0
- package/dist/cli/commands/document-run.js +539 -0
- package/dist/cli/index.js +2 -2
- package/dist/core/artifact-language.js +11 -1
- package/dist/core/document/chapters.d.ts +6 -0
- package/dist/core/document/chapters.js +21 -0
- package/dist/core/document/coverage.d.ts +25 -0
- package/dist/core/document/coverage.js +49 -0
- package/dist/core/document/digests.d.ts +41 -0
- package/dist/core/document/digests.js +136 -0
- package/dist/core/document/engine.d.ts +92 -0
- package/dist/core/document/engine.js +487 -0
- package/dist/core/{approval → document}/forbidden-patterns.d.ts +4 -2
- package/dist/core/document/forbidden-patterns.js +33 -0
- package/dist/core/document/gates.d.ts +27 -0
- package/dist/core/document/gates.js +33 -0
- package/dist/core/document/index.d.ts +7 -0
- package/dist/core/document/index.js +7 -0
- package/dist/core/document/input-digest.d.ts +55 -0
- package/dist/core/document/input-digest.js +121 -0
- package/dist/core/document/input-features.d.ts +24 -0
- package/dist/core/document/input-features.js +112 -0
- package/dist/core/document/lint.d.ts +22 -0
- package/dist/core/document/lint.js +29 -0
- package/dist/core/document/llm.d.ts +35 -0
- package/dist/core/document/llm.js +27 -0
- package/dist/core/document/map.d.ts +46 -0
- package/dist/core/document/map.js +212 -0
- package/dist/core/document/outline.d.ts +26 -0
- package/dist/core/document/outline.js +128 -0
- package/dist/core/document/paths.d.ts +23 -0
- package/dist/core/document/paths.js +31 -0
- package/dist/core/document/profile-validator.d.ts +23 -0
- package/dist/core/document/profile-validator.js +127 -0
- package/dist/core/document/profiles.d.ts +23 -0
- package/dist/core/document/profiles.js +69 -0
- package/dist/core/document/render.d.ts +19 -0
- package/dist/core/document/render.js +97 -0
- package/dist/core/document/review.d.ts +42 -0
- package/dist/core/document/review.js +157 -0
- package/dist/core/document/scene-detect.d.ts +25 -0
- package/dist/core/document/scene-detect.js +121 -0
- package/dist/core/document/schemas.d.ts +949 -0
- package/dist/core/document/schemas.js +158 -0
- package/dist/integrations/shared/capability-evidence.js +4 -8
- package/dist/integrations/shared/command-catalog.js +1 -1
- package/dist/integrations/shared/parity-manifest.js +4 -8
- package/package.json +1 -1
- package/prompts/apply/phase-a-plan.md +1 -1
- package/prompts/document/map/acceptance.md +8 -0
- package/prompts/document/map/api-design.md +17 -0
- package/prompts/document/map/architecture.md +7 -0
- package/prompts/document/map/closed-loop.md +8 -0
- package/prompts/document/map/compat-migration.md +8 -0
- package/prompts/document/map/config-runtime.md +8 -0
- package/prompts/document/map/core-logic.md +8 -0
- package/prompts/document/map/data-model.md +17 -0
- package/prompts/document/map/deploy.md +8 -0
- package/prompts/document/map/fix.md +7 -0
- package/prompts/document/map/goal.md +7 -0
- package/prompts/document/map/impact.md +7 -0
- package/prompts/document/map/implementability.md +8 -0
- package/prompts/document/map/mvp-boundary.md +7 -0
- package/prompts/document/map/non-goals.md +7 -0
- package/prompts/document/map/regression.md +7 -0
- package/prompts/document/map/reproduce.md +7 -0
- package/prompts/document/map/requirement.md +7 -0
- package/prompts/document/map/root-cause.md +7 -0
- package/prompts/document/map/signoff.md +8 -0
- package/prompts/document/map/tech-selection.md +17 -0
- package/prompts/document/map/test-strategy.md +7 -0
- package/prompts/document/map/ui-design.md +18 -0
- package/prompts/document/outline/general.md +37 -0
- package/prompts/document/review/ai-review.md +56 -0
- package/prompts/explore/explore-session.md +1 -1
- package/prompts/propose/design-draft.md +1 -1
- package/prompts/propose/proposal.md +1 -1
- package/prompts/propose/specs.md +1 -1
- package/prompts/propose/tasks-draft.md +1 -1
- package/prompts/refine/design-output.md +1 -1
- package/prompts/refine/update-artifacts.md +2 -0
- package/prompts/shared/artifact-language.md +13 -0
- package/skills/specflow-document/SKILL.md +124 -0
- package/templates/document/chapters/acceptance.yaml +16 -0
- package/templates/document/chapters/api-design.yaml +24 -0
- package/templates/document/chapters/architecture.yaml +18 -0
- package/templates/document/chapters/closed-loop.yaml +22 -0
- package/templates/document/chapters/compat-migration.yaml +17 -0
- package/templates/document/chapters/config-runtime.yaml +16 -0
- package/templates/document/chapters/core-logic.yaml +16 -0
- package/templates/document/chapters/data-model.yaml +25 -0
- package/templates/document/chapters/deploy.yaml +18 -0
- package/templates/document/chapters/fix.yaml +18 -0
- package/templates/document/chapters/goal.yaml +17 -0
- package/templates/document/chapters/impact.yaml +16 -0
- package/templates/document/chapters/implementability.yaml +22 -0
- package/templates/document/chapters/mvp-boundary.yaml +20 -0
- package/templates/document/chapters/non-goals.yaml +17 -0
- package/templates/document/chapters/regression.yaml +16 -0
- package/templates/document/chapters/reproduce.yaml +16 -0
- package/templates/document/chapters/requirement.yaml +18 -0
- package/templates/document/chapters/root-cause.yaml +18 -0
- package/templates/document/chapters/signoff.yaml +15 -0
- package/templates/document/chapters/tech-selection.yaml +20 -0
- package/templates/document/chapters/test-strategy.yaml +20 -0
- package/templates/document/chapters/ui-design.yaml +22 -0
- package/templates/document/profiles/0to1.yaml +12 -0
- package/templates/document/profiles/approve.yaml +22 -0
- package/templates/document/profiles/bugfix.yaml +10 -0
- package/templates/document/profiles/feature.yaml +11 -0
- package/dist/cli/commands/approval-assemble.d.ts +0 -64
- package/dist/cli/commands/approval-assemble.js +0 -408
- package/dist/core/approval/assemble.d.ts +0 -10
- package/dist/core/approval/assemble.js +0 -384
- package/dist/core/approval/bundle.d.ts +0 -9
- package/dist/core/approval/bundle.js +0 -172
- package/dist/core/approval/forbidden-patterns.js +0 -37
- package/dist/core/approval/index-schema.d.ts +0 -676
- package/dist/core/approval/index-schema.js +0 -193
- package/dist/core/approval/index.d.ts +0 -12
- package/dist/core/approval/index.js +0 -9
- package/dist/core/approval/lint.d.ts +0 -10
- package/dist/core/approval/lint.js +0 -302
- package/dist/core/approval/paths.d.ts +0 -13
- package/dist/core/approval/paths.js +0 -43
- package/dist/core/approval/pipeline.d.ts +0 -28
- package/dist/core/approval/pipeline.js +0 -146
- package/dist/core/approval/playbook-schema.d.ts +0 -182
- package/dist/core/approval/playbook-schema.js +0 -51
- package/dist/core/approval/render.d.ts +0 -20
- package/dist/core/approval/render.js +0 -210
- package/dist/core/approval/review-pack.d.ts +0 -26
- package/dist/core/approval/review-pack.js +0 -205
- package/dist/core/approval/types.d.ts +0 -223
- package/dist/core/approval/types.js +0 -1
- package/prompts/approval/acp-pipeline.md +0 -104
- package/prompts/approval/ai-review.md +0 -145
- package/prompts/approval/api-guidance.md +0 -179
- package/prompts/approval/database-guidance.md +0 -105
- package/prompts/approval/frontend-guidance.md +0 -249
- package/prompts/approval/generate.md +0 -1583
- package/prompts/approval/multi-repo-guidance.md +0 -238
- package/prompts/approval/project-conventions-guidance.md +0 -171
- package/prompts/approval/runtime-guidance.md +0 -64
- package/prompts/approval/segmented-generation.md +0 -157
- package/skills/specflow-approval/SKILL.md +0 -663
- package/templates/approval-index.yaml +0 -93
- package/templates/approval-part.md +0 -15
- package/templates/approval-playbook.yaml +0 -28
- package/templates/approval.md +0 -261
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — Pass 0.5 input compression + source segmentation (decision 8·P0-A, decision 9).
|
|
3
|
+
*
|
|
4
|
+
* Long inputs are split into stable segments (seg id → original file + line range),
|
|
5
|
+
* each segment is compressed in a bounded-context LLM call, then segment digests are
|
|
6
|
+
* merged into a global input digest (~1–2k tokens). Pass 1 generates the outline from the
|
|
7
|
+
* global digest; Pass 2 pulls original detail by `source_segments` from the segment index.
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { segmentIndexPath } from './paths.js';
|
|
12
|
+
const SEGMENT_CHAR_BUDGET = 4000; // ~1-2k tokens per segment, bounded context
|
|
13
|
+
export function segmentInputs(inputs) {
|
|
14
|
+
const segments = [];
|
|
15
|
+
for (const input of inputs) {
|
|
16
|
+
const lines = input.content.split('\n');
|
|
17
|
+
const n = Math.max(1, Math.ceil(lines.length / Math.max(1, Math.floor(SEGMENT_CHAR_BUDGET / 200))));
|
|
18
|
+
const perChunk = Math.max(1, Math.ceil(lines.length / n));
|
|
19
|
+
for (let i = 0; i < lines.length; i += perChunk) {
|
|
20
|
+
const chunk = lines.slice(i, i + perChunk);
|
|
21
|
+
const idx = segments.length + 1;
|
|
22
|
+
segments.push({
|
|
23
|
+
id: `seg-${input.type}-${String(idx).padStart(2, '0')}`,
|
|
24
|
+
type: input.type,
|
|
25
|
+
label: `${input.type} #${idx}`,
|
|
26
|
+
source: input.source,
|
|
27
|
+
startLine: i + 1,
|
|
28
|
+
endLine: i + chunk.length,
|
|
29
|
+
content: chunk.join('\n'),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return segments;
|
|
34
|
+
}
|
|
35
|
+
// ============= Compression (T4.2) =============
|
|
36
|
+
const COMPRESS_SYSTEM = '你是长文档输入压缩器。将给定原文段压缩为紧凑摘要,保留:关键实体名(接口/表/页面)、决策线索、约束。不要复述细节,只要提炼。';
|
|
37
|
+
export async function compressSegment(seg, llm) {
|
|
38
|
+
const input = {
|
|
39
|
+
system: COMPRESS_SYSTEM,
|
|
40
|
+
messages: [{ role: 'user', content: `段 ${seg.id}(${seg.label}):\n${seg.content}` }],
|
|
41
|
+
};
|
|
42
|
+
const summary = (await llm.complete(input)).content.trim();
|
|
43
|
+
return {
|
|
44
|
+
id: seg.id,
|
|
45
|
+
type: seg.type,
|
|
46
|
+
label: seg.label,
|
|
47
|
+
summary,
|
|
48
|
+
entity_candidates: extractEntityCandidates(summary),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Heuristic entity-candidate extraction: backtick tokens + PascalCase/snake tokens. */
|
|
52
|
+
export function extractEntityCandidates(text) {
|
|
53
|
+
const out = new Set();
|
|
54
|
+
for (const m of text.matchAll(/`([^`]+)`/g))
|
|
55
|
+
out.add(m[1].trim());
|
|
56
|
+
for (const m of text.matchAll(/\b([A-Z][A-Za-z0-9]{2,})\b/g))
|
|
57
|
+
out.add(m[1]);
|
|
58
|
+
return [...out];
|
|
59
|
+
}
|
|
60
|
+
// ============= Merge (T4.3) =============
|
|
61
|
+
const MERGE_SYSTEM = '你是长文档输入压缩合并器。将多段摘要合并为一份全局摘要(~1-2k token),保留各段 id、实体候选与关键约束,不丢失任何段。';
|
|
62
|
+
export async function mergeSegmentDigests(digests, llm) {
|
|
63
|
+
const payload = digests.map((d) => `[${d.id}] ${d.summary}`).join('\n\n');
|
|
64
|
+
const input = {
|
|
65
|
+
system: MERGE_SYSTEM,
|
|
66
|
+
messages: [{ role: 'user', content: payload }],
|
|
67
|
+
};
|
|
68
|
+
const summary = (await llm.complete(input)).content.trim();
|
|
69
|
+
return { segments: digests, summary };
|
|
70
|
+
}
|
|
71
|
+
// ============= Segment index (T4.4) =============
|
|
72
|
+
export async function writeSegmentIndex(workRoot, segments) {
|
|
73
|
+
const index = {
|
|
74
|
+
schema: 'specflow.document.segments/v1',
|
|
75
|
+
segments: segments.map((s) => ({
|
|
76
|
+
id: s.id,
|
|
77
|
+
type: s.type,
|
|
78
|
+
label: s.label,
|
|
79
|
+
source: s.source,
|
|
80
|
+
startLine: s.startLine,
|
|
81
|
+
endLine: s.endLine,
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
const path = segmentIndexPath(workRoot);
|
|
85
|
+
await fs.mkdir(join(workRoot, 'segments'), { recursive: true });
|
|
86
|
+
await fs.writeFile(path, JSON.stringify(index, null, 2), 'utf-8');
|
|
87
|
+
return path;
|
|
88
|
+
}
|
|
89
|
+
export async function readSegmentIndex(workRoot) {
|
|
90
|
+
const raw = JSON.parse(await fs.readFile(segmentIndexPath(workRoot), 'utf-8'));
|
|
91
|
+
return raw;
|
|
92
|
+
}
|
|
93
|
+
/** Fetch original text for a set of source_segments (bounded, decision 9). */
|
|
94
|
+
export async function fetchSegments(workRoot, segmentIds) {
|
|
95
|
+
const index = await readSegmentIndex(workRoot);
|
|
96
|
+
const wanted = new Set(segmentIds);
|
|
97
|
+
const matched = index.segments.filter((s) => wanted.has(s.id));
|
|
98
|
+
const parts = [];
|
|
99
|
+
for (const s of matched) {
|
|
100
|
+
parts.push(`[${s.id} · ${s.label}] ${s.source} ${s.startLine}-${s.endLine}`);
|
|
101
|
+
try {
|
|
102
|
+
const text = await fs.readFile(s.source, 'utf-8');
|
|
103
|
+
const lines = text.split('\n').slice(s.startLine - 1, s.endLine);
|
|
104
|
+
parts.push(lines.join('\n'));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
parts.push('(原文不可读,使用段摘要)');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return parts.join('\n\n');
|
|
111
|
+
}
|
|
112
|
+
// ============= Top-level orchestration (T4.x entry) =============
|
|
113
|
+
export async function compressInputs(workRoot, inputs, llm) {
|
|
114
|
+
const segments = segmentInputs(inputs);
|
|
115
|
+
await writeSegmentIndex(workRoot, segments);
|
|
116
|
+
const digests = [];
|
|
117
|
+
for (const seg of segments) {
|
|
118
|
+
digests.push(await compressSegment(seg, llm));
|
|
119
|
+
}
|
|
120
|
+
return mergeSegmentDigests(digests, llm);
|
|
121
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — input feature registry + `when` DSL.
|
|
3
|
+
*
|
|
4
|
+
* `when` expression grammar (design §3.1, decision 9):
|
|
5
|
+
* term := "input." <feature> | "(" expr ")" | "!" term
|
|
6
|
+
* and-or := term (("&&" | "||") term)*
|
|
7
|
+
* Built-in features: containsApiChange, hasPersistence, uiInScope, hasLogic.
|
|
8
|
+
* Unknown features surface as validation warnings (T2.3).
|
|
9
|
+
*/
|
|
10
|
+
export declare const INPUT_FEATURES: readonly ["containsApiChange", "hasPersistence", "uiInScope", "hasLogic", "includeSignoff"];
|
|
11
|
+
export type InputFeature = (typeof INPUT_FEATURES)[number];
|
|
12
|
+
export type InputFeatureMap = Record<string, boolean>;
|
|
13
|
+
export declare function isKnownFeature(name: string): boolean;
|
|
14
|
+
/** Tokenize a when expression into a simple AST via recursive descent. */
|
|
15
|
+
export interface WhenExpr {
|
|
16
|
+
op: 'and' | 'or' | 'not' | 'feature';
|
|
17
|
+
feature?: string;
|
|
18
|
+
left?: WhenExpr;
|
|
19
|
+
right?: WhenExpr;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseWhen(expr: string): WhenExpr;
|
|
22
|
+
export declare function evaluateWhen(expr: string, features: InputFeatureMap): boolean;
|
|
23
|
+
/** Collect feature names referenced in a when expression (for T2.3 validation). */
|
|
24
|
+
export declare function featuresInWhen(expr: string): string[];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — input feature registry + `when` DSL.
|
|
3
|
+
*
|
|
4
|
+
* `when` expression grammar (design §3.1, decision 9):
|
|
5
|
+
* term := "input." <feature> | "(" expr ")" | "!" term
|
|
6
|
+
* and-or := term (("&&" | "||") term)*
|
|
7
|
+
* Built-in features: containsApiChange, hasPersistence, uiInScope, hasLogic.
|
|
8
|
+
* Unknown features surface as validation warnings (T2.3).
|
|
9
|
+
*/
|
|
10
|
+
export const INPUT_FEATURES = [
|
|
11
|
+
'containsApiChange',
|
|
12
|
+
'hasPersistence',
|
|
13
|
+
'uiInScope',
|
|
14
|
+
'hasLogic',
|
|
15
|
+
'includeSignoff',
|
|
16
|
+
];
|
|
17
|
+
export function isKnownFeature(name) {
|
|
18
|
+
return INPUT_FEATURES.includes(name);
|
|
19
|
+
}
|
|
20
|
+
class WhenParser {
|
|
21
|
+
src;
|
|
22
|
+
pos = 0;
|
|
23
|
+
constructor(src) {
|
|
24
|
+
this.src = src;
|
|
25
|
+
this.src = src.replace(/\s+/g, '');
|
|
26
|
+
}
|
|
27
|
+
parse() {
|
|
28
|
+
const expr = this.parseOr();
|
|
29
|
+
if (this.pos < this.src.length) {
|
|
30
|
+
throw new Error(`WhenParser: unexpected token at ${this.pos}: "${this.src.slice(this.pos)}"`);
|
|
31
|
+
}
|
|
32
|
+
return expr;
|
|
33
|
+
}
|
|
34
|
+
parseOr() {
|
|
35
|
+
let left = this.parseAnd();
|
|
36
|
+
while (this.peek('||')) {
|
|
37
|
+
this.pos += 2;
|
|
38
|
+
left = { op: 'or', left, right: this.parseAnd() };
|
|
39
|
+
}
|
|
40
|
+
return left;
|
|
41
|
+
}
|
|
42
|
+
parseAnd() {
|
|
43
|
+
let left = this.parseNot();
|
|
44
|
+
while (this.peek('&&')) {
|
|
45
|
+
this.pos += 2;
|
|
46
|
+
left = { op: 'and', left, right: this.parseNot() };
|
|
47
|
+
}
|
|
48
|
+
return left;
|
|
49
|
+
}
|
|
50
|
+
parseNot() {
|
|
51
|
+
if (this.peek('!')) {
|
|
52
|
+
this.pos += 1;
|
|
53
|
+
return { op: 'not', right: this.parseNot() };
|
|
54
|
+
}
|
|
55
|
+
if (this.peek('(')) {
|
|
56
|
+
this.pos += 1;
|
|
57
|
+
const inner = this.parseOr();
|
|
58
|
+
if (!this.peek(')'))
|
|
59
|
+
throw new Error('WhenParser: missing ")"');
|
|
60
|
+
this.pos += 1;
|
|
61
|
+
return inner;
|
|
62
|
+
}
|
|
63
|
+
return this.parseFeature();
|
|
64
|
+
}
|
|
65
|
+
parseFeature() {
|
|
66
|
+
const m = this.src.slice(this.pos).match(/^input\.[A-Za-z_][A-Za-z0-9_]*/);
|
|
67
|
+
if (!m)
|
|
68
|
+
throw new Error(`WhenParser: expected input.<feature> at "${this.src.slice(this.pos)}"`);
|
|
69
|
+
this.pos += m[0].length;
|
|
70
|
+
return { op: 'feature', feature: m[0].slice('input.'.length) };
|
|
71
|
+
}
|
|
72
|
+
peek(token) {
|
|
73
|
+
return this.src.startsWith(token, this.pos);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function parseWhen(expr) {
|
|
77
|
+
if (!expr || !expr.trim())
|
|
78
|
+
throw new Error('WhenParser: empty expression');
|
|
79
|
+
return new WhenParser(expr).parse();
|
|
80
|
+
}
|
|
81
|
+
export function evaluateWhen(expr, features) {
|
|
82
|
+
const ast = parseWhen(expr);
|
|
83
|
+
return evalNode(ast, features);
|
|
84
|
+
}
|
|
85
|
+
function evalNode(node, features) {
|
|
86
|
+
switch (node.op) {
|
|
87
|
+
case 'feature':
|
|
88
|
+
return features[node.feature] === true;
|
|
89
|
+
case 'not':
|
|
90
|
+
return !evalNode(node.right, features);
|
|
91
|
+
case 'and':
|
|
92
|
+
return evalNode(node.left, features) && evalNode(node.right, features);
|
|
93
|
+
case 'or':
|
|
94
|
+
return evalNode(node.left, features) || evalNode(node.right, features);
|
|
95
|
+
default:
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Collect feature names referenced in a when expression (for T2.3 validation). */
|
|
100
|
+
export function featuresInWhen(expr) {
|
|
101
|
+
const out = [];
|
|
102
|
+
walk(parseWhen(expr), out);
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
function walk(node, acc) {
|
|
106
|
+
if (node.op === 'feature')
|
|
107
|
+
acc.push(node.feature);
|
|
108
|
+
if (node.left)
|
|
109
|
+
walk(node.left, acc);
|
|
110
|
+
if (node.right)
|
|
111
|
+
walk(node.right, acc);
|
|
112
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — lint: reference checks, stub detection, budget checks (decision 6).
|
|
3
|
+
*/
|
|
4
|
+
export interface LintDiagnostic {
|
|
5
|
+
code: string;
|
|
6
|
+
severity: 'error' | 'warning';
|
|
7
|
+
message: string;
|
|
8
|
+
chapter?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LintOptions {
|
|
11
|
+
/** Minimum meaningful content length for narrative (default 20). */
|
|
12
|
+
minNarrativeChars?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function lintNarrative(chapterId: string, narrative: string, options?: LintOptions): LintDiagnostic[];
|
|
15
|
+
export interface BudgetDiagnostic {
|
|
16
|
+
code: string;
|
|
17
|
+
severity: 'error' | 'warning';
|
|
18
|
+
message: string;
|
|
19
|
+
chapter?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Check chapter output against per-chapter budget (decision 6/P6). */
|
|
22
|
+
export declare function checkBudget(chapterId: string, tokens: number, budgetTokens?: number): BudgetDiagnostic[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — lint: reference checks, stub detection, budget checks (decision 6).
|
|
3
|
+
*/
|
|
4
|
+
import { findForbiddenMatch, findInconsistencyHints } from './forbidden-patterns.js';
|
|
5
|
+
export function lintNarrative(chapterId, narrative, options = {}) {
|
|
6
|
+
const diags = [];
|
|
7
|
+
const min = options.minNarrativeChars ?? 20;
|
|
8
|
+
const trimmed = narrative.trim();
|
|
9
|
+
if (trimmed.length < min) {
|
|
10
|
+
diags.push({ code: 'narrative_too_short', severity: 'error', chapter: chapterId, message: `Chapter "${chapterId}" narrative too short (< ${min} chars)` });
|
|
11
|
+
}
|
|
12
|
+
const forbidden = findForbiddenMatch(trimmed);
|
|
13
|
+
if (forbidden) {
|
|
14
|
+
diags.push({ code: 'forbidden_pattern', severity: 'error', chapter: chapterId, message: `Chapter "${chapterId}": forbidden stub pattern (${forbidden.source})` });
|
|
15
|
+
}
|
|
16
|
+
for (const hint of findInconsistencyHints(trimmed)) {
|
|
17
|
+
diags.push({ code: 'inconsistency_hint', severity: 'warning', chapter: chapterId, message: `Chapter "${chapterId}": possible inconsistency (${hint.source})` });
|
|
18
|
+
}
|
|
19
|
+
return diags;
|
|
20
|
+
}
|
|
21
|
+
/** Check chapter output against per-chapter budget (decision 6/P6). */
|
|
22
|
+
export function checkBudget(chapterId, tokens, budgetTokens) {
|
|
23
|
+
if (budgetTokens === undefined)
|
|
24
|
+
return [];
|
|
25
|
+
if (tokens > budgetTokens) {
|
|
26
|
+
return [{ code: 'budget_exceeded', severity: 'error', chapter: chapterId, message: `Chapter "${chapterId}" ${tokens} tokens exceeds budget ${budgetTokens}` }];
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — injectable LLM interface
|
|
3
|
+
*
|
|
4
|
+
* The engine does NOT bind to any specific LLM SDK. Consumers provide a
|
|
5
|
+
* `DocumentLLM` implementation. `completeJSON` parses + zod-validates the
|
|
6
|
+
* model response so all downstream consumers get typed data or a clean error.
|
|
7
|
+
*/
|
|
8
|
+
import type { z } from 'zod';
|
|
9
|
+
export interface LLMMessage {
|
|
10
|
+
role: 'system' | 'user' | 'assistant';
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LLMInput {
|
|
14
|
+
system?: string;
|
|
15
|
+
messages: LLMMessage[];
|
|
16
|
+
}
|
|
17
|
+
export interface LLMOutput {
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DocumentLLM {
|
|
21
|
+
/** Generic completion — returns raw text. */
|
|
22
|
+
complete(input: LLMInput): Promise<LLMOutput>;
|
|
23
|
+
/** Structured completion — returns a zod-validated value. */
|
|
24
|
+
completeJSON<T>(input: LLMInput, schema: z.ZodType<T>): Promise<T>;
|
|
25
|
+
/** Token estimate for budget checks (character-based is acceptable). */
|
|
26
|
+
countTokens(text: string): number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Default character-based token estimate.
|
|
30
|
+
* NOTE: under-counts Chinese (~1 char ≈ 1 token vs len/4); budget gates are
|
|
31
|
+
* intentionally lenient. Callers may override by wrapping countTokens.
|
|
32
|
+
*/
|
|
33
|
+
export declare function estimateTokens(text: string): number;
|
|
34
|
+
/** Strip markdown code-fence around a JSON payload (helper for completeJSON impls). */
|
|
35
|
+
export declare function extractJSON(content: string): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — injectable LLM interface
|
|
3
|
+
*
|
|
4
|
+
* The engine does NOT bind to any specific LLM SDK. Consumers provide a
|
|
5
|
+
* `DocumentLLM` implementation. `completeJSON` parses + zod-validates the
|
|
6
|
+
* model response so all downstream consumers get typed data or a clean error.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Default character-based token estimate.
|
|
10
|
+
* NOTE: under-counts Chinese (~1 char ≈ 1 token vs len/4); budget gates are
|
|
11
|
+
* intentionally lenient. Callers may override by wrapping countTokens.
|
|
12
|
+
*/
|
|
13
|
+
export function estimateTokens(text) {
|
|
14
|
+
if (!text)
|
|
15
|
+
return 0;
|
|
16
|
+
// Count CJK chars at 1 token/char, others at ~4 chars/token.
|
|
17
|
+
let tokens = 0;
|
|
18
|
+
for (const ch of text) {
|
|
19
|
+
tokens += /[\u2E80-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF\u3000-\u303F]/.test(ch) ? 1 : 0.25;
|
|
20
|
+
}
|
|
21
|
+
return Math.max(1, Math.ceil(tokens));
|
|
22
|
+
}
|
|
23
|
+
/** Strip markdown code-fence around a JSON payload (helper for completeJSON impls). */
|
|
24
|
+
export function extractJSON(content) {
|
|
25
|
+
const match = content.match(/```json\s*([\s\S]*?)```/) || content.match(/```\s*([\s\S]*?)```/);
|
|
26
|
+
return (match ? match[1] : content).trim();
|
|
27
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — Pass 2 per-chapter fill-in (decisions 6/8/9).
|
|
3
|
+
*
|
|
4
|
+
* Four-layer context injection per chapter:
|
|
5
|
+
* ① outline points + global entities/decisions
|
|
6
|
+
* ② dependency digests (navigation)
|
|
7
|
+
* ③ upstream contract entities injected deterministically (registry + referenced full contracts)
|
|
8
|
+
* ④ source segments (original input by source_segments)
|
|
9
|
+
*
|
|
10
|
+
* Fills by point kind: entity/mixed → structured contract entities; narrative/mixed → Markdown.
|
|
11
|
+
* Output budget enforced per chapter (decision 6/P6). fill_once falls back to per-chapter when
|
|
12
|
+
* the estimated input context exceeds the budget (decision 9).
|
|
13
|
+
*/
|
|
14
|
+
import { Outline, OutlineChapter, Entities, ChapterComponent } from './schemas.js';
|
|
15
|
+
import type { DocumentLLM, LLMInput } from './llm.js';
|
|
16
|
+
export interface ChapterFill {
|
|
17
|
+
chapterId: string;
|
|
18
|
+
narrative: string;
|
|
19
|
+
entities: Entities;
|
|
20
|
+
tokens: number;
|
|
21
|
+
}
|
|
22
|
+
export interface MapContext {
|
|
23
|
+
outline: Outline;
|
|
24
|
+
digests: Map<string, string>;
|
|
25
|
+
upstreamEntities: Entities;
|
|
26
|
+
workRoot: string;
|
|
27
|
+
chapterById: Map<string, ChapterComponent>;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildChapterContext(chapter: OutlineChapter, ctx: MapContext): Promise<{
|
|
30
|
+
input: LLMInput;
|
|
31
|
+
estimatedTokens: number;
|
|
32
|
+
}>;
|
|
33
|
+
export interface FillChapterResult {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
fill?: ChapterFill;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function fillChapter(llm: DocumentLLM, chapter: OutlineChapter, ctx: MapContext, budgetTokens?: number): Promise<FillChapterResult>;
|
|
39
|
+
export declare function shouldFillOnce(outline: Outline, ctx: MapContext): Promise<boolean>;
|
|
40
|
+
export interface FillOnceResult {
|
|
41
|
+
ok: boolean;
|
|
42
|
+
chapters: ChapterFill[];
|
|
43
|
+
fellBackToPerChapter: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function fillOnce(llm: DocumentLLM, outline: Outline, ctx: MapContext): Promise<FillOnceResult>;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Engine — Pass 2 per-chapter fill-in (decisions 6/8/9).
|
|
3
|
+
*
|
|
4
|
+
* Four-layer context injection per chapter:
|
|
5
|
+
* ① outline points + global entities/decisions
|
|
6
|
+
* ② dependency digests (navigation)
|
|
7
|
+
* ③ upstream contract entities injected deterministically (registry + referenced full contracts)
|
|
8
|
+
* ④ source segments (original input by source_segments)
|
|
9
|
+
*
|
|
10
|
+
* Fills by point kind: entity/mixed → structured contract entities; narrative/mixed → Markdown.
|
|
11
|
+
* Output budget enforced per chapter (decision 6/P6). fill_once falls back to per-chapter when
|
|
12
|
+
* the estimated input context exceeds the budget (decision 9).
|
|
13
|
+
*/
|
|
14
|
+
import { parseEntities } from './schemas.js';
|
|
15
|
+
import { estimateTokens } from './llm.js';
|
|
16
|
+
import { fetchSegments } from './input-digest.js';
|
|
17
|
+
const MAP_SYSTEM = '你是方案文档撰写员。按给定章节要点"填空题"式展开:逐要点填充,不自由发挥。' +
|
|
18
|
+
'kind=entity 或 mixed 的要点 → 产出结构化契约实体(JSON);kind=narrative 或 mixed 的要点 → 产出叙述 Markdown。' +
|
|
19
|
+
'契约实体必须满足字段约束(接口须含 failure_examples,表须含 ddl/migration/rollback)。';
|
|
20
|
+
// ============= Four-layer context build (T6.2) =============
|
|
21
|
+
export async function buildChapterContext(chapter, ctx) {
|
|
22
|
+
const { outline, digests, upstreamEntities, workRoot } = ctx;
|
|
23
|
+
// ① outline points + global entities/decisions
|
|
24
|
+
const chapterPoints = chapter.points.map((p) => `${p.id} [${p.kind}] ${p.text}${(p.references ?? []).length ? ` refs=${(p.references ?? []).join(',')}` : ''}`).join('\n');
|
|
25
|
+
const globalEntities = outline.entities.map((e) => `${e.id} (${e.frozen}${e.standalone ? ', standalone' : ''})`).join(', ');
|
|
26
|
+
// ② dependency digests
|
|
27
|
+
const deps = [];
|
|
28
|
+
const chComp = ctx.chapterById.get(chapter.id);
|
|
29
|
+
for (const dep of chComp?.depends_on ?? []) {
|
|
30
|
+
const d = digests.get(dep);
|
|
31
|
+
if (d)
|
|
32
|
+
deps.push(`[依赖 ${dep}] ${d}`);
|
|
33
|
+
}
|
|
34
|
+
// ③ upstream contract entities: registry (all) + full contracts for referenced ids
|
|
35
|
+
const referencedIds = new Set();
|
|
36
|
+
for (const p of chapter.points)
|
|
37
|
+
for (const r of p.references ?? [])
|
|
38
|
+
referencedIds.add(r);
|
|
39
|
+
const registry = [
|
|
40
|
+
...upstreamEntities.interfaces.map((i) => `I ${i.id}: ${i.name}`),
|
|
41
|
+
...upstreamEntities.tables.map((t) => `T ${t.id}: ${t.name}`),
|
|
42
|
+
].join('\n');
|
|
43
|
+
const fullContracts = [
|
|
44
|
+
...upstreamEntities.interfaces.filter((i) => referencedIds.has(i.id)),
|
|
45
|
+
...upstreamEntities.tables.filter((t) => referencedIds.has(t.id)),
|
|
46
|
+
].map((e) => JSON.stringify(e)).join('\n');
|
|
47
|
+
// ④ source segments
|
|
48
|
+
const segmentIds = new Set();
|
|
49
|
+
for (const p of chapter.points)
|
|
50
|
+
for (const s of p.source_segments ?? [])
|
|
51
|
+
segmentIds.add(s);
|
|
52
|
+
const segments = segmentIds.size > 0 ? await fetchSegments(workRoot, [...segmentIds]) : '';
|
|
53
|
+
const content = [
|
|
54
|
+
`## 章节 ${chapter.id}(${chapter.title})`,
|
|
55
|
+
`### 要点清单(填空)`,
|
|
56
|
+
chapterPoints,
|
|
57
|
+
`### 全局实体/决策`,
|
|
58
|
+
globalEntities,
|
|
59
|
+
deps.length ? `### 依赖摘要\n${deps.join('\n\n')}` : '',
|
|
60
|
+
registry ? `### 上游契约实体注册表\n${registry}` : '',
|
|
61
|
+
fullContracts ? `### 上游契约实体完整契约(引用)\n${fullContracts}` : '',
|
|
62
|
+
segments ? `### 本片片段(原文)\n${segments}` : '',
|
|
63
|
+
].filter(Boolean).join('\n\n');
|
|
64
|
+
const input = {
|
|
65
|
+
system: MAP_SYSTEM,
|
|
66
|
+
messages: [{ role: 'user', content }],
|
|
67
|
+
};
|
|
68
|
+
return { input, estimatedTokens: estimateTokens(content) + estimateTokens(MAP_SYSTEM) };
|
|
69
|
+
}
|
|
70
|
+
export async function fillChapter(llm, chapter, ctx, budgetTokens) {
|
|
71
|
+
let input;
|
|
72
|
+
let estimatedTokens;
|
|
73
|
+
try {
|
|
74
|
+
const built = await buildChapterContext(chapter, ctx);
|
|
75
|
+
input = built.input;
|
|
76
|
+
estimatedTokens = built.estimatedTokens;
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return { ok: false, error: `Chapter "${chapter.id}" context build failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
80
|
+
}
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await llm.complete(input);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
return { ok: false, error: `Chapter "${chapter.id}" LLM call failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
87
|
+
}
|
|
88
|
+
const narrative = res.content.trim();
|
|
89
|
+
// Extract embedded contract entities (JSON blocks). Strict: if this chapter's points require
|
|
90
|
+
// entities (kind=entity|mixed with references) and none are extracted, fail loudly rather than
|
|
91
|
+
// silently produce a contract-less chapter (decision 1 layered source of truth).
|
|
92
|
+
let entities = { interfaces: [], tables: [], decisions: [] };
|
|
93
|
+
const jsonMatch = narrative.match(/```json\s*([\s\S]*?)```/g);
|
|
94
|
+
if (jsonMatch) {
|
|
95
|
+
const merged = { interfaces: [], tables: [], decisions: [] };
|
|
96
|
+
for (const block of jsonMatch) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(block.replace(/```json\s*/, '').replace(/```$/, '').trim());
|
|
99
|
+
if (parsed.entities) {
|
|
100
|
+
const e = parseEntities(parsed.entities);
|
|
101
|
+
merged.interfaces.push(...e.interfaces);
|
|
102
|
+
merged.tables.push(...e.tables);
|
|
103
|
+
merged.decisions.push(...e.decisions);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// non-entity JSON blocks are ignored; strict entity-presence check below catches gaps
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
entities = merged;
|
|
111
|
+
}
|
|
112
|
+
// Clean the narrative: remove the embedded ```json entity blocks so they are not rendered
|
|
113
|
+
// verbatim inside chapters (contract entities are rendered deterministically in the 契约 section).
|
|
114
|
+
const cleanNarrative = narrative.replace(/```json\s*[\s\S]*?```/g, '').replace(/\n{3,}/g, '\n\n').trim();
|
|
115
|
+
// Strict: entity-requiring points must have their referenced entities filled.
|
|
116
|
+
const requiredEntityIds = new Set();
|
|
117
|
+
for (const p of chapter.points) {
|
|
118
|
+
if ((p.kind === 'entity' || p.kind === 'mixed') && (p.references ?? []).length > 0) {
|
|
119
|
+
for (const ref of p.references)
|
|
120
|
+
requiredEntityIds.add(ref);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const filledEntityIds = new Set([
|
|
124
|
+
...entities.interfaces.map((i) => i.id),
|
|
125
|
+
...entities.tables.map((t) => t.id),
|
|
126
|
+
...entities.decisions.map((d) => d.id),
|
|
127
|
+
]);
|
|
128
|
+
for (const id of requiredEntityIds) {
|
|
129
|
+
if (!filledEntityIds.has(id)) {
|
|
130
|
+
return { ok: false, error: `Chapter "${chapter.id}" requires entity "${id}" (point references) but none was extracted` };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const tokens = estimateTokens(cleanNarrative) + estimateTokens(JSON.stringify(entities));
|
|
134
|
+
// Input-side budget guard: reject clearly-over-budget inputs (decision 6/P6 hard gate).
|
|
135
|
+
const maxInputTokens = budgetTokens ? budgetTokens * 4 : undefined;
|
|
136
|
+
if (maxInputTokens !== undefined && estimatedTokens > maxInputTokens) {
|
|
137
|
+
return { ok: false, error: `Chapter "${chapter.id}" input context ${estimatedTokens} tokens exceeds 4× output budget` };
|
|
138
|
+
}
|
|
139
|
+
if (budgetTokens !== undefined && tokens > budgetTokens) {
|
|
140
|
+
return { ok: false, error: `Chapter "${chapter.id}" output ${tokens} tokens exceeds budget ${budgetTokens}` };
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
ok: true,
|
|
144
|
+
fill: { chapterId: chapter.id, narrative: cleanNarrative, entities, tokens },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// ============= fill_once decision (T6.5/T6.6) =============
|
|
148
|
+
export async function shouldFillOnce(outline, ctx) {
|
|
149
|
+
if (!outline.fill_once?.enabled)
|
|
150
|
+
return false;
|
|
151
|
+
const budget = outline.fill_once.input_budget_tokens ?? 8000;
|
|
152
|
+
let total = 0;
|
|
153
|
+
for (const ch of outline.chapters) {
|
|
154
|
+
const { estimatedTokens } = await buildChapterContext(ch, ctx);
|
|
155
|
+
total += estimatedTokens;
|
|
156
|
+
}
|
|
157
|
+
return total <= budget;
|
|
158
|
+
}
|
|
159
|
+
const FILL_ONCE_SYSTEM = '你是方案文档撰写员。一次填完全部章节,输出结构化 JSON:' +
|
|
160
|
+
'{"chapters":{"<chapterId>":{"narrative":"本章叙述 Markdown","entities":{"interfaces":[...],"tables":[...],"decisions":[...]}},...}}。' +
|
|
161
|
+
'每章内容必须差异化(针对该章要点),禁止复制同一段到所有章节。契约实体须含 failure_examples(接口)/ddl(表)。';
|
|
162
|
+
export async function fillOnce(llm, outline, ctx) {
|
|
163
|
+
// Build a single combined context (all chapters) — bounded by shouldFillOnce pre-check.
|
|
164
|
+
const sections = [];
|
|
165
|
+
for (const ch of outline.chapters) {
|
|
166
|
+
const { input } = await buildChapterContext(ch, ctx);
|
|
167
|
+
sections.push(input.messages[0].content);
|
|
168
|
+
}
|
|
169
|
+
const res = await llm.complete({
|
|
170
|
+
system: FILL_ONCE_SYSTEM,
|
|
171
|
+
messages: [{ role: 'user', content: sections.join('\n\n=====\n\n') }],
|
|
172
|
+
});
|
|
173
|
+
const content = res.content.trim();
|
|
174
|
+
// Parse structured per-chapter output; fall back to per-chapter fill on parse failure.
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(content);
|
|
177
|
+
if (!parsed.chapters || typeof parsed.chapters !== 'object') {
|
|
178
|
+
return { ok: false, chapters: [], fellBackToPerChapter: true, error: 'fill_once: missing chapters object' };
|
|
179
|
+
}
|
|
180
|
+
const chapters = [];
|
|
181
|
+
for (const ch of outline.chapters) {
|
|
182
|
+
const entry = parsed.chapters[ch.id];
|
|
183
|
+
if (!entry || typeof entry.narrative !== 'string' || entry.narrative.trim().length === 0) {
|
|
184
|
+
return { ok: false, chapters: [], fellBackToPerChapter: true, error: `fill_once: chapter ${ch.id} missing narrative` };
|
|
185
|
+
}
|
|
186
|
+
let entities = { interfaces: [], tables: [], decisions: [] };
|
|
187
|
+
try {
|
|
188
|
+
if (entry.entities)
|
|
189
|
+
entities = parseEntities(entry.entities);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
entities = { interfaces: [], tables: [], decisions: [] };
|
|
193
|
+
}
|
|
194
|
+
const narrative = entry.narrative.trim();
|
|
195
|
+
chapters.push({
|
|
196
|
+
chapterId: ch.id,
|
|
197
|
+
narrative,
|
|
198
|
+
entities,
|
|
199
|
+
tokens: estimateTokens(narrative) + estimateTokens(JSON.stringify(entities)),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return { ok: true, chapters, fellBackToPerChapter: false };
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
chapters: [],
|
|
208
|
+
fellBackToPerChapter: true,
|
|
209
|
+
error: `fill_once: unparsable output (${e instanceof Error ? e.message : String(e)})`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { Outline, ScenarioProfile } from './schemas.js';
|
|
9
|
+
import type { DocumentLLM } from './llm.js';
|
|
10
|
+
import { GlobalInputDigest } from './input-digest.js';
|
|
11
|
+
export interface OutlineDiagnostic {
|
|
12
|
+
code: string;
|
|
13
|
+
severity: 'error' | 'warning';
|
|
14
|
+
message: string;
|
|
15
|
+
chapter?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface GenerateOutlineOptions {
|
|
18
|
+
profile: ScenarioProfile;
|
|
19
|
+
inputDigest: GlobalInputDigest;
|
|
20
|
+
prompt?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function generateOutline(llm: DocumentLLM, options: GenerateOutlineOptions): Promise<Outline>;
|
|
23
|
+
export declare function lintOutline(outline: Outline, profile: ScenarioProfile): OutlineDiagnostic[];
|
|
24
|
+
export declare function outlineHasErrors(diags: OutlineDiagnostic[]): boolean;
|
|
25
|
+
export declare function freezeOutline(workRoot: string, outline: Outline): Promise<string>;
|
|
26
|
+
export declare function readOutline(workRoot: string): Promise<Outline>;
|