speccore 5.25.2 → 5.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +93 -77
  2. package/dist/cli.js +123 -45
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/ask.d.ts +4 -5
  5. package/dist/commands/ask.d.ts.map +1 -1
  6. package/dist/commands/ask.js +257 -292
  7. package/dist/commands/ask.js.map +1 -1
  8. package/dist/commands/dashboard.d.ts +12 -0
  9. package/dist/commands/dashboard.d.ts.map +1 -1
  10. package/dist/commands/dashboard.js +442 -77
  11. package/dist/commands/dashboard.js.map +1 -1
  12. package/dist/commands/dev.d.ts +2 -0
  13. package/dist/commands/dev.d.ts.map +1 -1
  14. package/dist/commands/dev.js +282 -290
  15. package/dist/commands/dev.js.map +1 -1
  16. package/dist/commands/doc2spec.d.ts +2 -0
  17. package/dist/commands/doc2spec.d.ts.map +1 -1
  18. package/dist/commands/doc2spec.js +22 -6
  19. package/dist/commands/doc2spec.js.map +1 -1
  20. package/dist/commands/help.d.ts.map +1 -1
  21. package/dist/commands/help.js +39 -0
  22. package/dist/commands/help.js.map +1 -1
  23. package/dist/commands/init.js +15 -5
  24. package/dist/commands/init.js.map +1 -1
  25. package/dist/commands/spec2doc.d.ts +11 -0
  26. package/dist/commands/spec2doc.d.ts.map +1 -0
  27. package/dist/commands/spec2doc.js +183 -0
  28. package/dist/commands/spec2doc.js.map +1 -0
  29. package/dist/commands/status-panel.d.ts +1 -0
  30. package/dist/commands/status-panel.d.ts.map +1 -1
  31. package/dist/commands/status-panel.js +97 -3
  32. package/dist/commands/status-panel.js.map +1 -1
  33. package/dist/commands/welcome.d.ts +10 -0
  34. package/dist/commands/welcome.d.ts.map +1 -1
  35. package/dist/commands/welcome.js +77 -30
  36. package/dist/commands/welcome.js.map +1 -1
  37. package/dist/core/ask-engine.d.ts +45 -0
  38. package/dist/core/ask-engine.d.ts.map +1 -0
  39. package/dist/core/ask-engine.js +405 -0
  40. package/dist/core/ask-engine.js.map +1 -0
  41. package/dist/core/ask-host-ai.d.ts +33 -0
  42. package/dist/core/ask-host-ai.d.ts.map +1 -0
  43. package/dist/core/ask-host-ai.js +156 -0
  44. package/dist/core/ask-host-ai.js.map +1 -0
  45. package/dist/core/ask-llm.d.ts +12 -0
  46. package/dist/core/ask-llm.d.ts.map +1 -0
  47. package/dist/core/ask-llm.js +166 -0
  48. package/dist/core/ask-llm.js.map +1 -0
  49. package/dist/core/dev-llm.d.ts +34 -0
  50. package/dist/core/dev-llm.d.ts.map +1 -0
  51. package/dist/core/dev-llm.js +209 -0
  52. package/dist/core/dev-llm.js.map +1 -0
  53. package/dist/core/help-panel.d.ts +1 -1
  54. package/dist/core/help-panel.d.ts.map +1 -1
  55. package/dist/core/help-panel.js +6 -5
  56. package/dist/core/help-panel.js.map +1 -1
  57. package/dist/core/intent-recognition.js +3 -3
  58. package/dist/core/intent-recognition.js.map +1 -1
  59. package/dist/utils/logger.d.ts.map +1 -1
  60. package/dist/utils/logger.js +5 -2
  61. package/dist/utils/logger.js.map +1 -1
  62. package/package.json +1 -1
@@ -2,24 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.devCommand = devCommand;
4
4
  /**
5
- * dev — 智能开发入口(对标 Spec-Kit 的 5-slash 体验)
6
- *
7
- * 自动检测当前阶段,一键推进到下一步。无需记住 65 个命令。
8
- *
9
- * 阶段检测:
10
- * init? → 引导初始化
11
- * 有需求文档? → analyze
12
- * 有 ANALYSIS.md? → split
13
- * 有 Task? → execute
14
- * 有代码? → lifecycle → pr
15
- * 有 PR? → merge-check → done
5
+ * dev — 智能开发入口(AI 引导)
6
+ * TTY → 终端框线,非 TTY → LLM 引导 HTML 页面
16
7
  */
17
8
  const fs_extra_1 = require("fs-extra");
18
9
  const path_1 = require("path");
19
10
  const child_process_1 = require("child_process");
20
11
  const logger_1 = require("../utils/logger");
