@tea-agent/loop-agent 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +127 -92
  3. package/dist/adapters/index.js +3 -2
  4. package/dist/adapters/loop-agent.js +44 -2
  5. package/dist/application/dag/args.js +420 -0
  6. package/dist/application/dag/generate-task-dag.js +280 -0
  7. package/dist/application/dag/report-dag.js +14 -0
  8. package/dist/application/dag/run-dag.js +93 -0
  9. package/dist/application/dag/validate-dag.js +101 -0
  10. package/dist/application/loop/run-action.js +23 -0
  11. package/dist/cli/catalog.js +2 -237
  12. package/dist/cli/command-definitions.js +571 -0
  13. package/dist/cli/index.js +2 -0
  14. package/dist/cli/program.js +65 -1
  15. package/dist/cli/router.js +13 -0
  16. package/dist/cli-governance/active-residue-check.js +38 -0
  17. package/dist/commands/dag-report.js +6 -107
  18. package/dist/commands/dag-run-task.js +8 -466
  19. package/dist/commands/dag-validate.js +7 -179
  20. package/dist/commands/examples.js +90 -0
  21. package/dist/commands/init.js +1495 -0
  22. package/dist/commands/loop.js +57 -31
  23. package/dist/commands/pi-prompt.js +2 -9
  24. package/dist/commands/run-dag.js +7 -180
  25. package/dist/executors/cursor-executor-artifacts.js +3 -4
  26. package/dist/executors/cursor-worker-client.js +13 -3
  27. package/dist/executors/dag-cursor-executor.js +2 -3
  28. package/dist/executors/dag-pi-executor.js +3 -4
  29. package/dist/executors/dag-static-executor.js +2 -5
  30. package/dist/executors/pi-defaults.js +9 -0
  31. package/dist/executors/shell-executor.js +12 -20
  32. package/dist/governance/manifest-types.js +1 -0
  33. package/dist/infrastructure/harness/active-residue-policy.js +73 -0
  34. package/dist/infrastructure/harness/artifact-store.js +72 -0
  35. package/dist/infrastructure/harness/atomic-write.js +49 -0
  36. package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
  37. package/dist/infrastructure/harness/loop-action-store.js +23 -0
  38. package/dist/infrastructure/harness/loop-store.js +41 -0
  39. package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
  40. package/dist/infrastructure/harness/task-store.js +77 -0
  41. package/dist/records/one-shot-runs.js +26 -61
  42. package/dist/records/promotion.js +3 -4
  43. package/dist/shared/artifacts-core.js +5 -5
  44. package/dist/shared/logger.js +9 -15
  45. package/dist/task/delegate.js +4 -4
  46. package/dist/task/runtime.js +5 -7
  47. package/dist/task/state.js +6 -20
  48. package/dist/workflows/dag/convergence/controller.js +277 -0
  49. package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
  50. package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
  51. package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
  52. package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
  53. package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
  54. package/dist/workflows/dag/lifecycle.js +6 -5
  55. package/dist/workflows/dag/node-execution.js +262 -0
  56. package/dist/workflows/dag/run-store.js +36 -0
  57. package/dist/workflows/dag/runner.js +82 -1341
  58. package/dist/workflows/dag/scheduler.js +84 -0
  59. package/dist/workflows/dag/upstream-artifacts.js +20 -18
  60. package/dist/workflows/loop/actions/cursor-fix.js +191 -0
  61. package/dist/workflows/loop/actions/dag-action.js +130 -0
  62. package/dist/workflows/loop/actions/pi-review.js +267 -0
  63. package/dist/workflows/loop/actions/shared.js +157 -0
  64. package/dist/workflows/loop/actions/shell-verify.js +82 -0
  65. package/dist/workflows/loop/actions/types.js +1 -0
  66. package/dist/workflows/loop/actions/workflow-action.js +255 -0
  67. package/dist/workflows/loop/actions.js +55 -1212
  68. package/dist/workflows/loop/closeout.js +5 -4
  69. package/dist/workflows/loop/context.js +2 -3
  70. package/dist/workflows/loop/events.js +3 -2
  71. package/dist/workflows/loop/policy/auto-policy.js +104 -0
  72. package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
  73. package/dist/workflows/loop/rounds.js +3 -3
  74. package/dist/workflows/loop/signals.js +4 -7
  75. package/dist/workflows/loop/state.js +11 -11
  76. package/docs/README.md +3 -2
  77. package/docs/architecture/runtime-boundaries.md +147 -0
  78. package/docs/exec-plans/active/README.md +4 -0
  79. package/docs/exec-plans/completed/README.md +6 -2
  80. package/package.json +2 -1
  81. package/skills/ai-engineering-context/SKILL.md +21 -21
  82. package/skills/loop-agent/SKILL.md +73 -188
  83. package/skills/loop-agent/references/README.md +6 -2
  84. package/skills/loop-agent/references/harness-policy.md +113 -113
  85. package/skills/loop-agent/references/learned/README.md +13 -13
  86. package/skills/loop-agent/references/long-running-loop.md +59 -0
  87. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
  88. package/skills/loop-agent/references/verification-and-failure-handling.md +18 -0
  89. package/skills/requesting-code-review/SKILL.md +40 -40
  90. package/skills/requesting-code-review/code-reviewer.md +4 -4
  91. package/skills/systematic-debugging/CREATION-LOG.md +43 -43
  92. package/skills/systematic-debugging/SKILL.md +113 -113
  93. package/skills/systematic-debugging/condition-based-waiting.md +20 -20
  94. package/skills/systematic-debugging/defense-in-depth.md +27 -27
  95. package/skills/systematic-debugging/root-cause-tracing.md +38 -38
  96. package/skills/systematic-debugging/test-academic.md +6 -6
  97. package/skills/systematic-debugging/test-pressure-1.md +6 -6
  98. package/skills/systematic-debugging/test-pressure-2.md +2 -2
  99. package/skills/systematic-debugging/test-pressure-3.md +6 -6
  100. package/skills/verification-before-completion/SKILL.md +37 -37
