prjct-cli 0.11.5 → 0.12.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 +58 -0
- package/README.md +81 -25
- package/bin/dev.js +1 -1
- package/bin/generate-views.js +209 -0
- package/bin/migrate-to-json.js +742 -0
- package/bin/prjct +5 -5
- package/bin/serve.js +226 -50
- package/core/__tests__/agentic/{memory-system.test.js → memory-system.test.ts} +12 -23
- package/core/__tests__/agentic/{plan-mode.test.js → plan-mode.test.ts} +26 -24
- package/core/__tests__/agentic/{prompt-builder.test.js → prompt-builder.test.ts} +3 -8
- package/core/__tests__/utils/{date-helper.test.js → date-helper.test.ts} +19 -30
- package/core/__tests__/utils/{output.test.js → output.test.ts} +12 -24
- package/core/agentic/agent-router.ts +137 -0
- package/core/agentic/chain-of-thought.ts +228 -0
- package/core/agentic/command-executor/command-executor.ts +384 -0
- package/core/agentic/command-executor/index.ts +16 -0
- package/core/agentic/command-executor/status-signal.ts +38 -0
- package/core/agentic/command-executor/types.ts +79 -0
- package/core/agentic/command-executor.ts +8 -0
- package/core/agentic/{context-builder.js → context-builder.ts} +92 -81
- package/core/agentic/context-filter.ts +365 -0
- package/core/agentic/ground-truth/index.ts +76 -0
- package/core/agentic/ground-truth/types.ts +33 -0
- package/core/agentic/ground-truth/utils.ts +48 -0
- package/core/agentic/ground-truth/verifiers/analyze.ts +54 -0
- package/core/agentic/ground-truth/verifiers/done.ts +75 -0
- package/core/agentic/ground-truth/verifiers/feature.ts +70 -0
- package/core/agentic/ground-truth/verifiers/index.ts +37 -0
- package/core/agentic/ground-truth/verifiers/init.ts +52 -0
- package/core/agentic/ground-truth/verifiers/now.ts +57 -0
- package/core/agentic/ground-truth/verifiers/ship.ts +85 -0
- package/core/agentic/ground-truth/verifiers/spec.ts +45 -0
- package/core/agentic/ground-truth/verifiers/sync.ts +47 -0
- package/core/agentic/ground-truth/verifiers.ts +6 -0
- package/core/agentic/ground-truth.ts +8 -0
- package/core/agentic/loop-detector/error-analysis.ts +97 -0
- package/core/agentic/loop-detector/hallucination.ts +71 -0
- package/core/agentic/loop-detector/index.ts +41 -0
- package/core/agentic/loop-detector/loop-detector.ts +222 -0
- package/core/agentic/loop-detector/types.ts +66 -0
- package/core/agentic/loop-detector.ts +8 -0
- package/core/agentic/memory-system/history.ts +53 -0
- package/core/agentic/memory-system/index.ts +192 -0
- package/core/agentic/memory-system/patterns.ts +156 -0
- package/core/agentic/memory-system/semantic-memories.ts +277 -0
- package/core/agentic/memory-system/session.ts +21 -0
- package/core/agentic/memory-system/types.ts +159 -0
- package/core/agentic/memory-system.ts +8 -0
- package/core/agentic/parallel-tools.ts +165 -0
- package/core/agentic/plan-mode/approval.ts +57 -0
- package/core/agentic/plan-mode/constants.ts +44 -0
- package/core/agentic/plan-mode/index.ts +28 -0
- package/core/agentic/plan-mode/plan-mode.ts +406 -0
- package/core/agentic/plan-mode/types.ts +193 -0
- package/core/agentic/plan-mode.ts +8 -0
- package/core/agentic/prompt-builder.ts +566 -0
- package/core/agentic/response-templates.ts +164 -0
- package/core/agentic/semantic-compression.ts +273 -0
- package/core/agentic/services.ts +206 -0
- package/core/agentic/smart-context.ts +476 -0
- package/core/agentic/{template-loader.js → template-loader.ts} +27 -16
- package/core/agentic/think-blocks.ts +202 -0
- package/core/agentic/tool-registry.ts +119 -0
- package/core/agentic/validation-rules.ts +313 -0
- package/core/agents/index.ts +28 -0
- package/core/agents/performance.ts +444 -0
- package/core/agents/types.ts +126 -0
- package/core/bus/{index.js → index.ts} +57 -61
- package/core/command-registry/categories.ts +23 -0
- package/core/command-registry/commands.ts +15 -0
- package/core/command-registry/core-commands.ts +319 -0
- package/core/command-registry/index.ts +158 -0
- package/core/command-registry/optional-commands.ts +119 -0
- package/core/command-registry/setup-commands.ts +53 -0
- package/core/command-registry/types.ts +59 -0
- package/core/command-registry.ts +9 -0
- package/core/commands/analysis.ts +298 -0
- package/core/commands/analytics.ts +288 -0
- package/core/commands/base.ts +273 -0
- package/core/commands/index.ts +211 -0
- package/core/commands/maintenance.ts +226 -0
- package/core/commands/planning.ts +311 -0
- package/core/commands/setup.ts +309 -0
- package/core/commands/shipping.ts +188 -0
- package/core/commands/types.ts +183 -0
- package/core/commands/workflow.ts +226 -0
- package/core/commands.ts +11 -0
- package/core/constants/formats.ts +187 -0
- package/core/constants/index.ts +7 -0
- package/core/{context-sync.js → context-sync.ts} +59 -26
- package/core/data/agents-manager.ts +76 -0
- package/core/data/analysis-manager.ts +83 -0
- package/core/data/base-manager.ts +156 -0
- package/core/data/ideas-manager.ts +81 -0
- package/core/data/index.ts +32 -0
- package/core/data/outcomes-manager.ts +96 -0
- package/core/data/project-manager.ts +75 -0
- package/core/data/roadmap-manager.ts +118 -0
- package/core/data/shipped-manager.ts +65 -0
- package/core/data/state-manager.ts +214 -0
- package/core/domain/{agent-generator.js → agent-generator.ts} +77 -57
- package/core/domain/{agent-loader.js → agent-loader.ts} +65 -56
- package/core/domain/{agent-matcher.js → agent-matcher.ts} +51 -24
- package/core/domain/{agent-validator.js → agent-validator.ts} +70 -37
- package/core/domain/{analyzer.js → analyzer.ts} +91 -85
- package/core/domain/{architect-session.js → architect-session.ts} +49 -34
- package/core/domain/{architecture-generator.js → architecture-generator.ts} +25 -13
- package/core/domain/{context-estimator.js → context-estimator.ts} +57 -36
- package/core/domain/{product-standards.js → product-standards.ts} +40 -26
- package/core/domain/{smart-cache.js → smart-cache.ts} +39 -30
- package/core/domain/{snapshot-manager.js → snapshot-manager.ts} +103 -100
- package/core/domain/{task-analyzer.js → task-analyzer.ts} +82 -43
- package/core/domain/task-stack/index.ts +19 -0
- package/core/domain/task-stack/parser.ts +86 -0
- package/core/domain/task-stack/storage.ts +123 -0
- package/core/domain/task-stack/task-stack.ts +340 -0
- package/core/domain/task-stack/types.ts +51 -0
- package/core/domain/task-stack.ts +8 -0
- package/core/{index.js → index.ts} +61 -18
- package/core/infrastructure/{agent-detector.js → agent-detector.ts} +55 -19
- package/core/infrastructure/agents/{claude-agent.js → claude-agent.ts} +61 -21
- package/core/infrastructure/{author-detector.js → author-detector.ts} +42 -49
- package/core/infrastructure/{capability-installer.js → capability-installer.ts} +51 -27
- package/core/infrastructure/{command-installer.js → command-installer/command-installer.ts} +43 -144
- package/core/infrastructure/command-installer/global-config.ts +106 -0
- package/core/infrastructure/command-installer/index.ts +25 -0
- package/core/infrastructure/command-installer/types.ts +41 -0
- package/core/infrastructure/command-installer.ts +8 -0
- package/core/infrastructure/{config-manager.js → config-manager.ts} +60 -80
- package/core/infrastructure/{editors-config.js → editors-config.ts} +33 -31
- package/core/infrastructure/legacy-installer-detector/cleanup.ts +216 -0
- package/core/infrastructure/legacy-installer-detector/detection.ts +95 -0
- package/core/infrastructure/legacy-installer-detector/index.ts +171 -0
- package/core/infrastructure/legacy-installer-detector/migration.ts +87 -0
- package/core/infrastructure/legacy-installer-detector/types.ts +42 -0
- package/core/infrastructure/legacy-installer-detector.ts +7 -0
- package/core/infrastructure/migrator/file-operations.ts +125 -0
- package/core/infrastructure/migrator/index.ts +288 -0
- package/core/infrastructure/migrator/project-scanner.ts +89 -0
- package/core/infrastructure/migrator/reports.ts +117 -0
- package/core/infrastructure/migrator/types.ts +124 -0
- package/core/infrastructure/migrator/validation.ts +94 -0
- package/core/infrastructure/migrator/version-migration.ts +117 -0
- package/core/infrastructure/migrator.ts +10 -0
- package/core/infrastructure/{path-manager.js → path-manager.ts} +51 -91
- package/core/infrastructure/session-manager/index.ts +23 -0
- package/core/infrastructure/session-manager/migration.ts +88 -0
- package/core/infrastructure/session-manager/session-manager.ts +307 -0
- package/core/infrastructure/session-manager/types.ts +45 -0
- package/core/infrastructure/session-manager.ts +8 -0
- package/core/infrastructure/{setup.js → setup.ts} +29 -21
- package/core/infrastructure/{update-checker.js → update-checker.ts} +40 -18
- package/core/outcomes/analyzer.ts +333 -0
- package/core/outcomes/index.ts +34 -0
- package/core/outcomes/recorder.ts +194 -0
- package/core/outcomes/types.ts +145 -0
- package/core/plugin/{hooks.js → hooks.ts} +56 -58
- package/core/plugin/{index.js → index.ts} +19 -8
- package/core/plugin/{loader.js → loader.ts} +87 -69
- package/core/plugin/{registry.js → registry.ts} +49 -45
- package/core/plugins/{webhook.js → webhook.ts} +43 -27
- package/core/schemas/agents.ts +27 -0
- package/core/schemas/analysis.ts +41 -0
- package/core/schemas/ideas.ts +83 -0
- package/core/schemas/index.ts +73 -0
- package/core/schemas/outcomes.ts +22 -0
- package/core/schemas/project.ts +26 -0
- package/core/schemas/roadmap.ts +90 -0
- package/core/schemas/shipped.ts +82 -0
- package/core/schemas/state.ts +107 -0
- package/core/session/index.ts +17 -0
- package/core/session/{metrics.js → metrics.ts} +64 -46
- package/core/session/{index.js → session-manager.ts} +51 -117
- package/core/session/types.ts +29 -0
- package/core/session/utils.ts +57 -0
- package/core/state/index.ts +25 -0
- package/core/state/manager.ts +376 -0
- package/core/state/types.ts +185 -0
- package/core/tsconfig.json +22 -0
- package/core/types/index.ts +506 -0
- package/core/utils/{animations.js → animations.ts} +74 -28
- package/core/utils/{branding.js → branding.ts} +29 -4
- package/core/utils/{date-helper.js → date-helper.ts} +31 -74
- package/core/utils/file-helper.ts +262 -0
- package/core/utils/{jsonl-helper.js → jsonl-helper.ts} +71 -107
- package/core/utils/{logger.js → logger.ts} +24 -12
- package/core/utils/{output.js → output.ts} +25 -13
- package/core/utils/{project-capabilities.js → project-capabilities.ts} +31 -18
- package/core/utils/{session-helper.js → session-helper.ts} +79 -66
- package/core/utils/{version.js → version.ts} +23 -31
- package/core/view-generator.ts +536 -0
- package/package.json +23 -17
- package/packages/shared/.turbo/turbo-build.log +14 -0
- package/packages/shared/dist/index.d.ts +8 -613
- package/packages/shared/dist/index.d.ts.map +1 -0
- package/packages/shared/dist/index.js +4110 -118
- package/packages/shared/dist/schemas.d.ts +408 -0
- package/packages/shared/dist/schemas.d.ts.map +1 -0
- package/packages/shared/dist/types.d.ts +144 -0
- package/packages/shared/dist/types.d.ts.map +1 -0
- package/packages/shared/dist/unified.d.ts +139 -0
- package/packages/shared/dist/unified.d.ts.map +1 -0
- package/packages/shared/dist/utils.d.ts +60 -0
- package/packages/shared/dist/utils.d.ts.map +1 -0
- package/packages/shared/package.json +4 -4
- package/packages/shared/src/index.ts +1 -0
- package/packages/shared/src/unified.ts +174 -0
- package/packages/web/app/api/claude/sessions/route.ts +1 -1
- package/packages/web/app/api/claude/status/route.ts +1 -1
- package/packages/web/app/api/migrate/route.ts +46 -0
- package/packages/web/app/api/projects/[id]/route.ts +1 -1
- package/packages/web/app/api/projects/[id]/stats/route.ts +30 -2
- package/packages/web/app/api/projects/[id]/status/route.ts +1 -1
- package/packages/web/app/api/projects/route.ts +1 -1
- package/packages/web/app/api/settings/route.ts +97 -0
- package/packages/web/app/api/v2/projects/[id]/unified/route.ts +57 -0
- package/packages/web/app/globals.css +38 -0
- package/packages/web/app/layout.tsx +10 -2
- package/packages/web/app/page.tsx +9 -224
- package/packages/web/app/project/[id]/page.tsx +191 -63
- package/packages/web/app/project/[id]/stats/loading.tsx +43 -0
- package/packages/web/app/project/[id]/stats/page.tsx +204 -163
- package/packages/web/app/settings/page.tsx +222 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +2 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +50 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +8 -0
- package/packages/web/components/ActivityTimeline/hooks/index.ts +2 -0
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +9 -0
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +23 -0
- package/packages/web/components/ActivityTimeline/index.ts +2 -0
- package/packages/web/components/AgentsCard/AgentsCard.tsx +63 -0
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +13 -0
- package/packages/web/components/AgentsCard/index.ts +2 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +134 -0
- package/packages/web/components/AppSidebar/index.ts +1 -0
- package/packages/web/components/BackLink/BackLink.tsx +18 -0
- package/packages/web/components/BackLink/BackLink.types.ts +5 -0
- package/packages/web/components/BackLink/index.ts +2 -0
- package/packages/web/components/BentoCard/BentoCard.constants.ts +16 -0
- package/packages/web/components/BentoCard/BentoCard.tsx +47 -0
- package/packages/web/components/BentoCard/BentoCard.types.ts +15 -0
- package/packages/web/components/BentoCard/index.ts +2 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +9 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +18 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +5 -0
- package/packages/web/components/BentoCardSkeleton/index.ts +2 -0
- package/packages/web/components/{stats → BentoGrid}/BentoGrid.tsx +4 -8
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +4 -0
- package/packages/web/components/BentoGrid/index.ts +2 -0
- package/packages/web/components/CommandButton/index.ts +1 -0
- package/packages/web/components/ConnectionStatus/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +254 -0
- package/packages/web/components/DashboardContent/index.ts +1 -0
- package/packages/web/components/DateGroup/DateGroup.tsx +18 -0
- package/packages/web/components/DateGroup/DateGroup.types.ts +6 -0
- package/packages/web/components/DateGroup/DateGroup.utils.ts +11 -0
- package/packages/web/components/DateGroup/index.ts +2 -0
- package/packages/web/components/{stats → EmptyState}/EmptyState.tsx +1 -10
- package/packages/web/components/EmptyState/EmptyState.types.ts +10 -0
- package/packages/web/components/EmptyState/index.ts +2 -0
- package/packages/web/components/EventRow/EventRow.constants.ts +10 -0
- package/packages/web/components/EventRow/EventRow.tsx +49 -0
- package/packages/web/components/EventRow/EventRow.types.ts +7 -0
- package/packages/web/components/EventRow/EventRow.utils.ts +49 -0
- package/packages/web/components/EventRow/index.ts +2 -0
- package/packages/web/components/ExpandButton/ExpandButton.tsx +18 -0
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +6 -0
- package/packages/web/components/ExpandButton/index.ts +2 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +14 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +5 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +13 -0
- package/packages/web/components/HealthGradientBackground/index.ts +2 -0
- package/packages/web/components/HeroSection/HeroSection.tsx +55 -0
- package/packages/web/components/HeroSection/HeroSection.types.ts +14 -0
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -0
- package/packages/web/components/HeroSection/hooks/index.ts +2 -0
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +45 -0
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +18 -0
- package/packages/web/components/HeroSection/index.ts +2 -0
- package/packages/web/components/{stats → IdeasCard}/IdeasCard.tsx +3 -14
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +9 -0
- package/packages/web/components/IdeasCard/index.ts +2 -0
- package/packages/web/components/InsightMessage/InsightMessage.tsx +9 -0
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +3 -0
- package/packages/web/components/InsightMessage/index.ts +2 -0
- package/packages/web/components/Logo/index.ts +1 -0
- package/packages/web/components/MarkdownContent/index.ts +1 -0
- package/packages/web/components/NowCard/NowCard.tsx +93 -0
- package/packages/web/components/NowCard/NowCard.types.ts +15 -0
- package/packages/web/components/NowCard/index.ts +2 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +20 -0
- package/packages/web/components/{stats → ProgressRing}/ProgressRing.tsx +4 -27
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +11 -0
- package/packages/web/components/ProgressRing/index.ts +2 -0
- package/packages/web/components/ProjectAvatar/index.ts +1 -0
- package/packages/web/components/Providers/index.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.tsx +72 -0
- package/packages/web/components/QueueCard/QueueCard.types.ts +11 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +12 -0
- package/packages/web/components/QueueCard/index.ts +2 -0
- package/packages/web/components/{stats → RoadmapCard}/RoadmapCard.tsx +3 -23
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +15 -0
- package/packages/web/components/RoadmapCard/index.ts +2 -0
- package/packages/web/components/{stats → ShipsCard}/ShipsCard.tsx +4 -22
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +12 -0
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +4 -0
- package/packages/web/components/ShipsCard/index.ts +2 -0
- package/packages/web/components/{stats → SparklineChart}/SparklineChart.tsx +1 -7
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +6 -0
- package/packages/web/components/SparklineChart/index.ts +2 -0
- package/packages/web/components/StreakCard/StreakCard.constants.ts +2 -0
- package/packages/web/components/{stats → StreakCard}/StreakCard.tsx +5 -11
- package/packages/web/components/StreakCard/StreakCard.types.ts +4 -0
- package/packages/web/components/StreakCard/index.ts +2 -0
- package/packages/web/components/TasksCounter/TasksCounter.tsx +14 -0
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +3 -0
- package/packages/web/components/TasksCounter/index.ts +2 -0
- package/packages/web/components/TechStackBadges/index.ts +1 -0
- package/packages/web/components/{TerminalTab.tsx → TerminalTabs/TerminalTab.tsx} +11 -0
- package/packages/web/components/{TerminalTabs.tsx → TerminalTabs/TerminalTabs.tsx} +29 -28
- package/packages/web/components/TerminalTabs/index.ts +1 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +27 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +3 -0
- package/packages/web/components/VelocityBadge/index.ts +2 -0
- package/packages/web/components/VelocityCard/VelocityCard.tsx +71 -0
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +7 -0
- package/packages/web/components/VelocityCard/index.ts +2 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +13 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +3 -0
- package/packages/web/components/WeeklySparkline/index.ts +2 -0
- package/packages/web/components/ui/input.tsx +21 -0
- package/packages/web/context/TerminalTabsContext.tsx +46 -1
- package/packages/web/hooks/useClaudeTerminal.ts +71 -21
- package/packages/web/hooks/useProjectStats.ts +55 -0
- package/packages/web/hooks/useProjects.ts +6 -6
- package/packages/web/lib/actions/projects.ts +15 -0
- package/packages/web/lib/json-loader.ts +630 -0
- package/packages/web/lib/services/index.ts +9 -0
- package/packages/web/lib/services/migration.server.ts +598 -0
- package/packages/web/lib/services/projects.server.ts +52 -0
- package/packages/web/lib/services/stats.server.ts +264 -0
- package/packages/web/lib/unified-loader.ts +396 -0
- package/packages/web/package.json +10 -7
- package/packages/web/server.ts +36 -6
- package/templates/commands/done.md +76 -32
- package/templates/commands/feature.md +121 -47
- package/templates/commands/idea.md +81 -8
- package/templates/commands/now.md +41 -17
- package/templates/commands/ship.md +64 -25
- package/templates/commands/sync.md +28 -3
- package/core/agentic/agent-router.js +0 -140
- package/core/agentic/chain-of-thought.js +0 -578
- package/core/agentic/command-executor.js +0 -417
- package/core/agentic/context-filter.js +0 -354
- package/core/agentic/ground-truth.js +0 -591
- package/core/agentic/loop-detector.js +0 -406
- package/core/agentic/memory-system.js +0 -845
- package/core/agentic/parallel-tools.js +0 -366
- package/core/agentic/plan-mode.js +0 -572
- package/core/agentic/prompt-builder.js +0 -352
- package/core/agentic/response-templates.js +0 -290
- package/core/agentic/semantic-compression.js +0 -517
- package/core/agentic/think-blocks.js +0 -657
- package/core/agentic/tool-registry.js +0 -184
- package/core/agentic/validation-rules.js +0 -380
- package/core/command-registry.js +0 -698
- package/core/commands.js +0 -2237
- package/core/domain/task-stack.js +0 -497
- package/core/infrastructure/legacy-installer-detector.js +0 -546
- package/core/infrastructure/migrator.js +0 -796
- package/core/infrastructure/session-manager.js +0 -390
- package/core/utils/file-helper.js +0 -329
- package/packages/web/app/api/projects/[id]/delete/route.ts +0 -21
- package/packages/web/app/api/stats/route.ts +0 -38
- package/packages/web/components/AppSidebar.tsx +0 -113
- package/packages/web/components/stats/ActivityTimeline.tsx +0 -201
- package/packages/web/components/stats/AgentsCard.tsx +0 -56
- package/packages/web/components/stats/BentoCard.tsx +0 -88
- package/packages/web/components/stats/HeroSection.tsx +0 -172
- package/packages/web/components/stats/NowCard.tsx +0 -71
- package/packages/web/components/stats/QueueCard.tsx +0 -58
- package/packages/web/components/stats/VelocityCard.tsx +0 -60
- package/packages/web/components/stats/index.ts +0 -17
- package/packages/web/hooks/useStats.ts +0 -28
- /package/packages/web/components/{CommandButton.tsx → CommandButton/CommandButton.tsx} +0 -0
- /package/packages/web/components/{ConnectionStatus.tsx → ConnectionStatus/ConnectionStatus.tsx} +0 -0
- /package/packages/web/components/{Logo.tsx → Logo/Logo.tsx} +0 -0
- /package/packages/web/components/{MarkdownContent.tsx → MarkdownContent/MarkdownContent.tsx} +0 -0
- /package/packages/web/components/{ProjectAvatar.tsx → ProjectAvatar/ProjectAvatar.tsx} +0 -0
- /package/packages/web/components/{providers.tsx → Providers/Providers.tsx} +0 -0
- /package/packages/web/components/{TechStackBadges.tsx → TechStackBadges/TechStackBadges.tsx} +0 -0
package/core/commands.js
DELETED
|
@@ -1,2237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agentic Commands Handler for prjct CLI
|
|
3
|
-
*
|
|
4
|
-
* 100% AGENTIC - Claude decides everything based on templates.
|
|
5
|
-
* ZERO if/else business logic.
|
|
6
|
-
*
|
|
7
|
-
* All commands use the agentic execution engine.
|
|
8
|
-
* Templates define what Claude should do.
|
|
9
|
-
*
|
|
10
|
-
* MIGRATED COMMANDS (18 total):
|
|
11
|
-
* - Sprint 1 (9 CRITICAL): init, analyze, sync, feature, bug, now, done, next, ship
|
|
12
|
-
* - Sprint 2 (4 IMPORTANT): context, recap, stuck, design
|
|
13
|
-
* - Sprint 3 (5 OPTIONAL): cleanup, progress, roadmap, status, build
|
|
14
|
-
*
|
|
15
|
-
* PENDING (3 total):
|
|
16
|
-
* - Sprint 4 (3 SETUP): start, setup, migrateAll
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const path = require('path')
|
|
20
|
-
|
|
21
|
-
const commandExecutor = require('./agentic/command-executor')
|
|
22
|
-
const contextBuilder = require('./agentic/context-builder')
|
|
23
|
-
const toolRegistry = require('./agentic/tool-registry')
|
|
24
|
-
const memorySystem = require('./agentic/memory-system')
|
|
25
|
-
const AgentRouter = require('./agentic/agent-router')
|
|
26
|
-
const pathManager = require('./infrastructure/path-manager')
|
|
27
|
-
const configManager = require('./infrastructure/config-manager')
|
|
28
|
-
const authorDetector = require('./infrastructure/author-detector')
|
|
29
|
-
const agentDetector = require('./infrastructure/agent-detector')
|
|
30
|
-
const migrator = require('./infrastructure/migrator')
|
|
31
|
-
const UpdateChecker = require('./infrastructure/update-checker')
|
|
32
|
-
const { VERSION } = require('./utils/version')
|
|
33
|
-
const dateHelper = require('./utils/date-helper')
|
|
34
|
-
const jsonlHelper = require('./utils/jsonl-helper')
|
|
35
|
-
const fileHelper = require('./utils/file-helper')
|
|
36
|
-
const out = require('./utils/output')
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Agentic Commands - Template-driven execution
|
|
40
|
-
*/
|
|
41
|
-
class PrjctCommands {
|
|
42
|
-
constructor() {
|
|
43
|
-
this.agent = null
|
|
44
|
-
this.agentInfo = null
|
|
45
|
-
this.currentAuthor = null
|
|
46
|
-
this.prjctDir = '.prjct'
|
|
47
|
-
this.updateChecker = new UpdateChecker()
|
|
48
|
-
this.updateNotificationShown = false
|
|
49
|
-
this.commandExecutor = commandExecutor
|
|
50
|
-
this.agentRouter = new AgentRouter()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Initialize agent (Claude Code, Desktop, or Terminal)
|
|
55
|
-
*/
|
|
56
|
-
async initializeAgent() {
|
|
57
|
-
if (this.agent) return this.agent
|
|
58
|
-
|
|
59
|
-
this.agentInfo = await agentDetector.detect()
|
|
60
|
-
|
|
61
|
-
if (!this.agentInfo.isSupported) {
|
|
62
|
-
throw new Error('Unsupported agent. Please use Claude Code, Claude Desktop, or Terminal.')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const Agent = require(`./infrastructure/agents/${this.agentInfo.type}-agent`)
|
|
66
|
-
this.agent = new Agent()
|
|
67
|
-
|
|
68
|
-
return this.agent
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Ensure project is initialized
|
|
73
|
-
*/
|
|
74
|
-
async ensureProjectInit(projectPath) {
|
|
75
|
-
if (await configManager.isConfigured(projectPath)) {
|
|
76
|
-
return { success: true }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
out.spin('initializing project...')
|
|
80
|
-
const initResult = await this.init(null, projectPath)
|
|
81
|
-
if (!initResult.success) {
|
|
82
|
-
return initResult
|
|
83
|
-
}
|
|
84
|
-
return { success: true }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Ensure author information is loaded
|
|
89
|
-
*/
|
|
90
|
-
async ensureAuthor() {
|
|
91
|
-
if (this.currentAuthor) return this.currentAuthor
|
|
92
|
-
this.currentAuthor = await authorDetector.detectAuthorForLogs()
|
|
93
|
-
return this.currentAuthor
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get global project path
|
|
98
|
-
*/
|
|
99
|
-
async getGlobalProjectPath(projectPath) {
|
|
100
|
-
if (await migrator.needsMigration(projectPath)) {
|
|
101
|
-
throw new Error('Project needs migration. Run /p:migrate first.')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
105
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
106
|
-
return pathManager.getGlobalProjectPath(projectId)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Log to memory
|
|
111
|
-
*/
|
|
112
|
-
async logToMemory(projectPath, action, data) {
|
|
113
|
-
try {
|
|
114
|
-
const author = await this.ensureAuthor()
|
|
115
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
116
|
-
const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
|
|
117
|
-
|
|
118
|
-
const entry = {
|
|
119
|
-
timestamp: dateHelper.getTimestamp(),
|
|
120
|
-
action,
|
|
121
|
-
data,
|
|
122
|
-
author: author.name,
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
await jsonlHelper.appendJsonLine(memoryPath, entry)
|
|
126
|
-
} catch (error) {
|
|
127
|
-
// Non-critical - don't fail the command
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* /p:now - Set or show current task
|
|
133
|
-
* AGENTIC EXECUTION
|
|
134
|
-
*/
|
|
135
|
-
async now(task = null, projectPath = process.cwd()) {
|
|
136
|
-
try {
|
|
137
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
138
|
-
if (!initResult.success) return initResult
|
|
139
|
-
|
|
140
|
-
const context = await contextBuilder.build(projectPath, { task })
|
|
141
|
-
|
|
142
|
-
if (task) {
|
|
143
|
-
// MANDATORY: Assign agent before setting task
|
|
144
|
-
const agentResult = await this._assignAgentForTask(task, projectPath, context)
|
|
145
|
-
const agent = agentResult.agent?.name || 'generalist'
|
|
146
|
-
const confidence = agentResult.routing?.confidence || 0.5
|
|
147
|
-
|
|
148
|
-
// Set task WITH agent
|
|
149
|
-
const nowContent = `# NOW\n\n**${task}**\n\nStarted: ${new Date().toLocaleString()}\nAgent: ${agent} (${Math.round(confidence * 100)}% confidence)\n`
|
|
150
|
-
await toolRegistry.get('Write')(context.paths.now, nowContent)
|
|
151
|
-
|
|
152
|
-
out.done(`${task} [${agent}]`)
|
|
153
|
-
|
|
154
|
-
await this.logToMemory(projectPath, 'task_started', {
|
|
155
|
-
task,
|
|
156
|
-
agent,
|
|
157
|
-
confidence,
|
|
158
|
-
timestamp: dateHelper.getTimestamp(),
|
|
159
|
-
})
|
|
160
|
-
return { success: true, task, agent }
|
|
161
|
-
} else {
|
|
162
|
-
// Show current task
|
|
163
|
-
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
164
|
-
|
|
165
|
-
if (!nowContent || nowContent.includes('No current task')) {
|
|
166
|
-
out.warn('no active task')
|
|
167
|
-
return { success: true, message: 'No active task' }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Extract task name and agent for minimal output
|
|
171
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
172
|
-
const agentMatch = nowContent.match(/Agent: ([^\s(]+)/)
|
|
173
|
-
const currentTask = taskMatch ? taskMatch[1] : 'unknown'
|
|
174
|
-
const currentAgent = agentMatch ? agentMatch[1] : ''
|
|
175
|
-
out.done(`working on: ${currentTask}${currentAgent ? ` [${currentAgent}]` : ''}`)
|
|
176
|
-
return { success: true, content: nowContent }
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
out.fail(error.message)
|
|
180
|
-
return { success: false, error: error.message }
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* /p:done - Complete current task
|
|
186
|
-
* AGENTIC EXECUTION
|
|
187
|
-
*/
|
|
188
|
-
async done(projectPath = process.cwd()) {
|
|
189
|
-
try {
|
|
190
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
191
|
-
if (!initResult.success) return initResult
|
|
192
|
-
|
|
193
|
-
const context = await contextBuilder.build(projectPath)
|
|
194
|
-
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
195
|
-
|
|
196
|
-
// Validate: must have active task
|
|
197
|
-
if (!nowContent || nowContent.includes('No current task') || nowContent.trim() === '# NOW') {
|
|
198
|
-
out.warn('no active task')
|
|
199
|
-
return { success: true, message: 'No active task to complete' }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Extract task and duration
|
|
203
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
204
|
-
const task = taskMatch ? taskMatch[1] : 'task'
|
|
205
|
-
|
|
206
|
-
const startedMatch = nowContent.match(/Started: (.+)/)
|
|
207
|
-
let duration = ''
|
|
208
|
-
if (startedMatch) {
|
|
209
|
-
const started = new Date(startedMatch[1])
|
|
210
|
-
duration = dateHelper.calculateDuration(started)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Clear now.md
|
|
214
|
-
const emptyNow = '# NOW\n\nNo current task. Use `/p:now` to set focus.\n'
|
|
215
|
-
await toolRegistry.get('Write')(context.paths.now, emptyNow)
|
|
216
|
-
|
|
217
|
-
out.done(`${task}${duration ? ` (${duration})` : ''}`)
|
|
218
|
-
|
|
219
|
-
await this.logToMemory(projectPath, 'task_completed', {
|
|
220
|
-
task,
|
|
221
|
-
duration,
|
|
222
|
-
timestamp: dateHelper.getTimestamp(),
|
|
223
|
-
})
|
|
224
|
-
return { success: true, task, duration }
|
|
225
|
-
} catch (error) {
|
|
226
|
-
out.fail(error.message)
|
|
227
|
-
return { success: false, error: error.message }
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* /p:next - Show priority queue
|
|
233
|
-
* AGENTIC EXECUTION
|
|
234
|
-
*/
|
|
235
|
-
async next(projectPath = process.cwd()) {
|
|
236
|
-
try {
|
|
237
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
238
|
-
if (!initResult.success) return initResult
|
|
239
|
-
|
|
240
|
-
const context = await contextBuilder.build(projectPath)
|
|
241
|
-
const nextContent = await toolRegistry.get('Read')(context.paths.next)
|
|
242
|
-
|
|
243
|
-
if (!nextContent || nextContent.trim() === '# NEXT\n\n## Priority Queue') {
|
|
244
|
-
out.warn('queue empty')
|
|
245
|
-
return { success: true, message: 'Queue is empty' }
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Count tasks for minimal output
|
|
249
|
-
const taskCount = (nextContent.match(/^- \[/gm) || []).length
|
|
250
|
-
out.done(`${taskCount} task${taskCount !== 1 ? 's' : ''} queued`)
|
|
251
|
-
|
|
252
|
-
return { success: true, content: nextContent }
|
|
253
|
-
} catch (error) {
|
|
254
|
-
out.fail(error.message)
|
|
255
|
-
return { success: false, error: error.message }
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* /p:init - Initialize prjct project
|
|
261
|
-
* AGENTIC EXECUTION
|
|
262
|
-
*
|
|
263
|
-
* 3 modes:
|
|
264
|
-
* 1. Existing project → analyze + generate agents
|
|
265
|
-
* 2. Blank project + idea → ARCHITECT MODE
|
|
266
|
-
* 3. Blank project no idea → ask for idea
|
|
267
|
-
*/
|
|
268
|
-
async init(idea = null, projectPath = process.cwd()) {
|
|
269
|
-
try {
|
|
270
|
-
await this.initializeAgent()
|
|
271
|
-
|
|
272
|
-
// Check if already configured
|
|
273
|
-
const isConfigured = await configManager.isConfigured(projectPath)
|
|
274
|
-
|
|
275
|
-
if (isConfigured) {
|
|
276
|
-
out.warn('already initialized')
|
|
277
|
-
return { success: false, message: 'Already initialized' }
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
out.spin('initializing...')
|
|
281
|
-
|
|
282
|
-
// Detect author from git
|
|
283
|
-
const author = await authorDetector.detect()
|
|
284
|
-
|
|
285
|
-
// Generate project ID from path hash
|
|
286
|
-
const config = await configManager.createConfig(projectPath, author)
|
|
287
|
-
const projectId = config.projectId
|
|
288
|
-
|
|
289
|
-
out.spin('creating structure...')
|
|
290
|
-
|
|
291
|
-
// Ensure global structure exists
|
|
292
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
293
|
-
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
294
|
-
|
|
295
|
-
// Create base files
|
|
296
|
-
// P1.1: Added patterns.json for Layered Memory System
|
|
297
|
-
const baseFiles = {
|
|
298
|
-
'core/now.md': '# NOW\n\nNo current task. Use `/p:now` to set focus.\n',
|
|
299
|
-
'core/next.md': '# NEXT\n\n## Priority Queue\n\n',
|
|
300
|
-
'core/context.md': '# CONTEXT\n\n',
|
|
301
|
-
'progress/shipped.md': '# SHIPPED 🚀\n\n',
|
|
302
|
-
'progress/metrics.md': '# METRICS\n\n',
|
|
303
|
-
'planning/ideas.md': '# IDEAS 💡\n\n## Brain Dump\n\n',
|
|
304
|
-
'planning/roadmap.md': '# ROADMAP\n\n',
|
|
305
|
-
'planning/specs/.gitkeep': '# Specs directory - created by /p:spec\n',
|
|
306
|
-
'memory/context.jsonl': '',
|
|
307
|
-
'memory/patterns.json': JSON.stringify({
|
|
308
|
-
version: 1,
|
|
309
|
-
decisions: {},
|
|
310
|
-
preferences: {},
|
|
311
|
-
workflows: {},
|
|
312
|
-
counters: {}
|
|
313
|
-
}, null, 2),
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
for (const [filePath, content] of Object.entries(baseFiles)) {
|
|
317
|
-
await toolRegistry.get('Write')(path.join(globalPath, filePath), content)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// Detect project state
|
|
322
|
-
const isEmpty = await this._detectEmptyDirectory(projectPath)
|
|
323
|
-
const hasCode = await this._detectExistingCode(projectPath)
|
|
324
|
-
|
|
325
|
-
// MODE 1: Existing project
|
|
326
|
-
if (hasCode || !isEmpty) {
|
|
327
|
-
out.spin('analyzing project...')
|
|
328
|
-
const analysisResult = await this.analyze({}, projectPath)
|
|
329
|
-
|
|
330
|
-
if (analysisResult.success) {
|
|
331
|
-
out.spin('generating agents...')
|
|
332
|
-
await this.sync(projectPath)
|
|
333
|
-
out.done('initialized')
|
|
334
|
-
return { success: true, mode: 'existing', projectId }
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// MODE 2 & 3: Blank project
|
|
339
|
-
if (isEmpty && !hasCode) {
|
|
340
|
-
if (!idea) {
|
|
341
|
-
out.done('blank project - provide idea for architect mode')
|
|
342
|
-
return { success: true, mode: 'blank_no_idea', projectId }
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// MODE 2: ARCHITECT MODE
|
|
346
|
-
out.spin('architect mode...')
|
|
347
|
-
const sessionPath = path.join(globalPath, 'planning', 'architect-session.md')
|
|
348
|
-
const sessionContent = `# Architect Session\n\n## Idea\n${idea}\n\n## Status\nInitialized - awaiting stack recommendation\n\nGenerated: ${new Date().toLocaleString()}\n`
|
|
349
|
-
await toolRegistry.get('Write')(sessionPath, sessionContent)
|
|
350
|
-
|
|
351
|
-
const commandInstaller = require('./infrastructure/command-installer')
|
|
352
|
-
await commandInstaller.installGlobalConfig()
|
|
353
|
-
|
|
354
|
-
out.done('architect mode ready')
|
|
355
|
-
return { success: true, mode: 'architect', projectId, idea }
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Update global CLAUDE.md with latest instructions (fallback for any case)
|
|
359
|
-
const commandInstaller = require('./infrastructure/command-installer')
|
|
360
|
-
await commandInstaller.installGlobalConfig()
|
|
361
|
-
|
|
362
|
-
out.done('initialized')
|
|
363
|
-
return { success: true, projectId }
|
|
364
|
-
} catch (error) {
|
|
365
|
-
out.fail(error.message)
|
|
366
|
-
return { success: false, error: error.message }
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Detect if directory is empty (excluding common files)
|
|
372
|
-
* @private
|
|
373
|
-
*/
|
|
374
|
-
async _detectEmptyDirectory(projectPath) {
|
|
375
|
-
try {
|
|
376
|
-
const entries = await fileHelper.listFiles(projectPath)
|
|
377
|
-
const meaningfulFiles = entries.filter(
|
|
378
|
-
(name) =>
|
|
379
|
-
!name.startsWith('.') &&
|
|
380
|
-
name !== 'node_modules' &&
|
|
381
|
-
name !== 'package.json' &&
|
|
382
|
-
name !== 'package-lock.json' &&
|
|
383
|
-
name !== 'README.md'
|
|
384
|
-
)
|
|
385
|
-
return meaningfulFiles.length === 0
|
|
386
|
-
} catch {
|
|
387
|
-
return true
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Detect if directory has existing code
|
|
393
|
-
* @private
|
|
394
|
-
*/
|
|
395
|
-
async _detectExistingCode(projectPath) {
|
|
396
|
-
try {
|
|
397
|
-
const codePatterns = [
|
|
398
|
-
'src',
|
|
399
|
-
'lib',
|
|
400
|
-
'app',
|
|
401
|
-
'components',
|
|
402
|
-
'pages',
|
|
403
|
-
'api',
|
|
404
|
-
'main.go',
|
|
405
|
-
'main.rs',
|
|
406
|
-
'main.py',
|
|
407
|
-
]
|
|
408
|
-
const entries = await fileHelper.listFiles(projectPath)
|
|
409
|
-
|
|
410
|
-
return entries.some((name) => codePatterns.includes(name))
|
|
411
|
-
} catch {
|
|
412
|
-
return false
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* All other commands - TODO: Migrate to agentic execution
|
|
418
|
-
* For now, return "not implemented" message
|
|
419
|
-
*/
|
|
420
|
-
/**
|
|
421
|
-
* /p:feature - Add feature with value analysis, roadmap, and task breakdown
|
|
422
|
-
* AGENTIC EXECUTION
|
|
423
|
-
*/
|
|
424
|
-
async feature(description, projectPath = process.cwd()) {
|
|
425
|
-
try {
|
|
426
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
427
|
-
if (!initResult.success) return initResult
|
|
428
|
-
|
|
429
|
-
if (!description) {
|
|
430
|
-
out.fail('description required')
|
|
431
|
-
return { success: false, error: 'Description required' }
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
out.spin(`planning ${description}...`)
|
|
435
|
-
|
|
436
|
-
const context = await contextBuilder.build(projectPath, { description })
|
|
437
|
-
|
|
438
|
-
// Task breakdown
|
|
439
|
-
const tasks = this._breakdownFeatureTasks(description)
|
|
440
|
-
|
|
441
|
-
// MANDATORY: Assign agent to each task
|
|
442
|
-
const tasksWithAgents = []
|
|
443
|
-
for (const taskDesc of tasks) {
|
|
444
|
-
const agentResult = await this._assignAgentForTask(taskDesc, projectPath, context)
|
|
445
|
-
const agent = agentResult.agent?.name || 'generalist'
|
|
446
|
-
tasksWithAgents.push({ task: taskDesc, agent })
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Write to next.md with agents
|
|
450
|
-
const nextContent =
|
|
451
|
-
(await toolRegistry.get('Read')(context.paths.next)) || '# NEXT\n\n## Priority Queue\n\n'
|
|
452
|
-
const taskSection =
|
|
453
|
-
`\n## Feature: ${description}\n\n` +
|
|
454
|
-
tasksWithAgents.map((t, i) => `${i + 1}. [${t.agent}] [ ] ${t.task}`).join('\n') +
|
|
455
|
-
`\n\nEstimated: ${tasks.length * 2}h\n`
|
|
456
|
-
|
|
457
|
-
await toolRegistry.get('Write')(context.paths.next, nextContent + taskSection)
|
|
458
|
-
|
|
459
|
-
// Log to memory with agent assignments
|
|
460
|
-
await this.logToMemory(projectPath, 'feature_planned', {
|
|
461
|
-
feature: description,
|
|
462
|
-
tasks: tasksWithAgents.length,
|
|
463
|
-
assignments: tasksWithAgents.map(t => ({ task: t.task, agent: t.agent })),
|
|
464
|
-
timestamp: dateHelper.getTimestamp(),
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
// Show summary with agent distribution
|
|
468
|
-
const agentCounts = tasksWithAgents.reduce((acc, t) => {
|
|
469
|
-
acc[t.agent] = (acc[t.agent] || 0) + 1
|
|
470
|
-
return acc
|
|
471
|
-
}, {})
|
|
472
|
-
const agentSummary = Object.entries(agentCounts).map(([a, c]) => `${a}:${c}`).join(' ')
|
|
473
|
-
|
|
474
|
-
out.done(`${tasks.length} tasks [${agentSummary}]`)
|
|
475
|
-
|
|
476
|
-
return { success: true, feature: description, tasks: tasksWithAgents }
|
|
477
|
-
} catch (error) {
|
|
478
|
-
out.fail(error.message)
|
|
479
|
-
return { success: false, error: error.message }
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Breakdown feature into tasks
|
|
485
|
-
* Claude would do intelligent breakdown based on feature description
|
|
486
|
-
* @private
|
|
487
|
-
*/
|
|
488
|
-
_breakdownFeatureTasks(description) {
|
|
489
|
-
// AGENTIC: Claude analyzes and creates tasks via templates/analysis/task-breakdown.md
|
|
490
|
-
// This returns a placeholder - real breakdown happens in template execution
|
|
491
|
-
return [
|
|
492
|
-
`Analyze and plan: ${description}`,
|
|
493
|
-
'Implement core functionality',
|
|
494
|
-
'Test and validate',
|
|
495
|
-
'Document changes',
|
|
496
|
-
]
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* /p:bug - Report and track bugs with auto-prioritization
|
|
501
|
-
* AGENTIC EXECUTION
|
|
502
|
-
*/
|
|
503
|
-
async bug(description, projectPath = process.cwd()) {
|
|
504
|
-
try {
|
|
505
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
506
|
-
if (!initResult.success) return initResult
|
|
507
|
-
|
|
508
|
-
if (!description) {
|
|
509
|
-
out.fail('bug description required')
|
|
510
|
-
return { success: false, error: 'Description required' }
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
out.spin('tracking bug...')
|
|
514
|
-
|
|
515
|
-
const context = await contextBuilder.build(projectPath, { description })
|
|
516
|
-
const severity = this._detectBugSeverity(description)
|
|
517
|
-
|
|
518
|
-
// MANDATORY: Assign agent to bug
|
|
519
|
-
const agentResult = await this._assignAgentForTask(`fix bug: ${description}`, projectPath, context)
|
|
520
|
-
const agent = agentResult.agent?.name || 'generalist'
|
|
521
|
-
|
|
522
|
-
// Add to next.md with priority and agent
|
|
523
|
-
const nextContent =
|
|
524
|
-
(await toolRegistry.get('Read')(context.paths.next)) || '# NEXT\n\n## Priority Queue\n\n'
|
|
525
|
-
const bugEntry = `\n## 🐛 BUG [${severity.toUpperCase()}] [${agent}]: ${description}\n\nReported: ${new Date().toLocaleString()}\nPriority: ${severity === 'critical' ? '⚠️ URGENT' : severity === 'high' ? '🔴 High' : '🟡 Normal'}\nAssigned: ${agent}\n`
|
|
526
|
-
|
|
527
|
-
// Insert at top if critical/high, at bottom otherwise
|
|
528
|
-
const updatedContent =
|
|
529
|
-
severity === 'critical' || severity === 'high'
|
|
530
|
-
? nextContent.replace('## Priority Queue\n\n', `## Priority Queue\n\n${bugEntry}\n`)
|
|
531
|
-
: nextContent + bugEntry
|
|
532
|
-
|
|
533
|
-
await toolRegistry.get('Write')(context.paths.next, updatedContent)
|
|
534
|
-
|
|
535
|
-
// Log to memory with agent
|
|
536
|
-
await this.logToMemory(projectPath, 'bug_reported', {
|
|
537
|
-
bug: description,
|
|
538
|
-
severity,
|
|
539
|
-
agent,
|
|
540
|
-
timestamp: dateHelper.getTimestamp(),
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
out.done(`bug [${severity}] → ${agent}`)
|
|
544
|
-
|
|
545
|
-
return { success: true, bug: description, severity, agent }
|
|
546
|
-
} catch (error) {
|
|
547
|
-
out.fail(error.message)
|
|
548
|
-
return { success: false, error: error.message }
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Detect bug severity from description
|
|
554
|
-
* Claude would do intelligent analysis
|
|
555
|
-
* @private
|
|
556
|
-
*/
|
|
557
|
-
_detectBugSeverity(description) {
|
|
558
|
-
// AGENTIC: Claude assesses severity via templates/analysis/bug-severity.md
|
|
559
|
-
// Returns default - real assessment happens in template execution
|
|
560
|
-
return 'medium'
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* /p:ship - Ship feature with complete automated workflow
|
|
565
|
-
* AGENTIC EXECUTION
|
|
566
|
-
*/
|
|
567
|
-
async ship(feature, projectPath = process.cwd()) {
|
|
568
|
-
try {
|
|
569
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
570
|
-
if (!initResult.success) return initResult
|
|
571
|
-
|
|
572
|
-
if (!feature) {
|
|
573
|
-
// Try to infer from current task
|
|
574
|
-
const context = await contextBuilder.build(projectPath)
|
|
575
|
-
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
576
|
-
if (nowContent && nowContent.includes('**')) {
|
|
577
|
-
const match = nowContent.match(/\*\*(.+?)\*\*/)
|
|
578
|
-
feature = match ? match[1] : 'current work'
|
|
579
|
-
} else {
|
|
580
|
-
feature = 'current work'
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
out.spin(`shipping ${feature}...`)
|
|
585
|
-
|
|
586
|
-
// Step 1: Lint
|
|
587
|
-
const lintResult = await this._runLint(projectPath)
|
|
588
|
-
|
|
589
|
-
// Step 2: Tests
|
|
590
|
-
out.spin('running tests...')
|
|
591
|
-
const testResult = await this._runTests(projectPath)
|
|
592
|
-
|
|
593
|
-
// Step 3-5: Version + changelog
|
|
594
|
-
out.spin('updating version...')
|
|
595
|
-
const newVersion = await this._bumpVersion(projectPath)
|
|
596
|
-
await this._updateChangelog(feature, newVersion, projectPath)
|
|
597
|
-
|
|
598
|
-
// Step 6-7: Git commit + push
|
|
599
|
-
out.spin('committing...')
|
|
600
|
-
const commitResult = await this._createShipCommit(feature, projectPath)
|
|
601
|
-
|
|
602
|
-
if (commitResult.success) {
|
|
603
|
-
out.spin('pushing...')
|
|
604
|
-
await this._gitPush(projectPath)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Log to memory and shipped.md
|
|
608
|
-
const context = await contextBuilder.build(projectPath)
|
|
609
|
-
const shippedContent =
|
|
610
|
-
(await toolRegistry.get('Read')(context.paths.shipped)) || '# SHIPPED 🚀\n\n'
|
|
611
|
-
const shippedEntry = `\n## ${feature}\n\nShipped: ${new Date().toLocaleString()}\nVersion: ${newVersion}\n`
|
|
612
|
-
await toolRegistry.get('Write')(context.paths.shipped, shippedContent + shippedEntry)
|
|
613
|
-
|
|
614
|
-
await this.logToMemory(projectPath, 'feature_shipped', {
|
|
615
|
-
feature,
|
|
616
|
-
version: newVersion,
|
|
617
|
-
timestamp: dateHelper.getTimestamp(),
|
|
618
|
-
})
|
|
619
|
-
|
|
620
|
-
// P1.1: Learn patterns from this ship
|
|
621
|
-
const config = await configManager.getConfig(projectPath)
|
|
622
|
-
const projectId = config.projectId
|
|
623
|
-
|
|
624
|
-
// Record shipping workflow patterns
|
|
625
|
-
await memorySystem.learnDecision(projectId, 'commit_footer', 'prjct', 'ship')
|
|
626
|
-
|
|
627
|
-
// Track if tests were run (for quick_ship pattern learning)
|
|
628
|
-
if (testResult.success) {
|
|
629
|
-
await memorySystem.recordDecision(projectId, 'test_before_ship', 'true', 'ship')
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// Record workflow if it's a quick ship (small changes)
|
|
633
|
-
const isQuickShip = !lintResult.success || !testResult.success
|
|
634
|
-
if (isQuickShip) {
|
|
635
|
-
await memorySystem.recordWorkflow(projectId, 'quick_ship', {
|
|
636
|
-
description: 'Ship without full checks',
|
|
637
|
-
feature_type: feature.toLowerCase().includes('doc') ? 'docs' : 'other'
|
|
638
|
-
})
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
out.done(`v${newVersion} shipped`)
|
|
642
|
-
|
|
643
|
-
return { success: true, feature, version: newVersion }
|
|
644
|
-
} catch (error) {
|
|
645
|
-
out.fail(error.message)
|
|
646
|
-
return { success: false, error: error.message }
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Run lint checks
|
|
652
|
-
* @private
|
|
653
|
-
*/
|
|
654
|
-
async _runLint(_projectPath) {
|
|
655
|
-
try {
|
|
656
|
-
const { stderr } = await toolRegistry.get('Bash')('npm run lint 2>&1 || true')
|
|
657
|
-
return { success: !stderr.includes('error'), message: 'passed' }
|
|
658
|
-
} catch {
|
|
659
|
-
return { success: false, message: 'no lint script (skipped)' }
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Run tests
|
|
665
|
-
* @private
|
|
666
|
-
*/
|
|
667
|
-
async _runTests(_projectPath) {
|
|
668
|
-
try {
|
|
669
|
-
const { stderr } = await toolRegistry.get('Bash')(
|
|
670
|
-
'npm test -- --passWithNoTests 2>&1 || true'
|
|
671
|
-
)
|
|
672
|
-
return { success: !stderr.includes('FAIL'), message: 'passed' }
|
|
673
|
-
} catch {
|
|
674
|
-
return { success: false, message: 'no test script (skipped)' }
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Bump version
|
|
680
|
-
* @private
|
|
681
|
-
*/
|
|
682
|
-
async _bumpVersion(projectPath) {
|
|
683
|
-
try {
|
|
684
|
-
const pkgPath = path.join(projectPath, 'package.json')
|
|
685
|
-
const pkg = await fileHelper.readJson(pkgPath, {})
|
|
686
|
-
const oldVersion = pkg.version || '0.0.0'
|
|
687
|
-
const [major, minor, patch] = oldVersion.split('.').map(Number)
|
|
688
|
-
const newVersion = `${major}.${minor}.${patch + 1}`
|
|
689
|
-
pkg.version = newVersion
|
|
690
|
-
await fileHelper.writeJson(pkgPath, pkg)
|
|
691
|
-
return newVersion
|
|
692
|
-
} catch {
|
|
693
|
-
return '0.0.1'
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Update CHANGELOG
|
|
699
|
-
* @private
|
|
700
|
-
*/
|
|
701
|
-
async _updateChangelog(feature, version, projectPath) {
|
|
702
|
-
try {
|
|
703
|
-
const changelogPath = path.join(projectPath, 'CHANGELOG.md')
|
|
704
|
-
const changelog = await fileHelper.readFile(changelogPath, '# Changelog\n\n')
|
|
705
|
-
|
|
706
|
-
const entry = `## [${version}] - ${dateHelper.formatDate(new Date())}\n\n### Added\n- ${feature}\n\n`
|
|
707
|
-
const updated = changelog.replace('# Changelog\n\n', `# Changelog\n\n${entry}`)
|
|
708
|
-
|
|
709
|
-
await fileHelper.writeFile(changelogPath, updated)
|
|
710
|
-
} catch (error) {
|
|
711
|
-
console.error(' Warning: Could not update CHANGELOG')
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* Create git commit for ship
|
|
717
|
-
* @private
|
|
718
|
-
*/
|
|
719
|
-
async _createShipCommit(feature, _projectPath) {
|
|
720
|
-
try {
|
|
721
|
-
await toolRegistry.get('Bash')('git add .')
|
|
722
|
-
|
|
723
|
-
const commitMsg = `feat: ${feature}\n\n🤖 Generated with [p/](https://www.prjct.app/)\nDesigned for [Claude](https://www.anthropic.com/claude)`
|
|
724
|
-
|
|
725
|
-
await toolRegistry.get('Bash')(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`)
|
|
726
|
-
|
|
727
|
-
return { success: true, message: 'Committed' }
|
|
728
|
-
} catch {
|
|
729
|
-
return { success: false, message: 'No changes to commit' }
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Push to remote
|
|
735
|
-
* @private
|
|
736
|
-
*/
|
|
737
|
-
async _gitPush(_projectPath) {
|
|
738
|
-
try {
|
|
739
|
-
await toolRegistry.get('Bash')('git push')
|
|
740
|
-
return { success: true, message: 'Pushed to remote' }
|
|
741
|
-
} catch {
|
|
742
|
-
return { success: false, message: 'Push failed (no remote or auth issue)' }
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* /p:context - Show project context and recent activity
|
|
748
|
-
* AGENTIC EXECUTION
|
|
749
|
-
*/
|
|
750
|
-
async context(projectPath = process.cwd()) {
|
|
751
|
-
try {
|
|
752
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
753
|
-
if (!initResult.success) return initResult
|
|
754
|
-
|
|
755
|
-
out.spin('loading context...')
|
|
756
|
-
const context = await contextBuilder.build(projectPath)
|
|
757
|
-
|
|
758
|
-
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
759
|
-
const nextContent = await toolRegistry.get('Read')(context.paths.next)
|
|
760
|
-
|
|
761
|
-
// Extract summary
|
|
762
|
-
let task = 'none'
|
|
763
|
-
if (nowContent && !nowContent.includes('No current task')) {
|
|
764
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
765
|
-
task = taskMatch ? taskMatch[1] : 'active'
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
const nextLines = nextContent?.split('\n').filter((line) => line.trim() && !line.startsWith('#')) || []
|
|
769
|
-
const queueCount = nextLines.length
|
|
770
|
-
|
|
771
|
-
await this.logToMemory(projectPath, 'context_viewed', { timestamp: dateHelper.getTimestamp() })
|
|
772
|
-
|
|
773
|
-
out.done(`task: ${task} | queue: ${queueCount}`)
|
|
774
|
-
return { success: true }
|
|
775
|
-
} catch (error) {
|
|
776
|
-
out.fail(error.message)
|
|
777
|
-
return { success: false, error: error.message }
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
/**
|
|
782
|
-
* /p:recap - Show project overview with progress
|
|
783
|
-
* AGENTIC EXECUTION
|
|
784
|
-
*/
|
|
785
|
-
async recap(projectPath = process.cwd()) {
|
|
786
|
-
try {
|
|
787
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
788
|
-
if (!initResult.success) return initResult
|
|
789
|
-
|
|
790
|
-
out.spin('loading recap...')
|
|
791
|
-
const context = await contextBuilder.build(projectPath)
|
|
792
|
-
|
|
793
|
-
const shippedContent = await toolRegistry.get('Read')(context.paths.shipped)
|
|
794
|
-
const shippedFeatures = shippedContent?.split('##').filter((s) => s.trim() && !s.includes('SHIPPED')) || []
|
|
795
|
-
|
|
796
|
-
const nextContent = await toolRegistry.get('Read')(context.paths.next)
|
|
797
|
-
const nextTasks = nextContent?.split('\n').filter((l) => l.match(/^\d+\./) || l.includes('[ ]')).length || 0
|
|
798
|
-
|
|
799
|
-
const ideasContent = await toolRegistry.get('Read')(context.paths.ideas)
|
|
800
|
-
const ideas = ideasContent?.split('##').filter((s) => s.trim() && !s.includes('IDEAS') && !s.includes('Brain')).length || 0
|
|
801
|
-
|
|
802
|
-
await this.logToMemory(projectPath, 'recap_viewed', {
|
|
803
|
-
shipped: shippedFeatures.length,
|
|
804
|
-
tasks: nextTasks,
|
|
805
|
-
timestamp: dateHelper.getTimestamp(),
|
|
806
|
-
})
|
|
807
|
-
|
|
808
|
-
out.done(`shipped: ${shippedFeatures.length} | queue: ${nextTasks} | ideas: ${ideas}`)
|
|
809
|
-
return { success: true, stats: { shipped: shippedFeatures.length, tasks: nextTasks, ideas } }
|
|
810
|
-
} catch (error) {
|
|
811
|
-
out.fail(error.message)
|
|
812
|
-
return { success: false, error: error.message }
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* /p:stuck - Get contextual help with problems
|
|
818
|
-
* AGENTIC EXECUTION
|
|
819
|
-
*/
|
|
820
|
-
async stuck(issue, projectPath = process.cwd()) {
|
|
821
|
-
try {
|
|
822
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
823
|
-
if (!initResult.success) return initResult
|
|
824
|
-
|
|
825
|
-
if (!issue) {
|
|
826
|
-
out.fail('issue description required')
|
|
827
|
-
return { success: false, error: 'Issue description required' }
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
out.spin('logging issue...')
|
|
831
|
-
|
|
832
|
-
const analyzer = require('./domain/analyzer')
|
|
833
|
-
analyzer.init(projectPath)
|
|
834
|
-
const packageJson = await analyzer.readPackageJson()
|
|
835
|
-
const detectedStack = packageJson?.name || 'project'
|
|
836
|
-
|
|
837
|
-
await this.logToMemory(projectPath, 'help_requested', {
|
|
838
|
-
issue,
|
|
839
|
-
stack: detectedStack,
|
|
840
|
-
timestamp: dateHelper.getTimestamp(),
|
|
841
|
-
})
|
|
842
|
-
|
|
843
|
-
out.done(`issue logged: ${issue.slice(0, 40)}`)
|
|
844
|
-
return { success: true, issue, stack: detectedStack }
|
|
845
|
-
} catch (error) {
|
|
846
|
-
out.fail(error.message)
|
|
847
|
-
return { success: false, error: error.message }
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
* /p:design - Design system architecture, APIs, and components
|
|
853
|
-
* AGENTIC EXECUTION
|
|
854
|
-
*/
|
|
855
|
-
/**
|
|
856
|
-
* Memory cleanup helper
|
|
857
|
-
* Rotates large JSONL files, archives old sessions, reports disk usage
|
|
858
|
-
* @private
|
|
859
|
-
*/
|
|
860
|
-
async _cleanupMemory(projectPath) {
|
|
861
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
862
|
-
|
|
863
|
-
const results = { rotated: [], totalSize: 0, freedSpace: 0 }
|
|
864
|
-
const jsonlFiles = [
|
|
865
|
-
pathManager.getFilePath(projectId, 'memory', 'context.jsonl'),
|
|
866
|
-
pathManager.getFilePath(projectId, 'progress', 'shipped.md'),
|
|
867
|
-
pathManager.getFilePath(projectId, 'planning', 'ideas.md'),
|
|
868
|
-
]
|
|
869
|
-
|
|
870
|
-
for (const filePath of jsonlFiles) {
|
|
871
|
-
try {
|
|
872
|
-
const sizeMB = await jsonlHelper.getFileSizeMB(filePath)
|
|
873
|
-
if (sizeMB > 0) {
|
|
874
|
-
results.totalSize += sizeMB
|
|
875
|
-
const rotated = await jsonlHelper.rotateJsonLinesIfNeeded(filePath, 10)
|
|
876
|
-
if (rotated) {
|
|
877
|
-
results.rotated.push(path.basename(filePath))
|
|
878
|
-
results.freedSpace += sizeMB
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
} catch {
|
|
882
|
-
// skip
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
return { success: true, results }
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* Internal cleanup helper for memory during normal cleanup
|
|
891
|
-
* @private
|
|
892
|
-
*/
|
|
893
|
-
async _cleanupMemoryInternal(projectPath) {
|
|
894
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
895
|
-
|
|
896
|
-
// Silently rotate large files
|
|
897
|
-
const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
|
|
898
|
-
await jsonlHelper.rotateJsonLinesIfNeeded(memoryPath, 10)
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
async design(target = null, options = {}, projectPath = process.cwd()) {
|
|
902
|
-
try {
|
|
903
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
904
|
-
if (!initResult.success) return initResult
|
|
905
|
-
|
|
906
|
-
const designType = options.type || 'architecture'
|
|
907
|
-
const validTypes = ['architecture', 'api', 'component', 'database', 'flow']
|
|
908
|
-
|
|
909
|
-
if (!validTypes.includes(designType)) {
|
|
910
|
-
out.fail(`invalid type: ${designType}`)
|
|
911
|
-
return { success: false, error: 'Invalid design type' }
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const designTarget = target || 'system'
|
|
915
|
-
out.spin(`designing ${designType}...`)
|
|
916
|
-
|
|
917
|
-
// Create designs directory if it doesn't exist
|
|
918
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
919
|
-
const designsPath = path.join(
|
|
920
|
-
pathManager.getGlobalProjectPath(projectId),
|
|
921
|
-
'planning',
|
|
922
|
-
'designs'
|
|
923
|
-
)
|
|
924
|
-
await fileHelper.ensureDir(designsPath)
|
|
925
|
-
|
|
926
|
-
// Generate design document based on type
|
|
927
|
-
let designContent = ''
|
|
928
|
-
|
|
929
|
-
switch (designType) {
|
|
930
|
-
case 'architecture':
|
|
931
|
-
designContent = this._generateArchitectureDesign(designTarget, projectPath)
|
|
932
|
-
break
|
|
933
|
-
case 'api':
|
|
934
|
-
designContent = this._generateApiDesign(designTarget)
|
|
935
|
-
break
|
|
936
|
-
case 'component':
|
|
937
|
-
designContent = this._generateComponentDesign(designTarget)
|
|
938
|
-
break
|
|
939
|
-
case 'database':
|
|
940
|
-
designContent = this._generateDatabaseDesign(designTarget)
|
|
941
|
-
break
|
|
942
|
-
case 'flow':
|
|
943
|
-
designContent = this._generateFlowDesign(designTarget)
|
|
944
|
-
break
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// Save design document
|
|
948
|
-
const designFileName = `${designType}-${designTarget.toLowerCase().replace(/\s+/g, '-')}.md`
|
|
949
|
-
const designFilePath = path.join(designsPath, designFileName)
|
|
950
|
-
await fileHelper.writeFile(designFilePath, designContent)
|
|
951
|
-
|
|
952
|
-
await this.logToMemory(projectPath, 'design_created', {
|
|
953
|
-
type: designType,
|
|
954
|
-
target: designTarget,
|
|
955
|
-
timestamp: dateHelper.getTimestamp(),
|
|
956
|
-
})
|
|
957
|
-
|
|
958
|
-
out.done(`${designType} design created`)
|
|
959
|
-
return { success: true, designPath: designFilePath, type: designType, target: designTarget }
|
|
960
|
-
} catch (error) {
|
|
961
|
-
out.fail(error.message)
|
|
962
|
-
return { success: false, error: error.message }
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
/**
|
|
967
|
-
* Generate architecture design document
|
|
968
|
-
* @private
|
|
969
|
-
*/
|
|
970
|
-
_generateArchitectureDesign(target, projectPath) {
|
|
971
|
-
// AGENTIC: Claude generates via templates/design/architecture.md
|
|
972
|
-
return `# Architecture Design: ${target}\n\n*Use templates/design/architecture.md for full design*\n`
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
/**
|
|
976
|
-
* Generate API design document
|
|
977
|
-
* @private
|
|
978
|
-
*/
|
|
979
|
-
_generateApiDesign(target) {
|
|
980
|
-
// AGENTIC: Claude generates via templates/design/api.md
|
|
981
|
-
return `# API Design: ${target}\n\n*Use templates/design/api.md for full design*\n`
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* Generate component design document
|
|
986
|
-
* @private
|
|
987
|
-
*/
|
|
988
|
-
_generateComponentDesign(target) {
|
|
989
|
-
// AGENTIC: Claude generates via templates/design/component.md
|
|
990
|
-
return `# Component Design: ${target}\n\n*Use templates/design/component.md for full design*\n`
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Generate database design document
|
|
995
|
-
* @private
|
|
996
|
-
*/
|
|
997
|
-
_generateDatabaseDesign(target) {
|
|
998
|
-
// AGENTIC: Claude generates via templates/design/database.md
|
|
999
|
-
return `# Database Design: ${target}\n\n*Use templates/design/database.md for full design*\n`
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
/**
|
|
1003
|
-
* Generate flow design document
|
|
1004
|
-
* @private
|
|
1005
|
-
*/
|
|
1006
|
-
_generateFlowDesign(target) {
|
|
1007
|
-
// AGENTIC: Claude generates via templates/design/flow.md
|
|
1008
|
-
return `# Flow Design: ${target}\n\n*Use templates/design/flow.md for full design*\n`
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
* /p:cleanup - Clean temp files and old entries
|
|
1013
|
-
* AGENTIC EXECUTION
|
|
1014
|
-
*/
|
|
1015
|
-
async cleanup(_options = {}, projectPath = process.cwd()) {
|
|
1016
|
-
try {
|
|
1017
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
1018
|
-
if (!initResult.success) return initResult
|
|
1019
|
-
|
|
1020
|
-
const isMemoryMode = _options.memory === true || _options.type === 'memory'
|
|
1021
|
-
|
|
1022
|
-
if (isMemoryMode) {
|
|
1023
|
-
out.spin('cleaning memory...')
|
|
1024
|
-
const result = await this._cleanupMemory(projectPath)
|
|
1025
|
-
out.done('memory cleaned')
|
|
1026
|
-
return result
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
out.spin('cleaning up...')
|
|
1030
|
-
|
|
1031
|
-
const context = await contextBuilder.build(projectPath)
|
|
1032
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
1033
|
-
|
|
1034
|
-
const cleaned = []
|
|
1035
|
-
|
|
1036
|
-
// Clean old memory entries (keep last 100)
|
|
1037
|
-
const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
|
|
1038
|
-
try {
|
|
1039
|
-
const entries = await jsonlHelper.readJsonLines(memoryPath)
|
|
1040
|
-
|
|
1041
|
-
if (entries.length > 100) {
|
|
1042
|
-
const kept = entries.slice(-100)
|
|
1043
|
-
await jsonlHelper.writeJsonLines(memoryPath, kept)
|
|
1044
|
-
cleaned.push(`Memory: ${entries.length - 100} old entries removed`)
|
|
1045
|
-
} else {
|
|
1046
|
-
cleaned.push('Memory: No cleanup needed')
|
|
1047
|
-
}
|
|
1048
|
-
} catch {
|
|
1049
|
-
cleaned.push('Memory: No file found')
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// Clean empty ideas sections
|
|
1053
|
-
const ideasPath = context.paths.ideas
|
|
1054
|
-
try {
|
|
1055
|
-
const ideasContent = await toolRegistry.get('Read')(ideasPath)
|
|
1056
|
-
const sections = ideasContent.split('##').filter((s) => s.trim())
|
|
1057
|
-
|
|
1058
|
-
// Remove empty sections
|
|
1059
|
-
const nonEmpty = sections.filter((section) => {
|
|
1060
|
-
const lines = section
|
|
1061
|
-
.trim()
|
|
1062
|
-
.split('\n')
|
|
1063
|
-
.filter((l) => l.trim())
|
|
1064
|
-
return lines.length > 1 // Keep if has more than just title
|
|
1065
|
-
})
|
|
1066
|
-
|
|
1067
|
-
if (sections.length !== nonEmpty.length) {
|
|
1068
|
-
const newContent =
|
|
1069
|
-
'# IDEAS 💡\n\n## Brain Dump\n\n' +
|
|
1070
|
-
nonEmpty
|
|
1071
|
-
.slice(1)
|
|
1072
|
-
.map((s) => '## ' + s.trim())
|
|
1073
|
-
.join('\n\n')
|
|
1074
|
-
await toolRegistry.get('Write')(ideasPath, newContent)
|
|
1075
|
-
cleaned.push(`Ideas: ${sections.length - nonEmpty.length} empty sections removed`)
|
|
1076
|
-
} else {
|
|
1077
|
-
cleaned.push('Ideas: No cleanup needed')
|
|
1078
|
-
}
|
|
1079
|
-
} catch {
|
|
1080
|
-
cleaned.push('Ideas: No file found')
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// Clean completed tasks from next.md (optional - user decides)
|
|
1084
|
-
const nextPath = context.paths.next
|
|
1085
|
-
try {
|
|
1086
|
-
const nextContent = await toolRegistry.get('Read')(nextPath)
|
|
1087
|
-
const completedTasks = (nextContent.match(/\[x\]/gi) || []).length
|
|
1088
|
-
|
|
1089
|
-
if (completedTasks > 0) {
|
|
1090
|
-
cleaned.push(
|
|
1091
|
-
`Queue: ${completedTasks} completed tasks found (not removed - use /p:done to clear)`
|
|
1092
|
-
)
|
|
1093
|
-
} else {
|
|
1094
|
-
cleaned.push('Queue: No completed tasks')
|
|
1095
|
-
}
|
|
1096
|
-
} catch {
|
|
1097
|
-
cleaned.push('Queue: No file found')
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
await this._cleanupMemoryInternal(projectPath)
|
|
1101
|
-
|
|
1102
|
-
await this.logToMemory(projectPath, 'cleanup_performed', {
|
|
1103
|
-
items: cleaned.length,
|
|
1104
|
-
timestamp: dateHelper.getTimestamp(),
|
|
1105
|
-
})
|
|
1106
|
-
|
|
1107
|
-
out.done(`${cleaned.length} items cleaned`)
|
|
1108
|
-
return { success: true, cleaned }
|
|
1109
|
-
} catch (error) {
|
|
1110
|
-
out.fail(error.message)
|
|
1111
|
-
return { success: false, error: error.message }
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
/**
|
|
1116
|
-
* /p:progress - Show metrics for period
|
|
1117
|
-
* AGENTIC EXECUTION
|
|
1118
|
-
*/
|
|
1119
|
-
async progress(period = 'week', projectPath = process.cwd()) {
|
|
1120
|
-
try {
|
|
1121
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
1122
|
-
if (!initResult.success) return initResult
|
|
1123
|
-
|
|
1124
|
-
const validPeriods = ['day', 'week', 'month', 'all']
|
|
1125
|
-
if (!validPeriods.includes(period)) period = 'week'
|
|
1126
|
-
|
|
1127
|
-
out.spin(`loading ${period} progress...`)
|
|
1128
|
-
|
|
1129
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
1130
|
-
const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
|
|
1131
|
-
|
|
1132
|
-
const startDate = period === 'day' ? dateHelper.getDaysAgo(1) :
|
|
1133
|
-
period === 'week' ? dateHelper.getDaysAgo(7) :
|
|
1134
|
-
period === 'month' ? dateHelper.getDaysAgo(30) : new Date(0)
|
|
1135
|
-
|
|
1136
|
-
let entries = []
|
|
1137
|
-
try {
|
|
1138
|
-
const allEntries = await jsonlHelper.readJsonLines(memoryPath)
|
|
1139
|
-
entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
|
|
1140
|
-
} catch { entries = [] }
|
|
1141
|
-
|
|
1142
|
-
const metrics = {
|
|
1143
|
-
tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
|
|
1144
|
-
featuresShipped: entries.filter((e) => e.action === 'feature_shipped').length,
|
|
1145
|
-
totalActions: entries.length,
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
await this.logToMemory(projectPath, 'progress_viewed', { period, metrics, timestamp: dateHelper.getTimestamp() })
|
|
1149
|
-
|
|
1150
|
-
out.done(`${period}: ${metrics.tasksCompleted} tasks | ${metrics.featuresShipped} shipped`)
|
|
1151
|
-
return { success: true, period, metrics }
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
out.fail(error.message)
|
|
1154
|
-
return { success: false, error: error.message }
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
/**
|
|
1159
|
-
* /p:roadmap - Show roadmap with ASCII logic maps
|
|
1160
|
-
* AGENTIC EXECUTION
|
|
1161
|
-
*/
|
|
1162
|
-
async roadmap(projectPath = process.cwd()) {
|
|
1163
|
-
try {
|
|
1164
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
1165
|
-
if (!initResult.success) return initResult
|
|
1166
|
-
|
|
1167
|
-
out.spin('loading roadmap...')
|
|
1168
|
-
const context = await contextBuilder.build(projectPath)
|
|
1169
|
-
const roadmapContent = await toolRegistry.get('Read')(context.paths.roadmap)
|
|
1170
|
-
|
|
1171
|
-
if (!roadmapContent || roadmapContent.trim() === '# ROADMAP') {
|
|
1172
|
-
out.warn('no roadmap yet')
|
|
1173
|
-
return { success: true, message: 'No roadmap' }
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
// Count features in roadmap
|
|
1177
|
-
const features = (roadmapContent.match(/##/g) || []).length
|
|
1178
|
-
|
|
1179
|
-
await this.logToMemory(projectPath, 'roadmap_viewed', { timestamp: dateHelper.getTimestamp() })
|
|
1180
|
-
|
|
1181
|
-
out.done(`${features} features in roadmap`)
|
|
1182
|
-
return { success: true, content: roadmapContent }
|
|
1183
|
-
} catch (error) {
|
|
1184
|
-
out.fail(error.message)
|
|
1185
|
-
return { success: false, error: error.message }
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
/**
|
|
1190
|
-
* Generate roadmap template
|
|
1191
|
-
* @private
|
|
1192
|
-
*/
|
|
1193
|
-
_generateRoadmapTemplate() {
|
|
1194
|
-
return `
|
|
1195
|
-
# ROADMAP
|
|
1196
|
-
|
|
1197
|
-
## Q1 2025 - Foundation
|
|
1198
|
-
|
|
1199
|
-
\`\`\`
|
|
1200
|
-
[Authentication] ──┐
|
|
1201
|
-
├──> [User Management] ──> [Dashboard]
|
|
1202
|
-
[Database Setup] ──┘
|
|
1203
|
-
\`\`\`
|
|
1204
|
-
|
|
1205
|
-
Status: 🟢 In Progress
|
|
1206
|
-
|
|
1207
|
-
## Q2 2025 - Core Features
|
|
1208
|
-
|
|
1209
|
-
\`\`\`
|
|
1210
|
-
[API v1] ──┐
|
|
1211
|
-
├──> [Integration] ──> [Beta Launch]
|
|
1212
|
-
[UI v2] ───┘
|
|
1213
|
-
\`\`\`
|
|
1214
|
-
|
|
1215
|
-
Status: ⏸️ Planned
|
|
1216
|
-
|
|
1217
|
-
## Dependencies
|
|
1218
|
-
|
|
1219
|
-
- Authentication → User Management
|
|
1220
|
-
- Database Setup → Authentication
|
|
1221
|
-
- API v1 + UI v2 → Integration
|
|
1222
|
-
`
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
/**
|
|
1226
|
-
* /p:status - KPI dashboard with ASCII graphics
|
|
1227
|
-
* AGENTIC EXECUTION
|
|
1228
|
-
*/
|
|
1229
|
-
async status(projectPath = process.cwd()) {
|
|
1230
|
-
try {
|
|
1231
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
1232
|
-
if (!initResult.success) return initResult
|
|
1233
|
-
|
|
1234
|
-
console.log('📊 Project Status Dashboard\n')
|
|
1235
|
-
|
|
1236
|
-
const context = await contextBuilder.build(projectPath)
|
|
1237
|
-
|
|
1238
|
-
// Read project data
|
|
1239
|
-
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
1240
|
-
const nextContent = await toolRegistry.get('Read')(context.paths.next)
|
|
1241
|
-
const shippedContent = await toolRegistry.get('Read')(context.paths.shipped)
|
|
1242
|
-
const ideasContent = await toolRegistry.get('Read')(context.paths.ideas)
|
|
1243
|
-
|
|
1244
|
-
// Calculate stats
|
|
1245
|
-
const stats = {
|
|
1246
|
-
activeTask: nowContent && !nowContent.includes('No current task'),
|
|
1247
|
-
tasksInQueue:
|
|
1248
|
-
nextContent
|
|
1249
|
-
?.split('\n')
|
|
1250
|
-
.filter((line) => line.trim().match(/^\d+\./) || line.includes('[ ]')).length || 0,
|
|
1251
|
-
featuresShipped:
|
|
1252
|
-
shippedContent
|
|
1253
|
-
?.split('##')
|
|
1254
|
-
.filter((section) => section.trim() && !section.includes('SHIPPED 🚀')).length || 0,
|
|
1255
|
-
ideasCaptured:
|
|
1256
|
-
ideasContent
|
|
1257
|
-
?.split('##')
|
|
1258
|
-
.filter(
|
|
1259
|
-
(section) =>
|
|
1260
|
-
section.trim() && !section.includes('IDEAS 💡') && !section.includes('Brain Dump')
|
|
1261
|
-
).length || 0,
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
// Header
|
|
1265
|
-
console.log('═══════════════════════════════════════════════════')
|
|
1266
|
-
console.log(` ${path.basename(projectPath)} - Status Overview`)
|
|
1267
|
-
console.log('═══════════════════════════════════════════════════\n')
|
|
1268
|
-
|
|
1269
|
-
// Current Focus
|
|
1270
|
-
console.log('## 🎯 Current Focus\n')
|
|
1271
|
-
if (stats.activeTask) {
|
|
1272
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
1273
|
-
const task = taskMatch ? taskMatch[1] : 'Active task'
|
|
1274
|
-
const startedMatch = nowContent.match(/Started: (.+)/)
|
|
1275
|
-
const started = startedMatch ? startedMatch[1] : 'Unknown'
|
|
1276
|
-
console.log(` 📌 ${task}`)
|
|
1277
|
-
console.log(` ⏱️ Started: ${started}\n`)
|
|
1278
|
-
} else {
|
|
1279
|
-
console.log(' No active task\n')
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
// Queue Status
|
|
1283
|
-
console.log('## 📋 Queue Status\n')
|
|
1284
|
-
console.log(` Tasks in Queue: ${stats.tasksInQueue}`)
|
|
1285
|
-
this._renderProgressBar('Queue Load', stats.tasksInQueue, 20)
|
|
1286
|
-
console.log('')
|
|
1287
|
-
|
|
1288
|
-
// Shipped Features
|
|
1289
|
-
console.log('## 🚀 Shipped Features\n')
|
|
1290
|
-
console.log(` Features Shipped: ${stats.featuresShipped}`)
|
|
1291
|
-
this._renderProgressBar('Progress', stats.featuresShipped, 10)
|
|
1292
|
-
console.log('')
|
|
1293
|
-
|
|
1294
|
-
// Ideas Backlog
|
|
1295
|
-
console.log('## 💡 Ideas Backlog\n')
|
|
1296
|
-
console.log(` Ideas Captured: ${stats.ideasCaptured}`)
|
|
1297
|
-
this._renderProgressBar('Backlog', stats.ideasCaptured, 15)
|
|
1298
|
-
console.log('')
|
|
1299
|
-
|
|
1300
|
-
// Overall Health
|
|
1301
|
-
console.log('## 💚 Overall Health\n')
|
|
1302
|
-
const health = this._calculateHealth(stats)
|
|
1303
|
-
console.log(` Health Score: ${health.score}/100`)
|
|
1304
|
-
this._renderProgressBar('Health', health.score, 100)
|
|
1305
|
-
console.log(` ${health.message}\n`)
|
|
1306
|
-
|
|
1307
|
-
console.log('💡 Next steps:')
|
|
1308
|
-
console.log('• /p:now → Start working on a task')
|
|
1309
|
-
console.log('• /p:feature → Add new feature')
|
|
1310
|
-
console.log('• /p:ship → Ship completed work')
|
|
1311
|
-
|
|
1312
|
-
await this.logToMemory(projectPath, 'status_viewed', {
|
|
1313
|
-
stats,
|
|
1314
|
-
health: health.score,
|
|
1315
|
-
timestamp: dateHelper.getTimestamp(),
|
|
1316
|
-
})
|
|
1317
|
-
|
|
1318
|
-
return { success: true, stats, health }
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
console.error('❌ Error:', error.message)
|
|
1321
|
-
return { success: false, error: error.message }
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* Render ASCII progress bar
|
|
1327
|
-
* @private
|
|
1328
|
-
*/
|
|
1329
|
-
_renderProgressBar(label, value, max) {
|
|
1330
|
-
const percentage = Math.min(100, Math.round((value / max) * 100))
|
|
1331
|
-
const barLength = 30
|
|
1332
|
-
const filled = Math.round((percentage / 100) * barLength)
|
|
1333
|
-
const empty = barLength - filled
|
|
1334
|
-
|
|
1335
|
-
const bar = '█'.repeat(filled) + '░'.repeat(empty)
|
|
1336
|
-
console.log(` ${label}: [${bar}] ${percentage}%`)
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
/**
|
|
1340
|
-
* Calculate project health score
|
|
1341
|
-
* @private
|
|
1342
|
-
*/
|
|
1343
|
-
_calculateHealth(stats) {
|
|
1344
|
-
// AGENTIC: Claude evaluates health via templates/analysis/health.md
|
|
1345
|
-
// Simple calculation - real assessment happens in template execution
|
|
1346
|
-
const hasActivity = stats.activeTask || stats.featuresShipped > 0
|
|
1347
|
-
return {
|
|
1348
|
-
score: hasActivity ? 70 : 50,
|
|
1349
|
-
message: hasActivity ? '🟢 Active' : '🟡 Ready to start',
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
/**
|
|
1354
|
-
* /p:build - Start task with agent assignment
|
|
1355
|
-
* AGENTIC EXECUTION
|
|
1356
|
-
*/
|
|
1357
|
-
async build(taskOrNumber, projectPath = process.cwd()) {
|
|
1358
|
-
try {
|
|
1359
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
1360
|
-
if (!initResult.success) return initResult
|
|
1361
|
-
|
|
1362
|
-
const context = await contextBuilder.build(projectPath, { task: taskOrNumber })
|
|
1363
|
-
|
|
1364
|
-
// Check if already working on something
|
|
1365
|
-
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
1366
|
-
if (nowContent && !nowContent.includes('No current task')) {
|
|
1367
|
-
console.log('⚠️ Already working on a task!')
|
|
1368
|
-
console.log(' Complete it with /p:done first\n')
|
|
1369
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
1370
|
-
const currentTask = taskMatch ? taskMatch[1] : 'current task'
|
|
1371
|
-
console.log(` Current: ${currentTask}`)
|
|
1372
|
-
return { success: false, message: 'Task already active' }
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
let task = taskOrNumber
|
|
1376
|
-
|
|
1377
|
-
// If number, get from queue
|
|
1378
|
-
if (!isNaN(taskOrNumber)) {
|
|
1379
|
-
const nextContent = await toolRegistry.get('Read')(context.paths.next)
|
|
1380
|
-
const tasks = nextContent
|
|
1381
|
-
.split('\n')
|
|
1382
|
-
.filter((line) => line.trim().match(/^\d+\./) || line.includes('[ ]'))
|
|
1383
|
-
|
|
1384
|
-
const index = parseInt(taskOrNumber) - 1
|
|
1385
|
-
if (index >= 0 && index < tasks.length) {
|
|
1386
|
-
task = tasks[index].replace(/^\d+\.\s*\[.\]\s*/, '').trim()
|
|
1387
|
-
console.log(`📋 Selected from queue: ${task}\n`)
|
|
1388
|
-
} else {
|
|
1389
|
-
console.log(`❌ Invalid task number. Queue has ${tasks.length} tasks.`)
|
|
1390
|
-
console.log(' Use /p:next to see queue')
|
|
1391
|
-
return { success: false, error: 'Invalid task number' }
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
if (!task) {
|
|
1396
|
-
console.log('❌ Task description required')
|
|
1397
|
-
console.log('Usage: /p:build "task description"')
|
|
1398
|
-
console.log(' or: /p:build 1 (select from queue)')
|
|
1399
|
-
return { success: false, error: 'Task required' }
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
console.log(`🏗️ Building: ${task}\n`)
|
|
1403
|
-
|
|
1404
|
-
// Detect complexity and estimate
|
|
1405
|
-
const complexity = this._detectComplexity(task)
|
|
1406
|
-
const estimate = complexity.hours
|
|
1407
|
-
|
|
1408
|
-
console.log('📊 Analysis:')
|
|
1409
|
-
console.log(` Complexity: ${complexity.level}`)
|
|
1410
|
-
console.log(` Estimated: ${estimate}h`)
|
|
1411
|
-
console.log(` Type: ${complexity.type}\n`)
|
|
1412
|
-
|
|
1413
|
-
// MANDATORY: Assign agent using router
|
|
1414
|
-
const agentResult = await this._assignAgentForTask(task, projectPath, context)
|
|
1415
|
-
const agent = agentResult.agent?.name || 'generalist'
|
|
1416
|
-
const confidence = agentResult.routing?.confidence || 0.5
|
|
1417
|
-
console.log(`🤖 Agent: ${agent} (${Math.round(confidence * 100)}% confidence)\n`)
|
|
1418
|
-
|
|
1419
|
-
// Set as current task with metadata
|
|
1420
|
-
const nowContentNew = `# NOW
|
|
1421
|
-
|
|
1422
|
-
**${task}**
|
|
1423
|
-
|
|
1424
|
-
Started: ${new Date().toLocaleString()}
|
|
1425
|
-
Estimated: ${estimate}h
|
|
1426
|
-
Complexity: ${complexity.level}
|
|
1427
|
-
Agent: ${agent} (${Math.round(confidence * 100)}% confidence)
|
|
1428
|
-
`
|
|
1429
|
-
await toolRegistry.get('Write')(context.paths.now, nowContentNew)
|
|
1430
|
-
|
|
1431
|
-
console.log('✅ Task started!\n')
|
|
1432
|
-
console.log('💡 Next steps:')
|
|
1433
|
-
console.log('• Start coding')
|
|
1434
|
-
console.log('• /p:done → Mark complete')
|
|
1435
|
-
console.log('• /p:stuck → Get help if needed')
|
|
1436
|
-
|
|
1437
|
-
await this.logToMemory(projectPath, 'task_built', {
|
|
1438
|
-
task,
|
|
1439
|
-
complexity: complexity.level,
|
|
1440
|
-
estimate,
|
|
1441
|
-
agent,
|
|
1442
|
-
confidence,
|
|
1443
|
-
timestamp: dateHelper.getTimestamp(),
|
|
1444
|
-
})
|
|
1445
|
-
|
|
1446
|
-
return { success: true, task, complexity, estimate, agent }
|
|
1447
|
-
} catch (error) {
|
|
1448
|
-
console.error('❌ Error:', error.message)
|
|
1449
|
-
return { success: false, error: error.message }
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
/**
|
|
1454
|
-
* Detect task complexity
|
|
1455
|
-
* @private
|
|
1456
|
-
*/
|
|
1457
|
-
_detectComplexity(task) {
|
|
1458
|
-
// AGENTIC: Claude analyzes complexity via templates/analysis/complexity.md
|
|
1459
|
-
// Returns default - real analysis happens in template execution
|
|
1460
|
-
return { level: 'medium', hours: 4, type: 'feature' }
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
/**
|
|
1464
|
-
* Assign agent for a task
|
|
1465
|
-
* AGENTIC: Claude decides via templates/agent-assignment.md
|
|
1466
|
-
* JS only orchestrates: load agents → build context → delegate to Claude
|
|
1467
|
-
* @private
|
|
1468
|
-
*/
|
|
1469
|
-
async _assignAgentForTask(taskDescription, projectPath, context) {
|
|
1470
|
-
try {
|
|
1471
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
1472
|
-
|
|
1473
|
-
// ORCHESTRATION ONLY: Load available agents
|
|
1474
|
-
const agentsPath = pathManager.getPath(projectId, 'agents')
|
|
1475
|
-
const agentFiles = await fileHelper.listFiles(agentsPath, '.md')
|
|
1476
|
-
const agents = agentFiles.map(f => f.replace('.md', ''))
|
|
1477
|
-
|
|
1478
|
-
// ORCHESTRATION ONLY: Build context for Claude
|
|
1479
|
-
const assignmentContext = {
|
|
1480
|
-
task: taskDescription,
|
|
1481
|
-
agents: agents.join(', ') || 'generalist',
|
|
1482
|
-
projectPath,
|
|
1483
|
-
// Claude will use this context + template to decide
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
// AGENTIC: Claude decides agent via template
|
|
1487
|
-
// The template templates/agent-assignment.md guides Claude's decision
|
|
1488
|
-
// For now, return structure that prompt-builder will use with template
|
|
1489
|
-
return {
|
|
1490
|
-
agent: { name: agents[0] || 'generalist', domain: 'auto' },
|
|
1491
|
-
routing: {
|
|
1492
|
-
confidence: 0.8,
|
|
1493
|
-
reason: 'Claude assigns via templates/agent-assignment.md',
|
|
1494
|
-
availableAgents: agents
|
|
1495
|
-
},
|
|
1496
|
-
_agenticNote: 'Use templates/agent-assignment.md for actual assignment'
|
|
1497
|
-
}
|
|
1498
|
-
} catch (error) {
|
|
1499
|
-
// Fallback - still return structure
|
|
1500
|
-
return {
|
|
1501
|
-
agent: { name: 'generalist', domain: 'general' },
|
|
1502
|
-
routing: { confidence: 0.5, reason: 'Fallback - no agents found' }
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
/**
|
|
1508
|
-
* Auto-assign agent based on task (sync wrapper for backward compat)
|
|
1509
|
-
* DEPRECATED: Use _assignAgentForTask instead
|
|
1510
|
-
* @private
|
|
1511
|
-
*/
|
|
1512
|
-
_autoAssignAgent(task) {
|
|
1513
|
-
// For backward compatibility, return generalist synchronously
|
|
1514
|
-
// New code should use _assignAgentForTask() which is async
|
|
1515
|
-
console.warn('DEPRECATED: Use _assignAgentForTask() for proper agent routing')
|
|
1516
|
-
return 'generalist'
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
/**
|
|
1520
|
-
* /p:analyze - Analyze repository and generate summary
|
|
1521
|
-
* AGENTIC EXECUTION
|
|
1522
|
-
*/
|
|
1523
|
-
async analyze(options = {}, projectPath = process.cwd()) {
|
|
1524
|
-
try {
|
|
1525
|
-
await this.initializeAgent()
|
|
1526
|
-
|
|
1527
|
-
console.log('🔍 Analyzing repository...\n')
|
|
1528
|
-
|
|
1529
|
-
// Initialize analyzer for this project
|
|
1530
|
-
const analyzer = require('./domain/analyzer')
|
|
1531
|
-
analyzer.init(projectPath)
|
|
1532
|
-
|
|
1533
|
-
// Build context
|
|
1534
|
-
const context = await contextBuilder.build(projectPath, options)
|
|
1535
|
-
|
|
1536
|
-
// Collect data using analyzer helpers (ZERO predetermined patterns)
|
|
1537
|
-
const analysisData = {
|
|
1538
|
-
// Package managers
|
|
1539
|
-
packageJson: await analyzer.readPackageJson(),
|
|
1540
|
-
cargoToml: await analyzer.readCargoToml(),
|
|
1541
|
-
goMod: await analyzer.readGoMod(),
|
|
1542
|
-
requirements: await analyzer.readRequirements(),
|
|
1543
|
-
|
|
1544
|
-
// Project structure
|
|
1545
|
-
directories: await analyzer.listDirectories(),
|
|
1546
|
-
fileCount: await analyzer.countFiles(),
|
|
1547
|
-
|
|
1548
|
-
// Git data
|
|
1549
|
-
gitStats: await analyzer.getGitStats(),
|
|
1550
|
-
gitLog: await analyzer.getGitLog(20),
|
|
1551
|
-
|
|
1552
|
-
// Common files
|
|
1553
|
-
hasDockerfile: await analyzer.fileExists('Dockerfile'),
|
|
1554
|
-
hasDockerCompose: await analyzer.fileExists('docker-compose.yml'),
|
|
1555
|
-
hasReadme: await analyzer.fileExists('README.md'),
|
|
1556
|
-
hasTsconfig: await analyzer.fileExists('tsconfig.json'),
|
|
1557
|
-
hasViteConfig:
|
|
1558
|
-
(await analyzer.fileExists('vite.config.ts')) ||
|
|
1559
|
-
(await analyzer.fileExists('vite.config.js')),
|
|
1560
|
-
hasNextConfig:
|
|
1561
|
-
(await analyzer.fileExists('next.config.js')) ||
|
|
1562
|
-
(await analyzer.fileExists('next.config.mjs')),
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
// Generate summary (Claude decides what's relevant based on data found)
|
|
1566
|
-
const summary = this._generateAnalysisSummary(analysisData, projectPath)
|
|
1567
|
-
|
|
1568
|
-
// Save to analysis/repo-summary.md
|
|
1569
|
-
const summaryPath =
|
|
1570
|
-
context.paths.analysis ||
|
|
1571
|
-
pathManager.getFilePath(
|
|
1572
|
-
await configManager.getProjectId(projectPath),
|
|
1573
|
-
'analysis',
|
|
1574
|
-
'repo-summary.md'
|
|
1575
|
-
)
|
|
1576
|
-
|
|
1577
|
-
await toolRegistry.get('Write')(summaryPath, summary)
|
|
1578
|
-
|
|
1579
|
-
// Log to memory
|
|
1580
|
-
await this.logToMemory(projectPath, 'repository_analyzed', {
|
|
1581
|
-
timestamp: dateHelper.getTimestamp(),
|
|
1582
|
-
fileCount: analysisData.fileCount,
|
|
1583
|
-
gitCommits: analysisData.gitStats.totalCommits,
|
|
1584
|
-
})
|
|
1585
|
-
|
|
1586
|
-
// Generate dynamic context for Claude
|
|
1587
|
-
const contextSync = require('./context-sync')
|
|
1588
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
1589
|
-
await contextSync.generateLocalContext(projectPath, projectId)
|
|
1590
|
-
|
|
1591
|
-
// Update global CLAUDE.md with latest instructions
|
|
1592
|
-
const commandInstaller = require('./infrastructure/command-installer')
|
|
1593
|
-
const globalConfigResult = await commandInstaller.installGlobalConfig()
|
|
1594
|
-
if (globalConfigResult.success) {
|
|
1595
|
-
console.log('📝 Updated ~/.claude/CLAUDE.md')
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
console.log('✅ Analysis complete!\n')
|
|
1599
|
-
console.log('📄 Full report: analysis/repo-summary.md')
|
|
1600
|
-
console.log('📝 Context: ~/.prjct-cli/projects/' + projectId + '/CLAUDE.md\n')
|
|
1601
|
-
console.log('Next steps:')
|
|
1602
|
-
console.log('• /p:sync → Generate agents based on stack')
|
|
1603
|
-
console.log('• /p:feature → Add a new feature')
|
|
1604
|
-
|
|
1605
|
-
return {
|
|
1606
|
-
success: true,
|
|
1607
|
-
summaryPath,
|
|
1608
|
-
data: analysisData,
|
|
1609
|
-
}
|
|
1610
|
-
} catch (error) {
|
|
1611
|
-
console.error('❌ Error:', error.message)
|
|
1612
|
-
return { success: false, error: error.message }
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
/**
|
|
1617
|
-
* Generate analysis summary from collected data
|
|
1618
|
-
* Claude decides what's relevant - NO predetermined patterns
|
|
1619
|
-
* @private
|
|
1620
|
-
*/
|
|
1621
|
-
_generateAnalysisSummary(data, projectPath) {
|
|
1622
|
-
const lines = []
|
|
1623
|
-
|
|
1624
|
-
lines.push('# Repository Analysis\n')
|
|
1625
|
-
lines.push(`Generated: ${new Date().toLocaleString()}\n`)
|
|
1626
|
-
|
|
1627
|
-
// Project name from path
|
|
1628
|
-
const projectName = path.basename(projectPath)
|
|
1629
|
-
lines.push(`## Project: ${projectName}\n`)
|
|
1630
|
-
|
|
1631
|
-
// Technologies detected (based on what files exist)
|
|
1632
|
-
lines.push('## Stack Detected\n')
|
|
1633
|
-
|
|
1634
|
-
if (data.packageJson) {
|
|
1635
|
-
lines.push('### JavaScript/TypeScript\n')
|
|
1636
|
-
lines.push('- **Package Manager**: npm/yarn/pnpm')
|
|
1637
|
-
if (data.packageJson.dependencies) {
|
|
1638
|
-
const deps = Object.keys(data.packageJson.dependencies)
|
|
1639
|
-
if (deps.length > 0) {
|
|
1640
|
-
lines.push(
|
|
1641
|
-
`- **Dependencies**: ${deps.slice(0, 10).join(', ')}${deps.length > 10 ? ` (+${deps.length - 10} more)` : ''}`
|
|
1642
|
-
)
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
if (data.hasNextConfig) lines.push('- **Framework**: Next.js detected')
|
|
1646
|
-
if (data.hasViteConfig) lines.push('- **Build Tool**: Vite detected')
|
|
1647
|
-
if (data.hasTsconfig) lines.push('- **Language**: TypeScript')
|
|
1648
|
-
lines.push('')
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
if (data.cargoToml) {
|
|
1652
|
-
lines.push('### Rust\n')
|
|
1653
|
-
lines.push('- **Package Manager**: Cargo')
|
|
1654
|
-
lines.push('- **Language**: Rust\n')
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
if (data.goMod) {
|
|
1658
|
-
lines.push('### Go\n')
|
|
1659
|
-
lines.push('- **Package Manager**: Go modules')
|
|
1660
|
-
lines.push('- **Language**: Go\n')
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
if (data.requirements) {
|
|
1664
|
-
lines.push('### Python\n')
|
|
1665
|
-
lines.push('- **Package Manager**: pip')
|
|
1666
|
-
lines.push('- **Language**: Python\n')
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
// Project structure
|
|
1670
|
-
lines.push('## Structure\n')
|
|
1671
|
-
lines.push(`- **Total Files**: ${data.fileCount}`)
|
|
1672
|
-
lines.push(
|
|
1673
|
-
`- **Directories**: ${data.directories.slice(0, 15).join(', ')}${data.directories.length > 15 ? ` (+${data.directories.length - 15} more)` : ''}`
|
|
1674
|
-
)
|
|
1675
|
-
|
|
1676
|
-
if (data.hasDockerfile) lines.push('- **Docker**: Detected')
|
|
1677
|
-
if (data.hasDockerCompose) lines.push('- **Docker Compose**: Detected')
|
|
1678
|
-
if (data.hasReadme) lines.push('- **Documentation**: README.md found')
|
|
1679
|
-
lines.push('')
|
|
1680
|
-
|
|
1681
|
-
// Git stats
|
|
1682
|
-
lines.push('## Git Statistics\n')
|
|
1683
|
-
lines.push(`- **Total Commits**: ${data.gitStats.totalCommits}`)
|
|
1684
|
-
lines.push(`- **Contributors**: ${data.gitStats.contributors}`)
|
|
1685
|
-
lines.push(`- **Age**: ${data.gitStats.age}`)
|
|
1686
|
-
lines.push('')
|
|
1687
|
-
|
|
1688
|
-
// Recent activity (if available)
|
|
1689
|
-
if (data.gitLog) {
|
|
1690
|
-
lines.push('## Recent Activity\n')
|
|
1691
|
-
const logLines = data.gitLog.split('\n').slice(0, 5)
|
|
1692
|
-
logLines.forEach((line) => {
|
|
1693
|
-
if (line.trim()) {
|
|
1694
|
-
const [hash, , time, msg] = line.split('|')
|
|
1695
|
-
lines.push(`- \`${hash}\` ${msg} (${time})`)
|
|
1696
|
-
}
|
|
1697
|
-
})
|
|
1698
|
-
lines.push('')
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
// Recommendations
|
|
1702
|
-
lines.push('## Recommendations\n')
|
|
1703
|
-
lines.push('Based on detected stack, consider generating specialized agents using `/p:sync`.\n')
|
|
1704
|
-
|
|
1705
|
-
lines.push('---\n')
|
|
1706
|
-
lines.push(
|
|
1707
|
-
'*This analysis was generated automatically. For updated information, run `/p:analyze` again.*\n'
|
|
1708
|
-
)
|
|
1709
|
-
|
|
1710
|
-
return lines.join('\n')
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
/**
|
|
1714
|
-
* /p:sync - Sync project state and generate dynamic agents
|
|
1715
|
-
* AGENTIC EXECUTION
|
|
1716
|
-
*/
|
|
1717
|
-
async sync(projectPath = process.cwd()) {
|
|
1718
|
-
try {
|
|
1719
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
1720
|
-
if (!initResult.success) return initResult
|
|
1721
|
-
|
|
1722
|
-
await this.initializeAgent()
|
|
1723
|
-
|
|
1724
|
-
console.log('🔄 Syncing project state...\n')
|
|
1725
|
-
|
|
1726
|
-
// Build context
|
|
1727
|
-
const context = await contextBuilder.build(projectPath)
|
|
1728
|
-
|
|
1729
|
-
// Step 1: Run analysis to get current state
|
|
1730
|
-
console.log('📊 Running analysis...')
|
|
1731
|
-
const analysisResult = await this.analyze({}, projectPath)
|
|
1732
|
-
|
|
1733
|
-
if (!analysisResult.success) {
|
|
1734
|
-
console.error('❌ Analysis failed')
|
|
1735
|
-
return analysisResult
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
// Step 2: Read analysis/repo-summary.md
|
|
1739
|
-
const summaryContent = await toolRegistry.get('Read')(context.paths.analysis)
|
|
1740
|
-
|
|
1741
|
-
if (!summaryContent) {
|
|
1742
|
-
console.error('❌ No analysis found. Run /p:analyze first.')
|
|
1743
|
-
return { success: false, error: 'No analysis found' }
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
console.log('✅ Analysis loaded\n')
|
|
1747
|
-
|
|
1748
|
-
// Step 3: Generate dynamic agents based on stack detected
|
|
1749
|
-
// Claude reads the summary and decides what specialists to create
|
|
1750
|
-
console.log('🤖 Generating specialized agents...\n')
|
|
1751
|
-
|
|
1752
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
1753
|
-
const AgentGenerator = require('./domain/agent-generator')
|
|
1754
|
-
const generator = new AgentGenerator(projectId)
|
|
1755
|
-
|
|
1756
|
-
const generatedAgents = await this._generateAgentsFromAnalysis(summaryContent, generator, projectPath)
|
|
1757
|
-
|
|
1758
|
-
// Step 4: Log to memory
|
|
1759
|
-
await this.logToMemory(projectPath, 'agents_generated', {
|
|
1760
|
-
timestamp: dateHelper.getTimestamp(),
|
|
1761
|
-
agents: generatedAgents,
|
|
1762
|
-
count: generatedAgents.length,
|
|
1763
|
-
})
|
|
1764
|
-
|
|
1765
|
-
// Generate dynamic context for Claude
|
|
1766
|
-
const contextSync = require('./context-sync')
|
|
1767
|
-
await contextSync.generateLocalContext(projectPath, projectId)
|
|
1768
|
-
|
|
1769
|
-
// Update global CLAUDE.md with latest instructions
|
|
1770
|
-
const commandInstaller = require('./infrastructure/command-installer')
|
|
1771
|
-
const globalConfigResult = await commandInstaller.installGlobalConfig()
|
|
1772
|
-
if (globalConfigResult.success) {
|
|
1773
|
-
console.log('📝 Updated ~/.claude/CLAUDE.md')
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
console.log('\n✅ Sync complete!\n')
|
|
1777
|
-
console.log(`🤖 Agents Generated: ${generatedAgents.length}`)
|
|
1778
|
-
generatedAgents.forEach((agent) => {
|
|
1779
|
-
console.log(` • ${agent}`)
|
|
1780
|
-
})
|
|
1781
|
-
console.log('📝 Context: ~/.prjct-cli/projects/' + projectId + '/CLAUDE.md')
|
|
1782
|
-
console.log('\n📋 Based on: analysis/repo-summary.md')
|
|
1783
|
-
console.log('💡 See templates/agents/AGENTS.md for reference\n')
|
|
1784
|
-
console.log('Next steps:')
|
|
1785
|
-
console.log('• /p:context → View project state')
|
|
1786
|
-
console.log('• /p:feature → Add a feature')
|
|
1787
|
-
|
|
1788
|
-
return {
|
|
1789
|
-
success: true,
|
|
1790
|
-
agents: generatedAgents,
|
|
1791
|
-
}
|
|
1792
|
-
} catch (error) {
|
|
1793
|
-
console.error('❌ Error:', error.message)
|
|
1794
|
-
return { success: false, error: error.message }
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
/**
|
|
1799
|
-
* Generate agents dynamically from analysis summary
|
|
1800
|
-
* 100% AGENTIC - Uses analyzer for raw data, Claude decides
|
|
1801
|
-
* NO hardcoded categorization or framework lists
|
|
1802
|
-
* @private
|
|
1803
|
-
*/
|
|
1804
|
-
async _generateAgentsFromAnalysis(summaryContent, generator, projectPath) {
|
|
1805
|
-
const agents = []
|
|
1806
|
-
|
|
1807
|
-
// 100% AGENTIC: Get raw project data, let Claude decide
|
|
1808
|
-
const analyzer = require('./domain/analyzer')
|
|
1809
|
-
analyzer.init(projectPath)
|
|
1810
|
-
|
|
1811
|
-
const projectData = {
|
|
1812
|
-
packageJson: await analyzer.readPackageJson(),
|
|
1813
|
-
extensions: await analyzer.getFileExtensions(),
|
|
1814
|
-
directories: await analyzer.listDirectories(),
|
|
1815
|
-
configFiles: await analyzer.listConfigFiles(),
|
|
1816
|
-
analysisSummary: summaryContent,
|
|
1817
|
-
projectPath
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
// Let the generator decide what agents to create
|
|
1821
|
-
// It reads templates/agents/AGENTS.md and decides based on actual project
|
|
1822
|
-
const generatedAgents = await generator.generateAgentsFromTech(projectData)
|
|
1823
|
-
|
|
1824
|
-
// Return agent names
|
|
1825
|
-
generatedAgents.forEach(agent => agents.push(agent.name || agent))
|
|
1826
|
-
|
|
1827
|
-
return agents
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
/**
|
|
1831
|
-
* First-time setup - Install commands to editors
|
|
1832
|
-
*/
|
|
1833
|
-
async start() {
|
|
1834
|
-
const commandInstaller = require('./infrastructure/command-installer')
|
|
1835
|
-
|
|
1836
|
-
console.log('🚀 Setting up prjct for Claude...\n')
|
|
1837
|
-
|
|
1838
|
-
// Check if Claude is installed
|
|
1839
|
-
const status = await commandInstaller.checkInstallation()
|
|
1840
|
-
|
|
1841
|
-
if (!status.claudeDetected) {
|
|
1842
|
-
return {
|
|
1843
|
-
success: false,
|
|
1844
|
-
message:
|
|
1845
|
-
'❌ Claude not detected.\n\nPlease install Claude Code or Claude Desktop first:\n' +
|
|
1846
|
-
' - Claude Code: https://claude.com/code\n' +
|
|
1847
|
-
' - Claude Desktop: https://claude.com/desktop',
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
// Install commands
|
|
1852
|
-
console.log('📦 Installing /p:* commands...')
|
|
1853
|
-
const result = await commandInstaller.installCommands()
|
|
1854
|
-
|
|
1855
|
-
if (!result.success) {
|
|
1856
|
-
return {
|
|
1857
|
-
success: false,
|
|
1858
|
-
message: `❌ Installation failed: ${result.error}`,
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
console.log(`\n✅ Installed ${result.installed.length} commands to:\n ${result.path}`)
|
|
1863
|
-
|
|
1864
|
-
if (result.errors.length > 0) {
|
|
1865
|
-
console.log(`\n⚠️ ${result.errors.length} errors:`)
|
|
1866
|
-
result.errors.forEach((e) => console.log(` - ${e.file}: ${e.error}`))
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
console.log('\n🎉 Setup complete!')
|
|
1870
|
-
console.log('\nNext steps:')
|
|
1871
|
-
console.log(' 1. Open Claude Code or Claude Desktop')
|
|
1872
|
-
console.log(' 2. Navigate to your project')
|
|
1873
|
-
console.log(' 3. Run: /p:init')
|
|
1874
|
-
|
|
1875
|
-
return {
|
|
1876
|
-
success: true,
|
|
1877
|
-
message: '',
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
/**
|
|
1882
|
-
* Reconfigure editor installations
|
|
1883
|
-
*/
|
|
1884
|
-
async setup(options = {}) {
|
|
1885
|
-
const commandInstaller = require('./infrastructure/command-installer')
|
|
1886
|
-
|
|
1887
|
-
console.log('🔧 Reconfiguring prjct...\n')
|
|
1888
|
-
|
|
1889
|
-
if (options.force) {
|
|
1890
|
-
console.log('🗑️ Removing existing installation...')
|
|
1891
|
-
await commandInstaller.uninstallCommands()
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
// Reinstall commands
|
|
1895
|
-
console.log('📦 Installing /p:* commands...')
|
|
1896
|
-
const result = await commandInstaller.updateCommands()
|
|
1897
|
-
|
|
1898
|
-
if (!result.success) {
|
|
1899
|
-
return {
|
|
1900
|
-
success: false,
|
|
1901
|
-
message: `❌ Setup failed: ${result.error}`,
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
console.log(`\n✅ Installed ${result.installed.length} commands`)
|
|
1906
|
-
|
|
1907
|
-
if (result.errors.length > 0) {
|
|
1908
|
-
console.log(`\n⚠️ ${result.errors.length} errors:`)
|
|
1909
|
-
result.errors.forEach((e) => console.log(` - ${e.file}: ${e.error}`))
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// Install global configuration
|
|
1913
|
-
console.log('\n📝 Installing global configuration...')
|
|
1914
|
-
const configResult = await commandInstaller.installGlobalConfig()
|
|
1915
|
-
|
|
1916
|
-
if (configResult.success) {
|
|
1917
|
-
if (configResult.action === 'created') {
|
|
1918
|
-
console.log('✅ Created ~/.claude/CLAUDE.md')
|
|
1919
|
-
} else if (configResult.action === 'updated') {
|
|
1920
|
-
console.log('✅ Updated ~/.claude/CLAUDE.md')
|
|
1921
|
-
} else if (configResult.action === 'appended') {
|
|
1922
|
-
console.log('✅ Added prjct config to ~/.claude/CLAUDE.md')
|
|
1923
|
-
}
|
|
1924
|
-
} else {
|
|
1925
|
-
console.log(`⚠️ ${configResult.error}`)
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
// Install status line for Claude Code
|
|
1929
|
-
console.log('\n⚡ Installing status line...')
|
|
1930
|
-
const statusLineResult = await this.installStatusLine()
|
|
1931
|
-
if (statusLineResult.success) {
|
|
1932
|
-
console.log('✅ Status line configured')
|
|
1933
|
-
} else {
|
|
1934
|
-
console.log(`⚠️ ${statusLineResult.error}`)
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
console.log('\n🎉 Setup complete!\n')
|
|
1938
|
-
|
|
1939
|
-
// Show beautiful ASCII art
|
|
1940
|
-
this.showAsciiArt()
|
|
1941
|
-
|
|
1942
|
-
return {
|
|
1943
|
-
success: true,
|
|
1944
|
-
message: '',
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
/**
|
|
1949
|
-
* Install status line script and configure settings.json
|
|
1950
|
-
*/
|
|
1951
|
-
async installStatusLine() {
|
|
1952
|
-
const fs = require('fs')
|
|
1953
|
-
const path = require('path')
|
|
1954
|
-
const os = require('os')
|
|
1955
|
-
|
|
1956
|
-
try {
|
|
1957
|
-
const claudeDir = path.join(os.homedir(), '.claude')
|
|
1958
|
-
const settingsPath = path.join(claudeDir, 'settings.json')
|
|
1959
|
-
const statusLinePath = path.join(claudeDir, 'prjct-statusline.sh')
|
|
1960
|
-
|
|
1961
|
-
// Copy status line script
|
|
1962
|
-
const scriptContent = `#!/bin/bash
|
|
1963
|
-
# prjct Status Line for Claude Code
|
|
1964
|
-
# Shows ⚡ prjct with animated spinner when command is running
|
|
1965
|
-
|
|
1966
|
-
# Read JSON context from stdin (provided by Claude Code)
|
|
1967
|
-
read -r json
|
|
1968
|
-
|
|
1969
|
-
# Spinner frames
|
|
1970
|
-
frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
|
1971
|
-
|
|
1972
|
-
# Calculate frame based on time (changes every 80ms)
|
|
1973
|
-
frame=$(($(date +%s%N 2>/dev/null || echo 0) / 80000000 % 10))
|
|
1974
|
-
|
|
1975
|
-
# Check if prjct command is running
|
|
1976
|
-
running_file="$HOME/.prjct-cli/.running"
|
|
1977
|
-
|
|
1978
|
-
if [ -f "$running_file" ]; then
|
|
1979
|
-
task=$(cat "$running_file" 2>/dev/null || echo "working")
|
|
1980
|
-
echo "⚡ prjct \${frames[$frame]} $task"
|
|
1981
|
-
else
|
|
1982
|
-
echo "⚡ prjct"
|
|
1983
|
-
fi
|
|
1984
|
-
`
|
|
1985
|
-
fs.writeFileSync(statusLinePath, scriptContent, { mode: 0o755 })
|
|
1986
|
-
|
|
1987
|
-
// Update settings.json
|
|
1988
|
-
let settings = {}
|
|
1989
|
-
if (fs.existsSync(settingsPath)) {
|
|
1990
|
-
try {
|
|
1991
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
|
|
1992
|
-
} catch {
|
|
1993
|
-
// Invalid JSON, start fresh
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
// Set status line configuration
|
|
1998
|
-
settings.statusLine = {
|
|
1999
|
-
type: 'command',
|
|
2000
|
-
command: statusLinePath
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
|
|
2004
|
-
|
|
2005
|
-
return { success: true }
|
|
2006
|
-
} catch (error) {
|
|
2007
|
-
return { success: false, error: error.message }
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
/**
|
|
2012
|
-
* Show beautiful ASCII art with quick start
|
|
2013
|
-
*/
|
|
2014
|
-
showAsciiArt() {
|
|
2015
|
-
const chalk = require('chalk')
|
|
2016
|
-
|
|
2017
|
-
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
2018
|
-
console.log('')
|
|
2019
|
-
console.log(chalk.bold.cyan(' ██████╗ ██████╗ ██╗ ██████╗████████╗'))
|
|
2020
|
-
console.log(chalk.bold.cyan(' ██╔══██╗██╔══██╗ ██║██╔════╝╚══██╔══╝'))
|
|
2021
|
-
console.log(chalk.bold.cyan(' ██████╔╝██████╔╝ ██║██║ ██║'))
|
|
2022
|
-
console.log(chalk.bold.cyan(' ██╔═══╝ ██╔══██╗██ ██║██║ ██║'))
|
|
2023
|
-
console.log(chalk.bold.cyan(' ██║ ██║ ██║╚█████╔╝╚██████╗ ██║'))
|
|
2024
|
-
console.log(chalk.bold.cyan(' ╚═╝ ╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═╝'))
|
|
2025
|
-
console.log('')
|
|
2026
|
-
console.log(` ${chalk.bold.cyan('prjct')}${chalk.magenta('/')}${chalk.green('cli')} ${chalk.dim.white('v' + VERSION + ' installed')}`)
|
|
2027
|
-
console.log('')
|
|
2028
|
-
console.log(` ${chalk.yellow('⚡')} Ship faster with zero friction`)
|
|
2029
|
-
console.log(` ${chalk.green('📝')} From idea to technical tasks in minutes`)
|
|
2030
|
-
console.log(` ${chalk.cyan('🤖')} Perfect context for AI agents`)
|
|
2031
|
-
console.log('')
|
|
2032
|
-
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
2033
|
-
console.log('')
|
|
2034
|
-
console.log(chalk.bold.cyan('🚀 Quick Start'))
|
|
2035
|
-
console.log(chalk.dim('─────────────────────────────────────────────────'))
|
|
2036
|
-
console.log('')
|
|
2037
|
-
console.log(` ${chalk.bold('1.')} Initialize your project:`)
|
|
2038
|
-
console.log(` ${chalk.green('cd your-project && prjct init')}`)
|
|
2039
|
-
console.log('')
|
|
2040
|
-
console.log(` ${chalk.bold('2.')} Set your current focus:`)
|
|
2041
|
-
console.log(` ${chalk.green('prjct now "build auth"')}`)
|
|
2042
|
-
console.log('')
|
|
2043
|
-
console.log(` ${chalk.bold('3.')} Ship & celebrate:`)
|
|
2044
|
-
console.log(` ${chalk.green('prjct ship "user login"')}`)
|
|
2045
|
-
console.log('')
|
|
2046
|
-
console.log(chalk.dim('─────────────────────────────────────────────────'))
|
|
2047
|
-
console.log('')
|
|
2048
|
-
console.log(` ${chalk.dim('Documentation:')} ${chalk.cyan('https://prjct.app')}`)
|
|
2049
|
-
console.log(` ${chalk.dim('Report issues:')} ${chalk.cyan('https://github.com/jlopezlira/prjct-cli/issues')}`)
|
|
2050
|
-
console.log('')
|
|
2051
|
-
console.log(chalk.bold.magenta('Happy shipping! 🚀'))
|
|
2052
|
-
console.log('')
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
/**
|
|
2056
|
-
* Migrate all legacy projects
|
|
2057
|
-
*/
|
|
2058
|
-
async migrateAll(options = {}) {
|
|
2059
|
-
const fs = require('fs').promises
|
|
2060
|
-
const path = require('path')
|
|
2061
|
-
|
|
2062
|
-
console.log('🔄 Scanning for legacy prjct projects...\n')
|
|
2063
|
-
|
|
2064
|
-
const homeDir = require('os').homedir()
|
|
2065
|
-
const globalRoot = path.join(homeDir, '.prjct-cli', 'projects')
|
|
2066
|
-
|
|
2067
|
-
// Get all project IDs
|
|
2068
|
-
let projectIds = []
|
|
2069
|
-
try {
|
|
2070
|
-
const dirs = await fs.readdir(globalRoot)
|
|
2071
|
-
projectIds = dirs.filter((d) => !d.startsWith('.'))
|
|
2072
|
-
} catch (error) {
|
|
2073
|
-
return {
|
|
2074
|
-
success: false,
|
|
2075
|
-
message: '❌ No prjct projects found',
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
console.log(`📁 Found ${projectIds.length} projects in global storage\n`)
|
|
2080
|
-
|
|
2081
|
-
const migrated = []
|
|
2082
|
-
const failed = []
|
|
2083
|
-
const skipped = []
|
|
2084
|
-
|
|
2085
|
-
for (const projectId of projectIds) {
|
|
2086
|
-
// Read global config to get project path
|
|
2087
|
-
const globalConfig = await configManager.readGlobalConfig(projectId)
|
|
2088
|
-
if (!globalConfig || !globalConfig.projectPath) {
|
|
2089
|
-
skipped.push({ projectId, reason: 'No project path in config' })
|
|
2090
|
-
continue
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
const projectPath = globalConfig.projectPath
|
|
2094
|
-
|
|
2095
|
-
// Check if needs migration
|
|
2096
|
-
if (!(await migrator.needsMigration(projectPath))) {
|
|
2097
|
-
skipped.push({ projectId, reason: 'Already migrated' })
|
|
2098
|
-
continue
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
console.log(`🔄 Migrating: ${projectPath}`)
|
|
2102
|
-
|
|
2103
|
-
try {
|
|
2104
|
-
const result = await migrator.migrate(projectPath, options)
|
|
2105
|
-
|
|
2106
|
-
if (result.success) {
|
|
2107
|
-
migrated.push({ projectId, path: projectPath })
|
|
2108
|
-
console.log(` ✅ ${result.message}`)
|
|
2109
|
-
} else {
|
|
2110
|
-
failed.push({ projectId, path: projectPath, error: result.message })
|
|
2111
|
-
console.log(` ❌ ${result.message}`)
|
|
2112
|
-
}
|
|
2113
|
-
} catch (error) {
|
|
2114
|
-
failed.push({ projectId, path: projectPath, error: error.message })
|
|
2115
|
-
console.log(` ❌ ${error.message}`)
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
console.log('')
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// Summary
|
|
2122
|
-
console.log('\n📊 Migration Summary:')
|
|
2123
|
-
console.log(` ✅ Migrated: ${migrated.length}`)
|
|
2124
|
-
console.log(` ⏭️ Skipped: ${skipped.length}`)
|
|
2125
|
-
console.log(` ❌ Failed: ${failed.length}`)
|
|
2126
|
-
|
|
2127
|
-
if (failed.length > 0) {
|
|
2128
|
-
console.log('\n❌ Failed migrations:')
|
|
2129
|
-
failed.forEach((f) => console.log(` - ${f.path}: ${f.error}`))
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
return {
|
|
2133
|
-
success: failed.length === 0,
|
|
2134
|
-
message: '',
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
/**
|
|
2139
|
-
* Execute architect plan and generate code
|
|
2140
|
-
*/
|
|
2141
|
-
async architect(action = 'execute', projectPath = process.cwd()) {
|
|
2142
|
-
if (action !== 'execute') {
|
|
2143
|
-
return {
|
|
2144
|
-
success: false,
|
|
2145
|
-
message: '❌ Invalid action. Use: /p:architect execute',
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
try {
|
|
2150
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
2151
|
-
if (!initResult.success) return initResult
|
|
2152
|
-
|
|
2153
|
-
console.log('🏗️ Architect Mode - Code Generation\n')
|
|
2154
|
-
|
|
2155
|
-
const globalPath = await this.getGlobalProjectPath(projectPath)
|
|
2156
|
-
|
|
2157
|
-
// Check if there's a completed plan
|
|
2158
|
-
const planPath = path.join(globalPath, 'planning', 'architect-session.md')
|
|
2159
|
-
|
|
2160
|
-
let planContent
|
|
2161
|
-
try {
|
|
2162
|
-
planContent = await fileHelper.readFile(planPath)
|
|
2163
|
-
} catch (error) {
|
|
2164
|
-
return {
|
|
2165
|
-
success: false,
|
|
2166
|
-
message:
|
|
2167
|
-
'❌ No architect plan found.\n\n' +
|
|
2168
|
-
'Create a plan first:\n' +
|
|
2169
|
-
' 1. Run /p:init in an empty directory\n' +
|
|
2170
|
-
' 2. Answer the discovery questions\n' +
|
|
2171
|
-
' 3. Plan will be auto-generated\n' +
|
|
2172
|
-
' 4. Then run /p:architect execute',
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
if (!planContent || planContent.trim() === '') {
|
|
2177
|
-
return {
|
|
2178
|
-
success: false,
|
|
2179
|
-
message: '❌ Architect plan is empty',
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
console.log('📋 Reading architect plan...\n')
|
|
2184
|
-
|
|
2185
|
-
// Extract key information from plan
|
|
2186
|
-
const ideaMatch = planContent.match(/## Project Idea\n(.+)/s)
|
|
2187
|
-
const stackMatch = planContent.match(/\*\*Stack:\*\*\n([\s\S]+?)\n\n/)
|
|
2188
|
-
const stepsMatch = planContent.match(/\*\*Implementation Steps:\*\*\n([\s\S]+?)\n\n/)
|
|
2189
|
-
|
|
2190
|
-
const idea = ideaMatch ? ideaMatch[1].split('\n')[0].trim() : 'Unknown project'
|
|
2191
|
-
const stack = stackMatch ? stackMatch[1] : 'Not specified'
|
|
2192
|
-
const steps = stepsMatch ? stepsMatch[1] : 'Not specified'
|
|
2193
|
-
|
|
2194
|
-
console.log(`📝 Project: ${idea}`)
|
|
2195
|
-
console.log(`\n🔧 Stack:\n${stack}`)
|
|
2196
|
-
console.log(`\n📋 Implementation Steps:\n${steps}`)
|
|
2197
|
-
|
|
2198
|
-
console.log('\n' + '='.repeat(60))
|
|
2199
|
-
console.log('🤖 READY TO GENERATE CODE')
|
|
2200
|
-
console.log('='.repeat(60))
|
|
2201
|
-
|
|
2202
|
-
console.log(
|
|
2203
|
-
'\nThe architect plan is ready. Claude will now:\n' +
|
|
2204
|
-
' 1. Read the architectural plan\n' +
|
|
2205
|
-
' 2. Use Context7 for official documentation\n' +
|
|
2206
|
-
' 3. Generate project structure\n' +
|
|
2207
|
-
' 4. Create starter files with boilerplate\n'
|
|
2208
|
-
)
|
|
2209
|
-
|
|
2210
|
-
console.log('\n💡 This command shows the plan.')
|
|
2211
|
-
console.log(' For code generation, Claude Code will read this plan')
|
|
2212
|
-
console.log(' and generate the structure automatically.\n')
|
|
2213
|
-
|
|
2214
|
-
await this.logToMemory(projectPath, 'architect_executed', {
|
|
2215
|
-
timestamp: dateHelper.getTimestamp(),
|
|
2216
|
-
idea,
|
|
2217
|
-
})
|
|
2218
|
-
|
|
2219
|
-
return {
|
|
2220
|
-
success: true,
|
|
2221
|
-
plan: planContent,
|
|
2222
|
-
idea,
|
|
2223
|
-
}
|
|
2224
|
-
} catch (error) {
|
|
2225
|
-
console.error('❌ Error:', error.message)
|
|
2226
|
-
return { success: false, error: error.message }
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
// Export both class and singleton instance
|
|
2232
|
-
// Class for CLI (new PrjctCommands())
|
|
2233
|
-
// Instance for direct use (require('./commands').sync())
|
|
2234
|
-
const instance = new PrjctCommands()
|
|
2235
|
-
|
|
2236
|
-
module.exports = instance
|
|
2237
|
-
module.exports.PrjctCommands = PrjctCommands
|