cortex-agents 3.4.0 → 4.0.1

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 (49) hide show
  1. package/.opencode/agents/architect.md +82 -89
  2. package/.opencode/agents/audit.md +57 -188
  3. package/.opencode/agents/{crosslayer.md → coder.md} +9 -52
  4. package/.opencode/agents/debug.md +151 -0
  5. package/.opencode/agents/devops.md +142 -0
  6. package/.opencode/agents/docs-writer.md +195 -0
  7. package/.opencode/agents/fix.md +119 -189
  8. package/.opencode/agents/implement.md +115 -74
  9. package/.opencode/agents/perf.md +151 -0
  10. package/.opencode/agents/refactor.md +163 -0
  11. package/.opencode/agents/{guard.md → security.md} +20 -85
  12. package/.opencode/agents/testing.md +115 -0
  13. package/.opencode/skills/data-engineering/SKILL.md +221 -0
  14. package/.opencode/skills/monitoring-observability/SKILL.md +251 -0
  15. package/.opencode/skills/ui-design/SKILL.md +402 -0
  16. package/README.md +303 -287
  17. package/dist/cli.js +6 -9
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +26 -28
  20. package/dist/registry.d.ts +4 -4
  21. package/dist/registry.d.ts.map +1 -1
  22. package/dist/registry.js +6 -6
  23. package/dist/tools/branch.d.ts +2 -2
  24. package/dist/tools/docs.d.ts +2 -2
  25. package/dist/tools/github.d.ts +3 -3
  26. package/dist/tools/plan.d.ts +28 -4
  27. package/dist/tools/plan.d.ts.map +1 -1
  28. package/dist/tools/plan.js +232 -4
  29. package/dist/tools/quality-gate.d.ts +28 -0
  30. package/dist/tools/quality-gate.d.ts.map +1 -0
  31. package/dist/tools/quality-gate.js +233 -0
  32. package/dist/tools/repl.d.ts +5 -0
  33. package/dist/tools/repl.d.ts.map +1 -1
  34. package/dist/tools/repl.js +58 -7
  35. package/dist/tools/worktree.d.ts +5 -32
  36. package/dist/tools/worktree.d.ts.map +1 -1
  37. package/dist/tools/worktree.js +75 -458
  38. package/dist/utils/change-scope.d.ts +33 -0
  39. package/dist/utils/change-scope.d.ts.map +1 -0
  40. package/dist/utils/change-scope.js +198 -0
  41. package/dist/utils/plan-extract.d.ts +21 -0
  42. package/dist/utils/plan-extract.d.ts.map +1 -1
  43. package/dist/utils/plan-extract.js +65 -0
  44. package/dist/utils/repl.d.ts +31 -0
  45. package/dist/utils/repl.d.ts.map +1 -1
  46. package/dist/utils/repl.js +126 -13
  47. package/package.json +1 -1
  48. package/.opencode/agents/qa.md +0 -265
  49. 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 = "3.4.0";
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 (implement, architect, fix, audit) handle complex tasks.\nUse your best available model.\n");
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 (crosslayer, qa, guard, ship) handle focused tasks.\nA faster/cheaper model works great here.\n");
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 (implement, architect, fix, audit):
634
+ Primary (architect, implement, fix):
635
635
  Handle complex tasks — select your best model.
636
636
 
637
- Subagents (crosslayer, qa, guard, ship):
637
+ Subagents (debug, coder, testing, security, devops, audit):
638
638
  Handle focused tasks — a fast/cheap model works great.
639
639
 
640
- TOOLS (32):
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
@@ -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;AAiLlD,eAAO,MAAM,YAAY,EAAE,MAsK1B,CAAC;AAGF,eAAe,YAAY,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: "Debug mode — troubleshooting and fixes",
17
- audit: "Review modecode quality assessment",
18
- crosslayer: "Crosslayer subagent — end-to-end implementation",
19
- qa: "QA subagent — writing tests",
20
- guard: "Security subagent — vulnerability audit",
21
- ship: "DevOps subagent — CI/CD and deployment",
16
+ fix: "Quick fix mode — fast turnaround",
17
+ debug: "Debug subagentroot 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
@@ -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 ["implement", "architect", "fix", "audit"];
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 ["crosslayer", "qa", "guard", "ship"];
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 ["implement", "architect", "fix", "audit", "crosslayer", "qa", "guard", "ship"];
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", "debug.md", "review.md", "fullstack.md", "testing.md", "security.md", "devops.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).
@@ -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,qDAAsD,CAAC;AAElF,oDAAoD;AACpD,eAAO,MAAM,SAAS,gDAAiD,CAAC;AAExE,+BAA+B;AAC/B,eAAO,MAAM,UAAU,0FAA6C,CAAC;AAErE;0EAC0E;AAC1E,eAAO,MAAM,uBAAuB,4BAA6B,CAAC;AAElE,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,qHASpB,CAAC;AAEX;;;GAGG;AACH,wBAAgB,iBAAiB;;;;IAMhC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM;;;;IAcxD"}
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 = ["implement", "architect", "fix", "audit"];
108
+ export const PRIMARY_AGENTS = ["architect", "implement", "fix"];
109
109
  /** Subagents receive a fast/cost-effective model */
110
- export const SUBAGENTS = ["crosslayer", "qa", "guard", "ship"];
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
- "testing.md",
124
- "security.md",
125
- "devops.md",
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.
@@ -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: "feature" | "bugfix" | "hotfix" | "refactor" | "docs" | "test" | "chore";
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: {
@@ -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" | "all" | "decision" | "flow" | undefined;
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: {
@@ -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?: "closed" | "open" | "all" | undefined;
21
+ state?: "all" | "open" | "closed" | undefined;
22
22
  labels?: string | undefined;
23
23
  milestone?: string | undefined;
24
24
  assignee?: string | undefined;
@@ -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: "feature" | "bugfix" | "refactor" | "spike" | "docs" | "architecture";
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?: "feature" | "bugfix" | "refactor" | "spike" | "docs" | "architecture" | "all" | undefined;
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
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/tools/plan.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;CA2Df,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;CAkEf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CAuBf,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;CAkBlB,CAAC;AAGH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,CAAC"}
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"}
@@ -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.join(plansPath, filename);
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.join(plansPath, filename);
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 };