@strayl/agent 0.1.3 → 0.1.4

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 (57) hide show
  1. package/package.json +5 -1
  2. package/skills/api-creation/SKILL.md +631 -0
  3. package/skills/authentication/SKILL.md +294 -0
  4. package/skills/frontend-design/SKILL.md +108 -0
  5. package/skills/landing-creation/SKILL.md +125 -0
  6. package/skills/reference/SKILL.md +149 -0
  7. package/skills/web-application-creation/SKILL.md +231 -0
  8. package/src/agent.ts +0 -465
  9. package/src/checkpoints/manager.ts +0 -112
  10. package/src/context/manager.ts +0 -185
  11. package/src/context/summarizer.ts +0 -104
  12. package/src/context/trim.ts +0 -55
  13. package/src/emitter.ts +0 -14
  14. package/src/hitl/manager.ts +0 -77
  15. package/src/hitl/transport.ts +0 -13
  16. package/src/index.ts +0 -116
  17. package/src/llm/client.ts +0 -276
  18. package/src/llm/gemini-native.ts +0 -307
  19. package/src/llm/models.ts +0 -64
  20. package/src/middleware/compose.ts +0 -24
  21. package/src/middleware/credential-scrubbing.ts +0 -31
  22. package/src/middleware/forbidden-packages.ts +0 -107
  23. package/src/middleware/plan-mode.ts +0 -143
  24. package/src/middleware/prompt-caching.ts +0 -21
  25. package/src/middleware/tool-compression.ts +0 -25
  26. package/src/middleware/tool-filter.ts +0 -13
  27. package/src/prompts/implementation-mode.md +0 -16
  28. package/src/prompts/plan-mode.md +0 -51
  29. package/src/prompts/system.ts +0 -173
  30. package/src/skills/loader.ts +0 -53
  31. package/src/stdin-listener.ts +0 -61
  32. package/src/subagents/definitions.ts +0 -72
  33. package/src/subagents/manager.ts +0 -161
  34. package/src/todos/manager.ts +0 -61
  35. package/src/tools/builtin/delete.ts +0 -29
  36. package/src/tools/builtin/edit.ts +0 -74
  37. package/src/tools/builtin/exec.ts +0 -216
  38. package/src/tools/builtin/glob.ts +0 -104
  39. package/src/tools/builtin/grep.ts +0 -115
  40. package/src/tools/builtin/ls.ts +0 -54
  41. package/src/tools/builtin/move.ts +0 -31
  42. package/src/tools/builtin/read.ts +0 -69
  43. package/src/tools/builtin/write.ts +0 -42
  44. package/src/tools/executor.ts +0 -51
  45. package/src/tools/external/database.ts +0 -285
  46. package/src/tools/external/enter-plan-mode.ts +0 -34
  47. package/src/tools/external/generate-image.ts +0 -110
  48. package/src/tools/external/hitl-tools.ts +0 -118
  49. package/src/tools/external/preview.ts +0 -28
  50. package/src/tools/external/proxy-fetch.ts +0 -51
  51. package/src/tools/external/task.ts +0 -38
  52. package/src/tools/external/wait.ts +0 -20
  53. package/src/tools/external/web-fetch.ts +0 -57
  54. package/src/tools/external/web-search.ts +0 -61
  55. package/src/tools/registry.ts +0 -36
  56. package/src/tools/zod-to-json-schema.ts +0 -86
  57. package/src/types.ts +0 -151
