@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.
- package/AGENTS.md +142 -142
- package/CHANGELOG.md +98 -106
- package/README.md +195 -195
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/validate-dag.js +14 -1
- package/dist/commands/init.js +677 -489
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/config-core.js +3 -2
- package/dist/executors/cursor-executor.js +1 -1
- package/dist/executors/model-routing.js +43 -0
- package/dist/governance/manifest-types.js +9 -1
- package/dist/task/runtime.js +27 -27
- package/dist/worker/pool/run-store.js +9 -1
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/init-hybrid.js +4 -13
- package/dist/workflows/dag/skill-instructions.js +4 -0
- package/dist/workflows/dag/types.js +1 -1
- package/dist/workflows/dag/validate.js +3 -2
- package/docs/README.md +72 -72
- package/docs/agent-dag-recovery-playbook.md +184 -184
- package/docs/agent-dag-runner.md +42 -42
- package/docs/architecture/runtime-boundaries.md +147 -147
- package/docs/cursor-executor-usage.md +25 -25
- package/docs/decisions/README.md +3 -3
- package/docs/design/README.md +36 -36
- package/docs/development-principles.md +73 -73
- package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
- package/docs/exec-plans/README.md +6 -6
- package/docs/exec-plans/active/README.md +7 -7
- package/docs/exec-plans/completed/README.md +19 -19
- package/docs/feature-workflow.md +186 -186
- package/docs/harness-methodology-debugging.md +153 -153
- package/docs/harness-methodology-tdd.md +130 -130
- package/docs/harness-methodology-verification.md +27 -27
- package/docs/init-surface.manifest.json +199 -175
- package/docs/loop-agent-harness.md +42 -42
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +3 -3
- package/docs/reports/README.md +5 -5
- package/docs/skills/README.md +6 -6
- package/docs/skills/vetted-skill-registry.md +26 -26
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +454 -454
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/agent-dag.base.json +195 -195
- package/docs/templates/agent-dag.final-verification.json +190 -190
- package/docs/templates/agent-dag.schema.json +316 -316
- package/docs/templates/agent-dag.supervised-implementation.json +500 -500
- package/docs/templates/exec-plan.md +64 -64
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/hybrid-dag.json +193 -193
- package/docs/templates/init-evolution-review.md +33 -33
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/progress-log.md +17 -17
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/verification-matrix.md +41 -41
- package/examples/decision-gate-agent-dag.json +123 -123
- package/examples/example-dag.json +51 -51
- package/examples/hybrid-loop-agent-dag.json +194 -194
- package/harness.json +69 -94
- package/package.json +66 -66
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/init-capability-evolution/SKILL.md +69 -69
- package/skills/loop-agent/SKILL.md +147 -147
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +403 -403
- package/skills/loop-agent/references/harness-policy.md +259 -259
- package/skills/loop-agent/references/hybrid-dag.md +216 -216
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +59 -59
- package/skills/loop-agent/references/model-routing.md +36 -36
- package/skills/loop-agent/references/multi-worktree.md +54 -54
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
- package/skills/loop-agent/references/task-workflow.md +84 -84
- package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/SKILL.md +296 -296
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/test-driven-development/SKILL.md +20 -20
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
package/dist/commands/init.js
CHANGED
|
@@ -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
|
|
725
|
-
? {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
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" ||
|
|
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
|
|
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 === "
|
|
1793
|
-
?
|
|
1794
|
-
: input.action.path === "
|
|
1795
|
-
?
|
|
1796
|
-
:
|
|
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
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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) {
|