prjct-cli 0.41.0 → 0.44.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 (38) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/bin/prjct.ts +26 -0
  3. package/core/agentic/command-executor.ts +15 -5
  4. package/core/ai-tools/formatters.ts +302 -0
  5. package/core/ai-tools/generator.ts +124 -0
  6. package/core/ai-tools/index.ts +15 -0
  7. package/core/ai-tools/registry.ts +195 -0
  8. package/core/cli/linear.ts +440 -0
  9. package/core/commands/analysis.ts +36 -2
  10. package/core/commands/commands.ts +2 -2
  11. package/core/commands/planning.ts +8 -4
  12. package/core/commands/shipping.ts +8 -6
  13. package/core/commands/workflow.ts +67 -17
  14. package/core/index.ts +3 -1
  15. package/core/integrations/issue-tracker/types.ts +7 -1
  16. package/core/integrations/linear/client.ts +56 -24
  17. package/core/integrations/linear/index.ts +3 -0
  18. package/core/integrations/linear/sync.ts +313 -0
  19. package/core/schemas/index.ts +3 -0
  20. package/core/schemas/issues.ts +144 -0
  21. package/core/schemas/state.ts +3 -0
  22. package/core/services/sync-service.ts +71 -4
  23. package/core/utils/agent-stream.ts +138 -0
  24. package/core/utils/next-steps.ts +95 -0
  25. package/core/utils/output.ts +26 -0
  26. package/core/utils/project-credentials.ts +148 -0
  27. package/core/workflow/index.ts +6 -0
  28. package/core/workflow/state-machine.ts +185 -0
  29. package/dist/bin/prjct.mjs +2399 -540
  30. package/dist/core/infrastructure/setup.js +238 -192
  31. package/package.json +1 -1
  32. package/templates/_bases/tracker-base.md +11 -0
  33. package/templates/commands/done.md +18 -13
  34. package/templates/commands/enrich.md +152 -18
  35. package/templates/commands/linear.md +169 -135
  36. package/templates/commands/sync.md +17 -0
  37. package/templates/commands/task.md +20 -11
  38. package/templates/global/CLAUDE.md +58 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,120 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.44.0] - 2026-01-29
