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.
- package/.opencode/agents/{plan.md → architect.md} +104 -58
- package/.opencode/agents/audit.md +183 -0
- package/.opencode/agents/{fullstack.md → coder.md} +10 -54
- package/.opencode/agents/debug.md +76 -201
- package/.opencode/agents/devops.md +16 -123
- package/.opencode/agents/docs-writer.md +195 -0
- package/.opencode/agents/fix.md +207 -0
- package/.opencode/agents/implement.md +433 -0
- package/.opencode/agents/perf.md +151 -0
- package/.opencode/agents/refactor.md +163 -0
- package/.opencode/agents/security.md +20 -85
- package/.opencode/agents/testing.md +1 -151
- package/.opencode/skills/data-engineering/SKILL.md +221 -0
- package/.opencode/skills/monitoring-observability/SKILL.md +251 -0
- package/README.md +315 -224
- package/dist/cli.js +85 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -22
- package/dist/registry.d.ts +8 -3
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +16 -2
- package/dist/tools/branch.d.ts +2 -2
- package/dist/tools/cortex.d.ts +2 -2
- package/dist/tools/cortex.js +7 -7
- package/dist/tools/docs.d.ts +2 -2
- package/dist/tools/environment.d.ts +31 -0
- package/dist/tools/environment.d.ts.map +1 -0
- package/dist/tools/environment.js +93 -0
- package/dist/tools/github.d.ts +42 -0
- package/dist/tools/github.d.ts.map +1 -0
- package/dist/tools/github.js +200 -0
- 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 +55 -0
- package/dist/tools/repl.d.ts.map +1 -0
- package/dist/tools/repl.js +291 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +25 -30
- package/dist/tools/worktree.d.ts +5 -32
- package/dist/tools/worktree.d.ts.map +1 -1
- package/dist/tools/worktree.js +75 -447
- 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/github.d.ts +104 -0
- package/dist/utils/github.d.ts.map +1 -0
- package/dist/utils/github.js +243 -0
- package/dist/utils/ide.d.ts +76 -0
- package/dist/utils/ide.d.ts.map +1 -0
- package/dist/utils/ide.js +307 -0
- package/dist/utils/plan-extract.d.ts +28 -0
- package/dist/utils/plan-extract.d.ts.map +1 -1
- package/dist/utils/plan-extract.js +90 -1
- package/dist/utils/repl.d.ts +145 -0
- package/dist/utils/repl.d.ts.map +1 -0
- package/dist/utils/repl.js +547 -0
- package/dist/utils/terminal.d.ts +53 -1
- package/dist/utils/terminal.d.ts.map +1 -1
- package/dist/utils/terminal.js +642 -5
- package/package.json +1 -1
- package/.opencode/agents/build.md +0 -294
- package/.opencode/agents/review.md +0 -314
- package/dist/plugin.d.ts +0 -1
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -4
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE Detection System
|
|
3
|
+
*
|
|
4
|
+
* Detects the current Integrated Development Environment or editor context.
|
|
5
|
+
* This is used to offer appropriate worktree launch options that match
|
|
6
|
+
* the user's current workflow.
|
|
7
|
+
*
|
|
8
|
+
* Detection is based on environment variables set by various IDEs when
|
|
9
|
+
* running terminal sessions within them.
|
|
10
|
+
*/
|
|
11
|
+
import { shellEscape, which } from "./shell.js";
|
|
12
|
+
/**
|
|
13
|
+
* Detect the current IDE/editor environment.
|
|
14
|
+
* Checks environment variables, process hierarchy, and context clues.
|
|
15
|
+
*/
|
|
16
|
+
export function detectIDE() {
|
|
17
|
+
const env = process.env;
|
|
18
|
+
// VS Code detection
|
|
19
|
+
// VSCODE_PID is set when running in VS Code's integrated terminal
|
|
20
|
+
// VSCODE_CWD is set in some VS Code contexts
|
|
21
|
+
// TERM_PROGRAM=vscode is set by VS Code's terminal
|
|
22
|
+
if (env.VSCODE_PID || env.VSCODE_CWD || env.TERM_PROGRAM === "vscode") {
|
|
23
|
+
return {
|
|
24
|
+
type: "vscode",
|
|
25
|
+
name: "Visual Studio Code",
|
|
26
|
+
version: env.VSCODE_VERSION,
|
|
27
|
+
hasIntegratedTerminal: true,
|
|
28
|
+
canOpenInTerminal: true,
|
|
29
|
+
canOpenInWindow: true,
|
|
30
|
+
detectionSource: env.VSCODE_PID ? "VSCODE_PID" :
|
|
31
|
+
env.VSCODE_CWD ? "VSCODE_CWD" : "TERM_PROGRAM",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// Cursor detection
|
|
35
|
+
// CURSOR_TRACE_ID is set by Cursor AI editor
|
|
36
|
+
// CURSOR_SHELL_VERSION indicates Cursor's shell integration
|
|
37
|
+
if (env.CURSOR_TRACE_ID || env.CURSOR_SHELL_VERSION) {
|
|
38
|
+
return {
|
|
39
|
+
type: "cursor",
|
|
40
|
+
name: "Cursor",
|
|
41
|
+
version: env.CURSOR_SHELL_VERSION,
|
|
42
|
+
hasIntegratedTerminal: true,
|
|
43
|
+
canOpenInTerminal: true,
|
|
44
|
+
canOpenInWindow: true,
|
|
45
|
+
detectionSource: env.CURSOR_TRACE_ID ? "CURSOR_TRACE_ID" : "CURSOR_SHELL_VERSION",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Windsurf detection
|
|
49
|
+
// WINDSURF_PARENT_PROCESS indicates Windsurf editor
|
|
50
|
+
// WINDSURF_EDITOR is set by Windsurf's terminal
|
|
51
|
+
if (env.WINDSURF_PARENT_PROCESS || env.WINDSURF_EDITOR) {
|
|
52
|
+
return {
|
|
53
|
+
type: "windsurf",
|
|
54
|
+
name: "Windsurf",
|
|
55
|
+
hasIntegratedTerminal: true,
|
|
56
|
+
canOpenInTerminal: true,
|
|
57
|
+
canOpenInWindow: true,
|
|
58
|
+
detectionSource: env.WINDSURF_PARENT_PROCESS ? "WINDSURF_PARENT_PROCESS" : "WINDSURF_EDITOR",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// JetBrains detection
|
|
62
|
+
// TERMINAL_EMULATOR contains "JetBrains" when in JB IDE terminal
|
|
63
|
+
// JETBRAINS_IDE is set by some JetBrains IDEs
|
|
64
|
+
if (env.TERMINAL_EMULATOR?.includes("JetBrains") || env.JETBRAINS_IDE) {
|
|
65
|
+
return {
|
|
66
|
+
type: "jetbrains",
|
|
67
|
+
name: env.JETBRAINS_IDE_NAME || "JetBrains IDE",
|
|
68
|
+
hasIntegratedTerminal: true,
|
|
69
|
+
canOpenInTerminal: true, // Can open in terminal, but no CLI for new window
|
|
70
|
+
canOpenInWindow: false, // JB IDEs don't have CLI window opening
|
|
71
|
+
detectionSource: env.JETBRAINS_IDE ? "JETBRAINS_IDE" : "TERMINAL_EMULATOR",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Zed detection
|
|
75
|
+
// ZED_TERM is set by Zed's integrated terminal
|
|
76
|
+
// TERM_PROGRAM=zed in some Zed configurations
|
|
77
|
+
if (env.ZED_TERM || env.TERM_PROGRAM === "zed") {
|
|
78
|
+
return {
|
|
79
|
+
type: "zed",
|
|
80
|
+
name: "Zed",
|
|
81
|
+
hasIntegratedTerminal: true,
|
|
82
|
+
canOpenInTerminal: true,
|
|
83
|
+
canOpenInWindow: true,
|
|
84
|
+
detectionSource: env.ZED_TERM ? "ZED_TERM" : "TERM_PROGRAM",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Terminal-only detection (fallback when no IDE detected)
|
|
88
|
+
// This means we're in a standalone terminal emulator
|
|
89
|
+
if (env.TERM_PROGRAM || env.TMUX || env.TERM) {
|
|
90
|
+
return {
|
|
91
|
+
type: "terminal",
|
|
92
|
+
name: env.TERM_PROGRAM || env.TERM || "Terminal",
|
|
93
|
+
hasIntegratedTerminal: false,
|
|
94
|
+
canOpenInTerminal: false,
|
|
95
|
+
canOpenInWindow: true,
|
|
96
|
+
detectionSource: env.TERM_PROGRAM ? "TERM_PROGRAM" :
|
|
97
|
+
env.TMUX ? "TMUX" : "TERM",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Unknown environment
|
|
101
|
+
return {
|
|
102
|
+
type: "unknown",
|
|
103
|
+
name: "Unknown",
|
|
104
|
+
hasIntegratedTerminal: false,
|
|
105
|
+
canOpenInTerminal: false,
|
|
106
|
+
canOpenInWindow: true,
|
|
107
|
+
detectionSource: "none",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the command to open a worktree in the IDE's integrated terminal.
|
|
112
|
+
* Returns null if the IDE doesn't support CLI-based opening.
|
|
113
|
+
*
|
|
114
|
+
* Note: This function is currently unused but kept for future use.
|
|
115
|
+
* All inputs are escaped to prevent command injection.
|
|
116
|
+
*/
|
|
117
|
+
export function getIDEOpenCommand(ide, worktreePath) {
|
|
118
|
+
// Escape the path to prevent command injection
|
|
119
|
+
const safePath = shellEscape(worktreePath);
|
|
120
|
+
switch (ide.type) {
|
|
121
|
+
case "vscode":
|
|
122
|
+
// VS Code: use `code` CLI to open folder in new window
|
|
123
|
+
return `code --new-window "${safePath}"`;
|
|
124
|
+
case "cursor":
|
|
125
|
+
// Cursor: use `cursor` CLI
|
|
126
|
+
return `cursor --new-window "${safePath}"`;
|
|
127
|
+
case "windsurf":
|
|
128
|
+
// Windsurf: use `windsurf` CLI
|
|
129
|
+
return `windsurf "${safePath}"`;
|
|
130
|
+
case "zed":
|
|
131
|
+
// Zed: use `zed` CLI
|
|
132
|
+
return `zed "${safePath}"`;
|
|
133
|
+
case "jetbrains":
|
|
134
|
+
// JetBrains: requires manual opening or platform-specific CLI
|
|
135
|
+
// Common CLIs: idea, webstorm, pycharm, etc.
|
|
136
|
+
return null;
|
|
137
|
+
default:
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the name of the CLI binary for the detected IDE.
|
|
143
|
+
* Returns null if no CLI is available or known.
|
|
144
|
+
*/
|
|
145
|
+
export function getIDECliBinary(ide) {
|
|
146
|
+
switch (ide.type) {
|
|
147
|
+
case "vscode":
|
|
148
|
+
return "code";
|
|
149
|
+
case "cursor":
|
|
150
|
+
return "cursor";
|
|
151
|
+
case "windsurf":
|
|
152
|
+
return "windsurf";
|
|
153
|
+
case "zed":
|
|
154
|
+
return "zed";
|
|
155
|
+
default:
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get a human-readable hint for installing an IDE's CLI binary.
|
|
161
|
+
* Each IDE has a different installation method.
|
|
162
|
+
*/
|
|
163
|
+
export function getInstallHint(type, binary) {
|
|
164
|
+
switch (type) {
|
|
165
|
+
case "vscode":
|
|
166
|
+
return "Cmd+Shift+P → 'Shell Command: Install code command in PATH'";
|
|
167
|
+
case "cursor":
|
|
168
|
+
return "Cmd+Shift+P → 'Shell Command: Install cursor command in PATH'";
|
|
169
|
+
case "windsurf":
|
|
170
|
+
return "Ensure Windsurf is installed and 'windsurf' is in PATH";
|
|
171
|
+
case "zed":
|
|
172
|
+
return "Ensure Zed is installed and 'zed' is in PATH";
|
|
173
|
+
default:
|
|
174
|
+
return `Ensure '${binary}' is in PATH`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Async version of detectIDE() that also checks whether the IDE's CLI binary
|
|
179
|
+
* is actually available in PATH. Use this in tools where you need to verify
|
|
180
|
+
* runtime availability before offering IDE launch options.
|
|
181
|
+
*
|
|
182
|
+
* The sync detectIDE() is preserved for non-async contexts.
|
|
183
|
+
*/
|
|
184
|
+
export async function detectIDEWithCLICheck() {
|
|
185
|
+
const ide = detectIDE();
|
|
186
|
+
const cliBinary = getIDECliBinary(ide);
|
|
187
|
+
if (cliBinary) {
|
|
188
|
+
const available = await which(cliBinary);
|
|
189
|
+
ide.cliAvailable = !!available;
|
|
190
|
+
ide.cliBinary = cliBinary;
|
|
191
|
+
if (!available) {
|
|
192
|
+
ide.cliInstallHint = getInstallHint(ide.type, cliBinary);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
ide.cliAvailable = false;
|
|
197
|
+
}
|
|
198
|
+
return ide;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if we're already inside an IDE terminal.
|
|
202
|
+
* This helps determine if we should offer "stay here" as primary option.
|
|
203
|
+
*/
|
|
204
|
+
export function isInIDETerminal() {
|
|
205
|
+
const ide = detectIDE();
|
|
206
|
+
return ide.hasIntegratedTerminal && !!process.env.TERM;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate contextual recommendations based on detected environment.
|
|
210
|
+
* Used by agents to offer appropriate launch options to users.
|
|
211
|
+
*/
|
|
212
|
+
export function generateEnvironmentRecommendations(ide) {
|
|
213
|
+
const recommendations = [];
|
|
214
|
+
if (ide.hasIntegratedTerminal && ide.canOpenInTerminal && ide.cliAvailable !== false) {
|
|
215
|
+
// Only offer IDE option when CLI is confirmed available or hasn't been checked yet
|
|
216
|
+
recommendations.push({
|
|
217
|
+
option: `Open in ${ide.name} (Recommended)`,
|
|
218
|
+
priority: "high",
|
|
219
|
+
reason: `${ide.name} integrated terminal maintains context and is familiar`,
|
|
220
|
+
mode: "ide",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else if (ide.hasIntegratedTerminal && ide.cliAvailable === false) {
|
|
224
|
+
// IDE detected but CLI missing — note it but don't recommend
|
|
225
|
+
recommendations.push({
|
|
226
|
+
option: `Open in ${ide.name} (CLI not installed)`,
|
|
227
|
+
priority: "low",
|
|
228
|
+
reason: `${ide.cliBinary || "CLI"} not in PATH. ${ide.cliInstallHint || "Install the CLI to enable this option."}`,
|
|
229
|
+
mode: "ide",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
recommendations.push({
|
|
233
|
+
option: "Open in new terminal tab",
|
|
234
|
+
priority: ide.cliAvailable === false ? "high" : "medium",
|
|
235
|
+
reason: "Open in your current terminal emulator as a new tab",
|
|
236
|
+
mode: "terminal",
|
|
237
|
+
});
|
|
238
|
+
recommendations.push({
|
|
239
|
+
option: "Open in-app PTY",
|
|
240
|
+
priority: "medium",
|
|
241
|
+
reason: "Embedded terminal within this OpenCode session",
|
|
242
|
+
mode: "pty",
|
|
243
|
+
});
|
|
244
|
+
recommendations.push({
|
|
245
|
+
option: "Run in background",
|
|
246
|
+
priority: "low",
|
|
247
|
+
reason: "AI implements headlessly while you keep working here",
|
|
248
|
+
mode: "background",
|
|
249
|
+
});
|
|
250
|
+
recommendations.push({
|
|
251
|
+
option: "Stay in current session",
|
|
252
|
+
priority: "low",
|
|
253
|
+
reason: "Continue working in this terminal session",
|
|
254
|
+
mode: "stay",
|
|
255
|
+
});
|
|
256
|
+
return recommendations;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Format environment detection as a human-readable report.
|
|
260
|
+
* Used by the detect_environment tool.
|
|
261
|
+
*/
|
|
262
|
+
export function formatEnvironmentReport(ide, terminalName) {
|
|
263
|
+
const lines = [
|
|
264
|
+
`## Environment Detection`,
|
|
265
|
+
``,
|
|
266
|
+
`**IDE/Editor:** ${ide.name}`,
|
|
267
|
+
`**Detection Method:** ${ide.detectionSource}`,
|
|
268
|
+
`**Terminal:** ${terminalName}`,
|
|
269
|
+
`**Platform:** ${process.platform}`,
|
|
270
|
+
``,
|
|
271
|
+
];
|
|
272
|
+
if (ide.version) {
|
|
273
|
+
lines.push(`**Version:** ${ide.version}`, ``);
|
|
274
|
+
}
|
|
275
|
+
lines.push(`### Capabilities`, ``);
|
|
276
|
+
if (ide.hasIntegratedTerminal) {
|
|
277
|
+
lines.push(`- ✓ Has integrated terminal`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
lines.push(`- ✗ No integrated terminal`);
|
|
281
|
+
}
|
|
282
|
+
if (ide.canOpenInWindow) {
|
|
283
|
+
if (ide.cliAvailable === false) {
|
|
284
|
+
lines.push(`- ⚠ Can open new window via CLI — but \`${ide.cliBinary || "cli"}\` NOT found in PATH`);
|
|
285
|
+
if (ide.cliInstallHint) {
|
|
286
|
+
lines.push(` Fix: ${ide.cliInstallHint}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (ide.cliAvailable === true) {
|
|
290
|
+
lines.push(`- ✓ Can open new window via CLI (\`${ide.cliBinary}\` available)`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
lines.push(`- ✓ Can open new window via CLI`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
lines.push(`- ✗ Cannot open new window via CLI`);
|
|
298
|
+
}
|
|
299
|
+
lines.push(``, `### Recommended Launch Options`, ``);
|
|
300
|
+
const recommendations = generateEnvironmentRecommendations(ide);
|
|
301
|
+
for (const rec of recommendations) {
|
|
302
|
+
const priorityBadge = rec.priority === "high" ? " (Recommended)" :
|
|
303
|
+
rec.priority === "medium" ? "" : "";
|
|
304
|
+
lines.push(`- **${rec.option}${priorityBadge}** — ${rec.reason}`);
|
|
305
|
+
}
|
|
306
|
+
return lines.join("\n");
|
|
307
|
+
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map plan types to git branch prefixes.
|
|
3
|
+
*/
|
|
4
|
+
export declare const TYPE_TO_PREFIX: Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Parse YAML frontmatter from plan content.
|
|
7
|
+
* Returns a map of key-value pairs, or null if no frontmatter found.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseFrontmatter(content: string): Record<string, string> | null;
|
|
10
|
+
/**
|
|
11
|
+
* Update or insert a field in the plan's YAML frontmatter.
|
|
12
|
+
* Returns the updated file content.
|
|
13
|
+
*/
|
|
14
|
+
export declare function upsertFrontmatterField(content: string, key: string, value: string): string;
|
|
1
15
|
/**
|
|
2
16
|
* Sections extracted from a plan for use in a PR body.
|
|
3
17
|
*/
|
|
@@ -13,6 +27,20 @@ export interface PlanSections {
|
|
|
13
27
|
/** The raw plan filename */
|
|
14
28
|
filename: string;
|
|
15
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract the branch name from plan frontmatter.
|
|
32
|
+
*
|
|
33
|
+
* Looks for `branch: feature/xyz` in YAML frontmatter.
|
|
34
|
+
* Returns the branch name string, or null if not found.
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractBranch(planContent: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Extract GitHub issue references from plan frontmatter.
|
|
39
|
+
*
|
|
40
|
+
* Looks for `issues: [42, 51]` in YAML frontmatter.
|
|
41
|
+
* Returns an array of issue numbers, or an empty array if none found.
|
|
42
|
+
*/
|
|
43
|
+
export declare function extractIssueRefs(planContent: string): number[];
|
|
16
44
|
/**
|
|
17
45
|
* Extract relevant sections from a plan markdown file for composing a PR body.
|
|
18
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-extract.d.ts","sourceRoot":"","sources":["../../src/utils/plan-extract.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAuCvF;AAmCD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAwBlE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"plan-extract.d.ts","sourceRoot":"","sources":["../../src/utils/plan-extract.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOjD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAY/E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB1F;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAShE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAY9D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAuCvF;AAmCD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAwBlE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA0C9C"}
|
|
@@ -2,6 +2,90 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
const CORTEX_DIR = ".cortex";
|
|
4
4
|
const PLANS_DIR = "plans";
|
|
5
|
+
/**
|
|
6
|
+
* Map plan types to git branch prefixes.
|
|
7
|
+
*/
|
|
8
|
+
export const TYPE_TO_PREFIX = {
|
|
9
|
+
feature: "feature",
|
|
10
|
+
bugfix: "bugfix",
|
|
11
|
+
refactor: "refactor",
|
|
12
|
+
architecture: "refactor",
|
|
13
|
+
spike: "feature",
|
|
14
|
+
docs: "docs",
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Parse YAML frontmatter from plan content.
|
|
18
|
+
* Returns a map of key-value pairs, or null if no frontmatter found.
|
|
19
|
+
*/
|
|
20
|
+
export function parseFrontmatter(content) {
|
|
21
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
22
|
+
if (!match)
|
|
23
|
+
return null;
|
|
24
|
+
const fm = {};
|
|
25
|
+
for (const line of match[1].split("\n")) {
|
|
26
|
+
const kv = line.match(/^(\w+):\s*"?([^"\n]*)"?\s*$/);
|
|
27
|
+
if (kv) {
|
|
28
|
+
fm[kv[1]] = kv[2].trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return fm;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Update or insert a field in the plan's YAML frontmatter.
|
|
35
|
+
* Returns the updated file content.
|
|
36
|
+
*/
|
|
37
|
+
export function upsertFrontmatterField(content, key, value) {
|
|
38
|
+
const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
|
|
39
|
+
if (!fmMatch)
|
|
40
|
+
return content;
|
|
41
|
+
const fmBody = fmMatch[2];
|
|
42
|
+
const fieldRegex = new RegExp(`^${key}:\\s*.*$`, "m");
|
|
43
|
+
let updatedFm;
|
|
44
|
+
if (fieldRegex.test(fmBody)) {
|
|
45
|
+
// Update existing field
|
|
46
|
+
updatedFm = fmBody.replace(fieldRegex, `${key}: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Insert before the closing ---
|
|
50
|
+
updatedFm = fmBody + `\n${key}: ${value}`;
|
|
51
|
+
}
|
|
52
|
+
return fmMatch[1] + updatedFm + fmMatch[3] + content.slice(fmMatch[0].length);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract the branch name from plan frontmatter.
|
|
56
|
+
*
|
|
57
|
+
* Looks for `branch: feature/xyz` in YAML frontmatter.
|
|
58
|
+
* Returns the branch name string, or null if not found.
|
|
59
|
+
*/
|
|
60
|
+
export function extractBranch(planContent) {
|
|
61
|
+
const frontmatterMatch = planContent.match(/^---\n([\s\S]*?)\n---/);
|
|
62
|
+
if (!frontmatterMatch)
|
|
63
|
+
return null;
|
|
64
|
+
const branchMatch = frontmatterMatch[1].match(/^branch:\s*(.+)$/m);
|
|
65
|
+
if (!branchMatch)
|
|
66
|
+
return null;
|
|
67
|
+
const branch = branchMatch[1].trim();
|
|
68
|
+
return branch || null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Extract GitHub issue references from plan frontmatter.
|
|
72
|
+
*
|
|
73
|
+
* Looks for `issues: [42, 51]` in YAML frontmatter.
|
|
74
|
+
* Returns an array of issue numbers, or an empty array if none found.
|
|
75
|
+
*/
|
|
76
|
+
export function extractIssueRefs(planContent) {
|
|
77
|
+
const frontmatterMatch = planContent.match(/^---\n([\s\S]*?)\n---/);
|
|
78
|
+
if (!frontmatterMatch)
|
|
79
|
+
return [];
|
|
80
|
+
// Match issues: [42, 51] or issues: [42]
|
|
81
|
+
const issuesMatch = frontmatterMatch[1].match(/issues:\s*\[([^\]]*)\]/);
|
|
82
|
+
if (!issuesMatch)
|
|
83
|
+
return [];
|
|
84
|
+
return issuesMatch[1]
|
|
85
|
+
.split(",")
|
|
86
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
87
|
+
.filter((n) => !isNaN(n) && n > 0);
|
|
88
|
+
}
|
|
5
89
|
/**
|
|
6
90
|
* Extract relevant sections from a plan markdown file for composing a PR body.
|
|
7
91
|
*
|
|
@@ -106,7 +190,12 @@ export function findPlanContent(worktree, planFilename, branchName) {
|
|
|
106
190
|
if (!fs.existsSync(plansDir))
|
|
107
191
|
return null;
|
|
108
192
|
if (planFilename) {
|
|
109
|
-
|
|
193
|
+
// Prevent path traversal — resolve and verify the path is strictly inside plansDir
|
|
194
|
+
const filepath = path.resolve(plansDir, planFilename);
|
|
195
|
+
const resolvedPlansDir = path.resolve(plansDir);
|
|
196
|
+
if (!filepath.startsWith(resolvedPlansDir + path.sep)) {
|
|
197
|
+
return null; // Reject traversal attempts and directory-level references (".", "")
|
|
198
|
+
}
|
|
110
199
|
if (fs.existsSync(filepath)) {
|
|
111
200
|
return { content: fs.readFileSync(filepath, "utf-8"), filename: planFilename };
|
|
112
201
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL Loop Utilities
|
|
3
|
+
*
|
|
4
|
+
* State management, plan task parsing, build/test command auto-detection,
|
|
5
|
+
* and progress formatting for the implement agent's iterative task loop.
|
|
6
|
+
*
|
|
7
|
+
* State is persisted to `.cortex/repl-state.json` so it survives context
|
|
8
|
+
* compaction, session restarts, and agent switches.
|
|
9
|
+
*/
|
|
10
|
+
export type TaskStatus = "pending" | "in_progress" | "passed" | "failed" | "skipped";
|
|
11
|
+
export interface TaskIteration {
|
|
12
|
+
/** ISO timestamp of this iteration */
|
|
13
|
+
at: string;
|
|
14
|
+
/** Outcome of this iteration */
|
|
15
|
+
result: "pass" | "fail" | "skip";
|
|
16
|
+
/** Test output summary, error message, or skip reason */
|
|
17
|
+
detail: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ReplTask {
|
|
20
|
+
/** Zero-based index in the task list */
|
|
21
|
+
index: number;
|
|
22
|
+
/** Task description from the plan */
|
|
23
|
+
description: string;
|
|
24
|
+
/** Acceptance criteria extracted from `- AC:` lines under the task */
|
|
25
|
+
acceptanceCriteria: string[];
|
|
26
|
+
/** Current status in the state machine */
|
|
27
|
+
status: TaskStatus;
|
|
28
|
+
/** Number of failed attempts (resets on pass) */
|
|
29
|
+
retries: number;
|
|
30
|
+
/** Full iteration history */
|
|
31
|
+
iterations: TaskIteration[];
|
|
32
|
+
/** ISO timestamp when the task was started */
|
|
33
|
+
startedAt?: string;
|
|
34
|
+
/** ISO timestamp when the task was completed/failed/skipped */
|
|
35
|
+
completedAt?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ReplState {
|
|
38
|
+
/** Source plan filename */
|
|
39
|
+
planFilename: string;
|
|
40
|
+
/** ISO timestamp when the loop started */
|
|
41
|
+
startedAt: string;
|
|
42
|
+
/** ISO timestamp when the loop completed (all tasks done or aborted) */
|
|
43
|
+
completedAt?: string;
|
|
44
|
+
/** Auto-detected or user-provided build command */
|
|
45
|
+
buildCommand: string | null;
|
|
46
|
+
/** Auto-detected or user-provided test command */
|
|
47
|
+
testCommand: string | null;
|
|
48
|
+
/** Optional lint command */
|
|
49
|
+
lintCommand: string | null;
|
|
50
|
+
/** Max retries per task before escalating to user (default: 3) */
|
|
51
|
+
maxRetries: number;
|
|
52
|
+
/** Index of the currently active task (-1 if not started) */
|
|
53
|
+
currentTaskIndex: number;
|
|
54
|
+
/** All tasks in the loop */
|
|
55
|
+
tasks: ReplTask[];
|
|
56
|
+
}
|
|
57
|
+
export interface CortexConfig {
|
|
58
|
+
/** Max retries per task before escalating to user */
|
|
59
|
+
maxRetries?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface CommandDetection {
|
|
62
|
+
buildCommand: string | null;
|
|
63
|
+
testCommand: string | null;
|
|
64
|
+
lintCommand: string | null;
|
|
65
|
+
/** Detected framework name (e.g., "vitest", "jest", "pytest") */
|
|
66
|
+
framework: string;
|
|
67
|
+
/** Whether auto-detection found anything */
|
|
68
|
+
detected: boolean;
|
|
69
|
+
}
|
|
70
|
+
export interface ParsedTask {
|
|
71
|
+
description: string;
|
|
72
|
+
acceptanceCriteria: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse plan tasks from plan markdown content.
|
|
76
|
+
*
|
|
77
|
+
* Looks for unchecked checkbox items (`- [ ] ...`) in a `## Tasks` section.
|
|
78
|
+
* Falls back to any unchecked checkboxes anywhere in the document.
|
|
79
|
+
* Strips the `Task N:` prefix if present to get a clean description.
|
|
80
|
+
* Extracts `- AC:` lines immediately following each task as acceptance criteria.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseTasksFromPlan(planContent: string): string[];
|
|
83
|
+
/**
|
|
84
|
+
* Parse plan tasks with their acceptance criteria.
|
|
85
|
+
*
|
|
86
|
+
* Returns structured tasks including `- AC:` lines found under each checkbox item.
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseTasksWithAC(planContent: string): ParsedTask[];
|
|
89
|
+
/**
|
|
90
|
+
* Auto-detect build, test, and lint commands from project configuration files.
|
|
91
|
+
*
|
|
92
|
+
* Detection priority:
|
|
93
|
+
* 1. package.json (npm/node projects)
|
|
94
|
+
* 2. Makefile
|
|
95
|
+
* 3. Cargo.toml (Rust)
|
|
96
|
+
* 4. go.mod (Go)
|
|
97
|
+
* 5. pyproject.toml / setup.py (Python)
|
|
98
|
+
* 6. mix.exs (Elixir)
|
|
99
|
+
*/
|
|
100
|
+
export declare function detectCommands(cwd: string): Promise<CommandDetection>;
|
|
101
|
+
/**
|
|
102
|
+
* Read cortex config from .cortex/config.json.
|
|
103
|
+
* Returns an empty config if the file doesn't exist or is malformed.
|
|
104
|
+
*/
|
|
105
|
+
export declare function readCortexConfig(cwd: string): CortexConfig;
|
|
106
|
+
/**
|
|
107
|
+
* Read the current REPL state from .cortex/repl-state.json.
|
|
108
|
+
* Returns null if no state file exists.
|
|
109
|
+
*/
|
|
110
|
+
export declare function readReplState(cwd: string): ReplState | null;
|
|
111
|
+
/**
|
|
112
|
+
* Write REPL state to .cortex/repl-state.json.
|
|
113
|
+
* Uses atomic write (temp file + rename) to prevent corruption.
|
|
114
|
+
*/
|
|
115
|
+
export declare function writeReplState(cwd: string, state: ReplState): void;
|
|
116
|
+
/**
|
|
117
|
+
* Get the next pending task (first task with status "pending").
|
|
118
|
+
* Returns null if all tasks are done.
|
|
119
|
+
*/
|
|
120
|
+
export declare function getNextTask(state: ReplState): ReplTask | null;
|
|
121
|
+
/**
|
|
122
|
+
* Get the currently in-progress task.
|
|
123
|
+
* Returns null if no task is in progress.
|
|
124
|
+
*/
|
|
125
|
+
export declare function getCurrentTask(state: ReplState): ReplTask | null;
|
|
126
|
+
/**
|
|
127
|
+
* Check if the loop is complete (no pending or in_progress tasks).
|
|
128
|
+
*/
|
|
129
|
+
export declare function isLoopComplete(state: ReplState): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Detect if a previous REPL loop was interrupted mid-task.
|
|
132
|
+
* Returns the incomplete state if found, null otherwise.
|
|
133
|
+
*/
|
|
134
|
+
export declare function detectIncompleteState(cwd: string): ReplState | null;
|
|
135
|
+
/**
|
|
136
|
+
* Format the current loop status as a human-readable string.
|
|
137
|
+
* Used by repl_status tool output.
|
|
138
|
+
*/
|
|
139
|
+
export declare function formatProgress(state: ReplState): string;
|
|
140
|
+
/**
|
|
141
|
+
* Format a full summary of the loop results for PR body inclusion.
|
|
142
|
+
* Returns a markdown block with a results table, counts, and timing.
|
|
143
|
+
*/
|
|
144
|
+
export declare function formatSummary(state: ReplState): string;
|
|
145
|
+
//# sourceMappingURL=repl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../src/utils/repl.ts"],"names":[],"mappings":"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"}
|