speccore 8.3.333 → 8.3.341
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/.agents/skills/spec-ask/SKILL.md +4 -0
- package/.agents/skills/spec-unitize/SKILL.md +9 -1
- package/.agents/skills/speccore-router/SKILL.md +12 -11
- package/README.en.md +3 -11
- package/README.md +3 -13
- package/dist/cli.js +64 -206
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +530 -481
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +6 -0
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +9 -7
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/doc2spec.d.ts +44 -1
- package/dist/commands/doc2spec.d.ts.map +1 -1
- package/dist/commands/doc2spec.js +257 -5
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/done.js +1 -5
- package/dist/commands/done.js.map +1 -1
- package/dist/commands/execute.js +118 -66
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +6 -54
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +71 -47
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/create.d.ts.map +1 -1
- package/dist/commands/iteration/create.js +72 -10
- package/dist/commands/iteration/create.js.map +1 -1
- package/dist/commands/iteration/taskify.d.ts.map +1 -1
- package/dist/commands/iteration/taskify.js +133 -35
- package/dist/commands/iteration/taskify.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +24 -2
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/prompts.d.ts +6 -0
- package/dist/commands/prompts.d.ts.map +1 -1
- package/dist/commands/prompts.js +19 -10
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/status-panel.d.ts.map +1 -1
- package/dist/commands/status-panel.js +23 -34
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/unitize.d.ts +3 -0
- package/dist/commands/unitize.d.ts.map +1 -1
- package/dist/commands/unitize.js +32 -9
- package/dist/commands/unitize.js.map +1 -1
- package/dist/commands/update-env-configs.d.ts.map +1 -1
- package/dist/commands/update-env-configs.js +2 -1
- package/dist/commands/update-env-configs.js.map +1 -1
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +41 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/welcome.d.ts.map +1 -1
- package/dist/commands/welcome.js +41 -0
- package/dist/commands/welcome.js.map +1 -1
- package/dist/core/analyze-engine.js +3 -3
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/arbitration/l3-human-arbiter.js +1 -1
- package/dist/core/arbitration/l3-human-arbiter.js.map +1 -1
- package/dist/core/arbitration/verdict-generator.js +1 -1
- package/dist/core/arbitration/verdict-generator.js.map +1 -1
- package/dist/core/ask-context.d.ts.map +1 -1
- package/dist/core/ask-context.js +14 -3
- package/dist/core/ask-context.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +55 -49
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/code-graph/reporter.d.ts.map +1 -1
- package/dist/core/code-graph/reporter.js +3 -12
- package/dist/core/code-graph/reporter.js.map +1 -1
- package/dist/core/code-graph/requirement-linker.js +1 -1
- package/dist/core/code-graph/requirement-linker.js.map +1 -1
- package/dist/core/code-scanner.js +1 -1
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/deploy/engine.d.ts.map +1 -1
- package/dist/core/deploy/engine.js +58 -44
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/environment-config.d.ts +13 -0
- package/dist/core/environment-config.d.ts.map +1 -1
- package/dist/core/environment-config.js +28 -1
- package/dist/core/environment-config.js.map +1 -1
- package/dist/core/error-diagnosis.js +2 -2
- package/dist/core/error-diagnosis.js.map +1 -1
- package/dist/core/git-integration.d.ts +5 -7
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +137 -12
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/global-knowledge.d.ts.map +1 -1
- package/dist/core/global-knowledge.js +25 -11
- package/dist/core/global-knowledge.js.map +1 -1
- package/dist/core/handoff-generator.d.ts.map +1 -1
- package/dist/core/handoff-generator.js +3 -1
- package/dist/core/handoff-generator.js.map +1 -1
- package/dist/core/index-guard.d.ts.map +1 -1
- package/dist/core/index-guard.js +4 -26
- package/dist/core/index-guard.js.map +1 -1
- package/dist/core/intent-recognition.d.ts +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +68 -68
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/knowledge-graph.d.ts.map +1 -1
- package/dist/core/knowledge-graph.js +6 -4
- package/dist/core/knowledge-graph.js.map +1 -1
- package/dist/core/name-validator.d.ts.map +1 -1
- package/dist/core/name-validator.js +41 -20
- package/dist/core/name-validator.js.map +1 -1
- package/dist/core/pinyin-initials.d.ts +10 -0
- package/dist/core/pinyin-initials.d.ts.map +1 -0
- package/dist/core/pinyin-initials.js +44 -0
- package/dist/core/pinyin-initials.js.map +1 -0
- package/dist/core/prompt-builder.js +1 -1
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js.map +1 -1
- package/dist/core/reindex-engine.js +4 -4
- package/dist/core/reindex-engine.js.map +1 -1
- package/dist/core/skeleton-residue.d.ts +13 -0
- package/dist/core/skeleton-residue.d.ts.map +1 -1
- package/dist/core/skeleton-residue.js +83 -14
- package/dist/core/skeleton-residue.js.map +1 -1
- package/dist/core/spec-merger.d.ts.map +1 -1
- package/dist/core/spec-merger.js +3 -2
- package/dist/core/spec-merger.js.map +1 -1
- package/dist/core/state.d.ts +10 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +20 -1
- package/dist/core/state.js.map +1 -1
- package/dist/core/structured-extractor.d.ts +6 -0
- package/dist/core/structured-extractor.d.ts.map +1 -1
- package/dist/core/structured-extractor.js +6 -0
- package/dist/core/structured-extractor.js.map +1 -1
- package/dist/core/task-paths.d.ts +12 -0
- package/dist/core/task-paths.d.ts.map +1 -1
- package/dist/core/task-paths.js +33 -1
- package/dist/core/task-paths.js.map +1 -1
- package/dist/core/unified-config.d.ts +7 -1
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +70 -21
- package/dist/core/unified-config.js.map +1 -1
- package/dist/core/unit-staffing.d.ts +21 -4
- package/dist/core/unit-staffing.d.ts.map +1 -1
- package/dist/core/unit-staffing.js +59 -12
- package/dist/core/unit-staffing.js.map +1 -1
- package/dist/core/usage-analytics.js +1 -1
- package/dist/core/usage-analytics.js.map +1 -1
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +45 -5
- package/dist/core/validator.js.map +1 -1
- package/dist/core/verify-report-aggregator.d.ts.map +1 -1
- package/dist/core/verify-report-aggregator.js +19 -2
- package/dist/core/verify-report-aggregator.js.map +1 -1
- package/dist/utils/task-utils.d.ts +4 -1
- package/dist/utils/task-utils.d.ts.map +1 -1
- package/dist/utils/task-utils.js +30 -4
- package/dist/utils/task-utils.js.map +1 -1
- package/package.json +4 -3
- package/templates/deploy-examples.yaml +33 -5
- package/templates/html/speccore-ask-explain.html +1 -1
- package/templates/html/speccore-ask-guide.html +1 -1
- package/templates/html/speccore-ask-match.html +1 -1
- package/templates/html/speccore-ask-pipeline.html +1 -1
- package/templates/html/speccore-ask-result.html +1 -1
- package/templates/html/speccore-help.html +2 -2
- package/templates/speccore-yml-example.yml +174 -54
- package/templates/verify-spec-example.yaml +66 -1
- package/dist/commands/audit.d.ts +0 -12
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js +0 -540
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/backup.d.ts +0 -18
- package/dist/commands/backup.d.ts.map +0 -1
- package/dist/commands/backup.js +0 -102
- package/dist/commands/backup.js.map +0 -1
- package/dist/commands/baseline.d.ts +0 -13
- package/dist/commands/baseline.d.ts.map +0 -1
- package/dist/commands/baseline.js +0 -227
- package/dist/commands/baseline.js.map +0 -1
- package/dist/commands/chain.d.ts +0 -55
- package/dist/commands/chain.d.ts.map +0 -1
- package/dist/commands/chain.js +0 -501
- package/dist/commands/chain.js.map +0 -1
- package/dist/commands/clarify.d.ts +0 -17
- package/dist/commands/clarify.d.ts.map +0 -1
- package/dist/commands/clarify.js +0 -351
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/code-index.d.ts +0 -21
- package/dist/commands/code-index.d.ts.map +0 -1
- package/dist/commands/code-index.js +0 -193
- package/dist/commands/code-index.js.map +0 -1
- package/dist/commands/context.d.ts +0 -10
- package/dist/commands/context.d.ts.map +0 -1
- package/dist/commands/context.js +0 -213
- package/dist/commands/context.js.map +0 -1
- package/dist/commands/current.d.ts +0 -10
- package/dist/commands/current.d.ts.map +0 -1
- package/dist/commands/current.js +0 -53
- package/dist/commands/current.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -18
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js +0 -854
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/diff.d.ts +0 -10
- package/dist/commands/diff.d.ts.map +0 -1
- package/dist/commands/diff.js +0 -160
- package/dist/commands/diff.js.map +0 -1
- package/dist/commands/global-status.d.ts +0 -9
- package/dist/commands/global-status.d.ts.map +0 -1
- package/dist/commands/global-status.js +0 -135
- package/dist/commands/global-status.js.map +0 -1
- package/dist/commands/graph.d.ts +0 -25
- package/dist/commands/graph.d.ts.map +0 -1
- package/dist/commands/graph.js +0 -523
- package/dist/commands/graph.js.map +0 -1
- package/dist/commands/handover.d.ts +0 -11
- package/dist/commands/handover.d.ts.map +0 -1
- package/dist/commands/handover.js +0 -170
- package/dist/commands/handover.js.map +0 -1
- package/dist/commands/health.d.ts +0 -7
- package/dist/commands/health.d.ts.map +0 -1
- package/dist/commands/health.js +0 -80
- package/dist/commands/health.js.map +0 -1
- package/dist/commands/history.d.ts +0 -9
- package/dist/commands/history.d.ts.map +0 -1
- package/dist/commands/history.js +0 -117
- package/dist/commands/history.js.map +0 -1
- package/dist/commands/impact.d.ts +0 -12
- package/dist/commands/impact.d.ts.map +0 -1
- package/dist/commands/impact.js +0 -238
- package/dist/commands/impact.js.map +0 -1
- package/dist/commands/import.d.ts +0 -19
- package/dist/commands/import.d.ts.map +0 -1
- package/dist/commands/import.js +0 -673
- package/dist/commands/import.js.map +0 -1
- package/dist/commands/knowledge.d.ts +0 -6
- package/dist/commands/knowledge.d.ts.map +0 -1
- package/dist/commands/knowledge.js +0 -94
- package/dist/commands/knowledge.js.map +0 -1
- package/dist/commands/lifecycle.d.ts +0 -9
- package/dist/commands/lifecycle.d.ts.map +0 -1
- package/dist/commands/lifecycle.js +0 -288
- package/dist/commands/lifecycle.js.map +0 -1
- package/dist/commands/merge-check.d.ts +0 -10
- package/dist/commands/merge-check.d.ts.map +0 -1
- package/dist/commands/merge-check.js +0 -183
- package/dist/commands/merge-check.js.map +0 -1
- package/dist/commands/notify.d.ts +0 -13
- package/dist/commands/notify.d.ts.map +0 -1
- package/dist/commands/notify.js +0 -63
- package/dist/commands/notify.js.map +0 -1
- package/dist/commands/rag-index.d.ts +0 -19
- package/dist/commands/rag-index.d.ts.map +0 -1
- package/dist/commands/rag-index.js +0 -286
- package/dist/commands/rag-index.js.map +0 -1
- package/dist/commands/recommend.d.ts +0 -13
- package/dist/commands/recommend.d.ts.map +0 -1
- package/dist/commands/recommend.js +0 -37
- package/dist/commands/recommend.js.map +0 -1
- package/dist/commands/refresh.d.ts +0 -21
- package/dist/commands/refresh.d.ts.map +0 -1
- package/dist/commands/refresh.js +0 -211
- package/dist/commands/refresh.js.map +0 -1
- package/dist/commands/rollback.d.ts +0 -8
- package/dist/commands/rollback.d.ts.map +0 -1
- package/dist/commands/rollback.js +0 -113
- package/dist/commands/rollback.js.map +0 -1
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/trace.d.ts +0 -12
- package/dist/commands/trace.d.ts.map +0 -1
- package/dist/commands/trace.js +0 -163
- package/dist/commands/trace.js.map +0 -1
- package/dist/commands/tracker.d.ts +0 -2
- package/dist/commands/tracker.d.ts.map +0 -1
- package/dist/commands/tracker.js +0 -28
- package/dist/commands/tracker.js.map +0 -1
- package/dist/commands/verdict.d.ts +0 -16
- package/dist/commands/verdict.d.ts.map +0 -1
- package/dist/commands/verdict.js +0 -189
- package/dist/commands/verdict.js.map +0 -1
- package/dist/commands/workspace.d.ts +0 -20
- package/dist/commands/workspace.d.ts.map +0 -1
- package/dist/commands/workspace.js +0 -142
- package/dist/commands/workspace.js.map +0 -1
- package/dist/core/constraint-set.d.ts +0 -67
- package/dist/core/constraint-set.d.ts.map +0 -1
- package/dist/core/constraint-set.js +0 -151
- package/dist/core/constraint-set.js.map +0 -1
- package/dist/core/smart-recommend.d.ts +0 -16
- package/dist/core/smart-recommend.d.ts.map +0 -1
- package/dist/core/smart-recommend.js +0 -149
- package/dist/core/smart-recommend.js.map +0 -1
- package/dist/core/task-type-registry.d.ts +0 -110
- package/dist/core/task-type-registry.d.ts.map +0 -1
- package/dist/core/task-type-registry.js +0 -334
- package/dist/core/task-type-registry.js.map +0 -1
- package/dist/utils/mermaid-render.d.ts +0 -39
- package/dist/utils/mermaid-render.d.ts.map +0 -1
- package/dist/utils/mermaid-render.js +0 -284
- package/dist/utils/mermaid-render.js.map +0 -1
package/dist/commands/dev.js
DELETED
|
@@ -1,854 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.devCommand = devCommand;
|
|
37
|
-
/**
|
|
38
|
-
* dev — 智能开发入口(AI 引导)
|
|
39
|
-
* TTY → 终端框线,非 TTY → LLM 引导 HTML 页面
|
|
40
|
-
*/
|
|
41
|
-
const fs_extra_1 = require("fs-extra");
|
|
42
|
-
const path_1 = require("path");
|
|
43
|
-
const child_process_1 = require("child_process");
|
|
44
|
-
const logger_1 = require("../utils/logger");
|
|
45
|
-
const ask_host_ai_1 = require("../core/ask-host-ai");
|
|
46
|
-
const context_1 = require("../core/context");
|
|
47
|
-
const ask_host_ai_2 = require("../core/ask-host-ai");
|
|
48
|
-
const pipeline_engine_1 = require("../core/pipeline-engine");
|
|
49
|
-
const questions_1 = require("../core/questions");
|
|
50
|
-
const handoff_generator_1 = require("../core/handoff-generator");
|
|
51
|
-
const I18N = {
|
|
52
|
-
zh: { title: 'SPECCORE DEV', subtitle: '阶段完成', subtitleGuide: 'Pipeline 引导', next: '下一步', autoAll: '一键自动', skip: '跳过当前', restart: '重新开始', inputPlaceholder: '或者直接说...', send: '发送', stepInit: '初始化', stepDoc: '导入需求', stepAnalyze: '分析需求', stepSplit: '拆分任务', stepExec: '执行开发', stepPr: '提交 PR', stepDone: '归档收尾', iterLabel: '迭代' },
|
|
53
|
-
en: { title: 'SPECCORE DEV', subtitle: 'phases done', subtitleGuide: 'Pipeline Guide', next: 'Next Step', autoAll: 'Auto All', skip: 'Skip', restart: 'Restart', inputPlaceholder: 'Or just say it...', send: 'Send', stepInit: 'Init', stepDoc: 'Import PRD', stepAnalyze: 'Analyze', stepSplit: 'Split Tasks', stepExec: 'Execute', stepPr: 'Create PR', stepDone: 'Archive', iterLabel: 'Iteration' },
|
|
54
|
-
};
|
|
55
|
-
async function hasFeatureDirs(iterDir) {
|
|
56
|
-
try {
|
|
57
|
-
const featuresDir = (0, path_1.join)(iterDir, '010-requirements', 'features');
|
|
58
|
-
const entries = await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true });
|
|
59
|
-
return entries.some(e => e.isDirectory() && !e.name.startsWith('.'));
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async function devCommand(options) {
|
|
66
|
-
// ── Pipeline 模式 ──
|
|
67
|
-
if (options.pipeline) {
|
|
68
|
-
const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
|
|
69
|
-
if (!iteration) {
|
|
70
|
-
logger_1.logger.error('--pipeline 需要 --iteration');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// 检查是否为恢复模式
|
|
74
|
-
if (options.resume) {
|
|
75
|
-
const hasPipeline = await pipeline_engine_1.PipelineEngine.hasActivePipeline(process.cwd(), iteration);
|
|
76
|
-
if (hasPipeline) {
|
|
77
|
-
// v6.91.1+: 检测到 analyze pipeline 未结束,从 analyze 阶段继续
|
|
78
|
-
const engineState = await new pipeline_engine_1.PipelineEngine({ iteration, name: 'analyze', cwd: process.cwd() }).getState();
|
|
79
|
-
if (engineState && !options.from) {
|
|
80
|
-
logger_1.logger.info(`🔄 检测到 analyze pipeline 未结束(当前步骤: ${engineState.currentStep}),从 analyze 阶段继续`);
|
|
81
|
-
options.from = 'analyze';
|
|
82
|
-
}
|
|
83
|
-
await autoPipeline(options);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
logger_1.logger.warn('没有找到活跃的 Dev Pipeline,启动新的 Pipeline...');
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// 初始化新的 Dev Pipeline
|
|
91
|
-
// 注意:对于 dev 命令,我们使用 autoPipeline 作为主要执行逻辑
|
|
92
|
-
await autoPipeline(options);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
// 非 TTY 或 AI 上下文 → HTML 页面
|
|
96
|
-
if ((0, ask_host_ai_1.isAiContext)() || !process.stdout.isTTY || options.web) {
|
|
97
|
-
const html = await renderDevHtml(options);
|
|
98
|
-
const outPath = options.output || (0, path_1.join)(process.cwd(), 'outputs', 'speccore-dev.html');
|
|
99
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(process.cwd(), 'outputs'));
|
|
100
|
-
await (0, fs_extra_1.writeFile)(outPath, html);
|
|
101
|
-
process.stdout.write(`✅ 页面已生成: file://${outPath}\n`);
|
|
102
|
-
process.stdout.write(`[SPECCORE_DEV: ${outPath}]\n`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
// ── Auto-pipeline mode ──
|
|
106
|
-
if (options.auto) {
|
|
107
|
-
await autoPipeline(options);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
// ── Single-step detect mode ──
|
|
111
|
-
const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
|
|
112
|
-
if (!iteration) {
|
|
113
|
-
const isInit = await (0, fs_extra_1.pathExists)('.speccore');
|
|
114
|
-
if (!isInit) {
|
|
115
|
-
logger_1.logger.info('\n🔍 项目尚未初始化');
|
|
116
|
-
logger_1.logger.info('运行: speccore init');
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
logger_1.logger.info('\n📭 当前迭代未设置');
|
|
120
|
-
logger_1.logger.info('创建迭代: speccore iteration create -n Q1');
|
|
121
|
-
logger_1.logger.info('或导入需求: speccore doc2spec -f PRD.docx --iter Q1');
|
|
122
|
-
}
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const iterDir = `Iteration-${iteration}`;
|
|
126
|
-
const legacyReq = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
|
|
127
|
-
const analysis = (0, path_1.join)(iterDir, '020-specs', 'overview', 'ANALYSIS.md');
|
|
128
|
-
if (!(await (0, fs_extra_1.pathExists)(legacyReq))) {
|
|
129
|
-
showPhase('导入需求', ['speccore doc2spec -f PRD.docx -i ' + iteration]);
|
|
130
|
-
}
|
|
131
|
-
else if (!(await (0, fs_extra_1.pathExists)(analysis))) {
|
|
132
|
-
showPhase('分析需求', ['speccore analyze --iteration=' + iteration]);
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
showPhase('拆分任务', ['speccore iteration split --iteration=' + iteration]);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function showPhase(phase, cmds) {
|
|
139
|
-
logger_1.logger.info(`\n📍 当前阶段: ${phase}`);
|
|
140
|
-
cmds.forEach(c => logger_1.logger.info(` → ${c}`));
|
|
141
|
-
}
|
|
142
|
-
async function autoPipeline(options) {
|
|
143
|
-
const PIPELINE_PHASES = [
|
|
144
|
-
// ── 准备层(一次性初始化 + 需求入口二选一)──
|
|
145
|
-
{ key: 'init', cmd: 'speccore init', layer: 'prepare' },
|
|
146
|
-
{ key: 'clarify', cmd: 'speccore clarify', layer: 'prepare' },
|
|
147
|
-
{ key: 'doc2spec', cmd: 'speccore doc2spec', layer: 'prepare' },
|
|
148
|
-
// ── 主流程(常用:导入需求后分析→…→导出)──
|
|
149
|
-
{ key: 'analyze', cmd: 'speccore analyze', layer: 'main' },
|
|
150
|
-
{ key: 'validate', cmd: 'speccore validate', layer: 'main' },
|
|
151
|
-
{ key: 'split', cmd: 'speccore iteration split', layer: 'main' },
|
|
152
|
-
{ key: 'plan', cmd: 'speccore plan', layer: 'main' },
|
|
153
|
-
{ key: 'execute', cmd: 'speccore execute', layer: 'main' },
|
|
154
|
-
{ key: 'verify', cmd: 'speccore verify', layer: 'main' },
|
|
155
|
-
{ key: 'pipeline', cmd: 'speccore pipeline', layer: 'main' },
|
|
156
|
-
{ key: 'pr', cmd: 'speccore pr', layer: 'main' },
|
|
157
|
-
{ key: 'done', cmd: 'speccore done', layer: 'main' },
|
|
158
|
-
{ key: 'spec2doc', cmd: 'speccore spec2doc', layer: 'main' },
|
|
159
|
-
];
|
|
160
|
-
// v8.3.184+: review 闸口(功能开发质量底线)——阶段执行前触发,软停等待宿主 AI 确认
|
|
161
|
-
// A: split 前(Spec 定稿)、B: verify 前(代码 review)、C: pipeline 前(部署前确认,永不跳过)
|
|
162
|
-
const GATES = {
|
|
163
|
-
split: { id: 'A', title: 'Spec 定稿', hint: '请 review 生成的 Spec(功能单元/端覆盖/验收标准),确认后继续', resumeFrom: 'split' },
|
|
164
|
-
verify: { id: 'B', title: '代码 review', hint: '请 review 生成的代码(重点扫 AI 标记的低置信点 / .issues.md / .deviations),确认后继续', resumeFrom: 'verify' },
|
|
165
|
-
pipeline: { id: 'C', title: '部署前确认', hint: '即将部署到环境(destructive 操作),确认后才执行', resumeFrom: 'pipeline' },
|
|
166
|
-
};
|
|
167
|
-
const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
|
|
168
|
-
if (!iteration) {
|
|
169
|
-
logger_1.logger.info('🤖 Auto-pipeline: 未找到迭代,先初始化...');
|
|
170
|
-
(0, child_process_1.execSync)('speccore init', { stdio: 'inherit' });
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const iterDir = `Iteration-${iteration}`;
|
|
174
|
-
logger_1.logger.info(`\n🤖 Auto-pipeline: ${iteration}\n`);
|
|
175
|
-
const phaseResults = [];
|
|
176
|
-
const pipelineStart = Date.now();
|
|
177
|
-
// ── 解析 auto-steps 范围 ──
|
|
178
|
-
let startIdx = 0;
|
|
179
|
-
let endIdx = PIPELINE_PHASES.length - 1;
|
|
180
|
-
// v8.3.189+: 默认(无 --from/--auto-steps)从主流程 analyze 开始,
|
|
181
|
-
// 跳过准备层的 init(一次性)与 clarify/doc2spec(需求入口,应显式指定或手动执行)。
|
|
182
|
-
// 未初始化时仍从 init 开始。
|
|
183
|
-
if (!options.from && !options.autoSteps) {
|
|
184
|
-
const hasInit = await (0, fs_extra_1.pathExists)((0, path_1.join)(process.cwd(), '.speccore'));
|
|
185
|
-
if (hasInit) {
|
|
186
|
-
const analyzeIdx = PIPELINE_PHASES.findIndex(p => p.key === 'analyze');
|
|
187
|
-
if (analyzeIdx >= 0) {
|
|
188
|
-
startIdx = analyzeIdx;
|
|
189
|
-
logger_1.logger.info('ℹ️ 已初始化,默认从主流程 analyze 开始(跳过 init/clarify/doc2spec)');
|
|
190
|
-
logger_1.logger.info(' 如需从需求入口开始: speccore dev --from clarify 或 --from doc2spec\n');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (options.from) {
|
|
195
|
-
const fi = PIPELINE_PHASES.findIndex(p => p.key === options.from);
|
|
196
|
-
if (fi >= 0)
|
|
197
|
-
startIdx = fi;
|
|
198
|
-
else {
|
|
199
|
-
logger_1.logger.error(`未知阶段: ${options.from}`);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (options.to) {
|
|
204
|
-
const ti = PIPELINE_PHASES.findIndex(p => p.key === options.to);
|
|
205
|
-
if (ti >= 0)
|
|
206
|
-
endIdx = ti;
|
|
207
|
-
else {
|
|
208
|
-
logger_1.logger.error(`未知阶段: ${options.to}`);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
// ── --auto-steps: 指定连续步骤自动执行 ──
|
|
213
|
-
if (options.autoSteps) {
|
|
214
|
-
const stepKeys = options.autoSteps.split(',').map(s => s.trim());
|
|
215
|
-
const indices = stepKeys.map(k => PIPELINE_PHASES.findIndex(p => p.key === k));
|
|
216
|
-
if (indices.some(i => i < 0)) {
|
|
217
|
-
const unknown = stepKeys.filter(k => !PIPELINE_PHASES.find(p => p.key === k));
|
|
218
|
-
logger_1.logger.error(`未知阶段: ${unknown.join(', ')}`);
|
|
219
|
-
logger_1.logger.info(`可用: ${PIPELINE_PHASES.map(p => p.key).join(', ')}`);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
// 验证连续性
|
|
223
|
-
const sorted = [...indices].sort((a, b) => a - b);
|
|
224
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
225
|
-
if (sorted[i] !== sorted[i - 1] + 1) {
|
|
226
|
-
logger_1.logger.error(`auto-steps 必须连续!${stepKeys.join(',')} 在流水线中不连续`);
|
|
227
|
-
logger_1.logger.info(`流水线顺序: ${PIPELINE_PHASES.map(p => p.key).join(' → ')}`);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
startIdx = sorted[0];
|
|
232
|
-
endIdx = sorted[sorted.length - 1];
|
|
233
|
-
logger_1.logger.info(`🎯 自动步骤: ${stepKeys.join(' → ')}\n`);
|
|
234
|
-
}
|
|
235
|
-
// ── 逐步执行 ──
|
|
236
|
-
let pausedGate = null;
|
|
237
|
-
let prevLayer = '';
|
|
238
|
-
for (let i = startIdx; i <= endIdx; i++) {
|
|
239
|
-
const phase = PIPELINE_PHASES[i];
|
|
240
|
-
// v8.3.189+: 分层展示(准备层 / 主流程)
|
|
241
|
-
if (phase.layer && phase.layer !== prevLayer) {
|
|
242
|
-
prevLayer = phase.layer;
|
|
243
|
-
logger_1.logger.info(phase.layer === 'prepare'
|
|
244
|
-
? '\n────────── 🏗️ 准备层(初始化 + 需求入口)──────────'
|
|
245
|
-
: '\n────────── 🚀 主流程(分析→拆分→执行→验证→部署→归档→导出)──────────');
|
|
246
|
-
}
|
|
247
|
-
// v8.3.184+: review 闸口软停(功能开发质量底线)
|
|
248
|
-
const gate = GATES[phase.key];
|
|
249
|
-
if (gate) {
|
|
250
|
-
const skipGate = options.demo && gate.id !== 'C'; // demo 跳过 A/B,C 永不跳过
|
|
251
|
-
if (!skipGate) {
|
|
252
|
-
logger_1.logger.warn(`\n🛑 [SPECCORE_GATE_${gate.id}] ${gate.title}`);
|
|
253
|
-
logger_1.logger.warn(` ${gate.hint}`);
|
|
254
|
-
logger_1.logger.warn(` 确认后继续: speccore run --auto --from ${gate.resumeFrom} -I ${iteration}\n`);
|
|
255
|
-
pausedGate = gate.id;
|
|
256
|
-
break; // 软停,等宿主 AI 确认后 --from 继续
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
logger_1.logger.info(`━━━ [${i - startIdx + 1}/${endIdx - startIdx + 1}] ${phase.key} ━━━`);
|
|
260
|
-
const phaseStart = Date.now();
|
|
261
|
-
let phaseStatus = 'success';
|
|
262
|
-
let phaseMessage = '完成';
|
|
263
|
-
let phaseQuestions = [];
|
|
264
|
-
try {
|
|
265
|
-
switch (phase.key) {
|
|
266
|
-
case 'init': {
|
|
267
|
-
if (!(await (0, fs_extra_1.pathExists)('.speccore'))) {
|
|
268
|
-
(0, child_process_1.execSync)('speccore init', { stdio: 'inherit' });
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
logger_1.logger.info(' ✅ 已初始化,跳过');
|
|
272
|
-
}
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
case 'clarify': {
|
|
276
|
-
// v8.3.184+: 一句话需求 → PRD(口语化需求入口,与 doc2spec 二选一)
|
|
277
|
-
const reqDoc = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
|
|
278
|
-
const hasFeatures = await hasFeatureDirs(iterDir);
|
|
279
|
-
if ((await (0, fs_extra_1.pathExists)(reqDoc)) || hasFeatures) {
|
|
280
|
-
logger_1.logger.info(' ✅ 需求已存在,跳过 clarify');
|
|
281
|
-
}
|
|
282
|
-
else if (options.demand) {
|
|
283
|
-
(0, child_process_1.execSync)(`speccore clarify "${options.demand}" --to ${iteration}`, { stdio: 'inherit' });
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
logger_1.logger.info(' ℹ️ 未提供一句话需求,跳过 clarify(走 doc2spec 导入文档)');
|
|
287
|
-
}
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
case 'doc2spec': {
|
|
291
|
-
const reqDoc = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
|
|
292
|
-
const hasFeatures = await hasFeatureDirs(iterDir);
|
|
293
|
-
if (!(await (0, fs_extra_1.pathExists)(reqDoc)) && !hasFeatures) {
|
|
294
|
-
logger_1.logger.info(' ℹ️ 未找到需求文档,跳过(请手动 doc2spec)');
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
// 检查需求文档是否有实质内容(不是空文件)
|
|
298
|
-
let hasContent = false;
|
|
299
|
-
try {
|
|
300
|
-
const { readFile: rf } = require('fs-extra');
|
|
301
|
-
const content = await rf(reqDoc, 'utf-8');
|
|
302
|
-
hasContent = content.replace(/^#+\s.*$/gm, '').replace(/\s/g, '').length > 50;
|
|
303
|
-
}
|
|
304
|
-
catch { }
|
|
305
|
-
if (hasContent) {
|
|
306
|
-
logger_1.logger.info(' ✅ 需求已导入且有实质内容,跳过');
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
logger_1.logger.info(' ⚠️ 需求文档存在但内容为空,请手动 doc2spec 导入');
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
case 'analyze': {
|
|
315
|
-
const analysis = (0, path_1.join)(iterDir, '020-specs', 'overview', 'ANALYSIS.md');
|
|
316
|
-
const techSpec = (0, path_1.join)(iterDir, '020-specs', 'overview', 'ANALYSIS.md');
|
|
317
|
-
let needsAnalysis = !(await (0, fs_extra_1.pathExists)(analysis));
|
|
318
|
-
let needsSpecs = false;
|
|
319
|
-
if (!needsAnalysis) {
|
|
320
|
-
try {
|
|
321
|
-
const { readFile: rf } = require('fs-extra');
|
|
322
|
-
const techContent = await rf(techSpec, 'utf-8');
|
|
323
|
-
const meaningful = techContent.replace(/_待填充_|_待补充_|\|\s*\|/g, '').replace(/\s/g, '');
|
|
324
|
-
needsSpecs = meaningful.length < 50;
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
needsSpecs = true;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
if (needsAnalysis || needsSpecs) {
|
|
331
|
-
(0, child_process_1.execSync)(`speccore analyze --auto -I ${iteration}`, { stdio: 'inherit' });
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
logger_1.logger.info(' ✅ 分析已完成,跳过');
|
|
335
|
-
phaseStatus = 'skipped';
|
|
336
|
-
phaseMessage = '分析已完成,跳过';
|
|
337
|
-
}
|
|
338
|
-
// v7.4.0+: CLARIFY_REPORT 质量检查
|
|
339
|
-
const clarifyPath = (0, path_1.join)(iterDir, '010-requirements', 'CLARIFY_REPORT.md');
|
|
340
|
-
if (await (0, fs_extra_1.pathExists)(clarifyPath)) {
|
|
341
|
-
try {
|
|
342
|
-
const clarifyContent = await (0, fs_extra_1.readFile)(clarifyPath, 'utf-8');
|
|
343
|
-
const lowScoreMatch = clarifyContent.match(/总分[::]\s*(\d+)/);
|
|
344
|
-
if (lowScoreMatch) {
|
|
345
|
-
const score = parseInt(lowScoreMatch[1]);
|
|
346
|
-
if (score < 60) {
|
|
347
|
-
logger_1.logger.warn(` ⚠️ CLARIFY_REPORT 质量评分偏低: ${score}/100`);
|
|
348
|
-
logger_1.logger.warn(` 建议重新澄清需求或手动补充后再继续`);
|
|
349
|
-
phaseStatus = 'warning';
|
|
350
|
-
phaseMessage = `CLARIFY_REPORT 质量评分 ${score}/100,建议重新澄清`;
|
|
351
|
-
phaseQuestions.push(`需求澄清质量评分 ${score}/100,低于 60 分基线`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch { }
|
|
356
|
-
}
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
case 'validate': {
|
|
360
|
-
// v8.3.184+: Spec 质量门禁(analyze 后校验完整性/一致性,不通过不进入 split)
|
|
361
|
-
const analysis = (0, path_1.join)(iterDir, '020-specs', 'overview', 'ANALYSIS.md');
|
|
362
|
-
if (await (0, fs_extra_1.pathExists)(analysis)) {
|
|
363
|
-
(0, child_process_1.execSync)(`speccore validate --iteration=${iteration}`, { stdio: 'inherit' });
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
logger_1.logger.info(' ⏭️ 无 Spec 可校验,跳过 validate');
|
|
367
|
-
}
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
case 'split': {
|
|
371
|
-
let hasTasks = false;
|
|
372
|
-
try {
|
|
373
|
-
const entries = await (0, fs_extra_1.readdir)((0, path_1.join)(iterDir, '030-tasks'), { withFileTypes: true });
|
|
374
|
-
hasTasks = entries.some((e) => e.isDirectory() && e.name.startsWith('Task-'));
|
|
375
|
-
}
|
|
376
|
-
catch { }
|
|
377
|
-
if (!hasTasks) {
|
|
378
|
-
(0, child_process_1.execSync)(`speccore iteration split -i ${iteration} --force`, { stdio: 'inherit' });
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
logger_1.logger.info(' ✅ 任务已拆分,跳过');
|
|
382
|
-
}
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
case 'plan': {
|
|
386
|
-
// v6.91.1+: 检查是否已有计划,避免重复生成
|
|
387
|
-
const plansDir = (0, path_1.join)(iterDir, '000-overview', 'plans');
|
|
388
|
-
let hasPlan = false;
|
|
389
|
-
try {
|
|
390
|
-
const { readdir: rd2, pathExists: pe2 } = require('fs-extra');
|
|
391
|
-
if (await pe2(plansDir)) {
|
|
392
|
-
const entries = await rd2(plansDir);
|
|
393
|
-
hasPlan = entries.some((e) => e.endsWith('.md'));
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
catch { }
|
|
397
|
-
if (hasPlan) {
|
|
398
|
-
logger_1.logger.info(' ✅ 执行计划已存在,跳过');
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
(0, child_process_1.execSync)(`speccore plan -i ${iteration} --force`, { stdio: 'inherit' });
|
|
402
|
-
}
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
case 'execute': {
|
|
406
|
-
// v6.91.1+: 检查是否还有 pending 任务
|
|
407
|
-
let hasPending = false;
|
|
408
|
-
try {
|
|
409
|
-
const { scanTasks } = await Promise.resolve().then(() => __importStar(require('../core/state')));
|
|
410
|
-
const tasks = await scanTasks(iteration);
|
|
411
|
-
hasPending = tasks.some(t => t.status === 'pending');
|
|
412
|
-
}
|
|
413
|
-
catch { }
|
|
414
|
-
if (!hasPending) {
|
|
415
|
-
logger_1.logger.info(' ✅ 所有任务已完成,跳过 execute');
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
(0, child_process_1.execSync)(`speccore execute --auto -i ${iteration}`, { stdio: 'inherit' });
|
|
419
|
-
}
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
case 'verify': {
|
|
423
|
-
// v8.3.184+: 验证(execute 后,编译+Lint+测试+ui 截图,只报告不阻断)
|
|
424
|
-
let hasPendingV = false;
|
|
425
|
-
try {
|
|
426
|
-
const { scanTasks } = await Promise.resolve().then(() => __importStar(require('../core/state')));
|
|
427
|
-
const tasks = await scanTasks(iteration);
|
|
428
|
-
hasPendingV = tasks.some(t => t.status === 'pending');
|
|
429
|
-
}
|
|
430
|
-
catch { }
|
|
431
|
-
if (hasPendingV) {
|
|
432
|
-
logger_1.logger.info(' ⏭️ 还有 pending 任务,跳过 verify');
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
(0, child_process_1.execSync)(`speccore verify`, { stdio: 'inherit' });
|
|
436
|
-
}
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
case 'pipeline': {
|
|
440
|
-
// v8.3.184+: 部署(闸口 C 在阶段前已软停,确认后才到这)
|
|
441
|
-
(0, child_process_1.execSync)(`speccore pipeline --all`, { stdio: 'inherit' });
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
case 'pr': {
|
|
445
|
-
// v6.91.1+: 检查是否有未提交的代码变更
|
|
446
|
-
let hasChanges = false;
|
|
447
|
-
try {
|
|
448
|
-
const { execSync: es2 } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
449
|
-
const gitStatus = es2('git status --short', { encoding: 'utf-8', cwd: process.cwd() });
|
|
450
|
-
hasChanges = gitStatus.trim().length > 0;
|
|
451
|
-
}
|
|
452
|
-
catch { }
|
|
453
|
-
if (!hasChanges) {
|
|
454
|
-
logger_1.logger.info(' ✅ 没有未提交的变更,跳过 pr');
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
(0, child_process_1.execSync)(`speccore pr -i ${iteration} --force`, { stdio: 'inherit' });
|
|
458
|
-
}
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
case 'done': {
|
|
462
|
-
// v6.91.1+: 检查是否已归档
|
|
463
|
-
const retroPath = (0, path_1.join)(iterDir, '000-overview', 'RETRO.md');
|
|
464
|
-
if (await (0, fs_extra_1.pathExists)(retroPath)) {
|
|
465
|
-
logger_1.logger.info(' ✅ 已生成复盘报告,跳过 done');
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
(0, child_process_1.execSync)(`speccore done --all -i ${iteration}`, { stdio: 'inherit' });
|
|
469
|
-
}
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
|
-
case 'spec2doc': {
|
|
473
|
-
(0, child_process_1.execSync)(`speccore spec2doc -i ${iteration}`, { stdio: 'inherit' });
|
|
474
|
-
break;
|
|
475
|
-
}
|
|
476
|
-
} // end switch
|
|
477
|
-
}
|
|
478
|
-
catch (err) {
|
|
479
|
-
phaseStatus = 'failed';
|
|
480
|
-
phaseMessage = err?.message?.slice(0, 100) || '执行失败';
|
|
481
|
-
logger_1.logger.error(` ❌ ${phase.key} 失败: ${phaseMessage}`);
|
|
482
|
-
}
|
|
483
|
-
// v7.4.0+: 每个阶段执行后收集疑问
|
|
484
|
-
try {
|
|
485
|
-
const qFiles = await (0, questions_1.collectQuestionFiles)();
|
|
486
|
-
for (const qf of qFiles) {
|
|
487
|
-
if (qf.includes(`-${phase.key}-`)) {
|
|
488
|
-
phaseQuestions.push(qf.split('/').pop() || qf);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
catch { }
|
|
493
|
-
phaseResults.push({
|
|
494
|
-
key: phase.key,
|
|
495
|
-
status: phaseStatus,
|
|
496
|
-
message: phaseMessage,
|
|
497
|
-
questions: phaseQuestions,
|
|
498
|
-
duration: Math.round((Date.now() - phaseStart) / 1000),
|
|
499
|
-
});
|
|
500
|
-
// v8.3.187+: 阶段交接摘要(generateHandoff)——关键阶段成功后生成精简传递摘要,
|
|
501
|
-
// 完整产物存文件、传递摘要只给下一阶段 AI 看,控制跨阶段上下文大小。
|
|
502
|
-
// 13 阶段流水线 → 6 阶段 handoff 映射:verify 对应 review 闸口。
|
|
503
|
-
if (phaseStatus === 'success') {
|
|
504
|
-
const handoffMap = {
|
|
505
|
-
analyze: ['analyze', 'split'],
|
|
506
|
-
split: ['split', 'plan'],
|
|
507
|
-
plan: ['plan', 'execute'],
|
|
508
|
-
execute: ['execute', 'review'],
|
|
509
|
-
verify: ['review', 'done'],
|
|
510
|
-
};
|
|
511
|
-
const handoff = handoffMap[phase.key];
|
|
512
|
-
if (handoff) {
|
|
513
|
-
try {
|
|
514
|
-
const handoffPath = await (0, handoff_generator_1.generateHandoff)(handoff[0], handoff[1], iteration);
|
|
515
|
-
logger_1.logger.info(` 📋 阶段交接摘要: ${handoffPath}`);
|
|
516
|
-
}
|
|
517
|
-
catch (err) {
|
|
518
|
-
logger_1.logger.debug(` 交接摘要生成跳过: ${err.message}`);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
logger_1.logger.info('');
|
|
523
|
-
}
|
|
524
|
-
// v7.4.0+: 生成 Pipeline 总结报告
|
|
525
|
-
const totalDuration = Math.round((Date.now() - pipelineStart) / 1000);
|
|
526
|
-
const reportDir = (0, path_1.join)(iterDir, '000-overview');
|
|
527
|
-
await (0, fs_extra_1.ensureDir)(reportDir);
|
|
528
|
-
const reportPath = (0, path_1.join)(reportDir, 'PIPELINE_REPORT.md');
|
|
529
|
-
const now = new Date().toLocaleString('zh-CN');
|
|
530
|
-
let report = `# Pipeline 执行报告\n\n`;
|
|
531
|
-
report += `> 迭代: ${iteration} | 执行时间: ${now} | 总耗时: ${totalDuration}s\n`;
|
|
532
|
-
report += `> 执行范围: ${PIPELINE_PHASES.slice(startIdx, endIdx + 1).map(p => p.key).join(' → ')}\n\n`;
|
|
533
|
-
report += `## 阶段执行结果\n\n`;
|
|
534
|
-
report += `| 阶段 | 状态 | 耗时 | 说明 |\n`;
|
|
535
|
-
report += `| :--- | :--- | :--- | :--- |\n`;
|
|
536
|
-
for (const pr of phaseResults) {
|
|
537
|
-
const icon = pr.status === 'success' ? '✅' : pr.status === 'skipped' ? '⏭️' : pr.status === 'warning' ? '⚠️' : '❌';
|
|
538
|
-
report += `| ${pr.key} | ${icon} ${pr.status} | ${pr.duration}s | ${pr.message} |\n`;
|
|
539
|
-
}
|
|
540
|
-
// 汇总所有疑问
|
|
541
|
-
const allQuestions = phaseResults.filter(pr => pr.questions.length > 0);
|
|
542
|
-
if (allQuestions.length > 0) {
|
|
543
|
-
report += `\n## 疑问清单\n\n`;
|
|
544
|
-
for (const pr of allQuestions) {
|
|
545
|
-
report += `### ${pr.key} 阶段\n\n`;
|
|
546
|
-
for (const q of pr.questions) {
|
|
547
|
-
report += `- ${q}\n`;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
// 失败项汇总
|
|
552
|
-
const failedPhases = phaseResults.filter(pr => pr.status === 'failed');
|
|
553
|
-
if (failedPhases.length > 0) {
|
|
554
|
-
report += `\n## 失败项需要处理\n\n`;
|
|
555
|
-
for (const fp of failedPhases) {
|
|
556
|
-
report += `- **${fp.key}**: ${fp.message}\n`;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
report += `\n---\n> 报告自动生成 by speccore dev auto-pipeline\n`;
|
|
560
|
-
await (0, fs_extra_1.writeFile)(reportPath, report, 'utf-8');
|
|
561
|
-
if (pausedGate) {
|
|
562
|
-
logger_1.logger.warn(`\n🛑 Pipeline 已暂停在闸口 ${pausedGate}(完成至 ${phaseResults.map(p => p.key).join(' → ')})`);
|
|
563
|
-
logger_1.logger.warn(` 确认后继续: speccore run --auto --from ${GATES[Object.keys(GATES).find(k => GATES[k].id === pausedGate)].resumeFrom} -I ${iteration}\n`);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
logger_1.logger.success(`\n✅ Pipeline 完成: ${PIPELINE_PHASES.slice(startIdx, endIdx + 1).map(p => p.key).join(' → ')}`);
|
|
567
|
-
}
|
|
568
|
-
logger_1.logger.info(`📊 执行报告 → ${reportPath}\n`);
|
|
569
|
-
}
|
|
570
|
-
async function renderDevHtml(options) {
|
|
571
|
-
const lang = options.lang || 'zh';
|
|
572
|
-
const version = require('../../package.json').version;
|
|
573
|
-
const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
|
|
574
|
-
const iterName = (!iteration || iteration.length < 2) ? '' : iteration;
|
|
575
|
-
const iterDir = iterName ? `Iteration-${iterName}` : '';
|
|
576
|
-
const now = new Date().toISOString().split('T')[0];
|
|
577
|
-
const isInit = await (0, fs_extra_1.pathExists)('.speccore');
|
|
578
|
-
const phases = [
|
|
579
|
-
{ name: lang === 'en' ? I18N.en.stepInit : I18N.zh.stepInit, key: 'init', done: !!isInit, icon: '🏗️', cmd: 'init', description: '初始化 SpecCore 项目结构', args: '',
|
|
580
|
-
detail: '创建 .speccore/ 目录及配置文件(CONSTITUTION.md、.speccore.yml 等),奠定项目规范基础。',
|
|
581
|
-
prerequisites: ['空目录或已有代码仓库'], outputs: ['.speccore/CONSTITUTION.md', '.speccore.yml', '.speccore/local/context.json'],
|
|
582
|
-
tips: '如果已有项目,init 会扫描现有工程结构自动填写技术栈。', examples: ['speccore init'] },
|
|
583
|
-
{ name: lang === 'en' ? I18N.en.stepDoc : I18N.zh.stepDoc, key: 'doc', done: false, icon: '📝', cmd: 'doc2spec', description: '导入 PRD 文档,AI 转换需求规格', args: '-f PRD.docx' + (iterName ? ' --iteration ' + iterName : ''),
|
|
584
|
-
detail: '将产品需求文档(Word / PDF / Markdown)导入迭代,AI 自动提取并转换为标准需求规格。也支持自然语言直接描述需求。',
|
|
585
|
-
prerequisites: ['已创建迭代', '需求文档或明确的功能描述'], outputs: ['Iteration-xxx/010-requirements/REQUIREMENT.md', 'Iteration-xxx/010-requirements/features/'],
|
|
586
|
-
tips: '如果文档格式复杂,可以先放到 .speccore/inbox/ 目录,AI 会自动识别并转换。', examples: ['/spec-ask "新增用户登录功能"', 'speccore doc2spec -f PRD.docx -i my-iter'] },
|
|
587
|
-
{ name: lang === 'en' ? I18N.en.stepAnalyze : I18N.zh.stepAnalyze, key: 'analyze', done: false, icon: '🧠', cmd: 'analyze', description: 'AI 分析需求文档,生成审计报告', args: iterName ? '--iteration=' + iterName : '',
|
|
588
|
-
detail: 'AI 深度分析需求文档,生成分端技术规格(ANALYSIS.md、TECH.md、UI_SPEC.md、TEST.md 等 7+ 份文档),并输出风险评估和依赖分析。',
|
|
589
|
-
prerequisites: ['需求文档已导入(REQUIREMENT.md 或 features/ 存在)'], outputs: ['Iteration-xxx/020-specs/overview/ANALYSIS.md', 'Iteration-xxx/020-specs/{功能模块}/{platform}/TECH.md', 'Iteration-xxx/020-specs/{功能模块}/{platform}/UI_SPEC.md'],
|
|
590
|
-
tips: '分析时 AI 会读取 CONSTITUTION.md 中的技术栈配置,确保输出符合项目规范。分析产物包含 Mermaid 图表。', examples: ['/spec-ask "分析 my-iter 的需求"', 'speccore analyze --auto -I my-iter'] },
|
|
591
|
-
{ name: lang === 'en' ? I18N.en.stepSplit : I18N.zh.stepSplit, key: 'split', done: false, icon: '📦', cmd: 'split', description: '将需求拆分为独立开发任务', args: '-f REQUIREMENT.md',
|
|
592
|
-
detail: '基于 ANALYSIS.md 中的技术规格,AI 将需求拆分为独立的开发任务(Task-001 / Task-002 ...),每个任务包含 REQ/TECH/SCHEMA 规格和跨端依赖分析。',
|
|
593
|
-
prerequisites: ['analyze 已完成(ANALYSIS.md 存在)'], outputs: ['Iteration-xxx/030-tasks/Task-001/', 'Iteration-xxx/030-tasks/Task-002/'],
|
|
594
|
-
tips: '拆分粒度受 STAFFING.md 团队规模影响。≤3人粗粒度,4-8人中粒度(默认),>8人细粒度。', examples: ['/spec-ask "拆分 my-iter 的任务"', 'speccore iteration split -i my-iter'] },
|
|
595
|
-
{ name: lang === 'en' ? I18N.en.stepExec : I18N.zh.stepExec, key: 'execute', done: false, icon: '⚡', cmd: 'execute', description: '按计划分批执行开发任务', args: '--auto',
|
|
596
|
-
detail: '按执行计划(plan 生成)分批开发。AI 读取 Task 的 TECH.md 和 SCHEMA.md,生成代码并写入源码路径。支持断点续传和批量执行。',
|
|
597
|
-
prerequisites: ['任务已拆分(030-tasks/ 下有 Task-xxx 目录)', '可选:已执行 plan 生成执行计划'], outputs: ['源码路径中的新增/修改代码文件(CONSTITUTION.md/PROJECT.yaml 声明的 code_path)', 'Task 目录下的规格文档更新(TEST.md/RISK.md 等)'],
|
|
598
|
-
tips: '推荐用 --batch-size 3 分批执行,每批完成后开新对话继续。失败任务会写入 .issues.md 和 .needs-retry。', examples: ['/spec-ask "开发 Task-001"', 'speccore execute --auto -i my-iter --batch-size 3'] },
|
|
599
|
-
{ name: lang === 'en' ? I18N.en.stepPr : I18N.zh.stepPr, key: 'pr', done: false, icon: '🔀', cmd: 'pr', description: '代码提交后创建 Pull Request', args: '--auto',
|
|
600
|
-
detail: '自动提交代码变更到 Git,创建 Pull Request 或 Merge Request。包含变更摘要、影响分析、测试建议。',
|
|
601
|
-
prerequisites: ['有未提交的代码变更', 'Git 仓库已配置'], outputs: ['Git commit', 'Pull Request / Merge Request'],
|
|
602
|
-
tips: '执行前会检查分支安全性(禁止直接提交到主分支)和未提交变更。支持 --auto 自动填写 PR 描述。', examples: ['/spec-ask "提交 my-iter 的代码"', 'speccore pr -i my-iter --auto'] },
|
|
603
|
-
{ name: lang === 'en' ? I18N.en.stepDone : I18N.zh.stepDone, key: 'done', done: false, icon: '✅', cmd: 'done', description: '校验、同步、审计,归档记录', args: '--all',
|
|
604
|
-
detail: '完成最终校验(validate)、双向同步(sync)、生成复盘报告(RETRO.md),标记迭代为完成状态。',
|
|
605
|
-
prerequisites: ['所有任务已执行完毕', '代码已提交'], outputs: ['Iteration-xxx/000-overview/RETRO.md', '迭代完成标记'],
|
|
606
|
-
tips: 'RETRO.md 包含需求完成度、任务回顾、问题清单、改进建议,可用于迭代复盘会议。', examples: ['/spec-ask "完成 my-iter"', 'speccore done --all -i my-iter'] },
|
|
607
|
-
];
|
|
608
|
-
if (iterDir) {
|
|
609
|
-
const reqDoc = (0, path_1.join)(iterDir, '010-requirements', 'REQUIREMENT.md');
|
|
610
|
-
const hasReqFeature = await hasFeatureDirs(iterDir); // 新结构: 010-requirements/{feature}/
|
|
611
|
-
const analysis = (0, path_1.join)(iterDir, '020-specs', 'overview', 'ANALYSIS.md');
|
|
612
|
-
if ((await (0, fs_extra_1.pathExists)(reqDoc)) || hasReqFeature)
|
|
613
|
-
phases[1].done = true;
|
|
614
|
-
if (await (0, fs_extra_1.pathExists)(analysis))
|
|
615
|
-
phases[2].done = true;
|
|
616
|
-
try {
|
|
617
|
-
const entries = await (0, fs_extra_1.readdir)((0, path_1.join)(iterDir, '030-tasks'), { withFileTypes: true });
|
|
618
|
-
if (entries.filter((e) => e.isDirectory() && e.name.startsWith('Task-')).length > 0)
|
|
619
|
-
phases[3].done = true;
|
|
620
|
-
}
|
|
621
|
-
catch { }
|
|
622
|
-
}
|
|
623
|
-
const currentIdx = phases.findIndex(p => !p.done);
|
|
624
|
-
const current = currentIdx >= 0 ? phases[currentIdx] : phases[phases.length - 1];
|
|
625
|
-
// Build pipeline state for LLM
|
|
626
|
-
const state = { iteration: iterName, phases, currentIdx, currentPhase: current };
|
|
627
|
-
// ── 尝试宿主 AI 获取智能建议 ──
|
|
628
|
-
let aiSuggestion = '';
|
|
629
|
-
try {
|
|
630
|
-
const hostResult = await (0, ask_host_ai_2.tryHostAi)('dev', 'pipeline-status', {
|
|
631
|
-
iteration: iterName,
|
|
632
|
-
currentPhase: current.name,
|
|
633
|
-
currentPhaseKey: current.key,
|
|
634
|
-
completedPhases: phases.filter(p => p.done).map(p => p.key),
|
|
635
|
-
pendingPhases: phases.filter(p => !p.done).map(p => p.key),
|
|
636
|
-
});
|
|
637
|
-
if (hostResult?.summary) {
|
|
638
|
-
aiSuggestion = hostResult.summary;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
catch { }
|
|
642
|
-
// Phase flow HTML
|
|
643
|
-
const flowHtml = phases.map((p, i) => {
|
|
644
|
-
const isCurrent = i === currentIdx;
|
|
645
|
-
const cls = p.done ? 'done' : isCurrent ? 'current' : 'pending';
|
|
646
|
-
return `<div class="phase ${cls}"><span class="phase-icon">${p.icon}</span><span class="phase-name">${p.name}</span>${i < phases.length - 1 ? '<span class="phase-arrow">→</span>' : ''}</div>`;
|
|
647
|
-
}).join('');
|
|
648
|
-
// Phase buttons for quick action
|
|
649
|
-
const phaseButtons = phases.map(p => {
|
|
650
|
-
if (p.done)
|
|
651
|
-
return `<button class="phase-btn done" disabled>${p.icon} ${p.name} ✓</button>`;
|
|
652
|
-
return `<button class="phase-btn" onclick="handleAction('jump-to','${p.key}')" title="跳转到${p.name}">${p.icon} ${p.name}</button>`;
|
|
653
|
-
}).join('\n');
|
|
654
|
-
// State data for JS
|
|
655
|
-
const stateJson = JSON.stringify(state).replace(/"/g, '"');
|
|
656
|
-
return `<!DOCTYPE html><html lang="zh-CN" data-theme="ocean"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>SpecCore Dev · Pipeline</title><style>@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=JetBrains+Mono:wght@400;600;700&display=swap');[data-theme="ocean"]{--cyan:#0ea5e9;--bg:#0b1929;--card:rgba(13,31,56,.95);--border:rgba(14,165,233,.15);--text:#bae6fd;--muted:#5b7fa5;--green:#14b8a6;--orange:#f97316;--purple:#6366f1}*{margin:0;padding:0;box-sizing:border-box}body{font-family:'JetBrains Mono',monospace;background:var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;gap:20px}.scanlines{position:fixed;inset:0;pointer-events:none;z-index:99;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,240,255,.01) 2px,rgba(0,240,255,.01) 4px)}.card{max-width:680px;width:100%;background:var(--card);border:1px solid var(--border);border-radius:16px;padding:36px;position:relative;overflow:hidden}.card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--cyan),transparent);animation:scanX 3s linear infinite}.card::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--cyan),transparent);animation:scanX-rev 3s linear infinite}@keyframes scanX{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}@keyframes scanX-rev{0%{transform:translateX(100%)}100%{transform:translateX(-100%)}}@keyframes scanY{0%{transform:translateY(-100%)}100%{transform:translateY(100%)}}@keyframes scanY-rev{0%{transform:translateY(100%)}100%{transform:translateY(-100%)}}.vline{position:absolute;top:0;width:1px;bottom:0;pointer-events:none}.vline.l{left:0;background:linear-gradient(180deg,transparent,var(--cyan),transparent);animation:scanY-rev 3s linear infinite}.vline.r{right:0;background:linear-gradient(180deg,transparent,var(--cyan),transparent);animation:scanY 3s linear infinite}h1{font-family:'Orbitron',sans-serif;font-size:22px;font-weight:900;background:linear-gradient(135deg,var(--cyan),var(--purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:2px;margin-bottom:4px}.subtitle{color:var(--muted);font-size:11px;margin-bottom:16px}.flow{display:flex;align-items:center;gap:16px;margin:16px 0;flex-wrap:wrap;justify-content:flex-start}.phase{display:flex;align-items:center;gap:12px;padding:10px 18px;border-radius:10px;font-size:12px;transition:all .3s;border:1px solid transparent}.phase.done{background:rgba(20,184,166,.1);border-color:rgba(20,184,166,.3);color:var(--green)}.phase.current{background:rgba(14,165,233,.15);border-color:rgba(14,165,233,.5);color:var(--cyan);box-shadow:0 0 16px rgba(14,165,233,.15);animation:glow 2s ease-in-out infinite}.phase.pending{background:rgba(255,255,255,.02);border-color:rgba(255,255,255,.05);color:var(--muted)}.phase-arrow{color:var(--muted);font-size:14px;margin:0 8px}@keyframes glow{0%,100%{box-shadow:0 0 16px rgba(14,165,233,.15)}50%{box-shadow:0 0 24px rgba(14,165,233,.3)}}.cur-section{padding:16px;background:rgba(14,165,233,.06);border:1px solid rgba(14,165,233,.15);border-radius:10px;margin:16px 0}.cur-label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:2px;margin-bottom:8px}.cur-cmd{font-family:'JetBrains Mono',monospace;font-size:14px;color:var(--cyan);background:rgba(14,165,233,.08);padding:6px 12px;border-radius:6px;display:inline-block}.action-btns{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.action-btns button,.phase-btn{padding:8px 16px;border-radius:8px;border:1px solid var(--border);background:var(--surface, rgba(255,255,255,.03));color:var(--text);cursor:pointer;font-size:11px;font-family:'JetBrains Mono',monospace;transition:all .2s}.action-btns button:hover,.phase-btn:hover{border-color:var(--cyan);color:var(--cyan);background:rgba(14,165,233,.08)}.action-btns button.primary{background:rgba(14,165,233,.12);border-color:rgba(14,165,233,.4);color:var(--cyan)}.action-btns button.danger{border-color:rgba(249,115,22,.3);color:var(--orange)}.action-btns button.danger:hover{border-color:var(--orange);background:rgba(249,115,22,.08)}.phase-btn{font-size:10px;padding:6px 10px}.phase-btn.done{opacity:.4;cursor:default}.input-row{display:flex;gap:8px;margin:16px 0}.input-row input{flex:1;padding:10px 14px;border-radius:10px;border:1px solid var(--border);background:rgba(255,255,255,.03);color:var(--text);font-size:12px;font-family:'JetBrains Mono',monospace;outline:none;transition:all .2s}.input-row input:focus{border-color:var(--cyan);box-shadow:0 0 12px rgba(14,165,233,.1)}.input-row button{padding:10px 16px;border-radius:10px;border:1px solid var(--cyan);background:rgba(14,165,233,.1);color:var(--cyan);cursor:pointer;font-size:12px;font-family:'JetBrains Mono',monospace;transition:all .2s}.input-row button:hover{background:rgba(14,165,233,.2)}.result-box{margin-top:16px;padding:16px;background:rgba(14,165,233,.04);border:1px solid rgba(14,165,233,.1);border-radius:10px;display:none}.result-box.show{display:block}.result-cmd{font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--green);background:rgba(20,184,166,.08);padding:6px 10px;border-radius:4px;margin:4px 0;display:inline-block}.result-summary{font-size:12px;color:var(--text);margin-bottom:8px}..quick-phase{display:flex;flex-wrap:wrap;gap:12px;margin-top:14px;justify-content:center}.detail-panel{margin-top:12px;border-top:1px solid rgba(14,165,233,.1);padding-top:12px}.detail-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:11px;color:var(--muted);transition:color .2s}.detail-toggle:hover{color:var(--cyan)}.detail-toggle::before{content:'▸';font-size:10px;transition:transform .2s}.detail-toggle.open::before{transform:rotate(90deg)}.detail-content{display:none;margin-top:10px;font-size:11px;line-height:1.7;color:var(--text)}.detail-content.show{display:block}.detail-block{margin:8px 0;padding:8px 10px;border-radius:6px;background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.04)}.detail-block-title{font-size:10px;font-weight:700;color:var(--cyan);margin-bottom:4px;text-transform:uppercase;letter-spacing:1px}.detail-block ul{margin:0;padding-left:16px;color:var(--muted);font-size:10px}.detail-block li{margin:2px 0}.detail-tip{margin:8px 0;padding:8px 10px;border-radius:6px;background:rgba(20,184,166,.04);border:1px solid rgba(20,184,166,.1);color:var(--green);font-size:10px}.detail-examples{margin:8px 0;padding:8px 10px;border-radius:6px;background:rgba(99,102,241,.04);border:1px solid rgba(99,102,241,.1)}.detail-examples code{display:block;font-size:10px;color:var(--purple);margin:3px 0;padding:2px 6px;background:rgba(0,0,0,.2);border-radius:3px;font-family:'JetBrains Mono',monospace}.footer{text-align:center;color:var(--muted);font-size:10px;margin-top:8px}</style></head><body><div class="scanlines"></div>
|
|
657
|
-
|
|
658
|
-
<div class="card"><div class="vline l"></div><div class="vline r"></div>
|
|
659
|
-
<h1>SPECCORE DEV</h1>
|
|
660
|
-
<div class="subtitle">${iterName ? '迭代: ' + iterName + ' · ' : ''}${phases.filter(p => p.done).length}/${phases.length} 阶段完成 · Pipeline 引导${aiSuggestion ? '<br><span style="color:var(--cyan);font-size:10px">💡 ' + aiSuggestion + '</span>' : ''}</div>
|
|
661
|
-
|
|
662
|
-
<div class="flow">${flowHtml}</div>
|
|
663
|
-
|
|
664
|
-
<div class="cur-section">
|
|
665
|
-
<div class="cur-label">📍 当前阶段</div>
|
|
666
|
-
<div class="cur-cmd">${current.icon} ${current.name} → speccore ${current.cmd}${current.args ? ' ' + current.args : ''}</div>
|
|
667
|
-
|
|
668
|
-
<div class="action-btns">
|
|
669
|
-
<button class="primary" onclick="handleAction('next','${current.key}')">▶ 下一步</button>
|
|
670
|
-
<button onclick="handleAction('auto-all','all')">⚡ 一键自动</button>
|
|
671
|
-
<button onclick="handleAction('skip-to','${currentIdx + 1 < phases.length ? phases[currentIdx + 1].key : current.key}')">⏭ 跳过当前</button>
|
|
672
|
-
<button class="danger" onclick="handleAction('restart','init')">🔄 重新开始</button>
|
|
673
|
-
</div>
|
|
674
|
-
${current.detail ? '<div class="detail-panel"><div class="detail-toggle" onclick="this.classList.toggle(\'open\');this.nextElementSibling.classList.toggle(\'show\')">📖 阶段详情</div><div class="detail-content">' + '<div class="detail-block"><div class="detail-block-title">📋 详细说明</div><div>' + current.detail + '</div></div>' + (current.prerequisites ? '<div class="detail-block"><div class="detail-block-title">📌 前置条件</div><ul>' + current.prerequisites.map(p => '<li>' + p + '</li>').join('') + '</ul></div>' : '') + (current.outputs ? '<div class="detail-block"><div class="detail-block-title">📤 产出物</div><ul>' + current.outputs.map(o => '<li>' + o + '</li>').join('') + '</ul></div>' : '') + (current.tips ? '<div class="detail-tip">💡 ' + current.tips + '</div>' : '') + (current.examples ? '<div class="detail-examples"><div class="detail-block-title">⌨️ 常用命令</div>' + current.examples.map(e => '<code>' + e + '</code>').join('') + '</div>' : '') + '</div></div>' : ''}
|
|
675
|
-
</div>
|
|
676
|
-
|
|
677
|
-
<div class="quick-phase">
|
|
678
|
-
<div style="font-size:10px;color:var(--muted);margin-right:8px;">直接跳转:</div>
|
|
679
|
-
${phaseButtons}
|
|
680
|
-
</div>
|
|
681
|
-
|
|
682
|
-
<div class="input-row">
|
|
683
|
-
<input id="userInput" type="text" placeholder="💬 或者直接说... 比如「从分析开始」「跳过导入直接拆分」" onkeydown="if(event.key==='Enter')handleCustom()">
|
|
684
|
-
<button onclick="handleCustom()">发送</button>
|
|
685
|
-
</div>
|
|
686
|
-
|
|
687
|
-
<div class="result-box" id="resultBox">
|
|
688
|
-
<div class="result-summary" id="resultSummary"></div>
|
|
689
|
-
<div id="resultCmds"></div>
|
|
690
|
-
</div>
|
|
691
|
-
</div>
|
|
692
|
-
|
|
693
|
-
<div class="footer">由 SpecCore 驱动 v${version} | ${now}</div>
|
|
694
|
-
|
|
695
|
-
<script>
|
|
696
|
-
const STATE = ${stateJson};
|
|
697
|
-
|
|
698
|
-
function handleAction(action, target) {
|
|
699
|
-
const result = devRules(action, target, STATE);
|
|
700
|
-
showResult(result);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function handleCustom() {
|
|
704
|
-
const input = document.getElementById('userInput').value.trim();
|
|
705
|
-
if (!input) return;
|
|
706
|
-
document.getElementById('userInput').value = '';
|
|
707
|
-
document.getElementById('resultSummary').textContent = '🤔 分析中...';
|
|
708
|
-
const box = document.getElementById('resultBox');
|
|
709
|
-
box.classList.add('show');
|
|
710
|
-
|
|
711
|
-
// AI 模拟分析(300ms 延迟模拟 LLM 思考)
|
|
712
|
-
document.getElementById('resultSummary').innerHTML = '<span style="animation:pulse 1s infinite">🤔 AI 分析中...</span>';
|
|
713
|
-
setTimeout(() => {
|
|
714
|
-
const result = devAI(STATE, input);
|
|
715
|
-
showResult(result);
|
|
716
|
-
}, 300);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
function showResult(result) {
|
|
720
|
-
const box = document.getElementById('resultBox');
|
|
721
|
-
box.classList.add('show');
|
|
722
|
-
document.getElementById('resultSummary').textContent = '🧠 ' + result.summary;
|
|
723
|
-
|
|
724
|
-
let cmdsHtml = '';
|
|
725
|
-
result.commands.forEach(c => {
|
|
726
|
-
cmdsHtml += '<div class="result-cmd">$ speccore ' + c.command + (c.args ? ' ' + c.args : '') + '</div>';
|
|
727
|
-
cmdsHtml += '<div style="font-size:10px;color:var(--muted);margin:2px 0 8px 4px;">→ ' + c.explanation + '</div>';
|
|
728
|
-
});
|
|
729
|
-
document.getElementById('resultCmds').innerHTML = cmdsHtml;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// ── Dev AI 引擎(前端规则层,模拟 LLM 行为)──
|
|
733
|
-
function devAI(state, input) {
|
|
734
|
-
const iterArg = state.iteration ? ' --iteration=' + state.iteration : '';
|
|
735
|
-
const lower = (input || '').toLowerCase();
|
|
736
|
-
const phases = state.phases;
|
|
737
|
-
const cur = state.currentPhase;
|
|
738
|
-
|
|
739
|
-
const CMD_KB = {
|
|
740
|
-
init: { args:'', desc:'初始化 SpecCore 项目结构', trigger:['初始化','init','新建项目','创建项目'] },
|
|
741
|
-
doc2spec: { args:'-f PRD.docx'+(iterArg), desc:'导入 PRD 文档,AI 转换需求规格', trigger:['导入','doc2spec','文档','PRD','需求文档','word'] },
|
|
742
|
-
analyze: { args:iterArg.replace('--iteration=','--iteration='), desc:'AI 分析需求,生成审计报告', trigger:['分析','analyze','审计','audit'] },
|
|
743
|
-
split: { args:'-f REQUIREMENT.md', desc:'拆分需求为独立开发任务', trigger:['拆分','split','分解','任务'] },
|
|
744
|
-
plan: { args:'--all', desc:'生成最优执行计划', trigger:['计划','plan','调度','安排'] },
|
|
745
|
-
execute: { args:'--auto --batch-size 3', desc:'按计划分批自动执行', trigger:['执行','execute','开发','做','开始','run'] },
|
|
746
|
-
pr: { args:'--auto', desc:'创建 Pull Request', trigger:['pr','pull','提交','合并','MR'] },
|
|
747
|
-
done: { args:'--all', desc:'校验、同步、归档收尾', trigger:['完成','done','归档','结束'] },
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
// 1. 全部自动
|
|
751
|
-
if (/一键|全部|自动|all|auto|搞定|剩下|remaining/i.test(lower)) {
|
|
752
|
-
const pending = phases.filter(p => !p.done);
|
|
753
|
-
if (pending.length === 0) return { summary:'所有阶段已完成!', commands:[{order:1,command:'dashboard',args:'--scope global',explanation:'查看全局仪表盘'}] };
|
|
754
|
-
return { summary:'一键执行剩余 ' + pending.length + ' 个阶段', commands:pending.map((p,i) => {
|
|
755
|
-
const kb = CMD_KB[p.key] || { args:'', desc:p.name };
|
|
756
|
-
return { order:i+1, command:p.cmd, args:kb.args, explanation:kb.desc };
|
|
757
|
-
})};
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// 2. 跳过
|
|
761
|
-
if (/跳过|skip|下一个|next/i.test(lower)) {
|
|
762
|
-
const nextIdx = state.currentIdx + 1;
|
|
763
|
-
if (nextIdx >= phases.length) return { summary:'已是最后阶段', commands:[{order:1,command:'done',args:'--all',explanation:'归档收尾'}] };
|
|
764
|
-
const next = phases[nextIdx];
|
|
765
|
-
const kb = CMD_KB[next.key] || { args:'', desc:next.name };
|
|
766
|
-
return { summary:'跳过 ' + cur.name + ' → ' + next.name, commands:[{order:1,command:next.cmd,args:kb.args,explanation:kb.desc}] };
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// 3. 阶段跳转
|
|
770
|
-
for (const p of phases) {
|
|
771
|
-
if (lower.includes(p.key) || lower.includes(p.name)) {
|
|
772
|
-
const kb = CMD_KB[p.key] || { args:'', desc:p.name };
|
|
773
|
-
if (p.done) return { summary:p.name + ' 已完成', commands:[{order:1,command:cur.cmd,args:CMD_KB[cur.key]?.args||iterArg,explanation:cur.name}] };
|
|
774
|
-
return { summary:'跳转到「' + p.name + '」', commands:[{order:1,command:p.cmd,args:kb.args,explanation:kb.desc}] };
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// 4. 多阶段组合
|
|
779
|
-
if (/先|再|然后|接着|最后|同时|也|之后|顺便|还有/.test(lower)) {
|
|
780
|
-
const matched = [];
|
|
781
|
-
for (const p of phases) {
|
|
782
|
-
if (lower.includes(p.key) || lower.includes(p.name)) {
|
|
783
|
-
const kb = CMD_KB[p.key] || { args:'', desc:p.name };
|
|
784
|
-
matched.push({ cmd:p.cmd, args:kb.args, desc:kb.desc });
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
if (matched.length >= 2) {
|
|
788
|
-
return { summary:matched.length + ' 步组合执行', commands:matched.map((m,i) => ({ order:i+1, command:m.cmd, args:m.args, explanation:m.desc })) };
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// 5. 模糊匹配
|
|
793
|
-
let bestCmd = null, bestScore = 0;
|
|
794
|
-
for (const [key, kb] of Object.entries(CMD_KB)) {
|
|
795
|
-
const score = kb.trigger.filter(t => lower.includes(t)).length;
|
|
796
|
-
if (score > bestScore) { bestScore = score; bestCmd = key; }
|
|
797
|
-
}
|
|
798
|
-
if (bestCmd && bestScore > 0) {
|
|
799
|
-
const p = phases.find(ph => ph.key === bestCmd);
|
|
800
|
-
if (p && !p.done) {
|
|
801
|
-
const kb = CMD_KB[bestCmd];
|
|
802
|
-
return { summary:'建议: ' + p.name, commands:[{order:1,command:p.cmd,args:kb.args,explanation:kb.desc}] };
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// 6. 不明意图 → 列出选项
|
|
807
|
-
return { summary:'你想怎么做?', commands:[
|
|
808
|
-
{order:1,command:cur.cmd,args:CMD_KB[cur.key]?.args||iterArg,explanation:'继续当前: ' + cur.name},
|
|
809
|
-
{order:2,command:'dashboard',args:'--scope global',explanation:'查看全局仪表盘'},
|
|
810
|
-
{order:3,command:'ask',args:'"'+input+'"',explanation:'用 ask 万能入口重新分析'},
|
|
811
|
-
]};
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// ── 按钮操作 ──
|
|
815
|
-
function handleAction(action, target) {
|
|
816
|
-
const iterArg = STATE.iteration ? ' --iteration=' + STATE.iteration : '';
|
|
817
|
-
const phases = STATE.phases;
|
|
818
|
-
const cur = STATE.currentPhase;
|
|
819
|
-
|
|
820
|
-
switch(action) {
|
|
821
|
-
case 'auto-all': {
|
|
822
|
-
const pending = phases.filter(p=>!p.done);
|
|
823
|
-
showResult({ summary:'一键执行全部 '+pending.length+' 个阶段', commands:pending.map((p,i)=>({order:i+1,command:p.cmd,args:p.args||iterArg,explanation:p.description})) });
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
case 'next':
|
|
827
|
-
showResult({ summary:'执行下一步: '+cur.name, commands:[{order:1,command:cur.cmd,args:cur.args||iterArg,explanation:cur.description}] });
|
|
828
|
-
return;
|
|
829
|
-
case 'skip-to': {
|
|
830
|
-
const nextIdx = STATE.currentIdx + 1;
|
|
831
|
-
if (nextIdx >= phases.length) { showResult({ summary:'已是最后阶段', commands:[{order:1,command:'dashboard',args:'--scope global',explanation:'查看全局仪表盘'}] }); return; }
|
|
832
|
-
const next = phases[nextIdx];
|
|
833
|
-
showResult({ summary:'跳过→'+next.name, commands:[{order:1,command:next.cmd,args:next.args||iterArg,explanation:next.description}] });
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
case 'jump-to': {
|
|
837
|
-
const tp = phases.find(p=>p.key===target);
|
|
838
|
-
if (!tp || tp.done) { showResult({ summary:tp?tp.name+' 已完成':'未找到', commands:[{order:1,command:cur.cmd,args:cur.args||iterArg,explanation:cur.name}] }); return; }
|
|
839
|
-
showResult({ summary:'跳转→'+tp.name, commands:[{order:1,command:tp.cmd,args:tp.args||iterArg,explanation:tp.description}] });
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
case 'restart': {
|
|
843
|
-
const fromIdx = phases.findIndex(p=>p.key===target);
|
|
844
|
-
const fromList = phases.slice(Math.max(0,fromIdx));
|
|
845
|
-
showResult({ summary:'从「'+(phases[fromIdx]?.name||'头')+'」重新开始', commands:fromList.map((p,i)=>({order:i+1,command:p.cmd,args:p.args||iterArg,explanation:p.description})) });
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
default:
|
|
849
|
-
showResult({ summary:'执行: '+cur.name, commands:[{order:1,command:cur.cmd,args:cur.args||iterArg,explanation:cur.description}] });
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
</script></body></html>`;
|
|
853
|
-
}
|
|
854
|
-
//# sourceMappingURL=dev.js.map
|