@tea-agent/loop-agent 0.4.0 → 0.5.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 (106) hide show
  1. package/AGENTS.md +142 -142
  2. package/CHANGELOG.md +98 -106
  3. package/README.md +195 -195
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/validate-dag.js +14 -1
  7. package/dist/commands/init.js +677 -489
  8. package/dist/commands/loop-benchmark.js +11 -11
  9. package/dist/commands/pi-reuse-benchmark.js +16 -16
  10. package/dist/executors/config-core.js +3 -2
  11. package/dist/executors/cursor-executor.js +1 -1
  12. package/dist/executors/model-routing.js +43 -0
  13. package/dist/governance/manifest-types.js +9 -1
  14. package/dist/task/runtime.js +27 -27
  15. package/dist/worker/pool/run-store.js +9 -1
  16. package/dist/workflows/dag/canvas-observer.js +275 -275
  17. package/dist/workflows/dag/init-hybrid.js +4 -13
  18. package/dist/workflows/dag/skill-instructions.js +4 -0
  19. package/dist/workflows/dag/types.js +1 -1
  20. package/dist/workflows/dag/validate.js +3 -2
  21. package/docs/README.md +72 -72
  22. package/docs/agent-dag-recovery-playbook.md +184 -184
  23. package/docs/agent-dag-runner.md +42 -42
  24. package/docs/architecture/runtime-boundaries.md +147 -147
  25. package/docs/cursor-executor-usage.md +25 -25
  26. package/docs/decisions/README.md +3 -3
  27. package/docs/design/README.md +36 -36
  28. package/docs/development-principles.md +73 -73
  29. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
  30. package/docs/exec-plans/README.md +6 -6
  31. package/docs/exec-plans/active/README.md +7 -7
  32. package/docs/exec-plans/completed/README.md +19 -19
  33. package/docs/feature-workflow.md +186 -186
  34. package/docs/harness-methodology-debugging.md +153 -153
  35. package/docs/harness-methodology-tdd.md +130 -130
  36. package/docs/harness-methodology-verification.md +27 -27
  37. package/docs/init-surface.manifest.json +199 -175
  38. package/docs/loop-agent-harness.md +42 -42
  39. package/docs/production-readiness.md +96 -96
  40. package/docs/progress/README.md +3 -3
  41. package/docs/reports/README.md +5 -5
  42. package/docs/skills/README.md +6 -6
  43. package/docs/skills/vetted-skill-registry.md +26 -26
  44. package/docs/templates/adr.md +60 -60
  45. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  46. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  47. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  48. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  49. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  50. package/docs/templates/agent-dag-report.schema.json +454 -454
  51. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  52. package/docs/templates/agent-dag.base.json +195 -195
  53. package/docs/templates/agent-dag.final-verification.json +190 -190
  54. package/docs/templates/agent-dag.schema.json +316 -316
  55. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  56. package/docs/templates/exec-plan.md +64 -64
  57. package/docs/templates/feature-spec.md +53 -53
  58. package/docs/templates/hybrid-dag.json +193 -193
  59. package/docs/templates/init-evolution-review.md +33 -33
  60. package/docs/templates/production-readiness-checklist.md +57 -57
  61. package/docs/templates/progress-log.md +17 -17
  62. package/docs/templates/project-start-checklist.md +9 -9
  63. package/docs/templates/qa-report.md +48 -48
  64. package/docs/templates/sprint-contract.md +29 -29
  65. package/docs/verification-matrix.md +41 -41
  66. package/examples/decision-gate-agent-dag.json +123 -123
  67. package/examples/example-dag.json +51 -51
  68. package/examples/hybrid-loop-agent-dag.json +194 -194
  69. package/harness.json +69 -94
  70. package/package.json +66 -66
  71. package/skills/ai-engineering-context/SKILL.md +48 -48
  72. package/skills/code-review-core/SKILL.md +20 -20
  73. package/skills/codebase-scout/SKILL.md +19 -19
  74. package/skills/init-capability-evolution/SKILL.md +69 -69
  75. package/skills/loop-agent/SKILL.md +147 -147
  76. package/skills/loop-agent/references/README.md +67 -67
  77. package/skills/loop-agent/references/command-reference.md +403 -403
  78. package/skills/loop-agent/references/harness-policy.md +259 -259
  79. package/skills/loop-agent/references/hybrid-dag.md +216 -216
  80. package/skills/loop-agent/references/learned/README.md +21 -21
  81. package/skills/loop-agent/references/long-running-loop.md +59 -59
  82. package/skills/loop-agent/references/model-routing.md +36 -36
  83. package/skills/loop-agent/references/multi-worktree.md +54 -54
  84. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  85. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  86. package/skills/loop-agent/references/pi-prompt.md +23 -23
  87. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
  88. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  89. package/skills/loop-agent/references/task-workflow.md +84 -84
  90. package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
  91. package/skills/requesting-code-review/SKILL.md +101 -101
  92. package/skills/requesting-code-review/code-reviewer.md +168 -168
  93. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  94. package/skills/systematic-debugging/SKILL.md +296 -296
  95. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  96. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  97. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  98. package/skills/systematic-debugging/find-polluter.sh +63 -63
  99. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  100. package/skills/systematic-debugging/test-academic.md +14 -14
  101. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  102. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  103. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  104. package/skills/test-driven-development/SKILL.md +20 -20
  105. package/skills/verification-before-completion/SKILL.md +154 -154
  106. package/skills/webapp-testing/SKILL.md +19 -19
@@ -6,6 +6,8 @@ import { copyDir } from "../shared/copy-dir.js";
6
6
  import { loadHarnessManifest } from "../governance/harness.js";
7
7
  const MANAGED_BLOCK_START = "<!-- LOOP_AGENT_INIT_START -->";
8
8
  const MANAGED_BLOCK_END = "<!-- LOOP_AGENT_INIT_END -->";
9
+ const GITIGNORE_BLOCK_START = "# LOOP_AGENT_INIT_START";
10
+ const GITIGNORE_BLOCK_END = "# LOOP_AGENT_INIT_END";
9
11
  const INIT_SURFACE_STATE_PATH = ".harness/init-surface.json";
10
12
  const CORE_DOC_FILES = [
11
13
  "README.md",
@@ -90,474 +92,474 @@ const COMPAT_PROMPTS = {
90
92
  ].join("\n"),
91
93
  };
92
94
  const GOVERNANCE_ROOT_TOKEN = "__LOOP_AGENT_GOVERNANCE_ROOT__";
