@wnlen/agent-execution-template 0.8.14

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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +440 -0
  3. package/README.zh-CN.md +447 -0
  4. package/bin/agent-execution-template.js +792 -0
  5. package/docs/SPEC.md +1039 -0
  6. package/docs/token-efficient-protocol-v0.1.md +101 -0
  7. package/package.json +35 -0
  8. package/template/en/ai/README.md +130 -0
  9. package/template/en/ai/project/archive/.gitkeep +0 -0
  10. package/template/en/ai/project/inbox/.gitkeep +1 -0
  11. package/template/en/ai/project/inbox/ideas/.gitkeep +1 -0
  12. package/template/en/ai/project/inbox/processed/.gitkeep +0 -0
  13. package/template/en/ai/project/inbox/raw/.gitkeep +1 -0
  14. package/template/en/ai/project/metrics.json +20 -0
  15. package/template/en/ai/project/project.md +42 -0
  16. package/template/en/ai/project/proposals/final-shape-updates/.gitkeep +1 -0
  17. package/template/en/ai/project/proposals/final-shape-updates/_template.md +64 -0
  18. package/template/en/ai/project/refs/architecture.md +23 -0
  19. package/template/en/ai/project/refs/commands.md +43 -0
  20. package/template/en/ai/project/refs/constraints.md +24 -0
  21. package/template/en/ai/project/refs/decisions.md +13 -0
  22. package/template/en/ai/project/refs/final-shape.md +59 -0
  23. package/template/en/ai/project/refs/module-map.md +31 -0
  24. package/template/en/ai/project/refs/roadmap.md +31 -0
  25. package/template/en/ai/project/result.json +34 -0
  26. package/template/en/ai/project/result.md +32 -0
  27. package/template/en/ai/project/runtime.md +74 -0
  28. package/template/en/ai/project/task.md +110 -0
  29. package/template/en/ai/template/LANG +1 -0
  30. package/template/en/ai/template/VERSION +1 -0
  31. package/template/en/ai/template/bootstrap.md +194 -0
  32. package/template/en/ai/template/prompt.md +118 -0
  33. package/template/en/ai/template/protocol.md +332 -0
  34. package/template/en/ai/template/reconcile.md +140 -0
  35. package/template/en/ai/template/rules/core.md +197 -0
  36. package/template/en/ai/template/rules/output.md +51 -0
  37. package/template/en/ai/template/schemas/metrics.schema.json +119 -0
  38. package/template/en/ai/template/schemas/result.schema.json +234 -0
  39. package/template/zh/ai/README.md +129 -0
  40. package/template/zh/ai/project/archive/.gitkeep +0 -0
  41. package/template/zh/ai/project/inbox/.gitkeep +1 -0
  42. package/template/zh/ai/project/inbox/ideas/.gitkeep +1 -0
  43. package/template/zh/ai/project/inbox/processed/.gitkeep +0 -0
  44. package/template/zh/ai/project/inbox/raw/.gitkeep +1 -0
  45. package/template/zh/ai/project/metrics.json +20 -0
  46. package/template/zh/ai/project/project.md +42 -0
  47. package/template/zh/ai/project/proposals/final-shape-updates/.gitkeep +1 -0
  48. package/template/zh/ai/project/proposals/final-shape-updates/_template.md +64 -0
  49. package/template/zh/ai/project/refs/architecture.md +23 -0
  50. package/template/zh/ai/project/refs/commands.md +43 -0
  51. package/template/zh/ai/project/refs/constraints.md +24 -0
  52. package/template/zh/ai/project/refs/decisions.md +13 -0
  53. package/template/zh/ai/project/refs/final-shape.md +54 -0
  54. package/template/zh/ai/project/refs/module-map.md +30 -0
  55. package/template/zh/ai/project/refs/roadmap.md +29 -0
  56. package/template/zh/ai/project/result.json +34 -0
  57. package/template/zh/ai/project/result.md +32 -0
  58. package/template/zh/ai/project/runtime.md +74 -0
  59. package/template/zh/ai/project/task.md +106 -0
  60. package/template/zh/ai/template/LANG +1 -0
  61. package/template/zh/ai/template/VERSION +1 -0
  62. package/template/zh/ai/template/bootstrap.md +180 -0
  63. package/template/zh/ai/template/prompt.md +104 -0
  64. package/template/zh/ai/template/protocol.md +300 -0
  65. package/template/zh/ai/template/reconcile.md +134 -0
  66. package/template/zh/ai/template/rules/core.md +174 -0
  67. package/template/zh/ai/template/rules/output.md +51 -0
  68. package/template/zh/ai/template/schemas/metrics.schema.json +119 -0
  69. package/template/zh/ai/template/schemas/result.schema.json +234 -0
  70. package/test/selftest.js +280 -0
