cortex-agents 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.opencode/agents/build.md +160 -0
  2. package/.opencode/agents/debug.md +141 -0
  3. package/.opencode/agents/devops.md +109 -0
  4. package/.opencode/agents/fullstack.md +84 -0
  5. package/.opencode/agents/plan.md +188 -0
  6. package/.opencode/agents/security.md +90 -0
  7. package/.opencode/agents/testing.md +89 -0
  8. package/.opencode/skills/code-quality/SKILL.md +251 -0
  9. package/.opencode/skills/deployment-automation/SKILL.md +258 -0
  10. package/.opencode/skills/git-workflow/SKILL.md +281 -0
  11. package/.opencode/skills/security-hardening/SKILL.md +209 -0
  12. package/.opencode/skills/testing-strategies/SKILL.md +159 -0
  13. package/.opencode/skills/web-development/SKILL.md +122 -0
  14. package/LICENSE +17 -0
  15. package/README.md +172 -0
  16. package/dist/cli.d.ts +3 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +174 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +42 -0
  22. package/dist/plugin.d.ts +1 -0
  23. package/dist/plugin.d.ts.map +1 -0
  24. package/dist/plugin.js +3 -0
  25. package/dist/tools/branch.d.ts +35 -0
  26. package/dist/tools/branch.d.ts.map +1 -0
  27. package/dist/tools/branch.js +176 -0
  28. package/dist/tools/cortex.d.ts +11 -0
  29. package/dist/tools/cortex.d.ts.map +1 -0
  30. package/dist/tools/cortex.js +149 -0
  31. package/dist/tools/plan.d.ts +59 -0
  32. package/dist/tools/plan.d.ts.map +1 -0
  33. package/dist/tools/plan.js +177 -0
  34. package/dist/tools/session.d.ts +36 -0
  35. package/dist/tools/session.d.ts.map +1 -0
  36. package/dist/tools/session.js +175 -0
  37. package/dist/tools/worktree.d.ts +45 -0
  38. package/dist/tools/worktree.d.ts.map +1 -0
  39. package/dist/tools/worktree.js +198 -0
  40. package/package.json +55 -0
