prjct-cli 0.44.1 → 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 +114 -0
- package/bin/prjct.ts +131 -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 +287 -29
- 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 +49 -8
- package/core/commands/commands.ts +60 -23
- 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 +14 -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 +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- 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 -12
- 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 +143 -0
- 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 +170 -329
- 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 -13
- package/core/storage/metrics-storage.ts +320 -0
- 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 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- 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 +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- 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,21 +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'
|
|
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'
|
|
25
22
|
import {
|
|
26
|
-
generateAIToolContexts,
|
|
27
23
|
DEFAULT_AI_TOOLS,
|
|
28
|
-
resolveToolIds,
|
|
29
24
|
detectInstalledTools,
|
|
25
|
+
generateAIToolContexts,
|
|
30
26
|
type ProjectContext,
|
|
31
|
-
|
|
27
|
+
resolveToolIds,
|
|
32
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'
|
|
33
35
|
|
|
34
36
|
const execAsync = promisify(exec)
|
|
35
37
|
|
|
@@ -69,16 +71,6 @@ interface Commands {
|
|
|
69
71
|
format: string
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
interface StackDetection {
|
|
73
|
-
hasFrontend: boolean
|
|
74
|
-
hasBackend: boolean
|
|
75
|
-
hasDatabase: boolean
|
|
76
|
-
hasDocker: boolean
|
|
77
|
-
hasTesting: boolean
|
|
78
|
-
frontendType: 'web' | 'mobile' | 'both' | null
|
|
79
|
-
frameworks: string[]
|
|
80
|
-
}
|
|
81
|
-
|
|
82
74
|
interface AgentInfo {
|
|
83
75
|
name: string
|
|
84
76
|
type: 'workflow' | 'domain'
|
|
@@ -91,6 +83,13 @@ interface AIToolResult {
|
|
|
91
83
|
success: boolean
|
|
92
84
|
}
|
|
93
85
|
|
|
86
|
+
interface SyncMetrics {
|
|
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
|
|
91
|
+
}
|
|
92
|
+
|
|
94
93
|
interface SyncResult {
|
|
95
94
|
success: boolean
|
|
96
95
|
projectId: string
|
|
@@ -103,11 +102,12 @@ interface SyncResult {
|
|
|
103
102
|
skills: { agent: string; skill: string }[]
|
|
104
103
|
contextFiles: string[]
|
|
105
104
|
aiTools: AIToolResult[]
|
|
105
|
+
syncMetrics?: SyncMetrics
|
|
106
106
|
error?: string
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
interface SyncOptions {
|
|
110
|
-
aiTools?: string[]
|
|
110
|
+
aiTools?: string[] // AI tools to generate context for (default: claude, cursor)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// ============================================================================
|
|
@@ -129,6 +129,7 @@ class SyncService {
|
|
|
129
129
|
*/
|
|
130
130
|
async sync(projectPath: string = process.cwd(), options: SyncOptions = {}): Promise<SyncResult> {
|
|
131
131
|
this.projectPath = projectPath
|
|
132
|
+
const startTime = Date.now()
|
|
132
133
|
|
|
133
134
|
// Resolve AI tools: supports 'auto', 'all', or specific list
|
|
134
135
|
let aiToolIds: string[]
|
|
@@ -166,16 +167,22 @@ class SyncService {
|
|
|
166
167
|
this.globalPath = pathManager.getGlobalProjectPath(this.projectId)
|
|
167
168
|
this.cliVersion = await this.getCliVersion()
|
|
168
169
|
|
|
169
|
-
// 2. Ensure directories exist
|
|
170
|
-
|
|
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
|
+
])
|
|
171
181
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
const stats = await this.gatherStats()
|
|
175
|
-
const commands = await this.detectCommands()
|
|
176
|
-
const stack = await this.detectStack()
|
|
182
|
+
// Wait for directories before writing files
|
|
183
|
+
await ensureDirsPromise
|
|
177
184
|
|
|
178
|
-
// 4. Generate all files
|
|
185
|
+
// 4. Generate all files (depends on gathered data)
|
|
179
186
|
const agents = await this.generateAgents(stack, stats)
|
|
180
187
|
const skills = this.configureSkills(agents)
|
|
181
188
|
const contextFiles = await this.generateContextFiles(git, stats, commands, agents)
|
|
@@ -196,8 +203,8 @@ class SyncService {
|
|
|
196
203
|
hasChanges: git.hasChanges,
|
|
197
204
|
commands,
|
|
198
205
|
agents: {
|
|
199
|
-
workflow: agents.filter(a => a.type === 'workflow').map(a => a.name),
|
|
200
|
-
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),
|
|
201
208
|
},
|
|
202
209
|
}
|
|
203
210
|
|
|
@@ -208,14 +215,16 @@ class SyncService {
|
|
|
208
215
|
aiToolIds
|
|
209
216
|
)
|
|
210
217
|
|
|
211
|
-
// 6. Update
|
|
212
|
-
await
|
|
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
|
+
])
|
|
213
224
|
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// 8. Log to memory
|
|
218
|
-
await this.logToMemory(git, stats)
|
|
225
|
+
// 9. Record metrics for value dashboard
|
|
226
|
+
const duration = Date.now() - startTime
|
|
227
|
+
const syncMetrics = await this.recordSyncMetrics(stats, contextFiles, agents, duration)
|
|
219
228
|
|
|
220
229
|
return {
|
|
221
230
|
success: true,
|
|
@@ -228,11 +237,12 @@ class SyncService {
|
|
|
228
237
|
agents,
|
|
229
238
|
skills,
|
|
230
239
|
contextFiles,
|
|
231
|
-
aiTools: aiToolResults.map(r => ({
|
|
240
|
+
aiTools: aiToolResults.map((r) => ({
|
|
232
241
|
toolId: r.toolId,
|
|
233
242
|
outputFile: r.outputFile,
|
|
234
243
|
success: r.success,
|
|
235
244
|
})),
|
|
245
|
+
syncMetrics,
|
|
236
246
|
}
|
|
237
247
|
} catch (error) {
|
|
238
248
|
return {
|
|
@@ -258,9 +268,10 @@ class SyncService {
|
|
|
258
268
|
|
|
259
269
|
private async ensureDirectories(): Promise<void> {
|
|
260
270
|
const dirs = ['storage', 'context', 'agents', 'memory', 'analysis', 'config', 'sync']
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
271
|
+
// Create all directories IN PARALLEL
|
|
272
|
+
await Promise.all(
|
|
273
|
+
dirs.map((dir) => fs.mkdir(path.join(this.globalPath, dir), { recursive: true }))
|
|
274
|
+
)
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
// ==========================================================================
|
|
@@ -291,13 +302,13 @@ class SyncService {
|
|
|
291
302
|
const { stdout: commits } = await execAsync('git rev-list --count HEAD', {
|
|
292
303
|
cwd: this.projectPath,
|
|
293
304
|
})
|
|
294
|
-
data.commits = parseInt(commits.trim()) || 0
|
|
305
|
+
data.commits = parseInt(commits.trim(), 10) || 0
|
|
295
306
|
|
|
296
307
|
// Contributors
|
|
297
308
|
const { stdout: contributors } = await execAsync('git shortlog -sn --all | wc -l', {
|
|
298
309
|
cwd: this.projectPath,
|
|
299
310
|
})
|
|
300
|
-
data.contributors = parseInt(contributors.trim()) || 0
|
|
311
|
+
data.contributors = parseInt(contributors.trim(), 10) || 0
|
|
301
312
|
|
|
302
313
|
// Status
|
|
303
314
|
const { stdout: status } = await execAsync('git status --porcelain', {
|
|
@@ -335,7 +346,7 @@ class SyncService {
|
|
|
335
346
|
const { stdout: weekly } = await execAsync('git log --oneline --since="1 week ago" | wc -l', {
|
|
336
347
|
cwd: this.projectPath,
|
|
337
348
|
})
|
|
338
|
-
data.weeklyCommits = parseInt(weekly.trim()) || 0
|
|
349
|
+
data.weeklyCommits = parseInt(weekly.trim(), 10) || 0
|
|
339
350
|
} catch {
|
|
340
351
|
// Not a git repo - use defaults
|
|
341
352
|
}
|
|
@@ -364,7 +375,7 @@ class SyncService {
|
|
|
364
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',
|
|
365
376
|
{ cwd: this.projectPath }
|
|
366
377
|
)
|
|
367
|
-
stats.fileCount = parseInt(stdout.trim()) || 0
|
|
378
|
+
stats.fileCount = parseInt(stdout.trim(), 10) || 0
|
|
368
379
|
} catch {
|
|
369
380
|
stats.fileCount = 0
|
|
370
381
|
}
|
|
@@ -407,7 +418,7 @@ class SyncService {
|
|
|
407
418
|
stats.ecosystem = 'Go'
|
|
408
419
|
stats.languages.push('Go')
|
|
409
420
|
}
|
|
410
|
-
if (await this.fileExists('requirements.txt') || await this.fileExists('pyproject.toml')) {
|
|
421
|
+
if ((await this.fileExists('requirements.txt')) || (await this.fileExists('pyproject.toml'))) {
|
|
411
422
|
stats.ecosystem = 'Python'
|
|
412
423
|
stats.languages.push('Python')
|
|
413
424
|
}
|
|
@@ -493,60 +504,8 @@ class SyncService {
|
|
|
493
504
|
// ==========================================================================
|
|
494
505
|
|
|
495
506
|
private async detectStack(): Promise<StackDetection> {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
hasBackend: false,
|
|
499
|
-
hasDatabase: false,
|
|
500
|
-
hasDocker: false,
|
|
501
|
-
hasTesting: false,
|
|
502
|
-
frontendType: null,
|
|
503
|
-
frameworks: [],
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
const pkgPath = path.join(this.projectPath, 'package.json')
|
|
508
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
|
|
509
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
510
|
-
|
|
511
|
-
// Frontend detection
|
|
512
|
-
if (deps.react || deps.vue || deps.svelte || deps['@angular/core']) {
|
|
513
|
-
stack.hasFrontend = true
|
|
514
|
-
stack.frontendType = 'web'
|
|
515
|
-
}
|
|
516
|
-
if (deps['react-native'] || deps.expo) {
|
|
517
|
-
stack.hasFrontend = true
|
|
518
|
-
stack.frontendType = stack.frontendType === 'web' ? 'both' : 'mobile'
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Backend detection
|
|
522
|
-
if (deps.express || deps.fastify || deps.hono || deps.koa || deps.nest) {
|
|
523
|
-
stack.hasBackend = true
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Database detection
|
|
527
|
-
if (deps.prisma || deps.mongoose || deps.pg || deps.mysql2 || deps.sequelize) {
|
|
528
|
-
stack.hasDatabase = true
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Testing detection
|
|
532
|
-
if (deps.jest || deps.vitest || deps.mocha || pkg.devDependencies?.['bun-types']) {
|
|
533
|
-
stack.hasTesting = true
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Collect frameworks
|
|
537
|
-
if (deps.react) stack.frameworks.push('React')
|
|
538
|
-
if (deps.next) stack.frameworks.push('Next.js')
|
|
539
|
-
if (deps.express) stack.frameworks.push('Express')
|
|
540
|
-
if (deps.hono) stack.frameworks.push('Hono')
|
|
541
|
-
} catch {
|
|
542
|
-
// No package.json
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Docker detection
|
|
546
|
-
stack.hasDocker =
|
|
547
|
-
(await this.fileExists('Dockerfile')) || (await this.fileExists('docker-compose.yml'))
|
|
548
|
-
|
|
549
|
-
return stack
|
|
507
|
+
const detector = new StackDetector(this.projectPath)
|
|
508
|
+
return detector.detect()
|
|
550
509
|
}
|
|
551
510
|
|
|
552
511
|
// ==========================================================================
|
|
@@ -569,40 +528,43 @@ class SyncService {
|
|
|
569
528
|
// Directory might not exist yet
|
|
570
529
|
}
|
|
571
530
|
|
|
572
|
-
// Workflow agents (always generated)
|
|
531
|
+
// Workflow agents (always generated) - IN PARALLEL
|
|
573
532
|
const workflowAgents = ['prjct-workflow', 'prjct-planner', 'prjct-shipper']
|
|
533
|
+
await Promise.all(workflowAgents.map((name) => this.generateWorkflowAgent(name, agentsPath)))
|
|
574
534
|
for (const name of workflowAgents) {
|
|
575
|
-
await this.generateWorkflowAgent(name, agentsPath)
|
|
576
535
|
agents.push({ name, type: 'workflow' })
|
|
577
536
|
}
|
|
578
537
|
|
|
579
|
-
// Domain agents (based on stack)
|
|
580
|
-
|
|
581
|
-
await this.generateDomainAgent('frontend', agentsPath, stats, stack)
|
|
582
|
-
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 }[] = []
|
|
583
540
|
|
|
584
|
-
|
|
585
|
-
|
|
541
|
+
if (stack.hasFrontend) {
|
|
542
|
+
domainAgentsToGenerate.push({ name: 'frontend', skill: 'javascript-typescript' })
|
|
543
|
+
domainAgentsToGenerate.push({ name: 'uxui', skill: 'frontend-design' })
|
|
586
544
|
}
|
|
587
|
-
|
|
588
545
|
if (stack.hasBackend) {
|
|
589
|
-
|
|
590
|
-
agents.push({ name: 'backend', type: 'domain', skill: 'javascript-typescript' })
|
|
546
|
+
domainAgentsToGenerate.push({ name: 'backend', skill: 'javascript-typescript' })
|
|
591
547
|
}
|
|
592
|
-
|
|
593
548
|
if (stack.hasDatabase) {
|
|
594
|
-
|
|
595
|
-
agents.push({ name: 'database', type: 'domain' })
|
|
549
|
+
domainAgentsToGenerate.push({ name: 'database' })
|
|
596
550
|
}
|
|
597
|
-
|
|
598
551
|
if (stack.hasTesting) {
|
|
599
|
-
|
|
600
|
-
agents.push({ name: 'testing', type: 'domain', skill: 'developer-kit' })
|
|
552
|
+
domainAgentsToGenerate.push({ name: 'testing', skill: 'developer-kit' })
|
|
601
553
|
}
|
|
602
|
-
|
|
603
554
|
if (stack.hasDocker) {
|
|
604
|
-
|
|
605
|
-
|
|
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 })
|
|
606
568
|
}
|
|
607
569
|
|
|
608
570
|
return agents
|
|
@@ -761,212 +723,13 @@ You are the ${name} expert for this project. Apply best practices for the detect
|
|
|
761
723
|
commands: Commands,
|
|
762
724
|
agents: AgentInfo[]
|
|
763
725
|
): Promise<string[]> {
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
files.push('context/CLAUDE.md')
|
|
770
|
-
|
|
771
|
-
// Generate now.md
|
|
772
|
-
await this.generateNowMd(contextPath)
|
|
773
|
-
files.push('context/now.md')
|
|
774
|
-
|
|
775
|
-
// Generate next.md
|
|
776
|
-
await this.generateNextMd(contextPath)
|
|
777
|
-
files.push('context/next.md')
|
|
778
|
-
|
|
779
|
-
// Generate ideas.md
|
|
780
|
-
await this.generateIdeasMd(contextPath)
|
|
781
|
-
files.push('context/ideas.md')
|
|
782
|
-
|
|
783
|
-
// Generate shipped.md
|
|
784
|
-
await this.generateShippedMd(contextPath)
|
|
785
|
-
files.push('context/shipped.md')
|
|
786
|
-
|
|
787
|
-
return files
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
private async generateClaudeMd(
|
|
791
|
-
contextPath: string,
|
|
792
|
-
git: GitData,
|
|
793
|
-
stats: ProjectStats,
|
|
794
|
-
commands: Commands,
|
|
795
|
-
agents: AgentInfo[]
|
|
796
|
-
): Promise<void> {
|
|
797
|
-
const workflowAgents = agents.filter((a) => a.type === 'workflow').map((a) => a.name)
|
|
798
|
-
const domainAgents = agents.filter((a) => a.type === 'domain').map((a) => a.name)
|
|
799
|
-
|
|
800
|
-
const content = `# ${stats.name} - Project Rules
|
|
801
|
-
<!-- projectId: ${this.projectId} -->
|
|
802
|
-
<!-- Generated: ${dateHelper.getTimestamp()} -->
|
|
803
|
-
<!-- Ecosystem: ${stats.ecosystem} | Type: ${stats.projectType} -->
|
|
804
|
-
|
|
805
|
-
## THIS PROJECT (${stats.ecosystem})
|
|
806
|
-
|
|
807
|
-
**Type:** ${stats.projectType}
|
|
808
|
-
**Path:** ${this.projectPath}
|
|
809
|
-
|
|
810
|
-
### Commands (USE THESE, NOT OTHERS)
|
|
811
|
-
|
|
812
|
-
| Action | Command |
|
|
813
|
-
|--------|---------|
|
|
814
|
-
| Install dependencies | \`${commands.install}\` |
|
|
815
|
-
| Run dev server | \`${commands.dev}\` |
|
|
816
|
-
| Run tests | \`${commands.test}\` |
|
|
817
|
-
| Build | \`${commands.build}\` |
|
|
818
|
-
| Lint | \`${commands.lint}\` |
|
|
819
|
-
| Format | \`${commands.format}\` |
|
|
820
|
-
|
|
821
|
-
### Code Conventions
|
|
822
|
-
|
|
823
|
-
- **Languages**: ${stats.languages.join(', ') || 'Not detected'}
|
|
824
|
-
- **Frameworks**: ${stats.frameworks.join(', ') || 'Not detected'}
|
|
825
|
-
|
|
826
|
-
---
|
|
827
|
-
|
|
828
|
-
## PRJCT RULES
|
|
829
|
-
|
|
830
|
-
### Path Resolution
|
|
831
|
-
**ALL prjct writes go to**: \`~/.prjct-cli/projects/${this.projectId}/\`
|
|
832
|
-
- NEVER write to \`.prjct/\`
|
|
833
|
-
- NEVER write to \`./\` for prjct data
|
|
834
|
-
|
|
835
|
-
### Workflow
|
|
836
|
-
\`\`\`
|
|
837
|
-
p. sync → p. task "desc" → [work] → p. done → p. ship
|
|
838
|
-
\`\`\`
|
|
839
|
-
|
|
840
|
-
| Command | Action |
|
|
841
|
-
|---------|--------|
|
|
842
|
-
| \`p. sync\` | Re-analyze project |
|
|
843
|
-
| \`p. task X\` | Start task |
|
|
844
|
-
| \`p. done\` | Complete subtask |
|
|
845
|
-
| \`p. ship X\` | Ship feature |
|
|
846
|
-
|
|
847
|
-
---
|
|
848
|
-
|
|
849
|
-
## PROJECT STATE
|
|
850
|
-
|
|
851
|
-
| Field | Value |
|
|
852
|
-
|-------|-------|
|
|
853
|
-
| Name | ${stats.name} |
|
|
854
|
-
| Version | ${stats.version} |
|
|
855
|
-
| Ecosystem | ${stats.ecosystem} |
|
|
856
|
-
| Branch | ${git.branch} |
|
|
857
|
-
| Files | ~${stats.fileCount} |
|
|
858
|
-
| Commits | ${git.commits} |
|
|
859
|
-
|
|
860
|
-
---
|
|
861
|
-
|
|
862
|
-
## AGENTS
|
|
863
|
-
|
|
864
|
-
Load from \`~/.prjct-cli/projects/${this.projectId}/agents/\`:
|
|
865
|
-
|
|
866
|
-
**Workflow**: ${workflowAgents.join(', ')}
|
|
867
|
-
**Domain**: ${domainAgents.join(', ') || 'none'}
|
|
868
|
-
`
|
|
869
|
-
|
|
870
|
-
await fs.writeFile(path.join(contextPath, 'CLAUDE.md'), content, 'utf-8')
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
private async generateNowMd(contextPath: string): Promise<void> {
|
|
874
|
-
// Read current task from state
|
|
875
|
-
let currentTask = null
|
|
876
|
-
try {
|
|
877
|
-
const statePath = path.join(this.globalPath, 'storage', 'state.json')
|
|
878
|
-
const state = JSON.parse(await fs.readFile(statePath, 'utf-8'))
|
|
879
|
-
currentTask = state.currentTask
|
|
880
|
-
} catch {
|
|
881
|
-
// No state file
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
const content = currentTask
|
|
885
|
-
? `# NOW
|
|
886
|
-
|
|
887
|
-
**${currentTask.description}**
|
|
888
|
-
|
|
889
|
-
Started: ${currentTask.startedAt}
|
|
890
|
-
${currentTask.branch ? `Branch: ${currentTask.branch.name}` : ''}
|
|
891
|
-
`
|
|
892
|
-
: `# NOW
|
|
893
|
-
|
|
894
|
-
_No active task_
|
|
895
|
-
|
|
896
|
-
Use \`p. task "description"\` to start working.
|
|
897
|
-
`
|
|
898
|
-
|
|
899
|
-
await fs.writeFile(path.join(contextPath, 'now.md'), content, 'utf-8')
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
private async generateNextMd(contextPath: string): Promise<void> {
|
|
903
|
-
let queue: { tasks: { description: string; priority?: string }[] } = { tasks: [] }
|
|
904
|
-
try {
|
|
905
|
-
const queuePath = path.join(this.globalPath, 'storage', 'queue.json')
|
|
906
|
-
queue = JSON.parse(await fs.readFile(queuePath, 'utf-8'))
|
|
907
|
-
} catch {
|
|
908
|
-
// No queue file
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
const content = `# NEXT
|
|
912
|
-
|
|
913
|
-
${
|
|
914
|
-
queue.tasks.length > 0
|
|
915
|
-
? queue.tasks.map((t, i) => `${i + 1}. ${t.description}${t.priority ? ` [${t.priority}]` : ''}`).join('\n')
|
|
916
|
-
: '_Empty queue_'
|
|
917
|
-
}
|
|
918
|
-
`
|
|
919
|
-
|
|
920
|
-
await fs.writeFile(path.join(contextPath, 'next.md'), content, 'utf-8')
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
private async generateIdeasMd(contextPath: string): Promise<void> {
|
|
924
|
-
let ideas: { ideas: { text: string; priority?: string }[] } = { ideas: [] }
|
|
925
|
-
try {
|
|
926
|
-
const ideasPath = path.join(this.globalPath, 'storage', 'ideas.json')
|
|
927
|
-
ideas = JSON.parse(await fs.readFile(ideasPath, 'utf-8'))
|
|
928
|
-
} catch {
|
|
929
|
-
// No ideas file
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
const content = `# IDEAS
|
|
933
|
-
|
|
934
|
-
${
|
|
935
|
-
ideas.ideas.length > 0
|
|
936
|
-
? ideas.ideas.map((i) => `- ${i.text}${i.priority ? ` [${i.priority}]` : ''}`).join('\n')
|
|
937
|
-
: '_No ideas captured yet_'
|
|
938
|
-
}
|
|
939
|
-
`
|
|
940
|
-
|
|
941
|
-
await fs.writeFile(path.join(contextPath, 'ideas.md'), content, 'utf-8')
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
private async generateShippedMd(contextPath: string): Promise<void> {
|
|
945
|
-
let shipped: { shipped: { name: string; version?: string; shippedAt: string }[] } = {
|
|
946
|
-
shipped: [],
|
|
947
|
-
}
|
|
948
|
-
try {
|
|
949
|
-
const shippedPath = path.join(this.globalPath, 'storage', 'shipped.json')
|
|
950
|
-
shipped = JSON.parse(await fs.readFile(shippedPath, 'utf-8'))
|
|
951
|
-
} catch {
|
|
952
|
-
// No shipped file
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const content = `# SHIPPED 🚀
|
|
956
|
-
|
|
957
|
-
${
|
|
958
|
-
shipped.shipped.length > 0
|
|
959
|
-
? shipped.shipped
|
|
960
|
-
.slice(-10)
|
|
961
|
-
.map((s) => `- **${s.name}**${s.version ? ` v${s.version}` : ''} - ${s.shippedAt}`)
|
|
962
|
-
.join('\n')
|
|
963
|
-
: '_Nothing shipped yet_'
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
**Total shipped:** ${shipped.shipped.length}
|
|
967
|
-
`
|
|
726
|
+
const generator = new ContextFileGenerator({
|
|
727
|
+
projectId: this.projectId!,
|
|
728
|
+
projectPath: this.projectPath,
|
|
729
|
+
globalPath: this.globalPath,
|
|
730
|
+
})
|
|
968
731
|
|
|
969
|
-
|
|
732
|
+
return generator.generate({ branch: git.branch, commits: git.commits }, stats, commands, agents)
|
|
970
733
|
}
|
|
971
734
|
|
|
972
735
|
// ==========================================================================
|
|
@@ -1039,7 +802,7 @@ ${
|
|
|
1039
802
|
state.lastSync = dateHelper.getTimestamp()
|
|
1040
803
|
state.lastUpdated = dateHelper.getTimestamp()
|
|
1041
804
|
state.context = {
|
|
1042
|
-
...(state.context as Record<string, unknown> || {}),
|
|
805
|
+
...((state.context as Record<string, unknown>) || {}),
|
|
1043
806
|
lastSession: dateHelper.getTimestamp(),
|
|
1044
807
|
lastAction: 'Synced project',
|
|
1045
808
|
nextAction: 'Run `p. task "description"` to start working',
|
|
@@ -1064,7 +827,85 @@ ${
|
|
|
1064
827
|
commitCount: git.commits,
|
|
1065
828
|
}
|
|
1066
829
|
|
|
1067
|
-
await fs.appendFile(memoryPath, JSON.stringify(event)
|
|
830
|
+
await fs.appendFile(memoryPath, `${JSON.stringify(event)}\n`, 'utf-8')
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// ==========================================================================
|
|
834
|
+
// METRICS RECORDING
|
|
835
|
+
// ==========================================================================
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Record sync metrics for the value dashboard
|
|
839
|
+
*
|
|
840
|
+
* Calculates token savings by comparing:
|
|
841
|
+
* - Original: Estimated tokens if we sent all source files
|
|
842
|
+
* - Filtered: Actual tokens in generated context files
|
|
843
|
+
*
|
|
844
|
+
* Token estimation: ~4 chars per token (industry standard)
|
|
845
|
+
*/
|
|
846
|
+
private async recordSyncMetrics(
|
|
847
|
+
stats: ProjectStats,
|
|
848
|
+
contextFiles: string[],
|
|
849
|
+
agents: AgentInfo[],
|
|
850
|
+
duration: number
|
|
851
|
+
): Promise<SyncMetrics> {
|
|
852
|
+
const CHARS_PER_TOKEN = 4
|
|
853
|
+
|
|
854
|
+
// Calculate filtered size (actual context files generated)
|
|
855
|
+
let filteredChars = 0
|
|
856
|
+
for (const file of contextFiles) {
|
|
857
|
+
try {
|
|
858
|
+
const filePath = path.join(this.globalPath, file)
|
|
859
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
860
|
+
filteredChars += content.length
|
|
861
|
+
} catch {
|
|
862
|
+
// File might not exist, skip
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Also count agent files
|
|
867
|
+
for (const agent of agents) {
|
|
868
|
+
try {
|
|
869
|
+
const agentPath = path.join(this.globalPath, 'agents', `${agent.name}.md`)
|
|
870
|
+
const content = await fs.readFile(agentPath, 'utf-8')
|
|
871
|
+
filteredChars += content.length
|
|
872
|
+
} catch {
|
|
873
|
+
// Skip if not found
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const filteredSize = Math.floor(filteredChars / CHARS_PER_TOKEN)
|
|
878
|
+
|
|
879
|
+
// Estimate original size (what it would take without prjct)
|
|
880
|
+
// Conservative estimate: avg 500 tokens per source file
|
|
881
|
+
// Plus overhead for manually creating context
|
|
882
|
+
const avgTokensPerFile = 500
|
|
883
|
+
const originalSize = stats.fileCount * avgTokensPerFile
|
|
884
|
+
|
|
885
|
+
// Calculate compression rate
|
|
886
|
+
const compressionRate =
|
|
887
|
+
originalSize > 0 ? Math.max(0, (originalSize - filteredSize) / originalSize) : 0
|
|
888
|
+
|
|
889
|
+
// Record to storage
|
|
890
|
+
try {
|
|
891
|
+
await metricsStorage.recordSync(this.projectId!, {
|
|
892
|
+
originalSize,
|
|
893
|
+
filteredSize,
|
|
894
|
+
duration,
|
|
895
|
+
isWatch: false,
|
|
896
|
+
agents: agents.filter((a) => a.type === 'domain').map((a) => a.name),
|
|
897
|
+
})
|
|
898
|
+
} catch (error) {
|
|
899
|
+
// Non-blocking - metrics are nice to have
|
|
900
|
+
console.error('Warning: Failed to record metrics:', (error as Error).message)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return {
|
|
904
|
+
duration,
|
|
905
|
+
originalSize,
|
|
906
|
+
filteredSize,
|
|
907
|
+
compressionRate,
|
|
908
|
+
}
|
|
1068
909
|
}
|
|
1069
910
|
|
|
1070
911
|
// ==========================================================================
|
|
@@ -1144,4 +985,4 @@ ${
|
|
|
1144
985
|
}
|
|
1145
986
|
|
|
1146
987
|
export const syncService = new SyncService()
|
|
1147
|
-
export { SyncService, SyncResult }
|
|
988
|
+
export { SyncService, type SyncResult }
|