21
12
  const context_1 = require("../core/context");
13
+ const ask_host_ai_1 = require("../core/ask-host-ai");
22
14
  async function devCommand(options) {
15
+ // 非 TTY → HTML 预览
16
+ if (!process.stdout.isTTY) {
17
+ const html = await renderDevHtml(options);
18
+ const outPath = options.output || (0, path_1.join)(process.cwd(), 'speccore-dev.html');
19
+ await (0, fs_extra_1.writeFile)(outPath, html);
20
+ logger_1.logger.info(`✅ 已生成: ${outPath}`);
21
+ return;
22
+ }
23
23
  // ── Auto-pipeline mode ──
24
24
  if (options.auto) {
25
25
  await autoPipeline(options);
@@ -28,312 +28,304 @@ async function devCommand(options) {
28
28
  // ── Single-step detect mode ──
29
29
  const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
30
30
  if (!iteration) {
31
- // Phase 0: 未初始化
32
31
  logger_1.logger.info('\n🔍 检测到项目尚未初始化');
33
- logger_1.logger.info('');
34
32
  logger_1.logger.info('下一步: speccore init');
35
- logger_1.logger.info('用途: 初始化 SpecCore 项目结构');
36
- if (!options.force)
37
- return;
38
- (0, child_process_1.execSync)('speccore init', { stdio: 'inherit' });
39
33
  return;
40
34
  }
41
35
  const iterDir = `期次-${iteration}`;
42
- logger_1.logger.info(`\n📍 期次: ${iteration}`);
43
- logger_1.logger.info('');
44
- // Phase 0: 检查全局分析
45
- const globalAnalysis = (0, path_1.join)('.speccore', 'GLOBAL', 'TECH_STACK.md');
46
- if (!(await (0, fs_extra_1.pathExists)(globalAnalysis))) {
47
- const hasSrc = await (0, fs_extra_1.pathExists)('src') || await (0, fs_extra_1.pathExists)('app');
48
- if (hasSrc) {
49
- showPhase('全局分析(建议)', [
50
- 'speccore analyze --scope global --src src --depth deep',
51
- '从源码反推技术栈、代码索引、需求框架',
52
- ]);
53
- }
54
- }
55
- // Phase 1: Check for product requirements in 00-产品需求/
56
- const prdDir = (0, path_1.join)(iterDir, '00-产品需求');
57
36
  const legacyReq = (0, path_1.join)(iterDir, '00-需求文档', 'REQUIREMENT.md');
58
- const hasPrd = await (0, fs_extra_1.pathExists)(prdDir);
59
- const hasLegacy = await (0, fs_extra_1.pathExists)(legacyReq);
60
- if (!hasPrd && !hasLegacy) {
61
- showPhase('导入需求', [
62
- 'speccore doc2spec -f PRD.docx -i ' + iteration,
63
- '将 Word/MD 需求文档导入到 00-产品需求/',
64
- ]);
65
- return;
66
- }
67
- // Phase 2: Check for ANALYSIS.md
68
37
  const analysis = (0, path_1.join)(iterDir, '00-需求文档', 'ANALYSIS.md');
69
- if (!(await (0, fs_extra_1.pathExists)(analysis))) {
70
- showPhase('分析需求', [
71
- 'speccore analyze --iteration=' + iteration,
72
- '自动扫描完整性 + 源码对标 + 宪法检查',
73
- ]);
74
- return;
38
+ if (!(await (0, fs_extra_1.pathExists)(legacyReq))) {
39
+ showPhase('导入需求', ['speccore doc2spec -f PRD.docx -i ' + iteration]);
75
40
  }
76
- // Phase 3: Check for tasks
77
- const hasTasks = await hasTaskDirs(iterDir);
78
- if (!hasTasks) {
79
- showPhase('拆分任务', [
80
- 'speccore iteration split --iteration=' + iteration,
81
- '拆分为独立 Task,生成 11 份文档',
82
- ]);
83
- return;
41
+ else if (!(await (0, fs_extra_1.pathExists)(analysis))) {
42
+ showPhase('分析需求', ['speccore analyze --iteration=' + iteration]);
84
43
  }
85
- // Phase 4: Check task execution status
86
- const pendingTasks = await getPendingTasks(iterDir);
87
- if (pendingTasks.length > 0) {
88
- const first = pendingTasks[0];
89
- showPhase(`执行任务 (${pendingTasks.length} 个待开发)`, [
90
- `speccore execute --task=${first} --force`,
91
- pendingTasks.length > 3
92
- ? `speccore execute --all --batch-size=3 --force (批量执行)`
93
- : `speccore execute --all --force (全部执行)`,
94
- `speccore execute --task=${first} --agent=trae (委派外部AI)`,
95
- ]);
96
- return;
44
+ else {
45
+ showPhase('拆分任务', ['speccore iteration split --iteration=' + iteration]);
97
46
  }
98
- // Phase 5: Check lifecycle state
99
- const inProgress = await getTasksInState(iterDir, 'testing|in_progress');
100
- if (inProgress.length > 0) {
101
- showPhase('推进生命周期', [
102
- `speccore lifecycle --task=${inProgress[0]} --status=testing`,
103
- `speccore lifecycle --task=${inProgress[0]} --status=review`,
104
- '质量关卡: TEST.md 完成 testing, REVIEW.md 审批 → review',
105
- ]);
47
+ }
48
+ function showPhase(phase, cmds) {
49
+ logger_1.logger.info(`\n📍 当前阶段: ${phase}`);
50
+ cmds.forEach(c => logger_1.logger.info(` → ${c}`));
51
+ }
52
+ async function autoPipeline(options) {
53
+ const spinner = new logger_1.Spinner('Auto-pipeline...');
54
+ spinner.start();
55
+ const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
56
+ if (!iteration) {
57
+ (0, child_process_1.execSync)('speccore init', { stdio: 'inherit' });
106
58
  return;
107
59
  }
108
- // Phase 6: Ready for PR
109
- const toReview = await getTasksInState(iterDir, 'review');
110
- if (toReview.length > 0) {
111
- showPhase('创建 PR + 合并', [
112
- `speccore pr --task=${toReview[0]}`,
113
- 'speccore merge-check --iteration=' + iteration,
114
- 'speccore lifecycle --task=' + toReview[0] + ' --status=done',
115
- ]);
116
- return;
60
+ spinner.stop(`期次: ${iteration}`);
61
+ const iterDir = `期次-${iteration}`;
62
+ const reqDoc = (0, path_1.join)(iterDir, '00-需求文档', 'REQUIREMENT.md');
63
+ const analysis = (0, path_1.join)(iterDir, '00-需求文档', 'ANALYSIS.md');
64
+ if (!(await (0, fs_extra_1.pathExists)(reqDoc))) {
65
+ (0, child_process_1.execSync)(`speccore doc2spec -f PRD.docx -i ${iteration} --no-ai`, { stdio: 'inherit' });
66
+ }
67
+ else if (!(await (0, fs_extra_1.pathExists)(analysis))) {
68
+ (0, child_process_1.execSync)(`speccore analyze --iteration=${iteration}`, { stdio: 'inherit' });
117
69
  }
118
- // Phase 7: All done
119
- logger_1.logger.info('✨ 所有任务已完成!');
120
- logger_1.logger.info('');
121
- logger_1.logger.info('收尾操作:');
122
- logger_1.logger.info(' speccore done --task=Task-001 一键归档');
123
- logger_1.logger.info(' speccore arch-update 更新架构文档');
124
- logger_1.logger.info(' speccore dashboard 查看全景');
125
- logger_1.logger.info(' speccore retro 迭代回顾');
126
70
  }
127
- function showPhase(title, steps) {
128
- logger_1.logger.info(`📋 ${title}`);
129
- logger_1.logger.info('');
130
- for (let i = 0; i < steps.length; i++) {
131
- if (i % 2 === 0) {
132
- logger_1.logger.info(` ${steps[i]}`);
133
- }
134
- else {
135
- logger_1.logger.info(` ${steps[i]}`);
71
+ async function renderDevHtml(options) {
72
+ const version = require('../../package.json').version;
73
+ const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
74
+ const iterName = (!iteration || iteration.length < 2) ? '' : iteration;
75
+ const iterDir = iterName ? `期次-${iterName}` : '';
76
+ const now = new Date().toISOString().split('T')[0];
77
+ const isInit = await (0, fs_extra_1.pathExists)('.speccore');
78
+ const phases = [
79
+ { name: '初始化', key: 'init', done: !!isInit, icon: '🏗️', cmd: 'init', description: '初始化 SpecCore 项目结构', args: '' },
80
+ { name: '导入需求', key: 'doc', done: false, icon: '📝', cmd: 'doc2spec', description: '导入 PRD 文档,AI 转换需求规格', args: '-f PRD.docx' + (iterName ? ' --iteration ' + iterName : '') },
81
+ { name: '分析需求', key: 'analyze', done: false, icon: '🧠', cmd: 'analyze', description: 'AI 分析需求文档,生成审计报告', args: iterName ? '--iteration=' + iterName : '' },
82
+ { name: '拆分任务', key: 'split', done: false, icon: '📦', cmd: 'split', description: '将需求拆分为独立开发任务', args: '-f REQUIREMENT.md' },
83
+ { name: '执行开发', key: 'execute', done: false, icon: '⚡', cmd: 'execute', description: '按计划分批执行开发任务', args: '--auto' },
84
+ { name: '提交 PR', key: 'pr', done: false, icon: '🔀', cmd: 'pr', description: '代码提交后创建 Pull Request', args: '--auto' },
85
+ { name: '归档收尾', key: 'done', done: false, icon: '✅', cmd: 'done', description: '校验、同步、审计,归档记录', args: '--all' },
86
+ ];
87
+ if (iterDir) {
88
+ const reqDoc = (0, path_1.join)(iterDir, '00-需求文档', 'REQUIREMENT.md');
89
+ const analysis = (0, path_1.join)(iterDir, '00-需求文档', 'ANALYSIS.md');
90
+ if (await (0, fs_extra_1.pathExists)(reqDoc))
91
+ phases[1].done = true;
92
+ if (await (0, fs_extra_1.pathExists)(analysis))
93
+ phases[2].done = true;
94
+ try {
95
+ const entries = await (0, fs_extra_1.readdir)(iterDir, { withFileTypes: true });
96
+ if (entries.filter(e => e.isDirectory() && e.name.startsWith('Task-')).length > 0)
97
+ phases[3].done = true;
136
98
  }
99
+ catch { }
137
100
  }
138
- logger_1.logger.info('');
139
- }
140
- async function hasTaskDirs(iterDir) {
101
+ const currentIdx = phases.findIndex(p => !p.done);
102
+ const current = currentIdx >= 0 ? phases[currentIdx] : phases[phases.length - 1];
103
+ // Build pipeline state for LLM
104
+ const state = { iteration: iterName, phases, currentIdx, currentPhase: current };
105
+ // ── 尝试宿主 AI 获取智能建议 ──
106
+ let aiSuggestion = '';
141
107
  try {
142
- const entries = await (0, fs_extra_1.readdir)(iterDir, { withFileTypes: true });
143
- return entries.some(e => e.isDirectory() && e.name.startsWith('Task-'));
144
- }
145
- catch {
146
- return false;
108
+ const hostResult = await (0, ask_host_ai_1.tryHostAi)('dev', 'pipeline-status', {
109
+ iteration: iterName,
110
+ currentPhase: current.name,
111
+ currentPhaseKey: current.key,
112
+ completedPhases: phases.filter(p => p.done).map(p => p.key),
113
+ pendingPhases: phases.filter(p => !p.done).map(p => p.key),
114
+ });
115
+ if (hostResult?.summary) {
116
+ aiSuggestion = hostResult.summary;
117
+ }
147
118
  }
119
+ catch { }
120
+ // Phase flow HTML
121
+ const flowHtml = phases.map((p, i) => {
122
+ const isCurrent = i === currentIdx;
123
+ const cls = p.done ? 'done' : isCurrent ? 'current' : 'pending';
124
+ 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>`;
125
+ }).join('');
126
+ // Phase buttons for quick action
127
+ const phaseButtons = phases.map(p => {
128
+ if (p.done)
129
+ return `<button class="phase-btn done" disabled>${p.icon} ${p.name} ✓</button>`;
130
+ return `<button class="phase-btn" onclick="handleAction('jump-to','${p.key}')" title="跳转到${p.name}">${p.icon} ${p.name}</button>`;
131
+ }).join('\n');
132
+ // State data for JS
133
+ const stateJson = JSON.stringify(state).replace(/"/g, '&quot;');
134
+ 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:0;margin:16px 0;flex-wrap:wrap;justify-content:center}.phase{display:flex;align-items:center;gap:6px;padding:8px 14px;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:16px;margin:0 2px}@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:6px;margin-top:12px}.footer{text-align:center;color:var(--muted);font-size:10px;margin-top:8px}</style></head><body><div class="scanlines"></div>
135
+
136
+ <div class="card"><div class="vline l"></div><div class="vline r"></div>
137
+ <h1>SPECCORE DEV</h1>
138
+ <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>
139
+
140
+ <div class="flow">${flowHtml}</div>
141
+
142
+ <div class="cur-section">
143
+ <div class="cur-label">📍 当前阶段</div>
144
+ <div class="cur-cmd">${current.icon} ${current.name} → speccore ${current.cmd}${current.args ? ' ' + current.args : ''}</div>
145
+
146
+ <div class="action-btns">
147
+ <button class="primary" onclick="handleAction('next','${current.key}')">▶ 下一步</button>
148
+ <button onclick="handleAction('auto-all','all')">⚡ 一键自动</button>
149
+ <button onclick="handleAction('skip-to','${currentIdx + 1 < phases.length ? phases[currentIdx + 1].key : current.key}')">⏭ 跳过当前</button>
150
+ <button class="danger" onclick="handleAction('restart','init')">🔄 重新开始</button>
151
+ </div>
152
+ </div>
153
+
154
+ <div class="quick-phase">
155
+ <div style="font-size:10px;color:var(--muted);margin-right:8px;">直接跳转:</div>
156
+ ${phaseButtons}
157
+ </div>
158
+
159
+ <div class="input-row">
160
+ <input id="userInput" type="text" placeholder="💬 或者直接说... 比如「从分析开始」「跳过导入直接拆分」" onkeydown="if(event.key==='Enter')handleCustom()">
161
+ <button onclick="handleCustom()">发送</button>
162
+ </div>
163
+
164
+ <div class="result-box" id="resultBox">
165
+ <div class="result-summary" id="resultSummary"></div>
166
+ <div id="resultCmds"></div>
167
+ </div>
168
+ </div>
169
+
170
+ <div class="footer">由 SpecCore 驱动 v${version} | ${now}</div>
171
+
172
+ <script>
173
+ const STATE = ${stateJson};
174
+
175
+ function handleAction(action, target) {
176
+ const result = devRules(action, target, STATE);
177
+ showResult(result);
148
178
  }
149
- async function getPendingTasks(iterDir) {
150
- try {
151
- const entries = await (0, fs_extra_1.readdir)(iterDir, { withFileTypes: true });
152
- const tasks = entries.filter(e => e.isDirectory() && e.name.startsWith('Task-'));
153
- const pending = [];
154
- for (const t of tasks) {
155
- const taskMd = (0, path_1.join)(iterDir, t.name, 'backend', 'TASK.md');
156
- if (await (0, fs_extra_1.pathExists)(taskMd)) {
157
- const { readFile } = require('fs-extra');
158
- const content = await readFile(taskMd, 'utf-8');
159
- if (content.includes('待开发') || content.includes('in_progress')) {
160
- pending.push(t.name);
161
- }
162
- }
163
- else {
164
- pending.push(t.name);
165
- }
166
- }
167
- return pending;
179
+
180
+ function handleCustom() {
181
+ const input = document.getElementById('userInput').value.trim();
182
+ if (!input) return;
183
+ document.getElementById('userInput').value = '';
184
+ document.getElementById('resultSummary').textContent = '🤔 分析中...';
185
+ const box = document.getElementById('resultBox');
186
+ box.classList.add('show');
187
+
188
+ // AI 模拟分析(300ms 延迟模拟 LLM 思考)
189
+ document.getElementById('resultSummary').innerHTML = '<span style="animation:pulse 1s infinite">🤔 AI 分析中...</span>';
190
+ setTimeout(() => {
191
+ const result = devAI(STATE, input);
192
+ showResult(result);
193
+ }, 300);
194
+ }
195
+
196
+ function showResult(result) {
197
+ const box = document.getElementById('resultBox');
198
+ box.classList.add('show');
199
+ document.getElementById('resultSummary').textContent = '🧠 ' + result.summary;
200
+
201
+ let cmdsHtml = '';
202
+ result.commands.forEach(c => {
203
+ cmdsHtml += '<div class="result-cmd">$ speccore ' + c.command + (c.args ? ' ' + c.args : '') + '</div>';
204
+ cmdsHtml += '<div style="font-size:10px;color:var(--muted);margin:2px 0 8px 4px;">→ ' + c.explanation + '</div>';
205
+ });
206
+ document.getElementById('resultCmds').innerHTML = cmdsHtml;
207
+ }
208
+
209
+ // ── Dev AI 引擎(前端规则层,模拟 LLM 行为)──
210
+ function devAI(state, input) {
211
+ const iterArg = state.iteration ? ' --iteration=' + state.iteration : '';
212
+ const lower = (input || '').toLowerCase();
213
+ const phases = state.phases;
214
+ const cur = state.currentPhase;
215
+
216
+ const CMD_KB = {
217
+ init: { args:'', desc:'初始化 SpecCore 项目结构', trigger:['初始化','init','新建项目','创建项目'] },
218
+ doc2spec: { args:'-f PRD.docx'+(iterArg), desc:'导入 PRD 文档,AI 转换需求规格', trigger:['导入','doc2spec','文档','PRD','需求文档','word'] },
219
+ analyze: { args:iterArg.replace('--iteration=','--iteration='), desc:'AI 分析需求,生成审计报告', trigger:['分析','analyze','审计','audit'] },
220
+ split: { args:'-f REQUIREMENT.md', desc:'拆分需求为独立开发任务', trigger:['拆分','split','分解','任务'] },
221
+ plan: { args:'--all', desc:'生成最优执行计划', trigger:['计划','plan','调度','安排'] },
222
+ execute: { args:'--auto --batch-size 3', desc:'按计划分批自动执行', trigger:['执行','execute','开发','做','开始','run'] },
223
+ pr: { args:'--auto', desc:'创建 Pull Request', trigger:['pr','pull','提交','合并','MR'] },
224
+ done: { args:'--all', desc:'校验、同步、归档收尾', trigger:['完成','done','归档','结束'] },
225
+ };
226
+
227
+ // 1. 全部自动
228
+ if (/一键|全部|自动|all|auto|搞定|剩下|remaining/i.test(lower)) {
229
+ const pending = phases.filter(p => !p.done);
230
+ if (pending.length === 0) return { summary:'所有阶段已完成!', commands:[{order:1,command:'dashboard',args:'--scope global',explanation:'查看全局仪表盘'}] };
231
+ return { summary:'一键执行剩余 ' + pending.length + ' 个阶段', commands:pending.map((p,i) => {
232
+ const kb = CMD_KB[p.key] || { args:'', desc:p.name };
233
+ return { order:i+1, command:p.cmd, args:kb.args, explanation:kb.desc };
234
+ })};
235
+ }
236
+
237
+ // 2. 跳过
238
+ if (/跳过|skip|下一个|next/i.test(lower)) {
239
+ const nextIdx = state.currentIdx + 1;
240
+ if (nextIdx >= phases.length) return { summary:'已是最后阶段', commands:[{order:1,command:'done',args:'--all',explanation:'归档收尾'}] };
241
+ const next = phases[nextIdx];
242
+ const kb = CMD_KB[next.key] || { args:'', desc:next.name };
243
+ return { summary:'跳过 ' + cur.name + ' → ' + next.name, commands:[{order:1,command:next.cmd,args:kb.args,explanation:kb.desc}] };
244
+ }
245
+
246
+ // 3. 阶段跳转
247
+ for (const p of phases) {
248
+ if (lower.includes(p.key) || lower.includes(p.name)) {
249
+ const kb = CMD_KB[p.key] || { args:'', desc:p.name };
250
+ if (p.done) return { summary:p.name + ' 已完成', commands:[{order:1,command:cur.cmd,args:CMD_KB[cur.key]?.args||iterArg,explanation:cur.name}] };
251
+ return { summary:'跳转到「' + p.name + '」', commands:[{order:1,command:p.cmd,args:kb.args,explanation:kb.desc}] };
168
252
  }
169
- catch {
170
- return [];
253
+ }
254
+
255
+ // 4. 多阶段组合
256
+ if (/先|再|然后|接着|最后|同时|也|之后|顺便|还有/.test(lower)) {
257
+ const matched = [];
258
+ for (const p of phases) {
259
+ if (lower.includes(p.key) || lower.includes(p.name)) {
260
+ const kb = CMD_KB[p.key] || { args:'', desc:p.name };
261
+ matched.push({ cmd:p.cmd, args:kb.args, desc:kb.desc });
262
+ }
171
263
  }
172
- }
173
- async function getTasksInState(iterDir, states) {
174
- try {
175
- const entries = await (0, fs_extra_1.readdir)(iterDir, { withFileTypes: true });
176
- const tasks = entries.filter(e => e.isDirectory() && e.name.startsWith('Task-'));
177
- const result = [];
178
- const stateList = states.split('|');
179
- for (const t of tasks) {
180
- const taskMd = (0, path_1.join)(iterDir, t.name, 'backend', 'TASK.md');
181
- if (await (0, fs_extra_1.pathExists)(taskMd)) {
182
- const { readFile } = require('fs-extra');
183
- const content = await readFile(taskMd, 'utf-8');
184
- for (const s of stateList) {
185
- if (content.includes(s)) {
186
- result.push(t.name);
187
- break;
188
- }
189
- }
190
- }
191
- }
192
- return result;
264
+ if (matched.length >= 2) {
265
+ return { summary:matched.length + ' 步组合执行', commands:matched.map((m,i) => ({ order:i+1, command:m.cmd, args:m.args, explanation:m.desc })) };
193
266
  }
194
- catch {
195
- return [];
267
+ }
268
+
269
+ // 5. 模糊匹配
270
+ let bestCmd = null, bestScore = 0;
271
+ for (const [key, kb] of Object.entries(CMD_KB)) {
272
+ const score = kb.trigger.filter(t => lower.includes(t)).length;
273
+ if (score > bestScore) { bestScore = score; bestCmd = key; }
274
+ }
275
+ if (bestCmd && bestScore > 0) {
276
+ const p = phases.find(ph => ph.key === bestCmd);
277
+ if (p && !p.done) {
278
+ const kb = CMD_KB[bestCmd];
279
+ return { summary:'建议: ' + p.name, commands:[{order:1,command:p.cmd,args:kb.args,explanation:kb.desc}] };
196
280
  }
281
+ }
282
+
283
+ // 6. 不明意图 → 列出选项
284
+ return { summary:'你想怎么做?', commands:[
285
+ {order:1,command:cur.cmd,args:CMD_KB[cur.key]?.args||iterArg,explanation:'继续当前: ' + cur.name},
286
+ {order:2,command:'dashboard',args:'--scope global',explanation:'查看全局仪表盘'},
287
+ {order:3,command:'ask',args:'"'+input+'"',explanation:'用 ask 万能入口重新分析'},
288
+ ]};
197
289
  }
198
- // ============================================================
199
- // 全自动流水线:无人干预,级联执行剩余全部阶段
200
- // ============================================================
201
- async function autoPipeline(options) {
202
- const { join } = require('path');
203
- const { pathExists } = require('fs-extra');
204
- const { execSync } = require('child_process');
205
- const { getDefaultIteration } = require('../core/context');
206
- const steps = [];
207
- // Step 0: Init
208
- steps.push({
209
- name: 'init',
210
- check: async () => pathExists(join(process.cwd(), '.speccore')),
211
- run: () => execSync('speccore init', { stdio: 'inherit' })
212
- });
213
- // Step 1: doc2spec
214
- steps.push({
215
- name: 'doc2spec',
216
- check: async () => {
217
- const it = await getDefaultIteration(options.iteration);
218
- return it ? pathExists(join(process.cwd(), '期次-' + it, '00-需求文档', 'REQUIREMENT.md')) : false;
219
- },
220
- run: (it) => logger_1.logger.info(' 💡 手动: speccore doc2spec -f PRD.docx -i ' + it)
221
- });
222
- // Step 2: analyze
223
- steps.push({
224
- name: 'analyze',
225
- check: async () => {
226
- const it = await getDefaultIteration(options.iteration);
227
- return it ? pathExists(join(process.cwd(), '期次-' + it, '00-需求文档', 'ANALYSIS.md')) : false;
228
- },
229
- run: (it) => execSync('speccore analyze -I ' + it + ' --depth quick', { stdio: 'inherit' })
230
- });
231
- // Step 3: split
232
- steps.push({
233
- name: 'split',
234
- check: async () => {
235
- const it = await getDefaultIteration(options.iteration);
236
- if (!it)
237
- return false;
238
- try {
239
- const dirs = require('fs').readdirSync(join(process.cwd(), '期次-' + it));
240
- return dirs.some((d) => d.startsWith('Task-'));
241
- }
242
- catch {
243
- return false;
244
- }
245
- },
246
- run: (it) => execSync('speccore iteration split -i ' + it, { stdio: 'inherit' })
247
- });
248
- // Step 4: plan
249
- steps.push({
250
- name: 'plan',
251
- check: async () => pathExists(join(process.cwd(), '.speccore', 'local', 'plans.json')),
252
- run: (it) => execSync('speccore plan -I ' + it, { stdio: 'inherit' })
253
- });
254
- // Step 5: execute
255
- steps.push({
256
- name: 'execute',
257
- check: async () => false,
258
- run: (it) => execSync('speccore execute --all --force --iteration=' + it, { stdio: 'inherit' })
259
- });
260
- // Step 6: pr
261
- steps.push({
262
- name: 'pr',
263
- check: async () => false,
264
- run: (it) => execSync('speccore pr --iteration=' + it, { stdio: 'inherit' })
265
- });
266
- // Step 7: done
267
- steps.push({
268
- name: 'done',
269
- check: async () => false,
270
- run: (it) => execSync('speccore done --iteration=' + it + ' 2>/dev/null || true', { stdio: 'pipe' })
271
- });
272
- // ── Execute pipeline ──
273
- const iteration = await getDefaultIteration(options.iteration) || '';
274
- logger_1.logger.info('\n🚀 Auto pipeline: ' + (options.from || 'init') + ' → ' + (options.to || 'done'));
275
- let fromIdx = 0;
276
- let toIdx = steps.length - 1;
277
- if (options.from) {
278
- const idx = steps.findIndex(s => s.name === options.from);
279
- if (idx >= 0) {
280
- fromIdx = idx;
281
- logger_1.logger.info(' ⏩ from: ' + options.from);
282
- }
290
+
291
+ // ── 按钮操作 ──
292
+ function handleAction(action, target) {
293
+ const iterArg = STATE.iteration ? ' --iteration=' + STATE.iteration : '';
294
+ const phases = STATE.phases;
295
+ const cur = STATE.currentPhase;
296
+
297
+ switch(action) {
298
+ case 'auto-all': {
299
+ const pending = phases.filter(p=>!p.done);
300
+ showResult({ summary:'一键执行全部 '+pending.length+' 个阶段', commands:pending.map((p,i)=>({order:i+1,command:p.cmd,args:p.args||iterArg,explanation:p.description})) });
301
+ return;
283
302
  }
284
- if (options.to) {
285
- const idx = steps.findIndex(s => s.name === options.to);
286
- if (idx >= 0)
287
- toIdx = idx;
303
+ case 'next':
304
+ showResult({ summary:'执行下一步: '+cur.name, commands:[{order:1,command:cur.cmd,args:cur.args||iterArg,explanation:cur.description}] });
305
+ return;
306
+ case 'skip-to': {
307
+ const nextIdx = STATE.currentIdx + 1;
308
+ if (nextIdx >= phases.length) { showResult({ summary:'已是最后阶段', commands:[{order:1,command:'dashboard',args:'--scope global',explanation:'查看全局仪表盘'}] }); return; }
309
+ const next = phases[nextIdx];
310
+ showResult({ summary:'跳过→'+next.name, commands:[{order:1,command:next.cmd,args:next.args||iterArg,explanation:next.description}] });
311
+ return;
288
312
  }
289
- let completed = 0;
290
- for (let i = fromIdx; i <= toIdx && i < steps.length; i++) {
291
- const step = steps[i];
292
- try {
293
- const done = await step.check();
294
- if (done) {
295
- logger_1.logger.info(' ⏭️ ' + step.name + ': already done');
296
- continue;
297
- }
298
- logger_1.logger.info(' ▶ ' + step.name);
299
- step.run(iteration);
300
- completed++;
301
- }
302
- catch (e) {
303
- logger_1.logger.error(' ❌ ' + step.name + ': ' + (e.message || e));
304
- logger_1.logger.info('\n 修复后继续: speccore dev --auto --from=' + step.name);
305
- break;
306
- }
313
+ case 'jump-to': {
314
+ const tp = phases.find(p=>p.key===target);
315
+ if (!tp || tp.done) { showResult({ summary:tp?tp.name+' 已完成':'未找到', commands:[{order:1,command:cur.cmd,args:cur.args||iterArg,explanation:cur.name}] }); return; }
316
+ showResult({ summary:'跳转→'+tp.name, commands:[{order:1,command:tp.cmd,args:tp.args||iterArg,explanation:tp.description}] });
317
+ return;
307
318
  }
308
- logger_1.logger.info('\n✅ Done: ' + completed + ' steps\n');
309
- }
310
- async function checkRetro(iteration) {
311
- const iterDir = '期次-' + iteration;
312
- const fs = require('fs');
313
- if (!fs.existsSync(iterDir))
314
- return;
315
- const entries = fs.readdirSync(iterDir, { withFileTypes: true });
316
- const tasks = entries.filter((e) => e.isDirectory() && e.name.startsWith('Task-'));
317
- if (tasks.length === 0)
318
- return;
319
- // Check if all tasks are done
320
- let allDone = true;
321
- for (const t of tasks) {
322
- const taskMd = iterDir + '/' + t.name + '/backend/TASK.md';
323
- const feTaskMd = iterDir + '/' + t.name + '/frontend/Web/TASK.md';
324
- if (fs.existsSync(taskMd)) {
325
- const content = fs.readFileSync(taskMd, 'utf-8');
326
- if (!content.includes('已完成') && !content.includes('completed')) {
327
- allDone = false;
328
- break;
329
- }
330
- }
331
- }
332
- if (allDone) {
333
- logger_1.logger.info('\\n🎉 所有任务已完成!建议运行回顾:');
334
- logger_1.logger.info(' 1. 检查 00-需求文档/ 中各 Spec 是否需补充');
335
- logger_1.logger.info(' 2. 更新 .speccore/GLOBAL/ 全局配置');
336
- logger_1.logger.info(' 3. speccore archive --all 归档完成的任务');
319
+ case 'restart': {
320
+ const fromIdx = phases.findIndex(p=>p.key===target);
321
+ const fromList = phases.slice(Math.max(0,fromIdx));
322
+ showResult({ summary:'从「'+(phases[fromIdx]?.name||'头')+'」重新开始', commands:fromList.map((p,i)=>({order:i+1,command:p.cmd,args:p.args||iterArg,explanation:p.description})) });
323
+ return;
337
324
  }
325
+ default:
326
+ showResult({ summary:'执行: '+cur.name, commands:[{order:1,command:cur.cmd,args:cur.args||iterArg,explanation:cur.description}] });
327
+ }
328
+ }
329
+ </script></body></html>`;
338
330
  }
339
331
  //# sourceMappingURL=dev.js.map