4
+
5
+ ### Feature: Workflow State Machine (PRJ-139)
6
+
7
+ Explicit states with valid transitions for prjct workflow. Users always know what commands are available.
8
+
9
+ **States:** `idle` → `working` → `completed` → `shipped` (with `paused` branch)
10
+
11
+ **New: State Machine (`core/workflow/state-machine.ts`)**
12
+ - Explicit state definitions with valid transitions
13
+ - State info shown after each command (e.g., `📍 State: WORKING`)
14
+ - Next steps derived from current state
15
+
16
+ **Updated Commands**
17
+ - `task` — Shows state: WORKING after starting
18
+ - `done` — Shows state: COMPLETED
19
+ - `pause` — Shows state: PAUSED
20
+ - `resume` — Shows state: WORKING
21
+
22
+ ### Feature: Auto-sync to Linear (PRJ-142 completion)
23
+
24
+ When starting/completing tasks with Linear IDs, prjct now auto-syncs status.
25
+
26
+ - `p. task PRJ-123` → Fetches issue title, marks as In Progress in Linear
27
+ - `p. done` → Marks issue as Done in Linear (if task has linearId)
28
+
29
+ ### Feature: Sync Summary (PRJ-119)
30
+
31
+ After `p. sync`, shows a compact summary with key metrics:
32
+ - Stack detected
33
+ - Files analyzed → context files generated
34
+ - Agents count
35
+ - Time elapsed
36
+
37
+ ### Improvement: Linear CLI filter by team
38
+
39
+ `list` command now uses configured team, showing only relevant issues.
40
+
41
+ ### Improvement: Clean Terminal UX guidelines
42
+
43
+ Added guidelines to CLAUDE.md for user-friendly tool call descriptions.
44
+
45
+ ## [0.43.0] - 2026-01-29
46
+
47
+ ### Feature: Bidirectional Sync Linear ↔ prjct (PRJ-142)
48
+
49
+ When `integrations.linear.enabled === true`, prjct now syncs bidirectionally with Linear, using a local cache for offline access and reduced API calls.
50
+
51
+ **New: Local Issue Cache (`storage/issues.json`)**
52
+ - Full copy of assigned Linear issues stored locally
53
+ - 30-minute staleness threshold for automatic refresh
54
+ - Local-first reads: no API call if issue is cached and fresh
55
+
56
+ **New: Sync Layer (`core/integrations/linear/sync.ts`)**
57
+ - `pullAll()` — Fetch all assigned issues to local cache
58
+ - `getIssue()` — Local-first fetch with API fallback
59
+ - `getIssueLocal()` — Local-only fetch (no API call)
60
+ - `pushStatus()` — Push status changes to Linear + update cache
61
+ - `isStale()` — Check if cache needs refresh
62
+
63
+ **New CLI Commands**
64
+ - `sync` — Pull all assigned issues to local storage
65
+ - `sync-status` — Check local cache status (hasCache, issueCount, isStale)
66
+ - `get-local <id>` — Get issue from local cache without API call
67
+
68
+ **Updated State Schema**
69
+ - `currentTask.linearId` — Linear identifier (e.g., "PRJ-123")
70
+ - `currentTask.linearUuid` — Linear internal UUID for API calls
71
+
72
+ **Updated Templates**
73
+ - `sync.md` — Syncs Linear issues when enabled
74
+ - `task.md` — Uses local-first approach, tracks linearId/linearUuid
75
+ - `done.md` — Pushes status to Linear via sync layer
76
+
77
+ **New Files:**
78
+ - `core/integrations/linear/sync.ts` — Sync layer implementation
79
+ - `core/schemas/issues.ts` — Zod schema for issues.json
80
+
81
+ ## [0.42.0] - 2026-01-29
82
+
83
+ ### Feature: Linear SDK Integration with Per-Project Credentials
84
+
85
+ Linear integration now uses the native `@linear/sdk` with per-project credential storage, enabling different projects to use different Linear workspaces.
86
+
87
+ **New: Per-Project Credentials**
88
+ - Credentials stored at `~/.prjct-cli/projects/{projectId}/config/credentials.json`
89
+ - Fallback chain: project credentials → macOS keychain → environment variable
90
+ - Each project can connect to a different Linear workspace
91
+
92
+ **New: CLI Bridge (`core/cli/linear.ts`)**
93
+ - Direct access to Linear SDK from templates
94
+ - Commands: `setup`, `list`, `get`, `create`, `update`, `start`, `done`, `comment`, `teams`, `projects`, `status`
95
+ - JSON output for easy parsing by Claude
96
+
97
+ **New: `prjct linear <cmd>` Subcommand**
98
+ - Direct CLI access: `prjct linear status`, `prjct linear list`, etc.
99
+ - Auto-resolves project ID from current directory
100
+
101
+ **Updated Templates**
102
+ - `linear.md` — Natural language interpretation guide for Claude
103
+ - `enrich.md` — Uses CLI instead of MCP for ticket enrichment
104
+
105
+ **Breaking: Removed MCP-only Trackers**
106
+ - Removed JIRA, GitHub Issues, Monday.com support (no native SDK)
107
+ - Only Linear is supported (has native SDK)
108
+
109
+ **New Files:**
110
+ - `core/cli/linear.ts` — CLI bridge for Linear SDK
111
+ - `core/utils/project-credentials.ts` — Per-project credential storage
112
+
113
+ **Modified:**
114
+ - `bin/prjct.ts` — Added `prjct linear` subcommand
115
+ - `templates/commands/linear.md` — Updated with CLI execution pattern
116
+ - `templates/commands/enrich.md` — Linear-only, uses CLI
117
+
3
118
  ## [0.40.0] - 2026-01-28
