prjct-cli 0.45.0 → 0.45.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 (207) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/bin/prjct.ts +117 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +58 -39
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +28 -4
  43. package/core/commands/commands.ts +57 -24
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +13 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +18 -19
  59. package/core/context-tools/imports-tool.ts +13 -33
  60. package/core/context-tools/index.ts +29 -54
  61. package/core/context-tools/recent-tool.ts +16 -22
  62. package/core/context-tools/signatures-tool.ts +17 -26
  63. package/core/context-tools/summary-tool.ts +20 -22
  64. package/core/context-tools/token-counter.ts +25 -20
  65. package/core/context-tools/types.ts +5 -5
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -16
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +25 -25
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +87 -345
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -17
  157. package/core/storage/metrics-storage.ts +39 -34
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -305
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +14 -14
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18755 -15574
  194. package/dist/core/infrastructure/command-installer.js +86 -79
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +246 -225
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
@@ -15,22 +15,23 @@
15
15
  * @version 1.0.0
16
16
  */
17
17
 
18
- import fs from 'fs/promises'
19
- import path from 'path'
20
- import { exec } from 'child_process'
21
- import { promisify } from 'util'
22
- import pathManager from '../infrastructure/path-manager'
23
- import configManager from '../infrastructure/config-manager'
24
- import dateHelper from '../utils/date-helper'
25
- import { metricsStorage } from '../storage/metrics-storage'
18
+ import { exec } from 'node:child_process'
19
+ import fs from 'node:fs/promises'
20
+ import path from 'node:path'
21
+ import { promisify } from 'node:util'
26
22
  import {
27
- generateAIToolContexts,
28
23
  DEFAULT_AI_TOOLS,
29
- resolveToolIds,
30
24
  detectInstalledTools,
25
+ generateAIToolContexts,
31
26
  type ProjectContext,
32
- type GenerateResult,
27
+ resolveToolIds,
33
28
  } from '../ai-tools'
29
+ import configManager from '../infrastructure/config-manager'
30
+ import pathManager from '../infrastructure/path-manager'
31
+ import { metricsStorage } from '../storage/metrics-storage'
32
+ import dateHelper from '../utils/date-helper'
33
+ import { ContextFileGenerator } from './context-generator'
34
+ import { type StackDetection, StackDetector } from './stack-detector'
34
35
 
35
36
  const execAsync = promisify(exec)
36
37
 
@@ -70,16 +71,6 @@ interface Commands {
70
71
  format: string
71
72
  }
72
73
 
