@tea-agent/loop-agent 0.2.1 → 0.4.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 +91 -87
- package/CHANGELOG.md +89 -52
- package/README.md +195 -180
- package/bin/agent-worker.js +22 -0
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/args.js +6 -0
- package/dist/application/dag/generate-task-dag.js +2 -0
- package/dist/application/dag/run-dag.js +3 -0
- package/dist/application/dag/validate-dag.js +40 -0
- package/dist/cli/command-definitions.js +2 -2
- package/dist/cli/program.js +24 -4
- package/dist/commands/init.js +1011 -459
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/cursor-executor.js +1 -1
- package/dist/executors/dag-pi-executor.js +8 -1
- package/dist/task/runtime.js +27 -27
- package/dist/worker/cli.js +119 -0
- package/dist/worker/loop-agent/command-result.js +1 -0
- package/dist/worker/loop-agent/loop-agent-client.js +105 -0
- package/dist/worker/loop-agent/parse-json.js +14 -0
- package/dist/worker/materialize/harness-task-materializer.js +157 -0
- package/dist/worker/pool/failure-routing.js +98 -0
- package/dist/worker/pool/run-store.js +117 -0
- package/dist/worker/pool/types.js +1 -0
- package/dist/worker/preflight.js +108 -0
- package/dist/worker/profile-mapping.js +76 -0
- package/dist/worker/progress-reporter.js +81 -0
- package/dist/worker/report/morning-report.js +69 -0
- package/dist/worker/repos/repo-resolver.js +23 -0
- package/dist/worker/run-task/run-task.js +359 -0
- package/dist/worker/runner/run-ready.js +216 -0
- package/dist/worker/task-graph/acceptance-schema.js +25 -0
- package/dist/worker/task-graph/ready-queue.js +23 -0
- package/dist/worker/task-graph/task-graph-schema.js +28 -0
- package/dist/worker/task-graph/types.js +1 -0
- package/dist/worker/task-graph/validate.js +188 -0
- package/dist/worker/task-spec/complexity-mapping.js +8 -0
- package/dist/worker/task-spec/schema.js +116 -0
- package/dist/worker/task-spec/types.js +1 -0
- package/dist/worker/task-spec/validate.js +352 -0
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +2 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/init-hybrid.js +3 -3
- package/dist/workflows/dag/skills.js +3 -3
- package/dist/workflows/dag/types.js +2 -0
- package/dist/workflows/dynamic/compile.js +11 -0
- package/dist/workflows/dynamic/spec.js +1 -0
- package/docs/README.md +72 -65
- package/docs/agent-dag-recovery-playbook.md +184 -184
- package/docs/agent-dag-runner.md +42 -40
- 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 -71
- 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 -10
- package/docs/exec-plans/completed/README.md +19 -9
- 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 +175 -0
- 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 -0
- package/docs/skills/vetted-skill-registry.md +26 -0
- 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 -0
- 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 +94 -92
- package/package.json +66 -62
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/code-review-core/SKILL.md +20 -0
- package/skills/codebase-scout/SKILL.md +19 -0
- package/skills/init-capability-evolution/SKILL.md +69 -0
- package/skills/loop-agent/SKILL.md +147 -145
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +403 -357
- package/skills/loop-agent/references/harness-policy.md +259 -258
- 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 -0
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -0
package/dist/commands/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { access, copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -5,6 +6,7 @@ import { copyDir } from "../shared/copy-dir.js";
|
|
|
5
6
|
import { loadHarnessManifest } from "../governance/harness.js";
|
|
6
7
|
const MANAGED_BLOCK_START = "<!-- LOOP_AGENT_INIT_START -->";
|
|
7
8
|
const MANAGED_BLOCK_END = "<!-- LOOP_AGENT_INIT_END -->";
|
|
9
|
+
const INIT_SURFACE_STATE_PATH = ".harness/init-surface.json";
|
|
8
10
|
const CORE_DOC_FILES = [
|
|
9
11
|
"README.md",
|
|
10
12
|
"architecture/runtime-boundaries.md",
|
|
@@ -88,474 +90,474 @@ const COMPAT_PROMPTS = {
|
|
|
88
90
|
].join("\n"),
|
|
89
91
|
};
|
|
90
92
|
const GOVERNANCE_ROOT_TOKEN = "__LOOP_AGENT_GOVERNANCE_ROOT__";
|
|
91
|
-
const INIT_CHECK_ENGINEERING_STRUCTURE_SH = `#!/usr/bin/env bash
|
|
92
|
-
set -euo pipefail
|
|
93
|
-
|
|
94
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
95
|
-
cd "\${ROOT_DIR}"
|
|
96
|
-
|
|
97
|
-
required_paths=(
|
|
98
|
-
"README.md"
|
|
99
|
-
"harness.json"
|
|
100
|
-
"AGENTS.md"
|
|
101
|
-
"${GOVERNANCE_ROOT_TOKEN}/README.md"
|
|
102
|
-
"${GOVERNANCE_ROOT_TOKEN}/development-principles.md"
|
|
103
|
-
"${GOVERNANCE_ROOT_TOKEN}/feature-workflow.md"
|
|
104
|
-
"${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
|
|
105
|
-
"${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
|
|
106
|
-
"${GOVERNANCE_ROOT_TOKEN}/templates"
|
|
107
|
-
"skills/loop-agent/SKILL.md"
|
|
108
|
-
".harness/prompts/analyze.md"
|
|
109
|
-
".harness/prompts/plan.md"
|
|
110
|
-
".harness/tasks"
|
|
111
|
-
".harness/dag-runs/active"
|
|
112
|
-
".harness/dag-runs/completed"
|
|
113
|
-
".harness/dag-runs/paused"
|
|
114
|
-
".harness/runs/active"
|
|
115
|
-
".harness/runs/completed"
|
|
116
|
-
".harness/runs/failed"
|
|
117
|
-
"scripts/check-engineering-structure.sh"
|
|
118
|
-
"scripts/check-doc-index.sh"
|
|
119
|
-
"scripts/check-doc-links.sh"
|
|
120
|
-
"scripts/check-active-plan-status.sh"
|
|
121
|
-
"scripts/check-exec-plan-index-sync.sh"
|
|
122
|
-
"scripts/check-harness-runtime-clean.sh"
|
|
123
|
-
"scripts/check-architecture-boundaries.sh"
|
|
124
|
-
"scripts/check-skill-entry.sh"
|
|
125
|
-
"scripts/check-repo.sh"
|
|
126
|
-
"scripts/ci-governance.sh"
|
|
127
|
-
"scripts/ci-tests.sh"
|
|
128
|
-
"scripts/ci.sh"
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
missing=()
|
|
132
|
-
for item in "\${required_paths[@]}"; do
|
|
133
|
-
if [[ ! -e "\${item}" ]]; then
|
|
134
|
-
missing+=("\${item}")
|
|
135
|
-
fi
|
|
136
|
-
done
|
|
137
|
-
|
|
138
|
-
if (( \${#missing[@]} > 0 )); then
|
|
139
|
-
echo "loop-agent 初始化检查失败,缺少必要路径:"
|
|
140
|
-
for item in "\${missing[@]}"; do
|
|
141
|
-
echo " - \${item}"
|
|
142
|
-
done
|
|
143
|
-
exit 1
|
|
144
|
-
fi
|
|
145
|
-
|
|
146
|
-
echo "loop-agent 工程结构检查通过"
|
|
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 工程结构检查通过"
|
|
147
149
|
`;
|
|
148
|
-
const INIT_CHECK_DOC_INDEX_SH = `#!/usr/bin/env bash
|
|
149
|
-
set -euo pipefail
|
|
150
|
-
|
|
151
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
152
|
-
cd "\${ROOT_DIR}"
|
|
153
|
-
|
|
154
|
-
required_docs=(
|
|
155
|
-
"${GOVERNANCE_ROOT_TOKEN}/README.md"
|
|
156
|
-
"${GOVERNANCE_ROOT_TOKEN}/development-principles.md"
|
|
157
|
-
"${GOVERNANCE_ROOT_TOKEN}/feature-workflow.md"
|
|
158
|
-
"${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
|
|
159
|
-
"${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
|
|
160
|
-
"${GOVERNANCE_ROOT_TOKEN}/loop-agent-harness.md"
|
|
161
|
-
"${GOVERNANCE_ROOT_TOKEN}/harness-methodology-tdd.md"
|
|
162
|
-
"${GOVERNANCE_ROOT_TOKEN}/harness-methodology-verification.md"
|
|
163
|
-
"${GOVERNANCE_ROOT_TOKEN}/harness-methodology-debugging.md"
|
|
164
|
-
"${GOVERNANCE_ROOT_TOKEN}/templates"
|
|
165
|
-
"${GOVERNANCE_ROOT_TOKEN}/exec-plans/active/README.md"
|
|
166
|
-
"${GOVERNANCE_ROOT_TOKEN}/exec-plans/completed/README.md"
|
|
167
|
-
"${GOVERNANCE_ROOT_TOKEN}/progress/README.md"
|
|
168
|
-
"${GOVERNANCE_ROOT_TOKEN}/reports/README.md"
|
|
169
|
-
"${GOVERNANCE_ROOT_TOKEN}/decisions/README.md"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
missing=()
|
|
173
|
-
for item in "\${required_docs[@]}"; do
|
|
174
|
-
if [[ ! -e "\${item}" ]]; then
|
|
175
|
-
missing+=("\${item}")
|
|
176
|
-
fi
|
|
177
|
-
done
|
|
178
|
-
|
|
179
|
-
if (( \${#missing[@]} > 0 )); then
|
|
180
|
-
echo "docs 索引检查失败,缺少必要文档或目录:" >&2
|
|
181
|
-
for item in "\${missing[@]}"; do
|
|
182
|
-
echo " - \${item}" >&2
|
|
183
|
-
done
|
|
184
|
-
exit 1
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
echo "docs 索引检查通过"
|
|
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 索引检查通过"
|
|
188
190
|
`;
|
|
189
|
-
const INIT_CHECK_DOC_LINKS_SH = `#!/usr/bin/env bash
|
|
190
|
-
set -euo pipefail
|
|
191
|
-
|
|
192
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
193
|
-
cd "\${ROOT_DIR}"
|
|
194
|
-
|
|
195
|
-
if [[ ! -d "${GOVERNANCE_ROOT_TOKEN}" ]]; then
|
|
196
|
-
echo "docs 链接检查失败:缺少 ${GOVERNANCE_ROOT_TOKEN}/" >&2
|
|
197
|
-
exit 1
|
|
198
|
-
fi
|
|
199
|
-
|
|
200
|
-
missing=()
|
|
201
|
-
while IFS= read -r -d '' file; do
|
|
202
|
-
while IFS= read -r link; do
|
|
203
|
-
[[ -z "\${link}" ]] && continue
|
|
204
|
-
[[ "\${link}" == http://* || "\${link}" == https://* || "\${link}" == mailto:* || "\${link}" == "#"* ]] && continue
|
|
205
|
-
[[ "\${link}" == *"://"* ]] && continue
|
|
206
|
-
target="\${link%%#*}"
|
|
207
|
-
[[ -z "\${target}" ]] && continue
|
|
208
|
-
[[ "\${target}" == /* ]] && continue
|
|
209
|
-
base="$(dirname "\${file}")"
|
|
210
|
-
if [[ ! -e "\${base}/\${target}" ]]; then
|
|
211
|
-
missing+=("\${file}: \${link}")
|
|
212
|
-
fi
|
|
213
|
-
done < <(grep -Eo '\\[[^]]+\\]\\([^)]+\\)' "\${file}" | sed -E 's/^.*\\(([^)]+)\\)$/\\1/' || true)
|
|
214
|
-
done < <(find "${GOVERNANCE_ROOT_TOKEN}" -type f -name '*.md' -print0)
|
|
215
|
-
|
|
216
|
-
if (( \${#missing[@]} > 0 )); then
|
|
217
|
-
echo "docs 链接检查失败:" >&2
|
|
218
|
-
for item in "\${missing[@]}"; do
|
|
219
|
-
echo " - \${item}" >&2
|
|
220
|
-
done
|
|
221
|
-
exit 1
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
echo "docs 链接检查通过"
|
|
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 链接检查通过"
|
|
225
227
|
`;
|
|
226
|
-
const INIT_CHECK_ACTIVE_PLAN_STATUS_SH = `#!/usr/bin/env bash
|
|
227
|
-
set -euo pipefail
|
|
228
|
-
|
|
229
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
230
|
-
cd "\${ROOT_DIR}"
|
|
231
|
-
|
|
232
|
-
active_dir="${GOVERNANCE_ROOT_TOKEN}/exec-plans/active"
|
|
233
|
-
[[ -d "\${active_dir}" ]] || { echo "active plan 检查失败:缺少 \${active_dir}" >&2; exit 1; }
|
|
234
|
-
|
|
235
|
-
bad=()
|
|
236
|
-
while IFS= read -r -d '' file; do
|
|
237
|
-
name="$(basename "\${file}")"
|
|
238
|
-
[[ "\${name}" == "README.md" ]] && continue
|
|
239
|
-
if grep -Eiq 'status:[[:space:]]*(completed|done|closed)|状态[::][[:space:]]*(已完成|完成|关闭)' "\${file}"; then
|
|
240
|
-
bad+=("\${file}")
|
|
241
|
-
fi
|
|
242
|
-
done < <(find "\${active_dir}" -maxdepth 1 -type f -name '*.md' -print0)
|
|
243
|
-
|
|
244
|
-
if (( \${#bad[@]} > 0 )); then
|
|
245
|
-
echo "active plan 状态检查失败:已完成计划不应留在 active 目录:" >&2
|
|
246
|
-
for item in "\${bad[@]}"; do
|
|
247
|
-
echo " - \${item}" >&2
|
|
248
|
-
done
|
|
249
|
-
exit 1
|
|
250
|
-
fi
|
|
251
|
-
|
|
252
|
-
echo "active plan 状态检查通过"
|
|
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 状态检查通过"
|
|
253
255
|
`;
|
|
254
|
-
const INIT_CHECK_EXEC_PLAN_INDEX_SYNC_SH = `#!/usr/bin/env bash
|
|
255
|
-
set -euo pipefail
|
|
256
|
-
|
|
257
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
258
|
-
cd "\${ROOT_DIR}"
|
|
259
|
-
|
|
260
|
-
required=(
|
|
261
|
-
"${GOVERNANCE_ROOT_TOKEN}/exec-plans/README.md"
|
|
262
|
-
"${GOVERNANCE_ROOT_TOKEN}/exec-plans/active/README.md"
|
|
263
|
-
"${GOVERNANCE_ROOT_TOKEN}/exec-plans/completed/README.md"
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
for item in "\${required[@]}"; do
|
|
267
|
-
if [[ ! -f "\${item}" ]]; then
|
|
268
|
-
echo "exec-plan 索引同步检查失败:缺少 \${item}" >&2
|
|
269
|
-
exit 1
|
|
270
|
-
fi
|
|
271
|
-
done
|
|
272
|
-
|
|
273
|
-
echo "exec-plan 索引同步检查通过"
|
|
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 索引同步检查通过"
|
|
274
276
|
`;
|
|
275
|
-
const INIT_CHECK_HARNESS_RUNTIME_CLEAN_SH = `#!/usr/bin/env bash
|
|
276
|
-
set -euo pipefail
|
|
277
|
-
|
|
278
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
279
|
-
cd "\${ROOT_DIR}"
|
|
280
|
-
|
|
281
|
-
failures=()
|
|
282
|
-
|
|
283
|
-
count_entries() {
|
|
284
|
-
local dir="$1"
|
|
285
|
-
if [[ ! -d "\${dir}" ]]; then
|
|
286
|
-
echo 0
|
|
287
|
-
return
|
|
288
|
-
fi
|
|
289
|
-
find "\${dir}" -mindepth 1 -maxdepth 1 ! -name .gitkeep | wc -l | tr -d ' '
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if [[ "\${HARNESS_ALLOW_ACTIVE_DAG_RUNS:-}" != "1" ]]; then
|
|
293
|
-
active_dag_count="$(count_entries ".harness/dag-runs/active")"
|
|
294
|
-
[[ "\${active_dag_count}" == "0" ]] || failures+=(".harness/dag-runs/active contains \${active_dag_count} entries")
|
|
295
|
-
fi
|
|
296
|
-
|
|
297
|
-
if [[ "\${HARNESS_ALLOW_ACTIVE_TOOL_RUNS:-\${HARNESS_ALLOW_ACTIVE_ONE_SHOT_RUNS:-}}" != "1" ]]; then
|
|
298
|
-
active_run_count="$(count_entries ".harness/runs/active")"
|
|
299
|
-
[[ "\${active_run_count}" == "0" ]] || failures+=(".harness/runs/active contains \${active_run_count} entries")
|
|
300
|
-
fi
|
|
301
|
-
|
|
302
|
-
if [[ -d artifacts ]]; then
|
|
303
|
-
artifact_count="$(find artifacts -mindepth 1 -maxdepth 1 ! -name .gitkeep | wc -l | tr -d ' ')"
|
|
304
|
-
[[ "\${artifact_count}" == "0" ]] || failures+=("root artifacts/ contains \${artifact_count} entries; use docs/reports or .harness run artifacts")
|
|
305
|
-
fi
|
|
306
|
-
|
|
307
|
-
if (( \${#failures[@]} > 0 )); then
|
|
308
|
-
echo "[HARNESS RUNTIME DIRTY]" >&2
|
|
309
|
-
for item in "\${failures[@]}"; do
|
|
310
|
-
echo " - \${item}" >&2
|
|
311
|
-
done
|
|
312
|
-
exit 1
|
|
313
|
-
fi
|
|
314
|
-
|
|
315
|
-
echo "harness runtime clean check passed"
|
|
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"
|
|
316
318
|
`;
|
|
317
|
-
const INIT_CHECK_ARCHITECTURE_BOUNDARIES_SH = `#!/usr/bin/env bash
|
|
318
|
-
set -euo pipefail
|
|
319
|
-
|
|
320
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
321
|
-
cd "\${ROOT_DIR}"
|
|
322
|
-
|
|
323
|
-
boundary_doc="${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
|
|
324
|
-
if [[ ! -f "\${boundary_doc}" ]]; then
|
|
325
|
-
echo "architecture boundary 检查失败:缺少 \${boundary_doc}" >&2
|
|
326
|
-
exit 1
|
|
327
|
-
fi
|
|
328
|
-
|
|
329
|
-
violations=()
|
|
330
|
-
|
|
331
|
-
check_ts_import_boundary() {
|
|
332
|
-
local from_dir="$1"
|
|
333
|
-
local forbidden_dir="$2"
|
|
334
|
-
local label="$3"
|
|
335
|
-
[[ -d "\${from_dir}" && -d "\${forbidden_dir}" ]] || return 0
|
|
336
|
-
while IFS= read -r -d '' file; do
|
|
337
|
-
if grep -Eq "from ['\\\"][^'\\\"]*\${forbidden_dir}/|import\\(['\\\"][^'\\\"]*\${forbidden_dir}/|require\\(['\\\"][^'\\\"]*\${forbidden_dir}/" "\${file}"; then
|
|
338
|
-
violations+=("\${label}: \${file} imports \${forbidden_dir}")
|
|
339
|
-
fi
|
|
340
|
-
done < <(find "\${from_dir}" -type f \\( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.mjs' -o -name '*.cjs' \\) -print0)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
# These checks are intentionally optional and stack-agnostic. They activate only
|
|
344
|
-
# when a target repository has recognizable source directories.
|
|
345
|
-
check_ts_import_boundary "src/workflows" "src/commands" "workflow-runtime"
|
|
346
|
-
check_ts_import_boundary "src/executors" "src/commands" "executors"
|
|
347
|
-
check_ts_import_boundary "src/domain" "src/infrastructure" "domain"
|
|
348
|
-
|
|
349
|
-
if (( \${#violations[@]} > 0 )); then
|
|
350
|
-
echo "architecture boundary 检查失败:" >&2
|
|
351
|
-
for item in "\${violations[@]}"; do
|
|
352
|
-
echo " - \${item}" >&2
|
|
353
|
-
done
|
|
354
|
-
echo "请更新 \${boundary_doc} 中的层边界,或修正反向依赖。" >&2
|
|
355
|
-
exit 1
|
|
356
|
-
fi
|
|
357
|
-
|
|
358
|
-
echo "architecture boundary 检查通过"
|
|
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 检查通过"
|
|
359
361
|
`;
|
|
360
|
-
const INIT_CHECK_SKILL_ENTRY_SH = `#!/usr/bin/env bash
|
|
361
|
-
set -euo pipefail
|
|
362
|
-
|
|
363
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
364
|
-
cd "\${ROOT_DIR}"
|
|
365
|
-
|
|
366
|
-
skill_md="skills/loop-agent/SKILL.md"
|
|
367
|
-
if [[ ! -f "\${skill_md}" ]]; then
|
|
368
|
-
echo "skill entry 检查失败:缺少 \${skill_md}" >&2
|
|
369
|
-
exit 1
|
|
370
|
-
fi
|
|
371
|
-
|
|
372
|
-
required_refs=(
|
|
373
|
-
"references/harness-policy.md"
|
|
374
|
-
"references/hybrid-dag.md"
|
|
375
|
-
"references/verification-and-failure-handling.md"
|
|
376
|
-
"references/command-reference.md"
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
missing=()
|
|
380
|
-
for ref in "\${required_refs[@]}"; do
|
|
381
|
-
[[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
|
|
382
|
-
done
|
|
383
|
-
|
|
384
|
-
while IFS= read -r ref; do
|
|
385
|
-
[[ -z "\${ref}" ]] && continue
|
|
386
|
-
[[ -f "skills/loop-agent/\${ref}" ]] || missing+=("\${ref}")
|
|
387
|
-
done < <(grep -Eo 'references/[A-Za-z0-9._/-]+\\.md' "\${skill_md}" | sort -u || true)
|
|
388
|
-
|
|
389
|
-
if (( \${#missing[@]} > 0 )); then
|
|
390
|
-
echo "skill entry 检查失败:引用的 reference 文件不存在:" >&2
|
|
391
|
-
printf ' - %s\\n' "\${missing[@]}" >&2
|
|
392
|
-
exit 1
|
|
393
|
-
fi
|
|
394
|
-
|
|
395
|
-
line_count="$(wc -l < "\${skill_md}" | tr -d ' ')"
|
|
396
|
-
if [[ "\${line_count}" =~ ^[0-9]+$ && "\${line_count}" -gt 220 ]]; then
|
|
397
|
-
echo "skill entry 检查警告:\${skill_md} 行数为 \${line_count},建议保持入口精简并把细节放入 references/" >&2
|
|
398
|
-
fi
|
|
399
|
-
|
|
400
|
-
echo "skill entry 检查通过:references ok, lines=\${line_count}"
|
|
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}"
|
|
401
403
|
`;
|
|
402
|
-
const INIT_CHECK_REPO_SH = `#!/usr/bin/env bash
|
|
403
|
-
set -euo pipefail
|
|
404
|
-
|
|
405
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
406
|
-
cd "\${ROOT_DIR}"
|
|
407
|
-
|
|
408
|
-
checks=(
|
|
409
|
-
"scripts/check-engineering-structure.sh"
|
|
410
|
-
"scripts/check-doc-index.sh"
|
|
411
|
-
"scripts/check-doc-links.sh"
|
|
412
|
-
"scripts/check-active-plan-status.sh"
|
|
413
|
-
"scripts/check-exec-plan-index-sync.sh"
|
|
414
|
-
"scripts/check-harness-runtime-clean.sh"
|
|
415
|
-
"scripts/check-architecture-boundaries.sh"
|
|
416
|
-
"scripts/check-skill-entry.sh"
|
|
417
|
-
)
|
|
418
|
-
|
|
419
|
-
for check in "\${checks[@]}"; do
|
|
420
|
-
echo "==> bash \${check}"
|
|
421
|
-
bash "\${check}"
|
|
422
|
-
echo
|
|
423
|
-
done
|
|
424
|
-
|
|
425
|
-
echo "仓库治理检查全部通过"
|
|
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 "仓库治理检查全部通过"
|
|
426
428
|
`;
|
|
427
|
-
const INIT_CI_GOVERNANCE_SH = `#!/usr/bin/env bash
|
|
428
|
-
set -euo pipefail
|
|
429
|
-
|
|
430
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
431
|
-
cd "\${ROOT_DIR}"
|
|
432
|
-
|
|
433
|
-
bash scripts/check-repo.sh
|
|
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
|
|
434
436
|
`;
|
|
435
|
-
const INIT_CI_TESTS_SH = `#!/usr/bin/env bash
|
|
436
|
-
set -euo pipefail
|
|
437
|
-
|
|
438
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
439
|
-
cd "\${ROOT_DIR}"
|
|
440
|
-
|
|
441
|
-
ran=0
|
|
442
|
-
notes=()
|
|
443
|
-
|
|
444
|
-
run_cmd() {
|
|
445
|
-
echo "==> $*"
|
|
446
|
-
"$@"
|
|
447
|
-
ran=1
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
run_bash() {
|
|
451
|
-
echo "==> $*"
|
|
452
|
-
bash -lc "$*"
|
|
453
|
-
ran=1
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
has_make_target() {
|
|
457
|
-
local target="$1"
|
|
458
|
-
[[ -f Makefile || -f makefile ]] || return 1
|
|
459
|
-
grep -Eq "^[[:alnum:]_.-]*\${target}[[:alnum:]_.-]*:" Makefile makefile 2>/dev/null
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
has_npm_script() {
|
|
463
|
-
local script="$1"
|
|
464
|
-
[[ -f package.json ]] || return 1
|
|
465
|
-
command -v node >/dev/null 2>&1 || return 1
|
|
466
|
-
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
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if [[ -f package.json ]]; then
|
|
470
|
-
if command -v npm >/dev/null 2>&1; then
|
|
471
|
-
for script in lint typecheck test build; do
|
|
472
|
-
if has_npm_script "\${script}"; then
|
|
473
|
-
run_cmd npm run "\${script}"
|
|
474
|
-
fi
|
|
475
|
-
done
|
|
476
|
-
else
|
|
477
|
-
notes+=("package.json exists but npm is not available")
|
|
478
|
-
fi
|
|
479
|
-
fi
|
|
480
|
-
|
|
481
|
-
if has_make_target verify; then
|
|
482
|
-
run_cmd make verify
|
|
483
|
-
elif has_make_target test; then
|
|
484
|
-
run_cmd make test
|
|
485
|
-
fi
|
|
486
|
-
|
|
487
|
-
if [[ -f go.mod ]]; then
|
|
488
|
-
if command -v go >/dev/null 2>&1; then
|
|
489
|
-
run_cmd go test ./...
|
|
490
|
-
else
|
|
491
|
-
notes+=("go.mod exists but go is not available")
|
|
492
|
-
fi
|
|
493
|
-
fi
|
|
494
|
-
|
|
495
|
-
if [[ -f Cargo.toml ]]; then
|
|
496
|
-
if command -v cargo >/dev/null 2>&1; then
|
|
497
|
-
run_cmd cargo test
|
|
498
|
-
else
|
|
499
|
-
notes+=("Cargo.toml exists but cargo is not available")
|
|
500
|
-
fi
|
|
501
|
-
fi
|
|
502
|
-
|
|
503
|
-
if [[ -f pyproject.toml || -f pytest.ini || -f tox.ini ]]; then
|
|
504
|
-
if command -v pytest >/dev/null 2>&1; then
|
|
505
|
-
run_cmd pytest
|
|
506
|
-
elif command -v tox >/dev/null 2>&1 && [[ -f tox.ini ]]; then
|
|
507
|
-
run_cmd tox
|
|
508
|
-
else
|
|
509
|
-
notes+=("Python project metadata found but pytest/tox is not available")
|
|
510
|
-
fi
|
|
511
|
-
fi
|
|
512
|
-
|
|
513
|
-
if [[ -f pom.xml ]]; then
|
|
514
|
-
if command -v mvn >/dev/null 2>&1; then
|
|
515
|
-
run_cmd mvn test
|
|
516
|
-
else
|
|
517
|
-
notes+=("pom.xml exists but mvn is not available")
|
|
518
|
-
fi
|
|
519
|
-
fi
|
|
520
|
-
|
|
521
|
-
if [[ -f build.gradle || -f build.gradle.kts || -f settings.gradle || -f settings.gradle.kts ]]; then
|
|
522
|
-
if [[ -x ./gradlew ]]; then
|
|
523
|
-
run_cmd ./gradlew test
|
|
524
|
-
elif command -v gradle >/dev/null 2>&1; then
|
|
525
|
-
run_cmd gradle test
|
|
526
|
-
else
|
|
527
|
-
notes+=("Gradle project metadata found but gradle/gradlew is not available")
|
|
528
|
-
fi
|
|
529
|
-
fi
|
|
530
|
-
|
|
531
|
-
if compgen -G "*.sln" >/dev/null || compgen -G "*.csproj" >/dev/null; then
|
|
532
|
-
if command -v dotnet >/dev/null 2>&1; then
|
|
533
|
-
run_cmd dotnet test
|
|
534
|
-
else
|
|
535
|
-
notes+=(".NET project metadata found but dotnet is not available")
|
|
536
|
-
fi
|
|
537
|
-
fi
|
|
538
|
-
|
|
539
|
-
if (( ran == 0 )); then
|
|
540
|
-
echo "未检测到可安全自动运行的项目测试入口。"
|
|
541
|
-
echo "loop-agent 治理检查仍可运行;请让初始化模型根据目标项目技术栈补充 scripts/ci-tests.sh 与 docs/verification-matrix.md。"
|
|
542
|
-
fi
|
|
543
|
-
|
|
544
|
-
if (( \${#notes[@]} > 0 )); then
|
|
545
|
-
echo "项目验证探测备注:"
|
|
546
|
-
for note in "\${notes[@]}"; do
|
|
547
|
-
echo " - \${note}"
|
|
548
|
-
done
|
|
549
|
-
fi
|
|
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
|
|
550
552
|
`;
|
|
551
|
-
const INIT_CI_SH = `#!/usr/bin/env bash
|
|
552
|
-
set -euo pipefail
|
|
553
|
-
|
|
554
|
-
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
555
|
-
cd "\${ROOT_DIR}"
|
|
556
|
-
|
|
557
|
-
bash scripts/ci-governance.sh
|
|
558
|
-
bash scripts/ci-tests.sh
|
|
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
|
|
559
561
|
`;
|
|
560
562
|
const INIT_SCRIPT_FILES = {
|
|
561
563
|
"scripts/check-engineering-structure.sh": INIT_CHECK_ENGINEERING_STRUCTURE_SH,
|
|
@@ -846,6 +848,236 @@ async function writeCompatPrompts(input) {
|
|
|
846
848
|
});
|
|
847
849
|
}
|
|
848
850
|
}
|
|
851
|
+
function sha256Text(value) {
|
|
852
|
+
return createHash("sha256").update(value).digest("hex");
|
|
853
|
+
}
|
|
854
|
+
function sha256Buffer(value) {
|
|
855
|
+
return createHash("sha256").update(value).digest("hex");
|
|
856
|
+
}
|
|
857
|
+
function isRecord(value) {
|
|
858
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
859
|
+
}
|
|
860
|
+
function normalizeRelativePath(relativePath) {
|
|
861
|
+
return relativePath.split(path.sep).join("/");
|
|
862
|
+
}
|
|
863
|
+
function manifestPathToTargetPath(relativePath, governanceRoot) {
|
|
864
|
+
if (governanceRoot !== "docs" && relativePath.startsWith("docs/")) {
|
|
865
|
+
return `${governanceRoot}/${relativePath.slice("docs/".length)}`;
|
|
866
|
+
}
|
|
867
|
+
return relativePath;
|
|
868
|
+
}
|
|
869
|
+
function targetPathToManifestPath(relativePath, governanceRoot) {
|
|
870
|
+
if (governanceRoot !== "docs" && relativePath.startsWith(`${governanceRoot}/`)) {
|
|
871
|
+
return `docs/${relativePath.slice(`${governanceRoot}/`.length)}`;
|
|
872
|
+
}
|
|
873
|
+
return relativePath;
|
|
874
|
+
}
|
|
875
|
+
async function readPackageVersion(assetRoot) {
|
|
876
|
+
const packageJson = JSON.parse(await readFile(path.join(assetRoot, "package.json"), "utf-8"));
|
|
877
|
+
return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
878
|
+
}
|
|
879
|
+
async function readInitSurfaceManifest(assetRoot) {
|
|
880
|
+
const manifestPath = path.join(assetRoot, "docs", "init-surface.manifest.json");
|
|
881
|
+
const raw = await readFile(manifestPath, "utf-8");
|
|
882
|
+
const manifest = JSON.parse(raw);
|
|
883
|
+
const initFullRequired = manifest.initFullRequired ?? [];
|
|
884
|
+
const initSurface = manifest.initSurface ?? {};
|
|
885
|
+
const entries = initFullRequired.map((relativePath) => ({
|
|
886
|
+
path: relativePath,
|
|
887
|
+
mode: initSurface[relativePath] ?? inferInitSurfaceMode(relativePath),
|
|
888
|
+
}));
|
|
889
|
+
if (!entries.some((entry) => entry.path === INIT_SURFACE_STATE_PATH)) {
|
|
890
|
+
entries.push({ path: INIT_SURFACE_STATE_PATH, mode: "state" });
|
|
891
|
+
}
|
|
892
|
+
return { manifest, raw, sha256: sha256Text(raw), entries };
|
|
893
|
+
}
|
|
894
|
+
function inferInitSurfaceMode(relativePath) {
|
|
895
|
+
if (relativePath === INIT_SURFACE_STATE_PATH)
|
|
896
|
+
return "state";
|
|
897
|
+
if (relativePath === ".harness/tasks" ||
|
|
898
|
+
relativePath === ".harness/dag-runs/active" ||
|
|
899
|
+
relativePath.endsWith("/")) {
|
|
900
|
+
return "directory";
|
|
901
|
+
}
|
|
902
|
+
if (relativePath === "README.md" || relativePath === "AGENTS.md")
|
|
903
|
+
return "managed-block";
|
|
904
|
+
if (relativePath === "harness.json" ||
|
|
905
|
+
relativePath.startsWith("scripts/") ||
|
|
906
|
+
relativePath.startsWith(".harness/prompts/") ||
|
|
907
|
+
relativePath.startsWith("docs/README.md") ||
|
|
908
|
+
relativePath.startsWith("docs/development-principles.md") ||
|
|
909
|
+
relativePath.startsWith("docs/feature-workflow.md") ||
|
|
910
|
+
relativePath.startsWith("docs/verification-matrix.md") ||
|
|
911
|
+
relativePath.startsWith("docs/loop-agent-harness.md") ||
|
|
912
|
+
relativePath.startsWith("docs/architecture/runtime-boundaries.md")) {
|
|
913
|
+
return "generated";
|
|
914
|
+
}
|
|
915
|
+
return "copied";
|
|
916
|
+
}
|
|
917
|
+
async function buildDesiredSurfaceContent(input) {
|
|
918
|
+
const manifestPath = targetPathToManifestPath(input.entry.path, input.governanceRoot);
|
|
919
|
+
if (input.entry.mode === "directory" || input.entry.mode === "state")
|
|
920
|
+
return {};
|
|
921
|
+
if (manifestPath === "README.md") {
|
|
922
|
+
return {
|
|
923
|
+
content: buildManagedReadmeBlock({
|
|
924
|
+
projectName: input.projectName,
|
|
925
|
+
governanceRoot: input.governanceRoot,
|
|
926
|
+
}),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (manifestPath === "AGENTS.md") {
|
|
930
|
+
return {
|
|
931
|
+
content: buildManagedAgentsBlock({
|
|
932
|
+
projectName: input.projectName,
|
|
933
|
+
governanceRoot: input.governanceRoot,
|
|
934
|
+
}),
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
if (manifestPath === "harness.json") {
|
|
938
|
+
const template = await readJsonIfExists(path.join(input.assetRoot, "harness.json"));
|
|
939
|
+
return {
|
|
940
|
+
content: `${JSON.stringify(buildHarness({
|
|
941
|
+
existing: {},
|
|
942
|
+
template,
|
|
943
|
+
projectName: input.projectName,
|
|
944
|
+
governanceRoot: input.governanceRoot,
|
|
945
|
+
}), null, 2)}\n`,
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
if (manifestPath.startsWith("scripts/")) {
|
|
949
|
+
const scripts = buildInitScriptFiles(input.governanceRoot);
|
|
950
|
+
return { content: scripts[manifestPath] };
|
|
951
|
+
}
|
|
952
|
+
if (manifestPath.startsWith(".harness/prompts/")) {
|
|
953
|
+
const name = path.basename(manifestPath);
|
|
954
|
+
return { content: COMPAT_PROMPTS[name] };
|
|
955
|
+
}
|
|
956
|
+
if (manifestPath.startsWith("docs/")) {
|
|
957
|
+
const doc = manifestPath.slice("docs/".length);
|
|
958
|
+
const generated = buildGeneratedCoreDoc({
|
|
959
|
+
doc,
|
|
960
|
+
projectName: input.projectName,
|
|
961
|
+
governanceRoot: input.governanceRoot,
|
|
962
|
+
});
|
|
963
|
+
if (generated !== undefined)
|
|
964
|
+
return { content: generated };
|
|
965
|
+
}
|
|
966
|
+
const sourcePath = path.join(input.assetRoot, manifestPath);
|
|
967
|
+
if (await exists(sourcePath))
|
|
968
|
+
return { content: await readFile(sourcePath, "utf-8"), sourcePath };
|
|
969
|
+
return {};
|
|
970
|
+
}
|
|
971
|
+
function extractManagedBlock(content) {
|
|
972
|
+
const start = content.indexOf(MANAGED_BLOCK_START);
|
|
973
|
+
const end = content.indexOf(MANAGED_BLOCK_END);
|
|
974
|
+
if (start < 0 || end <= start)
|
|
975
|
+
return undefined;
|
|
976
|
+
return content.slice(start, end + MANAGED_BLOCK_END.length);
|
|
977
|
+
}
|
|
978
|
+
async function readExistingSurfaceState(repoRoot) {
|
|
979
|
+
const filePath = path.join(repoRoot, INIT_SURFACE_STATE_PATH);
|
|
980
|
+
if (!(await exists(filePath)))
|
|
981
|
+
return undefined;
|
|
982
|
+
const parsed = JSON.parse(await readFile(filePath, "utf-8"));
|
|
983
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== 1)
|
|
984
|
+
return undefined;
|
|
985
|
+
return parsed;
|
|
986
|
+
}
|
|
987
|
+
async function buildCurrentSurfaceState(input) {
|
|
988
|
+
const assetRoot = await findPackageRoot();
|
|
989
|
+
const controllerVersion = await readPackageVersion(assetRoot);
|
|
990
|
+
const manifest = await readInitSurfaceManifest(assetRoot);
|
|
991
|
+
const files = {};
|
|
992
|
+
for (const manifestEntry of manifest.entries) {
|
|
993
|
+
const targetRelativePath = normalizeRelativePath(manifestPathToTargetPath(manifestEntry.path, input.governanceRoot));
|
|
994
|
+
const entry = { ...manifestEntry, path: targetRelativePath };
|
|
995
|
+
const targetPath = path.join(input.repoRoot, targetRelativePath);
|
|
996
|
+
const targetStat = await stat(targetPath).catch(() => null);
|
|
997
|
+
const desired = await buildDesiredSurfaceContent({
|
|
998
|
+
assetRoot,
|
|
999
|
+
repoRoot: input.repoRoot,
|
|
1000
|
+
projectName: input.projectName,
|
|
1001
|
+
governanceRoot: input.governanceRoot,
|
|
1002
|
+
entry,
|
|
1003
|
+
});
|
|
1004
|
+
const sourceSha256 = desired.content === undefined ? undefined : sha256Text(desired.content);
|
|
1005
|
+
const base = {
|
|
1006
|
+
status: targetStat ? "present" : "missing",
|
|
1007
|
+
relationship: "missing-from-target",
|
|
1008
|
+
sourceSha256,
|
|
1009
|
+
sourcePath: desired.sourcePath ? repoRelative(assetRoot, desired.sourcePath) : undefined,
|
|
1010
|
+
mode: entry.mode,
|
|
1011
|
+
};
|
|
1012
|
+
if (entry.mode === "state") {
|
|
1013
|
+
files[targetRelativePath] = {
|
|
1014
|
+
...base,
|
|
1015
|
+
status: "present",
|
|
1016
|
+
relationship: "state-file",
|
|
1017
|
+
};
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (!targetStat) {
|
|
1021
|
+
files[targetRelativePath] = base;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (entry.mode === "directory") {
|
|
1025
|
+
files[targetRelativePath] = {
|
|
1026
|
+
...base,
|
|
1027
|
+
relationship: targetStat.isDirectory() ? "directory-present" : "local-existing-unknown",
|
|
1028
|
+
};
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const current = await readFile(targetPath);
|
|
1032
|
+
const currentSha256 = sha256Buffer(current);
|
|
1033
|
+
if (entry.mode === "managed-block") {
|
|
1034
|
+
const currentBlock = extractManagedBlock(current.toString("utf-8"));
|
|
1035
|
+
files[targetRelativePath] = {
|
|
1036
|
+
...base,
|
|
1037
|
+
currentSha256,
|
|
1038
|
+
relationship: currentBlock && sourceSha256 && sha256Text(currentBlock) === sourceSha256
|
|
1039
|
+
? "managed-block-current"
|
|
1040
|
+
: "managed-block-present",
|
|
1041
|
+
};
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
files[targetRelativePath] = {
|
|
1045
|
+
...base,
|
|
1046
|
+
currentSha256,
|
|
1047
|
+
relationship: sourceSha256 && currentSha256 === sourceSha256
|
|
1048
|
+
? entry.mode === "generated"
|
|
1049
|
+
? "matches-current-generated"
|
|
1050
|
+
: "matches-current-package"
|
|
1051
|
+
: "local-existing-unknown",
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
return {
|
|
1055
|
+
schemaVersion: 1,
|
|
1056
|
+
controllerVersion,
|
|
1057
|
+
stateKind: input.stateKind,
|
|
1058
|
+
generatedAt: input.generatedAt ?? new Date().toISOString(),
|
|
1059
|
+
manifestSha256: manifest.sha256,
|
|
1060
|
+
files,
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
async function writeInitSurfaceState(input) {
|
|
1064
|
+
const state = await buildCurrentSurfaceState(input);
|
|
1065
|
+
const target = path.join(input.repoRoot, INIT_SURFACE_STATE_PATH);
|
|
1066
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
1067
|
+
await writeFile(target, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
1068
|
+
return state;
|
|
1069
|
+
}
|
|
1070
|
+
async function resolveInitProjectContext(input) {
|
|
1071
|
+
const harness = await readJsonIfExists(path.join(input.repoRoot, "harness.json"));
|
|
1072
|
+
return {
|
|
1073
|
+
projectName: input.projectName ??
|
|
1074
|
+
(typeof harness.project === "string" ? harness.project : undefined) ??
|
|
1075
|
+
path.basename(input.repoRoot),
|
|
1076
|
+
governanceRoot: input.governanceRoot ??
|
|
1077
|
+
(typeof harness.governanceRoot === "string" ? harness.governanceRoot : undefined) ??
|
|
1078
|
+
"docs",
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
849
1081
|
function buildManagedReadmeBlock(input) {
|
|
850
1082
|
return [
|
|
851
1083
|
MANAGED_BLOCK_START,
|
|
@@ -1389,8 +1621,277 @@ export async function initializeLoopAgentProject(options) {
|
|
|
1389
1621
|
}
|
|
1390
1622
|
await ensureHarnessDirs(repoRoot, written);
|
|
1391
1623
|
await writeCompatPrompts({ repoRoot, merge, written, skipped });
|
|
1624
|
+
await writeInitSurfaceState({ repoRoot, projectName, governanceRoot, stateKind: "recorded" });
|
|
1625
|
+
written.push(INIT_SURFACE_STATE_PATH);
|
|
1392
1626
|
return { repoRoot, projectName, governanceRoot, profile, written, skipped };
|
|
1393
1627
|
}
|
|
1628
|
+
function actionForMissing(pathName, state) {
|
|
1629
|
+
if (state.mode === "state")
|
|
1630
|
+
return undefined;
|
|
1631
|
+
if (state.mode === "directory") {
|
|
1632
|
+
return { type: "create-directory", path: pathName, reason: "required init directory is missing" };
|
|
1633
|
+
}
|
|
1634
|
+
if (state.mode === "copied") {
|
|
1635
|
+
return { type: "copy-missing", path: pathName, reason: "required bundled init file is missing" };
|
|
1636
|
+
}
|
|
1637
|
+
if (state.mode === "generated") {
|
|
1638
|
+
return { type: "write-generated-missing", path: pathName, reason: "required generated init file is missing" };
|
|
1639
|
+
}
|
|
1640
|
+
return { type: "refresh-managed-block", path: pathName, reason: "managed block file is missing or stale" };
|
|
1641
|
+
}
|
|
1642
|
+
function modelMergeTaskFor(pathName, state, allPaths) {
|
|
1643
|
+
return {
|
|
1644
|
+
path: pathName,
|
|
1645
|
+
reason: "target file exists but does not match the current loop-agent initialization surface",
|
|
1646
|
+
sourcePath: state.sourcePath,
|
|
1647
|
+
allowedPaths: [pathName],
|
|
1648
|
+
forbiddenPaths: [
|
|
1649
|
+
...allPaths.filter((candidate) => candidate !== pathName),
|
|
1650
|
+
".harness/dag-runs/**",
|
|
1651
|
+
".harness/runs/**",
|
|
1652
|
+
".git/**",
|
|
1653
|
+
],
|
|
1654
|
+
mergeRules: [
|
|
1655
|
+
"Preserve target-project user content and project-specific commands.",
|
|
1656
|
+
"Adopt the current loop-agent initialization structure where it does not conflict with local intent.",
|
|
1657
|
+
"Do not delete user-authored sections merely because they differ from the package template.",
|
|
1658
|
+
"Keep writes inside allowedPaths only.",
|
|
1659
|
+
],
|
|
1660
|
+
verification: [
|
|
1661
|
+
"loop-agent init check-update --repo-root . --json",
|
|
1662
|
+
"loop-agent init doctor --repo-root .",
|
|
1663
|
+
"loop-agent inspect --repo-root .",
|
|
1664
|
+
"bash scripts/check-repo.sh",
|
|
1665
|
+
],
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function recommendedNextFor(report) {
|
|
1669
|
+
const next = [];
|
|
1670
|
+
if (report.surfaceState === "missing") {
|
|
1671
|
+
next.push("loop-agent init update --repo-root <target> --bootstrap-surface");
|
|
1672
|
+
}
|
|
1673
|
+
if (report.deterministicActions.some((action) => action.type !== "bootstrap-surface")) {
|
|
1674
|
+
next.push("loop-agent init update --repo-root <target> --apply-safe");
|
|
1675
|
+
}
|
|
1676
|
+
if (report.modelMergeTasks.length > 0) {
|
|
1677
|
+
next.push("loop-agent init check-update --repo-root <target> --markdown");
|
|
1678
|
+
}
|
|
1679
|
+
if (report.humanDecisions.length > 0) {
|
|
1680
|
+
next.push("Review humanDecisions before applying semantic or policy-sensitive changes.");
|
|
1681
|
+
}
|
|
1682
|
+
if (next.length === 0)
|
|
1683
|
+
next.push("No init update actions are needed.");
|
|
1684
|
+
return next;
|
|
1685
|
+
}
|
|
1686
|
+
export async function checkInitUpdate(input) {
|
|
1687
|
+
const repoRoot = path.resolve(input.repoRoot);
|
|
1688
|
+
const { projectName, governanceRoot } = await resolveInitProjectContext({
|
|
1689
|
+
repoRoot,
|
|
1690
|
+
projectName: input.projectName,
|
|
1691
|
+
governanceRoot: input.governanceRoot,
|
|
1692
|
+
});
|
|
1693
|
+
const recordedState = await readExistingSurfaceState(repoRoot);
|
|
1694
|
+
const surfaceState = recordedState?.stateKind ?? "missing";
|
|
1695
|
+
const currentState = await buildCurrentSurfaceState({
|
|
1696
|
+
repoRoot,
|
|
1697
|
+
projectName,
|
|
1698
|
+
governanceRoot,
|
|
1699
|
+
stateKind: recordedState?.stateKind ?? "inferred-baseline",
|
|
1700
|
+
});
|
|
1701
|
+
const allPaths = Object.keys(currentState.files).sort();
|
|
1702
|
+
const deterministicActions = [];
|
|
1703
|
+
const modelMergeTasks = [];
|
|
1704
|
+
const humanDecisions = [];
|
|
1705
|
+
if (!recordedState) {
|
|
1706
|
+
deterministicActions.push({
|
|
1707
|
+
type: "bootstrap-surface",
|
|
1708
|
+
path: INIT_SURFACE_STATE_PATH,
|
|
1709
|
+
reason: "target project has no recorded init surface baseline",
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
for (const [pathName, state] of Object.entries(currentState.files)) {
|
|
1713
|
+
if (state.relationship === "missing-from-target") {
|
|
1714
|
+
const action = actionForMissing(pathName, state);
|
|
1715
|
+
if (action)
|
|
1716
|
+
deterministicActions.push(action);
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
if (state.relationship === "managed-block-present") {
|
|
1720
|
+
deterministicActions.push({
|
|
1721
|
+
type: "refresh-managed-block",
|
|
1722
|
+
path: pathName,
|
|
1723
|
+
reason: "managed block is missing or differs from the current package block",
|
|
1724
|
+
});
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
if (state.relationship === "local-existing-unknown") {
|
|
1728
|
+
if (state.mode === "directory") {
|
|
1729
|
+
humanDecisions.push({
|
|
1730
|
+
path: pathName,
|
|
1731
|
+
reason: "required init directory path exists but is not a directory",
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
modelMergeTasks.push(modelMergeTaskFor(pathName, state, allPaths));
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
const partial = {
|
|
1740
|
+
repoRoot,
|
|
1741
|
+
controllerVersion: currentState.controllerVersion,
|
|
1742
|
+
surfaceState,
|
|
1743
|
+
deterministicActions,
|
|
1744
|
+
modelMergeTasks,
|
|
1745
|
+
humanDecisions,
|
|
1746
|
+
summary: {
|
|
1747
|
+
missing: deterministicActions.filter((action) => action.type !== "bootstrap-surface").length,
|
|
1748
|
+
modelMerge: modelMergeTasks.length,
|
|
1749
|
+
humanDecision: humanDecisions.length,
|
|
1750
|
+
},
|
|
1751
|
+
};
|
|
1752
|
+
const recommendedNext = recommendedNextFor(partial);
|
|
1753
|
+
return {
|
|
1754
|
+
...partial,
|
|
1755
|
+
ok: deterministicActions.length === 0 &&
|
|
1756
|
+
modelMergeTasks.length === 0 &&
|
|
1757
|
+
humanDecisions.length === 0,
|
|
1758
|
+
recommendedNext,
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
async function applySafeAction(input) {
|
|
1762
|
+
const assetRoot = await findPackageRoot();
|
|
1763
|
+
const entry = {
|
|
1764
|
+
path: input.action.path,
|
|
1765
|
+
mode: input.action.type === "create-directory"
|
|
1766
|
+
? "directory"
|
|
1767
|
+
: input.action.type === "copy-missing"
|
|
1768
|
+
? "copied"
|
|
1769
|
+
: input.action.type === "refresh-managed-block"
|
|
1770
|
+
? "managed-block"
|
|
1771
|
+
: "generated",
|
|
1772
|
+
};
|
|
1773
|
+
const target = path.join(input.repoRoot, input.action.path);
|
|
1774
|
+
if (input.action.type === "create-directory") {
|
|
1775
|
+
if (await exists(target))
|
|
1776
|
+
return false;
|
|
1777
|
+
await mkdir(target, { recursive: true });
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
const desired = await buildDesiredSurfaceContent({
|
|
1781
|
+
assetRoot,
|
|
1782
|
+
repoRoot: input.repoRoot,
|
|
1783
|
+
projectName: input.projectName,
|
|
1784
|
+
governanceRoot: input.governanceRoot,
|
|
1785
|
+
entry,
|
|
1786
|
+
});
|
|
1787
|
+
if (desired.content === undefined)
|
|
1788
|
+
return false;
|
|
1789
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
1790
|
+
if (input.action.type === "refresh-managed-block") {
|
|
1791
|
+
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);
|
|
1797
|
+
await writeFile(target, next, "utf-8");
|
|
1798
|
+
return true;
|
|
1799
|
+
}
|
|
1800
|
+
if (await exists(target))
|
|
1801
|
+
return false;
|
|
1802
|
+
await writeFile(target, desired.content, "utf-8");
|
|
1803
|
+
return true;
|
|
1804
|
+
}
|
|
1805
|
+
export async function applyInitUpdate(input) {
|
|
1806
|
+
const repoRoot = path.resolve(input.repoRoot);
|
|
1807
|
+
const { projectName, governanceRoot } = await resolveInitProjectContext({
|
|
1808
|
+
repoRoot,
|
|
1809
|
+
projectName: input.projectName,
|
|
1810
|
+
governanceRoot: input.governanceRoot,
|
|
1811
|
+
});
|
|
1812
|
+
const applied = [];
|
|
1813
|
+
const skipped = [];
|
|
1814
|
+
if (input.bootstrapSurface) {
|
|
1815
|
+
await writeInitSurfaceState({ repoRoot, projectName, governanceRoot, stateKind: "inferred-baseline" });
|
|
1816
|
+
applied.push({
|
|
1817
|
+
type: "bootstrap-surface",
|
|
1818
|
+
path: INIT_SURFACE_STATE_PATH,
|
|
1819
|
+
reason: "wrote inferred init surface baseline",
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
if (input.applySafe) {
|
|
1823
|
+
const report = await checkInitUpdate({ repoRoot, projectName, governanceRoot });
|
|
1824
|
+
for (const action of report.deterministicActions) {
|
|
1825
|
+
if (action.type === "bootstrap-surface") {
|
|
1826
|
+
skipped.push(action);
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
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;
|
|
1839
|
+
}
|
|
1840
|
+
if (await applySafeAction({ repoRoot, projectName, governanceRoot, action }))
|
|
1841
|
+
applied.push(action);
|
|
1842
|
+
else
|
|
1843
|
+
skipped.push(action);
|
|
1844
|
+
}
|
|
1845
|
+
await writeInitSurfaceState({ repoRoot, projectName, governanceRoot, stateKind: "inferred-baseline" });
|
|
1846
|
+
}
|
|
1847
|
+
return {
|
|
1848
|
+
applied,
|
|
1849
|
+
skipped,
|
|
1850
|
+
report: await checkInitUpdate({ repoRoot, projectName, governanceRoot }),
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function formatCheckUpdateText(report) {
|
|
1854
|
+
return [
|
|
1855
|
+
`loop-agent init update check for ${report.repoRoot}`,
|
|
1856
|
+
`controllerVersion: ${report.controllerVersion}`,
|
|
1857
|
+
`surfaceState: ${report.surfaceState}`,
|
|
1858
|
+
`deterministicActions: ${report.deterministicActions.length}`,
|
|
1859
|
+
`modelMergeTasks: ${report.modelMergeTasks.length}`,
|
|
1860
|
+
`humanDecisions: ${report.humanDecisions.length}`,
|
|
1861
|
+
"recommendedNext:",
|
|
1862
|
+
...report.recommendedNext.map((item) => `- ${item}`),
|
|
1863
|
+
].join("\n");
|
|
1864
|
+
}
|
|
1865
|
+
function formatCheckUpdateMarkdown(report) {
|
|
1866
|
+
const lines = [
|
|
1867
|
+
"# loop-agent init check-update",
|
|
1868
|
+
"",
|
|
1869
|
+
`- repoRoot: \`${report.repoRoot}\``,
|
|
1870
|
+
`- controllerVersion: \`${report.controllerVersion}\``,
|
|
1871
|
+
`- surfaceState: \`${report.surfaceState}\``,
|
|
1872
|
+
"",
|
|
1873
|
+
"## Deterministic Actions",
|
|
1874
|
+
"",
|
|
1875
|
+
...(report.deterministicActions.length
|
|
1876
|
+
? report.deterministicActions.map((action) => `- \`${action.type}\` \`${action.path}\`: ${action.reason}`)
|
|
1877
|
+
: ["- None"]),
|
|
1878
|
+
"",
|
|
1879
|
+
"## Model Merge Tasks",
|
|
1880
|
+
"",
|
|
1881
|
+
];
|
|
1882
|
+
if (report.modelMergeTasks.length === 0) {
|
|
1883
|
+
lines.push("- None", "");
|
|
1884
|
+
}
|
|
1885
|
+
else {
|
|
1886
|
+
for (const task of report.modelMergeTasks) {
|
|
1887
|
+
lines.push(`### ${task.path}`, "", task.reason, "", "allowedPaths:", ...task.allowedPaths.map((item) => `- ${item}`), "", "forbiddenPaths:", ...task.forbiddenPaths.map((item) => `- ${item}`), "", "mergeRules:", ...task.mergeRules.map((item) => `- ${item}`), "", "verification:", ...task.verification.map((item) => `- ${item}`), "");
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
lines.push("## Human Decisions", "", ...(report.humanDecisions.length
|
|
1891
|
+
? report.humanDecisions.map((decision) => `- \`${decision.path}\`: ${decision.reason}`)
|
|
1892
|
+
: ["- None"]), "", "## Recommended Next", "", ...report.recommendedNext.map((item) => `- ${item}`), "");
|
|
1893
|
+
return lines.join("\n");
|
|
1894
|
+
}
|
|
1394
1895
|
export async function runInitDoctor(input) {
|
|
1395
1896
|
const repoRoot = path.resolve(input.repoRoot);
|
|
1396
1897
|
const checks = [];
|
|
@@ -1432,9 +1933,12 @@ function parseInitArgs(repoRoot, args) {
|
|
|
1432
1933
|
let provider;
|
|
1433
1934
|
let model;
|
|
1434
1935
|
let json = false;
|
|
1936
|
+
let markdown = false;
|
|
1937
|
+
let bootstrapSurface = false;
|
|
1938
|
+
let applySafe = false;
|
|
1435
1939
|
for (let i = 0; i < args.length; i += 1) {
|
|
1436
1940
|
const arg = args[i];
|
|
1437
|
-
if ((arg === "instructions" || arg === "doctor") && !subcommand) {
|
|
1941
|
+
if ((arg === "instructions" || arg === "doctor" || arg === "check-update" || arg === "update") && !subcommand) {
|
|
1438
1942
|
subcommand = arg;
|
|
1439
1943
|
continue;
|
|
1440
1944
|
}
|
|
@@ -1464,6 +1968,12 @@ function parseInitArgs(repoRoot, args) {
|
|
|
1464
1968
|
model = arg.slice("--model=".length);
|
|
1465
1969
|
else if (arg === "--json")
|
|
1466
1970
|
json = true;
|
|
1971
|
+
else if (arg === "--markdown")
|
|
1972
|
+
markdown = true;
|
|
1973
|
+
else if (arg === "--bootstrap-surface")
|
|
1974
|
+
bootstrapSurface = true;
|
|
1975
|
+
else if (arg === "--apply-safe")
|
|
1976
|
+
applySafe = true;
|
|
1467
1977
|
else if (arg.startsWith("-"))
|
|
1468
1978
|
throw new Error(`unknown init flag: ${arg}`);
|
|
1469
1979
|
else
|
|
@@ -1471,7 +1981,20 @@ function parseInitArgs(repoRoot, args) {
|
|
|
1471
1981
|
}
|
|
1472
1982
|
if (profile !== "full" && profile !== "minimal")
|
|
1473
1983
|
throw new Error("init --profile must be full or minimal");
|
|
1474
|
-
return {
|
|
1984
|
+
return {
|
|
1985
|
+
repoRoot,
|
|
1986
|
+
projectName,
|
|
1987
|
+
governanceRoot,
|
|
1988
|
+
profile,
|
|
1989
|
+
merge,
|
|
1990
|
+
provider,
|
|
1991
|
+
model,
|
|
1992
|
+
subcommand,
|
|
1993
|
+
json,
|
|
1994
|
+
markdown,
|
|
1995
|
+
bootstrapSurface,
|
|
1996
|
+
applySafe,
|
|
1997
|
+
};
|
|
1475
1998
|
}
|
|
1476
1999
|
export async function runInit(repoRoot, rawArgs) {
|
|
1477
2000
|
const parsed = parseInitArgs(repoRoot, rawArgs);
|
|
@@ -1486,9 +2009,38 @@ export async function runInit(repoRoot, rawArgs) {
|
|
|
1486
2009
|
process.exitCode = 1;
|
|
1487
2010
|
return;
|
|
1488
2011
|
}
|
|
2012
|
+
if (parsed.subcommand === "check-update") {
|
|
2013
|
+
const report = await checkInitUpdate(parsed);
|
|
2014
|
+
if (parsed.json)
|
|
2015
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2016
|
+
else if (parsed.markdown)
|
|
2017
|
+
console.log(formatCheckUpdateMarkdown(report));
|
|
2018
|
+
else
|
|
2019
|
+
console.log(formatCheckUpdateText(report));
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
if (parsed.subcommand === "update") {
|
|
2023
|
+
if (!parsed.bootstrapSurface && !parsed.applySafe) {
|
|
2024
|
+
throw new Error("usage: init update [--bootstrap-surface] [--apply-safe]");
|
|
2025
|
+
}
|
|
2026
|
+
const result = await applyInitUpdate(parsed);
|
|
2027
|
+
console.log(parsed.json ? JSON.stringify(result, null, 2) : formatInitUpdateResult(result));
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
1489
2030
|
const result = await initializeLoopAgentProject(parsed);
|
|
1490
2031
|
console.log(parsed.json ? JSON.stringify(result, null, 2) : formatInitResult(result));
|
|
1491
2032
|
}
|
|
2033
|
+
function formatInitUpdateResult(result) {
|
|
2034
|
+
return [
|
|
2035
|
+
"loop-agent init update complete",
|
|
2036
|
+
`applied: ${result.applied.length}`,
|
|
2037
|
+
`skipped: ${result.skipped.length}`,
|
|
2038
|
+
`remaining deterministicActions: ${result.report.deterministicActions.length}`,
|
|
2039
|
+
`remaining modelMergeTasks: ${result.report.modelMergeTasks.length}`,
|
|
2040
|
+
"recommendedNext:",
|
|
2041
|
+
...result.report.recommendedNext.map((item) => `- ${item}`),
|
|
2042
|
+
].join("\n");
|
|
2043
|
+
}
|
|
1492
2044
|
function formatInitResult(result) {
|
|
1493
2045
|
return [
|
|
1494
2046
|
`Initialized loop-agent harness at ${result.repoRoot}`,
|