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.
- package/.opencode/agents/build.md +160 -0
- package/.opencode/agents/debug.md +141 -0
- package/.opencode/agents/devops.md +109 -0
- package/.opencode/agents/fullstack.md +84 -0
- package/.opencode/agents/plan.md +188 -0
- package/.opencode/agents/security.md +90 -0
- package/.opencode/agents/testing.md +89 -0
- package/.opencode/skills/code-quality/SKILL.md +251 -0
- package/.opencode/skills/deployment-automation/SKILL.md +258 -0
- package/.opencode/skills/git-workflow/SKILL.md +281 -0
- package/.opencode/skills/security-hardening/SKILL.md +209 -0
- package/.opencode/skills/testing-strategies/SKILL.md +159 -0
- package/.opencode/skills/web-development/SKILL.md +122 -0
- package/LICENSE +17 -0
- package/README.md +172 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +174 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +3 -0
- package/dist/tools/branch.d.ts +35 -0
- package/dist/tools/branch.d.ts.map +1 -0
- package/dist/tools/branch.js +176 -0
- package/dist/tools/cortex.d.ts +11 -0
- package/dist/tools/cortex.d.ts.map +1 -0
- package/dist/tools/cortex.js +149 -0
- package/dist/tools/plan.d.ts +59 -0
- package/dist/tools/plan.d.ts.map +1 -0
- package/dist/tools/plan.js +177 -0
- package/dist/tools/session.d.ts +36 -0
- package/dist/tools/session.d.ts.map +1 -0
- package/dist/tools/session.js +175 -0
- package/dist/tools/worktree.d.ts +45 -0
- package/dist/tools/worktree.d.ts.map +1 -0
- package/dist/tools/worktree.js +198 -0
- package/package.json +55 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
const PROTECTED_BRANCHES = ["main", "master", "develop", "production", "staging"];
|
|
3
|
+
export const create = tool({
|
|
4
|
+
description: "Create and checkout a new git branch with proper naming convention",
|
|
5
|
+
args: {
|
|
6
|
+
name: tool.schema
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Branch name slug (e.g., 'user-authentication', 'fix-login')"),
|
|
9
|
+
type: tool.schema
|
|
10
|
+
.enum(["feature", "bugfix", "hotfix", "refactor", "docs", "test", "chore"])
|
|
11
|
+
.describe("Branch type - determines prefix"),
|
|
12
|
+
},
|
|
13
|
+
async execute(args, context) {
|
|
14
|
+
const { name, type } = args;
|
|
15
|
+
const branchName = `${type}/${name}`;
|
|
16
|
+
// Check if we're in a git repository
|
|
17
|
+
try {
|
|
18
|
+
await Bun.$ `git rev-parse --git-dir`.cwd(context.worktree).text();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return "✗ Error: Not in a git repository";
|
|
22
|
+
}
|
|
23
|
+
// Check if branch already exists
|
|
24
|
+
try {
|
|
25
|
+
const branches = await Bun.$ `git branch --list ${branchName}`.cwd(context.worktree).text();
|
|
26
|
+
if (branches.trim()) {
|
|
27
|
+
return `✗ Error: Branch '${branchName}' already exists.
|
|
28
|
+
|
|
29
|
+
Use branch_switch to switch to it, or choose a different name.`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore error, branch check is optional
|
|
34
|
+
}
|
|
35
|
+
// Create and checkout the branch
|
|
36
|
+
try {
|
|
37
|
+
await Bun.$ `git checkout -b ${branchName}`.cwd(context.worktree);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return `✗ Error creating branch: ${error.message || error}`;
|
|
41
|
+
}
|
|
42
|
+
return `✓ Created and switched to branch: ${branchName}
|
|
43
|
+
|
|
44
|
+
You are now on branch '${branchName}'.
|
|
45
|
+
Make your changes and commit when ready.`;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
export const status = tool({
|
|
49
|
+
description: "Get current git branch status - branch name, uncommitted changes, and whether on protected branch",
|
|
50
|
+
args: {},
|
|
51
|
+
async execute(args, context) {
|
|
52
|
+
// Check if we're in a git repository
|
|
53
|
+
try {
|
|
54
|
+
await Bun.$ `git rev-parse --git-dir`.cwd(context.worktree).text();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return "✗ Not in a git repository";
|
|
58
|
+
}
|
|
59
|
+
let currentBranch = "";
|
|
60
|
+
let hasChanges = false;
|
|
61
|
+
let stagedChanges = false;
|
|
62
|
+
let untrackedFiles = false;
|
|
63
|
+
let isProtected = false;
|
|
64
|
+
let aheadBehind = "";
|
|
65
|
+
// Get current branch
|
|
66
|
+
try {
|
|
67
|
+
currentBranch = (await Bun.$ `git branch --show-current`.cwd(context.worktree).text()).trim();
|
|
68
|
+
if (!currentBranch) {
|
|
69
|
+
currentBranch = "(detached HEAD)";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
currentBranch = "(unknown)";
|
|
74
|
+
}
|
|
75
|
+
// Check if protected
|
|
76
|
+
isProtected = PROTECTED_BRANCHES.includes(currentBranch);
|
|
77
|
+
// Check for changes
|
|
78
|
+
try {
|
|
79
|
+
const statusOutput = await Bun.$ `git status --porcelain`.cwd(context.worktree).text();
|
|
80
|
+
const lines = statusOutput.trim().split("\n").filter((l) => l);
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const status = line.substring(0, 2);
|
|
83
|
+
if (status[0] !== " " && status[0] !== "?") {
|
|
84
|
+
stagedChanges = true;
|
|
85
|
+
}
|
|
86
|
+
if (status[1] !== " " && status[1] !== "?") {
|
|
87
|
+
hasChanges = true;
|
|
88
|
+
}
|
|
89
|
+
if (status === "??") {
|
|
90
|
+
untrackedFiles = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Ignore error
|
|
96
|
+
}
|
|
97
|
+
// Check ahead/behind
|
|
98
|
+
try {
|
|
99
|
+
const result = await Bun.$ `git rev-list --left-right --count HEAD...@{upstream}`.cwd(context.worktree).text();
|
|
100
|
+
const [ahead, behind] = result.trim().split(/\s+/);
|
|
101
|
+
if (parseInt(ahead) > 0 || parseInt(behind) > 0) {
|
|
102
|
+
aheadBehind = `Ahead: ${ahead}, Behind: ${behind}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// No upstream or error
|
|
107
|
+
}
|
|
108
|
+
// Build output
|
|
109
|
+
let output = `Git Status:
|
|
110
|
+
|
|
111
|
+
Branch: ${currentBranch}`;
|
|
112
|
+
if (isProtected) {
|
|
113
|
+
output += ` ⚠️ PROTECTED`;
|
|
114
|
+
}
|
|
115
|
+
output += `\n`;
|
|
116
|
+
if (stagedChanges || hasChanges || untrackedFiles) {
|
|
117
|
+
output += `\nChanges:`;
|
|
118
|
+
if (stagedChanges)
|
|
119
|
+
output += `\n • Staged changes (ready to commit)`;
|
|
120
|
+
if (hasChanges)
|
|
121
|
+
output += `\n • Unstaged changes`;
|
|
122
|
+
if (untrackedFiles)
|
|
123
|
+
output += `\n • Untracked files`;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
output += `\nWorking tree clean.`;
|
|
127
|
+
}
|
|
128
|
+
if (aheadBehind) {
|
|
129
|
+
output += `\n\n${aheadBehind}`;
|
|
130
|
+
}
|
|
131
|
+
if (isProtected) {
|
|
132
|
+
output += `\n
|
|
133
|
+
⚠️ You are on a protected branch (${currentBranch}).
|
|
134
|
+
Consider creating a feature/bugfix branch before making changes.
|
|
135
|
+
Use branch_create or worktree_create.`;
|
|
136
|
+
}
|
|
137
|
+
return output;
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
export const switch_ = tool({
|
|
141
|
+
description: "Switch to an existing git branch",
|
|
142
|
+
args: {
|
|
143
|
+
branch: tool.schema.string().describe("Branch name to switch to"),
|
|
144
|
+
},
|
|
145
|
+
async execute(args, context) {
|
|
146
|
+
const { branch } = args;
|
|
147
|
+
// Check if branch exists
|
|
148
|
+
try {
|
|
149
|
+
const branches = await Bun.$ `git branch --list ${branch}`.cwd(context.worktree).text();
|
|
150
|
+
if (!branches.trim()) {
|
|
151
|
+
// Try remote branch
|
|
152
|
+
const remoteBranches = await Bun.$ `git branch -r --list origin/${branch}`.cwd(context.worktree).text();
|
|
153
|
+
if (!remoteBranches.trim()) {
|
|
154
|
+
return `✗ Error: Branch '${branch}' not found locally or on origin.
|
|
155
|
+
|
|
156
|
+
Use branch_create to create a new branch.`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Ignore error, try checkout anyway
|
|
162
|
+
}
|
|
163
|
+
// Switch to branch
|
|
164
|
+
try {
|
|
165
|
+
await Bun.$ `git checkout ${branch}`.cwd(context.worktree);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return `✗ Error switching branch: ${error.message || error}
|
|
169
|
+
|
|
170
|
+
You may have uncommitted changes. Commit or stash them first.`;
|
|
171
|
+
}
|
|
172
|
+
return `✓ Switched to branch: ${branch}`;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
// Export with underscore suffix to avoid reserved word
|
|
176
|
+
export { switch_ as switch };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const init: {
|
|
2
|
+
description: string;
|
|
3
|
+
args: {};
|
|
4
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
5
|
+
};
|
|
6
|
+
export declare const status: {
|
|
7
|
+
description: string;
|
|
8
|
+
args: {};
|
|
9
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=cortex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cortex.d.ts","sourceRoot":"","sources":["../../src/tools/cortex.ts"],"names":[],"mappings":"AAmEA,eAAO,MAAM,IAAI;;;;CAkDf,CAAC;AAEH,eAAO,MAAM,MAAM;;;;CAyDjB,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
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 DEFAULT_CONFIG = {
|
|
6
|
+
$schema: "https://k2p5.dev/cortex-config.json",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
worktree: {
|
|
9
|
+
root: "../.worktrees",
|
|
10
|
+
autoCleanup: false,
|
|
11
|
+
},
|
|
12
|
+
branches: {
|
|
13
|
+
protected: ["main", "master", "develop"],
|
|
14
|
+
defaultType: "feature",
|
|
15
|
+
},
|
|
16
|
+
plans: {
|
|
17
|
+
namingPattern: "{date}-{type}-{slug}",
|
|
18
|
+
includeMermaid: true,
|
|
19
|
+
},
|
|
20
|
+
sessions: {
|
|
21
|
+
retention: 30,
|
|
22
|
+
includeDecisions: true,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const GITIGNORE_CONTENT = `# Keep plans (they're valuable documentation)
|
|
26
|
+
!plans/
|
|
27
|
+
|
|
28
|
+
# Ignore session files (local context)
|
|
29
|
+
sessions/
|
|
30
|
+
|
|
31
|
+
# Ignore local config overrides
|
|
32
|
+
config.local.json
|
|
33
|
+
`;
|
|
34
|
+
const README_CONTENT = `# .cortex
|
|
35
|
+
|
|
36
|
+
This directory contains project context for the K2P5 development agents.
|
|
37
|
+
|
|
38
|
+
## Structure
|
|
39
|
+
|
|
40
|
+
- \`plans/\` - Saved implementation plans (version controlled)
|
|
41
|
+
- \`sessions/\` - Session summaries (gitignored)
|
|
42
|
+
- \`config.json\` - Project configuration
|
|
43
|
+
|
|
44
|
+
## Plans
|
|
45
|
+
|
|
46
|
+
Plans are saved by the Plan agent and can be loaded by Build/Debug agents.
|
|
47
|
+
They include:
|
|
48
|
+
- Architecture diagrams (mermaid)
|
|
49
|
+
- Task breakdowns
|
|
50
|
+
- Technical decisions
|
|
51
|
+
|
|
52
|
+
## Sessions
|
|
53
|
+
|
|
54
|
+
Session summaries capture key decisions made during development.
|
|
55
|
+
They are gitignored by default but can be kept if needed.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
The K2P5 agents will automatically use this directory for:
|
|
60
|
+
- Saving implementation plans before coding
|
|
61
|
+
- Recording session summaries with key decisions
|
|
62
|
+
- Managing worktree and branch workflows
|
|
63
|
+
`;
|
|
64
|
+
export const init = tool({
|
|
65
|
+
description: "Initialize .cortex directory in project root for plan storage, session history, and configuration",
|
|
66
|
+
args: {},
|
|
67
|
+
async execute(args, context) {
|
|
68
|
+
const cortexPath = path.join(context.worktree, CORTEX_DIR);
|
|
69
|
+
const plansPath = path.join(cortexPath, "plans");
|
|
70
|
+
const sessionsPath = path.join(cortexPath, "sessions");
|
|
71
|
+
// Check if already exists
|
|
72
|
+
if (fs.existsSync(cortexPath)) {
|
|
73
|
+
const hasConfig = fs.existsSync(path.join(cortexPath, "config.json"));
|
|
74
|
+
const hasPlans = fs.existsSync(plansPath);
|
|
75
|
+
const hasSessions = fs.existsSync(sessionsPath);
|
|
76
|
+
if (hasConfig && hasPlans && hasSessions) {
|
|
77
|
+
return `✓ .cortex directory already initialized at ${cortexPath}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Create directories
|
|
81
|
+
fs.mkdirSync(plansPath, { recursive: true });
|
|
82
|
+
fs.mkdirSync(sessionsPath, { recursive: true });
|
|
83
|
+
// Create config.json
|
|
84
|
+
fs.writeFileSync(path.join(cortexPath, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
85
|
+
// Create .gitignore
|
|
86
|
+
fs.writeFileSync(path.join(cortexPath, ".gitignore"), GITIGNORE_CONTENT);
|
|
87
|
+
// Create README.md
|
|
88
|
+
fs.writeFileSync(path.join(cortexPath, "README.md"), README_CONTENT);
|
|
89
|
+
// Create .gitkeep in plans to ensure it's tracked
|
|
90
|
+
fs.writeFileSync(path.join(plansPath, ".gitkeep"), "");
|
|
91
|
+
return `✓ Initialized .cortex directory at ${cortexPath}
|
|
92
|
+
|
|
93
|
+
Created:
|
|
94
|
+
- .cortex/config.json (configuration)
|
|
95
|
+
- .cortex/plans/ (implementation plans)
|
|
96
|
+
- .cortex/sessions/ (session summaries)
|
|
97
|
+
- .cortex/.gitignore (ignores sessions, keeps plans)
|
|
98
|
+
- .cortex/README.md (documentation)
|
|
99
|
+
|
|
100
|
+
Plans will be version controlled. Sessions are gitignored by default.`;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
export const status = tool({
|
|
104
|
+
description: "Check .cortex directory status - whether it exists, plan count, session count",
|
|
105
|
+
args: {},
|
|
106
|
+
async execute(args, context) {
|
|
107
|
+
const cortexPath = path.join(context.worktree, CORTEX_DIR);
|
|
108
|
+
if (!fs.existsSync(cortexPath)) {
|
|
109
|
+
return `✗ .cortex directory not found at ${cortexPath}
|
|
110
|
+
|
|
111
|
+
Run cortex_init to initialize.`;
|
|
112
|
+
}
|
|
113
|
+
const plansPath = path.join(cortexPath, "plans");
|
|
114
|
+
const sessionsPath = path.join(cortexPath, "sessions");
|
|
115
|
+
let planCount = 0;
|
|
116
|
+
let sessionCount = 0;
|
|
117
|
+
let recentPlans = [];
|
|
118
|
+
let recentSessions = [];
|
|
119
|
+
if (fs.existsSync(plansPath)) {
|
|
120
|
+
const plans = fs
|
|
121
|
+
.readdirSync(plansPath)
|
|
122
|
+
.filter((f) => f.endsWith(".md"))
|
|
123
|
+
.sort()
|
|
124
|
+
.reverse();
|
|
125
|
+
planCount = plans.length;
|
|
126
|
+
recentPlans = plans.slice(0, 3);
|
|
127
|
+
}
|
|
128
|
+
if (fs.existsSync(sessionsPath)) {
|
|
129
|
+
const sessions = fs
|
|
130
|
+
.readdirSync(sessionsPath)
|
|
131
|
+
.filter((f) => f.endsWith(".md"))
|
|
132
|
+
.sort()
|
|
133
|
+
.reverse();
|
|
134
|
+
sessionCount = sessions.length;
|
|
135
|
+
recentSessions = sessions.slice(0, 3);
|
|
136
|
+
}
|
|
137
|
+
let output = `✓ .cortex directory found at ${cortexPath}
|
|
138
|
+
|
|
139
|
+
Plans: ${planCount}`;
|
|
140
|
+
if (recentPlans.length > 0) {
|
|
141
|
+
output += `\n Recent: ${recentPlans.join(", ")}`;
|
|
142
|
+
}
|
|
143
|
+
output += `\n\nSessions: ${sessionCount}`;
|
|
144
|
+
if (recentSessions.length > 0) {
|
|
145
|
+
output += `\n Recent: ${recentSessions.join(", ")}`;
|
|
146
|
+
}
|
|
147
|
+
return output;
|
|
148
|
+
},
|
|
149
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export declare const save: {
|
|
2
|
+
description: string;
|
|
3
|
+
args: {
|
|
4
|
+
title: import("zod").ZodString;
|
|
5
|
+
type: import("zod").ZodEnum<{
|
|
6
|
+
feature: "feature";
|
|
7
|
+
bugfix: "bugfix";
|
|
8
|
+
refactor: "refactor";
|
|
9
|
+
spike: "spike";
|
|
10
|
+
docs: "docs";
|
|
11
|
+
architecture: "architecture";
|
|
12
|
+
}>;
|
|
13
|
+
content: import("zod").ZodString;
|
|
14
|
+
tasks: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
15
|
+
};
|
|
16
|
+
execute(args: {
|
|
17
|
+
title: string;
|
|
18
|
+
type: "feature" | "bugfix" | "refactor" | "spike" | "docs" | "architecture";
|
|
19
|
+
content: string;
|
|
20
|
+
tasks?: string[] | undefined;
|
|
21
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
22
|
+
};
|
|
23
|
+
export declare const list: {
|
|
24
|
+
description: string;
|
|
25
|
+
args: {
|
|
26
|
+
type: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
27
|
+
feature: "feature";
|
|
28
|
+
bugfix: "bugfix";
|
|
29
|
+
refactor: "refactor";
|
|
30
|
+
spike: "spike";
|
|
31
|
+
docs: "docs";
|
|
32
|
+
architecture: "architecture";
|
|
33
|
+
all: "all";
|
|
34
|
+
}>>;
|
|
35
|
+
};
|
|
36
|
+
execute(args: {
|
|
37
|
+
type?: "feature" | "bugfix" | "refactor" | "spike" | "docs" | "architecture" | "all" | undefined;
|
|
38
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
39
|
+
};
|
|
40
|
+
export declare const load: {
|
|
41
|
+
description: string;
|
|
42
|
+
args: {
|
|
43
|
+
filename: import("zod").ZodString;
|
|
44
|
+
};
|
|
45
|
+
execute(args: {
|
|
46
|
+
filename: string;
|
|
47
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
48
|
+
};
|
|
49
|
+
export declare const delete_: {
|
|
50
|
+
description: string;
|
|
51
|
+
args: {
|
|
52
|
+
filename: import("zod").ZodString;
|
|
53
|
+
};
|
|
54
|
+
execute(args: {
|
|
55
|
+
filename: string;
|
|
56
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
57
|
+
};
|
|
58
|
+
export { delete_ as delete };
|
|
59
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
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 PLANS_DIR = "plans";
|
|
6
|
+
function slugify(text) {
|
|
7
|
+
return text
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^\w\s-]/g, "")
|
|
10
|
+
.replace(/\s+/g, "-")
|
|
11
|
+
.replace(/-+/g, "-")
|
|
12
|
+
.substring(0, 50);
|
|
13
|
+
}
|
|
14
|
+
function getDatePrefix() {
|
|
15
|
+
const now = new Date();
|
|
16
|
+
return now.toISOString().split("T")[0]; // YYYY-MM-DD
|
|
17
|
+
}
|
|
18
|
+
function ensureCortexDir(worktree) {
|
|
19
|
+
const cortexPath = path.join(worktree, CORTEX_DIR);
|
|
20
|
+
const plansPath = path.join(cortexPath, PLANS_DIR);
|
|
21
|
+
if (!fs.existsSync(plansPath)) {
|
|
22
|
+
fs.mkdirSync(plansPath, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
return plansPath;
|
|
25
|
+
}
|
|
26
|
+
export const save = tool({
|
|
27
|
+
description: "Save an implementation plan to .cortex/plans/ with mermaid diagram support",
|
|
28
|
+
args: {
|
|
29
|
+
title: tool.schema.string().describe("Plan title (e.g., 'User Authentication System')"),
|
|
30
|
+
type: tool.schema
|
|
31
|
+
.enum(["feature", "bugfix", "refactor", "architecture", "spike", "docs"])
|
|
32
|
+
.describe("Plan type"),
|
|
33
|
+
content: tool.schema
|
|
34
|
+
.string()
|
|
35
|
+
.describe("Full plan content in markdown (can include mermaid diagrams)"),
|
|
36
|
+
tasks: tool.schema
|
|
37
|
+
.array(tool.schema.string())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Optional list of tasks"),
|
|
40
|
+
},
|
|
41
|
+
async execute(args, context) {
|
|
42
|
+
const { title, type, content, tasks } = args;
|
|
43
|
+
const plansPath = ensureCortexDir(context.worktree);
|
|
44
|
+
const datePrefix = getDatePrefix();
|
|
45
|
+
const slug = slugify(title);
|
|
46
|
+
const filename = `${datePrefix}-${type}-${slug}.md`;
|
|
47
|
+
const filepath = path.join(plansPath, filename);
|
|
48
|
+
// Build frontmatter
|
|
49
|
+
const frontmatter = `---
|
|
50
|
+
title: "${title}"
|
|
51
|
+
type: ${type}
|
|
52
|
+
created: ${new Date().toISOString()}
|
|
53
|
+
status: draft
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
`;
|
|
57
|
+
// Build task list if provided
|
|
58
|
+
let taskSection = "";
|
|
59
|
+
if (tasks && tasks.length > 0) {
|
|
60
|
+
taskSection = `\n## Tasks\n\n${tasks.map((t) => `- [ ] ${t}`).join("\n")}\n`;
|
|
61
|
+
}
|
|
62
|
+
// Combine content
|
|
63
|
+
const fullContent = frontmatter + `# ${title}\n\n` + content + taskSection;
|
|
64
|
+
// Write file
|
|
65
|
+
fs.writeFileSync(filepath, fullContent);
|
|
66
|
+
return `✓ Plan saved successfully
|
|
67
|
+
|
|
68
|
+
File: ${filename}
|
|
69
|
+
Path: ${filepath}
|
|
70
|
+
|
|
71
|
+
The plan includes:
|
|
72
|
+
- Title: ${title}
|
|
73
|
+
- Type: ${type}
|
|
74
|
+
- Tasks: ${tasks?.length || 0}
|
|
75
|
+
|
|
76
|
+
You can load this plan later with plan_load or view all plans with plan_list.`;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
export const list = tool({
|
|
80
|
+
description: "List all saved plans in .cortex/plans/",
|
|
81
|
+
args: {
|
|
82
|
+
type: tool.schema
|
|
83
|
+
.enum(["feature", "bugfix", "refactor", "architecture", "spike", "docs", "all"])
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Filter by plan type (default: all)"),
|
|
86
|
+
},
|
|
87
|
+
async execute(args, context) {
|
|
88
|
+
const { type = "all" } = args;
|
|
89
|
+
const plansPath = path.join(context.worktree, CORTEX_DIR, PLANS_DIR);
|
|
90
|
+
if (!fs.existsSync(plansPath)) {
|
|
91
|
+
return `No plans found. The .cortex/plans/ directory doesn't exist.
|
|
92
|
+
|
|
93
|
+
Use plan_save to create your first plan, or cortex_init to initialize the directory.`;
|
|
94
|
+
}
|
|
95
|
+
const files = fs
|
|
96
|
+
.readdirSync(plansPath)
|
|
97
|
+
.filter((f) => f.endsWith(".md") && f !== ".gitkeep")
|
|
98
|
+
.sort()
|
|
99
|
+
.reverse();
|
|
100
|
+
if (files.length === 0) {
|
|
101
|
+
return "No plans found in .cortex/plans/";
|
|
102
|
+
}
|
|
103
|
+
let output = "📋 Saved Plans:\n\n";
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
const filepath = path.join(plansPath, file);
|
|
106
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
107
|
+
// Parse frontmatter
|
|
108
|
+
let title = file;
|
|
109
|
+
let planType = "unknown";
|
|
110
|
+
let created = "";
|
|
111
|
+
let status = "draft";
|
|
112
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
113
|
+
if (frontmatterMatch) {
|
|
114
|
+
const fm = frontmatterMatch[1];
|
|
115
|
+
const titleMatch = fm.match(/title:\s*"?([^"\n]+)"?/);
|
|
116
|
+
const typeMatch = fm.match(/type:\s*(\w+)/);
|
|
117
|
+
const createdMatch = fm.match(/created:\s*(\S+)/);
|
|
118
|
+
const statusMatch = fm.match(/status:\s*(\w+)/);
|
|
119
|
+
if (titleMatch)
|
|
120
|
+
title = titleMatch[1];
|
|
121
|
+
if (typeMatch)
|
|
122
|
+
planType = typeMatch[1];
|
|
123
|
+
if (createdMatch)
|
|
124
|
+
created = createdMatch[1].split("T")[0];
|
|
125
|
+
if (statusMatch)
|
|
126
|
+
status = statusMatch[1];
|
|
127
|
+
}
|
|
128
|
+
// Filter by type if specified
|
|
129
|
+
if (type !== "all" && planType !== type) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
output += `• ${title}\n`;
|
|
133
|
+
output += ` File: ${file}\n`;
|
|
134
|
+
output += ` Type: ${planType} | Created: ${created} | Status: ${status}\n\n`;
|
|
135
|
+
}
|
|
136
|
+
return output.trim();
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
export const load = tool({
|
|
140
|
+
description: "Load a saved plan by filename",
|
|
141
|
+
args: {
|
|
142
|
+
filename: tool.schema.string().describe("Plan filename (e.g., '2024-02-22-feature-auth.md')"),
|
|
143
|
+
},
|
|
144
|
+
async execute(args, context) {
|
|
145
|
+
const { filename } = args;
|
|
146
|
+
const plansPath = path.join(context.worktree, CORTEX_DIR, PLANS_DIR);
|
|
147
|
+
const filepath = path.join(plansPath, filename);
|
|
148
|
+
if (!fs.existsSync(filepath)) {
|
|
149
|
+
return `✗ Plan not found: ${filename}
|
|
150
|
+
|
|
151
|
+
Use plan_list to see available plans.`;
|
|
152
|
+
}
|
|
153
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
154
|
+
return `📋 Plan: ${filename}
|
|
155
|
+
${"=".repeat(50)}
|
|
156
|
+
|
|
157
|
+
${content}`;
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
export const delete_ = tool({
|
|
161
|
+
description: "Delete a saved plan",
|
|
162
|
+
args: {
|
|
163
|
+
filename: tool.schema.string().describe("Plan filename to delete"),
|
|
164
|
+
},
|
|
165
|
+
async execute(args, context) {
|
|
166
|
+
const { filename } = args;
|
|
167
|
+
const plansPath = path.join(context.worktree, CORTEX_DIR, PLANS_DIR);
|
|
168
|
+
const filepath = path.join(plansPath, filename);
|
|
169
|
+
if (!fs.existsSync(filepath)) {
|
|
170
|
+
return `✗ Plan not found: ${filename}`;
|
|
171
|
+
}
|
|
172
|
+
fs.unlinkSync(filepath);
|
|
173
|
+
return `✓ Deleted plan: ${filename}`;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
// Export with underscore suffix to avoid reserved word
|
|
177
|
+
export { delete_ as delete };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const save: {
|
|
2
|
+
description: string;
|
|
3
|
+
args: {
|
|
4
|
+
summary: import("zod").ZodString;
|
|
5
|
+
decisions: import("zod").ZodArray<import("zod").ZodString>;
|
|
6
|
+
filesChanged: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
7
|
+
relatedPlan: import("zod").ZodOptional<import("zod").ZodString>;
|
|
8
|
+
branch: import("zod").ZodOptional<import("zod").ZodString>;
|
|
9
|
+
};
|
|
10
|
+
execute(args: {
|
|
11
|
+
summary: string;
|
|
12
|
+
decisions: string[];
|
|
13
|
+
filesChanged?: string[] | undefined;
|
|
14
|
+
relatedPlan?: string | undefined;
|
|
15
|
+
branch?: string | undefined;
|
|
16
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
17
|
+
};
|
|
18
|
+
export declare const list: {
|
|
19
|
+
description: string;
|
|
20
|
+
args: {
|
|
21
|
+
limit: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
22
|
+
};
|
|
23
|
+
execute(args: {
|
|
24
|
+
limit?: number | undefined;
|
|
25
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
26
|
+
};
|
|
27
|
+
export declare const load: {
|
|
28
|
+
description: string;
|
|
29
|
+
args: {
|
|
30
|
+
filename: import("zod").ZodString;
|
|
31
|
+
};
|
|
32
|
+
execute(args: {
|
|
33
|
+
filename: string;
|
|
34
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"AA2BA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;CAsFf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CA8Df,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CAuBf,CAAC"}
|