@@ -1,31 +0,0 @@
1
- import type { Middleware, ToolCall } from "../types.js";
2
-
3
- const CREDENTIAL_PATTERNS = [
4
- /(?:token|api_key|apikey|secret|password|credential|auth|bearer)\s*[:=]\s*["']?([^\s"',}{]{8,})["']?/gi,
5
- /(?:sk|pk|rk|ak)-[a-zA-Z0-9]{20,}/g, // Common API key prefixes
6
- /ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens
7
- /gho_[a-zA-Z0-9]{36}/g,
8
- /github_pat_[a-zA-Z0-9_]{82}/g,
9
- /xox[bpas]-[a-zA-Z0-9-]+/g, // Slack tokens
10
- /eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}/g, // JWT-like tokens
11
- ];
12
-
13
- function scrubCredentials(text: string): string {
14
- let scrubbed = text;
15
- for (const pattern of CREDENTIAL_PATTERNS) {
16
- scrubbed = scrubbed.replace(pattern, (match) => {
17
- // Keep first 4 chars for identification
18
- const prefix = match.slice(0, 4);
19
- return `${prefix}****[REDACTED]`;
20
- });
21
- }
22
- return scrubbed;
23
- }
24
-
25
- export const credentialScrubbingMiddleware: Middleware = {
26
- name: "credential-scrubbing",
27
- wrapToolCall: async (_call: ToolCall, next: () => Promise<string>): Promise<string> => {
28
- const result = await next();
29
- return scrubCredentials(result);
30
- },
31
- };
@@ -1,107 +0,0 @@
1
- import type { Middleware, ToolCall } from "../types.js";
2
-
3
- const FORBIDDEN_PACKAGES: Record<string, string> = {
4
- // Database drivers
5
- pg: "Use @neondatabase/serverless instead",
6
- mysql2: "Use @planetscale/database instead",
7
- "better-sqlite3": "Use @libsql/client instead",
8
- sqlite3: "Use @libsql/client instead",
9
- mongodb: "Use mongoose with serverless adapter instead",
10
-
11
- // ORMs
12
- prisma: "Use drizzle-orm instead",
13
- "@prisma/client": "Use drizzle-orm instead",
14
- typeorm: "Use drizzle-orm instead",
15
- sequelize: "Use drizzle-orm instead",
16
- knex: "Use drizzle-orm instead",
17
- "mikro-orm": "Use drizzle-orm instead",
18
-
19
- // Server frameworks (not needed with TanStack Start)
20
- express: "Not needed — use TanStack Start + Nitro",
21
- fastify: "Not needed — use TanStack Start + Nitro",
22
- koa: "Not needed — use TanStack Start + Nitro",
23
- hapi: "Not needed — use TanStack Start + Nitro",
24
- "nest.js": "Not needed — use TanStack Start + Nitro",
25
- "@nestjs/core": "Not needed — use TanStack Start + Nitro",
26
-
27
- // Native bindings
28
- bcrypt: "Use bcryptjs instead (pure JS)",
29
- sharp: "Use @cf-wasm/photon instead",
30
- canvas: "Use @napi-rs/canvas instead",
31
-
32
- // Build tools (don't change the build setup)
33
- webpack: "Project uses Vite — don't add webpack",
34
- parcel: "Project uses Vite — don't add parcel",
35
- rollup: "Project uses Vite via TanStack Start",
36
-
37
- // Outdated frameworks
38
- gatsby: "Use Next.js, Remix, or TanStack Start instead",
39
- "create-react-app": "Use Vite + React instead",
40
-
41
- // Auth
42
- passport: "Use better-auth instead",
43
- "next-auth": "Use better-auth instead",
44
-
45
- // CSS
46
- "styled-components": "Use Tailwind CSS instead",
47
- emotion: "Use Tailwind CSS instead",
48
- "@emotion/react": "Use Tailwind CSS instead",
49
- sass: "Use Tailwind CSS instead",
50
- less: "Use Tailwind CSS instead",
51
- };
52
-
53
- function extractNpmPackages(command: string): string[] {
54
- // Split chained commands
55
- const parts = command.split(/&&|;|\|/).map(s => s.trim());
56
- const packages: string[] = [];
57
-
58
- for (const part of parts) {
59
- // Match npm install/add patterns
60
- const match = part.match(/(?:npm\s+(?:install|i|add)|yarn\s+add|pnpm\s+(?:add|install)|bun\s+(?:add|install))\s+(.+)/i);
61
- if (!match) continue;
62
-
63
- const args = match[1].split(/\s+/);
64
- for (const arg of args) {
65
- if (arg.startsWith("-")) continue; // Skip flags
66
- // Strip version specifier: @scope/pkg@1.2.3 → @scope/pkg
67
- const pkg = arg.replace(/@[^@/]+$/, "");
68
- if (pkg) packages.push(pkg);
69
- }
70
- }
71
-
72
- return packages;
73
- }
74
-
75
- export const forbiddenPackagesMiddleware: Middleware = {
76
- name: "forbidden-packages",
77
- wrapToolCall: async (call: ToolCall, next: () => Promise<string>): Promise<string> => {
78
- if (call.function.name !== "exec") return next();
79
-
80
- let args: { command?: string };
81
- try {
82
- args = JSON.parse(call.function.arguments);
83
- } catch {
84
- return next();
85
- }
86
-
87
- if (!args.command) return next();
88
-
89
- const packages = extractNpmPackages(args.command);
90
- const blocked: Array<{ pkg: string; reason: string }> = [];
91
-
92
- for (const pkg of packages) {
93
- if (FORBIDDEN_PACKAGES[pkg]) {
94
- blocked.push({ pkg, reason: FORBIDDEN_PACKAGES[pkg] });
95
- }
96
- }
97
-
98
- if (blocked.length > 0) {
99
- const details = blocked.map(b => `- ${b.pkg}: ${b.reason}`).join("\n");
100
- return JSON.stringify({
101
- error: `Blocked packages detected:\n${details}\n\nUse the suggested alternatives instead.`,
102
- });
103
- }
104
-
105
- return next();
106
- },
107
- };
@@ -1,143 +0,0 @@
1
- import type { Middleware, ToolCall, AgentMode, Message } from "../types.js";
2
-
3
- /**
4
- * Tools blocked in plan mode — anything that writes, executes, or modifies.
5
- */
6
- const PLAN_MODE_BLOCKED = new Set([
7
- "exec",
8
- "getLogs",
9
- "write_file",
10
- "edit_file",
11
- "delete_file",
12
- "move_file",
13
- "show_preview",
14
- "generate_image",
15
- "requestEnvVar",
16
- "enterPlanMode",
17
- "create_database",
18
- "run_database_query",
19
- "prepare_database_migration",
20
- "complete_database_migration",
21
- ]);
22
-
23
- /**
24
- * Tools blocked in implementation/normal mode (planning-only tools).
25
- */
26
- const IMPL_MODE_BLOCKED = new Set([
27
- "writePlan",
28
- ]);
29
-
30
- /**
31
- * Creates middleware that filters tools based on current agent mode.
32
- */
33
- export function createPlanModeMiddleware(getMode: () => AgentMode): Middleware {
34
- return {
35
- name: "planModeFilter",
36
- filterTools: (tools) => {
37
- const mode = getMode();
38
- if (mode === "plan") {
39
- return tools.filter(t => !PLAN_MODE_BLOCKED.has(t.function.name));
40
- }
41
- if (mode === "implement") {
42
- // Block planning tools + enterPlanMode
43
- return tools.filter(t =>
44
- !IMPL_MODE_BLOCKED.has(t.function.name) && t.function.name !== "enterPlanMode"
45
- );
46
- }
47
- // Normal mode: block planning-only tools
48
- return tools.filter(t => !IMPL_MODE_BLOCKED.has(t.function.name));
49
- },
50
- wrapToolCall: async (call: ToolCall, next: () => Promise<string>) => {
51
- const mode = getMode();
52
- const name = call.function.name;
53
-
54
- if (mode === "plan" && PLAN_MODE_BLOCKED.has(name)) {
55
- return JSON.stringify({
56
- error: `Tool "${name}" is not available in Plan Mode. Use read-only tools only.`,
57
- });
58
- }
59
- if (mode !== "plan" && IMPL_MODE_BLOCKED.has(name)) {
60
- return JSON.stringify({
61
- error: `Tool "${name}" is only available in Plan Mode.`,
62
- });
63
- }
64
- if (mode === "implement" && name === "enterPlanMode") {
65
- return JSON.stringify({
66
- error: `Cannot enter plan mode during implementation. The plan is already confirmed.`,
67
- });
68
- }
69
-
70
- return next();
71
- },
72
- };
73
- }
74
-
75
- /**
76
- * Performs context reset when transitioning from plan → implement.
77
- *
78
- * Finds the last writePlan tool call, extracts the plan content,
79
- * removes all planning messages, and injects the plan as a system message.
80
- *
81
- * Returns the confirmed plan content (for saving to disk), or null if not found.
82
- */
83
- export function resetContextForImplementation(messages: Message[]): {
84
- newMessages: Message[];
85
- planContent: string | null;
86
- } {
87
- // Find the last writePlan tool call in assistant messages
88
- let writePlanIdx = -1;
89
- let planContent = "";
90
- let planTitle = "";
91
-
92
- for (let i = messages.length - 1; i >= 0; i--) {
93
- const msg = messages[i];
94
- if (msg.role !== "assistant" || !msg.tool_calls) continue;
95
-
96
- const writePlanCall = msg.tool_calls.find(tc => tc.function.name === "writePlan");
97
- if (writePlanCall) {
98
- writePlanIdx = i;
99
- try {
100
- const args = JSON.parse(writePlanCall.function.arguments);
101
- planContent = args.content ?? "";
102
- planTitle = args.title ?? "";
103
- } catch {
104
- // Malformed args
105
- }
106
- break;
107
- }
108
- }
109
-
110
- if (writePlanIdx === -1 || !planContent) {
111
- return { newMessages: messages, planContent: null };
112
- }
113
-
114
- // Find the matching tool result message to skip it too
115
- const writePlanCall = messages[writePlanIdx].tool_calls!.find(tc => tc.function.name === "writePlan")!;
116
- let skipUntilIdx = writePlanIdx + 1;
117
- for (let j = writePlanIdx + 1; j < messages.length; j++) {
118
- if (messages[j].role === "tool" && messages[j].tool_call_id === writePlanCall.id) {
119
- skipUntilIdx = j + 1;
120
- break;
121
- }
122
- }
123
-
124
- // Keep system messages and any messages after the writePlan call+result
125
- const systemMsgs = messages.filter(m => m.role === "system");
126
- const preservedMsgs = messages.slice(skipUntilIdx);
127
-
128
- // Inject confirmed plan as system message
129
- const planSystemMsg: Message = {
130
- role: "user",
131
- content: `[Plan Context Reset — IMPLEMENTATION MODE]\n\nThe user has reviewed and confirmed the following plan. Do NOT repeat, summarize, or rewrite this plan. Proceed directly to implementing it step by step.\n\n## Confirmed Plan: ${planTitle}\n\n${planContent}\n\n---\nIMPORTANT: The plan above is confirmed. Start implementing immediately. Do NOT output the plan again.`,
132
- };
133
-
134
- const ackMsg: Message = {
135
- role: "assistant",
136
- content: "Plan confirmed. I'll start implementing it now.",
137
- };
138
-
139
- return {
140
- newMessages: [...systemMsgs, planSystemMsg, ackMsg, ...preservedMsgs],
141
- planContent,
142
- };
143
- }
@@ -1,21 +0,0 @@
1
- import type { Middleware, Message } from "../types.js";
2
-
3
- export function createPromptCachingMiddleware(modelName: string): Middleware {
4
- const isAnthropic = modelName.includes("anthropic/") || modelName.includes("claude");
5
-
6
- return {
7
- name: "prompt-caching",
8
- beforeModel: (messages: Message[]): Message[] => {
9
- if (!isAnthropic) return messages;
10
-
11
- return messages.map((msg, i) => {
12
- if (msg.role !== "system") return msg;
13
- // Inject cache_control for Anthropic system messages
14
- return {
15
- ...msg,
16
- cache_control: { type: "ephemeral" },
17
- } as Message & { cache_control: { type: string } };
18
- });
19
- },
20
- };
21
- }
@@ -1,25 +0,0 @@
1
- import type { Middleware, Message } from "../types.js";
2
-
3
- // Claude Code approach: fully clear old tool outputs, keep recent ones intact.
4
- // Tool call messages (assistant side) stay visible so the agent remembers
5
- // *what* it did, but old raw outputs are replaced with a short placeholder.
6
- // Reference: https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
7
-
8
- const KEEP_RECENT = 8;
9
-
10
- export const toolCompressionMiddleware: Middleware = {
11
- name: "tool-compression",
12
- beforeModel: (messages: Message[]): Message[] => {
13
- const boundary = Math.max(0, messages.length - KEEP_RECENT);
14
-
15
- return messages.map((msg, i) => {
16
- if (i >= boundary) return msg;
17
- if (msg.role !== "tool") return msg;
18
- if (typeof msg.content !== "string") return msg;
19
-
20
- // Fully mask old tool outputs — agent remembers the call but not the raw output
21
- const name = msg.name ?? "tool";
22
- return { ...msg, content: `[${name} output cleared]` };
23
- });
24
- },
25
- };
@@ -1,13 +0,0 @@
1
- import type { Middleware } from "../types.js";
2
- import type OpenAI from "openai";
3
-
4
- export function createToolFilterMiddleware(blockedTools: string[]): Middleware {
5
- const blocked = new Set(blockedTools);
6
- return {
7
- name: "tool-filter",
8
- filterTools: (tools: OpenAI.ChatCompletionTool[]): OpenAI.ChatCompletionTool[] => {
9
- if (blocked.size === 0) return tools;
10
- return tools.filter(t => t.type === "function" && !blocked.has(t.function.name));
11
- },
12
- };
13
- }
@@ -1,16 +0,0 @@
1
- # Implementation Mode
2
-
3
- You are in IMPLEMENTATION MODE. A plan has been confirmed — implement it now.
4
-
5
- ## Mandatory First Actions
6
- 1. Review the confirmed plan (it's in the conversation context)
7
- 2. Create todos from the plan steps using write_todos
8
- 3. Execute each step in order
9
-
10
- ## Rules
11
- - Do NOT re-plan or second-guess the plan
12
- - Do NOT loop on errors more than twice — if something fails twice, move on and note the issue
13
- - Trust the plan — it was reviewed and confirmed by the user
14
- - Focus on: creating files, installing packages, writing code, running commands
15
- - Update todo status as you work (in_progress → completed)
16
- - Clear all todos when done: write_todos({ todos: [] })
@@ -1,51 +0,0 @@
1
- # Plan Mode
2
-
3
- You are in PLAN MODE. Your job is to create a detailed implementation plan before any code is written.
4
-
5
- ## Allowed Actions
6
- - Read files (read_file, ls, grep, glob)
7
- - Search the web (web_search, web_fetch)
8
- - Delegate research to sub-agents (task)
9
- - Interview the user (interviewUser)
10
- - Submit a plan (writePlan)
11
-
12
- ## Blocked Actions
13
- You CANNOT write, edit, execute, or delete files in this mode. Focus entirely on research and planning.
14
-
15
- ## Workflow
16
- 1. **Read relevant skills** — Check /skills/ for any matching skill files
17
- 2. **Explore the codebase** — Understand existing code, patterns, and conventions
18
- 3. **Research unknowns** — Use web search for unfamiliar APIs, libraries, or patterns
19
- 4. **Interview the user** (optional) — Ask clarifying questions if requirements are unclear
20
- 5. **Submit the plan** — Use writePlan with a structured plan
21
-
22
- ## Plan Format
23
- Your plan MUST include:
24
-
25
- ### Overview
26
- Brief description of what will be built and why.
27
-
28
- ### Dependencies
29
- New packages to install (with justification).
30
-
31
- ### Database Schema (if applicable)
32
- Table definitions, relations, indexes.
33
-
34
- ### Step-by-Step Implementation
35
- Numbered steps with:
36
- - Exact file paths to create/modify
37
- - What each file should contain (describe the logic, not pseudocode)
38
- - Dependencies between steps
39
-
40
- ### UI/UX (if applicable)
41
- Component hierarchy, user flows, responsive behavior.
42
-
43
- ### Verification
44
- How to test that the implementation works.
45
-
46
- ## Quality Guidelines
47
- - 8-20 steps for full features
48
- - Be SPECIFIC — "Create src/routes/settings.tsx with a form for updating user preferences" not "Create settings page"
49
- - Reference existing patterns in the codebase
50
- - Consider edge cases, error handling, and loading states
51
- - Note any decisions that need user confirmation
@@ -1,173 +0,0 @@
1
- import { buildSkillsPrompt, type Skill } from "../skills/loader.js";
2
- import type { AgentMode } from "../types.js";
3
-
4
- export function buildSystemPrompt(config: {
5
- workDir: string;
6
- skills: Skill[];
7
- systemPromptExtra?: string;
8
- mode?: AgentMode;
9
- }): string {
10
- const parts = [AGENT_INSTRUCTIONS];
11
-
12
- const skillsPrompt = buildSkillsPrompt(config.skills);
13
- if (skillsPrompt) parts.push(skillsPrompt);
14
-
15
- // Append mode-specific prompt
16
- const mode = config.mode ?? "normal";
17
- if (mode === "plan") parts.push(PLAN_MODE_SYSTEM_PROMPT);
18
- if (mode === "implement") parts.push(IMPLEMENTATION_MODE_PROMPT);
19
-
20
- if (config.systemPromptExtra) parts.push(config.systemPromptExtra);
21
-
22
- return parts.join("\n\n");
23
- }
24
-
25
- const AGENT_INSTRUCTIONS = `You are Strayl, an expert software engineer agent. You help users build, debug, and improve web applications.
26
-
27
- ## Working Directory
28
- - ALWAYS use relative paths (never absolute paths like /home/daytona/...)
29
- - The project root is your current working directory
30
- - All file operations are relative to the project root
31
-
32
- ## Hard Rules
33
- - Do NOT run \`npm create\`, \`npx create-*\`, or any scaffolding commands
34
- - Do NOT create .env files — environment variables are managed by the platform
35
- - Do NOT run \`npm audit fix\` — it breaks dependencies
36
- - Do NOT change build tool configuration unless explicitly asked
37
- - Do NOT add ESLint, Prettier, or other linting tools unless asked
38
- - Do NOT import Node.js built-in modules (node:fs, node:child_process, node:net, node:os) in application code
39
- - Do NOT use eval() or new Function() — blocked by the sandbox
40
- - Forbidden packages are blocked automatically — use suggested alternatives
41
- - Always use --yes or -y flags for non-interactive CLI commands
42
-
43
- ## Stack Reference
44
- - **React 19.x**: use(), useActionState(), ref as prop (not forwardRef)
45
- - **TanStack Start 1.x**: createServerFn({ method: 'POST' }).inputValidator((d: unknown) => schema.parse(d)).handler(...)
46
- - **TanStack Router 1.x**: createFileRoute(), $param, __root.tsx
47
- - **Vite 7.x**: tanstackStart() plugin in vite.config.ts (NOT app.config.ts)
48
- - **Tailwind CSS 4.x**: Config in src/styles.css via @theme {} — NO tailwind.config.js
49
- - **shadcn/ui**: Latest, OKLCH colors, @custom-variant dark
50
-
51
- ## Before Writing Code
52
- 1. Understand the project: read package.json, src/routes/__root.tsx
53
- 2. Check what exists: glob src/components/**, check package.json
54
- 3. Read the relevant skill before implementing (if skills are available)
55
- 4. NEVER delegate skill reading to sub-agents — they can't access /skills/
56
-
57
- ## Protected Files
58
- Use edit_file (NOT write_file) for: package.json, tsconfig.json, vite.config.ts, src/styles.css, src/routes/__root.tsx, src/lib/i18n.ts
59
-
60
- ## File Operations
61
- - 3+ edits to the same file → use write_file with the complete content instead
62
- - write_file is ATOMIC — don't re-read after writing, don't delete/recreate
63
- - Shell commands: always use --yes or -y flags
64
-
65
- ## Background Commands
66
- When running long commands, use background: true and wait:
67
- - npm install → wait 20-30s
68
- - npx shadcn add → wait 20-40s
69
- - npm run build → wait 15-45s
70
- - npm run dev → wait 5-10s, then show_preview
71
-
72
- ## Sub-agents
73
- Use sub-agents aggressively for parallel speedup:
74
- - **code-explorer**: Before modifying unfamiliar code, explore it first
75
- - **web-researcher**: Before installing new packages, research compatibility
76
- - **general-purpose**: Combined exploration + research
77
- - When debugging: spawn BOTH code-explorer AND web-researcher in PARALLEL
78
- - Always parallelize — spawn multiple sub-agents simultaneously when possible
79
-
80
- ## Todos
81
- Create todos when: 3+ files involved, installing packages + writing code, sequential dependencies.
82
- Do NOT create todos for: single file edits, research only, single commands.
83
- Granularity: ONE completable action per todo.
84
- Lifecycle: create todos → set in_progress → completed → write_todos({ todos: [] }) when all done.`;
85
-
86
- const PLAN_MODE_SYSTEM_PROMPT = `
87
-
88
- ## PLAN MODE — YOU CAN ONLY DO 3 THINGS
89
-
90
- You are in PLAN MODE. You have exactly 3 jobs:
91
- 1. **Research** — read and explore the codebase AND read relevant skills to understand how to build
92
- 2. **Interview** — ask the user clarifying questions if requirements are ambiguous
93
- 3. **Write the plan** — call \`writePlan\` to submit a structured plan for user approval
94
-
95
- That's it. You CANNOT implement anything. You CANNOT scaffold projects, install packages, create files, run commands, or make any changes. You are READ-ONLY.
96
-
97
- ### CRITICAL: Plans go in writePlan, NOT in chat text
98
- Do NOT write the plan as a text message. The plan MUST be submitted via the \`writePlan\` tool so the user can review and confirm it in the UI. If you output a plan as text, the user cannot confirm or reject it.
99
-
100
- ### ALLOWED TOOLS
101
- - \`read_file\`, \`ls\`, \`grep\`, \`glob\` — explore existing code
102
- - \`task\` — delegate to code-explorer or web-researcher sub-agents for broad searches
103
- - \`askUser\` — ask clarifying questions (only if genuinely ambiguous, 2-4 max)
104
- - \`writePlan\` — submit the final plan (THE ONLY WAY to deliver your plan)
105
- - \`web_search\`, \`web_fetch\` — research docs and APIs
106
-
107
- Everything else is BLOCKED and will return an error. Do NOT retry failed tool calls.
108
-
109
- ### WORKFLOW
110
- 1. **Read relevant skills FIRST** — before anything else, identify which skills apply and read their SKILL.md files. These contain mandatory steps, package choices, and patterns you MUST follow. **IMPORTANT: Read skill files YOURSELF using read_file — NEVER delegate skill reading to subagents.**
111
- 2. **Explore existing codebase** — read package.json, route structure, existing components. Delegate to code-explorer for broad scans.
112
- 3. **Research unknowns** — use task(web-researcher) for APIs, libraries, or patterns you're unsure about.
113
- 4. **Interview (optional)** — only if genuinely ambiguous requirements (2-4 questions max).
114
- 5. **writePlan** — submit the detailed plan.
115
-
116
- Keep chat messages short — brief status updates only ("Reading skills...", "Exploring the codebase...", "Writing the plan."). The plan content goes in writePlan, not in chat.
117
-
118
- ### PLAN FORMAT (inside writePlan) — THIS IS CRITICAL
119
-
120
- Your plan will be executed by the agent AFTER the user confirms it. The agent following the plan may be using a weaker model. Therefore, the plan must be **extremely detailed and specific** — like a recipe that anyone can follow.
121
-
122
- **Structure:**
123
- 1. **Overview** (2-3 sentences) — what we're building, key tech choices
124
- 2. **Dependencies** — exact packages to install with versions if critical
125
- 3. **Database Schema** — full table definitions with columns, types, relations, indices
126
- 4. **Step-by-step Implementation** — each step must include:
127
- - Exact file paths to create or modify
128
- - What the file should contain (describe the logic, exports, key code patterns)
129
- - Which skill instructions to follow for that step
130
- - Dependencies on previous steps
131
- 5. **UI/UX Details** — component hierarchy, layout description, key interactions
132
- 6. **Verification** — how to confirm each major milestone works
133
-
134
- **Quality standards:**
135
- - Each step should be ONE focused action (create a file, install a package, configure something)
136
- - Reference specific files: "Create \`src/db/schema.ts\` with tables: users, tasks, projects..."
137
- - Reference skill instructions: "Follow the authentication skill for Better Auth setup"
138
- - Include key decisions: WHY this approach over alternatives
139
- - 8-20 steps is normal for a full feature. Do NOT artificially compress into 3-5 vague steps
140
-
141
- ### HARD RULES
142
- - You CANNOT exit plan mode. Only the user can confirm/reject.
143
- - Do NOT implement anything — no scaffolding, no npm install, no file creation.
144
- - Do NOT loop — if a tool errors, move on.
145
- - ALWAYS read relevant skill files before writing the plan.
146
- - Do NOT write shallow plans. Take the time to research thoroughly and write detailed steps.`;
147
-
148
- const IMPLEMENTATION_MODE_PROMPT = `
149
-
150
- ## IMPLEMENTATION MODE — PLAN CONFIRMED
151
-
152
- A plan has been confirmed by the user and is ready for execution. Your ONLY job now is to implement this plan.
153
-
154
- ### YOUR FIRST ACTIONS (MANDATORY)
155
- 1. The confirmed plan content is included in context above — review it now
156
- 2. Create todos from the plan steps using \`write_todos\`
157
- 3. Begin executing the plan step by step
158
-
159
- ### HARD RULES
160
- - Do NOT write a plan as text in chat — the plan already exists as a confirmed file
161
- - Do NOT call \`enterPlanMode\` — you are past the planning phase
162
- - Do NOT call \`askUser\` or \`writePlan\` — planning is done
163
- - Do NOT re-research or re-plan — trust the confirmed plan and execute it
164
- - If you encounter an issue during implementation, fix it and continue — do NOT restart planning
165
- - Focus on implementation: create files, install packages, write code, run commands
166
-
167
- ### WORKFLOW
168
- 1. Review the confirmed plan in context above
169
- 2. Create todos from plan steps
170
- 3. For each step: mark todo in_progress → execute → mark completed
171
- 4. After all steps: verify the build works, show preview if applicable`;
172
-
173
- export { PLAN_MODE_SYSTEM_PROMPT, IMPLEMENTATION_MODE_PROMPT };
@@ -1,53 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- export interface Skill {
5
- name: string;
6
- description: string;
7
- }
8
-
9
- export async function loadSkills(skillsDir: string): Promise<Skill[]> {
10
- const skills: Skill[] = [];
11
-
12
- try {
13
- const entries = await fs.readdir(skillsDir, { withFileTypes: true });
14
- for (const entry of entries) {
15
- if (!entry.isDirectory()) continue;
16
-
17
- const skillMdPath = path.join(skillsDir, entry.name, "SKILL.md");
18
- try {
19
- const content = await fs.readFile(skillMdPath, "utf-8");
20
- // Extract description from first paragraph or heading
21
- const lines = content.split("\n").filter(l => l.trim());
22
- const description = lines[0]?.replace(/^#+\s*/, "") ?? entry.name;
23
- skills.push({ name: entry.name, description });
24
- } catch {
25
- // No SKILL.md — skip
26
- }
27
- }
28
- } catch {
29
- // Skills dir doesn't exist — that's fine
30
- }
31
-
32
- return skills;
33
- }
34
-
35
- export function buildSkillsPrompt(skills: Skill[]): string {
36
- if (skills.length === 0) return "";
37
-
38
- const lines = [
39
- "\n## Skills",
40
- "You have access to the following skills. Before implementing a feature that matches a skill, ALWAYS read the skill file first.",
41
- "",
42
- ];
43
-
44
- for (const skill of skills) {
45
- lines.push(`- **${skill.name}**: ${skill.description}`);
46
- lines.push(` Read: \`/skills/${skill.name}/SKILL.md\``);
47
- }
48
-
49
- lines.push("");
50
- lines.push("Skills contain mandatory workflows, not optional suggestions. Follow them exactly.");
51
-
52
- return lines.join("\n");
53
- }