@tea-agent/loop-agent 0.35.1-beta.0 → 0.35.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/AGENTS.md +110 -108
  2. package/CHANGELOG.md +24 -26
  3. package/README.md +165 -165
  4. package/bin/agent-worker.js +0 -0
  5. package/bin/loop-agent.js +57 -21
  6. package/dist/application/task-lifecycle/advance.js +0 -1
  7. package/dist/build-stamp.json +6 -0
  8. package/dist/cli/program.js +2 -2
  9. package/dist/commands/cursor-prompt.js +6 -6
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/loop-benchmark.js +11 -11
  13. package/dist/commands/pi-reuse-benchmark.js +16 -16
  14. package/dist/commands/run-dag-progress.js +0 -14
  15. package/dist/commands/task-advance.js +3 -33
  16. package/dist/executors/dag-pi-executor.js +44 -0
  17. package/dist/shared/operator/capabilities.js +1 -38
  18. package/dist/shared/package-metadata.js +42 -0
  19. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  20. package/dist/worker/console/chat/pi-runtime.js +25 -41
  21. package/dist/worker/console/chat/routes.js +4 -27
  22. package/dist/worker/console/operation-runner.js +0 -24
  23. package/dist/worker/console/operator-actions.js +0 -58
  24. package/dist/worker/console/static/assets/index-CvsQgALl.js +56 -0
  25. package/dist/worker/console/static/assets/{index-Dups4sSM.css → index-hJqCPs_g.css} +1 -1
  26. package/dist/worker/console/static/index.html +2 -2
  27. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -0
  28. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -13
  29. package/dist/worker/console/static-src/operator-chat/useComposer.js +7 -30
  30. package/dist/worker/loop-agent/loop-agent-client.js +17 -3
  31. package/dist/worker/observability/read-model.js +20 -0
  32. package/dist/worker/observe/static/copy.js +67 -67
  33. package/dist/worker/observe/static/dag-layout.d.ts +36 -36
  34. package/dist/worker/observe/static/dom.js +220 -220
  35. package/dist/worker/observe/static/relations.js +133 -133
  36. package/dist/worker/observe/static/run-processing.js +148 -148
  37. package/dist/worker/observe/static/views/batch.js +227 -227
  38. package/dist/worker/observe/static/views/failures.js +143 -143
  39. package/dist/worker/observe/static/views/feature.js +492 -492
  40. package/dist/worker/observe/static/views/run.js +453 -453
  41. package/dist/worker/observe/static/views/shell.js +7 -7
  42. package/dist/worker/observe/static/views/timeline.js +163 -163
  43. package/dist/worker/preflight.js +2 -1
  44. package/dist/workflows/dag/backend-test-scenario-param.js +33 -23
  45. package/dist/workflows/dag/canvas-observer.js +275 -275
  46. package/dist/workflows/dag/contract-output-registry.js +14 -0
  47. package/dist/workflows/dag/contract-validator-registrations.js +8 -0
  48. package/dist/workflows/dag/dynamic-runtime/shared.js +9 -1
  49. package/dist/workflows/dag/frontend-implementation-contract.js +233 -39
  50. package/dist/workflows/dag/frontend-prewrite-gate.js +364 -61
  51. package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
  52. package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
  53. package/dist/workflows/dag/frontend-recovery-run.js +539 -0
  54. package/dist/workflows/dag/frontend-repair.js +219 -18
  55. package/dist/workflows/dag/frontend-verification-trace.js +47 -32
  56. package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
  57. package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
  58. package/dist/workflows/dag/init-hybrid.js +41 -24
  59. package/dist/workflows/dag/node-execution.js +89 -0
  60. package/dist/workflows/dag/recovery-recommendation.js +58 -0
  61. package/dist/workflows/dag/runner.js +245 -11
  62. package/dist/workflows/dag/scheduler.js +257 -3
  63. package/dist/workflows/dag/types.js +130 -2
  64. package/docs/architecture/evolution.md +73 -73
  65. package/docs/architecture/system-overview.md +100 -100
  66. package/docs/architecture/worker-and-feature.md +122 -122
  67. package/docs/skills/README.md +7 -7
  68. package/docs/templates/adr.md +60 -60
  69. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  70. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  71. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  72. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  73. package/docs/templates/agent-dag-report.schema.json +473 -473
  74. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  75. package/docs/templates/backend-test-result.schema.json +99 -99
  76. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -87
  77. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -153
  78. package/docs/templates/feature-spec.md +53 -53
  79. package/docs/templates/frontend-design-contract.md +42 -42
  80. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  81. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  82. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  83. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  84. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  85. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  86. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  87. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  88. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  89. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  90. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  91. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  92. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  93. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  94. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  95. package/docs/templates/frontend-eval/metrics.md +138 -138
  96. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  97. package/docs/templates/frontend-task-constraints.md +35 -35
  98. package/docs/templates/frontend-task-requirement.md +70 -70
  99. package/docs/templates/init-evolution-review.md +35 -35
  100. package/docs/templates/init-managed-agents.md +154 -156
  101. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  102. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  103. package/docs/templates/knowledge-sync-dag.json +178 -178
  104. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  105. package/docs/templates/product-line/closeout.yaml +9 -9
  106. package/docs/templates/product-line/design.md +13 -13
  107. package/docs/templates/product-line/links.md +10 -10
  108. package/docs/templates/product-line/requirement.md +17 -17
  109. package/docs/templates/product-line/test-plan.md +7 -7
  110. package/docs/templates/project-start-checklist.md +9 -9
  111. package/docs/templates/qa-report.md +48 -48
  112. package/docs/templates/sprint-contract.md +29 -29
  113. package/docs/templates/worker-dogfood-evidence.md +80 -80
  114. package/docs/templates/worker-dogfood-setup.md +68 -68
  115. package/harness.json +2 -5
  116. package/package.json +2 -2
  117. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  118. package/scripts/kb-graph-incremental-prepare.mjs +0 -0
  119. package/scripts/kb-graph-materialize.mjs +105 -105
  120. package/scripts/kb-graph-promote.mjs +164 -164
  121. package/scripts/kb-query.mjs +554 -554
  122. package/skills/agent-worker/SKILL.md +48 -48
  123. package/skills/agent-worker/references/agent-worker-operator.md +159 -159
  124. package/skills/ai-engineering-context/SKILL.md +48 -48
  125. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +0 -0
  126. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +0 -0
  127. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +0 -0
  128. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +0 -0
  129. package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +0 -0
  130. package/skills/analyze-product-requirements/scripts/test-validators.mjs +0 -0
  131. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +0 -0
  132. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +0 -0
  133. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +0 -0
  134. package/skills/browser-tools/browser-content.js +103 -103
  135. package/skills/browser-tools/browser-cookies.js +35 -35
  136. package/skills/browser-tools/browser-eval.js +53 -53
  137. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  138. package/skills/browser-tools/browser-nav.js +44 -44
  139. package/skills/browser-tools/browser-pick.js +162 -162
  140. package/skills/browser-tools/browser-screenshot.js +34 -34
  141. package/skills/browser-tools/browser-start.js +86 -86
  142. package/skills/browser-tools/package-lock.json +2556 -2556
  143. package/skills/browser-tools/package.json +19 -19
  144. package/skills/code-review-core/SKILL.md +20 -20
  145. package/skills/codebase-scout/SKILL.md +19 -19
  146. package/skills/grill-me/SKILL.md +10 -10
  147. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +0 -0
  148. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +0 -0
  149. package/skills/loop-agent/SKILL.md +0 -1
  150. package/skills/loop-agent/references/command-reference.md +639 -641
  151. package/skills/loop-agent/references/docs-converge.md +126 -126
  152. package/skills/loop-agent/references/learned/README.md +21 -21
  153. package/skills/loop-agent/references/pi-prompt.md +23 -23
  154. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  155. package/skills/playwright-cli/references/element-attributes.md +23 -23
  156. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  157. package/skills/playwright-cli/references/request-mocking.md +87 -87
  158. package/skills/playwright-cli/references/running-code.md +241 -241
  159. package/skills/playwright-cli/references/session-management.md +225 -225
  160. package/skills/playwright-cli/references/storage-state.md +275 -275
  161. package/skills/playwright-cli/references/test-generation.md +433 -433
  162. package/skills/requesting-code-review/SKILL.md +101 -101
  163. package/skills/requesting-code-review/code-reviewer.md +168 -168
  164. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  165. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  166. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  167. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  168. package/skills/systematic-debugging/find-polluter.sh +63 -63
  169. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  170. package/skills/systematic-debugging/test-academic.md +14 -14
  171. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  172. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  173. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  174. package/skills/using-git-worktrees/SKILL.md +215 -215
  175. package/skills/verification-before-completion/SKILL.md +154 -154
  176. package/skills/webapp-testing/SKILL.md +19 -19
  177. package/dist/worker/console/operation-wait.js +0 -241
  178. package/dist/worker/console/static/assets/index-SjjjZnV3.js +0 -56
  179. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