4
119
 
5
120
  ### Feature: Enhanced Skill System
package/bin/prjct.ts CHANGED
@@ -85,6 +85,32 @@ if (args[0] === 'start' || args[0] === 'setup') {
85
85
  console.error('Server error:', (error as Error).message)
86
86
  process.exitCode = 1
87
87
  }
88
+ } else if (args[0] === 'linear') {
89
+ // Linear CLI subcommand - direct access to Linear SDK
90
+ const { spawn } = await import('child_process')
91
+ const projectPath = process.cwd()
92
+ const projectId = await configManager.getProjectId(projectPath)
93
+
94
+ if (!projectId) {
95
+ console.error('No prjct project found. Run "prjct init" first.')
96
+ process.exitCode = 1
97
+ } else {
98
+ // Get the path to the linear CLI
99
+ const linearCliPath = path.join(__dirname, '..', 'core', 'cli', 'linear.ts')
100
+
101
+ // Forward args to linear CLI, adding --project flag
102
+ const linearArgs = ['--project', projectId, ...args.slice(1)]
103
+
104
+ // Use bun to run the CLI
105
+ const child = spawn('bun', [linearCliPath, ...linearArgs], {
106
+ stdio: 'inherit',
107
+ cwd: projectPath,
108
+ })
109
+
110
+ child.on('close', (code) => {
111
+ process.exitCode = code || 0
112
+ })
113
+ }
88
114
  } else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
89
115
  // Show version with provider status
90
116
  const detection = detectAllProviders()
@@ -20,6 +20,7 @@ import groundTruth from './ground-truth'
20
20
  import planMode from './plan-mode'
21
21
  import templateExecutor from './template-executor'
22
22
  import orchestratorExecutor from './orchestrator-executor'
23
+ import { agentStream } from '../utils/agent-stream'
23
24
 
