cortex-agents 2.3.1 → 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.
Files changed (70) hide show
  1. package/.opencode/agents/{plan.md → architect.md} +104 -58
  2. package/.opencode/agents/audit.md +183 -0
  3. package/.opencode/agents/{fullstack.md → coder.md} +10 -54
  4. package/.opencode/agents/debug.md +76 -201
  5. package/.opencode/agents/devops.md +16 -123
  6. package/.opencode/agents/docs-writer.md +195 -0
  7. package/.opencode/agents/fix.md +207 -0
  8. package/.opencode/agents/implement.md +433 -0
  9. package/.opencode/agents/perf.md +151 -0
  10. package/.opencode/agents/refactor.md +163 -0
  11. package/.opencode/agents/security.md +20 -85
  12. package/.opencode/agents/testing.md +1 -151
  13. package/.opencode/skills/data-engineering/SKILL.md +221 -0
  14. package/.opencode/skills/monitoring-observability/SKILL.md +251 -0
  15. package/README.md +315 -224
  16. package/dist/cli.js +85 -17
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +60 -22
  19. package/dist/registry.d.ts +8 -3
  20. package/dist/registry.d.ts.map +1 -1
  21. package/dist/registry.js +16 -2
  22. package/dist/tools/branch.d.ts +2 -2
  23. package/dist/tools/cortex.d.ts +2 -2
  24. package/dist/tools/cortex.js +7 -7
  25. package/dist/tools/docs.d.ts +2 -2
  26. package/dist/tools/environment.d.ts +31 -0
  27. package/dist/tools/environment.d.ts.map +1 -0
  28. package/dist/tools/environment.js +93 -0
  29. package/dist/tools/github.d.ts +42 -0
  30. package/dist/tools/github.d.ts.map +1 -0
  31. package/dist/tools/github.js +200 -0
  32. package/dist/tools/plan.d.ts +28 -4
  33. package/dist/tools/plan.d.ts.map +1 -1
  34. package/dist/tools/plan.js +232 -4
  35. package/dist/tools/quality-gate.d.ts +28 -0
  36. package/dist/tools/quality-gate.d.ts.map +1 -0
  37. package/dist/tools/quality-gate.js +233 -0
  38. package/dist/tools/repl.d.ts +55 -0
  39. package/dist/tools/repl.d.ts.map +1 -0
  40. package/dist/tools/repl.js +291 -0
  41. package/dist/tools/task.d.ts +2 -0
  42. package/dist/tools/task.d.ts.map +1 -1
  43. package/dist/tools/task.js +25 -30
  44. package/dist/tools/worktree.d.ts +5 -32
  45. package/dist/tools/worktree.d.ts.map +1 -1
  46. package/dist/tools/worktree.js +75 -447
  47. package/dist/utils/change-scope.d.ts +33 -0
  48. package/dist/utils/change-scope.d.ts.map +1 -0
  49. package/dist/utils/change-scope.js +198 -0
  50. package/dist/utils/github.d.ts +104 -0
  51. package/dist/utils/github.d.ts.map +1 -0
  52. package/dist/utils/github.js +243 -0
  53. package/dist/utils/ide.d.ts +76 -0
  54. package/dist/utils/ide.d.ts.map +1 -0
  55. package/dist/utils/ide.js +307 -0
  56. package/dist/utils/plan-extract.d.ts +28 -0
  57. package/dist/utils/plan-extract.d.ts.map +1 -1
  58. package/dist/utils/plan-extract.js +90 -1
  59. package/dist/utils/repl.d.ts +145 -0
  60. package/dist/utils/repl.d.ts.map +1 -0
  61. package/dist/utils/repl.js +547 -0
  62. package/dist/utils/terminal.d.ts +53 -1
  63. package/dist/utils/terminal.d.ts.map +1 -1
  64. package/dist/utils/terminal.js +642 -5
  65. package/package.json +1 -1
  66. package/.opencode/agents/build.md +0 -294
  67. package/.opencode/agents/review.md +0 -314
  68. package/dist/plugin.d.ts +0 -1
  69. package/dist/plugin.d.ts.map +0 -1
  70. package/dist/plugin.js +0 -4
