@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.
- package/CHANGELOG.md +17 -1
- package/README.md +127 -92
- 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 +93 -0
- package/dist/application/dag/validate-dag.js +101 -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 +1495 -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/lifecycle.js +6 -5
- package/dist/workflows/dag/node-execution.js +262 -0
- 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 +3 -2
- package/docs/architecture/runtime-boundaries.md +147 -0
- package/docs/exec-plans/active/README.md +4 -0
- package/docs/exec-plans/completed/README.md +6 -2
- package/package.json +2 -1
- package/skills/ai-engineering-context/SKILL.md +21 -21
- package/skills/loop-agent/SKILL.md +73 -188
- package/skills/loop-agent/references/README.md +6 -2
- package/skills/loop-agent/references/harness-policy.md +113 -113
- 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/pi-subagent-assisted-mode.md +0 -2
- package/skills/loop-agent/references/verification-and-failure-handling.md +18 -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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeJsonAtomic, writeTextAtomic, } from "./atomic-write.js";
|
|
4
|
+
function resolveRepoRootFromDagRunDir(runDir) {
|
|
5
|
+
const normalized = path.resolve(runDir);
|
|
6
|
+
const lifecycleDir = path.dirname(normalized);
|
|
7
|
+
const dagRunsDir = path.dirname(lifecycleDir);
|
|
8
|
+
const harnessDir = path.dirname(dagRunsDir);
|
|
9
|
+
const repoRoot = path.dirname(harnessDir);
|
|
10
|
+
if (path.basename(harnessDir) !== ".harness" ||
|
|
11
|
+
path.basename(dagRunsDir) !== "dag-runs") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return repoRoot;
|
|
15
|
+
}
|
|
16
|
+
function resolveRepoRootFromHarnessPath(targetPath) {
|
|
17
|
+
const normalized = path.resolve(targetPath);
|
|
18
|
+
const parts = normalized.split(path.sep);
|
|
19
|
+
const harnessIndex = parts.indexOf(".harness");
|
|
20
|
+
if (harnessIndex <= 0) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return parts.slice(0, harnessIndex).join(path.sep);
|
|
24
|
+
}
|
|
25
|
+
function dagWriteOptions(runDir, options) {
|
|
26
|
+
return {
|
|
27
|
+
repoRoot: options?.repoRoot ?? resolveRepoRootFromDagRunDir(runDir),
|
|
28
|
+
allowCompletedFactsWrite: options?.allowCompletedFactsWrite,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function pathWriteOptions(targetPath, options) {
|
|
32
|
+
return {
|
|
33
|
+
repoRoot: options?.repoRoot ?? resolveRepoRootFromHarnessPath(targetPath),
|
|
34
|
+
allowCompletedFactsWrite: options?.allowCompletedFactsWrite,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function completedRelocationWriteOptions(runDir) {
|
|
38
|
+
return {
|
|
39
|
+
...dagWriteOptions(runDir),
|
|
40
|
+
allowCompletedFactsWrite: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function dagCursorSdkArtifactDir(runDir, nodeId) {
|
|
44
|
+
return path.join(runDir, "artifacts", nodeId);
|
|
45
|
+
}
|
|
46
|
+
export async function writeDagNodeTextArtifact(runDir, nodeId, fileName, content, options) {
|
|
47
|
+
const targetPath = path.join(runDir, nodeId, fileName);
|
|
48
|
+
await writeTextAtomic(targetPath, content, dagWriteOptions(runDir, options));
|
|
49
|
+
return targetPath;
|
|
50
|
+
}
|
|
51
|
+
export async function writeDagNodeJsonArtifact(runDir, nodeId, fileName, value, options) {
|
|
52
|
+
const targetPath = path.join(runDir, nodeId, fileName);
|
|
53
|
+
await writeJsonAtomic(targetPath, value, dagWriteOptions(runDir, options));
|
|
54
|
+
return targetPath;
|
|
55
|
+
}
|
|
56
|
+
export async function writeDagRunJsonArtifact(runDir, relativePath, value, options) {
|
|
57
|
+
const targetPath = path.join(runDir, relativePath);
|
|
58
|
+
await writeJsonAtomic(targetPath, value, dagWriteOptions(runDir, options));
|
|
59
|
+
return targetPath;
|
|
60
|
+
}
|
|
61
|
+
export async function ensureDagNodeArtifactDir(runDir, nodeId) {
|
|
62
|
+
const artifactDir = dagCursorSdkArtifactDir(runDir, nodeId);
|
|
63
|
+
await mkdir(artifactDir, { recursive: true });
|
|
64
|
+
return artifactDir;
|
|
65
|
+
}
|
|
66
|
+
export async function writeTextArtifactFile(targetPath, content, options) {
|
|
67
|
+
await writeTextAtomic(targetPath, content, pathWriteOptions(targetPath, options));
|
|
68
|
+
}
|
|
69
|
+
/** Rewrite node records after active/paused -> completed run-dir transfer. */
|
|
70
|
+
export async function writeRelocatedDagNodeRecord(runDir, nodeId, record) {
|
|
71
|
+
await writeDagRunJsonArtifact(runDir, `${nodeId}.json`, record, completedRelocationWriteOptions(runDir));
|
|
72
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { copyFile, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertHarnessWriteAllowed, } from "./completed-facts-guard.js";
|
|
4
|
+
async function writeAtomic(targetPath, content, options) {
|
|
5
|
+
assertHarnessWriteAllowed(targetPath, options);
|
|
6
|
+
const resolved = options?.repoRoot && !path.isAbsolute(targetPath)
|
|
7
|
+
? path.resolve(options.repoRoot, targetPath)
|
|
8
|
+
: path.resolve(targetPath);
|
|
9
|
+
await mkdir(path.dirname(resolved), { recursive: true });
|
|
10
|
+
const tempPath = `${resolved}.${process.pid}.tmp`;
|
|
11
|
+
try {
|
|
12
|
+
await writeFile(tempPath, content, "utf-8");
|
|
13
|
+
try {
|
|
14
|
+
await rename(tempPath, resolved);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
await copyFile(tempPath, resolved);
|
|
18
|
+
await unlink(tempPath).catch(() => { });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
await unlink(tempPath).catch(() => { });
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function writeJsonAtomic(targetPath, value, options) {
|
|
27
|
+
const json = `${JSON.stringify(value, null, 2)}\n`;
|
|
28
|
+
await writeAtomic(targetPath, json, options);
|
|
29
|
+
}
|
|
30
|
+
export async function writeTextAtomic(targetPath, text, options) {
|
|
31
|
+
await writeAtomic(targetPath, text, options);
|
|
32
|
+
}
|
|
33
|
+
export function serializeJsonl(entries) {
|
|
34
|
+
if (entries.length === 0)
|
|
35
|
+
return "";
|
|
36
|
+
return `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
|
|
37
|
+
}
|
|
38
|
+
export async function writeJsonlAtomic(targetPath, entries, options) {
|
|
39
|
+
await writeAtomic(targetPath, serializeJsonl(entries), options);
|
|
40
|
+
}
|
|
41
|
+
export async function appendJsonlLineAtomic(targetPath, entry, options) {
|
|
42
|
+
assertHarnessWriteAllowed(targetPath, options);
|
|
43
|
+
const resolved = options?.repoRoot && !path.isAbsolute(targetPath)
|
|
44
|
+
? path.resolve(options.repoRoot, targetPath)
|
|
45
|
+
: path.resolve(targetPath);
|
|
46
|
+
const existing = await readFile(resolved, "utf-8").catch(() => "");
|
|
47
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? existing : `${existing}\n`;
|
|
48
|
+
await writeAtomic(targetPath, `${prefix}${JSON.stringify(entry)}\n`, options);
|
|
49
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const COMPLETED_DAG_RUNS_PREFIX = ".harness/dag-runs/completed/";
|
|
3
|
+
const COMPLETED_RUNS_PREFIX = ".harness/runs/completed/";
|
|
4
|
+
/** Normalize a target path to a repo-relative POSIX path for guard checks. */
|
|
5
|
+
export function normalizeHarnessRelativePath(targetPath, repoRoot) {
|
|
6
|
+
const posixInput = targetPath.split(path.sep).join("/");
|
|
7
|
+
if (!repoRoot) {
|
|
8
|
+
const normalized = posixInput.replace(/^\.\/+/, "");
|
|
9
|
+
const harnessIndex = normalized.indexOf("/.harness/");
|
|
10
|
+
if (harnessIndex >= 0) {
|
|
11
|
+
return normalized.slice(harnessIndex + 1);
|
|
12
|
+
}
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
const resolvedRoot = path.resolve(repoRoot);
|
|
16
|
+
const resolvedTarget = path.isAbsolute(targetPath)
|
|
17
|
+
? path.resolve(targetPath)
|
|
18
|
+
: path.resolve(resolvedRoot, targetPath);
|
|
19
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
20
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
21
|
+
return posixInput.replace(/^\.\/+/, "");
|
|
22
|
+
}
|
|
23
|
+
return relative.split(path.sep).join("/");
|
|
24
|
+
}
|
|
25
|
+
function isCompletedFactsPath(relativePath) {
|
|
26
|
+
return (relativePath.startsWith(COMPLETED_DAG_RUNS_PREFIX) ||
|
|
27
|
+
relativePath === ".harness/dag-runs/completed" ||
|
|
28
|
+
relativePath.startsWith(COMPLETED_RUNS_PREFIX) ||
|
|
29
|
+
relativePath === ".harness/runs/completed");
|
|
30
|
+
}
|
|
31
|
+
/** Throw when a write would mutate completed DAG or one-shot run facts. */
|
|
32
|
+
export function assertHarnessWriteAllowed(targetPath, options) {
|
|
33
|
+
if (options?.allowCompletedFactsWrite) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const relative = normalizeHarnessRelativePath(targetPath, options?.repoRoot);
|
|
37
|
+
if (isCompletedFactsPath(relative)) {
|
|
38
|
+
throw new Error(`harness write guard: refusing to mutate completed run facts: ${relative}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { writeJsonAtomic, writeTextAtomic, } from "./atomic-write.js";
|
|
3
|
+
function loopActionFile(repoRoot, taskId, subdir, filename) {
|
|
4
|
+
return path.join(repoRoot, ".harness", "tasks", taskId, "loop", subdir, filename);
|
|
5
|
+
}
|
|
6
|
+
function loopActionWriteOptions(repoRoot) {
|
|
7
|
+
return { repoRoot };
|
|
8
|
+
}
|
|
9
|
+
export async function writeLoopShellVerifyRecord(repoRoot, taskId, round, record) {
|
|
10
|
+
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "verification", `round-${round}.json`), record, loopActionWriteOptions(repoRoot));
|
|
11
|
+
}
|
|
12
|
+
export async function writeLoopCursorFixRecord(repoRoot, taskId, round, record) {
|
|
13
|
+
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "reviews", `round-${round}-cursor-fix.json`), record, loopActionWriteOptions(repoRoot));
|
|
14
|
+
}
|
|
15
|
+
export async function writeLoopDagRecord(repoRoot, taskId, round, record) {
|
|
16
|
+
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "dag", `round-${round}.json`), record, loopActionWriteOptions(repoRoot));
|
|
17
|
+
}
|
|
18
|
+
export async function writeLoopPiReviewRecord(repoRoot, taskId, round, record) {
|
|
19
|
+
await writeJsonAtomic(loopActionFile(repoRoot, taskId, "reviews", `round-${round}.json`), record, loopActionWriteOptions(repoRoot));
|
|
20
|
+
}
|
|
21
|
+
export async function writeLoopPiReviewPrompt(repoRoot, taskId, round, prompt) {
|
|
22
|
+
await writeTextAtomic(loopActionFile(repoRoot, taskId, "reviews", `round-${round}-prompt.md`), prompt, loopActionWriteOptions(repoRoot));
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { appendJsonlLineAtomic, writeJsonAtomic, writeJsonlAtomic, writeTextAtomic, } from "./atomic-write.js";
|
|
4
|
+
function loopDir(repoRoot, taskId) {
|
|
5
|
+
return path.join(repoRoot, ".harness", "tasks", taskId, "loop");
|
|
6
|
+
}
|
|
7
|
+
function loopFile(repoRoot, taskId, filename) {
|
|
8
|
+
return path.join(loopDir(repoRoot, taskId), filename);
|
|
9
|
+
}
|
|
10
|
+
function loopWriteOptions(repoRoot) {
|
|
11
|
+
return { repoRoot };
|
|
12
|
+
}
|
|
13
|
+
export async function ensureLoopDir(repoRoot, taskId) {
|
|
14
|
+
const dir = loopDir(repoRoot, taskId);
|
|
15
|
+
await mkdir(dir, { recursive: true });
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
export async function writeLoopState(repoRoot, taskId, state) {
|
|
19
|
+
await writeJsonAtomic(loopFile(repoRoot, taskId, "state.json"), state, loopWriteOptions(repoRoot));
|
|
20
|
+
}
|
|
21
|
+
export async function writeLoopObjective(repoRoot, taskId, text) {
|
|
22
|
+
await writeTextAtomic(loopFile(repoRoot, taskId, "objective.md"), text, loopWriteOptions(repoRoot));
|
|
23
|
+
}
|
|
24
|
+
export async function writeLoopContext(repoRoot, taskId, text) {
|
|
25
|
+
await writeTextAtomic(loopFile(repoRoot, taskId, "context.md"), text, loopWriteOptions(repoRoot));
|
|
26
|
+
}
|
|
27
|
+
export async function writeLoopCloseout(repoRoot, taskId, text) {
|
|
28
|
+
await writeTextAtomic(loopFile(repoRoot, taskId, "closeout.md"), text, loopWriteOptions(repoRoot));
|
|
29
|
+
}
|
|
30
|
+
export async function writeLoopRounds(repoRoot, taskId, entries) {
|
|
31
|
+
await writeJsonlAtomic(loopFile(repoRoot, taskId, "rounds.jsonl"), entries, loopWriteOptions(repoRoot));
|
|
32
|
+
}
|
|
33
|
+
export async function writeLoopSignals(repoRoot, taskId, entries) {
|
|
34
|
+
await writeJsonlAtomic(loopFile(repoRoot, taskId, "signals.jsonl"), entries, loopWriteOptions(repoRoot));
|
|
35
|
+
}
|
|
36
|
+
export async function writeLoopEvents(repoRoot, taskId, entries) {
|
|
37
|
+
await writeJsonlAtomic(loopFile(repoRoot, taskId, "events.jsonl"), entries, loopWriteOptions(repoRoot));
|
|
38
|
+
}
|
|
39
|
+
export async function appendLoopEventLine(repoRoot, taskId, entry) {
|
|
40
|
+
await appendJsonlLineAtomic(loopFile(repoRoot, taskId, "events.jsonl"), entry, loopWriteOptions(repoRoot));
|
|
41
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { access, cp, mkdir, rename, rm, unlink } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeJsonAtomic, writeTextAtomic } from "./atomic-write.js";
|
|
4
|
+
import { assertHarnessWriteAllowed } from "./completed-facts-guard.js";
|
|
5
|
+
function runsDir(repoRoot, lifecycle) {
|
|
6
|
+
return path.join(repoRoot, ".harness", "runs", lifecycle);
|
|
7
|
+
}
|
|
8
|
+
function activeWriteOptions(repoRoot) {
|
|
9
|
+
return { repoRoot };
|
|
10
|
+
}
|
|
11
|
+
function completedTransferWriteOptions(repoRoot) {
|
|
12
|
+
return { repoRoot, allowCompletedFactsWrite: true };
|
|
13
|
+
}
|
|
14
|
+
export async function ensureOneShotRunDirs(repoRoot) {
|
|
15
|
+
for (const lifecycle of ["active", "completed", "failed"]) {
|
|
16
|
+
await mkdir(runsDir(repoRoot, lifecycle), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* If `baseName` already exists in the lifecycle dir, append `-2`, `-3`, etc.
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveUniqueOneShotRunPath(repoRoot, lifecycle, baseName) {
|
|
23
|
+
const dir = runsDir(repoRoot, lifecycle);
|
|
24
|
+
let candidate = path.join(dir, baseName);
|
|
25
|
+
let counter = 1;
|
|
26
|
+
while (true) {
|
|
27
|
+
try {
|
|
28
|
+
await access(candidate);
|
|
29
|
+
counter += 1;
|
|
30
|
+
const ext = path.extname(baseName);
|
|
31
|
+
const stem = path.basename(baseName, ext);
|
|
32
|
+
candidate = path.join(dir, `${stem}-${counter}${ext}`);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function createOneShotActiveRunDir(repoRoot, baseName) {
|
|
40
|
+
await ensureOneShotRunDirs(repoRoot);
|
|
41
|
+
const runDir = await resolveUniqueOneShotRunPath(repoRoot, "active", baseName);
|
|
42
|
+
await mkdir(runDir, { recursive: true });
|
|
43
|
+
return runDir;
|
|
44
|
+
}
|
|
45
|
+
export async function writeOneShotRunMarkdown(repoRoot, runDir, content) {
|
|
46
|
+
await writeTextAtomic(path.join(runDir, "run.md"), content, activeWriteOptions(repoRoot));
|
|
47
|
+
}
|
|
48
|
+
export async function writeOneShotActiveLogAtPath(repoRoot, logPath, content) {
|
|
49
|
+
await writeTextAtomic(logPath, content, activeWriteOptions(repoRoot));
|
|
50
|
+
}
|
|
51
|
+
export async function writeOneShotRunMeta(repoRoot, runDir, meta) {
|
|
52
|
+
await writeJsonAtomic(path.join(runDir, "meta.json"), meta, activeWriteOptions(repoRoot));
|
|
53
|
+
}
|
|
54
|
+
async function moveRunDirectory(fromDir, toDir) {
|
|
55
|
+
await mkdir(path.dirname(toDir), { recursive: true });
|
|
56
|
+
try {
|
|
57
|
+
await rename(fromDir, toDir);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
await cp(fromDir, toDir, { recursive: true });
|
|
61
|
+
await rm(fromDir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function transferOneShotRunToCompleted(repoRoot, activeRunDir, destBaseName) {
|
|
65
|
+
await ensureOneShotRunDirs(repoRoot);
|
|
66
|
+
const completedPath = await resolveUniqueOneShotRunPath(repoRoot, "completed", destBaseName);
|
|
67
|
+
assertHarnessWriteAllowed(completedPath, completedTransferWriteOptions(repoRoot));
|
|
68
|
+
await moveRunDirectory(activeRunDir, completedPath);
|
|
69
|
+
return completedPath;
|
|
70
|
+
}
|
|
71
|
+
export async function transferOneShotRunToFailed(repoRoot, activeRunDir, destBaseName) {
|
|
72
|
+
await ensureOneShotRunDirs(repoRoot);
|
|
73
|
+
const failedPath = await resolveUniqueOneShotRunPath(repoRoot, "failed", destBaseName);
|
|
74
|
+
assertHarnessWriteAllowed(failedPath, activeWriteOptions(repoRoot));
|
|
75
|
+
await moveRunDirectory(activeRunDir, failedPath);
|
|
76
|
+
return failedPath;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Legacy `.md`-only finalize path: move or rewrite a flat log into completed/.
|
|
80
|
+
* Completed writes use the guarded transfer bypass inside this store.
|
|
81
|
+
*/
|
|
82
|
+
export async function finalizeLegacyRunLogToCompleted(repoRoot, logPath, content) {
|
|
83
|
+
await ensureOneShotRunDirs(repoRoot);
|
|
84
|
+
const completedDir = runsDir(repoRoot, "completed");
|
|
85
|
+
const completedPath = path.join(completedDir, path.basename(logPath));
|
|
86
|
+
try {
|
|
87
|
+
await rename(logPath, completedPath);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
await writeTextAtomic(completedPath, content, completedTransferWriteOptions(repoRoot));
|
|
91
|
+
await unlink(logPath).catch(() => { });
|
|
92
|
+
}
|
|
93
|
+
return completedPath;
|
|
94
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { appendJsonlLineAtomic, writeJsonAtomic, writeTextAtomic, } from "./atomic-write.js";
|
|
4
|
+
function taskDir(repoRoot, taskId) {
|
|
5
|
+
return path.join(repoRoot, ".harness", "tasks", taskId);
|
|
6
|
+
}
|
|
7
|
+
function taskFile(repoRoot, taskId, filename) {
|
|
8
|
+
return path.join(taskDir(repoRoot, taskId), filename);
|
|
9
|
+
}
|
|
10
|
+
function taskWriteOptions(repoRoot) {
|
|
11
|
+
return { repoRoot };
|
|
12
|
+
}
|
|
13
|
+
export function resolveTaskContextFromDir(taskDir) {
|
|
14
|
+
const normalized = path.resolve(taskDir);
|
|
15
|
+
const taskId = path.basename(normalized);
|
|
16
|
+
const tasksDir = path.dirname(normalized);
|
|
17
|
+
const harnessDir = path.dirname(tasksDir);
|
|
18
|
+
const repoRoot = path.dirname(harnessDir);
|
|
19
|
+
if (path.basename(tasksDir) !== "tasks" || path.basename(harnessDir) !== ".harness") {
|
|
20
|
+
throw new Error(`invalid task dir: ${taskDir}`);
|
|
21
|
+
}
|
|
22
|
+
return { repoRoot, taskId };
|
|
23
|
+
}
|
|
24
|
+
export function resolveTaskStateContext(statePath) {
|
|
25
|
+
const normalized = path.resolve(statePath);
|
|
26
|
+
const filename = path.basename(normalized);
|
|
27
|
+
if (filename !== ".workflow_state.json") {
|
|
28
|
+
throw new Error(`invalid workflow state path: ${statePath}`);
|
|
29
|
+
}
|
|
30
|
+
const taskId = path.basename(path.dirname(normalized));
|
|
31
|
+
const tasksDir = path.dirname(path.dirname(normalized));
|
|
32
|
+
const harnessDir = path.dirname(tasksDir);
|
|
33
|
+
const repoRoot = path.dirname(harnessDir);
|
|
34
|
+
if (path.basename(tasksDir) !== "tasks" || path.basename(harnessDir) !== ".harness") {
|
|
35
|
+
throw new Error(`invalid workflow state path: ${statePath}`);
|
|
36
|
+
}
|
|
37
|
+
return { repoRoot, taskId };
|
|
38
|
+
}
|
|
39
|
+
export async function writeTaskConfig(repoRoot, taskId, config) {
|
|
40
|
+
await writeJsonAtomic(taskFile(repoRoot, taskId, "task.json"), config, taskWriteOptions(repoRoot));
|
|
41
|
+
}
|
|
42
|
+
export async function writeWorkflowState(repoRoot, taskId, state) {
|
|
43
|
+
const options = taskWriteOptions(repoRoot);
|
|
44
|
+
const stateFile = taskFile(repoRoot, taskId, ".workflow_state.json");
|
|
45
|
+
const bakFile = `${stateFile}.bak`;
|
|
46
|
+
await writeJsonAtomic(stateFile, state, options);
|
|
47
|
+
await writeJsonAtomic(bakFile, state, options);
|
|
48
|
+
}
|
|
49
|
+
export async function initializeTaskLogStubs(repoRoot, taskId) {
|
|
50
|
+
const options = taskWriteOptions(repoRoot);
|
|
51
|
+
const logsDir = path.join(taskDir(repoRoot, taskId), "logs");
|
|
52
|
+
await writeTextAtomic(path.join(logsDir, "executor.jsonl"), "", options);
|
|
53
|
+
await writeTextAtomic(path.join(logsDir, "decisions.jsonl"), "", options);
|
|
54
|
+
}
|
|
55
|
+
function taskArtifactFile(repoRoot, taskId, fileName) {
|
|
56
|
+
return path.join(taskDir(repoRoot, taskId), "artifacts", fileName);
|
|
57
|
+
}
|
|
58
|
+
function taskLogFile(repoRoot, taskId, fileName) {
|
|
59
|
+
return path.join(taskDir(repoRoot, taskId), "logs", fileName);
|
|
60
|
+
}
|
|
61
|
+
export async function writeTaskArtifactFile(repoRoot, taskId, fileName, content) {
|
|
62
|
+
await writeTextAtomic(taskArtifactFile(repoRoot, taskId, fileName), content, taskWriteOptions(repoRoot));
|
|
63
|
+
}
|
|
64
|
+
export async function appendTaskWorkflowLog(repoRoot, taskId, message) {
|
|
65
|
+
const options = taskWriteOptions(repoRoot);
|
|
66
|
+
const logPath = taskLogFile(repoRoot, taskId, "workflow.log");
|
|
67
|
+
const resolved = options.repoRoot && !path.isAbsolute(logPath)
|
|
68
|
+
? path.resolve(options.repoRoot, logPath)
|
|
69
|
+
: path.resolve(logPath);
|
|
70
|
+
const existing = await readFile(resolved, "utf-8").catch(() => "");
|
|
71
|
+
const line = `[${new Date().toISOString()}] ${message}\n`;
|
|
72
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? existing : `${existing}\n`;
|
|
73
|
+
await writeTextAtomic(logPath, `${prefix}${line}`, options);
|
|
74
|
+
}
|
|
75
|
+
export async function appendTaskLogJsonlRecord(repoRoot, taskId, fileName, record) {
|
|
76
|
+
await appendJsonlLineAtomic(taskLogFile(repoRoot, taskId, fileName), record, taskWriteOptions(repoRoot));
|
|
77
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { truncateOutput } from '../executors/cursor-executor.js';
|
|
4
|
+
import { createOneShotActiveRunDir, ensureOneShotRunDirs, finalizeLegacyRunLogToCompleted, transferOneShotRunToCompleted, transferOneShotRunToFailed, writeOneShotActiveLogAtPath, writeOneShotRunMarkdown, writeOneShotRunMeta, } from '../infrastructure/harness/one-shot-run-store.js';
|
|
4
5
|
/** Maximum chars for result summary in the run log. */
|
|
5
6
|
export const RUN_LOG_RESULT_MAX = 4_000;
|
|
6
7
|
/** Placeholder written to active logs before execution finishes. */
|
|
@@ -19,11 +20,7 @@ export async function computeRunDir(cwd, task) {
|
|
|
19
20
|
}
|
|
20
21
|
const datePrefix = new Date().toISOString().slice(0, 10);
|
|
21
22
|
const slug = deriveRunSlug(task);
|
|
22
|
-
|
|
23
|
-
await mkdir(activeDir, { recursive: true });
|
|
24
|
-
const runDir = await resolveUniqueRunPath(activeDir, `${datePrefix}-${slug}`);
|
|
25
|
-
await mkdir(runDir, { recursive: true });
|
|
26
|
-
return runDir;
|
|
23
|
+
return createOneShotActiveRunDir(cwd, `${datePrefix}-${slug}`);
|
|
27
24
|
}
|
|
28
25
|
/**
|
|
29
26
|
* Persist a human-readable run log under `.harness/runs/`.
|
|
@@ -46,44 +43,25 @@ export async function persistCursorRunLog(cwd, input) {
|
|
|
46
43
|
const datePrefix = new Date().toISOString().slice(0, 10);
|
|
47
44
|
const slug = deriveRunSlug(input.task);
|
|
48
45
|
const baseName = `${datePrefix}-${slug}`;
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const failedDir = path.join(harnessDir, 'runs', 'failed');
|
|
52
|
-
await mkdir(activeDir, { recursive: true });
|
|
53
|
-
await mkdir(completedDir, { recursive: true });
|
|
54
|
-
await mkdir(failedDir, { recursive: true });
|
|
55
|
-
const activeRunDir = input.runDir ?? await resolveUniqueRunPath(activeDir, baseName);
|
|
56
|
-
await mkdir(activeRunDir, { recursive: true });
|
|
46
|
+
await ensureOneShotRunDirs(cwd);
|
|
47
|
+
const activeRunDir = input.runDir ?? await createOneShotActiveRunDir(cwd, baseName);
|
|
57
48
|
const content = buildRunLogMarkdown(input);
|
|
58
|
-
|
|
59
|
-
await
|
|
60
|
-
await writeRunMeta(activeRunDir, input);
|
|
49
|
+
await writeOneShotRunMarkdown(cwd, activeRunDir, content);
|
|
50
|
+
await writeRunMeta(cwd, activeRunDir, input);
|
|
61
51
|
if (input.result.ok) {
|
|
62
|
-
const completedPath = await
|
|
63
|
-
await moveRunDirectory(activeRunDir, completedPath);
|
|
52
|
+
const completedPath = await transferOneShotRunToCompleted(cwd, activeRunDir, path.basename(activeRunDir));
|
|
64
53
|
return { logPath: path.join(completedPath, 'run.md'), completed: true };
|
|
65
54
|
}
|
|
66
|
-
const failedPath = await resolveUniqueRunPath(failedDir, path.basename(activeRunDir));
|
|
67
55
|
if (input.result.status === 'failed' || input.result.status === 'timeout' || input.result.status === 'cancelled') {
|
|
68
|
-
await
|
|
56
|
+
const failedPath = await transferOneShotRunToFailed(cwd, activeRunDir, path.basename(activeRunDir));
|
|
69
57
|
return { logPath: path.join(failedPath, 'run.md'), completed: false };
|
|
70
58
|
}
|
|
71
|
-
return { logPath:
|
|
59
|
+
return { logPath: path.join(activeRunDir, 'run.md'), completed: false };
|
|
72
60
|
}
|
|
73
|
-
async function
|
|
74
|
-
await mkdir(path.dirname(toDir), { recursive: true });
|
|
75
|
-
try {
|
|
76
|
-
await rename(fromDir, toDir);
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
await cp(fromDir, toDir, { recursive: true });
|
|
80
|
-
await rm(fromDir, { recursive: true, force: true });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async function writeRunMeta(runDir, input, statusOverride) {
|
|
61
|
+
async function writeRunMeta(repoRoot, runDir, input, statusOverride) {
|
|
84
62
|
const runId = path.basename(runDir);
|
|
85
63
|
const status = statusOverride ?? (input.result.ok ? 'completed' : input.result.status);
|
|
86
|
-
await
|
|
64
|
+
await writeOneShotRunMeta(repoRoot, runDir, {
|
|
87
65
|
runId,
|
|
88
66
|
taskId: input.taskId,
|
|
89
67
|
status,
|
|
@@ -95,7 +73,7 @@ async function writeRunMeta(runDir, input, statusOverride) {
|
|
|
95
73
|
failureCategory: input.result.failureCategory,
|
|
96
74
|
artifactsDir: 'artifacts',
|
|
97
75
|
logPath: 'run.md',
|
|
98
|
-
}
|
|
76
|
+
});
|
|
99
77
|
}
|
|
100
78
|
function isRunMarkdownPath(logPath) {
|
|
101
79
|
return path.basename(logPath) === 'run.md';
|
|
@@ -119,16 +97,10 @@ export async function createActiveCursorRun(cwd, input) {
|
|
|
119
97
|
const datePrefix = new Date().toISOString().slice(0, 10);
|
|
120
98
|
const slug = deriveRunSlug(input.task);
|
|
121
99
|
const baseName = `${datePrefix}-${slug}`;
|
|
122
|
-
const
|
|
123
|
-
await mkdir(activeDir, { recursive: true });
|
|
124
|
-
await mkdir(path.join(harnessDir, 'runs', 'completed'), { recursive: true });
|
|
125
|
-
await mkdir(path.join(harnessDir, 'runs', 'failed'), { recursive: true });
|
|
126
|
-
const activeRunDir = await resolveUniqueRunPath(activeDir, baseName);
|
|
127
|
-
await mkdir(activeRunDir, { recursive: true });
|
|
128
|
-
const activePath = path.join(activeRunDir, 'run.md');
|
|
100
|
+
const activeRunDir = await createOneShotActiveRunDir(cwd, baseName);
|
|
129
101
|
const content = buildActiveRunLogMarkdown(input);
|
|
130
|
-
await
|
|
131
|
-
await writeRunMeta(activeRunDir, {
|
|
102
|
+
await writeOneShotRunMarkdown(cwd, activeRunDir, content);
|
|
103
|
+
await writeRunMeta(cwd, activeRunDir, {
|
|
132
104
|
taskId: input.taskId,
|
|
133
105
|
task: input.task,
|
|
134
106
|
model: input.model,
|
|
@@ -144,7 +116,7 @@ export async function createActiveCursorRun(cwd, input) {
|
|
|
144
116
|
stderr: '',
|
|
145
117
|
},
|
|
146
118
|
}, 'running');
|
|
147
|
-
return { logPath:
|
|
119
|
+
return { logPath: path.join(activeRunDir, 'run.md'), taskId: input.taskId };
|
|
148
120
|
}
|
|
149
121
|
/**
|
|
150
122
|
* Finalize an active run log with execution results.
|
|
@@ -157,30 +129,23 @@ export async function finalizeCursorRunLog(logPath, input) {
|
|
|
157
129
|
const existing = await readFile(logPath, 'utf-8').catch(() => '');
|
|
158
130
|
const taskId = existing ? parseCursorRunLog(existing, logPath).taskId : undefined;
|
|
159
131
|
const content = buildRunLogMarkdown({ ...input, taskId });
|
|
160
|
-
await writeFile(logPath, content, 'utf-8');
|
|
161
132
|
const activeRunDir = runDirForLogPath(logPath);
|
|
162
133
|
if (activeRunDir) {
|
|
163
|
-
await
|
|
134
|
+
await writeOneShotRunMarkdown(input.cwd, activeRunDir, content);
|
|
135
|
+
await writeRunMeta(input.cwd, activeRunDir, { ...input, taskId });
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await writeOneShotActiveLogAtPath(input.cwd, logPath, content);
|
|
164
139
|
}
|
|
165
|
-
const targetLifecycle = input.result.ok ? 'completed' : 'failed';
|
|
166
|
-
const targetDir = path.join(harnessDir, 'runs', targetLifecycle);
|
|
167
|
-
await mkdir(targetDir, { recursive: true });
|
|
168
140
|
if (isRunMarkdownPath(logPath)) {
|
|
169
|
-
const
|
|
170
|
-
await
|
|
171
|
-
return { logPath: path.join(
|
|
141
|
+
const transfer = input.result.ok ? transferOneShotRunToCompleted : transferOneShotRunToFailed;
|
|
142
|
+
const destDir = await transfer(input.cwd, path.dirname(logPath), path.basename(path.dirname(logPath)));
|
|
143
|
+
return { logPath: path.join(destDir, 'run.md'), completed: input.result.ok };
|
|
172
144
|
}
|
|
173
145
|
if (!input.result.ok) {
|
|
174
146
|
return { logPath, completed: false };
|
|
175
147
|
}
|
|
176
|
-
const completedPath =
|
|
177
|
-
try {
|
|
178
|
-
await rename(logPath, completedPath);
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
await writeFile(completedPath, content, 'utf-8');
|
|
182
|
-
await unlink(logPath).catch(() => { });
|
|
183
|
-
}
|
|
148
|
+
const completedPath = await finalizeLegacyRunLogToCompleted(input.cwd, logPath, content);
|
|
184
149
|
return { logPath: completedPath, completed: true };
|
|
185
150
|
}
|
|
186
151
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { getArtifactPath } from '../shared/artifacts-core.js';
|
|
3
|
+
import { getArtifactPath, writeArtifactForStep } from '../shared/artifacts-core.js';
|
|
4
4
|
import { getDagRunDir, readDagRunState } from '../workflows/dag/lifecycle.js';
|
|
5
5
|
import { parseCursorRunLog, resolveRunStatus } from './one-shot-runs.js';
|
|
6
6
|
import { getTaskDir } from '../task/runtime.js';
|
|
@@ -52,9 +52,8 @@ async function writePromotion(repoRoot, taskId, runId, sourceKind, modifyLog, ve
|
|
|
52
52
|
const taskDir = getTaskDir(repoRoot, taskId);
|
|
53
53
|
const modifyLogPath = getArtifactPath(taskDir, 'implement');
|
|
54
54
|
const verifyResultPath = getArtifactPath(taskDir, 'verify');
|
|
55
|
-
await
|
|
56
|
-
await
|
|
57
|
-
await writeFile(verifyResultPath, verifyResult, 'utf-8');
|
|
55
|
+
await writeArtifactForStep(taskDir, 'implement', modifyLog);
|
|
56
|
+
await writeArtifactForStep(taskDir, 'verify', verifyResult);
|
|
58
57
|
return { taskId, runId, sourceKind, modifyLogPath, verifyResultPath };
|
|
59
58
|
}
|
|
60
59
|
async function loadCompletedDagEvidence(repoRoot, runId) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
1
|
import path from 'node:path';
|
|
2
|
+
import { resolveTaskContextFromDir, writeTaskArtifactFile, } from '../infrastructure/harness/task-store.js';
|
|
3
3
|
export const ARTIFACTS_DIR = 'artifacts';
|
|
4
4
|
const SPEC_ARTIFACT_STEPS = ['analyze', 'plan'];
|
|
5
5
|
const DEFAULT_ARTIFACTS = {
|
|
@@ -42,15 +42,15 @@ const REQUIRED_HEADINGS = {
|
|
|
42
42
|
retrospective: ['# 复盘报告', '## 1. 质量复盘'],
|
|
43
43
|
};
|
|
44
44
|
export async function initializeArtifacts(taskDir) {
|
|
45
|
-
const
|
|
46
|
-
await
|
|
47
|
-
await Promise.all(Object.entries(DEFAULT_ARTIFACTS).map(([name, content]) => writeFile(path.join(artifactsDir, name), content, 'utf-8')));
|
|
45
|
+
const { repoRoot, taskId } = resolveTaskContextFromDir(taskDir);
|
|
46
|
+
await Promise.all(Object.entries(DEFAULT_ARTIFACTS).map(([name, content]) => writeTaskArtifactFile(repoRoot, taskId, name, content)));
|
|
48
47
|
}
|
|
49
48
|
export function getArtifactPath(taskDir, step) {
|
|
50
49
|
return path.join(taskDir, ARTIFACTS_DIR, getArtifactFileName(step));
|
|
51
50
|
}
|
|
52
51
|
export async function writeArtifactForStep(taskDir, step, content) {
|
|
53
|
-
|
|
52
|
+
const { repoRoot, taskId } = resolveTaskContextFromDir(taskDir);
|
|
53
|
+
await writeTaskArtifactFile(repoRoot, taskId, getArtifactFileName(step), `${content.trim()}\n`);
|
|
54
54
|
}
|
|
55
55
|
function normalizeArtifactContent(content) {
|
|
56
56
|
return content.replace(/\r/g, '').trim().replace(/\s+/g, ' ');
|
package/dist/shared/logger.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'node:path';
|
|
1
|
+
import { appendTaskLogJsonlRecord, appendTaskWorkflowLog, resolveTaskContextFromDir, } from '../infrastructure/harness/task-store.js';
|
|
3
2
|
export async function appendWorkflowLog(taskDir, message) {
|
|
4
|
-
const
|
|
5
|
-
await
|
|
6
|
-
const line = `[${new Date().toISOString()}] ${message}\n`;
|
|
7
|
-
await appendFile(path.join(logsDir, 'workflow.log'), line, 'utf-8');
|
|
3
|
+
const { repoRoot, taskId } = resolveTaskContextFromDir(taskDir);
|
|
4
|
+
await appendTaskWorkflowLog(repoRoot, taskId, message);
|
|
8
5
|
}
|
|
9
6
|
export async function appendExecutorRecord(taskDir, record) {
|
|
10
|
-
const
|
|
11
|
-
await
|
|
12
|
-
await appendFile(path.join(logsDir, 'executor.jsonl'), `${JSON.stringify(record)}\n`, 'utf-8');
|
|
7
|
+
const { repoRoot, taskId } = resolveTaskContextFromDir(taskDir);
|
|
8
|
+
await appendTaskLogJsonlRecord(repoRoot, taskId, 'executor.jsonl', record);
|
|
13
9
|
}
|
|
14
10
|
export async function appendDecisionRecord(taskDir, record) {
|
|
15
|
-
const
|
|
16
|
-
await
|
|
17
|
-
await appendFile(path.join(logsDir, 'decisions.jsonl'), `${JSON.stringify(record)}\n`, 'utf-8');
|
|
11
|
+
const { repoRoot, taskId } = resolveTaskContextFromDir(taskDir);
|
|
12
|
+
await appendTaskLogJsonlRecord(repoRoot, taskId, 'decisions.jsonl', record);
|
|
18
13
|
}
|
|
19
14
|
export async function appendGoalEventRecord(taskDir, record) {
|
|
20
|
-
const
|
|
21
|
-
await
|
|
22
|
-
await appendFile(path.join(logsDir, 'goal-events.jsonl'), `${JSON.stringify(record)}\n`, 'utf-8');
|
|
15
|
+
const { repoRoot, taskId } = resolveTaskContextFromDir(taskDir);
|
|
16
|
+
await appendTaskLogJsonlRecord(repoRoot, taskId, 'goal-events.jsonl', record);
|
|
23
17
|
}
|