prjct-cli 0.46.0 → 0.48.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 +49 -3787
- package/bin/prjct.ts +3 -47
- package/core/agentic/command-executor.ts +8 -1
- package/core/agentic/context-builder.ts +4 -4
- package/core/agentic/memory-system.ts +3 -1
- package/core/agentic/prompt-builder.ts +6 -2
- package/core/agentic/smart-context.ts +2 -2
- package/core/ai-tools/generator.ts +11 -1
- package/core/cli/linear.ts +5 -4
- package/core/commands/analytics.ts +3 -1
- package/core/commands/command-data.ts +16 -0
- package/core/commands/commands.ts +8 -1
- package/core/commands/register.ts +1 -0
- package/core/commands/registry.ts +3 -2
- package/core/commands/setup.ts +4 -4
- package/core/commands/shipping.ts +26 -3
- package/core/commands/workflow.ts +105 -2
- package/core/constants/index.ts +3 -1
- package/core/context-tools/imports-tool.ts +1 -1
- package/core/context-tools/signatures-tool.ts +1 -1
- package/core/plugin/hooks.ts +1 -1
- package/core/services/agent-generator.ts +58 -6
- package/core/services/context-generator.ts +35 -15
- package/core/utils/help.ts +320 -0
- package/core/utils/markdown-builder.ts +9 -3
- package/core/utils/preserve-sections.ts +1 -1
- package/core/utils/project-commands.ts +0 -6
- package/core/utils/subtask-table.ts +234 -0
- package/core/workflow/index.ts +1 -0
- package/core/workflow/workflow-preferences.ts +312 -0
- package/dist/bin/prjct.mjs +4540 -3902
- package/package.json +1 -1
- package/templates/commands/workflow.md +150 -0
- package/templates/global/CLAUDE.md +27 -0
package/bin/prjct.ts
CHANGED
|
@@ -184,53 +184,9 @@ if (args[0] === 'start' || args[0] === 'setup') {
|
|
|
184
184
|
}
|
|
185
185
|
} else if (args[0] === 'help' || args[0] === '-h' || args[0] === '--help') {
|
|
186
186
|
// Show help - bypass setup check to always show help
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
QUICK START
|
|
192
|
-
-----------
|
|
193
|
-
Claude/Gemini:
|
|
194
|
-
1. prjct start Configure your AI provider
|
|
195
|
-
2. cd my-project && prjct init
|
|
196
|
-
3. Open in Claude Code or Gemini CLI
|
|
197
|
-
4. Type: p. sync Analyze project
|
|
198
|
-
|
|
199
|
-
Cursor IDE:
|
|
200
|
-
1. cd my-project && prjct init
|
|
201
|
-
2. Open in Cursor
|
|
202
|
-
3. Type: /sync Analyze project
|
|
203
|
-
|
|
204
|
-
COMMANDS (inside your AI agent)
|
|
205
|
-
-------------------------------
|
|
206
|
-
Claude/Gemini Cursor Description
|
|
207
|
-
─────────────────────────────────────────────────────
|
|
208
|
-
p. sync /sync Analyze project
|
|
209
|
-
p. task "desc" /task "desc" Start a task
|
|
210
|
-
p. done /done Complete subtask
|
|
211
|
-
p. ship "name" /ship "name" Ship with PR
|
|
212
|
-
|
|
213
|
-
TERMINAL COMMANDS (this CLI)
|
|
214
|
-
----------------------------
|
|
215
|
-
prjct start First-time setup (Claude/Gemini global config)
|
|
216
|
-
prjct init Initialize project (required for Cursor)
|
|
217
|
-
prjct setup Reconfigure installations
|
|
218
|
-
prjct sync Sync project state
|
|
219
|
-
prjct watch Auto-sync on file changes (Ctrl+C to stop)
|
|
220
|
-
prjct doctor Check system health and dependencies
|
|
221
|
-
prjct uninstall Complete system removal of prjct
|
|
222
|
-
|
|
223
|
-
FLAGS
|
|
224
|
-
-----
|
|
225
|
-
--quiet, -q Suppress all output (only errors to stderr)
|
|
226
|
-
--version, -v Show version
|
|
227
|
-
--help, -h Show this help
|
|
228
|
-
|
|
229
|
-
MORE INFO
|
|
230
|
-
---------
|
|
231
|
-
Documentation: https://prjct.app
|
|
232
|
-
GitHub: https://github.com/jlopezlira/prjct-cli
|
|
233
|
-
`)
|
|
187
|
+
const { getHelp } = await import('../core/utils/help')
|
|
188
|
+
const topic = args[1] // Optional: prjct help <command>
|
|
189
|
+
console.log(getHelp(topic))
|
|
234
190
|
process.exitCode = 0
|
|
235
191
|
} else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
|
|
236
192
|
// Show version with provider status
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
SimpleExecutionResult,
|
|
19
19
|
} from '../types'
|
|
20
20
|
import { agentStream } from '../utils/agent-stream'
|
|
21
|
+
import { printSubtaskProgress, type SubtaskDisplay } from '../utils/subtask-table'
|
|
21
22
|
import chainOfThought from './chain-of-thought'
|
|
22
23
|
import contextBuilder from './context-builder'
|
|
23
24
|
import groundTruth from './ground-truth'
|
|
@@ -192,7 +193,13 @@ export class CommandExecutor {
|
|
|
192
193
|
|
|
193
194
|
// Show subtasks if fragmented
|
|
194
195
|
if (orchestratorContext.requiresFragmentation && orchestratorContext.subtasks) {
|
|
195
|
-
|
|
196
|
+
const subtaskDisplay: SubtaskDisplay[] = orchestratorContext.subtasks.map((s) => ({
|
|
197
|
+
id: s.id,
|
|
198
|
+
domain: s.domain,
|
|
199
|
+
description: s.description,
|
|
200
|
+
status: s.status,
|
|
201
|
+
}))
|
|
202
|
+
printSubtaskProgress(subtaskDisplay)
|
|
196
203
|
}
|
|
197
204
|
} catch (error) {
|
|
198
205
|
// Orchestration failed - log warning but continue without it
|
|
@@ -15,10 +15,10 @@ import { isNotFoundError } from '../types/fs'
|
|
|
15
15
|
// Re-export types for convenience
|
|
16
16
|
export type { ContextPaths, ContextState, ProjectContext } from '../types'
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
type Paths = ContextPaths
|
|
20
|
-
type Context = ProjectContext
|
|
21
|
-
type State = ContextState
|
|
18
|
+
// Type aliases exported for backward compatibility (used by external consumers)
|
|
19
|
+
export type Paths = ContextPaths
|
|
20
|
+
export type Context = ProjectContext
|
|
21
|
+
export type State = ContextState
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Builds and caches project context for Claude decisions.
|
|
@@ -574,7 +574,9 @@ export class SemanticMemories extends CachedStore<MemoryDatabase> {
|
|
|
574
574
|
const matchingIds = new Set<string>()
|
|
575
575
|
for (const tag of parsedTags) {
|
|
576
576
|
const ids = db.index[tag]
|
|
577
|
-
|
|
577
|
+
for (const id of ids) {
|
|
578
|
+
matchingIds.add(id)
|
|
579
|
+
}
|
|
578
580
|
}
|
|
579
581
|
return db.memories.filter((m) => matchingIds.has(m.id))
|
|
580
582
|
}
|
|
@@ -502,10 +502,14 @@ class PromptBuilder {
|
|
|
502
502
|
parts.push('\n## THINK FIRST (reasoning from analysis)\n')
|
|
503
503
|
if (thinkBlock.conclusions && thinkBlock.conclusions.length > 0) {
|
|
504
504
|
parts.push('Conclusions:\n')
|
|
505
|
-
|
|
505
|
+
for (const c of thinkBlock.conclusions) {
|
|
506
|
+
parts.push(` → ${c}\n`)
|
|
507
|
+
}
|
|
506
508
|
}
|
|
507
509
|
parts.push('Plan:\n')
|
|
508
|
-
|
|
510
|
+
for (let i = 0; i < thinkBlock.plan.length; i++) {
|
|
511
|
+
parts.push(` ${i + 1}. ${thinkBlock.plan[i]}\n`)
|
|
512
|
+
}
|
|
509
513
|
parts.push(`Confidence: ${Math.round((thinkBlock.confidence || 0.5) * 100)}%\n`)
|
|
510
514
|
}
|
|
511
515
|
|
|
@@ -32,8 +32,8 @@ export type {
|
|
|
32
32
|
StackInfo,
|
|
33
33
|
} from '../types'
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
type ProjectState = SmartContextProjectState
|
|
35
|
+
// Type alias exported for backward compatibility (used by external consumers)
|
|
36
|
+
export type ProjectState = SmartContextProjectState
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* SmartContext - Intelligent context filtering.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import fs from 'node:fs/promises'
|
|
9
9
|
import path from 'node:path'
|
|
10
|
-
import { mergePreservedSections } from '../utils/preserve-sections'
|
|
10
|
+
import { mergePreservedSections, validatePreserveBlocks } from '../utils/preserve-sections'
|
|
11
11
|
import { getFormatter, type ProjectContext } from './formatters'
|
|
12
12
|
import { AI_TOOLS, type AIToolConfig, DEFAULT_AI_TOOLS, getAIToolConfig } from './registry'
|
|
13
13
|
|
|
@@ -88,6 +88,16 @@ async function generateForTool(
|
|
|
88
88
|
// Read existing file to preserve user customizations
|
|
89
89
|
try {
|
|
90
90
|
const existingContent = await fs.readFile(outputPath, 'utf-8')
|
|
91
|
+
|
|
92
|
+
// Validate existing preserved blocks
|
|
93
|
+
const validation = validatePreserveBlocks(existingContent)
|
|
94
|
+
if (!validation.valid) {
|
|
95
|
+
console.warn(`⚠️ ${config.outputFile} has invalid preserve blocks:`)
|
|
96
|
+
for (const error of validation.errors) {
|
|
97
|
+
console.warn(` ${error}`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
content = mergePreservedSections(content, existingContent)
|
|
92
102
|
} catch {
|
|
93
103
|
// File doesn't exist yet - use generated content as-is
|
package/core/cli/linear.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* All output is JSON for easy parsing by Claude.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
import type { CreateIssueInput, Issue } from '../integrations/issue-tracker/types'
|
|
27
28
|
import { linearService, linearSync } from '../integrations/linear'
|
|
28
29
|
import {
|
|
29
30
|
getCredentialSource,
|
|
@@ -140,7 +141,7 @@ async function main(): Promise<void> {
|
|
|
140
141
|
|
|
141
142
|
// Use team issues if teamId is configured, otherwise assigned issues
|
|
142
143
|
const creds = await getProjectCredentials(projectId!)
|
|
143
|
-
let issues
|
|
144
|
+
let issues: Issue[]
|
|
144
145
|
if (creds.linear?.teamId) {
|
|
145
146
|
issues = await linearService.fetchTeamIssues(creds.linear.teamId, { limit })
|
|
146
147
|
} else {
|
|
@@ -254,7 +255,7 @@ async function main(): Promise<void> {
|
|
|
254
255
|
error('JSON input required. Usage: create \'{"title":"...", "teamId":"..."}\'')
|
|
255
256
|
}
|
|
256
257
|
|
|
257
|
-
let input
|
|
258
|
+
let input: Record<string, unknown>
|
|
258
259
|
try {
|
|
259
260
|
input = JSON.parse(inputJson)
|
|
260
261
|
} catch {
|
|
@@ -274,7 +275,7 @@ async function main(): Promise<void> {
|
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
277
|
|
|
277
|
-
const issue = await linearService.createIssue(input)
|
|
278
|
+
const issue = await linearService.createIssue(input as unknown as CreateIssueInput)
|
|
278
279
|
output(issue)
|
|
279
280
|
break
|
|
280
281
|
}
|
|
@@ -291,7 +292,7 @@ async function main(): Promise<void> {
|
|
|
291
292
|
error('JSON input required. Usage: update <id> \'{"description":"..."}\'')
|
|
292
293
|
}
|
|
293
294
|
|
|
294
|
-
let input
|
|
295
|
+
let input: Record<string, unknown>
|
|
295
296
|
try {
|
|
296
297
|
input = JSON.parse(inputJson)
|
|
297
298
|
} catch {
|
|
@@ -256,7 +256,9 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
256
256
|
|
|
257
257
|
if (command.features) {
|
|
258
258
|
console.log('\nFeatures:')
|
|
259
|
-
|
|
259
|
+
for (const f of command.features) {
|
|
260
|
+
console.log(` • ${f}`)
|
|
261
|
+
}
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
console.log(`\n${'═'.repeat(50)}\n`)
|
|
@@ -285,6 +285,22 @@ export const COMMANDS: CommandMeta[] = [
|
|
|
285
285
|
requiresProject: true,
|
|
286
286
|
isOptional: true,
|
|
287
287
|
},
|
|
288
|
+
{
|
|
289
|
+
name: 'workflow',
|
|
290
|
+
group: 'optional',
|
|
291
|
+
description: 'Configure workflow hooks via natural language',
|
|
292
|
+
usage: { claude: '/p:workflow ["config"]', terminal: 'prjct workflow ["config"]' },
|
|
293
|
+
params: '["natural language config"]',
|
|
294
|
+
implemented: true,
|
|
295
|
+
hasTemplate: true,
|
|
296
|
+
requiresProject: true,
|
|
297
|
+
isOptional: true,
|
|
298
|
+
features: [
|
|
299
|
+
'Natural language configuration',
|
|
300
|
+
'Before/after hooks for task, done, ship, sync',
|
|
301
|
+
'Permanent, session, or one-time preferences',
|
|
302
|
+
],
|
|
303
|
+
},
|
|
288
304
|
|
|
289
305
|
// ===== SETUP COMMANDS =====
|
|
290
306
|
{
|
|
@@ -90,6 +90,13 @@ class PrjctCommands {
|
|
|
90
90
|
return this.workflow.resume(taskId, projectPath)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
async workflowPrefs(
|
|
94
|
+
input: string | null = null,
|
|
95
|
+
projectPath: string = process.cwd()
|
|
96
|
+
): Promise<CommandResult> {
|
|
97
|
+
return this.workflow.workflow(input, projectPath)
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
// ========== Planning Commands ==========
|
|
94
101
|
|
|
95
102
|
async init(
|
|
@@ -213,7 +220,7 @@ class PrjctCommands {
|
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
showAsciiArt(): void {
|
|
216
|
-
|
|
223
|
+
this.setupCmds.showAsciiArt()
|
|
217
224
|
}
|
|
218
225
|
|
|
219
226
|
// ========== Delegated Base Methods ==========
|
|
@@ -58,6 +58,7 @@ export function registerAllCommands(): void {
|
|
|
58
58
|
commandRegistry.registerMethod('next', workflow, 'next', getMeta('next'))
|
|
59
59
|
commandRegistry.registerMethod('pause', workflow, 'pause', getMeta('pause'))
|
|
60
60
|
commandRegistry.registerMethod('resume', workflow, 'resume', getMeta('resume'))
|
|
61
|
+
commandRegistry.registerMethod('workflow', workflow, 'workflow', getMeta('workflow'))
|
|
61
62
|
|
|
62
63
|
// Planning commands
|
|
63
64
|
commandRegistry.registerMethod('init', planning, 'init', getMeta('init'))
|
|
@@ -117,10 +117,11 @@ export class CommandRegistry {
|
|
|
117
117
|
const wrapper: HandlerFn<unknown> = async (params, context) => {
|
|
118
118
|
// Legacy commands expect (param?, projectPath) signature
|
|
119
119
|
// Most commands use first param + projectPath
|
|
120
|
+
type LegacyMethod = (...args: unknown[]) => Promise<CommandResult>
|
|
120
121
|
if (params !== undefined && params !== null) {
|
|
121
|
-
return (method as
|
|
122
|
+
return (method as LegacyMethod).call(instance, params, context.projectPath)
|
|
122
123
|
}
|
|
123
|
-
return (method as
|
|
124
|
+
return (method as LegacyMethod).call(instance, context.projectPath)
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
this.handlerFns.set(name, wrapper)
|
package/core/commands/setup.ts
CHANGED
|
@@ -50,9 +50,9 @@ export class SetupCommands extends PrjctCommandsBase {
|
|
|
50
50
|
|
|
51
51
|
if ((result.errors?.length ?? 0) > 0) {
|
|
52
52
|
console.log(`\n⚠️ ${result.errors?.length ?? 0} errors:`)
|
|
53
|
-
|
|
53
|
+
for (const e of result.errors ?? []) {
|
|
54
54
|
console.log(` - ${e.file}: ${e.error}`)
|
|
55
|
-
|
|
55
|
+
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
console.log('\n🎉 Setup complete!')
|
|
@@ -92,9 +92,9 @@ export class SetupCommands extends PrjctCommandsBase {
|
|
|
92
92
|
|
|
93
93
|
if ((result.errors?.length ?? 0) > 0) {
|
|
94
94
|
console.log(`\n⚠️ ${result.errors?.length ?? 0} errors:`)
|
|
95
|
-
|
|
95
|
+
for (const e of result.errors ?? []) {
|
|
96
96
|
console.log(` - ${e.file}: ${e.error}`)
|
|
97
|
-
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
console.log('\n📝 Installing global configuration...')
|
|
@@ -10,6 +10,7 @@ import type { CommandResult } from '../types'
|
|
|
10
10
|
import { isNotFoundError } from '../types/fs'
|
|
11
11
|
import { showNextSteps } from '../utils/next-steps'
|
|
12
12
|
import { detectProjectCommands } from '../utils/project-commands'
|
|
13
|
+
import { runWorkflowHooks } from '../workflow/workflow-preferences'
|
|
13
14
|
import { configManager, dateHelper, fileHelper, out, PrjctCommandsBase, toolRegistry } from './base'
|
|
14
15
|
|
|
15
16
|
export class ShippingCommands extends PrjctCommandsBase {
|
|
@@ -48,13 +49,20 @@ export class ShippingCommands extends PrjctCommandsBase {
|
|
|
48
49
|
/**
|
|
49
50
|
* /p:ship - Ship feature with complete automated workflow
|
|
50
51
|
*/
|
|
51
|
-
async ship(
|
|
52
|
+
async ship(
|
|
53
|
+
feature: string | null,
|
|
54
|
+
projectPath: string = process.cwd(),
|
|
55
|
+
options: { skipHooks?: boolean } = {}
|
|
56
|
+
): Promise<CommandResult> {
|
|
52
57
|
try {
|
|
53
58
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
54
59
|
if (!initResult.success) return initResult
|
|
55
60
|
|
|
56
|
-
const
|
|
57
|
-
|
|
61
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
62
|
+
if (!projectId) {
|
|
63
|
+
out.fail('no project ID')
|
|
64
|
+
return { success: false, error: 'No project ID found' }
|
|
65
|
+
}
|
|
58
66
|
|
|
59
67
|
let featureName = feature
|
|
60
68
|
if (!featureName) {
|
|
@@ -63,6 +71,15 @@ export class ShippingCommands extends PrjctCommandsBase {
|
|
|
63
71
|
featureName = currentTask?.description || 'current work'
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
// Run before_ship hooks (using memory-based preferences)
|
|
75
|
+
const beforeResult = await runWorkflowHooks(projectId, 'before', 'ship', {
|
|
76
|
+
projectPath,
|
|
77
|
+
skipHooks: options.skipHooks,
|
|
78
|
+
})
|
|
79
|
+
if (!beforeResult.success) {
|
|
80
|
+
return { success: false, error: `Hook failed: ${beforeResult.failed}` }
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
// Ship steps with progress indicator
|
|
67
84
|
out.step(1, 5, `Linting ${featureName}...`)
|
|
68
85
|
const lintResult = await this._runLint(projectPath)
|
|
@@ -108,6 +125,12 @@ export class ShippingCommands extends PrjctCommandsBase {
|
|
|
108
125
|
})
|
|
109
126
|
}
|
|
110
127
|
|
|
128
|
+
// Run after_ship hooks
|
|
129
|
+
await runWorkflowHooks(projectId, 'after', 'ship', {
|
|
130
|
+
projectPath,
|
|
131
|
+
skipHooks: options.skipHooks,
|
|
132
|
+
})
|
|
133
|
+
|
|
111
134
|
out.done(`v${newVersion} shipped`)
|
|
112
135
|
showNextSteps('ship')
|
|
113
136
|
|
|
@@ -16,6 +16,16 @@ import { queueStorage, stateStorage } from '../storage'
|
|
|
16
16
|
import type { CommandResult } from '../types'
|
|
17
17
|
import { showNextSteps, showStateInfo } from '../utils/next-steps'
|
|
18
18
|
import { getLinearApiKey, getProjectCredentials } from '../utils/project-credentials'
|
|
19
|
+
import {
|
|
20
|
+
formatWorkflowPreferences,
|
|
21
|
+
type HookCommand,
|
|
22
|
+
type HookPhase,
|
|
23
|
+
listWorkflowPreferences,
|
|
24
|
+
type PreferenceScope,
|
|
25
|
+
removeWorkflowPreference,
|
|
26
|
+
runWorkflowHooks,
|
|
27
|
+
setWorkflowPreference,
|
|
28
|
+
} from '../workflow/workflow-preferences'
|
|
19
29
|
import { configManager, dateHelper, out, PrjctCommandsBase } from './base'
|
|
20
30
|
|
|
21
31
|
export class WorkflowCommands extends PrjctCommandsBase {
|
|
@@ -24,7 +34,8 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
24
34
|
*/
|
|
25
35
|
async now(
|
|
26
36
|
task: string | null = null,
|
|
27
|
-
projectPath: string = process.cwd()
|
|
37
|
+
projectPath: string = process.cwd(),
|
|
38
|
+
options: { skipHooks?: boolean } = {}
|
|
28
39
|
): Promise<CommandResult> {
|
|
29
40
|
try {
|
|
30
41
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
@@ -37,6 +48,15 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
if (task) {
|
|
51
|
+
// Run before_task hooks (using memory-based preferences)
|
|
52
|
+
const beforeResult = await runWorkflowHooks(projectId, 'before', 'task', {
|
|
53
|
+
projectPath,
|
|
54
|
+
skipHooks: options.skipHooks,
|
|
55
|
+
})
|
|
56
|
+
if (!beforeResult.success) {
|
|
57
|
+
return { success: false, error: `Hook failed: ${beforeResult.failed}` }
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
// AGENTIC: Use CommandExecutor for full orchestration support
|
|
41
61
|
const result = await commandExecutor.execute('task', { task }, projectPath)
|
|
42
62
|
|
|
@@ -97,6 +117,12 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
97
117
|
timestamp: dateHelper.getTimestamp(),
|
|
98
118
|
})
|
|
99
119
|
|
|
120
|
+
// Run after_task hooks
|
|
121
|
+
await runWorkflowHooks(projectId, 'after', 'task', {
|
|
122
|
+
projectPath,
|
|
123
|
+
skipHooks: options.skipHooks,
|
|
124
|
+
})
|
|
125
|
+
|
|
100
126
|
return {
|
|
101
127
|
// Include full CommandExecutor result first (orchestratorContext, prompt, etc.)
|
|
102
128
|
...result,
|
|
@@ -127,7 +153,10 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
127
153
|
/**
|
|
128
154
|
* /p:done - Complete current task
|
|
129
155
|
*/
|
|
130
|
-
async done(
|
|
156
|
+
async done(
|
|
157
|
+
projectPath: string = process.cwd(),
|
|
158
|
+
options: { skipHooks?: boolean } = {}
|
|
159
|
+
): Promise<CommandResult> {
|
|
131
160
|
try {
|
|
132
161
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
133
162
|
if (!initResult.success) return initResult
|
|
@@ -146,6 +175,15 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
146
175
|
return { success: true, message: 'No active task to complete' }
|
|
147
176
|
}
|
|
148
177
|
|
|
178
|
+
// Run before_done hooks (using memory-based preferences)
|
|
179
|
+
const beforeResult = await runWorkflowHooks(projectId, 'before', 'done', {
|
|
180
|
+
projectPath,
|
|
181
|
+
skipHooks: options.skipHooks,
|
|
182
|
+
})
|
|
183
|
+
if (!beforeResult.success) {
|
|
184
|
+
return { success: false, error: `Hook failed: ${beforeResult.failed}` }
|
|
185
|
+
}
|
|
186
|
+
|
|
149
187
|
const task = currentTask.description
|
|
150
188
|
let duration = ''
|
|
151
189
|
if (currentTask.startedAt) {
|
|
@@ -184,6 +222,13 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
184
222
|
duration,
|
|
185
223
|
timestamp: dateHelper.getTimestamp(),
|
|
186
224
|
})
|
|
225
|
+
|
|
226
|
+
// Run after_done hooks
|
|
227
|
+
await runWorkflowHooks(projectId, 'after', 'done', {
|
|
228
|
+
projectPath,
|
|
229
|
+
skipHooks: options.skipHooks,
|
|
230
|
+
})
|
|
231
|
+
|
|
187
232
|
return { success: true, task, duration }
|
|
188
233
|
} catch (error) {
|
|
189
234
|
out.fail((error as Error).message)
|
|
@@ -312,4 +357,62 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
312
357
|
return { success: false, error: (error as Error).message }
|
|
313
358
|
}
|
|
314
359
|
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* /p:workflow - View and manage workflow preferences
|
|
363
|
+
*
|
|
364
|
+
* When called without arguments, shows current preferences.
|
|
365
|
+
* With arguments, parses natural language and updates preferences.
|
|
366
|
+
*/
|
|
367
|
+
async workflow(
|
|
368
|
+
input: string | null = null,
|
|
369
|
+
projectPath: string = process.cwd()
|
|
370
|
+
): Promise<CommandResult> {
|
|
371
|
+
try {
|
|
372
|
+
const initResult = await this.ensureProjectInit(projectPath)
|
|
373
|
+
if (!initResult.success) return initResult
|
|
374
|
+
|
|
375
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
376
|
+
if (!projectId) {
|
|
377
|
+
out.fail('no project ID')
|
|
378
|
+
return { success: false, error: 'No project ID found' }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!input) {
|
|
382
|
+
// Show current preferences
|
|
383
|
+
const preferences = await listWorkflowPreferences(projectId)
|
|
384
|
+
console.log(formatWorkflowPreferences(preferences))
|
|
385
|
+
return { success: true, preferences }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Return info for template-based processing
|
|
389
|
+
// The template/LLM will parse the natural language and call the appropriate functions
|
|
390
|
+
return {
|
|
391
|
+
success: true,
|
|
392
|
+
projectId,
|
|
393
|
+
input,
|
|
394
|
+
// Export functions for template use
|
|
395
|
+
setWorkflowPreference: async (pref: {
|
|
396
|
+
hook: HookPhase
|
|
397
|
+
command: HookCommand
|
|
398
|
+
action: string
|
|
399
|
+
scope: PreferenceScope
|
|
400
|
+
}) => {
|
|
401
|
+
await setWorkflowPreference(projectId, {
|
|
402
|
+
...pref,
|
|
403
|
+
createdAt: dateHelper.getTimestamp(),
|
|
404
|
+
})
|
|
405
|
+
},
|
|
406
|
+
removeWorkflowPreference: async (hook: HookPhase, command: HookCommand) => {
|
|
407
|
+
await removeWorkflowPreference(projectId, hook, command)
|
|
408
|
+
},
|
|
409
|
+
listWorkflowPreferences: async () => {
|
|
410
|
+
return listWorkflowPreferences(projectId)
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
out.fail((error as Error).message)
|
|
415
|
+
return { success: false, error: (error as Error).message }
|
|
416
|
+
}
|
|
417
|
+
}
|
|
315
418
|
}
|
package/core/constants/index.ts
CHANGED
|
@@ -108,7 +108,9 @@ export const ROADMAP = {
|
|
|
108
108
|
const lines = [`## ${feature}`, '', `Status: ${ROADMAP_STATUS[status]}`]
|
|
109
109
|
if (tasks && tasks.length > 0) {
|
|
110
110
|
lines.push('', '### Tasks', '')
|
|
111
|
-
|
|
111
|
+
for (const task of tasks) {
|
|
112
|
+
lines.push(`- [ ] ${task}`)
|
|
113
|
+
}
|
|
112
114
|
}
|
|
113
115
|
return `${lines.join('\n')}\n\n`
|
|
114
116
|
},
|
|
@@ -229,7 +229,7 @@ function extractImports(
|
|
|
229
229
|
for (const patternDef of patterns) {
|
|
230
230
|
patternDef.pattern.lastIndex = 0
|
|
231
231
|
|
|
232
|
-
let match
|
|
232
|
+
let match: RegExpExecArray | null
|
|
233
233
|
while ((match = patternDef.pattern.exec(content)) !== null) {
|
|
234
234
|
const source = match[patternDef.sourceIndex]
|
|
235
235
|
if (!source || seen.has(source)) continue
|
|
@@ -439,7 +439,7 @@ function extractFromContent(content: string, patterns: ExtractionPattern[]): Cod
|
|
|
439
439
|
// Reset lastIndex for global regex
|
|
440
440
|
patternDef.pattern.lastIndex = 0
|
|
441
441
|
|
|
442
|
-
let match
|
|
442
|
+
let match: RegExpExecArray | null
|
|
443
443
|
while ((match = patternDef.pattern.exec(content)) !== null) {
|
|
444
444
|
const name = match[patternDef.nameIndex]
|
|
445
445
|
if (!name) continue
|
package/core/plugin/hooks.ts
CHANGED
|
@@ -40,7 +40,7 @@ const HookPoints = {
|
|
|
40
40
|
TRANSFORM_METRICS: 'transform:metrics',
|
|
41
41
|
} as const
|
|
42
42
|
|
|
43
|
-
type HookPoint = (typeof HookPoints)[keyof typeof HookPoints]
|
|
43
|
+
export type HookPoint = (typeof HookPoints)[keyof typeof HookPoints]
|
|
44
44
|
type HookHandler = (data: unknown, context?: unknown) => unknown | Promise<unknown>
|
|
45
45
|
|
|
46
46
|
interface HookEntry {
|