@@ -46,60 +46,6 @@ function isHomeFingerprint(value) {
46
46
  keys[1] === "sha256" &&
47
47
  isSha256(value.sha256));
48
48
  }
49
- /**
50
- * Shape guard for the controller-owned gitignore migration assessment.
51
- * `assessGitignoreMigration` is the single schema fact source; status/report
52
- * reads fail closed on any shape that the writer could not have produced
53
- * (AC-005). `queryError` may be absent and arrays may be empty; extra keys are
54
- * tolerated for forward compatibility.
55
- */
56
- function isGitignoreMigrationAssessment(value) {
57
- if (!isRecord(value))
58
- return false;
59
- const assessment = value;
60
- if (assessment.schemaVersion !== 1)
61
- return false;
62
- if (typeof assessment.managedBlockRefreshed !== "boolean")
63
- return false;
64
- if (typeof assessment.needsAgentsReview !== "boolean")
65
- return false;
66
- if (!isRecord(assessment.git))
67
- return false;
68
- const git = assessment.git;
69
- if (typeof git.isRepo !== "boolean")
70
- return false;
71
- if (git.queryError !== undefined && typeof git.queryError !== "string") {
72
- return false;
73
- }
74
- if (!Array.isArray(assessment.harnessTracked) ||
75
- !assessment.harnessTracked.every((entry) => typeof entry === "string")) {
76
- return false;
77
- }
78
- if (!Array.isArray(assessment.agentsTracked) ||
79
- !assessment.agentsTracked.every((entry) => typeof entry === "string")) {
80
- return false;
81
- }
82
- if (!Array.isArray(assessment.stagedRisk) ||
83
- !assessment.stagedRisk.every((entry) => typeof entry === "string")) {
84
- return false;
85
- }
86
- if (!Array.isArray(assessment.followUp) ||
87
- !assessment.followUp.every((entry) => typeof entry === "string")) {
88
- return false;
89
- }
90
- if (!Array.isArray(assessment.recommendedCommands))
91
- return false;
92
- for (const entry of assessment.recommendedCommands) {
93
- if (!isRecord(entry))
94
- return false;
95
- if (typeof entry.command !== "string" || typeof entry.note !== "string") {
96
- return false;
97
- }
98
- }
99
- return (assessment.status === "clean" ||
100
- assessment.status === "recommended" ||
101
- assessment.status === "blocked");
102
- }
103
49
  function cloneJson(value) {
104
50
  return JSON.parse(JSON.stringify(value));
105
51
  }
