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
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import prompts from "prompts";
|
|
6
6
|
import { PRIMARY_AGENTS, SUBAGENTS, ALL_AGENTS, DISABLED_BUILTIN_AGENTS, STALE_AGENT_FILES, getPrimaryChoices, getSubagentChoices, } from "./registry.js";
|
|
7
|
-
const VERSION = "
|
|
7
|
+
const VERSION = "4.0.0";
|
|
8
8
|
const PLUGIN_NAME = "cortex-agents";
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
@@ -395,7 +395,7 @@ async function promptModelSelection() {
|
|
|
395
395
|
description: "provider/model format",
|
|
396
396
|
value: "__custom__",
|
|
397
397
|
});
|
|
398
|
-
console.log("Primary agents (
|
|
398
|
+
console.log("Primary agents (architect, implement, fix) handle complex tasks.\nUse your best available model.\n");
|
|
399
399
|
const { primaryModel } = await prompts({
|
|
400
400
|
type: "select",
|
|
401
401
|
name: "primaryModel",
|
|
@@ -429,7 +429,7 @@ async function promptModelSelection() {
|
|
|
429
429
|
description: "provider/model format",
|
|
430
430
|
value: "__custom__",
|
|
431
431
|
});
|
|
432
|
-
console.log("Subagents (
|
|
432
|
+
console.log("Subagents (debug, coder, testing, security, devops, audit) handle focused tasks.\nA faster/cheaper model works great here.\n");
|
|
433
433
|
const { subagentModel } = await prompts({
|
|
434
434
|
type: "select",
|
|
435
435
|
name: "subagentModel",
|
|
@@ -631,20 +631,17 @@ EXAMPLES:
|
|
|
631
631
|
npx ${PLUGIN_NAME} status # Check status
|
|
632
632
|
|
|
633
633
|
AGENTS:
|
|
634
|
-
Primary (
|
|
634
|
+
Primary (architect, implement, fix):
|
|
635
635
|
Handle complex tasks — select your best model.
|
|
636
636
|
|
|
637
|
-
Subagents (
|
|
637
|
+
Subagents (debug, coder, testing, security, devops, audit):
|
|
638
638
|
Handle focused tasks — a fast/cheap model works great.
|
|
639
639
|
|
|
640
|
-
TOOLS (
|
|
640
|
+
TOOLS (29):
|
|
641
641
|
cortex_init, cortex_status .cortex directory management
|
|
642
642
|
cortex_configure Per-project model configuration
|
|
643
643
|
worktree_create, worktree_list Git worktree management
|
|
644
644
|
worktree_remove, worktree_open
|
|
645
|
-
worktree_launch Launch worktree (terminal/PTY/background)
|
|
646
|
-
detect_environment Detect IDE/terminal for launch options
|
|
647
|
-
get_environment_info Quick environment info for agents
|
|
648
645
|
branch_create, branch_status Git branch operations
|
|
649
646
|
branch_switch
|
|
650
647
|
plan_save, plan_list Plan persistence
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA+LlD,eAAO,MAAM,YAAY,EAAE,MAkJ1B,CAAC;AAGF,eAAe,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,19 +6,23 @@ import * as plan from "./tools/plan";
|
|
|
6
6
|
import * as session from "./tools/session";
|
|
7
7
|
import * as docs from "./tools/docs";
|
|
8
8
|
import * as task from "./tools/task";
|
|
9
|
-
import * as environment from "./tools/environment";
|
|
10
9
|
import * as github from "./tools/github";
|
|
11
10
|
import * as repl from "./tools/repl";
|
|
11
|
+
import * as qualityGate from "./tools/quality-gate";
|
|
12
12
|
// ─── Agent Descriptions (for handover toasts) ───────────────────────────────
|
|
13
13
|
const AGENT_DESCRIPTIONS = {
|
|
14
14
|
implement: "Development mode — ready to implement",
|
|
15
15
|
architect: "Planning mode — read-only analysis",
|
|
16
|
-
fix: "
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
fix: "Quick fix mode — fast turnaround",
|
|
17
|
+
debug: "Debug subagent — root cause analysis",
|
|
18
|
+
coder: "Coder subagent — multi-layer implementation",
|
|
19
|
+
testing: "Testing subagent — writing tests",
|
|
20
|
+
security: "Security subagent — vulnerability audit",
|
|
21
|
+
devops: "DevOps subagent — CI/CD and deployment",
|
|
22
|
+
audit: "Audit subagent — code quality assessment",
|
|
23
|
+
refactor: "Refactor subagent — behavior-preserving restructuring",
|
|
24
|
+
"docs-writer": "Docs subagent — documentation generation",
|
|
25
|
+
perf: "Perf subagent — performance analysis",
|
|
22
26
|
};
|
|
23
27
|
const TOOL_NOTIFICATIONS = {
|
|
24
28
|
task_finalize: {
|
|
@@ -44,6 +48,7 @@ const TOOL_NOTIFICATIONS = {
|
|
|
44
48
|
errorTitle: "Plan Delete Failed",
|
|
45
49
|
errorMsg: (_, out) => out.substring(0, 100),
|
|
46
50
|
},
|
|
51
|
+
// plan_commit — excluded: uses factory-based toasts in createCommit()
|
|
47
52
|
session_save: {
|
|
48
53
|
successTitle: "Session Saved",
|
|
49
54
|
successMsg: () => "Session summary recorded",
|
|
@@ -107,6 +112,16 @@ const TOOL_NOTIFICATIONS = {
|
|
|
107
112
|
errorTitle: "Report Failed",
|
|
108
113
|
errorMsg: (_, out) => out.substring(0, 100),
|
|
109
114
|
},
|
|
115
|
+
quality_gate_summary: {
|
|
116
|
+
successTitle: "Quality Gate",
|
|
117
|
+
successMsg: (_args, output) => {
|
|
118
|
+
const recMatch = output.match(/\*\*Recommendation:\s*(GO|NO-GO|GO-WITH-WARNINGS)\*\*/);
|
|
119
|
+
return recMatch ? `Recommendation: ${recMatch[1]}` : "Summary generated";
|
|
120
|
+
},
|
|
121
|
+
errorTitle: "Quality Gate Failed",
|
|
122
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
123
|
+
successDuration: 5000,
|
|
124
|
+
},
|
|
110
125
|
};
|
|
111
126
|
// ─── Error Message Extraction ────────────────────────────────────────────────
|
|
112
127
|
//
|
|
@@ -145,7 +160,6 @@ export const CortexPlugin = async (ctx) => {
|
|
|
145
160
|
worktree_list: worktree.list,
|
|
146
161
|
worktree_remove: worktree.createRemove(ctx.client),
|
|
147
162
|
worktree_open: worktree.open,
|
|
148
|
-
worktree_launch: worktree.createLaunch(ctx.client, ctx.$),
|
|
149
163
|
// Branch tools - git branch operations (factory for toast notifications)
|
|
150
164
|
branch_create: branch.createCreate(ctx.client),
|
|
151
165
|
branch_status: branch.status,
|
|
@@ -155,6 +169,7 @@ export const CortexPlugin = async (ctx) => {
|
|
|
155
169
|
plan_list: plan.list,
|
|
156
170
|
plan_load: plan.load,
|
|
157
171
|
plan_delete: plan.delete_,
|
|
172
|
+
plan_commit: plan.createCommit(ctx.client),
|
|
158
173
|
// Session tools - session summaries with decisions
|
|
159
174
|
session_save: session.save,
|
|
160
175
|
session_list: session.list,
|
|
@@ -166,9 +181,6 @@ export const CortexPlugin = async (ctx) => {
|
|
|
166
181
|
docs_index: docs.index,
|
|
167
182
|
// Task tools - finalize workflow (commit, push, PR)
|
|
168
183
|
task_finalize: task.finalize,
|
|
169
|
-
// Environment tools - IDE/terminal detection for contextual options
|
|
170
|
-
detect_environment: environment.detectEnvironment,
|
|
171
|
-
get_environment_info: environment.getEnvironmentInfo,
|
|
172
184
|
// GitHub integration tools - work item listing, issue selection, project boards
|
|
173
185
|
github_status: github.status,
|
|
174
186
|
github_issues: github.issues,
|
|
@@ -177,7 +189,10 @@ export const CortexPlugin = async (ctx) => {
|
|
|
177
189
|
repl_init: repl.init,
|
|
178
190
|
repl_status: repl.status,
|
|
179
191
|
repl_report: repl.report,
|
|
192
|
+
repl_resume: repl.resume,
|
|
180
193
|
repl_summary: repl.summary,
|
|
194
|
+
// Quality gate aggregation tool
|
|
195
|
+
quality_gate_summary: qualityGate.qualityGateSummary,
|
|
181
196
|
},
|
|
182
197
|
// ── Post-execution toast notifications ────────────────────────────────
|
|
183
198
|
//
|
|
@@ -256,23 +271,6 @@ export const CortexPlugin = async (ctx) => {
|
|
|
256
271
|
},
|
|
257
272
|
});
|
|
258
273
|
}
|
|
259
|
-
// PTY exited notifications (relevant for worktree terminal sessions)
|
|
260
|
-
if (event.type === "pty.exited") {
|
|
261
|
-
const rawExitCode = event.properties.exitCode;
|
|
262
|
-
const exitCode = typeof rawExitCode === "number" && Number.isInteger(rawExitCode)
|
|
263
|
-
? rawExitCode
|
|
264
|
-
: -1;
|
|
265
|
-
await ctx.client.tui.showToast({
|
|
266
|
-
body: {
|
|
267
|
-
title: "Terminal Exited",
|
|
268
|
-
message: exitCode === 0
|
|
269
|
-
? "Terminal session completed successfully"
|
|
270
|
-
: `Terminal exited with code ${exitCode}`,
|
|
271
|
-
variant: exitCode === 0 ? "success" : "warning",
|
|
272
|
-
duration: exitCode === 0 ? 4000 : 8000,
|
|
273
|
-
},
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
274
|
}
|
|
277
275
|
catch {
|
|
278
276
|
// Toast failure is non-fatal — silently ignore
|
package/dist/registry.d.ts
CHANGED
|
@@ -19,16 +19,16 @@ export interface ModelEntry {
|
|
|
19
19
|
}
|
|
20
20
|
export declare const MODEL_REGISTRY: ModelEntry[];
|
|
21
21
|
/** Primary agents receive the best available model */
|
|
22
|
-
export declare const PRIMARY_AGENTS: readonly ["
|
|
22
|
+
export declare const PRIMARY_AGENTS: readonly ["architect", "implement", "fix"];
|
|
23
23
|
/** Subagents receive a fast/cost-effective model */
|
|
24
|
-
export declare const SUBAGENTS: readonly ["
|
|
24
|
+
export declare const SUBAGENTS: readonly ["debug", "coder", "testing", "security", "devops", "audit", "refactor", "docs-writer", "perf"];
|
|
25
25
|
/** All agent names combined */
|
|
26
|
-
export declare const ALL_AGENTS: readonly ["
|
|
26
|
+
export declare const ALL_AGENTS: readonly ["architect", "implement", "fix", "debug", "coder", "testing", "security", "devops", "audit", "refactor", "docs-writer", "perf"];
|
|
27
27
|
/** OpenCode built-in agents disabled when cortex-agents is installed.
|
|
28
28
|
* Replaced by cortex equivalents: build → implement, plan → architect */
|
|
29
29
|
export declare const DISABLED_BUILTIN_AGENTS: readonly ["build", "plan"];
|
|
30
30
|
/** Old agent files to clean up from previous cortex-agents versions */
|
|
31
|
-
export declare const STALE_AGENT_FILES: readonly ["build.md", "plan.md", "
|
|
31
|
+
export declare const STALE_AGENT_FILES: readonly ["build.md", "plan.md", "review.md", "fullstack.md", "crosslayer.md", "qa.md", "guard.md", "ship.md"];
|
|
32
32
|
/**
|
|
33
33
|
* Build the interactive choices list for primary model selection.
|
|
34
34
|
* Shows premium and standard tier models (excluding fast).
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,UAAU,EAuGtC,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,UAAU,EAuGtC,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,cAAc,4CAA6C,CAAC;AAEzE,oDAAoD;AACpD,eAAO,MAAM,SAAS,0GAA2G,CAAC;AAElI,+BAA+B;AAC/B,eAAO,MAAM,UAAU,2IAA6C,CAAC;AAErE;0EAC0E;AAC1E,eAAO,MAAM,uBAAuB,4BAA6B,CAAC;AAElE,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,gHASpB,CAAC;AAEX;;;GAGG;AACH,wBAAgB,iBAAiB;;;;IAMhC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM;;;;IAcxD"}
|
package/dist/registry.js
CHANGED
|
@@ -105,9 +105,9 @@ export const MODEL_REGISTRY = [
|
|
|
105
105
|
},
|
|
106
106
|
];
|
|
107
107
|
/** Primary agents receive the best available model */
|
|
108
|
-
export const PRIMARY_AGENTS = ["
|
|
108
|
+
export const PRIMARY_AGENTS = ["architect", "implement", "fix"];
|
|
109
109
|
/** Subagents receive a fast/cost-effective model */
|
|
110
|
-
export const SUBAGENTS = ["
|
|
110
|
+
export const SUBAGENTS = ["debug", "coder", "testing", "security", "devops", "audit", "refactor", "docs-writer", "perf"];
|
|
111
111
|
/** All agent names combined */
|
|
112
112
|
export const ALL_AGENTS = [...PRIMARY_AGENTS, ...SUBAGENTS];
|
|
113
113
|
/** OpenCode built-in agents disabled when cortex-agents is installed.
|
|
@@ -117,12 +117,12 @@ export const DISABLED_BUILTIN_AGENTS = ["build", "plan"];
|
|
|
117
117
|
export const STALE_AGENT_FILES = [
|
|
118
118
|
"build.md",
|
|
119
119
|
"plan.md",
|
|
120
|
-
"debug.md",
|
|
121
120
|
"review.md",
|
|
122
121
|
"fullstack.md",
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
122
|
+
"crosslayer.md",
|
|
123
|
+
"qa.md",
|
|
124
|
+
"guard.md",
|
|
125
|
+
"ship.md",
|
|
126
126
|
];
|
|
127
127
|
/**
|
|
128
128
|
* Build the interactive choices list for primary model selection.
|
package/dist/tools/branch.d.ts
CHANGED
|
@@ -9,10 +9,10 @@ export declare function createCreate(client: Client): {
|
|
|
9
9
|
args: {
|
|
10
10
|
name: import("zod").ZodString;
|
|
11
11
|
type: import("zod").ZodEnum<{
|
|
12
|
+
refactor: "refactor";
|
|
12
13
|
feature: "feature";
|
|
13
14
|
bugfix: "bugfix";
|
|
14
15
|
hotfix: "hotfix";
|
|
15
|
-
refactor: "refactor";
|
|
16
16
|
docs: "docs";
|
|
17
17
|
test: "test";
|
|
18
18
|
chore: "chore";
|
|
@@ -20,7 +20,7 @@ export declare function createCreate(client: Client): {
|
|
|
20
20
|
};
|
|
21
21
|
execute(args: {
|
|
22
22
|
name: string;
|
|
23
|
-
type: "
|
|
23
|
+
type: "refactor" | "feature" | "bugfix" | "hotfix" | "docs" | "test" | "chore";
|
|
24
24
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
25
25
|
};
|
|
26
26
|
export declare const status: {
|
package/dist/tools/docs.d.ts
CHANGED
|
@@ -35,13 +35,13 @@ export declare const list: {
|
|
|
35
35
|
args: {
|
|
36
36
|
type: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
37
37
|
feature: "feature";
|
|
38
|
-
all: "all";
|
|
39
38
|
decision: "decision";
|
|
39
|
+
all: "all";
|
|
40
40
|
flow: "flow";
|
|
41
41
|
}>>;
|
|
42
42
|
};
|
|
43
43
|
execute(args: {
|
|
44
|
-
type?: "feature" | "
|
|
44
|
+
type?: "feature" | "decision" | "all" | "flow" | undefined;
|
|
45
45
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
46
46
|
};
|
|
47
47
|
export declare const index: {
|
package/dist/tools/github.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ export declare const issues: {
|
|
|
7
7
|
description: string;
|
|
8
8
|
args: {
|
|
9
9
|
state: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
10
|
-
closed: "closed";
|
|
11
|
-
open: "open";
|
|
12
10
|
all: "all";
|
|
11
|
+
open: "open";
|
|
12
|
+
closed: "closed";
|
|
13
13
|
}>>;
|
|
14
14
|
labels: import("zod").ZodOptional<import("zod").ZodString>;
|
|
15
15
|
milestone: import("zod").ZodOptional<import("zod").ZodString>;
|
|
@@ -18,7 +18,7 @@ export declare const issues: {
|
|
|
18
18
|
detailed: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
19
19
|
};
|
|
20
20
|
execute(args: {
|
|
21
|
-
state?: "
|
|
21
|
+
state?: "all" | "open" | "closed" | undefined;
|
|
22
22
|
labels?: string | undefined;
|
|
23
23
|
milestone?: string | undefined;
|
|
24
24
|
assignee?: string | undefined;
|
package/dist/tools/plan.d.ts
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
type Client = PluginInput["client"];
|
|
1
3
|
export declare const save: {
|
|
2
4
|
description: string;
|
|
3
5
|
args: {
|
|
4
6
|
title: import("zod").ZodString;
|
|
5
7
|
type: import("zod").ZodEnum<{
|
|
8
|
+
refactor: "refactor";
|
|
6
9
|
feature: "feature";
|
|
7
10
|
bugfix: "bugfix";
|
|
8
|
-
refactor: "refactor";
|
|
9
11
|
spike: "spike";
|
|
10
12
|
docs: "docs";
|
|
11
13
|
architecture: "architecture";
|
|
12
14
|
}>;
|
|
13
15
|
content: import("zod").ZodString;
|
|
14
16
|
tasks: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
17
|
+
branch: import("zod").ZodOptional<import("zod").ZodString>;
|
|
15
18
|
};
|
|
16
19
|
execute(args: {
|
|
17
20
|
title: string;
|
|
18
|
-
type: "
|
|
21
|
+
type: "refactor" | "feature" | "bugfix" | "spike" | "docs" | "architecture";
|
|
19
22
|
content: string;
|
|
20
23
|
tasks?: string[] | undefined;
|
|
24
|
+
branch?: string | undefined;
|
|
21
25
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
22
26
|
};
|
|
23
27
|
export declare const list: {
|
|
24
28
|
description: string;
|
|
25
29
|
args: {
|
|
26
30
|
type: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
31
|
+
refactor: "refactor";
|
|
27
32
|
feature: "feature";
|
|
28
33
|
bugfix: "bugfix";
|
|
29
|
-
refactor: "refactor";
|
|
30
34
|
spike: "spike";
|
|
31
35
|
docs: "docs";
|
|
32
36
|
architecture: "architecture";
|
|
@@ -34,7 +38,7 @@ export declare const list: {
|
|
|
34
38
|
}>>;
|
|
35
39
|
};
|
|
36
40
|
execute(args: {
|
|
37
|
-
type?: "
|
|
41
|
+
type?: "refactor" | "feature" | "bugfix" | "spike" | "docs" | "architecture" | "all" | undefined;
|
|
38
42
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
39
43
|
};
|
|
40
44
|
export declare const load: {
|
|
@@ -55,5 +59,25 @@ export declare const delete_: {
|
|
|
55
59
|
filename: string;
|
|
56
60
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
57
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Factory function that creates the plan_commit tool with access
|
|
64
|
+
* to the OpenCode client for toast notifications.
|
|
65
|
+
*
|
|
66
|
+
* Creates a git branch from plan metadata, stages .cortex/ artifacts,
|
|
67
|
+
* commits them, and writes the branch name back into the plan frontmatter.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createCommit(client: Client): {
|
|
70
|
+
description: string;
|
|
71
|
+
args: {
|
|
72
|
+
planFilename: import("zod").ZodString;
|
|
73
|
+
branchType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
74
|
+
branchName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
75
|
+
};
|
|
76
|
+
execute(args: {
|
|
77
|
+
planFilename: string;
|
|
78
|
+
branchType?: string | undefined;
|
|
79
|
+
branchName?: string | undefined;
|
|
80
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
81
|
+
};
|
|
58
82
|
export { delete_ as delete };
|
|
59
83
|
//# sourceMappingURL=plan.d.ts.map
|
package/dist/tools/plan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/tools/plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/tools/plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAgBvD,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AA2BpC,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;CAgEf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;CAkEf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CA6Bf,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;CAwBlB,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;EAsN1C;AAGD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,CAAC"}
|
package/dist/tools/plan.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
+
import { git } from "../utils/shell.js";
|
|
5
|
+
import { TYPE_TO_PREFIX, parseFrontmatter, upsertFrontmatterField, } from "../utils/plan-extract.js";
|
|
4
6
|
const CORTEX_DIR = ".cortex";
|
|
5
7
|
const PLANS_DIR = "plans";
|
|
8
|
+
const PROTECTED_BRANCHES = ["main", "master", "develop", "production", "staging"];
|
|
6
9
|
function slugify(text) {
|
|
7
10
|
return text
|
|
8
11
|
.toLowerCase()
|
|
@@ -37,20 +40,25 @@ export const save = tool({
|
|
|
37
40
|
.array(tool.schema.string())
|
|
38
41
|
.optional()
|
|
39
42
|
.describe("Optional list of tasks"),
|
|
43
|
+
branch: tool.schema
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Optional branch name to store in frontmatter (set by plan_commit)"),
|
|
40
47
|
},
|
|
41
48
|
async execute(args, context) {
|
|
42
|
-
const { title, type, content, tasks } = args;
|
|
49
|
+
const { title, type, content, tasks, branch } = args;
|
|
43
50
|
const plansPath = ensureCortexDir(context.worktree);
|
|
44
51
|
const datePrefix = getDatePrefix();
|
|
45
52
|
const slug = slugify(title);
|
|
46
53
|
const filename = `${datePrefix}-${type}-${slug}.md`;
|
|
47
54
|
const filepath = path.join(plansPath, filename);
|
|
48
55
|
// Build frontmatter
|
|
56
|
+
const branchLine = branch ? `\nbranch: ${branch}` : "";
|
|
49
57
|
const frontmatter = `---
|
|
50
58
|
title: "${title}"
|
|
51
59
|
type: ${type}
|
|
52
60
|
created: ${new Date().toISOString()}
|
|
53
|
-
status: draft
|
|
61
|
+
status: draft${branchLine}
|
|
54
62
|
---
|
|
55
63
|
|
|
56
64
|
`;
|
|
@@ -144,7 +152,12 @@ export const load = tool({
|
|
|
144
152
|
async execute(args, context) {
|
|
145
153
|
const { filename } = args;
|
|
146
154
|
const plansPath = path.join(context.worktree, CORTEX_DIR, PLANS_DIR);
|
|
147
|
-
const filepath = path.
|
|
155
|
+
const filepath = path.resolve(plansPath, filename);
|
|
156
|
+
const resolvedPlansDir = path.resolve(plansPath);
|
|
157
|
+
// Prevent path traversal (../ or absolute paths)
|
|
158
|
+
if (!filepath.startsWith(resolvedPlansDir + path.sep)) {
|
|
159
|
+
return `✗ Invalid plan filename: path traversal not allowed`;
|
|
160
|
+
}
|
|
148
161
|
if (!fs.existsSync(filepath)) {
|
|
149
162
|
return `✗ Plan not found: ${filename}
|
|
150
163
|
|
|
@@ -165,7 +178,12 @@ export const delete_ = tool({
|
|
|
165
178
|
async execute(args, context) {
|
|
166
179
|
const { filename } = args;
|
|
167
180
|
const plansPath = path.join(context.worktree, CORTEX_DIR, PLANS_DIR);
|
|
168
|
-
const filepath = path.
|
|
181
|
+
const filepath = path.resolve(plansPath, filename);
|
|
182
|
+
const resolvedPlansDir = path.resolve(plansPath);
|
|
183
|
+
// Prevent path traversal (../ or absolute paths)
|
|
184
|
+
if (!filepath.startsWith(resolvedPlansDir + path.sep)) {
|
|
185
|
+
return `✗ Invalid plan filename: path traversal not allowed`;
|
|
186
|
+
}
|
|
169
187
|
if (!fs.existsSync(filepath)) {
|
|
170
188
|
return `✗ Plan not found: ${filename}`;
|
|
171
189
|
}
|
|
@@ -173,5 +191,215 @@ export const delete_ = tool({
|
|
|
173
191
|
return `✓ Deleted plan: ${filename}`;
|
|
174
192
|
},
|
|
175
193
|
});
|
|
194
|
+
/**
|
|
195
|
+
* Factory function that creates the plan_commit tool with access
|
|
196
|
+
* to the OpenCode client for toast notifications.
|
|
197
|
+
*
|
|
198
|
+
* Creates a git branch from plan metadata, stages .cortex/ artifacts,
|
|
199
|
+
* commits them, and writes the branch name back into the plan frontmatter.
|
|
200
|
+
*/
|
|
201
|
+
export function createCommit(client) {
|
|
202
|
+
return tool({
|
|
203
|
+
description: "Create a git branch from a saved plan, commit .cortex/ artifacts to it, and " +
|
|
204
|
+
"write the branch name into the plan frontmatter. Keeps main clean.",
|
|
205
|
+
args: {
|
|
206
|
+
planFilename: tool.schema
|
|
207
|
+
.string()
|
|
208
|
+
.describe("Plan filename from .cortex/plans/ (e.g., '2026-02-26-feature-auth.md')"),
|
|
209
|
+
branchType: tool.schema
|
|
210
|
+
.string()
|
|
211
|
+
.optional()
|
|
212
|
+
.describe("Override branch prefix (default: derived from plan type)"),
|
|
213
|
+
branchName: tool.schema
|
|
214
|
+
.string()
|
|
215
|
+
.optional()
|
|
216
|
+
.describe("Override branch slug (default: derived from plan title)"),
|
|
217
|
+
},
|
|
218
|
+
async execute(args, context) {
|
|
219
|
+
const { planFilename, branchType, branchName: nameOverride } = args;
|
|
220
|
+
const cwd = context.worktree;
|
|
221
|
+
// ── 1. Validate: git repo ─────────────────────────────────
|
|
222
|
+
try {
|
|
223
|
+
await git(cwd, "rev-parse", "--git-dir");
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return "✗ Error: Not in a git repository. Initialize git first.";
|
|
227
|
+
}
|
|
228
|
+
// ── 2. Read and parse the plan ────────────────────────────
|
|
229
|
+
const plansPath = path.join(cwd, CORTEX_DIR, PLANS_DIR);
|
|
230
|
+
const filepath = path.resolve(plansPath, planFilename);
|
|
231
|
+
const resolvedPlansDir = path.resolve(plansPath);
|
|
232
|
+
// Prevent path traversal (../ or absolute paths)
|
|
233
|
+
if (!filepath.startsWith(resolvedPlansDir + path.sep)) {
|
|
234
|
+
return `✗ Invalid plan filename: path traversal not allowed`;
|
|
235
|
+
}
|
|
236
|
+
if (!fs.existsSync(filepath)) {
|
|
237
|
+
return `✗ Plan not found: ${planFilename}
|
|
238
|
+
|
|
239
|
+
Use plan_list to see available plans.`;
|
|
240
|
+
}
|
|
241
|
+
let planContent = fs.readFileSync(filepath, "utf-8");
|
|
242
|
+
const fm = parseFrontmatter(planContent);
|
|
243
|
+
if (!fm) {
|
|
244
|
+
return `✗ Plan has no frontmatter: ${planFilename}
|
|
245
|
+
|
|
246
|
+
Expected YAML frontmatter with title and type fields.`;
|
|
247
|
+
}
|
|
248
|
+
const planTitle = fm.title || "untitled";
|
|
249
|
+
const planType = fm.type || "feature";
|
|
250
|
+
// ── 3. Determine branch name ──────────────────────────────
|
|
251
|
+
const VALID_PREFIXES = Object.values(TYPE_TO_PREFIX);
|
|
252
|
+
const rawPrefix = branchType || TYPE_TO_PREFIX[planType] || "feature";
|
|
253
|
+
const prefix = VALID_PREFIXES.includes(rawPrefix) ? rawPrefix : "feature";
|
|
254
|
+
// Always sanitize name through slugify (even overrides)
|
|
255
|
+
const slug = slugify(nameOverride || planTitle);
|
|
256
|
+
const fullBranchName = `${prefix}/${slug}`;
|
|
257
|
+
// ── 4. Check current branch ───────────────────────────────
|
|
258
|
+
let currentBranch = "";
|
|
259
|
+
try {
|
|
260
|
+
const { stdout } = await git(cwd, "branch", "--show-current");
|
|
261
|
+
currentBranch = stdout.trim();
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
currentBranch = "";
|
|
265
|
+
}
|
|
266
|
+
const isOnProtected = PROTECTED_BRANCHES.includes(currentBranch);
|
|
267
|
+
let branchCreated = false;
|
|
268
|
+
// ── 5. Create or switch to branch ─────────────────────────
|
|
269
|
+
if (isOnProtected || !currentBranch) {
|
|
270
|
+
// Try to create the branch
|
|
271
|
+
try {
|
|
272
|
+
await git(cwd, "checkout", "-b", fullBranchName);
|
|
273
|
+
branchCreated = true;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Branch may already exist — try switching to it
|
|
277
|
+
try {
|
|
278
|
+
await git(cwd, "checkout", fullBranchName);
|
|
279
|
+
}
|
|
280
|
+
catch (switchErr) {
|
|
281
|
+
try {
|
|
282
|
+
await client.tui.showToast({
|
|
283
|
+
body: {
|
|
284
|
+
title: `Plan Commit: ${planFilename}`,
|
|
285
|
+
message: `Failed to create/switch branch: ${switchErr.message || switchErr}`,
|
|
286
|
+
variant: "error",
|
|
287
|
+
duration: 8000,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// Toast failure is non-fatal
|
|
293
|
+
}
|
|
294
|
+
return `✗ Error creating branch '${fullBranchName}': ${switchErr.message || switchErr}
|
|
295
|
+
|
|
296
|
+
You may have uncommitted changes. Commit or stash them first.`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// If already on a non-protected branch, skip branch creation
|
|
301
|
+
// ── 6. Update plan frontmatter with branch ────────────────
|
|
302
|
+
// If we created/switched to a new branch → use that name
|
|
303
|
+
// If already on a non-protected branch → use whatever we're on
|
|
304
|
+
const targetBranch = (isOnProtected || branchCreated) ? fullBranchName : currentBranch;
|
|
305
|
+
planContent = upsertFrontmatterField(planContent, "branch", targetBranch);
|
|
306
|
+
fs.writeFileSync(filepath, planContent);
|
|
307
|
+
// ── 7. Stage .cortex/ directory ───────────────────────────
|
|
308
|
+
try {
|
|
309
|
+
await git(cwd, "add", path.join(cwd, CORTEX_DIR));
|
|
310
|
+
}
|
|
311
|
+
catch (stageErr) {
|
|
312
|
+
return `✗ Error staging .cortex/ directory: ${stageErr.message || stageErr}`;
|
|
313
|
+
}
|
|
314
|
+
// ── 8. Commit ─────────────────────────────────────────────
|
|
315
|
+
const commitMsg = `chore(plan): ${planTitle}`;
|
|
316
|
+
let commitHash = "";
|
|
317
|
+
try {
|
|
318
|
+
// Check if there's anything staged
|
|
319
|
+
const { stdout: statusOut } = await git(cwd, "status", "--porcelain");
|
|
320
|
+
const stagedLines = statusOut
|
|
321
|
+
.trim()
|
|
322
|
+
.split("\n")
|
|
323
|
+
.filter((l) => l && l[0] !== " " && l[0] !== "?");
|
|
324
|
+
if (stagedLines.length === 0) {
|
|
325
|
+
// Nothing to commit — plan already committed
|
|
326
|
+
try {
|
|
327
|
+
const { stdout: hashOut } = await git(cwd, "rev-parse", "--short", "HEAD");
|
|
328
|
+
commitHash = hashOut.trim();
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
commitHash = "(unknown)";
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
await client.tui.showToast({
|
|
335
|
+
body: {
|
|
336
|
+
title: `Plan: ${targetBranch}`,
|
|
337
|
+
message: "Already committed — no new changes",
|
|
338
|
+
variant: "info",
|
|
339
|
+
duration: 4000,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// Toast failure is non-fatal
|
|
345
|
+
}
|
|
346
|
+
return `✓ Plan already committed on branch: ${targetBranch}
|
|
347
|
+
|
|
348
|
+
Branch: ${targetBranch}
|
|
349
|
+
Commit: ${commitHash} (no new changes)
|
|
350
|
+
Plan: ${planFilename}
|
|
351
|
+
|
|
352
|
+
The plan branch is ready for implementation.
|
|
353
|
+
Use worktree_create or switch to the Implement agent.`;
|
|
354
|
+
}
|
|
355
|
+
await git(cwd, "commit", "-m", commitMsg);
|
|
356
|
+
const { stdout: hashOut } = await git(cwd, "rev-parse", "--short", "HEAD");
|
|
357
|
+
commitHash = hashOut.trim();
|
|
358
|
+
}
|
|
359
|
+
catch (commitErr) {
|
|
360
|
+
try {
|
|
361
|
+
await client.tui.showToast({
|
|
362
|
+
body: {
|
|
363
|
+
title: `Plan Commit`,
|
|
364
|
+
message: `Commit failed: ${commitErr.message || commitErr}`,
|
|
365
|
+
variant: "error",
|
|
366
|
+
duration: 8000,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// Toast failure is non-fatal
|
|
372
|
+
}
|
|
373
|
+
return `✗ Error committing: ${commitErr.message || commitErr}`;
|
|
374
|
+
}
|
|
375
|
+
// ── 9. Success notification ───────────────────────────────
|
|
376
|
+
try {
|
|
377
|
+
await client.tui.showToast({
|
|
378
|
+
body: {
|
|
379
|
+
title: `Plan Committed`,
|
|
380
|
+
message: `${targetBranch} — ${commitHash}`,
|
|
381
|
+
variant: "success",
|
|
382
|
+
duration: 5000,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// Toast failure is non-fatal
|
|
388
|
+
}
|
|
389
|
+
return `✓ Plan committed to branch
|
|
390
|
+
|
|
391
|
+
Branch: ${targetBranch}${branchCreated ? " (created)" : ""}
|
|
392
|
+
Commit: ${commitHash} — ${commitMsg}
|
|
393
|
+
Plan: ${planFilename}
|
|
394
|
+
|
|
395
|
+
The plan and .cortex/ artifacts are committed on '${targetBranch}'.
|
|
396
|
+
Main branch is clean.
|
|
397
|
+
|
|
398
|
+
Next steps:
|
|
399
|
+
• Switch to Implement agent to begin coding
|
|
400
|
+
• Or use worktree_create to work in an isolated copy`;
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
}
|
|
176
404
|
// Export with underscore suffix to avoid reserved word
|
|
177
405
|
export { delete_ as delete };
|