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
|
@@ -15,22 +15,23 @@
|
|
|
15
15
|
* @version 1.0.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
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
|
-
|
|
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
|
|
97
|
-
originalSize: number
|
|
98
|
-
filteredSize: number
|
|
99
|
-
compressionRate: number
|
|
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[]
|
|
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
|
-
|
|
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
|
-
//
|
|
183
|
-
|
|
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
|
|
222
|
-
await
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
|
517
|
-
|
|
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
|
-
|
|
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
|
-
|
|
605
|
-
|
|
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
|
-
|
|
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
|
-
|
|
615
|
-
agents.push({ name: 'database', type: 'domain' })
|
|
549
|
+
domainAgentsToGenerate.push({ name: 'database' })
|
|
616
550
|
}
|
|
617
|
-
|
|
618
551
|
if (stack.hasTesting) {
|
|
619
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
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)
|
|
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 =
|
|
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 }
|