@@ -225,143 +171,6 @@ function assertTerminalCoherence(state) {
225
171
  throw new Error("invalid init upgrade status/phase coherence");
226
172
  }
227
173
  }
228
- /** Read-only git query; never mutates the user index (no status refresh, no write). */
229
- function runGitQuery(cwd, args) {
230
- return new Promise((resolve) => {
231
- const child = spawn("git", ["-C", cwd, ...args], {
232
- stdio: ["ignore", "pipe", "pipe"],
233
- });
234
- const stdoutChunks = [];
235
- const stderrChunks = [];
236
- child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
237
- child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
238
- child.on("error", (error) => {
239
- resolve({ ok: false, stdout: "", stderr: error.message });
240
- });
241
- child.on("close", (code) => {
242
- resolve({
243
- ok: code === 0,
244
- stdout: Buffer.concat(stdoutChunks).toString("utf8"),
245
- stderr: Buffer.concat(stderrChunks).toString("utf8"),
246
- });
247
- });
248
- });
249
- }
250
- function splitNulPaths(output) {
251
- return output.split("\0").filter((entry) => entry.length > 0);
252
- }
253
- /**
254
- * Gitignore migration assessment (PRD 2026-08-13): inventories tracked
255
- * `.harness/**` / `.agents/**` with read-only git queries after the managed
256
- * block has converged, and recommends index-only untrack commands. The
257
- * controller never mutates the user Git index and never deletes local files.
258
- */
259
- export async function assessGitignoreMigration(repoRoot, input) {
260
- const base = {
261
- schemaVersion: 1,
262
- managedBlockRefreshed: input.managedBlockRefreshed,
263
- harnessTracked: [],
264
- agentsTracked: [],
265
- stagedRisk: [],
266
- needsAgentsReview: false,
267
- followUp: [
268
- "run `git status --short` and confirm only the intended untrack changes",
269
- "confirm the .harness/ and .agents/ directories still exist locally after untracking",
270
- "run `loop-agent init doctor` to re-check the gitignore loop-agent block",
271
- ],
272
- };
273
- const inside = await runGitQuery(repoRoot, ["rev-parse", "--is-inside-work-tree"]);
274
- if (!inside.ok) {
275
- return {
276
- ...base,
277
- git: {
278
- isRepo: false,
279
- queryError: inside.stderr.trim() || "not inside a Git work tree",
280
- },
281
- recommendedCommands: [],
282
- status: "blocked",
283
- };
284
- }
285
- const tracked = await runGitQuery(repoRoot, [
286
- "ls-files",
287
- "-z",
288
- "--cached",
289
- "--",
290
- ".harness",
291
- ".agents",
292
- ]);
293
- if (!tracked.ok) {
294
- return {
295
- ...base,
296
- git: {
297
- isRepo: true,
298
- queryError: tracked.stderr.trim() || "git ls-files query failed",
299
- },
300
- recommendedCommands: [],
301
- status: "blocked",
302
- };
303
- }
304
- const staged = await runGitQuery(repoRoot, [
305
- "diff",
306
- "--cached",
307
- "--name-only",
308
- "-z",
309
- ]);
310
- if (!staged.ok) {
311
- return {
312
- ...base,
313
- git: {
314
- isRepo: true,
315
- queryError: staged.stderr.trim() || "git diff --cached query failed",
316
- },
317
- recommendedCommands: [],
318
- status: "blocked",
319
- };
320
- }
321
- const trackedPaths = splitNulPaths(tracked.stdout);
322
- const harnessTracked = trackedPaths.filter((entry) => entry.startsWith(".harness/"));
323
- const agentsTracked = trackedPaths.filter((entry) => entry.startsWith(".agents/"));
324
- const stagedRisk = splitNulPaths(staged.stdout);
325
- const needsAgentsReview = agentsTracked.length > 0;
326
- if (stagedRisk.length > 0) {
327
- // Fail closed: staged entries make any untrack advice ambiguous.
328
- return {
329
- ...base,
330
- git: { isRepo: true },
331
- harnessTracked,
332
- agentsTracked,
333
- stagedRisk,
334
- needsAgentsReview,
335
- recommendedCommands: [],
336
- status: "blocked",
337
- };
338
- }
339
- const recommendedCommands = [];
340
- if (harnessTracked.length > 0) {
341
- recommendedCommands.push({
342
- command: "git rm -r --cached --ignore-unmatch .harness",
343
- note: "untracks tracked .harness runtime facts; index-only, working-tree files are kept",
344
- });
345
- }
346
- if (agentsTracked.length > 0) {
347
- recommendedCommands.push({
348
- command: "git rm -r --cached --ignore-unmatch .agents",
349
- note: "untracks tracked .agents local skills; index-only, working-tree files are kept; review team-shareable customizations first",
350
- });
351
- }
352
- return {
353
- ...base,
354
- git: { isRepo: true },
355
- harnessTracked,
356
- agentsTracked,
357
- stagedRisk,
358
- needsAgentsReview,
359
- recommendedCommands,
360
- status: harnessTracked.length > 0 || agentsTracked.length > 0
361
- ? "recommended"
362
- : "clean",
363
- };
364
- }
365
174
  function runRoot(repoRoot) {
366
175
  return path.join(repoRoot, RUN_ROOT);
367
176
  }