73
- interface StackDetection {
74
- hasFrontend: boolean
75
- hasBackend: boolean
76
- hasDatabase: boolean
77
- hasDocker: boolean
78
- hasTesting: boolean
79
- frontendType: 'web' | 'mobile' | 'both' | null
80
- frameworks: string[]
81
- }
82
-
83
74
  interface AgentInfo {
84
75
  name: string
85
76
  type: 'workflow' | 'domain'
@@ -93,10 +84,10 @@ interface AIToolResult {
93
84
  }
94
85
 
95
86
  interface SyncMetrics {
96
- duration: number // Sync duration in ms
97
- originalSize: number // Estimated tokens before compression
98
- filteredSize: number // Actual tokens in context files
99
- compressionRate: number // Percentage saved
87
+ duration: number // Sync duration in ms
88
+ originalSize: number // Estimated tokens before compression
89
+ filteredSize: number // Actual tokens in context files
90
+ compressionRate: number // Percentage saved
100
91
  }
101
92
 
102
93
  interface SyncResult {
@@ -116,7 +107,7 @@ interface SyncResult {
116
107
  }
117
108
 
118
109
  interface SyncOptions {
119
- aiTools?: string[] // AI tools to generate context for (default: claude, cursor)
110
+ aiTools?: string[] // AI tools to generate context for (default: claude, cursor)
120
111
  }
121
112
 
122
113
  // ============================================================================
@@ -176,16 +167,22 @@ class SyncService {
176
167
  this.globalPath = pathManager.getGlobalProjectPath(this.projectId)
177
168
  this.cliVersion = await this.getCliVersion()
178
169
 
179
- // 2. Ensure directories exist
180
- await this.ensureDirectories()
170
+ // 2. Ensure directories exist (non-blocking)
171
+ const ensureDirsPromise = this.ensureDirectories()
172
+
173
+ // 3. Gather all data IN PARALLEL (30-50% speedup)
174
+ // These operations are independent and can run concurrently
175
+ const [git, stats, commands, stack] = await Promise.all([
176
+ this.analyzeGit(),
177
+ this.gatherStats(),
178
+ this.detectCommands(),
179
+ this.detectStack(),
180
+ ])
181
181
 
182
- // 3. Gather all data
183
- const git = await this.analyzeGit()
184
- const stats = await this.gatherStats()
185
- const commands = await this.detectCommands()
186
- const stack = await this.detectStack()
182
+ // Wait for directories before writing files
183
+ await ensureDirsPromise
187
184
 
188
- // 4. Generate all files
185
+ // 4. Generate all files (depends on gathered data)
189
186
  const agents = await this.generateAgents(stack, stats)
190
187
  const skills = this.configureSkills(agents)
191
188
  const contextFiles = await this.generateContextFiles(git, stats, commands, agents)
@@ -206,8 +203,8 @@ class SyncService {
206
203
  hasChanges: git.hasChanges,
207
204
  commands,
208
205
  agents: {
209
- workflow: agents.filter(a => a.type === 'workflow').map(a => a.name),
210
- domain: agents.filter(a => a.type === 'domain').map(a => a.name),
206
+ workflow: agents.filter((a) => a.type === 'workflow').map((a) => a.name),
207
+ domain: agents.filter((a) => a.type === 'domain').map((a) => a.name),
211
208
  },
212
209
  }
213
210
 
@@ -218,23 +215,16 @@ class SyncService {
218
215
  aiToolIds
219
216
  )
220
217
 
221
- // 6. Update project.json
222
- await this.updateProjectJson(git, stats)
223
-
224
- // 7. Update state.json with enterprise fields
225
- await this.updateStateJson(stats, stack)
226
-
227
- // 8. Log to memory
228
- await this.logToMemory(git, stats)
218
+ // 6-8. Update files IN PARALLEL (write to different files)
219
+ await Promise.all([
220
+ this.updateProjectJson(git, stats),
221
+ this.updateStateJson(stats, stack),
222
+ this.logToMemory(git, stats),
223
+ ])
229
224
 
230
225
  // 9. Record metrics for value dashboard
231
226
  const duration = Date.now() - startTime
232
- const syncMetrics = await this.recordSyncMetrics(
233
- stats,
234
- contextFiles,
235
- agents,
236
- duration
237
- )
227
+ const syncMetrics = await this.recordSyncMetrics(stats, contextFiles, agents, duration)
238
228
 
239
229
  return {
240
230
  success: true,
@@ -247,7 +237,7 @@ class SyncService {
247
237
  agents,
248
238
  skills,
249
239
  contextFiles,
250
- aiTools: aiToolResults.map(r => ({
240
+ aiTools: aiToolResults.map((r) => ({
251
241
  toolId: r.toolId,
252
242
  outputFile: r.outputFile,
253
243
  success: r.success,
@@ -278,9 +268,10 @@ class SyncService {
278
268
 
279
269
  private async ensureDirectories(): Promise<void> {
280
270
  const dirs = ['storage', 'context', 'agents', 'memory', 'analysis', 'config', 'sync']
281
- for (const dir of dirs) {
282
- await fs.mkdir(path.join(this.globalPath, dir), { recursive: true })
283
- }
271
+ // Create all directories IN PARALLEL
272
+ await Promise.all(
273
+ dirs.map((dir) => fs.mkdir(path.join(this.globalPath, dir), { recursive: true }))
274
+ )
284
275
  }
285
276
 
286
277
  // ==========================================================================
@@ -311,13 +302,13 @@ class SyncService {
311
302
  const { stdout: commits } = await execAsync('git rev-list --count HEAD', {
312
303
  cwd: this.projectPath,
313
304
  })
314
- data.commits = parseInt(commits.trim()) || 0
305
+ data.commits = parseInt(commits.trim(), 10) || 0
315
306
 
316
307
  // Contributors
317
308
  const { stdout: contributors } = await execAsync('git shortlog -sn --all | wc -l', {
318
309
  cwd: this.projectPath,
319
310
  })
320
- data.contributors = parseInt(contributors.trim()) || 0
311
+ data.contributors = parseInt(contributors.trim(), 10) || 0
321
312
 
322
313
  // Status
323
314
  const { stdout: status } = await execAsync('git status --porcelain', {
@@ -355,7 +346,7 @@ class SyncService {
355
346
  const { stdout: weekly } = await execAsync('git log --oneline --since="1 week ago" | wc -l', {
356
347
  cwd: this.projectPath,
357
348
  })
358
- data.weeklyCommits = parseInt(weekly.trim()) || 0
349
+ data.weeklyCommits = parseInt(weekly.trim(), 10) || 0
359
350
  } catch {
360
351
  // Not a git repo - use defaults
361
352
  }
@@ -384,7 +375,7 @@ class SyncService {
384
375
  'find . -type f \\( -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.py" -o -name "*.go" -o -name "*.rs" \\) -not -path "./node_modules/*" -not -path "./.git/*" | wc -l',
385
376
  { cwd: this.projectPath }
386
377
  )
387
- stats.fileCount = parseInt(stdout.trim()) || 0
378
+ stats.fileCount = parseInt(stdout.trim(), 10) || 0
388
379
  } catch {
389
380
  stats.fileCount = 0
390
381
  }
@@ -427,7 +418,7 @@ class SyncService {
427
418
  stats.ecosystem = 'Go'
428
419
  stats.languages.push('Go')
429
420
  }
430
- if (await this.fileExists('requirements.txt') || await this.fileExists('pyproject.toml')) {
421
+ if ((await this.fileExists('requirements.txt')) || (await this.fileExists('pyproject.toml'))) {
431
422
  stats.ecosystem = 'Python'
432
423
  stats.languages.push('Python')
433
424
  }
@@ -513,60 +504,8 @@ class SyncService {
513
504
  // ==========================================================================
514
505
 
515
506
  private async detectStack(): Promise<StackDetection> {
516
- const stack: StackDetection = {
517
- hasFrontend: false,
518
- hasBackend: false,
519
- hasDatabase: false,
520
- hasDocker: false,
521
- hasTesting: false,
522
- frontendType: null,
523
- frameworks: [],
524
- }
525
-
526
- try {
527
- const pkgPath = path.join(this.projectPath, 'package.json')
528
- const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
529
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
530
-
531
- // Frontend detection
532
- if (deps.react || deps.vue || deps.svelte || deps['@angular/core']) {
533
- stack.hasFrontend = true
534
- stack.frontendType = 'web'
535
- }
536
- if (deps['react-native'] || deps.expo) {
537
- stack.hasFrontend = true
538
- stack.frontendType = stack.frontendType === 'web' ? 'both' : 'mobile'
539
- }
540
-
541
- // Backend detection
542
- if (deps.express || deps.fastify || deps.hono || deps.koa || deps.nest) {
543
- stack.hasBackend = true
544
- }
545
-
546
- // Database detection
547
- if (deps.prisma || deps.mongoose || deps.pg || deps.mysql2 || deps.sequelize) {
548
- stack.hasDatabase = true
549
- }
550
-
551
- // Testing detection
552
- if (deps.jest || deps.vitest || deps.mocha || pkg.devDependencies?.['bun-types']) {
553
- stack.hasTesting = true
554
- }
555
-
556
- // Collect frameworks
557
- if (deps.react) stack.frameworks.push('React')
558
- if (deps.next) stack.frameworks.push('Next.js')
559
- if (deps.express) stack.frameworks.push('Express')
560
- if (deps.hono) stack.frameworks.push('Hono')
561
- } catch {
562
- // No package.json
563
- }
564
-
565
- // Docker detection
566
- stack.hasDocker =
567
- (await this.fileExists('Dockerfile')) || (await this.fileExists('docker-compose.yml'))
568
-
569
- return stack
507
+ const detector = new StackDetector(this.projectPath)
508
+ return detector.detect()
570
509
  }
571
510
 
572
511
  // ==========================================================================
@@ -589,40 +528,43 @@ class SyncService {
589
528
  // Directory might not exist yet
590
529
  }
591
530
 
592
- // Workflow agents (always generated)
531
+ // Workflow agents (always generated) - IN PARALLEL
593
532
  const workflowAgents = ['prjct-workflow', 'prjct-planner', 'prjct-shipper']
533
+ await Promise.all(workflowAgents.map((name) => this.generateWorkflowAgent(name, agentsPath)))
594
534
  for (const name of workflowAgents) {
595
- await this.generateWorkflowAgent(name, agentsPath)
596
535
  agents.push({ name, type: 'workflow' })
597
536
  }
598
537
 
599
- // Domain agents (based on stack)
600
- if (stack.hasFrontend) {
601
- await this.generateDomainAgent('frontend', agentsPath, stats, stack)
602
- agents.push({ name: 'frontend', type: 'domain', skill: 'javascript-typescript' })
538
+ // Domain agents (based on stack) - COLLECT AND GENERATE IN PARALLEL
539
+ const domainAgentsToGenerate: { name: string; skill?: string }[] = []
603
540
 
604
- await this.generateDomainAgent('uxui', agentsPath, stats, stack)
605
- agents.push({ name: 'uxui', type: 'domain', skill: 'frontend-design' })
541
+ if (stack.hasFrontend) {
542
+ domainAgentsToGenerate.push({ name: 'frontend', skill: 'javascript-typescript' })
543
+ domainAgentsToGenerate.push({ name: 'uxui', skill: 'frontend-design' })
606
544
  }
607
-
608
545
  if (stack.hasBackend) {
609
- await this.generateDomainAgent('backend', agentsPath, stats, stack)
610
- agents.push({ name: 'backend', type: 'domain', skill: 'javascript-typescript' })
546
+ domainAgentsToGenerate.push({ name: 'backend', skill: 'javascript-typescript' })
611
547
  }
612
-
613
548
  if (stack.hasDatabase) {
614
- await this.generateDomainAgent('database', agentsPath, stats, stack)
615
- agents.push({ name: 'database', type: 'domain' })
549
+ domainAgentsToGenerate.push({ name: 'database' })
616
550
  }
617
-
618
551
  if (stack.hasTesting) {
619
- await this.generateDomainAgent('testing', agentsPath, stats, stack)
620
- agents.push({ name: 'testing', type: 'domain', skill: 'developer-kit' })
552
+ domainAgentsToGenerate.push({ name: 'testing', skill: 'developer-kit' })
621
553
  }
622
-
623
554
  if (stack.hasDocker) {
624
- await this.generateDomainAgent('devops', agentsPath, stats, stack)
625
- agents.push({ name: 'devops', type: 'domain', skill: 'developer-kit' })
555
+ domainAgentsToGenerate.push({ name: 'devops', skill: 'developer-kit' })
556
+ }
557
+
558
+ // Generate all domain agents IN PARALLEL
559
+ await Promise.all(
560
+ domainAgentsToGenerate.map((agent) =>
561
+ this.generateDomainAgent(agent.name, agentsPath, stats, stack)
562
+ )
563
+ )
564
+
565
+ // Add to agents list
566
+ for (const agent of domainAgentsToGenerate) {
567
+ agents.push({ name: agent.name, type: 'domain', skill: agent.skill })
626
568
  }
627
569
 
628
570
  return agents
@@ -781,212 +723,13 @@ You are the ${name} expert for this project. Apply best practices for the detect
781
723
  commands: Commands,
782
724
  agents: AgentInfo[]
783
725
  ): Promise<string[]> {
784
- const contextPath = path.join(this.globalPath, 'context')
785
- const files: string[] = []
786
-
787
- // Generate CLAUDE.md
788
- await this.generateClaudeMd(contextPath, git, stats, commands, agents)
789
- files.push('context/CLAUDE.md')
790
-
791
- // Generate now.md
792
- await this.generateNowMd(contextPath)
793
- files.push('context/now.md')
794
-
795
- // Generate next.md
796
- await this.generateNextMd(contextPath)
797
- files.push('context/next.md')
798
-
799
- // Generate ideas.md
800
- await this.generateIdeasMd(contextPath)
801
- files.push('context/ideas.md')
802
-
803
- // Generate shipped.md
804
- await this.generateShippedMd(contextPath)
805
- files.push('context/shipped.md')
806
-
807
- return files
808
- }
809
-
810
- private async generateClaudeMd(
811
- contextPath: string,
812
- git: GitData,
813
- stats: ProjectStats,
814
- commands: Commands,
815
- agents: AgentInfo[]
816
- ): Promise<void> {
817
- const workflowAgents = agents.filter((a) => a.type === 'workflow').map((a) => a.name)
818
- const domainAgents = agents.filter((a) => a.type === 'domain').map((a) => a.name)
819
-
820
- const content = `# ${stats.name} - Project Rules
821
- <!-- projectId: ${this.projectId} -->
822
- <!-- Generated: ${dateHelper.getTimestamp()} -->
823
- <!-- Ecosystem: ${stats.ecosystem} | Type: ${stats.projectType} -->
824
-
825
- ## THIS PROJECT (${stats.ecosystem})
826
-
827
- **Type:** ${stats.projectType}
828
- **Path:** ${this.projectPath}
829
-
830
- ### Commands (USE THESE, NOT OTHERS)
831
-
832
- | Action | Command |
833
- |--------|---------|
834
- | Install dependencies | \`${commands.install}\` |
835
- | Run dev server | \`${commands.dev}\` |
836
- | Run tests | \`${commands.test}\` |
837
- | Build | \`${commands.build}\` |
838
- | Lint | \`${commands.lint}\` |
839
- | Format | \`${commands.format}\` |
840
-
841
- ### Code Conventions
842
-
843
- - **Languages**: ${stats.languages.join(', ') || 'Not detected'}
844
- - **Frameworks**: ${stats.frameworks.join(', ') || 'Not detected'}
845
-
846
- ---
847
-
848
- ## PRJCT RULES
849
-
850
- ### Path Resolution
851
- **ALL prjct writes go to**: \`~/.prjct-cli/projects/${this.projectId}/\`
852
- - NEVER write to \`.prjct/\`
853
- - NEVER write to \`./\` for prjct data
854
-
855
- ### Workflow
856
- \`\`\`
857
- p. sync → p. task "desc" → [work] → p. done → p. ship
858
- \`\`\`
859
-
860
- | Command | Action |
861
- |---------|--------|
862
- | \`p. sync\` | Re-analyze project |
863
- | \`p. task X\` | Start task |
864
- | \`p. done\` | Complete subtask |
865
- | \`p. ship X\` | Ship feature |
866
-
867
- ---
868
-
869
- ## PROJECT STATE
870
-
871
- | Field | Value |
872
- |-------|-------|
873
- | Name | ${stats.name} |
874
- | Version | ${stats.version} |
875
- | Ecosystem | ${stats.ecosystem} |
876
- | Branch | ${git.branch} |
877
- | Files | ~${stats.fileCount} |
878
- | Commits | ${git.commits} |
879
-
880
- ---
881
-
882
- ## AGENTS
883
-
884
- Load from \`~/.prjct-cli/projects/${this.projectId}/agents/\`:
885
-
886
- **Workflow**: ${workflowAgents.join(', ')}
887
- **Domain**: ${domainAgents.join(', ') || 'none'}
888
- `
889
-
890
- await fs.writeFile(path.join(contextPath, 'CLAUDE.md'), content, 'utf-8')
891
- }
892
-
893
- private async generateNowMd(contextPath: string): Promise<void> {
894
- // Read current task from state
895
- let currentTask = null
896
- try {
897
- const statePath = path.join(this.globalPath, 'storage', 'state.json')
898
- const state = JSON.parse(await fs.readFile(statePath, 'utf-8'))
899
- currentTask = state.currentTask
900
- } catch {
901
- // No state file
902
- }
903
-
904
- const content = currentTask
905
- ? `# NOW
906
-
907
- **${currentTask.description}**
908
-
909
- Started: ${currentTask.startedAt}
910
- ${currentTask.branch ? `Branch: ${currentTask.branch.name}` : ''}
911
- `
912
- : `# NOW
913
-
914
- _No active task_
915
-
916
- Use \`p. task "description"\` to start working.
917
- `
918
-
919
- await fs.writeFile(path.join(contextPath, 'now.md'), content, 'utf-8')
920
- }
921
-
922
- private async generateNextMd(contextPath: string): Promise<void> {
923
- let queue: { tasks: { description: string; priority?: string }[] } = { tasks: [] }
924
- try {
925
- const queuePath = path.join(this.globalPath, 'storage', 'queue.json')
926
- queue = JSON.parse(await fs.readFile(queuePath, 'utf-8'))
927
- } catch {
928
- // No queue file
929
- }
930
-
931
- const content = `# NEXT
932
-
933
- ${
934
- queue.tasks.length > 0
935
- ? queue.tasks.map((t, i) => `${i + 1}. ${t.description}${t.priority ? ` [${t.priority}]` : ''}`).join('\n')
936
- : '_Empty queue_'
937
- }
938
- `
939
-
940
- await fs.writeFile(path.join(contextPath, 'next.md'), content, 'utf-8')
941
- }
942
-
943
- private async generateIdeasMd(contextPath: string): Promise<void> {
944
- let ideas: { ideas: { text: string; priority?: string }[] } = { ideas: [] }
945
- try {
946
- const ideasPath = path.join(this.globalPath, 'storage', 'ideas.json')
947
- ideas = JSON.parse(await fs.readFile(ideasPath, 'utf-8'))
948
- } catch {
949
- // No ideas file
950
- }
951
-
952
- const content = `# IDEAS
953
-
954
- ${
955
- ideas.ideas.length > 0
956
- ? ideas.ideas.map((i) => `- ${i.text}${i.priority ? ` [${i.priority}]` : ''}`).join('\n')
957
- : '_No ideas captured yet_'
958
- }
959
- `
960
-
961
- await fs.writeFile(path.join(contextPath, 'ideas.md'), content, 'utf-8')
962
- }
963
-
964
- private async generateShippedMd(contextPath: string): Promise<void> {
965
- let shipped: { shipped: { name: string; version?: string; shippedAt: string }[] } = {
966
- shipped: [],
967
- }
968
- try {
969
- const shippedPath = path.join(this.globalPath, 'storage', 'shipped.json')
970
- shipped = JSON.parse(await fs.readFile(shippedPath, 'utf-8'))
971
- } catch {
972
- // No shipped file
973
- }
974
-
975
- const content = `# SHIPPED 🚀
976
-
977
- ${
978
- shipped.shipped.length > 0
979
- ? shipped.shipped
980
- .slice(-10)
981
- .map((s) => `- **${s.name}**${s.version ? ` v${s.version}` : ''} - ${s.shippedAt}`)
982
- .join('\n')
983
- : '_Nothing shipped yet_'
984
- }
985
-
986
- **Total shipped:** ${shipped.shipped.length}
987
- `
726
+ const generator = new ContextFileGenerator({
727
+ projectId: this.projectId!,
728
+ projectPath: this.projectPath,
729
+ globalPath: this.globalPath,
730
+ })
988
731
 
989
- await fs.writeFile(path.join(contextPath, 'shipped.md'), content, 'utf-8')
732
+ return generator.generate({ branch: git.branch, commits: git.commits }, stats, commands, agents)
990
733
  }
991
734
 
992
735
  // ==========================================================================
@@ -1059,7 +802,7 @@ ${
1059
802
  state.lastSync = dateHelper.getTimestamp()
1060
803
  state.lastUpdated = dateHelper.getTimestamp()
1061
804
  state.context = {
1062
- ...(state.context as Record<string, unknown> || {}),
805
+ ...((state.context as Record<string, unknown>) || {}),
1063
806
  lastSession: dateHelper.getTimestamp(),
1064
807
  lastAction: 'Synced project',
1065
808
  nextAction: 'Run `p. task "description"` to start working',
@@ -1084,7 +827,7 @@ ${
1084
827
  commitCount: git.commits,
1085
828
  }
1086
829
 
1087
- await fs.appendFile(memoryPath, JSON.stringify(event) + '\n', 'utf-8')
830
+ await fs.appendFile(memoryPath, `${JSON.stringify(event)}\n`, 'utf-8')
1088
831
  }
1089
832
 
1090
833
  // ==========================================================================
@@ -1140,9 +883,8 @@ ${
1140
883
  const originalSize = stats.fileCount * avgTokensPerFile
1141
884
 
1142
885
  // Calculate compression rate
1143
- const compressionRate = originalSize > 0
1144
- ? Math.max(0, (originalSize - filteredSize) / originalSize)
1145
- : 0
886
+ const compressionRate =
887
+ originalSize > 0 ? Math.max(0, (originalSize - filteredSize) / originalSize) : 0
1146
888
 
1147
889
  // Record to storage
1148
890
  try {
@@ -1151,7 +893,7 @@ ${
1151
893
  filteredSize,
1152
894
  duration,
1153
895
  isWatch: false,
1154
- agents: agents.filter(a => a.type === 'domain').map(a => a.name),
896
+ agents: agents.filter((a) => a.type === 'domain').map((a) => a.name),
1155
897
  })
1156
898
  } catch (error) {
1157
899
  // Non-blocking - metrics are nice to have
@@ -1243,4 +985,4 @@ ${
1243
985
  }
1244
986
 
1245
987
  export const syncService = new SyncService()
1246
- export { SyncService, SyncResult }
988
+ export { SyncService, type SyncResult }