@gordon.gan/specflow 1.5.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/document/forbidden-patterns.d.ts +8 -0
- 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 -6
- package/dist/integrations/shared/command-catalog.js +1 -1
- package/dist/integrations/shared/parity-manifest.js +4 -6
- package/package.json +2 -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/GUIDANCE_PACKS.md +1 -1
- 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 -21
- package/dist/cli/commands/approval-assemble.js +0 -95
- package/dist/core/approval/assemble.d.ts +0 -10
- package/dist/core/approval/assemble.js +0 -391
- package/dist/core/approval/index-schema.d.ts +0 -473
- package/dist/core/approval/index-schema.js +0 -146
- package/dist/core/approval/index.d.ts +0 -4
- package/dist/core/approval/index.js +0 -3
- package/dist/core/approval/paths.d.ts +0 -8
- package/dist/core/approval/paths.js +0 -28
- package/dist/core/approval/types.d.ts +0 -120
- package/dist/core/approval/types.js +0 -1
- 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 -1498
- package/prompts/approval/multi-repo-guidance.md +0 -202
- package/prompts/approval/project-conventions-guidance.md +0 -171
- package/prompts/approval/segmented-generation.md +0 -151
- package/skills/specflow-approval/SKILL.md +0 -640
- package/templates/approval-index.yaml +0 -72
- package/templates/approval-part.md +0 -15
- package/templates/approval.md +0 -261
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ explore(可选)→ propose → refine → apply → review → test → veri
|
|
|
72
72
|
│ ↓ │
|
|
73
73
|
│ refine ≥2 轮精化:挑战假设 / 新方案 / 探边界 / 质疑 scope │
|
|
74
74
|
│ ↓ │
|
|
75
|
-
│
|
|
75
|
+
│ document [可选] 通用长文档引擎 → 方案文档(0→1 / bugfix / feature) │
|
|
76
76
|
│ ↓ │
|
|
77
77
|
│ apply Phase A 重写 tasks.md → Phase B 逐任务 TDD 执行 │
|
|
78
78
|
│ (可选 /specflow:apply --yes:少打断,成功后串行验收) │
|
|
@@ -126,7 +126,7 @@ npm install -g @gordon.gan/specflow
|
|
|
126
126
|
npm install -g github:Gordon-Gan-Jiang/specflow
|
|
127
127
|
|
|
128
128
|
# 验证
|
|
129
|
-
specflow --version # 以 npm / package.json 为准(当前 1.
|
|
129
|
+
specflow --version # 以 npm / package.json 为准(当前 1.8.0-alpha)
|
|
130
130
|
specflow --help
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -266,7 +266,7 @@ specflow init
|
|
|
266
266
|
| 探索(可选) | `/specflow:explore` | `specflow:explore` | `$specflow-explore` |
|
|
267
267
|
| 规划 | `/specflow:propose` | `specflow:propose` | `$specflow-propose` |
|
|
268
268
|
| 精化 | `/specflow:refine` | `specflow:refine` | `$specflow-refine` |
|
|
269
|
-
|
|
|
269
|
+
| 文档(可选) | `/specflow:document` | `specflow:document` | `$specflow-document` |
|
|
270
270
|
| 构建 | `/specflow:apply` | `specflow:apply` | `$specflow-apply` |
|
|
271
271
|
| 审查 | `/specflow:review` | `specflow:review` | `$specflow-review` |
|
|
272
272
|
| 测试 | `/specflow:test` | `specflow:test` | `$specflow-test` |
|
|
@@ -282,7 +282,7 @@ specflow init
|
|
|
282
282
|
| **explore** | 读代码、比方案、定边界;产出 `explore.md`,confirmed 后 handoff 到 propose |
|
|
283
283
|
| **propose** | 一次产出 proposal、delta specs、design、tasks(第一轮深度思考,非占位骨架);叙述语言跟 `artifacts.language` |
|
|
284
284
|
| **refine** | 内部多轮循环(≥2 轮,AI 判断收敛);可更新任意 artifact |
|
|
285
|
-
| **
|
|
285
|
+
| **document** | 可选:`specflow document run <0to1|bugfix|feature>` 用**通用长文档引擎**生成方案文档——两遍扫描(Pass 1 大纲冻结 → Pass 2 逐章按要点填空)+ 分层真源(契约实体 zod + 受控叙述)+ 四层上下文注入(大纲要点 + 依赖摘要 + 契约注入 + 本片片段,保证注意力不稀释 / token 不易打满)+ 覆盖双通道校验 + AI Review。多业务场景 × 多方案模板,详见 `docs/GENERIC_LONGDOC_ENGINE_DESIGN.md` |
|
|
286
286
|
| **apply** | Phase A 重写 tasks.md;Phase B 按语言路由 ECC 审查 + TDD 逐任务执行;可选 `--yes` |
|
|
287
287
|
| **review** | 代码审查,对照 specs 检查回归 |
|
|
288
288
|
| **test** | 单元 + 集成 + E2E + 回归测试 |
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI: specflow document {plan|validate|run} — Generic LongDoc Engine, IDE-model driven.
|
|
3
|
+
*
|
|
4
|
+
* The engine does NOT call a third-party LLM API. In Cursor/Codex the Agent runs on the IDE's
|
|
5
|
+
* model, so:
|
|
6
|
+
* - `document plan <profile>` emits a step list the Agent executes with the IDE model.
|
|
7
|
+
* - `document validate --work-root <path>` deterministically validates + renders Agent-written
|
|
8
|
+
* artifacts (outline.yaml / chapters/*.md / entities.json) — no LLM call.
|
|
9
|
+
* - `document run <profile>` guides: plan → Agent executes → validate (requires injected LLM only
|
|
10
|
+
* for library/tests; CLI itself never fetches an API).
|
|
11
|
+
*/
|
|
12
|
+
import type { Command } from 'commander';
|
|
13
|
+
import type { DocumentLLM } from '../../core/document/llm.js';
|
|
14
|
+
export interface DocumentPlanOptions {
|
|
15
|
+
text?: string;
|
|
16
|
+
proposal?: string;
|
|
17
|
+
design?: string;
|
|
18
|
+
specs?: string;
|
|
19
|
+
tasks?: string;
|
|
20
|
+
code?: string;
|
|
21
|
+
workRoot?: string;
|
|
22
|
+
json?: boolean;
|
|
23
|
+
markdown?: boolean;
|
|
24
|
+
signoff?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface DocumentValidateOptions {
|
|
27
|
+
workRoot?: string;
|
|
28
|
+
json?: boolean;
|
|
29
|
+
noRender?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function planDocumentCommand(profileId: string, options: DocumentPlanOptions): Promise<{
|
|
32
|
+
exitCode: number;
|
|
33
|
+
payload: unknown;
|
|
34
|
+
}>;
|
|
35
|
+
export declare function validateDocumentCommand(options: DocumentValidateOptions): Promise<{
|
|
36
|
+
exitCode: number;
|
|
37
|
+
payload: unknown;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function injectLLM(llm: DocumentLLM): void;
|
|
40
|
+
export declare function runDocumentCommand(profileId: string | undefined, options: {
|
|
41
|
+
text?: string;
|
|
42
|
+
proposal?: string;
|
|
43
|
+
design?: string;
|
|
44
|
+
specs?: string;
|
|
45
|
+
code?: string;
|
|
46
|
+
workRoot?: string;
|
|
47
|
+
json?: boolean;
|
|
48
|
+
signoff?: boolean;
|
|
49
|
+
} & {
|
|
50
|
+
skipReview?: boolean;
|
|
51
|
+
maxRounds?: number;
|
|
52
|
+
}): Promise<{
|
|
53
|
+
exitCode: number;
|
|
54
|
+
payload: unknown;
|
|
55
|
+
}>;
|
|
56
|
+
export declare function detectDocumentCommand(options: {
|
|
57
|
+
text?: string;
|
|
58
|
+
proposal?: string;
|
|
59
|
+
design?: string;
|
|
60
|
+
specs?: string;
|
|
61
|
+
code?: string;
|
|
62
|
+
json?: boolean;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
exitCode: number;
|
|
65
|
+
payload: unknown;
|
|
66
|
+
}>;
|
|
67
|
+
export interface ApproveOptions {
|
|
68
|
+
proposal?: string;
|
|
69
|
+
specs?: string;
|
|
70
|
+
design?: string;
|
|
71
|
+
tasks?: string;
|
|
72
|
+
change?: string;
|
|
73
|
+
workspaceRoot?: string;
|
|
74
|
+
changes?: string;
|
|
75
|
+
workRoot?: string;
|
|
76
|
+
signoff?: boolean;
|
|
77
|
+
bundle?: boolean;
|
|
78
|
+
json?: boolean;
|
|
79
|
+
}
|
|
80
|
+
export declare function approveDocumentCommand(options: ApproveOptions): Promise<{
|
|
81
|
+
exitCode: number;
|
|
82
|
+
payload: unknown;
|
|
83
|
+
}>;
|
|
84
|
+
export declare function registerDocumentRunCommand(program: Command): void;
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI: specflow document {plan|validate|run} — Generic LongDoc Engine, IDE-model driven.
|
|
3
|
+
*
|
|
4
|
+
* The engine does NOT call a third-party LLM API. In Cursor/Codex the Agent runs on the IDE's
|
|
5
|
+
* model, so:
|
|
6
|
+
* - `document plan <profile>` emits a step list the Agent executes with the IDE model.
|
|
7
|
+
* - `document validate --work-root <path>` deterministically validates + renders Agent-written
|
|
8
|
+
* artifacts (outline.yaml / chapters/*.md / entities.json) — no LLM call.
|
|
9
|
+
* - `document run <profile>` guides: plan → Agent executes → validate (requires injected LLM only
|
|
10
|
+
* for library/tests; CLI itself never fetches an API).
|
|
11
|
+
*/
|
|
12
|
+
import { promises as fs } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { loadChapterLibrary } from '../../core/document/chapters.js';
|
|
15
|
+
import { loadProfile } from '../../core/document/profiles.js';
|
|
16
|
+
import { runDocument, planSteps, validateWork } from '../../core/document/engine.js';
|
|
17
|
+
import { detectScene, confirmationPrompt } from '../../core/document/scene-detect.js';
|
|
18
|
+
async function readInputFile(path, type) {
|
|
19
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
20
|
+
return { type, source: path, content };
|
|
21
|
+
}
|
|
22
|
+
async function collectInputs(options) {
|
|
23
|
+
const inputs = [];
|
|
24
|
+
if (options.text)
|
|
25
|
+
inputs.push({ type: 'description', source: 'description', content: options.text });
|
|
26
|
+
if (options.proposal)
|
|
27
|
+
inputs.push(await readInputFile(options.proposal, 'proposal'));
|
|
28
|
+
if (options.design)
|
|
29
|
+
inputs.push(await readInputFile(options.design, 'design'));
|
|
30
|
+
if (options.specs)
|
|
31
|
+
inputs.push(await readInputFile(options.specs, 'specs'));
|
|
32
|
+
if (options.tasks)
|
|
33
|
+
inputs.push(await readInputFile(options.tasks, 'tasks'));
|
|
34
|
+
if (options.code)
|
|
35
|
+
inputs.push(await readInputFile(options.code, 'code'));
|
|
36
|
+
return inputs;
|
|
37
|
+
}
|
|
38
|
+
function inputFeatures(inputs, opts) {
|
|
39
|
+
return {
|
|
40
|
+
containsApiChange: inputs.some((i) => i.type === 'design' || i.type === 'specs'),
|
|
41
|
+
hasPersistence: inputs.some((i) => i.type === 'specs'),
|
|
42
|
+
uiInScope: false,
|
|
43
|
+
hasLogic: true,
|
|
44
|
+
includeSignoff: opts?.includeSignoff ?? false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function stepsToMarkdown(steps, profileId) {
|
|
48
|
+
const lines = [
|
|
49
|
+
`# 文档生成步骤(${profileId})`,
|
|
50
|
+
'',
|
|
51
|
+
'> 请你在当前 IDE 会话中用模型逐步执行以下步骤,产物写入 workRoot。',
|
|
52
|
+
'> 完成后运行 `specflow document validate --work-root <path>` 校验。',
|
|
53
|
+
'',
|
|
54
|
+
];
|
|
55
|
+
for (const s of steps) {
|
|
56
|
+
lines.push(`## ${s.id}(${s.phase})`, '');
|
|
57
|
+
lines.push(`**做什么**:${s.description}`, '');
|
|
58
|
+
lines.push(`**参考提示词**:\`${s.prompt}\``, '');
|
|
59
|
+
if (s.inputFiles.length)
|
|
60
|
+
lines.push(`**输入**:${s.inputFiles.join(', ')}`, '');
|
|
61
|
+
lines.push(`**输出**:\`${s.outputFile}\``, '');
|
|
62
|
+
lines.push(`**必做**:${s.required ? '是' : '否(可选)'}`, '');
|
|
63
|
+
lines.push('', '---', '');
|
|
64
|
+
}
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
// ============= document plan =============
|
|
68
|
+
export async function planDocumentCommand(profileId, options) {
|
|
69
|
+
try {
|
|
70
|
+
const library = await loadChapterLibrary();
|
|
71
|
+
const profile = await loadProfile(profileId);
|
|
72
|
+
const inputs = await collectInputs(options);
|
|
73
|
+
const workRoot = options.workRoot ?? join(process.cwd(), '.specflow', 'document');
|
|
74
|
+
await fs.mkdir(workRoot, { recursive: true });
|
|
75
|
+
const result = planSteps({
|
|
76
|
+
profile,
|
|
77
|
+
library,
|
|
78
|
+
inputFeatures: inputFeatures(inputs, { includeSignoff: options.signoff }),
|
|
79
|
+
inputs,
|
|
80
|
+
workRoot,
|
|
81
|
+
projectRoot: process.cwd(),
|
|
82
|
+
});
|
|
83
|
+
const payload = {
|
|
84
|
+
ok: true,
|
|
85
|
+
profile: profileId,
|
|
86
|
+
workRoot,
|
|
87
|
+
chapters: result.chapters,
|
|
88
|
+
steps: result.steps,
|
|
89
|
+
};
|
|
90
|
+
if (options.json) {
|
|
91
|
+
return { exitCode: 0, payload };
|
|
92
|
+
}
|
|
93
|
+
if (options.markdown) {
|
|
94
|
+
console.info(stepsToMarkdown(result.steps, profileId));
|
|
95
|
+
return { exitCode: 0, payload };
|
|
96
|
+
}
|
|
97
|
+
console.info(`文档生成步骤(${profileId}),workRoot: ${workRoot}`);
|
|
98
|
+
console.info('');
|
|
99
|
+
for (const s of result.steps) {
|
|
100
|
+
console.info(` [${s.phase}] ${s.id}: ${s.title}`);
|
|
101
|
+
console.info(` → ${s.outputFile}`);
|
|
102
|
+
}
|
|
103
|
+
console.info('');
|
|
104
|
+
console.info('请用 IDE 模型按上述步骤生成产物,然后运行:');
|
|
105
|
+
console.info(` specflow document validate --work-root ${workRoot}`);
|
|
106
|
+
return { exitCode: 0, payload };
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
if (options.json)
|
|
111
|
+
return { exitCode: 1, payload: { ok: false, error: message } };
|
|
112
|
+
console.error(`Error: ${message}`);
|
|
113
|
+
return { exitCode: 1, payload: { ok: false, error: message } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ============= document validate =============
|
|
117
|
+
export async function validateDocumentCommand(options) {
|
|
118
|
+
try {
|
|
119
|
+
const workRoot = options.workRoot ?? join(process.cwd(), '.specflow', 'document');
|
|
120
|
+
const library = await loadChapterLibrary();
|
|
121
|
+
// Detect profile from outline.yaml (or require it).
|
|
122
|
+
const outlineRaw = await fs.readFile(join(workRoot, 'outline.yaml'), 'utf-8');
|
|
123
|
+
const profileMatch = outlineRaw.match(/^profile:\s*(\S+)/m);
|
|
124
|
+
if (!profileMatch) {
|
|
125
|
+
throw new Error(`Cannot detect profile from ${join(workRoot, 'outline.yaml')}`);
|
|
126
|
+
}
|
|
127
|
+
const profile = await loadProfile(profileMatch[1]);
|
|
128
|
+
const result = await validateWork({
|
|
129
|
+
profile,
|
|
130
|
+
library,
|
|
131
|
+
workRoot,
|
|
132
|
+
renderHtmlOutput: !options.noRender,
|
|
133
|
+
});
|
|
134
|
+
const payload = {
|
|
135
|
+
ok: result.ok,
|
|
136
|
+
workRoot,
|
|
137
|
+
document: result.documentMd ? join(workRoot, 'document.md') : undefined,
|
|
138
|
+
html: result.documentHtml ? join(workRoot, 'document.html') : undefined,
|
|
139
|
+
diagnostics: result.diagnostics,
|
|
140
|
+
};
|
|
141
|
+
if (options.json) {
|
|
142
|
+
return { exitCode: result.ok ? 0 : 1, payload };
|
|
143
|
+
}
|
|
144
|
+
if (!result.ok) {
|
|
145
|
+
console.error(`Document validation failed:`);
|
|
146
|
+
for (const d of result.diagnostics) {
|
|
147
|
+
console.error(` [${d.gate}][${d.severity}] ${d.message}`);
|
|
148
|
+
}
|
|
149
|
+
return { exitCode: 1, payload };
|
|
150
|
+
}
|
|
151
|
+
console.info(`Document valid: ${join(workRoot, 'document.md')}`);
|
|
152
|
+
if (result.documentHtml)
|
|
153
|
+
console.info(`HTML: ${join(workRoot, 'document.html')}`);
|
|
154
|
+
return { exitCode: 0, payload };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
158
|
+
if (options.json)
|
|
159
|
+
return { exitCode: 1, payload: { ok: false, error: message } };
|
|
160
|
+
console.error(`Error: ${message}`);
|
|
161
|
+
return { exitCode: 1, payload: { ok: false, error: message } };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ============= document run (Agent-driven guide; library `runDocument` needs an injected LLM) =============
|
|
165
|
+
let injectedLLM = null;
|
|
166
|
+
export function injectLLM(llm) {
|
|
167
|
+
injectedLLM = llm;
|
|
168
|
+
}
|
|
169
|
+
function getInjectedLLM() {
|
|
170
|
+
return injectedLLM;
|
|
171
|
+
}
|
|
172
|
+
export async function runDocumentCommand(profileId, options) {
|
|
173
|
+
// Collect inputs first (needed for scene auto-detection).
|
|
174
|
+
const inputs = await collectInputs(options);
|
|
175
|
+
if (inputs.length === 0) {
|
|
176
|
+
const msg = 'No input provided. Use --text or --proposal/--design/--specs/--code.';
|
|
177
|
+
if (options.json)
|
|
178
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
179
|
+
console.error(`Error: ${msg}`);
|
|
180
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
181
|
+
}
|
|
182
|
+
const text = inputs.map((i) => i.content).join('\n');
|
|
183
|
+
// Scene resolution: explicit profile, or auto-detect from natural language; confirm when uncertain.
|
|
184
|
+
let resolvedProfile = profileId ?? '';
|
|
185
|
+
if (!resolvedProfile) {
|
|
186
|
+
const det = detectScene(text);
|
|
187
|
+
if (det.profile && det.confidence >= 0.5) {
|
|
188
|
+
resolvedProfile = det.profile;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const msg = confirmationPrompt(det, text) +
|
|
192
|
+
'\n\n确认后重新运行,例如:\n specflow document run <profile> --text "..."';
|
|
193
|
+
const payload = { ok: false, needsConfirmation: true, candidates: det.candidates, message: msg };
|
|
194
|
+
if (options.json)
|
|
195
|
+
return { exitCode: 0, payload };
|
|
196
|
+
console.info(msg);
|
|
197
|
+
return { exitCode: 0, payload: { ok: false, needsConfirmation: true } };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// One-click entry: no injected LLM → emit the Agent execution directive. The Agent (running on
|
|
201
|
+
// the IDE model) reads this and auto-completes plan → generate → validate. CLI itself never fetches an API.
|
|
202
|
+
const llm = getInjectedLLM();
|
|
203
|
+
if (!llm) {
|
|
204
|
+
const workRoot = options.workRoot ?? join(process.cwd(), '.specflow', 'document');
|
|
205
|
+
await fs.mkdir(workRoot, { recursive: true });
|
|
206
|
+
const library = await loadChapterLibrary();
|
|
207
|
+
const profile = await loadProfile(resolvedProfile);
|
|
208
|
+
const plan = planSteps({ profile, library, inputFeatures: inputFeatures(inputs, { includeSignoff: options.signoff }), inputs, workRoot, projectRoot: process.cwd() });
|
|
209
|
+
const directive = [
|
|
210
|
+
`# 一键生成技术文档(${resolvedProfile})`,
|
|
211
|
+
'',
|
|
212
|
+
`请作为 Agent 用当前 IDE 模型自动完成以下步骤(勿中途停下来等用户分步),产物写入 workRoot: ${workRoot}`,
|
|
213
|
+
'',
|
|
214
|
+
`## 步骤`,
|
|
215
|
+
...plan.steps.map((s) => `- [${s.phase}] **${s.id}**: ${s.title} → 输出 \`${s.outputFile}\`${s.required ? '' : '(可选)'}`),
|
|
216
|
+
'',
|
|
217
|
+
`## 完成后`,
|
|
218
|
+
`运行 \`specflow document validate --work-root ${workRoot}\`,通过后向用户报告 document.md 路径;失败则修正产物后重跑。`,
|
|
219
|
+
'',
|
|
220
|
+
`提示词模板参考(IDE 资产):`,
|
|
221
|
+
`- outline: .claude/specflow/prompts/document/outline/general.md`,
|
|
222
|
+
`- map: .claude/specflow/prompts/document/map/<章节>.md`,
|
|
223
|
+
`- review: .claude/specflow/prompts/document/review/ai-review.md`,
|
|
224
|
+
].join('\n');
|
|
225
|
+
const payload = { ok: true, mode: 'agent-directive', workRoot, steps: plan.steps };
|
|
226
|
+
if (options.json)
|
|
227
|
+
return { exitCode: 0, payload };
|
|
228
|
+
console.info(directive);
|
|
229
|
+
return { exitCode: 0, payload };
|
|
230
|
+
}
|
|
231
|
+
// Delegates to the library runDocument for completeness (tests use this).
|
|
232
|
+
try {
|
|
233
|
+
const library = await loadChapterLibrary();
|
|
234
|
+
const profile = await loadProfile(resolvedProfile);
|
|
235
|
+
const workRoot = options.workRoot ?? join(process.cwd(), '.specflow', 'document');
|
|
236
|
+
await fs.mkdir(workRoot, { recursive: true });
|
|
237
|
+
const result = await runDocument({
|
|
238
|
+
llm,
|
|
239
|
+
workRoot,
|
|
240
|
+
profile,
|
|
241
|
+
library,
|
|
242
|
+
inputs,
|
|
243
|
+
inputFeatures: inputFeatures(inputs, { includeSignoff: options.signoff }),
|
|
244
|
+
change: resolvedProfile,
|
|
245
|
+
skipReview: options.skipReview ?? false,
|
|
246
|
+
maxRounds: options.maxRounds ?? 3,
|
|
247
|
+
renderHtmlOutput: false,
|
|
248
|
+
persist: true,
|
|
249
|
+
});
|
|
250
|
+
const payload = {
|
|
251
|
+
ok: result.ok,
|
|
252
|
+
stage: result.stage,
|
|
253
|
+
workRoot,
|
|
254
|
+
document: result.documentMd ? join(workRoot, 'document.md') : undefined,
|
|
255
|
+
diagnostics: result.diagnostics,
|
|
256
|
+
};
|
|
257
|
+
if (options.json)
|
|
258
|
+
return { exitCode: result.ok ? 0 : 1, payload };
|
|
259
|
+
if (!result.ok) {
|
|
260
|
+
console.error(`Document generation failed at stage "${result.stage}":`);
|
|
261
|
+
for (const d of result.diagnostics)
|
|
262
|
+
console.error(` [${d.gate}][${d.severity}] ${d.message}`);
|
|
263
|
+
return { exitCode: 1, payload };
|
|
264
|
+
}
|
|
265
|
+
console.info(`Document generated (${resolvedProfile}) in ${workRoot}`);
|
|
266
|
+
return { exitCode: 0, payload };
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
270
|
+
if (options.json)
|
|
271
|
+
return { exitCode: 1, payload: { ok: false, error: message } };
|
|
272
|
+
console.error(`Error: ${message}`);
|
|
273
|
+
return { exitCode: 1, payload: { ok: false, error: message } };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// ============= document detect =============
|
|
277
|
+
export async function detectDocumentCommand(options) {
|
|
278
|
+
const inputs = await collectInputs(options);
|
|
279
|
+
const text = inputs.map((i) => i.content).join('\n');
|
|
280
|
+
const det = detectScene(text);
|
|
281
|
+
const payload = {
|
|
282
|
+
ok: true,
|
|
283
|
+
detected: det.profile,
|
|
284
|
+
confidence: det.confidence,
|
|
285
|
+
candidates: det.candidates,
|
|
286
|
+
reasons: det.reasons,
|
|
287
|
+
};
|
|
288
|
+
if (options.json)
|
|
289
|
+
return { exitCode: 0, payload };
|
|
290
|
+
if (det.profile) {
|
|
291
|
+
console.info(`检测到场景: ${det.profile}(置信度 ${Math.round(det.confidence * 100)}%)`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.info(confirmationPrompt(det, text));
|
|
295
|
+
}
|
|
296
|
+
return { exitCode: 0, payload };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Discover multi-repo four-artifact sets under a workspace root.
|
|
300
|
+
* A repo contributes if `<repoRoot>/specflow/changes/<change>/` contains proposal/specs/design/tasks.
|
|
301
|
+
*/
|
|
302
|
+
async function discoverRepoChanges(workspaceRoot, spec) {
|
|
303
|
+
const repos = [];
|
|
304
|
+
if (spec) {
|
|
305
|
+
for (const part of spec.split(',')) {
|
|
306
|
+
const [repo, change] = part.trim().split(':');
|
|
307
|
+
if (repo && change)
|
|
308
|
+
repos.push({ repo, change, root: join(workspaceRoot, repo) });
|
|
309
|
+
}
|
|
310
|
+
return repos;
|
|
311
|
+
}
|
|
312
|
+
// Auto-scan: workspaceRoot/<repo>/specflow/changes/*/
|
|
313
|
+
const entries = await fs.readdir(workspaceRoot, { withFileTypes: true });
|
|
314
|
+
for (const e of entries) {
|
|
315
|
+
if (!e.isDirectory())
|
|
316
|
+
continue;
|
|
317
|
+
const changesDir = join(workspaceRoot, e.name, 'specflow', 'changes');
|
|
318
|
+
try {
|
|
319
|
+
const changes = await fs.readdir(changesDir);
|
|
320
|
+
for (const ch of changes) {
|
|
321
|
+
if (ch.startsWith('archive'))
|
|
322
|
+
continue;
|
|
323
|
+
repos.push({ repo: e.name, change: ch, root: join(workspaceRoot, e.name) });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// no specflow/changes in this dir → skip
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return repos;
|
|
331
|
+
}
|
|
332
|
+
export async function approveDocumentCommand(options) {
|
|
333
|
+
const library = await loadChapterLibrary();
|
|
334
|
+
const profile = await loadProfile('approve');
|
|
335
|
+
const dirs = [];
|
|
336
|
+
if (options.workspaceRoot) {
|
|
337
|
+
dirs.push(...await discoverRepoChanges(options.workspaceRoot, options.changes ?? ''));
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const change = options.change ?? 'approval';
|
|
341
|
+
dirs.push({ repo: 'local', change, root: process.cwd() });
|
|
342
|
+
}
|
|
343
|
+
if (dirs.length === 0) {
|
|
344
|
+
const msg = 'No four-artifact change found. Provide --proposal/--specs/--design/--tasks or --workspace-root.';
|
|
345
|
+
if (options.json)
|
|
346
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
347
|
+
console.error(`Error: ${msg}`);
|
|
348
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
349
|
+
}
|
|
350
|
+
// Per-repo approve plan (read four artifacts, emit Agent generation steps).
|
|
351
|
+
const plans = [];
|
|
352
|
+
for (const d of dirs) {
|
|
353
|
+
const changeDir = join(d.root, 'specflow', 'changes', d.change);
|
|
354
|
+
const inputs = [];
|
|
355
|
+
for (const [type, file] of [['proposal', 'proposal.md'], ['specs', 'spec.md'], ['design', 'design.md'], ['tasks', 'tasks.md']]) {
|
|
356
|
+
try {
|
|
357
|
+
const content = await fs.readFile(join(changeDir, file), 'utf-8');
|
|
358
|
+
inputs.push({ type, source: join(changeDir, file), content });
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// optional missing artifact → skip
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (inputs.length === 0) {
|
|
365
|
+
// fall back to explicit options (four artifacts)
|
|
366
|
+
if (options.proposal)
|
|
367
|
+
inputs.push(await readInputFile(options.proposal, 'proposal'));
|
|
368
|
+
if (options.specs)
|
|
369
|
+
inputs.push(await readInputFile(options.specs, 'specs'));
|
|
370
|
+
if (options.design)
|
|
371
|
+
inputs.push(await readInputFile(options.design, 'design'));
|
|
372
|
+
if (options.tasks)
|
|
373
|
+
inputs.push(await readInputFile(options.tasks, 'tasks'));
|
|
374
|
+
}
|
|
375
|
+
const workRoot = options.workRoot ?? join(changeDir, 'approval');
|
|
376
|
+
await fs.mkdir(workRoot, { recursive: true });
|
|
377
|
+
const plan = planSteps({
|
|
378
|
+
profile,
|
|
379
|
+
library,
|
|
380
|
+
inputFeatures: inputFeatures(inputs, { includeSignoff: options.signoff }),
|
|
381
|
+
inputs,
|
|
382
|
+
workRoot,
|
|
383
|
+
projectRoot: d.root,
|
|
384
|
+
});
|
|
385
|
+
plans.push({ repo: d.repo, change: d.change, steps: plan.steps, workRoot });
|
|
386
|
+
}
|
|
387
|
+
// Multi-repo bundle: merge each repo's generated document.md into one approval-unified.md.
|
|
388
|
+
if (options.bundle) {
|
|
389
|
+
if (plans.length < 2) {
|
|
390
|
+
const msg = '--bundle requires multiple repos (--workspace-root or --changes with ≥2).';
|
|
391
|
+
if (options.json)
|
|
392
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
393
|
+
console.error(`Error: ${msg}`);
|
|
394
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
395
|
+
}
|
|
396
|
+
const sections = [`# 审批合订(多仓)`, '', `> 各仓审批文档合订视图(只读)。各仓 document.md 为真源。`, ''];
|
|
397
|
+
const errors = [];
|
|
398
|
+
for (const p of plans) {
|
|
399
|
+
const docPath = join(p.workRoot, 'document.md');
|
|
400
|
+
try {
|
|
401
|
+
const content = await fs.readFile(docPath, 'utf-8');
|
|
402
|
+
sections.push(`---`, '', `## 来源 · ${p.repo}(\`${p.change}\`)`, '', content.trim(), '');
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
errors.push(`${p.repo}/${p.change} document.md 未生成(请先完成该仓生成并 validate)`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (errors.length > 0) {
|
|
409
|
+
const msg = `合订失败:\n ${errors.join('\n ')}`;
|
|
410
|
+
if (options.json)
|
|
411
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
412
|
+
console.error(msg);
|
|
413
|
+
return { exitCode: 1, payload: { ok: false, error: msg } };
|
|
414
|
+
}
|
|
415
|
+
const unifiedPath = join(plans[0].workRoot, 'approval-unified.md');
|
|
416
|
+
await fs.writeFile(unifiedPath, sections.join('\n'), 'utf-8');
|
|
417
|
+
const payload = { ok: true, mode: 'bundle', unifiedPath, sources: plans.map((p) => ({ repo: p.repo, change: p.change, document: join(p.workRoot, 'document.md') })) };
|
|
418
|
+
if (options.json)
|
|
419
|
+
return { exitCode: 0, payload };
|
|
420
|
+
console.info(`多仓合订完成: ${unifiedPath}`);
|
|
421
|
+
return { exitCode: 0, payload };
|
|
422
|
+
}
|
|
423
|
+
// Build directive.
|
|
424
|
+
const lines = ['# 审批文档生成(approve · 四件套 → 审批)', ''];
|
|
425
|
+
lines.push('请作为 Agent 用当前 IDE 模型,对每个仓的四件套生成审批文档(含闭环检查/可实施性/质量红线),产物写入各仓 workRoot,完成后 validate。');
|
|
426
|
+
lines.push('');
|
|
427
|
+
for (const p of plans) {
|
|
428
|
+
lines.push(`## 仓 ${p.repo}(change: ${p.change})· workRoot: ${p.workRoot}`);
|
|
429
|
+
for (const s of p.steps) {
|
|
430
|
+
lines.push(`- [${s.phase}] **${s.id}**: ${s.title} → \`${s.outputFile}\``);
|
|
431
|
+
}
|
|
432
|
+
lines.push(` - 完成后: \`specflow document validate --work-root ${p.workRoot}\``);
|
|
433
|
+
lines.push('');
|
|
434
|
+
}
|
|
435
|
+
if (plans.length > 1) {
|
|
436
|
+
lines.push('## 多仓合订(主仓)');
|
|
437
|
+
lines.push('各仓审批文档生成并 validate 通过后,主仓汇总为统一视图(只读合订):');
|
|
438
|
+
lines.push(` - 合并各仓 document.md 为 approval-unified.md(主仓: ${plans[0].repo})`);
|
|
439
|
+
}
|
|
440
|
+
const payload = { ok: true, mode: 'approve', plans: plans.map((p) => ({ repo: p.repo, change: p.change, workRoot: p.workRoot, steps: p.steps })) };
|
|
441
|
+
if (options.json)
|
|
442
|
+
return { exitCode: 0, payload };
|
|
443
|
+
console.info(lines.join('\n'));
|
|
444
|
+
return { exitCode: 0, payload };
|
|
445
|
+
}
|
|
446
|
+
// ============= registration =============
|
|
447
|
+
export function registerDocumentRunCommand(program) {
|
|
448
|
+
const doc = program
|
|
449
|
+
.command('document')
|
|
450
|
+
.description('Generate scenario-based solution documents (Generic LongDoc Engine)');
|
|
451
|
+
doc
|
|
452
|
+
.command('detect')
|
|
453
|
+
.description('Auto-detect the scene profile (0to1 | bugfix | feature) from natural language')
|
|
454
|
+
.option('--text <text>', 'Free-text problem description')
|
|
455
|
+
.option('--proposal <path>', 'Proposal input file')
|
|
456
|
+
.option('--design <path>', 'Design input file')
|
|
457
|
+
.option('--specs <path>', 'Specs input file')
|
|
458
|
+
.option('--code <path>', 'Code input file')
|
|
459
|
+
.option('--json', 'Output detection as JSON')
|
|
460
|
+
.action(async (opts) => {
|
|
461
|
+
const { exitCode, payload } = await detectDocumentCommand(opts);
|
|
462
|
+
if (exitCode !== 0)
|
|
463
|
+
process.exitCode = exitCode;
|
|
464
|
+
if (opts.json)
|
|
465
|
+
console.info(JSON.stringify(payload, null, 2));
|
|
466
|
+
});
|
|
467
|
+
doc
|
|
468
|
+
.command('plan <profile>')
|
|
469
|
+
.description('Emit the Agent-executable step list (IDE model does the work; no LLM API call)')
|
|
470
|
+
.option('--text <text>', 'Free-text problem description')
|
|
471
|
+
.option('--proposal <path>', 'Proposal input file')
|
|
472
|
+
.option('--design <path>', 'Design input file')
|
|
473
|
+
.option('--specs <path>', 'Specs input file')
|
|
474
|
+
.option('--code <path>', 'Code input file')
|
|
475
|
+
.option('--work-root <path>', 'Work directory (default: .specflow/document)')
|
|
476
|
+
.option('--json', 'Output step list as JSON')
|
|
477
|
+
.option('--markdown', 'Output step list as Markdown')
|
|
478
|
+
.action(async (profile, opts) => {
|
|
479
|
+
const { exitCode, payload } = await planDocumentCommand(profile, opts);
|
|
480
|
+
if (exitCode !== 0)
|
|
481
|
+
process.exitCode = exitCode;
|
|
482
|
+
if (opts.json)
|
|
483
|
+
console.info(JSON.stringify(payload, null, 2));
|
|
484
|
+
});
|
|
485
|
+
doc
|
|
486
|
+
.command('validate')
|
|
487
|
+
.description('Deterministically validate + render Agent-written artifacts (no LLM)')
|
|
488
|
+
.option('--work-root <path>', 'Work directory (default: .specflow/document)')
|
|
489
|
+
.option('--json', 'Output result as JSON')
|
|
490
|
+
.option('--no-render', 'Skip HTML rendering')
|
|
491
|
+
.action(async (opts) => {
|
|
492
|
+
const { exitCode, payload } = await validateDocumentCommand(opts);
|
|
493
|
+
if (exitCode !== 0)
|
|
494
|
+
process.exitCode = exitCode;
|
|
495
|
+
if (opts.json)
|
|
496
|
+
console.info(JSON.stringify(payload, null, 2));
|
|
497
|
+
});
|
|
498
|
+
doc
|
|
499
|
+
.command('run [profile]')
|
|
500
|
+
.description('One-click generate a scenario document. Omit <profile> to auto-detect scene; confirm when uncertain.')
|
|
501
|
+
.option('--text <text>', 'Free-text problem description')
|
|
502
|
+
.option('--proposal <path>', 'Proposal input file')
|
|
503
|
+
.option('--design <path>', 'Design input file')
|
|
504
|
+
.option('--specs <path>', 'Specs input file')
|
|
505
|
+
.option('--code <path>', 'Code input file')
|
|
506
|
+
.option('--work-root <path>', 'Work directory (default: .specflow/document)')
|
|
507
|
+
.option('--signoff', 'Include an approval sign-off section')
|
|
508
|
+
.option('--skip-review', 'Skip Pass 3 AI review')
|
|
509
|
+
.option('--max-rounds <n>', 'AI review max rounds', '3')
|
|
510
|
+
.option('--json', 'Output result as JSON')
|
|
511
|
+
.action(async (profile, opts) => {
|
|
512
|
+
const { exitCode, payload } = await runDocumentCommand(profile, opts);
|
|
513
|
+
if (exitCode !== 0)
|
|
514
|
+
process.exitCode = exitCode;
|
|
515
|
+
if (opts.json)
|
|
516
|
+
console.info(JSON.stringify(payload, null, 2));
|
|
517
|
+
});
|
|
518
|
+
doc
|
|
519
|
+
.command('approve')
|
|
520
|
+
.description('Generate an approval document from the four artifacts (proposal/specs/design/tasks). Multi-repo with --workspace-root.')
|
|
521
|
+
.option('--proposal <path>', 'Proposal file')
|
|
522
|
+
.option('--specs <path>', 'Specs file')
|
|
523
|
+
.option('--design <path>', 'Design file')
|
|
524
|
+
.option('--tasks <path>', 'Tasks file')
|
|
525
|
+
.option('--change <name>', 'Change name (single-repo mode)')
|
|
526
|
+
.option('--workspace-root <path>', 'Multi-repo workspace root (scan per-repo four artifacts)')
|
|
527
|
+
.option('--changes <spec>', 'Multi-repo "repo1:change1,repo2:change2"')
|
|
528
|
+
.option('--work-root <path>', 'Work directory')
|
|
529
|
+
.option('--signoff', 'Include the approval sign-off section')
|
|
530
|
+
.option('--bundle', 'Merge each repo document.md into approval-unified.md (after all repos generated+validated)')
|
|
531
|
+
.option('--json', 'Output result as JSON')
|
|
532
|
+
.action(async (opts) => {
|
|
533
|
+
const { exitCode, payload } = await approveDocumentCommand(opts);
|
|
534
|
+
if (exitCode !== 0)
|
|
535
|
+
process.exitCode = exitCode;
|
|
536
|
+
if (opts.json)
|
|
537
|
+
console.info(JSON.stringify(payload, null, 2));
|
|
538
|
+
});
|
|
539
|
+
}
|
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 {
|
|
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
|
-
|
|
47
|
+
registerDocumentRunCommand(program);
|
|
48
48
|
program.exitOverride();
|
|
49
49
|
try {
|
|
50
50
|
await program.parseAsync();
|