@tea-agent/loop-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +121 -0
- package/CHANGELOG.md +37 -0
- package/README.md +144 -0
- package/bin/loop-agent.js +21 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/loop-agent.js +98 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli/catalog.js +259 -0
- package/dist/cli/help.js +55 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/program.js +505 -0
- package/dist/cli.js +12 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/cursor-prompt.js +222 -0
- package/dist/commands/cursor-worker.js +43 -0
- package/dist/commands/dag-approve.js +102 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +56 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +177 -0
- package/dist/commands/dag-resume.js +34 -0
- package/dist/commands/dag-run-task.js +470 -0
- package/dist/commands/dag-validate.js +186 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +132 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +5 -0
- package/dist/commands/doctor.js +50 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +195 -0
- package/dist/commands/knowledge.js +64 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +241 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/pi-prompt.js +181 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag.js +184 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +84 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/cursor-worker-entry.js +8 -0
- package/dist/executors/config-core.js +55 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/cursor-artifacts.js +33 -0
- package/dist/executors/cursor-execution-log.js +81 -0
- package/dist/executors/cursor-executor-artifacts.js +135 -0
- package/dist/executors/cursor-executor.js +468 -0
- package/dist/executors/cursor-run.js +115 -0
- package/dist/executors/cursor-tool.js +94 -0
- package/dist/executors/cursor-worker-client.js +213 -0
- package/dist/executors/cursor-worker-protocol.js +18 -0
- package/dist/executors/cursor-worker-server.js +54 -0
- package/dist/executors/cursor-worker.js +3 -0
- package/dist/executors/cursor.js +6 -0
- package/dist/executors/dag-cursor-executor.js +88 -0
- package/dist/executors/dag-pi-executor.js +322 -0
- package/dist/executors/dag-static-executor.js +45 -0
- package/dist/executors/dag.js +4 -0
- package/dist/executors/index.js +8 -0
- package/dist/executors/model-routing.js +60 -0
- package/dist/executors/pi-event-serializer.js +43 -0
- package/dist/executors/pi-executor.js +606 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +255 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/shell-executor.js +300 -0
- package/dist/executors/shell-presets.js +47 -0
- package/dist/executors/shell-verification.js +251 -0
- package/dist/executors/shell-write-guard.js +126 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +434 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +128 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +152 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +236 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +421 -0
- package/dist/records/promotion.js +199 -0
- package/dist/shared/artifacts-core.js +88 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +165 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +23 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +238 -0
- package/dist/shared/timeout-policy.js +19 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/task/config-types.js +97 -0
- package/dist/task/config.js +2 -0
- package/dist/task/delegate.js +220 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +9 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +149 -0
- package/dist/task/runtime.js +699 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +55 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +171 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/decision-envelope.js +502 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/executor-registry.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +111 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +405 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +855 -0
- package/dist/workflows/dag/knowledge-curator.js +162 -0
- package/dist/workflows/dag/lifecycle.js +484 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +130 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +226 -0
- package/dist/workflows/dag/repair-artifact.js +136 -0
- package/dist/workflows/dag/report.js +1019 -0
- package/dist/workflows/dag/runner.js +1677 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/skill-instructions.js +471 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +275 -0
- package/dist/workflows/dag/upstream-artifacts.js +95 -0
- package/dist/workflows/dag/validate.js +527 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +360 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +114 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions.js +1334 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +134 -0
- package/dist/workflows/loop/context.js +48 -0
- package/dist/workflows/loop/events.js +25 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +55 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +62 -0
- package/docs/agent-dag-recovery-playbook.md +158 -0
- package/docs/agent-dag-runner.md +40 -0
- package/docs/cursor-executor-usage.md +25 -0
- package/docs/decisions/README.md +3 -0
- package/docs/design/README.md +36 -0
- package/docs/development-principles.md +71 -0
- package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -0
- package/docs/exec-plans/README.md +6 -0
- package/docs/exec-plans/active/README.md +5 -0
- package/docs/exec-plans/completed/README.md +5 -0
- package/docs/feature-workflow.md +184 -0
- package/docs/harness-methodology-debugging.md +153 -0
- package/docs/harness-methodology-tdd.md +130 -0
- package/docs/harness-methodology-verification.md +27 -0
- package/docs/loop-agent-harness.md +42 -0
- package/docs/progress/README.md +3 -0
- package/docs/reports/README.md +3 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +423 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +195 -0
- package/docs/templates/agent-dag.final-verification.json +190 -0
- package/docs/templates/agent-dag.schema.json +316 -0
- package/docs/templates/agent-dag.supervised-implementation.json +500 -0
- package/docs/templates/exec-plan.md +64 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/hybrid-dag.json +193 -0
- package/docs/templates/progress-log.md +17 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +42 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/verification-matrix.md +30 -0
- package/examples/decision-gate-agent-dag.json +123 -0
- package/examples/example-dag.json +51 -0
- package/examples/hybrid-loop-agent-dag.json +194 -0
- package/harness.json +92 -0
- package/package.json +61 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/loop-agent/SKILL.md +260 -0
- package/skills/loop-agent/references/README.md +63 -0
- package/skills/loop-agent/references/command-reference.md +315 -0
- package/skills/loop-agent/references/harness-policy.md +258 -0
- package/skills/loop-agent/references/hybrid-dag.md +216 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/model-routing.md +36 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +85 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +83 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -0
- package/skills/loop-agent/references/task-workflow.md +84 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +74 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createDagCanvasObserver, resolveCanvasPath, } from "../workflows/dag/canvas-observer.js";
|
|
4
|
+
import { loadDagSpecFromFile, runDag } from "../workflows/dag/runner.js";
|
|
5
|
+
import { assertValidDagSpec, collectForbiddenExecutorIssues, } from "../workflows/dag/validate.js";
|
|
6
|
+
export function parseRunDagArgs(args, defaultCwd) {
|
|
7
|
+
if (args.length === 0) {
|
|
8
|
+
throw new Error("usage: run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]]");
|
|
9
|
+
}
|
|
10
|
+
let dagPath;
|
|
11
|
+
let cwd;
|
|
12
|
+
let initOnly = false;
|
|
13
|
+
let dryRun = false;
|
|
14
|
+
let maxConcurrent;
|
|
15
|
+
let runId;
|
|
16
|
+
let noCursor = false;
|
|
17
|
+
let canvasPath;
|
|
18
|
+
let canvasName;
|
|
19
|
+
let canvasesDir;
|
|
20
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === "--dag") {
|
|
23
|
+
dagPath = args[++i];
|
|
24
|
+
}
|
|
25
|
+
else if (arg.startsWith("--dag=")) {
|
|
26
|
+
dagPath = arg.slice(6);
|
|
27
|
+
}
|
|
28
|
+
else if (arg === "--cwd" || arg === "-C") {
|
|
29
|
+
cwd = args[++i];
|
|
30
|
+
}
|
|
31
|
+
else if (arg.startsWith("--cwd=")) {
|
|
32
|
+
cwd = arg.slice(6);
|
|
33
|
+
}
|
|
34
|
+
else if (arg === "--init-only") {
|
|
35
|
+
initOnly = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === "--dry-run") {
|
|
38
|
+
dryRun = true;
|
|
39
|
+
}
|
|
40
|
+
else if (arg === "--max-concurrent") {
|
|
41
|
+
maxConcurrent = Number(args[++i]);
|
|
42
|
+
}
|
|
43
|
+
else if (arg === "--run-id") {
|
|
44
|
+
runId = args[++i];
|
|
45
|
+
}
|
|
46
|
+
else if (arg === "--no-cursor") {
|
|
47
|
+
noCursor = true;
|
|
48
|
+
}
|
|
49
|
+
else if (arg === "--canvas-path") {
|
|
50
|
+
canvasPath = args[++i];
|
|
51
|
+
}
|
|
52
|
+
else if (arg.startsWith("--canvas-path=")) {
|
|
53
|
+
canvasPath = arg.slice(14);
|
|
54
|
+
}
|
|
55
|
+
else if (arg === "--canvas") {
|
|
56
|
+
canvasName = args[++i];
|
|
57
|
+
}
|
|
58
|
+
else if (arg.startsWith("--canvas=")) {
|
|
59
|
+
canvasName = arg.slice(9);
|
|
60
|
+
}
|
|
61
|
+
else if (arg === "--canvases-dir") {
|
|
62
|
+
canvasesDir = args[++i];
|
|
63
|
+
}
|
|
64
|
+
else if (arg.startsWith("--canvases-dir=")) {
|
|
65
|
+
canvasesDir = arg.slice(15);
|
|
66
|
+
}
|
|
67
|
+
else if (!arg.startsWith("-")) {
|
|
68
|
+
if (!dagPath)
|
|
69
|
+
dagPath = arg;
|
|
70
|
+
else
|
|
71
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
throw new Error(`unknown run-dag flag: ${arg}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!dagPath) {
|
|
78
|
+
throw new Error("run-dag requires --dag <path>");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
dagPath: path.resolve(dagPath),
|
|
82
|
+
cwd: path.resolve(cwd ?? defaultCwd ?? process.cwd()),
|
|
83
|
+
initOnly,
|
|
84
|
+
dryRun,
|
|
85
|
+
maxConcurrent,
|
|
86
|
+
runId,
|
|
87
|
+
noCursor,
|
|
88
|
+
canvasPath,
|
|
89
|
+
canvasName,
|
|
90
|
+
canvasesDir,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function loadRawTaskExecutorMap(dagPath) {
|
|
94
|
+
const raw = JSON.parse(await readFile(dagPath, "utf-8"));
|
|
95
|
+
const map = new Map();
|
|
96
|
+
for (const task of raw.tasks ?? []) {
|
|
97
|
+
if (typeof task.id === "string")
|
|
98
|
+
map.set(task.id, task.executor);
|
|
99
|
+
}
|
|
100
|
+
return map;
|
|
101
|
+
}
|
|
102
|
+
function assertNoCursorTasks(spec, rawTaskExecutors) {
|
|
103
|
+
const issues = collectForbiddenExecutorIssues(spec, ["cursor"]);
|
|
104
|
+
if (issues.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
const details = issues
|
|
107
|
+
.map((issue) => {
|
|
108
|
+
const implicitDefault = rawTaskExecutors.get(issue.taskId) === undefined;
|
|
109
|
+
return `task=${issue.taskId}${implicitDefault ? " (implicit default)" : ""}`;
|
|
110
|
+
})
|
|
111
|
+
.join("; ");
|
|
112
|
+
throw new Error(`--no-cursor forbids cursor executor tasks: ${details}`);
|
|
113
|
+
}
|
|
114
|
+
function getCanvasFlushErrorMessage(error) {
|
|
115
|
+
return error instanceof Error ? error.message : String(error);
|
|
116
|
+
}
|
|
117
|
+
async function flushCanvasSafely(canvas) {
|
|
118
|
+
if (!canvas)
|
|
119
|
+
return undefined;
|
|
120
|
+
try {
|
|
121
|
+
await canvas.flush();
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Canvas is a derived view; failed writes must not decide canonical DAG success.
|
|
126
|
+
return getCanvasFlushErrorMessage(error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function runRunDag(repoRoot, rawArgs) {
|
|
130
|
+
const parsed = parseRunDagArgs(rawArgs, repoRoot);
|
|
131
|
+
const spec = await loadDagSpecFromFile(parsed.dagPath);
|
|
132
|
+
if (parsed.noCursor) {
|
|
133
|
+
const rawTaskExecutors = await loadRawTaskExecutorMap(parsed.dagPath);
|
|
134
|
+
assertNoCursorTasks(spec, rawTaskExecutors);
|
|
135
|
+
}
|
|
136
|
+
assertValidDagSpec(spec);
|
|
137
|
+
const resolvedCanvasPath = resolveCanvasPath({
|
|
138
|
+
canvasPath: parsed.canvasPath,
|
|
139
|
+
canvasName: parsed.canvasName,
|
|
140
|
+
canvasesDir: parsed.canvasesDir,
|
|
141
|
+
cwd: parsed.cwd,
|
|
142
|
+
});
|
|
143
|
+
const canvas = resolvedCanvasPath
|
|
144
|
+
? createDagCanvasObserver({ canvasPath: resolvedCanvasPath, spec })
|
|
145
|
+
: undefined;
|
|
146
|
+
if (parsed.dryRun) {
|
|
147
|
+
const summary = await runDag(spec, {
|
|
148
|
+
cwd: parsed.cwd,
|
|
149
|
+
dryRun: true,
|
|
150
|
+
runId: parsed.runId,
|
|
151
|
+
observer: canvas?.observer,
|
|
152
|
+
});
|
|
153
|
+
const canvasError = await flushCanvasSafely(canvas);
|
|
154
|
+
console.log(JSON.stringify({
|
|
155
|
+
mode: "dry-run",
|
|
156
|
+
title: spec.title,
|
|
157
|
+
taskCount: spec.tasks.length,
|
|
158
|
+
ranks: summary.ranks,
|
|
159
|
+
runId: summary.runId,
|
|
160
|
+
runDir: summary.runDir,
|
|
161
|
+
...(resolvedCanvasPath ? { canvasPath: resolvedCanvasPath } : {}),
|
|
162
|
+
...(canvasError ? { canvasError } : {}),
|
|
163
|
+
}, null, 2));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const maxConcurrent = parsed.maxConcurrent;
|
|
167
|
+
const summary = await runDag(spec, {
|
|
168
|
+
cwd: parsed.cwd,
|
|
169
|
+
initOnly: parsed.initOnly,
|
|
170
|
+
dryRun: false,
|
|
171
|
+
maxConcurrent,
|
|
172
|
+
runId: parsed.runId,
|
|
173
|
+
observer: canvas?.observer,
|
|
174
|
+
});
|
|
175
|
+
const canvasError = await flushCanvasSafely(canvas);
|
|
176
|
+
const payload = resolvedCanvasPath
|
|
177
|
+
? {
|
|
178
|
+
...summary,
|
|
179
|
+
canvasPath: resolvedCanvasPath,
|
|
180
|
+
...(canvasError ? { canvasError } : {}),
|
|
181
|
+
}
|
|
182
|
+
: summary;
|
|
183
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
184
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { formatSpineAuditJson, formatSpineAuditMarkdown, runSpineAudit, } from "../governance/spine-audit.js";
|
|
2
|
+
export function parseSpineArgs(args) {
|
|
3
|
+
const [command, taskId, ...rest] = args;
|
|
4
|
+
if (command !== "audit" || !taskId) {
|
|
5
|
+
throw new Error("usage: spine audit <task-id> [--json|--markdown]");
|
|
6
|
+
}
|
|
7
|
+
let json = false;
|
|
8
|
+
let markdown = false;
|
|
9
|
+
for (const arg of rest) {
|
|
10
|
+
if (arg === "--json") {
|
|
11
|
+
json = true;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (arg === "--markdown") {
|
|
15
|
+
markdown = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`unknown spine audit argument: ${arg}`);
|
|
19
|
+
}
|
|
20
|
+
if (!json && !markdown)
|
|
21
|
+
json = true;
|
|
22
|
+
return { command, taskId, json, markdown };
|
|
23
|
+
}
|
|
24
|
+
export async function runSpine(repoRoot, args) {
|
|
25
|
+
const parsed = parseSpineArgs(args);
|
|
26
|
+
const audit = await runSpineAudit(repoRoot, parsed.taskId);
|
|
27
|
+
if (!audit.ok) {
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
if (parsed.json) {
|
|
31
|
+
process.stdout.write(formatSpineAuditJson(audit));
|
|
32
|
+
}
|
|
33
|
+
if (parsed.markdown) {
|
|
34
|
+
if (parsed.json)
|
|
35
|
+
process.stdout.write("\n");
|
|
36
|
+
process.stdout.write(formatSpineAuditMarkdown(audit));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function percentile(values, p) {
|
|
4
|
+
if (values.length === 0)
|
|
5
|
+
return null;
|
|
6
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
7
|
+
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
|
8
|
+
return sorted[Math.max(0, index)];
|
|
9
|
+
}
|
|
10
|
+
function formatMs(ms) {
|
|
11
|
+
if (ms >= 60_000)
|
|
12
|
+
return `${(ms / 60_000).toFixed(1)} min`;
|
|
13
|
+
return `${(ms / 1_000).toFixed(1)}s`;
|
|
14
|
+
}
|
|
15
|
+
export async function runStats(repoRoot) {
|
|
16
|
+
const tasksDir = path.join(repoRoot, '.harness', 'tasks');
|
|
17
|
+
const stepRecords = {};
|
|
18
|
+
let taskDirs = [];
|
|
19
|
+
try {
|
|
20
|
+
const entries = await readdir(tasksDir, { withFileTypes: true });
|
|
21
|
+
taskDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
console.log('no task directories found');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const taskId of taskDirs) {
|
|
28
|
+
const jsonlPath = path.join(tasksDir, taskId, 'logs', 'executor.jsonl');
|
|
29
|
+
try {
|
|
30
|
+
const content = await readFile(jsonlPath, 'utf-8');
|
|
31
|
+
for (const line of content.split(/\r?\n/)) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (!trimmed)
|
|
34
|
+
continue;
|
|
35
|
+
try {
|
|
36
|
+
const record = JSON.parse(trimmed);
|
|
37
|
+
if (typeof record.step === 'string' && typeof record.durationMs === 'number') {
|
|
38
|
+
const step = record.step;
|
|
39
|
+
if (!stepRecords[step])
|
|
40
|
+
stepRecords[step] = [];
|
|
41
|
+
stepRecords[step].push({
|
|
42
|
+
durationMs: record.durationMs,
|
|
43
|
+
ok: Boolean(record.ok),
|
|
44
|
+
step,
|
|
45
|
+
timedOut: Boolean(record.timedOut),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// skip unparsable lines
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// skip missing executor.jsonl
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const stepOrder = ['analyze', 'plan', 'spec', 'implement', 'verify', 'retrospective'];
|
|
59
|
+
let totalRecords = 0;
|
|
60
|
+
let totalTimedOut = 0;
|
|
61
|
+
for (const step of stepOrder) {
|
|
62
|
+
const records = stepRecords[step] ?? [];
|
|
63
|
+
if (records.length === 0)
|
|
64
|
+
continue;
|
|
65
|
+
totalRecords += records.length;
|
|
66
|
+
const durations = records.map((r) => r.durationMs);
|
|
67
|
+
const timedOutCount = records.filter((r) => r.timedOut).length;
|
|
68
|
+
totalTimedOut += timedOutCount;
|
|
69
|
+
console.log(`${step} (${records.length} runs)`);
|
|
70
|
+
console.log(` count : ${records.length}`);
|
|
71
|
+
console.log(` min : ${formatMs(Math.min(...durations))}`);
|
|
72
|
+
console.log(` P50 : ${formatMs(percentile(durations, 50))}`);
|
|
73
|
+
console.log(` P95 : ${formatMs(percentile(durations, 95))}`);
|
|
74
|
+
console.log(` P99 : ${formatMs(percentile(durations, 99))}`);
|
|
75
|
+
console.log(` max : ${formatMs(Math.max(...durations))}`);
|
|
76
|
+
if (timedOutCount > 0)
|
|
77
|
+
console.log(` timedOut: ${timedOutCount}`);
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
console.log(`${'='.repeat(40)}`);
|
|
81
|
+
console.log(`total runs: ${totalRecords}`);
|
|
82
|
+
if (totalTimedOut > 0)
|
|
83
|
+
console.log(`total timed out: ${totalTimedOut}`);
|
|
84
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getTaskDir, getTaskPaths, getTaskStatus, loadTaskConfig, refreshSourceState, } from '../task/runtime.js';
|
|
2
|
+
import { getArtifactPath } from '../shared/artifacts-core.js';
|
|
3
|
+
import { buildTaskReadModel } from '../task/read-model.js';
|
|
4
|
+
export async function runStatus(repoRoot, taskId) {
|
|
5
|
+
await refreshSourceState(repoRoot, taskId);
|
|
6
|
+
const state = await getTaskStatus(repoRoot, taskId);
|
|
7
|
+
const taskConfig = await loadTaskConfig(repoRoot, taskId);
|
|
8
|
+
const taskDir = getTaskDir(repoRoot, taskId);
|
|
9
|
+
const taskPaths = getTaskPaths(repoRoot, taskId);
|
|
10
|
+
const readModel = await buildTaskReadModel(repoRoot, taskId);
|
|
11
|
+
console.log(JSON.stringify({
|
|
12
|
+
taskId,
|
|
13
|
+
taskDir,
|
|
14
|
+
taskRoot: taskDir,
|
|
15
|
+
sourceOfTruth: 'task',
|
|
16
|
+
artifactPaths: {
|
|
17
|
+
taskConfig: taskPaths.taskConfigPath,
|
|
18
|
+
workflowState: taskPaths.statePath,
|
|
19
|
+
sourceDir: taskPaths.sourceDir,
|
|
20
|
+
artifactsDir: `${taskDir}/artifacts`,
|
|
21
|
+
analysis: getArtifactPath(taskDir, 'analyze'),
|
|
22
|
+
plan: getArtifactPath(taskDir, 'plan'),
|
|
23
|
+
modifyLog: getArtifactPath(taskDir, 'implement'),
|
|
24
|
+
verifyResult: getArtifactPath(taskDir, 'verify'),
|
|
25
|
+
retrospective: getArtifactPath(taskDir, 'retrospective'),
|
|
26
|
+
},
|
|
27
|
+
runRefs: {
|
|
28
|
+
oneShotRuns: readModel.runRefs.oneShotRuns,
|
|
29
|
+
dagRuns: readModel.runRefs.dagRuns,
|
|
30
|
+
},
|
|
31
|
+
readModel,
|
|
32
|
+
actionContext: {
|
|
33
|
+
mode: 'dag',
|
|
34
|
+
allowedEditRoots: taskConfig.allowedPaths,
|
|
35
|
+
forbiddenPaths: taskConfig.forbiddenPaths,
|
|
36
|
+
constraints: taskConfig.hardConstraints,
|
|
37
|
+
requiresPromotion: readModel.promotion.required && !readModel.promotion.ready,
|
|
38
|
+
},
|
|
39
|
+
currentStep: state.currentStep,
|
|
40
|
+
status: state.status,
|
|
41
|
+
complexity: taskConfig.complexity,
|
|
42
|
+
maxFixLoops: taskConfig.maxFixLoops,
|
|
43
|
+
verifyEnv: taskConfig.verifyEnv ?? 'clean',
|
|
44
|
+
maxGoalContinuationsPerRun: taskConfig.maxGoalContinuationsPerRun ?? 5,
|
|
45
|
+
goal: state.goal,
|
|
46
|
+
sourceState: state.sourceState,
|
|
47
|
+
completedSteps: state.completedSteps,
|
|
48
|
+
decisionLogSize: state.decisionLog.length,
|
|
49
|
+
artifacts: state.artifacts,
|
|
50
|
+
nextActions: [
|
|
51
|
+
readModel.nextAction.command,
|
|
52
|
+
`npm run dev -- instructions dag-draft --task ${taskId} --json`,
|
|
53
|
+
],
|
|
54
|
+
nextSuggestedCommand: readModel.nextAction.command,
|
|
55
|
+
}, null, 2));
|
|
56
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature-study task initialization (compatibility-only).
|
|
3
|
+
* Creates taskKind=feature-study tasks for external reference analysis.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { buildReferenceIndex, parseReferenceDocArg, parseReferenceRepoArg } from '../shared/reference-context.js';
|
|
8
|
+
import { createTask, getTaskPaths, loadTaskConfig } from '../task/runtime.js';
|
|
9
|
+
function parseStudyInitArgs(args) {
|
|
10
|
+
const referenceRepos = [];
|
|
11
|
+
const referenceDocs = [];
|
|
12
|
+
const referenceGlobs = [];
|
|
13
|
+
let title;
|
|
14
|
+
let targetRepoLabel = 'aimax';
|
|
15
|
+
let storageModel = 'json-dir-markdown';
|
|
16
|
+
const positional = [];
|
|
17
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
18
|
+
const arg = args[i];
|
|
19
|
+
if (arg === '--reference-repo') {
|
|
20
|
+
const value = args[i + 1];
|
|
21
|
+
if (!value)
|
|
22
|
+
throw new Error('usage: study init <task-id> --reference-repo name:/absolute/path');
|
|
23
|
+
referenceRepos.push(parseReferenceRepoArg(value));
|
|
24
|
+
i += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (arg === '--reference-doc') {
|
|
28
|
+
const value = args[i + 1];
|
|
29
|
+
if (!value)
|
|
30
|
+
throw new Error('usage: study init <task-id> --reference-doc [/path | name:/path]');
|
|
31
|
+
referenceDocs.push(parseReferenceDocArg(value));
|
|
32
|
+
i += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === '--reference-glob') {
|
|
36
|
+
const value = args[i + 1];
|
|
37
|
+
if (!value)
|
|
38
|
+
throw new Error('usage: study init <task-id> --reference-glob <glob>');
|
|
39
|
+
referenceGlobs.push(value);
|
|
40
|
+
i += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === '--target-repo') {
|
|
44
|
+
const value = args[i + 1];
|
|
45
|
+
if (!value)
|
|
46
|
+
throw new Error('usage: study init <task-id> --target-repo <label>');
|
|
47
|
+
targetRepoLabel = value;
|
|
48
|
+
i += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === '--storage-model') {
|
|
52
|
+
const value = args[i + 1];
|
|
53
|
+
if (!value)
|
|
54
|
+
throw new Error('usage: study init <task-id> --storage-model json-dir-markdown');
|
|
55
|
+
storageModel = value;
|
|
56
|
+
i += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
positional.push(arg);
|
|
60
|
+
}
|
|
61
|
+
const [taskId, ...titleParts] = positional;
|
|
62
|
+
if (!taskId) {
|
|
63
|
+
throw new Error('usage: study init <task-id> [title] [--reference-repo name:/path] [--reference-doc path] [--reference-glob glob]');
|
|
64
|
+
}
|
|
65
|
+
if (titleParts.length > 0) {
|
|
66
|
+
title = titleParts.join(' ');
|
|
67
|
+
}
|
|
68
|
+
if (referenceGlobs.length > 0) {
|
|
69
|
+
for (const repo of referenceRepos) {
|
|
70
|
+
repo.focusGlobs = [...new Set([...(repo.focusGlobs ?? []), ...referenceGlobs])];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
taskId,
|
|
75
|
+
title,
|
|
76
|
+
referenceRepos,
|
|
77
|
+
referenceDocs,
|
|
78
|
+
referenceGlobs,
|
|
79
|
+
targetRepoLabel,
|
|
80
|
+
storageModel,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function runStudyInit(repoRoot, args) {
|
|
84
|
+
const options = parseStudyInitArgs(args);
|
|
85
|
+
await createTask(repoRoot, options.taskId, options.title ?? options.taskId);
|
|
86
|
+
const paths = getTaskPaths(repoRoot, options.taskId);
|
|
87
|
+
const taskConfig = await loadTaskConfig(repoRoot, options.taskId);
|
|
88
|
+
const nextConfig = {
|
|
89
|
+
...taskConfig,
|
|
90
|
+
taskKind: 'feature-study',
|
|
91
|
+
contextProfile: 'slim',
|
|
92
|
+
complexity: 'large',
|
|
93
|
+
referenceRepos: options.referenceRepos,
|
|
94
|
+
referenceDocs: options.referenceDocs,
|
|
95
|
+
hardConstraints: [
|
|
96
|
+
`目标仓库: ${options.targetRepoLabel}`,
|
|
97
|
+
`存储模型: ${options.storageModel}(禁止引入 SQLite 作为 goal 状态主存储)`,
|
|
98
|
+
'必须先完成参考特性分析,再输出可落地的轻量实现计划',
|
|
99
|
+
],
|
|
100
|
+
notes: 'feature-study task created by study init',
|
|
101
|
+
};
|
|
102
|
+
delete nextConfig.flow;
|
|
103
|
+
await writeFile(paths.taskConfigPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf-8');
|
|
104
|
+
await mkdir(paths.sourceDir, { recursive: true });
|
|
105
|
+
await writeFile(path.join(paths.sourceDir, '需求.md'), renderRequirementTemplate(options), 'utf-8');
|
|
106
|
+
await writeFile(path.join(paths.sourceDir, '执行约束.md'), renderConstraintTemplate(options), 'utf-8');
|
|
107
|
+
await writeFile(path.join(paths.sourceDir, '参考说明.md'), renderReferenceNotesTemplate(options), 'utf-8');
|
|
108
|
+
let indexed = false;
|
|
109
|
+
if (options.referenceRepos.length > 0 || options.referenceDocs.length > 0) {
|
|
110
|
+
await buildReferenceIndex(repoRoot, options.taskId, nextConfig);
|
|
111
|
+
indexed = true;
|
|
112
|
+
}
|
|
113
|
+
console.log(JSON.stringify({
|
|
114
|
+
taskId: options.taskId,
|
|
115
|
+
taskDir: paths.taskDir,
|
|
116
|
+
taskKind: 'feature-study',
|
|
117
|
+
indexed,
|
|
118
|
+
referenceRepos: options.referenceRepos.map((repo) => repo.name),
|
|
119
|
+
referenceDocs: options.referenceDocs.map((doc) => doc.name ?? doc.path),
|
|
120
|
+
next: [
|
|
121
|
+
'edit source/需求.md and source/执行约束.md',
|
|
122
|
+
indexed
|
|
123
|
+
? `npm run dev -- dag run-task ${options.taskId} --profile auto --strict-models`
|
|
124
|
+
: `npm run dev -- reference index ${options.taskId}; npm run dev -- dag run-task ${options.taskId} --profile auto --strict-models`,
|
|
125
|
+
],
|
|
126
|
+
}, null, 2));
|
|
127
|
+
}
|
|
128
|
+
function renderRequirementTemplate(options) {
|
|
129
|
+
return [
|
|
130
|
+
'# 需求',
|
|
131
|
+
'',
|
|
132
|
+
'## 背景',
|
|
133
|
+
'',
|
|
134
|
+
'- 参考外部代码库中的既有特性,分析其设计与实现原理',
|
|
135
|
+
'- 在目标仓库中以更轻量的方式落地等价能力',
|
|
136
|
+
'',
|
|
137
|
+
'## 目标',
|
|
138
|
+
'',
|
|
139
|
+
'- [ ] 完成参考特性分析(架构、状态机、数据流、边界)',
|
|
140
|
+
`- [ ] 在 ${options.targetRepoLabel} 设计 ${options.storageModel} 存储与状态管理方案`,
|
|
141
|
+
'- [ ] 输出可执行的实现计划并通过 verify',
|
|
142
|
+
'',
|
|
143
|
+
'## 验收标准',
|
|
144
|
+
'',
|
|
145
|
+
'- 分析报告能定位参考实现的关键模块与调用链',
|
|
146
|
+
'- 实现计划明确目录/JSON/Markdown 文件布局,不依赖 SQLite',
|
|
147
|
+
'- 实现后具备最小可用的 set/get/pause/resume/clear(或等价能力)',
|
|
148
|
+
'',
|
|
149
|
+
].join('\n');
|
|
150
|
+
}
|
|
151
|
+
function renderConstraintTemplate(options) {
|
|
152
|
+
return [
|
|
153
|
+
'# 执行约束',
|
|
154
|
+
'',
|
|
155
|
+
'## 目标仓库',
|
|
156
|
+
'',
|
|
157
|
+
`- ${options.targetRepoLabel}`,
|
|
158
|
+
'',
|
|
159
|
+
'## 存储约束',
|
|
160
|
+
'',
|
|
161
|
+
`- 必须使用 ${options.storageModel}`,
|
|
162
|
+
'- 禁止把 SQLite 作为 thread/task goal 的主存储',
|
|
163
|
+
'- 状态应可通过目录 + JSON 文件恢复',
|
|
164
|
+
'',
|
|
165
|
+
'## 范围',
|
|
166
|
+
'',
|
|
167
|
+
'- 优先最小可用闭环,不做过度抽象',
|
|
168
|
+
'- 参考代码只读,不在参考仓库内提交改动',
|
|
169
|
+
'',
|
|
170
|
+
].join('\n');
|
|
171
|
+
}
|
|
172
|
+
function renderReferenceNotesTemplate(options) {
|
|
173
|
+
const repos = options.referenceRepos.map((repo) => `- ${repo.name}: \`${repo.path}\``).join('\n') || '- (未配置)';
|
|
174
|
+
const docs = options.referenceDocs.map((doc) => `- ${doc.name ?? path.basename(doc.path)}: \`${doc.path}\``).join('\n') || '- (未配置)';
|
|
175
|
+
return [
|
|
176
|
+
'# 参考说明',
|
|
177
|
+
'',
|
|
178
|
+
'## 参考仓库',
|
|
179
|
+
'',
|
|
180
|
+
repos,
|
|
181
|
+
'',
|
|
182
|
+
'## 参考文档',
|
|
183
|
+
'',
|
|
184
|
+
docs,
|
|
185
|
+
'',
|
|
186
|
+
'## 索引',
|
|
187
|
+
'',
|
|
188
|
+
'- 运行 `reference index <task-id>` 会生成 `source/references/index.json`',
|
|
189
|
+
'- analyze/plan 步骤会自动附带索引中的参考文件',
|
|
190
|
+
'',
|
|
191
|
+
].join('\n');
|
|
192
|
+
}
|