cortex-agents 3.4.0 → 4.0.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/.opencode/agents/architect.md +81 -89
- package/.opencode/agents/audit.md +57 -188
- package/.opencode/agents/{crosslayer.md → coder.md} +8 -52
- package/.opencode/agents/debug.md +151 -0
- package/.opencode/agents/devops.md +142 -0
- package/.opencode/agents/docs-writer.md +195 -0
- package/.opencode/agents/fix.md +118 -189
- package/.opencode/agents/implement.md +114 -74
- package/.opencode/agents/perf.md +151 -0
- package/.opencode/agents/refactor.md +163 -0
- package/.opencode/agents/{guard.md → security.md} +20 -85
- package/.opencode/agents/testing.md +115 -0
- package/.opencode/skills/data-engineering/SKILL.md +221 -0
- package/.opencode/skills/monitoring-observability/SKILL.md +251 -0
- package/README.md +302 -287
- package/dist/cli.js +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -28
- package/dist/registry.d.ts +4 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +6 -6
- package/dist/tools/branch.d.ts +2 -2
- package/dist/tools/docs.d.ts +2 -2
- package/dist/tools/github.d.ts +3 -3
- package/dist/tools/plan.d.ts +28 -4
- package/dist/tools/plan.d.ts.map +1 -1
- package/dist/tools/plan.js +232 -4
- package/dist/tools/quality-gate.d.ts +28 -0
- package/dist/tools/quality-gate.d.ts.map +1 -0
- package/dist/tools/quality-gate.js +233 -0
- package/dist/tools/repl.d.ts +5 -0
- package/dist/tools/repl.d.ts.map +1 -1
- package/dist/tools/repl.js +58 -7
- package/dist/tools/worktree.d.ts +5 -32
- package/dist/tools/worktree.d.ts.map +1 -1
- package/dist/tools/worktree.js +75 -458
- package/dist/utils/change-scope.d.ts +33 -0
- package/dist/utils/change-scope.d.ts.map +1 -0
- package/dist/utils/change-scope.js +198 -0
- package/dist/utils/plan-extract.d.ts +21 -0
- package/dist/utils/plan-extract.d.ts.map +1 -1
- package/dist/utils/plan-extract.js +65 -0
- package/dist/utils/repl.d.ts +31 -0
- package/dist/utils/repl.d.ts.map +1 -1
- package/dist/utils/repl.js +126 -13
- package/package.json +1 -1
- package/.opencode/agents/qa.md +0 -265
- package/.opencode/agents/ship.md +0 -249
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Gate Summary Tool
|
|
3
|
+
*
|
|
4
|
+
* Aggregates sub-agent findings into a unified report with severity sorting,
|
|
5
|
+
* go/no-go recommendation, and PR body content.
|
|
6
|
+
*/
|
|
7
|
+
export declare const qualityGateSummary: {
|
|
8
|
+
description: string;
|
|
9
|
+
args: {
|
|
10
|
+
scope: import("zod").ZodOptional<import("zod").ZodString>;
|
|
11
|
+
testing: import("zod").ZodOptional<import("zod").ZodString>;
|
|
12
|
+
security: import("zod").ZodOptional<import("zod").ZodString>;
|
|
13
|
+
audit: import("zod").ZodOptional<import("zod").ZodString>;
|
|
14
|
+
perf: import("zod").ZodOptional<import("zod").ZodString>;
|
|
15
|
+
devops: import("zod").ZodOptional<import("zod").ZodString>;
|
|
16
|
+
docsWriter: import("zod").ZodOptional<import("zod").ZodString>;
|
|
17
|
+
};
|
|
18
|
+
execute(args: {
|
|
19
|
+
scope?: string | undefined;
|
|
20
|
+
testing?: string | undefined;
|
|
21
|
+
security?: string | undefined;
|
|
22
|
+
audit?: string | undefined;
|
|
23
|
+
perf?: string | undefined;
|
|
24
|
+
devops?: string | undefined;
|
|
25
|
+
docsWriter?: string | undefined;
|
|
26
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=quality-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-gate.d.ts","sourceRoot":"","sources":["../../src/tools/quality-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6CH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;CA+I7B,CAAC"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Gate Summary Tool
|
|
3
|
+
*
|
|
4
|
+
* Aggregates sub-agent findings into a unified report with severity sorting,
|
|
5
|
+
* go/no-go recommendation, and PR body content.
|
|
6
|
+
*/
|
|
7
|
+
import { tool } from "@opencode-ai/plugin";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
const CORTEX_DIR = ".cortex";
|
|
11
|
+
const QUALITY_GATE_FILE = "quality-gate.json";
|
|
12
|
+
// ─── Severity ordering ───────────────────────────────────────────────────────
|
|
13
|
+
const SEVERITY_ORDER = {
|
|
14
|
+
CRITICAL: 0,
|
|
15
|
+
HIGH: 1,
|
|
16
|
+
MEDIUM: 2,
|
|
17
|
+
LOW: 3,
|
|
18
|
+
INFO: 4,
|
|
19
|
+
};
|
|
20
|
+
// ─── quality_gate_summary ────────────────────────────────────────────────────
|
|
21
|
+
export const qualityGateSummary = tool({
|
|
22
|
+
description: "Aggregate sub-agent quality gate findings into a unified report. " +
|
|
23
|
+
"Parses findings from @testing, @security, @audit, @perf, @devops, and @docs-writer, " +
|
|
24
|
+
"sorts by severity, provides a go/no-go recommendation, and generates PR body content. " +
|
|
25
|
+
"Pass each agent's raw report text as a separate entry.",
|
|
26
|
+
args: {
|
|
27
|
+
scope: tool.schema
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Change scope classification: trivial, low, standard, high"),
|
|
31
|
+
testing: tool.schema
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Raw report from @testing sub-agent"),
|
|
35
|
+
security: tool.schema
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Raw report from @security sub-agent"),
|
|
39
|
+
audit: tool.schema
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Raw report from @audit sub-agent"),
|
|
43
|
+
perf: tool.schema
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Raw report from @perf sub-agent"),
|
|
47
|
+
devops: tool.schema
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Raw report from @devops sub-agent"),
|
|
51
|
+
docsWriter: tool.schema
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Raw report from @docs-writer sub-agent"),
|
|
55
|
+
},
|
|
56
|
+
async execute(args, context) {
|
|
57
|
+
const cwd = context.worktree;
|
|
58
|
+
const reports = [];
|
|
59
|
+
// Parse each provided report
|
|
60
|
+
const agentEntries = [
|
|
61
|
+
["testing", args.testing],
|
|
62
|
+
["security", args.security],
|
|
63
|
+
["audit", args.audit],
|
|
64
|
+
["perf", args.perf],
|
|
65
|
+
["devops", args.devops],
|
|
66
|
+
["docs-writer", args.docsWriter],
|
|
67
|
+
];
|
|
68
|
+
for (const [agent, raw] of agentEntries) {
|
|
69
|
+
if (!raw)
|
|
70
|
+
continue;
|
|
71
|
+
reports.push(parseReport(agent, raw));
|
|
72
|
+
}
|
|
73
|
+
if (reports.length === 0) {
|
|
74
|
+
return `\u2717 No sub-agent reports provided. Pass at least one agent report to aggregate.`;
|
|
75
|
+
}
|
|
76
|
+
// Collect all findings and sort by severity
|
|
77
|
+
const allFindings = reports.flatMap((r) => r.findings);
|
|
78
|
+
allFindings.sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99));
|
|
79
|
+
// Determine recommendation
|
|
80
|
+
const hasCritical = allFindings.some((f) => f.severity === "CRITICAL");
|
|
81
|
+
const hasHigh = allFindings.some((f) => f.severity === "HIGH");
|
|
82
|
+
const hasMedium = allFindings.some((f) => f.severity === "MEDIUM");
|
|
83
|
+
let recommendation;
|
|
84
|
+
if (hasCritical || hasHigh) {
|
|
85
|
+
recommendation = "NO-GO";
|
|
86
|
+
}
|
|
87
|
+
else if (hasMedium) {
|
|
88
|
+
recommendation = "GO-WITH-WARNINGS";
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
recommendation = "GO";
|
|
92
|
+
}
|
|
93
|
+
// Build state for persistence
|
|
94
|
+
const state = {
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
scope: args.scope ?? "unknown",
|
|
97
|
+
reports,
|
|
98
|
+
recommendation,
|
|
99
|
+
};
|
|
100
|
+
// Persist state
|
|
101
|
+
persistState(cwd, state);
|
|
102
|
+
// Format output
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push(`\u2713 Quality Gate Summary`);
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push(`**Recommendation: ${recommendation}**`);
|
|
107
|
+
lines.push(`Scope: ${args.scope ?? "unknown"}`);
|
|
108
|
+
lines.push(`Agents: ${reports.map((r) => r.agent).join(", ")}`);
|
|
109
|
+
lines.push(`Total findings: ${allFindings.length}`);
|
|
110
|
+
lines.push("");
|
|
111
|
+
// Per-agent verdicts
|
|
112
|
+
lines.push("## Agent Results");
|
|
113
|
+
for (const report of reports) {
|
|
114
|
+
const agentFindings = report.findings;
|
|
115
|
+
const counts = countBySeverity(agentFindings);
|
|
116
|
+
lines.push(`- **@${report.agent}**: ${report.verdict} — ${formatCounts(counts)}`);
|
|
117
|
+
}
|
|
118
|
+
// All findings sorted by severity
|
|
119
|
+
if (allFindings.length > 0) {
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push("## Findings (sorted by severity)");
|
|
122
|
+
for (const finding of allFindings) {
|
|
123
|
+
const loc = finding.location ? ` (${finding.location})` : "";
|
|
124
|
+
lines.push(`- **[${finding.severity}]** @${finding.agent}: ${finding.title}${loc}`);
|
|
125
|
+
if (finding.description) {
|
|
126
|
+
lines.push(` ${finding.description.substring(0, 200)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Blockers
|
|
131
|
+
const blockers = allFindings.filter((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
132
|
+
if (blockers.length > 0) {
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push("## Blockers (must fix before merge)");
|
|
135
|
+
for (const b of blockers) {
|
|
136
|
+
lines.push(`- **[${b.severity}]** @${b.agent}: ${b.title}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// PR body section
|
|
140
|
+
lines.push("");
|
|
141
|
+
lines.push("## PR Body Quality Gate Section");
|
|
142
|
+
lines.push("```");
|
|
143
|
+
lines.push("## Quality Gate");
|
|
144
|
+
for (const report of reports) {
|
|
145
|
+
const counts = countBySeverity(report.findings);
|
|
146
|
+
lines.push(`- ${capitalize(report.agent)}: ${report.verdict} — ${formatCounts(counts)}`);
|
|
147
|
+
}
|
|
148
|
+
lines.push(`- **Recommendation: ${recommendation}**`);
|
|
149
|
+
lines.push("```");
|
|
150
|
+
return lines.join("\n");
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
// ─── Parsing helpers ─────────────────────────────────────────────────────────
|
|
154
|
+
function parseReport(agent, raw) {
|
|
155
|
+
const findings = [];
|
|
156
|
+
// Extract verdict
|
|
157
|
+
let verdict = "Unknown";
|
|
158
|
+
const verdictMatch = raw.match(/\*\*Verdict\*\*:\s*(.+)/i)
|
|
159
|
+
?? raw.match(/Verdict:\s*(.+)/i);
|
|
160
|
+
if (verdictMatch) {
|
|
161
|
+
verdict = verdictMatch[1].trim();
|
|
162
|
+
}
|
|
163
|
+
// Extract findings by severity markers
|
|
164
|
+
const findingRegex = /####?\s*\[(CRITICAL|HIGH|MEDIUM|LOW|INFO|BLOCKING|WARNING|ERROR|SUGGESTION|NITPICK|PRAISE)\]\s*(.+)/gi;
|
|
165
|
+
let match;
|
|
166
|
+
while ((match = findingRegex.exec(raw)) !== null) {
|
|
167
|
+
const rawSeverity = match[1].toUpperCase();
|
|
168
|
+
const title = match[2].trim();
|
|
169
|
+
// Normalize severity
|
|
170
|
+
let severity;
|
|
171
|
+
switch (rawSeverity) {
|
|
172
|
+
case "CRITICAL":
|
|
173
|
+
case "BLOCKING":
|
|
174
|
+
case "ERROR":
|
|
175
|
+
severity = "CRITICAL";
|
|
176
|
+
break;
|
|
177
|
+
case "HIGH":
|
|
178
|
+
severity = "HIGH";
|
|
179
|
+
break;
|
|
180
|
+
case "MEDIUM":
|
|
181
|
+
case "WARNING":
|
|
182
|
+
case "SUGGESTION":
|
|
183
|
+
severity = "MEDIUM";
|
|
184
|
+
break;
|
|
185
|
+
case "LOW":
|
|
186
|
+
case "NITPICK":
|
|
187
|
+
severity = "LOW";
|
|
188
|
+
break;
|
|
189
|
+
default:
|
|
190
|
+
severity = "INFO";
|
|
191
|
+
}
|
|
192
|
+
// Try to extract location from lines following the finding
|
|
193
|
+
const afterMatch = raw.substring(match.index + match[0].length, match.index + match[0].length + 500);
|
|
194
|
+
const locationMatch = afterMatch.match(/\*\*Location\*\*:\s*`?([^`\n]+)/i)
|
|
195
|
+
?? afterMatch.match(/\*\*File\*\*:\s*`?([^`\n]+)/i);
|
|
196
|
+
const descMatch = afterMatch.match(/\*\*Description\*\*:\s*(.+)/i);
|
|
197
|
+
findings.push({
|
|
198
|
+
agent,
|
|
199
|
+
severity,
|
|
200
|
+
title,
|
|
201
|
+
location: locationMatch?.[1]?.trim(),
|
|
202
|
+
description: descMatch?.[1]?.trim() ?? "",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return { agent, verdict, findings, raw };
|
|
206
|
+
}
|
|
207
|
+
function countBySeverity(findings) {
|
|
208
|
+
const counts = {};
|
|
209
|
+
for (const f of findings) {
|
|
210
|
+
counts[f.severity] = (counts[f.severity] ?? 0) + 1;
|
|
211
|
+
}
|
|
212
|
+
return counts;
|
|
213
|
+
}
|
|
214
|
+
function formatCounts(counts) {
|
|
215
|
+
const parts = [];
|
|
216
|
+
for (const sev of ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]) {
|
|
217
|
+
if (counts[sev]) {
|
|
218
|
+
parts.push(`${counts[sev]} ${sev.toLowerCase()}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return parts.length > 0 ? parts.join(", ") : "no findings";
|
|
222
|
+
}
|
|
223
|
+
function capitalize(s) {
|
|
224
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
225
|
+
}
|
|
226
|
+
function persistState(cwd, state) {
|
|
227
|
+
const dir = path.join(cwd, CORTEX_DIR);
|
|
228
|
+
if (!fs.existsSync(dir)) {
|
|
229
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
const filepath = path.join(dir, QUALITY_GATE_FILE);
|
|
232
|
+
fs.writeFileSync(filepath, JSON.stringify(state, null, 2));
|
|
233
|
+
}
|
package/dist/tools/repl.d.ts
CHANGED
|
@@ -42,6 +42,11 @@ export declare const report: {
|
|
|
42
42
|
detail: string;
|
|
43
43
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
44
44
|
};
|
|
45
|
+
export declare const resume: {
|
|
46
|
+
description: string;
|
|
47
|
+
args: {};
|
|
48
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
49
|
+
};
|
|
45
50
|
export declare const summary: {
|
|
46
51
|
description: string;
|
|
47
52
|
args: {};
|
package/dist/tools/repl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../src/tools/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../src/tools/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0BH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;CAwGf,CAAC;AAIH,eAAO,MAAM,MAAM;;;;CA0BjB,CAAC;AAIH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;CAwCjB,CAAC;AAkFH,eAAO,MAAM,MAAM;;;;CA+CjB,CAAC;AAIH,eAAO,MAAM,OAAO;;;;CAclB,CAAC"}
|
package/dist/tools/repl.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { tool } from "@opencode-ai/plugin";
|
|
11
11
|
import * as fs from "fs";
|
|
12
12
|
import * as path from "path";
|
|
13
|
-
import {
|
|
13
|
+
import { parseTasksWithAC, detectCommands, readCortexConfig, readReplState, writeReplState, getNextTask, getCurrentTask, isLoopComplete, detectIncompleteState, formatProgress, formatSummary, } from "../utils/repl.js";
|
|
14
14
|
const CORTEX_DIR = ".cortex";
|
|
15
15
|
const PLANS_DIR = "plans";
|
|
16
16
|
// ─── repl_init ───────────────────────────────────────────────────────────────
|
|
@@ -36,8 +36,9 @@ export const init = tool({
|
|
|
36
36
|
.describe("Max retries per failing task before escalating to user (default: 3)"),
|
|
37
37
|
},
|
|
38
38
|
async execute(args, context) {
|
|
39
|
-
const { planFilename, buildCommand, testCommand, maxRetries = 3 } = args;
|
|
40
39
|
const cwd = context.worktree;
|
|
40
|
+
const config = readCortexConfig(cwd);
|
|
41
|
+
const { planFilename, buildCommand, testCommand, maxRetries = config.maxRetries ?? 3 } = args;
|
|
41
42
|
// 1. Validate plan filename
|
|
42
43
|
if (!planFilename || planFilename === "." || planFilename === "..") {
|
|
43
44
|
return `\u2717 Error: Invalid plan filename.`;
|
|
@@ -53,9 +54,9 @@ export const init = tool({
|
|
|
53
54
|
return `\u2717 Error: Plan not found: ${planFilename}\n\nUse plan_list to see available plans.`;
|
|
54
55
|
}
|
|
55
56
|
const planContent = fs.readFileSync(planPath, "utf-8");
|
|
56
|
-
// 2. Parse tasks from plan
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
57
|
+
// 2. Parse tasks from plan (with acceptance criteria)
|
|
58
|
+
const parsedTasks = parseTasksWithAC(planContent);
|
|
59
|
+
if (parsedTasks.length === 0) {
|
|
59
60
|
return `\u2717 Error: No tasks found in plan: ${planFilename}\n\nThe plan must contain unchecked checkbox items (- [ ] ...) in a ## Tasks section.`;
|
|
60
61
|
}
|
|
61
62
|
// 3. Auto-detect commands (or use overrides)
|
|
@@ -63,9 +64,10 @@ export const init = tool({
|
|
|
63
64
|
const finalBuild = buildCommand ?? detected.buildCommand;
|
|
64
65
|
const finalTest = testCommand ?? detected.testCommand;
|
|
65
66
|
// 4. Build initial state
|
|
66
|
-
const tasks =
|
|
67
|
+
const tasks = parsedTasks.map((parsed, i) => ({
|
|
67
68
|
index: i,
|
|
68
|
-
description:
|
|
69
|
+
description: parsed.description,
|
|
70
|
+
acceptanceCriteria: parsed.acceptanceCriteria,
|
|
69
71
|
status: "pending",
|
|
70
72
|
retries: 0,
|
|
71
73
|
iterations: [],
|
|
@@ -120,6 +122,7 @@ export const status = tool({
|
|
|
120
122
|
const next = getNextTask(state);
|
|
121
123
|
if (next) {
|
|
122
124
|
next.status = "in_progress";
|
|
125
|
+
next.startedAt = new Date().toISOString();
|
|
123
126
|
state.currentTaskIndex = next.index;
|
|
124
127
|
writeReplState(context.worktree, state);
|
|
125
128
|
}
|
|
@@ -179,6 +182,7 @@ function processReport(state, task, result, detail, cwd) {
|
|
|
179
182
|
switch (result) {
|
|
180
183
|
case "pass": {
|
|
181
184
|
task.status = "passed";
|
|
185
|
+
task.completedAt = new Date().toISOString();
|
|
182
186
|
const attempt = task.iterations.length;
|
|
183
187
|
const suffix = attempt === 1 ? "1st" : attempt === 2 ? "2nd" : attempt === 3 ? "3rd" : `${attempt}th`;
|
|
184
188
|
output = `\u2713 Task #${taskNum} PASSED (${suffix} attempt)\n "${taskDesc}"\n Detail: ${detail.substring(0, 200)}`;
|
|
@@ -190,6 +194,7 @@ function processReport(state, task, result, detail, cwd) {
|
|
|
190
194
|
if (task.retries >= state.maxRetries) {
|
|
191
195
|
// Retries exhausted
|
|
192
196
|
task.status = "failed";
|
|
197
|
+
task.completedAt = new Date().toISOString();
|
|
193
198
|
output = `\u2717 Task #${taskNum} FAILED \u2014 retries exhausted (${attempt}/${state.maxRetries} attempts)\n "${taskDesc}"\n Detail: ${detail.substring(0, 200)}\n\n\u2192 ASK THE USER how to proceed. Suggest: fix manually, skip task, or abort loop.`;
|
|
194
199
|
}
|
|
195
200
|
else {
|
|
@@ -204,6 +209,7 @@ function processReport(state, task, result, detail, cwd) {
|
|
|
204
209
|
}
|
|
205
210
|
case "skip": {
|
|
206
211
|
task.status = "skipped";
|
|
212
|
+
task.completedAt = new Date().toISOString();
|
|
207
213
|
output = `\u2298 Task #${taskNum} SKIPPED\n "${taskDesc}"\n Reason: ${detail.substring(0, 200)}`;
|
|
208
214
|
break;
|
|
209
215
|
}
|
|
@@ -212,6 +218,7 @@ function processReport(state, task, result, detail, cwd) {
|
|
|
212
218
|
const next = getNextTask(state);
|
|
213
219
|
if (next) {
|
|
214
220
|
next.status = "in_progress";
|
|
221
|
+
next.startedAt = new Date().toISOString();
|
|
215
222
|
state.currentTaskIndex = next.index;
|
|
216
223
|
output += `\n\n\u2192 Next: Task #${next.index + 1} "${next.description}"`;
|
|
217
224
|
}
|
|
@@ -224,6 +231,50 @@ function processReport(state, task, result, detail, cwd) {
|
|
|
224
231
|
writeReplState(cwd, state);
|
|
225
232
|
return output;
|
|
226
233
|
}
|
|
234
|
+
// ─── repl_resume ─────────────────────────────────────────────────────────────
|
|
235
|
+
export const resume = tool({
|
|
236
|
+
description: "Detect and resume an interrupted REPL loop. Checks for incomplete state " +
|
|
237
|
+
"in .cortex/repl-state.json and offers to continue from where it left off. " +
|
|
238
|
+
"Call this at the start of a session to recover from crashes or context loss.",
|
|
239
|
+
args: {},
|
|
240
|
+
async execute(args, context) {
|
|
241
|
+
const state = detectIncompleteState(context.worktree);
|
|
242
|
+
if (!state) {
|
|
243
|
+
return `\u2717 No interrupted REPL loop found.\n\nEither no loop has been started, or the previous loop completed normally.`;
|
|
244
|
+
}
|
|
245
|
+
const total = state.tasks.length;
|
|
246
|
+
const passed = state.tasks.filter((t) => t.status === "passed").length;
|
|
247
|
+
const failed = state.tasks.filter((t) => t.status === "failed").length;
|
|
248
|
+
const skipped = state.tasks.filter((t) => t.status === "skipped").length;
|
|
249
|
+
const done = passed + failed + skipped;
|
|
250
|
+
const current = getCurrentTask(state);
|
|
251
|
+
const next = getNextTask(state);
|
|
252
|
+
const lines = [];
|
|
253
|
+
lines.push(`\u2713 Interrupted REPL loop detected`);
|
|
254
|
+
lines.push("");
|
|
255
|
+
lines.push(`Plan: ${state.planFilename}`);
|
|
256
|
+
lines.push(`Progress: ${done}/${total} tasks completed (${passed} passed, ${failed} failed, ${skipped} skipped)`);
|
|
257
|
+
lines.push(`Started: ${state.startedAt}`);
|
|
258
|
+
if (current) {
|
|
259
|
+
lines.push("");
|
|
260
|
+
lines.push(`Interrupted task (#${current.index + 1}):`);
|
|
261
|
+
lines.push(` "${current.description}"`);
|
|
262
|
+
lines.push(` Status: in_progress (${current.retries} retries so far)`);
|
|
263
|
+
if (current.iterations.length > 0) {
|
|
264
|
+
const lastIter = current.iterations[current.iterations.length - 1];
|
|
265
|
+
lines.push(` Last attempt: ${lastIter.result} — ${lastIter.detail.substring(0, 100)}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (next) {
|
|
269
|
+
lines.push("");
|
|
270
|
+
lines.push(`Next pending task (#${next.index + 1}):`);
|
|
271
|
+
lines.push(` "${next.description}"`);
|
|
272
|
+
}
|
|
273
|
+
lines.push("");
|
|
274
|
+
lines.push(`\u2192 Run repl_status to continue the loop from where it left off.`);
|
|
275
|
+
return lines.join("\n");
|
|
276
|
+
},
|
|
277
|
+
});
|
|
227
278
|
// ─── repl_summary ────────────────────────────────────────────────────────────
|
|
228
279
|
export const summary = tool({
|
|
229
280
|
description: "Generate a formatted summary of the REPL loop results for inclusion in " +
|
package/dist/tools/worktree.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
2
|
type Client = PluginInput["client"];
|
|
3
|
-
type Shell = PluginInput["$"];
|
|
4
3
|
/**
|
|
5
4
|
* Factory function that creates the worktree_create tool with access
|
|
6
5
|
* to the OpenCode client for toast notifications.
|
|
@@ -10,18 +9,20 @@ export declare function createCreate(client: Client): {
|
|
|
10
9
|
args: {
|
|
11
10
|
name: import("zod").ZodString;
|
|
12
11
|
type: import("zod").ZodEnum<{
|
|
12
|
+
refactor: "refactor";
|
|
13
13
|
feature: "feature";
|
|
14
14
|
bugfix: "bugfix";
|
|
15
15
|
hotfix: "hotfix";
|
|
16
|
-
refactor: "refactor";
|
|
17
16
|
spike: "spike";
|
|
18
17
|
docs: "docs";
|
|
19
18
|
test: "test";
|
|
20
19
|
}>;
|
|
20
|
+
fromBranch: import("zod").ZodOptional<import("zod").ZodString>;
|
|
21
21
|
};
|
|
22
22
|
execute(args: {
|
|
23
23
|
name: string;
|
|
24
|
-
type: "
|
|
24
|
+
type: "refactor" | "feature" | "bugfix" | "hotfix" | "spike" | "docs" | "test";
|
|
25
|
+
fromBranch?: string | undefined;
|
|
25
26
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
26
27
|
};
|
|
27
28
|
export declare const list: {
|
|
@@ -31,7 +32,7 @@ export declare const list: {
|
|
|
31
32
|
};
|
|
32
33
|
/**
|
|
33
34
|
* Factory function that creates the worktree_remove tool with access
|
|
34
|
-
* to the OpenCode client for toast notifications
|
|
35
|
+
* to the OpenCode client for toast notifications.
|
|
35
36
|
*/
|
|
36
37
|
export declare function createRemove(client: Client): {
|
|
37
38
|
description: string;
|
|
@@ -53,33 +54,5 @@ export declare const open: {
|
|
|
53
54
|
name: string;
|
|
54
55
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
55
56
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Factory function that creates the worktree_launch tool with access
|
|
58
|
-
* to the OpenCode client (for PTY and toast) and shell.
|
|
59
|
-
*
|
|
60
|
-
* This uses a closure to capture `client` and `shell` since ToolContext
|
|
61
|
-
* does not provide access to the OpenCode client API.
|
|
62
|
-
*/
|
|
63
|
-
export declare function createLaunch(client: Client, shell: Shell): {
|
|
64
|
-
description: string;
|
|
65
|
-
args: {
|
|
66
|
-
name: import("zod").ZodString;
|
|
67
|
-
mode: import("zod").ZodEnum<{
|
|
68
|
-
terminal: "terminal";
|
|
69
|
-
pty: "pty";
|
|
70
|
-
background: "background";
|
|
71
|
-
}>;
|
|
72
|
-
plan: import("zod").ZodOptional<import("zod").ZodString>;
|
|
73
|
-
agent: import("zod").ZodOptional<import("zod").ZodString>;
|
|
74
|
-
prompt: import("zod").ZodOptional<import("zod").ZodString>;
|
|
75
|
-
};
|
|
76
|
-
execute(args: {
|
|
77
|
-
name: string;
|
|
78
|
-
mode: "terminal" | "pty" | "background";
|
|
79
|
-
plan?: string | undefined;
|
|
80
|
-
agent?: string | undefined;
|
|
81
|
-
prompt?: string | undefined;
|
|
82
|
-
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
83
|
-
};
|
|
84
57
|
export {};
|
|
85
58
|
//# sourceMappingURL=worktree.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/tools/worktree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/tools/worktree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAQvD,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAEpC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;EA0H1C;AAED,eAAO,MAAM,IAAI;;;;CAiCf,CAAC;AAEH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;EA0F1C;AAED,eAAO,MAAM,IAAI;;;;;;;;CAgDf,CAAC"}
|