prjct-cli 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +230 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct-core.mjs +1748 -0
- package/dist/bin/prjct.mjs +17 -36672
- package/dist/cli/linear.mjs +14 -0
- package/dist/daemon/entry.mjs +1429 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,1515 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SyncService - Comprehensive Project Sync
|
|
3
|
-
*
|
|
4
|
-
* Handles ALL sync operations in a single TypeScript execution:
|
|
5
|
-
* - Git analysis
|
|
6
|
-
* - Project stats
|
|
7
|
-
* - Context file generation
|
|
8
|
-
* - Agent generation
|
|
9
|
-
* - Skill configuration
|
|
10
|
-
* - State updates
|
|
11
|
-
*
|
|
12
|
-
* This eliminates the need for Claude to make 50+ individual tool calls.
|
|
13
|
-
* Instead, one command does everything.
|
|
14
|
-
*
|
|
15
|
-
* @version 1.0.0
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { exec } from 'node:child_process'
|
|
19
|
-
import fs from 'node:fs/promises'
|
|
20
|
-
import os from 'node:os'
|
|
21
|
-
import path from 'node:path'
|
|
22
|
-
import { promisify } from 'node:util'
|
|
23
|
-
import {
|
|
24
|
-
DEFAULT_AI_TOOLS,
|
|
25
|
-
detectInstalledTools,
|
|
26
|
-
generateAIToolContexts,
|
|
27
|
-
type ProjectContext,
|
|
28
|
-
resolveToolIds,
|
|
29
|
-
} from '../ai-tools'
|
|
30
|
-
import { indexProject } from '../domain/bm25'
|
|
31
|
-
import { affectedDomains, propagateChanges } from '../domain/change-propagator'
|
|
32
|
-
import { detectChanges, hasHashRegistry, saveHashes } from '../domain/file-hasher'
|
|
33
|
-
import { indexCoChanges } from '../domain/git-cochange'
|
|
34
|
-
import { indexImports } from '../domain/import-graph'
|
|
35
|
-
import { getErrorMessage } from '../errors'
|
|
36
|
-
import commandInstaller from '../infrastructure/command-installer'
|
|
37
|
-
import configManager from '../infrastructure/config-manager'
|
|
38
|
-
import pathManager from '../infrastructure/path-manager'
|
|
39
|
-
import { analysisStorage } from '../storage/analysis-storage'
|
|
40
|
-
import { archiveStorage } from '../storage/archive-storage'
|
|
41
|
-
import { ideasStorage } from '../storage/ideas-storage'
|
|
42
|
-
import { metricsStorage } from '../storage/metrics-storage'
|
|
43
|
-
import { migrateJsonToSqlite } from '../storage/migrate-json'
|
|
44
|
-
import { queueStorage } from '../storage/queue-storage'
|
|
45
|
-
import { shippedStorage } from '../storage/shipped-storage'
|
|
46
|
-
import { stateStorage } from '../storage/state-storage'
|
|
47
|
-
import type {
|
|
48
|
-
GitData,
|
|
49
|
-
IncrementalInfo,
|
|
50
|
-
ProjectCommands,
|
|
51
|
-
ProjectStats,
|
|
52
|
-
ProjectSyncResult,
|
|
53
|
-
StackDetection,
|
|
54
|
-
SyncAgentInfo,
|
|
55
|
-
SyncMetrics,
|
|
56
|
-
SyncOptions,
|
|
57
|
-
VerificationReport,
|
|
58
|
-
} from '../types'
|
|
59
|
-
import { type ContextSources, defaultSources, type SourceInfo } from '../utils/citations'
|
|
60
|
-
import * as dateHelper from '../utils/date-helper'
|
|
61
|
-
import log from '../utils/logger'
|
|
62
|
-
import { ContextFileGenerator } from './context-generator'
|
|
63
|
-
import { localStateGenerator } from './local-state-generator'
|
|
64
|
-
import { memoryService } from './memory-service'
|
|
65
|
-
import { skillInstaller } from './skill-installer'
|
|
66
|
-
import { StackDetector } from './stack-detector'
|
|
67
|
-
import { syncVerifier } from './sync-verifier'
|
|
68
|
-
|
|
69
|
-
const execAsync = promisify(exec)
|
|
70
|
-
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// SYNC SERVICE
|
|
73
|
-
// ============================================================================
|
|
74
|
-
|
|
75
|
-
class SyncService {
|
|
76
|
-
private projectPath: string
|
|
77
|
-
private projectId: string | null = null
|
|
78
|
-
private globalPath: string = ''
|
|
79
|
-
private cliVersion: string = '0.0.0'
|
|
80
|
-
/** Task feedback context for agent generation (PRJ-272) */
|
|
81
|
-
private taskFeedbackContext?: {
|
|
82
|
-
patternsDiscovered: string[]
|
|
83
|
-
knownGotchas: string[]
|
|
84
|
-
agentAccuracy: Array<{ agent: string; rating: string; note?: string }>
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
constructor() {
|
|
88
|
-
this.projectPath = process.cwd()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Main sync method - does everything in one call
|
|
93
|
-
*/
|
|
94
|
-
async sync(
|
|
95
|
-
projectPath: string = process.cwd(),
|
|
96
|
-
options: SyncOptions = {}
|
|
97
|
-
): Promise<ProjectSyncResult> {
|
|
98
|
-
this.projectPath = projectPath
|
|
99
|
-
const startTime = Date.now()
|
|
100
|
-
|
|
101
|
-
// Resolve AI tools: supports 'auto', 'all', or specific list
|
|
102
|
-
// Default behavior: claude + any detected IDE tools (.cursor/, .windsurf/)
|
|
103
|
-
let aiToolIds: string[]
|
|
104
|
-
if (!options.aiTools || options.aiTools.length === 0) {
|
|
105
|
-
// Start with default CLI tools and add detected IDE tools
|
|
106
|
-
const detectedIdeTools = (await detectInstalledTools(projectPath)).filter(
|
|
107
|
-
(id) => !DEFAULT_AI_TOOLS.includes(id)
|
|
108
|
-
)
|
|
109
|
-
aiToolIds = [...DEFAULT_AI_TOOLS, ...detectedIdeTools]
|
|
110
|
-
} else if (options.aiTools[0] === 'auto') {
|
|
111
|
-
aiToolIds = await detectInstalledTools(projectPath)
|
|
112
|
-
if (aiToolIds.length === 0) aiToolIds = ['claude'] // fallback
|
|
113
|
-
} else if (options.aiTools[0] === 'all') {
|
|
114
|
-
aiToolIds = await resolveToolIds('all', projectPath)
|
|
115
|
-
} else {
|
|
116
|
-
aiToolIds = options.aiTools
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
// 1. Get project config
|
|
121
|
-
this.projectId = await configManager.getProjectId(projectPath)
|
|
122
|
-
if (!this.projectId) {
|
|
123
|
-
return {
|
|
124
|
-
success: false,
|
|
125
|
-
projectId: '',
|
|
126
|
-
cliVersion: '',
|
|
127
|
-
git: this.emptyGitData(),
|
|
128
|
-
stats: this.emptyStats(),
|
|
129
|
-
commands: this.emptyCommands(),
|
|
130
|
-
stack: this.emptyStack(),
|
|
131
|
-
agents: [],
|
|
132
|
-
skills: [],
|
|
133
|
-
skillsInstalled: [],
|
|
134
|
-
contextFiles: [],
|
|
135
|
-
aiTools: [],
|
|
136
|
-
error: 'No prjct project. Run p. init first.',
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.globalPath = pathManager.getGlobalProjectPath(this.projectId)
|
|
141
|
-
this.cliVersion = await this.getCliVersion()
|
|
142
|
-
|
|
143
|
-
// 2. Ensure directories exist (non-blocking)
|
|
144
|
-
const ensureDirsPromise = this.ensureDirectories()
|
|
145
|
-
|
|
146
|
-
// 2b. Auto-migrate JSON → SQLite (idempotent, skips if already done)
|
|
147
|
-
await ensureDirsPromise
|
|
148
|
-
await migrateJsonToSqlite(this.projectId)
|
|
149
|
-
|
|
150
|
-
// 3. Gather all data IN PARALLEL (30-50% speedup)
|
|
151
|
-
// These operations are independent and can run concurrently
|
|
152
|
-
const [git, stats, commands, stack] = await Promise.all([
|
|
153
|
-
this.analyzeGit(),
|
|
154
|
-
this.gatherStats(),
|
|
155
|
-
this.detectCommands(),
|
|
156
|
-
this.detectStack(),
|
|
157
|
-
])
|
|
158
|
-
|
|
159
|
-
// 3a. Incremental change detection
|
|
160
|
-
// Determine if we can skip expensive operations based on file changes
|
|
161
|
-
const isFullSync = options.full === true
|
|
162
|
-
let incrementalInfo: IncrementalInfo | undefined
|
|
163
|
-
let shouldRebuildIndexes = true
|
|
164
|
-
let shouldRegenerateAgents = true
|
|
165
|
-
let changedDomains = new Set<string>()
|
|
166
|
-
|
|
167
|
-
if (!isFullSync && hasHashRegistry(this.projectId!)) {
|
|
168
|
-
try {
|
|
169
|
-
const { diff, currentHashes } = await detectChanges(this.projectPath, this.projectId!)
|
|
170
|
-
const totalChanged = diff.added.length + diff.modified.length + diff.deleted.length
|
|
171
|
-
|
|
172
|
-
if (totalChanged === 0 && !options.changedFiles?.length) {
|
|
173
|
-
// Nothing changed — skip expensive rebuilds
|
|
174
|
-
shouldRebuildIndexes = false
|
|
175
|
-
shouldRegenerateAgents = false
|
|
176
|
-
incrementalInfo = {
|
|
177
|
-
isIncremental: true,
|
|
178
|
-
filesChanged: 0,
|
|
179
|
-
filesUnchanged: diff.unchanged.length,
|
|
180
|
-
indexesRebuilt: false,
|
|
181
|
-
agentsRegenerated: false,
|
|
182
|
-
affectedDomains: [],
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
// Some files changed — propagate through import graph
|
|
186
|
-
const propagated = propagateChanges(diff, this.projectId!)
|
|
187
|
-
changedDomains = affectedDomains(propagated.allAffected)
|
|
188
|
-
|
|
189
|
-
// Only rebuild indexes if source files changed
|
|
190
|
-
const sourceExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'])
|
|
191
|
-
const hasSourceChanges = propagated.allAffected.some((f) => {
|
|
192
|
-
const ext = f.substring(f.lastIndexOf('.'))
|
|
193
|
-
return sourceExtensions.has(ext)
|
|
194
|
-
})
|
|
195
|
-
shouldRebuildIndexes = hasSourceChanges
|
|
196
|
-
|
|
197
|
-
// Only regenerate agents if stack/domains might have changed
|
|
198
|
-
// (new files added to previously empty domains, or config files changed)
|
|
199
|
-
const configChanged = propagated.directlyChanged.some(
|
|
200
|
-
(f) =>
|
|
201
|
-
f === 'package.json' ||
|
|
202
|
-
f === 'tsconfig.json' ||
|
|
203
|
-
f.includes('Dockerfile') ||
|
|
204
|
-
f.includes('docker-compose')
|
|
205
|
-
)
|
|
206
|
-
shouldRegenerateAgents = configChanged
|
|
207
|
-
|
|
208
|
-
incrementalInfo = {
|
|
209
|
-
isIncremental: true,
|
|
210
|
-
filesChanged: totalChanged,
|
|
211
|
-
filesUnchanged: diff.unchanged.length,
|
|
212
|
-
indexesRebuilt: shouldRebuildIndexes,
|
|
213
|
-
agentsRegenerated: shouldRegenerateAgents,
|
|
214
|
-
affectedDomains: Array.from(changedDomains),
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Save updated hashes AFTER determining diff (commit new state)
|
|
219
|
-
saveHashes(this.projectId!, currentHashes)
|
|
220
|
-
} catch (error) {
|
|
221
|
-
log.debug('Incremental detection failed, falling back to full sync', {
|
|
222
|
-
error: getErrorMessage(error),
|
|
223
|
-
})
|
|
224
|
-
// Fall through to full sync
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
// First sync or --full flag: compute and save hashes for next time
|
|
228
|
-
try {
|
|
229
|
-
const { currentHashes } = await detectChanges(this.projectPath, this.projectId!)
|
|
230
|
-
saveHashes(this.projectId!, currentHashes)
|
|
231
|
-
} catch (error) {
|
|
232
|
-
log.debug('Hash computation failed (non-critical)', {
|
|
233
|
-
error: getErrorMessage(error),
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// 3b. Build file-ranking indexes IN PARALLEL (BM25, import graph, co-change)
|
|
239
|
-
// Skip if no source files changed (incremental optimization)
|
|
240
|
-
if (shouldRebuildIndexes) {
|
|
241
|
-
try {
|
|
242
|
-
await Promise.all([
|
|
243
|
-
indexProject(this.projectPath, this.projectId!),
|
|
244
|
-
indexImports(this.projectPath, this.projectId!),
|
|
245
|
-
indexCoChanges(this.projectPath, this.projectId!),
|
|
246
|
-
])
|
|
247
|
-
} catch (error) {
|
|
248
|
-
log.debug('File ranking index build failed (non-critical)', {
|
|
249
|
-
error: getErrorMessage(error),
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 4. Generate all files (depends on gathered data)
|
|
255
|
-
// Load task feedback for agent generation (PRJ-272)
|
|
256
|
-
let taskFeedbackContext:
|
|
257
|
-
| {
|
|
258
|
-
patternsDiscovered: string[]
|
|
259
|
-
knownGotchas: string[]
|
|
260
|
-
agentAccuracy: Array<{ agent: string; rating: string; note?: string }>
|
|
261
|
-
}
|
|
262
|
-
| undefined
|
|
263
|
-
if (shouldRegenerateAgents) {
|
|
264
|
-
try {
|
|
265
|
-
const feedback = await stateStorage.getAggregatedFeedback(this.projectId!)
|
|
266
|
-
if (
|
|
267
|
-
feedback.patternsDiscovered.length > 0 ||
|
|
268
|
-
feedback.knownGotchas.length > 0 ||
|
|
269
|
-
feedback.agentAccuracy.length > 0
|
|
270
|
-
) {
|
|
271
|
-
taskFeedbackContext = feedback
|
|
272
|
-
}
|
|
273
|
-
} catch {
|
|
274
|
-
// Feedback loading failure should not block agent generation
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Skip agent regeneration if nothing structural changed
|
|
279
|
-
const agents = shouldRegenerateAgents
|
|
280
|
-
? await this.generateAgents(stack, stats, taskFeedbackContext)
|
|
281
|
-
: await this.loadExistingAgents()
|
|
282
|
-
const skills = this.configureSkills(agents)
|
|
283
|
-
const skillsInstalled = shouldRegenerateAgents ? await this.autoInstallSkills(agents) : []
|
|
284
|
-
const sources = this.buildSources(stats, commands)
|
|
285
|
-
const contextFiles = await this.generateContextFiles(git, stats, commands, agents, sources)
|
|
286
|
-
|
|
287
|
-
// 5. Generate AI tool context files (multi-agent output)
|
|
288
|
-
const projectContext: ProjectContext = {
|
|
289
|
-
projectId: this.projectId,
|
|
290
|
-
name: stats.name,
|
|
291
|
-
version: stats.version,
|
|
292
|
-
ecosystem: stats.ecosystem,
|
|
293
|
-
projectType: stats.projectType,
|
|
294
|
-
languages: stats.languages,
|
|
295
|
-
frameworks: stats.frameworks,
|
|
296
|
-
repoPath: this.projectPath,
|
|
297
|
-
branch: git.branch,
|
|
298
|
-
fileCount: stats.fileCount,
|
|
299
|
-
commits: git.commits,
|
|
300
|
-
hasChanges: git.hasChanges,
|
|
301
|
-
commands,
|
|
302
|
-
agents: {
|
|
303
|
-
workflow: agents.filter((a) => a.type === 'workflow').map((a) => a.name),
|
|
304
|
-
domain: agents.filter((a) => a.type === 'domain').map((a) => a.name),
|
|
305
|
-
},
|
|
306
|
-
sources,
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const aiToolResults = await generateAIToolContexts(
|
|
310
|
-
projectContext,
|
|
311
|
-
this.globalPath,
|
|
312
|
-
this.projectPath,
|
|
313
|
-
aiToolIds
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
// 6-8. Update files IN PARALLEL (write to different files)
|
|
317
|
-
await Promise.all([
|
|
318
|
-
this.updateProjectJson(git, stats),
|
|
319
|
-
this.updateStateJson(stats, stack),
|
|
320
|
-
this.logToMemory(git, stats),
|
|
321
|
-
this.saveDraftAnalysis(git, stats, stack),
|
|
322
|
-
])
|
|
323
|
-
|
|
324
|
-
// 9. Record metrics for value dashboard
|
|
325
|
-
const duration = Date.now() - startTime
|
|
326
|
-
const syncMetrics = await this.recordSyncMetrics(stats, contextFiles, agents, duration)
|
|
327
|
-
|
|
328
|
-
// 9b. Archive stale data (PRJ-267)
|
|
329
|
-
await this.archiveStaleData()
|
|
330
|
-
|
|
331
|
-
// 10. Update global config and commands (CLI does EVERYTHING)
|
|
332
|
-
// This ensures `prjct sync` from terminal updates global CLAUDE.md and commands
|
|
333
|
-
await commandInstaller.installGlobalConfig()
|
|
334
|
-
await commandInstaller.syncCommands()
|
|
335
|
-
|
|
336
|
-
// 11. Run verification checks (built-in + custom from config)
|
|
337
|
-
let verification: VerificationReport | undefined
|
|
338
|
-
try {
|
|
339
|
-
const localConfig = await configManager.readConfig(this.projectPath)
|
|
340
|
-
verification = await syncVerifier.verify(
|
|
341
|
-
this.projectPath,
|
|
342
|
-
this.globalPath,
|
|
343
|
-
localConfig?.verification
|
|
344
|
-
)
|
|
345
|
-
} catch (error) {
|
|
346
|
-
log.debug('Verification failed (non-critical)', { error: getErrorMessage(error) })
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
success: true,
|
|
351
|
-
projectId: this.projectId,
|
|
352
|
-
cliVersion: this.cliVersion,
|
|
353
|
-
git,
|
|
354
|
-
stats,
|
|
355
|
-
commands,
|
|
356
|
-
stack,
|
|
357
|
-
agents,
|
|
358
|
-
skills,
|
|
359
|
-
skillsInstalled,
|
|
360
|
-
contextFiles,
|
|
361
|
-
aiTools: aiToolResults.map((r) => ({
|
|
362
|
-
toolId: r.toolId,
|
|
363
|
-
outputFile: r.outputFile,
|
|
364
|
-
success: r.success,
|
|
365
|
-
})),
|
|
366
|
-
syncMetrics,
|
|
367
|
-
verification,
|
|
368
|
-
incremental: incrementalInfo,
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {
|
|
371
|
-
return {
|
|
372
|
-
success: false,
|
|
373
|
-
projectId: this.projectId || '',
|
|
374
|
-
cliVersion: this.cliVersion,
|
|
375
|
-
git: this.emptyGitData(),
|
|
376
|
-
stats: this.emptyStats(),
|
|
377
|
-
commands: this.emptyCommands(),
|
|
378
|
-
stack: this.emptyStack(),
|
|
379
|
-
agents: [],
|
|
380
|
-
skills: [],
|
|
381
|
-
skillsInstalled: [],
|
|
382
|
-
contextFiles: [],
|
|
383
|
-
aiTools: [],
|
|
384
|
-
error: getErrorMessage(error),
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ==========================================================================
|
|
390
|
-
// DIRECTORY SETUP
|
|
391
|
-
// ==========================================================================
|
|
392
|
-
|
|
393
|
-
private async ensureDirectories(): Promise<void> {
|
|
394
|
-
const dirs = ['storage', 'context', 'agents', 'memory', 'analysis', 'config', 'sync']
|
|
395
|
-
// Create all directories IN PARALLEL
|
|
396
|
-
await Promise.all(
|
|
397
|
-
dirs.map((dir) => fs.mkdir(path.join(this.globalPath, dir), { recursive: true }))
|
|
398
|
-
)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// ==========================================================================
|
|
402
|
-
// GIT ANALYSIS
|
|
403
|
-
// ==========================================================================
|
|
404
|
-
|
|
405
|
-
private async analyzeGit(): Promise<GitData> {
|
|
406
|
-
const data: GitData = {
|
|
407
|
-
branch: 'main',
|
|
408
|
-
commits: 0,
|
|
409
|
-
contributors: 0,
|
|
410
|
-
hasChanges: false,
|
|
411
|
-
stagedFiles: [],
|
|
412
|
-
modifiedFiles: [],
|
|
413
|
-
untrackedFiles: [],
|
|
414
|
-
recentCommits: [],
|
|
415
|
-
weeklyCommits: 0,
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
try {
|
|
419
|
-
// Branch
|
|
420
|
-
const { stdout: branch } = await execAsync('git branch --show-current', {
|
|
421
|
-
cwd: this.projectPath,
|
|
422
|
-
})
|
|
423
|
-
data.branch = branch.trim() || 'main'
|
|
424
|
-
|
|
425
|
-
// Total commits
|
|
426
|
-
const { stdout: commits } = await execAsync('git rev-list --count HEAD', {
|
|
427
|
-
cwd: this.projectPath,
|
|
428
|
-
})
|
|
429
|
-
data.commits = parseInt(commits.trim(), 10) || 0
|
|
430
|
-
|
|
431
|
-
// Contributors
|
|
432
|
-
const { stdout: contributors } = await execAsync('git shortlog -sn --all | wc -l', {
|
|
433
|
-
cwd: this.projectPath,
|
|
434
|
-
})
|
|
435
|
-
data.contributors = parseInt(contributors.trim(), 10) || 0
|
|
436
|
-
|
|
437
|
-
// Status
|
|
438
|
-
const { stdout: status } = await execAsync('git status --porcelain', {
|
|
439
|
-
cwd: this.projectPath,
|
|
440
|
-
})
|
|
441
|
-
const lines = status.trim().split('\n').filter(Boolean)
|
|
442
|
-
data.hasChanges = lines.length > 0
|
|
443
|
-
|
|
444
|
-
for (const line of lines) {
|
|
445
|
-
const code = line.substring(0, 2)
|
|
446
|
-
const file = line.substring(3)
|
|
447
|
-
if (code.startsWith('A') || code.startsWith('M ')) {
|
|
448
|
-
data.stagedFiles.push(file)
|
|
449
|
-
} else if (code.includes('M')) {
|
|
450
|
-
data.modifiedFiles.push(file)
|
|
451
|
-
} else if (code.startsWith('??')) {
|
|
452
|
-
data.untrackedFiles.push(file)
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Recent commits
|
|
457
|
-
const { stdout: log } = await execAsync(
|
|
458
|
-
'git log --oneline -20 --pretty=format:"%h|%s|%ad" --date=short',
|
|
459
|
-
{ cwd: this.projectPath }
|
|
460
|
-
)
|
|
461
|
-
data.recentCommits = log
|
|
462
|
-
.split('\n')
|
|
463
|
-
.filter(Boolean)
|
|
464
|
-
.map((line) => {
|
|
465
|
-
const [hash, message, date] = line.split('|')
|
|
466
|
-
return { hash, message, date }
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
// Weekly commits
|
|
470
|
-
const { stdout: weekly } = await execAsync('git log --oneline --since="1 week ago" | wc -l', {
|
|
471
|
-
cwd: this.projectPath,
|
|
472
|
-
})
|
|
473
|
-
data.weeklyCommits = parseInt(weekly.trim(), 10) || 0
|
|
474
|
-
} catch (error) {
|
|
475
|
-
log.debug('Git analysis failed (not a git repo?)', { error: getErrorMessage(error) })
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return data
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// ==========================================================================
|
|
482
|
-
// PROJECT STATS
|
|
483
|
-
// ==========================================================================
|
|
484
|
-
|
|
485
|
-
private async gatherStats(): Promise<ProjectStats> {
|
|
486
|
-
const stats: ProjectStats = {
|
|
487
|
-
fileCount: 0,
|
|
488
|
-
version: '0.0.0',
|
|
489
|
-
name: path.basename(this.projectPath),
|
|
490
|
-
ecosystem: 'unknown',
|
|
491
|
-
projectType: 'simple',
|
|
492
|
-
languages: [],
|
|
493
|
-
frameworks: [],
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Count files
|
|
497
|
-
try {
|
|
498
|
-
const { stdout } = await execAsync(
|
|
499
|
-
'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',
|
|
500
|
-
{ cwd: this.projectPath }
|
|
501
|
-
)
|
|
502
|
-
stats.fileCount = parseInt(stdout.trim(), 10) || 0
|
|
503
|
-
} catch (error) {
|
|
504
|
-
log.debug('File count failed', { path: this.projectPath, error: getErrorMessage(error) })
|
|
505
|
-
stats.fileCount = 0
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Read package.json
|
|
509
|
-
try {
|
|
510
|
-
const pkgPath = path.join(this.projectPath, 'package.json')
|
|
511
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
|
|
512
|
-
stats.version = pkg.version || '0.0.0'
|
|
513
|
-
stats.name = pkg.name || stats.name
|
|
514
|
-
stats.ecosystem = 'JavaScript'
|
|
515
|
-
|
|
516
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
517
|
-
|
|
518
|
-
// Detect frameworks
|
|
519
|
-
if (deps.react || deps['react-dom']) stats.frameworks.push('React')
|
|
520
|
-
if (deps.next) stats.frameworks.push('Next.js')
|
|
521
|
-
if (deps.vue) stats.frameworks.push('Vue')
|
|
522
|
-
if (deps.express) stats.frameworks.push('Express')
|
|
523
|
-
if (deps.hono) stats.frameworks.push('Hono')
|
|
524
|
-
if (deps['@angular/core']) stats.frameworks.push('Angular')
|
|
525
|
-
if (deps.svelte) stats.frameworks.push('Svelte')
|
|
526
|
-
|
|
527
|
-
// Detect languages
|
|
528
|
-
if (pkg.devDependencies?.typescript || (await this.fileExists('tsconfig.json'))) {
|
|
529
|
-
stats.languages.push('TypeScript')
|
|
530
|
-
} else {
|
|
531
|
-
stats.languages.push('JavaScript')
|
|
532
|
-
}
|
|
533
|
-
} catch (error) {
|
|
534
|
-
log.debug('No package.json found', { path: this.projectPath, error: getErrorMessage(error) })
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Check other ecosystems
|
|
538
|
-
if (await this.fileExists('Cargo.toml')) {
|
|
539
|
-
stats.ecosystem = 'Rust'
|
|
540
|
-
stats.languages.push('Rust')
|
|
541
|
-
}
|
|
542
|
-
if (await this.fileExists('go.mod')) {
|
|
543
|
-
stats.ecosystem = 'Go'
|
|
544
|
-
stats.languages.push('Go')
|
|
545
|
-
}
|
|
546
|
-
if ((await this.fileExists('requirements.txt')) || (await this.fileExists('pyproject.toml'))) {
|
|
547
|
-
stats.ecosystem = 'Python'
|
|
548
|
-
stats.languages.push('Python')
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Determine project type
|
|
552
|
-
if (stats.fileCount > 300 || stats.frameworks.length >= 3) {
|
|
553
|
-
stats.projectType = 'enterprise'
|
|
554
|
-
} else if (stats.fileCount > 50 || stats.frameworks.length >= 2) {
|
|
555
|
-
stats.projectType = 'complex'
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return stats
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// ==========================================================================
|
|
562
|
-
// COMMAND DETECTION
|
|
563
|
-
// ==========================================================================
|
|
564
|
-
|
|
565
|
-
private async detectCommands(): Promise<ProjectCommands> {
|
|
566
|
-
const commands: ProjectCommands = {
|
|
567
|
-
install: 'npm install',
|
|
568
|
-
run: 'npm run',
|
|
569
|
-
test: 'npm test',
|
|
570
|
-
build: 'npm run build',
|
|
571
|
-
dev: 'npm run dev',
|
|
572
|
-
lint: 'npm run lint',
|
|
573
|
-
format: 'npm run format',
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// Detect package manager
|
|
577
|
-
if (await this.fileExists('bun.lockb')) {
|
|
578
|
-
commands.install = 'bun install'
|
|
579
|
-
commands.run = 'bun run'
|
|
580
|
-
commands.test = 'bun test'
|
|
581
|
-
commands.build = 'bun run build'
|
|
582
|
-
commands.dev = 'bun run dev'
|
|
583
|
-
commands.lint = 'bun run lint'
|
|
584
|
-
commands.format = 'bun run format'
|
|
585
|
-
} else if (await this.fileExists('pnpm-lock.yaml')) {
|
|
586
|
-
commands.install = 'pnpm install'
|
|
587
|
-
commands.run = 'pnpm run'
|
|
588
|
-
commands.test = 'pnpm test'
|
|
589
|
-
commands.build = 'pnpm run build'
|
|
590
|
-
commands.dev = 'pnpm run dev'
|
|
591
|
-
commands.lint = 'pnpm run lint'
|
|
592
|
-
commands.format = 'pnpm run format'
|
|
593
|
-
} else if (await this.fileExists('yarn.lock')) {
|
|
594
|
-
commands.install = 'yarn'
|
|
595
|
-
commands.run = 'yarn'
|
|
596
|
-
commands.test = 'yarn test'
|
|
597
|
-
commands.build = 'yarn build'
|
|
598
|
-
commands.dev = 'yarn dev'
|
|
599
|
-
commands.lint = 'yarn lint'
|
|
600
|
-
commands.format = 'yarn format'
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Non-JS ecosystems
|
|
604
|
-
if (await this.fileExists('Cargo.toml')) {
|
|
605
|
-
commands.install = 'cargo build'
|
|
606
|
-
commands.run = 'cargo run'
|
|
607
|
-
commands.test = 'cargo test'
|
|
608
|
-
commands.build = 'cargo build --release'
|
|
609
|
-
commands.dev = 'cargo run'
|
|
610
|
-
commands.lint = 'cargo clippy'
|
|
611
|
-
commands.format = 'cargo fmt'
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (await this.fileExists('go.mod')) {
|
|
615
|
-
commands.install = 'go mod download'
|
|
616
|
-
commands.run = 'go run .'
|
|
617
|
-
commands.test = 'go test ./...'
|
|
618
|
-
commands.build = 'go build'
|
|
619
|
-
commands.dev = 'go run .'
|
|
620
|
-
commands.lint = 'golangci-lint run'
|
|
621
|
-
commands.format = 'go fmt ./...'
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
return commands
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// ==========================================================================
|
|
628
|
-
// SOURCE CITATIONS
|
|
629
|
-
// ==========================================================================
|
|
630
|
-
|
|
631
|
-
private buildSources(stats: ProjectStats, commands: ProjectCommands): ContextSources {
|
|
632
|
-
const sources = defaultSources()
|
|
633
|
-
|
|
634
|
-
// Determine ecosystem source file
|
|
635
|
-
const ecosystemFiles: Record<string, string> = {
|
|
636
|
-
JavaScript: 'package.json',
|
|
637
|
-
Rust: 'Cargo.toml',
|
|
638
|
-
Go: 'go.mod',
|
|
639
|
-
Python: 'pyproject.toml',
|
|
640
|
-
}
|
|
641
|
-
const ecosystemFile = ecosystemFiles[stats.ecosystem] || 'filesystem'
|
|
642
|
-
const detected = (file: string): SourceInfo => ({ file, type: 'detected' })
|
|
643
|
-
const inferred = (file: string): SourceInfo => ({ file, type: 'inferred' })
|
|
644
|
-
|
|
645
|
-
sources.ecosystem = detected(ecosystemFile)
|
|
646
|
-
sources.name = detected(ecosystemFile)
|
|
647
|
-
sources.version = detected(ecosystemFile)
|
|
648
|
-
sources.languages = detected(ecosystemFile)
|
|
649
|
-
sources.frameworks = detected(ecosystemFile)
|
|
650
|
-
|
|
651
|
-
// Commands source is the lock file or ecosystem file
|
|
652
|
-
if (commands.install.startsWith('bun')) {
|
|
653
|
-
sources.commands = detected('bun.lockb')
|
|
654
|
-
} else if (commands.install.startsWith('pnpm')) {
|
|
655
|
-
sources.commands = detected('pnpm-lock.yaml')
|
|
656
|
-
} else if (commands.install === 'yarn') {
|
|
657
|
-
sources.commands = detected('yarn.lock')
|
|
658
|
-
} else if (commands.install.startsWith('cargo')) {
|
|
659
|
-
sources.commands = detected('Cargo.toml')
|
|
660
|
-
} else if (commands.install.startsWith('go')) {
|
|
661
|
-
sources.commands = detected('go.mod')
|
|
662
|
-
} else {
|
|
663
|
-
sources.commands = detected('package.json')
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Project type is inferred from file count + framework count
|
|
667
|
-
sources.projectType = inferred('file count + frameworks')
|
|
668
|
-
|
|
669
|
-
// Git is always from git
|
|
670
|
-
sources.git = detected('git')
|
|
671
|
-
|
|
672
|
-
return sources
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// ==========================================================================
|
|
676
|
-
// STACK DETECTION
|
|
677
|
-
// ==========================================================================
|
|
678
|
-
|
|
679
|
-
private async detectStack(): Promise<StackDetection> {
|
|
680
|
-
const detector = new StackDetector(this.projectPath)
|
|
681
|
-
return detector.detect()
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// ==========================================================================
|
|
685
|
-
// AGENT GENERATION
|
|
686
|
-
// ==========================================================================
|
|
687
|
-
|
|
688
|
-
private async generateAgents(
|
|
689
|
-
stack: StackDetection,
|
|
690
|
-
stats: ProjectStats,
|
|
691
|
-
feedbackContext?: {
|
|
692
|
-
patternsDiscovered: string[]
|
|
693
|
-
knownGotchas: string[]
|
|
694
|
-
agentAccuracy: Array<{ agent: string; rating: string; note?: string }>
|
|
695
|
-
}
|
|
696
|
-
): Promise<SyncAgentInfo[]> {
|
|
697
|
-
this.taskFeedbackContext = feedbackContext
|
|
698
|
-
const agents: SyncAgentInfo[] = []
|
|
699
|
-
const agentsPath = path.join(this.globalPath, 'agents')
|
|
700
|
-
|
|
701
|
-
// Purge old agents
|
|
702
|
-
try {
|
|
703
|
-
const files = await fs.readdir(agentsPath)
|
|
704
|
-
for (const file of files) {
|
|
705
|
-
if (file.endsWith('.md')) {
|
|
706
|
-
await fs.unlink(path.join(agentsPath, file))
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
} catch (error) {
|
|
710
|
-
log.debug('Failed to purge old agents', { path: agentsPath, error: getErrorMessage(error) })
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Workflow agents (always generated) - IN PARALLEL
|
|
714
|
-
const workflowAgents = ['prjct-workflow', 'prjct-planner', 'prjct-shipper']
|
|
715
|
-
await Promise.all(workflowAgents.map((name) => this.generateWorkflowAgent(name, agentsPath)))
|
|
716
|
-
for (const name of workflowAgents) {
|
|
717
|
-
agents.push({ name, type: 'workflow' })
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Domain agents (based on stack) - COLLECT AND GENERATE IN PARALLEL
|
|
721
|
-
const domainAgentsToGenerate: { name: string; skill?: string }[] = []
|
|
722
|
-
|
|
723
|
-
if (stack.hasFrontend) {
|
|
724
|
-
domainAgentsToGenerate.push({ name: 'frontend', skill: 'javascript-typescript' })
|
|
725
|
-
domainAgentsToGenerate.push({ name: 'uxui', skill: 'frontend-design' })
|
|
726
|
-
}
|
|
727
|
-
if (stack.hasBackend) {
|
|
728
|
-
domainAgentsToGenerate.push({ name: 'backend', skill: 'javascript-typescript' })
|
|
729
|
-
}
|
|
730
|
-
if (stack.hasDatabase) {
|
|
731
|
-
domainAgentsToGenerate.push({ name: 'database' })
|
|
732
|
-
}
|
|
733
|
-
if (stack.hasTesting) {
|
|
734
|
-
domainAgentsToGenerate.push({ name: 'testing', skill: 'developer-kit' })
|
|
735
|
-
}
|
|
736
|
-
if (stack.hasDocker) {
|
|
737
|
-
domainAgentsToGenerate.push({ name: 'devops', skill: 'developer-kit' })
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// Generate all domain agents IN PARALLEL
|
|
741
|
-
await Promise.all(
|
|
742
|
-
domainAgentsToGenerate.map((agent) =>
|
|
743
|
-
this.generateDomainAgent(agent.name, agentsPath, stats, stack)
|
|
744
|
-
)
|
|
745
|
-
)
|
|
746
|
-
|
|
747
|
-
// Add to agents list
|
|
748
|
-
for (const agent of domainAgentsToGenerate) {
|
|
749
|
-
agents.push({ name: agent.name, type: 'domain', skill: agent.skill })
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
return agents
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* Load existing agent info from disk (for incremental sync when agents don't need regeneration).
|
|
757
|
-
* Reads the agents directory and returns metadata without regenerating files.
|
|
758
|
-
*/
|
|
759
|
-
private async loadExistingAgents(): Promise<SyncAgentInfo[]> {
|
|
760
|
-
const agentsPath = path.join(this.globalPath, 'agents')
|
|
761
|
-
const agents: SyncAgentInfo[] = []
|
|
762
|
-
|
|
763
|
-
try {
|
|
764
|
-
const files = await fs.readdir(agentsPath)
|
|
765
|
-
const workflowNames = new Set(['prjct-workflow', 'prjct-planner', 'prjct-shipper'])
|
|
766
|
-
|
|
767
|
-
for (const file of files) {
|
|
768
|
-
if (!file.endsWith('.md')) continue
|
|
769
|
-
const name = file.replace('.md', '')
|
|
770
|
-
const type = workflowNames.has(name) ? ('workflow' as const) : ('domain' as const)
|
|
771
|
-
agents.push({ name, type })
|
|
772
|
-
}
|
|
773
|
-
} catch {
|
|
774
|
-
// No existing agents — fall back to generation
|
|
775
|
-
return []
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
return agents
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
/**
|
|
782
|
-
* Resolve {{> partial-name }} includes in template content.
|
|
783
|
-
* Loads partials from templates/subagents/.
|
|
784
|
-
*/
|
|
785
|
-
private async resolveTemplateIncludes(content: string): Promise<string> {
|
|
786
|
-
const includePattern = /\{\{>\s*([\w-]+)\s*\}\}/g
|
|
787
|
-
const matches = [...content.matchAll(includePattern)]
|
|
788
|
-
|
|
789
|
-
if (matches.length === 0) return content
|
|
790
|
-
|
|
791
|
-
let resolved = content
|
|
792
|
-
for (const match of matches) {
|
|
793
|
-
const partialName = match[1]
|
|
794
|
-
const partialPath = path.join(
|
|
795
|
-
__dirname,
|
|
796
|
-
'..',
|
|
797
|
-
'..',
|
|
798
|
-
'templates',
|
|
799
|
-
'subagents',
|
|
800
|
-
`${partialName}.md`
|
|
801
|
-
)
|
|
802
|
-
try {
|
|
803
|
-
const partialContent = await fs.readFile(partialPath, 'utf-8')
|
|
804
|
-
resolved = resolved.replace(match[0], partialContent.trim())
|
|
805
|
-
} catch {
|
|
806
|
-
// Partial not found — leave marker for debugging
|
|
807
|
-
resolved = resolved.replace(match[0], `<!-- partial "${partialName}" not found -->`)
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return resolved
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
private async generateWorkflowAgent(name: string, agentsPath: string): Promise<void> {
|
|
815
|
-
// Try to read template
|
|
816
|
-
let content = ''
|
|
817
|
-
try {
|
|
818
|
-
const templatePath = path.join(
|
|
819
|
-
__dirname,
|
|
820
|
-
'..',
|
|
821
|
-
'..',
|
|
822
|
-
'templates',
|
|
823
|
-
'subagents',
|
|
824
|
-
'workflow',
|
|
825
|
-
`${name}.md`
|
|
826
|
-
)
|
|
827
|
-
content = await fs.readFile(templatePath, 'utf-8')
|
|
828
|
-
content = await this.resolveTemplateIncludes(content)
|
|
829
|
-
} catch (error) {
|
|
830
|
-
log.debug('Workflow agent template not found, generating minimal', {
|
|
831
|
-
name,
|
|
832
|
-
error: getErrorMessage(error),
|
|
833
|
-
})
|
|
834
|
-
content = this.generateMinimalWorkflowAgent(name)
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
await fs.writeFile(path.join(agentsPath, `${name}.md`), content, 'utf-8')
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
private async generateDomainAgent(
|
|
841
|
-
name: string,
|
|
842
|
-
agentsPath: string,
|
|
843
|
-
stats: ProjectStats,
|
|
844
|
-
stack: StackDetection
|
|
845
|
-
): Promise<void> {
|
|
846
|
-
// Try to read template
|
|
847
|
-
let content = ''
|
|
848
|
-
try {
|
|
849
|
-
const templatePath = path.join(
|
|
850
|
-
__dirname,
|
|
851
|
-
'..',
|
|
852
|
-
'..',
|
|
853
|
-
'templates',
|
|
854
|
-
'subagents',
|
|
855
|
-
'domain',
|
|
856
|
-
`${name}.md`
|
|
857
|
-
)
|
|
858
|
-
content = await fs.readFile(templatePath, 'utf-8')
|
|
859
|
-
|
|
860
|
-
// Resolve includes before variable replacement
|
|
861
|
-
content = await this.resolveTemplateIncludes(content)
|
|
862
|
-
|
|
863
|
-
// Inject project-specific context
|
|
864
|
-
content = content.replace('{projectName}', stats.name)
|
|
865
|
-
content = content.replace('{frameworks}', stack.frameworks.join(', ') || 'None detected')
|
|
866
|
-
content = content.replace('{ecosystem}', stats.ecosystem)
|
|
867
|
-
} catch (error) {
|
|
868
|
-
log.debug('Domain agent template not found, generating minimal', {
|
|
869
|
-
name,
|
|
870
|
-
error: getErrorMessage(error),
|
|
871
|
-
})
|
|
872
|
-
content = this.generateMinimalDomainAgent(name, stats, stack)
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// Inject task feedback learnings (PRJ-272)
|
|
876
|
-
content = this.injectFeedbackSection(content, name)
|
|
877
|
-
|
|
878
|
-
await fs.writeFile(path.join(agentsPath, `${name}.md`), content, 'utf-8')
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Inject a "Recent Learnings" section into agent content from task feedback (PRJ-272)
|
|
883
|
-
*/
|
|
884
|
-
private injectFeedbackSection(content: string, agentName: string): string {
|
|
885
|
-
if (!this.taskFeedbackContext) return content
|
|
886
|
-
|
|
887
|
-
const { patternsDiscovered, knownGotchas, agentAccuracy } = this.taskFeedbackContext
|
|
888
|
-
|
|
889
|
-
const agentNotes = agentAccuracy.filter(
|
|
890
|
-
(a) => a.agent === `${agentName}.md` || a.agent === agentName
|
|
891
|
-
)
|
|
892
|
-
|
|
893
|
-
const hasContent =
|
|
894
|
-
patternsDiscovered.length > 0 || knownGotchas.length > 0 || agentNotes.length > 0
|
|
895
|
-
if (!hasContent) return content
|
|
896
|
-
|
|
897
|
-
const lines: string[] = ['\n## Recent Learnings (from completed tasks)\n']
|
|
898
|
-
|
|
899
|
-
if (patternsDiscovered.length > 0) {
|
|
900
|
-
lines.push('### Discovered Patterns')
|
|
901
|
-
for (const pattern of patternsDiscovered) {
|
|
902
|
-
lines.push(`- ${pattern}`)
|
|
903
|
-
}
|
|
904
|
-
lines.push('')
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (knownGotchas.length > 0) {
|
|
908
|
-
lines.push('### Known Gotchas')
|
|
909
|
-
for (const gotcha of knownGotchas) {
|
|
910
|
-
lines.push(`- ${gotcha}`)
|
|
911
|
-
}
|
|
912
|
-
lines.push('')
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
if (agentNotes.length > 0) {
|
|
916
|
-
lines.push('### Agent Accuracy Notes')
|
|
917
|
-
for (const note of agentNotes) {
|
|
918
|
-
const desc = note.note ? ` — ${note.note}` : ''
|
|
919
|
-
lines.push(`- ${note.rating}${desc}`)
|
|
920
|
-
}
|
|
921
|
-
lines.push('')
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
return content + lines.join('\n')
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
private generateMinimalWorkflowAgent(name: string): string {
|
|
928
|
-
const descriptions: Record<string, string> = {
|
|
929
|
-
'prjct-workflow': 'Task lifecycle: now, done, pause, resume',
|
|
930
|
-
'prjct-planner': 'Planning: task, prd, spec, bug',
|
|
931
|
-
'prjct-shipper': 'Shipping: ship, merge, review',
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
return `---
|
|
935
|
-
name: ${name}
|
|
936
|
-
description: ${descriptions[name] || 'Workflow agent'}
|
|
937
|
-
tools: Read, Write, Glob
|
|
938
|
-
---
|
|
939
|
-
|
|
940
|
-
# ${name.toUpperCase()}
|
|
941
|
-
|
|
942
|
-
Workflow agent for prjct operations.
|
|
943
|
-
|
|
944
|
-
## Project Context
|
|
945
|
-
|
|
946
|
-
When invoked:
|
|
947
|
-
1. Read \`.prjct/prjct.config.json\` → extract \`projectId\`
|
|
948
|
-
2. Read \`~/.prjct-cli/projects/{projectId}/storage/state.json\`
|
|
949
|
-
3. Execute requested operation
|
|
950
|
-
`
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
private generateMinimalDomainAgent(
|
|
954
|
-
name: string,
|
|
955
|
-
stats: ProjectStats,
|
|
956
|
-
stack: StackDetection
|
|
957
|
-
): string {
|
|
958
|
-
return `---
|
|
959
|
-
name: ${name}
|
|
960
|
-
description: ${name.charAt(0).toUpperCase() + name.slice(1)} specialist for ${stats.name}
|
|
961
|
-
tools: Read, Write, Glob, Grep
|
|
962
|
-
skills: []
|
|
963
|
-
---
|
|
964
|
-
|
|
965
|
-
# ${name.toUpperCase()} AGENT
|
|
966
|
-
|
|
967
|
-
Domain specialist for ${name} tasks.
|
|
968
|
-
|
|
969
|
-
## Project Context
|
|
970
|
-
|
|
971
|
-
- **Project**: ${stats.name}
|
|
972
|
-
- **Ecosystem**: ${stats.ecosystem}
|
|
973
|
-
- **Frameworks**: ${stack.frameworks.join(', ') || 'None detected'}
|
|
974
|
-
|
|
975
|
-
## Your Role
|
|
976
|
-
|
|
977
|
-
You are the ${name} expert for this project. Apply best practices for the detected stack.
|
|
978
|
-
`
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// ==========================================================================
|
|
982
|
-
// SKILL CONFIGURATION
|
|
983
|
-
// ==========================================================================
|
|
984
|
-
|
|
985
|
-
private configureSkills(agents: SyncAgentInfo[]): { agent: string; skill: string }[] {
|
|
986
|
-
const skills: { agent: string; skill: string }[] = []
|
|
987
|
-
|
|
988
|
-
for (const agent of agents) {
|
|
989
|
-
if (agent.skill) {
|
|
990
|
-
skills.push({ agent: agent.name, skill: agent.skill })
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
// Write skills.json
|
|
995
|
-
const skillsConfig = {
|
|
996
|
-
projectId: this.projectId,
|
|
997
|
-
syncedAt: dateHelper.getTimestamp(),
|
|
998
|
-
skills: skills.map((s) => ({
|
|
999
|
-
name: s.skill,
|
|
1000
|
-
linkedAgents: [s.agent],
|
|
1001
|
-
})),
|
|
1002
|
-
agentSkillMap: Object.fromEntries(skills.map((s) => [s.agent, s.skill])),
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
fs.writeFile(
|
|
1006
|
-
path.join(this.globalPath, 'config', 'skills.json'),
|
|
1007
|
-
JSON.stringify(skillsConfig, null, 2),
|
|
1008
|
-
'utf-8'
|
|
1009
|
-
).catch((error) => {
|
|
1010
|
-
log.debug('Failed to write skills.json', { error: getErrorMessage(error) })
|
|
1011
|
-
})
|
|
1012
|
-
|
|
1013
|
-
return skills
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// ==========================================================================
|
|
1017
|
-
// SKILL AUTO-INSTALLATION
|
|
1018
|
-
// ==========================================================================
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* Auto-install skills from skill-mappings.json for generated agents.
|
|
1022
|
-
* Reads the mapping, checks which packages are needed, and installs missing ones.
|
|
1023
|
-
*/
|
|
1024
|
-
private async autoInstallSkills(
|
|
1025
|
-
agents: SyncAgentInfo[]
|
|
1026
|
-
): Promise<{ name: string; agent: string; status: 'installed' | 'skipped' | 'error' }[]> {
|
|
1027
|
-
const results: { name: string; agent: string; status: 'installed' | 'skipped' | 'error' }[] = []
|
|
1028
|
-
|
|
1029
|
-
try {
|
|
1030
|
-
// Load skill mappings
|
|
1031
|
-
const mappingsPath = path.join(
|
|
1032
|
-
__dirname,
|
|
1033
|
-
'..',
|
|
1034
|
-
'..',
|
|
1035
|
-
'templates',
|
|
1036
|
-
'config',
|
|
1037
|
-
'skill-mappings.json'
|
|
1038
|
-
)
|
|
1039
|
-
const mappingsContent = await fs.readFile(mappingsPath, 'utf-8')
|
|
1040
|
-
const mappings = JSON.parse(mappingsContent)
|
|
1041
|
-
const agentToSkillMap = mappings.agentToSkillMap || {}
|
|
1042
|
-
|
|
1043
|
-
// Collect all packages to install, grouped by agent
|
|
1044
|
-
const packagesToInstall: { pkg: string; agent: string }[] = []
|
|
1045
|
-
for (const agent of agents) {
|
|
1046
|
-
const mapping = agentToSkillMap[agent.name]
|
|
1047
|
-
if (mapping?.packages) {
|
|
1048
|
-
for (const pkg of mapping.packages) {
|
|
1049
|
-
packagesToInstall.push({ pkg, agent: agent.name })
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
if (packagesToInstall.length === 0) return results
|
|
1055
|
-
|
|
1056
|
-
// Install each package (check if already installed first)
|
|
1057
|
-
const skillsDir = path.join(os.homedir(), '.claude', 'skills')
|
|
1058
|
-
for (const { pkg, agent } of packagesToInstall) {
|
|
1059
|
-
// Extract skill name from package path (e.g., "anthropics/skills/frontend-design" -> "frontend-design")
|
|
1060
|
-
const skillName = pkg.split('/').pop() || pkg
|
|
1061
|
-
|
|
1062
|
-
// Check if already installed
|
|
1063
|
-
const subdirPath = path.join(skillsDir, skillName, 'SKILL.md')
|
|
1064
|
-
const flatPath = path.join(skillsDir, `${skillName}.md`)
|
|
1065
|
-
|
|
1066
|
-
let alreadyInstalled = false
|
|
1067
|
-
try {
|
|
1068
|
-
await fs.access(subdirPath)
|
|
1069
|
-
alreadyInstalled = true
|
|
1070
|
-
} catch {
|
|
1071
|
-
try {
|
|
1072
|
-
await fs.access(flatPath)
|
|
1073
|
-
alreadyInstalled = true
|
|
1074
|
-
} catch {
|
|
1075
|
-
// Not installed
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
if (alreadyInstalled) {
|
|
1080
|
-
results.push({ name: skillName, agent, status: 'skipped' })
|
|
1081
|
-
continue
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
// Install via skillInstaller (supports owner/repo format)
|
|
1085
|
-
try {
|
|
1086
|
-
// Parse package as owner/repo or owner/repo@skill format
|
|
1087
|
-
// "anthropics/skills/frontend-design" -> owner=anthropics, repo=skills, skill=frontend-design
|
|
1088
|
-
const parts = pkg.split('/')
|
|
1089
|
-
let installSource: string
|
|
1090
|
-
if (parts.length === 3) {
|
|
1091
|
-
// owner/repo/skill -> owner/repo@skill
|
|
1092
|
-
installSource = `${parts[0]}/${parts[1]}@${parts[2]}`
|
|
1093
|
-
} else {
|
|
1094
|
-
installSource = pkg
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
const installResult = await skillInstaller.install(installSource)
|
|
1098
|
-
if (installResult.installed.length > 0) {
|
|
1099
|
-
results.push({ name: skillName, agent, status: 'installed' })
|
|
1100
|
-
log.info(`Installed skill: ${skillName} for agent: ${agent}`)
|
|
1101
|
-
} else if (installResult.errors.length > 0) {
|
|
1102
|
-
results.push({ name: skillName, agent, status: 'error' })
|
|
1103
|
-
log.debug(`Failed to install skill ${skillName}`, { errors: installResult.errors })
|
|
1104
|
-
} else {
|
|
1105
|
-
results.push({ name: skillName, agent, status: 'skipped' })
|
|
1106
|
-
}
|
|
1107
|
-
} catch (error) {
|
|
1108
|
-
results.push({ name: skillName, agent, status: 'error' })
|
|
1109
|
-
log.debug(`Skill install error for ${skillName}`, { error: getErrorMessage(error) })
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
} catch (error) {
|
|
1113
|
-
log.debug('Skill auto-installation failed (non-critical)', { error: getErrorMessage(error) })
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
return results
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
// ==========================================================================
|
|
1120
|
-
// CONTEXT FILE GENERATION
|
|
1121
|
-
// ==========================================================================
|
|
1122
|
-
|
|
1123
|
-
private async generateContextFiles(
|
|
1124
|
-
git: GitData,
|
|
1125
|
-
stats: ProjectStats,
|
|
1126
|
-
commands: ProjectCommands,
|
|
1127
|
-
agents: SyncAgentInfo[],
|
|
1128
|
-
sources?: ContextSources
|
|
1129
|
-
): Promise<string[]> {
|
|
1130
|
-
const generator = new ContextFileGenerator({
|
|
1131
|
-
projectId: this.projectId!,
|
|
1132
|
-
projectPath: this.projectPath,
|
|
1133
|
-
globalPath: this.globalPath,
|
|
1134
|
-
})
|
|
1135
|
-
|
|
1136
|
-
return generator.generate(git, stats, commands, agents, sources)
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// ==========================================================================
|
|
1140
|
-
// PROJECT.JSON UPDATE
|
|
1141
|
-
// ==========================================================================
|
|
1142
|
-
|
|
1143
|
-
private async updateProjectJson(git: GitData, stats: ProjectStats): Promise<void> {
|
|
1144
|
-
const projectJsonPath = path.join(this.globalPath, 'project.json')
|
|
1145
|
-
|
|
1146
|
-
// Read existing
|
|
1147
|
-
let existing: Record<string, unknown> = {}
|
|
1148
|
-
try {
|
|
1149
|
-
existing = JSON.parse(await fs.readFile(projectJsonPath, 'utf-8'))
|
|
1150
|
-
} catch (error) {
|
|
1151
|
-
log.debug('No existing project.json', {
|
|
1152
|
-
path: projectJsonPath,
|
|
1153
|
-
error: getErrorMessage(error),
|
|
1154
|
-
})
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
const updated = {
|
|
1158
|
-
...existing,
|
|
1159
|
-
projectId: this.projectId,
|
|
1160
|
-
repoPath: this.projectPath,
|
|
1161
|
-
name: stats.name,
|
|
1162
|
-
version: stats.version,
|
|
1163
|
-
cliVersion: this.cliVersion,
|
|
1164
|
-
techStack: stats.frameworks,
|
|
1165
|
-
fileCount: stats.fileCount,
|
|
1166
|
-
commitCount: git.commits,
|
|
1167
|
-
stack: stats.ecosystem,
|
|
1168
|
-
currentBranch: git.branch,
|
|
1169
|
-
hasUncommittedChanges: git.hasChanges,
|
|
1170
|
-
createdAt: existing.createdAt || dateHelper.getTimestamp(),
|
|
1171
|
-
lastSync: dateHelper.getTimestamp(),
|
|
1172
|
-
// Staleness tracking (PRJ-120)
|
|
1173
|
-
lastSyncCommit: git.recentCommits[0]?.hash || null,
|
|
1174
|
-
lastSyncBranch: git.branch,
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
await fs.writeFile(projectJsonPath, JSON.stringify(updated, null, 2), 'utf-8')
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// ==========================================================================
|
|
1181
|
-
// STATE.JSON UPDATE
|
|
1182
|
-
// ==========================================================================
|
|
1183
|
-
|
|
1184
|
-
private async updateStateJson(stats: ProjectStats, stack: StackDetection): Promise<void> {
|
|
1185
|
-
const statePath = path.join(this.globalPath, 'storage', 'state.json')
|
|
1186
|
-
|
|
1187
|
-
// Read existing
|
|
1188
|
-
let state: Record<string, unknown> = {}
|
|
1189
|
-
try {
|
|
1190
|
-
state = JSON.parse(await fs.readFile(statePath, 'utf-8'))
|
|
1191
|
-
} catch (error) {
|
|
1192
|
-
log.debug('No existing state.json', { path: statePath, error: getErrorMessage(error) })
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
// Update with enterprise fields
|
|
1196
|
-
state.projectId = this.projectId
|
|
1197
|
-
state.stack = {
|
|
1198
|
-
language: stats.languages[0] || 'Unknown',
|
|
1199
|
-
framework: stats.frameworks[0] || null,
|
|
1200
|
-
}
|
|
1201
|
-
state.domains = {
|
|
1202
|
-
hasFrontend: stack.hasFrontend,
|
|
1203
|
-
hasBackend: stack.hasBackend,
|
|
1204
|
-
hasDatabase: stack.hasDatabase,
|
|
1205
|
-
hasTesting: stack.hasTesting,
|
|
1206
|
-
hasDocker: stack.hasDocker,
|
|
1207
|
-
}
|
|
1208
|
-
state.projectType = stats.projectType
|
|
1209
|
-
state.metrics = {
|
|
1210
|
-
totalFiles: stats.fileCount,
|
|
1211
|
-
}
|
|
1212
|
-
state.lastSync = dateHelper.getTimestamp()
|
|
1213
|
-
state.lastUpdated = dateHelper.getTimestamp()
|
|
1214
|
-
state.context = {
|
|
1215
|
-
...((state.context as Record<string, unknown>) || {}),
|
|
1216
|
-
lastSession: dateHelper.getTimestamp(),
|
|
1217
|
-
lastAction: 'Synced project',
|
|
1218
|
-
nextAction: 'Run `p. task "description"` to start working',
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8')
|
|
1222
|
-
|
|
1223
|
-
// Also generate local .prjct-state.md (PRJ-112)
|
|
1224
|
-
try {
|
|
1225
|
-
await localStateGenerator.generate(
|
|
1226
|
-
this.projectPath,
|
|
1227
|
-
state as import('../schemas/state').StateJson
|
|
1228
|
-
)
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
log.debug('Local state generation failed (optional)', { error: getErrorMessage(error) })
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
// ==========================================================================
|
|
1235
|
-
// MEMORY LOGGING
|
|
1236
|
-
// ==========================================================================
|
|
1237
|
-
|
|
1238
|
-
private async logToMemory(git: GitData, stats: ProjectStats): Promise<void> {
|
|
1239
|
-
const memoryPath = path.join(this.globalPath, 'memory', 'events.jsonl')
|
|
1240
|
-
|
|
1241
|
-
const event = {
|
|
1242
|
-
ts: dateHelper.getTimestamp(),
|
|
1243
|
-
action: 'sync',
|
|
1244
|
-
branch: git.branch,
|
|
1245
|
-
uncommitted: git.hasChanges,
|
|
1246
|
-
fileCount: stats.fileCount,
|
|
1247
|
-
commitCount: git.commits,
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
await fs.appendFile(memoryPath, `${JSON.stringify(event)}\n`, 'utf-8')
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// ==========================================================================
|
|
1254
|
-
// METRICS RECORDING
|
|
1255
|
-
// ==========================================================================
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* Record sync metrics for the value dashboard
|
|
1259
|
-
*
|
|
1260
|
-
* Calculates token savings by comparing:
|
|
1261
|
-
* - Original: Estimated tokens if we sent all source files
|
|
1262
|
-
* - Filtered: Actual tokens in generated context files
|
|
1263
|
-
*
|
|
1264
|
-
* Token estimation: ~4 chars per token (industry standard)
|
|
1265
|
-
*/
|
|
1266
|
-
private async recordSyncMetrics(
|
|
1267
|
-
stats: ProjectStats,
|
|
1268
|
-
contextFiles: string[],
|
|
1269
|
-
agents: SyncAgentInfo[],
|
|
1270
|
-
duration: number
|
|
1271
|
-
): Promise<SyncMetrics> {
|
|
1272
|
-
const CHARS_PER_TOKEN = 4
|
|
1273
|
-
|
|
1274
|
-
// Calculate filtered size (actual context files generated)
|
|
1275
|
-
let filteredChars = 0
|
|
1276
|
-
for (const file of contextFiles) {
|
|
1277
|
-
try {
|
|
1278
|
-
const filePath = path.join(this.globalPath, file)
|
|
1279
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
1280
|
-
filteredChars += content.length
|
|
1281
|
-
} catch (error) {
|
|
1282
|
-
log.debug('Context file not found for metrics', { file, error: getErrorMessage(error) })
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// Also count agent files
|
|
1287
|
-
for (const agent of agents) {
|
|
1288
|
-
try {
|
|
1289
|
-
const agentPath = path.join(this.globalPath, 'agents', `${agent.name}.md`)
|
|
1290
|
-
const content = await fs.readFile(agentPath, 'utf-8')
|
|
1291
|
-
filteredChars += content.length
|
|
1292
|
-
} catch (error) {
|
|
1293
|
-
log.debug('Agent file not found for metrics', {
|
|
1294
|
-
agent: agent.name,
|
|
1295
|
-
error: getErrorMessage(error),
|
|
1296
|
-
})
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const filteredSize = Math.floor(filteredChars / CHARS_PER_TOKEN)
|
|
1301
|
-
|
|
1302
|
-
// Estimate original size (what it would take without prjct)
|
|
1303
|
-
// Conservative estimate: avg 500 tokens per source file
|
|
1304
|
-
// Plus overhead for manually creating context
|
|
1305
|
-
const avgTokensPerFile = 500
|
|
1306
|
-
const originalSize = stats.fileCount * avgTokensPerFile
|
|
1307
|
-
|
|
1308
|
-
// Calculate compression rate
|
|
1309
|
-
const compressionRate =
|
|
1310
|
-
originalSize > 0 ? Math.max(0, (originalSize - filteredSize) / originalSize) : 0
|
|
1311
|
-
|
|
1312
|
-
// Record to storage
|
|
1313
|
-
try {
|
|
1314
|
-
await metricsStorage.recordSync(this.projectId!, {
|
|
1315
|
-
originalSize,
|
|
1316
|
-
filteredSize,
|
|
1317
|
-
duration,
|
|
1318
|
-
isWatch: false,
|
|
1319
|
-
agents: agents.filter((a) => a.type === 'domain').map((a) => a.name),
|
|
1320
|
-
})
|
|
1321
|
-
} catch (error) {
|
|
1322
|
-
log.debug('Failed to record sync metrics', { error: getErrorMessage(error) })
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
return {
|
|
1326
|
-
duration,
|
|
1327
|
-
originalSize,
|
|
1328
|
-
filteredSize,
|
|
1329
|
-
compressionRate,
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// ==========================================================================
|
|
1334
|
-
// DRAFT ANALYSIS (PRJ-263)
|
|
1335
|
-
// ==========================================================================
|
|
1336
|
-
|
|
1337
|
-
/**
|
|
1338
|
-
* Save sync results as a draft analysis.
|
|
1339
|
-
* Preserves existing sealed analysis — only the draft is overwritten.
|
|
1340
|
-
* Incorporates task feedback from completed tasks (PRJ-272).
|
|
1341
|
-
*/
|
|
1342
|
-
private async saveDraftAnalysis(
|
|
1343
|
-
git: GitData,
|
|
1344
|
-
stats: ProjectStats,
|
|
1345
|
-
_stack: StackDetection
|
|
1346
|
-
): Promise<void> {
|
|
1347
|
-
try {
|
|
1348
|
-
const commitHash = git.recentCommits[0]?.hash || null
|
|
1349
|
-
|
|
1350
|
-
// Load aggregated feedback from completed tasks (PRJ-272)
|
|
1351
|
-
let patterns: Array<{ name: string; description: string; location?: string }> = []
|
|
1352
|
-
let antiPatterns: Array<{ issue: string; file: string; suggestion: string }> = []
|
|
1353
|
-
try {
|
|
1354
|
-
const feedback = await stateStorage.getAggregatedFeedback(this.projectId!)
|
|
1355
|
-
|
|
1356
|
-
// Convert discovered patterns to CodePattern objects
|
|
1357
|
-
if (feedback.patternsDiscovered.length > 0) {
|
|
1358
|
-
patterns = feedback.patternsDiscovered.map((p) => ({
|
|
1359
|
-
name: p,
|
|
1360
|
-
description: `Discovered during task execution: ${p}`,
|
|
1361
|
-
}))
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
// Convert known gotchas (recurring issues) to AntiPattern objects
|
|
1365
|
-
if (feedback.knownGotchas.length > 0) {
|
|
1366
|
-
antiPatterns = feedback.knownGotchas.map((g) => ({
|
|
1367
|
-
issue: g,
|
|
1368
|
-
file: 'multiple',
|
|
1369
|
-
suggestion: `Recurring issue reported across tasks: ${g}`,
|
|
1370
|
-
}))
|
|
1371
|
-
}
|
|
1372
|
-
} catch {
|
|
1373
|
-
// Feedback aggregation failure should not block analysis
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
await analysisStorage.saveDraft(this.projectId!, {
|
|
1377
|
-
projectId: this.projectId!,
|
|
1378
|
-
languages: stats.languages,
|
|
1379
|
-
frameworks: stats.frameworks,
|
|
1380
|
-
configFiles: [],
|
|
1381
|
-
fileCount: stats.fileCount,
|
|
1382
|
-
patterns,
|
|
1383
|
-
antiPatterns,
|
|
1384
|
-
analyzedAt: dateHelper.getTimestamp(),
|
|
1385
|
-
status: 'draft',
|
|
1386
|
-
commitHash: commitHash ?? undefined,
|
|
1387
|
-
})
|
|
1388
|
-
} catch (error) {
|
|
1389
|
-
log.debug('Failed to save draft analysis (non-critical)', { error: getErrorMessage(error) })
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
// ==========================================================================
|
|
1394
|
-
// ARCHIVAL (PRJ-267)
|
|
1395
|
-
// ==========================================================================
|
|
1396
|
-
|
|
1397
|
-
/**
|
|
1398
|
-
* Archive stale data across all storage types.
|
|
1399
|
-
* Runs during sync to keep active storage lean.
|
|
1400
|
-
*/
|
|
1401
|
-
private async archiveStaleData(): Promise<void> {
|
|
1402
|
-
if (!this.projectId) return
|
|
1403
|
-
|
|
1404
|
-
try {
|
|
1405
|
-
const [shipped, dormant, staleQueue, stalePaused, memoryCapped] = await Promise.all([
|
|
1406
|
-
shippedStorage.archiveOldShipped(this.projectId).catch(() => 0),
|
|
1407
|
-
ideasStorage.markDormantIdeas(this.projectId).catch(() => 0),
|
|
1408
|
-
queueStorage.removeStaleCompleted(this.projectId).catch(() => 0),
|
|
1409
|
-
stateStorage.archiveStalePausedTasks(this.projectId).catch(() => []),
|
|
1410
|
-
memoryService.capEntries(this.projectId).catch(() => 0),
|
|
1411
|
-
])
|
|
1412
|
-
|
|
1413
|
-
const totalArchived =
|
|
1414
|
-
shipped + dormant + staleQueue + (stalePaused as unknown[]).length + memoryCapped
|
|
1415
|
-
|
|
1416
|
-
if (totalArchived > 0) {
|
|
1417
|
-
log.info('Archived stale data', {
|
|
1418
|
-
shipped,
|
|
1419
|
-
dormant,
|
|
1420
|
-
staleQueue,
|
|
1421
|
-
stalePaused: (stalePaused as unknown[]).length,
|
|
1422
|
-
memoryCapped,
|
|
1423
|
-
total: totalArchived,
|
|
1424
|
-
})
|
|
1425
|
-
|
|
1426
|
-
// Record archive stats
|
|
1427
|
-
const stats = archiveStorage.getStats(this.projectId)
|
|
1428
|
-
log.debug('Archive stats', stats)
|
|
1429
|
-
}
|
|
1430
|
-
} catch (error) {
|
|
1431
|
-
log.debug('Archival failed (non-critical)', { error: getErrorMessage(error) })
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
// ==========================================================================
|
|
1436
|
-
// HELPERS
|
|
1437
|
-
// ==========================================================================
|
|
1438
|
-
|
|
1439
|
-
private async fileExists(filename: string): Promise<boolean> {
|
|
1440
|
-
try {
|
|
1441
|
-
await fs.access(path.join(this.projectPath, filename))
|
|
1442
|
-
return true
|
|
1443
|
-
} catch (error) {
|
|
1444
|
-
log.debug('File not found', { filename, error: getErrorMessage(error) })
|
|
1445
|
-
return false
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
private async getCliVersion(): Promise<string> {
|
|
1450
|
-
try {
|
|
1451
|
-
// Try to read from package.json in the module
|
|
1452
|
-
const pkgPath = path.join(__dirname, '..', '..', 'package.json')
|
|
1453
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
|
|
1454
|
-
return pkg.version || '0.0.0'
|
|
1455
|
-
} catch (error) {
|
|
1456
|
-
log.debug('Failed to read CLI version', { error: getErrorMessage(error) })
|
|
1457
|
-
return '0.0.0'
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
// Empty data structures for error cases
|
|
1462
|
-
private emptyGitData(): GitData {
|
|
1463
|
-
return {
|
|
1464
|
-
branch: 'main',
|
|
1465
|
-
commits: 0,
|
|
1466
|
-
contributors: 0,
|
|
1467
|
-
hasChanges: false,
|
|
1468
|
-
stagedFiles: [],
|
|
1469
|
-
modifiedFiles: [],
|
|
1470
|
-
untrackedFiles: [],
|
|
1471
|
-
recentCommits: [],
|
|
1472
|
-
weeklyCommits: 0,
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
private emptyStats(): ProjectStats {
|
|
1477
|
-
return {
|
|
1478
|
-
fileCount: 0,
|
|
1479
|
-
version: '0.0.0',
|
|
1480
|
-
name: 'unknown',
|
|
1481
|
-
ecosystem: 'unknown',
|
|
1482
|
-
projectType: 'simple',
|
|
1483
|
-
languages: [],
|
|
1484
|
-
frameworks: [],
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
private emptyCommands(): ProjectCommands {
|
|
1489
|
-
return {
|
|
1490
|
-
install: 'npm install',
|
|
1491
|
-
run: 'npm run',
|
|
1492
|
-
test: 'npm test',
|
|
1493
|
-
build: 'npm run build',
|
|
1494
|
-
dev: 'npm run dev',
|
|
1495
|
-
lint: 'npm run lint',
|
|
1496
|
-
format: 'npm run format',
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
private emptyStack(): StackDetection {
|
|
1501
|
-
return {
|
|
1502
|
-
hasFrontend: false,
|
|
1503
|
-
hasBackend: false,
|
|
1504
|
-
hasDatabase: false,
|
|
1505
|
-
hasDocker: false,
|
|
1506
|
-
hasTesting: false,
|
|
1507
|
-
frontendType: null,
|
|
1508
|
-
frameworks: [],
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
export const syncService = new SyncService()
|
|
1514
|
-
export { SyncService }
|
|
1515
|
-
export type { ProjectSyncResult as SyncResult } from '../types'
|