@@ -1,10 +1,11 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { writeLoopCloseout } from "../../infrastructure/harness/loop-store.js";
2
4
  import { getLoopPaths } from "./paths.js";
3
5
  import { readLoopRounds } from "./rounds.js";
4
6
  import { readLoopSignals, pendingLoopSignals } from "./signals.js";
5
7
  import { loadLoopState } from "./state.js";
6
8
  import { loadTaskConfig } from "../../task/runtime.js";
7
- import { repoRelativePath } from "../../shared/path-refs.js";
8
9
  function unique(values) {
9
10
  return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
10
11
  }
@@ -68,7 +69,7 @@ export async function generateLoopCloseout(repoRoot, taskId) {
68
69
  : [`Loop status is ${state.status}; treat this closeout as partial.`]),
69
70
  ]);
70
71
  const recordedAt = new Date().toISOString();
71
- const relativeCloseoutPath = repoRelativePath(repoRoot, paths.closeoutPath);
72
+ const relativeCloseoutPath = path.relative(repoRoot, paths.closeoutPath);
72
73
  const record = {
73
74
  schemaVersion: 1,
74
75
  taskId,
@@ -129,6 +130,6 @@ export async function generateLoopCloseout(repoRoot, taskId) {
129
130
  "- It does not modify completed DAG or one-shot run facts.",
130
131
  "",
131
132
  ].join("\n");
132
- await writeFile(paths.closeoutPath, markdown, "utf-8");
133
+ await writeLoopCloseout(repoRoot, taskId, markdown);
133
134
  return record;
134
135
  }
@@ -1,5 +1,4 @@
1
- import { writeFile } from "node:fs/promises";
2
- import { getLoopPaths } from "./paths.js";
1
+ import { writeLoopContext } from "../../infrastructure/harness/loop-store.js";
3
2
  import { readLoopRounds } from "./rounds.js";
4
3
  import { refreshLoopState } from "./state.js";
5
4
  import { appendLoopEvent } from "./events.js";
@@ -36,7 +35,7 @@ export async function rewriteLoopContext(repoRoot, taskId) {
36
35
  `- Last decision: ${latest.decision}.`,
37
36
  "",
38
37
  ].join("\n");
39
- await writeFile(getLoopPaths(repoRoot, taskId).contextPath, content, "utf-8");
38
+ await writeLoopContext(repoRoot, taskId, content);
40
39
  await refreshLoopState(repoRoot, taskId);
