prjct-cli 0.19.0 → 0.20.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 +66 -6
- package/CLAUDE.md +56 -15
- package/README.md +5 -6
- package/bin/prjct +59 -42
- package/bin/prjct.ts +60 -0
- package/core/__tests__/agentic/memory-system.test.ts +18 -3
- package/core/__tests__/agentic/plan-mode.test.ts +55 -26
- package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
- package/core/__tests__/utils/project-commands.test.ts +72 -0
- package/core/agentic/agent-router.ts +3 -12
- package/core/agentic/command-executor.ts +372 -3
- package/core/agentic/context-builder.ts +7 -27
- package/core/agentic/ground-truth.ts +604 -5
- package/core/agentic/index.ts +180 -0
- package/core/agentic/loop-detector.ts +418 -4
- package/core/agentic/memory-system.ts +857 -3
- package/core/agentic/plan-mode.ts +491 -4
- package/core/agentic/prompt-builder.ts +44 -65
- package/core/agentic/services.ts +13 -5
- package/core/agentic/skill-loader.ts +112 -0
- package/core/agentic/smart-context.ts +37 -122
- package/core/agentic/template-loader.ts +79 -122
- package/core/agentic/tool-registry.ts +5 -11
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +4 -2
- package/core/bus/bus.ts +262 -0
- package/core/bus/index.ts +3 -313
- package/core/commands/analysis.ts +5 -5
- package/core/commands/analytics.ts +11 -11
- package/core/commands/base.ts +33 -209
- package/core/commands/cleanup.ts +148 -0
- package/core/commands/command-data.ts +346 -0
- package/core/commands/commands.ts +216 -0
- package/core/commands/design.ts +83 -0
- package/core/commands/index.ts +13 -207
- package/core/commands/maintenance.ts +52 -473
- package/core/commands/planning.ts +3 -3
- package/core/commands/register.ts +104 -0
- package/core/commands/registry.ts +441 -0
- package/core/commands/setup.ts +25 -9
- package/core/commands/shipping.ts +48 -11
- package/core/commands/snapshots.ts +299 -0
- package/core/commands/workflow.ts +2 -2
- package/core/constants/index.ts +254 -4
- package/core/domain/agent-loader.ts +5 -6
- package/core/domain/task-stack.ts +555 -4
- package/core/errors.ts +127 -1
- package/core/events/events.ts +87 -0
- package/core/events/index.ts +4 -138
- package/core/index.ts +15 -23
- package/core/infrastructure/agent-detector.ts +126 -201
- package/core/infrastructure/author-detector.ts +99 -171
- package/core/infrastructure/command-installer.ts +476 -4
- package/core/infrastructure/config-manager.ts +41 -37
- package/core/infrastructure/path-manager.ts +59 -9
- package/core/infrastructure/permission-manager.ts +286 -0
- package/core/outcomes/analyzer.ts +7 -41
- package/core/outcomes/index.ts +1 -1
- package/core/outcomes/recorder.ts +1 -1
- package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
- package/core/plugin/loader.ts +5 -5
- package/core/plugin/registry.ts +2 -2
- package/core/schemas/ideas.ts +85 -54
- package/core/schemas/index.ts +14 -33
- package/core/schemas/permissions.ts +177 -0
- package/core/schemas/project.ts +39 -12
- package/core/schemas/roadmap.ts +94 -59
- package/core/schemas/schemas.ts +39 -0
- package/core/schemas/shipped.ts +87 -60
- package/core/schemas/state.ts +110 -70
- package/core/server/index.ts +21 -0
- package/core/server/routes.ts +165 -0
- package/core/server/server.ts +136 -0
- package/core/server/sse.ts +135 -0
- package/core/services/agent-service.ts +170 -0
- package/core/services/breakdown-service.ts +126 -0
- package/core/services/index.ts +21 -0
- package/core/services/memory-service.ts +108 -0
- package/core/services/project-service.ts +146 -0
- package/core/services/skill-service.ts +253 -0
- package/core/session/compaction.ts +257 -0
- package/core/session/index.ts +20 -8
- package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
- package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
- package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +10 -26
- package/core/storage/index.ts +14 -162
- package/core/storage/queue-storage.ts +13 -11
- package/core/storage/shipped-storage.ts +4 -17
- package/core/storage/state-storage.ts +35 -43
- package/core/storage/storage-manager.ts +42 -52
- package/core/storage/storage.ts +160 -0
- package/core/sync/auth-config.ts +1 -8
- package/core/sync/index.ts +17 -10
- package/core/sync/oauth-handler.ts +1 -6
- package/core/sync/sync-client.ts +6 -34
- package/core/sync/sync-manager.ts +11 -40
- package/core/types/agentic.ts +577 -0
- package/core/types/agents.ts +145 -0
- package/core/types/bus.ts +82 -0
- package/core/types/commands.ts +366 -0
- package/core/types/config.ts +66 -0
- package/core/types/core.ts +96 -0
- package/core/types/domain.ts +71 -0
- package/core/types/events.ts +42 -0
- package/core/types/fs.ts +56 -0
- package/core/types/index.ts +387 -500
- package/core/types/infrastructure.ts +196 -0
- package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
- package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
- package/core/types/plugin.ts +25 -0
- package/core/types/server.ts +54 -0
- package/core/types/services.ts +65 -0
- package/core/types/session.ts +135 -0
- package/core/types/storage.ts +148 -0
- package/core/types/sync.ts +121 -0
- package/core/types/task.ts +72 -0
- package/core/types/template.ts +24 -0
- package/core/types/utils.ts +90 -0
- package/core/utils/cache.ts +195 -0
- package/core/utils/collection-filters.ts +245 -0
- package/core/utils/date-helper.ts +1 -5
- package/core/utils/file-helper.ts +20 -10
- package/core/utils/jsonl-helper.ts +5 -8
- package/core/utils/markdown-builder.ts +277 -0
- package/core/utils/project-commands.ts +132 -0
- package/core/utils/runtime.ts +119 -0
- package/dist/bin/prjct.mjs +12568 -0
- package/package.json +13 -8
- package/scripts/build.js +106 -0
- package/scripts/postinstall.js +50 -8
- package/templates/agentic/agents/uxui.md +210 -0
- package/templates/agentic/subagent-generation.md +1 -1
- package/templates/commands/bug.md +219 -41
- package/templates/commands/feature.md +368 -80
- package/templates/commands/serve.md +118 -0
- package/templates/commands/ship.md +152 -14
- package/templates/commands/skill.md +110 -0
- package/templates/commands/sync.md +63 -4
- package/templates/commands/test.md +40 -188
- package/templates/mcp-config.json +0 -36
- package/templates/permissions/default.jsonc +60 -0
- package/templates/permissions/permissive.jsonc +49 -0
- package/templates/permissions/strict.jsonc +62 -0
- package/templates/skills/code-review.md +47 -0
- package/templates/skills/debug.md +61 -0
- package/templates/skills/refactor.md +47 -0
- package/templates/subagents/domain/devops.md +1 -1
- package/templates/subagents/domain/testing.md +6 -10
- package/templates/subagents/workflow/prjct-shipper.md +16 -7
- package/templates/tools/bash.txt +22 -0
- package/templates/tools/edit.txt +18 -0
- package/templates/tools/glob.txt +19 -0
- package/templates/tools/grep.txt +21 -0
- package/templates/tools/read.txt +14 -0
- package/templates/tools/task.txt +20 -0
- package/templates/tools/webfetch.txt +16 -0
- package/templates/tools/websearch.txt +18 -0
- package/templates/tools/write.txt +17 -0
- package/core/agentic/command-executor/command-executor.ts +0 -312
- package/core/agentic/command-executor/index.ts +0 -16
- package/core/agentic/command-executor/status-signal.ts +0 -38
- package/core/agentic/command-executor/types.ts +0 -79
- package/core/agentic/ground-truth/index.ts +0 -76
- package/core/agentic/ground-truth/types.ts +0 -33
- package/core/agentic/ground-truth/utils.ts +0 -48
- package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
- package/core/agentic/ground-truth/verifiers/done.ts +0 -75
- package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
- package/core/agentic/ground-truth/verifiers/index.ts +0 -37
- package/core/agentic/ground-truth/verifiers/init.ts +0 -52
- package/core/agentic/ground-truth/verifiers/now.ts +0 -57
- package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
- package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
- package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
- package/core/agentic/ground-truth/verifiers.ts +0 -6
- package/core/agentic/loop-detector/error-analysis.ts +0 -97
- package/core/agentic/loop-detector/hallucination.ts +0 -71
- package/core/agentic/loop-detector/index.ts +0 -41
- package/core/agentic/loop-detector/loop-detector.ts +0 -222
- package/core/agentic/loop-detector/types.ts +0 -66
- package/core/agentic/memory-system/history.ts +0 -53
- package/core/agentic/memory-system/index.ts +0 -192
- package/core/agentic/memory-system/patterns.ts +0 -156
- package/core/agentic/memory-system/semantic-memories.ts +0 -278
- package/core/agentic/memory-system/session.ts +0 -21
- package/core/agentic/plan-mode/approval.ts +0 -57
- package/core/agentic/plan-mode/constants.ts +0 -44
- package/core/agentic/plan-mode/index.ts +0 -28
- package/core/agentic/plan-mode/plan-mode.ts +0 -407
- package/core/agentic/plan-mode/types.ts +0 -193
- package/core/agents/types.ts +0 -126
- package/core/command-registry/categories.ts +0 -23
- package/core/command-registry/commands.ts +0 -15
- package/core/command-registry/core-commands.ts +0 -344
- package/core/command-registry/index.ts +0 -158
- package/core/command-registry/optional-commands.ts +0 -163
- package/core/command-registry/setup-commands.ts +0 -83
- package/core/command-registry/types.ts +0 -59
- package/core/command-registry.ts +0 -9
- package/core/commands/types.ts +0 -185
- package/core/commands.ts +0 -11
- package/core/constants/formats.ts +0 -187
- package/core/context-sync.ts +0 -18
- package/core/data/index.ts +0 -27
- package/core/data/md-base-manager.ts +0 -203
- package/core/data/md-ideas-manager.ts +0 -155
- package/core/data/md-queue-manager.ts +0 -180
- package/core/data/md-shipped-manager.ts +0 -90
- package/core/data/md-state-manager.ts +0 -137
- package/core/domain/task-stack/index.ts +0 -19
- package/core/domain/task-stack/parser.ts +0 -86
- package/core/domain/task-stack/storage.ts +0 -123
- package/core/domain/task-stack/task-stack.ts +0 -340
- package/core/domain/task-stack/types.ts +0 -51
- package/core/infrastructure/command-installer/command-installer.ts +0 -327
- package/core/infrastructure/command-installer/global-config.ts +0 -136
- package/core/infrastructure/command-installer/index.ts +0 -25
- package/core/infrastructure/command-installer/types.ts +0 -41
- package/core/infrastructure/session-manager/index.ts +0 -23
- package/core/infrastructure/session-manager/types.ts +0 -45
- package/core/infrastructure/session-manager.ts +0 -8
- package/core/serializers/ideas-serializer.ts +0 -187
- package/core/serializers/index.ts +0 -36
- package/core/serializers/queue-serializer.ts +0 -210
- package/core/serializers/shipped-serializer.ts +0 -108
- package/core/serializers/state-serializer.ts +0 -136
- package/core/session/types.ts +0 -29
- /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Registry - Central command routing and execution
|
|
3
|
+
*
|
|
4
|
+
* Replaces the aggregator anti-pattern in commands.ts with a proper registry.
|
|
5
|
+
* Each command is registered as a handler that receives context and returns a result.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CommandResult } from '../types'
|
|
9
|
+
import configManager from '../infrastructure/config-manager'
|
|
10
|
+
import pathManager from '../infrastructure/path-manager'
|
|
11
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
12
|
+
|
|
13
|
+
// Re-export types for convenience
|
|
14
|
+
export type {
|
|
15
|
+
ExecutionContext,
|
|
16
|
+
CommandHandler,
|
|
17
|
+
HandlerFn,
|
|
18
|
+
RegistryCommandUsage as CommandUsage,
|
|
19
|
+
BlockingRules,
|
|
20
|
+
CommandMeta,
|
|
21
|
+
CategoryInfo,
|
|
22
|
+
RegistryStats,
|
|
23
|
+
CommandValidationResult as ValidationResult,
|
|
24
|
+
} from '../types'
|
|
25
|
+
|
|
26
|
+
import type {
|
|
27
|
+
ExecutionContext,
|
|
28
|
+
CommandHandler,
|
|
29
|
+
HandlerFn,
|
|
30
|
+
CommandMeta,
|
|
31
|
+
CategoryInfo,
|
|
32
|
+
RegistryStats,
|
|
33
|
+
} from '../types'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Command Registry - Routes commands to handlers
|
|
37
|
+
*
|
|
38
|
+
* Single source of truth for command metadata and execution.
|
|
39
|
+
* Supports:
|
|
40
|
+
* - Class-based handlers (CommandHandler interface)
|
|
41
|
+
* - Function-based handlers (HandlerFn type)
|
|
42
|
+
* - Bound method registration from existing classes
|
|
43
|
+
* - Full metadata registration from static definitions
|
|
44
|
+
*/
|
|
45
|
+
export class CommandRegistry {
|
|
46
|
+
private handlers: Map<string, CommandHandler<unknown>> = new Map()
|
|
47
|
+
private handlerFns: Map<string, HandlerFn<unknown>> = new Map()
|
|
48
|
+
private metadata: Map<string, CommandMeta> = new Map()
|
|
49
|
+
private categories: Map<string, CategoryInfo> = new Map()
|
|
50
|
+
private noProjectCommands: Set<string> = new Set(['init', 'setup', 'start', 'migrateAll'])
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register a command handler (class-based)
|
|
54
|
+
*/
|
|
55
|
+
register<TParams>(handler: CommandHandler<TParams>, meta?: Partial<CommandMeta>): void {
|
|
56
|
+
this.handlers.set(handler.name, handler as CommandHandler<unknown>)
|
|
57
|
+
this.setMeta(handler.name, meta)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Register a command handler function (function-based)
|
|
62
|
+
*/
|
|
63
|
+
registerFn<TParams>(name: string, handler: HandlerFn<TParams>, meta?: Partial<CommandMeta>): void {
|
|
64
|
+
this.handlerFns.set(name, handler as HandlerFn<unknown>)
|
|
65
|
+
this.setMeta(name, meta)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set command metadata with defaults
|
|
70
|
+
*/
|
|
71
|
+
private setMeta(name: string, meta?: Partial<CommandMeta>): void {
|
|
72
|
+
const requiresProject = meta?.requiresProject ?? !this.noProjectCommands.has(name)
|
|
73
|
+
this.metadata.set(name, {
|
|
74
|
+
name,
|
|
75
|
+
group: meta?.group ?? 'unknown',
|
|
76
|
+
description: meta?.description ?? '',
|
|
77
|
+
requiresProject,
|
|
78
|
+
usage: meta?.usage ?? { claude: null, terminal: null },
|
|
79
|
+
implemented: meta?.implemented ?? true,
|
|
80
|
+
hasTemplate: meta?.hasTemplate ?? false,
|
|
81
|
+
params: meta?.params,
|
|
82
|
+
blockingRules: meta?.blockingRules,
|
|
83
|
+
features: meta?.features,
|
|
84
|
+
isOptional: meta?.isOptional,
|
|
85
|
+
deprecated: meta?.deprecated,
|
|
86
|
+
replacedBy: meta?.replacedBy,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Register a category
|
|
92
|
+
*/
|
|
93
|
+
registerCategory(name: string, info: CategoryInfo): void {
|
|
94
|
+
this.categories.set(name, info)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Register a bound method from an existing command group
|
|
99
|
+
* Bridges legacy command classes to the registry pattern
|
|
100
|
+
*/
|
|
101
|
+
registerMethod<T extends object>(
|
|
102
|
+
name: string,
|
|
103
|
+
instance: T,
|
|
104
|
+
methodName: keyof T,
|
|
105
|
+
meta?: Partial<CommandMeta>
|
|
106
|
+
): void {
|
|
107
|
+
const method = instance[methodName]
|
|
108
|
+
if (typeof method !== 'function') {
|
|
109
|
+
throw new Error(`${String(methodName)} is not a function`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create a wrapper that adapts legacy method signature to HandlerFn
|
|
113
|
+
const wrapper: HandlerFn<unknown> = async (params, context) => {
|
|
114
|
+
// Legacy commands expect (param?, projectPath) signature
|
|
115
|
+
// Most commands use first param + projectPath
|
|
116
|
+
if (params !== undefined && params !== null) {
|
|
117
|
+
return (method as Function).call(instance, params, context.projectPath)
|
|
118
|
+
}
|
|
119
|
+
return (method as Function).call(instance, context.projectPath)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.handlerFns.set(name, wrapper)
|
|
123
|
+
this.setMeta(name, meta)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if a command is registered
|
|
128
|
+
*/
|
|
129
|
+
has(name: string): boolean {
|
|
130
|
+
return this.handlers.has(name) || this.handlerFns.has(name)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get list of registered commands
|
|
135
|
+
*/
|
|
136
|
+
list(): string[] {
|
|
137
|
+
return [
|
|
138
|
+
...this.handlers.keys(),
|
|
139
|
+
...this.handlerFns.keys(),
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get commands by group
|
|
145
|
+
*/
|
|
146
|
+
listByGroup(group: string): string[] {
|
|
147
|
+
return Array.from(this.metadata.entries())
|
|
148
|
+
.filter(([, meta]) => meta.group === group)
|
|
149
|
+
.map(([name]) => name)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get all groups
|
|
154
|
+
*/
|
|
155
|
+
getGroups(): string[] {
|
|
156
|
+
const groups = new Set<string>()
|
|
157
|
+
for (const meta of this.metadata.values()) {
|
|
158
|
+
groups.add(meta.group)
|
|
159
|
+
}
|
|
160
|
+
return Array.from(groups)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get command metadata
|
|
165
|
+
*/
|
|
166
|
+
getMeta(name: string): CommandMeta | undefined {
|
|
167
|
+
return this.metadata.get(name)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ===== Query Methods (from static registry) =====
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get all commands
|
|
174
|
+
*/
|
|
175
|
+
getAll(): CommandMeta[] {
|
|
176
|
+
return Array.from(this.metadata.values())
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get command by name
|
|
181
|
+
*/
|
|
182
|
+
getByName(name: string): CommandMeta | undefined {
|
|
183
|
+
return this.metadata.get(name)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get commands by category/group
|
|
188
|
+
*/
|
|
189
|
+
getByCategory(category: string): CommandMeta[] {
|
|
190
|
+
return this.getAll().filter((c) => c.group === category)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get all implemented commands
|
|
195
|
+
*/
|
|
196
|
+
getAllImplemented(): CommandMeta[] {
|
|
197
|
+
return this.getAll().filter((c) => c.implemented)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get all commands with templates
|
|
202
|
+
*/
|
|
203
|
+
getAllWithTemplates(): CommandMeta[] {
|
|
204
|
+
return this.getAll().filter((c) => c.hasTemplate)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get commands available in Claude Code
|
|
209
|
+
*/
|
|
210
|
+
getClaudeCommands(): CommandMeta[] {
|
|
211
|
+
return this.getAll().filter((c) => c.usage.claude !== null)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get commands available in terminal
|
|
216
|
+
*/
|
|
217
|
+
getTerminalCommands(): CommandMeta[] {
|
|
218
|
+
return this.getAll().filter((c) => c.usage.terminal !== null)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get all categories
|
|
223
|
+
*/
|
|
224
|
+
getAllCategories(): Map<string, CategoryInfo> {
|
|
225
|
+
return new Map(this.categories)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get category metadata
|
|
230
|
+
*/
|
|
231
|
+
getCategory(category: string): CategoryInfo | undefined {
|
|
232
|
+
return this.categories.get(category)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get commands that require initialization
|
|
237
|
+
*/
|
|
238
|
+
getRequiresInit(): CommandMeta[] {
|
|
239
|
+
return this.getAll().filter((c) => c.requiresProject)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get commands with blocking rules
|
|
244
|
+
*/
|
|
245
|
+
getWithBlockingRules(): CommandMeta[] {
|
|
246
|
+
return this.getAll().filter((c) => c.blockingRules !== undefined)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get optional commands
|
|
251
|
+
*/
|
|
252
|
+
getOptionalCommands(): CommandMeta[] {
|
|
253
|
+
return this.getAll().filter((c) => c.isOptional)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get deprecated commands
|
|
258
|
+
*/
|
|
259
|
+
getDeprecatedCommands(): CommandMeta[] {
|
|
260
|
+
return this.getAll().filter((c) => c.deprecated)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get statistics
|
|
265
|
+
*/
|
|
266
|
+
getStats(): RegistryStats {
|
|
267
|
+
const all = this.getAll()
|
|
268
|
+
const byCategory: Record<string, number> = {}
|
|
269
|
+
|
|
270
|
+
for (const category of this.categories.keys()) {
|
|
271
|
+
byCategory[category] = all.filter((c) => c.group === category).length
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
total: all.length,
|
|
276
|
+
implemented: all.filter((c) => c.implemented).length,
|
|
277
|
+
withTemplates: all.filter((c) => c.hasTemplate).length,
|
|
278
|
+
claudeOnly: all.filter((c) => c.usage.claude && !c.usage.terminal).length,
|
|
279
|
+
terminalOnly: all.filter((c) => !c.usage.claude && c.usage.terminal).length,
|
|
280
|
+
both: all.filter((c) => c.usage.claude && c.usage.terminal).length,
|
|
281
|
+
requiresInit: all.filter((c) => c.requiresProject).length,
|
|
282
|
+
byCategory,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Validate registry
|
|
288
|
+
*/
|
|
289
|
+
validate(): { valid: boolean; issues: string[] } {
|
|
290
|
+
const issues: string[] = []
|
|
291
|
+
const all = this.getAll()
|
|
292
|
+
|
|
293
|
+
// Check for duplicate names
|
|
294
|
+
const names = all.map((c) => c.name)
|
|
295
|
+
const duplicates = names.filter((name, index) => names.indexOf(name) !== index)
|
|
296
|
+
if (duplicates.length > 0) {
|
|
297
|
+
issues.push(`Duplicate command names: ${duplicates.join(', ')}`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check for commands with templates but not implemented
|
|
301
|
+
const notImplemented = all.filter((c) => c.hasTemplate && !c.implemented)
|
|
302
|
+
if (notImplemented.length > 0) {
|
|
303
|
+
issues.push(
|
|
304
|
+
`Commands with templates but not implemented: ${notImplemented.map((c) => c.name).join(', ')}`
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check for invalid categories
|
|
309
|
+
const validCategories = Array.from(this.categories.keys())
|
|
310
|
+
if (validCategories.length > 0) {
|
|
311
|
+
const invalidCategories = all.filter((c) => !validCategories.includes(c.group))
|
|
312
|
+
if (invalidCategories.length > 0) {
|
|
313
|
+
issues.push(
|
|
314
|
+
`Invalid categories: ${invalidCategories.map((c) => `${c.name}:${c.group}`).join(', ')}`
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
valid: issues.length === 0,
|
|
321
|
+
issues,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Build execution context for a project
|
|
327
|
+
*/
|
|
328
|
+
async buildContext(projectPath: string): Promise<ExecutionContext> {
|
|
329
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
330
|
+
|
|
331
|
+
if (!projectId) {
|
|
332
|
+
throw new Error('No prjct project found. Run /p:init first.')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
projectId,
|
|
337
|
+
projectPath,
|
|
338
|
+
globalPath: pathManager.getGlobalProjectPath(projectId),
|
|
339
|
+
timestamp: getTimestamp(),
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Execute a command by name
|
|
345
|
+
*/
|
|
346
|
+
async execute<TParams = void>(
|
|
347
|
+
name: string,
|
|
348
|
+
params: TParams,
|
|
349
|
+
projectPath: string = process.cwd()
|
|
350
|
+
): Promise<CommandResult> {
|
|
351
|
+
const meta = this.metadata.get(name)
|
|
352
|
+
|
|
353
|
+
// Build context (may throw if project not initialized)
|
|
354
|
+
let context: ExecutionContext
|
|
355
|
+
if (meta?.requiresProject === false) {
|
|
356
|
+
context = {
|
|
357
|
+
projectId: '',
|
|
358
|
+
projectPath,
|
|
359
|
+
globalPath: '',
|
|
360
|
+
timestamp: getTimestamp(),
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
try {
|
|
364
|
+
context = await this.buildContext(projectPath)
|
|
365
|
+
} catch (error) {
|
|
366
|
+
return {
|
|
367
|
+
success: false,
|
|
368
|
+
error: (error as Error).message,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check class-based handlers first
|
|
374
|
+
const handler = this.handlers.get(name)
|
|
375
|
+
if (handler) {
|
|
376
|
+
return handler.execute(params, context)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Check function-based handlers
|
|
380
|
+
const handlerFn = this.handlerFns.get(name)
|
|
381
|
+
if (handlerFn) {
|
|
382
|
+
return handlerFn(params, context)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
success: false,
|
|
387
|
+
error: `Command not found: ${name}`,
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Execute without requiring project (for init, setup commands)
|
|
393
|
+
* @deprecated Use execute() - it auto-detects based on command metadata
|
|
394
|
+
*/
|
|
395
|
+
async executeWithoutProject<TParams = void>(
|
|
396
|
+
name: string,
|
|
397
|
+
params: TParams,
|
|
398
|
+
projectPath: string = process.cwd()
|
|
399
|
+
): Promise<CommandResult> {
|
|
400
|
+
const handler = this.handlers.get(name)
|
|
401
|
+
if (handler) {
|
|
402
|
+
const context: ExecutionContext = {
|
|
403
|
+
projectId: '',
|
|
404
|
+
projectPath,
|
|
405
|
+
globalPath: '',
|
|
406
|
+
timestamp: getTimestamp(),
|
|
407
|
+
}
|
|
408
|
+
return handler.execute(params, context)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const handlerFn = this.handlerFns.get(name)
|
|
412
|
+
if (handlerFn) {
|
|
413
|
+
const context: ExecutionContext = {
|
|
414
|
+
projectId: '',
|
|
415
|
+
projectPath,
|
|
416
|
+
globalPath: '',
|
|
417
|
+
timestamp: getTimestamp(),
|
|
418
|
+
}
|
|
419
|
+
return handlerFn(params, context)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
success: false,
|
|
424
|
+
error: `Command not found: ${name}`,
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Clear all registrations (useful for testing)
|
|
430
|
+
*/
|
|
431
|
+
clear(): void {
|
|
432
|
+
this.handlers.clear()
|
|
433
|
+
this.handlerFns.clear()
|
|
434
|
+
this.metadata.clear()
|
|
435
|
+
this.categories.clear()
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Singleton instance
|
|
440
|
+
export const commandRegistry = new CommandRegistry()
|
|
441
|
+
export default commandRegistry
|
package/core/commands/setup.ts
CHANGED
|
@@ -8,7 +8,7 @@ import os from 'os'
|
|
|
8
8
|
import chalk from 'chalk'
|
|
9
9
|
|
|
10
10
|
import commandInstaller from '../infrastructure/command-installer'
|
|
11
|
-
import type { CommandResult, SetupOptions } from '
|
|
11
|
+
import type { CommandResult, SetupOptions, MigrateOptions } from '../types'
|
|
12
12
|
import { PrjctCommandsBase } from './base'
|
|
13
13
|
import { VERSION } from '../utils/version'
|
|
14
14
|
|
|
@@ -41,11 +41,11 @@ export class SetupCommands extends PrjctCommandsBase {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
console.log(`\n✅ Installed ${result.installed
|
|
44
|
+
console.log(`\n✅ Installed ${result.installed?.length ?? 0} commands to:\n ${result.path}`)
|
|
45
45
|
|
|
46
|
-
if (result.errors
|
|
47
|
-
console.log(`\n⚠️ ${result.errors
|
|
48
|
-
result.errors
|
|
46
|
+
if ((result.errors?.length ?? 0) > 0) {
|
|
47
|
+
console.log(`\n⚠️ ${result.errors?.length ?? 0} errors:`)
|
|
48
|
+
result.errors?.forEach((e: { file: string; error: string }) => console.log(` - ${e.file}: ${e.error}`))
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
console.log('\n🎉 Setup complete!')
|
|
@@ -81,11 +81,11 @@ export class SetupCommands extends PrjctCommandsBase {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
console.log(`\n✅ Installed ${result.installed
|
|
84
|
+
console.log(`\n✅ Installed ${result.installed?.length ?? 0} commands`)
|
|
85
85
|
|
|
86
|
-
if (result.errors
|
|
87
|
-
console.log(`\n⚠️ ${result.errors
|
|
88
|
-
result.errors
|
|
86
|
+
if ((result.errors?.length ?? 0) > 0) {
|
|
87
|
+
console.log(`\n⚠️ ${result.errors?.length ?? 0} errors:`)
|
|
88
|
+
result.errors?.forEach((e: { file: string; error: string }) => console.log(` - ${e.file}: ${e.error}`))
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
console.log('\n📝 Installing global configuration...')
|
|
@@ -219,4 +219,20 @@ fi
|
|
|
219
219
|
console.log('')
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Migrate all projects to UUID format
|
|
224
|
+
*/
|
|
225
|
+
async migrateAll(_options: MigrateOptions = {}): Promise<CommandResult> {
|
|
226
|
+
console.log('🔄 Migrating all projects to UUID format...\n')
|
|
227
|
+
|
|
228
|
+
// TODO: Implement full migration logic
|
|
229
|
+
// For now, return success as this is a stub
|
|
230
|
+
console.log('✅ Migration complete (no projects needed migration)\n')
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
message: 'Migration complete',
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
222
238
|
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import path from 'path'
|
|
7
7
|
|
|
8
8
|
import memorySystem from '../agentic/memory-system'
|
|
9
|
-
import type { CommandResult } from '
|
|
9
|
+
import type { CommandResult } from '../types'
|
|
10
|
+
import { detectProjectCommands } from '../utils/project-commands'
|
|
10
11
|
import {
|
|
11
12
|
PrjctCommandsBase,
|
|
12
13
|
toolRegistry,
|
|
@@ -18,6 +19,38 @@ import {
|
|
|
18
19
|
import { stateStorage, shippedStorage } from '../storage'
|
|
19
20
|
|
|
20
21
|
export class ShippingCommands extends PrjctCommandsBase {
|
|
22
|
+
/**
|
|
23
|
+
* Run a command and capture exit code without throwing.
|
|
24
|
+
*
|
|
25
|
+
* Reason: `toolRegistry.Bash` swallows non-zero exits into stderr; we still want a reliable success flag.
|
|
26
|
+
*/
|
|
27
|
+
private async _runWithExitCode(command: string): Promise<{ exitCode: number; output: string }> {
|
|
28
|
+
const bash = toolRegistry.get('Bash')!
|
|
29
|
+
const escaped = command.replace(/"/g, '\\"')
|
|
30
|
+
const wrapped = `bash -lc "set +e; ${escaped} 2>&1; echo __EXIT:$?"`
|
|
31
|
+
const result = (await bash(wrapped)) as { stdout: string; stderr: string }
|
|
32
|
+
const output = `${result.stdout}\n${result.stderr}`.trim()
|
|
33
|
+
|
|
34
|
+
const lines = output.split('\n')
|
|
35
|
+
let marker: string | undefined
|
|
36
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
37
|
+
if (lines[i].startsWith('__EXIT:')) {
|
|
38
|
+
marker = lines[i]
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const exitCode = marker ? Number(marker.replace('__EXIT:', '').trim()) : 1
|
|
43
|
+
|
|
44
|
+
// Remove marker from output for cleaner logs
|
|
45
|
+
const cleaned = output
|
|
46
|
+
.split('\n')
|
|
47
|
+
.filter((line) => !line.startsWith('__EXIT:'))
|
|
48
|
+
.join('\n')
|
|
49
|
+
.trim()
|
|
50
|
+
|
|
51
|
+
return { exitCode: Number.isFinite(exitCode) ? exitCode : 1, output: cleaned }
|
|
52
|
+
}
|
|
53
|
+
|
|
21
54
|
/**
|
|
22
55
|
* /p:ship - Ship feature with complete automated workflow
|
|
23
56
|
*/
|
|
@@ -93,26 +126,30 @@ export class ShippingCommands extends PrjctCommandsBase {
|
|
|
93
126
|
/**
|
|
94
127
|
* Run lint checks
|
|
95
128
|
*/
|
|
96
|
-
async _runLint(
|
|
129
|
+
async _runLint(projectPath: string): Promise<{ success: boolean; message: string }> {
|
|
97
130
|
try {
|
|
98
|
-
const
|
|
99
|
-
return { success:
|
|
131
|
+
const detected = await detectProjectCommands(projectPath)
|
|
132
|
+
if (!detected.lint) return { success: true, message: 'skipped (no lint detected)' }
|
|
133
|
+
|
|
134
|
+
const { exitCode } = await this._runWithExitCode(detected.lint.command)
|
|
135
|
+
return { success: exitCode === 0, message: exitCode === 0 ? 'passed' : 'failed' }
|
|
100
136
|
} catch {
|
|
101
|
-
return { success:
|
|
137
|
+
return { success: true, message: 'skipped (lint detection failed)' }
|
|
102
138
|
}
|
|
103
139
|
}
|
|
104
140
|
|
|
105
141
|
/**
|
|
106
142
|
* Run tests
|
|
107
143
|
*/
|
|
108
|
-
async _runTests(
|
|
144
|
+
async _runTests(projectPath: string): Promise<{ success: boolean; message: string }> {
|
|
109
145
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
const detected = await detectProjectCommands(projectPath)
|
|
147
|
+
if (!detected.test) return { success: true, message: 'skipped (no tests detected)' }
|
|
148
|
+
|
|
149
|
+
const { exitCode } = await this._runWithExitCode(detected.test.command)
|
|
150
|
+
return { success: exitCode === 0, message: exitCode === 0 ? 'passed' : 'failed' }
|
|
114
151
|
} catch {
|
|
115
|
-
return { success:
|
|
152
|
+
return { success: true, message: 'skipped (test detection failed)' }
|
|
116
153
|
}
|
|
117
154
|
}
|
|
118
155
|
|