24
25
  import type {
25
26
  OrchestratorContext,
@@ -183,12 +184,21 @@ export class CommandExecutor {
183
184
  projectPath
184
185
  )
185
186
 
186
- // Log orchestrator results
187
- console.log(`🎯 Orchestrator:`)
188
- console.log(` → Domains: ${orchestratorContext.detectedDomains.join(', ')}`)
189
- console.log(` → Agents: ${orchestratorContext.agents.map(a => a.name).join(', ') || 'none loaded'}`)
187
+ // Show orchestration with agent stream
188
+ if (orchestratorContext.detectedDomains.length > 0) {
189
+ agentStream.orchestrate(orchestratorContext.detectedDomains)
190
+ }
191
+
192
+ // Show each agent being activated
193
+ for (const agent of orchestratorContext.agents) {
194
+ const domain = agent.domain || agent.name.replace('.md', '')
195
+ agentStream.startAgent(agent.name, domain, `Loading ${domain} specialist...`)
196
+ agentStream.endAgent(true)
197
+ }
198
+
199
+ // Show subtasks if fragmented
190
200
  if (orchestratorContext.requiresFragmentation && orchestratorContext.subtasks) {
191
- console.log(` → Subtasks: ${orchestratorContext.subtasks.length}`)
201
+ agentStream.status('📋', `${orchestratorContext.subtasks.length} subtasks planned`)
192
202
  }
193
203
  } catch (error) {
194
204
  // Orchestration failed - log warning but continue without it
@@ -0,0 +1,302 @@
1
+ /**
2
+ * AI Tool Formatters
3
+ *
4
+ * Each AI tool has different context preferences:
5
+ * - Claude Code: Detailed markdown with sections
6
+ * - Cursor: Concise rules format
7
+ * - Copilot: Minimal bullet points
8
+ * - Windsurf: Similar to Cursor
9
+ */
10
+
11
+ import type { AIToolConfig } from './registry'
12
+
13
+ export interface ProjectContext {
14
+ projectId: string
15
+ name: string
16
+ version: string
17
+ ecosystem: string
18
+ projectType: string
19
+ languages: string[]
20
+ frameworks: string[]
21
+ repoPath: string
22
+ branch: string
23
+ fileCount: number
24
+ commits: number
25
+ hasChanges: boolean
26
+ commands: {
27
+ install: string
28
+ dev: string
29
+ test: string
30
+ build: string
31
+ lint: string
32
+ format: string
33
+ }
34
+ agents: {
35
+ workflow: string[]
36
+ domain: string[]
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Format context for Claude Code (CLAUDE.md)
42
+ * Detailed markdown with full context
43
+ */
44
+ export function formatForClaude(ctx: ProjectContext, config: AIToolConfig): string {
45
+ return `# ${ctx.name} - Project Rules
46
+ <!-- projectId: ${ctx.projectId} -->
47
+ <!-- Generated: ${new Date().toISOString()} -->
48
+ <!-- Ecosystem: ${ctx.ecosystem} | Type: ${ctx.projectType} -->
49
+
50
+ ## THIS PROJECT (${ctx.ecosystem})
51
+
52
+ **Type:** ${ctx.projectType}
53
+ **Path:** ${ctx.repoPath}
54
+
55
+ ### Commands (USE THESE, NOT OTHERS)
56
+
57
+ | Action | Command |
58
+ |--------|---------|
59
+ | Install dependencies | \`${ctx.commands.install}\` |
60
+ | Run dev server | \`${ctx.commands.dev}\` |
61
+ | Run tests | \`${ctx.commands.test}\` |
62
+ | Build | \`${ctx.commands.build}\` |
63
+ | Lint | \`${ctx.commands.lint}\` |
64
+ | Format | \`${ctx.commands.format}\` |
65
+
66
+ ### Code Conventions
67
+
68
+ - **Languages**: ${ctx.languages.join(', ') || 'Not detected'}
69
+ - **Frameworks**: ${ctx.frameworks.join(', ') || 'Not detected'}
70
+
71
+ ---
72
+
73
+ ## PRJCT RULES
74
+
75
+ ### Path Resolution
76
+ **ALL prjct writes go to**: \`~/.prjct-cli/projects/${ctx.projectId}/\`
77
+ - NEVER write to \`.prjct/\`
78
+ - NEVER write to \`./\` for prjct data
79
+
80
+ ### Workflow
81
+ \`\`\`
82
+ p. sync → p. task "desc" → [work] → p. done → p. ship
83
+ \`\`\`
84
+
85
+ | Command | Action |
86
+ |---------|--------|
87
+ | \`p. sync\` | Re-analyze project |
88
+ | \`p. task X\` | Start task |
89
+ | \`p. done\` | Complete subtask |
90
+ | \`p. ship X\` | Ship feature |
91
+
92
+ ---
93
+
94
+ ## PROJECT STATE
95
+
96
+ | Field | Value |
97
+ |-------|-------|
98
+ | Name | ${ctx.name} |
99
+ | Version | ${ctx.version} |
100
+ | Ecosystem | ${ctx.ecosystem} |
101
+ | Branch | ${ctx.branch} |
102
+ | Files | ~${ctx.fileCount} |
103
+ | Commits | ${ctx.commits} |
104
+
105
+ ---
106
+
107
+ ## AGENTS
108
+
109
+ Load from \`~/.prjct-cli/projects/${ctx.projectId}/agents/\`:
110
+
111
+ **Workflow**: ${ctx.agents.workflow.join(', ')}
112
+ **Domain**: ${ctx.agents.domain.join(', ') || 'none'}
113
+ `
114
+ }
115
+
116
+ /**
117
+ * Format context for Cursor (.cursorrules)
118
+ * Concise rules format, optimized for inline suggestions
119
+ */
120
+ export function formatForCursor(ctx: ProjectContext, config: AIToolConfig): string {
121
+ const rules: string[] = []
122
+
123
+ // Project identity
124
+ rules.push(`You are working on ${ctx.name}, a ${ctx.projectType} ${ctx.ecosystem} project.`)
125
+ rules.push('')
126
+
127
+ // Tech stack
128
+ rules.push('## Tech Stack')
129
+ if (ctx.languages.length > 0) {
130
+ rules.push(`- Languages: ${ctx.languages.join(', ')}`)
131
+ }
132
+ if (ctx.frameworks.length > 0) {
133
+ rules.push(`- Frameworks: ${ctx.frameworks.join(', ')}`)
134
+ }
135
+ rules.push('')
136
+
137
+ // Commands
138
+ rules.push('## Commands')
139
+ rules.push(`- Install: \`${ctx.commands.install}\``)
140
+ rules.push(`- Dev: \`${ctx.commands.dev}\``)
141
+ rules.push(`- Test: \`${ctx.commands.test}\``)
142
+ rules.push(`- Build: \`${ctx.commands.build}\``)
143
+ rules.push('')
144
+
145
+ // Code style - language agnostic
146
+ rules.push('## Code Style')
147
+ rules.push(`- Follow ${ctx.ecosystem} conventions`)
148
+ rules.push('- Match existing code patterns in this project')
149
+ rules.push('- Use idiomatic constructs for the language')
150
+ rules.push('')
151
+
152
+ // Best practices
153
+ rules.push('## Best Practices')
154
+ rules.push('- Write clean, readable code')
155
+ rules.push('- Add comments only for complex logic')
156
+ rules.push('- Keep functions small and focused')
157
+ rules.push('- Handle errors appropriately')
158
+ rules.push('- Write tests for new functionality')
159
+
160
+ return rules.join('\n')
161
+ }
162
+
163
+ /**
164
+ * Format context for GitHub Copilot (.github/copilot-instructions.md)
165
+ * Minimal bullet points
166
+ */
167
+ export function formatForCopilot(ctx: ProjectContext, config: AIToolConfig): string {
168
+ const lines: string[] = []
169
+
170
+ lines.push('# Copilot Instructions')
171
+ lines.push('')
172
+ lines.push(`This is ${ctx.name}, a ${ctx.ecosystem} project.`)
173
+ lines.push('')
174
+
175
+ // Key info
176
+ lines.push('## Project Info')
177
+ lines.push(`- Type: ${ctx.projectType}`)
178
+ lines.push(`- Stack: ${ctx.frameworks.join(', ') || ctx.ecosystem}`)
179
+ lines.push('')
180
+
181
+ // Conventions
182
+ lines.push('## Conventions')
183
+ lines.push(`- Follow ${ctx.ecosystem} conventions`)
184
+ lines.push('- Match existing code patterns')
185
+ lines.push('- Keep code clean and readable')
186
+ lines.push('')
187
+
188
+ // Commands
189
+ lines.push('## Commands')
190
+ lines.push(`- Test: \`${ctx.commands.test}\``)
191
+ lines.push(`- Build: \`${ctx.commands.build}\``)
192
+
193
+ return lines.join('\n')
194
+ }
195
+
196
+ /**
197
+ * Format context for Windsurf (.windsurfrules)
198
+ * Optimized for Cascade AI with flow-based suggestions
199
+ */
200
+ export function formatForWindsurf(ctx: ProjectContext, config: AIToolConfig): string {
201
+ const rules: string[] = []
202
+
203
+ // Project identity
204
+ rules.push(`# ${ctx.name}`)
205
+ rules.push('')
206
+ rules.push(`${ctx.projectType} project using ${ctx.ecosystem}.`)
207
+ rules.push('')
208
+
209
+ // Tech stack (concise)
210
+ rules.push('## Stack')
211
+ rules.push(`- ${ctx.languages.join(', ')}`)
212
+ if (ctx.frameworks.length > 0) {
213
+ rules.push(`- ${ctx.frameworks.join(', ')}`)
214
+ }
215
+ rules.push('')
216
+
217
+ // Commands (essential only)
218
+ rules.push('## Commands')
219
+ rules.push(`\`\`\`bash`)
220
+ rules.push(`# Install`)
221
+ rules.push(ctx.commands.install)
222
+ rules.push(`# Dev`)
223
+ rules.push(ctx.commands.dev)
224
+ rules.push(`# Test`)
225
+ rules.push(ctx.commands.test)
226
+ rules.push(`# Build`)
227
+ rules.push(ctx.commands.build)
228
+ rules.push(`\`\`\``)
229
+ rules.push('')
230
+
231
+ // Code style - language agnostic
232
+ rules.push('## Rules')
233
+ rules.push(`- Follow ${ctx.ecosystem} conventions`)
234
+ rules.push('- Match existing project patterns')
235
+ rules.push('- Clean code, minimal comments')
236
+ rules.push('- Test new functionality')
237
+
238
+ return rules.join('\n')
239
+ }
240
+
241
+ /**
242
+ * Format context for Continue.dev (.continue/config.json)
243
+ * JSON config with system message and context providers
244
+ */
245
+ export function formatForContinue(ctx: ProjectContext, config: AIToolConfig): string {
246
+ const systemMessage = [
247
+ `You are working on ${ctx.name}, a ${ctx.projectType} ${ctx.ecosystem} project.`,
248
+ '',
249
+ `Stack: ${ctx.languages.join(', ')}${ctx.frameworks.length > 0 ? ` with ${ctx.frameworks.join(', ')}` : ''}`,
250
+ '',
251
+ 'Commands:',
252
+ `- Install: ${ctx.commands.install}`,
253
+ `- Dev: ${ctx.commands.dev}`,
254
+ `- Test: ${ctx.commands.test}`,
255
+ `- Build: ${ctx.commands.build}`,
256
+ '',
257
+ `Follow ${ctx.ecosystem} conventions. Match existing code patterns.`,
258
+ ].join('\n')
259
+
260
+ const continueConfig = {
261
+ systemMessage,
262
+ models: [],
263
+ contextProviders: [
264
+ { name: 'code' },
265
+ { name: 'docs' },
266
+ { name: 'diff' },
267
+ { name: 'terminal' },
268
+ { name: 'problems' },
269
+ { name: 'folder' },
270
+ { name: 'codebase' },
271
+ ],
272
+ slashCommands: [
273
+ { name: 'edit', description: 'Edit selected code' },
274
+ { name: 'comment', description: 'Add comments to code' },
275
+ { name: 'share', description: 'Export conversation' },
276
+ { name: 'cmd', description: 'Run terminal command' },
277
+ ],
278
+ customCommands: [
279
+ {
280
+ name: 'test',
281
+ prompt: `Write tests for the selected code. Use the project's testing conventions. Test command: ${ctx.commands.test}`,
282
+ },
283
+ ],
284
+ }
285
+
286
+ return JSON.stringify(continueConfig, null, 2)
287
+ }
288
+
289
+ /**
290
+ * Get formatter function for a tool
291
+ */
292
+ export function getFormatter(toolId: string): ((ctx: ProjectContext, config: AIToolConfig) => string) | null {
293
+ const formatters: Record<string, (ctx: ProjectContext, config: AIToolConfig) => string> = {
294
+ claude: formatForClaude,
295
+ cursor: formatForCursor,
296
+ copilot: formatForCopilot,
297
+ windsurf: formatForWindsurf,
298
+ continue: formatForContinue,
299
+ }
300
+
301
+ return formatters[toolId] || null
302
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * AI Tools Context Generator
3
+ *
4
+ * Generates optimized context files for each AI coding tool.
5
+ * Each tool gets context in its preferred format.
6
+ */
7
+
8
+ import fs from 'fs/promises'
9
+ import path from 'path'
10
+ import { AI_TOOLS, DEFAULT_AI_TOOLS, getAIToolConfig, type AIToolConfig } from './registry'
11
+ import { getFormatter, type ProjectContext } from './formatters'
12
+
13
+ export interface GenerateResult {
14
+ toolId: string
15
+ outputFile: string
16
+ outputPath: string
17
+ success: boolean
18
+ error?: string
19
+ }
20
+
21
+ /**
22
+ * Generate context files for specified AI tools
23
+ */
24
+ export async function generateAIToolContexts(
25
+ context: ProjectContext,
26
+ globalPath: string,
27
+ repoPath: string,
28
+ toolIds: string[] = DEFAULT_AI_TOOLS
29
+ ): Promise<GenerateResult[]> {
30
+ const results: GenerateResult[] = []
31
+
32
+ for (const toolId of toolIds) {
33
+ const config = getAIToolConfig(toolId)
34
+ if (!config) {
35
+ results.push({
36
+ toolId,
37
+ outputFile: '',
38
+ outputPath: '',
39
+ success: false,
40
+ error: `Unknown tool: ${toolId}`,
41
+ })
42
+ continue
43
+ }
44
+
45
+ const result = await generateForTool(context, config, globalPath, repoPath)
46
+ results.push(result)
47
+ }
48
+
49
+ return results
50
+ }
51
+
52
+ /**
53
+ * Generate context for a single AI tool
54
+ */
55
+ async function generateForTool(
56
+ context: ProjectContext,
57
+ config: AIToolConfig,
58
+ globalPath: string,
59
+ repoPath: string
60
+ ): Promise<GenerateResult> {
61
+ const formatter = getFormatter(config.id)
62
+ if (!formatter) {
63
+ return {
64
+ toolId: config.id,
65
+ outputFile: config.outputFile,
66
+ outputPath: '',
67
+ success: false,
68
+ error: `No formatter for: ${config.id}`,
69
+ }
70
+ }
71
+
72
+ try {
73
+ // Generate content
74
+ const content = formatter(context, config)
75
+
76
+ // Determine output path
77
+ let outputPath: string
78
+ if (config.outputPath === 'repo') {
79
+ outputPath = path.join(repoPath, config.outputFile)
80
+ } else {
81
+ outputPath = path.join(globalPath, 'context', config.outputFile)
82
+ }
83
+
84
+ // Ensure directory exists
85
+ await fs.mkdir(path.dirname(outputPath), { recursive: true })
86
+
87
+ // Write file
88
+ await fs.writeFile(outputPath, content, 'utf-8')
89
+
90
+ return {
91
+ toolId: config.id,
92
+ outputFile: config.outputFile,
93
+ outputPath,
94
+ success: true,
95
+ }
96
+ } catch (error) {
97
+ return {
98
+ toolId: config.id,
99
+ outputFile: config.outputFile,
100
+ outputPath: '',
101
+ success: false,
102
+ error: (error as Error).message,
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Get list of files that would be generated
109
+ */
110
+ export function getOutputFiles(
111
+ toolIds: string[] = DEFAULT_AI_TOOLS
112
+ ): { toolId: string; file: string; location: 'repo' | 'global' }[] {
113
+ return toolIds
114
+ .map(id => {
115
+ const config = AI_TOOLS[id]
116
+ if (!config) return null
117
+ return {
118
+ toolId: id,
119
+ file: config.outputFile,
120
+ location: config.outputPath,
121
+ }
122
+ })
123
+ .filter((item): item is NonNullable<typeof item> => item !== null)
124
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * AI Tools Module
3
+ *
4
+ * Multi-agent context generation for AI coding tools.
5
+ * Supports: Claude Code, Cursor, Copilot, Windsurf, Continue.dev
6
+ *
7
+ * Features:
8
+ * - Auto-detection of installed tools
9
+ * - Language-agnostic formatters
10
+ * - Tool-specific output formats
11
+ */
12
+
13
+ export * from './registry'
14
+ export * from './formatters'
15
+ export * from './generator'