@tea-agent/loop-agent 0.22.0 → 0.24.0

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 (103) hide show
  1. package/AGENTS.md +42 -108
  2. package/CHANGELOG.md +85 -0
  3. package/README.md +8 -5
  4. package/bin/agent-worker.js +0 -0
  5. package/dist/application/context-usage/skill-resolution-stats.js +263 -0
  6. package/dist/application/dag/generate-task-dag.js +17 -3
  7. package/dist/cli/command-definitions.js +8 -7
  8. package/dist/cli/program.js +17 -15
  9. package/dist/commands/doctor.js +269 -18
  10. package/dist/commands/init.js +101 -86
  11. package/dist/commands/stats.js +40 -11
  12. package/dist/executors/shell-executor.js +20 -7
  13. package/dist/shared/operator/capabilities.js +486 -3
  14. package/dist/worker/console/app-data.js +6 -0
  15. package/dist/worker/console/chat/artifact-card.js +23 -0
  16. package/dist/worker/console/chat/chat-event-store.js +495 -0
  17. package/dist/worker/console/chat/chat-ui-policy.js +25 -0
  18. package/dist/worker/console/chat/composer-draft-store.js +45 -0
  19. package/dist/worker/console/chat/context-panel.js +54 -0
  20. package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
  21. package/dist/worker/console/chat/explore-tools.js +299 -0
  22. package/dist/worker/console/chat/human-gate-card.js +37 -0
  23. package/dist/worker/console/chat/instruction-skills.js +217 -0
  24. package/dist/worker/console/chat/interview-adapter.js +136 -0
  25. package/dist/worker/console/chat/model-resolver.js +106 -0
  26. package/dist/worker/console/chat/operation-card.js +23 -0
  27. package/dist/worker/console/chat/pi-console-config.js +158 -0
  28. package/dist/worker/console/chat/pi-runtime.js +1143 -0
  29. package/dist/worker/console/chat/repo-browser.js +140 -0
  30. package/dist/worker/console/chat/repo-walk.js +116 -0
  31. package/dist/worker/console/chat/resource-loader.js +67 -0
  32. package/dist/worker/console/chat/routes.js +1646 -0
  33. package/dist/worker/console/chat/runtime-context.js +24 -0
  34. package/dist/worker/console/chat/runtime-selection.js +37 -0
  35. package/dist/worker/console/chat/session-store.js +437 -0
  36. package/dist/worker/console/chat/shortcuts.js +15 -0
  37. package/dist/worker/console/chat/tool-adapter.js +125 -0
  38. package/dist/worker/console/chat/tools.js +195 -0
  39. package/dist/worker/console/chat/usage.js +37 -0
  40. package/dist/worker/console/chat/workspace-landing.js +56 -0
  41. package/dist/worker/console/dag-confirmation.js +42 -8
  42. package/dist/worker/console/human-gate-token.js +130 -0
  43. package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
  44. package/dist/worker/console/operation-runner.js +6 -2
  45. package/dist/worker/console/operation-sse.js +26 -0
  46. package/dist/worker/console/operator-actions.js +420 -7
  47. package/dist/worker/console/server.js +68 -1
  48. package/dist/worker/console/static/assets/index-BTbrEHnO.css +1 -0
  49. package/dist/worker/console/static/assets/index-D9qLevoP.js +27 -0
  50. package/dist/worker/console/static/index.html +2 -2
  51. package/dist/worker/feature/profile-schema.js +1 -1
  52. package/dist/workflows/dag/backend-test-markdown-workflow.js +9 -5
  53. package/dist/workflows/dag/backend-test-result-contract.js +229 -0
  54. package/dist/workflows/dag/frontend-lint-baseline.js +4 -4
  55. package/dist/workflows/dag/init-hybrid.js +71 -22
  56. package/dist/workflows/dag/node-execution.js +38 -1
  57. package/dist/workflows/dag/output-protocol.js +89 -0
  58. package/dist/workflows/dag/prompt.js +35 -1
  59. package/dist/workflows/dag/recovery-recommendation.js +45 -0
  60. package/dist/workflows/dag/report.js +28 -1
  61. package/dist/workflows/dag/rerun-task.js +1 -1
  62. package/dist/workflows/dag/scheduler.js +9 -0
  63. package/dist/workflows/dag/types.js +12 -0
  64. package/dist/workflows/dag/validate.js +55 -0
  65. package/docs/README.md +73 -156
  66. package/docs/architecture/README.md +7 -6
  67. package/docs/architecture/dag-execution.md +2 -2
  68. package/docs/architecture/evolution.md +16 -14
  69. package/docs/architecture/system-overview.md +1 -1
  70. package/docs/architecture/worker-and-feature.md +3 -3
  71. package/docs/governance/README.md +15 -0
  72. package/docs/{harness-methodology-debugging.md → governance/harness-methodology-debugging.md} +27 -3
  73. package/docs/init-surface.manifest.json +22 -4
  74. package/docs/operations/README.md +12 -0
  75. package/docs/{local-development-environment.md → operations/local-development-environment.md} +1 -1
  76. package/docs/skills/vetted-skill-registry.md +23 -3
  77. package/docs/templates/README.md +55 -0
  78. package/docs/templates/backend-test-dag.json +2 -2
  79. package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
  80. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
  81. package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
  82. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
  83. package/docs/templates/hybrid-dag.json +1 -1
  84. package/docs/templates/progress-log.md +9 -2
  85. package/harness.json +4 -4
  86. package/package.json +5 -5
  87. package/scripts/kb-bootstrap-init-skeleton.sh +2 -2
  88. package/skills/agent-worker/SKILL.md +1 -1
  89. package/skills/grill-with-docs/SKILL.md +44 -52
  90. package/skills/grill-with-docs/adr-format.md +37 -26
  91. package/skills/grill-with-docs/context-format.md +18 -26
  92. package/skills/loop-agent/SKILL.md +28 -112
  93. package/skills/loop-agent/references/command-reference.md +9 -3
  94. package/skills/loop-agent/references/harness-policy.md +3 -3
  95. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  96. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  97. package/skills/loop-agent/references/task-workflow.md +2 -0
  98. package/skills/systematic-debugging/SKILL.md +20 -4
  99. package/skills/test-driven-development/SKILL.md +10 -3
  100. package/dist/worker/console/static/assets/index-CUDke82y.js +0 -18
  101. package/dist/worker/console/static/assets/index-wSEksVSO.css +0 -1
  102. /package/docs/{harness-methodology-tdd.md → governance/harness-methodology-tdd.md} +0 -0
  103. /package/docs/{harness-methodology-verification.md → governance/harness-methodology-verification.md} +0 -0
