@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,186 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadDagSpecFromFile } from "../workflows/dag/runner.js";
|
|
4
|
+
import { assertValidDagSpec, collectExecutorModelWarnings, collectForbiddenExecutorIssues, collectGovernanceWarnings, validateDagSpec, } from "../workflows/dag/validate.js";
|
|
5
|
+
import { inferGovernanceProfile } from "../workflows/dag/governance-profile.js";
|
|
6
|
+
import { topoSortToRanks } from "../workflows/dag/topo.js";
|
|
7
|
+
import { runSpineAudit } from "../governance/spine-audit.js";
|
|
8
|
+
export function parseDagValidateArgs(args) {
|
|
9
|
+
if (args.length === 0) {
|
|
10
|
+
throw new Error("usage: dag validate --dag <path> [--strict-models] [--strict-governance]");
|
|
11
|
+
}
|
|
12
|
+
let dagPath;
|
|
13
|
+
let strictModelMatrix = false;
|
|
14
|
+
let strictGovernance = false;
|
|
15
|
+
const forbidExecutors = [];
|
|
16
|
+
let spineTaskId;
|
|
17
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
18
|
+
const arg = args[i];
|
|
19
|
+
if (arg === "--dag") {
|
|
20
|
+
dagPath = args[++i];
|
|
21
|
+
if (!dagPath) {
|
|
22
|
+
throw new Error("dag validate --dag requires a path");
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg.startsWith("--dag=")) {
|
|
27
|
+
dagPath = arg.slice("--dag=".length);
|
|
28
|
+
if (!dagPath) {
|
|
29
|
+
throw new Error("dag validate --dag requires a path");
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (arg === "--strict-models") {
|
|
34
|
+
strictModelMatrix = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (arg === "--strict-governance") {
|
|
38
|
+
strictGovernance = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (arg === "--forbid-executor") {
|
|
42
|
+
const executor = args[++i];
|
|
43
|
+
if (!executor) {
|
|
44
|
+
throw new Error("dag validate --forbid-executor requires an executor");
|
|
45
|
+
}
|
|
46
|
+
forbidExecutors.push(executor);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg === "--spine-task") {
|
|
50
|
+
spineTaskId = args[++i];
|
|
51
|
+
if (!spineTaskId) {
|
|
52
|
+
throw new Error("dag validate --spine-task requires a task id");
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (arg.startsWith("--spine-task=")) {
|
|
57
|
+
spineTaskId = arg.slice("--spine-task=".length);
|
|
58
|
+
if (!spineTaskId) {
|
|
59
|
+
throw new Error("dag validate --spine-task requires a task id");
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg.startsWith("--forbid-executor=")) {
|
|
64
|
+
const executor = arg.slice("--forbid-executor=".length);
|
|
65
|
+
if (!executor) {
|
|
66
|
+
throw new Error("dag validate --forbid-executor requires an executor");
|
|
67
|
+
}
|
|
68
|
+
forbidExecutors.push(executor);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg.startsWith("-")) {
|
|
72
|
+
throw new Error(`unknown dag validate flag: ${arg}`);
|
|
73
|
+
}
|
|
74
|
+
if (dagPath) {
|
|
75
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
76
|
+
}
|
|
77
|
+
dagPath = arg;
|
|
78
|
+
}
|
|
79
|
+
if (!dagPath) {
|
|
80
|
+
throw new Error("dag validate requires --dag <path>");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
dagPath: path.resolve(dagPath),
|
|
84
|
+
strictModelMatrix,
|
|
85
|
+
strictGovernance,
|
|
86
|
+
forbidExecutors,
|
|
87
|
+
spineTaskId,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function formatModelMatrixWarnings(warnings) {
|
|
91
|
+
return warnings.map((warning) => ({
|
|
92
|
+
executor: warning.executor,
|
|
93
|
+
complexity: warning.complexity,
|
|
94
|
+
expected: warning.expected,
|
|
95
|
+
actual: warning.actual,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
function formatGovernanceWarnings(warnings) {
|
|
99
|
+
return warnings.map((warning) => ({
|
|
100
|
+
type: warning.type,
|
|
101
|
+
message: warning.message,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
function ensureStrictModelMatrix(warnings) {
|
|
105
|
+
if (warnings.length === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const details = warnings
|
|
109
|
+
.map((entry) => `executor=${entry.executor}, complexity=${entry.complexity}, expected=${entry.expected}, actual=${entry.actual}`)
|
|
110
|
+
.join("; ");
|
|
111
|
+
throw new Error(`non-canonical executorModels overrides are blocked by --strict-models: ${details}`);
|
|
112
|
+
}
|
|
113
|
+
async function loadRawTaskExecutorMap(dagPath) {
|
|
114
|
+
const raw = JSON.parse(await readFile(dagPath, "utf-8"));
|
|
115
|
+
const map = new Map();
|
|
116
|
+
for (const task of raw.tasks ?? []) {
|
|
117
|
+
if (typeof task.id === "string")
|
|
118
|
+
map.set(task.id, task.executor);
|
|
119
|
+
}
|
|
120
|
+
return map;
|
|
121
|
+
}
|
|
122
|
+
function ensureNoForbiddenExecutors(spec, forbidExecutors, rawTaskExecutors) {
|
|
123
|
+
const supported = new Set(["cursor", "pi", "shell", "static"]);
|
|
124
|
+
const normalized = forbidExecutors.map((executor) => {
|
|
125
|
+
if (!supported.has(executor)) {
|
|
126
|
+
throw new Error(`unsupported executor for --forbid-executor: ${executor}`);
|
|
127
|
+
}
|
|
128
|
+
return executor;
|
|
129
|
+
});
|
|
130
|
+
const issues = collectForbiddenExecutorIssues(spec, normalized);
|
|
131
|
+
if (issues.length === 0)
|
|
132
|
+
return;
|
|
133
|
+
const details = issues
|
|
134
|
+
.map((issue) => {
|
|
135
|
+
const implicitDefault = rawTaskExecutors.get(issue.taskId) === undefined;
|
|
136
|
+
return `task=${issue.taskId}, executor=${issue.executor}${implicitDefault ? " (implicit default)" : ""}`;
|
|
137
|
+
})
|
|
138
|
+
.join("; ");
|
|
139
|
+
throw new Error(`forbidden executor present: ${details}`);
|
|
140
|
+
}
|
|
141
|
+
export async function runDagValidate(repoRoot, rawArgs) {
|
|
142
|
+
const parsed = parseDagValidateArgs(rawArgs);
|
|
143
|
+
const spec = await loadDagSpecFromFile(parsed.dagPath);
|
|
144
|
+
const rawTaskExecutors = await loadRawTaskExecutorMap(parsed.dagPath);
|
|
145
|
+
ensureNoForbiddenExecutors(spec, parsed.forbidExecutors, rawTaskExecutors);
|
|
146
|
+
const validationIssues = validateDagSpec(spec);
|
|
147
|
+
const warnings = collectExecutorModelWarnings(spec);
|
|
148
|
+
const governanceWarnings = collectGovernanceWarnings(validationIssues);
|
|
149
|
+
if (parsed.strictModelMatrix) {
|
|
150
|
+
ensureStrictModelMatrix(warnings);
|
|
151
|
+
}
|
|
152
|
+
const spineAudit = parsed.spineTaskId
|
|
153
|
+
? await runSpineAudit(repoRoot, parsed.spineTaskId)
|
|
154
|
+
: undefined;
|
|
155
|
+
if (parsed.strictGovernance && spineAudit && !spineAudit.ok) {
|
|
156
|
+
throw new Error(`minimal spec spine audit failed for ${parsed.spineTaskId}: ${spineAudit.findings
|
|
157
|
+
.filter((finding) => finding.severity === "error")
|
|
158
|
+
.map((finding) => `${finding.type}: ${finding.message}`)
|
|
159
|
+
.join("; ")}`);
|
|
160
|
+
}
|
|
161
|
+
assertValidDagSpec(spec, {
|
|
162
|
+
issues: validationIssues,
|
|
163
|
+
strictGovernance: parsed.strictGovernance,
|
|
164
|
+
});
|
|
165
|
+
const { ranks } = topoSortToRanks(spec);
|
|
166
|
+
const governanceProfile = inferGovernanceProfile(spec);
|
|
167
|
+
console.log(JSON.stringify({
|
|
168
|
+
mode: "validate",
|
|
169
|
+
title: spec.title,
|
|
170
|
+
taskCount: spec.tasks.length,
|
|
171
|
+
ranks,
|
|
172
|
+
governanceProfile,
|
|
173
|
+
spineAudit: spineAudit
|
|
174
|
+
? {
|
|
175
|
+
ok: spineAudit.ok,
|
|
176
|
+
taskId: spineAudit.taskId,
|
|
177
|
+
findings: spineAudit.findings,
|
|
178
|
+
verifyCommandLabels: spineAudit.verifyCommandLabels,
|
|
179
|
+
}
|
|
180
|
+
: undefined,
|
|
181
|
+
warnings: [
|
|
182
|
+
...formatModelMatrixWarnings(warnings),
|
|
183
|
+
...formatGovernanceWarnings(governanceWarnings),
|
|
184
|
+
],
|
|
185
|
+
}, null, 2));
|
|
186
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compileWorkflowToDag, defaultCompileManifestPath, } from "../workflows/dynamic/compile.js";
|
|
4
|
+
import { parseWorkflowSpec } from "../workflows/dynamic/validate.js";
|
|
5
|
+
import { resolveCliPath } from "../shared/path-refs.js";
|
|
6
|
+
export function parseDagWorkflowCompileArgs(args) {
|
|
7
|
+
if (args.length === 0) {
|
|
8
|
+
throw new Error("usage: dag workflow-compile --workflow <path> --output <path> [--manifest <path>]");
|
|
9
|
+
}
|
|
10
|
+
let workflowPath;
|
|
11
|
+
let outputPath;
|
|
12
|
+
let manifestPath;
|
|
13
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
14
|
+
const arg = args[i];
|
|
15
|
+
if (arg === "--workflow") {
|
|
16
|
+
workflowPath = args[++i];
|
|
17
|
+
if (!workflowPath) {
|
|
18
|
+
throw new Error("dag workflow-compile --workflow requires a path");
|
|
19
|
+
}
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg.startsWith("--workflow=")) {
|
|
23
|
+
workflowPath = arg.slice("--workflow=".length);
|
|
24
|
+
if (!workflowPath) {
|
|
25
|
+
throw new Error("dag workflow-compile --workflow requires a path");
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (arg === "--output") {
|
|
30
|
+
outputPath = args[++i];
|
|
31
|
+
if (!outputPath) {
|
|
32
|
+
throw new Error("dag workflow-compile --output requires a path");
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg.startsWith("--output=")) {
|
|
37
|
+
outputPath = arg.slice("--output=".length);
|
|
38
|
+
if (!outputPath) {
|
|
39
|
+
throw new Error("dag workflow-compile --output requires a path");
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === "--manifest") {
|
|
44
|
+
manifestPath = args[++i];
|
|
45
|
+
if (!manifestPath) {
|
|
46
|
+
throw new Error("dag workflow-compile --manifest requires a path");
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (arg.startsWith("--manifest=")) {
|
|
51
|
+
manifestPath = arg.slice("--manifest=".length);
|
|
52
|
+
if (!manifestPath) {
|
|
53
|
+
throw new Error("dag workflow-compile --manifest requires a path");
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (arg.startsWith("-")) {
|
|
58
|
+
throw new Error(`unknown dag workflow-compile flag: ${arg}`);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
61
|
+
}
|
|
62
|
+
if (!workflowPath) {
|
|
63
|
+
throw new Error("dag workflow-compile requires --workflow <path>");
|
|
64
|
+
}
|
|
65
|
+
if (!outputPath) {
|
|
66
|
+
throw new Error("dag workflow-compile requires --output <path>");
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
workflowPath: resolveCliPath(workflowPath),
|
|
70
|
+
outputPath: resolveCliPath(outputPath),
|
|
71
|
+
manifestPath: manifestPath ? resolveCliPath(manifestPath) : undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export async function runDagWorkflowCompile(_repoRoot, rawArgs) {
|
|
75
|
+
const parsed = parseDagWorkflowCompileArgs(rawArgs);
|
|
76
|
+
const raw = JSON.parse(await readFile(parsed.workflowPath, "utf-8"));
|
|
77
|
+
const spec = parseWorkflowSpec(raw);
|
|
78
|
+
const result = compileWorkflowToDag(spec, { sourcePath: parsed.workflowPath });
|
|
79
|
+
const manifestPath = parsed.manifestPath ?? defaultCompileManifestPath(parsed.outputPath);
|
|
80
|
+
await mkdir(path.dirname(parsed.outputPath), { recursive: true });
|
|
81
|
+
await mkdir(path.dirname(manifestPath), { recursive: true });
|
|
82
|
+
await writeFile(parsed.outputPath, `${JSON.stringify(result.dag, null, 2)}\n`, "utf-8");
|
|
83
|
+
await writeFile(manifestPath, `${JSON.stringify(result.manifest, null, 2)}\n`, "utf-8");
|
|
84
|
+
console.log(JSON.stringify({
|
|
85
|
+
mode: "workflow-compile",
|
|
86
|
+
name: spec.meta.name,
|
|
87
|
+
nodeCount: spec.nodes.length,
|
|
88
|
+
outputPath: parsed.outputPath,
|
|
89
|
+
manifestPath,
|
|
90
|
+
}, null, 2));
|
|
91
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { WORKFLOW_PLAN_PROFILES, buildWorkflowSpecForProfile, } from "../workflows/dynamic/profiles.js";
|
|
4
|
+
import { assertValidWorkflowSpec } from "../workflows/dynamic/validate.js";
|
|
5
|
+
import { resolveCliPath } from "../shared/path-refs.js";
|
|
6
|
+
function isWorkflowPlanProfile(value) {
|
|
7
|
+
return WORKFLOW_PLAN_PROFILES.includes(value);
|
|
8
|
+
}
|
|
9
|
+
export function parseDagWorkflowPlanArgs(args) {
|
|
10
|
+
if (args.length === 0) {
|
|
11
|
+
throw new Error("usage: dag workflow-plan <task-id> --profile pr-review --output <path> [--changed-file <path>]...");
|
|
12
|
+
}
|
|
13
|
+
const [taskId, ...rest] = args;
|
|
14
|
+
if (!taskId || taskId.startsWith("-")) {
|
|
15
|
+
throw new Error("dag workflow-plan requires <task-id>");
|
|
16
|
+
}
|
|
17
|
+
let profile;
|
|
18
|
+
let outputPath;
|
|
19
|
+
let base;
|
|
20
|
+
let head;
|
|
21
|
+
const changedFiles = [];
|
|
22
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
23
|
+
const arg = rest[i];
|
|
24
|
+
if (arg === "--profile") {
|
|
25
|
+
const value = rest[++i];
|
|
26
|
+
if (!value)
|
|
27
|
+
throw new Error("dag workflow-plan --profile requires a value");
|
|
28
|
+
if (!isWorkflowPlanProfile(value)) {
|
|
29
|
+
throw new Error(`unsupported workflow profile: ${value} (expected ${WORKFLOW_PLAN_PROFILES.join("|")})`);
|
|
30
|
+
}
|
|
31
|
+
profile = value;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg.startsWith("--profile=")) {
|
|
35
|
+
const value = arg.slice("--profile=".length);
|
|
36
|
+
if (!isWorkflowPlanProfile(value)) {
|
|
37
|
+
throw new Error(`unsupported workflow profile: ${value} (expected ${WORKFLOW_PLAN_PROFILES.join("|")})`);
|
|
38
|
+
}
|
|
39
|
+
profile = value;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (arg === "--output") {
|
|
43
|
+
outputPath = rest[++i];
|
|
44
|
+
if (!outputPath)
|
|
45
|
+
throw new Error("dag workflow-plan --output requires a path");
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (arg.startsWith("--output=")) {
|
|
49
|
+
outputPath = arg.slice("--output=".length);
|
|
50
|
+
if (!outputPath)
|
|
51
|
+
throw new Error("dag workflow-plan --output requires a path");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg === "--base") {
|
|
55
|
+
base = rest[++i];
|
|
56
|
+
if (!base)
|
|
57
|
+
throw new Error("dag workflow-plan --base requires a value");
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (arg.startsWith("--base=")) {
|
|
61
|
+
base = arg.slice("--base=".length);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (arg === "--head") {
|
|
65
|
+
head = rest[++i];
|
|
66
|
+
if (!head)
|
|
67
|
+
throw new Error("dag workflow-plan --head requires a value");
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (arg.startsWith("--head=")) {
|
|
71
|
+
head = arg.slice("--head=".length);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (arg === "--changed-file") {
|
|
75
|
+
const file = rest[++i];
|
|
76
|
+
if (!file) {
|
|
77
|
+
throw new Error("dag workflow-plan --changed-file requires a path");
|
|
78
|
+
}
|
|
79
|
+
changedFiles.push(file);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg.startsWith("--changed-file=")) {
|
|
83
|
+
const file = arg.slice("--changed-file=".length);
|
|
84
|
+
if (!file) {
|
|
85
|
+
throw new Error("dag workflow-plan --changed-file requires a path");
|
|
86
|
+
}
|
|
87
|
+
changedFiles.push(file);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg.startsWith("-")) {
|
|
91
|
+
throw new Error(`unknown dag workflow-plan flag: ${arg}`);
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
94
|
+
}
|
|
95
|
+
if (!profile) {
|
|
96
|
+
throw new Error("dag workflow-plan requires --profile <profile>");
|
|
97
|
+
}
|
|
98
|
+
if (!outputPath) {
|
|
99
|
+
throw new Error("dag workflow-plan requires --output <path>");
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
taskId,
|
|
103
|
+
profile,
|
|
104
|
+
outputPath: resolveCliPath(outputPath),
|
|
105
|
+
base,
|
|
106
|
+
head,
|
|
107
|
+
changedFiles,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export async function runDagWorkflowPlan(_repoRoot, rawArgs) {
|
|
111
|
+
const parsed = parseDagWorkflowPlanArgs(rawArgs);
|
|
112
|
+
const spec = buildWorkflowSpecForProfile({
|
|
113
|
+
profile: parsed.profile,
|
|
114
|
+
taskId: parsed.taskId,
|
|
115
|
+
base: parsed.base,
|
|
116
|
+
head: parsed.head,
|
|
117
|
+
changedFiles: parsed.changedFiles,
|
|
118
|
+
});
|
|
119
|
+
assertValidWorkflowSpec(spec);
|
|
120
|
+
await mkdir(path.dirname(parsed.outputPath), { recursive: true });
|
|
121
|
+
await writeFile(parsed.outputPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
|
|
122
|
+
console.log(JSON.stringify({
|
|
123
|
+
mode: "workflow-plan",
|
|
124
|
+
profile: parsed.profile,
|
|
125
|
+
taskId: parsed.taskId,
|
|
126
|
+
nodeCount: spec.nodes.length,
|
|
127
|
+
final: spec.final.from,
|
|
128
|
+
outputPath: parsed.outputPath,
|
|
129
|
+
}, null, 2));
|
|
130
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { assertValidWorkflowSpec, parseWorkflowSpec, validateWorkflowSpec, } from "../workflows/dynamic/validate.js";
|
|
3
|
+
import { compileWorkflowToDag } from "../workflows/dynamic/compile.js";
|
|
4
|
+
import { assertValidDagSpec, validateDagSpec } from "../workflows/dag/validate.js";
|
|
5
|
+
import { resolveCliPath } from "../shared/path-refs.js";
|
|
6
|
+
export function parseDagWorkflowValidateArgs(args) {
|
|
7
|
+
if (args.length === 0) {
|
|
8
|
+
throw new Error("usage: dag workflow-validate --workflow <path> [--strict-governance]");
|
|
9
|
+
}
|
|
10
|
+
let workflowPath;
|
|
11
|
+
let strictGovernance = false;
|
|
12
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
13
|
+
const arg = args[i];
|
|
14
|
+
if (arg === "--workflow") {
|
|
15
|
+
workflowPath = args[++i];
|
|
16
|
+
if (!workflowPath) {
|
|
17
|
+
throw new Error("dag workflow-validate --workflow requires a path");
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg.startsWith("--workflow=")) {
|
|
22
|
+
workflowPath = arg.slice("--workflow=".length);
|
|
23
|
+
if (!workflowPath) {
|
|
24
|
+
throw new Error("dag workflow-validate --workflow requires a path");
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--strict-governance") {
|
|
29
|
+
strictGovernance = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg.startsWith("-")) {
|
|
33
|
+
throw new Error(`unknown dag workflow-validate flag: ${arg}`);
|
|
34
|
+
}
|
|
35
|
+
if (workflowPath) {
|
|
36
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
37
|
+
}
|
|
38
|
+
workflowPath = arg;
|
|
39
|
+
}
|
|
40
|
+
if (!workflowPath) {
|
|
41
|
+
throw new Error("dag workflow-validate requires --workflow <path>");
|
|
42
|
+
}
|
|
43
|
+
return { workflowPath: resolveCliPath(workflowPath), strictGovernance };
|
|
44
|
+
}
|
|
45
|
+
export async function runDagWorkflowValidate(_repoRoot, rawArgs) {
|
|
46
|
+
const parsed = parseDagWorkflowValidateArgs(rawArgs);
|
|
47
|
+
const raw = JSON.parse(await readFile(parsed.workflowPath, "utf-8"));
|
|
48
|
+
const spec = parseWorkflowSpec(raw);
|
|
49
|
+
const issues = validateWorkflowSpec(spec);
|
|
50
|
+
assertValidWorkflowSpec(spec);
|
|
51
|
+
const compiled = compileWorkflowToDag(spec, { sourcePath: parsed.workflowPath });
|
|
52
|
+
const dagIssues = validateDagSpec(compiled.dag);
|
|
53
|
+
assertValidDagSpec(compiled.dag, {
|
|
54
|
+
issues: dagIssues,
|
|
55
|
+
strictGovernance: parsed.strictGovernance,
|
|
56
|
+
});
|
|
57
|
+
console.log(JSON.stringify({
|
|
58
|
+
mode: "workflow-validate",
|
|
59
|
+
name: spec.meta.name,
|
|
60
|
+
nodeCount: spec.nodes.length,
|
|
61
|
+
final: spec.final.from,
|
|
62
|
+
strictGovernance: parsed.strictGovernance,
|
|
63
|
+
issues,
|
|
64
|
+
dagIssues,
|
|
65
|
+
}, null, 2));
|
|
66
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { delegateTask } from '../task/delegate.js';
|
|
4
|
+
import { runCursorDelegatedTask } from '../executors/cursor-run.js';
|
|
5
|
+
import { buildCursorRecoverySteps } from '../executors/cursor-executor-artifacts.js';
|
|
6
|
+
import { formatCursorFailureMessage } from '../executors/cursor-execution-log.js';
|
|
7
|
+
import { loadHarnessManifest } from '../governance/harness.js';
|
|
8
|
+
const DELEGATE_USAGE = 'usage: delegate <taskId> [--executor pi|cursor] [--base <branch>] [--branch <name>] '
|
|
9
|
+
+ '[--no-symlink] [--auto-run] [--no-auto-run]';
|
|
10
|
+
export function parseDelegateArgs(args) {
|
|
11
|
+
if (args.length === 0) {
|
|
12
|
+
throw new Error(DELEGATE_USAGE);
|
|
13
|
+
}
|
|
14
|
+
let taskId;
|
|
15
|
+
let base;
|
|
16
|
+
let branch;
|
|
17
|
+
let symlinkNodeModules = true;
|
|
18
|
+
let autoRun = true;
|
|
19
|
+
let executor;
|
|
20
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === '--base') {
|
|
23
|
+
base = args[i + 1];
|
|
24
|
+
if (!base)
|
|
25
|
+
throw new Error('--base requires a value');
|
|
26
|
+
i += 1;
|
|
27
|
+
}
|
|
28
|
+
else if (arg === '--branch') {
|
|
29
|
+
branch = args[i + 1];
|
|
30
|
+
if (!branch)
|
|
31
|
+
throw new Error('--branch requires a value');
|
|
32
|
+
i += 1;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === '--executor') {
|
|
35
|
+
const value = args[i + 1];
|
|
36
|
+
if (value !== 'pi' && value !== 'cursor') {
|
|
37
|
+
throw new Error('--executor must be "pi" or "cursor"');
|
|
38
|
+
}
|
|
39
|
+
executor = value;
|
|
40
|
+
i += 1;
|
|
41
|
+
}
|
|
42
|
+
else if (arg === '--no-symlink') {
|
|
43
|
+
symlinkNodeModules = false;
|
|
44
|
+
}
|
|
45
|
+
else if (arg === '--auto-run') {
|
|
46
|
+
autoRun = true;
|
|
47
|
+
}
|
|
48
|
+
else if (arg === '--no-auto-run') {
|
|
49
|
+
autoRun = false;
|
|
50
|
+
}
|
|
51
|
+
else if (arg === '--supervised') {
|
|
52
|
+
throw new Error('--supervised was removed with the tmux white-box subagent lifecycle. '
|
|
53
|
+
+ 'Use `delegate <taskId> --executor cursor --auto-run` for in-process Cursor SDK.');
|
|
54
|
+
}
|
|
55
|
+
else if (!arg.startsWith('--')) {
|
|
56
|
+
if (taskId)
|
|
57
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
58
|
+
taskId = arg;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new Error(`unknown delegate flag: ${arg}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!taskId) {
|
|
65
|
+
throw new Error('delegate requires <taskId> as the first positional argument');
|
|
66
|
+
}
|
|
67
|
+
return { taskId, base, branch, symlinkNodeModules, autoRun, executor };
|
|
68
|
+
}
|
|
69
|
+
export function printCursorFailureRecovery(taskId, result, cursorResult) {
|
|
70
|
+
console.error(`[delegate] cursor direct run failed: ${formatCursorFailureMessage(cursorResult.failureCategory)}`);
|
|
71
|
+
console.error(` logs: .harness/tasks/${taskId}/logs/result.summary.md`);
|
|
72
|
+
for (const line of buildCursorRecoverySteps({
|
|
73
|
+
channel: 'delegate',
|
|
74
|
+
taskId,
|
|
75
|
+
sessionName: result.sessionName,
|
|
76
|
+
worktreePath: result.worktreePath,
|
|
77
|
+
failureCategory: cursorResult.failureCategory,
|
|
78
|
+
})) {
|
|
79
|
+
console.error(line);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function printCursorDelegateNextSteps(taskId, result) {
|
|
83
|
+
const lines = [
|
|
84
|
+
`[delegate] cursor worktree ready (SDK direct).`,
|
|
85
|
+
` worktree: ${result.worktreePath}`,
|
|
86
|
+
` Next — run Cursor SDK in-process:`,
|
|
87
|
+
` npm run dev -- delegate ${taskId} --executor cursor --auto-run`,
|
|
88
|
+
` After work completes:`,
|
|
89
|
+
` npm run dev -- harvest ${taskId}`,
|
|
90
|
+
];
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
console.error(line);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function buildPiAutoRunMigrationMessage(taskId, worktreePath) {
|
|
96
|
+
const dagPath = path.join(os.tmpdir(), `${taskId}-dag.json`);
|
|
97
|
+
const cwd = worktreePath ?? '<repo-root>';
|
|
98
|
+
return [
|
|
99
|
+
'delegate --executor pi --auto-run has been removed.',
|
|
100
|
+
'Use the DAG path instead:',
|
|
101
|
+
` 1. npm run dev -- dag run-task ${taskId} --profile auto --strict-models --output ${JSON.stringify(dagPath)}`,
|
|
102
|
+
` 2. npm run dev -- dag validate --dag ${JSON.stringify(dagPath)} --strict-models --strict-governance`,
|
|
103
|
+
` 3. npm run dev -- run-dag --dag ${JSON.stringify(dagPath)} --cwd ${JSON.stringify(cwd)}`,
|
|
104
|
+
].join('\n');
|
|
105
|
+
}
|
|
106
|
+
export async function runDelegate(repoRoot, rawArgs) {
|
|
107
|
+
const parsed = parseDelegateArgs(rawArgs);
|
|
108
|
+
const manifest = await loadHarnessManifest(repoRoot);
|
|
109
|
+
const result = await delegateTask(repoRoot, manifest, parsed.taskId, {
|
|
110
|
+
base: parsed.base,
|
|
111
|
+
branch: parsed.branch,
|
|
112
|
+
symlinkNodeModules: parsed.symlinkNodeModules,
|
|
113
|
+
executor: parsed.executor,
|
|
114
|
+
});
|
|
115
|
+
if (parsed.autoRun) {
|
|
116
|
+
if (result.executor === 'cursor') {
|
|
117
|
+
const cursorResult = await runCursorDelegatedTask(result.worktreePath, parsed.taskId, manifest, {
|
|
118
|
+
executionMode: 'direct',
|
|
119
|
+
});
|
|
120
|
+
if (!cursorResult.ok) {
|
|
121
|
+
printCursorFailureRecovery(parsed.taskId, result, cursorResult);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (result.executor === 'pi') {
|
|
125
|
+
throw new Error(buildPiAutoRunMigrationMessage(parsed.taskId, result.worktreePath));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (result.executor === 'cursor') {
|
|
129
|
+
printCursorDelegateNextSteps(parsed.taskId, result);
|
|
130
|
+
}
|
|
131
|
+
console.log(JSON.stringify(result, null, 2));
|
|
132
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { checkPiAvailability, resolvePiBackend } from '../executors/pi-executor.js';
|
|
4
|
+
import { checkPiSdkAvailability } from '../executors/pi-sdk-executor.js';
|
|
5
|
+
import { resolveCursorExecutorConfig } from '../executors/config-core.js';
|
|
6
|
+
import { isCursorWorkerEnabled, isCursorWorkerRunning } from '../executors/cursor-worker-client.js';
|
|
7
|
+
function checkCursorAvailability() {
|
|
8
|
+
if (!process.env.CURSOR_API_KEY?.trim()) {
|
|
9
|
+
return { ok: false, detail: 'CURSOR_API_KEY not set' };
|
|
10
|
+
}
|
|
11
|
+
return { ok: true, detail: 'CURSOR_API_KEY configured' };
|
|
12
|
+
}
|
|
13
|
+
export async function runDoctor(adapter, repoRoot) {
|
|
14
|
+
const manifest = await adapter.loadHarnessManifest(repoRoot);
|
|
15
|
+
const requiredPaths = [];
|
|
16
|
+
for (const key of ['readme', 'agents', 'governanceIndex', 'principles', 'workflow', 'verificationMatrix']) {
|
|
17
|
+
const value = manifest.entrypoints[key];
|
|
18
|
+
if (value)
|
|
19
|
+
requiredPaths.push(value);
|
|
20
|
+
}
|
|
21
|
+
const checkRepoScript = manifest.scripts?.checkRepo;
|
|
22
|
+
if (checkRepoScript)
|
|
23
|
+
requiredPaths.push(checkRepoScript);
|
|
24
|
+
requiredPaths.push('.harness/prompts/analyze.md');
|
|
25
|
+
requiredPaths.push('.harness/prompts/plan.md');
|
|
26
|
+
for (const rel of requiredPaths) {
|
|
27
|
+
await access(path.join(repoRoot, rel));
|
|
28
|
+
}
|
|
29
|
+
const pi = checkPiAvailability();
|
|
30
|
+
if (!pi.ok) {
|
|
31
|
+
throw new Error(`pi unavailable: ${pi.detail}`);
|
|
32
|
+
}
|
|
33
|
+
const backendMode = resolvePiBackend();
|
|
34
|
+
const sdk = await checkPiSdkAvailability(repoRoot);
|
|
35
|
+
console.log(`doctor: pi backend=${backendMode} cli=${pi.ok ? 'ok' : 'fail'} sdk=${sdk.ok ? 'ok' : 'unavailable'}`);
|
|
36
|
+
if (backendMode === 'sdk-first' && !sdk.ok) {
|
|
37
|
+
console.error(`doctor warning: CODE_AGENT_PI_BACKEND=sdk-first but pi SDK unavailable (${sdk.detail}); steps will fall back to CLI`);
|
|
38
|
+
}
|
|
39
|
+
const cursorCfg = resolveCursorExecutorConfig(manifest);
|
|
40
|
+
if (cursorCfg) {
|
|
41
|
+
const cursor = checkCursorAvailability();
|
|
42
|
+
if (!cursor.ok) {
|
|
43
|
+
console.error(`doctor warning: executors.cursor registered but ${cursor.detail}`);
|
|
44
|
+
}
|
|
45
|
+
if (isCursorWorkerEnabled()) {
|
|
46
|
+
console.log(`doctor: CURSOR_USE_WORKER=1 (running=${isCursorWorkerRunning()})`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log('doctor ok');
|
|
50
|
+
}
|