@tea-agent/loop-agent 0.1.0 → 0.2.1
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 +62 -45
- package/CHANGELOG.md +60 -28
- package/README.md +160 -124
- package/bin/loop-agent.js +21 -21
- package/dist/adapters/index.js +3 -2
- package/dist/adapters/loop-agent.js +44 -2
- package/dist/application/dag/args.js +420 -0
- package/dist/application/dag/generate-task-dag.js +280 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +106 -0
- package/dist/application/dag/validate-dag.js +102 -0
- package/dist/application/loop/run-action.js +23 -0
- package/dist/cli/catalog.js +2 -237
- package/dist/cli/command-definitions.js +571 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/program.js +65 -1
- package/dist/cli/router.js +13 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/commands/dag-report.js +6 -107
- package/dist/commands/dag-run-task.js +8 -466
- package/dist/commands/dag-validate.js +7 -179
- package/dist/commands/examples.js +90 -0
- package/dist/commands/init.js +1518 -0
- package/dist/commands/loop.js +57 -31
- package/dist/commands/pi-prompt.js +2 -9
- package/dist/commands/run-dag.js +7 -180
- package/dist/executors/cursor-executor-artifacts.js +3 -4
- package/dist/executors/cursor-worker-client.js +13 -3
- package/dist/executors/dag-cursor-executor.js +2 -3
- package/dist/executors/dag-pi-executor.js +3 -4
- package/dist/executors/dag-static-executor.js +2 -5
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/shell-executor.js +12 -20
- package/dist/governance/manifest-types.js +1 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +49 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +23 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +77 -0
- package/dist/records/one-shot-runs.js +26 -61
- package/dist/records/promotion.js +3 -4
- package/dist/shared/artifacts-core.js +5 -5
- package/dist/shared/logger.js +9 -15
- package/dist/task/delegate.js +4 -4
- package/dist/task/runtime.js +5 -7
- package/dist/task/state.js +6 -20
- package/dist/workflows/dag/convergence/controller.js +277 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
- package/dist/workflows/dag/failure-routing.js +82 -0
- package/dist/workflows/dag/lifecycle.js +101 -8
- package/dist/workflows/dag/node-execution.js +262 -0
- package/dist/workflows/dag/report.js +73 -1
- package/dist/workflows/dag/run-store.js +36 -0
- package/dist/workflows/dag/runner.js +82 -1341
- package/dist/workflows/dag/scheduler.js +84 -0
- package/dist/workflows/dag/upstream-artifacts.js +20 -18
- package/dist/workflows/loop/actions/cursor-fix.js +191 -0
- package/dist/workflows/loop/actions/dag-action.js +130 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +55 -1212
- package/dist/workflows/loop/closeout.js +5 -4
- package/dist/workflows/loop/context.js +2 -3
- package/dist/workflows/loop/events.js +3 -2
- package/dist/workflows/loop/policy/auto-policy.js +104 -0
- package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
- package/dist/workflows/loop/rounds.js +3 -3
- package/dist/workflows/loop/signals.js +4 -7
- package/dist/workflows/loop/state.js +11 -11
- package/docs/README.md +47 -44
- package/docs/agent-dag-recovery-playbook.md +32 -6
- package/docs/agent-dag-runner.md +17 -17
- package/docs/architecture/runtime-boundaries.md +147 -0
- package/docs/cursor-executor-usage.md +5 -5
- package/docs/decisions/README.md +2 -2
- package/docs/design/README.md +24 -24
- package/docs/development-principles.md +50 -50
- package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
- package/docs/exec-plans/README.md +4 -4
- package/docs/exec-plans/active/README.md +10 -5
- package/docs/exec-plans/completed/README.md +9 -5
- package/docs/feature-workflow.md +111 -109
- package/docs/harness-methodology-verification.md +18 -18
- package/docs/loop-agent-harness.md +36 -36
- package/docs/production-readiness.md +96 -0
- package/docs/progress/README.md +2 -2
- package/docs/reports/README.md +4 -2
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
- package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
- package/docs/templates/agent-dag-report.schema.json +33 -2
- package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
- package/docs/templates/agent-dag.base.json +195 -195
- package/docs/templates/agent-dag.final-verification.json +190 -190
- package/docs/templates/agent-dag.schema.json +17 -17
- package/docs/templates/agent-dag.supervised-implementation.json +500 -500
- package/docs/templates/hybrid-dag.json +193 -193
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +7 -7
- package/docs/templates/project-start-checklist.md +8 -8
- package/docs/templates/qa-report.md +17 -11
- package/docs/templates/sprint-contract.md +19 -19
- package/docs/verification-matrix.md +37 -26
- package/examples/example-dag.json +51 -51
- package/examples/hybrid-loop-agent-dag.json +194 -194
- package/harness.json +5 -5
- package/package.json +62 -61
- package/skills/ai-engineering-context/SKILL.md +21 -21
- package/skills/loop-agent/SKILL.md +56 -171
- package/skills/loop-agent/references/README.md +6 -2
- package/skills/loop-agent/references/command-reference.md +107 -65
- package/skills/loop-agent/references/harness-policy.md +115 -115
- package/skills/loop-agent/references/hybrid-dag.md +30 -30
- package/skills/loop-agent/references/learned/README.md +13 -13
- package/skills/loop-agent/references/long-running-loop.md +59 -0
- package/skills/loop-agent/references/model-routing.md +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
- package/skills/loop-agent/references/pi-prompt.md +9 -9
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
- package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
- package/skills/loop-agent/references/task-workflow.md +19 -19
- package/skills/loop-agent/references/verification-and-failure-handling.md +54 -0
- package/skills/requesting-code-review/SKILL.md +40 -40
- package/skills/requesting-code-review/code-reviewer.md +4 -4
- package/skills/systematic-debugging/CREATION-LOG.md +43 -43
- package/skills/systematic-debugging/SKILL.md +113 -113
- package/skills/systematic-debugging/condition-based-waiting.md +20 -20
- package/skills/systematic-debugging/defense-in-depth.md +27 -27
- package/skills/systematic-debugging/root-cause-tracing.md +38 -38
- package/skills/systematic-debugging/test-academic.md +6 -6
- package/skills/systematic-debugging/test-pressure-1.md +6 -6
- package/skills/systematic-debugging/test-pressure-2.md +2 -2
- package/skills/systematic-debugging/test-pressure-3.md +6 -6
- package/skills/verification-before-completion/SKILL.md +37 -37
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { readFile
|
|
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 =
|
|
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
|
|
133
|
+
await writeLoopCloseout(repoRoot, taskId, markdown);
|
|
133
134
|
return record;
|
|
134
135
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
89
|
+
await ensureLoopDir(repoRoot, taskId);
|
|
90
90
|
if (!(await exists(paths.objectivePath))) {
|
|
91
|
-
await
|
|
91
|
+
await writeLoopObjective(repoRoot, taskId, defaultObjective(taskId));
|
|
92
92
|
}
|
|
93
93
|
if (!(await exists(paths.contextPath))) {
|
|
94
|
-
await
|
|
94
|
+
await writeLoopContext(repoRoot, taskId, defaultContext());
|
|
95
95
|
}
|
|
96
96
|
if (!(await exists(paths.roundsPath))) {
|
|
97
|
-
await
|
|
97
|
+
await writeLoopRounds(repoRoot, taskId, []);
|
|
98
98
|
}
|
|
99
99
|
if (!(await exists(paths.signalsPath))) {
|
|
100
|
-
await
|
|
100
|
+
await writeLoopSignals(repoRoot, taskId, []);
|
|
101
101
|
}
|
|
102
102
|
if (!(await exists(paths.eventsPath))) {
|
|
103
|
-
await
|
|
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
|
@@ -1,61 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 文档索引
|
|
2
2
|
|
|
3
|
-
`docs/`
|
|
3
|
+
`docs/` 是 loop-agent 的治理根目录,包含工作流规则、方法论、验证规则、执行计划、报告、进度日志、决策记录和可复用模板。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
顶层 `AGENTS.md` 是操作地图。长期知识应落在此处:决策、契约、计划、验证证据、调试笔记和可复用流程规则应记录在 `docs/` 下,而不是只留在聊天里。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 核心文档
|
|
8
8
|
|
|
9
|
-
- `development-principles.md` —
|
|
10
|
-
- `
|
|
11
|
-
- `
|
|
12
|
-
- `
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
9
|
+
- `development-principles.md` — 仓库开发原则
|
|
10
|
+
- `architecture/runtime-boundaries.md` — runtime 层边界与依赖方向
|
|
11
|
+
- `feature-workflow.md` — 有边界的功能工作流
|
|
12
|
+
- `verification-matrix.md` — 验证命令选择
|
|
13
|
+
- `production-readiness.md` — Production Readiness v0.1 范围、证据与 DAG hardening 标准
|
|
14
|
+
- `loop-agent-harness.md` — runtime 与 command surface 概览
|
|
15
|
+
- `agent-dag-runner.md` — Agent DAG runner 指南
|
|
16
|
+
- `cursor-executor-usage.md` — Cursor executor 用法
|
|
17
|
+
- `dynamic-workflow-dag-engine-roadmap.md` — Dynamic Workflow DAG Engine 路线图与适配分析
|
|
16
18
|
|
|
17
|
-
##
|
|
19
|
+
## 方法论
|
|
18
20
|
|
|
19
|
-
- `harness-methodology-tdd.md` —
|
|
20
|
-
- `harness-methodology-verification.md` —
|
|
21
|
-
- `harness-methodology-debugging.md` —
|
|
21
|
+
- `harness-methodology-tdd.md` — 行为变更与 bug 修复的 TDD 纪律
|
|
22
|
+
- `harness-methodology-verification.md` — 完成声明前的验证纪律
|
|
23
|
+
- `harness-methodology-debugging.md` — 修复前的系统化调试工作流
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## 产物目录
|
|
24
26
|
|
|
25
|
-
- `design/README.md` —
|
|
26
|
-
- `exec-plans/active/README.md` —
|
|
27
|
-
- `exec-plans/completed/README.md` —
|
|
28
|
-
- `progress/README.md` —
|
|
29
|
-
- `reports/README.md` —
|
|
30
|
-
- `decisions/README.md` —
|
|
31
|
-
- `templates/` —
|
|
27
|
+
- `design/README.md` — 设计草稿与实现契约
|
|
28
|
+
- `exec-plans/active/README.md` — 进行中的执行计划
|
|
29
|
+
- `exec-plans/completed/README.md` — 已完成的执行计划
|
|
30
|
+
- `progress/README.md` — 进度交接日志
|
|
31
|
+
- `reports/README.md` — 验证与审计报告
|
|
32
|
+
- `decisions/README.md` — 架构决策
|
|
33
|
+
- `templates/` — 可复用的规划、报告与 DAG 模板
|
|
32
34
|
|
|
33
|
-
##
|
|
35
|
+
## 仓库 Skills
|
|
34
36
|
|
|
35
|
-
- `../skills/loop-agent/` — loop-agent
|
|
36
|
-
-
|
|
37
|
+
- `../skills/loop-agent/` — loop-agent 自身的 skill 指令与参考资料
|
|
38
|
+
- 每个额外 skill 在仓库根 `../skills/` 下使用独立子目录;这些本地副本由 DAG 模板引用,维护不依赖外部 agent skill 目录
|
|
37
39
|
|
|
38
|
-
##
|
|
40
|
+
## 模板
|
|
39
41
|
|
|
40
|
-
- `templates/project-start-checklist.md` —
|
|
41
|
-
- `templates/feature-spec.md` —
|
|
42
|
-
- `templates/sprint-contract.md` —
|
|
43
|
-
- `templates/exec-plan.md` —
|
|
44
|
-
- `templates/progress-log.md` —
|
|
45
|
-
- `templates/qa-report.md` —
|
|
46
|
-
- `templates/
|
|
42
|
+
- `templates/project-start-checklist.md` — 开工前检查清单
|
|
43
|
+
- `templates/feature-spec.md` — 有边界的功能规格
|
|
44
|
+
- `templates/sprint-contract.md` — 实现契约与验收标准
|
|
45
|
+
- `templates/exec-plan.md` — 非平凡工作的执行计划
|
|
46
|
+
- `templates/progress-log.md` — 进度与交接日志
|
|
47
|
+
- `templates/qa-report.md` — 验证与 QA 证据
|
|
48
|
+
- `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
|
|
49
|
+
- `templates/adr.md` — 架构决策记录(ADR)
|
|
47
50
|
|
|
48
|
-
##
|
|
51
|
+
## 维护
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
bash scripts/check-repo.sh
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
文档变更后运行:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bash scripts/check-repo.sh
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Windows 上通过 Git Bash 或已配置的兼容 Bash 运行脚本。实际文件操作使用平台原生路径;`/` 仅用于 repo 引用、JSON/Markdown 证据引用和 glob 约定。
|
|
60
|
+
|
|
61
|
+
完整本地门禁:
|
|
59
62
|
|
|
60
63
|
```bash
|
|
61
64
|
bash scripts/ci.sh
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Agent DAG Recovery Playbook
|
|
1
|
+
# Agent DAG Recovery Playbook(恢复手册)
|
|
2
2
|
|
|
3
3
|
> **关联**:[`agent-dag-runner.md`](agent-dag-runner.md)(CLI 与 run 语义)· [`templates/agent-dag-decision-gate.prompt.md`](templates/agent-dag-decision-gate.prompt.md)(Decision Gate 消费 recovery 证据)
|
|
4
4
|
|
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
Agent DAG **recovery planning 是只读、派生、advisory** 的。`dag report` 与 `buildDagDecisionGateEvidence()` 从 `.harness/dag-runs/` 的 canonical facts 聚合 `normalizedFailureCategory` → `recoveryRecommendation`,供人工或 Decision Gate prompt 消费。
|
|
8
8
|
|
|
9
|
+
Production Readiness v0.1 在 normalized DAG category 之上增加 product-line routing。Report 与 doctor 输出应保留 raw DAG fact 并派生,不重写已完成 facts:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
raw_failure_category
|
|
13
|
+
dag_normalized_failure_category
|
|
14
|
+
product_line_failure_category
|
|
15
|
+
recommended_follow_up
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Product-line taxonomy 定义见 `design/state-and-failure-taxonomy.md`。
|
|
19
|
+
|
|
9
20
|
**非目标(本 playbook 不覆盖、runner 不实现):**
|
|
10
21
|
|
|
11
22
|
- 自动 retry / resume 节点执行
|
|
@@ -47,7 +58,7 @@ npm run dev -- dag decision inspect --run-id <run-id> [--node-id <node-id>]
|
|
|
47
58
|
npm run dev -- dag decision validate --run-id <run-id> [--node-id <node-id>]
|
|
48
59
|
```
|
|
49
60
|
|
|
50
|
-
### Paused run
|
|
61
|
+
### Paused run operator 路径
|
|
51
62
|
|
|
52
63
|
1. `dag report --paused-latest --json` 或 `dag doctor` — 定位最新 paused run 与 `primaryRecovery`
|
|
53
64
|
2. `dag status --run-id <id>` — 读 `approvalFlow`、`escalationArtifactPath`、`pendingNodes`
|
|
@@ -79,7 +90,22 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
|
|
|
79
90
|
| `inspect-upstream` | 先查上游失败 | SKIPPED 下游节点 |
|
|
80
91
|
| `unknown` | 未映射类别(不应出现在正常派生路径) | 内部兜底 |
|
|
81
92
|
|
|
82
|
-
##
|
|
93
|
+
## Product-Line Routing v0.1
|
|
94
|
+
|
|
95
|
+
| Product-line category | Default follow-up |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `SpecUnclear` | `spec-clarification` |
|
|
98
|
+
| `ContractMismatch` | `architecture-contract-fix` |
|
|
99
|
+
| `ProductBug` | `dev-fix` |
|
|
100
|
+
| `TestBug` | `qa-fix-test` |
|
|
101
|
+
| `EnvFailure` | `env-fix` 或 retry verify |
|
|
102
|
+
| `FlakyTest` | `flaky-test-analysis` |
|
|
103
|
+
| `RiskyChange` | `human-review` / `architecture-review` |
|
|
104
|
+
| `DependencyFailure` | unblock dependency |
|
|
105
|
+
| `NeedsHuman` | `human-review` |
|
|
106
|
+
| `Unknown` | human triage |
|
|
107
|
+
|
|
108
|
+
## 类别 → 动作 → operator 指引
|
|
83
109
|
|
|
84
110
|
| Normalized category | Recovery action | Operator guidance | Anti-patterns |
|
|
85
111
|
|---------------------|-----------------|-------------------|---------------|
|
|
@@ -107,9 +133,9 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
|
|
|
107
133
|
1. **Primary Failure** — `primaryFailure`(node 或 run scope)
|
|
108
134
|
2. **Recovery Action** — `primaryRecovery`(action、summary、reason、flags、commandHint)
|
|
109
135
|
3. **Blocked Downstream / Skipped Nodes** — `downstreamSkippedNodes`
|
|
110
|
-
4. **Recommended Operator Action** —
|
|
136
|
+
4. **Recommended Operator Action** — 面向 operator 的步骤摘要
|
|
111
137
|
|
|
112
|
-
保存 handoff 时重定向到平台临时目录或 `docs/reports/`,不要写入 `.harness/dag-runs/`。
|
|
138
|
+
保存 handoff 时重定向到平台临时目录或 `docs/reports/`,不要写入 `.harness/dag-runs/`。
|
|
113
139
|
|
|
114
140
|
## Decision Gate 消费约定
|
|
115
141
|
|
|
@@ -123,7 +149,7 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
|
|
|
123
149
|
|
|
124
150
|
`dag doctor` 与 `dag status` 通过 `detectDagRunHealthIssues()` 检测 lifecycle 不一致,**不** mutate run facts。
|
|
125
151
|
|
|
126
|
-
| Code | 典型场景 |
|
|
152
|
+
| Code | 典型场景 | operator 指引 |
|
|
127
153
|
|------|----------|------------|
|
|
128
154
|
| `terminal-in-active` | run 已完成但 `active/<run-id>/` 残留 | 对照 `completed/` canonical facts;手动 archive 或删除 stale 目录 |
|
|
129
155
|
| `paused-in-active` | pause 后目录未迁至 `paused/` | `dag doctor` 诊断;修复 facts 后再 approve/resume |
|
package/docs/agent-dag-runner.md
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
# Agent DAG Runner
|
|
2
2
|
|
|
3
|
-
Agent DAG
|
|
3
|
+
Agent DAG 是 loop-agent 的声明式编排 runtime。DAG 将工作拆为节点、按序执行 eligible ranks、记录 artifacts,并用 gate 做 review 与验证。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 基本用法
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
loop-agent dag run-task <task-id> --profile auto --strict-models --output <temp-dir>/<task-id>-dag.json
|
|
9
|
-
loop-agent dag validate --dag <temp-dir>/<task-id>-dag.json --strict-models --strict-governance
|
|
10
|
-
loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
`<temp-dir>`
|
|
8
|
+
loop-agent dag run-task <task-id> --profile auto --strict-models --output <temp-dir>/<task-id>-dag.json
|
|
9
|
+
loop-agent dag validate --dag <temp-dir>/<task-id>-dag.json --strict-models --strict-governance
|
|
10
|
+
loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`<temp-dir>` 为平台原生临时目录。Windows 上 `--output`、`--dag`、`--cwd` 的实际值用原生路径。
|
|
14
14
|
|
|
15
15
|
## Executors
|
|
16
16
|
|
|
17
|
-
- `static
|
|
18
|
-
- `shell
|
|
19
|
-
- `pi
|
|
20
|
-
- `cursor
|
|
17
|
+
- `static`:确定性生成的 artifacts 或 notes
|
|
18
|
+
- `shell`:验证与文件系统检查
|
|
19
|
+
- `pi`:规划、review、诊断;节点设 `toolProfile: "write"` 时有界写入
|
|
20
|
+
- `cursor`:显式启用时的可选有界写后端
|
|
21
21
|
|
|
22
22
|
## Skills
|
|
23
23
|
|
|
24
|
-
DAG
|
|
24
|
+
DAG spec 可声明 `defaults.skills`、`skillsByRole` 与节点级 `skills`。Runner 从 `skills/<skill-name>/SKILL.md` 解析本地指令,并在各节点 `skills.json` artifact 中记录解析元数据。
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
`loop-agent` skill 位于 `skills/loop-agent/SKILL.md`。遗留根路径 `skill/SKILL.md` 仅为旧 worktree 保留兼容 fallback。
|
|
27
27
|
|
|
28
28
|
## Artifacts
|
|
29
29
|
|
|
30
|
-
DAG artifacts
|
|
30
|
+
DAG artifacts 位于:
|
|
31
31
|
|
|
32
32
|
```text
|
|
33
33
|
.harness/dag-runs/<state>/<run-id>/artifacts/<node-id>/
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
根目录 `artifacts/` 不是有效的默认 DAG artifact 位置。
|
|
37
37
|
|
|
38
38
|
## Shell Gates
|
|
39
39
|
|
|
40
|
-
- `shell.verdictGate`
|
|
40
|
+
- `shell.verdictGate` 从注入的当前 run 目录读取 `$HARNESS_DAG_RUN_DIR/<fromNodeId>.json`;不应自行发现 active run paths。
|