cortex-agents 1.1.0 → 2.2.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 (49) hide show
  1. package/.opencode/agents/build.md +97 -11
  2. package/.opencode/agents/debug.md +24 -2
  3. package/.opencode/agents/devops.md +1 -2
  4. package/.opencode/agents/fullstack.md +1 -2
  5. package/.opencode/agents/plan.md +16 -7
  6. package/.opencode/agents/security.md +1 -2
  7. package/.opencode/agents/testing.md +1 -2
  8. package/.opencode/skills/api-design/SKILL.md +348 -0
  9. package/.opencode/skills/architecture-patterns/SKILL.md +323 -0
  10. package/.opencode/skills/backend-development/SKILL.md +329 -0
  11. package/.opencode/skills/code-quality/SKILL.md +12 -0
  12. package/.opencode/skills/database-design/SKILL.md +347 -0
  13. package/.opencode/skills/deployment-automation/SKILL.md +7 -0
  14. package/.opencode/skills/design-patterns/SKILL.md +295 -0
  15. package/.opencode/skills/desktop-development/SKILL.md +295 -0
  16. package/.opencode/skills/frontend-development/SKILL.md +210 -0
  17. package/.opencode/skills/mobile-development/SKILL.md +407 -0
  18. package/.opencode/skills/performance-optimization/SKILL.md +330 -0
  19. package/.opencode/skills/testing-strategies/SKILL.md +33 -0
  20. package/README.md +309 -111
  21. package/dist/cli.js +264 -36
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +43 -0
  24. package/dist/plugin.js +3 -2
  25. package/dist/registry.d.ts +45 -0
  26. package/dist/registry.d.ts.map +1 -0
  27. package/dist/registry.js +140 -0
  28. package/dist/tools/cortex.d.ts.map +1 -1
  29. package/dist/tools/cortex.js +3 -4
  30. package/dist/tools/docs.d.ts +52 -0
  31. package/dist/tools/docs.d.ts.map +1 -0
  32. package/dist/tools/docs.js +328 -0
  33. package/dist/tools/task.d.ts +20 -0
  34. package/dist/tools/task.d.ts.map +1 -0
  35. package/dist/tools/task.js +302 -0
  36. package/dist/tools/worktree.d.ts +32 -0
  37. package/dist/tools/worktree.d.ts.map +1 -1
  38. package/dist/tools/worktree.js +403 -2
  39. package/dist/utils/plan-extract.d.ts +37 -0
  40. package/dist/utils/plan-extract.d.ts.map +1 -0
  41. package/dist/utils/plan-extract.js +137 -0
  42. package/dist/utils/propagate.d.ts +22 -0
  43. package/dist/utils/propagate.d.ts.map +1 -0
  44. package/dist/utils/propagate.js +64 -0
  45. package/dist/utils/worktree-detect.d.ts +20 -0
  46. package/dist/utils/worktree-detect.d.ts.map +1 -0
  47. package/dist/utils/worktree-detect.js +42 -0
  48. package/package.json +17 -7
  49. package/.opencode/skills/web-development/SKILL.md +0 -122