@@ -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
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * REPL Loop Tools
3
+ *
4
+ * Four tools for the implement agent's iterative task-by-task development loop:
5
+ * - repl_init — Initialize a loop from a plan
6
+ * - repl_status — Get current progress and next task
7
+ * - repl_report — Report task outcome (pass/fail/skip)
8
+ * - repl_summary — Generate markdown summary for PR body
9
+ */
10
+ export declare const init: {
11
+ description: string;
12
+ args: {
13
+ planFilename: import("zod").ZodString;
14
+ buildCommand: import("zod").ZodOptional<import("zod").ZodString>;
15
+ testCommand: import("zod").ZodOptional<import("zod").ZodString>;
16
+ maxRetries: import("zod").ZodOptional<import("zod").ZodNumber>;
17
+ };
18
+ execute(args: {
19
+ planFilename: string;
20
+ buildCommand?: string | undefined;
21
+ testCommand?: string | undefined;
22
+ maxRetries?: number | undefined;
23
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
24
+ };
25
+ export declare const status: {
26
+ description: string;
27
+ args: {};
28
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
29
+ };
30
+ export declare const report: {
31
+ description: string;
32
+ args: {
33
+ result: import("zod").ZodEnum<{
34
+ pass: "pass";
35
+ fail: "fail";
36
+ skip: "skip";
37
+ }>;
38
+ detail: import("zod").ZodString;
39
+ };
40
+ execute(args: {
41
+ result: "pass" | "fail" | "skip";
42
+ detail: string;
43
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
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
+ };
50
+ export declare const summary: {
51
+ description: string;
52
+ args: {};
53
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
54
+ };
55
+ //# sourceMappingURL=repl.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * REPL Loop Tools
3
+ *
4
+ * Four tools for the implement agent's iterative task-by-task development loop:
5
+ * - repl_init — Initialize a loop from a plan
6
+ * - repl_status — Get current progress and next task
7
+ * - repl_report — Report task outcome (pass/fail/skip)
8
+ * - repl_summary — Generate markdown summary for PR body
9
+ */
10
+ import { tool } from "@opencode-ai/plugin";
11
+ import * as fs from "fs";
12
+ import * as path from "path";
13
+ import { parseTasksWithAC, detectCommands, readCortexConfig, readReplState, writeReplState, getNextTask, getCurrentTask, isLoopComplete, detectIncompleteState, formatProgress, formatSummary, } from "../utils/repl.js";
14
+ const CORTEX_DIR = ".cortex";
15
+ const PLANS_DIR = "plans";
16
+ // ─── repl_init ───────────────────────────────────────────────────────────────
17
+ export const init = tool({
18
+ description: "Initialize a REPL implementation loop from a plan. Parses plan tasks, " +
19
+ "auto-detects build/test commands, and creates .cortex/repl-state.json " +
20
+ "for tracking progress through each task iteratively.",
21
+ args: {
22
+ planFilename: tool.schema
23
+ .string()
24
+ .describe("Plan filename from .cortex/plans/ to load tasks from"),
25
+ buildCommand: tool.schema
26
+ .string()
27
+ .optional()
28
+ .describe("Override auto-detected build command (e.g., 'npm run build')"),
29
+ testCommand: tool.schema
30
+ .string()
31
+ .optional()
32
+ .describe("Override auto-detected test command (e.g., 'npm test')"),
33
+ maxRetries: tool.schema
34
+ .number()
35
+ .optional()
36
+ .describe("Max retries per failing task before escalating to user (default: 3)"),
37
+ },
38
+ async execute(args, context) {
39
+ const cwd = context.worktree;
40
+ const config = readCortexConfig(cwd);
41
+ const { planFilename, buildCommand, testCommand, maxRetries = config.maxRetries ?? 3 } = args;
42
+ // 1. Validate plan filename
43
+ if (!planFilename || planFilename === "." || planFilename === "..") {
44
+ return `\u2717 Error: Invalid plan filename.`;
45
+ }
46
+ const plansDir = path.join(cwd, CORTEX_DIR, PLANS_DIR);
47
+ const planPath = path.resolve(plansDir, planFilename);
48
+ const resolvedPlansDir = path.resolve(plansDir);
49
+ // Prevent path traversal — resolved path must be strictly inside plans dir
50
+ if (!planPath.startsWith(resolvedPlansDir + path.sep)) {
51
+ return `\u2717 Error: Invalid plan filename.`;
52
+ }
53
+ if (!fs.existsSync(planPath)) {
54
+ return `\u2717 Error: Plan not found: ${planFilename}\n\nUse plan_list to see available plans.`;
55
+ }
56
+ const planContent = fs.readFileSync(planPath, "utf-8");
57
+ // 2. Parse tasks from plan (with acceptance criteria)
58
+ const parsedTasks = parseTasksWithAC(planContent);
59
+ if (parsedTasks.length === 0) {
60
+ return `\u2717 Error: No tasks found in plan: ${planFilename}\n\nThe plan must contain unchecked checkbox items (- [ ] ...) in a ## Tasks section.`;
61
+ }
62
+ // 3. Auto-detect commands (or use overrides)
63
+ const detected = await detectCommands(cwd);
64
+ const finalBuild = buildCommand ?? detected.buildCommand;
65
+ const finalTest = testCommand ?? detected.testCommand;
66
+ // 4. Build initial state
67
+ const tasks = parsedTasks.map((parsed, i) => ({
68
+ index: i,
69
+ description: parsed.description,
70
+ acceptanceCriteria: parsed.acceptanceCriteria,
71
+ status: "pending",
72
+ retries: 0,
73
+ iterations: [],
74
+ }));
75
+ const state = {
76
+ planFilename,
77
+ startedAt: new Date().toISOString(),
78
+ buildCommand: finalBuild,
79
+ testCommand: finalTest,
80
+ lintCommand: detected.lintCommand,
81
+ maxRetries,
82
+ currentTaskIndex: -1,
83
+ tasks,
84
+ };
85
+ // 5. Write state
86
+ writeReplState(cwd, state);
87
+ // 6. Format output
88
+ const cmdInfo = detected.detected
89
+ ? `Auto-detected (${detected.framework})`
90
+ : "Not detected \u2014 provide overrides if needed";
91
+ return `\u2713 REPL loop initialized
92
+
93
+ Plan: ${planFilename}
94
+ Tasks: ${tasks.length}
95
+ Detection: ${cmdInfo}
96
+
97
+ Build: ${finalBuild || "(none)"}
98
+ Test: ${finalTest || "(none)"}
99
+ ${detected.lintCommand ? `Lint: ${detected.lintCommand}` : ""}
100
+ Max retries: ${maxRetries}
101
+
102
+ First task (#1):
103
+ "${tasks[0].description}"
104
+
105
+ Run \`repl_status\` to begin, then implement the task and run build/tests.`;
106
+ },
107
+ });
108
+ // ─── repl_status ─────────────────────────────────────────────────────────────
109
+ export const status = tool({
110
+ description: "Get the current REPL loop progress \u2014 which task is active, " +
111
+ "what\u2019s been completed, retry counts, and detected build/test commands. " +
112
+ "Call this to decide what to implement next.",
113
+ args: {},
114
+ async execute(args, context) {
115
+ const state = readReplState(context.worktree);
116
+ if (!state) {
117
+ return `\u2717 No REPL loop active.\n\nRun repl_init with a plan filename to start a loop.`;
118
+ }
119
+ // Auto-advance: if no task is in_progress, promote the next pending task
120
+ const current = getCurrentTask(state);
121
+ if (!current && !isLoopComplete(state)) {
122
+ const next = getNextTask(state);
123
+ if (next) {
124
+ next.status = "in_progress";
125
+ next.startedAt = new Date().toISOString();
126
+ state.currentTaskIndex = next.index;
127
+ writeReplState(context.worktree, state);
128
+ }
129
+ }
130
+ return `\u2713 REPL Loop Status\n\n${formatProgress(state)}`;
131
+ },
132
+ });
133
+ // ─── repl_report ─────────────────────────────────────────────────────────────
134
+ export const report = tool({
135
+ description: "Report the outcome of the current task iteration. " +
136
+ "After implementing a task and running build/tests, report whether it passed, " +
137
+ "failed, or should be skipped. The loop will auto-advance on pass, " +
138
+ "retry on fail (up to max), or escalate to user when retries exhausted.",
139
+ args: {
140
+ result: tool.schema
141
+ .enum(["pass", "fail", "skip"])
142
+ .describe("Task result: 'pass' (build+tests green), 'fail' (something broke), 'skip' (defer task)"),
143
+ detail: tool.schema
144
+ .string()
145
+ .describe("Result details: test output summary, error message, or skip reason"),
146
+ },
147
+ async execute(args, context) {
148
+ const { result, detail } = args;
149
+ const state = readReplState(context.worktree);
150
+ if (!state) {
151
+ return `\u2717 No REPL loop active. Run repl_init first.`;
152
+ }
153
+ // Find the current in_progress task
154
+ const current = getCurrentTask(state);
155
+ if (!current) {
156
+ // Try to find the task at currentTaskIndex
157
+ if (state.currentTaskIndex >= 0 && state.currentTaskIndex < state.tasks.length) {
158
+ const task = state.tasks[state.currentTaskIndex];
159
+ if (task.status === "pending") {
160
+ task.status = "in_progress";
161
+ }
162
+ return processReport(state, task, result, detail, context.worktree);
163
+ }
164
+ return `\u2717 No task is currently in progress.\n\nRun repl_status to advance to the next task.`;
165
+ }
166
+ return processReport(state, current, result, detail, context.worktree);
167
+ },
168
+ });
169
+ /**
170
+ * Process a report for a task and update state.
171
+ */
172
+ function processReport(state, task, result, detail, cwd) {
173
+ // Record iteration
174
+ task.iterations.push({
175
+ at: new Date().toISOString(),
176
+ result,
177
+ detail: detail.substring(0, 2000), // Cap detail length
178
+ });
179
+ const taskNum = task.index + 1;
180
+ const taskDesc = task.description;
181
+ let output;
182
+ switch (result) {
183
+ case "pass": {
184
+ task.status = "passed";
185
+ task.completedAt = new Date().toISOString();
186
+ const attempt = task.iterations.length;
187
+ const suffix = attempt === 1 ? "1st" : attempt === 2 ? "2nd" : attempt === 3 ? "3rd" : `${attempt}th`;
188
+ output = `\u2713 Task #${taskNum} PASSED (${suffix} attempt)\n "${taskDesc}"\n Detail: ${detail.substring(0, 200)}`;
189
+ break;
190
+ }
191
+ case "fail": {
192
+ task.retries += 1;
193
+ const attempt = task.iterations.length;
194
+ if (task.retries >= state.maxRetries) {
195
+ // Retries exhausted
196
+ task.status = "failed";
197
+ task.completedAt = new Date().toISOString();
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.`;
199
+ }
200
+ else {
201
+ // Retries remaining — stay in_progress
202
+ const remaining = state.maxRetries - task.retries;
203
+ output = `\u26A0 Task #${taskNum} FAILED (attempt ${attempt}/${state.maxRetries})\n "${taskDesc}"\n Detail: ${detail.substring(0, 200)}\n\n\u2192 Fix the issue and run build/tests again. ${remaining} retr${remaining > 1 ? "ies" : "y"} remaining.`;
204
+ // Don't advance — keep task in_progress
205
+ writeReplState(cwd, state);
206
+ return output;
207
+ }
208
+ break;
209
+ }
210
+ case "skip": {
211
+ task.status = "skipped";
212
+ task.completedAt = new Date().toISOString();
213
+ output = `\u2298 Task #${taskNum} SKIPPED\n "${taskDesc}"\n Reason: ${detail.substring(0, 200)}`;
214
+ break;
215
+ }
216
+ }
217
+ // Advance to next task
218
+ const next = getNextTask(state);
219
+ if (next) {
220
+ next.status = "in_progress";
221
+ next.startedAt = new Date().toISOString();
222
+ state.currentTaskIndex = next.index;
223
+ output += `\n\n\u2192 Next: Task #${next.index + 1} "${next.description}"`;
224
+ }
225
+ else {
226
+ // All tasks done
227
+ state.currentTaskIndex = -1;
228
+ state.completedAt = new Date().toISOString();
229
+ output += "\n\n\u2713 All tasks complete. Run repl_summary to generate the results report, then proceed to the quality gate (Step 7).";
230
+ }
231
+ writeReplState(cwd, state);
232
+ return output;
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
+ });
278
+ // ─── repl_summary ────────────────────────────────────────────────────────────
279
+ export const summary = tool({
280
+ description: "Generate a formatted summary of the REPL loop results for inclusion in " +
281
+ "the quality gate report and PR body. Call after all tasks are complete " +
282
+ "or when the loop is terminated.",
283
+ args: {},
284
+ async execute(args, context) {
285
+ const state = readReplState(context.worktree);
286
+ if (!state) {
287
+ return `\u2717 No REPL loop data found.\n\nRun repl_init to start a loop, or this may have been cleaned up already.`;
288
+ }
289
+ return formatSummary(state);
290
+ },
291
+ });
@@ -7,6 +7,7 @@ export declare const finalize: {
7
7
  baseBranch: import("zod").ZodOptional<import("zod").ZodString>;
8
8
  planFilename: import("zod").ZodOptional<import("zod").ZodString>;
9
9
  draft: import("zod").ZodOptional<import("zod").ZodBoolean>;
10
+ issueRefs: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodNumber>>;
10
11
  };
11
12
  execute(args: {
12
13
  commitMessage: string;
@@ -15,6 +16,7 @@ export declare const finalize: {
15
16
  baseBranch?: string | undefined;
16
17
  planFilename?: string | undefined;
17
18
  draft?: boolean | undefined;
19
+ issueRefs?: number[] | undefined;
18
20
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
19
21
  };
20
22
  //# sourceMappingURL=task.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAyGA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;CAqNnB,CAAC"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AA+EA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;CA0OnB,CAAC"}