prjct-cli 0.13.3 → 0.15.1
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 +122 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/setup.md +18 -3
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/templates/mcp-config.json +20 -1
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { MdBaseManager } from './md-base-manager'
|
|
9
9
|
import { parseState, serializeState } from '../serializers'
|
|
10
|
+
import { generateUUID } from '../schemas'
|
|
10
11
|
import type { StateJson, CurrentTask, PreviousTask } from '../schemas/state'
|
|
11
12
|
|
|
12
13
|
class MdStateManager extends MdBaseManager<StateJson> {
|
|
@@ -71,7 +72,7 @@ class MdStateManager extends MdBaseManager<StateJson> {
|
|
|
71
72
|
}))
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
async pauseTask(projectId: string): Promise<StateJson> {
|
|
75
|
+
async pauseTask(projectId: string, reason?: string): Promise<StateJson> {
|
|
75
76
|
const state = await this.read(projectId)
|
|
76
77
|
if (!state.currentTask) {
|
|
77
78
|
throw new Error('No active task to pause')
|
|
@@ -82,7 +83,8 @@ class MdStateManager extends MdBaseManager<StateJson> {
|
|
|
82
83
|
description: state.currentTask.description,
|
|
83
84
|
status: 'paused',
|
|
84
85
|
startedAt: state.currentTask.startedAt,
|
|
85
|
-
pausedAt: new Date().toISOString()
|
|
86
|
+
pausedAt: new Date().toISOString(),
|
|
87
|
+
pauseReason: reason
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
return this.update(projectId, () => ({
|
|
@@ -92,24 +94,26 @@ class MdStateManager extends MdBaseManager<StateJson> {
|
|
|
92
94
|
}))
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
async resumeTask(projectId: string): Promise<
|
|
97
|
+
async resumeTask(projectId: string, _taskId?: string): Promise<CurrentTask | null> {
|
|
96
98
|
const state = await this.read(projectId)
|
|
97
99
|
if (!state.previousTask) {
|
|
98
|
-
|
|
100
|
+
return null
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
const currentTask: CurrentTask = {
|
|
102
104
|
id: state.previousTask.id,
|
|
103
105
|
description: state.previousTask.description,
|
|
104
|
-
startedAt: new Date().toISOString(),
|
|
105
|
-
sessionId:
|
|
106
|
+
startedAt: new Date().toISOString(),
|
|
107
|
+
sessionId: generateUUID()
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
await this.update(projectId, () => ({
|
|
109
111
|
currentTask,
|
|
110
112
|
previousTask: null,
|
|
111
113
|
lastUpdated: new Date().toISOString()
|
|
112
114
|
}))
|
|
115
|
+
|
|
116
|
+
return currentTask
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
async clearTask(projectId: string): Promise<StateJson> {
|
|
@@ -18,21 +18,8 @@ interface AgentConfig {
|
|
|
18
18
|
contextFilter?: string
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
frameworks: string[]
|
|
24
|
-
buildTools: string[]
|
|
25
|
-
testFrameworks: string[]
|
|
26
|
-
databases: string[]
|
|
27
|
-
tools: string[]
|
|
28
|
-
allDependencies: string[]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface GenerateContext {
|
|
32
|
-
detectedTech: TechSummary
|
|
33
|
-
analysisSummary?: string
|
|
34
|
-
projectPath?: string
|
|
35
|
-
}
|
|
21
|
+
// TechSummary and GenerateContext interfaces REMOVED.
|
|
22
|
+
// Agent generation is AGENTIC - Claude reads raw data and decides.
|
|
36
23
|
|
|
37
24
|
interface Agent {
|
|
38
25
|
name: string
|
|
@@ -51,8 +38,9 @@ class AgentGenerator {
|
|
|
51
38
|
|
|
52
39
|
constructor(projectId: string | null = null) {
|
|
53
40
|
this.projectId = projectId
|
|
41
|
+
// NEW: Write to data/agents/ for JSON storage (OpenCode-style)
|
|
54
42
|
this.outputDir = projectId
|
|
55
|
-
? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
|
|
43
|
+
? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'data', 'agents')
|
|
56
44
|
: path.join(os.homedir(), '.prjct-cli', 'agents')
|
|
57
45
|
this.loader = new AgentLoader(projectId)
|
|
58
46
|
}
|
|
@@ -60,61 +48,33 @@ class AgentGenerator {
|
|
|
60
48
|
/**
|
|
61
49
|
* Generate specialized agent with deep expertise
|
|
62
50
|
* Universal - works with ANY technology stack
|
|
51
|
+
* Writes JSON to data/agents/{name}.json
|
|
63
52
|
*/
|
|
64
53
|
async generateDynamicAgent(agentName: string, config: AgentConfig): Promise<{ name: string }> {
|
|
65
54
|
log.debug(`Generating ${agentName} agent...`)
|
|
66
55
|
await fs.mkdir(this.outputDir, { recursive: true })
|
|
67
56
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
57
|
+
// Write as JSON (OpenCode-style storage)
|
|
58
|
+
const agent = {
|
|
59
|
+
name: agentName,
|
|
60
|
+
role: config.role || agentName,
|
|
61
|
+
domain: config.domain || 'general',
|
|
62
|
+
expertise: config.expertise || '',
|
|
63
|
+
contextFilter: config.contextFilter || 'Only relevant files',
|
|
64
|
+
createdAt: new Date().toISOString()
|
|
65
|
+
}
|
|
70
66
|
|
|
71
|
-
const outputPath = path.join(this.outputDir, `${agentName}.
|
|
72
|
-
await fs.writeFile(outputPath,
|
|
67
|
+
const outputPath = path.join(this.outputDir, `${agentName}.json`)
|
|
68
|
+
await fs.writeFile(outputPath, JSON.stringify(agent, null, 2), 'utf-8')
|
|
73
69
|
log.debug(`${agentName} agent created`)
|
|
74
70
|
|
|
75
71
|
return { name: agentName }
|
|
76
72
|
}
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*/
|
|
83
|
-
async generateAgentsFromTech(context: GenerateContext): Promise<Array<{ name: string }>> {
|
|
84
|
-
const { detectedTech } = context
|
|
85
|
-
const agents: string[] = []
|
|
86
|
-
|
|
87
|
-
// Read agent generation template - Claude will use this to decide
|
|
88
|
-
const templatePath = path.join(__dirname, '../../templates/agents/AGENTS.md')
|
|
89
|
-
try {
|
|
90
|
-
await fs.readFile(templatePath, 'utf-8')
|
|
91
|
-
} catch {
|
|
92
|
-
// Fallback if template doesn't exist
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Build context for Claude to decide
|
|
96
|
-
const techSummary: TechSummary = {
|
|
97
|
-
languages: detectedTech.languages || [],
|
|
98
|
-
frameworks: detectedTech.frameworks || [],
|
|
99
|
-
buildTools: detectedTech.buildTools || [],
|
|
100
|
-
testFrameworks: detectedTech.testFrameworks || [],
|
|
101
|
-
databases: detectedTech.databases || [],
|
|
102
|
-
tools: detectedTech.tools || [],
|
|
103
|
-
allDependencies: detectedTech.allDependencies || [],
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Generate agents for detected domains
|
|
107
|
-
if (techSummary.languages.length > 0 || techSummary.frameworks.length > 0) {
|
|
108
|
-
await this.generateDynamicAgent('developer', {
|
|
109
|
-
role: 'Development Specialist',
|
|
110
|
-
domain: 'general',
|
|
111
|
-
projectContext: techSummary,
|
|
112
|
-
})
|
|
113
|
-
agents.push('developer')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return agents.map((name) => ({ name }))
|
|
117
|
-
}
|
|
74
|
+
// NOTE: generateAgentsFromTech() was REMOVED intentionally.
|
|
75
|
+
// Agent generation is AGENTIC - Claude reads analysis/repo-summary.md
|
|
76
|
+
// and DECIDES what specialists to create by calling generateDynamicAgent().
|
|
77
|
+
// DO NOT add any automatic agent generation logic here.
|
|
118
78
|
|
|
119
79
|
/**
|
|
120
80
|
* Build comprehensive agent prompt
|
|
@@ -175,10 +135,10 @@ ${config.contextFilter || 'Only relevant files'}
|
|
|
175
135
|
|
|
176
136
|
try {
|
|
177
137
|
const files = await fs.readdir(this.outputDir)
|
|
178
|
-
const agentFiles = files.filter((f) => f.endsWith('.
|
|
138
|
+
const agentFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.'))
|
|
179
139
|
|
|
180
140
|
for (const file of agentFiles) {
|
|
181
|
-
const type = file.replace('.
|
|
141
|
+
const type = file.replace('.json', '')
|
|
182
142
|
|
|
183
143
|
if (!requiredAgents.includes(type)) {
|
|
184
144
|
const filePath = path.join(this.outputDir, file)
|
|
@@ -200,7 +160,7 @@ ${config.contextFilter || 'Only relevant files'}
|
|
|
200
160
|
async listAgents(): Promise<string[]> {
|
|
201
161
|
try {
|
|
202
162
|
const files = await fs.readdir(this.outputDir)
|
|
203
|
-
return files.filter((f) => f.endsWith('.
|
|
163
|
+
return files.filter((f) => f.endsWith('.json') && !f.startsWith('.')).map((f) => f.replace('.json', ''))
|
|
204
164
|
} catch {
|
|
205
165
|
return []
|
|
206
166
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Bus for Sync
|
|
3
|
+
*
|
|
4
|
+
* Events are published on every Storage write/delete.
|
|
5
|
+
* Events accumulate in sync/pending.json until /p:ship or /p:sync.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs/promises'
|
|
9
|
+
import path from 'path'
|
|
10
|
+
import os from 'os'
|
|
11
|
+
|
|
12
|
+
export interface SyncEvent {
|
|
13
|
+
type: string
|
|
14
|
+
path: string[]
|
|
15
|
+
data: unknown
|
|
16
|
+
timestamp: string
|
|
17
|
+
projectId: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type EventType =
|
|
21
|
+
| 'task.created'
|
|
22
|
+
| 'task.updated'
|
|
23
|
+
| 'task.completed'
|
|
24
|
+
| 'task.deleted'
|
|
25
|
+
| 'feature.created'
|
|
26
|
+
| 'feature.updated'
|
|
27
|
+
| 'feature.shipped'
|
|
28
|
+
| 'feature.deleted'
|
|
29
|
+
| 'idea.created'
|
|
30
|
+
| 'idea.updated'
|
|
31
|
+
| 'idea.deleted'
|
|
32
|
+
| 'session.started'
|
|
33
|
+
| 'session.completed'
|
|
34
|
+
| 'shipped.created'
|
|
35
|
+
| 'agent.created'
|
|
36
|
+
| 'agent.updated'
|
|
37
|
+
| 'agent.deleted'
|
|
38
|
+
| 'project.updated'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Infer event type from path and action
|
|
42
|
+
*/
|
|
43
|
+
export function inferEventType(pathArray: string[], action: 'write' | 'delete'): EventType {
|
|
44
|
+
const entity = pathArray[0]
|
|
45
|
+
|
|
46
|
+
if (action === 'delete') {
|
|
47
|
+
return `${entity}.deleted` as EventType
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// For writes, we'd need to check if it's new or update
|
|
51
|
+
// For simplicity, use 'updated' (can be refined later)
|
|
52
|
+
return `${entity}.updated` as EventType
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class EventBus {
|
|
56
|
+
private pendingPath(projectId: string): string {
|
|
57
|
+
return path.join(os.homedir(), '.prjct-cli/projects', projectId, 'sync/pending.json')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Publish event to pending queue
|
|
62
|
+
*/
|
|
63
|
+
async publish(event: SyncEvent): Promise<void> {
|
|
64
|
+
const filePath = this.pendingPath(event.projectId)
|
|
65
|
+
|
|
66
|
+
// Read existing pending events
|
|
67
|
+
let pending: SyncEvent[] = []
|
|
68
|
+
try {
|
|
69
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
70
|
+
pending = JSON.parse(content)
|
|
71
|
+
} catch {
|
|
72
|
+
// File doesn't exist yet
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Add new event
|
|
76
|
+
pending.push(event)
|
|
77
|
+
|
|
78
|
+
// Ensure directory exists
|
|
79
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
80
|
+
|
|
81
|
+
// Write back
|
|
82
|
+
await fs.writeFile(filePath, JSON.stringify(pending, null, 2), 'utf-8')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get all pending events for a project
|
|
87
|
+
*/
|
|
88
|
+
async getPending(projectId: string): Promise<SyncEvent[]> {
|
|
89
|
+
const filePath = this.pendingPath(projectId)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
93
|
+
return JSON.parse(content)
|
|
94
|
+
} catch {
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clear pending events after successful sync
|
|
101
|
+
*/
|
|
102
|
+
async clearPending(projectId: string): Promise<void> {
|
|
103
|
+
const filePath = this.pendingPath(projectId)
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await fs.writeFile(filePath, '[]', 'utf-8')
|
|
107
|
+
} catch {
|
|
108
|
+
// Ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Update last sync timestamp
|
|
114
|
+
*/
|
|
115
|
+
async updateLastSync(projectId: string): Promise<void> {
|
|
116
|
+
const filePath = path.join(os.homedir(), '.prjct-cli/projects', projectId, 'sync/last-sync.json')
|
|
117
|
+
|
|
118
|
+
const data = {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
success: true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
124
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get last sync info
|
|
129
|
+
*/
|
|
130
|
+
async getLastSync(projectId: string): Promise<{ timestamp: string; success: boolean } | null> {
|
|
131
|
+
const filePath = path.join(os.homedir(), '.prjct-cli/projects', projectId, 'sync/last-sync.json')
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
135
|
+
return JSON.parse(content)
|
|
136
|
+
} catch {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const eventBus = new EventBus()
|
|
143
|
+
export default eventBus
|
package/core/index.ts
CHANGED
|
@@ -39,7 +39,7 @@ async function main(): Promise<void> {
|
|
|
39
39
|
process.exit(0)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
if (['-h', '--help',
|
|
42
|
+
if (['-h', '--help', undefined].includes(commandName)) {
|
|
43
43
|
displayHelp()
|
|
44
44
|
process.exit(0)
|
|
45
45
|
}
|
|
@@ -100,29 +100,32 @@ async function main(): Promise<void> {
|
|
|
100
100
|
result = await commands.setup(options)
|
|
101
101
|
} else if (commandName === 'migrate-all') {
|
|
102
102
|
result = await commands.migrateAll(options)
|
|
103
|
-
} else if (commandName === 'progress') {
|
|
104
|
-
const period = parsedArgs[0] || 'week'
|
|
105
|
-
result = await commands.progress(period)
|
|
106
|
-
} else if (commandName === 'build') {
|
|
107
|
-
const taskOrNumber = parsedArgs.join(' ')
|
|
108
|
-
result = await commands.build(taskOrNumber)
|
|
109
103
|
} else {
|
|
110
104
|
// Standard commands - type-safe invocation
|
|
111
105
|
const param = parsedArgs.join(' ') || null
|
|
112
106
|
const standardCommands: Record<string, (p: string | null) => Promise<CommandResult>> = {
|
|
113
|
-
|
|
107
|
+
// Core workflow
|
|
108
|
+
work: (p) => commands.work(p),
|
|
114
109
|
done: () => commands.done(),
|
|
115
110
|
next: () => commands.next(),
|
|
111
|
+
pause: (p) => commands.pause(p || ''),
|
|
112
|
+
resume: (p) => commands.resume(p),
|
|
113
|
+
// Planning
|
|
116
114
|
init: (p) => commands.init(p),
|
|
117
115
|
feature: (p) => commands.feature(p || ''),
|
|
118
116
|
bug: (p) => commands.bug(p || ''),
|
|
119
|
-
|
|
117
|
+
idea: (p) => commands.idea(p || ''),
|
|
118
|
+
spec: (p) => commands.spec(p),
|
|
120
119
|
ship: (p) => commands.ship(p),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
// Analytics
|
|
121
|
+
dash: (p) => commands.dash(p || 'default'),
|
|
122
|
+
help: (p) => commands.help(p || ''),
|
|
123
|
+
// Maintenance
|
|
124
|
+
recover: () => commands.recover(),
|
|
125
|
+
undo: () => commands.undo(),
|
|
126
|
+
redo: () => commands.redo(),
|
|
127
|
+
history: () => commands.history(),
|
|
128
|
+
// Setup
|
|
126
129
|
sync: () => commands.sync(),
|
|
127
130
|
start: () => commands.start(),
|
|
128
131
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Capability Installer
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* AGENTIC: This module provides TOOLS, not DECISIONS.
|
|
5
|
+
* Claude reads project context and decides what to install/configure.
|
|
6
|
+
*
|
|
7
|
+
* NO hardcoded configurations for specific frameworks.
|
|
8
|
+
* Configuration should be template-driven, not code-driven.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
import { exec } from 'child_process'
|
|
7
12
|
import { promisify } from 'util'
|
|
8
|
-
import fs from 'fs/promises'
|
|
9
|
-
import path from 'path'
|
|
10
|
-
import projectCapabilities from '../utils/project-capabilities'
|
|
11
13
|
|
|
12
14
|
const execAsync = promisify(exec)
|
|
13
15
|
|
|
@@ -25,20 +27,12 @@ interface InstallResult {
|
|
|
25
27
|
error?: string
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
interface ConfigureResult {
|
|
29
|
-
configured: boolean
|
|
30
|
-
framework?: string
|
|
31
|
-
tool?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface PackageJson {
|
|
35
|
-
devDependencies?: Record<string, string>
|
|
36
|
-
scripts?: Record<string, string>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
30
|
class CapabilityInstaller {
|
|
40
31
|
/**
|
|
41
|
-
* Install capability
|
|
32
|
+
* Install capability using provided command
|
|
33
|
+
*
|
|
34
|
+
* AGENTIC: Claude provides the install command based on analysis.
|
|
35
|
+
* This is just the executor - no hardcoded logic.
|
|
42
36
|
*/
|
|
43
37
|
async install(
|
|
44
38
|
capability: string,
|
|
@@ -49,7 +43,6 @@ class CapabilityInstaller {
|
|
|
49
43
|
const startTime = Date.now()
|
|
50
44
|
|
|
51
45
|
try {
|
|
52
|
-
// Execute installation command
|
|
53
46
|
const { stdout, stderr } = await execAsync(command)
|
|
54
47
|
|
|
55
48
|
const duration = Date.now() - startTime
|
|
@@ -73,138 +66,9 @@ class CapabilityInstaller {
|
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
async configure(capability: string, projectPath: string): Promise<ConfigureResult> {
|
|
80
|
-
const configs: Record<string, () => Promise<ConfigureResult>> = {
|
|
81
|
-
test: () => this.configureTest(projectPath),
|
|
82
|
-
design: () => this.configureDesign(projectPath),
|
|
83
|
-
docs: () => this.configureDocs(projectPath),
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (configs[capability]) {
|
|
87
|
-
return await configs[capability]()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return { configured: false }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Configure test framework
|
|
95
|
-
*/
|
|
96
|
-
async configureTest(projectPath: string): Promise<ConfigureResult> {
|
|
97
|
-
// Check if package.json exists
|
|
98
|
-
const pkgPath = path.join(projectPath, 'package.json')
|
|
99
|
-
const pkg: PackageJson = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
|
|
100
|
-
|
|
101
|
-
const hasVitest = pkg.devDependencies?.vitest
|
|
102
|
-
const hasJest = pkg.devDependencies?.jest
|
|
103
|
-
|
|
104
|
-
if (hasVitest) {
|
|
105
|
-
// Create vitest.config.js
|
|
106
|
-
const config = `import { defineConfig } from 'vitest/config'
|
|
107
|
-
|
|
108
|
-
export default defineConfig({
|
|
109
|
-
test: {
|
|
110
|
-
globals: true,
|
|
111
|
-
environment: 'jsdom',
|
|
112
|
-
setupFiles: './test/setup.js'
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
`
|
|
116
|
-
await fs.writeFile(path.join(projectPath, 'vitest.config.js'), config)
|
|
117
|
-
|
|
118
|
-
// Add test script
|
|
119
|
-
pkg.scripts = pkg.scripts || {}
|
|
120
|
-
pkg.scripts.test = 'vitest'
|
|
121
|
-
pkg.scripts['test:ui'] = 'vitest --ui'
|
|
122
|
-
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
|
|
123
|
-
|
|
124
|
-
return { configured: true, framework: 'vitest' }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (hasJest) {
|
|
128
|
-
// Create jest.config.js
|
|
129
|
-
const config = `module.exports = {
|
|
130
|
-
testEnvironment: 'jsdom',
|
|
131
|
-
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
|
|
132
|
-
moduleNameMapper: {
|
|
133
|
-
'\\\\.(css|less|scss|sass)$': 'identity-obj-proxy'
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
`
|
|
137
|
-
await fs.writeFile(path.join(projectPath, 'jest.config.js'), config)
|
|
138
|
-
|
|
139
|
-
// Add test script
|
|
140
|
-
pkg.scripts = pkg.scripts || {}
|
|
141
|
-
pkg.scripts.test = 'jest'
|
|
142
|
-
pkg.scripts['test:watch'] = 'jest --watch'
|
|
143
|
-
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
|
|
144
|
-
|
|
145
|
-
return { configured: true, framework: 'jest' }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { configured: false }
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Configure design system
|
|
153
|
-
*/
|
|
154
|
-
async configureDesign(projectPath: string): Promise<ConfigureResult> {
|
|
155
|
-
const pkgPath = path.join(projectPath, 'package.json')
|
|
156
|
-
const pkg: PackageJson = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
|
|
157
|
-
|
|
158
|
-
if (pkg.devDependencies?.storybook) {
|
|
159
|
-
// Storybook auto-configures itself during init
|
|
160
|
-
return { configured: true, tool: 'storybook' }
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { configured: false }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Configure documentation
|
|
168
|
-
*/
|
|
169
|
-
async configureDocs(projectPath: string): Promise<ConfigureResult> {
|
|
170
|
-
const pkgPath = path.join(projectPath, 'package.json')
|
|
171
|
-
const pkg: PackageJson = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
|
|
172
|
-
|
|
173
|
-
if (pkg.devDependencies?.jsdoc) {
|
|
174
|
-
// Create jsdoc.json
|
|
175
|
-
const config = {
|
|
176
|
-
source: {
|
|
177
|
-
include: ['src'],
|
|
178
|
-
includePattern: '.+\\.js(doc|x)?$',
|
|
179
|
-
excludePattern: '(node_modules|docs)',
|
|
180
|
-
},
|
|
181
|
-
opts: {
|
|
182
|
-
destination: './docs',
|
|
183
|
-
recurse: true,
|
|
184
|
-
},
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
await fs.writeFile(path.join(projectPath, 'jsdoc.json'), JSON.stringify(config, null, 2))
|
|
188
|
-
|
|
189
|
-
// Add docs script
|
|
190
|
-
pkg.scripts = pkg.scripts || {}
|
|
191
|
-
pkg.scripts.docs = 'jsdoc -c jsdoc.json'
|
|
192
|
-
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
|
|
193
|
-
|
|
194
|
-
return { configured: true, tool: 'jsdoc' }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return { configured: false }
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Verify installation succeeded
|
|
202
|
-
*/
|
|
203
|
-
async verify(capability: string, projectPath: string): Promise<boolean> {
|
|
204
|
-
const detected = await projectCapabilities.detect(projectPath)
|
|
205
|
-
|
|
206
|
-
return detected[capability as keyof typeof detected] === true
|
|
207
|
-
}
|
|
69
|
+
// NOTE: configure() and hardcoded framework configs REMOVED.
|
|
70
|
+
// Configuration is AGENTIC - Claude reads templates and generates configs.
|
|
71
|
+
// See templates/workflows/ for configuration guidance.
|
|
208
72
|
}
|
|
209
73
|
|
|
210
74
|
const capabilityInstaller = new CapabilityInstaller()
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import fs from 'fs/promises'
|
|
7
|
+
import { accessSync } from 'fs'
|
|
7
8
|
import path from 'path'
|
|
8
9
|
import os from 'os'
|
|
9
10
|
import type { FindProjectsOptions } from './types'
|
|
@@ -34,7 +35,7 @@ export async function findAllProjects(options: FindProjectsOptions = {}): Promis
|
|
|
34
35
|
.map((dir) => path.join(os.homedir(), dir))
|
|
35
36
|
.filter((dirPath) => {
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
+
accessSync(dirPath)
|
|
38
39
|
return true
|
|
39
40
|
} catch {
|
|
40
41
|
return false
|
|
@@ -36,13 +36,11 @@ class PathManager {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Generate a unique project ID
|
|
40
|
-
*
|
|
39
|
+
* Generate a unique project ID using UUID.
|
|
40
|
+
* Standard UUID format for PostgreSQL consistency.
|
|
41
41
|
*/
|
|
42
|
-
generateProjectId(
|
|
43
|
-
|
|
44
|
-
const hash = crypto.createHash('sha256').update(absolutePath).digest('hex')
|
|
45
|
-
return hash.substring(0, 12) // Use first 12 chars for readability
|
|
42
|
+
generateProjectId(_projectPath: string): string {
|
|
43
|
+
return crypto.randomUUID()
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
/**
|