@@ -13,16 +13,20 @@ const GITIGNORE_BLOCK_END = "# LOOP_AGENT_INIT_END";
13
13
  const INIT_SURFACE_STATE_PATH = ".harness/init-surface.json";
14
14
  const DEFAULT_GOVERNANCE_ROOT = "ai_workspace/loop-agent";
15
15
  const CORE_DOC_FILES = [
16
- "README.md",
17
- "architecture/runtime-boundaries.md",
18
- "development-principles.md",
19
- "feature-workflow.md",
20
- "verification-matrix.md",
21
- "loop-agent-harness.md",
22
- "harness-methodology-tdd.md",
23
- "harness-methodology-verification.md",
24
- "harness-methodology-debugging.md",
16
+ { source: "README.md", target: "README.md" },
17
+ { source: "architecture/runtime-boundaries.md", target: "architecture/runtime-boundaries.md" },
18
+ { source: "governance/development-principles.md", target: "development-principles.md" },
19
+ { source: "governance/feature-workflow.md", target: "feature-workflow.md" },
20
+ { source: "governance/verification-matrix.md", target: "verification-matrix.md" },
21
+ { source: "runtime/loop-agent-harness.md", target: "loop-agent-harness.md" },
22
+ { source: "governance/harness-methodology-tdd.md", target: "harness-methodology-tdd.md" },
23
+ { source: "governance/harness-methodology-verification.md", target: "harness-methodology-verification.md" },
24
+ { source: "governance/harness-methodology-debugging.md", target: "harness-methodology-debugging.md" },
25
+ { source: "templates/README.md", target: "templates/README.md" },
25
26
  ];