@@ -0,0 +1,175 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ const CORTEX_DIR = ".cortex";
5
+ const SESSIONS_DIR = "sessions";
6
+ function getDatePrefix() {
7
+ const now = new Date();
8
+ return now.toISOString().split("T")[0]; // YYYY-MM-DD
9
+ }
10
+ function ensureSessionsDir(worktree) {
11
+ const cortexPath = path.join(worktree, CORTEX_DIR);
12
+ const sessionsPath = path.join(cortexPath, SESSIONS_DIR);
13
+ if (!fs.existsSync(sessionsPath)) {
14
+ fs.mkdirSync(sessionsPath, { recursive: true });
15
+ }
16
+ return sessionsPath;
17
+ }
18
+ function generateSessionId() {
19
+ return Math.random().toString(36).substring(2, 10);
20
+ }
21
+ export const save = tool({
22
+ description: "Save a session summary with key decisions to .cortex/sessions/",
23
+ args: {
24
+ summary: tool.schema
25
+ .string()
26
+ .describe("Brief summary of what was accomplished in this session"),
27
+ decisions: tool.schema
28
+ .array(tool.schema.string())
29
+ .describe("List of key decisions made during the session"),
30
+ filesChanged: tool.schema
31
+ .array(tool.schema.string())
32
+ .optional()
33
+ .describe("Optional list of files that were modified"),
34
+ relatedPlan: tool.schema
35
+ .string()
36
+ .optional()
37
+ .describe("Optional filename of related plan"),
38
+ branch: tool.schema
39
+ .string()
40
+ .optional()
41
+ .describe("Git branch this work was done on"),
42
+ },
43
+ async execute(args, context) {
44
+ const { summary, decisions, filesChanged, relatedPlan, branch } = args;
45
+ const sessionsPath = ensureSessionsDir(context.worktree);
46
+ const datePrefix = getDatePrefix();
47
+ const sessionId = generateSessionId();
48
+ const filename = `${datePrefix}-${sessionId}.md`;
49
+ const filepath = path.join(sessionsPath, filename);
50
+ // Get current branch if not provided
51
+ let currentBranch = branch;
52
+ if (!currentBranch) {
53
+ try {
54
+ currentBranch = (await Bun.$ `git branch --show-current`.cwd(context.worktree).text()).trim();
55
+ }
56
+ catch {
57
+ currentBranch = "unknown";
58
+ }
59
+ }
60
+ // Build content
61
+ let content = `---
62
+ date: ${new Date().toISOString()}
63
+ branch: ${currentBranch}
64
+ ${relatedPlan ? `relatedPlan: ${relatedPlan}` : ""}
65
+ ---
66
+
67
+ # Session Summary
68
+
69
+ ${summary}
70
+
71
+ ## Key Decisions
72
+
73
+ ${decisions.map((d) => `- ${d}`).join("\n")}
74
+ `;
75
+ if (filesChanged && filesChanged.length > 0) {
76
+ content += `
77
+ ## Files Changed
78
+
79
+ ${filesChanged.map((f) => `- \`${f}\``).join("\n")}
80
+ `;
81
+ }
82
+ if (relatedPlan) {
83
+ content += `
84
+ ## Related Plan
85
+
86
+ See: \`.cortex/plans/${relatedPlan}\`
87
+ `;
88
+ }
89
+ // Write file
90
+ fs.writeFileSync(filepath, content);
91
+ return `✓ Session summary saved
92
+
93
+ File: ${filename}
94
+ Branch: ${currentBranch}
95
+ Decisions recorded: ${decisions.length}
96
+ ${filesChanged ? `Files tracked: ${filesChanged.length}` : ""}
97
+
98
+ Session summaries are stored in .cortex/sessions/ and gitignored by default.`;
99
+ },
100
+ });
101
+ export const list = tool({
102
+ description: "List recent session summaries from .cortex/sessions/",
103
+ args: {
104
+ limit: tool.schema
105
+ .number()
106
+ .optional()
107
+ .describe("Maximum number of sessions to return (default: 10)"),
108
+ },
109
+ async execute(args, context) {
110
+ const { limit = 10 } = args;
111
+ const sessionsPath = path.join(context.worktree, CORTEX_DIR, SESSIONS_DIR);
112
+ if (!fs.existsSync(sessionsPath)) {
113
+ return `No sessions found. The .cortex/sessions/ directory doesn't exist.
114
+
115
+ Sessions are created when you use session_save after completing work.`;
116
+ }
117
+ const files = fs
118
+ .readdirSync(sessionsPath)
119
+ .filter((f) => f.endsWith(".md"))
120
+ .sort()
121
+ .reverse()
122
+ .slice(0, limit);
123
+ if (files.length === 0) {
124
+ return "No session summaries found in .cortex/sessions/";
125
+ }
126
+ let output = `📝 Recent Sessions (showing ${files.length}):\n\n`;
127
+ for (const file of files) {
128
+ const filepath = path.join(sessionsPath, file);
129
+ const content = fs.readFileSync(filepath, "utf-8");
130
+ // Parse frontmatter
131
+ let date = file.substring(0, 10);
132
+ let branch = "unknown";
133
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
134
+ if (frontmatterMatch) {
135
+ const fm = frontmatterMatch[1];
136
+ const dateMatch = fm.match(/date:\s*(\S+)/);
137
+ const branchMatch = fm.match(/branch:\s*(\S+)/);
138
+ if (dateMatch)
139
+ date = dateMatch[1].split("T")[0];
140
+ if (branchMatch)
141
+ branch = branchMatch[1];
142
+ }
143
+ // Get first line of summary (after frontmatter and heading)
144
+ const summaryMatch = content.match(/# Session Summary\n\n([^\n]+)/);
145
+ const summaryPreview = summaryMatch
146
+ ? summaryMatch[1].substring(0, 80) + (summaryMatch[1].length > 80 ? "..." : "")
147
+ : "(no summary)";
148
+ output += `• ${date} [${branch}]\n`;
149
+ output += ` ${summaryPreview}\n`;
150
+ output += ` File: ${file}\n\n`;
151
+ }
152
+ return output.trim();
153
+ },
154
+ });
155
+ export const load = tool({
156
+ description: "Load a session summary by filename",
157
+ args: {
158
+ filename: tool.schema.string().describe("Session filename"),
159
+ },
160
+ async execute(args, context) {
161
+ const { filename } = args;
162
+ const sessionsPath = path.join(context.worktree, CORTEX_DIR, SESSIONS_DIR);
163
+ const filepath = path.join(sessionsPath, filename);
164
+ if (!fs.existsSync(filepath)) {
165
+ return `✗ Session not found: ${filename}
166
+
167
+ Use session_list to see available sessions.`;
168
+ }
169
+ const content = fs.readFileSync(filepath, "utf-8");
170
+ return `📝 Session: ${filename}
171
+ ${"=".repeat(50)}
172
+
173
+ ${content}`;
174
+ },
175
+ });
@@ -0,0 +1,45 @@
1
+ export declare const create: {
2
+ description: string;
3
+ args: {
4
+ name: import("zod").ZodString;
5
+ type: import("zod").ZodEnum<{
6
+ feature: "feature";
7
+ bugfix: "bugfix";
8
+ hotfix: "hotfix";
9
+ refactor: "refactor";
10
+ spike: "spike";
11
+ docs: "docs";
12
+ test: "test";
13
+ }>;
14
+ };
15
+ execute(args: {
16
+ name: string;
17
+ type: "feature" | "bugfix" | "hotfix" | "refactor" | "spike" | "docs" | "test";
18
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
19
+ };
20
+ export declare const list: {
21
+ description: string;
22
+ args: {};
23
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
24
+ };
25
+ export declare const remove: {
26
+ description: string;
27
+ args: {
28
+ name: import("zod").ZodString;
29
+ deleteBranch: import("zod").ZodOptional<import("zod").ZodBoolean>;
30
+ };
31
+ execute(args: {
32
+ name: string;
33
+ deleteBranch?: boolean | undefined;
34
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
35
+ };
36
+ export declare const open: {
37
+ description: string;
38
+ args: {
39
+ name: import("zod").ZodString;
40
+ };
41
+ execute(args: {
42
+ name: string;
43
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
44
+ };
45
+ //# sourceMappingURL=worktree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/tools/worktree.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;CA8DjB,CAAC;AAEH,eAAO,MAAM,IAAI;;;;CAiCf,CAAC;AAEH,eAAO,MAAM,MAAM;;;;;;;;;;CA4DjB,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CAgDf,CAAC"}
@@ -0,0 +1,198 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ const WORKTREE_ROOT = "../.worktrees";
5
+ export const create = tool({
6
+ description: "Create a new git worktree for isolated development. Worktrees are created in ../.worktrees/",
7
+ args: {
8
+ name: tool.schema
9
+ .string()
10
+ .describe("Worktree name (e.g., 'auth-feature', 'login-bugfix')"),
11
+ type: tool.schema
12
+ .enum(["feature", "bugfix", "hotfix", "refactor", "spike", "docs", "test"])
13
+ .describe("Type of work - determines branch prefix"),
14
+ },
15
+ async execute(args, context) {
16
+ const { name, type } = args;
17
+ const branchName = `${type}/${name}`;
18
+ const worktreePath = path.join(context.worktree, WORKTREE_ROOT, name);
19
+ const absoluteWorktreePath = path.resolve(worktreePath);
20
+ // Check if we're in a git repository
21
+ try {
22
+ const gitCheck = await Bun.$ `git rev-parse --git-dir`.cwd(context.worktree).text();
23
+ if (!gitCheck.trim()) {
24
+ return "✗ Error: Not in a git repository";
25
+ }
26
+ }
27
+ catch {
28
+ return "✗ Error: Not in a git repository. Initialize git first.";
29
+ }
30
+ // Check if worktree already exists
31
+ if (fs.existsSync(absoluteWorktreePath)) {
32
+ return `✗ Error: Worktree already exists at ${absoluteWorktreePath}
33
+
34
+ Use worktree_list to see existing worktrees.`;
35
+ }
36
+ // Create parent directory if needed
37
+ const worktreeParent = path.dirname(absoluteWorktreePath);
38
+ if (!fs.existsSync(worktreeParent)) {
39
+ fs.mkdirSync(worktreeParent, { recursive: true });
40
+ }
41
+ // Create the worktree with a new branch
42
+ try {
43
+ await Bun.$ `git worktree add -b ${branchName} ${absoluteWorktreePath}`.cwd(context.worktree);
44
+ }
45
+ catch (error) {
46
+ // Branch might already exist, try without -b
47
+ try {
48
+ await Bun.$ `git worktree add ${absoluteWorktreePath} ${branchName}`.cwd(context.worktree);
49
+ }
50
+ catch (error2) {
51
+ return `✗ Error creating worktree: ${error2.message || error2}`;
52
+ }
53
+ }
54
+ return `✓ Created worktree successfully
55
+
56
+ Branch: ${branchName}
57
+ Path: ${absoluteWorktreePath}
58
+
59
+ To work in this worktree:
60
+ cd ${absoluteWorktreePath}
61
+
62
+ Or use worktree_open to get a command to open a new terminal there.`;
63
+ },
64
+ });
65
+ export const list = tool({
66
+ description: "List all git worktrees for this project",
67
+ args: {},
68
+ async execute(args, context) {
69
+ try {
70
+ const result = await Bun.$ `git worktree list`.cwd(context.worktree).text();
71
+ if (!result.trim()) {
72
+ return "No worktrees found.";
73
+ }
74
+ const lines = result.trim().split("\n");
75
+ let output = "Git Worktrees:\n\n";
76
+ for (const line of lines) {
77
+ const parts = line.split(/\s+/);
78
+ const worktreePath = parts[0];
79
+ const commit = parts[1];
80
+ const branch = parts[2]?.replace(/[\[\]]/g, "") || "detached";
81
+ const isMain = worktreePath === context.worktree;
82
+ const marker = isMain ? " (main)" : "";
83
+ output += `• ${branch}${marker}\n`;
84
+ output += ` Path: ${worktreePath}\n`;
85
+ output += ` Commit: ${commit}\n\n`;
86
+ }
87
+ return output.trim();
88
+ }
89
+ catch (error) {
90
+ return `✗ Error listing worktrees: ${error.message || error}`;
91
+ }
92
+ },
93
+ });
94
+ export const remove = tool({
95
+ description: "Remove a git worktree (after merging). Optionally deletes the branch.",
96
+ args: {
97
+ name: tool.schema.string().describe("Worktree name to remove"),
98
+ deleteBranch: tool.schema
99
+ .boolean()
100
+ .optional()
101
+ .describe("Also delete the associated branch (default: false)"),
102
+ },
103
+ async execute(args, context) {
104
+ const { name, deleteBranch = false } = args;
105
+ const worktreePath = path.join(context.worktree, WORKTREE_ROOT, name);
106
+ const absoluteWorktreePath = path.resolve(worktreePath);
107
+ // Check if worktree exists
108
+ if (!fs.existsSync(absoluteWorktreePath)) {
109
+ return `✗ Error: Worktree not found at ${absoluteWorktreePath}
110
+
111
+ Use worktree_list to see existing worktrees.`;
112
+ }
113
+ // Get branch name before removing
114
+ let branchName = "";
115
+ try {
116
+ branchName = await Bun.$ `git -C ${absoluteWorktreePath} branch --show-current`.text();
117
+ branchName = branchName.trim();
118
+ }
119
+ catch {
120
+ // Ignore error, branch detection is optional
121
+ }
122
+ // Remove the worktree
123
+ try {
124
+ await Bun.$ `git worktree remove ${absoluteWorktreePath}`.cwd(context.worktree);
125
+ }
126
+ catch (error) {
127
+ // Try force remove if there are changes
128
+ try {
129
+ await Bun.$ `git worktree remove --force ${absoluteWorktreePath}`.cwd(context.worktree);
130
+ }
131
+ catch (error2) {
132
+ return `✗ Error removing worktree: ${error2.message || error2}
133
+
134
+ The worktree may have uncommitted changes. Commit or stash them first.`;
135
+ }
136
+ }
137
+ let output = `✓ Removed worktree at ${absoluteWorktreePath}`;
138
+ // Delete branch if requested
139
+ if (deleteBranch && branchName) {
140
+ try {
141
+ await Bun.$ `git branch -d ${branchName}`.cwd(context.worktree);
142
+ output += `\n✓ Deleted branch ${branchName}`;
143
+ }
144
+ catch (error) {
145
+ output += `\n⚠ Could not delete branch ${branchName}: ${error.message || error}`;
146
+ output += "\n (Branch may not be fully merged. Use git branch -D to force delete.)";
147
+ }
148
+ }
149
+ return output;
150
+ },
151
+ });
152
+ export const open = tool({
153
+ description: "Get the command to open a new terminal window in a worktree directory",
154
+ args: {
155
+ name: tool.schema.string().describe("Worktree name"),
156
+ },
157
+ async execute(args, context) {
158
+ const { name } = args;
159
+ const worktreePath = path.join(context.worktree, WORKTREE_ROOT, name);
160
+ const absoluteWorktreePath = path.resolve(worktreePath);
161
+ // Check if worktree exists
162
+ if (!fs.existsSync(absoluteWorktreePath)) {
163
+ return `✗ Error: Worktree not found at ${absoluteWorktreePath}
164
+
165
+ Use worktree_list to see existing worktrees.`;
166
+ }
167
+ // Detect OS and provide appropriate command
168
+ const platform = process.platform;
169
+ let command = "";
170
+ let instructions = "";
171
+ if (platform === "darwin") {
172
+ // macOS
173
+ command = `open -a Terminal "${absoluteWorktreePath}"`;
174
+ instructions = `Or with iTerm2: open -a iTerm "${absoluteWorktreePath}"`;
175
+ }
176
+ else if (platform === "linux") {
177
+ // Linux - try common terminals
178
+ command = `gnome-terminal --working-directory="${absoluteWorktreePath}" || xterm -e "cd '${absoluteWorktreePath}' && $SHELL" || konsole --workdir "${absoluteWorktreePath}"`;
179
+ instructions = "Command tries gnome-terminal, xterm, then konsole.";
180
+ }
181
+ else if (platform === "win32") {
182
+ // Windows
183
+ command = `start cmd /k "cd /d ${absoluteWorktreePath}"`;
184
+ instructions = `Or with PowerShell: Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '${absoluteWorktreePath}'"`;
185
+ }
186
+ else {
187
+ command = `cd "${absoluteWorktreePath}"`;
188
+ instructions = "Unknown platform. Use the cd command above.";
189
+ }
190
+ return `To open a new terminal in the worktree:
191
+
192
+ ${command}
193
+
194
+ ${instructions}
195
+
196
+ Worktree path: ${absoluteWorktreePath}`;
197
+ },
198
+ });
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "cortex-agents",
3
+ "version": "1.0.0",
4
+ "description": "Cortex agents for OpenCode - worktree workflow, plan persistence, and session management",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "cortex-agents": "dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ ".opencode"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepare": "npm run build",
18
+ "postinstall": "echo 'Run: npx cortex-agents install'"
19
+ },
20
+ "keywords": [
21
+ "opencode",
22
+ "plugin",
23
+ "cortex",
24
+ "agents",
25
+ "skills",
26
+ "ai",
27
+ "coding",
28
+ "worktree",
29
+ "git",
30
+ "planning"
31
+ ],
32
+ "author": "",
33
+ "license": "Apache-2.0",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/your-org/cortex-agents"
37
+ },
38
+ "homepage": "https://github.com/your-org/cortex-agents#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/your-org/cortex-agents/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ },
45
+ "peerDependencies": {
46
+ "@opencode-ai/plugin": "^1.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@opencode-ai/plugin": "^1.0.0",
50
+ "@types/bun": "^1.3.9",
51
+ "@types/node": "^20.0.0",
52
+ "bun-types": "^1.0.0",
53
+ "typescript": "^5.0.0"
54
+ }
55
+ }