prjct-cli 0.45.0 → 0.45.3
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/CHANGELOG.md +75 -0
- package/bin/prjct.ts +117 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +58 -39
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +28 -4
- package/core/commands/commands.ts +57 -24
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +13 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +18 -19
- package/core/context-tools/imports-tool.ts +13 -33
- package/core/context-tools/index.ts +29 -54
- package/core/context-tools/recent-tool.ts +16 -22
- package/core/context-tools/signatures-tool.ts +17 -26
- package/core/context-tools/summary-tool.ts +20 -22
- package/core/context-tools/token-counter.ts +25 -20
- package/core/context-tools/types.ts +5 -5
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -16
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +25 -25
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +87 -345
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -17
- package/core/storage/metrics-storage.ts +39 -34
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -305
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +14 -14
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18755 -15574
- package/dist/core/infrastructure/command-installer.js +86 -79
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +246 -225
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentGenerator - Domain and workflow agent generation
|
|
3
|
+
*
|
|
4
|
+
* Extracted from sync-service.ts for single responsibility.
|
|
5
|
+
* Generates agent markdown files based on project stack.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs/promises'
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
import type { StackDetection } from './stack-detector'
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// TYPES
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
export interface AgentInfo {
|
|
19
|
+
name: string
|
|
20
|
+
type: 'workflow' | 'domain'
|
|
21
|
+
skill?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ProjectStats {
|
|
25
|
+
fileCount: number
|
|
26
|
+
version: string
|
|
27
|
+
name: string
|
|
28
|
+
ecosystem: string
|
|
29
|
+
projectType: string
|
|
30
|
+
languages: string[]
|
|
31
|
+
frameworks: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// AGENT GENERATOR CLASS
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export class AgentGenerator {
|
|
39
|
+
private agentsPath: string
|
|
40
|
+
private templatesPath: string
|
|
41
|
+
|
|
42
|
+
constructor(agentsPath: string, templatesPath?: string) {
|
|
43
|
+
this.agentsPath = agentsPath
|
|
44
|
+
this.templatesPath = templatesPath || path.join(__dirname, '..', '..', 'templates', 'subagents')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate all agents based on stack detection
|
|
49
|
+
*/
|
|
50
|
+
async generate(stack: StackDetection, stats: ProjectStats): Promise<AgentInfo[]> {
|
|
51
|
+
const agents: AgentInfo[] = []
|
|
52
|
+
|
|
53
|
+
// Purge old agents
|
|
54
|
+
await this.purgeOldAgents()
|
|
55
|
+
|
|
56
|
+
// Workflow agents (always generated) - IN PARALLEL
|
|
57
|
+
const workflowAgents = await this.generateWorkflowAgents()
|
|
58
|
+
agents.push(...workflowAgents)
|
|
59
|
+
|
|
60
|
+
// Domain agents (based on stack) - IN PARALLEL
|
|
61
|
+
const domainAgents = await this.generateDomainAgents(stack, stats)
|
|
62
|
+
agents.push(...domainAgents)
|
|
63
|
+
|
|
64
|
+
return agents
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Remove existing agent files
|
|
69
|
+
*/
|
|
70
|
+
private async purgeOldAgents(): Promise<void> {
|
|
71
|
+
try {
|
|
72
|
+
const files = await fs.readdir(this.agentsPath)
|
|
73
|
+
await Promise.all(
|
|
74
|
+
files
|
|
75
|
+
.filter((file) => file.endsWith('.md'))
|
|
76
|
+
.map((file) => fs.unlink(path.join(this.agentsPath, file)))
|
|
77
|
+
)
|
|
78
|
+
} catch {
|
|
79
|
+
// Directory might not exist yet
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate workflow agents (always included)
|
|
85
|
+
*/
|
|
86
|
+
private async generateWorkflowAgents(): Promise<AgentInfo[]> {
|
|
87
|
+
const workflowAgentNames = ['prjct-workflow', 'prjct-planner', 'prjct-shipper']
|
|
88
|
+
|
|
89
|
+
await Promise.all(workflowAgentNames.map((name) => this.generateWorkflowAgent(name)))
|
|
90
|
+
|
|
91
|
+
return workflowAgentNames.map((name) => ({ name, type: 'workflow' as const }))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate domain agents based on stack
|
|
96
|
+
*/
|
|
97
|
+
private async generateDomainAgents(
|
|
98
|
+
stack: StackDetection,
|
|
99
|
+
stats: ProjectStats
|
|
100
|
+
): Promise<AgentInfo[]> {
|
|
101
|
+
const agentsToGenerate: { name: string; skill?: string }[] = []
|
|
102
|
+
|
|
103
|
+
if (stack.hasFrontend) {
|
|
104
|
+
agentsToGenerate.push({ name: 'frontend', skill: 'javascript-typescript' })
|
|
105
|
+
agentsToGenerate.push({ name: 'uxui', skill: 'frontend-design' })
|
|
106
|
+
}
|
|
107
|
+
if (stack.hasBackend) {
|
|
108
|
+
agentsToGenerate.push({ name: 'backend', skill: 'javascript-typescript' })
|
|
109
|
+
}
|
|
110
|
+
if (stack.hasDatabase) {
|
|
111
|
+
agentsToGenerate.push({ name: 'database' })
|
|
112
|
+
}
|
|
113
|
+
if (stack.hasTesting) {
|
|
114
|
+
agentsToGenerate.push({ name: 'testing', skill: 'developer-kit' })
|
|
115
|
+
}
|
|
116
|
+
if (stack.hasDocker) {
|
|
117
|
+
agentsToGenerate.push({ name: 'devops', skill: 'developer-kit' })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Generate all domain agents IN PARALLEL
|
|
121
|
+
await Promise.all(
|
|
122
|
+
agentsToGenerate.map((agent) => this.generateDomainAgent(agent.name, stats, stack))
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return agentsToGenerate.map((agent) => ({
|
|
126
|
+
name: agent.name,
|
|
127
|
+
type: 'domain' as const,
|
|
128
|
+
skill: agent.skill,
|
|
129
|
+
}))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate a single workflow agent
|
|
134
|
+
*/
|
|
135
|
+
private async generateWorkflowAgent(name: string): Promise<void> {
|
|
136
|
+
let content = ''
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const templatePath = path.join(this.templatesPath, 'workflow', `${name}.md`)
|
|
140
|
+
content = await fs.readFile(templatePath, 'utf-8')
|
|
141
|
+
} catch {
|
|
142
|
+
// Generate minimal agent
|
|
143
|
+
content = this.generateMinimalWorkflowAgent(name)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await fs.writeFile(path.join(this.agentsPath, `${name}.md`), content, 'utf-8')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate a single domain agent
|
|
151
|
+
*/
|
|
152
|
+
private async generateDomainAgent(
|
|
153
|
+
name: string,
|
|
154
|
+
stats: ProjectStats,
|
|
155
|
+
stack: StackDetection
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
let content = ''
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const templatePath = path.join(this.templatesPath, 'domain', `${name}.md`)
|
|
161
|
+
content = await fs.readFile(templatePath, 'utf-8')
|
|
162
|
+
|
|
163
|
+
// Inject project-specific context
|
|
164
|
+
content = content.replace('{projectName}', stats.name)
|
|
165
|
+
content = content.replace('{frameworks}', stack.frameworks.join(', ') || 'None detected')
|
|
166
|
+
content = content.replace('{ecosystem}', stats.ecosystem)
|
|
167
|
+
} catch {
|
|
168
|
+
// Generate minimal agent
|
|
169
|
+
content = this.generateMinimalDomainAgent(name, stats, stack)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await fs.writeFile(path.join(this.agentsPath, `${name}.md`), content, 'utf-8')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate minimal workflow agent content
|
|
177
|
+
*/
|
|
178
|
+
private generateMinimalWorkflowAgent(name: string): string {
|
|
179
|
+
const descriptions: Record<string, string> = {
|
|
180
|
+
'prjct-workflow': 'Task lifecycle: now, done, pause, resume',
|
|
181
|
+
'prjct-planner': 'Planning: task, prd, spec, bug',
|
|
182
|
+
'prjct-shipper': 'Shipping: ship, merge, review',
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return `---
|
|
186
|
+
name: ${name}
|
|
187
|
+
description: ${descriptions[name] || 'Workflow agent'}
|
|
188
|
+
tools: Read, Write, Glob
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
# ${name.toUpperCase()}
|
|
192
|
+
|
|
193
|
+
Workflow agent for prjct operations.
|
|
194
|
+
|
|
195
|
+
## Project Context
|
|
196
|
+
|
|
197
|
+
When invoked:
|
|
198
|
+
1. Read \`.prjct/prjct.config.json\` → extract \`projectId\`
|
|
199
|
+
2. Read \`~/.prjct-cli/projects/{projectId}/storage/state.json\`
|
|
200
|
+
3. Execute requested operation
|
|
201
|
+
`
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Generate minimal domain agent content
|
|
206
|
+
*/
|
|
207
|
+
private generateMinimalDomainAgent(
|
|
208
|
+
name: string,
|
|
209
|
+
stats: ProjectStats,
|
|
210
|
+
stack: StackDetection
|
|
211
|
+
): string {
|
|
212
|
+
return `---
|
|
213
|
+
name: ${name}
|
|
214
|
+
description: ${name.charAt(0).toUpperCase() + name.slice(1)} specialist for ${stats.name}
|
|
215
|
+
tools: Read, Write, Glob, Grep
|
|
216
|
+
skills: []
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
# ${name.toUpperCase()} AGENT
|
|
220
|
+
|
|
221
|
+
Domain specialist for ${name} tasks.
|
|
222
|
+
|
|
223
|
+
## Project Context
|
|
224
|
+
|
|
225
|
+
- **Project**: ${stats.name}
|
|
226
|
+
- **Ecosystem**: ${stats.ecosystem}
|
|
227
|
+
- **Frameworks**: ${stack.frameworks.join(', ') || 'None detected'}
|
|
228
|
+
|
|
229
|
+
## Your Role
|
|
230
|
+
|
|
231
|
+
You are the ${name} expert for this project. Apply best practices for the detected stack.
|
|
232
|
+
`
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const createAgentGenerator = (agentsPath: string) => new AgentGenerator(agentsPath)
|
|
237
|
+
export default AgentGenerator
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Handles agent detection, initialization, and routing tasks to appropriate agents.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import agentDetector from '../infrastructure/agent-detector'
|
|
8
7
|
import AgentRouter from '../agentic/agent-router'
|
|
9
|
-
import type { AgentInfo, AgentAssignmentResult, ProjectContext } from '../types'
|
|
10
8
|
import { AgentError } from '../errors'
|
|
9
|
+
import agentDetector from '../infrastructure/agent-detector'
|
|
10
|
+
import type { AgentAssignmentResult, AgentInfo, ProjectContext } from '../types'
|
|
11
11
|
|
|
12
12
|
// Valid agent types - whitelist for security (prevents path traversal)
|
|
13
13
|
const VALID_AGENT_TYPES = ['claude'] as const
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Handles task breakdown, severity detection, and complexity estimation.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { ComplexityEstimate, Severity } from '../types'
|
|
8
8
|
|
|
9
9
|
export class BreakdownService {
|
|
10
10
|
/**
|
|
@@ -77,9 +77,7 @@ export class BreakdownService {
|
|
|
77
77
|
'authorization',
|
|
78
78
|
]
|
|
79
79
|
|
|
80
|
-
const hasComplexKeyword = complexKeywords.some((kw) =>
|
|
81
|
-
description.toLowerCase().includes(kw)
|
|
82
|
-
)
|
|
80
|
+
const hasComplexKeyword = complexKeywords.some((kw) => description.toLowerCase().includes(kw))
|
|
83
81
|
|
|
84
82
|
if (hasComplexKeyword || wordCount > 30) {
|
|
85
83
|
return { level: 'high', hours: 8 }
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextFileGenerator - Generates markdown context files
|
|
3
|
+
*
|
|
4
|
+
* Responsible for generating:
|
|
5
|
+
* - CLAUDE.md (main context for AI agents)
|
|
6
|
+
* - now.md (current task)
|
|
7
|
+
* - next.md (task queue)
|
|
8
|
+
* - ideas.md (captured ideas)
|
|
9
|
+
* - shipped.md (completed features)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs/promises'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import dateHelper from '../utils/date-helper'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// TYPES
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export interface GitData {
|
|
21
|
+
branch: string
|
|
22
|
+
commits: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProjectStats {
|
|
26
|
+
name: string
|
|
27
|
+
version: string
|
|
28
|
+
ecosystem: string
|
|
29
|
+
projectType: string
|
|
30
|
+
fileCount: number
|
|
31
|
+
languages: string[]
|
|
32
|
+
frameworks: string[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Commands {
|
|
36
|
+
install: string
|
|
37
|
+
dev: string
|
|
38
|
+
test: string
|
|
39
|
+
build: string
|
|
40
|
+
lint: string
|
|
41
|
+
format: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AgentInfo {
|
|
45
|
+
name: string
|
|
46
|
+
type: 'workflow' | 'domain'
|
|
47
|
+
skill?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ContextGeneratorConfig {
|
|
51
|
+
projectId: string
|
|
52
|
+
projectPath: string
|
|
53
|
+
globalPath: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// CONTEXT FILE GENERATOR
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
export class ContextFileGenerator {
|
|
61
|
+
private config: ContextGeneratorConfig
|
|
62
|
+
|
|
63
|
+
constructor(config: ContextGeneratorConfig) {
|
|
64
|
+
this.config = config
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate all context files in parallel
|
|
69
|
+
*/
|
|
70
|
+
async generate(
|
|
71
|
+
git: GitData,
|
|
72
|
+
stats: ProjectStats,
|
|
73
|
+
commands: Commands,
|
|
74
|
+
agents: AgentInfo[]
|
|
75
|
+
): Promise<string[]> {
|
|
76
|
+
const contextPath = path.join(this.config.globalPath, 'context')
|
|
77
|
+
|
|
78
|
+
// Generate all context files IN PARALLEL
|
|
79
|
+
await Promise.all([
|
|
80
|
+
this.generateClaudeMd(contextPath, git, stats, commands, agents),
|
|
81
|
+
this.generateNowMd(contextPath),
|
|
82
|
+
this.generateNextMd(contextPath),
|
|
83
|
+
this.generateIdeasMd(contextPath),
|
|
84
|
+
this.generateShippedMd(contextPath),
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
'context/CLAUDE.md',
|
|
89
|
+
'context/now.md',
|
|
90
|
+
'context/next.md',
|
|
91
|
+
'context/ideas.md',
|
|
92
|
+
'context/shipped.md',
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ==========================================================================
|
|
97
|
+
// INDIVIDUAL GENERATORS
|
|
98
|
+
// ==========================================================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate CLAUDE.md - main context file for AI agents
|
|
102
|
+
*/
|
|
103
|
+
private async generateClaudeMd(
|
|
104
|
+
contextPath: string,
|
|
105
|
+
git: GitData,
|
|
106
|
+
stats: ProjectStats,
|
|
107
|
+
commands: Commands,
|
|
108
|
+
agents: AgentInfo[]
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
const workflowAgents = agents.filter((a) => a.type === 'workflow').map((a) => a.name)
|
|
111
|
+
const domainAgents = agents.filter((a) => a.type === 'domain').map((a) => a.name)
|
|
112
|
+
|
|
113
|
+
const content = `# ${stats.name} - Project Rules
|
|
114
|
+
<!-- projectId: ${this.config.projectId} -->
|
|
115
|
+
<!-- Generated: ${dateHelper.getTimestamp()} -->
|
|
116
|
+
<!-- Ecosystem: ${stats.ecosystem} | Type: ${stats.projectType} -->
|
|
117
|
+
|
|
118
|
+
## THIS PROJECT (${stats.ecosystem})
|
|
119
|
+
|
|
120
|
+
**Type:** ${stats.projectType}
|
|
121
|
+
**Path:** ${this.config.projectPath}
|
|
122
|
+
|
|
123
|
+
### Commands (USE THESE, NOT OTHERS)
|
|
124
|
+
|
|
125
|
+
| Action | Command |
|
|
126
|
+
|--------|---------|
|
|
127
|
+
| Install dependencies | \`${commands.install}\` |
|
|
128
|
+
| Run dev server | \`${commands.dev}\` |
|
|
129
|
+
| Run tests | \`${commands.test}\` |
|
|
130
|
+
| Build | \`${commands.build}\` |
|
|
131
|
+
| Lint | \`${commands.lint}\` |
|
|
132
|
+
| Format | \`${commands.format}\` |
|
|
133
|
+
|
|
134
|
+
### Code Conventions
|
|
135
|
+
|
|
136
|
+
- **Languages**: ${stats.languages.join(', ') || 'Not detected'}
|
|
137
|
+
- **Frameworks**: ${stats.frameworks.join(', ') || 'Not detected'}
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## PRJCT RULES
|
|
142
|
+
|
|
143
|
+
### Path Resolution
|
|
144
|
+
**ALL prjct writes go to**: \`~/.prjct-cli/projects/${this.config.projectId}/\`
|
|
145
|
+
- NEVER write to \`.prjct/\`
|
|
146
|
+
- NEVER write to \`./\` for prjct data
|
|
147
|
+
|
|
148
|
+
### Workflow
|
|
149
|
+
\`\`\`
|
|
150
|
+
p. sync → p. task "desc" → [work] → p. done → p. ship
|
|
151
|
+
\`\`\`
|
|
152
|
+
|
|
153
|
+
| Command | Action |
|
|
154
|
+
|---------|--------|
|
|
155
|
+
| \`p. sync\` | Re-analyze project |
|
|
156
|
+
| \`p. task X\` | Start task |
|
|
157
|
+
| \`p. done\` | Complete subtask |
|
|
158
|
+
| \`p. ship X\` | Ship feature |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## PROJECT STATE
|
|
163
|
+
|
|
164
|
+
| Field | Value |
|
|
165
|
+
|-------|-------|
|
|
166
|
+
| Name | ${stats.name} |
|
|
167
|
+
| Version | ${stats.version} |
|
|
168
|
+
| Ecosystem | ${stats.ecosystem} |
|
|
169
|
+
| Branch | ${git.branch} |
|
|
170
|
+
| Files | ~${stats.fileCount} |
|
|
171
|
+
| Commits | ${git.commits} |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## AGENTS
|
|
176
|
+
|
|
177
|
+
Load from \`~/.prjct-cli/projects/${this.config.projectId}/agents/\`:
|
|
178
|
+
|
|
179
|
+
**Workflow**: ${workflowAgents.join(', ')}
|
|
180
|
+
**Domain**: ${domainAgents.join(', ') || 'none'}
|
|
181
|
+
`
|
|
182
|
+
|
|
183
|
+
await fs.writeFile(path.join(contextPath, 'CLAUDE.md'), content, 'utf-8')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate now.md - current task status
|
|
188
|
+
*/
|
|
189
|
+
private async generateNowMd(contextPath: string): Promise<void> {
|
|
190
|
+
let currentTask = null
|
|
191
|
+
try {
|
|
192
|
+
const statePath = path.join(this.config.globalPath, 'storage', 'state.json')
|
|
193
|
+
const state = JSON.parse(await fs.readFile(statePath, 'utf-8'))
|
|
194
|
+
currentTask = state.currentTask
|
|
195
|
+
} catch {
|
|
196
|
+
// No state file
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const content = currentTask
|
|
200
|
+
? `# NOW
|
|
201
|
+
|
|
202
|
+
**${currentTask.description}**
|
|
203
|
+
|
|
204
|
+
Started: ${currentTask.startedAt}
|
|
205
|
+
${currentTask.branch ? `Branch: ${currentTask.branch.name}` : ''}
|
|
206
|
+
`
|
|
207
|
+
: `# NOW
|
|
208
|
+
|
|
209
|
+
_No active task_
|
|
210
|
+
|
|
211
|
+
Use \`p. task "description"\` to start working.
|
|
212
|
+
`
|
|
213
|
+
|
|
214
|
+
await fs.writeFile(path.join(contextPath, 'now.md'), content, 'utf-8')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Generate next.md - task queue
|
|
219
|
+
*/
|
|
220
|
+
private async generateNextMd(contextPath: string): Promise<void> {
|
|
221
|
+
let queue: { tasks: { description: string; priority?: string }[] } = { tasks: [] }
|
|
222
|
+
try {
|
|
223
|
+
const queuePath = path.join(this.config.globalPath, 'storage', 'queue.json')
|
|
224
|
+
queue = JSON.parse(await fs.readFile(queuePath, 'utf-8'))
|
|
225
|
+
} catch {
|
|
226
|
+
// No queue file
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const content = `# NEXT
|
|
230
|
+
|
|
231
|
+
${
|
|
232
|
+
queue.tasks.length > 0
|
|
233
|
+
? queue.tasks
|
|
234
|
+
.map((t, i) => `${i + 1}. ${t.description}${t.priority ? ` [${t.priority}]` : ''}`)
|
|
235
|
+
.join('\n')
|
|
236
|
+
: '_Empty queue_'
|
|
237
|
+
}
|
|
238
|
+
`
|
|
239
|
+
|
|
240
|
+
await fs.writeFile(path.join(contextPath, 'next.md'), content, 'utf-8')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Generate ideas.md - captured ideas
|
|
245
|
+
*/
|
|
246
|
+
private async generateIdeasMd(contextPath: string): Promise<void> {
|
|
247
|
+
let ideas: { ideas: { text: string; priority?: string }[] } = { ideas: [] }
|
|
248
|
+
try {
|
|
249
|
+
const ideasPath = path.join(this.config.globalPath, 'storage', 'ideas.json')
|
|
250
|
+
ideas = JSON.parse(await fs.readFile(ideasPath, 'utf-8'))
|
|
251
|
+
} catch {
|
|
252
|
+
// No ideas file
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const content = `# IDEAS
|
|
256
|
+
|
|
257
|
+
${
|
|
258
|
+
ideas.ideas.length > 0
|
|
259
|
+
? ideas.ideas.map((i) => `- ${i.text}${i.priority ? ` [${i.priority}]` : ''}`).join('\n')
|
|
260
|
+
: '_No ideas captured yet_'
|
|
261
|
+
}
|
|
262
|
+
`
|
|
263
|
+
|
|
264
|
+
await fs.writeFile(path.join(contextPath, 'ideas.md'), content, 'utf-8')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generate shipped.md - completed features
|
|
269
|
+
*/
|
|
270
|
+
private async generateShippedMd(contextPath: string): Promise<void> {
|
|
271
|
+
let shipped: { shipped: { name: string; version?: string; shippedAt: string }[] } = {
|
|
272
|
+
shipped: [],
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const shippedPath = path.join(this.config.globalPath, 'storage', 'shipped.json')
|
|
276
|
+
shipped = JSON.parse(await fs.readFile(shippedPath, 'utf-8'))
|
|
277
|
+
} catch {
|
|
278
|
+
// No shipped file
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const content = `# SHIPPED 🚀
|
|
282
|
+
|
|
283
|
+
${
|
|
284
|
+
shipped.shipped.length > 0
|
|
285
|
+
? shipped.shipped
|
|
286
|
+
.slice(-10)
|
|
287
|
+
.map((s) => `- **${s.name}**${s.version ? ` v${s.version}` : ''} - ${s.shippedAt}`)
|
|
288
|
+
.join('\n')
|
|
289
|
+
: '_Nothing shipped yet_'
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
**Total shipped:** ${shipped.shipped.length}
|
|
293
|
+
`
|
|
294
|
+
|
|
295
|
+
await fs.writeFile(path.join(contextPath, 'shipped.md'), content, 'utf-8')
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export default ContextFileGenerator
|