@wnlen/agent-execution-template 0.8.19 → 0.8.21

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/test/selftest.js CHANGED
@@ -40,6 +40,17 @@ function write(cwd, relativePath, content) {
40
40
  fs.writeFileSync(path.join(cwd, relativePath), content);
41
41
  }
42
42
 
43
+ function countOccurrences(content, pattern) {
44
+ return content.split(pattern).length - 1;
45
+ }
46
+
47
+ function managedEntrypointBlock(content) {
48
+ const start = content.indexOf("<!-- agent-execution-template:start -->");
49
+ const end = content.indexOf("<!-- agent-execution-template:end -->", start);
50
+ assert(start >= 0 && end >= 0, "entrypoint content should include a managed block");
51
+ return content.slice(start, end + "<!-- agent-execution-template:end -->".length);
52
+ }
53
+
43
54
  function createTempProject(name) {
44
55
  return fs.mkdtempSync(path.join(os.tmpdir(), `${name}-`));
45
56
  }
@@ -48,12 +59,24 @@ function testInitUpdateDoctor() {
48
59
  const cwd = createTempProject("agent-execution-template-selftest");
49
60
 
50
61
  run(["init"], cwd);
62
+ assert(exists(cwd, "AGENTS.md"), "init should create root AGENTS.md entrypoint");
63
+ assert(exists(cwd, "CLAUDE.md"), "init should create root CLAUDE.md entrypoint");
64
+ assert(read(cwd, "AGENTS.md").includes("agent-execution-template:start"), "AGENTS.md should include managed protocol block");
65
+ assert(read(cwd, "AGENTS.md").includes("ai/template/prompt.md"), "AGENTS.md should route agents to the protocol prompt");
66
+ assert(read(cwd, "CLAUDE.md").includes("ai/template/prompt.md"), "CLAUDE.md should route Claude to the same protocol prompt");
67
+ assert(managedEntrypointBlock(read(cwd, "AGENTS.md")) === managedEntrypointBlock(read(cwd, "CLAUDE.md")), "AGENTS.md and CLAUDE.md should contain the same managed compatibility block");
68
+ assert(read(cwd, "AGENTS.md").includes("有意同时写入 `AGENTS.md` 和 `CLAUDE.md`"), "AGENTS.md should explain intentional compatibility duplication");
69
+ assert(read(cwd, "AGENTS.md").includes("在 `ai/template/prompt.md` 完成路由前"), "AGENTS.md should stay a short router");
70
+ assert(!read(cwd, "AGENTS.md").includes("ai/template/bootstrap.md"), "AGENTS.md should not duplicate bootstrap routing details");
71
+ assert(!read(cwd, "CLAUDE.md").includes("ai/template/bootstrap.md"), "CLAUDE.md should not duplicate bootstrap routing details");
51
72
  assert(read(cwd, "ai/template/LANG") === "zh\n", "init should default to zh template");
52
73
  assert(exists(cwd, "ai/template/VERSION"), "init should create template VERSION");
53
74
  assert(exists(cwd, "ai/template/bootstrap.md"), "init should create template bootstrap prompt");
54
75
  assert(exists(cwd, "ai/template/execution-policy.md"), "init should create execution policy prompt");
55
76
  assert(exists(cwd, "ai/template/prompt.md"), "init should create template prompt");
56
77
  assert(exists(cwd, "ai/template/reconcile.md"), "init should create template reconcile prompt");
78
+ assert(exists(cwd, "ai/template/schemas/result.schema.json"), "init should create result schema");
79
+ assert(exists(cwd, "ai/template/schemas/metrics.schema.json"), "init should create metrics schema");
57
80
  assert(exists(cwd, "ai/project/inbox/.gitkeep"), "init should create inbox directory");
58
81
  assert(exists(cwd, "ai/project/project.md"), "init should create project.md");
59
82
  assert(exists(cwd, "ai/project/task.md"), "init should create task.md");
@@ -78,10 +101,12 @@ function testInitUpdateDoctor() {
78
101
  assert(read(cwd, "ai/template/bootstrap.md").includes("未吸收资料"), "bootstrap handoff should audit unabsorbed material");
79
102
  assert(read(cwd, "ai/template/bootstrap.md").includes("冲突处理"), "bootstrap handoff should audit conflict handling");
80
103
  assert(read(cwd, "ai/template/prompt.md").includes("任务草稿交接"), "execution prompt should include task handoff");
81
- assert(read(cwd, "ai/template/prompt.md").includes("ai/template/execution-policy.md"), "execution prompt should read execution policy");
104
+ assert(read(cwd, "ai/template/prompt.md").includes("本文件只负责路由"), "execution prompt should be a lightweight router");
105
+ assert(read(cwd, "ai/template/prompt.md").includes("才读取 `ai/template/protocol.md`"), "execution prompt should lazy-load execution policy only for execution");
106
+ assert(read(cwd, "ai/template/bootstrap.md").includes("不要把 `AGENTS.md` 或 `CLAUDE.md` 当作项目业务证据"), "bootstrap should not treat root agent entrypoints as project evidence");
82
107
  assert(read(cwd, "ai/template/execution-policy.md").includes("风险分级"), "execution policy should include risk rubric");
83
108
  assert(read(cwd, "ai/template/execution-policy.md").includes("execution_policy.task_tree"), "execution policy should require task tree persistence");
84
- assert(read(cwd, "ai/template/prompt.md").includes("默认也只处理 `ai/project/inbox/*.md`"), "execution prompt should narrow inbox reconciliation");
109
+ assert(read(cwd, "ai/template/prompt.md").includes("也默认只处理 `ai/project/inbox/*.md`"), "execution prompt should narrow inbox reconciliation");
85
110
  assert(read(cwd, "ai/template/protocol.md").includes("`bounded_continuous`"), "protocol should include bounded continuous execution");
86
111
  assert(read(cwd, "ai/template/execution-policy.md").includes("垂直切片"), "protocol should require vertical-slice progress for continuous execution");
87
112
  assert(read(cwd, "ai/template/execution-policy.md").includes("可独立验收的垂直切片"), "execution policy should define L1 granularity");
@@ -89,6 +114,8 @@ function testInitUpdateDoctor() {
89
114
  assert(read(cwd, "ai/template/execution-policy.md").includes("不要为每个微小 L3 操作写回"), "execution policy should limit task tree write-back churn");
90
115
  assert(read(cwd, "ai/template/execution-policy.md").includes("公共接口、数据模型、权限、安全"), "execution policy should constrain Yellow corrections");
91
116
  assert(read(cwd, "ai/template/execution-policy.md").includes("用户可见输出"), "execution policy should define user-visible output rules");
117
+ assert(read(cwd, "ai/template/execution-policy.md").includes("用户可见的计划"), "execution policy should keep user-visible planning in the installed language");
118
+ assert(read(cwd, "ai/template/rules/output.md").includes("默认使用 `ai/template/LANG`"), "output rules should keep human-readable results in the installed language");
92
119
  assert(read(cwd, "ai/template/execution-policy.md").includes("不要默认展示完整 L2/L3/L4"), "execution policy should avoid exposing full subtask trees by default");
93
120
  assert(read(cwd, "ai/template/execution-policy.md").includes("不要展示内部协议字段"), "execution policy should hide internal protocol details by default");
94
121
  assert(read(cwd, "ai/template/execution-policy.md").includes("L1 为 2 个或更多,自动启用"), "protocol should auto-enable continuous execution from L1 count");
@@ -110,12 +137,12 @@ function testInitUpdateDoctor() {
110
137
  assert(read(cwd, "ai/template/prompt.md").includes("不要重新 bootstrap"), "execution prompt should reconcile inbox material when project context already exists");
111
138
  assert(read(cwd, "ai/template/prompt.md").includes("整合 ai/project/inbox/ 里的新资料"), "execution prompt should route natural reconcile entry");
112
139
  assert(read(cwd, "ai/template/prompt.md").includes("继续推进这个项目"), "execution prompt should route natural continue entry");
113
- assert(read(cwd, "ai/template/prompt.md").includes("不要在任务仍是草稿时直接执行"), "execution prompt should stop after drafting a task");
140
+ assert(read(cwd, "ai/template/prompt.md").includes("草稿不能直接执行"), "execution prompt should stop after drafting a task");
114
141
  assert(read(cwd, "ai/template/prompt.md").includes("用户可见输出"), "execution prompt should reference user-visible output rules");
115
142
  assert(read(cwd, "ai/template/prompt.md").includes("strategy_update"), "execution prompt should route strategy updates");
116
143
  assert(read(cwd, "ai/template/reconcile.md").includes("上下文整合"), "init should install reconcile prompt");
117
144
  assert(read(cwd, "ai/template/reconcile.md").includes("整合计划"), "reconcile prompt should require a plan first");
118
- assert(read(cwd, "ai/template/reconcile.md").includes("不要递归读取 `processed/**` 或 `ideas/**`"), "reconcile prompt should exclude processed and ideas recursively");
145
+ assert(read(cwd, "ai/template/reconcile.md").includes("不要递归读取") && read(cwd, "ai/template/reconcile.md").includes("`processed/**` 或 `ideas/**`"), "reconcile prompt should exclude processed and ideas recursively");
119
146
  assert(read(cwd, "ai/template/reconcile.md").includes("ai/project/inbox/processed/raw/file.md"), "reconcile prompt should archive absorbed raw inbox material");
120
147
  assert(read(cwd, "ai/template/reconcile.md").includes("未吸收资料"), "reconcile handoff should audit unabsorbed material");
121
148
  assert(read(cwd, "ai/template/reconcile.md").includes("冲突处理"), "reconcile handoff should audit conflict handling");
@@ -128,7 +155,9 @@ function testInitUpdateDoctor() {
128
155
  assert(read(cwd, "ai/project/proposals/final-shape-updates/_template.md").includes("`accepted`"), "proposal template should describe accepted status");
129
156
  const initOutput = run(["init"], cwd);
130
157
  assert(initOutput.includes("开始初始化这个项目"), "init output should provide compact natural bootstrap prompt");
131
- assert(initOutput.includes("[初始化]"), "init output should distinguish pre-bootstrap guidance");
158
+ assert(initOutput.includes("根目录 AI 兼容入口: AGENTS.md / CLAUDE.md"), "init output should mention root AI compatibility entrypoints");
159
+ assert(initOutput.includes("协议已安装。项目上下文尚未初始化"), "init output should distinguish protocol install from project context bootstrap");
160
+ assert(initOutput.includes("[下一步:让 AI 初始化项目上下文]"), "init output should guide the AI bootstrap as the next step");
132
161
  assert(initOutput.includes("开始初始化这个项目,并吸收 ai/project/inbox/ 里的资料"), "init output should explain bootstrap with existing material");
133
162
  assert(initOutput.includes("整合 ai/project/inbox/ 里的新资料"), "init output should provide compact natural reconcile prompt");
134
163
  assert(initOutput.includes("优化上下文"), "init output should expose project context refresh in user language");
@@ -138,7 +167,11 @@ function testInitUpdateDoctor() {
138
167
  assert(!initOutput.includes("维护者提示"), "init output should not show source checkout guidance in user projects");
139
168
  assert(!initOutput.includes("[已更新]"), "init output should hide detailed file changes by default");
140
169
  assert(!initOutput.includes("Read ai/template/bootstrap.md"), "init output should not use weak Read bootstrap command");
170
+ assert(countOccurrences(read(cwd, "AGENTS.md"), "agent-execution-template:start") === 1, "init should not duplicate AGENTS.md managed blocks");
171
+ assert(countOccurrences(read(cwd, "CLAUDE.md"), "agent-execution-template:start") === 1, "init should not duplicate CLAUDE.md managed blocks");
141
172
  assert(run(["init", "--verbose"], cwd).includes("[已更新] ai/template/VERSION"), "init --verbose should show detailed file changes");
173
+ assert(countOccurrences(read(cwd, "AGENTS.md"), "agent-execution-template:start") === 1, "re-running init should keep one AGENTS.md managed block");
174
+ assert(countOccurrences(read(cwd, "CLAUDE.md"), "agent-execution-template:start") === 1, "re-running init should keep one CLAUDE.md managed block");
142
175
  const reconcileOutput = run(["reconcile"], cwd);
143
176
  assert(reconcileOutput.includes("Agent Execution Template 上下文整合"), "reconcile should use installed Chinese language");
144
177
  assert(reconcileOutput.includes("整合 ai/project/inbox/ 里的新资料"), "reconcile should print natural Chinese prompt");
@@ -150,10 +183,14 @@ function testInitUpdateDoctor() {
150
183
  write(cwd, "ai/project/project.md", "USER PROJECT MARKER\n");
151
184
  run(["update"], cwd);
152
185
  assert(read(cwd, "ai/project/project.md") === "USER PROJECT MARKER\n", "update must not overwrite project.md");
186
+ assert(read(cwd, "AGENTS.md").includes("ai/template/prompt.md"), "update should keep root agent entrypoint block installed");
153
187
 
154
188
  const doctorOutput = run(["doctor"], cwd);
189
+ assert(doctorOutput.includes("根目录 AI 兼容入口已安装: AGENTS.md / CLAUDE.md"), "doctor should report root AI compatibility entrypoint status");
155
190
  assert(doctorOutput.includes("ai/project/result.json JSON"), "doctor should validate result JSON");
191
+ assert(doctorOutput.includes("ai/project/result.json schema"), "doctor should validate result schema");
156
192
  assert(doctorOutput.includes("ai/project/metrics.json JSON"), "doctor should validate metrics JSON");
193
+ assert(doctorOutput.includes("ai/project/metrics.json schema"), "doctor should validate metrics schema");
157
194
  assert(doctorOutput.includes("ai/project/task.md front matter"), "doctor should validate task front matter");
158
195
  }
159
196
 
@@ -161,8 +198,19 @@ function testEnglishInitUpdateDoctor() {
161
198
  const cwd = createTempProject("agent-execution-template-en");
162
199
 
163
200
  const initOutput = run(["init", "--lang", "en"], cwd);
201
+ assert(exists(cwd, "AGENTS.md"), "English init should create root AGENTS.md entrypoint");
202
+ assert(exists(cwd, "CLAUDE.md"), "English init should create root CLAUDE.md entrypoint");
203
+ assert(read(cwd, "AGENTS.md").includes("ai/template/prompt.md"), "English AGENTS.md should route agents to the protocol prompt");
204
+ assert(read(cwd, "CLAUDE.md").includes("ai/template/prompt.md"), "English CLAUDE.md should route Claude to the same protocol prompt");
205
+ assert(managedEntrypointBlock(read(cwd, "AGENTS.md")) === managedEntrypointBlock(read(cwd, "CLAUDE.md")), "English AGENTS.md and CLAUDE.md should contain the same managed compatibility block");
206
+ assert(read(cwd, "AGENTS.md").includes("intentionally duplicated in `AGENTS.md` and `CLAUDE.md`"), "English AGENTS.md should explain intentional compatibility duplication");
207
+ assert(read(cwd, "AGENTS.md").includes("before `ai/template/prompt.md` routes"), "English AGENTS.md should stay a short router");
208
+ assert(!read(cwd, "AGENTS.md").includes("ai/template/bootstrap.md"), "English AGENTS.md should not duplicate bootstrap routing details");
209
+ assert(!read(cwd, "CLAUDE.md").includes("ai/template/bootstrap.md"), "English CLAUDE.md should not duplicate bootstrap routing details");
164
210
  assert(read(cwd, "ai/template/LANG") === "en\n", "init --lang en should install English template");
165
211
  assert(exists(cwd, "ai/template/execution-policy.md"), "English init should create execution policy prompt");
212
+ assert(exists(cwd, "ai/template/schemas/result.schema.json"), "English init should create result schema");
213
+ assert(exists(cwd, "ai/template/schemas/metrics.schema.json"), "English init should create metrics schema");
166
214
  assert(read(cwd, "ai/template/bootstrap.md").includes("Confirmation Dimensions"), "English init should install English bootstrap prompt");
167
215
  assert(read(cwd, "ai/template/bootstrap.md").includes("Do not summarize this file"), "English bootstrap prompt should prevent summary-only behavior");
168
216
  assert(read(cwd, "ai/template/bootstrap.md").includes("ai/project/refs/final-shape.md"), "English bootstrap prompt should initialize the North Star");
@@ -175,7 +223,9 @@ function testEnglishInitUpdateDoctor() {
175
223
  assert(read(cwd, "ai/template/bootstrap.md").includes("Unabsorbed material"), "English bootstrap handoff should audit unabsorbed material");
176
224
  assert(read(cwd, "ai/template/bootstrap.md").includes("Conflict handling"), "English bootstrap handoff should audit conflict handling");
177
225
  assert(read(cwd, "ai/template/prompt.md").includes("Start initializing this project"), "English execution prompt should route natural bootstrap entry");
178
- assert(read(cwd, "ai/template/prompt.md").includes("ai/template/execution-policy.md"), "English execution prompt should read execution policy");
226
+ assert(read(cwd, "ai/template/prompt.md").includes("This file only routes the workflow"), "English execution prompt should be a lightweight router");
227
+ assert(read(cwd, "ai/template/prompt.md").includes("Only then read `ai/template/protocol.md`"), "English execution prompt should lazy-load execution policy only for execution");
228
+ assert(read(cwd, "ai/template/bootstrap.md").includes("Do not treat `AGENTS.md` or `CLAUDE.md` as project business evidence"), "English bootstrap should not treat root agent entrypoints as project evidence");
179
229
  assert(read(cwd, "ai/template/execution-policy.md").includes("Risk Rubric"), "English execution policy should include risk rubric");
180
230
  assert(read(cwd, "ai/template/execution-policy.md").includes("execution_policy.task_tree"), "English execution policy should require task tree persistence");
181
231
  assert(read(cwd, "ai/template/prompt.md").includes("default to only `ai/project/inbox/*.md`"), "English execution prompt should narrow inbox reconciliation");
@@ -186,6 +236,8 @@ function testEnglishInitUpdateDoctor() {
186
236
  assert(read(cwd, "ai/template/execution-policy.md").includes("every tiny L3 operation"), "English execution policy should limit task tree write-back churn");
187
237
  assert(read(cwd, "ai/template/execution-policy.md").includes("public interfaces, data models, permissions"), "English execution policy should constrain Yellow corrections");
188
238
  assert(read(cwd, "ai/template/execution-policy.md").includes("User-Visible Output"), "English execution policy should define user-visible output rules");
239
+ assert(read(cwd, "ai/template/execution-policy.md").includes("user-visible plans"), "English execution policy should keep user-visible planning in the installed language");
240
+ assert(read(cwd, "ai/template/rules/output.md").includes("installed language from `ai/template/LANG`"), "English output rules should keep human-readable results in the installed language");
189
241
  assert(read(cwd, "ai/template/execution-policy.md").includes("do not show full L2/L3/L4 by default"), "English execution policy should avoid exposing full subtask trees by default");
190
242
  assert(read(cwd, "ai/template/execution-policy.md").includes("do not show internal protocol fields"), "English execution policy should hide internal protocol details by default");
191
243
  assert(read(cwd, "ai/template/execution-policy.md").includes("Automatically use `bounded_continuous`"), "English protocol should auto-enable continuous execution from L1 count");
@@ -222,7 +274,9 @@ function testEnglishInitUpdateDoctor() {
222
274
  assert(read(cwd, "ai/template/reconcile.md").includes("Unabsorbed material"), "English reconcile handoff should audit unabsorbed material");
223
275
  assert(read(cwd, "ai/template/reconcile.md").includes("Conflict handling"), "English reconcile handoff should audit conflict handling");
224
276
  assert(initOutput.includes("Start initializing this project"), "English init output should provide English bootstrap prompt");
225
- assert(initOutput.includes("[Initialize]"), "English init output should distinguish pre-bootstrap guidance");
277
+ assert(initOutput.includes("root AI compatibility entrypoints: AGENTS.md / CLAUDE.md"), "English init output should mention root AI compatibility entrypoints");
278
+ assert(initOutput.includes("protocol installed. Project context is not initialized yet"), "English init output should distinguish protocol install from project context bootstrap");
279
+ assert(initOutput.includes("[Next: ask the AI to initialize project context]"), "English init output should guide the AI bootstrap as the next step");
226
280
  assert(initOutput.includes("Start initializing this project and absorb the material in ai/project/inbox/"), "English init output should explain bootstrap with existing material");
227
281
  assert(initOutput.includes("Reconcile the new material in ai/project/inbox/"), "English init output should provide English reconcile prompt");
228
282
  assert(initOutput.includes("Improve context"), "English init output should expose context refresh in user language");
@@ -240,7 +294,9 @@ function testEnglishInitUpdateDoctor() {
240
294
 
241
295
  const doctorOutput = run(["doctor"], cwd);
242
296
  assert(doctorOutput.includes("Template language: en"), "doctor should show installed English language");
297
+ assert(doctorOutput.includes("root AI compatibility entrypoints installed: AGENTS.md / CLAUDE.md"), "English doctor should report root AI compatibility entrypoint status");
243
298
  assert(doctorOutput.includes("ai/project/result.json JSON"), "English doctor should validate result JSON");
299
+ assert(doctorOutput.includes("ai/project/result.json schema"), "English doctor should validate result schema");
244
300
  assert(doctorOutput.includes("ai/project/task.md front matter"), "English doctor should validate task front matter");
245
301
  assert(doctorOutput.includes("[OK] Ready to run"), "doctor should use installed English language");
246
302
  const reconcileOutput = run(["reconcile"], cwd);
@@ -271,6 +327,60 @@ function testDoctorFailureAndWarning() {
271
327
  const invalidJsonOutput = run(["doctor"], invalidJsonCwd, 1);
272
328
  assert(invalidJsonOutput.includes("JSON 无效"), "doctor should fail invalid result JSON");
273
329
 
330
+ const invalidResultSchemaCwd = createTempProject("agent-execution-template-invalid-result-schema");
331
+ run(["init"], invalidResultSchemaCwd);
332
+ write(invalidResultSchemaCwd, "ai/project/result.json", JSON.stringify({
333
+ protocol_version: "0.8",
334
+ status: "success",
335
+ scope_followed: true,
336
+ files_read: [],
337
+ refs_read: [],
338
+ files_changed: [],
339
+ commands_run: [],
340
+ verification: {
341
+ level: "none",
342
+ passed: false,
343
+ evidence: []
344
+ },
345
+ assumptions: [],
346
+ issues: [],
347
+ next: [],
348
+ runtime_update: {
349
+ required: false,
350
+ changes: [],
351
+ reason: ""
352
+ }
353
+ }, null, 2));
354
+ const invalidResultSchemaOutput = run(["doctor"], invalidResultSchemaCwd, 1);
355
+ assert(invalidResultSchemaOutput.includes("不符合协议 schema"), "doctor should fail result schema violations");
356
+ assert(invalidResultSchemaOutput.includes("$.verification.passed must be true"), "doctor should enforce success verification");
357
+
358
+ const invalidMetricsSchemaCwd = createTempProject("agent-execution-template-invalid-metrics-schema");
359
+ run(["init"], invalidMetricsSchemaCwd);
360
+ write(invalidMetricsSchemaCwd, "ai/project/metrics.json", JSON.stringify({
361
+ protocol_version: "0.8",
362
+ task_id: "",
363
+ task_type: "",
364
+ model: "",
365
+ model_tier: "cheap",
366
+ escalated: true,
367
+ escalation_reason: "",
368
+ model_policy_followed: true,
369
+ escalation_trigger_hit: "",
370
+ strong_model_role: "",
371
+ input_tokens_estimated: 0,
372
+ output_tokens_estimated: 0,
373
+ duration_minutes: 0,
374
+ success: false,
375
+ human_fix_required: false,
376
+ failure_reason: "",
377
+ reuse_potential: "low",
378
+ notes: []
379
+ }, null, 2));
380
+ const invalidMetricsSchemaOutput = run(["doctor"], invalidMetricsSchemaCwd, 1);
381
+ assert(invalidMetricsSchemaOutput.includes("不符合协议 schema"), "doctor should fail metrics schema violations");
382
+ assert(invalidMetricsSchemaOutput.includes("$.escalation_reason must have length >= 1"), "doctor should enforce escalated metrics details");
383
+
274
384
  const taskWarnCwd = createTempProject("agent-execution-template-task-frontmatter");
275
385
  run(["init"], taskWarnCwd);
276
386
  write(taskWarnCwd, "ai/project/task.md", "# Task only\n");
@@ -297,6 +407,32 @@ permission: {}
297
407
  assert(taskPolicyWarnOutput.includes("任务 front matter 缺少关键字段"), "doctor should warn when execution policy fields are incomplete");
298
408
  }
299
409
 
410
+ function testRootEntrypointPreservesUserContent() {
411
+ const cwd = createTempProject("agent-execution-template-entrypoints");
412
+ write(cwd, "AGENTS.md", "# Existing agent rules\n\nKeep this user rule.\n");
413
+
414
+ run(["init"], cwd);
415
+ assert(read(cwd, "AGENTS.md").includes("Keep this user rule."), "init should preserve existing AGENTS.md content");
416
+ assert(read(cwd, "AGENTS.md").includes("agent-execution-template:start"), "init should append a managed AGENTS.md block");
417
+ assert(countOccurrences(read(cwd, "AGENTS.md"), "agent-execution-template:start") === 1, "init should append one AGENTS.md block");
418
+
419
+ run(["init"], cwd);
420
+ assert(read(cwd, "AGENTS.md").includes("Keep this user rule."), "re-running init should preserve existing AGENTS.md content");
421
+ assert(countOccurrences(read(cwd, "AGENTS.md"), "agent-execution-template:start") === 1, "re-running init should replace, not duplicate, the managed AGENTS.md block");
422
+
423
+ run(["update"], cwd);
424
+ assert(read(cwd, "AGENTS.md").includes("Keep this user rule."), "update should preserve existing AGENTS.md content");
425
+ assert(countOccurrences(read(cwd, "AGENTS.md"), "agent-execution-template:start") === 1, "update should keep one managed AGENTS.md block");
426
+
427
+ fs.unlinkSync(path.join(cwd, "CLAUDE.md"));
428
+ const missingClaudeOutput = run(["doctor"], cwd);
429
+ assert(missingClaudeOutput.includes("缺少根目录 AI 兼容入口托管块"), "doctor should warn when one root agent entrypoint is missing");
430
+
431
+ fs.unlinkSync(path.join(cwd, "AGENTS.md"));
432
+ const doctorOutput = run(["doctor"], cwd);
433
+ assert(doctorOutput.includes("缺少根目录 AI 兼容入口托管块"), "doctor should warn when root agent entrypoints are missing");
434
+ }
435
+
300
436
  function testRefreshBacksUpAndImportsOldProject() {
301
437
  const cwd = createTempProject("agent-execution-template-refresh");
302
438
  run(["init"], cwd);
@@ -380,6 +516,7 @@ function main() {
380
516
  testInitUpdateDoctor();
381
517
  testEnglishInitUpdateDoctor();
382
518
  testDoctorFailureAndWarning();
519
+ testRootEntrypointPreservesUserContent();
383
520
  testRefreshBacksUpAndImportsOldProject();
384
521
  testNextCommandRoutesByProjectState();
385
522
  testPermissionErrorIsActionable();