sillyspec 3.9.0 → 3.9.1
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/.claude/skills/sillyspec-archive/SKILL.md +17 -0
- package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
- package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
- package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +170 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +52 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +17 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
- package/.sillyspec/changes/auto-mode/design.md +50 -0
- package/.sillyspec/changes/auto-mode/proposal.md +19 -0
- package/.sillyspec/changes/auto-mode/requirements.md +21 -0
- package/.sillyspec/changes/auto-mode/tasks.md +7 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/changes/unified-docs-design/design.md +199 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +12 -5
- package/package.json +7 -9
- package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
- package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
- package/packages/dashboard/dist/index.html +3 -2
- package/packages/dashboard/package-lock.json +226 -6
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +336 -113
- package/packages/dashboard/server/parser.js +333 -29
- package/packages/dashboard/server/watcher.js +203 -131
- package/packages/dashboard/src/App.vue +187 -11
- package/packages/dashboard/src/components/ActionBar.vue +26 -42
- package/packages/dashboard/src/components/CommandPalette.vue +40 -65
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +160 -0
- package/packages/dashboard/src/components/DocTree.vue +58 -0
- package/packages/dashboard/src/components/LogStream.vue +13 -33
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +80 -45
- package/packages/dashboard/src/components/ProjectList.vue +103 -45
- package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/composables/useDashboard.js +20 -6
- package/packages/dashboard/src/composables/useKeyboard.js +6 -4
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +134 -22
- package/src/init.js +83 -228
- package/src/migrate.js +117 -0
- package/src/progress.js +459 -0
- package/src/run.js +624 -0
- package/src/setup.js +2 -72
- package/src/stages/archive.js +54 -0
- package/src/stages/brainstorm.js +239 -0
- package/src/stages/doctor.js +303 -0
- package/src/stages/execute.js +262 -0
- package/src/stages/index.js +26 -0
- package/src/stages/plan.js +282 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/quick.js +64 -0
- package/src/stages/scan.js +141 -0
- package/src/stages/status.js +65 -0
- package/src/stages/verify.js +135 -0
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/docs/.vitepress/config.mts +0 -45
- package/docs/.vitepress/dist/404.html +0 -25
- package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
- package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
- package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
- package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
- package/docs/.vitepress/dist/hashmap.json +0 -1
- package/docs/.vitepress/dist/index.html +0 -28
- package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
- package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
- package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
- package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
- package/docs/.vitepress/dist/sillyspec/install.html +0 -32
- package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
- package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
- package/docs/.vitepress/dist/vp-icons.css +0 -1
- package/docs/index.md +0 -34
- package/docs/sillyspec/commands.md +0 -218
- package/docs/sillyspec/dashboard.md +0 -51
- package/docs/sillyspec/file-io.md +0 -34
- package/docs/sillyspec/getting-started.md +0 -61
- package/docs/sillyspec/install.md +0 -51
- package/docs/sillyspec/lifecycle.md +0 -146
- package/docs/sillyspec/structure.md +0 -62
- package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
- package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
- package/src/step.js +0 -543
- package/templates/archive.md +0 -120
- package/templates/brainstorm.md +0 -170
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -304
- package/templates/explore.md +0 -59
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -146
- package/templates/quick.md +0 -135
- package/templates/scan-quick.md +0 -49
- package/templates/scan.md +0 -156
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -75
- package/templates/verify.md +0 -236
- package/templates/workspace-sync.md +0 -99
- package/templates/workspace.md +0 -70
- /package/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
- /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
- /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
- /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
- /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
package/src/step.js
DELETED
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync, statSync, rmSync } from 'fs';
|
|
2
|
-
import { join, resolve } from 'path';
|
|
3
|
-
import { randomBytes } from 'crypto';
|
|
4
|
-
|
|
5
|
-
// ── 简易 YAML 工具 ──
|
|
6
|
-
|
|
7
|
-
function parseYaml(text) {
|
|
8
|
-
const result = {};
|
|
9
|
-
for (const line of text.split('\n')) {
|
|
10
|
-
const trimmed = line.trimStart();
|
|
11
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
12
|
-
const m = trimmed.match(/^(\w[\w-]*):\s*(.*)/);
|
|
13
|
-
if (m) {
|
|
14
|
-
const key = m[1];
|
|
15
|
-
const val = m[2].trim();
|
|
16
|
-
if (val === '' || val === '[]') result[key] = val === '[]' ? [] : '';
|
|
17
|
-
else if (val.startsWith('[')) result[key] = val.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
|
|
18
|
-
else if (/^\d+$/.test(val)) result[key] = parseInt(val, 10);
|
|
19
|
-
else result[key] = val;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function parseManifest(text) {
|
|
26
|
-
const lines = text.split('\n');
|
|
27
|
-
const result = { phase: '', description: '', requires: [], steps: [] };
|
|
28
|
-
let inSteps = false;
|
|
29
|
-
let currentStep = null;
|
|
30
|
-
|
|
31
|
-
for (const line of lines) {
|
|
32
|
-
const trimmed = line.trimStart();
|
|
33
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
34
|
-
|
|
35
|
-
if (inSteps && trimmed.startsWith('- file:')) {
|
|
36
|
-
if (currentStep) result.steps.push(currentStep);
|
|
37
|
-
currentStep = { file: '', name: '' };
|
|
38
|
-
currentStep.file = trimmed.match(/^- file:\s*(.+)/)?.[1]?.trim() || '';
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (inSteps && currentStep && trimmed.startsWith('name:')) {
|
|
42
|
-
currentStep.name = trimmed.match(/^name:\s*(.+)/)?.[1]?.trim() || '';
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (inSteps && !trimmed.startsWith('-') && !trimmed.startsWith(' ')) {
|
|
46
|
-
if (currentStep) result.steps.push(currentStep);
|
|
47
|
-
currentStep = null;
|
|
48
|
-
inSteps = false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (trimmed.startsWith('phase:')) result.phase = trimmed.match(/^phase:\s*(.+)/)?.[1]?.trim() || '';
|
|
52
|
-
else if (trimmed.startsWith('description:')) result.description = trimmed.match(/^description:\s*(.+)/)?.[1]?.trim() || '';
|
|
53
|
-
else if (trimmed.startsWith('requires:')) {
|
|
54
|
-
const m = trimmed.match(/^requires:\s*\[?(.*?)\]?$/);
|
|
55
|
-
result.requires = m ? m[1].split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
56
|
-
}
|
|
57
|
-
else if (trimmed.startsWith('steps:')) inSteps = true;
|
|
58
|
-
}
|
|
59
|
-
if (currentStep) result.steps.push(currentStep);
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function toYaml(obj) {
|
|
64
|
-
return Object.entries(obj).map(([k, v]) => {
|
|
65
|
-
if (Array.isArray(v)) return `${k}: []`;
|
|
66
|
-
return `${k}: ${v}`;
|
|
67
|
-
}).join('\n') + '\n';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ── 文件操作 ──
|
|
71
|
-
|
|
72
|
-
function atomicWrite(filePath, content) {
|
|
73
|
-
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
74
|
-
writeFileSync(tmpPath, content, 'utf8');
|
|
75
|
-
writeFileSync(filePath, content, 'utf8');
|
|
76
|
-
try { unlinkSync(tmpPath); } catch {}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function findSillyspecDir(dir) {
|
|
80
|
-
const p = join(dir, '.sillyspec');
|
|
81
|
-
if (existsSync(p)) return p;
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function getAvailablePhases(sillyDir) {
|
|
86
|
-
const stepsDir = join(sillyDir, 'steps');
|
|
87
|
-
if (!existsSync(stepsDir)) return [];
|
|
88
|
-
return readdirSync(stepsDir, { withFileTypes: true })
|
|
89
|
-
.filter(d => d.isDirectory() && existsSync(join(stepsDir, d.name, 'manifest.yaml')))
|
|
90
|
-
.map(d => d.name);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── 会话管理 ──
|
|
94
|
-
|
|
95
|
-
function getSessionId(dir, explicitId) {
|
|
96
|
-
if (explicitId) return explicitId;
|
|
97
|
-
if (process.env.SILLYSPEC_SESSION) return process.env.SILLYSPEC_SESSION;
|
|
98
|
-
const markerFile = join(dir, '.sillyspec', '.session');
|
|
99
|
-
if (existsSync(markerFile)) return readFileSync(markerFile, 'utf8').trim();
|
|
100
|
-
// 生成新 ID 并原子写入
|
|
101
|
-
const id = randomBytes(4).toString('hex');
|
|
102
|
-
const sillyDir = join(dir, '.sillyspec');
|
|
103
|
-
try {
|
|
104
|
-
writeFileSync(markerFile, id + '\n', { flag: 'wx' });
|
|
105
|
-
} catch {
|
|
106
|
-
// 已存在,读取
|
|
107
|
-
return readFileSync(markerFile, 'utf8').trim();
|
|
108
|
-
}
|
|
109
|
-
return id;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function getProgress(sillyDir, sessionId) {
|
|
113
|
-
const pFile = join(sillyDir, 'sessions', sessionId, 'progress.yaml');
|
|
114
|
-
if (!existsSync(pFile)) return {};
|
|
115
|
-
return parseYaml(readFileSync(pFile, 'utf8'));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function saveProgress(sillyDir, sessionId, progress) {
|
|
119
|
-
const pDir = join(sillyDir, 'sessions', sessionId);
|
|
120
|
-
mkdirSync(pDir, { recursive: true });
|
|
121
|
-
atomicWrite(join(pDir, 'progress.yaml'), toYaml(progress));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function updateLastActive(sillyDir, sessionId) {
|
|
125
|
-
const pDir = join(sillyDir, 'sessions', sessionId);
|
|
126
|
-
mkdirSync(pDir, { recursive: true });
|
|
127
|
-
atomicWrite(join(pDir, 'last-active'), Date.now().toString());
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function getLastActive(sillyDir, sessionId) {
|
|
131
|
-
const f = join(sillyDir, 'sessions', sessionId, 'last-active');
|
|
132
|
-
if (!existsSync(f)) return null;
|
|
133
|
-
try { return parseInt(readFileSync(f, 'utf8').trim(), 10); } catch { return null; }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ── 时间格式化 ──
|
|
137
|
-
|
|
138
|
-
function timeAgo(ts) {
|
|
139
|
-
if (!ts) return '未知';
|
|
140
|
-
const diff = Date.now() - ts;
|
|
141
|
-
const mins = Math.floor(diff / 60000);
|
|
142
|
-
if (mins < 60) return `${mins}m ago`;
|
|
143
|
-
const hours = Math.floor(mins / 60);
|
|
144
|
-
if (hours < 24) return `${hours}h ago`;
|
|
145
|
-
const days = Math.floor(hours / 24);
|
|
146
|
-
return `${days}d ago`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ── 核心逻辑 ──
|
|
150
|
-
|
|
151
|
-
function getManifest(sillyDir, phase) {
|
|
152
|
-
const mFile = join(sillyDir, 'steps', phase, 'manifest.yaml');
|
|
153
|
-
if (!existsSync(mFile)) return null;
|
|
154
|
-
return parseManifest(readFileSync(mFile, 'utf8'));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function checkPhaseComplete(sillyDir, phase, progress) {
|
|
158
|
-
const m = getManifest(sillyDir, phase);
|
|
159
|
-
if (!m) return false;
|
|
160
|
-
const p = parseInt(progress[phase], 10) || 0;
|
|
161
|
-
return p >= m.steps.length;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function formatNextOutput(manifest, stepNum, stepFile) {
|
|
165
|
-
return `---\nphase: ${manifest.phase}\nstep: ${stepNum}/${manifest.steps.length}\nname: ${manifest.steps[stepNum - 1].name}\n---\n\n${stepFile}\n\n---\n完成后请执行: sillyspec step ${manifest.phase} --next\n`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function handleNext(dir, sillyDir, phase, sessionId, json) {
|
|
169
|
-
const manifest = getManifest(sillyDir, phase);
|
|
170
|
-
if (!manifest) {
|
|
171
|
-
return { ok: false, error: `❌ 阶段 "${phase}" 不存在`, available: getAvailablePhases(sillyDir) };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 前置依赖提醒
|
|
175
|
-
for (const req of manifest.requires) {
|
|
176
|
-
const progress = getProgress(sillyDir, sessionId);
|
|
177
|
-
if (!checkPhaseComplete(sillyDir, req, progress)) {
|
|
178
|
-
console.log(`⚠️ 建议先完成 ${req} 阶段`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const progress = getProgress(sillyDir, sessionId);
|
|
183
|
-
let p = parseInt(progress[phase], 10);
|
|
184
|
-
if (isNaN(p) || p < 0) {
|
|
185
|
-
console.log(`⚠️ progress 值非法,已重置为 0`);
|
|
186
|
-
p = 0;
|
|
187
|
-
progress[phase] = 0;
|
|
188
|
-
saveProgress(sillyDir, sessionId, progress);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const total = manifest.steps.length;
|
|
192
|
-
|
|
193
|
-
if (p >= total) {
|
|
194
|
-
if (json) return { ok: true, done: true, phase, total };
|
|
195
|
-
console.log(`DONE ✅`);
|
|
196
|
-
return { ok: true, done: true, phase, total };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 确定要分发的步骤
|
|
200
|
-
let nextStep;
|
|
201
|
-
if (p === 0) {
|
|
202
|
-
nextStep = 1;
|
|
203
|
-
} else {
|
|
204
|
-
nextStep = p + 1;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (nextStep > total) {
|
|
208
|
-
if (json) return { ok: true, done: true, phase, total };
|
|
209
|
-
console.log(`DONE ✅`);
|
|
210
|
-
return { ok: true, done: true, phase, total };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 标记进度
|
|
214
|
-
progress[phase] = nextStep;
|
|
215
|
-
saveProgress(sillyDir, sessionId, progress);
|
|
216
|
-
updateLastActive(sillyDir, sessionId);
|
|
217
|
-
|
|
218
|
-
// 读取步骤文件
|
|
219
|
-
const stepFile = join(sillyDir, 'steps', phase, manifest.steps[nextStep - 1].file);
|
|
220
|
-
if (!existsSync(stepFile)) {
|
|
221
|
-
return { ok: false, error: `❌ 步骤文件不存在: ${manifest.steps[nextStep - 1].file}` };
|
|
222
|
-
}
|
|
223
|
-
const content = readFileSync(stepFile, 'utf8');
|
|
224
|
-
|
|
225
|
-
if (json) {
|
|
226
|
-
return { ok: true, done: false, phase, step: nextStep, total, name: manifest.steps[nextStep - 1].name, content };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
console.log(formatNextOutput(manifest, nextStep, content));
|
|
230
|
-
return { ok: true, done: false, phase, step: nextStep, total, name: manifest.steps[nextStep - 1].name };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function handleJump(dir, sillyDir, phase, target, sessionId, json) {
|
|
234
|
-
const manifest = getManifest(sillyDir, phase);
|
|
235
|
-
if (!manifest) {
|
|
236
|
-
return { ok: false, error: `❌ 阶段 "${phase}" 不存在`, available: getAvailablePhases(sillyDir) };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const total = manifest.steps.length;
|
|
240
|
-
if (target < 1 || target > total) {
|
|
241
|
-
return { ok: false, error: `❌ 步骤 ${target} 越界(有效范围: 1-${total})` };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const progress = getProgress(sillyDir, sessionId);
|
|
245
|
-
progress[phase] = target;
|
|
246
|
-
saveProgress(sillyDir, sessionId, progress);
|
|
247
|
-
updateLastActive(sillyDir, sessionId);
|
|
248
|
-
|
|
249
|
-
const stepFile = join(sillyDir, 'steps', phase, manifest.steps[target - 1].file);
|
|
250
|
-
if (!existsSync(stepFile)) {
|
|
251
|
-
return { ok: false, error: `❌ 步骤文件不存在: ${manifest.steps[target - 1].file}` };
|
|
252
|
-
}
|
|
253
|
-
const content = readFileSync(stepFile, 'utf8');
|
|
254
|
-
|
|
255
|
-
if (json) {
|
|
256
|
-
return { ok: true, phase, step: target, total, name: manifest.steps[target - 1].name, content };
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
console.log(formatNextOutput(manifest, target, content));
|
|
260
|
-
return { ok: true, phase, step: target, total, name: manifest.steps[target - 1].name };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function handleStatus(sillyDir, sessionId, json) {
|
|
264
|
-
const phases = getAvailablePhases(sillyDir);
|
|
265
|
-
const progress = getProgress(sillyDir, sessionId);
|
|
266
|
-
const results = [];
|
|
267
|
-
|
|
268
|
-
for (const phase of phases) {
|
|
269
|
-
const manifest = getManifest(sillyDir, phase);
|
|
270
|
-
const p = parseInt(progress[phase], 10) || 0;
|
|
271
|
-
const total = manifest ? manifest.steps.length : 0;
|
|
272
|
-
const name = manifest ? (manifest.steps[Math.max(0, p - 1)]?.name || '') : '';
|
|
273
|
-
results.push({ phase, progress: p, total, name });
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (json) return { ok: true, phases: results };
|
|
277
|
-
|
|
278
|
-
for (const r of results) {
|
|
279
|
-
if (r.total === 0) {
|
|
280
|
-
console.log(`${r.phase}: 未开始`);
|
|
281
|
-
} else if (r.progress === 0) {
|
|
282
|
-
console.log(`${r.phase}: 未开始`);
|
|
283
|
-
} else if (r.progress >= r.total) {
|
|
284
|
-
console.log(`${r.phase}: 已完成 ✅`);
|
|
285
|
-
} else {
|
|
286
|
-
console.log(`${r.phase}: 步骤 ${r.progress}/${r.total}(${r.name})`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return { ok: true, phases: results };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function handleList(sillyDir, phase, sessionId, json) {
|
|
293
|
-
const manifest = getManifest(sillyDir, phase);
|
|
294
|
-
if (!manifest) {
|
|
295
|
-
return { ok: false, error: `❌ 阶段 "${phase}" 不存在`, available: getAvailablePhases(sillyDir) };
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const progress = getProgress(sillyDir, sessionId);
|
|
299
|
-
const current = parseInt(progress[phase], 10) || 0;
|
|
300
|
-
const results = [];
|
|
301
|
-
|
|
302
|
-
if (!json) console.log(`# ${phase} — ${manifest.description}`);
|
|
303
|
-
|
|
304
|
-
for (let i = 0; i < manifest.steps.length; i++) {
|
|
305
|
-
const num = i + 1;
|
|
306
|
-
const s = manifest.steps[i];
|
|
307
|
-
let status = '';
|
|
308
|
-
if (num < current) status = '✅';
|
|
309
|
-
else if (num === current) status = '← 当前';
|
|
310
|
-
results.push({ step: num, name: s.name, status: num < current ? 'done' : num === current ? 'current' : 'pending' });
|
|
311
|
-
|
|
312
|
-
if (!json) {
|
|
313
|
-
console.log(` ${num}. ${s.name.padEnd(16)}${status}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return { ok: true, phase, description: manifest.description, steps: results };
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function handleSessions(sillyDir, json) {
|
|
320
|
-
const sessionsDir = join(sillyDir, 'sessions');
|
|
321
|
-
if (!existsSync(sessionsDir)) {
|
|
322
|
-
if (json) return { ok: true, sessions: [] };
|
|
323
|
-
console.log('暂无会话');
|
|
324
|
-
return { ok: true, sessions: [] };
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const dirs = readdirSync(sessionsDir, { withFileTypes: true })
|
|
328
|
-
.filter(d => d.isDirectory())
|
|
329
|
-
.map(d => d.name);
|
|
330
|
-
|
|
331
|
-
if (dirs.length === 0) {
|
|
332
|
-
if (json) return { ok: true, sessions: [] };
|
|
333
|
-
console.log('暂无会话');
|
|
334
|
-
return { ok: true, sessions: [] };
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const phases = getAvailablePhases(sillyDir);
|
|
338
|
-
const results = [];
|
|
339
|
-
|
|
340
|
-
for (const sid of dirs) {
|
|
341
|
-
const progress = getProgress(sillyDir, sid);
|
|
342
|
-
const last = getLastActive(sillyDir, sid);
|
|
343
|
-
const phaseSummaries = phases.map(ph => {
|
|
344
|
-
const manifest = getManifest(sillyDir, ph);
|
|
345
|
-
const p = parseInt(progress[ph], 10) || 0;
|
|
346
|
-
const total = manifest ? manifest.steps.length : 0;
|
|
347
|
-
return { phase: ph, progress: p, total };
|
|
348
|
-
});
|
|
349
|
-
const allDone = phaseSummaries.every(ps => ps.total > 0 && ps.progress >= ps.total);
|
|
350
|
-
results.push({ sessionId: sid, phases: phaseSummaries, lastActive: last, allDone });
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (json) return { ok: true, sessions: results };
|
|
354
|
-
|
|
355
|
-
for (const r of results) {
|
|
356
|
-
const phaseStr = r.phases.map(ps => `${ps.phase} ${ps.progress}/${ps.total}`).join(' ');
|
|
357
|
-
const done = r.allDone ? ' ✅' : '';
|
|
358
|
-
console.log(`${r.sessionId} ${phaseStr} (last: ${timeAgo(r.lastActive)})${done}`);
|
|
359
|
-
}
|
|
360
|
-
return { ok: true, sessions: results };
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function handleClean(sillyDir, options, json) {
|
|
364
|
-
const { allDone, olderThan, sessionId: targetSession } = options;
|
|
365
|
-
const sessionsDir = join(sillyDir, 'sessions');
|
|
366
|
-
if (!existsSync(sessionsDir)) {
|
|
367
|
-
if (json) return { ok: true, cleaned: [], dryRun: !targetSession && !allDone && !olderThan };
|
|
368
|
-
console.log('暂无会话');
|
|
369
|
-
return { ok: true, cleaned: [], dryRun: true };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const dirs = readdirSync(sessionsDir, { withFileTypes: true })
|
|
373
|
-
.filter(d => d.isDirectory())
|
|
374
|
-
.map(d => d.name);
|
|
375
|
-
|
|
376
|
-
const phases = getAvailablePhases(sillyDir);
|
|
377
|
-
const toClean = [];
|
|
378
|
-
|
|
379
|
-
for (const sid of dirs) {
|
|
380
|
-
const progress = getProgress(sillyDir, sid);
|
|
381
|
-
const last = getLastActive(sillyDir, sid);
|
|
382
|
-
const isAllDone = phases.every(ph => {
|
|
383
|
-
const manifest = getManifest(sillyDir, ph);
|
|
384
|
-
const p = parseInt(progress[ph], 10) || 0;
|
|
385
|
-
return manifest && p >= manifest.steps.length;
|
|
386
|
-
});
|
|
387
|
-
const ageMs = last ? Date.now() - last : Infinity;
|
|
388
|
-
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
389
|
-
|
|
390
|
-
let match = false;
|
|
391
|
-
if (targetSession) match = sid === targetSession;
|
|
392
|
-
else if (allDone) match = true;
|
|
393
|
-
else if (olderThan !== null) match = ageDays >= olderThan;
|
|
394
|
-
|
|
395
|
-
toClean.push({ sessionId: sid, allDone: isAllDone, lastActive: last, ageDays, match });
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// 无参数 = dry-run
|
|
399
|
-
const isDryRun = !targetSession && !allDone && olderThan === null;
|
|
400
|
-
|
|
401
|
-
if (isDryRun) {
|
|
402
|
-
if (json) return { ok: true, dryRun: true, sessions: toClean.map(s => ({ sessionId: s.sessionId, allDone: s.allDone, lastActive: s.lastActive, ageDays: Math.floor(s.ageDays) })) };
|
|
403
|
-
console.log('📋 会话列表(dry-run,未执行清理):');
|
|
404
|
-
console.log('');
|
|
405
|
-
for (const s of toClean) {
|
|
406
|
-
const status = s.allDone ? '✅ 全部完成' : `进行中(${Math.floor(s.ageDays)}天前活跃)`;
|
|
407
|
-
console.log(` ${s.sessionId} ${status} (last: ${timeAgo(s.lastActive)})`);
|
|
408
|
-
}
|
|
409
|
-
return { ok: true, dryRun: true, sessions: toClean };
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// 执行清理
|
|
413
|
-
const cleaned = toClean.filter(s => s.match);
|
|
414
|
-
if (cleaned.length === 0) {
|
|
415
|
-
if (json) return { ok: true, cleaned: [] };
|
|
416
|
-
console.log('没有匹配的会话需要清理');
|
|
417
|
-
return { ok: true, cleaned: [] };
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
for (const s of cleaned) {
|
|
421
|
-
rmSync(join(sessionsDir, s.sessionId), { recursive: true });
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (json) return { ok: true, cleaned: cleaned.map(s => s.sessionId) };
|
|
425
|
-
console.log(`🗑️ 已清理 ${cleaned.length} 个会话:`);
|
|
426
|
-
for (const s of cleaned) console.log(` - ${s.sessionId}`);
|
|
427
|
-
return { ok: true, cleaned: cleaned.map(s => s.sessionId) };
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// ── 主入口 ──
|
|
431
|
-
|
|
432
|
-
export async function cmdStep(dir, args = []) {
|
|
433
|
-
const sillyDir = findSillyspecDir(dir);
|
|
434
|
-
if (!sillyDir) {
|
|
435
|
-
console.log('❌ 未找到 .sillyspec/ 目录,请先执行 sillyspec init');
|
|
436
|
-
process.exit(1);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// 解析参数
|
|
440
|
-
let json = false;
|
|
441
|
-
let sessionId = null;
|
|
442
|
-
let action = null;
|
|
443
|
-
let phase = null;
|
|
444
|
-
let jumpTarget = null;
|
|
445
|
-
let cleanAllDone = false;
|
|
446
|
-
let cleanOlderThan = null;
|
|
447
|
-
let cleanSession = null;
|
|
448
|
-
|
|
449
|
-
for (let i = 0; i < args.length; i++) {
|
|
450
|
-
const a = args[i];
|
|
451
|
-
if (a === '--json') json = true;
|
|
452
|
-
else if (a === '--session' && args[i + 1]) { sessionId = args[++i]; }
|
|
453
|
-
else if (a === '--next') action = 'next';
|
|
454
|
-
else if (a === '--status') action = 'status';
|
|
455
|
-
else if (a === '--list') action = 'list';
|
|
456
|
-
else if (a === '--jump' && args[i + 1]) { action = 'jump'; jumpTarget = parseInt(args[++i], 10); }
|
|
457
|
-
else if (a === '--sessions') action = 'sessions';
|
|
458
|
-
else if (a === '--clean') {
|
|
459
|
-
action = 'clean';
|
|
460
|
-
// 检查后续选项
|
|
461
|
-
if (args[i + 1] === '--all-done') { cleanAllDone = true; i++; }
|
|
462
|
-
else if (args[i + 1] === '--session' && args[i + 2]) { cleanSession = args[i + 2]; i += 2; }
|
|
463
|
-
else if (args[i + 1] === '--older-than' && args[i + 2]) { cleanOlderThan = parseFloat(args[i + 2]); i += 2; }
|
|
464
|
-
}
|
|
465
|
-
else if (!a.startsWith('-')) {
|
|
466
|
-
if (!phase) phase = a;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const sid = getSessionId(dir, sessionId);
|
|
471
|
-
|
|
472
|
-
// 如果 phase 提供但步骤目录不存在
|
|
473
|
-
if (phase && action && action !== 'sessions' && action !== 'clean') {
|
|
474
|
-
const stepsDir = join(sillyDir, 'steps');
|
|
475
|
-
if (!existsSync(stepsDir) || !existsSync(join(stepsDir, phase, 'manifest.yaml'))) {
|
|
476
|
-
const available = getAvailablePhases(sillyDir);
|
|
477
|
-
if (json) {
|
|
478
|
-
console.log(JSON.stringify({ ok: false, error: `阶段 "${phase}" 不存在`, available }));
|
|
479
|
-
process.exit(1);
|
|
480
|
-
}
|
|
481
|
-
console.log(`❌ 阶段 "${phase}" 不存在`);
|
|
482
|
-
if (available.length > 0) {
|
|
483
|
-
console.log(`可用阶段: ${available.join(', ')}`);
|
|
484
|
-
} else {
|
|
485
|
-
console.log('暂无可用阶段(.sillyspec/steps/ 下无 manifest.yaml)');
|
|
486
|
-
}
|
|
487
|
-
process.exit(1);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
let result;
|
|
492
|
-
switch (action) {
|
|
493
|
-
case 'next':
|
|
494
|
-
result = handleNext(dir, sillyDir, phase, sid, json);
|
|
495
|
-
break;
|
|
496
|
-
case 'jump':
|
|
497
|
-
result = handleJump(dir, sillyDir, phase, jumpTarget, sid, json);
|
|
498
|
-
break;
|
|
499
|
-
case 'status':
|
|
500
|
-
result = handleStatus(sillyDir, sid, json);
|
|
501
|
-
break;
|
|
502
|
-
case 'list':
|
|
503
|
-
result = handleList(sillyDir, phase, sid, json);
|
|
504
|
-
break;
|
|
505
|
-
case 'sessions':
|
|
506
|
-
result = handleSessions(sillyDir, json);
|
|
507
|
-
break;
|
|
508
|
-
case 'clean':
|
|
509
|
-
result = handleClean(sillyDir, { allDone: cleanAllDone, olderThan: cleanOlderThan, sessionId: cleanSession }, json);
|
|
510
|
-
break;
|
|
511
|
-
default:
|
|
512
|
-
console.log('❌ 请指定操作: --next, --status, --list, --jump <n>, --sessions, --clean');
|
|
513
|
-
console.log('');
|
|
514
|
-
console.log('用法:');
|
|
515
|
-
console.log(' sillyspec step <phase> --next 获取步骤 + 自动推进');
|
|
516
|
-
console.log(' sillyspec step <phase> --status 查看进度');
|
|
517
|
-
console.log(' sillyspec step <phase> --list 查看步骤列表');
|
|
518
|
-
console.log(' sillyspec step <phase> --jump <n> 跳到指定步骤');
|
|
519
|
-
console.log(' sillyspec step --sessions 查看所有会话');
|
|
520
|
-
console.log(' sillyspec step --clean dry-run 列出可清理会话');
|
|
521
|
-
console.log(' sillyspec step --clean --all-done 清理所有阶段完成的会话');
|
|
522
|
-
console.log(' sillyspec step --clean --session <id> 清理指定会话');
|
|
523
|
-
console.log(' sillyspec step --clean --older-than <days> 清理 N 天前的会话');
|
|
524
|
-
console.log('');
|
|
525
|
-
console.log('选项:');
|
|
526
|
-
console.log(' --session <id> 指定会话 ID');
|
|
527
|
-
console.log(' --json 输出 JSON');
|
|
528
|
-
process.exit(1);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (result && !result.ok) {
|
|
532
|
-
if (json) {
|
|
533
|
-
console.log(JSON.stringify(result));
|
|
534
|
-
} else if (result.error) {
|
|
535
|
-
console.log(result.error);
|
|
536
|
-
}
|
|
537
|
-
process.exit(1);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
if (json && result) {
|
|
541
|
-
console.log(JSON.stringify(result));
|
|
542
|
-
}
|
|
543
|
-
}
|
package/templates/archive.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
## 交互规范
|
|
2
|
-
**当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。**
|
|
3
|
-
|
|
4
|
-
## 核心约束(必须遵守)
|
|
5
|
-
- ❌ 未经验证就归档(必须先确认验证通过)
|
|
6
|
-
- ❌ 未勾选的 checkbox 未告知用户就归档
|
|
7
|
-
- ❌ 归档后留下活跃变更的残留状态
|
|
8
|
-
- ❌ 覆盖已存在的归档目录
|
|
9
|
-
|
|
10
|
-
## 变更名称
|
|
11
|
-
$ARGUMENTS
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## 流程
|
|
16
|
-
|
|
17
|
-
### 1. 前置检查(门禁)
|
|
18
|
-
|
|
19
|
-
读取 `.sillyspec/changes/<change-name>/` 下所有必要文件,逐项检查:
|
|
20
|
-
|
|
21
|
-
- [ ] **文件完整性:** 检查 `design.md` 是否存在,缺失则警告
|
|
22
|
-
- [ ] **任务完成度:** 读取 `tasks.md`,统计已完成/未完成任务数。**有未完成的 → 用 AskUserQuestion 询问:**
|
|
23
|
-
- ① 继续归档(未完成任务将被标记完成)
|
|
24
|
-
- ② 取消,回去完成任务
|
|
25
|
-
|
|
26
|
-
> 任一门禁不通过且用户选择取消 → 终止流程。
|
|
27
|
-
|
|
28
|
-
### 2. 展示归档清单
|
|
29
|
-
|
|
30
|
-
展示即将归档的内容摘要:
|
|
31
|
-
- 变更目录名
|
|
32
|
-
- 包含的文件列表
|
|
33
|
-
- 任务完成统计(✅ 已完成 / ⬜ 未完成)
|
|
34
|
-
- 一句话总结本次变更
|
|
35
|
-
- quicklog 修改记录(如有 `.sillyspec/changes/<change-name>/quicklog/` 目录)
|
|
36
|
-
|
|
37
|
-
### 3. Spec 沉淀
|
|
38
|
-
|
|
39
|
-
将 `.sillyspec/changes/<change-name>/` 下的设计文档**复制到 `.sillyspec/knowledge/` 主目录**,确保已完成的设计规范可被后续变更参考。如目标已存在同名文件则跳过并提示。
|
|
40
|
-
|
|
41
|
-
### 4. 用户确认
|
|
42
|
-
|
|
43
|
-
用 AskUserQuestion 让用户确认:
|
|
44
|
-
- ① 确认归档
|
|
45
|
-
- ② 取消
|
|
46
|
-
|
|
47
|
-
### 4.5 生成归档摘要
|
|
48
|
-
|
|
49
|
-
在变更目录下自动生成 `SUMMARY.md`:
|
|
50
|
-
|
|
51
|
-
```markdown
|
|
52
|
-
# <变更名> 归档
|
|
53
|
-
|
|
54
|
-
- 创建:YYYY-MM-DD
|
|
55
|
-
- 完成:YYYY-MM-DD
|
|
56
|
-
- 涉及阶段:brainstorm → plan → execute → verify
|
|
57
|
-
|
|
58
|
-
## 关键决策
|
|
59
|
-
- (从 design.md 提取 3-5 条核心决策)
|
|
60
|
-
|
|
61
|
-
## 产出文件
|
|
62
|
-
- design.md — 设计文档
|
|
63
|
-
- tasks.md — 任务清单
|
|
64
|
-
- quicklog/ — 关联 quick 修改(N次)
|
|
65
|
-
- quick1: 描述
|
|
66
|
-
- quick2: 描述
|
|
67
|
-
|
|
68
|
-
## 代码变更统计
|
|
69
|
-
- 新增 X 文件,修改 Y 文件,删除 Z 文件
|
|
70
|
-
- 详见 CHANGELOG.md
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
在变更目录下自动生成 `CHANGELOG.md`:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
# 收集该变更相关的 git commit(按变更名过滤或按时间范围)
|
|
77
|
-
git log --oneline --no-merges -- .sillyspec/changes/<change-name>/ 2>/dev/null
|
|
78
|
-
# 以及变更目录创建后的所有 commit
|
|
79
|
-
git log --oneline --no-merges --since="<创建时间>" -- "*.ts" "*.js" "*.vue" "*.java" 2>/dev/null
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
写入 CHANGELOG.md,格式:
|
|
83
|
-
```markdown
|
|
84
|
-
# <变更名> 变更日志
|
|
85
|
-
|
|
86
|
-
## brainstorm 阶段
|
|
87
|
-
- (相关 commit)
|
|
88
|
-
|
|
89
|
-
## plan 阶段
|
|
90
|
-
- (相关 commit)
|
|
91
|
-
|
|
92
|
-
## execute 阶段
|
|
93
|
-
- (相关 commit)
|
|
94
|
-
|
|
95
|
-
## quick 修改
|
|
96
|
-
- (相关 commit)
|
|
97
|
-
|
|
98
|
-
## verify 阶段
|
|
99
|
-
- (相关 commit)
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### 5. 执行归档
|
|
103
|
-
|
|
104
|
-
- 目标路径:`.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/`
|
|
105
|
-
- **检查目标路径是否已存在**,存在则中止并报错,防止覆盖
|
|
106
|
-
- 移动变更目录到归档路径
|
|
107
|
-
|
|
108
|
-
### 6. 归档后更新
|
|
109
|
-
|
|
110
|
-
- **tasks.md:** 确保所有 checkbox 都已勾选 `[x]`
|
|
111
|
-
- **ROADMAP.md**(如存在):标记对应 Phase 已完成
|
|
112
|
-
- **Git 暂存:** `git add .sillyspec/`
|
|
113
|
-
|
|
114
|
-
**工作区模式下:** 如果变更属于某个子项目,cd 到子项目目录执行 git add。工作区根目录无 git 则跳过。
|
|
115
|
-
|
|
116
|
-
💡 归档产出已暂存。准备好后用 `/sillyspec:commit` 提交。
|
|
117
|
-
|
|
118
|
-
### 最后说:
|
|
119
|
-
|
|
120
|
-
> ✅ 变更 `<change-name>` 已归档到 `archive/YYYY-MM-DD-<change-name>/`。继续:`/sillyspec:brainstorm "新想法"`
|