93
- const INIT_CHECK_ENGINEERING_STRUCTURE_SH = `#!/usr/bin/env bash
94
- set -euo pipefail
95
-
96
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
97
- cd "\${ROOT_DIR}"
98
-
99
- required_paths=(
100
- "README.md"
101
- "harness.json"
102
- "AGENTS.md"
103
- "${GOVERNANCE_ROOT_TOKEN}/README.md"
104
- "${GOVERNANCE_ROOT_TOKEN}/development-principles.md"
105
- "${GOVERNANCE_ROOT_TOKEN}/feature-workflow.md"
106
- "${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
107
- "${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
108
- "${GOVERNANCE_ROOT_TOKEN}/templates"
109
- "skills/loop-agent/SKILL.md"
110
- ".harness/prompts/analyze.md"
111
- ".harness/prompts/plan.md"
112
- ".harness/tasks"
113
- ".harness/dag-runs/active"
114
- ".harness/dag-runs/completed"
115
- ".harness/dag-runs/paused"
116
- ".harness/runs/active"
117
- ".harness/runs/completed"
118
- ".harness/runs/failed"
119
- "scripts/check-engineering-structure.sh"
120
- "scripts/check-doc-index.sh"
121
- "scripts/check-doc-links.sh"
122
- "scripts/check-active-plan-status.sh"
123
- "scripts/check-exec-plan-index-sync.sh"
124
- "scripts/check-harness-runtime-clean.sh"
125
- "scripts/check-architecture-boundaries.sh"
126
- "scripts/check-skill-entry.sh"
127
- "scripts/check-repo.sh"
128
- "scripts/ci-governance.sh"
129
- "scripts/ci-tests.sh"
130
- "scripts/ci.sh"
131
- )
132
-
133
- missing=()
134
- for item in "\${required_paths[@]}"; do
135
- if [[ ! -e "\${item}" ]]; then
136
- missing+=("\${item}")
137
- fi
138
- done
139
-
140
- if (( \${#missing[@]} > 0 )); then
141
- echo "loop-agent 初始化检查失败,缺少必要路径:"
142
- for item in "\${missing[@]}"; do
143
- echo " - \${item}"
144
- done
145
- exit 1
146
- fi
147
-
148
- echo "loop-agent 工程结构检查通过"
95
+ const INIT_CHECK_ENGINEERING_STRUCTURE_SH = `#!/usr/bin/env bash
96
+ set -euo pipefail
97
+
98
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
99
+ cd "\${ROOT_DIR}"
100
+
101
+ required_paths=(
102
+ "README.md"
103
+ "harness.json"
104
+ "AGENTS.md"
105
+ "${GOVERNANCE_ROOT_TOKEN}/README.md"
106
+ "${GOVERNANCE_ROOT_TOKEN}/development-principles.md"
107
+ "${GOVERNANCE_ROOT_TOKEN}/feature-workflow.md"
108
+ "${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
109
+ "${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
110
+ "${GOVERNANCE_ROOT_TOKEN}/templates"
111
+ "skills/loop-agent/SKILL.md"
112
+ ".harness/prompts/analyze.md"
113
+ ".harness/prompts/plan.md"
114
+ ".harness/tasks"
115
+ ".harness/dag-runs/active"
116
+ ".harness/dag-runs/completed"
117
+ ".harness/dag-runs/paused"
118
+ ".harness/runs/active"
119
+ ".harness/runs/completed"
120
+ ".harness/runs/failed"
121
+ "scripts/check-engineering-structure.sh"
122
+ "scripts/check-doc-index.sh"
123
+ "scripts/check-doc-links.sh"
124
+ "scripts/check-active-plan-status.sh"
125
+ "scripts/check-exec-plan-index-sync.sh"
126
+ "scripts/check-harness-runtime-clean.sh"
127
+ "scripts/check-architecture-boundaries.sh"
128
+ "scripts/check-skill-entry.sh"
129
+ "scripts/check-repo.sh"
130
+ "scripts/ci-governance.sh"
131
+ "scripts/ci-tests.sh"
132
+ "scripts/ci.sh"
133
+ )
134
+
135
+ missing=()
136
+ for item in "\${required_paths[@]}"; do
137
+ if [[ ! -e "\${item}" ]]; then
138
+ missing+=("\${item}")
139
+ fi
140
+ done
141
+
142
+ if (( \${#missing[@]} > 0 )); then
143
+ echo "loop-agent 初始化检查失败,缺少必要路径:"
144
+ for item in "\${missing[@]}"; do
145
+ echo " - \${item}"
146
+ done
147
+ exit 1
148
+ fi
149
+
150
+ echo "loop-agent 工程结构检查通过"
149
151
  `;
150
- const INIT_CHECK_DOC_INDEX_SH = `#!/usr/bin/env bash
151
- set -euo pipefail
152
-
153
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
154
- cd "\${ROOT_DIR}"
155
-
156
- required_docs=(
157
- "${GOVERNANCE_ROOT_TOKEN}/README.md"
158
- "${GOVERNANCE_ROOT_TOKEN}/development-principles.md"
159
- "${GOVERNANCE_ROOT_TOKEN}/feature-workflow.md"
160
- "${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
161
- "${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
162
- "${GOVERNANCE_ROOT_TOKEN}/loop-agent-harness.md"
163
- "${GOVERNANCE_ROOT_TOKEN}/harness-methodology-tdd.md"
164
- "${GOVERNANCE_ROOT_TOKEN}/harness-methodology-verification.md"
165
- "${GOVERNANCE_ROOT_TOKEN}/harness-methodology-debugging.md"
166
- "${GOVERNANCE_ROOT_TOKEN}/templates"
167
- "${GOVERNANCE_ROOT_TOKEN}/exec-plans/active/README.md"
168
- "${GOVERNANCE_ROOT_TOKEN}/exec-plans/completed/README.md"
169
- "${GOVERNANCE_ROOT_TOKEN}/progress/README.md"
170
- "${GOVERNANCE_ROOT_TOKEN}/reports/README.md"
171
- "${GOVERNANCE_ROOT_TOKEN}/decisions/README.md"
172
- )
173
-
174
- missing=()
175
- for item in "\${required_docs[@]}"; do
176
- if [[ ! -e "\${item}" ]]; then
177
- missing+=("\${item}")
178
- fi
179
- done
180
-
181
- if (( \${#missing[@]} > 0 )); then
182
- echo "docs 索引检查失败,缺少必要文档或目录:" >&2
183
- for item in "\${missing[@]}"; do
184
- echo " - \${item}" >&2
185
- done
186
- exit 1
187
- fi
188
-
189
- echo "docs 索引检查通过"
152
+ const INIT_CHECK_DOC_INDEX_SH = `#!/usr/bin/env bash
153
+ set -euo pipefail
154
+
155
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
156
+ cd "\${ROOT_DIR}"
157
+
158
+ required_docs=(
159
+ "${GOVERNANCE_ROOT_TOKEN}/README.md"
160
+ "${GOVERNANCE_ROOT_TOKEN}/development-principles.md"
161
+ "${GOVERNANCE_ROOT_TOKEN}/feature-workflow.md"
162
+ "${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
163
+ "${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
164
+ "${GOVERNANCE_ROOT_TOKEN}/loop-agent-harness.md"
165
+ "${GOVERNANCE_ROOT_TOKEN}/harness-methodology-tdd.md"
166
+ "${GOVERNANCE_ROOT_TOKEN}/harness-methodology-verification.md"
167
+ "${GOVERNANCE_ROOT_TOKEN}/harness-methodology-debugging.md"
168
+ "${GOVERNANCE_ROOT_TOKEN}/templates"
169
+ "${GOVERNANCE_ROOT_TOKEN}/exec-plans/active/README.md"
170
+ "${GOVERNANCE_ROOT_TOKEN}/exec-plans/completed/README.md"
171
+ "${GOVERNANCE_ROOT_TOKEN}/progress/README.md"
172
+ "${GOVERNANCE_ROOT_TOKEN}/reports/README.md"
173
+ "${GOVERNANCE_ROOT_TOKEN}/decisions/README.md"
174
+ )
175
+
176
+ missing=()
177
+ for item in "\${required_docs[@]}"; do
178
+ if [[ ! -e "\${item}" ]]; then
179
+ missing+=("\${item}")
180
+ fi
181
+ done
182
+
183
+ if (( \${#missing[@]} > 0 )); then
184
+ echo "docs 索引检查失败,缺少必要文档或目录:" >&2
185
+ for item in "\${missing[@]}"; do
186
+ echo " - \${item}" >&2
187
+ done
188
+ exit 1
189
+ fi
190
+
191
+ echo "docs 索引检查通过"
190
192
  `;
191
- const INIT_CHECK_DOC_LINKS_SH = `#!/usr/bin/env bash
192
- set -euo pipefail
193
-
194
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
195
- cd "\${ROOT_DIR}"
196
-
197
- if [[ ! -d "${GOVERNANCE_ROOT_TOKEN}" ]]; then
198
- echo "docs 链接检查失败:缺少 ${GOVERNANCE_ROOT_TOKEN}/" >&2
199
- exit 1
200
- fi
201
-
202
- missing=()
203
- while IFS= read -r -d '' file; do
204
- while IFS= read -r link; do
205
- [[ -z "\${link}" ]] && continue
206
- [[ "\${link}" == http://* || "\${link}" == https://* || "\${link}" == mailto:* || "\${link}" == "#"* ]] && continue
207
- [[ "\${link}" == *"://"* ]] && continue
208
- target="\${link%%#*}"
209
- [[ -z "\${target}" ]] && continue
210
- [[ "\${target}" == /* ]] && continue
211
- base="$(dirname "\${file}")"
212
- if [[ ! -e "\${base}/\${target}" ]]; then
213
- missing+=("\${file}: \${link}")
214
- fi
215
- done < <(grep -Eo '\\[[^]]+\\]\\([^)]+\\)' "\${file}" | sed -E 's/^.*\\(([^)]+)\\)$/\\1/' || true)
216
- done < <(find "${GOVERNANCE_ROOT_TOKEN}" -type f -name '*.md' -print0)
217
-
218
- if (( \${#missing[@]} > 0 )); then
219
- echo "docs 链接检查失败:" >&2
220
- for item in "\${missing[@]}"; do
221
- echo " - \${item}" >&2
222
- done
223
- exit 1
224
- fi
225
-
226
- echo "docs 链接检查通过"
193
+ const INIT_CHECK_DOC_LINKS_SH = `#!/usr/bin/env bash
194
+ set -euo pipefail
195
+
196
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
197
+ cd "\${ROOT_DIR}"
198
+
199
+ if [[ ! -d "${GOVERNANCE_ROOT_TOKEN}" ]]; then
200
+ echo "docs 链接检查失败:缺少 ${GOVERNANCE_ROOT_TOKEN}/" >&2
201
+ exit 1
202
+ fi
203
+
204
+ missing=()
205
+ while IFS= read -r -d '' file; do
206
+ while IFS= read -r link; do
207
+ [[ -z "\${link}" ]] && continue
208
+ [[ "\${link}" == http://* || "\${link}" == https://* || "\${link}" == mailto:* || "\${link}" == "#"* ]] && continue
209
+ [[ "\${link}" == *"://"* ]] && continue
210
+ target="\${link%%#*}"
211
+ [[ -z "\${target}" ]] && continue
212
+ [[ "\${target}" == /* ]] && continue
213
+ base="$(dirname "\${file}")"
214
+ if [[ ! -e "\${base}/\${target}" ]]; then
215
+ missing+=("\${file}: \${link}")
216
+ fi
217
+ done < <(grep -Eo '\\[[^]]+\\]\\([^)]+\\)' "\${file}" | sed -E 's/^.*\\(([^)]+)\\)$/\\1/' || true)
218
+ done < <(find "${GOVERNANCE_ROOT_TOKEN}" -type f -name '*.md' -print0)
219
+
220
+ if (( \${#missing[@]} > 0 )); then
221
+ echo "docs 链接检查失败:" >&2
222
+ for item in "\${missing[@]}"; do
223
+ echo " - \${item}" >&2
224
+ done
225
+ exit 1
226
+ fi
227
+
228
+ echo "docs 链接检查通过"
227
229
  `;
228
- const INIT_CHECK_ACTIVE_PLAN_STATUS_SH = `#!/usr/bin/env bash
229
- set -euo pipefail
230
-
231
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
232
- cd "\${ROOT_DIR}"
233
-
234
- active_dir="${GOVERNANCE_ROOT_TOKEN}/exec-plans/active"
235
- [[ -d "\${active_dir}" ]] || { echo "active plan 检查失败:缺少 \${active_dir}" >&2; exit 1; }
236
-
237
- bad=()
238
- while IFS= read -r -d '' file; do
239
- name="$(basename "\${file}")"
240
- [[ "\${name}" == "README.md" ]] && continue
241
- if grep -Eiq 'status:[[:space:]]*(completed|done|closed)|状态[::][[:space:]]*(已完成|完成|关闭)' "\${file}"; then
242
- bad+=("\${file}")
243
- fi
244
- done < <(find "\${active_dir}" -maxdepth 1 -type f -name '*.md' -print0)
245
-
246
- if (( \${#bad[@]} > 0 )); then
247
- echo "active plan 状态检查失败:已完成计划不应留在 active 目录:" >&2
248
- for item in "\${bad[@]}"; do
249
- echo " - \${item}" >&2
250
- done
251
- exit 1
252
- fi
253
-
254
- echo "active plan 状态检查通过"
230
+ const INIT_CHECK_ACTIVE_PLAN_STATUS_SH = `#!/usr/bin/env bash
231
+ set -euo pipefail
232
+
233
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
234
+ cd "\${ROOT_DIR}"
235
+
236
+ active_dir="${GOVERNANCE_ROOT_TOKEN}/exec-plans/active"
237
+ [[ -d "\${active_dir}" ]] || { echo "active plan 检查失败:缺少 \${active_dir}" >&2; exit 1; }
238
+
239
+ bad=()
240
+ while IFS= read -r -d '' file; do
241
+ name="$(basename "\${file}")"
242
+ [[ "\${name}" == "README.md" ]] && continue
243
+ if grep -Eiq 'status:[[:space:]]*(completed|done|closed)|状态[::][[:space:]]*(已完成|完成|关闭)' "\${file}"; then
244
+ bad+=("\${file}")
245
+ fi
246
+ done < <(find "\${active_dir}" -maxdepth 1 -type f -name '*.md' -print0)
247
+
248
+ if (( \${#bad[@]} > 0 )); then
249
+ echo "active plan 状态检查失败:已完成计划不应留在 active 目录:" >&2
250
+ for item in "\${bad[@]}"; do
251
+ echo " - \${item}" >&2
252
+ done
253
+ exit 1
254
+ fi
255
+
256
+ echo "active plan 状态检查通过"
255
257
  `;
256
- const INIT_CHECK_EXEC_PLAN_INDEX_SYNC_SH = `#!/usr/bin/env bash
257
- set -euo pipefail
258
-
259
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
260
- cd "\${ROOT_DIR}"
261
-
262
- required=(
263
- "${GOVERNANCE_ROOT_TOKEN}/exec-plans/README.md"
264
- "${GOVERNANCE_ROOT_TOKEN}/exec-plans/active/README.md"
265
- "${GOVERNANCE_ROOT_TOKEN}/exec-plans/completed/README.md"
266
- )
267
-
268
- for item in "\${required[@]}"; do
269
- if [[ ! -f "\${item}" ]]; then
270
- echo "exec-plan 索引同步检查失败:缺少 \${item}" >&2
271
- exit 1
272
- fi
273
- done
274
-
275
- echo "exec-plan 索引同步检查通过"
258
+ const INIT_CHECK_EXEC_PLAN_INDEX_SYNC_SH = `#!/usr/bin/env bash
259
+ set -euo pipefail
260
+
261
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
262
+ cd "\${ROOT_DIR}"
263
+
264
+ required=(
265
+ "${GOVERNANCE_ROOT_TOKEN}/exec-plans/README.md"
266
+ "${GOVERNANCE_ROOT_TOKEN}/exec-plans/active/README.md"
267
+ "${GOVERNANCE_ROOT_TOKEN}/exec-plans/completed/README.md"
268
+ )
269
+
270
+ for item in "\${required[@]}"; do
271
+ if [[ ! -f "\${item}" ]]; then
272
+ echo "exec-plan 索引同步检查失败:缺少 \${item}" >&2
273
+ exit 1
274
+ fi
275
+ done
276
+
277
+ echo "exec-plan 索引同步检查通过"
276
278
  `;
277
- const INIT_CHECK_HARNESS_RUNTIME_CLEAN_SH = `#!/usr/bin/env bash
278
- set -euo pipefail
279
-
280
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
281
- cd "\${ROOT_DIR}"
282
-
283
- failures=()
284
-
285
- count_entries() {
286
- local dir="$1"
287
- if [[ ! -d "\${dir}" ]]; then
288
- echo 0
289
- return
290
- fi
291
- find "\${dir}" -mindepth 1 -maxdepth 1 ! -name .gitkeep | wc -l | tr -d ' '
292
- }
293
-
294
- if [[ "\${HARNESS_ALLOW_ACTIVE_DAG_RUNS:-}" != "1" ]]; then
295
- active_dag_count="$(count_entries ".harness/dag-runs/active")"
296
- [[ "\${active_dag_count}" == "0" ]] || failures+=(".harness/dag-runs/active contains \${active_dag_count} entries")
297
- fi
298
-
299
- if [[ "\${HARNESS_ALLOW_ACTIVE_TOOL_RUNS:-\${HARNESS_ALLOW_ACTIVE_ONE_SHOT_RUNS:-}}" != "1" ]]; then
300
- active_run_count="$(count_entries ".harness/runs/active")"
301
- [[ "\${active_run_count}" == "0" ]] || failures+=(".harness/runs/active contains \${active_run_count} entries")
302
- fi
303
-
304
- if [[ -d artifacts ]]; then
305
- artifact_count="$(find artifacts -mindepth 1 -maxdepth 1 ! -name .gitkeep | wc -l | tr -d ' ')"
306
- [[ "\${artifact_count}" == "0" ]] || failures+=("root artifacts/ contains \${artifact_count} entries; use docs/reports or .harness run artifacts")
307
- fi
308
-
309
- if (( \${#failures[@]} > 0 )); then
310
- echo "[HARNESS RUNTIME DIRTY]" >&2
311
- for item in "\${failures[@]}"; do
312
- echo " - \${item}" >&2
313
- done
314
- exit 1
315
- fi
316
-
317
- echo "harness runtime clean check passed"
279
+ const INIT_CHECK_HARNESS_RUNTIME_CLEAN_SH = `#!/usr/bin/env bash
280
+ set -euo pipefail
281
+
282
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
283
+ cd "\${ROOT_DIR}"
284
+
285
+ failures=()
286
+
287
+ count_entries() {
288
+ local dir="$1"
289
+ if [[ ! -d "\${dir}" ]]; then
290
+ echo 0
291
+ return
292
+ fi
293
+ find "\${dir}" -mindepth 1 -maxdepth 1 ! -name .gitkeep | wc -l | tr -d ' '
294
+ }
295
+
296
+ if [[ "\${HARNESS_ALLOW_ACTIVE_DAG_RUNS:-}" != "1" ]]; then
297
+ active_dag_count="$(count_entries ".harness/dag-runs/active")"
298
+ [[ "\${active_dag_count}" == "0" ]] || failures+=(".harness/dag-runs/active contains \${active_dag_count} entries")
299
+ fi
300
+
301
+ if [[ "\${HARNESS_ALLOW_ACTIVE_TOOL_RUNS:-\${HARNESS_ALLOW_ACTIVE_ONE_SHOT_RUNS:-}}" != "1" ]]; then
302
+ active_run_count="$(count_entries ".harness/runs/active")"
303
+ [[ "\${active_run_count}" == "0" ]] || failures+=(".harness/runs/active contains \${active_run_count} entries")
304
+ fi
305
+
306
+ if [[ -d artifacts ]]; then
307
+ artifact_count="$(find artifacts -mindepth 1 -maxdepth 1 ! -name .gitkeep | wc -l | tr -d ' ')"
308
+ [[ "\${artifact_count}" == "0" ]] || failures+=("root artifacts/ contains \${artifact_count} entries; use docs/reports or .harness run artifacts")
309
+ fi
310
+
311
+ if (( \${#failures[@]} > 0 )); then
312
+ echo "[HARNESS RUNTIME DIRTY]" >&2
313
+ for item in "\${failures[@]}"; do
314
+ echo " - \${item}" >&2
315
+ done
316
+ exit 1
317
+ fi
318
+
319
+ echo "harness runtime clean check passed"
318
320
  `;
319
- const INIT_CHECK_ARCHITECTURE_BOUNDARIES_SH = `#!/usr/bin/env bash
320
- set -euo pipefail
321
-
322
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
323
- cd "\${ROOT_DIR}"
324
-
325
- boundary_doc="${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
326
- if [[ ! -f "\${boundary_doc}" ]]; then
327
- echo "architecture boundary 检查失败:缺少 \${boundary_doc}" >&2
328
- exit 1
329
- fi
330
-
331
- violations=()
332
-
333
- check_ts_import_boundary() {
334
- local from_dir="$1"
335
- local forbidden_dir="$2"
336
- local label="$3"
337
- [[ -d "\${from_dir}" && -d "\${forbidden_dir}" ]] || return 0
338
- while IFS= read -r -d '' file; do
339
- if grep -Eq "from ['\\\"][^'\\\"]*\${forbidden_dir}/|import\\(['\\\"][^'\\\"]*\${forbidden_dir}/|require\\(['\\\"][^'\\\"]*\${forbidden_dir}/" "\${file}"; then
340
- violations+=("\${label}: \${file} imports \${forbidden_dir}")
341
- fi
342
- done < <(find "\${from_dir}" -type f \\( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.mjs' -o -name '*.cjs' \\) -print0)
343
- }
344
-
345
- # These checks are intentionally optional and stack-agnostic. They activate only
346
- # when a target repository has recognizable source directories.
347
- check_ts_import_boundary "src/workflows" "src/commands" "workflow-runtime"
348
- check_ts_import_boundary "src/executors" "src/commands" "executors"
349
- check_ts_import_boundary "src/domain" "src/infrastructure" "domain"
350
-
351
- if (( \${#violations[@]} > 0 )); then
352
- echo "architecture boundary 检查失败:" >&2
353
- for item in "\${violations[@]}"; do
354
- echo " - \${item}" >&2
355
- done
356
- echo "请更新 \${boundary_doc} 中的层边界,或修正反向依赖。" >&2
357
- exit 1
358
- fi
359
-
360
- echo "architecture boundary 检查通过"
321
+ const INIT_CHECK_ARCHITECTURE_BOUNDARIES_SH = `#!/usr/bin/env bash
322
+ set -euo pipefail
323
+
324
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
325
+ cd "\${ROOT_DIR}"
326
+
327
+ boundary_doc="${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
328
+ if [[ ! -f "\${boundary_doc}" ]]; then
329
+ echo "architecture boundary 检查失败:缺少 \${boundary_doc}" >&2
330
+ exit 1
331
+ fi
332
+
333
+ violations=()
334
+
335
+ check_ts_import_boundary() {
336
+ local from_dir="$1"
337
+ local forbidden_dir="$2"
338
+ local label="$3"
339
+ [[ -d "\${from_dir}" && -d "\${forbidden_dir}" ]] || return 0
340
+ while IFS= read -r -d '' file; do
341
+ if grep -Eq "from ['\\\"][^'\\\"]*\${forbidden_dir}/|import\\(['\\\"][^'\\\"]*\${forbidden_dir}/|require\\(['\\\"][^'\\\"]*\${forbidden_dir}/" "\${file}"; then
342
+ violations+=("\${label}: \${file} imports \${forbidden_dir}")
343
+ fi
344
+ done < <(find "\${from_dir}" -type f \\( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.mjs' -o -name '*.cjs' \\) -print0)
345
+ }
346
+
347
+ # These checks are intentionally optional and stack-agnostic. They activate only
348
+ # when a target repository has recognizable source directories.
349
+ check_ts_import_boundary "src/workflows" "src/commands" "workflow-runtime"
350
+ check_ts_import_boundary "src/executors" "src/commands" "executors"
351
+ check_ts_import_boundary "src/domain" "src/infrastructure" "domain"
352
+
353
+ if (( \${#violations[@]} > 0 )); then
354
+ echo "architecture boundary 检查失败:" >&2
355
+ for item in "\${violations[@]}"; do
356
+ echo " - \${item}" >&2
357
+ done
358
+ echo "请更新 \${boundary_doc} 中的层边界,或修正反向依赖。" >&2
359
+ exit 1
360
+ fi
361
+
362
+ echo "architecture boundary 检查通过"
361
363
  `;
362
- const INIT_CHECK_SKILL_ENTRY_SH = `#!/usr/bin/env bash
363
- set -euo pipefail
364
-
365
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
366
- cd "\${ROOT_DIR}"
367
-
368
- skill_md="skills/loop-agent/SKILL.md"
369
- if [[ ! -f "\${skill_md}" ]]; then
370
- echo "skill entry 检查失败:缺少 \${skill_md}" >&2
371
- exit 1
372
- fi
373
-
374
- required_refs=(
375
- "references/harness-policy.md"
376
- "references/hybrid-dag.md"
377
- "references/verification-and-failure-handling.md"
378
- "references/command-reference.md"
379
- )
380
-
381
- missing=()
382
- for ref in "\${required_refs[@]}"; do
383
- [[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
384
- done
385
-
386
- while IFS= read -r ref; do
387
- [[ -z "\${ref}" ]] && continue
388
- [[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
389
- done < <(grep -Eo 'references/[A-Za-z0-9._/-]+\\.md' "\${skill_md}" | sort -u || true)
390
-
391
- if (( \${#missing[@]} > 0 )); then
392
- echo "skill entry 检查失败:引用的 reference 文件不存在:" >&2
393
- printf ' - %s\\n' "\${missing[@]}" >&2
394
- exit 1
395
- fi
396
-
397
- line_count="$(wc -l < "\${skill_md}" | tr -d ' ')"
398
- if [[ "\${line_count}" =~ ^[0-9]+$ && "\${line_count}" -gt 220 ]]; then
399
- echo "skill entry 检查警告:\${skill_md} 行数为 \${line_count},建议保持入口精简并把细节放入 references/" >&2
400
- fi
401
-
402
- echo "skill entry 检查通过:references ok, lines=\${line_count}"
364
+ const INIT_CHECK_SKILL_ENTRY_SH = `#!/usr/bin/env bash
365
+ set -euo pipefail
366
+
367
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
368
+ cd "\${ROOT_DIR}"
369
+
370
+ skill_md="skills/loop-agent/SKILL.md"
371
+ if [[ ! -f "\${skill_md}" ]]; then
372
+ echo "skill entry 检查失败:缺少 \${skill_md}" >&2
373
+ exit 1
374
+ fi
375
+
376
+ required_refs=(
377
+ "references/harness-policy.md"
378
+ "references/hybrid-dag.md"
379
+ "references/verification-and-failure-handling.md"
380
+ "references/command-reference.md"
381
+ )
382
+
383
+ missing=()
384
+ for ref in "\${required_refs[@]}"; do
385
+ [[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
386
+ done
387
+
388
+ while IFS= read -r ref; do
389
+ [[ -z "\${ref}" ]] && continue
390
+ [[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
391
+ done < <(grep -Eo 'references/[A-Za-z0-9._/-]+\\.md' "\${skill_md}" | sort -u || true)
392
+
393
+ if (( \${#missing[@]} > 0 )); then
394
+ echo "skill entry 检查失败:引用的 reference 文件不存在:" >&2
395
+ printf ' - %s\\n' "\${missing[@]}" >&2
396
+ exit 1
397
+ fi
398
+
399
+ line_count="$(wc -l < "\${skill_md}" | tr -d ' ')"
400
+ if [[ "\${line_count}" =~ ^[0-9]+$ && "\${line_count}" -gt 220 ]]; then
401
+ echo "skill entry 检查警告:\${skill_md} 行数为 \${line_count},建议保持入口精简并把细节放入 references/" >&2
402
+ fi
403
+
404
+ echo "skill entry 检查通过:references ok, lines=\${line_count}"
403
405
  `;
404
- const INIT_CHECK_REPO_SH = `#!/usr/bin/env bash
405
- set -euo pipefail
406
-
407
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
408
- cd "\${ROOT_DIR}"
409
-
410
- checks=(
411
- "scripts/check-engineering-structure.sh"
412
- "scripts/check-doc-index.sh"
413
- "scripts/check-doc-links.sh"
414
- "scripts/check-active-plan-status.sh"
415
- "scripts/check-exec-plan-index-sync.sh"
416
- "scripts/check-harness-runtime-clean.sh"
417
- "scripts/check-architecture-boundaries.sh"
418
- "scripts/check-skill-entry.sh"
419
- )
420
-
421
- for check in "\${checks[@]}"; do
422
- echo "==> bash \${check}"
423
- bash "\${check}"
424
- echo
425
- done
426
-
427
- echo "仓库治理检查全部通过"
406
+ const INIT_CHECK_REPO_SH = `#!/usr/bin/env bash
407
+ set -euo pipefail
408
+
409
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
410
+ cd "\${ROOT_DIR}"
411
+
412
+ checks=(
413
+ "scripts/check-engineering-structure.sh"
414
+ "scripts/check-doc-index.sh"
415
+ "scripts/check-doc-links.sh"
416
+ "scripts/check-active-plan-status.sh"
417
+ "scripts/check-exec-plan-index-sync.sh"
418
+ "scripts/check-harness-runtime-clean.sh"
419
+ "scripts/check-architecture-boundaries.sh"
420
+ "scripts/check-skill-entry.sh"
421
+ )
422
+
423
+ for check in "\${checks[@]}"; do
424
+ echo "==> bash \${check}"
425
+ bash "\${check}"
426
+ echo
427
+ done
428
+
429
+ echo "仓库治理检查全部通过"
428
430
  `;
429
- const INIT_CI_GOVERNANCE_SH = `#!/usr/bin/env bash
430
- set -euo pipefail
431
-
432
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
433
- cd "\${ROOT_DIR}"
434
-
435
- bash scripts/check-repo.sh
431
+ const INIT_CI_GOVERNANCE_SH = `#!/usr/bin/env bash
432
+ set -euo pipefail
433
+
434
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
435
+ cd "\${ROOT_DIR}"
436
+
437
+ bash scripts/check-repo.sh
436
438
  `;
437
- const INIT_CI_TESTS_SH = `#!/usr/bin/env bash
438
- set -euo pipefail
439
-
440
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
441
- cd "\${ROOT_DIR}"
442
-
443
- ran=0
444
- notes=()
445
-
446
- run_cmd() {
447
- echo "==> $*"
448
- "$@"
449
- ran=1
450
- }
451
-
452
- run_bash() {
453
- echo "==> $*"
454
- bash -lc "$*"
455
- ran=1
456
- }
457
-
458
- has_make_target() {
459
- local target="$1"
460
- [[ -f Makefile || -f makefile ]] || return 1
461
- grep -Eq "^[[:alnum:]_.-]*\${target}[[:alnum:]_.-]*:" Makefile makefile 2>/dev/null
462
- }
463
-
464
- has_npm_script() {
465
- local script="$1"
466
- [[ -f package.json ]] || return 1
467
- command -v node >/dev/null 2>&1 || return 1
468
- node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); process.exit(p.scripts && p.scripts[process.argv[1]] ? 0 : 1)" "\${script}" >/dev/null 2>&1
469
- }
470
-
471
- if [[ -f package.json ]]; then
472
- if command -v npm >/dev/null 2>&1; then
473
- for script in lint typecheck test build; do
474
- if has_npm_script "\${script}"; then
475
- run_cmd npm run "\${script}"
476
- fi
477
- done
478
- else
479
- notes+=("package.json exists but npm is not available")
480
- fi
481
- fi
482
-
483
- if has_make_target verify; then
484
- run_cmd make verify
485
- elif has_make_target test; then
486
- run_cmd make test
487
- fi
488
-
489
- if [[ -f go.mod ]]; then
490
- if command -v go >/dev/null 2>&1; then
491
- run_cmd go test ./...
492
- else
493
- notes+=("go.mod exists but go is not available")
494
- fi
495
- fi
496
-
497
- if [[ -f Cargo.toml ]]; then
498
- if command -v cargo >/dev/null 2>&1; then
499
- run_cmd cargo test
500
- else
501
- notes+=("Cargo.toml exists but cargo is not available")
502
- fi
503
- fi
504
-
505
- if [[ -f pyproject.toml || -f pytest.ini || -f tox.ini ]]; then
506
- if command -v pytest >/dev/null 2>&1; then
507
- run_cmd pytest
508
- elif command -v tox >/dev/null 2>&1 && [[ -f tox.ini ]]; then
509
- run_cmd tox
510
- else
511
- notes+=("Python project metadata found but pytest/tox is not available")
512
- fi
513
- fi
514
-
515
- if [[ -f pom.xml ]]; then
516
- if command -v mvn >/dev/null 2>&1; then
517
- run_cmd mvn test
518
- else
519
- notes+=("pom.xml exists but mvn is not available")
520
- fi
521
- fi
522
-
523
- if [[ -f build.gradle || -f build.gradle.kts || -f settings.gradle || -f settings.gradle.kts ]]; then
524
- if [[ -x ./gradlew ]]; then
525
- run_cmd ./gradlew test
526
- elif command -v gradle >/dev/null 2>&1; then
527
- run_cmd gradle test
528
- else
529
- notes+=("Gradle project metadata found but gradle/gradlew is not available")
530
- fi
531
- fi
532
-
533
- if compgen -G "*.sln" >/dev/null || compgen -G "*.csproj" >/dev/null; then
534
- if command -v dotnet >/dev/null 2>&1; then
535
- run_cmd dotnet test
536
- else
537
- notes+=(".NET project metadata found but dotnet is not available")
538
- fi
539
- fi
540
-
541
- if (( ran == 0 )); then
542
- echo "未检测到可安全自动运行的项目测试入口。"
543
- echo "loop-agent 治理检查仍可运行;请让初始化模型根据目标项目技术栈补充 scripts/ci-tests.sh 与 docs/verification-matrix.md。"
544
- fi
545
-
546
- if (( \${#notes[@]} > 0 )); then
547
- echo "项目验证探测备注:"
548
- for note in "\${notes[@]}"; do
549
- echo " - \${note}"
550
- done
551
- fi
439
+ const INIT_CI_TESTS_SH = `#!/usr/bin/env bash
440
+ set -euo pipefail
441
+
442
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
443
+ cd "\${ROOT_DIR}"
444
+
445
+ ran=0
446
+ notes=()
447
+
448
+ run_cmd() {
449
+ echo "==> $*"
450
+ "$@"
451
+ ran=1
452
+ }
453
+
454
+ run_bash() {
455
+ echo "==> $*"
456
+ bash -lc "$*"
457
+ ran=1
458
+ }
459
+
460
+ has_make_target() {
461
+ local target="$1"
462
+ [[ -f Makefile || -f makefile ]] || return 1
463
+ grep -Eq "^[[:alnum:]_.-]*\${target}[[:alnum:]_.-]*:" Makefile makefile 2>/dev/null
464
+ }
465
+
466
+ has_npm_script() {
467
+ local script="$1"
468
+ [[ -f package.json ]] || return 1
469
+ command -v node >/dev/null 2>&1 || return 1
470
+ node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); process.exit(p.scripts && p.scripts[process.argv[1]] ? 0 : 1)" "\${script}" >/dev/null 2>&1
471
+ }
472
+
473
+ if [[ -f package.json ]]; then
474
+ if command -v npm >/dev/null 2>&1; then
475
+ for script in lint typecheck test build; do
476
+ if has_npm_script "\${script}"; then
477
+ run_cmd npm run "\${script}"
478
+ fi
479
+ done
480
+ else
481
+ notes+=("package.json exists but npm is not available")
482
+ fi
483
+ fi
484
+
485
+ if has_make_target verify; then
486
+ run_cmd make verify
487
+ elif has_make_target test; then
488
+ run_cmd make test
489
+ fi
490
+
491
+ if [[ -f go.mod ]]; then
492
+ if command -v go >/dev/null 2>&1; then
493
+ run_cmd go test ./...
494
+ else
495
+ notes+=("go.mod exists but go is not available")
496
+ fi
497
+ fi
498
+
499
+ if [[ -f Cargo.toml ]]; then
500
+ if command -v cargo >/dev/null 2>&1; then
501
+ run_cmd cargo test
502
+ else
503
+ notes+=("Cargo.toml exists but cargo is not available")
504
+ fi
505
+ fi
506
+
507
+ if [[ -f pyproject.toml || -f pytest.ini || -f tox.ini ]]; then
508
+ if command -v pytest >/dev/null 2>&1; then
509
+ run_cmd pytest
510
+ elif command -v tox >/dev/null 2>&1 && [[ -f tox.ini ]]; then
511
+ run_cmd tox
512
+ else
513
+ notes+=("Python project metadata found but pytest/tox is not available")
514
+ fi
515
+ fi
516
+
517
+ if [[ -f pom.xml ]]; then
518
+ if command -v mvn >/dev/null 2>&1; then
519
+ run_cmd mvn test
520
+ else
521
+ notes+=("pom.xml exists but mvn is not available")
522
+ fi
523
+ fi
524
+
525
+ if [[ -f build.gradle || -f build.gradle.kts || -f settings.gradle || -f settings.gradle.kts ]]; then
526
+ if [[ -x ./gradlew ]]; then
527
+ run_cmd ./gradlew test
528
+ elif command -v gradle >/dev/null 2>&1; then
529
+ run_cmd gradle test
530
+ else
531
+ notes+=("Gradle project metadata found but gradle/gradlew is not available")
532
+ fi
533
+ fi
534
+
535
+ if compgen -G "*.sln" >/dev/null || compgen -G "*.csproj" >/dev/null; then
536
+ if command -v dotnet >/dev/null 2>&1; then
537
+ run_cmd dotnet test
538
+ else
539
+ notes+=(".NET project metadata found but dotnet is not available")
540
+ fi
541
+ fi
542
+
543
+ if (( ran == 0 )); then
544
+ echo "未检测到可安全自动运行的项目测试入口。"
545
+ echo "loop-agent 治理检查仍可运行;请让初始化模型根据目标项目技术栈补充 scripts/ci-tests.sh 与 docs/verification-matrix.md。"
546
+ fi
547
+
548
+ if (( \${#notes[@]} > 0 )); then
549
+ echo "项目验证探测备注:"
550
+ for note in "\${notes[@]}"; do
551
+ echo " - \${note}"
552
+ done
553
+ fi
552
554
  `;
553
- const INIT_CI_SH = `#!/usr/bin/env bash
554
- set -euo pipefail
555
-
556
- ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
557
- cd "\${ROOT_DIR}"
558
-
559
- bash scripts/ci-governance.sh
560
- bash scripts/ci-tests.sh
555
+ const INIT_CI_SH = `#!/usr/bin/env bash
556
+ set -euo pipefail
557
+
558
+ ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
559
+ cd "\${ROOT_DIR}"
560
+
561
+ bash scripts/ci-governance.sh
562
+ bash scripts/ci-tests.sh
561
563
  `;
562
564
  const INIT_SCRIPT_FILES = {
563
565
  "scripts/check-engineering-structure.sh": INIT_CHECK_ENGINEERING_STRUCTURE_SH,
@@ -720,17 +722,88 @@ function mergeManagedBlock(existing, block) {
720
722
  }
721
723
  return `${existing.trimEnd()}\n\n${block}\n`;
722
724
  }
725
+ function mergeGitignoreManagedBlock(existing, block) {
726
+ const start = existing.indexOf(GITIGNORE_BLOCK_START);
727
+ const end = existing.indexOf(GITIGNORE_BLOCK_END);
728
+ if (start >= 0 && end > start) {
729
+ return `${existing.slice(0, start).trimEnd()}\n\n${block}\n${existing.slice(end + GITIGNORE_BLOCK_END.length).trimStart()}`.trimEnd() + "\n";
730
+ }
731
+ if (!existing.trim())
732
+ return `${block}\n`;
733
+ return `${existing.trimEnd()}\n\n${block}\n`;
734
+ }
735
+ /** Shared ignore rules for loop-agent runtime facts; keep prompts/ and directory structure shareable. */
736
+ export function buildManagedGitignoreBlock() {
737
+ return [
738
+ GITIGNORE_BLOCK_START,
739
+ "# loop-agent runtime: ignore personal/session facts; keep prompts and directory placeholders shareable",
740
+ "",
741
+ "# tasks (source + runtime state per developer/session)",
742
+ ".harness/tasks/*",
743
+ "!.harness/tasks/.gitkeep",
744
+ "",
745
+ "# DAG runs",
746
+ ".harness/dag-runs/active/*",
747
+ "!.harness/dag-runs/active/.gitkeep",
748
+ ".harness/dag-runs/completed/*",
749
+ "!.harness/dag-runs/completed/.gitkeep",
750
+ ".harness/dag-runs/paused/*",
751
+ "!.harness/dag-runs/paused/.gitkeep",
752
+ "",
753
+ "# one-shot executor runs",
754
+ ".harness/runs/*",
755
+ "!.harness/runs/.gitkeep",
756
+ "",
757
+ "# session / cache / logs / recomputable surface state",
758
+ ".harness/live/",
759
+ ".harness/cache/",
760
+ ".harness/*.log",
761
+ ".harness/init-surface.json",
762
+ "",
763
+ "# worker task pool",
764
+ ".task-pool/",
765
+ "",
766
+ "# multi-worktree parallel mode",
767
+ ".worktrees/",
768
+ GITIGNORE_BLOCK_END,
769
+ ].join("\n");
770
+ }
771
+ function extractGitignoreManagedBlock(content) {
772
+ const start = content.indexOf(GITIGNORE_BLOCK_START);
773
+ const end = content.indexOf(GITIGNORE_BLOCK_END);
774
+ if (start < 0 || end <= start)
775
+ return undefined;
776
+ return content.slice(start, end + GITIGNORE_BLOCK_END.length);
777
+ }
723
778
  function buildHarness(input) {
724
- const modelOverride = input.provider || input.model
725
- ? {
726
- analyze: { provider: input.provider, model: input.model },
727
- plan: { provider: input.provider, model: input.model },
728
- implement: { provider: input.provider, model: input.model },
729
- verify: { provider: input.provider, model: input.model },
730
- retrospective: { provider: input.provider, model: input.model },
731
- }
732
- : undefined;
733
- return {
779
+ const templateExecutors = isRecord(input.template.executors)
780
+ ? { ...input.template.executors }
781
+ : {};
782
+ const existingExecutors = isRecord(input.existing.executors)
783
+ ? input.existing.executors
784
+ : {};
785
+ // Cursor executor is opt-in for package developers; do not project it to target apps.
786
+ // Keep it only when the target already configured executors.cursor.
787
+ if (!isRecord(existingExecutors.cursor)) {
788
+ delete templateExecutors.cursor;
789
+ }
790
+ const mergedExecutors = mergeRecord(templateExecutors, existingExecutors);
791
+ // requiresApiKey is Cursor-facing only; never project it on pi.
792
+ if (isRecord(mergedExecutors.pi) && "requiresApiKey" in mergedExecutors.pi) {
793
+ const pi = { ...mergedExecutors.pi };
794
+ delete pi.requiresApiKey;
795
+ mergedExecutors.pi = pi;
796
+ }
797
+ // Prefer DAG-facing executors.*.defaultModel over legacy top-level models.*.
798
+ // --provider/--model still apply as a convenience override for pi defaultModel.
799
+ if (input.provider || input.model) {
800
+ const piExisting = isRecord(mergedExecutors.pi) ? mergedExecutors.pi : {};
801
+ mergedExecutors.pi = {
802
+ ...piExisting,
803
+ ...(input.model ? { defaultModel: input.model } : {}),
804
+ };
805
+ }
806
+ const harness = {
734
807
  ...input.existing,
735
808
  version: 1,
736
809
  project: input.projectName,
@@ -771,11 +844,30 @@ function buildHarness(input) {
771
844
  standardVerify: "bash scripts/ci-governance.sh",
772
845
  fullVerify: "bash scripts/ci.sh",
773
846
  }),
774
- models: modelOverride ?? input.existing.models ?? input.template.models ?? {},
775
- modelProfiles: input.existing.modelProfiles ?? input.template.modelProfiles ?? {},
776
- modelRouting: input.existing.modelRouting ?? input.template.modelRouting ?? {},
777
- executors: mergeRecord(input.template.executors, input.existing.executors),
847
+ executors: mergedExecutors,
778
848
  };
849
+ // Do not project legacy step-routing models / modelProfiles / modelRouting into
850
+ // new or refreshed harness.json. Keep them only when the target already has them
851
+ // so old files remain loadable until the user migrates.
852
+ if (isRecord(input.existing.models) && Object.keys(input.existing.models).length > 0) {
853
+ harness.models = input.existing.models;
854
+ }
855
+ else {
856
+ delete harness.models;
857
+ }
858
+ if (isRecord(input.existing.modelProfiles) && Object.keys(input.existing.modelProfiles).length > 0) {
859
+ harness.modelProfiles = input.existing.modelProfiles;
860
+ }
861
+ else {
862
+ delete harness.modelProfiles;
863
+ }
864
+ if (isRecord(input.existing.modelRouting) && Object.keys(input.existing.modelRouting).length > 0) {
865
+ harness.modelRouting = input.existing.modelRouting;
866
+ }
867
+ else {
868
+ delete harness.modelRouting;
869
+ }
870
+ return harness;
779
871
  }
780
872
  async function writeTextIfMissing(input) {
781
873
  const target = path.join(input.repoRoot, input.relativePath);
@@ -899,8 +991,11 @@ function inferInitSurfaceMode(relativePath) {
899
991
  relativePath.endsWith("/")) {
900
992
  return "directory";
901
993
  }
902
- if (relativePath === "README.md" || relativePath === "AGENTS.md")
994
+ if (relativePath === "README.md" ||
995
+ relativePath === "AGENTS.md" ||
996
+ relativePath === ".gitignore") {
903
997
  return "managed-block";
998
+ }
904
999
  if (relativePath === "harness.json" ||
905
1000
  relativePath.startsWith("scripts/") ||
906
1001
  relativePath.startsWith(".harness/prompts/") ||
@@ -934,6 +1029,9 @@ async function buildDesiredSurfaceContent(input) {
934
1029
  }),
935
1030
  };
936
1031
  }
1032
+ if (manifestPath === ".gitignore") {
1033
+ return { content: buildManagedGitignoreBlock() };
1034
+ }
937
1035
  if (manifestPath === "harness.json") {
938
1036
  const template = await readJsonIfExists(path.join(input.assetRoot, "harness.json"));
939
1037
  return {
@@ -953,6 +1051,16 @@ async function buildDesiredSurfaceContent(input) {
953
1051
  const name = path.basename(manifestPath);
954
1052
  return { content: COMPAT_PROMPTS[name] };
955
1053
  }
1054
+ // `.agents/skills/...` is a target-only mirror of the bundled `skills/...`
1055
+ // directory; the package ships `skills/`, never `.agents/skills/`.
1056
+ if (manifestPath.startsWith(".agents/skills/")) {
1057
+ const sourceManifestPath = manifestPath.slice(".agents/".length);
1058
+ const mirrorSourcePath = path.join(input.assetRoot, sourceManifestPath);
1059
+ if (await exists(mirrorSourcePath)) {
1060
+ return { content: await readFile(mirrorSourcePath, "utf-8"), sourcePath: mirrorSourcePath };
1061
+ }
1062
+ return {};
1063
+ }
956
1064
  if (manifestPath.startsWith("docs/")) {
957
1065
  const doc = manifestPath.slice("docs/".length);
958
1066
  const generated = buildGeneratedCoreDoc({
@@ -1031,7 +1139,10 @@ async function buildCurrentSurfaceState(input) {
1031
1139
  const current = await readFile(targetPath);
1032
1140
  const currentSha256 = sha256Buffer(current);
1033
1141
  if (entry.mode === "managed-block") {
1034
- const currentBlock = extractManagedBlock(current.toString("utf-8"));
1142
+ const text = current.toString("utf-8");
1143
+ const currentBlock = entry.path === ".gitignore" || targetRelativePath === ".gitignore"
1144
+ ? extractGitignoreManagedBlock(text)
1145
+ : extractManagedBlock(text);
1035
1146
  files[targetRelativePath] = {
1036
1147
  ...base,
1037
1148
  currentSha256,
@@ -1462,7 +1573,8 @@ export function buildInitInstructions(input) {
1462
1573
  "- After deterministic initialization, inspect README/config/build files (for example package.json, pyproject.toml, go.mod, Cargo.toml, pom.xml, Gradle files, Makefile, .sln/.csproj, or project-specific scripts) and adapt `scripts/ci-tests.sh` plus the verification matrix to the real project.",
1463
1574
  "- Enrich the root `README.md`: keep the deterministic project title and the loop-agent managed block intact, and fill the human-authored sections (项目概览, 技术栈与目录结构, 开发与验证) from the target project's actual files. The root README must serve both as a human-first project entry and as an agent work entry; replace the initialization-model supplement comments when the project files provide the information.",
1464
1575
  "- Populate `docs/verification-matrix.md` with the target project's actual quick, standard, and full verification commands derived from its real language and toolchain, keeping the governance rows intact.",
1465
- "- Copy repo-local skills by default so the target repo has auditable skill instructions.",
1576
+ "- Copy repo-local skills by default so the target repo has auditable skill instructions; full profile also mirrors skills into `.agents/skills/` for external agents.",
1577
+ "- Merge a loop-agent managed block into `.gitignore` that ignores harness runtime facts (tasks, dag-runs, runs, live, cache, init-surface.json, .task-pool) while keeping prompts and directory placeholders shareable.",
1466
1578
  "- Do not copy examples by default; examples stay bundled in the tool and are available through `loop-agent examples`.",
1467
1579
  "- Add or update a loop-agent managed block in AGENTS.md.",
1468
1580
  "- The generated AGENTS.md must include documentation convergence and structured DAG write-boundary rules so target projects keep the same working discipline as this repository.",
@@ -1556,6 +1668,18 @@ export async function initializeLoopAgentProject(options) {
1556
1668
  written,
1557
1669
  skipped,
1558
1670
  });
1671
+ const gitignorePath = path.join(repoRoot, ".gitignore");
1672
+ const existingGitignore = (await exists(gitignorePath))
1673
+ ? await readFile(gitignorePath, "utf-8")
1674
+ : "";
1675
+ await writeText({
1676
+ repoRoot,
1677
+ relativePath: ".gitignore",
1678
+ content: mergeGitignoreManagedBlock(existingGitignore, buildManagedGitignoreBlock()),
1679
+ merge: true,
1680
+ written,
1681
+ skipped,
1682
+ });
1559
1683
  for (const doc of CORE_DOC_FILES) {
1560
1684
  const generated = buildGeneratedCoreDoc({ doc, projectName, governanceRoot });
1561
1685
  if (generated) {
@@ -1604,9 +1728,21 @@ export async function initializeLoopAgentProject(options) {
1604
1728
  targetRelativePath: "skills",
1605
1729
  written,
1606
1730
  });
1731
+ // Mirror bundled skills into the agent-compatible `.agents/skills/` path so
1732
+ // external agents (e.g. OpenCode) that auto-discover `.agents/skills/<name>/SKILL.md`
1733
+ // share the same skill content as loop-agent's primary `skills/` path.
1734
+ // The package still ships only `skills/`; `.agents/skills` is a target projection.
1735
+ await copyDirMerge({
1736
+ assetRoot,
1737
+ repoRoot,
1738
+ sourceRelativePath: "skills",
1739
+ targetRelativePath: ".agents/skills",
1740
+ written,
1741
+ });
1607
1742
  }
1608
1743
  else {
1609
1744
  skipped.push("skills/");
1745
+ skipped.push(".agents/skills/");
1610
1746
  }
1611
1747
  skipped.push("examples/");
1612
1748
  for (const [relativePath, content] of Object.entries(buildInitScriptFiles(governanceRoot))) {
@@ -1736,6 +1872,27 @@ export async function checkInitUpdate(input) {
1736
1872
  }
1737
1873
  }
1738
1874
  }
1875
+ // Safe harness hygiene: strip obsolete pi.requiresApiKey without full harness rewrite.
1876
+ // Cursor requiresApiKey is intentionally kept when present.
1877
+ const harnessPath = path.join(repoRoot, "harness.json");
1878
+ if (await exists(harnessPath)) {
1879
+ try {
1880
+ const harness = JSON.parse(await readFile(harnessPath, "utf-8"));
1881
+ if (isRecord(harness) &&
1882
+ isRecord(harness.executors) &&
1883
+ isRecord(harness.executors.pi) &&
1884
+ Object.prototype.hasOwnProperty.call(harness.executors.pi, "requiresApiKey")) {
1885
+ deterministicActions.push({
1886
+ type: "strip-pi-requires-api-key",
1887
+ path: "harness.json",
1888
+ reason: "executors.pi.requiresApiKey is unused; strip it while keeping user model config and cursor.requiresApiKey",
1889
+ });
1890
+ }
1891
+ }
1892
+ catch {
1893
+ // leave malformed harness for model merge / doctor
1894
+ }
1895
+ }
1739
1896
  const partial = {
1740
1897
  repoRoot,
1741
1898
  controllerVersion: currentState.controllerVersion,
@@ -1759,6 +1916,29 @@ export async function checkInitUpdate(input) {
1759
1916
  };
1760
1917
  }
1761
1918
  async function applySafeAction(input) {
1919
+ if (input.action.type === "strip-pi-requires-api-key") {
1920
+ const target = path.join(input.repoRoot, "harness.json");
1921
+ if (!(await exists(target)))
1922
+ return false;
1923
+ const harness = JSON.parse(await readFile(target, "utf-8"));
1924
+ if (!isRecord(harness) || !isRecord(harness.executors) || !isRecord(harness.executors.pi)) {
1925
+ return false;
1926
+ }
1927
+ if (!Object.prototype.hasOwnProperty.call(harness.executors.pi, "requiresApiKey")) {
1928
+ return false;
1929
+ }
1930
+ const nextPi = { ...harness.executors.pi };
1931
+ delete nextPi.requiresApiKey;
1932
+ const next = {
1933
+ ...harness,
1934
+ executors: {
1935
+ ...harness.executors,
1936
+ pi: nextPi,
1937
+ },
1938
+ };
1939
+ await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, "utf-8");
1940
+ return true;
1941
+ }
1762
1942
  const assetRoot = await findPackageRoot();
1763
1943
  const entry = {
1764
1944
  path: input.action.path,
@@ -1789,11 +1969,13 @@ async function applySafeAction(input) {
1789
1969
  await mkdir(path.dirname(target), { recursive: true });
1790
1970
  if (input.action.type === "refresh-managed-block") {
1791
1971
  const existing = (await exists(target)) ? await readFile(target, "utf-8") : "";
1792
- const next = input.action.path === "README.md" && existing.trim().length === 0
1793
- ? buildTargetReadme({ projectName: input.projectName, governanceRoot: input.governanceRoot })
1794
- : input.action.path === "AGENTS.md" && existing.trim().length === 0
1795
- ? `# AGENTS.md\n\n${desired.content}\n`
1796
- : mergeManagedBlock(existing, desired.content);
1972
+ const next = input.action.path === ".gitignore"
1973
+ ? mergeGitignoreManagedBlock(existing, desired.content)
1974
+ : input.action.path === "README.md" && existing.trim().length === 0
1975
+ ? buildTargetReadme({ projectName: input.projectName, governanceRoot: input.governanceRoot })
1976
+ : input.action.path === "AGENTS.md" && existing.trim().length === 0
1977
+ ? `# AGENTS.md\n\n${desired.content}\n`
1978
+ : mergeManagedBlock(existing, desired.content);
1797
1979
  await writeFile(target, next, "utf-8");
1798
1980
  return true;
1799
1981
  }
@@ -1826,16 +2008,18 @@ export async function applyInitUpdate(input) {
1826
2008
  skipped.push(action);
1827
2009
  continue;
1828
2010
  }
1829
- const currentState = await buildCurrentSurfaceState({
1830
- repoRoot,
1831
- projectName,
1832
- governanceRoot,
1833
- stateKind: "inferred-baseline",
1834
- });
1835
- const relationship = currentState.files[action.path]?.relationship;
1836
- if (relationship === "local-existing-unknown") {
1837
- skipped.push(action);
1838
- continue;
2011
+ if (action.type !== "strip-pi-requires-api-key") {
2012
+ const currentState = await buildCurrentSurfaceState({
2013
+ repoRoot,
2014
+ projectName,
2015
+ governanceRoot,
2016
+ stateKind: "inferred-baseline",
2017
+ });
2018
+ const relationship = currentState.files[action.path]?.relationship;
2019
+ if (relationship === "local-existing-unknown") {
2020
+ skipped.push(action);
2021
+ continue;
2022
+ }
1839
2023
  }
1840
2024
  if (await applySafeAction({ repoRoot, projectName, governanceRoot, action }))
1841
2025
  applied.push(action);
@@ -1909,6 +2093,10 @@ export async function runInitDoctor(input) {
1909
2093
  const readme = path.join(repoRoot, "README.md");
1910
2094
  add("README loop-agent block", (await exists(readme)) && (await readFile(readme, "utf-8")).includes(MANAGED_BLOCK_START), "managed block present");
1911
2095
  add("repo-local skills", await exists(path.join(repoRoot, "skills", "loop-agent", "SKILL.md")), "skills/loop-agent/SKILL.md");
2096
+ add("agent-compatible skills mirror", await exists(path.join(repoRoot, ".agents", "skills", "loop-agent", "SKILL.md")), ".agents/skills/loop-agent/SKILL.md");
2097
+ const gitignorePath = path.join(repoRoot, ".gitignore");
2098
+ const gitignoreContent = (await exists(gitignorePath)) ? await readFile(gitignorePath, "utf-8") : "";
2099
+ add("gitignore loop-agent block", gitignoreContent.includes(GITIGNORE_BLOCK_START) && gitignoreContent.includes(".harness/tasks/*"), ".gitignore managed runtime ignores");
1912
2100
  const requiredScripts = Object.keys(INIT_SCRIPT_FILES);
1913
2101
  const missingScripts = [];
1914
2102
  for (const script of requiredScripts) {