@tea-agent/loop-agent 0.10.0 → 0.12.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 +10 -2
- package/CHANGELOG.md +91 -24
- package/README.md +84 -12
- package/dist/application/dag/args.js +1 -12
- package/dist/application/dag/generate-task-dag.js +38 -2
- package/dist/application/dag/run-dag.js +11 -27
- package/dist/application/dag/validate-dag.js +13 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +44 -16
- package/dist/cli/program.js +40 -23
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +58 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli.js +33 -0
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +547 -95
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/commands/plan.js +50 -0
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +1 -1
- package/dist/governance/checks.js +6 -3
- package/dist/governance/exec-plans.js +545 -0
- package/dist/governance/manifest-types.js +24 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +488 -0
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +29 -12
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +1 -2
- package/dist/worker/cli.js +32 -3
- package/dist/worker/delivery/final-verification.js +47 -11
- package/dist/worker/delivery/package.js +63 -10
- package/dist/worker/feature/run.js +60 -8
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/read-model.js +338 -83
- package/dist/worker/observe/paths.js +17 -0
- package/dist/worker/observe/routes.js +165 -21
- package/dist/worker/observe/server.js +59 -1
- package/dist/worker/observe/static/api.js +27 -0
- package/dist/worker/observe/static/app.js +120 -2317
- package/dist/worker/observe/static/constants.js +148 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/dag-helpers.js +172 -0
- package/dist/worker/observe/static/dag-model.js +72 -0
- package/dist/worker/observe/static/dom.js +61 -0
- package/dist/worker/observe/static/format-pool.js +67 -0
- package/dist/worker/observe/static/format.js +292 -0
- package/dist/worker/observe/static/index.html +300 -82
- package/dist/worker/observe/static/kpi.js +94 -0
- package/dist/worker/observe/static/relations.js +128 -0
- package/dist/worker/observe/static/router.js +85 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +68 -0
- package/dist/worker/observe/static/state.js +253 -0
- package/dist/worker/observe/static/styles.css +1720 -495
- package/dist/worker/observe/static/views/batch.js +226 -0
- package/dist/worker/observe/static/views/dag-graph.js +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +477 -0
- package/dist/worker/observe/static/views/dag.js +362 -0
- package/dist/worker/observe/static/views/dashboard.js +442 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +453 -0
- package/dist/worker/observe/static/views/pool.js +347 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +205 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +260 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/run-task/run-task.js +22 -12
- package/dist/worker/runner/run-ready.js +76 -12
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/init-hybrid.js +797 -27
- package/dist/workflows/dag/node-execution.js +183 -35
- package/dist/workflows/dag/repair-artifact.js +91 -0
- package/dist/workflows/dag/report.js +50 -0
- package/dist/workflows/dag/retry-policy.js +138 -0
- package/dist/workflows/dag/runner.js +77 -17
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +5 -4
- package/dist/workflows/dag/skill-snapshot.js +529 -0
- package/dist/workflows/dag/types.js +86 -10
- package/dist/workflows/dag/validate.js +73 -12
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +36 -33
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/agent-dag-runner.md +28 -3
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +140 -0
- package/docs/architecture/evolution.md +53 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +45 -17
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +43 -21
- package/docs/development-principles.md +2 -2
- package/docs/exec-plans/active/README.md +1 -3
- package/docs/exec-plans/completed/README.md +23 -0
- package/docs/feature-workflow.md +78 -4
- package/docs/harness-methodology-debugging.md +1 -1
- package/docs/harness-methodology-tdd.md +3 -3
- package/docs/init-surface.manifest.json +60 -25
- package/docs/loop-agent-harness.md +28 -4
- package/docs/progress/README.md +50 -1
- package/docs/reports/README.md +90 -18
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +23 -6
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +70 -3
- package/docs/templates/agent-dag.supervised-implementation.json +9 -8
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
- package/docs/templates/backend-test-dag.json +276 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +10 -12
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/project-start-checklist.md +2 -2
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +10 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +7 -15
- package/package.json +22 -46
- package/scripts/check-product-line-docs.sh +10 -7
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +51 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +14 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +36 -20
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { access, copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { access, copyFile, mkdir, readdir, readFile, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { copyDir } from "../shared/copy-dir.js";
|
|
6
7
|
import { loadHarnessManifest } from "../governance/harness.js";
|
|
@@ -9,6 +10,7 @@ const MANAGED_BLOCK_END = "<!-- LOOP_AGENT_INIT_END -->";
|
|
|
9
10
|
const GITIGNORE_BLOCK_START = "# LOOP_AGENT_INIT_START";
|
|
10
11
|
const GITIGNORE_BLOCK_END = "# LOOP_AGENT_INIT_END";
|
|
11
12
|
const INIT_SURFACE_STATE_PATH = ".harness/init-surface.json";
|
|
13
|
+
const DEFAULT_GOVERNANCE_ROOT = "ai_workspace/loop-agent";
|
|
12
14
|
const CORE_DOC_FILES = [
|
|
13
15
|
"README.md",
|
|
14
16
|
"architecture/runtime-boundaries.md",
|
|
@@ -108,7 +110,7 @@ required_paths=(
|
|
|
108
110
|
"${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md"
|
|
109
111
|
"${GOVERNANCE_ROOT_TOKEN}/architecture/runtime-boundaries.md"
|
|
110
112
|
"${GOVERNANCE_ROOT_TOKEN}/templates"
|
|
111
|
-
"skills/loop-agent/SKILL.md"
|
|
113
|
+
".agents/skills/loop-agent/SKILL.md"
|
|
112
114
|
".harness/prompts/analyze.md"
|
|
113
115
|
".harness/prompts/plan.md"
|
|
114
116
|
".harness/tasks"
|
|
@@ -305,7 +307,7 @@ fi
|
|
|
305
307
|
|
|
306
308
|
if [[ -d artifacts ]]; then
|
|
307
309
|
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
|
|
310
|
+
[[ "\${artifact_count}" == "0" ]] || failures+=("root artifacts/ contains \${artifact_count} entries; use ${GOVERNANCE_ROOT_TOKEN}/reports or .harness run artifacts")
|
|
309
311
|
fi
|
|
310
312
|
|
|
311
313
|
if (( \${#failures[@]} > 0 )); then
|
|
@@ -367,41 +369,54 @@ set -euo pipefail
|
|
|
367
369
|
ROOT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
368
370
|
cd "\${ROOT_DIR}"
|
|
369
371
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
)
|
|
372
|
+
check_skill() {
|
|
373
|
+
local skill_name="$1"
|
|
374
|
+
shift
|
|
375
|
+
local skill_dir=".agents/skills/\${skill_name}"
|
|
376
|
+
local skill_md="\${skill_dir}/SKILL.md"
|
|
377
|
+
local missing=()
|
|
382
378
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
if [[ ! -f "\${skill_md}" ]]; then
|
|
380
|
+
echo "skill entry 检查失败:缺少 \${skill_md}" >&2
|
|
381
|
+
exit 1
|
|
382
|
+
fi
|
|
383
|
+
grep -Eq "^name:[[:space:]]*\${skill_name}[[:space:]]*$" "\${skill_md}" || missing+=("frontmatter name")
|
|
384
|
+
grep -Eq '^description:[[:space:]]*[^[:space:]]' "\${skill_md}" || missing+=("frontmatter description")
|
|
387
385
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
386
|
+
local ref
|
|
387
|
+
for ref in "$@"; do
|
|
388
|
+
[[ -f "\${skill_dir}/\${ref}" ]] || missing+=("\${ref}")
|
|
389
|
+
grep -Fq "\${ref}" "\${skill_md}" || missing+=("SKILL.md -> \${ref}")
|
|
390
|
+
done
|
|
391
|
+
if (( \${#missing[@]} > 0 )); then
|
|
392
|
+
echo "skill entry 检查失败(\${skill_name}):" >&2
|
|
393
|
+
printf ' - %s\\n' "\${missing[@]}" >&2
|
|
394
|
+
exit 1
|
|
395
|
+
fi
|
|
392
396
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
397
|
+
local line_count
|
|
398
|
+
line_count="$(wc -l < "\${skill_md}" | tr -d ' ')"
|
|
399
|
+
if [[ "\${line_count}" =~ ^[0-9]+$ && "\${line_count}" -gt 180 ]]; then
|
|
400
|
+
echo "skill entry 检查失败:\${skill_md} 行数为 \${line_count},入口应保持精简" >&2
|
|
401
|
+
exit 1
|
|
402
|
+
fi
|
|
403
|
+
echo "skill entry 检查通过:\${skill_name} references ok, lines=\${line_count}"
|
|
404
|
+
}
|
|
398
405
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
406
|
+
check_skill "loop-agent" \\
|
|
407
|
+
"references/harness-policy.md" \\
|
|
408
|
+
"references/hybrid-dag.md" \\
|
|
409
|
+
"references/verification-and-failure-handling.md" \\
|
|
410
|
+
"references/command-reference.md"
|
|
411
|
+
check_skill "agent-worker" "references/agent-worker-operator.md"
|
|
403
412
|
|
|
404
|
-
|
|
413
|
+
description_line="$(grep -E '^description:' .agents/skills/agent-worker/SKILL.md | head -n 1 | tr '[:upper:]' '[:lower:]')"
|
|
414
|
+
for term in "agent-worker" "feature packet" "taskspec" "task pool" "self-host" "candidate" "loop-agent"; do
|
|
415
|
+
if [[ "\${description_line}" != *"\${term}"* ]]; then
|
|
416
|
+
echo "skill entry 检查失败(agent-worker):description 缺少触发/路由词 \${term}" >&2
|
|
417
|
+
exit 1
|
|
418
|
+
fi
|
|
419
|
+
done
|
|
405
420
|
`;
|
|
406
421
|
const INIT_CHECK_REPO_SH = `#!/usr/bin/env bash
|
|
407
422
|
set -euo pipefail
|
|
@@ -578,7 +593,7 @@ fi
|
|
|
578
593
|
|
|
579
594
|
if (( ran == 0 )); then
|
|
580
595
|
echo "未检测到可安全自动运行的项目测试入口。"
|
|
581
|
-
echo "loop-agent 治理检查仍可运行;请让初始化模型根据目标项目技术栈补充 scripts/ci-tests.sh 与
|
|
596
|
+
echo "loop-agent 治理检查仍可运行;请让初始化模型根据目标项目技术栈补充 scripts/ci-tests.sh 与 ${GOVERNANCE_ROOT_TOKEN}/verification-matrix.md。"
|
|
582
597
|
fi
|
|
583
598
|
|
|
584
599
|
if (( \${#notes[@]} > 0 )); then
|
|
@@ -666,14 +681,14 @@ function buildManagedAgentsBlock(input) {
|
|
|
666
681
|
"",
|
|
667
682
|
"### 初始化闭环",
|
|
668
683
|
"",
|
|
669
|
-
|
|
684
|
+
`当用户要求在本仓库初始化 loop-agent,或发现 \`README.md\` / \`${input.governanceRoot}/verification-matrix.md\` 仍停留在通用初始化状态时,模型应在同一轮工作中完成初始化闭环,不要把补全文档和验证矩阵留给用户交互。`,
|
|
670
685
|
"",
|
|
671
686
|
"标准流程:",
|
|
672
687
|
"",
|
|
673
688
|
"1. 运行 `loop-agent init instructions --repo-root .` 获取完整初始化指引。",
|
|
674
689
|
"2. 运行 `loop-agent init --repo-root . --profile full --merge` 完成确定性脚手架。",
|
|
675
690
|
"3. 立即读取目标项目的 README、manifest/build/config 文件和顶层源码/模块目录,识别项目类型、模块、运行入口和验证命令。",
|
|
676
|
-
|
|
691
|
+
`4. 基于真实项目文件补全或修正根 \`README.md\`、\`${input.governanceRoot}/verification-matrix.md\`,必要时适配 \`scripts/ci-tests.sh\`。`,
|
|
677
692
|
"5. 运行 `loop-agent init doctor --repo-root .`、`loop-agent inspect --repo-root .`、`loop-agent docs audit --repo-root .` 和 quick verification。",
|
|
678
693
|
"",
|
|
679
694
|
"如果仓库信息不足,写明“尚未从仓库文件中识别到 ...”以及下一步需要的事实;不要留下泛化占位符,也不要只提示用户稍后手填。",
|
|
@@ -724,7 +739,7 @@ function buildManagedAgentsBlock(input) {
|
|
|
724
739
|
"",
|
|
725
740
|
"- `.harness/tasks/` 保存任务状态和 source 材料。",
|
|
726
741
|
"- `.harness/dag-runs/` 与 `.harness/runs/` 保存运行事实;已完成事实只读。",
|
|
727
|
-
"-
|
|
742
|
+
"- `.agents/skills/` 保存目标项目本地 skill 指令;本地缺失时 runtime 可回退到 npm 包内置 skills。",
|
|
728
743
|
"- 示例内置在工具中;优先使用 `loop-agent examples list` 和 `loop-agent examples show <name>` 查看,不默认提交到目标仓库。",
|
|
729
744
|
"",
|
|
730
745
|
"### 验证",
|
|
@@ -747,7 +762,7 @@ function buildManagedAgentsBlock(input) {
|
|
|
747
762
|
"",
|
|
748
763
|
"### 交接",
|
|
749
764
|
"",
|
|
750
|
-
|
|
765
|
+
`较大的交接应说明改了什么、为什么这样改、验证命令和结果、影响到的 docs/tests/scripts/contracts、剩余风险和下一步。长期结论应进入 \`${input.governanceRoot}/progress\`、\`${input.governanceRoot}/reports\`、\`${input.governanceRoot}/exec-plans\`、\`${input.governanceRoot}/decisions\`、测试、脚本或模板。`,
|
|
751
766
|
MANAGED_BLOCK_END,
|
|
752
767
|
].join("\n");
|
|
753
768
|
}
|
|
@@ -820,11 +835,9 @@ function buildHarness(input) {
|
|
|
820
835
|
const existingExecutors = isRecord(input.existing.executors)
|
|
821
836
|
? input.existing.executors
|
|
822
837
|
: {};
|
|
823
|
-
// Cursor executor
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
delete templateExecutors.cursor;
|
|
827
|
-
}
|
|
838
|
+
// Cursor executor must never be projected; Pi-only governed runtime.
|
|
839
|
+
delete templateExecutors.cursor;
|
|
840
|
+
delete existingExecutors.cursor;
|
|
828
841
|
const mergedExecutors = mergeRecord(templateExecutors, existingExecutors);
|
|
829
842
|
// requiresApiKey is Cursor-facing only; never project it on pi.
|
|
830
843
|
if (isRecord(mergedExecutors.pi) && "requiresApiKey" in mergedExecutors.pi) {
|
|
@@ -999,12 +1012,18 @@ function manifestPathToTargetPath(relativePath, governanceRoot) {
|
|
|
999
1012
|
if (governanceRoot !== "docs" && relativePath.startsWith("docs/")) {
|
|
1000
1013
|
return `${governanceRoot}/${relativePath.slice("docs/".length)}`;
|
|
1001
1014
|
}
|
|
1015
|
+
if (relativePath.startsWith("skills/")) {
|
|
1016
|
+
return `.agents/${relativePath}`;
|
|
1017
|
+
}
|
|
1002
1018
|
return relativePath;
|
|
1003
1019
|
}
|
|
1004
1020
|
function targetPathToManifestPath(relativePath, governanceRoot) {
|
|
1005
1021
|
if (governanceRoot !== "docs" && relativePath.startsWith(`${governanceRoot}/`)) {
|
|
1006
1022
|
return `docs/${relativePath.slice(`${governanceRoot}/`.length)}`;
|
|
1007
1023
|
}
|
|
1024
|
+
if (relativePath.startsWith(".agents/skills/")) {
|
|
1025
|
+
return relativePath.slice(".agents/".length);
|
|
1026
|
+
}
|
|
1008
1027
|
return relativePath;
|
|
1009
1028
|
}
|
|
1010
1029
|
async function readPackageVersion(assetRoot) {
|
|
@@ -1017,15 +1036,62 @@ async function readInitSurfaceManifest(assetRoot) {
|
|
|
1017
1036
|
const manifest = JSON.parse(raw);
|
|
1018
1037
|
const initFullRequired = manifest.initFullRequired ?? [];
|
|
1019
1038
|
const initSurface = manifest.initSurface ?? {};
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1039
|
+
const seenPaths = new Set();
|
|
1040
|
+
const entries = [];
|
|
1041
|
+
for (const relativePath of initFullRequired) {
|
|
1042
|
+
if (seenPaths.has(relativePath))
|
|
1043
|
+
continue;
|
|
1044
|
+
seenPaths.add(relativePath);
|
|
1045
|
+
entries.push({
|
|
1046
|
+
path: relativePath,
|
|
1047
|
+
mode: initSurface[relativePath] ?? inferInitSurfaceMode(relativePath),
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
// Fresh `init --profile full` copies the entire `docs/templates/` and
|
|
1051
|
+
// `skills/` directories via copyDirMerge. Every file actually projected to a
|
|
1052
|
+
// target project must be represented in the desired init surface so that
|
|
1053
|
+
// check-update can detect missing or stale copies during controller upgrades.
|
|
1054
|
+
// The static manifest intentionally only pins explicit modes for the subset
|
|
1055
|
+
// that needs them; the rest are auto-discovered here as "copied" entries.
|
|
1056
|
+
for (const discovered of await discoverCopiedSurfaceFiles(assetRoot)) {
|
|
1057
|
+
if (seenPaths.has(discovered))
|
|
1058
|
+
continue;
|
|
1059
|
+
seenPaths.add(discovered);
|
|
1060
|
+
entries.push({
|
|
1061
|
+
path: discovered,
|
|
1062
|
+
mode: initSurface[discovered] ?? inferInitSurfaceMode(discovered),
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1024
1065
|
if (!entries.some((entry) => entry.path === INIT_SURFACE_STATE_PATH)) {
|
|
1025
1066
|
entries.push({ path: INIT_SURFACE_STATE_PATH, mode: "state" });
|
|
1026
1067
|
}
|
|
1027
1068
|
return { manifest, raw, sha256: sha256Text(raw), entries };
|
|
1028
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Returns manifest-path entries for every file that fresh `init --profile full`
|
|
1072
|
+
* copies from the bundled package into a target project: all of `docs/templates/`
|
|
1073
|
+
* and all of `skills/` (the latter is mirrored to `.agents/skills/` at the
|
|
1074
|
+
* target). Paths use the package source convention so manifestPathToTargetPath
|
|
1075
|
+
* can remap them per governance root.
|
|
1076
|
+
*/
|
|
1077
|
+
async function discoverCopiedSurfaceFiles(assetRoot) {
|
|
1078
|
+
const discovered = [];
|
|
1079
|
+
const roots = [
|
|
1080
|
+
{ source: "docs/templates", prefix: "docs/templates/" },
|
|
1081
|
+
{ source: "skills", prefix: "skills/" },
|
|
1082
|
+
];
|
|
1083
|
+
for (const { source, prefix } of roots) {
|
|
1084
|
+
const rootPath = path.join(assetRoot, source);
|
|
1085
|
+
const rootInfo = await stat(rootPath).catch(() => null);
|
|
1086
|
+
if (!rootInfo?.isDirectory())
|
|
1087
|
+
continue;
|
|
1088
|
+
const files = await listRelativeFiles(rootPath);
|
|
1089
|
+
for (const relativePath of files) {
|
|
1090
|
+
discovered.push(`${prefix}${relativePath}`);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return discovered.sort();
|
|
1094
|
+
}
|
|
1029
1095
|
function inferInitSurfaceMode(relativePath) {
|
|
1030
1096
|
if (relativePath === INIT_SURFACE_STATE_PATH)
|
|
1031
1097
|
return "state";
|
|
@@ -1195,10 +1261,20 @@ async function buildCurrentSurfaceState(input) {
|
|
|
1195
1261
|
};
|
|
1196
1262
|
continue;
|
|
1197
1263
|
}
|
|
1264
|
+
let semanticallyMatchesGeneratedJson = false;
|
|
1265
|
+
if (targetRelativePath === "harness.json" && desired.content !== undefined) {
|
|
1266
|
+
try {
|
|
1267
|
+
semanticallyMatchesGeneratedJson = isDeepStrictEqual(JSON.parse(current.toString("utf-8")), JSON.parse(desired.content));
|
|
1268
|
+
}
|
|
1269
|
+
catch {
|
|
1270
|
+
// Invalid JSON remains local-existing-unknown for model merge / doctor.
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1198
1273
|
files[targetRelativePath] = {
|
|
1199
1274
|
...base,
|
|
1200
1275
|
currentSha256,
|
|
1201
|
-
relationship:
|
|
1276
|
+
relationship: semanticallyMatchesGeneratedJson ||
|
|
1277
|
+
(sourceSha256 !== undefined && currentSha256 === sourceSha256)
|
|
1202
1278
|
? entry.mode === "generated"
|
|
1203
1279
|
? "matches-current-generated"
|
|
1204
1280
|
: "matches-current-package"
|
|
@@ -1221,15 +1297,278 @@ async function writeInitSurfaceState(input) {
|
|
|
1221
1297
|
await writeFile(target, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
1222
1298
|
return state;
|
|
1223
1299
|
}
|
|
1300
|
+
function isCurrentInitRelationship(relationship) {
|
|
1301
|
+
return (relationship === "matches-current-package" ||
|
|
1302
|
+
relationship === "matches-current-generated" ||
|
|
1303
|
+
relationship === "managed-block-current" ||
|
|
1304
|
+
relationship === "directory-present" ||
|
|
1305
|
+
relationship === "state-file");
|
|
1306
|
+
}
|
|
1307
|
+
async function fileSha256IfExists(filePath) {
|
|
1308
|
+
const info = await stat(filePath).catch(() => null);
|
|
1309
|
+
if (!info || !info.isFile())
|
|
1310
|
+
return undefined;
|
|
1311
|
+
return sha256Buffer(await readFile(filePath));
|
|
1312
|
+
}
|
|
1313
|
+
async function isDirectoryEmpty(dirPath) {
|
|
1314
|
+
const info = await stat(dirPath).catch(() => null);
|
|
1315
|
+
if (!info || !info.isDirectory())
|
|
1316
|
+
return false;
|
|
1317
|
+
return (await readdir(dirPath)).length === 0;
|
|
1318
|
+
}
|
|
1319
|
+
async function listRelativeFiles(rootPath) {
|
|
1320
|
+
const rootInfo = await stat(rootPath).catch(() => null);
|
|
1321
|
+
if (!rootInfo?.isDirectory())
|
|
1322
|
+
return [];
|
|
1323
|
+
const files = [];
|
|
1324
|
+
async function walk(currentPath, relativeDir) {
|
|
1325
|
+
for (const entry of await readdir(currentPath, { withFileTypes: true })) {
|
|
1326
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
1327
|
+
const absolutePath = path.join(currentPath, entry.name);
|
|
1328
|
+
if (entry.isDirectory())
|
|
1329
|
+
await walk(absolutePath, relativePath);
|
|
1330
|
+
else if (entry.isFile())
|
|
1331
|
+
files.push(relativePath);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
await walk(rootPath, "");
|
|
1335
|
+
return files.sort();
|
|
1336
|
+
}
|
|
1337
|
+
async function expectedLegacyInitFileSha(input) {
|
|
1338
|
+
if (input.legacyPath.startsWith("docs/")) {
|
|
1339
|
+
const doc = input.legacyPath.slice("docs/".length);
|
|
1340
|
+
const generated = buildGeneratedCoreDoc({
|
|
1341
|
+
doc,
|
|
1342
|
+
projectName: input.projectName,
|
|
1343
|
+
governanceRoot: "docs",
|
|
1344
|
+
});
|
|
1345
|
+
if (generated !== undefined)
|
|
1346
|
+
return sha256Text(generated);
|
|
1347
|
+
const generatedDir = GOVERNANCE_README_DIRS.find((dir) => input.legacyPath === `docs/${dir}/README.md`);
|
|
1348
|
+
if (generatedDir) {
|
|
1349
|
+
return sha256Text(buildGovernanceDirectoryReadme({
|
|
1350
|
+
dir: generatedDir,
|
|
1351
|
+
projectName: input.projectName,
|
|
1352
|
+
}));
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (!input.legacyPath.startsWith("docs/") && !input.legacyPath.startsWith("skills/")) {
|
|
1356
|
+
return undefined;
|
|
1357
|
+
}
|
|
1358
|
+
return fileSha256IfExists(path.join(input.assetRoot, input.legacyPath));
|
|
1359
|
+
}
|
|
1360
|
+
function collectSafeRetiredDirectories(paths) {
|
|
1361
|
+
const dirs = new Set();
|
|
1362
|
+
for (const retiredPath of paths) {
|
|
1363
|
+
let dir = path.posix.dirname(retiredPath);
|
|
1364
|
+
while (dir !== "." && dir !== "/") {
|
|
1365
|
+
if (dir === "skills" || dir.startsWith("skills/") || dir === "docs" || dir.startsWith("docs/")) {
|
|
1366
|
+
dirs.add(dir);
|
|
1367
|
+
}
|
|
1368
|
+
dir = path.posix.dirname(dir);
|
|
1369
|
+
}
|
|
1370
|
+
if (retiredPath.startsWith("skills/"))
|
|
1371
|
+
dirs.add("skills");
|
|
1372
|
+
}
|
|
1373
|
+
return [...dirs].sort((left, right) => right.split("/").length - left.split("/").length);
|
|
1374
|
+
}
|
|
1375
|
+
function needsHarnessModelMigration(harness) {
|
|
1376
|
+
if ("model" in harness || "models" in harness || "modelProfiles" in harness || "modelRouting" in harness) {
|
|
1377
|
+
return true;
|
|
1378
|
+
}
|
|
1379
|
+
if (isRecord(harness.executors)) {
|
|
1380
|
+
if ("cursor" in harness.executors)
|
|
1381
|
+
return true;
|
|
1382
|
+
if (isRecord(harness.executors.pi)) {
|
|
1383
|
+
return ("requiresApiKey" in harness.executors.pi ||
|
|
1384
|
+
"defaultModel" in harness.executors.pi);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
function migrateHarnessModelFields(harness) {
|
|
1390
|
+
const next = { ...harness };
|
|
1391
|
+
const legacyModel = typeof next.model === "string"
|
|
1392
|
+
? next.model
|
|
1393
|
+
: isRecord(next.executors) &&
|
|
1394
|
+
isRecord(next.executors.pi) &&
|
|
1395
|
+
typeof next.executors.pi.defaultModel === "string"
|
|
1396
|
+
? next.executors.pi.defaultModel
|
|
1397
|
+
: undefined;
|
|
1398
|
+
delete next.model;
|
|
1399
|
+
delete next.models;
|
|
1400
|
+
delete next.modelProfiles;
|
|
1401
|
+
delete next.modelRouting;
|
|
1402
|
+
const executors = isRecord(next.executors) ? { ...next.executors } : {};
|
|
1403
|
+
delete executors.cursor;
|
|
1404
|
+
const pi = isRecord(executors.pi) ? { ...executors.pi } : {};
|
|
1405
|
+
delete pi.requiresApiKey;
|
|
1406
|
+
delete pi.defaultModel;
|
|
1407
|
+
if (legacyModel) {
|
|
1408
|
+
for (const level of ["LOW", "MED", "HIGH"]) {
|
|
1409
|
+
if (typeof pi[level] !== "string")
|
|
1410
|
+
pi[level] = legacyModel;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
executors.pi = pi;
|
|
1414
|
+
next.executors = executors;
|
|
1415
|
+
return next;
|
|
1416
|
+
}
|
|
1417
|
+
function hasLegacyHarnessGovernancePaths(harness) {
|
|
1418
|
+
for (const sectionName of ["entrypoints", "artifacts"]) {
|
|
1419
|
+
const section = harness[sectionName];
|
|
1420
|
+
if (isRecord(section) &&
|
|
1421
|
+
Object.values(section).some((value) => typeof value === "string" && value.startsWith("docs/"))) {
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1427
|
+
async function collectRetiredLayoutActions(input) {
|
|
1428
|
+
const blockedTargetPaths = new Set();
|
|
1429
|
+
if (!input.recordedState)
|
|
1430
|
+
return { actions: [], blockedTargetPaths };
|
|
1431
|
+
const actions = [];
|
|
1432
|
+
const retiredOwnedPaths = [];
|
|
1433
|
+
const currentPaths = new Set(Object.keys(input.currentState.files));
|
|
1434
|
+
const recordedPaths = new Set(Object.keys(input.recordedState.files));
|
|
1435
|
+
for (const [oldPath, oldState] of Object.entries(input.recordedState.files)) {
|
|
1436
|
+
const targetPath = normalizeRelativePath(manifestPathToTargetPath(oldPath, input.governanceRoot));
|
|
1437
|
+
if (targetPath === oldPath || !currentPaths.has(targetPath))
|
|
1438
|
+
continue;
|
|
1439
|
+
if (oldState.mode === "directory" || oldState.mode === "state")
|
|
1440
|
+
continue;
|
|
1441
|
+
const oldAbsolute = path.join(input.repoRoot, oldPath);
|
|
1442
|
+
const oldHash = await fileSha256IfExists(oldAbsolute);
|
|
1443
|
+
if (!oldHash)
|
|
1444
|
+
continue;
|
|
1445
|
+
if (!oldState.currentSha256 || oldHash !== oldState.currentSha256) {
|
|
1446
|
+
input.humanDecisions.push({
|
|
1447
|
+
path: oldPath,
|
|
1448
|
+
reason: `legacy init file may have local edits; review before migrating to ${targetPath}`,
|
|
1449
|
+
});
|
|
1450
|
+
// The user owns a modified copy at the legacy path; do not silently
|
|
1451
|
+
// install the package version at the canonical target path.
|
|
1452
|
+
blockedTargetPaths.add(targetPath);
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
const targetAbsolute = path.join(input.repoRoot, targetPath);
|
|
1456
|
+
const targetHash = await fileSha256IfExists(targetAbsolute);
|
|
1457
|
+
const targetState = input.currentState.files[targetPath];
|
|
1458
|
+
if (!targetHash) {
|
|
1459
|
+
if (targetState?.sourceSha256 !== undefined &&
|
|
1460
|
+
targetState.sourceSha256 !== oldHash) {
|
|
1461
|
+
actions.push({
|
|
1462
|
+
type: "remove-owned-file",
|
|
1463
|
+
path: oldPath,
|
|
1464
|
+
reason: `remove unchanged legacy init file so the current ${targetPath} can be installed`,
|
|
1465
|
+
});
|
|
1466
|
+
retiredOwnedPaths.push(oldPath);
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
actions.push({
|
|
1470
|
+
type: "migrate-owned-file",
|
|
1471
|
+
path: oldPath,
|
|
1472
|
+
targetPath,
|
|
1473
|
+
reason: `move loop-agent-owned legacy init file to ${targetPath}`,
|
|
1474
|
+
});
|
|
1475
|
+
retiredOwnedPaths.push(oldPath);
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
const recordedTargetState = input.recordedState.files[targetPath];
|
|
1479
|
+
if (targetHash === oldHash ||
|
|
1480
|
+
(input.recordedState.stateKind === "recorded" &&
|
|
1481
|
+
recordedTargetState?.currentSha256 !== undefined &&
|
|
1482
|
+
recordedTargetState.currentSha256 === targetHash) ||
|
|
1483
|
+
isCurrentInitRelationship(targetState?.relationship)) {
|
|
1484
|
+
actions.push({
|
|
1485
|
+
type: "remove-owned-file",
|
|
1486
|
+
path: oldPath,
|
|
1487
|
+
reason: `remove duplicate loop-agent-owned legacy init file; current path is ${targetPath}`,
|
|
1488
|
+
});
|
|
1489
|
+
retiredOwnedPaths.push(oldPath);
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
input.humanDecisions.push({
|
|
1493
|
+
path: oldPath,
|
|
1494
|
+
reason: `legacy init file is owned, but target path ${targetPath} already exists with different content`,
|
|
1495
|
+
});
|
|
1496
|
+
blockedTargetPaths.add(targetPath);
|
|
1497
|
+
}
|
|
1498
|
+
for (const legacyRoot of ["docs", "skills"]) {
|
|
1499
|
+
for (const relativePath of await listRelativeFiles(path.join(input.repoRoot, legacyRoot))) {
|
|
1500
|
+
const oldPath = `${legacyRoot}/${relativePath}`;
|
|
1501
|
+
if (recordedPaths.has(oldPath))
|
|
1502
|
+
continue;
|
|
1503
|
+
const targetPath = normalizeRelativePath(manifestPathToTargetPath(oldPath, input.governanceRoot));
|
|
1504
|
+
if (targetPath === oldPath)
|
|
1505
|
+
continue;
|
|
1506
|
+
const expectedSha = await expectedLegacyInitFileSha({
|
|
1507
|
+
assetRoot: input.assetRoot,
|
|
1508
|
+
projectName: input.projectName,
|
|
1509
|
+
legacyPath: oldPath,
|
|
1510
|
+
});
|
|
1511
|
+
if (!expectedSha)
|
|
1512
|
+
continue;
|
|
1513
|
+
const oldHash = await fileSha256IfExists(path.join(input.repoRoot, oldPath));
|
|
1514
|
+
if (!oldHash)
|
|
1515
|
+
continue;
|
|
1516
|
+
const targetHash = await fileSha256IfExists(path.join(input.repoRoot, targetPath));
|
|
1517
|
+
if (targetHash === oldHash) {
|
|
1518
|
+
actions.push({
|
|
1519
|
+
type: "remove-owned-file",
|
|
1520
|
+
path: oldPath,
|
|
1521
|
+
reason: `remove duplicate legacy init file; current path is ${targetPath}`,
|
|
1522
|
+
});
|
|
1523
|
+
retiredOwnedPaths.push(oldPath);
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
if (oldHash !== expectedSha) {
|
|
1527
|
+
input.humanDecisions.push({
|
|
1528
|
+
path: oldPath,
|
|
1529
|
+
reason: `legacy init-shaped file is not recorded and differs from the current bundled/generated asset; review before migrating to ${targetPath}`,
|
|
1530
|
+
});
|
|
1531
|
+
blockedTargetPaths.add(targetPath);
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (!targetHash) {
|
|
1535
|
+
actions.push({
|
|
1536
|
+
type: "migrate-owned-file",
|
|
1537
|
+
path: oldPath,
|
|
1538
|
+
targetPath,
|
|
1539
|
+
reason: `move package-matching legacy init file to ${targetPath}`,
|
|
1540
|
+
});
|
|
1541
|
+
retiredOwnedPaths.push(oldPath);
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
input.humanDecisions.push({
|
|
1545
|
+
path: oldPath,
|
|
1546
|
+
reason: `legacy init-shaped file matches the package, but target path ${targetPath} already exists with different content`,
|
|
1547
|
+
});
|
|
1548
|
+
blockedTargetPaths.add(targetPath);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
for (const dir of collectSafeRetiredDirectories(retiredOwnedPaths)) {
|
|
1552
|
+
actions.push({
|
|
1553
|
+
type: "remove-empty-directory",
|
|
1554
|
+
path: dir,
|
|
1555
|
+
reason: "remove directory if it is empty after legacy init files are migrated",
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
return { actions, blockedTargetPaths };
|
|
1559
|
+
}
|
|
1224
1560
|
async function resolveInitProjectContext(input) {
|
|
1225
1561
|
const harness = await readJsonIfExists(path.join(input.repoRoot, "harness.json"));
|
|
1562
|
+
const recordedGovernanceRoot = typeof harness.governanceRoot === "string" ? harness.governanceRoot : undefined;
|
|
1226
1563
|
return {
|
|
1227
1564
|
projectName: input.projectName ??
|
|
1228
1565
|
(typeof harness.project === "string" ? harness.project : undefined) ??
|
|
1229
1566
|
path.basename(input.repoRoot),
|
|
1230
1567
|
governanceRoot: input.governanceRoot ??
|
|
1231
|
-
(
|
|
1232
|
-
|
|
1568
|
+
(recordedGovernanceRoot === "docs"
|
|
1569
|
+
? DEFAULT_GOVERNANCE_ROOT
|
|
1570
|
+
: recordedGovernanceRoot) ??
|
|
1571
|
+
DEFAULT_GOVERNANCE_ROOT,
|
|
1233
1572
|
};
|
|
1234
1573
|
}
|
|
1235
1574
|
function buildManagedReadmeBlock(input) {
|
|
@@ -1442,7 +1781,7 @@ function buildTargetDevelopmentPrinciples(input) {
|
|
|
1442
1781
|
"1. One task advances one bounded work block.",
|
|
1443
1782
|
"2. Search existing code, docs, scripts, and tests before designing new behavior.",
|
|
1444
1783
|
"3. Shell verification is the completion authority.",
|
|
1445
|
-
|
|
1784
|
+
`4. Runtime state belongs in \`.harness/\`; durable decisions belong in \`${input.governanceRoot}/\`.`,
|
|
1446
1785
|
"5. Model writer nodes must be bounded by explicit allowed and forbidden paths.",
|
|
1447
1786
|
"6. Advisory model output must be followed by deterministic verification.",
|
|
1448
1787
|
"7. Repeated constraints should become docs, tests, scripts, checks, or templates.",
|
|
@@ -1469,7 +1808,7 @@ function buildTargetFeatureWorkflow(input) {
|
|
|
1469
1808
|
"3. Contract: state deliverables, non-goals, completion criteria, verification commands, and failure conditions.",
|
|
1470
1809
|
"4. Implement: make the smallest coherent change and update required docs, scripts, and tests.",
|
|
1471
1810
|
"5. Verify: run governance checks plus target project verification.",
|
|
1472
|
-
|
|
1811
|
+
`6. Handoff: record evidence in ${input.governanceRoot}/progress, ${input.governanceRoot}/reports, an exec plan, or an ADR when useful.`,
|
|
1473
1812
|
"",
|
|
1474
1813
|
"## Agent DAG Path",
|
|
1475
1814
|
"",
|
|
@@ -1486,6 +1825,13 @@ function buildTargetFeatureWorkflow(input) {
|
|
|
1486
1825
|
"",
|
|
1487
1826
|
"Before executing a DAG, review profile routing, governance profile, writer writeSet, allowed paths, forbidden paths, shell verification, and decision gate mode.",
|
|
1488
1827
|
"",
|
|
1828
|
+
"## Specialized Task Kinds",
|
|
1829
|
+
"",
|
|
1830
|
+
"Set `taskKind` in `.harness/tasks/<task-id>/task.json` to route a task to a dedicated DAG profile instead of the default auto profile:",
|
|
1831
|
+
"",
|
|
1832
|
+
"- `taskKind: \"backend-test\"` selects the dedicated backend test DAG. Its Pi nodes analyze requirements, generate and review backend cases, generate pytest, and retrospect on results; shell gate/execution nodes enforce the review verdict and run the target project's pytest. The backend test templates (`backend-test-dag.json` and the `backend-test-dag.*.prompt.md` files) ship inside the loop-agent package as static references and are projected to target projects under the governance `templates/` directory.",
|
|
1833
|
+
"- Only eligible read-only Pi nodes (planner, scout, reviewer, verifier, closeout with no write-capable tool profile) receive the conservative automatic retry policy. Supervisor, implementer, writer, docs-only, dynamic, shell, static, and decision-gate nodes are not retried automatically. Eligible nodes cannot write repository files; the controller only records immutable attempt evidence under `.harness/dag-runs/<state>/<run-id>/<node-id>/attempt-<n>.json`.",
|
|
1834
|
+
"",
|
|
1489
1835
|
"## Verification",
|
|
1490
1836
|
"",
|
|
1491
1837
|
`Use \`${input.governanceRoot}/verification-matrix.md\` to choose the narrowest command that proves the claim.`,
|
|
@@ -1543,8 +1889,8 @@ function buildTargetLoopAgentHarness(input) {
|
|
|
1543
1889
|
"- `.harness/dag-runs/` stores DAG run facts.",
|
|
1544
1890
|
"- `.harness/runs/` stores one-shot executor facts.",
|
|
1545
1891
|
"- `.harness/task-pool/` stores optional agent-worker Task Pool state, batch artifacts, failure handoffs, and local Observe events; it is runtime state and normally ignored by Git.",
|
|
1546
|
-
|
|
1547
|
-
"-
|
|
1892
|
+
`- \`${input.governanceRoot}/\` stores loop-agent generated governance, plans, reports, progress, and decisions.`,
|
|
1893
|
+
"- `.agents/skills/` stores project repo-local skill instructions; the CLI can fall back to bundled package skills when needed.",
|
|
1548
1894
|
"",
|
|
1549
1895
|
"## Default Workflow",
|
|
1550
1896
|
"",
|
|
@@ -1554,9 +1900,9 @@ function buildTargetLoopAgentHarness(input) {
|
|
|
1554
1900
|
"",
|
|
1555
1901
|
"After a run, use `loop-agent dag report --run-id <run-id> --markdown` to read canonical run facts and `loop-agent dag doctor --run-id <run-id> --markdown` to diagnose failed or paused runs. Failed DAG runs should produce a failure handoff via `loop-agent dag closeout-draft --run-id <run-id>` instead of a successful closeout.",
|
|
1556
1902
|
"",
|
|
1557
|
-
|
|
1903
|
+
`Use \`${input.governanceRoot}/templates/production-readiness-checklist.md\` when claiming Production Readiness v0.1 for low/medium-risk single-repo DAG work.`,
|
|
1558
1904
|
"",
|
|
1559
|
-
|
|
1905
|
+
`For real product-line Worker samples, use \`${input.governanceRoot}/templates/worker-dogfood-setup.md\` and \`${input.governanceRoot}/templates/worker-dogfood-evidence.md\`. Explicit failed-task retries must preserve prior evidence and use a new worker run id.`,
|
|
1560
1906
|
"",
|
|
1561
1907
|
"## Script Matrix",
|
|
1562
1908
|
"",
|
|
@@ -1572,13 +1918,16 @@ function buildGeneratedCoreDoc(input) {
|
|
|
1572
1918
|
case "architecture/runtime-boundaries.md":
|
|
1573
1919
|
return buildTargetRuntimeBoundaries({ projectName: input.projectName });
|
|
1574
1920
|
case "development-principles.md":
|
|
1575
|
-
return buildTargetDevelopmentPrinciples({
|
|
1921
|
+
return buildTargetDevelopmentPrinciples({
|
|
1922
|
+
projectName: input.projectName,
|
|
1923
|
+
governanceRoot: input.governanceRoot,
|
|
1924
|
+
});
|
|
1576
1925
|
case "feature-workflow.md":
|
|
1577
1926
|
return buildTargetFeatureWorkflow({ governanceRoot: input.governanceRoot });
|
|
1578
1927
|
case "verification-matrix.md":
|
|
1579
1928
|
return buildTargetVerificationMatrix();
|
|
1580
1929
|
case "loop-agent-harness.md":
|
|
1581
|
-
return buildTargetLoopAgentHarness({ projectName: input.projectName });
|
|
1930
|
+
return buildTargetLoopAgentHarness({ projectName: input.projectName, governanceRoot: input.governanceRoot });
|
|
1582
1931
|
default:
|
|
1583
1932
|
return undefined;
|
|
1584
1933
|
}
|
|
@@ -1599,7 +1948,7 @@ function buildGovernanceDirectoryReadme(input) {
|
|
|
1599
1948
|
}
|
|
1600
1949
|
export function buildInitInstructions(input) {
|
|
1601
1950
|
const projectName = input.projectName ?? path.basename(path.resolve(input.repoRoot));
|
|
1602
|
-
const governanceRoot = input.governanceRoot ??
|
|
1951
|
+
const governanceRoot = input.governanceRoot ?? DEFAULT_GOVERNANCE_ROOT;
|
|
1603
1952
|
return [
|
|
1604
1953
|
"# loop-agent Initialization Instructions",
|
|
1605
1954
|
"",
|
|
@@ -1610,7 +1959,7 @@ export function buildInitInstructions(input) {
|
|
|
1610
1959
|
`- Project name: default \`${projectName}\`.`,
|
|
1611
1960
|
`- Governance root: default \`${governanceRoot}\`.`,
|
|
1612
1961
|
"- Confirm provider/model. Defaults may be used, but the target project may need a different provider/model matrix.",
|
|
1613
|
-
"- Default: merge existing AGENTS.md, harness.json, and docs
|
|
1962
|
+
"- Default: merge existing AGENTS.md, harness.json, and loop-agent governance docs instead of overwriting user content.",
|
|
1614
1963
|
"",
|
|
1615
1964
|
"## Apply Defaults",
|
|
1616
1965
|
"",
|
|
@@ -1624,8 +1973,8 @@ export function buildInitInstructions(input) {
|
|
|
1624
1973
|
`- Generate target-project versions of \`${governanceRoot}/README.md\`, \`${governanceRoot}/development-principles.md\`, \`${governanceRoot}/architecture/runtime-boundaries.md\`, \`${governanceRoot}/feature-workflow.md\`, \`${governanceRoot}/verification-matrix.md\`, and \`${governanceRoot}/loop-agent-harness.md\`; copy only generic methodology/template material as-is.`,
|
|
1625
1974
|
"- 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.",
|
|
1626
1975
|
"- 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.",
|
|
1627
|
-
|
|
1628
|
-
"-
|
|
1976
|
+
`- Populate \`${governanceRoot}/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.`,
|
|
1977
|
+
"- Project repo-local skills live in `.agents/skills/`. Do not create a root `skills/` directory in the target project; the package's bundled `skills/` remains the built-in fallback.",
|
|
1629
1978
|
"- Merge a loop-agent managed block into `.gitignore` that ignores harness runtime facts (tasks, dag-runs, runs, live, cache, init-surface.json, .harness/task-pool, legacy .task-pool residue) while keeping prompts and directory placeholders shareable.",
|
|
1630
1979
|
"- Do not copy examples by default; examples stay bundled in the tool and are available through `loop-agent examples`.",
|
|
1631
1980
|
"- Add or update a loop-agent managed block in AGENTS.md.",
|
|
@@ -1652,8 +2001,8 @@ export function buildInitInstructions(input) {
|
|
|
1652
2001
|
"",
|
|
1653
2002
|
"1. Read the target project's existing README, manifest/build/config files, and top-level source/module directories.",
|
|
1654
2003
|
"2. Replace the root README's `初始化模型补充` comments when the information can be inferred from files. At minimum, fill project overview, technology stack/directory structure, and development/verification commands.",
|
|
1655
|
-
|
|
1656
|
-
|
|
2004
|
+
`3. Update \`${governanceRoot}/verification-matrix.md\` with the target project's actual quick, standard, and full verification commands.`,
|
|
2005
|
+
`4. Confirm the generated docs expose \`${governanceRoot}/templates/production-readiness-checklist.md\` plus the operator recovery path: \`dag report\`, \`dag doctor --run-id --markdown\`, and failed-run \`dag closeout-draft\` failure handoff.`,
|
|
1657
2006
|
"5. Update `scripts/ci-tests.sh` only when the conservative generated detector is insufficient for the target project.",
|
|
1658
2007
|
"6. Run `loop-agent init doctor`, `loop-agent inspect`, `loop-agent docs audit`, and the quick verification command.",
|
|
1659
2008
|
"",
|
|
@@ -1672,7 +2021,7 @@ export function buildInitInstructions(input) {
|
|
|
1672
2021
|
export async function initializeLoopAgentProject(options) {
|
|
1673
2022
|
const repoRoot = path.resolve(options.repoRoot);
|
|
1674
2023
|
const projectName = options.projectName ?? path.basename(repoRoot);
|
|
1675
|
-
const governanceRoot = options.governanceRoot ??
|
|
2024
|
+
const governanceRoot = options.governanceRoot ?? DEFAULT_GOVERNANCE_ROOT;
|
|
1676
2025
|
const profile = options.profile ?? "full";
|
|
1677
2026
|
const merge = options.merge ?? true;
|
|
1678
2027
|
const written = [];
|
|
@@ -1773,17 +2122,8 @@ export async function initializeLoopAgentProject(options) {
|
|
|
1773
2122
|
written,
|
|
1774
2123
|
});
|
|
1775
2124
|
if (profile === "full") {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
repoRoot,
|
|
1779
|
-
sourceRelativePath: "skills",
|
|
1780
|
-
targetRelativePath: "skills",
|
|
1781
|
-
written,
|
|
1782
|
-
});
|
|
1783
|
-
// Mirror bundled skills into the agent-compatible `.agents/skills/` path so
|
|
1784
|
-
// external agents (e.g. OpenCode) that auto-discover `.agents/skills/<name>/SKILL.md`
|
|
1785
|
-
// share the same skill content as loop-agent's primary `skills/` path.
|
|
1786
|
-
// The package still ships only `skills/`; `.agents/skills` is a target projection.
|
|
2125
|
+
// Project repo-local skills live only under `.agents/skills/`.
|
|
2126
|
+
// The package still ships `skills/` as the built-in fallback source.
|
|
1787
2127
|
await copyDirMerge({
|
|
1788
2128
|
assetRoot,
|
|
1789
2129
|
repoRoot,
|
|
@@ -1793,7 +2133,6 @@ export async function initializeLoopAgentProject(options) {
|
|
|
1793
2133
|
});
|
|
1794
2134
|
}
|
|
1795
2135
|
else {
|
|
1796
|
-
skipped.push("skills/");
|
|
1797
2136
|
skipped.push(".agents/skills/");
|
|
1798
2137
|
}
|
|
1799
2138
|
skipped.push("examples/");
|
|
@@ -1879,6 +2218,7 @@ export async function checkInitUpdate(input) {
|
|
|
1879
2218
|
governanceRoot: input.governanceRoot,
|
|
1880
2219
|
});
|
|
1881
2220
|
const recordedState = await readExistingSurfaceState(repoRoot);
|
|
2221
|
+
const assetRoot = await findPackageRoot();
|
|
1882
2222
|
const surfaceState = recordedState?.stateKind ?? "missing";
|
|
1883
2223
|
const currentState = await buildCurrentSurfaceState({
|
|
1884
2224
|
repoRoot,
|
|
@@ -1890,6 +2230,24 @@ export async function checkInitUpdate(input) {
|
|
|
1890
2230
|
const deterministicActions = [];
|
|
1891
2231
|
const modelMergeTasks = [];
|
|
1892
2232
|
const humanDecisions = [];
|
|
2233
|
+
const retiredLayoutResult = await collectRetiredLayoutActions({
|
|
2234
|
+
assetRoot,
|
|
2235
|
+
repoRoot,
|
|
2236
|
+
projectName,
|
|
2237
|
+
governanceRoot,
|
|
2238
|
+
recordedState,
|
|
2239
|
+
currentState,
|
|
2240
|
+
humanDecisions,
|
|
2241
|
+
});
|
|
2242
|
+
const retiredLayoutActions = retiredLayoutResult.actions;
|
|
2243
|
+
const retiredTargetPaths = new Set(retiredLayoutActions
|
|
2244
|
+
.filter((action) => action.type === "migrate-owned-file" && action.targetPath)
|
|
2245
|
+
.map((action) => action.targetPath));
|
|
2246
|
+
// Targets occupied by a user-modified legacy copy must not be silently
|
|
2247
|
+
// (re)installed from the package; preserve them for a human/model merge.
|
|
2248
|
+
for (const blockedPath of retiredLayoutResult.blockedTargetPaths) {
|
|
2249
|
+
retiredTargetPaths.add(blockedPath);
|
|
2250
|
+
}
|
|
1893
2251
|
if (!recordedState) {
|
|
1894
2252
|
deterministicActions.push({
|
|
1895
2253
|
type: "bootstrap-surface",
|
|
@@ -1899,6 +2257,8 @@ export async function checkInitUpdate(input) {
|
|
|
1899
2257
|
}
|
|
1900
2258
|
for (const [pathName, state] of Object.entries(currentState.files)) {
|
|
1901
2259
|
if (state.relationship === "missing-from-target") {
|
|
2260
|
+
if (retiredTargetPaths.has(pathName))
|
|
2261
|
+
continue;
|
|
1902
2262
|
const action = actionForMissing(pathName, state);
|
|
1903
2263
|
if (action)
|
|
1904
2264
|
deterministicActions.push(action);
|
|
@@ -1919,11 +2279,23 @@ export async function checkInitUpdate(input) {
|
|
|
1919
2279
|
reason: "required init directory path exists but is not a directory",
|
|
1920
2280
|
});
|
|
1921
2281
|
}
|
|
2282
|
+
else if (recordedState?.stateKind === "recorded" &&
|
|
2283
|
+
(state.mode === "copied" || state.mode === "generated") &&
|
|
2284
|
+
state.currentSha256 !== undefined &&
|
|
2285
|
+
recordedState.files[pathName]?.currentSha256 === state.currentSha256) {
|
|
2286
|
+
deterministicActions.push({
|
|
2287
|
+
type: "refresh-owned-file",
|
|
2288
|
+
path: pathName,
|
|
2289
|
+
mode: state.mode,
|
|
2290
|
+
reason: "refresh an unchanged recorded init file to the current generated or bundled content",
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
1922
2293
|
else {
|
|
1923
2294
|
modelMergeTasks.push(modelMergeTaskFor(pathName, state, allPaths));
|
|
1924
2295
|
}
|
|
1925
2296
|
}
|
|
1926
2297
|
}
|
|
2298
|
+
deterministicActions.unshift(...retiredLayoutActions);
|
|
1927
2299
|
// Safe harness hygiene: strip obsolete pi.requiresApiKey without full harness rewrite.
|
|
1928
2300
|
// Cursor requiresApiKey is intentionally kept when present.
|
|
1929
2301
|
const harnessPath = path.join(repoRoot, "harness.json");
|
|
@@ -1939,14 +2311,21 @@ export async function checkInitUpdate(input) {
|
|
|
1939
2311
|
reason: "harness.json is missing or has a stale IDE JSON Schema reference",
|
|
1940
2312
|
});
|
|
1941
2313
|
}
|
|
1942
|
-
if (
|
|
1943
|
-
isRecord(harness
|
|
1944
|
-
|
|
1945
|
-
|
|
2314
|
+
if (recordedState &&
|
|
2315
|
+
isRecord(harness) &&
|
|
2316
|
+
governanceRoot !== "docs" &&
|
|
2317
|
+
(harness.governanceRoot === "docs" || hasLegacyHarnessGovernancePaths(harness))) {
|
|
2318
|
+
deterministicActions.push({
|
|
2319
|
+
type: "migrate-harness-governance-root",
|
|
2320
|
+
path: "harness.json",
|
|
2321
|
+
reason: `replace the legacy default governanceRoot docs with ${governanceRoot}`,
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
if (isRecord(harness) && needsHarnessModelMigration(harness)) {
|
|
1946
2325
|
deterministicActions.push({
|
|
1947
|
-
type: "
|
|
2326
|
+
type: "migrate-harness-model-fields",
|
|
1948
2327
|
path: "harness.json",
|
|
1949
|
-
reason: "
|
|
2328
|
+
reason: "harness.json contains legacy model fields or executor fields that can be safely normalized",
|
|
1950
2329
|
});
|
|
1951
2330
|
}
|
|
1952
2331
|
}
|
|
@@ -2016,16 +2395,79 @@ async function applySafeAction(input) {
|
|
|
2016
2395
|
await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, "utf-8");
|
|
2017
2396
|
return true;
|
|
2018
2397
|
}
|
|
2398
|
+
if (input.action.type === "migrate-harness-model-fields") {
|
|
2399
|
+
const target = path.join(input.repoRoot, "harness.json");
|
|
2400
|
+
if (!(await exists(target)))
|
|
2401
|
+
return false;
|
|
2402
|
+
const harness = JSON.parse(await readFile(target, "utf-8"));
|
|
2403
|
+
if (!isRecord(harness) || !needsHarnessModelMigration(harness))
|
|
2404
|
+
return false;
|
|
2405
|
+
await writeFile(target, `${JSON.stringify(migrateHarnessModelFields(harness), null, 2)}\n`, "utf-8");
|
|
2406
|
+
return true;
|
|
2407
|
+
}
|
|
2408
|
+
if (input.action.type === "migrate-harness-governance-root") {
|
|
2409
|
+
const target = path.join(input.repoRoot, "harness.json");
|
|
2410
|
+
if (!(await exists(target)))
|
|
2411
|
+
return false;
|
|
2412
|
+
const harness = JSON.parse(await readFile(target, "utf-8"));
|
|
2413
|
+
if (!isRecord(harness) ||
|
|
2414
|
+
(harness.governanceRoot !== "docs" && !hasLegacyHarnessGovernancePaths(harness))) {
|
|
2415
|
+
return false;
|
|
2416
|
+
}
|
|
2417
|
+
const next = {
|
|
2418
|
+
...harness,
|
|
2419
|
+
governanceRoot: input.governanceRoot,
|
|
2420
|
+
};
|
|
2421
|
+
for (const sectionName of ["entrypoints", "artifacts"]) {
|
|
2422
|
+
const section = harness[sectionName];
|
|
2423
|
+
if (!isRecord(section))
|
|
2424
|
+
continue;
|
|
2425
|
+
next[sectionName] = Object.fromEntries(Object.entries(section).map(([key, value]) => [
|
|
2426
|
+
key,
|
|
2427
|
+
typeof value === "string" && value.startsWith("docs/")
|
|
2428
|
+
? `${input.governanceRoot}/${value.slice("docs/".length)}`
|
|
2429
|
+
: value,
|
|
2430
|
+
]));
|
|
2431
|
+
}
|
|
2432
|
+
await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, "utf-8");
|
|
2433
|
+
return true;
|
|
2434
|
+
}
|
|
2435
|
+
if (input.action.type === "migrate-owned-file") {
|
|
2436
|
+
if (!input.action.targetPath)
|
|
2437
|
+
return false;
|
|
2438
|
+
const source = path.join(input.repoRoot, input.action.path);
|
|
2439
|
+
const target = path.join(input.repoRoot, input.action.targetPath);
|
|
2440
|
+
if (!(await exists(source)) || (await exists(target)))
|
|
2441
|
+
return false;
|
|
2442
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
2443
|
+
await rename(source, target);
|
|
2444
|
+
return true;
|
|
2445
|
+
}
|
|
2446
|
+
if (input.action.type === "remove-owned-file") {
|
|
2447
|
+
const target = path.join(input.repoRoot, input.action.path);
|
|
2448
|
+
if (!(await exists(target)))
|
|
2449
|
+
return false;
|
|
2450
|
+
await rm(target, { force: true });
|
|
2451
|
+
return true;
|
|
2452
|
+
}
|
|
2453
|
+
if (input.action.type === "remove-empty-directory") {
|
|
2454
|
+
const target = path.join(input.repoRoot, input.action.path);
|
|
2455
|
+
if (!(await isDirectoryEmpty(target)))
|
|
2456
|
+
return false;
|
|
2457
|
+
await rmdir(target);
|
|
2458
|
+
return true;
|
|
2459
|
+
}
|
|
2019
2460
|
const assetRoot = await findPackageRoot();
|
|
2020
2461
|
const entry = {
|
|
2021
2462
|
path: input.action.path,
|
|
2022
|
-
mode: input.action.
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2463
|
+
mode: input.action.mode ??
|
|
2464
|
+
(input.action.type === "create-directory"
|
|
2465
|
+
? "directory"
|
|
2466
|
+
: input.action.type === "copy-missing"
|
|
2467
|
+
? "copied"
|
|
2468
|
+
: input.action.type === "refresh-managed-block"
|
|
2469
|
+
? "managed-block"
|
|
2470
|
+
: "generated"),
|
|
2029
2471
|
};
|
|
2030
2472
|
const target = path.join(input.repoRoot, input.action.path);
|
|
2031
2473
|
if (input.action.type === "create-directory") {
|
|
@@ -2056,6 +2498,10 @@ async function applySafeAction(input) {
|
|
|
2056
2498
|
await writeFile(target, next, "utf-8");
|
|
2057
2499
|
return true;
|
|
2058
2500
|
}
|
|
2501
|
+
if (input.action.type === "refresh-owned-file") {
|
|
2502
|
+
await writeFile(target, desired.content, "utf-8");
|
|
2503
|
+
return true;
|
|
2504
|
+
}
|
|
2059
2505
|
if (await exists(target))
|
|
2060
2506
|
return false;
|
|
2061
2507
|
await writeFile(target, desired.content, "utf-8");
|
|
@@ -2085,7 +2531,14 @@ export async function applyInitUpdate(input) {
|
|
|
2085
2531
|
skipped.push(action);
|
|
2086
2532
|
continue;
|
|
2087
2533
|
}
|
|
2088
|
-
if (action.type !== "strip-pi-requires-api-key" &&
|
|
2534
|
+
if (action.type !== "strip-pi-requires-api-key" &&
|
|
2535
|
+
action.type !== "add-harness-schema-ref" &&
|
|
2536
|
+
action.type !== "refresh-owned-file" &&
|
|
2537
|
+
action.type !== "migrate-harness-governance-root" &&
|
|
2538
|
+
action.type !== "migrate-harness-model-fields" &&
|
|
2539
|
+
action.type !== "migrate-owned-file" &&
|
|
2540
|
+
action.type !== "remove-owned-file" &&
|
|
2541
|
+
action.type !== "remove-empty-directory") {
|
|
2089
2542
|
const currentState = await buildCurrentSurfaceState({
|
|
2090
2543
|
repoRoot,
|
|
2091
2544
|
projectName,
|
|
@@ -2169,8 +2622,7 @@ export async function runInitDoctor(input) {
|
|
|
2169
2622
|
add("AGENTS.md loop-agent block", (await exists(agents)) && (await readFile(agents, "utf-8")).includes(MANAGED_BLOCK_START), "managed block present");
|
|
2170
2623
|
const readme = path.join(repoRoot, "README.md");
|
|
2171
2624
|
add("README loop-agent block", (await exists(readme)) && (await readFile(readme, "utf-8")).includes(MANAGED_BLOCK_START), "managed block present");
|
|
2172
|
-
add("repo-local skills", await exists(path.join(repoRoot, "skills", "loop-agent", "SKILL.md")), "skills/loop-agent/SKILL.md");
|
|
2173
|
-
add("agent-compatible skills mirror", await exists(path.join(repoRoot, ".agents", "skills", "loop-agent", "SKILL.md")), ".agents/skills/loop-agent/SKILL.md");
|
|
2625
|
+
add("repo-local skills", await exists(path.join(repoRoot, ".agents", "skills", "loop-agent", "SKILL.md")), ".agents/skills/loop-agent/SKILL.md");
|
|
2174
2626
|
const gitignorePath = path.join(repoRoot, ".gitignore");
|
|
2175
2627
|
const gitignoreContent = (await exists(gitignorePath)) ? await readFile(gitignorePath, "utf-8") : "";
|
|
2176
2628
|
add("gitignore loop-agent block", gitignoreContent.includes(GITIGNORE_BLOCK_START) && gitignoreContent.includes(".harness/tasks/*"), ".gitignore managed runtime ignores");
|
|
@@ -2192,7 +2644,7 @@ export async function runInitDoctor(input) {
|
|
|
2192
2644
|
function parseInitArgs(repoRoot, args) {
|
|
2193
2645
|
let subcommand;
|
|
2194
2646
|
let projectName;
|
|
2195
|
-
let governanceRoot
|
|
2647
|
+
let governanceRoot;
|
|
2196
2648
|
let profile = "full";
|
|
2197
2649
|
let merge = true;
|
|
2198
2650
|
let provider;
|
|
@@ -2315,7 +2767,7 @@ function formatInitResult(result) {
|
|
|
2315
2767
|
`written: ${result.written.length}`,
|
|
2316
2768
|
`skipped: ${result.skipped.length}`,
|
|
2317
2769
|
"next: loop-agent init doctor --repo-root <target>",
|
|
2318
|
-
|
|
2770
|
+
`model-executed init: continue automatically by inspecting the target project, adapting README.md, ${result.governanceRoot}/verification-matrix.md, and scripts/ci-tests.sh when needed, then run inspect/docs audit/quick verification.`,
|
|
2319
2771
|
].join("\n");
|
|
2320
2772
|
}
|
|
2321
2773
|
export async function listInitializedFiles(repoRoot) {
|