@@ -0,0 +1,792 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const PACKAGE_ROOT = path.resolve(__dirname, "..");
7
+ const TEMPLATE_ROOT = path.join(PACKAGE_ROOT, "template");
8
+ const TARGET_AI = path.join(process.cwd(), "ai");
9
+ const DEFAULT_LANG = "zh";
10
+ const SUPPORTED_LANGS = new Set(["zh", "en"]);
11
+
12
+ const REQUIRED_FILES = [
13
+ "ai/template/LANG",
14
+ "ai/template/VERSION",
15
+ "ai/template/bootstrap.md",
16
+ "ai/template/prompt.md",
17
+ "ai/template/reconcile.md",
18
+ "ai/template/protocol.md",
19
+ "ai/template/rules/core.md",
20
+ "ai/template/rules/output.md",
21
+ "ai/project/inbox/.gitkeep",
22
+ "ai/project/project.md",
23
+ "ai/project/runtime.md",
24
+ "ai/project/task.md",
25
+ "ai/project/result.json",
26
+ "ai/project/result.md",
27
+ "ai/project/metrics.json"
28
+ ];
29
+
30
+ const RECOMMENDED_FILES = [
31
+ "ai/project/inbox/ideas/.gitkeep",
32
+ "ai/project/inbox/processed/.gitkeep",
33
+ "ai/project/inbox/raw/.gitkeep",
34
+ "ai/project/proposals/final-shape-updates/.gitkeep",
35
+ "ai/project/proposals/final-shape-updates/_template.md",
36
+ "ai/project/refs/final-shape.md",
37
+ "ai/project/refs/module-map.md",
38
+ "ai/project/refs/roadmap.md"
39
+ ];
40
+
41
+ const JSON_HEALTH_FILES = [
42
+ "ai/project/result.json",
43
+ "ai/project/metrics.json"
44
+ ];
45
+
46
+ const TASK_HEALTH_PATTERNS = [
47
+ /^task_id:\s*/m,
48
+ /^type:\s*/m,
49
+ /^priority:\s*/m,
50
+ /^risk_level:\s*/m,
51
+ /^model_policy:/m,
52
+ /^refs:/m,
53
+ /^permission:/m
54
+ ];
55
+
56
+ const TEXT = {
57
+ zh: {
58
+ usage: `Agent Execution Template
59
+
60
+ 用法:
61
+ agent-execution-template init [--lang zh|en] [--verbose]
62
+ agent-execution-template next [--lang zh|en]
63
+ agent-execution-template refresh [--lang zh|en]
64
+ agent-execution-template improve-context [--lang zh|en]
65
+ agent-execution-template update [--lang zh|en]
66
+ agent-execution-template reconcile [--lang zh|en]
67
+ agent-execution-template strategy [--lang zh|en]
68
+ agent-execution-template doctor
69
+ `,
70
+ unknown: "未知",
71
+ sourceMissing: "找不到模板来源",
72
+ ready: "Agent Execution Template 已就绪。",
73
+ initGuide: `[初始化]
74
+ 1. 直接初始化
75
+ 对 AI 说: 开始初始化这个项目
76
+ 2. 带资料初始化
77
+ 先放到: ai/project/inbox/
78
+ 对 AI 说: 开始初始化这个项目,并吸收 ai/project/inbox/ 里的资料
79
+
80
+ [后续]
81
+ 1. 继续推进
82
+ 对 AI 说: 继续推进这个项目
83
+ 2. 吸收新资料
84
+ 先放到: ai/project/inbox/
85
+ 对 AI 说: 整合 ai/project/inbox/ 里的新资料
86
+ 3. 优化上下文
87
+ 运行命令: npx -y @wnlen/agent-execution-template refresh
88
+ 4. 评估方向
89
+ 先放到: ai/project/inbox/ideas/
90
+ 对 AI 说: 把 ai/project/inbox/ideas/ 里的新灵感生成方向修订提案
91
+ 5. 查看下一步
92
+ 运行命令: npx -y @wnlen/agent-execution-template next
93
+
94
+ [区分标准]
95
+ 资料 = 已确定的事实、文档、流程、接口、业务规则
96
+ 方向 = 还没决定的新想法、产品策略、架构调整、路线变化`,
97
+ start: "开始:",
98
+ startPrompt: "开始初始化这个项目",
99
+ then: "然后:",
100
+ reviewProject: "按 AI 输出确认或修正项目上下文",
101
+ giveTask: "需要执行任务时,说:继续推进这个项目",
102
+ confirmTask: "需要吸收新资料时,先放入 ai/project/inbox/,然后说:",
103
+ executePrompt: "整合 ai/project/inbox/ 里的新资料",
104
+ strategyHint: "需要修订方向时,先放入 ai/project/inbox/ideas/,然后生成 strategy_update 提案。",
105
+ files: "文件",
106
+ check: "检查",
107
+ details: "详情:",
108
+ refreshTitle: "Agent Execution Template 项目上下文重整",
109
+ improveContextTitle: "Agent Execution Template 上下文总结优化",
110
+ refreshBackedUp: "已备份旧项目上下文",
111
+ refreshImported: "已将旧项目上下文放入",
112
+ refreshReady: "新的 ai/project/** 已生成。",
113
+ refreshPrompt: "整合 ai/project/inbox/ 里的新资料,基于旧上下文重新生成更精良的 ai/project/",
114
+ refreshNoProject: "未发现旧 ai/project/**,已执行普通初始化。",
115
+ updateTitle: "Agent Execution Template 更新",
116
+ updated: "已将 ai/template/** 更新到",
117
+ projectNotModified: "ai/project/** 未修改。",
118
+ doctorTitle: "Agent Execution Template 检查",
119
+ templateVersion: "模板版本",
120
+ templateLang: "模板语言",
121
+ missing: "缺失",
122
+ warn: "警告",
123
+ pass: "通过",
124
+ fail: "失败",
125
+ empty: "为空",
126
+ invalidJson: "JSON 无效",
127
+ taskFrontMatterIncomplete: "任务 front matter 缺少关键字段",
128
+ versionMismatch: "模板版本与包版本不一致",
129
+ runInit: "请运行 npx -y @wnlen/agent-execution-template init",
130
+ readyWithWarnings: "已就绪,但存在警告",
131
+ readyToRun: "已就绪",
132
+ invalidLang: "不支持的语言,请使用 zh 或 en",
133
+ reconcileTitle: "Agent Execution Template 上下文整合",
134
+ reconcilePut: "把新的业务、产品、架构或流程资料放到:",
135
+ reconcileAsk: "然后对 AI 说:",
136
+ reconcilePrompt: "整合 ai/project/inbox/ 里的新资料",
137
+ strategyTitle: "Agent Execution Template 方向修订",
138
+ strategyPut: "把新的产品、业务、架构或方向灵感放到:",
139
+ strategyAsk: "然后对 AI 说:",
140
+ strategyPrompt: "把 ai/project/inbox/ideas/ 里的新灵感生成方向修订提案",
141
+ strategyReview: "人类确认提案后,再说:",
142
+ strategyApplyPrompt: "确认,合并这个提案",
143
+ nextTitle: "Agent Execution Template 下一步",
144
+ nextRunInit: "当前项目还没有安装模板。先运行:",
145
+ nextTellAgent: "把这句话发给你的 AI coding 工具:",
146
+ nextRunCommand: "运行这个命令:",
147
+ nextReviewProposal: "已有方向修订提案。先审查提案;确认后对 AI 说:",
148
+ repairHint: "缺失的 project 推荐文件可通过重新运行 init 安全补齐;已有 ai/project/** 不会被覆盖。",
149
+ permissionDenied: "无法写入目标路径",
150
+ permissionHint: `请检查 ai/** 的归属和权限。常见修复:
151
+ sudo chown -R "$(id -un):$(id -gn)" ai
152
+ find ai -type d -exec chmod u+rwx {} +
153
+ find ai -type f -exec chmod u+rw {} +`,
154
+ changeLabels: {
155
+ created: "已创建",
156
+ updated: "已更新",
157
+ kept: "已保留"
158
+ },
159
+ changeUnit: (label, count) => `${label} ${count} 个`
160
+ },
161
+ en: {
162
+ usage: `Agent Execution Template
163
+
164
+ Usage:
165
+ agent-execution-template init [--lang zh|en] [--verbose]
166
+ agent-execution-template next [--lang zh|en]
167
+ agent-execution-template refresh [--lang zh|en]
168
+ agent-execution-template improve-context [--lang zh|en]
169
+ agent-execution-template update [--lang zh|en]
170
+ agent-execution-template reconcile [--lang zh|en]
171
+ agent-execution-template strategy [--lang zh|en]
172
+ agent-execution-template doctor
173
+ `,
174
+ unknown: "unknown",
175
+ sourceMissing: "Template source not found",
176
+ ready: "Agent Execution Template ready.",
177
+ initGuide: `[Initialize]
178
+ 1. Initialize directly
179
+ Tell the AI: Start initializing this project
180
+ 2. Initialize with material
181
+ Put it in: ai/project/inbox/
182
+ Tell the AI: Start initializing this project and absorb the material in ai/project/inbox/
183
+
184
+ [Follow-up]
185
+ 1. Continue work
186
+ Tell the AI: Continue this project
187
+ 2. Absorb new material
188
+ Put it in: ai/project/inbox/
189
+ Tell the AI: Reconcile the new material in ai/project/inbox/
190
+ 3. Improve context
191
+ Run: npx -y @wnlen/agent-execution-template refresh
192
+ 4. Evaluate direction
193
+ Put it in: ai/project/inbox/ideas/
194
+ Tell the AI: Generate a direction amendment proposal from ai/project/inbox/ideas/
195
+ 5. Show next step
196
+ Run: npx -y @wnlen/agent-execution-template next
197
+
198
+ [Rule of thumb]
199
+ Material = confirmed facts, docs, workflows, APIs, or business rules
200
+ Direction = undecided ideas, product strategy, architecture changes, or roadmap changes`,
201
+ start: "Start:",
202
+ startPrompt: "Start initializing this project",
203
+ then: "Then:",
204
+ reviewProject: "Confirm or correct the project context from the agent output",
205
+ giveTask: "When you want to execute work, say: Continue this project",
206
+ confirmTask: "When you need to absorb new material, put it in ai/project/inbox/, then say:",
207
+ executePrompt: "Reconcile the new material in ai/project/inbox/",
208
+ strategyHint: "When direction changes, put ideas in ai/project/inbox/ideas/ and produce a strategy_update proposal.",
209
+ files: "Files",
210
+ check: "Check",
211
+ details: "Details:",
212
+ refreshTitle: "Agent Execution Template project context refresh",
213
+ improveContextTitle: "Agent Execution Template project context improvement",
214
+ refreshBackedUp: "Backed up old project context",
215
+ refreshImported: "Imported old project context into",
216
+ refreshReady: "Generated a fresh ai/project/**.",
217
+ refreshPrompt: "Reconcile the new material in ai/project/inbox/ and regenerate a stronger ai/project/ from the old context",
218
+ refreshNoProject: "No old ai/project/** found; ran normal init.",
219
+ updateTitle: "Agent Execution Template update",
220
+ updated: "Updated ai/template/** to",
221
+ projectNotModified: "ai/project/** was not modified.",
222
+ doctorTitle: "Agent Execution Template Doctor",
223
+ templateVersion: "Template version",
224
+ templateLang: "Template language",
225
+ missing: "MISSING",
226
+ warn: "WARN",
227
+ pass: "OK",
228
+ fail: "FAIL",
229
+ empty: "is empty",
230
+ invalidJson: "contains invalid JSON",
231
+ taskFrontMatterIncomplete: "task front matter is missing required fields",
232
+ versionMismatch: "template version does not match package version",
233
+ runInit: "Run npx -y @wnlen/agent-execution-template init",
234
+ readyWithWarnings: "Ready to run with warnings",
235
+ readyToRun: "Ready to run",
236
+ invalidLang: "Unsupported language. Use zh or en",
237
+ reconcileTitle: "Agent Execution Template Context Reconcile",
238
+ reconcilePut: "Put new business, product, architecture, or process material in:",
239
+ reconcileAsk: "Then tell your agent:",
240
+ reconcilePrompt: "Reconcile the new material in ai/project/inbox/",
241
+ strategyTitle: "Agent Execution Template Strategy Update",
242
+ strategyPut: "Put new product, business, architecture, or direction ideas in:",
243
+ strategyAsk: "Then tell your agent:",
244
+ strategyPrompt: "Generate a direction amendment proposal from ai/project/inbox/ideas/",
245
+ strategyReview: "After human confirmation, say:",
246
+ strategyApplyPrompt: "Confirmed, merge this proposal",
247
+ nextTitle: "Agent Execution Template next step",
248
+ nextRunInit: "This project has not installed the template yet. Run:",
249
+ nextTellAgent: "Send this to your AI coding tool:",
250
+ nextRunCommand: "Run this command:",
251
+ nextReviewProposal: "A direction amendment proposal exists. Review it first; after confirmation, tell the AI:",
252
+ repairHint: "Missing recommended project files can be safely added by running init again; existing ai/project/** files are not overwritten.",
253
+ permissionDenied: "Cannot write target path",
254
+ permissionHint: `Check ownership and permissions under ai/**. Common fix:
255
+ sudo chown -R "$(id -un):$(id -gn)" ai
256
+ find ai -type d -exec chmod u+rwx {} +
257
+ find ai -type f -exec chmod u+rw {} +`,
258
+ changeLabels: {
259
+ created: "CREATED",
260
+ updated: "UPDATED",
261
+ kept: "KEPT"
262
+ },
263
+ changeUnit: (label, count) => `${count} ${label.toLowerCase()}`
264
+ }
265
+ };
266
+
267
+ function readVersion(root) {
268
+ const versionFile = path.join(root, "VERSION");
269
+ if (!fs.existsSync(versionFile)) return null;
270
+ return fs.readFileSync(versionFile, "utf8").trim() || null;
271
+ }
272
+
273
+ function readPackageVersion() {
274
+ const packageFile = path.join(PACKAGE_ROOT, "package.json");
275
+ if (!fs.existsSync(packageFile)) return null;
276
+ try {
277
+ const pkg = JSON.parse(fs.readFileSync(packageFile, "utf8"));
278
+ return pkg.version || null;
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+
284
+ function readInstalledLang() {
285
+ const langFile = path.join(TARGET_AI, "template", "LANG");
286
+ if (!fs.existsSync(langFile)) return DEFAULT_LANG;
287
+ const lang = fs.readFileSync(langFile, "utf8").trim();
288
+ return SUPPORTED_LANGS.has(lang) ? lang : DEFAULT_LANG;
289
+ }
290
+
291
+ function parseLang(args, fallback = DEFAULT_LANG) {
292
+ let lang = fallback;
293
+ for (let index = 0; index < args.length; index += 1) {
294
+ const arg = args[index];
295
+ if (arg === "--lang" || arg === "-l") {
296
+ lang = args[index + 1] || "";
297
+ index += 1;
298
+ } else if (arg.startsWith("--lang=")) {
299
+ lang = arg.slice("--lang=".length);
300
+ }
301
+ }
302
+ return lang;
303
+ }
304
+
305
+ function getText(lang) {
306
+ return TEXT[SUPPORTED_LANGS.has(lang) ? lang : DEFAULT_LANG];
307
+ }
308
+
309
+ function getSourceAi(lang) {
310
+ return path.join(TEMPLATE_ROOT, lang, "ai");
311
+ }
312
+
313
+ function usage(lang = DEFAULT_LANG) {
314
+ console.log(getText(lang).usage);
315
+ }
316
+
317
+ function ensureDir(dir) {
318
+ fs.mkdirSync(dir, { recursive: true });
319
+ }
320
+
321
+ function listFiles(dir, base = dir) {
322
+ if (!fs.existsSync(dir)) return [];
323
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
324
+ const files = [];
325
+ for (const entry of entries) {
326
+ const fullPath = path.join(dir, entry.name);
327
+ if (entry.isDirectory()) {
328
+ files.push(...listFiles(fullPath, base));
329
+ } else if (entry.isFile()) {
330
+ files.push(path.relative(base, fullPath));
331
+ }
332
+ }
333
+ return files.sort();
334
+ }
335
+
336
+ function copyFile(sourceRoot, targetRoot, relativePath, overwrite) {
337
+ const source = path.join(sourceRoot, relativePath);
338
+ const target = path.join(targetRoot, relativePath);
339
+ const existed = fs.existsSync(target);
340
+ if (!overwrite && fs.existsSync(target)) {
341
+ return { action: "kept", path: relativePath };
342
+ }
343
+ ensureDir(path.dirname(target));
344
+ fs.copyFileSync(source, target);
345
+ return { action: existed ? "updated" : "created", path: relativePath };
346
+ }
347
+
348
+ function copyTree(sourceRoot, targetRoot, overwrite) {
349
+ return listFiles(sourceRoot).map((relativePath) =>
350
+ copyFile(sourceRoot, targetRoot, relativePath, overwrite)
351
+ );
352
+ }
353
+
354
+ function formatTimestamp(date = new Date()) {
355
+ const pad = (value) => String(value).padStart(2, "0");
356
+ return [
357
+ date.getFullYear(),
358
+ pad(date.getMonth() + 1),
359
+ pad(date.getDate())
360
+ ].join("") + "-" + [
361
+ pad(date.getHours()),
362
+ pad(date.getMinutes()),
363
+ pad(date.getSeconds())
364
+ ].join("");
365
+ }
366
+
367
+ function uniqueBackupPath(basePath) {
368
+ if (!fs.existsSync(basePath)) return basePath;
369
+ for (let index = 1; index < 1000; index += 1) {
370
+ const candidate = `${basePath}-${index}`;
371
+ if (!fs.existsSync(candidate)) return candidate;
372
+ }
373
+ throw new Error(`Could not allocate backup path: ${basePath}`);
374
+ }
375
+
376
+ function printChanges(title, changes, lang) {
377
+ const text = getText(lang);
378
+ console.log(title);
379
+ for (const change of changes) {
380
+ console.log(`[${text.changeLabels[change.action] || change.action}] ${change.path}`);
381
+ }
382
+ }
383
+
384
+ function summarizeChanges(changes, lang) {
385
+ const counts = changes.reduce((acc, change) => {
386
+ acc[change.action] = (acc[change.action] || 0) + 1;
387
+ return acc;
388
+ }, {});
389
+ const text = getText(lang);
390
+ return ["created", "updated", "kept"]
391
+ .filter((action) => counts[action])
392
+ .map((action) => text.changeUnit(text.changeLabels[action], counts[action]))
393
+ .join(", ");
394
+ }
395
+
396
+ function hasUsefulFile(dir, { excludeDirs = [], excludeFiles = [] } = {}) {
397
+ if (!fs.existsSync(dir)) return false;
398
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
399
+ for (const entry of entries) {
400
+ if (entry.name === ".gitkeep" || excludeFiles.includes(entry.name)) {
401
+ continue;
402
+ }
403
+ const fullPath = path.join(dir, entry.name);
404
+ if (entry.isDirectory()) {
405
+ if (excludeDirs.includes(entry.name)) {
406
+ continue;
407
+ }
408
+ if (hasUsefulFile(fullPath, { excludeDirs, excludeFiles })) {
409
+ return true;
410
+ }
411
+ } else if (entry.isFile()) {
412
+ return true;
413
+ }
414
+ }
415
+ return false;
416
+ }
417
+
418
+ function readProjectTemplate(lang) {
419
+ const projectFile = path.join(getSourceAi(lang), "project", "project.md");
420
+ if (!fs.existsSync(projectFile)) return null;
421
+ return fs.readFileSync(projectFile, "utf8");
422
+ }
423
+
424
+ function projectStillLooksFresh(lang) {
425
+ const projectFile = path.join(TARGET_AI, "project", "project.md");
426
+ if (!fs.existsSync(projectFile)) return true;
427
+ const template = readProjectTemplate(lang);
428
+ if (!template) return false;
429
+ return fs.readFileSync(projectFile, "utf8") === template;
430
+ }
431
+
432
+ function proposalState() {
433
+ const proposalDir = path.join(TARGET_AI, "project", "proposals", "final-shape-updates");
434
+ if (!fs.existsSync(proposalDir)) return null;
435
+ const proposalFiles = listFiles(proposalDir)
436
+ .filter((file) => file.endsWith(".md") && file !== "_template.md");
437
+ let hasProposed = false;
438
+ for (const file of proposalFiles) {
439
+ const content = fs.readFileSync(path.join(proposalDir, file), "utf8");
440
+ const statusMatch = content.match(/^status:\s*["']?([^"'\s]+)["']?\s*$/m);
441
+ const status = statusMatch ? statusMatch[1] : null;
442
+ if (/^status:\s*["']?accepted["']?\s*$/m.test(content)) {
443
+ return "accepted";
444
+ }
445
+ if (!status || status === "proposed") {
446
+ hasProposed = true;
447
+ }
448
+ }
449
+ return hasProposed ? "proposed" : null;
450
+ }
451
+
452
+ function init({ lang = DEFAULT_LANG, verbose = false, quiet = false } = {}) {
453
+ const text = getText(lang);
454
+ if (!SUPPORTED_LANGS.has(lang)) {
455
+ console.error(`[${text.fail}] ${text.invalidLang}: ${lang}`);
456
+ process.exitCode = 1;
457
+ return;
458
+ }
459
+
460
+ const sourceAi = getSourceAi(lang);
461
+ if (!fs.existsSync(sourceAi)) {
462
+ console.error(`[${text.fail}] ${text.sourceMissing}: ${sourceAi}`);
463
+ process.exitCode = 1;
464
+ return;
465
+ }
466
+
467
+ ensureDir(TARGET_AI);
468
+
469
+ const changes = [];
470
+ changes.push(...copyTree(path.join(sourceAi, "template"), path.join(TARGET_AI, "template"), true)
471
+ .map((change) => ({ ...change, path: path.join("ai/template", change.path) })));
472
+
473
+ const readmeChange = copyFile(sourceAi, TARGET_AI, "README.md", !fs.existsSync(path.join(TARGET_AI, "README.md")));
474
+ changes.push({ ...readmeChange, path: path.join("ai", readmeChange.path) });
475
+
476
+ changes.push(...copyTree(path.join(sourceAi, "project"), path.join(TARGET_AI, "project"), false)
477
+ .map((change) => ({ ...change, path: path.join("ai/project", change.path) })));
478
+
479
+ if (!quiet) {
480
+ console.log(`${text.ready}
481
+
482
+ ${text.initGuide}
483
+
484
+ ${text.files}: ${summarizeChanges(changes, lang)}
485
+ ${text.check}: npx -y @wnlen/agent-execution-template doctor
486
+ `);
487
+
488
+ if (verbose) {
489
+ printChanges(text.details, changes, lang);
490
+ }
491
+ }
492
+ }
493
+
494
+ function refresh({ lang = DEFAULT_LANG, title } = {}) {
495
+ const text = getText(lang);
496
+ if (!SUPPORTED_LANGS.has(lang)) {
497
+ console.error(`[${text.fail}] ${text.invalidLang}: ${lang}`);
498
+ process.exitCode = 1;
499
+ return;
500
+ }
501
+
502
+ const projectPath = path.join(TARGET_AI, "project");
503
+
504
+ if (!fs.existsSync(projectPath)) {
505
+ init({ lang, verbose: false, quiet: false });
506
+ console.log(`[${text.pass}] ${text.refreshNoProject}`);
507
+ return;
508
+ }
509
+
510
+ ensureDir(TARGET_AI);
511
+ const backupPath = uniqueBackupPath(path.join(TARGET_AI, `project.backup.${formatTimestamp()}`));
512
+ fs.renameSync(projectPath, backupPath);
513
+
514
+ init({ lang, verbose: false, quiet: true });
515
+
516
+ const importPath = path.join(TARGET_AI, "project", "inbox", "raw", "old-project");
517
+ ensureDir(importPath);
518
+ copyTree(backupPath, importPath, false);
519
+
520
+ console.log(`${title || text.refreshTitle}
521
+ [${text.pass}] ${text.refreshBackedUp}: ${path.relative(process.cwd(), backupPath)}
522
+ [${text.pass}] ${text.refreshImported}: ${path.relative(process.cwd(), importPath)}
523
+ [${text.pass}] ${text.refreshReady}
524
+
525
+ ${text.then}
526
+ ${text.refreshPrompt}
527
+ `);
528
+ }
529
+
530
+ function next({ lang = readInstalledLang() } = {}) {
531
+ const text = getText(lang);
532
+ if (!SUPPORTED_LANGS.has(lang)) {
533
+ console.error(`[${text.fail}] ${text.invalidLang}: ${lang}`);
534
+ process.exitCode = 1;
535
+ return;
536
+ }
537
+
538
+ console.log(`${text.nextTitle}\n`);
539
+
540
+ const templatePath = path.join(TARGET_AI, "template");
541
+ const projectPath = path.join(TARGET_AI, "project");
542
+ if (!fs.existsSync(templatePath) || !fs.existsSync(projectPath)) {
543
+ console.log(`${text.nextRunInit}
544
+ npx -y @wnlen/agent-execution-template init
545
+ `);
546
+ return;
547
+ }
548
+
549
+ const state = proposalState();
550
+ if (state === "accepted") {
551
+ console.log(`${text.nextTellAgent}
552
+ ${text.strategyApplyPrompt}
553
+ `);
554
+ return;
555
+ }
556
+ if (state === "proposed") {
557
+ console.log(`${text.nextReviewProposal}
558
+ ${text.strategyApplyPrompt}
559
+ `);
560
+ return;
561
+ }
562
+
563
+ if (hasUsefulFile(path.join(projectPath, "inbox", "ideas"))) {
564
+ console.log(`${text.nextTellAgent}
565
+ ${text.strategyPrompt}
566
+ `);
567
+ return;
568
+ }
569
+
570
+ if (hasUsefulFile(path.join(projectPath, "inbox"), { excludeDirs: ["ideas", "processed"] })) {
571
+ console.log(`${text.nextTellAgent}
572
+ ${text.executePrompt}
573
+ `);
574
+ return;
575
+ }
576
+
577
+ if (projectStillLooksFresh(lang)) {
578
+ console.log(`${text.nextTellAgent}
579
+ ${text.startPrompt}
580
+ `);
581
+ return;
582
+ }
583
+
584
+ console.log(`${text.nextTellAgent}
585
+ ${lang === "zh" ? "继续推进这个项目" : "Continue this project"}
586
+ `);
587
+ }
588
+
589
+ function update({ lang = readInstalledLang() } = {}) {
590
+ const text = getText(lang);
591
+ if (!SUPPORTED_LANGS.has(lang)) {
592
+ console.error(`[${text.fail}] ${text.invalidLang}: ${lang}`);
593
+ process.exitCode = 1;
594
+ return;
595
+ }
596
+
597
+ const sourceTemplate = path.join(getSourceAi(lang), "template");
598
+ const targetTemplate = path.join(TARGET_AI, "template");
599
+ if (!fs.existsSync(sourceTemplate)) {
600
+ console.error(`[${text.fail}] ${text.sourceMissing}: ${sourceTemplate}`);
601
+ process.exitCode = 1;
602
+ return;
603
+ }
604
+ ensureDir(targetTemplate);
605
+ const changes = copyTree(sourceTemplate, targetTemplate, true)
606
+ .map((change) => ({ ...change, path: path.join("ai/template", change.path) }));
607
+ printChanges(text.updateTitle, changes, lang);
608
+ console.log(`[${text.pass}] ${text.updated} ${readVersion(sourceTemplate) || text.unknown}. ${text.projectNotModified}`);
609
+ }
610
+
611
+ function reconcile({ lang = readInstalledLang() } = {}) {
612
+ const text = getText(lang);
613
+ if (!SUPPORTED_LANGS.has(lang)) {
614
+ console.error(`[${text.fail}] ${text.invalidLang}: ${lang}`);
615
+ process.exitCode = 1;
616
+ return;
617
+ }
618
+
619
+ console.log(`${text.reconcileTitle}
620
+
621
+ ${text.reconcilePut}
622
+ ai/project/inbox/
623
+
624
+ ${text.reconcileAsk}
625
+ ${text.reconcilePrompt}
626
+ `);
627
+ }
628
+
629
+ function strategy({ lang = readInstalledLang() } = {}) {
630
+ const text = getText(lang);
631
+ if (!SUPPORTED_LANGS.has(lang)) {
632
+ console.error(`[${text.fail}] ${text.invalidLang}: ${lang}`);
633
+ process.exitCode = 1;
634
+ return;
635
+ }
636
+
637
+ console.log(`${text.strategyTitle}
638
+
639
+ ${text.strategyPut}
640
+ ai/project/inbox/ideas/
641
+
642
+ ${text.strategyAsk}
643
+ ${text.strategyPrompt}
644
+
645
+ ${text.strategyReview}
646
+ ${text.strategyApplyPrompt}
647
+ `);
648
+ }
649
+
650
+ function isPermissionError(error) {
651
+ return error && (error.code === "EACCES" || error.code === "EPERM");
652
+ }
653
+
654
+ function printFatal(error, lang) {
655
+ const text = getText(lang);
656
+ if (isPermissionError(error)) {
657
+ const target = error.dest || error.path || process.cwd();
658
+ console.error(`[${text.fail}] ${text.permissionDenied}: ${target}`);
659
+ console.error(text.permissionHint);
660
+ process.exitCode = 1;
661
+ return;
662
+ }
663
+ console.error(error && error.stack ? error.stack : String(error));
664
+ process.exitCode = 1;
665
+ }
666
+
667
+ function doctor() {
668
+ const lang = readInstalledLang();
669
+ const text = getText(lang);
670
+ console.log(`${text.doctorTitle}\n`);
671
+ console.log(`${text.templateVersion}: ${readVersion(path.join(TARGET_AI, "template")) || text.unknown}`);
672
+ console.log(`${text.templateLang}: ${lang}\n`);
673
+
674
+ let missing = 0;
675
+ let warnings = 0;
676
+ const installedVersion = readVersion(path.join(TARGET_AI, "template"));
677
+ const packageVersion = readPackageVersion();
678
+
679
+ for (const file of REQUIRED_FILES) {
680
+ const fullPath = path.join(process.cwd(), file);
681
+ if (!fs.existsSync(fullPath)) {
682
+ console.log(`[${text.missing}] ${file}`);
683
+ missing += 1;
684
+ continue;
685
+ }
686
+ const content = fs.readFileSync(fullPath, "utf8");
687
+ if (
688
+ ["ai/project/project.md", "ai/project/runtime.md", "ai/project/task.md"].includes(file) &&
689
+ content.trim().length === 0
690
+ ) {
691
+ console.log(`[${text.warn}] ${file} ${text.empty}`);
692
+ warnings += 1;
693
+ continue;
694
+ }
695
+ console.log(`[${text.pass}] ${file}`);
696
+ }
697
+
698
+ for (const file of RECOMMENDED_FILES) {
699
+ const fullPath = path.join(process.cwd(), file);
700
+ if (!fs.existsSync(fullPath)) {
701
+ console.log(`[${text.warn}] ${file} ${text.missing}`);
702
+ warnings += 1;
703
+ continue;
704
+ }
705
+ console.log(`[${text.pass}] ${file}`);
706
+ }
707
+
708
+ for (const file of JSON_HEALTH_FILES) {
709
+ const fullPath = path.join(process.cwd(), file);
710
+ if (!fs.existsSync(fullPath)) {
711
+ continue;
712
+ }
713
+ try {
714
+ JSON.parse(fs.readFileSync(fullPath, "utf8"));
715
+ console.log(`[${text.pass}] ${file} JSON`);
716
+ } catch {
717
+ console.log(`[${text.fail}] ${file} ${text.invalidJson}`);
718
+ missing += 1;
719
+ }
720
+ }
721
+
722
+ const taskPath = path.join(process.cwd(), "ai/project/task.md");
723
+ if (fs.existsSync(taskPath)) {
724
+ const taskContent = fs.readFileSync(taskPath, "utf8");
725
+ const hasFrontMatter = taskContent.startsWith("---\n");
726
+ const hasRequiredTaskFields = TASK_HEALTH_PATTERNS.every((pattern) => pattern.test(taskContent));
727
+ if (hasFrontMatter && hasRequiredTaskFields) {
728
+ console.log(`[${text.pass}] ai/project/task.md front matter`);
729
+ } else {
730
+ console.log(`[${text.warn}] ai/project/task.md ${text.taskFrontMatterIncomplete}`);
731
+ warnings += 1;
732
+ }
733
+ }
734
+
735
+ if (installedVersion && packageVersion && installedVersion !== packageVersion) {
736
+ console.log(`[${text.warn}] ai/template/VERSION ${text.versionMismatch}: ${installedVersion} != ${packageVersion}`);
737
+ warnings += 1;
738
+ }
739
+
740
+ if (missing > 0) {
741
+ console.log(`\n[${text.fail}] ${text.runInit}`);
742
+ process.exitCode = 1;
743
+ } else if (warnings > 0) {
744
+ console.log(`\n[${text.pass}] ${text.readyWithWarnings}`);
745
+ console.log(text.repairHint);
746
+ } else {
747
+ console.log(`\n[${text.pass}] ${text.readyToRun}`);
748
+ }
749
+ }
750
+
751
+ const args = process.argv.slice(2);
752
+ const command = args[0] || "help";
753
+ const verbose = args.includes("--verbose");
754
+ const requestedLang = parseLang(
755
+ args,
756
+ command === "update" ||
757
+ command === "doctor" ||
758
+ command === "reconcile" ||
759
+ command === "strategy" ||
760
+ command === "refresh" ||
761
+ command === "improve-context" ||
762
+ command === "next"
763
+ ? readInstalledLang()
764
+ : DEFAULT_LANG
765
+ );
766
+
767
+ try {
768
+ if (command === "init") {
769
+ init({ lang: requestedLang, verbose });
770
+ } else if (command === "next") {
771
+ next({ lang: requestedLang });
772
+ } else if (command === "refresh") {
773
+ refresh({ lang: requestedLang });
774
+ } else if (command === "improve-context") {
775
+ refresh({ lang: requestedLang, title: getText(requestedLang).improveContextTitle });
776
+ } else if (command === "update") {
777
+ update({ lang: requestedLang });
778
+ } else if (command === "reconcile") {
779
+ reconcile({ lang: requestedLang });
780
+ } else if (command === "strategy") {
781
+ strategy({ lang: requestedLang });
782
+ } else if (command === "doctor") {
783
+ doctor();
784
+ } else {
785
+ usage(requestedLang);
786
+ if (command !== "help" && command !== "--help" && command !== "-h") {
787
+ process.exitCode = 1;
788
+ }
789
+ }
790
+ } catch (error) {
791
+ printFatal(error, requestedLang);
792
+ }