@@ -431,7 +240,6 @@ async function writeControllerAnchor(input) {
431
240
  throw new Error("init upgrade controller authority anchor path escapes Git directory");
432
241
  }
433
242
  let authoritativeHomeFingerprint;
434
- let authoritativeGitignoreMigrationSha256;
435
243
  if (input.initialize) {
436
244
  if (await exists(location.file)) {
437
245
  throw new Error("init upgrade controller authority anchor already exists");
@@ -444,14 +252,6 @@ async function writeControllerAnchor(input) {
444
252
  else {
445
253
  const current = await readControllerAnchor(location.repoRoot, input.state);
446
254
  authoritativeHomeFingerprint = current.homeFingerprint;
447
- // Preserve the gitignore migration artifact binding across later
448
- // mergeAuthority/completion writes; fail closed on a corrupt or
449
- // non-SHA-256 binding instead of silently dropping or propagating it.
450
- const digest = input.gitignoreMigrationSha256 ?? current.gitignoreMigrationSha256;
451
- if (digest !== undefined && !isSha256(digest)) {
452
- throw new Error("init upgrade controller authority gitignore migration digest binding is invalid");
453
- }
454
- authoritativeGitignoreMigrationSha256 = digest;
455
255
  }
456
256
  await writeJsonAtomic(location.file, {
457
257
  schemaVersion: 1,
@@ -459,9 +259,6 @@ async function writeControllerAnchor(input) {
459
259
  runId: input.state.runId,
460
260
  controllerIdentity: input.state.controllerIdentity,
461
261
  homeFingerprint: authoritativeHomeFingerprint,
462
- ...(authoritativeGitignoreMigrationSha256 !== undefined
463
- ? { gitignoreMigrationSha256: authoritativeGitignoreMigrationSha256 }
464
- : {}),
465
262
  ...(input.mergeAuthority ? { mergeAuthority: input.mergeAuthority } : {}),
466
263
  ...(input.completion ? { completion: input.completion } : {}),
467
264
  });
@@ -654,7 +451,7 @@ async function saveState(directory, state) {
654
451
  state.updatedAt = new Date().toISOString();
655
452
  await writeJsonAtomic(path.join(directory, "state.json"), state);
656
453
  }
657
- function toResult(state, verification, gitignoreMigration) {
454
+ function toResult(state, verification) {
658
455
  return {
659
456
  runId: state.runId,
660
457
  status: state.status,
@@ -665,10 +462,9 @@ function toResult(state, verification, gitignoreMigration) {
665
462
  humanDecisions: state.humanDecisions,
666
463
  nextAction: state.nextAction,
667
464
  ...(verification ? { verification } : {}),
668
- ...(gitignoreMigration ? { gitignoreMigration } : {}),
669
465
  };
670
466
  }
671
- function renderReport(state, verification, gitignoreMigration) {
467
+ function renderReport(state, verification) {
672
468
  const lines = [
673
469
  "# loop-agent init upgrade report",
674
470
  "",
@@ -684,47 +480,6 @@ function renderReport(state, verification, gitignoreMigration) {
684
480
  `- retired path actions: ${state.actionReceipts.filter((action) => action.type === "migrate-owned-file" || action.type === "remove-owned-file" || action.type === "remove-empty-directory").length}`,
685
481
  `- remaining model merge tasks: ${state.mergeTasks.length}`,
686
482
  `- remaining human decisions: ${state.humanDecisions.length}`,
687
- `- gitignore migration assessment: \`.harness/init-upgrades/${state.runId}/gitignore-migration.json\` (index-only untrack guidance; the controller never modifies the Git index)`,
688
- ...(gitignoreMigration
689
- ? [
690
- "",
691
- "## Gitignore migration assessment",
692
- "",
693
- `- status: \`${gitignoreMigration.status}\``,
694
- `- managed block refreshed: \`${gitignoreMigration.managedBlockRefreshed}\``,
695
- `- tracked \`.harness\` inventory: ${gitignoreMigration.harnessTracked.length > 0
696
- ? gitignoreMigration.harnessTracked
697
- .map((entry) => `\`${entry}\``)
698
- .join(", ")
699
- : "(none)"}`,
700
- `- tracked \`.agents\` inventory: ${gitignoreMigration.agentsTracked.length > 0
701
- ? gitignoreMigration.agentsTracked
702
- .map((entry) => `\`${entry}\``)
703
- .join(", ")
704
- : "(none)"}`,
705
- ...(gitignoreMigration.stagedRisk.length > 0
706
- ? [
707
- `- staged risk: ${gitignoreMigration.stagedRisk
708
- .map((entry) => `\`${entry}\``)
709
- .join(", ")}`,
710
- ]
711
- : []),
712
- ...(gitignoreMigration.git.queryError
713
- ? [
714
- `- git query blocker: \`${gitignoreMigration.git.queryError}\``,
715
- ]
716
- : []),
717
- `- \`.agents\` review required: \`${gitignoreMigration.needsAgentsReview}\``,
718
- "- recommended commands:",
719
- ...(gitignoreMigration.recommendedCommands.length > 0
720
- ? gitignoreMigration.recommendedCommands.map((entry) => ` - \`${entry.command}\` — ${entry.note}`)
721
- : [" - (none)"]),
722
- "- follow-up:",
723
- ...(gitignoreMigration.followUp.length > 0
724
- ? gitignoreMigration.followUp.map((entry) => ` - ${entry}`)
725
- : [" - (none)"]),
726
- ]
727
- : []),
728
483
  "",
729
484
  "## Next action",
730
485
  "",
@@ -746,8 +501,8 @@ function renderReport(state, verification, gitignoreMigration) {
746
501
  ];
747
502
  return `${lines.join("\n")}\n`;
748
503
  }
749
- async function writeReport(directory, state, verification, gitignoreMigration) {
750
- await writeFile(path.join(directory, "final-report.md"), renderReport(state, verification, gitignoreMigration), "utf-8");
504
+ async function writeReport(directory, state, verification) {
505
+ await writeFile(path.join(directory, "final-report.md"), renderReport(state, verification), "utf-8");
751
506
  }
752
507
  async function assertInactive(input) {
753
508
  let activity;
@@ -2548,62 +2303,14 @@ async function readValidatedStatusState(repoRoot, runId) {
2548
2303
  throw new Error("Controller identity drift detected for persisted init upgrade state");
2549
2304
  }
2550
2305
  }
2551
- return { state, directory, anchor };
2552
- }
2553
- /**
2554
- * Read-only projection of the controller-owned gitignore migration assessment
2555
- * for status/report reads. The artifact is written by executeUpgrade after the
2556
- * managed block has converged; a missing artifact is tolerated only while the
2557
- * run has not yet reached that write point (DISCOVER/VERSION_GATE). Every
2558
- * other missing, malformed, or shape-invalid artifact fails closed so
2559
- * status/report never trust mutable or incomplete content (AC-005).
2560
- */
2561
- async function readGitignoreMigrationProjection(directory, state, gitignoreMigrationSha256) {
2562
- const file = path.join(directory, "gitignore-migration.json");
2563
- let rawBytes;
2564
- let raw;
2565
- try {
2566
- rawBytes = await readFile(file);
2567
- raw = rawBytes.toString("utf-8");
2568
- }
2569
- catch (error) {
2570
- const cause = error;
2571
- if (cause.code === "ENOENT") {
2572
- if (state.phase === "DISCOVER" || state.phase === "VERSION_GATE") {
2573
- return undefined;
2574
- }
2575
- throw new Error(`init upgrade integrity error: gitignore migration artifact is missing for run ${state.runId} (phase ${state.phase}); refusing to trust an incomplete run`);
2576
- }
2577
- throw error;
2578
- }
2579
- let parsed;
2580
- try {
2581
- parsed = JSON.parse(raw);
2582
- }
2583
- catch {
2584
- throw new Error(`init upgrade integrity error: gitignore migration artifact is malformed JSON for run ${state.runId} (${file})`);
2585
- }
2586
- if (!isGitignoreMigrationAssessment(parsed)) {
2587
- throw new Error(`init upgrade integrity error: gitignore migration artifact has an invalid shape for run ${state.runId} (${file})`);
2588
- }
2589
- // The artifact exists, so this run has generated an assessment; the
2590
- // controller authority binding must exist, be a legal SHA-256, and match
2591
- // the raw artifact bytes on disk (fail closed otherwise).
2592
- if (gitignoreMigrationSha256 === undefined || !isSha256(gitignoreMigrationSha256)) {
2593
- throw new Error(`init upgrade integrity error: gitignore migration artifact digest binding is missing or invalid for run ${state.runId}`);
2594
- }
2595
- if (sha256(rawBytes) !== gitignoreMigrationSha256) {
2596
- throw new Error(`init upgrade integrity error: gitignore migration artifact digest does not match the controller authority binding for run ${state.runId}`);
2597
- }
2598
- return parsed;
2306
+ return { state, directory };
2599
2307
  }
2600
2308
  async function readStatusResult(repoRoot, runId) {
2601
- const { state, directory, anchor } = await readValidatedStatusState(repoRoot, runId);
2602
- if (state.status !== "completed") {
2603
- return toResult(state, undefined, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256));
2604
- }
2309
+ const { state } = await readValidatedStatusState(repoRoot, runId);
2310
+ if (state.status !== "completed")
2311
+ return toResult(state);
2605
2312
  const authenticated = await readFreshCompletedVerification(repoRoot, state);
2606
- return toResult(state, authenticated.verification, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256));
2313
+ return toResult(state, authenticated.verification);
2607
2314
  }
2608
2315
  async function executeUpgrade(directory, state, input) {
2609
2316
  const identity = await readIdentity();
@@ -2738,50 +2445,17 @@ async function executeUpgrade(directory, state, input) {
2738
2445
  }
2739
2446
  }
2740
2447
  state.humanDecisions = post.humanDecisions;
2741
- // Gitignore migration assessment: after the managed block has converged,
2742
- // inventory tracked .harness/.agents paths with read-only git queries and
2743
- // recommend index-only untrack commands. The controller never mutates the
2744
- // user Git index; suggested commands only change the index and keep the
2745
- // working tree intact.
2746
- const gitignoreMigration = await assessGitignoreMigration(state.repoRoot, {
2747
- managedBlockRefreshed: !post.deterministicActions.some((action) => action.type === "refresh-managed-block" &&
2748
- action.path === ".gitignore"),
2749
- });
2750
- await writeJsonAtomic(path.join(directory, "gitignore-migration.json"), gitignoreMigration);
2751
- // Bind the raw on-disk artifact bytes to the Git-dir controller authority
2752
- // before any needs-human-decision/model-merge/completed return; later
2753
- // mergeAuthority/completion anchor writes preserve this binding.
2754
- await writeControllerAnchor({
2755
- state,
2756
- gitignoreMigrationSha256: sha256(await readFile(path.join(directory, "gitignore-migration.json"))),
2757
- });
2758
- if (gitignoreMigration.status === "blocked" ||
2759
- gitignoreMigration.needsAgentsReview) {
2760
- // Route through the existing needs-human-decision pause: `.agents`
2761
- // tracked content must be reviewed (PRD §7) and staged blockers must be
2762
- // resolved before safe untrack guidance can be given (PRD §8).
2763
- post.humanDecisions.push(gitignoreMigration.status === "blocked"
2764
- ? {
2765
- path: ".harness/** (gitignore migration)",
2766
- reason: "staged changes or a failed git query block safe untrack guidance; resolve the blocker, then rerun the migration assessment",
2767
- }
2768
- : {
2769
- path: ".agents/** (gitignore migration)",
2770
- reason: "tracked .agents content may include team-shareable customizations; review before running the suggested untrack commands",
2771
- });
2772
- }
2773
- state.humanDecisions = post.humanDecisions;
2774
2448
  if (post.humanDecisions.length > 0) {
2775
2449
  state.mergeTasks = [];
2776
2450
  state.mergeGuard = undefined;
2777
2451
  state.status = "needs-human-decision";
2778
2452
  state.phase = "MODEL_MERGE";
2779
2453
  state.nextAction =
2780
- "Resolve the recorded destructive, credential, or ambiguous decisions; the controller will not overwrite uncertain user intent. For gitignore migration: review tracked .agents customizations or resolve the staged blocker first, then rerun the assessment so index-only untrack commands can be recommended.";
2454
+ "Resolve the recorded destructive, credential, or ambiguous decisions; the controller will not overwrite uncertain user intent.";
2781
2455
  await writeJsonAtomic(path.join(directory, "merge-tasks.json"), state.mergeTasks);
2782
2456
  await saveState(directory, state);
2783
- await writeReport(directory, state, undefined, gitignoreMigration);
2784
- return toResult(state, undefined, gitignoreMigration);
2457
+ await writeReport(directory, state);
2458
+ return toResult(state);
2785
2459
  }
2786
2460
  if (post.modelMergeTasks.length > 0) {
2787
2461
  const activeTask = post.modelMergeTasks[0];
@@ -2813,8 +2487,8 @@ async function executeUpgrade(directory, state, input) {
2813
2487
  state.nextAction = `Complete only task ${activeTask.taskId} within allowedPaths=[${activeTask.allowedPaths.join(", ")}], then call init upgrade --continue. The controller will reject every other workspace change and all symlink/realpath escapes.`;
2814
2488
  await writeJsonAtomic(path.join(directory, "merge-tasks.json"), state.mergeTasks);
2815
2489
  await saveState(directory, state);
2816
- await writeReport(directory, state, undefined, gitignoreMigration);
2817
- return toResult(state, undefined, gitignoreMigration);
2490
+ await writeReport(directory, state);
2491
+ return toResult(state);
2818
2492
  }
2819
2493
  state.mergeTasks = [];
2820
2494
  state.mergeGuard = undefined;
@@ -2830,7 +2504,7 @@ async function executeUpgrade(directory, state, input) {
2830
2504
  state.nextAction =
2831
2505
  "Init upgrade completed; reload the host session so updated AGENTS, skills, and recovery extensions are discovered.";
2832
2506
  // The report is controller-produced before its exact bytes are anchored.
2833
- await writeReport(directory, state, verification, gitignoreMigration);
2507
+ await writeReport(directory, state, verification);
2834
2508
  const finalReportSha256 = sha256(await readFile(path.join(directory, "final-report.md")));
2835
2509
  await writeControllerAnchor({
2836
2510
  state,
@@ -2855,8 +2529,8 @@ async function executeUpgrade(directory, state, input) {
2855
2529
  : "Verification failed. Inspect verification.json; repair only the controller-returned concrete merge path or address the reported external blocker, then use --continue.";
2856
2530
  }
2857
2531
  await saveState(directory, state);
2858
- await writeReport(directory, state, verification, gitignoreMigration);
2859
- return toResult(state, verification, gitignoreMigration);
2532
+ await writeReport(directory, state, verification);
2533
+ return toResult(state, verification);
2860
2534
  }
2861
2535
  export async function runInitUpgrade(input) {
2862
2536
  const repoRoot = path.resolve(input.repoRoot);
@@ -2867,20 +2541,14 @@ export async function runInitUpgrade(input) {
2867
2541
  if (mode === "status")
2868
2542
  return await readStatusResult(repoRoot, input.runId);
2869
2543
  if (mode === "report") {
2870
- const { state, directory, anchor } = await readValidatedStatusState(repoRoot, input.runId);
2544
+ const { state } = await readValidatedStatusState(repoRoot, input.runId);
2871
2545
  if (state.status === "completed") {
2872
2546
  const authenticated = await readFreshCompletedVerification(repoRoot, state);
2873
- // Completed report bytes are anchored by finalReportSha256; still
2874
- // verify the gitignore migration artifact binding so shape-valid
2875
- // tampering fails closed here as well.
2876
- await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256);
2877
2547
  return { markdown: authenticated.reportMarkdown };
2878
2548
  }
2879
2549
  // Non-completed reports are projections of the validated state, never an
2880
2550
  // unauthenticated read of a mutable run-local report file.
2881
- return {
2882
- markdown: renderReport(state, undefined, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256)),
2883
- };
2551
+ return { markdown: renderReport(state) };
2884
2552
  }
2885
2553
  const runId = mode === "continue"
2886
2554
  ? input.runId
@@ -846,19 +846,55 @@ function mergeGitignoreManagedBlock(existing, block) {
846
846
  return `${block}\n`;
847
847
  return `${existing.trimEnd()}\n\n${block}\n`;
848
848
  }
849
- /**
850
- * Shared ignore rules for loop-agent runtime facts. The four directories are
851
- * local, rebuildable runtime surface: every developer runs loop-agent init
852
- * after cloning, so nothing inside them is team-shareable by default.
853
- * `scripts/` and `ai_workspace/loop-agent/` stay on the shared commit surface.
854
- */
849
+ /** Shared ignore rules for loop-agent runtime facts; keep prompts/ and directory structure shareable. */
855
850
  export function buildManagedGitignoreBlock() {
856
851
  return [
857
852
  GITIGNORE_BLOCK_START,
858
- "# loop-agent runtime: local, rebuildable facts; do not ignore scripts/ or ai_workspace/loop-agent/",
859
- ".harness/",
860
- ".agents/",
853
+ "# loop-agent runtime: ignore personal/session facts; keep prompts and directory placeholders shareable",
854
+ "",
855
+ "# tasks (source + runtime state per developer/session)",
856
+ ".harness/tasks/*",
857
+ "!.harness/tasks/.gitkeep",
858
+ "",
859
+ "# DAG runs",
860
+ ".harness/dag-runs/active/*",
861
+ "!.harness/dag-runs/active/.gitkeep",
862
+ ".harness/dag-runs/completed/*",
863
+ "!.harness/dag-runs/completed/.gitkeep",
864
+ ".harness/dag-runs/paused/*",
865
+ "!.harness/dag-runs/paused/.gitkeep",
866
+ "",
867
+ "# one-shot executor runs",
868
+ ".harness/runs/*",
869
+ "!.harness/runs/.gitkeep",
870
+ "!.harness/runs/active/",
871
+ "!.harness/runs/active/.gitkeep",
872
+ "!.harness/runs/completed/",
873
+ "!.harness/runs/completed/.gitkeep",
874
+ "!.harness/runs/failed/",
875
+ "!.harness/runs/failed/.gitkeep",
876
+ "",
877
+ "# session / cache / logs / recomputable surface state",
878
+ ".harness/live/",
879
+ ".harness/cache/",
880
+ ".harness/*.log",
881
+ ".harness/init-surface.json",
882
+ ".harness/init-upgrades/",
883
+ "",
884
+ "# Eval Lab runtime (candidates, campaigns, scorecards, dogfood scratch)",
885
+ ".harness/evaluation/",
886
+ "",
887
+ "# fullstack / worker dogfood evidence (local campaign scratch)",
888
+ ".harness/dogfood-evidence/",
889
+ "",
890
+ "# worker task pool",
891
+ ".harness/task-pool/*",
861
892
  ".task-pool/",
893
+ "",
894
+ "# repo-local skill dependencies installed inside projected skills",
895
+ ".agents/skills/*/node_modules/",
896
+ "",
897
+ "# multi-worktree parallel mode",
862
898
  ".worktrees/",
863
899
  GITIGNORE_BLOCK_END,
864
900
  ].join("\n");
@@ -1051,8 +1087,26 @@ async function ensureHarnessDirs(repoRoot, written) {
1051
1087
  await mkdir(path.join(repoRoot, dir), { recursive: true });
1052
1088
  written.push(dir);
1053
1089
  }
1054
- // No .gitkeep placeholders: .harness/ is fully ignored by the managed
1055
- // gitignore block, and runtime dirs are recreated on demand by init/doctor.
1090
+ const placeholders = [
1091
+ ".harness/tasks/.gitkeep",
1092
+ ".harness/dag-runs/active/.gitkeep",
1093
+ ".harness/dag-runs/completed/.gitkeep",
1094
+ ".harness/dag-runs/paused/.gitkeep",
1095
+ ".harness/runs/.gitkeep",
1096
+ ".harness/runs/active/.gitkeep",
1097
+ ".harness/runs/completed/.gitkeep",
1098
+ ".harness/runs/failed/.gitkeep",
1099
+ ];
1100
+ for (const relativePath of placeholders) {
1101
+ const target = path.join(repoRoot, relativePath);
1102
+ try {
1103
+ await access(target);
1104
+ }
1105
+ catch {
1106
+ await writeFile(target, "", "utf8");
1107
+ written.push(relativePath);
1108
+ }
1109
+ }
1056
1110
  }
1057
1111
  async function writeCompatPrompts(input) {
1058
1112
  for (const [name, content] of Object.entries(COMPAT_PROMPTS)) {
@@ -2436,7 +2490,6 @@ export function buildInitInstructions(input) {
2436
2490
  "When the user says `loop-agent初始化更新`, `loop-agent 初始化更新`, `更新 loop-agent 初始化内容`, or an equivalent write request, run `loop-agent init upgrade --repo-root . --json` as the single controller-owned entry. Complete returned single-file allowedPaths merge tasks and call `--continue` until a stable terminal result; do not stop at check-update, needs-safe-update, needs-model-merge, or verification-pending.",
2437
2491
  "Explicit `检查初始化更新`, `初始化更新校验`, or `只检查,不要修改` stays strictly read-only: run only `loop-agent init check-update --repo-root . --markdown` and do not create an upgrade run.",
2438
2492
  "Upgrade manages project `.opencode/plugins/`, `.pi/extensions/`, and `.pi/settings.json` by nested merge. Pi must trust the project before loading these settings; do not read or write `~/.pi/agent/settings.json` by default.",
2439
- "The upgrade also writes a read-only gitignore migration assessment (`.harness/init-upgrades/<run-id>/gitignore-migration.json`): tracked `.harness/**` and `.agents/**` get index-only `git rm -r --cached --ignore-unmatch` guidance (working-tree files are kept). Pause for human review when `.agents` content is tracked or when staged changes / git query failures block safe guidance; never run git rm yourself.",
2440
2493
  "",
2441
2494
  "## Apply Defaults",
2442
2495
  "",
@@ -2452,7 +2505,7 @@ export function buildInitInstructions(input) {
2452
2505
  "- 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.",
2453
2506
  `- 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.`,
2454
2507
  "- 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.",
2455
- "- Merge a loop-agent managed block into `.gitignore` that ignores exactly the four local, rebuildable runtime directories (`.harness/`, `.agents/`, `.task-pool/`, `.worktrees/`) while keeping `scripts/` and `ai_workspace/loop-agent/` on the shared commit surface; keep all user rules outside the block untouched.",
2508
+ "- Merge a loop-agent managed block into `.gitignore` that ignores harness runtime facts (tasks, dag-runs, runs, evaluation, dogfood-evidence, live, cache, init-surface.json, .harness/task-pool, legacy .task-pool residue) while keeping prompts and directory placeholders shareable.",
2456
2509
  "- Do not copy examples by default; examples stay bundled in the tool and are available through `loop-agent examples`.",
2457
2510
  "- Add or update a loop-agent managed block in AGENTS.md.",
2458
2511
  "- The generated AGENTS.md must include documentation convergence and structured DAG write-boundary rules so target projects keep the same working discipline as this repository.",
@@ -3329,7 +3382,7 @@ export async function runInitDoctor(input) {
3329
3382
  ? await readFile(gitignorePath, "utf-8")
3330
3383
  : "";
3331
3384
  add("gitignore loop-agent block", gitignoreContent.includes(GITIGNORE_BLOCK_START) &&
3332
- gitignoreContent.includes(".harness/"), ".gitignore managed runtime ignores");
3385
+ gitignoreContent.includes(".harness/tasks/*"), ".gitignore managed runtime ignores");
3333
3386
  const requiredScripts = Object.keys(INIT_SCRIPT_FILES);
3334
3387
  const missingScripts = [];
3335
3388
  for (const script of requiredScripts) {
@@ -37,17 +37,17 @@ export function parseLoopBenchmarkArgs(args) {
37
37
  return { json, markdown, outputPath };
38
38
  }
39
39
  export function printLoopBenchmarkUsage() {
40
- console.log(`usage: loop-benchmark [options]
41
-
42
- Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
43
-
44
- Options:
45
- --json Emit JSON (default when no format flag is set)
46
- --markdown Emit Markdown report
47
- --output <path> Write Markdown report to a repo-relative or absolute path
48
- -h, --help Show this help
49
-
50
- Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
40
+ console.log(`usage: loop-benchmark [options]
41
+
42
+ Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
43
+
44
+ Options:
45
+ --json Emit JSON (default when no format flag is set)
46
+ --markdown Emit Markdown report
47
+ --output <path> Write Markdown report to a repo-relative or absolute path
48
+ -h, --help Show this help
49
+
50
+ Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
51
51
  Recommendation never changes convergence.enabled default.`);
52
52
  }
53
53
  export async function runLoopBenchmark(repoRoot, rawArgs) {