prjct-cli 1.22.0 → 1.23.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 +147 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct.mjs +917 -35845
- package/dist/bin/prjct.mjs.map +7 -0
- package/dist/cli/linear.mjs +16 -0
- package/dist/cli/linear.mjs.map +7 -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,1274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analysis Commands: analyze, sync, and related helpers
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import fs from 'node:fs/promises'
|
|
6
|
-
import path from 'node:path'
|
|
7
|
-
import prompts from 'prompts'
|
|
8
|
-
import memorySystem from '../agentic/memory-system'
|
|
9
|
-
import { generateContext } from '../context/generator'
|
|
10
|
-
import analyzer from '../domain/analyzer'
|
|
11
|
-
import commandInstaller from '../infrastructure/command-installer'
|
|
12
|
-
import { formatCost } from '../schemas/metrics'
|
|
13
|
-
import { createStalenessChecker, memoryService, syncService } from '../services'
|
|
14
|
-
import { formatDiffPreview, formatFullDiff, generateSyncDiff } from '../services/diff-generator'
|
|
15
|
-
import { analysisStorage } from '../storage/analysis-storage'
|
|
16
|
-
import { metricsStorage } from '../storage/metrics-storage'
|
|
17
|
-
import type { AnalyzeOptions, CommandResult, ProjectContext } from '../types'
|
|
18
|
-
import { getErrorMessage } from '../types/fs'
|
|
19
|
-
import { showNextSteps } from '../utils/next-steps'
|
|
20
|
-
import out from '../utils/output'
|
|
21
|
-
import {
|
|
22
|
-
configManager,
|
|
23
|
-
contextBuilder,
|
|
24
|
-
dateHelper,
|
|
25
|
-
PrjctCommandsBase,
|
|
26
|
-
pathManager,
|
|
27
|
-
toolRegistry,
|
|
28
|
-
} from './base'
|
|
29
|
-
|
|
30
|
-
export class AnalysisCommands extends PrjctCommandsBase {
|
|
31
|
-
/**
|
|
32
|
-
* /p:analyze - Analyze repository and generate summary
|
|
33
|
-
*/
|
|
34
|
-
async analyze(
|
|
35
|
-
options: AnalyzeOptions = {},
|
|
36
|
-
projectPath: string = process.cwd()
|
|
37
|
-
): Promise<CommandResult> {
|
|
38
|
-
try {
|
|
39
|
-
await this.initializeAgent()
|
|
40
|
-
|
|
41
|
-
console.log('🔍 Analyzing repository...\n')
|
|
42
|
-
|
|
43
|
-
analyzer.init(projectPath)
|
|
44
|
-
|
|
45
|
-
const context = (await contextBuilder.build(projectPath, options)) as ProjectContext
|
|
46
|
-
|
|
47
|
-
const analysisData = {
|
|
48
|
-
packageJson: await analyzer.readPackageJson(),
|
|
49
|
-
cargoToml: await analyzer.readCargoToml(),
|
|
50
|
-
goMod: await analyzer.readGoMod(),
|
|
51
|
-
requirements: await analyzer.readRequirements(),
|
|
52
|
-
directories: await analyzer.listDirectories(),
|
|
53
|
-
fileCount: await analyzer.countFiles(),
|
|
54
|
-
gitStats: await analyzer.getGitStats(),
|
|
55
|
-
gitLog: await analyzer.getGitLog(20),
|
|
56
|
-
hasDockerfile: await analyzer.fileExists('Dockerfile'),
|
|
57
|
-
hasDockerCompose: await analyzer.fileExists('docker-compose.yml'),
|
|
58
|
-
hasReadme: await analyzer.fileExists('README.md'),
|
|
59
|
-
hasTsconfig: await analyzer.fileExists('tsconfig.json'),
|
|
60
|
-
hasViteConfig:
|
|
61
|
-
(await analyzer.fileExists('vite.config.ts')) ||
|
|
62
|
-
(await analyzer.fileExists('vite.config.js')),
|
|
63
|
-
hasNextConfig:
|
|
64
|
-
(await analyzer.fileExists('next.config.js')) ||
|
|
65
|
-
(await analyzer.fileExists('next.config.mjs')),
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const summary = this._generateAnalysisSummary(analysisData, projectPath)
|
|
69
|
-
|
|
70
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
71
|
-
const summaryPath =
|
|
72
|
-
context.paths.analysis || pathManager.getFilePath(projectId!, 'analysis', 'repo-summary.md')
|
|
73
|
-
|
|
74
|
-
await toolRegistry.get('Write')!(summaryPath, summary)
|
|
75
|
-
|
|
76
|
-
await this.logToMemory(projectPath, 'repository_analyzed', {
|
|
77
|
-
timestamp: dateHelper.getTimestamp(),
|
|
78
|
-
fileCount: analysisData.fileCount,
|
|
79
|
-
gitCommits: analysisData.gitStats.totalCommits,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
await generateContext(projectId!, projectPath)
|
|
83
|
-
|
|
84
|
-
const aiProvider = require('../infrastructure/ai-provider')
|
|
85
|
-
const activeProvider = await aiProvider.getActiveProvider()
|
|
86
|
-
|
|
87
|
-
const globalConfigResult = await commandInstaller.installGlobalConfig()
|
|
88
|
-
if (globalConfigResult.success) {
|
|
89
|
-
console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log('✅ Analysis complete!\n')
|
|
93
|
-
console.log('📄 Full report: analysis/repo-summary.md')
|
|
94
|
-
console.log(`📝 Context: ~/.prjct-cli/projects/${projectId}/${activeProvider.contextFile}\n`)
|
|
95
|
-
console.log('Next steps:')
|
|
96
|
-
console.log('• /p:sync → Generate agents based on stack')
|
|
97
|
-
console.log('• /p:feature → Add a new feature')
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
success: true,
|
|
101
|
-
summaryPath,
|
|
102
|
-
data: analysisData,
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error('❌ Error:', getErrorMessage(error))
|
|
106
|
-
return { success: false, error: getErrorMessage(error) }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Generate analysis summary from collected data
|
|
112
|
-
*/
|
|
113
|
-
_generateAnalysisSummary(data: Record<string, unknown>, projectPath: string): string {
|
|
114
|
-
const lines: string[] = []
|
|
115
|
-
|
|
116
|
-
lines.push('# Repository Analysis\n')
|
|
117
|
-
lines.push(`Generated: ${new Date().toLocaleString()}\n`)
|
|
118
|
-
|
|
119
|
-
const projectName = path.basename(projectPath)
|
|
120
|
-
lines.push(`## Project: ${projectName}\n`)
|
|
121
|
-
|
|
122
|
-
lines.push('## Stack Detected\n')
|
|
123
|
-
|
|
124
|
-
if (data.packageJson) {
|
|
125
|
-
const pkg = data.packageJson as { dependencies?: Record<string, string> }
|
|
126
|
-
lines.push('### JavaScript/TypeScript\n')
|
|
127
|
-
lines.push('- **Package Manager**: npm/yarn/pnpm')
|
|
128
|
-
if (pkg.dependencies) {
|
|
129
|
-
const deps = Object.keys(pkg.dependencies)
|
|
130
|
-
if (deps.length > 0) {
|
|
131
|
-
lines.push(
|
|
132
|
-
`- **Dependencies**: ${deps.slice(0, 10).join(', ')}${deps.length > 10 ? ` (+${deps.length - 10} more)` : ''}`
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (data.hasNextConfig) lines.push('- **Framework**: Next.js detected')
|
|
137
|
-
if (data.hasViteConfig) lines.push('- **Build Tool**: Vite detected')
|
|
138
|
-
if (data.hasTsconfig) lines.push('- **Language**: TypeScript')
|
|
139
|
-
lines.push('')
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (data.cargoToml) {
|
|
143
|
-
lines.push('### Rust\n')
|
|
144
|
-
lines.push('- **Package Manager**: Cargo')
|
|
145
|
-
lines.push('- **Language**: Rust\n')
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (data.goMod) {
|
|
149
|
-
lines.push('### Go\n')
|
|
150
|
-
lines.push('- **Package Manager**: Go modules')
|
|
151
|
-
lines.push('- **Language**: Go\n')
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (data.requirements) {
|
|
155
|
-
lines.push('### Python\n')
|
|
156
|
-
lines.push('- **Package Manager**: pip')
|
|
157
|
-
lines.push('- **Language**: Python\n')
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const directories = data.directories as string[] | undefined
|
|
161
|
-
lines.push('## Structure\n')
|
|
162
|
-
lines.push(`- **Total Files**: ${data.fileCount}`)
|
|
163
|
-
lines.push(
|
|
164
|
-
`- **Directories**: ${directories?.slice(0, 15).join(', ') || 'none'}${(directories?.length || 0) > 15 ? ` (+${(directories?.length || 0) - 15} more)` : ''}`
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
if (data.hasDockerfile) lines.push('- **Docker**: Detected')
|
|
168
|
-
if (data.hasDockerCompose) lines.push('- **Docker Compose**: Detected')
|
|
169
|
-
if (data.hasReadme) lines.push('- **Documentation**: README.md found')
|
|
170
|
-
lines.push('')
|
|
171
|
-
|
|
172
|
-
const gitStats = data.gitStats as
|
|
173
|
-
| { totalCommits?: number; contributors?: number; age?: string }
|
|
174
|
-
| undefined
|
|
175
|
-
lines.push('## Git Statistics\n')
|
|
176
|
-
lines.push(`- **Total Commits**: ${gitStats?.totalCommits || 0}`)
|
|
177
|
-
lines.push(`- **Contributors**: ${gitStats?.contributors || 0}`)
|
|
178
|
-
lines.push(`- **Age**: ${gitStats?.age || 'unknown'}`)
|
|
179
|
-
lines.push('')
|
|
180
|
-
|
|
181
|
-
if (data.gitLog) {
|
|
182
|
-
lines.push('## Recent Activity\n')
|
|
183
|
-
const logLines = (data.gitLog as string).split('\n').slice(0, 5)
|
|
184
|
-
logLines.forEach((line) => {
|
|
185
|
-
if (line.trim()) {
|
|
186
|
-
const [hash, , time, msg] = line.split('|')
|
|
187
|
-
lines.push(`- \`${hash}\` ${msg} (${time})`)
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
lines.push('')
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
lines.push('## Recommendations\n')
|
|
194
|
-
lines.push('Based on detected stack, consider generating specialized agents using `/p:sync`.\n')
|
|
195
|
-
|
|
196
|
-
lines.push('---\n')
|
|
197
|
-
lines.push(
|
|
198
|
-
'*This analysis was generated automatically. For updated information, run `/p:analyze` again.*\n'
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
return lines.join('\n')
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* /p:sync - Comprehensive project sync with diff preview
|
|
206
|
-
*
|
|
207
|
-
* Uses syncService to do ALL operations in one TypeScript execution:
|
|
208
|
-
* - Git analysis
|
|
209
|
-
* - Project stats
|
|
210
|
-
* - Agent generation
|
|
211
|
-
* - Context file generation
|
|
212
|
-
* - Skill configuration
|
|
213
|
-
* - State updates
|
|
214
|
-
*
|
|
215
|
-
* Options:
|
|
216
|
-
* - --preview: Show what would change without applying
|
|
217
|
-
* - --yes: Skip confirmation prompt
|
|
218
|
-
* - --json: Output structured JSON for LLM consumption (non-interactive)
|
|
219
|
-
*
|
|
220
|
-
* When running in non-TTY mode (e.g., from an LLM), the CLI outputs
|
|
221
|
-
* structured JSON instead of interactive prompts. The LLM should:
|
|
222
|
-
* 1. Run `prjct sync --preview --json` to get diff data
|
|
223
|
-
* 2. Show diff to user and use AskUserQuestion for confirmation
|
|
224
|
-
* 3. Run `prjct sync --yes` if user confirms
|
|
225
|
-
*
|
|
226
|
-
* This eliminates the need for Claude to make 50+ individual tool calls.
|
|
227
|
-
*
|
|
228
|
-
* @see PRJ-125
|
|
229
|
-
*/
|
|
230
|
-
async sync(
|
|
231
|
-
projectPath: string = process.cwd(),
|
|
232
|
-
options: {
|
|
233
|
-
aiTools?: string[]
|
|
234
|
-
preview?: boolean
|
|
235
|
-
yes?: boolean
|
|
236
|
-
json?: boolean
|
|
237
|
-
package?: string
|
|
238
|
-
full?: boolean
|
|
239
|
-
} = {}
|
|
240
|
-
): Promise<CommandResult> {
|
|
241
|
-
try {
|
|
242
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
243
|
-
if (!initResult.success) return initResult
|
|
244
|
-
|
|
245
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
246
|
-
if (!projectId) {
|
|
247
|
-
out.failWithHint('NO_PROJECT_ID')
|
|
248
|
-
return { success: false, error: 'No project ID found' }
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
252
|
-
const startTime = Date.now()
|
|
253
|
-
|
|
254
|
-
// Handle package-specific sync for monorepos
|
|
255
|
-
if (options.package) {
|
|
256
|
-
const monoInfo = await pathManager.detectMonorepo(projectPath)
|
|
257
|
-
if (!monoInfo.isMonorepo) {
|
|
258
|
-
return {
|
|
259
|
-
success: false,
|
|
260
|
-
error: 'Not a monorepo. --package flag only works in monorepos.',
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const pkg = monoInfo.packages.find(
|
|
265
|
-
(p) => p.name === options.package || p.relativePath === options.package
|
|
266
|
-
)
|
|
267
|
-
if (!pkg) {
|
|
268
|
-
const available = monoInfo.packages.map((p) => p.name).join(', ')
|
|
269
|
-
return {
|
|
270
|
-
success: false,
|
|
271
|
-
error: `Package "${options.package}" not found. Available: ${available}`,
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Sync only the specified package
|
|
276
|
-
const result = await syncService.sync(projectPath, {
|
|
277
|
-
aiTools: options.aiTools,
|
|
278
|
-
packagePath: pkg.path,
|
|
279
|
-
packageName: pkg.name,
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
if (options.json) {
|
|
283
|
-
console.log(
|
|
284
|
-
JSON.stringify({ success: result.success, package: pkg.name, path: pkg.relativePath })
|
|
285
|
-
)
|
|
286
|
-
} else {
|
|
287
|
-
out.done(`Synced package: ${pkg.name}`)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return { success: result.success }
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Generate diff preview if we have existing context
|
|
294
|
-
const claudeMdPath = path.join(globalPath, 'context', 'CLAUDE.md')
|
|
295
|
-
let existingContent: string | null = null
|
|
296
|
-
try {
|
|
297
|
-
existingContent = await fs.readFile(claudeMdPath, 'utf-8')
|
|
298
|
-
} catch {
|
|
299
|
-
// No existing file - first sync
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Detect non-interactive mode (LLM or piped input)
|
|
303
|
-
const isNonInteractive = !process.stdin.isTTY || options.json
|
|
304
|
-
|
|
305
|
-
// For preview mode or when we have existing content, show diff first
|
|
306
|
-
if (existingContent && !options.yes) {
|
|
307
|
-
if (!isNonInteractive) {
|
|
308
|
-
out.spin('Analyzing changes...')
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Do a dry-run sync to see what would change
|
|
312
|
-
const result = await syncService.sync(projectPath, {
|
|
313
|
-
aiTools: options.aiTools,
|
|
314
|
-
full: options.full,
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
if (!result.success) {
|
|
318
|
-
if (isNonInteractive) {
|
|
319
|
-
console.log(JSON.stringify({ success: false, error: result.error || 'Sync failed' }))
|
|
320
|
-
return { success: false, error: result.error }
|
|
321
|
-
}
|
|
322
|
-
out.fail(result.error || 'Sync failed')
|
|
323
|
-
return { success: false, error: result.error }
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Read the newly generated CLAUDE.md
|
|
327
|
-
let newContent: string
|
|
328
|
-
try {
|
|
329
|
-
newContent = await fs.readFile(claudeMdPath, 'utf-8')
|
|
330
|
-
} catch {
|
|
331
|
-
newContent = ''
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Generate diff
|
|
335
|
-
const diff = generateSyncDiff(existingContent, newContent)
|
|
336
|
-
|
|
337
|
-
if (!isNonInteractive) {
|
|
338
|
-
out.stop()
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (!diff.hasChanges) {
|
|
342
|
-
if (isNonInteractive) {
|
|
343
|
-
console.log(
|
|
344
|
-
JSON.stringify({
|
|
345
|
-
success: true,
|
|
346
|
-
action: 'no_changes',
|
|
347
|
-
message: 'No changes detected (context is up to date)',
|
|
348
|
-
})
|
|
349
|
-
)
|
|
350
|
-
return { success: true, message: 'No changes' }
|
|
351
|
-
}
|
|
352
|
-
out.done('No changes detected (context is up to date)')
|
|
353
|
-
return { success: true, message: 'No changes' }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Helper to restore original CLAUDE.md (undo sync's write)
|
|
357
|
-
const restoreOriginal = async () => {
|
|
358
|
-
if (existingContent != null) {
|
|
359
|
-
await fs.writeFile(claudeMdPath, existingContent, 'utf-8')
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Non-interactive mode: return JSON for LLM to handle
|
|
364
|
-
if (isNonInteractive) {
|
|
365
|
-
// Restore original — LLM will call `prjct sync --yes` to apply
|
|
366
|
-
await restoreOriginal()
|
|
367
|
-
|
|
368
|
-
// Build a plain-text diff summary for LLM to show user
|
|
369
|
-
const diffSummary = {
|
|
370
|
-
added: diff.added.map((s) => ({ name: s.name, lineCount: s.lineCount })),
|
|
371
|
-
modified: diff.modified.map((s) => ({ name: s.name, lineCount: s.lineCount })),
|
|
372
|
-
removed: diff.removed.map((s) => ({ name: s.name, lineCount: s.lineCount })),
|
|
373
|
-
preserved: diff.preserved,
|
|
374
|
-
tokensBefore: diff.tokensBefore,
|
|
375
|
-
tokensAfter: diff.tokensAfter,
|
|
376
|
-
tokenDelta: diff.tokenDelta,
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
console.log(
|
|
380
|
-
JSON.stringify({
|
|
381
|
-
success: true,
|
|
382
|
-
action: 'confirm_required',
|
|
383
|
-
message: 'Changes detected. Confirmation required to apply.',
|
|
384
|
-
diff: diffSummary,
|
|
385
|
-
fullDiff: options.preview
|
|
386
|
-
? {
|
|
387
|
-
added: diff.added,
|
|
388
|
-
modified: diff.modified,
|
|
389
|
-
removed: diff.removed,
|
|
390
|
-
}
|
|
391
|
-
: undefined,
|
|
392
|
-
hint: 'Run `prjct sync --yes` to apply changes',
|
|
393
|
-
})
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
return {
|
|
397
|
-
success: true,
|
|
398
|
-
isPreview: true,
|
|
399
|
-
diff,
|
|
400
|
-
message: 'Preview complete (awaiting confirmation)',
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Show diff preview (interactive mode)
|
|
405
|
-
console.log(formatDiffPreview(diff))
|
|
406
|
-
|
|
407
|
-
// Preview-only mode (--preview / --dry-run) - restore and don't apply
|
|
408
|
-
if (options.preview) {
|
|
409
|
-
await restoreOriginal()
|
|
410
|
-
return {
|
|
411
|
-
success: true,
|
|
412
|
-
isPreview: true,
|
|
413
|
-
diff,
|
|
414
|
-
message: 'Preview complete (no changes applied)',
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Interactive confirmation (TTY mode only)
|
|
419
|
-
const response = await prompts({
|
|
420
|
-
type: 'select',
|
|
421
|
-
name: 'action',
|
|
422
|
-
message: 'Apply these changes?',
|
|
423
|
-
choices: [
|
|
424
|
-
{ title: 'Yes, apply changes', value: 'apply' },
|
|
425
|
-
{ title: 'No, cancel', value: 'cancel' },
|
|
426
|
-
{ title: 'Show full diff', value: 'diff' },
|
|
427
|
-
],
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
if (response.action === 'cancel' || !response.action) {
|
|
431
|
-
await restoreOriginal()
|
|
432
|
-
out.warn('Sync cancelled — no changes applied')
|
|
433
|
-
return { success: false, message: 'Cancelled by user' }
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (response.action === 'diff') {
|
|
437
|
-
console.log(`\n${formatFullDiff(diff)}`)
|
|
438
|
-
const confirm = await prompts({
|
|
439
|
-
type: 'confirm',
|
|
440
|
-
name: 'apply',
|
|
441
|
-
message: 'Apply these changes?',
|
|
442
|
-
initial: true,
|
|
443
|
-
})
|
|
444
|
-
if (!confirm.apply) {
|
|
445
|
-
await restoreOriginal()
|
|
446
|
-
out.warn('Sync cancelled — no changes applied')
|
|
447
|
-
return { success: false, message: 'Cancelled by user' }
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// User approved — changes already applied by sync
|
|
452
|
-
out.done('Changes applied')
|
|
453
|
-
return this.showSyncResult(result, startTime)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// First sync or --yes flag - proceed directly
|
|
457
|
-
out.spin('Syncing project...')
|
|
458
|
-
|
|
459
|
-
// Use syncService to do EVERYTHING in one call
|
|
460
|
-
const result = await syncService.sync(projectPath, {
|
|
461
|
-
aiTools: options.aiTools,
|
|
462
|
-
full: options.full,
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
if (!result.success) {
|
|
466
|
-
out.fail(result.error || 'Sync failed')
|
|
467
|
-
return { success: false, error: result.error }
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
out.stop()
|
|
471
|
-
return this.showSyncResult(result, startTime)
|
|
472
|
-
} catch (error) {
|
|
473
|
-
out.fail(getErrorMessage(error))
|
|
474
|
-
return { success: false, error: getErrorMessage(error) }
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Display sync results (extracted to avoid duplication)
|
|
480
|
-
*
|
|
481
|
-
* UX Design (PRJ-100):
|
|
482
|
-
* - Summary first: success + key metrics on first lines
|
|
483
|
-
* - Scannable: single-line metrics, minimal vertical space
|
|
484
|
-
* - Changes focused: show what changed, not everything that exists
|
|
485
|
-
* - Next steps prominent: clear call to action at bottom
|
|
486
|
-
*/
|
|
487
|
-
private async showSyncResult(
|
|
488
|
-
result: Awaited<ReturnType<typeof syncService.sync>>,
|
|
489
|
-
startTime: number
|
|
490
|
-
): Promise<CommandResult> {
|
|
491
|
-
const elapsed = Date.now() - startTime
|
|
492
|
-
const contextFilesCount =
|
|
493
|
-
result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0)
|
|
494
|
-
const agentCount = result.agents.length
|
|
495
|
-
const domainAgentCount = result.agents.filter((a) => a.type === 'domain').length
|
|
496
|
-
|
|
497
|
-
// Update global config (silent - don't clutter output)
|
|
498
|
-
await commandInstaller.installGlobalConfig()
|
|
499
|
-
|
|
500
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
501
|
-
// SUCCESS LINE - Immediate confirmation with timing
|
|
502
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
503
|
-
out.done(`Synced ${result.stats.name || 'project'} (${(elapsed / 1000).toFixed(1)}s)`)
|
|
504
|
-
console.log('')
|
|
505
|
-
|
|
506
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
507
|
-
// SUMMARY BOX - Key metrics grouped visually
|
|
508
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
509
|
-
const compressionPct = result.syncMetrics?.compressionRate
|
|
510
|
-
? Math.round(result.syncMetrics.compressionRate * 100)
|
|
511
|
-
: 0
|
|
512
|
-
const framework = result.stats.frameworks.length > 0 ? ` (${result.stats.frameworks[0]})` : ''
|
|
513
|
-
const boxLines = [
|
|
514
|
-
`${result.stats.fileCount} files → ${contextFilesCount} context | ${agentCount} agents${compressionPct > 10 ? ` | ${compressionPct}% reduction` : ''}`,
|
|
515
|
-
`Stack: ${result.stats.ecosystem}${framework} | Branch: ${result.git.branch}`,
|
|
516
|
-
]
|
|
517
|
-
out.box('Sync Summary', boxLines.join('\n'))
|
|
518
|
-
|
|
519
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
520
|
-
// CHANGES SECTION - What was generated/updated
|
|
521
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
522
|
-
const generatedItems: string[] = []
|
|
523
|
-
if (result.contextFiles.length > 0) {
|
|
524
|
-
generatedItems.push(`${result.contextFiles.length} context files`)
|
|
525
|
-
}
|
|
526
|
-
const successTools = result.aiTools?.filter((t) => t.success) || []
|
|
527
|
-
if (successTools.length > 0) {
|
|
528
|
-
generatedItems.push(`AI tools: ${successTools.map((t) => t.toolId).join(', ')}`)
|
|
529
|
-
}
|
|
530
|
-
if (agentCount > 0) {
|
|
531
|
-
const agentSummary =
|
|
532
|
-
domainAgentCount > 0
|
|
533
|
-
? `${agentCount} agents (${domainAgentCount} domain)`
|
|
534
|
-
: `${agentCount} agents`
|
|
535
|
-
generatedItems.push(agentSummary)
|
|
536
|
-
}
|
|
537
|
-
if (result.skills.length > 0) {
|
|
538
|
-
const skillWord = result.skills.length === 1 ? 'skill' : 'skills'
|
|
539
|
-
generatedItems.push(`${result.skills.length} ${skillWord}`)
|
|
540
|
-
}
|
|
541
|
-
const installed = result.skillsInstalled?.filter((s) => s.status === 'installed') || []
|
|
542
|
-
if (installed.length > 0) {
|
|
543
|
-
const word = installed.length === 1 ? 'skill' : 'skills'
|
|
544
|
-
generatedItems.push(`${installed.length} ${word} auto-installed`)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
out.section('Generated')
|
|
548
|
-
out.list(generatedItems, { bullet: '✓' })
|
|
549
|
-
console.log('')
|
|
550
|
-
|
|
551
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
552
|
-
// STATUS INDICATOR - Repository state
|
|
553
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
554
|
-
if (result.git.hasChanges) {
|
|
555
|
-
out.warn('Uncommitted changes detected')
|
|
556
|
-
console.log('')
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
560
|
-
// VERIFICATION - Post-sync validation checks
|
|
561
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
562
|
-
if (result.verification) {
|
|
563
|
-
const v = result.verification
|
|
564
|
-
if (v.passed) {
|
|
565
|
-
const items = v.checks.map((c) => `${c.name} (${c.durationMs}ms)`)
|
|
566
|
-
out.section('Verified')
|
|
567
|
-
out.list(items, { bullet: '✓' })
|
|
568
|
-
} else {
|
|
569
|
-
out.section('Verification')
|
|
570
|
-
const items = v.checks.map((c) =>
|
|
571
|
-
c.passed ? `✓ ${c.name}` : `✗ ${c.name}${c.error ? ` — ${c.error}` : ''}`
|
|
572
|
-
)
|
|
573
|
-
out.list(items)
|
|
574
|
-
if (v.skippedCount > 0) {
|
|
575
|
-
out.warn(`${v.skippedCount} check(s) skipped (fail-fast)`)
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
console.log('')
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
582
|
-
// NEXT STEPS - Clear call to action
|
|
583
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
584
|
-
showNextSteps('sync')
|
|
585
|
-
|
|
586
|
-
return {
|
|
587
|
-
success: true,
|
|
588
|
-
data: result,
|
|
589
|
-
metrics: {
|
|
590
|
-
elapsed,
|
|
591
|
-
contextFilesCount,
|
|
592
|
-
agentCount,
|
|
593
|
-
fileCount: result.stats.fileCount,
|
|
594
|
-
},
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* /p:stats - Session summary and value dashboard
|
|
600
|
-
*
|
|
601
|
-
* Displays:
|
|
602
|
-
* - Session activity (tasks completed, features shipped today)
|
|
603
|
-
* - Patterns learned (from memory system)
|
|
604
|
-
* - Token savings (total, compression rate, estimated cost)
|
|
605
|
-
* - Performance metrics (sync count, avg duration)
|
|
606
|
-
* - Agent usage breakdown
|
|
607
|
-
* - 30-day trend visualization
|
|
608
|
-
*
|
|
609
|
-
* @see PRJ-89
|
|
610
|
-
*/
|
|
611
|
-
async stats(
|
|
612
|
-
projectPath: string = process.cwd(),
|
|
613
|
-
options: { json?: boolean; export?: boolean } = {}
|
|
614
|
-
): Promise<CommandResult> {
|
|
615
|
-
try {
|
|
616
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
617
|
-
if (!initResult.success) return initResult
|
|
618
|
-
|
|
619
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
620
|
-
if (!projectId) {
|
|
621
|
-
return { success: false, error: 'No project ID found' }
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Get metrics summary
|
|
625
|
-
const summary = await metricsStorage.getSummary(projectId)
|
|
626
|
-
const dailyStats = await metricsStorage.getDailyStats(projectId, 30)
|
|
627
|
-
|
|
628
|
-
// Get session activity (today's events)
|
|
629
|
-
const sessionActivity = await this._getSessionActivity(projectId)
|
|
630
|
-
|
|
631
|
-
// Get learned patterns
|
|
632
|
-
const patternsSummary = await memorySystem.getPatternsSummary(projectId)
|
|
633
|
-
|
|
634
|
-
// JSON output mode
|
|
635
|
-
if (options.json) {
|
|
636
|
-
const jsonOutput = {
|
|
637
|
-
session: sessionActivity,
|
|
638
|
-
patterns: patternsSummary,
|
|
639
|
-
totalTokensSaved: summary.totalTokensSaved,
|
|
640
|
-
estimatedCostSaved: summary.estimatedCostSaved,
|
|
641
|
-
compressionRate: summary.compressionRate,
|
|
642
|
-
syncCount: summary.syncCount,
|
|
643
|
-
avgSyncDuration: summary.avgSyncDuration,
|
|
644
|
-
topAgents: summary.topAgents,
|
|
645
|
-
last30DaysTokens: summary.last30DaysTokens,
|
|
646
|
-
trend: summary.trend,
|
|
647
|
-
dailyStats,
|
|
648
|
-
}
|
|
649
|
-
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
650
|
-
return { success: true, data: jsonOutput }
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Get project info for header
|
|
654
|
-
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
655
|
-
let projectName = 'Unknown'
|
|
656
|
-
try {
|
|
657
|
-
const projectJson = JSON.parse(
|
|
658
|
-
await fs.readFile(path.join(globalPath, 'project.json'), 'utf-8')
|
|
659
|
-
)
|
|
660
|
-
projectName = projectJson.name || 'Unknown'
|
|
661
|
-
} catch {
|
|
662
|
-
// Use fallback
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// Determine first sync date
|
|
666
|
-
const metricsData = await metricsStorage.read(projectId)
|
|
667
|
-
const firstSyncDate = metricsData.firstSync
|
|
668
|
-
? new Date(metricsData.firstSync).toLocaleDateString('en-US', {
|
|
669
|
-
month: 'short',
|
|
670
|
-
day: 'numeric',
|
|
671
|
-
year: 'numeric',
|
|
672
|
-
})
|
|
673
|
-
: 'N/A'
|
|
674
|
-
|
|
675
|
-
// ASCII Dashboard
|
|
676
|
-
console.log('')
|
|
677
|
-
console.log('╭─────────────────────────────────────────────────╮')
|
|
678
|
-
console.log('│ 📊 prjct-cli Stats Dashboard │')
|
|
679
|
-
console.log(
|
|
680
|
-
`│ Project: ${projectName.padEnd(20).slice(0, 20)} | Since: ${firstSyncDate.padEnd(12).slice(0, 12)} │`
|
|
681
|
-
)
|
|
682
|
-
console.log('╰─────────────────────────────────────────────────╯')
|
|
683
|
-
console.log('')
|
|
684
|
-
|
|
685
|
-
// Session Activity Section (PRJ-89)
|
|
686
|
-
console.log("🎯 TODAY'S ACTIVITY")
|
|
687
|
-
if (sessionActivity.sessionDuration) {
|
|
688
|
-
console.log(` Duration: ${sessionActivity.sessionDuration}`)
|
|
689
|
-
}
|
|
690
|
-
console.log(` Tasks completed: ${sessionActivity.tasksCompleted}`)
|
|
691
|
-
console.log(` Features shipped: ${sessionActivity.featuresShipped}`)
|
|
692
|
-
if (sessionActivity.agentsUsed.length > 0) {
|
|
693
|
-
const agentStr = sessionActivity.agentsUsed
|
|
694
|
-
.slice(0, 3)
|
|
695
|
-
.map((a) => `${a.name} (${a.count}×)`)
|
|
696
|
-
.join(', ')
|
|
697
|
-
console.log(` Agents used: ${agentStr}`)
|
|
698
|
-
}
|
|
699
|
-
console.log('')
|
|
700
|
-
|
|
701
|
-
// Learned Patterns Section (PRJ-89)
|
|
702
|
-
if (patternsSummary.decisions > 0 || patternsSummary.preferences > 0) {
|
|
703
|
-
console.log('🧠 PATTERNS LEARNED')
|
|
704
|
-
console.log(
|
|
705
|
-
` Decisions: ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total)`
|
|
706
|
-
)
|
|
707
|
-
console.log(` Preferences: ${patternsSummary.preferences} saved`)
|
|
708
|
-
console.log(` Workflows: ${patternsSummary.workflows} tracked`)
|
|
709
|
-
console.log('')
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// Token Savings Section
|
|
713
|
-
console.log('💰 TOKEN SAVINGS')
|
|
714
|
-
console.log(` Total saved: ${this._formatTokens(summary.totalTokensSaved)} tokens`)
|
|
715
|
-
console.log(
|
|
716
|
-
` Compression: ${(summary.compressionRate * 100).toFixed(0)}% average reduction`
|
|
717
|
-
)
|
|
718
|
-
console.log(` Estimated cost: ${formatCost(summary.estimatedCostSaved)} saved`)
|
|
719
|
-
console.log('')
|
|
720
|
-
|
|
721
|
-
// Performance Section
|
|
722
|
-
console.log('⚡ PERFORMANCE')
|
|
723
|
-
console.log(` Syncs completed: ${summary.syncCount.toLocaleString()}`)
|
|
724
|
-
console.log(` Avg sync time: ${this._formatDuration(summary.avgSyncDuration)}`)
|
|
725
|
-
console.log('')
|
|
726
|
-
|
|
727
|
-
// Agent Usage Section
|
|
728
|
-
if (summary.topAgents.length > 0) {
|
|
729
|
-
console.log('🤖 AGENT USAGE (all time)')
|
|
730
|
-
const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
|
|
731
|
-
for (const agent of summary.topAgents) {
|
|
732
|
-
const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
|
|
733
|
-
console.log(` ${agent.agentName.padEnd(12)}: ${pct}% (${agent.usageCount} uses)`)
|
|
734
|
-
}
|
|
735
|
-
console.log('')
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// 30-Day Trend Section
|
|
739
|
-
if (dailyStats.length > 0) {
|
|
740
|
-
console.log('📈 TREND (last 30 days)')
|
|
741
|
-
const sparkline = this._generateSparkline(dailyStats)
|
|
742
|
-
console.log(` ${sparkline} ${this._formatTokens(summary.last30DaysTokens)} tokens saved`)
|
|
743
|
-
|
|
744
|
-
if (summary.trend !== 0) {
|
|
745
|
-
const trendIcon = summary.trend > 0 ? '↑' : '↓'
|
|
746
|
-
const trendSign = summary.trend > 0 ? '+' : ''
|
|
747
|
-
console.log(
|
|
748
|
-
` ${trendIcon} ${trendSign}${summary.trend.toFixed(0)}% vs previous 30 days`
|
|
749
|
-
)
|
|
750
|
-
}
|
|
751
|
-
console.log('')
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Footer
|
|
755
|
-
console.log('───────────────────────────────────────────────────')
|
|
756
|
-
console.log(`Export: prjct stats --export > stats.md`)
|
|
757
|
-
console.log('')
|
|
758
|
-
|
|
759
|
-
// Export mode - return markdown
|
|
760
|
-
if (options.export) {
|
|
761
|
-
const markdown = this._generateStatsMarkdown(
|
|
762
|
-
summary,
|
|
763
|
-
dailyStats,
|
|
764
|
-
projectName,
|
|
765
|
-
firstSyncDate,
|
|
766
|
-
sessionActivity,
|
|
767
|
-
patternsSummary
|
|
768
|
-
)
|
|
769
|
-
console.log(markdown)
|
|
770
|
-
return { success: true, data: { markdown } }
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
return {
|
|
774
|
-
success: true,
|
|
775
|
-
data: { ...summary, session: sessionActivity, patterns: patternsSummary },
|
|
776
|
-
}
|
|
777
|
-
} catch (error) {
|
|
778
|
-
console.error('❌ Error:', getErrorMessage(error))
|
|
779
|
-
return { success: false, error: getErrorMessage(error) }
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* /p:status - Check if CLAUDE.md context is stale
|
|
785
|
-
*
|
|
786
|
-
* Uses git commit history to detect when significant changes
|
|
787
|
-
* have occurred since the last sync.
|
|
788
|
-
*
|
|
789
|
-
* @see PRJ-120
|
|
790
|
-
*/
|
|
791
|
-
async status(
|
|
792
|
-
projectPath: string = process.cwd(),
|
|
793
|
-
options: { json?: boolean } = {}
|
|
794
|
-
): Promise<CommandResult> {
|
|
795
|
-
try {
|
|
796
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
797
|
-
if (!initResult.success) return initResult
|
|
798
|
-
|
|
799
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
800
|
-
if (!projectId) {
|
|
801
|
-
if (options.json) {
|
|
802
|
-
console.log(JSON.stringify({ success: false, error: 'No project ID found' }))
|
|
803
|
-
} else {
|
|
804
|
-
out.fail('No project ID found')
|
|
805
|
-
}
|
|
806
|
-
return { success: false, error: 'No project ID found' }
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Create staleness checker and run check
|
|
810
|
-
const checker = createStalenessChecker(projectPath)
|
|
811
|
-
const status = await checker.check(projectId)
|
|
812
|
-
|
|
813
|
-
// Get session info
|
|
814
|
-
const sessionInfo = await checker.getSessionInfo(projectId)
|
|
815
|
-
|
|
816
|
-
// Get analysis status (PRJ-263)
|
|
817
|
-
const analysisStatus = await analysisStorage.getStatus(projectId)
|
|
818
|
-
|
|
819
|
-
// JSON output mode
|
|
820
|
-
if (options.json) {
|
|
821
|
-
console.log(
|
|
822
|
-
JSON.stringify({
|
|
823
|
-
success: true,
|
|
824
|
-
...status,
|
|
825
|
-
session: sessionInfo,
|
|
826
|
-
analysis: analysisStatus,
|
|
827
|
-
})
|
|
828
|
-
)
|
|
829
|
-
return {
|
|
830
|
-
success: true,
|
|
831
|
-
data: { ...status, session: sessionInfo, analysis: analysisStatus },
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Human-readable output
|
|
836
|
-
console.log('')
|
|
837
|
-
console.log(checker.formatStatus(status))
|
|
838
|
-
console.log('')
|
|
839
|
-
console.log(checker.formatSessionInfo(sessionInfo))
|
|
840
|
-
|
|
841
|
-
// Show analysis status (PRJ-263)
|
|
842
|
-
if (analysisStatus.hasSealed || analysisStatus.hasDraft) {
|
|
843
|
-
console.log('')
|
|
844
|
-
console.log('Analysis:')
|
|
845
|
-
if (analysisStatus.hasSealed) {
|
|
846
|
-
console.log(` Sealed: ${analysisStatus.sealedCommit} (${analysisStatus.sealedAt})`)
|
|
847
|
-
}
|
|
848
|
-
if (analysisStatus.hasDraft) {
|
|
849
|
-
console.log(` Draft: ${analysisStatus.draftCommit} (pending seal)`)
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
console.log('')
|
|
854
|
-
|
|
855
|
-
return { success: true, data: { ...status, session: sessionInfo, analysis: analysisStatus } }
|
|
856
|
-
} catch (error) {
|
|
857
|
-
const errMsg = getErrorMessage(error)
|
|
858
|
-
if (options.json) {
|
|
859
|
-
console.log(JSON.stringify({ success: false, error: errMsg }))
|
|
860
|
-
} else {
|
|
861
|
-
out.fail(errMsg)
|
|
862
|
-
}
|
|
863
|
-
return { success: false, error: errMsg }
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
/**
|
|
868
|
-
* prjct seal - Seal the current draft analysis (PRJ-263)
|
|
869
|
-
*
|
|
870
|
-
* Locks the current draft with a SHA-256 signature.
|
|
871
|
-
* Only sealed analysis feeds task context.
|
|
872
|
-
*/
|
|
873
|
-
async seal(
|
|
874
|
-
projectPath: string = process.cwd(),
|
|
875
|
-
options: { json?: boolean } = {}
|
|
876
|
-
): Promise<CommandResult> {
|
|
877
|
-
try {
|
|
878
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
879
|
-
if (!initResult.success) return initResult
|
|
880
|
-
|
|
881
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
882
|
-
if (!projectId) {
|
|
883
|
-
if (options.json) {
|
|
884
|
-
console.log(JSON.stringify({ success: false, error: 'No project ID found' }))
|
|
885
|
-
}
|
|
886
|
-
return { success: false, error: 'No project ID found' }
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
const result = await analysisStorage.seal(projectId)
|
|
890
|
-
|
|
891
|
-
if (options.json) {
|
|
892
|
-
console.log(
|
|
893
|
-
JSON.stringify({
|
|
894
|
-
success: result.success,
|
|
895
|
-
signature: result.signature,
|
|
896
|
-
error: result.error,
|
|
897
|
-
})
|
|
898
|
-
)
|
|
899
|
-
return { success: result.success, error: result.error }
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
if (!result.success) {
|
|
903
|
-
out.fail(result.error || 'Seal failed')
|
|
904
|
-
return { success: false, error: result.error }
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
out.done('Analysis sealed')
|
|
908
|
-
console.log(` Signature: ${result.signature?.substring(0, 16)}...`)
|
|
909
|
-
console.log('')
|
|
910
|
-
|
|
911
|
-
return { success: true, data: { signature: result.signature } }
|
|
912
|
-
} catch (error) {
|
|
913
|
-
const errMsg = getErrorMessage(error)
|
|
914
|
-
if (options.json) {
|
|
915
|
-
console.log(JSON.stringify({ success: false, error: errMsg }))
|
|
916
|
-
} else {
|
|
917
|
-
out.fail(errMsg)
|
|
918
|
-
}
|
|
919
|
-
return { success: false, error: errMsg }
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
* prjct verify - Verify integrity of sealed analysis (PRJ-263)
|
|
925
|
-
*
|
|
926
|
-
* Modes:
|
|
927
|
-
* - Default: Cryptographic verification (signature check)
|
|
928
|
-
* - --semantic: Semantic verification (data accuracy check, PRJ-270)
|
|
929
|
-
*/
|
|
930
|
-
async verify(
|
|
931
|
-
projectPath: string = process.cwd(),
|
|
932
|
-
options: { json?: boolean; semantic?: boolean } = {}
|
|
933
|
-
): Promise<CommandResult> {
|
|
934
|
-
// Semantic verification mode (PRJ-270)
|
|
935
|
-
if (options.semantic) {
|
|
936
|
-
return this.semanticVerify(projectPath, options)
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Default: Cryptographic verification (PRJ-263)
|
|
940
|
-
try {
|
|
941
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
942
|
-
if (!initResult.success) return initResult
|
|
943
|
-
|
|
944
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
945
|
-
if (!projectId) {
|
|
946
|
-
return { success: false, error: 'No project ID found' }
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
const result = await analysisStorage.verify(projectId)
|
|
950
|
-
|
|
951
|
-
if (options.json) {
|
|
952
|
-
console.log(JSON.stringify(result))
|
|
953
|
-
return { success: result.valid }
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (result.valid) {
|
|
957
|
-
out.done(result.message)
|
|
958
|
-
} else {
|
|
959
|
-
out.fail(result.message)
|
|
960
|
-
}
|
|
961
|
-
console.log('')
|
|
962
|
-
|
|
963
|
-
return { success: result.valid, data: result }
|
|
964
|
-
} catch (error) {
|
|
965
|
-
const errMsg = getErrorMessage(error)
|
|
966
|
-
out.fail(errMsg)
|
|
967
|
-
return { success: false, error: errMsg }
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* prjct analysis verify --semantic - Semantic verification of analysis results (PRJ-270)
|
|
973
|
-
*
|
|
974
|
-
* Validates that analysis data matches actual project state:
|
|
975
|
-
* - Frameworks exist in package.json
|
|
976
|
-
* - Languages match file extensions
|
|
977
|
-
* - Pattern locations reference real files
|
|
978
|
-
* - File count is accurate
|
|
979
|
-
* - Anti-pattern files exist
|
|
980
|
-
*/
|
|
981
|
-
async semanticVerify(
|
|
982
|
-
projectPath: string = process.cwd(),
|
|
983
|
-
options: { json?: boolean } = {}
|
|
984
|
-
): Promise<CommandResult> {
|
|
985
|
-
try {
|
|
986
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
987
|
-
if (!initResult.success) return initResult
|
|
988
|
-
|
|
989
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
990
|
-
if (!projectId) {
|
|
991
|
-
if (options.json) {
|
|
992
|
-
console.log(JSON.stringify({ success: false, error: 'No project ID found' }))
|
|
993
|
-
} else {
|
|
994
|
-
out.fail('No project ID found')
|
|
995
|
-
}
|
|
996
|
-
return { success: false, error: 'No project ID found' }
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// Get project path from project.json
|
|
1000
|
-
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
1001
|
-
let repoPath = projectPath
|
|
1002
|
-
try {
|
|
1003
|
-
const projectJson = JSON.parse(
|
|
1004
|
-
await fs.readFile(path.join(globalPath, 'project.json'), 'utf-8')
|
|
1005
|
-
)
|
|
1006
|
-
repoPath = projectJson.repoPath || projectPath
|
|
1007
|
-
} catch {
|
|
1008
|
-
// Use fallback projectPath
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
// Run semantic verification
|
|
1012
|
-
const result = await analysisStorage.semanticVerify(projectId, repoPath)
|
|
1013
|
-
|
|
1014
|
-
// JSON output mode
|
|
1015
|
-
if (options.json) {
|
|
1016
|
-
console.log(JSON.stringify(result))
|
|
1017
|
-
return { success: result.passed, data: result }
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Human-readable output
|
|
1021
|
-
console.log('')
|
|
1022
|
-
if (result.passed) {
|
|
1023
|
-
out.done('Semantic verification passed')
|
|
1024
|
-
console.log(
|
|
1025
|
-
` ${result.passedCount}/${result.checks.length} checks passed (${result.totalMs}ms)`
|
|
1026
|
-
)
|
|
1027
|
-
} else {
|
|
1028
|
-
out.fail('Semantic verification failed')
|
|
1029
|
-
console.log(` ${result.failedCount}/${result.checks.length} checks failed`)
|
|
1030
|
-
}
|
|
1031
|
-
console.log('')
|
|
1032
|
-
|
|
1033
|
-
// Show check details
|
|
1034
|
-
console.log('Check Results:')
|
|
1035
|
-
for (const check of result.checks) {
|
|
1036
|
-
const icon = check.passed ? '✓' : '✗'
|
|
1037
|
-
const status = check.passed
|
|
1038
|
-
? `${check.output} (${check.durationMs}ms)`
|
|
1039
|
-
: check.error || 'Failed'
|
|
1040
|
-
console.log(` ${icon} ${check.name}: ${status}`)
|
|
1041
|
-
}
|
|
1042
|
-
console.log('')
|
|
1043
|
-
|
|
1044
|
-
return { success: result.passed, data: result }
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
const errMsg = getErrorMessage(error)
|
|
1047
|
-
if (options.json) {
|
|
1048
|
-
console.log(JSON.stringify({ success: false, error: errMsg }))
|
|
1049
|
-
} else {
|
|
1050
|
-
out.fail(errMsg)
|
|
1051
|
-
}
|
|
1052
|
-
return { success: false, error: errMsg }
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
/**
|
|
1057
|
-
* Get session activity stats from today's events
|
|
1058
|
-
* @see PRJ-89
|
|
1059
|
-
*/
|
|
1060
|
-
private async _getSessionActivity(projectId: string): Promise<{
|
|
1061
|
-
sessionDuration: string | null
|
|
1062
|
-
tasksCompleted: number
|
|
1063
|
-
featuresShipped: number
|
|
1064
|
-
agentsUsed: { name: string; count: number }[]
|
|
1065
|
-
}> {
|
|
1066
|
-
try {
|
|
1067
|
-
// Get today's events from memory
|
|
1068
|
-
const recentHistory = await memoryService.getRecentEvents(projectId, 100)
|
|
1069
|
-
|
|
1070
|
-
const today = new Date().toISOString().split('T')[0]
|
|
1071
|
-
const todayEvents = recentHistory.filter((e) => {
|
|
1072
|
-
const ts = (e.timestamp || e.ts) as string | undefined
|
|
1073
|
-
return ts?.startsWith(today)
|
|
1074
|
-
})
|
|
1075
|
-
|
|
1076
|
-
// Calculate session duration (time between first and last event today)
|
|
1077
|
-
let sessionDuration: string | null = null
|
|
1078
|
-
if (todayEvents.length >= 2) {
|
|
1079
|
-
const timestamps = todayEvents
|
|
1080
|
-
.map((e) => new Date((e.timestamp || e.ts) as string).getTime())
|
|
1081
|
-
.filter((t) => !Number.isNaN(t))
|
|
1082
|
-
.sort((a, b) => a - b)
|
|
1083
|
-
|
|
1084
|
-
if (timestamps.length >= 2) {
|
|
1085
|
-
const durationMs = timestamps[timestamps.length - 1] - timestamps[0]
|
|
1086
|
-
sessionDuration = dateHelper.formatDuration(durationMs)
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// Count tasks completed today
|
|
1091
|
-
const tasksCompleted = todayEvents.filter((e) => e.action === 'task_completed').length
|
|
1092
|
-
|
|
1093
|
-
// Count features shipped today
|
|
1094
|
-
const featuresShipped = todayEvents.filter((e) => e.action === 'feature_shipped').length
|
|
1095
|
-
|
|
1096
|
-
// Count agent usage from sync events
|
|
1097
|
-
const agentCounts = new Map<string, number>()
|
|
1098
|
-
for (const event of todayEvents) {
|
|
1099
|
-
if (event.action === 'sync' && Array.isArray(event.subagents)) {
|
|
1100
|
-
for (const agent of event.subagents as string[]) {
|
|
1101
|
-
agentCounts.set(agent, (agentCounts.get(agent) || 0) + 1)
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
const agentsUsed = Array.from(agentCounts.entries())
|
|
1107
|
-
.map(([name, count]) => ({ name, count }))
|
|
1108
|
-
.sort((a, b) => b.count - a.count)
|
|
1109
|
-
|
|
1110
|
-
return {
|
|
1111
|
-
sessionDuration,
|
|
1112
|
-
tasksCompleted,
|
|
1113
|
-
featuresShipped,
|
|
1114
|
-
agentsUsed,
|
|
1115
|
-
}
|
|
1116
|
-
} catch {
|
|
1117
|
-
return {
|
|
1118
|
-
sessionDuration: null,
|
|
1119
|
-
tasksCompleted: 0,
|
|
1120
|
-
featuresShipped: 0,
|
|
1121
|
-
agentsUsed: [],
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
// =========== Stats Helper Methods ===========
|
|
1127
|
-
|
|
1128
|
-
private _formatTokens(tokens: number): string {
|
|
1129
|
-
if (tokens >= 1_000_000) {
|
|
1130
|
-
return `${(tokens / 1_000_000).toFixed(1)}M`
|
|
1131
|
-
}
|
|
1132
|
-
if (tokens >= 1_000) {
|
|
1133
|
-
return `${(tokens / 1_000).toFixed(1)}K`
|
|
1134
|
-
}
|
|
1135
|
-
return tokens.toLocaleString()
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
private _formatDuration(ms: number): string {
|
|
1139
|
-
if (ms < 1000) {
|
|
1140
|
-
return `${Math.round(ms)}ms`
|
|
1141
|
-
}
|
|
1142
|
-
return `${(ms / 1000).toFixed(1)}s`
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
private _generateSparkline(dailyStats: { tokensSaved: number }[]): string {
|
|
1146
|
-
if (dailyStats.length === 0) return ''
|
|
1147
|
-
|
|
1148
|
-
const chars = '▁▂▃▄▅▆▇█'
|
|
1149
|
-
const values = dailyStats.map((d) => d.tokensSaved)
|
|
1150
|
-
const max = Math.max(...values, 1)
|
|
1151
|
-
|
|
1152
|
-
return values
|
|
1153
|
-
.map((v) => {
|
|
1154
|
-
const idx = Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)
|
|
1155
|
-
return chars[idx]
|
|
1156
|
-
})
|
|
1157
|
-
.join('')
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
private _generateStatsMarkdown(
|
|
1161
|
-
summary: {
|
|
1162
|
-
totalTokensSaved: number
|
|
1163
|
-
estimatedCostSaved: number
|
|
1164
|
-
compressionRate: number
|
|
1165
|
-
syncCount: number
|
|
1166
|
-
avgSyncDuration: number
|
|
1167
|
-
topAgents: { agentName: string; usageCount: number }[]
|
|
1168
|
-
last30DaysTokens: number
|
|
1169
|
-
trend: number
|
|
1170
|
-
},
|
|
1171
|
-
_dailyStats: { date: string; tokensSaved: number; syncs: number }[],
|
|
1172
|
-
projectName: string,
|
|
1173
|
-
firstSyncDate: string,
|
|
1174
|
-
sessionActivity?: {
|
|
1175
|
-
sessionDuration: string | null
|
|
1176
|
-
tasksCompleted: number
|
|
1177
|
-
featuresShipped: number
|
|
1178
|
-
agentsUsed: { name: string; count: number }[]
|
|
1179
|
-
},
|
|
1180
|
-
patternsSummary?: {
|
|
1181
|
-
decisions: number
|
|
1182
|
-
learnedDecisions: number
|
|
1183
|
-
workflows: number
|
|
1184
|
-
preferences: number
|
|
1185
|
-
}
|
|
1186
|
-
): string {
|
|
1187
|
-
const lines: string[] = []
|
|
1188
|
-
|
|
1189
|
-
lines.push(`# ${projectName} - Stats Dashboard`)
|
|
1190
|
-
lines.push('')
|
|
1191
|
-
lines.push(`_Generated: ${new Date().toLocaleString()} | Tracking since: ${firstSyncDate}_`)
|
|
1192
|
-
lines.push('')
|
|
1193
|
-
|
|
1194
|
-
// Session Activity (PRJ-89)
|
|
1195
|
-
if (sessionActivity) {
|
|
1196
|
-
lines.push("## 🎯 Today's Activity")
|
|
1197
|
-
lines.push('')
|
|
1198
|
-
lines.push(`| Metric | Value |`)
|
|
1199
|
-
lines.push(`|--------|-------|`)
|
|
1200
|
-
if (sessionActivity.sessionDuration) {
|
|
1201
|
-
lines.push(`| Duration | ${sessionActivity.sessionDuration} |`)
|
|
1202
|
-
}
|
|
1203
|
-
lines.push(`| Tasks completed | ${sessionActivity.tasksCompleted} |`)
|
|
1204
|
-
lines.push(`| Features shipped | ${sessionActivity.featuresShipped} |`)
|
|
1205
|
-
if (sessionActivity.agentsUsed.length > 0) {
|
|
1206
|
-
const agentStr = sessionActivity.agentsUsed
|
|
1207
|
-
.slice(0, 3)
|
|
1208
|
-
.map((a) => `${a.name} (${a.count}×)`)
|
|
1209
|
-
.join(', ')
|
|
1210
|
-
lines.push(`| Agents used | ${agentStr} |`)
|
|
1211
|
-
}
|
|
1212
|
-
lines.push('')
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
// Patterns Learned (PRJ-89)
|
|
1216
|
-
if (patternsSummary && (patternsSummary.decisions > 0 || patternsSummary.preferences > 0)) {
|
|
1217
|
-
lines.push('## 🧠 Patterns Learned')
|
|
1218
|
-
lines.push('')
|
|
1219
|
-
lines.push(`| Type | Count |`)
|
|
1220
|
-
lines.push(`|------|-------|`)
|
|
1221
|
-
lines.push(
|
|
1222
|
-
`| Decisions | ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total) |`
|
|
1223
|
-
)
|
|
1224
|
-
lines.push(`| Preferences | ${patternsSummary.preferences} |`)
|
|
1225
|
-
lines.push(`| Workflows | ${patternsSummary.workflows} |`)
|
|
1226
|
-
lines.push('')
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
lines.push('## 💰 Token Savings')
|
|
1230
|
-
lines.push('')
|
|
1231
|
-
lines.push(`| Metric | Value |`)
|
|
1232
|
-
lines.push(`|--------|-------|`)
|
|
1233
|
-
lines.push(`| Total saved | ${this._formatTokens(summary.totalTokensSaved)} tokens |`)
|
|
1234
|
-
lines.push(`| Compression | ${(summary.compressionRate * 100).toFixed(0)}% |`)
|
|
1235
|
-
lines.push(`| Cost saved | ${formatCost(summary.estimatedCostSaved)} |`)
|
|
1236
|
-
lines.push('')
|
|
1237
|
-
|
|
1238
|
-
lines.push('## ⚡ Performance')
|
|
1239
|
-
lines.push('')
|
|
1240
|
-
lines.push(`| Metric | Value |`)
|
|
1241
|
-
lines.push(`|--------|-------|`)
|
|
1242
|
-
lines.push(`| Syncs | ${summary.syncCount} |`)
|
|
1243
|
-
lines.push(`| Avg time | ${this._formatDuration(summary.avgSyncDuration)} |`)
|
|
1244
|
-
lines.push('')
|
|
1245
|
-
|
|
1246
|
-
if (summary.topAgents.length > 0) {
|
|
1247
|
-
lines.push('## 🤖 Agent Usage')
|
|
1248
|
-
lines.push('')
|
|
1249
|
-
lines.push(`| Agent | Usage |`)
|
|
1250
|
-
lines.push(`|-------|-------|`)
|
|
1251
|
-
const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
|
|
1252
|
-
for (const agent of summary.topAgents) {
|
|
1253
|
-
const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
|
|
1254
|
-
lines.push(`| ${agent.agentName} | ${pct}% (${agent.usageCount}) |`)
|
|
1255
|
-
}
|
|
1256
|
-
lines.push('')
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
lines.push('## 📈 30-Day Trend')
|
|
1260
|
-
lines.push('')
|
|
1261
|
-
lines.push(`- Tokens saved: ${this._formatTokens(summary.last30DaysTokens)}`)
|
|
1262
|
-
if (summary.trend !== 0) {
|
|
1263
|
-
const trendSign = summary.trend > 0 ? '+' : ''
|
|
1264
|
-
lines.push(`- Trend: ${trendSign}${summary.trend.toFixed(0)}% vs previous period`)
|
|
1265
|
-
}
|
|
1266
|
-
lines.push('')
|
|
1267
|
-
|
|
1268
|
-
lines.push('---')
|
|
1269
|
-
lines.push('')
|
|
1270
|
-
lines.push('_Generated with [prjct-cli](https://prjct.app)_')
|
|
1271
|
-
|
|
1272
|
-
return lines.join('\n')
|
|
1273
|
-
}
|
|
1274
|
-
}
|