27
+ function coreDocSourceForTarget(target) {
28
+ return CORE_DOC_FILES.find((entry) => entry.target === target)?.source ?? target;
29
+ }
26
30
  const GOVERNANCE_README_DIRS = [
27
31
  "decisions",
28
32
  "design",
@@ -771,68 +775,59 @@ function buildManagedAgentsBlock(input) {
771
775
  "### 默认立场",
772
776
  "",
773
777
  "- 仓库是记录系统:决策、契约、计划、验证、报告和交接应进入可追踪文件。",
774
- "- 一次只推进一个有边界、可验证的工作块。",
775
- "- 实现前先搜索现有代码、文档、脚本和测试,避免重复造轮子。",
778
+ "- 一次只推进一个有边界、可验证的工作块;实现前先搜索现有代码、文档、脚本和测试。",
776
779
  "- Shell 验证是完成依据;模型建议不能替代命令证据。",
777
780
  "- 保留无关的用户改动,不要回退自己没有做的修改。",
778
781
  "- 委托模型写入前,必须把写入边界写成结构化 `task.json.allowedPaths` / `task.json.forbiddenPaths`,再审查生成 DAG 的 writer `writeSet`;不要只依赖 `source/执行约束.md` 的自然语言约束。",
779
- "- 本仓库对 openCode 等主会话的定位是 **Compatibility / Operator Assist**:主会话编排 `loop-agent` / `agent-worker` CLI 与只读诊断,**不是**默认实现 agent。",
782
+ "- 本仓库对 openCode 等主会话的定位是 **Compatibility / Operator Assist**:主会话编排 CLI 与只读诊断,**不是**默认实现 agent。",
780
783
  "",
781
784
  "### 主会话硬约束(Compatibility / Operator Assist)",
782
785
  "",
783
- "主会话(含 openCode、Cursor Chat、其他宿主 agent)必须遵守以下硬约束;skills 与本文件是纪律文档,**不能**替代 `task.json` / DAG `writeSet` / runtime 执法。",
786
+ "主会话(含 openCode、Cursor Chat、其他宿主 agent)= **operator-only**;skills 与本文件是纪律文档,**不能**替代 `task.json` / DAG `writeSet` / runtime 执法。",
784
787
  "",
785
788
  "| 类别 | 规则 |",
786
789
  "|---|---|",
787
- "| **角色** | 主会话 = **operator-only**,不是 implementer。 |",
788
- "| **允许** | 调用已发布 `loop-agent` / `agent-worker` CLI;只读 `status` / `dag status` / `dag doctor` / `dag report` / `inspect` / `agent-worker observe`;准备或修正任务源 `source/*` 与 `task.json` 边界;记录 human gate;跑 shell 验证与 handoff。 |",
789
- "| **禁止** | 绕过 `loop-agent` / `agent-worker` CLI,用宿主 Edit/Write/ApplyPatch 等直接改业务实现;在 CLI/DAG 失败后「救火改文件」;用聊天自述代替 shell 验证完成声明。 |",
790
- "| **失败时只允许** | `loop-agent dag doctor` / `dag report` / `dag reconcile-run`(及适用的 `agent-worker task reconcile` / `pool mark-failed`);记 human gate;修正 **任务源 / task.json / DAG 包** 后 re-validate 并 **经 CLI 重跑**。 |",
791
- "| **实现写入** | 业务代码与功能修复 **只** 经受治理 DAG writer(`implement-pi` / `repair-pi` 等)通过 `dag run-task` / `run-dag`(或 worker 子进程调用的同一路径)完成。 |",
790
+ "| **允许** | 已发布 `loop-agent` / `agent-worker` CLI;只读 status/doctor/report/inspect/observe;准备 `source/*` 与 `task.json` 边界;human gate;shell 验证与 handoff。 |",
791
+ "| **禁止** | 绕过 CLI 用宿主 Edit/Write/ApplyPatch 直接改业务实现;CLI/DAG 失败后「救火改文件」;用聊天自述代替 shell 验证。 |",
792
+ "| **失败时** | `dag doctor` / `dag report` / `dag reconcile-run`(及适用 worker reconcile);修正任务源/`task.json`/DAG 后经 CLI 重跑。 |",
793
+ "| **实现写入** | 业务代码 **只** 经受治理 DAG writer(`implement-pi` / `repair-pi`)经 `dag run-task` / `run-dag`。 |",
792
794
  "",
793
795
  "**永远不要**:`loop-agent` / `agent-worker` 失败 ⇒ 主会话直接改仓库实现。",
794
796
  "",
795
797
  "### 自然语言入口路由",
796
798
  "",
797
- "用户用中文表达意图时,按以下三类入口路由;只读与写入必须严格区分。",
798
- "",
799
799
  "| 用户表达 | 入口 | 执行动作 |",
800
800
  "|---|---|---|",
801
801
  "| loop-agent 初始化 / loop agent 初始化 / loop agent初始化 / 初始化 loop-agent | 初始化 | 完成确定性初始化闭环 |",
802
802
  "| 初始化更新校验 / 检查初始化更新 / loop-agent 初始化更新校验 / loop agent初始化更新校验 | 更新校验 | 只读报告,不写入 |",
803
- "| 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 升级对齐 | 自动应用确定性安全动作;活跃运行或人工决策时零写入 |",
804
- "| 初始化安全更新 / 应用初始化更新 / loop-agent 初始化安全更新 / loop agent初始化安全更新 | 安全更新 | check-update 后只执行确定性安全动作 |",
803
+ "| 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 升级对齐 | 自动应用确定性安全动作;活跃 DAG/Worker 或人工决策时零写入 |",
804
+ "| 初始化安全更新 / 应用初始化更新 / loop-agent 初始化安全更新 / loop agent初始化安全更新 | 安全更新 | check-update,再只执行确定性安全动作 |",
805
805
  "",
806
- "**更新校验(只读)**:用户说“初始化更新校验”“检查初始化更新”时,只读执行下面命令,汇报 deterministic actions、model merge、human decisions 和 next steps。**这一步不得自动执行 `apply-safe` 或模型合并。**",
806
+ "**更新校验(只读)**:只读执行下面命令;**不得自动**执行 `apply-safe` 或模型合并。",
807
807
  "",
808
808
  "```bash",
809
809
  "loop-agent init check-update --repo-root . --markdown",
810
810
  "```",
811
811
  "",
812
- "**升级对齐(写入型统一入口)**:用户说“初始化对齐”“升级后对齐”时,执行 `loop-agent init reconcile --repo-root .`。该入口不自动 bootstrap surface、不自动模型合并;存在活跃 DAG/Worker、human decisions 或无法确认 Worker 状态时必须零写入并报告状态。",
812
+ "**升级对齐**:`loop-agent init reconcile --repo-root .`;存在活跃 DAG/Worker、human decisions 时必须零写入。",
813
813
  "",
814
- "**安全更新(写入型)**:用户明确说“初始化安全更新”“应用初始化更新”时,先 check-update,再执行确定性安全动作;surface 缺失时先 bootstrap,存在 human decisions 时停下等用户决策。model merge 任务必须带 allowed/forbidden paths,且不得自动决策。安全更新完成后必须复查 `loop-agent init doctor --repo-root .`、`loop-agent inspect --repo-root .`、`loop-agent docs audit --repo-root .` 与目标项目治理检查(如 `bash scripts/check-repo.sh`)。",
814
+ "**安全更新**:先 check-update,再 `apply-safe`;surface 缺失才 `--bootstrap-surface`。完成后复查 `init doctor` / `inspect` / `docs audit` 与目标治理检查。",
815
815
  "",
816
816
  "```bash",
817
817
  "loop-agent init check-update --repo-root . --markdown",
818
818
  "loop-agent init update --repo-root . --apply-safe",
819
819
  "```",
820
820
  "",
821
- "仅当 check-update 报告 surface 缺失时才执行 `loop-agent init update --repo-root . --bootstrap-surface`。",
822
- "",
823
821
  "### 初始化闭环",
824
822
  "",
825
823
  `当用户要求初始化 loop-agent(“初始化 loop-agent”“loop agent 初始化”“loop agent初始化”“loop-agent 初始化”),或发现 \`README.md\` / \`${input.governanceRoot}/verification-matrix.md\` 仍停留在通用初始化状态时,模型应在同一轮工作中完成初始化闭环,不要把补全文档和验证矩阵留给用户交互。`,
826
824
  "",
827
- "标准流程:",
825
+ "1. 运行 `loop-agent init instructions --repo-root .`。",
826
+ "2. 运行 `loop-agent init --repo-root . --profile full --merge`。",
827
+ `3. 基于真实项目文件补全根 \`README.md\`、\`${input.governanceRoot}/verification-matrix.md\`,必要时适配 \`scripts/ci-tests.sh\`。`,
828
+ "4. 复查 `init doctor` / `inspect` / `docs audit` 与 quick verification。",
828
829
  "",
829
- "1. 运行 `loop-agent init instructions --repo-root .` 获取完整初始化指引。",
830
- "2. 运行 `loop-agent init --repo-root . --profile full --merge` 完成确定性脚手架。",
831
- "3. 立即读取目标项目的 README、manifest/build/config 文件和顶层源码/模块目录,识别项目类型、模块、运行入口和验证命令。",
832
- `4. 基于真实项目文件补全或修正根 \`README.md\`、\`${input.governanceRoot}/verification-matrix.md\`,必要时适配 \`scripts/ci-tests.sh\`。`,
833
- "5. 运行 `loop-agent init doctor --repo-root .`、`loop-agent inspect --repo-root .`、`loop-agent docs audit --repo-root .` 和 quick verification。",
834
- "",
835
- "如果仓库信息不足,写明“尚未从仓库文件中识别到 ...”以及下一步需要的事实;不要留下泛化占位符,也不要只提示用户稍后手填。",
830
+ "信息不足时写明尚未识别到的事实;不要留下泛化占位符。",
836
831
  "",
837
832
  "### 文档收敛",
838
833
  "",
@@ -840,21 +835,10 @@ function buildManagedAgentsBlock(input) {
840
835
  "",
841
836
  "### 开始顺序",
842
837
  "",
843
- "改文件前先完成:",
844
- "",
845
- "1. 运行 `pwd`。",
846
- "2. 阅读 `README.md`。",
847
- "3. 阅读 `harness.json`。",
848
- `4. 阅读 \`${input.governanceRoot}/README.md\`。`,
849
- `5. 实现类工作继续阅读 \`${input.governanceRoot}/development-principles.md\`、\`${input.governanceRoot}/feature-workflow.md\` 和 \`${input.governanceRoot}/verification-matrix.md\`。`,
850
- `6. 涉及测试、验证声明或调试时继续阅读 \`${input.governanceRoot}/harness-methodology-tdd.md\`、\`${input.governanceRoot}/harness-methodology-verification.md\` 和 \`${input.governanceRoot}/harness-methodology-debugging.md\`。`,
851
- "7. 查看最近提交、相关 plan/progress/report,并检查 `git status --short --branch`。",
852
- "8. 运行与本次任务相关的最小基线验证。",
838
+ `pwd → \`README.md\` → \`harness.json\` → \`${input.governanceRoot}/README.md\` → 实现类再读 principles/feature-workflow/verification-matrix;测试纪律读 harness-methodology-*;\`git status\`;最小基线验证。`,
853
839
  "",
854
840
  "### Agent DAG 路径",
855
841
  "",
856
- "默认使用 Agent DAG 作为实现工作流:",
857
- "",
858
842
  "```bash",
859
843
  "loop-agent new-task <task-id> \"任务标题\"",
860
844
  "# write .harness/tasks/<task-id>/source/需求.md",
@@ -864,64 +848,40 @@ function buildManagedAgentsBlock(input) {
864
848
  "loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .",
865
849
  "```",
866
850
  "",
867
- "任务 source 是必需项。`source/需求.md` 写目标、范围、非目标、验收标准和相关链接;`source/执行约束.md` 写允许路径、禁止路径、受保护变更、不变量、预期验证和失败条件。",
868
- "",
869
- "委托写入前,还要把允许/禁止路径同步到 `.harness/tasks/<task-id>/task.json` 的 `allowedPaths` / `forbiddenPaths` 字段,并在执行前审查生成 DAG 的 writer `writeSet` 是否窄且准确。",
851
+ "`source/需求.md` 与 `source/执行约束.md` 必需;写入前同步 `task.json.allowedPaths` / `task.json.forbiddenPaths` 并审查 writer `writeSet`。",
870
852
  "",
871
853
  "凡是影响项目公共契约、执行入口、交付流水线、自动化/治理、数据模型、安全或权限模型、跨模块行为、用户可见工作流的改动,都必须在编辑实现文件前先创建任务、写好两个 source 文件、生成 DAG,并审查 DAG/writeSet。",
872
854
  "",
873
- "执行 DAG 前必须审查 profile routing、governance profile、writer 的 `writeSet`、`allowedPaths`、`forbiddenPaths`、shell verification 和 decision gate mode。不要执行占位或过宽的写入范围。",
874
- "",
875
855
  "### 任务类型路由(taskKind)",
876
856
  "",
877
857
  "- 用户明确提出后端测试、接口/API 测试、pytest,或语境明确为后端的自动化测试时,必须把 `.harness/tasks/<task-id>/task.json` 的 `taskKind` 设置为 `\"backend-test\"`,不得保留默认 `standard`。",
878
- "- `backend-test` 是 `taskKind`,不是 `--profile` 的可选值;运行 `dag run-task` 时继续使用 `--profile auto`,也可按需显式选择 `minimal` / `standard` / `reviewed` / `supervised`,不要把业务模板名当作 profile。",
858
+ "- `backend-test` 是 `taskKind`,不是 `--profile` 的可选值;运行 `dag run-task` 时继续使用 `--profile auto`。",
879
859
  "- 仅出现“自动化测试”且无法判断前后端时,先阅读任务源与目标项目技术栈再决定,禁止无条件路由到 `backend-test`。",
880
860
  "",
881
861
  "### 运行看板(只读)",
882
862
  "",
883
- "启动统一 Operator Console(含只读 Inspect):",
884
- "",
885
863
  "```bash",
886
864
  "agent-worker console serve --repo . --port 8790",
887
865
  "```",
888
866
  "",
889
- "浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;可用 `--host 0.0.0.0` 做局域网访问(无网络鉴权,仅可信网络),`--debug` 输出请求日志。不要直接暴露到公开网络。`agent-worker observe serve` 仅为兼容入口。",
867
+ "浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;不要直接暴露到公开网络。`agent-worker observe serve` 仅为兼容入口。",
890
868
  "",
891
869
  "### DAG 诊断与收口",
892
870
  "",
893
- "执行后优先使用 `loop-agent dag report --run-id <run-id> --markdown` 读取 run facts;失败或 paused run 使用 `loop-agent dag doctor --run-id <run-id> --markdown` 诊断。需要生命周期对齐时用 `loop-agent dag reconcile-run --run-id <run-id>`(先只读检查,显式 action 才写入)。失败 DAG run 不应写成成功 closeout,应使用 `loop-agent dag closeout-draft --run-id <run-id>` 生成 failure handoff,保留 what failed、evidence、classification、recommended follow-up、safe retry conditions 和 human decision needed。",
871
+ " `loop-agent dag report --run-id <run-id> --markdown` facts;失败/paused `loop-agent dag doctor --run-id <run-id> --markdown`;生命周期对齐用 `loop-agent dag reconcile-run`。失败 run `dag closeout-draft` 生成 failure handoff,不要写成成功 closeout。",
894
872
  "",
895
- "失败恢复默认序列:doctor/report → classify → reconcilereplan → CLI 重跑 → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。",
896
- "",
897
- "### 运行态与 Skills",
898
- "",
899
- "- `.harness/tasks/` 保存任务状态和 source 材料。",
900
- "- `.harness/dag-runs/` 与 `.harness/runs/` 保存运行事实;已完成事实只读。",
901
- "- `.agents/skills/` 保存目标项目本地 skill 指令;本地缺失时 runtime 可回退到 npm 包内置 skills。",
902
- "- 示例内置在工具中;优先使用 `loop-agent examples list` 和 `loop-agent examples show <name>` 查看,不默认提交到目标仓库。",
903
- "",
904
- "### 验证",
905
- "",
906
- `使用 \`${input.governanceRoot}/verification-matrix.md\` 选择验证命令。常用门禁:`,
907
- "",
908
- "```bash",
909
- "bash scripts/check-repo.sh",
910
- "bash scripts/ci-governance.sh",
911
- "bash scripts/ci-tests.sh",
912
- "bash scripts/ci.sh",
913
- "loop-agent inspect",
914
- "loop-agent doctor",
915
- "loop-agent docs audit",
916
- "```",
873
+ "恢复:doctor/report → classify → reconcile/replan → CLI 重跑 → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。",
917
874
  "",
918
- "`scripts/ci-tests.sh` 必须反映目标项目真实语言和工具链。初始化生成版本会保守探测常见入口;当已知项目专属命令时,应按目标项目实际情况适配。",
875
+ "### 运行态与验证",
919
876
  "",
920
- "Windows 上运行 `scripts/*.sh` 时使用 Git Bash 或兼容 Bash。实际文件操作使用平台原生路径;`/` 仅用于稳定仓库引用、Markdown/JSON 证据引用和 glob 约定。",
877
+ "- `.harness/tasks/`、`.harness/dag-runs/`、`.harness/runs/` 保存运行事实;已完成事实只读。",
878
+ "- `.agents/skills/` 为本地 skills;缺失时可回退 npm 包内置。",
879
+ `- 验证命令选择:\`${input.governanceRoot}/verification-matrix.md\`。常用:\`bash scripts/check-repo.sh\`、\`bash scripts/ci.sh\`、\`loop-agent inspect\`、\`loop-agent doctor\`、\`loop-agent docs audit\`。`,
880
+ "- `scripts/ci-tests.sh` 必须反映目标项目真实工具链。Windows 脚本用 Git Bash;仓库引用用 `/`。",
921
881
  "",
922
882
  "### 交接",
923
883
  "",
924
- `较大的交接应说明改了什么、为什么这样改、验证命令和结果、影响到的 docs/tests/scripts/contracts、剩余风险和下一步。长期结论应进入 \`${input.governanceRoot}/progress\`、\`${input.governanceRoot}/reports\`、\`${input.governanceRoot}/exec-plans\`、\`${input.governanceRoot}/decisions\`、测试、脚本或模板。`,
884
+ `交接写清变更、原因、验证证据、影响面、风险与下一步;长期结论进入 \`${input.governanceRoot}/progress\`、reportsexec-plansdecisions。`,
925
885
  MANAGED_BLOCK_END,
926
886
  ].join("\n");
927
887
  }
@@ -1373,6 +1333,10 @@ async function buildDesiredSurfaceContent(input) {
1373
1333
  });
1374
1334
  if (generated !== undefined)
1375
1335
  return { content: generated };
1336
+ const mappedSourcePath = path.join(input.assetRoot, "docs", coreDocSourceForTarget(doc));
1337
+ if (await exists(mappedSourcePath)) {
1338
+ return { content: await readFile(mappedSourcePath, "utf-8"), sourcePath: mappedSourcePath };
1339
+ }
1376
1340
  }