41
40
  await appendLoopEvent(repoRoot, taskId, {
42
41
  type: "context_rewrite",
@@ -1,4 +1,5 @@
1
- import { appendFile, readFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
+ import { appendLoopEventLine } from "../../infrastructure/harness/loop-store.js";
2
3
  import { getLoopPaths } from "./paths.js";
3
4
  export async function appendLoopEvent(repoRoot, taskId, input) {
4
5
  const now = new Date().toISOString();
@@ -12,7 +13,7 @@ export async function appendLoopEvent(repoRoot, taskId, input) {
12
13
  refs: input.refs ?? [],
13
14
  recordedAt: now,
14
15
  };
15
- await appendFile(getLoopPaths(repoRoot, taskId).eventsPath, `${JSON.stringify(entry)}\n`, "utf-8");
16
+ await appendLoopEventLine(repoRoot, taskId, entry);
16
17
  return entry;
17
18
  }
18
19
  export async function readLoopEvents(repoRoot, taskId) {
@@ -0,0 +1,104 @@
1
+ import { pendingLoopSignals } from "../signals.js";
2
+ export function decideNextLoopAutoAction(state, signals = [], options = {}) {
3
+ const pendingSignals = pendingLoopSignals(signals);
4
+ const urgent = pendingSignals.find((signal) => signal.urgent);
5
+ if (urgent) {
6
+ return {
7
+ action: "pause",
8
+ reason: `urgent ${urgent.type} signal: ${urgent.message}`,
9
+ decision: "pause",
10
+ };
11
+ }
12
+ const scopeChanged = pendingSignals.find((signal) => signal.type === "scope_changed");
13
+ if (scopeChanged) {
14
+ return {
15
+ action: "pause",
16
+ reason: `scope changed signal requires objective/context review: ${scopeChanged.message}`,
17
+ decision: "pause",
18
+ };
19
+ }
20
+ const reviewSignal = pendingSignals.find((signal) => signal.type === "review_feedback" || signal.type === "human_followup");
21
+ if (reviewSignal) {
22
+ return {
23
+ action: "pi-review",
24
+ reason: `${reviewSignal.type} signal requires read-only review: ${reviewSignal.message}`,
25
+ decision: "continue",
26
+ };
27
+ }
28
+ if (state.round >= state.maxRounds) {
29
+ return {
30
+ action: "blocked",
31
+ reason: `loop maxRounds reached (${state.round}/${state.maxRounds})`,
32
+ decision: "blocked",
33
+ };
34
+ }
35
+ if (state.failureStreak.category && state.failureStreak.count >= 2) {
36
+ return {
37
+ action: "blocked",
38
+ reason: `repeated failure category ${state.failureStreak.category} x${state.failureStreak.count}`,
39
+ decision: "blocked",
40
+ };
41
+ }
42
+ if (state.freshness.sourceStale) {
43
+ return {
44
+ action: "pause",
45
+ reason: "task source changed; rebuild or review action prompt before continuing",
46
+ decision: "pause",
47
+ };
48
+ }
49
+ if (state.lastAction === "shell-verify") {
50
+ return {
51
+ action: "pi-review",
52
+ reason: "shell verification already ran; request read-only review before next implementation decision",
53
+ decision: "continue",
54
+ };
55
+ }
56
+ if (state.lastAction === "pi-review") {
57
+ if (options.latestPiReview?.recommendedAction === "implement_fix") {
58
+ const policy = options.loopAutoWritePolicy ?? "off";
59
+ const approvalSignal = pendingSignals.find((signal) => signal.type === "approval");
60
+ const allowedByPolicy = policy === "enabled" ||
61
+ (policy === "approval-required" &&
62
+ (Boolean(options.allowCursorFix) || Boolean(approvalSignal)));
63
+ if (options.cursorFixPolicyError) {
64
+ return {
65
+ action: "pause",
66
+ reason: `cursor-fix policy guard failed: ${options.cursorFixPolicyError}`,
67
+ decision: "pause",
68
+ };
69
+ }
70
+ if (allowedByPolicy) {
71
+ return {
72
+ action: "cursor-fix",
73
+ reason: `pi-review recommended implement_fix; loopAutoWritePolicy=${policy}`,
74
+ decision: "continue",
75
+ };
76
+ }
77
+ }
78
+ return {
79
+ action: "dag",
80
+ reason: "read-only review produced next action; generate a governed DAG review packet",
81
+ decision: "continue",
82
+ };
83
+ }
84
+ const approvalSignal = pendingSignals.find((signal) => signal.type === "approval");
85
+ if (approvalSignal) {
86
+ return {
87
+ action: "dag",
88
+ reason: `approval signal received; generate next governed DAG packet: ${approvalSignal.message}`,
89
+ decision: "continue",
90
+ };
91
+ }
92
+ if (state.lastAction === "cursor-fix" || state.lastAction === "dag") {
93
+ return {
94
+ action: "shell-verify",
95
+ reason: `${state.lastAction} round must be followed by deterministic shell verification`,
96
+ decision: "continue",
97
+ };
98
+ }
99
+ return {
100
+ action: "dag",
101
+ reason: "default to DAG governed review packet for the next bounded round",
102
+ decision: "continue",
103
+ };
104
+ }
@@ -0,0 +1,31 @@
1
+ import { pathMatchesPattern } from "../../../shared/git-progress.js";
2
+ export function normalizePattern(pattern) {
3
+ return pattern.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/\*\*$/, "");
4
+ }
5
+ export function patternsOverlap(allowed, forbidden) {
6
+ const a = normalizePattern(allowed);
7
+ const f = normalizePattern(forbidden);
8
+ return (a === f ||
9
+ a.startsWith(`${f}/`) ||
10
+ f.startsWith(`${a}/`) ||
11
+ pathMatchesPattern(a, forbidden) ||
12
+ pathMatchesPattern(f, allowed));
13
+ }
14
+ export function validateLoopCursorFixPolicy(input) {
15
+ if (input.allowedPaths.length === 0) {
16
+ throw new Error("loop cursor-fix requires non-empty task allowedPaths");
17
+ }
18
+ for (const allowed of input.allowedPaths) {
19
+ for (const forbidden of input.forbiddenPaths) {
20
+ if (patternsOverlap(allowed, forbidden)) {
21
+ throw new Error(`loop cursor-fix allowed/forbidden paths overlap: ${allowed} vs ${forbidden}`);
22
+ }
23
+ }
24
+ }
25
+ if (input.complexity !== undefined &&
26
+ input.complexity !== "small" &&
27
+ !input.hasDagRound &&
28
+ !input.dagFallbackReason?.trim()) {
29
+ throw new Error("loop cursor-fix for medium/large tasks requires a prior loop dag round or task dagFallbackReason");
30
+ }
31
+ }
@@ -1,4 +1,5 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
+ import { writeLoopRounds } from "../../infrastructure/harness/loop-store.js";
2
3
  import { getLoopPaths } from "./paths.js";
3
4
  import { refreshLoopState } from "./state.js";
4
5
  import { appendLoopEvent } from "./events.js";
@@ -33,14 +34,13 @@ export async function appendLoopRound(repoRoot, taskId, input) {
33
34
  failureCategory: input.failureCategory ?? "",
34
35
  completedCriteria: input.completedCriteria ?? [],
35
36
  };
36
- const paths = getLoopPaths(repoRoot, taskId);
37
37
  await appendLoopEvent(repoRoot, taskId, {
38
38
  type: "round_start",
39
39
  round: entry.round,
40
40
  message: `recording round ${entry.round} action ${entry.action}`,
41
41
  refs: entry.refs,
42
42
  });
43
- await writeFile(paths.roundsPath, `${rounds.map((round) => JSON.stringify(round)).join("\n")}${rounds.length > 0 ? "\n" : ""}${JSON.stringify(entry)}\n`, "utf-8");
43
+ await writeLoopRounds(repoRoot, taskId, [...rounds, entry]);
44
44
  const previousState = await refreshLoopState(repoRoot, taskId, {
45
45
  status: statusForDecision(entry.decision),
46
46
  round: entry.round,
@@ -1,4 +1,5 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
+ import { writeLoopSignals as persistLoopSignals } from "../../infrastructure/harness/loop-store.js";
2
3
  import { getLoopPaths } from "./paths.js";
3
4
  import { LOOP_SIGNAL_TYPES, } from "./types.js";
4
5
  export function parseLoopSignalType(value) {
@@ -31,7 +32,7 @@ export async function appendLoopSignal(repoRoot, taskId, input) {
31
32
  if (!entry.message) {
32
33
  throw new Error("loop signal message cannot be empty");
33
34
  }
34
- await writeLoopSignals(repoRoot, taskId, [...signals, entry]);
35
+ await persistLoopSignals(repoRoot, taskId, [...signals, entry]);
35
36
  return entry;
36
37
  }
37
38
  export function pendingLoopSignals(signals) {
@@ -46,10 +47,6 @@ export async function drainLoopSignals(repoRoot, taskId, ids) {
46
47
  const drained = signals.map((signal) => idSet.has(signal.id) && !signal.drainedAt
47
48
  ? { ...signal, drainedAt: now }
48
49
  : signal);
49
- await writeLoopSignals(repoRoot, taskId, drained);
50
+ await persistLoopSignals(repoRoot, taskId, drained);
50
51
  return drained;
51
52
  }
52
- async function writeLoopSignals(repoRoot, taskId, signals) {
53
- await writeFile(getLoopPaths(repoRoot, taskId).signalsPath, signals.map((signal) => JSON.stringify(signal)).join("\n") +
54
- (signals.length > 0 ? "\n" : ""), "utf-8");
55
- }
@@ -1,6 +1,7 @@
1
- import { access, mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { ensureLoopDir, writeLoopContext, writeLoopObjective, writeLoopRounds, writeLoopSignals, writeLoopEvents, writeLoopState, } from "../../infrastructure/harness/loop-store.js";
2
4
  import { getTaskDir } from "../../task/runtime.js";
3
- import { repoRelativePath } from "../../shared/path-refs.js";
4
5
  import { hashFileIfPresent, hashTaskSource } from "./hash.js";
5
6
  import { getLoopPaths } from "./paths.js";
6
7
  import { defaultContext, defaultObjective } from "./templates.js";
@@ -21,7 +22,7 @@ async function exists(filePath) {
21
22
  }
22
23
  }
23
24
  function rel(repoRoot, filePath) {
24
- return repoRelativePath(repoRoot, filePath);
25
+ return path.relative(repoRoot, filePath);
25
26
  }
26
27
  export async function buildLoopState(repoRoot, taskId, paths = getLoopPaths(repoRoot, taskId), previous) {
27
28
  const now = new Date().toISOString();
@@ -59,8 +60,7 @@ export async function buildLoopState(repoRoot, taskId, paths = getLoopPaths(repo
59
60
  };
60
61
  }
61
62
  export async function saveLoopState(repoRoot, taskId, state) {
62
- const paths = getLoopPaths(repoRoot, taskId);
63
- await writeFile(paths.statePath, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
63
+ await writeLoopState(repoRoot, taskId, state);
64
64
  }
65
65
  export async function loadLoopState(repoRoot, taskId) {
66
66
  const paths = getLoopPaths(repoRoot, taskId);
@@ -86,21 +86,21 @@ export async function initializeLoop(repoRoot, taskId, options = {}) {
86
86
  if (stateExists && !options.recover) {
87
87
  throw new Error(`loop already initialized for task: ${taskId}`);
88
88
  }
89
- await mkdir(paths.loopDir, { recursive: true });
89
+ await ensureLoopDir(repoRoot, taskId);
90
90
  if (!(await exists(paths.objectivePath))) {
91
- await writeFile(paths.objectivePath, defaultObjective(taskId), "utf-8");
91
+ await writeLoopObjective(repoRoot, taskId, defaultObjective(taskId));
92
92
  }
93
93
  if (!(await exists(paths.contextPath))) {
94
- await writeFile(paths.contextPath, defaultContext(), "utf-8");
94
+ await writeLoopContext(repoRoot, taskId, defaultContext());
95
95
  }
96
96
  if (!(await exists(paths.roundsPath))) {
97
- await writeFile(paths.roundsPath, "", "utf-8");
97
+ await writeLoopRounds(repoRoot, taskId, []);
98
98
  }
99
99
  if (!(await exists(paths.signalsPath))) {
100
- await writeFile(paths.signalsPath, "", "utf-8");
100
+ await writeLoopSignals(repoRoot, taskId, []);
101
101
  }
102
102
  if (!(await exists(paths.eventsPath))) {
103
- await writeFile(paths.eventsPath, "", "utf-8");
103
+ await writeLoopEvents(repoRoot, taskId, []);
104
104
  }
105
105
  const previous = stateExists ? await loadLoopState(repoRoot, taskId) : undefined;
106
106
  const state = await buildLoopState(repoRoot, taskId, paths, {
package/docs/README.md CHANGED
@@ -6,8 +6,9 @@ Top-level `AGENTS.md` is the operating map. Durable knowledge belongs here: deci
6
6
 
7
7
  ## Core Documents
8
8
 
9
- - `development-principles.md` — repository development principles
10
- - `feature-workflow.md` — bounded feature workflow
9
+ - `development-principles.md` — repository development principles
10
+ - `architecture/runtime-boundaries.md` — runtime layer boundaries and dependency direction
11
+ - `feature-workflow.md` — bounded feature workflow
11
12
  - `verification-matrix.md` — verification command selection
12
13
  - `loop-agent-harness.md` — runtime and command surface overview
13
14
  - `agent-dag-runner.md` — Agent DAG runner guide
@@ -0,0 +1,147 @@
1
+ # Runtime Boundaries
2
+
3
+ 本文定义 loop-agent 各 runtime 层的 module interface、允许的依赖方向,以及治理检查 hook。目标是把「命令、文档、import 架构」从多处维护收敛为可机器校验的边界契约,而不在本阶段改变 runtime 行为。
4
+
5
+ ## 分层概览
6
+
7
+ ```text
8
+ Skill layer
9
+ └─ 入口策略、reference 路由、硬规则(skills/loop-agent/SKILL.md + references/)
10
+
11
+ CLI layer (src/cli/)
12
+ └─ argv 解析、adapter 解析、调用 application / command handler、格式化输出
13
+
14
+ Application layer (src/application/,逐步引入)
15
+ └─ 一次用户意图的 typed use-case interface,供 CLI、Loop、Workflow 复用
16
+
17
+ Workflow runtime (src/workflows/)
18
+ └─ DAG / Dynamic / Loop 核心执行规则;不应依赖 commands
19
+
20
+ Executors (src/executors/)
21
+ └─ Cursor / Pi / Shell 等外部工具适配;不应依赖 commands 或 CLI formatting
22
+
23
+ Infrastructure / Store (src/infrastructure/,逐步引入)
24
+ └─ .harness 文件系统副作用、run lifecycle、原子写入规则
25
+
26
+ Governance (scripts/check-*.sh, src/governance/)
27
+ └─ 防止 command registry、skill entry、import architecture 漂移
28
+ ```
29
+
30
+ ## 各层职责
31
+
32
+ ### Skill layer
33
+
34
+ - **位置**:`skills/loop-agent/SKILL.md` 及 `skills/loop-agent/references/**`
35
+ - **职责**:定义 agent 何时启用 loop-agent、默认执行路径(Agent DAG)、硬规则与 reference 路由;不承载完整操作手册。
36
+ - **禁止**:在入口 skill 中重复维护 CLI command 列表或与 `src/cli/catalog.ts` 冲突的事实源。
37
+
38
+ ### CLI layer
39
+
40
+ - **位置**:`src/cli.ts`、`src/cli/**`
41
+ - **职责**:解析全局 flag(如 `--repo-root`)、解析 top-level / subcommand、调用 command handler、输出 help / JSON / Markdown。
42
+ - **允许依赖**:`src/commands/**`(handler wiring)、`src/adapters/**`、`src/shared/**`、逐步引入的 `src/application/**`。
43
+ - **禁止**:在 router 中维护与 catalog 独立的 command 事实源(Phase 1 整改目标)。
44
+
45
+ ### Application layer
46
+
47
+ - **位置**:`src/application/**`(按计划逐步引入)
48
+ - **职责**:封装一次用户意图的 typed result(如 validate DAG、run DAG、report),供 CLI 与 workflow runtime 共用。
49
+ - **允许依赖**:`src/workflows/**`、`src/infrastructure/**`、`src/task/**`、`src/records/**`。
50
+ - **禁止**:直接承担 CLI formatting 或 argv 解析。
51
+
52
+ ### Workflow runtime
53
+
54
+ - **位置**:`src/workflows/dag/**`、`src/workflows/dynamic/**`、`src/workflows/loop/**`
55
+ - **职责**:DAG spec 校验与执行、dynamic workflow 编译与 expansion、Loop 状态机与 action 编排。
56
+ - **允许依赖**:`src/executors/**`、`src/task/**`、`src/records/**`、`src/shared/**`、application use-case(目标态)。
57
+ - **禁止**:`import` 来自 `src/commands/**`(见下方过渡例外)。
58
+
59
+ ### Executors
60
+
61
+ - **位置**:`src/executors/**`
62
+ - **职责**:封装 Cursor SDK/CLI、Pi SDK/CLI、shell 执行与 write guard。
63
+ - **允许依赖**:`src/shared/**`、外部 SDK。
64
+ - **禁止**:依赖 `src/commands/**` 或 CLI 输出格式。
65
+
66
+ ### Infrastructure / Store
67
+
68
+ - **位置**:`src/infrastructure/harness/**`(按计划逐步引入);过渡期部分逻辑仍在 `src/workflows/dag/lifecycle.ts`、`src/records/**`。
69
+ - **职责**:`.harness/tasks`、`.harness/dag-runs`、`.harness/runs`、loop state 的集中读写;completed run facts 只读约束。
70
+ - **禁止**:把 raw path mutation 扩散给 runner、loop action 或 command handler。
71
+
72
+ ### Governance
73
+
74
+ - **位置**:`scripts/check-repo.sh` 及子脚本、`src/governance/**`、相关 Vitest。
75
+ - **职责**:在 CI / in-flight DAG 中检测文档链接、exec plan 状态、架构 import、command registry 漂移、skill entry 完整性。
76
+
77
+ ## 允许的依赖方向
78
+
79
+ ```text
80
+ Skill (docs) ──advisory──> CLI catalog / command-reference(人类可读,机器 drift check)
81
+
82
+ CLI ──────────> Application (目标态)
83
+ CLI ──────────> Commands (handler 薄层)
84
+ Commands ──────────> Application / Workflows / Infrastructure
85
+
86
+ Workflow runtime ──────────> Executors / Task / Records / Application (目标态)
87
+ Workflow runtime ──X──> Commands (目标态禁止;见过渡例外)
88
+
89
+ Executors ──────────> Shared / 外部 SDK
90
+ Executors ──X──> Commands / CLI
91
+
92
+ Application ──────────> Workflows / Infrastructure / Task / Records
93
+ Application ──X──> CLI formatting
94
+
95
+ Infrastructure ──────────> Shared / node:fs
96
+ Runner / Loop ──(迁移中)──> 逐步改为仅经 Store / Application
97
+ ```
98
+
99
+ **规则摘要**
100
+
101
+ | From | May import | Must not import |
102
+ |------|------------|-----------------|
103
+ | `src/commands/**` | application, workflows, infrastructure, task, records, executors, shared | — |
104
+ | `src/workflows/**` | executors, task, records, shared, application(目标) | `src/commands/**` |
105
+ | `src/executors/**` | shared, 外部 SDK | `src/commands/**`, `src/cli/**` |
106
+ | `src/cli/**` | commands, adapters, shared, application(目标) | — |
107
+ | `skills/**` | (Markdown 引用,非 TS import) | 运行时 TS 模块 |
108
+
109
+ ## 已知过渡耦合
110
+
111
+ 当前没有允许的 `src/workflows/**` 或 `src/executors/**` → `src/commands/**` TypeScript import。
112
+
113
+ `scripts/check-architecture-boundaries.sh` 的 transitional allowlist 保持为空。任何新增的 `workflows/executors -> commands` import 必须导致检查 **exit 1**;如果未来确有临时例外,必须先写入 active exec plan,说明移除时间和验证门禁。
114
+
115
+ ## Governance hooks
116
+
117
+ 以下脚本由 `scripts/check-repo.sh` 调用(Phase 0 起):
118
+
119
+ | Script | 检查内容 | 失败条件 |
120
+ |--------|----------|----------|
121
+ | `scripts/check-architecture-boundaries.sh` | `workflows` / `executors` → `commands` import | 新的未 allowlist violation |
122
+ | `scripts/check-command-registry-drift.sh` | `command-reference.md` 中的 top-level command vs `src/cli/catalog.ts` | 文档引用未注册 command |
123
+ | `scripts/check-skill-entry.sh` | `SKILL.md` reference 文件存在、行数阈值 | reference 缺失(fail);行数 > 220(warn) |
124
+
125
+ 相关 exec plan:`docs/exec-plans/active/2026-07-04-runtime-boundary-remediation.md`。
126
+
127
+ ### 验证命令
128
+
129
+ ```bash
130
+ bash scripts/check-repo.sh
131
+ bash scripts/check-architecture-boundaries.sh
132
+ bash scripts/check-command-registry-drift.sh
133
+ bash scripts/check-skill-entry.sh
134
+ ```
135
+
136
+ Runtime 变更另需 `npm run typecheck` 及对应 targeted Vitest(见 exec plan 各 Phase 验证关口)。
137
+
138
+ ## 演进里程碑
139
+
140
+ | Phase | 边界变化 |
141
+ |-------|----------|
142
+ | 0(当前) | 文档 + 机器 guard;已知 loop/actions.ts 耦合 advisory |
143
+ | 1 | CLI command definition 单源 |
144
+ | 2 | Skill entry 瘦身 + frontmatter references |
145
+ | 3 | DAG application use-case;Loop 开始脱离 command JSON |
146
+ | 6 | `workflows → commands` import check 已从 advisory 改为 **fail** |
147
+ | 7 | Harness Store / Repository 集中 `.harness` 副作用 |
@@ -3,3 +3,7 @@
3
3
  Use this directory for execution plans that are currently in progress.
4
4
 
5
5
  Source repositories may keep concrete active plan files next to this README. The npm package carries this README as a directory contract and does not copy loop-agent source-history active plans; target repositories generate their own active plans.
6
+
7
+ Current active plans:
8
+
9
+ - [2026-07-04-dag-role-skill-alignment.md](2026-07-04-dag-role-skill-alignment.md)
@@ -1,5 +1,9 @@
1
- # Completed Execution Plans
2
-
1
+ # Completed Execution Plans
2
+
3
3
  Completed plans are moved here after their implementation and verification finish.
4
4
 
5
5
  The npm package carries this README as a directory contract. Concrete completed plans are target-repository history and are not copied from loop-agent's source history.
6
+
7
+ - [`2026-07-02-loop-agent-subject-restructure.md`](2026-07-02-loop-agent-subject-restructure.md) — promoted the former `tools/code-agent` runtime to the repository root, renamed it to `loop-agent`, and removed the old memory plugin product lines.
8
+ - [`2026-07-04-remove-level1-fallback.md`](2026-07-04-remove-level1-fallback.md) — removed the historical sequential Level 1 fallback and converged the runtime, docs, and command surface on DAG execution.
9
+ - [`2026-07-04-runtime-boundary-remediation.md`](2026-07-04-runtime-boundary-remediation.md) — consolidated CLI/skill/runtime boundaries, extracted DAG/Loop runtime seams, and centralized harness store/guard policy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js"
@@ -10,6 +10,7 @@
10
10
  "bin/",
11
11
  "dist/",
12
12
  "docs/*.md",
13
+ "docs/architecture/runtime-boundaries.md",
13
14
  "docs/decisions/README.md",
14
15
  "docs/design/README.md",
15
16
  "docs/exec-plans/README.md",
@@ -1,48 +1,48 @@
1
1
  ---
2
2
  name: ai-engineering-context
3
- description: Use when an AI coding agent starts loop-agent work, prepares DAG or Loop context, delegates role-specific nodes, or decides where requirements, facts, evidence, and handoff notes belong.
3
+ description: AI coding agent 启动 loop-agent 工作、准备 DAG Loop context、委派 role-specific 节点,或决定 requirementsfactsevidencehandoff notes 应落何处时使用。
4
4
  ---
5
5
 
6
6
  # AI Engineering Context
7
7
 
8
- Context is an engineering artifact, not chat residue. Keep requirements, role boundaries, write authority, evidence, and handoff facts explicit.
8
+ Context 是工程 artifact,不是 chat 残留。须显式保留 requirementsrole 边界、write authorityevidence handoff facts
9
9
 
10
10
  ## When to Use
11
11
 
12
- Use when starting a task, DAG, workflow, or Loop round; preparing role prompts; delegating to Cursor/Pi/shell/static executors; or handling stale plans, conflicting requirements, or missing evidence.
12
+ 在启动 taskDAGworkflow Loop round;准备 role prompt;委派给 Cursor/Pi/shell/static executor;或处理 stale plan、冲突 requirements、缺失 evidence 时使用。
13
13
 
14
- Do not use for private platform paths, personal memory, Google Drive rules, or replacing task skills.
14
+ 不要用于 private platform pathspersonal memoryGoogle Drive 规则,或替代 task skills
15
15
 
16
16
  ## Context Priority
17
17
 
18
- Prefer facts in this order: latest user instruction; task source/contract; DAG/Loop artifacts; docs/plans/ADRs; code/tests; chat history as hints only.
18
+ 按以下顺序优先采信 facts:最新 user instructiontask source/contractDAG/Loop artifactsdocs/plans/ADRscode/testschat history 仅作 hint。
19
19
 
20
- If sources conflict, stop and name the conflict.
20
+ sources 冲突,停止并点明冲突。
21
21
 
22
22
  ## Role Boundaries
23
23
 
24
- - Planner: contract, DAG shape, write boundary, verification plan; no implementation edits.
25
- - Scout: code/test/doc/artifact facts; no repo writes.
26
- - Implementer: bounded changes inside writeSet; no scope widening or completion claims.
27
- - Reviewer: bugs, regressions, missing tests, risk; no rewrites unless assigned.
28
- - Verifier: command evidence, reproduction, failure category; model judgment is not proof.
29
- - Supervisor: gates, escalation, repair scope; no write-guard or human-gate bypass.
30
- - Closeout: evidence, risks, next steps; no hidden failures.
24
+ - PlannercontractDAG shapewrite boundaryverification plan;不做 implementation edits
25
+ - Scoutcode/test/doc/artifact facts;不写 repo
26
+ - Implementer:在 writeSet 内做 bounded changes;不扩大 scope 或宣称完成。
27
+ - Reviewerbugsregressionsmissing testsrisk;除非被指派,否则不重写。
28
+ - Verifiercommand evidencereproductionfailure categorymodel judgment 不是 proof
29
+ - Supervisorgatesescalationrepair scope;不绕过 write-guard human-gate
30
+ - Closeoutevidencerisksnext steps;不隐藏 failures
31
31
 
32
32
  ## Prompt Contract
33
33
 
34
- Every delegated node prompt should include objective, task id, role, executor, allowed paths, forbidden paths, writeSet, upstream artifact refs, concise excerpts, output contract, expected evidence, non-goals, and stop conditions.
34
+ 每个 delegated node prompt 应包含:objectivetask idroleexecutorallowed pathsforbidden pathswriteSetupstream artifact refsconcise excerptsoutput contractexpected evidencenon-goalsstop conditions
35
35
 
36
- Read-only nodes must return findings in node output only. They must not create scratch files in the repo.
36
+ read-only node 只能在 node output 返回 findings。不得在 repo 中创建 scratch files
37
37
 
38
38
  ## Persistence Rules
39
39
 
40
- Requirements and constraints go in task `source/`. Execution state and node artifacts go in `.harness/`. Durable plans, reports, decisions go in `docs/`. Reusable process guidance goes in `skills/`. Chat is transient only.
40
+ Requirements constraints 写入 task `source/`。Execution state node artifacts 写入 `.harness/`。Durable plansreportsdecisions 写入 `docs/`。可复用 process guidance 写入 `skills/`。Chat transient
41
41
 
42
42
  ## Failure Handling
43
43
 
44
- - Missing context: run scout or read the durable source.
45
- - Ambiguous requirement: update the contract before implementation.
46
- - Verification failure: diagnose cause before changing code.
47
- - Missing skill instructions: treat as context defect; do not assume hidden behavior.
48
- - Missing fresh verification: do not close out.
44
+ - Missing context:运行 scout 或读取 durable source
45
+ - Ambiguous requirement:implementation 前更新 contract
46
+ - Verification failure:改 code 前先 diagnose cause
47
+ - Missing skill instructions:视为 context defect;不要假设 hidden behavior
48
+ - Missing fresh verification:不要 close out