scrum-skills 0.3.3 → 0.3.4
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/diagram-design/SKILL.md +11 -1
- package/.agents/skills/diagram-design/UPSTREAM.json +13 -1
- package/.agents/skills/diagram-design/references/primitives-core.md +4 -0
- package/README.md +224 -224
- package/assets/board/200.html +1 -1
- package/assets/board/404.html +1 -1
- package/assets/board/_nuxt/{BtzUbHO_.js → BGwhkDRl.js} +1 -1
- package/assets/board/_nuxt/{BkcaOYQ_.js → BU8vdZUs.js} +4 -4
- package/assets/board/_nuxt/{COecDWbb.js → BmrSh0rL.js} +1 -1
- package/assets/board/_nuxt/{CtFknnHZ.js → C-fx-vFO.js} +1 -1
- package/assets/board/_nuxt/{DZ--fXuU.js → Cajxc4Dw.js} +1 -1
- package/assets/board/_nuxt/{ChUHb39z.js → CoipdpDu.js} +1 -1
- package/assets/board/_nuxt/{1XlYaCp9.js → D1V1gysQ.js} +1 -1
- package/assets/board/_nuxt/D_ct-Stz.js +1 -1
- package/assets/board/_nuxt/{CReA4TEB.js → Dmg78zOY.js} +1 -1
- package/assets/board/_nuxt/{BizDeOK5.js → DwCE3305.js} +1 -1
- package/assets/board/_nuxt/{DjzLR9Bl.js → DwzGxoX2.js} +1 -1
- package/assets/board/_nuxt/{BbxmvdIF.js → Utb4TVOl.js} +1 -1
- package/assets/board/_nuxt/_id_.B6iqDPxe.css +1 -0
- package/assets/board/_nuxt/{oSJF4yHw.js → b7xtxFRl.js} +1 -1
- package/assets/board/_nuxt/builds/latest.json +1 -1
- package/assets/board/_nuxt/builds/meta/08bebe14-0f1b-4d2a-8de4-e84e122d10e9.json +1 -0
- package/assets/board/_nuxt/burndown.Do6IbM6o.css +1 -0
- package/assets/board/_nuxt/diagrams.pyhqNtmr.css +1 -0
- package/assets/board/_nuxt/{entry.BCuHiFgF.css → entry.BBv-sBnP.css} +1 -1
- package/assets/board/_nuxt/epics.Dmessplu.css +1 -0
- package/assets/board/_nuxt/error-404.CgJNdpB5.css +1 -0
- package/assets/board/_nuxt/error-500.WYSoH44y.css +1 -0
- package/assets/board/_nuxt/gantt.Dmp00GUo.css +1 -0
- package/assets/board/_nuxt/last-change.1Su4GrCN.css +1 -0
- package/assets/board/_nuxt/milestones.U0xKCsEo.css +1 -0
- package/assets/board/_nuxt/pages.Dbj2IVk8.css +1 -0
- package/assets/board/_nuxt/tests.dXlUX16A.css +1 -0
- package/assets/board/burndown/index.html +1 -1
- package/assets/board/diagrams/index.html +1 -1
- package/assets/board/epics/index.html +1 -1
- package/assets/board/favicon.svg +16 -16
- package/assets/board/gantt/index.html +1 -1
- package/assets/board/index.html +1 -1
- package/assets/board/last-change/index.html +1 -1
- package/assets/board/milestones/index.html +1 -1
- package/assets/board/tests/index.html +1 -1
- package/bin/scrum-skills.mjs +204 -204
- package/docs/ai-assistant-guide.md +1008 -1008
- package/package.json +42 -42
- package/templates/AGENTS.md +84 -84
- package/tools/agile-tools.mjs +510 -510
- package/tools/ai.mjs +464 -464
- package/tools/git-commit.mjs +216 -216
- package/tools/projects.mjs +240 -240
- package/tools/serve.mjs +180 -180
- package/assets/board/_nuxt/_id_.ItzoSaBL.css +0 -1
- package/assets/board/_nuxt/builds/meta/2218461a-f45b-41d6-ae44-b0d2332a9511.json +0 -1
- package/assets/board/_nuxt/burndown.CT8fx1Ge.css +0 -1
- package/assets/board/_nuxt/diagrams.D1ybqBem.css +0 -1
- package/assets/board/_nuxt/epics.CUeDIEa0.css +0 -1
- package/assets/board/_nuxt/error-404.Y9imKiDC.css +0 -1
- package/assets/board/_nuxt/error-500.DufSBGVC.css +0 -1
- package/assets/board/_nuxt/gantt.CiC2Ts1t.css +0 -1
- package/assets/board/_nuxt/last-change.C05Vc3BF.css +0 -1
- package/assets/board/_nuxt/milestones.Civ3sMiQ.css +0 -1
- package/assets/board/_nuxt/pages.BshWbOyo.css +0 -1
- package/assets/board/_nuxt/tests.Ci9fBCka.css +0 -1
package/bin/scrum-skills.mjs
CHANGED
|
@@ -1,204 +1,204 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* scrum-skills CLI(零依赖,node >= 18)
|
|
4
|
-
* 命令:
|
|
5
|
-
* scrum-skills init [dir] [--force] 初始化敏捷开发文件到目标文件夹(默认当前目录)
|
|
6
|
-
* 拷贝 .agents/skills/、AGENTS.md、tools/validate.mjs,建 agile/ 三文件骨架
|
|
7
|
-
* 已存在 AGENTS.md 时拒绝,--force 覆盖(备份为 AGENTS.md.bak)
|
|
8
|
-
* scrum-skills update [dir] [--dry-run] 更新已初始化项目里的 skill 文件(.agents/、AGENTS.md、tools/validate.mjs)
|
|
9
|
-
* 只刷新框架文件,绝不碰 agile/ 数据
|
|
10
|
-
* 除 AGENTS.md 外一律无条件整篇覆盖、不备份
|
|
11
|
-
* AGENTS.md 覆盖前备份为 <时间戳>.bak 并提示用 agent 合并
|
|
12
|
-
* 内容相同的不动;--dry-run 只报告不写入
|
|
13
|
-
* scrum-skills serve [dir] [port] 启动可视化 server(默认当前目录,端口 4173)
|
|
14
|
-
* scrum-skills validate [dir] 校验敏捷数据(默认当前目录)
|
|
15
|
-
*/
|
|
16
|
-
import { cpSync, mkdirSync, existsSync, writeFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
17
|
-
import { join, resolve, dirname, basename } from 'node:path';
|
|
18
|
-
import { spawnSync } from 'node:child_process';
|
|
19
|
-
import { fileURLToPath } from 'node:url';
|
|
20
|
-
|
|
21
|
-
const PKG_ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
22
|
-
const VERSION = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version;
|
|
23
|
-
const HELP = `用法:
|
|
24
|
-
scrum-skills init [dir] [--force] 初始化敏捷开发文件到目标文件夹(默认当前目录)
|
|
25
|
-
scrum-skills update [dir] [--dry-run] 更新已初始化项目的 skill 框架文件(不碰 agile/ 数据)
|
|
26
|
-
scrum-skills serve [dir] [port] 启动可视化 server(默认当前目录,端口 4173)
|
|
27
|
-
scrum-skills validate [dir] 校验敏捷数据(默认当前目录)
|
|
28
|
-
scrum-skills help 显示本帮助
|
|
29
|
-
scrum-skills -v / --version 显示版本号
|
|
30
|
-
|
|
31
|
-
scsk 是 scrum-skills 的等价简写,如: scsk serve`;
|
|
32
|
-
|
|
33
|
-
const [cmd, ...args] = process.argv.slice(2);
|
|
34
|
-
const fail = msg => { console.error(`✘ ${msg}`); process.exit(1); };
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 备份一个即将被覆盖的文件,返回备份路径。
|
|
38
|
-
* 名字带时间戳:老做法固定叫 `*.bak`,第二次 update 就把上一次的备份顶掉了,
|
|
39
|
-
* 而 AGENTS.md 几乎一定会被本地改过,备份互相覆盖等于没备份。
|
|
40
|
-
*/
|
|
41
|
-
function backupFile(file) {
|
|
42
|
-
const d = new Date();
|
|
43
|
-
const pad = n => String(n).padStart(2, '0');
|
|
44
|
-
const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
45
|
-
let target = `${file}.${stamp}.bak`;
|
|
46
|
-
for (let i = 2; existsSync(target); i += 1) target = `${file}.${stamp}-${i}.bak`;
|
|
47
|
-
cpSync(file, target);
|
|
48
|
-
return target;
|
|
49
|
-
}
|
|
50
|
-
function init() {
|
|
51
|
-
const force = args.includes('--force');
|
|
52
|
-
const dir = resolve(args.find(a => !a.startsWith('--')) || process.cwd());
|
|
53
|
-
if (!existsSync(dir)) fail(`目录不存在: ${dir}`);
|
|
54
|
-
|
|
55
|
-
const agentsMd = join(dir, 'AGENTS.md');
|
|
56
|
-
if (existsSync(agentsMd) && !force)
|
|
57
|
-
fail(`目标目录已有 AGENTS.md。确认覆盖请加 --force(旧文件会先备份成 AGENTS.md.<时间戳>.bak)`);
|
|
58
|
-
const backed = existsSync(agentsMd) ? backupFile(agentsMd) : null;
|
|
59
|
-
if (backed) console.log(`· 已把现有 AGENTS.md 备份为 ${basename(backed)}`);
|
|
60
|
-
|
|
61
|
-
cpSync(join(PKG_ROOT, '.agents'), join(dir, '.agents'), { recursive: true, force: true });
|
|
62
|
-
writeFileSync(agentsMd, readFileSync(join(PKG_ROOT, 'templates/AGENTS.md'), 'utf8'));
|
|
63
|
-
|
|
64
|
-
mkdirSync(join(dir, 'tools'), { recursive: true });
|
|
65
|
-
cpSync(join(PKG_ROOT, 'tools/validate.mjs'), join(dir, 'tools/validate.mjs'));
|
|
66
|
-
// 交付说明的写入工具:纪律要求「写之前先看 git」,工具得跟着装过去
|
|
67
|
-
cpSync(join(PKG_ROOT, 'tools/last-change.mjs'), join(dir, 'tools/last-change.mjs'));
|
|
68
|
-
// 架构图例:入库工具 + 出图质检(上游 self_check 的 Node 移植版,不带 Python)
|
|
69
|
-
cpSync(join(PKG_ROOT, 'tools/diagram.mjs'), join(dir, 'tools/diagram.mjs'));
|
|
70
|
-
cpSync(join(PKG_ROOT, 'tools/diagram-lint.mjs'), join(dir, 'tools/diagram-lint.mjs'));
|
|
71
|
-
// 老图导入摘要(顶替上游 drawio / mermaid / excalidraw 三个 Python 提取脚本)
|
|
72
|
-
cpSync(join(PKG_ROOT, 'tools/diagram-import.mjs'), join(dir, 'tools/diagram-import.mjs'));
|
|
73
|
-
|
|
74
|
-
// 数据骨架:schema v3(已有文件不覆盖,避免踩坏真实数据)
|
|
75
|
-
mkdirSync(join(dir, 'agile'), { recursive: true });
|
|
76
|
-
const skeleton = {
|
|
77
|
-
'backlog.json': { schemaVersion: 3, epics: [], stories: [] },
|
|
78
|
-
'sprints.json': { schemaVersion: 3, sprints: [], milestones: [] },
|
|
79
|
-
'journal.json': { schemaVersion: 3, journals: [] },
|
|
80
|
-
// 测试资产:页面空状态也认这个文件,先给空壳,agent 收口时往里加用例
|
|
81
|
-
'tests.json': { schemaVersion: 3, suites: [], runs: [] },
|
|
82
|
-
// 架构图例:索引与产物分开(产物在 agile/diagrams/,由 tools/diagram.mjs 登记)
|
|
83
|
-
'diagrams.json': { schemaVersion: 3, diagrams: [] },
|
|
84
|
-
};
|
|
85
|
-
const created = [];
|
|
86
|
-
for (const [name, body] of Object.entries(skeleton)) {
|
|
87
|
-
const p = join(dir, 'agile', name);
|
|
88
|
-
if (existsSync(p)) continue;
|
|
89
|
-
writeFileSync(p, JSON.stringify(body, null, 2) + '\n');
|
|
90
|
-
created.push(`agile/${name}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log(`✔ 已初始化到 ${dir}
|
|
94
|
-
- .agents/skills/ 四个敏捷 skill(planning / sprint / tdd / report)
|
|
95
|
-
+ 内置 diagram-design(画架构图/时序图/ER 图,无需另装、不依赖 Python)
|
|
96
|
-
- AGENTS.md agent 入口纪律文件${backed ? `(旧文件已备份为 ${basename(backed)})` : ''}
|
|
97
|
-
- tools/validate.mjs 数据校验脚本
|
|
98
|
-
- tools/diagram.mjs 架构图例入库(质检 → 落盘 → 建索引 → 校验回滚)
|
|
99
|
-
- tools/diagram-import.mjs 老图导入摘要(mermaid / drawio / excalidraw)
|
|
100
|
-
- agile/ 数据骨架${created.length ? `(新建 ${created.join('、')})` : '(已存在,未覆盖)'}
|
|
101
|
-
|
|
102
|
-
下一步:
|
|
103
|
-
1. 在目标项目里让 AI 读 AGENTS.md,用 scrum-planning 建第一个 story
|
|
104
|
-
2. 每次改动后运行: node tools/validate.mjs
|
|
105
|
-
3. 看板: 在目标项目目录下运行 scrum-skills serve,打开 http://localhost:4173`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function walkFiles(dir, base = '') {
|
|
109
|
-
const out = [];
|
|
110
|
-
for (const name of readdirSync(dir)) {
|
|
111
|
-
const abs = join(dir, name);
|
|
112
|
-
const rel = base ? `${base}/${name}` : name;
|
|
113
|
-
if (statSync(abs).isDirectory()) out.push(...walkFiles(abs, rel));
|
|
114
|
-
else out.push({ abs, rel });
|
|
115
|
-
}
|
|
116
|
-
return out;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** 收集要更新的框架文件:相对路径 → 源绝对路径 */
|
|
120
|
-
function frameworkFiles() {
|
|
121
|
-
const out = [];
|
|
122
|
-
for (const f of walkFiles(join(PKG_ROOT, '.agents')))
|
|
123
|
-
out.push({ rel: `.agents/${f.rel}`, src: f.abs });
|
|
124
|
-
out.push({ rel: 'AGENTS.md', src: join(PKG_ROOT, 'templates/AGENTS.md') });
|
|
125
|
-
out.push({ rel: 'tools/validate.mjs', src: join(PKG_ROOT, 'tools/validate.mjs') });
|
|
126
|
-
out.push({ rel: 'tools/last-change.mjs', src: join(PKG_ROOT, 'tools/last-change.mjs') });
|
|
127
|
-
out.push({ rel: 'tools/diagram.mjs', src: join(PKG_ROOT, 'tools/diagram.mjs') });
|
|
128
|
-
out.push({ rel: 'tools/diagram-lint.mjs', src: join(PKG_ROOT, 'tools/diagram-lint.mjs') });
|
|
129
|
-
out.push({ rel: 'tools/diagram-import.mjs', src: join(PKG_ROOT, 'tools/diagram-import.mjs') });
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function update() {
|
|
134
|
-
const dryRun = args.includes('--dry-run');
|
|
135
|
-
const dir = resolve(args.find(a => !a.startsWith('--')) || process.cwd());
|
|
136
|
-
if (!existsSync(dir)) fail(`目录不存在: ${dir}`);
|
|
137
|
-
if (!existsSync(join(dir, '.agents')) && !existsSync(join(dir, 'AGENTS.md')))
|
|
138
|
-
fail(`${dir} 未初始化(找不到 .agents/ 与 AGENTS.md)。请先运行: scrum-skills init`);
|
|
139
|
-
|
|
140
|
-
const added = [], changed = [], same = [];
|
|
141
|
-
// 只有 AGENTS.md 是「会被本地改」的文件:覆盖前留备份,其余框架文件一律直覆盖
|
|
142
|
-
let agentsBackup = null;
|
|
143
|
-
for (const { rel, src } of frameworkFiles()) {
|
|
144
|
-
const dest = join(dir, rel);
|
|
145
|
-
const srcText = readFileSync(src, 'utf8');
|
|
146
|
-
if (!existsSync(dest)) { added.push(rel); if (!dryRun) {
|
|
147
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
148
|
-
writeFileSync(dest, srcText);
|
|
149
|
-
} continue; }
|
|
150
|
-
if (readFileSync(dest, 'utf8') === srcText) { same.push(rel); continue; }
|
|
151
|
-
changed.push(rel);
|
|
152
|
-
if (dryRun) continue;
|
|
153
|
-
// 备份只给 AGENTS.md:项目特有纪律都写在里面,整篇覆盖会直接抹掉
|
|
154
|
-
if (rel === 'AGENTS.md') agentsBackup = basename(backupFile(dest));
|
|
155
|
-
writeFileSync(dest, srcText);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const verb = dryRun ? '(dry-run,未写入)' : '';
|
|
159
|
-
const agentsChanged = changed.includes('AGENTS.md');
|
|
160
|
-
const tail = agentsBackup
|
|
161
|
-
? `提示: 框架文件整篇覆盖,只有 AGENTS.md 留了备份。
|
|
162
|
-
你在 AGENTS.md 里的本地改动都在这份备份里,让 agent 助手(CodeBuddy / Claude Code 等)合并回来即可,直接说:
|
|
163
|
-
「对比 AGENTS.md 与 ${agentsBackup},把我的本地改动合并回来」
|
|
164
|
-
确认合并无误后再删掉备份。其余框架文件(.agents/、tools/)没有备份,也不需要合并。`
|
|
165
|
-
: dryRun && agentsChanged
|
|
166
|
-
? '提示: 正式执行时只有 AGENTS.md 会先备份再覆盖(→ AGENTS.md.<yyyyMMdd-HHmmss>.bak,可据它让 agent 合并本地改动),其余框架文件直接覆盖、不留备份。'
|
|
167
|
-
: changed.length
|
|
168
|
-
? '其余框架文件(.agents/、tools/)为整篇覆盖、不留备份,无需合并;AGENTS.md 已是最新。'
|
|
169
|
-
: '已是最新,无需操作。';
|
|
170
|
-
console.log(`✔ skill 框架文件更新完成${verb} — ${dir}
|
|
171
|
-
- 新增 ${added.length} 个${added.length ? `: ${added.join(', ')}` : ''}
|
|
172
|
-
- 更新 ${changed.length} 个${changed.length ? `: ${changed.join(', ')}` : ''}
|
|
173
|
-
- 未变 ${same.length} 个
|
|
174
|
-
- agile/ 数据目录未做任何改动
|
|
175
|
-
- 备份策略: 只有 AGENTS.md 覆盖前备份${agentsBackup ? `(→ ${agentsBackup})` : ''},其余文件直接覆盖
|
|
176
|
-
|
|
177
|
-
${tail}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function runTool(script, needPort) {
|
|
181
|
-
const positional = args.filter(a => !a.startsWith('--'));
|
|
182
|
-
const dir = resolve(positional[0] || process.cwd());
|
|
183
|
-
if (!existsSync(dir)) fail(`目录不存在: ${dir}`);
|
|
184
|
-
const port = needPort ? (positional[1] || '4173') : null;
|
|
185
|
-
const r = spawnSync(process.execPath,
|
|
186
|
-
[join(PKG_ROOT, 'tools', script), dir, ...(port ? [port] : [])],
|
|
187
|
-
{ stdio: 'inherit' });
|
|
188
|
-
process.exit(r.status ?? 1);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
switch (cmd) {
|
|
192
|
-
case 'init': init(); break;
|
|
193
|
-
case 'update': update(); break;
|
|
194
|
-
case 'serve': runTool('serve.mjs', true); break;
|
|
195
|
-
case 'validate': runTool('validate.mjs', false); break;
|
|
196
|
-
case 'help': case '-h': case '--help': case undefined:
|
|
197
|
-
console.log(HELP); break;
|
|
198
|
-
case '-v': case '--version': case 'version':
|
|
199
|
-
console.log(VERSION); break;
|
|
200
|
-
default:
|
|
201
|
-
console.error(`未知命令: ${cmd}\n`);
|
|
202
|
-
console.log(HELP);
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scrum-skills CLI(零依赖,node >= 18)
|
|
4
|
+
* 命令:
|
|
5
|
+
* scrum-skills init [dir] [--force] 初始化敏捷开发文件到目标文件夹(默认当前目录)
|
|
6
|
+
* 拷贝 .agents/skills/、AGENTS.md、tools/validate.mjs,建 agile/ 三文件骨架
|
|
7
|
+
* 已存在 AGENTS.md 时拒绝,--force 覆盖(备份为 AGENTS.md.bak)
|
|
8
|
+
* scrum-skills update [dir] [--dry-run] 更新已初始化项目里的 skill 文件(.agents/、AGENTS.md、tools/validate.mjs)
|
|
9
|
+
* 只刷新框架文件,绝不碰 agile/ 数据
|
|
10
|
+
* 除 AGENTS.md 外一律无条件整篇覆盖、不备份
|
|
11
|
+
* AGENTS.md 覆盖前备份为 <时间戳>.bak 并提示用 agent 合并
|
|
12
|
+
* 内容相同的不动;--dry-run 只报告不写入
|
|
13
|
+
* scrum-skills serve [dir] [port] 启动可视化 server(默认当前目录,端口 4173)
|
|
14
|
+
* scrum-skills validate [dir] 校验敏捷数据(默认当前目录)
|
|
15
|
+
*/
|
|
16
|
+
import { cpSync, mkdirSync, existsSync, writeFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
17
|
+
import { join, resolve, dirname, basename } from 'node:path';
|
|
18
|
+
import { spawnSync } from 'node:child_process';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const PKG_ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
22
|
+
const VERSION = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version;
|
|
23
|
+
const HELP = `用法:
|
|
24
|
+
scrum-skills init [dir] [--force] 初始化敏捷开发文件到目标文件夹(默认当前目录)
|
|
25
|
+
scrum-skills update [dir] [--dry-run] 更新已初始化项目的 skill 框架文件(不碰 agile/ 数据)
|
|
26
|
+
scrum-skills serve [dir] [port] 启动可视化 server(默认当前目录,端口 4173)
|
|
27
|
+
scrum-skills validate [dir] 校验敏捷数据(默认当前目录)
|
|
28
|
+
scrum-skills help 显示本帮助
|
|
29
|
+
scrum-skills -v / --version 显示版本号
|
|
30
|
+
|
|
31
|
+
scsk 是 scrum-skills 的等价简写,如: scsk serve`;
|
|
32
|
+
|
|
33
|
+
const [cmd, ...args] = process.argv.slice(2);
|
|
34
|
+
const fail = msg => { console.error(`✘ ${msg}`); process.exit(1); };
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 备份一个即将被覆盖的文件,返回备份路径。
|
|
38
|
+
* 名字带时间戳:老做法固定叫 `*.bak`,第二次 update 就把上一次的备份顶掉了,
|
|
39
|
+
* 而 AGENTS.md 几乎一定会被本地改过,备份互相覆盖等于没备份。
|
|
40
|
+
*/
|
|
41
|
+
function backupFile(file) {
|
|
42
|
+
const d = new Date();
|
|
43
|
+
const pad = n => String(n).padStart(2, '0');
|
|
44
|
+
const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
45
|
+
let target = `${file}.${stamp}.bak`;
|
|
46
|
+
for (let i = 2; existsSync(target); i += 1) target = `${file}.${stamp}-${i}.bak`;
|
|
47
|
+
cpSync(file, target);
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
function init() {
|
|
51
|
+
const force = args.includes('--force');
|
|
52
|
+
const dir = resolve(args.find(a => !a.startsWith('--')) || process.cwd());
|
|
53
|
+
if (!existsSync(dir)) fail(`目录不存在: ${dir}`);
|
|
54
|
+
|
|
55
|
+
const agentsMd = join(dir, 'AGENTS.md');
|
|
56
|
+
if (existsSync(agentsMd) && !force)
|
|
57
|
+
fail(`目标目录已有 AGENTS.md。确认覆盖请加 --force(旧文件会先备份成 AGENTS.md.<时间戳>.bak)`);
|
|
58
|
+
const backed = existsSync(agentsMd) ? backupFile(agentsMd) : null;
|
|
59
|
+
if (backed) console.log(`· 已把现有 AGENTS.md 备份为 ${basename(backed)}`);
|
|
60
|
+
|
|
61
|
+
cpSync(join(PKG_ROOT, '.agents'), join(dir, '.agents'), { recursive: true, force: true });
|
|
62
|
+
writeFileSync(agentsMd, readFileSync(join(PKG_ROOT, 'templates/AGENTS.md'), 'utf8'));
|
|
63
|
+
|
|
64
|
+
mkdirSync(join(dir, 'tools'), { recursive: true });
|
|
65
|
+
cpSync(join(PKG_ROOT, 'tools/validate.mjs'), join(dir, 'tools/validate.mjs'));
|
|
66
|
+
// 交付说明的写入工具:纪律要求「写之前先看 git」,工具得跟着装过去
|
|
67
|
+
cpSync(join(PKG_ROOT, 'tools/last-change.mjs'), join(dir, 'tools/last-change.mjs'));
|
|
68
|
+
// 架构图例:入库工具 + 出图质检(上游 self_check 的 Node 移植版,不带 Python)
|
|
69
|
+
cpSync(join(PKG_ROOT, 'tools/diagram.mjs'), join(dir, 'tools/diagram.mjs'));
|
|
70
|
+
cpSync(join(PKG_ROOT, 'tools/diagram-lint.mjs'), join(dir, 'tools/diagram-lint.mjs'));
|
|
71
|
+
// 老图导入摘要(顶替上游 drawio / mermaid / excalidraw 三个 Python 提取脚本)
|
|
72
|
+
cpSync(join(PKG_ROOT, 'tools/diagram-import.mjs'), join(dir, 'tools/diagram-import.mjs'));
|
|
73
|
+
|
|
74
|
+
// 数据骨架:schema v3(已有文件不覆盖,避免踩坏真实数据)
|
|
75
|
+
mkdirSync(join(dir, 'agile'), { recursive: true });
|
|
76
|
+
const skeleton = {
|
|
77
|
+
'backlog.json': { schemaVersion: 3, epics: [], stories: [] },
|
|
78
|
+
'sprints.json': { schemaVersion: 3, sprints: [], milestones: [] },
|
|
79
|
+
'journal.json': { schemaVersion: 3, journals: [] },
|
|
80
|
+
// 测试资产:页面空状态也认这个文件,先给空壳,agent 收口时往里加用例
|
|
81
|
+
'tests.json': { schemaVersion: 3, suites: [], runs: [] },
|
|
82
|
+
// 架构图例:索引与产物分开(产物在 agile/diagrams/,由 tools/diagram.mjs 登记)
|
|
83
|
+
'diagrams.json': { schemaVersion: 3, diagrams: [] },
|
|
84
|
+
};
|
|
85
|
+
const created = [];
|
|
86
|
+
for (const [name, body] of Object.entries(skeleton)) {
|
|
87
|
+
const p = join(dir, 'agile', name);
|
|
88
|
+
if (existsSync(p)) continue;
|
|
89
|
+
writeFileSync(p, JSON.stringify(body, null, 2) + '\n');
|
|
90
|
+
created.push(`agile/${name}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(`✔ 已初始化到 ${dir}
|
|
94
|
+
- .agents/skills/ 四个敏捷 skill(planning / sprint / tdd / report)
|
|
95
|
+
+ 内置 diagram-design(画架构图/时序图/ER 图,无需另装、不依赖 Python)
|
|
96
|
+
- AGENTS.md agent 入口纪律文件${backed ? `(旧文件已备份为 ${basename(backed)})` : ''}
|
|
97
|
+
- tools/validate.mjs 数据校验脚本
|
|
98
|
+
- tools/diagram.mjs 架构图例入库(质检 → 落盘 → 建索引 → 校验回滚)
|
|
99
|
+
- tools/diagram-import.mjs 老图导入摘要(mermaid / drawio / excalidraw)
|
|
100
|
+
- agile/ 数据骨架${created.length ? `(新建 ${created.join('、')})` : '(已存在,未覆盖)'}
|
|
101
|
+
|
|
102
|
+
下一步:
|
|
103
|
+
1. 在目标项目里让 AI 读 AGENTS.md,用 scrum-planning 建第一个 story
|
|
104
|
+
2. 每次改动后运行: node tools/validate.mjs
|
|
105
|
+
3. 看板: 在目标项目目录下运行 scrum-skills serve,打开 http://localhost:4173`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function walkFiles(dir, base = '') {
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const name of readdirSync(dir)) {
|
|
111
|
+
const abs = join(dir, name);
|
|
112
|
+
const rel = base ? `${base}/${name}` : name;
|
|
113
|
+
if (statSync(abs).isDirectory()) out.push(...walkFiles(abs, rel));
|
|
114
|
+
else out.push({ abs, rel });
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 收集要更新的框架文件:相对路径 → 源绝对路径 */
|
|
120
|
+
function frameworkFiles() {
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const f of walkFiles(join(PKG_ROOT, '.agents')))
|
|
123
|
+
out.push({ rel: `.agents/${f.rel}`, src: f.abs });
|
|
124
|
+
out.push({ rel: 'AGENTS.md', src: join(PKG_ROOT, 'templates/AGENTS.md') });
|
|
125
|
+
out.push({ rel: 'tools/validate.mjs', src: join(PKG_ROOT, 'tools/validate.mjs') });
|
|
126
|
+
out.push({ rel: 'tools/last-change.mjs', src: join(PKG_ROOT, 'tools/last-change.mjs') });
|
|
127
|
+
out.push({ rel: 'tools/diagram.mjs', src: join(PKG_ROOT, 'tools/diagram.mjs') });
|
|
128
|
+
out.push({ rel: 'tools/diagram-lint.mjs', src: join(PKG_ROOT, 'tools/diagram-lint.mjs') });
|
|
129
|
+
out.push({ rel: 'tools/diagram-import.mjs', src: join(PKG_ROOT, 'tools/diagram-import.mjs') });
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function update() {
|
|
134
|
+
const dryRun = args.includes('--dry-run');
|
|
135
|
+
const dir = resolve(args.find(a => !a.startsWith('--')) || process.cwd());
|
|
136
|
+
if (!existsSync(dir)) fail(`目录不存在: ${dir}`);
|
|
137
|
+
if (!existsSync(join(dir, '.agents')) && !existsSync(join(dir, 'AGENTS.md')))
|
|
138
|
+
fail(`${dir} 未初始化(找不到 .agents/ 与 AGENTS.md)。请先运行: scrum-skills init`);
|
|
139
|
+
|
|
140
|
+
const added = [], changed = [], same = [];
|
|
141
|
+
// 只有 AGENTS.md 是「会被本地改」的文件:覆盖前留备份,其余框架文件一律直覆盖
|
|
142
|
+
let agentsBackup = null;
|
|
143
|
+
for (const { rel, src } of frameworkFiles()) {
|
|
144
|
+
const dest = join(dir, rel);
|
|
145
|
+
const srcText = readFileSync(src, 'utf8');
|
|
146
|
+
if (!existsSync(dest)) { added.push(rel); if (!dryRun) {
|
|
147
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
148
|
+
writeFileSync(dest, srcText);
|
|
149
|
+
} continue; }
|
|
150
|
+
if (readFileSync(dest, 'utf8') === srcText) { same.push(rel); continue; }
|
|
151
|
+
changed.push(rel);
|
|
152
|
+
if (dryRun) continue;
|
|
153
|
+
// 备份只给 AGENTS.md:项目特有纪律都写在里面,整篇覆盖会直接抹掉
|
|
154
|
+
if (rel === 'AGENTS.md') agentsBackup = basename(backupFile(dest));
|
|
155
|
+
writeFileSync(dest, srcText);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const verb = dryRun ? '(dry-run,未写入)' : '';
|
|
159
|
+
const agentsChanged = changed.includes('AGENTS.md');
|
|
160
|
+
const tail = agentsBackup
|
|
161
|
+
? `提示: 框架文件整篇覆盖,只有 AGENTS.md 留了备份。
|
|
162
|
+
你在 AGENTS.md 里的本地改动都在这份备份里,让 agent 助手(CodeBuddy / Claude Code 等)合并回来即可,直接说:
|
|
163
|
+
「对比 AGENTS.md 与 ${agentsBackup},把我的本地改动合并回来」
|
|
164
|
+
确认合并无误后再删掉备份。其余框架文件(.agents/、tools/)没有备份,也不需要合并。`
|
|
165
|
+
: dryRun && agentsChanged
|
|
166
|
+
? '提示: 正式执行时只有 AGENTS.md 会先备份再覆盖(→ AGENTS.md.<yyyyMMdd-HHmmss>.bak,可据它让 agent 合并本地改动),其余框架文件直接覆盖、不留备份。'
|
|
167
|
+
: changed.length
|
|
168
|
+
? '其余框架文件(.agents/、tools/)为整篇覆盖、不留备份,无需合并;AGENTS.md 已是最新。'
|
|
169
|
+
: '已是最新,无需操作。';
|
|
170
|
+
console.log(`✔ skill 框架文件更新完成${verb} — ${dir}
|
|
171
|
+
- 新增 ${added.length} 个${added.length ? `: ${added.join(', ')}` : ''}
|
|
172
|
+
- 更新 ${changed.length} 个${changed.length ? `: ${changed.join(', ')}` : ''}
|
|
173
|
+
- 未变 ${same.length} 个
|
|
174
|
+
- agile/ 数据目录未做任何改动
|
|
175
|
+
- 备份策略: 只有 AGENTS.md 覆盖前备份${agentsBackup ? `(→ ${agentsBackup})` : ''},其余文件直接覆盖
|
|
176
|
+
|
|
177
|
+
${tail}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function runTool(script, needPort) {
|
|
181
|
+
const positional = args.filter(a => !a.startsWith('--'));
|
|
182
|
+
const dir = resolve(positional[0] || process.cwd());
|
|
183
|
+
if (!existsSync(dir)) fail(`目录不存在: ${dir}`);
|
|
184
|
+
const port = needPort ? (positional[1] || '4173') : null;
|
|
185
|
+
const r = spawnSync(process.execPath,
|
|
186
|
+
[join(PKG_ROOT, 'tools', script), dir, ...(port ? [port] : [])],
|
|
187
|
+
{ stdio: 'inherit' });
|
|
188
|
+
process.exit(r.status ?? 1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
switch (cmd) {
|
|
192
|
+
case 'init': init(); break;
|
|
193
|
+
case 'update': update(); break;
|
|
194
|
+
case 'serve': runTool('serve.mjs', true); break;
|
|
195
|
+
case 'validate': runTool('validate.mjs', false); break;
|
|
196
|
+
case 'help': case '-h': case '--help': case undefined:
|
|
197
|
+
console.log(HELP); break;
|
|
198
|
+
case '-v': case '--version': case 'version':
|
|
199
|
+
console.log(VERSION); break;
|
|
200
|
+
default:
|
|
201
|
+
console.error(`未知命令: ${cmd}\n`);
|
|
202
|
+
console.log(HELP);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|