@@ -3,10 +3,9 @@ import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  const CORTEX_DIR = ".cortex";
5
5
  const DEFAULT_CONFIG = {
6
- $schema: "https://k2p5.dev/cortex-config.json",
7
6
  version: "1.0.0",
8
7
  worktree: {
9
- root: "../.worktrees",
8
+ root: ".worktrees",
10
9
  autoCleanup: false,
11
10
  },
12
11
  branches: {
@@ -33,7 +32,7 @@ config.local.json
33
32
  `;
34
33
  const README_CONTENT = `# .cortex
35
34
 
36
- This directory contains project context for the K2P5 development agents.
35
+ This directory contains project context for the Cortex development agents.
37
36
 
38
37
  ## Structure
39
38
 
@@ -56,7 +55,7 @@ They are gitignored by default but can be kept if needed.
56
55
 
57
56
  ## Usage
58
57
 
59
- The K2P5 agents will automatically use this directory for:
58
+ The Cortex agents will automatically use this directory for:
60
59
  - Saving implementation plans before coding
61
60
  - Recording session summaries with key decisions
62
61
  - Managing worktree and branch workflows
@@ -0,0 +1,52 @@
1
+ export declare const init: {
2
+ description: string;
3
+ args: {
4
+ path: import("zod").ZodOptional<import("zod").ZodString>;
5
+ confirm: import("zod").ZodOptional<import("zod").ZodBoolean>;
6
+ };
7
+ execute(args: {
8
+ path?: string | undefined;
9
+ confirm?: boolean | undefined;
10
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
11
+ };
12
+ export declare const save: {
13
+ description: string;
14
+ args: {
15
+ title: import("zod").ZodString;
16
+ type: import("zod").ZodEnum<{
17
+ feature: "feature";
18
+ decision: "decision";
19
+ flow: "flow";
20
+ }>;
21
+ content: import("zod").ZodString;
22
+ tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
23
+ relatedFiles: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
24
+ };
25
+ execute(args: {
26
+ title: string;
27
+ type: "feature" | "decision" | "flow";
28
+ content: string;
29
+ tags?: string[] | undefined;
30
+ relatedFiles?: string[] | undefined;
31
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
32
+ };
33
+ export declare const list: {
34
+ description: string;
35
+ args: {
36
+ type: import("zod").ZodOptional<import("zod").ZodEnum<{
37
+ feature: "feature";
38
+ all: "all";
39
+ decision: "decision";
40
+ flow: "flow";
41
+ }>>;
42
+ };
43
+ execute(args: {
44
+ type?: "feature" | "all" | "decision" | "flow" | undefined;
45
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
46
+ };
47
+ export declare const index: {
48
+ description: string;
49
+ args: {};
50
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
51
+ };
52
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/tools/docs.ts"],"names":[],"mappings":"AAoNA,eAAO,MAAM,IAAI;;;;;;;;;;CA4Df,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;CAiEf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;;;;;;CAmEf,CAAC;AAEH,eAAO,MAAM,KAAK;;;;CAahB,CAAC"}
@@ -0,0 +1,328 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ const DOCS_DIR = "docs";
5
+ const DOC_TYPES = ["decisions", "features", "flows"];
6
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
7
+ function slugify(text) {
8
+ return text
9
+ .toLowerCase()
10
+ .replace(/[^\w\s-]/g, "")
11
+ .replace(/\s+/g, "-")
12
+ .replace(/-+/g, "-")
13
+ .substring(0, 60);
14
+ }
15
+ function getDatePrefix() {
16
+ return new Date().toISOString().split("T")[0]; // YYYY-MM-DD
17
+ }
18
+ /** Map singular type to plural folder name */
19
+ function typeToFolder(type) {
20
+ const map = {
21
+ decision: "decisions",
22
+ feature: "features",
23
+ flow: "flows",
24
+ };
25
+ return map[type] || type;
26
+ }
27
+ function ensureDocsDir(worktree, docsPath) {
28
+ const resolved = path.join(worktree, docsPath || DOCS_DIR);
29
+ for (const sub of DOC_TYPES) {
30
+ fs.mkdirSync(path.join(resolved, sub), { recursive: true });
31
+ }
32
+ return resolved;
33
+ }
34
+ function parseFrontmatter(content) {
35
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
36
+ if (!match)
37
+ return null;
38
+ const fm = match[1];
39
+ const get = (key) => {
40
+ const m = fm.match(new RegExp(`${key}:\\s*"?([^"\\n]+)"?`));
41
+ return m ? m[1].trim() : undefined;
42
+ };
43
+ const getArray = (key) => {
44
+ const m = fm.match(new RegExp(`${key}:\\s*\\[([^\\]]*)]`));
45
+ if (!m)
46
+ return [];
47
+ return m[1]
48
+ .split(",")
49
+ .map((s) => s.trim().replace(/^"|"$/g, ""))
50
+ .filter(Boolean);
51
+ };
52
+ return {
53
+ title: get("title") || "Untitled",
54
+ type: get("type") || "unknown",
55
+ date: get("date") || "",
56
+ status: get("status"),
57
+ tags: getArray("tags"),
58
+ related_files: getArray("related_files"),
59
+ };
60
+ }
61
+ // ─── Templates ───────────────────────────────────────────────────────────────
62
+ function buildFrontmatter(args) {
63
+ const date = new Date().toISOString();
64
+ const tagsStr = args.tags && args.tags.length > 0
65
+ ? `[${args.tags.map((t) => `"${t}"`).join(", ")}]`
66
+ : "[]";
67
+ const filesStr = args.relatedFiles && args.relatedFiles.length > 0
68
+ ? `[${args.relatedFiles.map((f) => `"${f}"`).join(", ")}]`
69
+ : "[]";
70
+ return `---
71
+ title: "${args.title}"
72
+ type: ${args.type}
73
+ date: ${date}
74
+ ${args.status ? `status: ${args.status}` : ""}
75
+ tags: ${tagsStr}
76
+ related_files: ${filesStr}
77
+ ---`.replace(/\n{2,}/g, "\n");
78
+ }
79
+ function buildDocument(args) {
80
+ const statusMap = {
81
+ decision: "accepted",
82
+ feature: "implemented",
83
+ flow: "",
84
+ };
85
+ const frontmatter = buildFrontmatter({
86
+ title: args.title,
87
+ type: args.type,
88
+ status: statusMap[args.type] || undefined,
89
+ tags: args.tags,
90
+ relatedFiles: args.relatedFiles,
91
+ });
92
+ const relatedSection = args.relatedFiles && args.relatedFiles.length > 0
93
+ ? `\n\n## Related Files\n\n${args.relatedFiles.map((f) => `- \`${f}\``).join("\n")}`
94
+ : "";
95
+ return `${frontmatter}\n\n${args.content}${relatedSection}\n`;
96
+ }
97
+ function rebuildIndex(docsRoot) {
98
+ const sections = [
99
+ { type: "decisions", label: "Decisions", icon: "📋", entries: [] },
100
+ { type: "features", label: "Features", icon: "🚀", entries: [] },
101
+ { type: "flows", label: "Flows", icon: "🔄", entries: [] },
102
+ ];
103
+ for (const section of sections) {
104
+ const folderPath = path.join(docsRoot, section.type);
105
+ if (!fs.existsSync(folderPath))
106
+ continue;
107
+ const files = fs
108
+ .readdirSync(folderPath)
109
+ .filter((f) => f.endsWith(".md") && f !== ".gitkeep")
110
+ .sort()
111
+ .reverse();
112
+ for (const file of files) {
113
+ const content = fs.readFileSync(path.join(folderPath, file), "utf-8");
114
+ const fm = parseFrontmatter(content);
115
+ if (fm) {
116
+ section.entries.push({ ...fm, filename: file, folder: section.type });
117
+ }
118
+ }
119
+ }
120
+ const now = new Date().toISOString().split("T")[0];
121
+ let index = `# Project Documentation\n\n> Auto-generated by cortex-agents. Last updated: ${now}\n`;
122
+ for (const section of sections) {
123
+ index += `\n## ${section.icon} ${section.label} (${section.entries.length})\n\n`;
124
+ if (section.entries.length === 0) {
125
+ index += `_No ${section.label.toLowerCase()} documented yet._\n`;
126
+ continue;
127
+ }
128
+ if (section.type === "flows") {
129
+ index += `| Date | Title | Tags |\n|------|-------|------|\n`;
130
+ }
131
+ else {
132
+ index += `| Date | Title | Status | Tags |\n|------|-------|--------|------|\n`;
133
+ }
134
+ for (const entry of section.entries) {
135
+ const date = entry.date ? entry.date.split("T")[0] : "—";
136
+ const link = `[${entry.title}](${entry.folder}/${entry.filename})`;
137
+ const tags = entry.tags && entry.tags.length > 0 ? entry.tags.join(", ") : "—";
138
+ if (section.type === "flows") {
139
+ index += `| ${date} | ${link} | ${tags} |\n`;
140
+ }
141
+ else {
142
+ const status = entry.status || "—";
143
+ index += `| ${date} | ${link} | ${status} | ${tags} |\n`;
144
+ }
145
+ }
146
+ }
147
+ const indexPath = path.join(docsRoot, "INDEX.md");
148
+ fs.writeFileSync(indexPath, index);
149
+ const totalDocs = sections.reduce((sum, s) => sum + s.entries.length, 0);
150
+ return `✓ Index rebuilt: ${totalDocs} documents indexed in docs/INDEX.md`;
151
+ }
152
+ // ─── Tools ───────────────────────────────────────────────────────────────────
153
+ export const init = tool({
154
+ description: "Initialize docs/ directory with folders for decisions, features, and flows documentation",
155
+ args: {
156
+ path: tool.schema
157
+ .string()
158
+ .optional()
159
+ .describe("Custom docs directory path relative to project root (default: 'docs')"),
160
+ confirm: tool.schema
161
+ .boolean()
162
+ .optional()
163
+ .describe("If docs/ already exists, set to true to confirm using the existing folder"),
164
+ },
165
+ async execute(args, context) {
166
+ const docsPath = args.path || DOCS_DIR;
167
+ const docsRoot = path.join(context.worktree, docsPath);
168
+ // Check if docs/ already exists
169
+ if (fs.existsSync(docsRoot)) {
170
+ if (!args.confirm) {
171
+ // Check what's already there
172
+ const existing = fs.readdirSync(docsRoot);
173
+ return `⚠ Directory '${docsPath}/' already exists with ${existing.length} entries: ${existing.join(", ")}
174
+
175
+ To use this existing folder, call docs_init again with confirm: true.
176
+ This will add the decisions/, features/, and flows/ subfolders without overwriting existing files.`;
177
+ }
178
+ }
179
+ // Create folder structure
180
+ for (const sub of DOC_TYPES) {
181
+ const subPath = path.join(docsRoot, sub);
182
+ fs.mkdirSync(subPath, { recursive: true });
183
+ const gitkeep = path.join(subPath, ".gitkeep");
184
+ if (!fs.existsSync(gitkeep)) {
185
+ fs.writeFileSync(gitkeep, "");
186
+ }
187
+ }
188
+ // Create initial INDEX.md
189
+ const indexPath = path.join(docsRoot, "INDEX.md");
190
+ if (!fs.existsSync(indexPath)) {
191
+ rebuildIndex(docsRoot);
192
+ }
193
+ return `✓ Documentation directory initialized at ${docsPath}/
194
+
195
+ Structure:
196
+ ${docsPath}/
197
+ ├── INDEX.md Auto-generated index of all docs
198
+ ├── decisions/ Architecture & technology decisions (ADRs)
199
+ ├── features/ Feature documentation with diagrams
200
+ └── flows/ Process & data flow documentation
201
+
202
+ Use docs_save to create documentation files.
203
+ Use docs_list to browse existing docs.
204
+ The INDEX.md is automatically rebuilt whenever you save a new document.`;
205
+ },
206
+ });
207
+ export const save = tool({
208
+ description: "Save a documentation file with mermaid diagrams to docs/. Auto-rebuilds the index.",
209
+ args: {
210
+ title: tool.schema
211
+ .string()
212
+ .describe("Document title (e.g., 'User Authentication System')"),
213
+ type: tool.schema
214
+ .enum(["decision", "feature", "flow"])
215
+ .describe("Document type: decision (ADR), feature (component docs), or flow (sequence/process)"),
216
+ content: tool.schema
217
+ .string()
218
+ .describe("Full markdown content including mermaid diagram blocks. Must follow the template structure for the chosen type."),
219
+ tags: tool.schema
220
+ .array(tool.schema.string())
221
+ .optional()
222
+ .describe("Tags for categorization (e.g., ['auth', 'security'])"),
223
+ relatedFiles: tool.schema
224
+ .array(tool.schema.string())
225
+ .optional()
226
+ .describe("Source files related to this document (e.g., ['src/auth.ts'])"),
227
+ },
228
+ async execute(args, context) {
229
+ const { title, type, content, tags, relatedFiles } = args;
230
+ const docsRoot = path.join(context.worktree, DOCS_DIR);
231
+ const folder = typeToFolder(type);
232
+ const folderPath = path.join(docsRoot, folder);
233
+ // Auto-init if docs/ doesn't exist
234
+ if (!fs.existsSync(folderPath)) {
235
+ ensureDocsDir(context.worktree);
236
+ }
237
+ const datePrefix = getDatePrefix();
238
+ const slug = slugify(title);
239
+ const filename = `${datePrefix}-${slug}.md`;
240
+ const filepath = path.join(folderPath, filename);
241
+ // Build the full document
242
+ const fullContent = buildDocument({
243
+ title,
244
+ type,
245
+ content,
246
+ tags,
247
+ relatedFiles,
248
+ });
249
+ fs.writeFileSync(filepath, fullContent);
250
+ // Auto-rebuild index
251
+ const indexResult = rebuildIndex(docsRoot);
252
+ return `✓ Documentation saved
253
+
254
+ File: ${folder}/${filename}
255
+ Path: ${filepath}
256
+ Type: ${type}
257
+ Tags: ${tags?.join(", ") || "none"}
258
+ Related files: ${relatedFiles?.length || 0}
259
+
260
+ ${indexResult}`;
261
+ },
262
+ });
263
+ export const list = tool({
264
+ description: "List all documentation files in docs/",
265
+ args: {
266
+ type: tool.schema
267
+ .enum(["decision", "feature", "flow", "all"])
268
+ .optional()
269
+ .describe("Filter by document type (default: all)"),
270
+ },
271
+ async execute(args, context) {
272
+ const { type = "all" } = args;
273
+ const docsRoot = path.join(context.worktree, DOCS_DIR);
274
+ if (!fs.existsSync(docsRoot)) {
275
+ return `No docs/ directory found.
276
+
277
+ Run docs_init to set up the documentation folder structure.`;
278
+ }
279
+ const foldersToScan = type === "all" ? [...DOC_TYPES] : [typeToFolder(type)];
280
+ let output = "📚 Project Documentation:\n";
281
+ let totalCount = 0;
282
+ for (const folder of foldersToScan) {
283
+ const folderPath = path.join(docsRoot, folder);
284
+ if (!fs.existsSync(folderPath))
285
+ continue;
286
+ const files = fs
287
+ .readdirSync(folderPath)
288
+ .filter((f) => f.endsWith(".md") && f !== ".gitkeep")
289
+ .sort()
290
+ .reverse();
291
+ if (files.length === 0)
292
+ continue;
293
+ const icon = folder === "decisions" ? "📋" : folder === "features" ? "🚀" : "🔄";
294
+ output += `\n${icon} ${folder.charAt(0).toUpperCase() + folder.slice(1)} (${files.length}):\n\n`;
295
+ for (const file of files) {
296
+ const content = fs.readFileSync(path.join(folderPath, file), "utf-8");
297
+ const fm = parseFrontmatter(content);
298
+ const title = fm?.title || file;
299
+ const date = fm?.date ? fm.date.split("T")[0] : file.substring(0, 10);
300
+ const tags = fm?.tags && fm.tags.length > 0
301
+ ? ` [${fm.tags.join(", ")}]`
302
+ : "";
303
+ const status = fm?.status ? ` (${fm.status})` : "";
304
+ output += ` • ${title}${status}${tags}\n`;
305
+ output += ` File: ${folder}/${file} | Date: ${date}\n\n`;
306
+ totalCount++;
307
+ }
308
+ }
309
+ if (totalCount === 0) {
310
+ return `No documentation files found in docs/.
311
+
312
+ Use docs_save to create your first document, or docs_init to set up the folder structure.`;
313
+ }
314
+ output += `\nTotal: ${totalCount} documents. See docs/INDEX.md for the full index.`;
315
+ return output;
316
+ },
317
+ });
318
+ export const index = tool({
319
+ description: "Rebuild docs/INDEX.md with links to all documentation files. Called automatically by docs_save.",
320
+ args: {},
321
+ async execute(_args, context) {
322
+ const docsRoot = path.join(context.worktree, DOCS_DIR);
323
+ if (!fs.existsSync(docsRoot)) {
324
+ return `No docs/ directory found. Run docs_init first.`;
325
+ }
326
+ return rebuildIndex(docsRoot);
327
+ },
328
+ });
@@ -0,0 +1,20 @@
1
+ export declare const finalize: {
2
+ description: string;
3
+ args: {
4
+ commitMessage: import("zod").ZodString;
5
+ prTitle: import("zod").ZodOptional<import("zod").ZodString>;
6
+ prBody: import("zod").ZodOptional<import("zod").ZodString>;
7
+ baseBranch: import("zod").ZodOptional<import("zod").ZodString>;
8
+ planFilename: import("zod").ZodOptional<import("zod").ZodString>;
9
+ draft: import("zod").ZodOptional<import("zod").ZodBoolean>;
10
+ };
11
+ execute(args: {
12
+ commitMessage: string;
13
+ prTitle?: string | undefined;
14
+ prBody?: string | undefined;
15
+ baseBranch?: string | undefined;
16
+ planFilename?: string | undefined;
17
+ draft?: boolean | undefined;
18
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
19
+ };
20
+ //# sourceMappingURL=task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAyGA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;CA0OnB,CAAC"}