1377
1341
  const sourcePath = path.join(input.assetRoot, manifestPath);
1378
1342
  if (await exists(sourcePath))
@@ -1582,6 +1546,10 @@ async function expectedLegacyInitFileSha(input) {
1582
1546
  if (!input.legacyPath.startsWith("docs/") && !input.legacyPath.startsWith("skills/")) {
1583
1547
  return undefined;
1584
1548
  }
1549
+ if (input.legacyPath.startsWith("docs/")) {
1550
+ const doc = input.legacyPath.slice("docs/".length);
1551
+ return fileSha256IfExists(path.join(input.assetRoot, "docs", coreDocSourceForTarget(doc)));
1552
+ }
1585
1553
  return fileSha256IfExists(path.join(input.assetRoot, input.legacyPath));
1586
1554
  }
1587
1555
  function collectSafeRetiredDirectories(paths) {
@@ -1912,6 +1880,7 @@ function buildTargetDocsReadme(input) {
1912
1880
  "- `progress/README.md` - 进度交接日志 / progress handoff logs",
1913
1881
  "- `reports/README.md` - 验证与审计报告 / verification and audit reports",
1914
1882
  "- `decisions/README.md` - 架构决策 / architecture decisions",
1883
+ "- `templates/README.md` - 模板入口与类别说明 / template entrypoint and category guide",
1915
1884
  "- `templates/` - 可复用的计划、报告与 DAG 模板 / reusable planning, reporting, and DAG templates",
1916
1885
  "- `templates/production-readiness-checklist.md` - 低/中风险单仓库 DAG 任务的 production readiness 检查清单 / production readiness checklist for low/medium-risk single-repo DAG work",
1917
1886
  "- `templates/worker-dogfood-setup.md` - 发布控制器下的真实 Worker sample setup / real Worker sample setup with a published controller",
@@ -2016,10 +1985,34 @@ function buildTargetDevelopmentPrinciples(input) {
2016
1985
  "9. Do not add placeholders as completed implementation.",
2017
1986
  "10. Prefer existing local project patterns before adding new abstractions.",
2018
1987
  "",
1988
+ "## Task Slicing: Vertical Tracer Bullets First",
1989
+ "",
1990
+ "Principle 1 covers **granularity** (one bounded block). This section covers **shape**: each slice should cross the real integration layers the work needs and leave an independently verifiable narrow loop.",
1991
+ "",
1992
+ "- Every slice needs its own acceptance criteria, verification commands, and failure conditions.",
1993
+ "- Prefer vertical tracer bullets over horizontal layering. Paths like \"schema → API → UI → tests\" are *possible* examples only; do not assume every project has those layers.",
1994
+ "- Horizontal anti-patterns: finish all of one layer before the next; or write every test first, then implement everything.",
1995
+ "- For behavior changes, use one failing test → minimal implementation → green → next behavior. Do not batch all RED then all GREEN.",
1996
+ "- Split large features into multiple independently runnable tasks/DAGs instead of one oversized writer across every layer.",
1997
+ "",
1998
+ "### Autonomy vs Governance (independent layers)",
1999
+ "",
2000
+ "| Dimension | Meaning | How to decide |",
2001
+ "|---|---|---|",
2002
+ "| Autonomy | Whether the slice needs synchronous human judgment, external access, or non-automatable decisions | Declare AFK/HITL in Contract, open questions, or human gate signals |",
2003
+ "| Governance | How strong review, repair, write-set, and verification gates must be | Default `--profile auto`; route to `minimal` / `standard` / `reviewed` / `supervised` by risk and delivery signals |",
2004
+ "",
2005
+ "- AFK does not mean `minimal` is required; ordinary automatable work may land on `standard` or `reviewed`.",
2006
+ "- HITL does not mean choosing `supervised` alone yields a correct human decision; require a concrete pause reason / decision gate.",
2007
+ "- Use `minimal` only for a single narrow writer, deterministic post shell verification, and no escalation signals.",
2008
+ "- High-risk, public-contract, init/runtime/CI/governance surface, or real human judgment gates should escalate via `auto`, or explicitly choose `reviewed` / `supervised`.",
2009
+ "",
2019
2010
  "## Target Project Adaptation",
2020
2011
  "",
2021
2012
  "The initialized scripts provide language-neutral governance. The initialization model should adapt project-specific verification commands after reading the target project's actual files and toolchain.",
2022
2013
  "",
2014
+ "Repo-local skills live under `.agents/skills/` (mirrored from the package `skills/` fallback).",
2015
+ "",
2023
2016
  ].join("\n");
2024
2017
  }
2025
2018
  function buildTargetFeatureWorkflow(input) {
@@ -2202,6 +2195,28 @@ function buildGovernanceDirectoryReadme(input) {
2202
2195
  .split("/")
2203
2196
  .map((part) => part.replace(/-/g, " "))
2204
2197
  .join(" / ");
2198
+ if (input.dir === "decisions" || input.dir.endsWith("/decisions")) {
2199
+ return [
2200
+ `# ${title}`,
2201
+ "",
2202
+ `This directory stores ${input.projectName} architecture decision records (ADRs).`,
2203
+ "",
2204
+ "## When to write an ADR",
2205
+ "",
2206
+ "Write an ADR only when **all three** are true:",
2207
+ "",
2208
+ "1. **Hard to reverse** — changing the decision later has meaningful cost.",
2209
+ "2. **Surprising without context** — a future reader will ask why this path was chosen.",
2210
+ "3. **Real trade-off** — genuine alternatives existed and one was chosen for specific reasons.",
2211
+ "",
2212
+ "If any condition is missing, skip the ADR. Temporary scheduling, obvious implementation choices, and facts with no alternatives do not belong here.",
2213
+ "",
2214
+ "Use the ADR template under this governance root's `templates/adr.md` (or the package template when projecting). Update this README index when adding a new ADR.",
2215
+ "",
2216
+ "初始化只创建目录契约,不复制 loop-agent 源仓库的历史决策正文。目标项目中的具体 ADR 应由后续任务按真实取舍生成。",
2217
+ "",
2218
+ ].join("\n");
2219
+ }
2205
2220
  return [
2206
2221
  `# ${title}`,
2207
2222
  "",
@@ -2347,11 +2362,11 @@ export async function initializeLoopAgentProject(options) {
2347
2362
  skipped,
2348
2363
  });
2349
2364
  for (const doc of CORE_DOC_FILES) {
2350
- const generated = buildGeneratedCoreDoc({ doc, projectName, governanceRoot });
2365
+ const generated = buildGeneratedCoreDoc({ doc: doc.target, projectName, governanceRoot });
2351
2366
  if (generated) {
2352
2367
  await writeTextIfMissing({
2353
2368
  repoRoot,
2354
- relativePath: path.join(governanceRoot, doc),
2369
+ relativePath: path.join(governanceRoot, doc.target),
2355
2370
  content: generated,
2356
2371
  merge,
2357
2372
  written,
@@ -2362,8 +2377,8 @@ export async function initializeLoopAgentProject(options) {
2362
2377
  await copyFileIfMissing({
2363
2378
  assetRoot,
2364
2379
  repoRoot,
2365
- sourceRelativePath: path.join("docs", doc),
2366
- targetRelativePath: path.join(governanceRoot, doc),
2380
+ sourceRelativePath: path.join("docs", doc.source),
2381
+ targetRelativePath: path.join(governanceRoot, doc.target),
2367
2382
  merge,
2368
2383
  written,
2369
2384
  skipped,
@@ -1,5 +1,6 @@
1
- import { readFile, readdir } from 'node:fs/promises';
2
- import path from 'node:path';
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { buildContextUsageReport, formatContextUsageReportHuman, parseStatsContextArgs, } from "../application/context-usage/skill-resolution-stats.js";
3
4
  function percentile(values, p) {
4
5
  if (values.length === 0)
5
6
  return null;
@@ -12,8 +13,8 @@ function formatMs(ms) {
12
13
  return `${(ms / 60_000).toFixed(1)} min`;
13
14
  return `${(ms / 1_000).toFixed(1)}s`;
14
15
  }
15
- export async function runStats(repoRoot) {
16
- const tasksDir = path.join(repoRoot, '.harness', 'tasks');
16
+ async function runLegacyStepStats(repoRoot) {
17
+ const tasksDir = path.join(repoRoot, ".harness", "tasks");
17
18
  const stepRecords = {};
18
19
  let taskDirs = [];
19
20
  try {
@@ -21,20 +22,20 @@ export async function runStats(repoRoot) {
21
22
  taskDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
22
23
  }
23
24
  catch {
24
- console.log('no task directories found');
25
+ console.log("no task directories found");
25
26
  return;
26
27
  }
27
28
  for (const taskId of taskDirs) {
28
- const jsonlPath = path.join(tasksDir, taskId, 'logs', 'executor.jsonl');
29
+ const jsonlPath = path.join(tasksDir, taskId, "logs", "executor.jsonl");
29
30
  try {
30
- const content = await readFile(jsonlPath, 'utf-8');
31
+ const content = await readFile(jsonlPath, "utf-8");
31
32
  for (const line of content.split(/\r?\n/)) {
32
33
  const trimmed = line.trim();
33
34
  if (!trimmed)
34
35
  continue;
35
36
  try {
36
37
  const record = JSON.parse(trimmed);
37
- if (typeof record.step === 'string' && typeof record.durationMs === 'number') {
38
+ if (typeof record.step === "string" && typeof record.durationMs === "number") {
38
39
  const step = record.step;
39
40
  if (!stepRecords[step])
40
41
  stepRecords[step] = [];
@@ -55,7 +56,7 @@ export async function runStats(repoRoot) {
55
56
  // skip missing executor.jsonl
56
57
  }
57
58
  }
58
- const stepOrder = ['analyze', 'plan', 'spec', 'implement', 'verify', 'retrospective'];
59
+ const stepOrder = ["analyze", "plan", "spec", "implement", "verify", "retrospective"];
59
60
  let totalRecords = 0;
60
61
  let totalTimedOut = 0;
61
62
  for (const step of stepOrder) {
@@ -75,10 +76,38 @@ export async function runStats(repoRoot) {
75
76
  console.log(` max : ${formatMs(Math.max(...durations))}`);
76
77
  if (timedOutCount > 0)
77
78
  console.log(` timedOut: ${timedOutCount}`);
78
- console.log('');
79
+ console.log("");
79
80
  }
80
- console.log(`${'='.repeat(40)}`);
81
+ console.log(`${"=".repeat(40)}`);
81
82
  console.log(`total runs: ${totalRecords}`);
82
83
  if (totalTimedOut > 0)
83
84
  console.log(`total timed out: ${totalTimedOut}`);
84
85
  }
86
+ async function runContextStats(repoRoot, args) {
87
+ const parsed = parseStatsContextArgs(args);
88
+ const report = await buildContextUsageReport(repoRoot, { last: parsed.last });
89
+ if (parsed.json) {
90
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
91
+ }
92
+ else {
93
+ console.log(formatContextUsageReportHuman(report));
94
+ }
95
+ if (!report.ok)
96
+ process.exitCode = 1;
97
+ }
98
+ /**
99
+ * `stats` — legacy step-duration histogram (default, no subcommand).
100
+ * `stats context [--last N] [--json]` — skill-resolution / outcome telemetry.
101
+ */
102
+ export async function runStats(repoRoot, args = []) {
103
+ const [subcommand, ...rest] = args;
104
+ if (subcommand === undefined) {
105
+ await runLegacyStepStats(repoRoot);
106
+ return;
107
+ }
108
+ if (subcommand === "context") {
109
+ await runContextStats(repoRoot, rest);
110
+ return;
111
+ }
112
+ throw new Error(`unknown stats subcommand: ${subcommand}\nusage: stats | stats context [--last <n>] [--json]`);
113
+ }
@@ -17,7 +17,7 @@ import { formatFrontendReviewContextStdout, runFrontendReviewContextGate, } from
17
17
  import { materializeFrontendLintAssessment, materializeFrontendLintBaseline, } from "../workflows/dag/frontend-lint-baseline.js";
18
18
  import { formatTraceabilityGateStdout, materializeBackendTestCaseManifest, runBackendTestTraceabilityGate, } from "../workflows/dag/backend-test-case-manifest.js";
19
19
  import { materializeBackendTestExecutionContract } from "../workflows/dag/backend-test-execution-contract.js";
20
- import { materializeBackendTestResultFromRunDir, parseJunitXml } from "../workflows/dag/backend-test-result-contract.js";
20
+ import { materializeBackendTestResultFromRunDir, parsePytestHtmlReport } from "../workflows/dag/backend-test-result-contract.js";
21
21
  import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, hasBlockingBackendMarkdownSafetyFindings, inspectBackendTestEnvironment, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
22
22
  import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
23
23
  import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
@@ -102,7 +102,12 @@ export async function executeShellCommand(input) {
102
102
  const startedAt = Date.now();
103
103
  const injectedEnv = input.dagRunMeta
104
104
  ? {
105
- HARNESS_DAG_RUN_DIR: input.dagRunMeta.runDir,
105
+ // Git Bash treats backslashes as escape characters inside double
106
+ // quotes. On Windows, path.join() produces backslash runDirs like
107
+ // D:\\...\\active\\<runId>, which corrupt "${HARNESS_DAG_RUN_DIR}/..."
108
+ // expansions. Normalize to POSIX separators so bash sees a stable
109
+ // path regardless of host platform.
110
+ HARNESS_DAG_RUN_DIR: input.dagRunMeta.runDir.replaceAll(path.sep, "/"),
106
111
  HARNESS_DAG_RUN_ID: input.dagRunMeta.runId,
107
112
  }
108
113
  : undefined;
@@ -384,10 +389,18 @@ async function executeBackendTestPipeline(input, meta) {
384
389
  'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
385
390
  'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
386
391
  'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" >&2; exit 127; fi',
387
- `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 "\${PYTHON_BIN}" -m pytest ${pytestTargets} -v -p no:cacheprovider -o junit_logging=all -o junit_log_passing_tests=true --junitxml="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.junit.xml"`,
392
+ // Run pytest exactly once and emit the native pytest-html
393
+ // self-contained report. The HTML/facts renderers read per-case
394
+ // captured stdout (HTTP_REQUEST/HTTP_RESPONSE) directly from the
395
+ // pytest-html data-jsonblob island, so a JUnit XML report is no
396
+ // longer generated.
397
+ `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 "\${PYTHON_BIN}" -m pytest ${pytestTargets} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
388
398
  "STATUS=$?",
389
399
  'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
390
- 'if { [ "${STATUS}" -eq 0 ] || [ "${STATUS}" -eq 1 ]; } && [ -s "${HARNESS_DAG_RUN_DIR}/reports/backend-test.junit.xml" ]; then exit 0; fi',
400
+ // exit 0 (all pass) or 1 (assertion failures) with a valid
401
+ // pytest-html report are reportable; collection errors / crashes
402
+ // (exit >= 2) or a missing report surface as a pipeline error below.
403
+ 'if { [ "${STATUS}" -eq 0 ] || [ "${STATUS}" -eq 1 ]; } && [ -s "${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" ]; then exit 0; fi',
391
404
  'exit "${STATUS}"',
392
405
  ].join("; ");
393
406
  const results = await executePipelineCommands(input, meta, [pytestCommand]);
@@ -396,11 +409,11 @@ async function executeBackendTestPipeline(input, meta) {
396
409
  return { ok: false, stdout: results.map((result) => result.stdout).join("\n"), stderr: failure.stderr, failureCategory: failure.failureCategory, durationMs: Date.now() - started };
397
410
  }
398
411
  const reportsDir = path.join(meta.runDir, "reports");
399
- const junitContent = await readFile(path.join(reportsDir, "backend-test.junit.xml"), "utf8");
412
+ const pytestHtmlContent = await readFile(path.join(reportsDir, "backend-test.html"), "utf8");
400
413
  const pytestExitCode = Number.parseInt((await readFile(path.join(reportsDir, "backend-test-pytest-exit.txt"), "utf8")).trim(), 10);
401
414
  if (![0, 1].includes(pytestExitCode))
402
415
  throw new Error(`pytest did not complete with a reportable exit code: ${pytestExitCode}`);
403
- const parsed = parseJunitXml(junitContent);
416
+ const parsed = parsePytestHtmlReport(pytestHtmlContent);
404
417
  const cases = await collectBackendTestHumanCaseCatalog(input.cwd);
405
418
  const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
406
419
  const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
@@ -413,7 +426,7 @@ async function executeBackendTestPipeline(input, meta) {
413
426
  traceabilitySummary,
414
427
  });
415
428
  const htmlPath = await writeRunReport(meta.runDir, "backend-test.html", htmlContent);
416
- const facts = renderBackendTestFacts({ parsed, cases, pytestExitCode, junitRelativePath: "reports/backend-test.junit.xml", htmlRelativePath: "reports/backend-test.html", junitContent, htmlContent, caseValidationSummary, traceabilitySummary });
429
+ const facts = renderBackendTestFacts({ parsed, cases, pytestExitCode, htmlRelativePath: "reports/backend-test.html", htmlContent, caseValidationSummary, traceabilitySummary });
417
430
  const markdownPath = await writeRunReport(meta.runDir, "backend-test.md", facts);
418
431
  const factsPath = await writeRunReport(meta.runDir, "backend-test-facts.md", facts);
419
432
  const sanitizedOutputs = results.map((result) => redactBackendTestOutput(result.stdout));