prjct-cli 0.20.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.
Files changed (226) hide show
  1. package/CHANGELOG.md +24 -6
  2. package/CLAUDE.md +56 -15
  3. package/README.md +5 -6
  4. package/bin/prjct +59 -42
  5. package/bin/prjct.ts +60 -0
  6. package/core/__tests__/agentic/memory-system.test.ts +18 -3
  7. package/core/__tests__/agentic/plan-mode.test.ts +55 -26
  8. package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
  9. package/core/__tests__/utils/project-commands.test.ts +72 -0
  10. package/core/agentic/agent-router.ts +3 -12
  11. package/core/agentic/command-executor.ts +372 -3
  12. package/core/agentic/context-builder.ts +7 -27
  13. package/core/agentic/ground-truth.ts +604 -5
  14. package/core/agentic/index.ts +180 -0
  15. package/core/agentic/loop-detector.ts +418 -4
  16. package/core/agentic/memory-system.ts +857 -3
  17. package/core/agentic/plan-mode.ts +491 -4
  18. package/core/agentic/prompt-builder.ts +44 -65
  19. package/core/agentic/services.ts +13 -5
  20. package/core/agentic/skill-loader.ts +112 -0
  21. package/core/agentic/smart-context.ts +37 -122
  22. package/core/agentic/template-loader.ts +79 -122
  23. package/core/agentic/tool-registry.ts +5 -11
  24. package/core/agents/index.ts +1 -1
  25. package/core/agents/performance.ts +4 -2
  26. package/core/bus/bus.ts +262 -0
  27. package/core/bus/index.ts +3 -313
  28. package/core/commands/analysis.ts +5 -5
  29. package/core/commands/analytics.ts +11 -11
  30. package/core/commands/base.ts +33 -209
  31. package/core/commands/cleanup.ts +148 -0
  32. package/core/commands/command-data.ts +346 -0
  33. package/core/commands/commands.ts +216 -0
  34. package/core/commands/design.ts +83 -0
  35. package/core/commands/index.ts +13 -207
  36. package/core/commands/maintenance.ts +52 -473
  37. package/core/commands/planning.ts +3 -3
  38. package/core/commands/register.ts +104 -0
  39. package/core/commands/registry.ts +441 -0
  40. package/core/commands/setup.ts +25 -9
  41. package/core/commands/shipping.ts +48 -11
  42. package/core/commands/snapshots.ts +299 -0
  43. package/core/commands/workflow.ts +2 -2
  44. package/core/constants/index.ts +254 -4
  45. package/core/domain/agent-loader.ts +5 -6
  46. package/core/domain/task-stack.ts +555 -4
  47. package/core/errors.ts +127 -1
  48. package/core/events/events.ts +87 -0
  49. package/core/events/index.ts +4 -138
  50. package/core/index.ts +15 -23
  51. package/core/infrastructure/agent-detector.ts +126 -201
  52. package/core/infrastructure/author-detector.ts +99 -171
  53. package/core/infrastructure/command-installer.ts +476 -4
  54. package/core/infrastructure/config-manager.ts +41 -37
  55. package/core/infrastructure/path-manager.ts +59 -9
  56. package/core/infrastructure/permission-manager.ts +286 -0
  57. package/core/outcomes/analyzer.ts +7 -41
  58. package/core/outcomes/index.ts +1 -1
  59. package/core/outcomes/recorder.ts +1 -1
  60. package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
  61. package/core/plugin/loader.ts +5 -5
  62. package/core/plugin/registry.ts +2 -2
  63. package/core/schemas/ideas.ts +85 -54
  64. package/core/schemas/index.ts +14 -33
  65. package/core/schemas/permissions.ts +177 -0
  66. package/core/schemas/project.ts +39 -12
  67. package/core/schemas/roadmap.ts +94 -59
  68. package/core/schemas/schemas.ts +39 -0
  69. package/core/schemas/shipped.ts +87 -60
  70. package/core/schemas/state.ts +110 -70
  71. package/core/server/index.ts +21 -0
  72. package/core/server/routes.ts +165 -0
  73. package/core/server/server.ts +136 -0
  74. package/core/server/sse.ts +135 -0
  75. package/core/services/agent-service.ts +170 -0
  76. package/core/services/breakdown-service.ts +126 -0
  77. package/core/services/index.ts +21 -0
  78. package/core/services/memory-service.ts +108 -0
  79. package/core/services/project-service.ts +146 -0
  80. package/core/services/skill-service.ts +253 -0
  81. package/core/session/compaction.ts +257 -0
  82. package/core/session/index.ts +20 -8
  83. package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
  84. package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
  85. package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
  86. package/core/session/utils.ts +1 -1
  87. package/core/storage/ideas-storage.ts +10 -26
  88. package/core/storage/index.ts +14 -162
  89. package/core/storage/queue-storage.ts +13 -11
  90. package/core/storage/shipped-storage.ts +4 -17
  91. package/core/storage/state-storage.ts +35 -43
  92. package/core/storage/storage-manager.ts +42 -52
  93. package/core/storage/storage.ts +160 -0
  94. package/core/sync/auth-config.ts +1 -8
  95. package/core/sync/index.ts +17 -10
  96. package/core/sync/oauth-handler.ts +1 -6
  97. package/core/sync/sync-client.ts +6 -34
  98. package/core/sync/sync-manager.ts +11 -40
  99. package/core/types/agentic.ts +577 -0
  100. package/core/types/agents.ts +145 -0
  101. package/core/types/bus.ts +82 -0
  102. package/core/types/commands.ts +366 -0
  103. package/core/types/config.ts +66 -0
  104. package/core/types/core.ts +96 -0
  105. package/core/types/domain.ts +71 -0
  106. package/core/types/events.ts +42 -0
  107. package/core/types/fs.ts +56 -0
  108. package/core/types/index.ts +387 -500
  109. package/core/types/infrastructure.ts +196 -0
  110. package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
  111. package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
  112. package/core/types/plugin.ts +25 -0
  113. package/core/types/server.ts +54 -0
  114. package/core/types/services.ts +65 -0
  115. package/core/types/session.ts +135 -0
  116. package/core/types/storage.ts +148 -0
  117. package/core/types/sync.ts +121 -0
  118. package/core/types/task.ts +72 -0
  119. package/core/types/template.ts +24 -0
  120. package/core/types/utils.ts +90 -0
  121. package/core/utils/cache.ts +195 -0
  122. package/core/utils/collection-filters.ts +245 -0
  123. package/core/utils/date-helper.ts +1 -5
  124. package/core/utils/file-helper.ts +20 -10
  125. package/core/utils/jsonl-helper.ts +5 -8
  126. package/core/utils/markdown-builder.ts +277 -0
  127. package/core/utils/project-commands.ts +132 -0
  128. package/core/utils/runtime.ts +119 -0
  129. package/dist/bin/prjct.mjs +12568 -0
  130. package/package.json +13 -8
  131. package/scripts/build.js +106 -0
  132. package/scripts/postinstall.js +50 -8
  133. package/templates/agentic/subagent-generation.md +1 -1
  134. package/templates/commands/serve.md +118 -0
  135. package/templates/commands/ship.md +13 -2
  136. package/templates/commands/skill.md +110 -0
  137. package/templates/commands/sync.md +1 -1
  138. package/templates/commands/test.md +23 -4
  139. package/templates/permissions/default.jsonc +60 -0
  140. package/templates/permissions/permissive.jsonc +49 -0
  141. package/templates/permissions/strict.jsonc +62 -0
  142. package/templates/skills/code-review.md +47 -0
  143. package/templates/skills/debug.md +61 -0
  144. package/templates/skills/refactor.md +47 -0
  145. package/templates/subagents/domain/devops.md +1 -1
  146. package/templates/subagents/domain/testing.md +6 -10
  147. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  148. package/templates/tools/bash.txt +22 -0
  149. package/templates/tools/edit.txt +18 -0
  150. package/templates/tools/glob.txt +19 -0
  151. package/templates/tools/grep.txt +21 -0
  152. package/templates/tools/read.txt +14 -0
  153. package/templates/tools/task.txt +20 -0
  154. package/templates/tools/webfetch.txt +16 -0
  155. package/templates/tools/websearch.txt +18 -0
  156. package/templates/tools/write.txt +17 -0
  157. package/core/agentic/command-executor/command-executor.ts +0 -312
  158. package/core/agentic/command-executor/index.ts +0 -16
  159. package/core/agentic/command-executor/status-signal.ts +0 -38
  160. package/core/agentic/command-executor/types.ts +0 -79
  161. package/core/agentic/ground-truth/index.ts +0 -76
  162. package/core/agentic/ground-truth/types.ts +0 -33
  163. package/core/agentic/ground-truth/utils.ts +0 -48
  164. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  165. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  166. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  167. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  168. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  169. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  170. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  171. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  172. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  173. package/core/agentic/ground-truth/verifiers.ts +0 -6
  174. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  175. package/core/agentic/loop-detector/hallucination.ts +0 -71
  176. package/core/agentic/loop-detector/index.ts +0 -41
  177. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  178. package/core/agentic/loop-detector/types.ts +0 -66
  179. package/core/agentic/memory-system/history.ts +0 -53
  180. package/core/agentic/memory-system/index.ts +0 -192
  181. package/core/agentic/memory-system/patterns.ts +0 -156
  182. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  183. package/core/agentic/memory-system/session.ts +0 -21
  184. package/core/agentic/plan-mode/approval.ts +0 -57
  185. package/core/agentic/plan-mode/constants.ts +0 -44
  186. package/core/agentic/plan-mode/index.ts +0 -28
  187. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  188. package/core/agentic/plan-mode/types.ts +0 -193
  189. package/core/agents/types.ts +0 -126
  190. package/core/command-registry/categories.ts +0 -23
  191. package/core/command-registry/commands.ts +0 -15
  192. package/core/command-registry/core-commands.ts +0 -344
  193. package/core/command-registry/index.ts +0 -158
  194. package/core/command-registry/optional-commands.ts +0 -163
  195. package/core/command-registry/setup-commands.ts +0 -83
  196. package/core/command-registry/types.ts +0 -59
  197. package/core/command-registry.ts +0 -9
  198. package/core/commands/types.ts +0 -185
  199. package/core/commands.ts +0 -11
  200. package/core/constants/formats.ts +0 -187
  201. package/core/context-sync.ts +0 -18
  202. package/core/data/index.ts +0 -27
  203. package/core/data/md-base-manager.ts +0 -203
  204. package/core/data/md-ideas-manager.ts +0 -155
  205. package/core/data/md-queue-manager.ts +0 -180
  206. package/core/data/md-shipped-manager.ts +0 -90
  207. package/core/data/md-state-manager.ts +0 -137
  208. package/core/domain/task-stack/index.ts +0 -19
  209. package/core/domain/task-stack/parser.ts +0 -86
  210. package/core/domain/task-stack/storage.ts +0 -123
  211. package/core/domain/task-stack/task-stack.ts +0 -340
  212. package/core/domain/task-stack/types.ts +0 -51
  213. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  214. package/core/infrastructure/command-installer/global-config.ts +0 -136
  215. package/core/infrastructure/command-installer/index.ts +0 -25
  216. package/core/infrastructure/command-installer/types.ts +0 -41
  217. package/core/infrastructure/session-manager/index.ts +0 -23
  218. package/core/infrastructure/session-manager/types.ts +0 -45
  219. package/core/infrastructure/session-manager.ts +0 -8
  220. package/core/serializers/ideas-serializer.ts +0 -187
  221. package/core/serializers/index.ts +0 -36
  222. package/core/serializers/queue-serializer.ts +0 -210
  223. package/core/serializers/shipped-serializer.ts +0 -108
  224. package/core/serializers/state-serializer.ts +0 -136
  225. package/core/session/types.ts +0 -29
  226. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -13,12 +13,12 @@ describe('PromptBuilder', () => {
13
13
 
14
14
  beforeEach(() => {
15
15
  builder = promptBuilder
16
- builder._currentContext = null
16
+ builder.resetContext()
17
17
  })
18
18
 
19
19
  describe('Critical Rules (Compressed)', () => {
20
20
  it('should include git safety rules', () => {
21
- builder._currentContext = { files: [], filteredSize: 0 }
21
+ builder.setContext({ files: [], filteredSize: 0 })
22
22
  const rules = builder.buildCriticalRules()
23
23
 
24
24
  expect(rules).toContain('GIT SAFETY')
@@ -27,7 +27,7 @@ describe('PromptBuilder', () => {
27
27
  })
28
28
 
29
29
  it('should include read-first requirement', () => {
30
- builder._currentContext = { files: [], filteredSize: 0 }
30
+ builder.setContext({ files: [], filteredSize: 0 })
31
31
  const rules = builder.buildCriticalRules()
32
32
 
33
33
  expect(rules).toContain('READ FIRST')
@@ -35,21 +35,21 @@ describe('PromptBuilder', () => {
35
35
  })
36
36
 
37
37
  it('should include pattern matching requirement', () => {
38
- builder._currentContext = { files: [], filteredSize: 0 }
38
+ builder.setContext({ files: [], filteredSize: 0 })
39
39
  const rules = builder.buildCriticalRules()
40
40
 
41
41
  expect(rules).toContain('MATCH PATTERNS')
42
42
  })
43
43
 
44
44
  it('should include no-hallucination rules', () => {
45
- builder._currentContext = { files: [], filteredSize: 0 }
45
+ builder.setContext({ files: [], filteredSize: 0 })
46
46
  const rules = builder.buildCriticalRules()
47
47
 
48
48
  expect(rules).toContain('NO HALLUCINATIONS')
49
49
  })
50
50
 
51
51
  it('should show file count in context', () => {
52
- builder._currentContext = { files: ['a.js', 'b.js', 'c.js'] }
52
+ builder.setContext({ files: ['a.js', 'b.js', 'c.js'] })
53
53
  const rules = builder.buildCriticalRules()
54
54
 
55
55
  expect(rules).toContain('3 files available')
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Project command detection tests
3
+ * Ensures prjct-cli uses the repo's own test/lint tooling (not hardcoded).
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test'
7
+ import fs from 'fs/promises'
8
+ import path from 'path'
9
+
10
+ import { detectProjectCommands } from '../../utils/project-commands'
11
+
12
+ let tmpRoot: string | null = null
13
+
14
+ async function writeJson(filePath: string, data: unknown): Promise<void> {
15
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
16
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')
17
+ }
18
+
19
+ async function writeText(filePath: string, content: string): Promise<void> {
20
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
21
+ await fs.writeFile(filePath, content, 'utf-8')
22
+ }
23
+
24
+ describe('detectProjectCommands', () => {
25
+ beforeEach(async () => {
26
+ tmpRoot = await fs.mkdtemp(path.join(process.cwd(), '.tmp', 'detect-commands-'))
27
+ })
28
+
29
+ afterEach(async () => {
30
+ if (!tmpRoot) return
31
+ await fs.rm(tmpRoot, { recursive: true, force: true })
32
+ tmpRoot = null
33
+ })
34
+
35
+ it('detects JS project and respects declared pnpm packageManager', async () => {
36
+ await writeJson(path.join(tmpRoot!, 'package.json'), {
37
+ name: 'x',
38
+ packageManager: 'pnpm@9.0.0',
39
+ scripts: { test: 'vitest', lint: 'eslint .', typecheck: 'tsc -p tsconfig.json' },
40
+ })
41
+
42
+ const detected = await detectProjectCommands(tmpRoot!)
43
+ expect(detected.stack).toBe('js')
44
+ expect(detected.packageManager).toBe('pnpm')
45
+ expect(detected.test?.command).toBe('pnpm test')
46
+ expect(detected.lint?.command).toBe('pnpm run lint')
47
+ expect(detected.typecheck?.command).toBe('pnpm run typecheck')
48
+ })
49
+
50
+ it('detects Python pytest via pytest.ini', async () => {
51
+ await writeText(path.join(tmpRoot!, 'pytest.ini'), '[pytest]\n')
52
+ const detected = await detectProjectCommands(tmpRoot!)
53
+ expect(detected.stack).toBe('python')
54
+ expect(detected.test?.command).toBe('pytest')
55
+ })
56
+
57
+ it('detects Go via go.mod', async () => {
58
+ await writeText(path.join(tmpRoot!, 'go.mod'), 'module example.com/test\n')
59
+ const detected = await detectProjectCommands(tmpRoot!)
60
+ expect(detected.stack).toBe('go')
61
+ expect(detected.test?.command).toBe('go test ./...')
62
+ })
63
+
64
+ it('detects Rust via Cargo.toml', async () => {
65
+ await writeText(path.join(tmpRoot!, 'Cargo.toml'), '[package]\nname="x"\nversion="0.1.0"\n')
66
+ const detected = await detectProjectCommands(tmpRoot!)
67
+ expect(detected.stack).toBe('rust')
68
+ expect(detected.test?.command).toBe('cargo test')
69
+ })
70
+ })
71
+
72
+
@@ -15,19 +15,10 @@ import fs from 'fs/promises'
15
15
  import path from 'path'
16
16
  import configManager from '../infrastructure/config-manager'
17
17
  import pathManager from '../infrastructure/path-manager'
18
+ import type { Agent, AssignmentContext } from '../types'
18
19
 
19
- interface Agent {
20
- name: string
21
- content: string
22
- }
23
-
24
- interface AssignmentContext {
25
- task: string
26
- availableAgents: string[]
27
- projectPath: string
28
- projectId: string | null
29
- _template: string
30
- }
20
+ // Re-export types for convenience
21
+ export type { Agent, AssignmentContext } from '../types'
31
22
 
32
23
  /**
33
24
  * Routes tasks to specialized agents based on Claude's decisions.
@@ -1,8 +1,377 @@
1
1
  /**
2
2
  * Command Executor
3
- * Re-exports from command-executor/index.ts for backwards compatibility.
3
+ * Orchestrates command execution with agentic delegation.
4
+ *
5
+ * @module agentic/command-executor
6
+ * @version 3.4
4
7
  */
5
8
 
6
- import commandExecutor from './command-executor/index'
7
- export * from './command-executor/index'
9
+ import fs from 'fs'
10
+ import path from 'path'
11
+ import os from 'os'
12
+ import templateLoader from './template-loader'
13
+ import contextBuilder from './context-builder'
14
+ import promptBuilder from './prompt-builder'
15
+ import toolRegistry from './tool-registry'
16
+ import loopDetector from './loop-detector'
17
+ import chainOfThought from './chain-of-thought'
18
+ import memorySystem from './memory-system'
19
+ import groundTruth from './ground-truth'
20
+ import planMode from './plan-mode'
21
+
22
+ import type {
23
+ ExecutionResult,
24
+ SimpleExecutionResult,
25
+ ExecutionToolsFn,
26
+ ApprovalContext,
27
+ } from '../types'
28
+
29
+ // =============================================================================
30
+ // Status Signal
31
+ // =============================================================================
32
+
33
+ const RUNNING_FILE = path.join(os.homedir(), '.prjct-cli', '.running')
34
+
35
+ /**
36
+ * Signal that a command is running (for status line)
37
+ */
38
+ export function signalStart(commandName: string): void {
39
+ try {
40
+ const dir = path.dirname(RUNNING_FILE)
41
+ if (!fs.existsSync(dir)) {
42
+ fs.mkdirSync(dir, { recursive: true })
43
+ }
44
+ fs.writeFileSync(RUNNING_FILE, `/p:${commandName}`)
45
+ } catch {
46
+ // Silently ignore - status line is optional
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Signal that command has finished (for status line)
52
+ */
53
+ export function signalEnd(): void {
54
+ try {
55
+ if (fs.existsSync(RUNNING_FILE)) {
56
+ fs.unlinkSync(RUNNING_FILE)
57
+ }
58
+ } catch {
59
+ // Silently ignore - status line is optional
60
+ }
61
+ }
62
+
63
+ // =============================================================================
64
+ // Command Executor Class
65
+ // =============================================================================
66
+
67
+ export class CommandExecutor {
68
+ /**
69
+ * Signal that a command is running (for status line)
70
+ */
71
+ signalStart(commandName: string): void {
72
+ signalStart(commandName)
73
+ }
74
+
75
+ /**
76
+ * Signal that command has finished (for status line)
77
+ */
78
+ signalEnd(): void {
79
+ signalEnd()
80
+ }
81
+
82
+ /**
83
+ * Execute a prjct command with full agentic delegation
84
+ */
85
+ async execute(
86
+ commandName: string,
87
+ params: Record<string, unknown>,
88
+ projectPath: string
89
+ ): Promise<ExecutionResult> {
90
+ // Signal start for status line
91
+ this.signalStart(commandName)
92
+
93
+ // Context for loop detection
94
+ const loopContext = (params.task as string) || (params.description as string) || ''
95
+
96
+ // Check if we're in a loop BEFORE attempting
97
+ if (loopDetector.shouldEscalate(commandName, loopContext)) {
98
+ const escalation = loopDetector.getEscalationInfo(commandName, loopContext)
99
+ this.signalEnd()
100
+ return {
101
+ success: false,
102
+ error: escalation?.message,
103
+ escalation,
104
+ isLoopDetected: true,
105
+ suggestion: escalation?.suggestion,
106
+ }
107
+ }
108
+
109
+ try {
110
+ // 1. Load template
111
+ const template = await templateLoader.load(commandName)
112
+
113
+ // 2. Build METADATA context only (lazy loading - no file reads yet)
114
+ const metadataContext = await contextBuilder.build(projectPath, params)
115
+
116
+ // 2.55. P3.4 PLAN MODE: Check if command requires planning
117
+ const requiresPlanning = planMode.requiresPlanning(commandName)
118
+ const isDestructive = planMode.isDestructive(commandName)
119
+ const isInPlanningMode = planMode.isInPlanningMode(metadataContext.projectId!)
120
+
121
+ // Start planning mode if required and not already in it
122
+ let activePlan = null
123
+ if (requiresPlanning && !isInPlanningMode && !params.skipPlanning) {
124
+ activePlan = planMode.startPlanning(metadataContext.projectId!, commandName, params)
125
+ } else if (isInPlanningMode) {
126
+ activePlan = planMode.getActivePlan(metadataContext.projectId!)
127
+ }
128
+
129
+ // 2.6. GROUND TRUTH: Verify actual state before critical operations
130
+ let groundTruthResult = null
131
+ if (groundTruth.requiresVerification(commandName)) {
132
+ const preState = await contextBuilder.loadStateForCommand(metadataContext, commandName)
133
+ groundTruthResult = await groundTruth.verify(
134
+ commandName,
135
+ metadataContext as unknown as Parameters<typeof groundTruth.verify>[1],
136
+ preState
137
+ )
138
+
139
+ // Log warnings but don't block (user can override)
140
+ if (!groundTruthResult.verified && groundTruthResult.warnings.length > 0) {
141
+ console.log(groundTruth.formatWarnings(groundTruthResult))
142
+ }
143
+ }
144
+
145
+ // 2.8. CHAIN OF THOUGHT: Reasoning for critical commands
146
+ let reasoning = null
147
+ if (chainOfThought.requiresReasoning(commandName)) {
148
+ const reasoningState = await contextBuilder.loadStateForCommand(metadataContext, commandName)
149
+ reasoning = await chainOfThought.reason(
150
+ commandName,
151
+ metadataContext as unknown as Parameters<typeof chainOfThought.reason>[1],
152
+ reasoningState as Parameters<typeof chainOfThought.reason>[2]
153
+ )
154
+
155
+ // If reasoning shows critical issues, warn but continue
156
+ if (reasoning.reasoning && !reasoning.reasoning.allPassed) {
157
+ console.log('⚠️ Chain of Thought detected issues:')
158
+ console.log(chainOfThought.formatPlan(reasoning))
159
+ }
160
+ }
161
+
162
+ // 3. AGENTIC: Claude decides agent assignment via templates
163
+ let context: Record<string, unknown> = metadataContext as unknown as Record<string, unknown>
164
+
165
+ // Provide agent info to context so Claude can delegate
166
+ context = {
167
+ ...context,
168
+ agentsPath: path.join(os.homedir(), '.prjct-cli', 'projects', metadataContext.projectId || '', 'agents'),
169
+ agentRoutingPath: path.join(__dirname, '..', '..', 'templates', 'agentic', 'agent-routing.md'),
170
+ agenticDelegation: true,
171
+ }
172
+
173
+ // 6. Load state with filtered context
174
+ const state = await contextBuilder.loadState(metadataContext)
175
+
176
+ // 7. MEMORY: Load learned patterns AND relevant memories for this command
177
+ let learnedPatterns = null
178
+ let relevantMemories = null
179
+ if (metadataContext.projectId) {
180
+ learnedPatterns = {
181
+ commit_footer: await memorySystem.getSmartDecision(metadataContext.projectId, 'commit_footer'),
182
+ branch_naming: await memorySystem.getSmartDecision(metadataContext.projectId, 'branch_naming'),
183
+ test_before_ship: await memorySystem.getSmartDecision(metadataContext.projectId, 'test_before_ship'),
184
+ preferred_agent: await memorySystem.getSmartDecision(
185
+ metadataContext.projectId,
186
+ `preferred_agent_${commandName}`
187
+ ),
188
+ }
189
+
190
+ // P3.3: Get relevant memories for context
191
+ relevantMemories = await memorySystem.getRelevantMemories(
192
+ metadataContext.projectId,
193
+ { commandName, params },
194
+ 5
195
+ )
196
+ }
197
+
198
+ // 9. Build prompt - NO agent assignment here, Claude decides via templates
199
+ const planInfo = {
200
+ isPlanning: requiresPlanning || isInPlanningMode,
201
+ requiresApproval: isDestructive && !params.approved,
202
+ active: activePlan,
203
+ allowedTools: planMode.getAllowedTools(isInPlanningMode, template.frontmatter['allowed-tools'] || []),
204
+ }
205
+ // Agent is null - Claude assigns via Task tool using agent-routing.md
206
+ const prompt = promptBuilder.build(
207
+ template,
208
+ context as Parameters<typeof promptBuilder.build>[1],
209
+ state,
210
+ null,
211
+ learnedPatterns,
212
+ null,
213
+ relevantMemories,
214
+ planInfo
215
+ )
216
+
217
+ // Log agentic mode
218
+ console.log(`🤖 Agentic delegation enabled - Claude will assign agent via Task tool`)
219
+
220
+ // Record successful attempt
221
+ loopDetector.recordSuccess(commandName, loopContext)
222
+
223
+ // Signal end for status line
224
+ this.signalEnd()
225
+
226
+ return {
227
+ success: true,
228
+ template,
229
+ context,
230
+ state,
231
+ prompt,
232
+ agenticDelegation: true,
233
+ agentsPath: context.agentsPath as string,
234
+ agentRoutingPath: context.agentRoutingPath as string,
235
+ reasoning,
236
+ groundTruth: groundTruthResult,
237
+ learnedPatterns,
238
+ relevantMemories,
239
+ memory: {
240
+ create: (memory: unknown) =>
241
+ memorySystem.createMemory(metadataContext.projectId!, memory as Parameters<typeof memorySystem.createMemory>[1]),
242
+ autoRemember: (type: string, value: string, ctx?: string) =>
243
+ memorySystem.autoRemember(metadataContext.projectId!, type, value, ctx),
244
+ search: (query: string) => memorySystem.searchMemories(metadataContext.projectId!, query),
245
+ findByTags: (tags: string[]) => memorySystem.findByTags(metadataContext.projectId!, tags),
246
+ getStats: () => memorySystem.getMemoryStats(metadataContext.projectId!),
247
+ },
248
+ plan: {
249
+ active: activePlan,
250
+ isPlanning: requiresPlanning || isInPlanningMode,
251
+ isDestructive,
252
+ requiresApproval: isDestructive && !params.approved,
253
+ recordInfo: (info: unknown) =>
254
+ planMode.recordGatheredInfo(metadataContext.projectId!, info as Parameters<typeof planMode.recordGatheredInfo>[1]),
255
+ setAnalysis: (analysis: unknown) => planMode.setAnalysis(metadataContext.projectId!, analysis as Parameters<typeof planMode.setAnalysis>[1]),
256
+ propose: (plan: unknown) =>
257
+ planMode.proposePlan(metadataContext.projectId!, plan as Parameters<typeof planMode.proposePlan>[1]),
258
+ approve: (feedback?: string | null) => planMode.approvePlan(metadataContext.projectId!, feedback),
259
+ reject: (reason?: string | null) => planMode.rejectPlan(metadataContext.projectId!, reason),
260
+ getApprovalPrompt: () =>
261
+ planMode.generateApprovalPrompt(commandName, {
262
+ // Reason: `context` here is the command execution context, not the plan-mode ApprovalContext.
263
+ // Provide required fields with safe defaults to avoid unsafe casting.
264
+ changedFiles: [],
265
+ filesToDelete: [],
266
+ operation: ((): ApprovalContext['operation'] => {
267
+ if (commandName === 'ship') return 'git_push'
268
+ if (commandName === 'cleanup') return 'delete_files'
269
+ return 'run_command'
270
+ })(),
271
+ warnings: [],
272
+ } satisfies ApprovalContext),
273
+ startExecution: () => planMode.startExecution(metadataContext.projectId!),
274
+ getNextStep: () => planMode.getNextStep(metadataContext.projectId!),
275
+ completeStep: (result?: unknown) => planMode.completeStep(metadataContext.projectId!, result as Parameters<typeof planMode.completeStep>[1]),
276
+ failStep: (error: string) => planMode.failStep(metadataContext.projectId!, error),
277
+ abort: (reason?: string) => planMode.abortPlan(metadataContext.projectId!, reason),
278
+ getStatus: () => planMode.formatStatus(metadataContext.projectId!),
279
+ getAllowedTools: () =>
280
+ planMode.getAllowedTools(isInPlanningMode, template.frontmatter['allowed-tools'] || []),
281
+ },
282
+ }
283
+ } catch (error) {
284
+ // Signal end for status line
285
+ this.signalEnd()
286
+
287
+ // Record failed attempt for loop detection
288
+ const attemptInfo = loopDetector.recordAttempt(commandName, loopContext, {
289
+ success: false,
290
+ error: (error as Error).message,
291
+ })
292
+
293
+ // Check if we should escalate after this failure
294
+ if (attemptInfo.shouldEscalate) {
295
+ const escalation = loopDetector.getEscalationInfo(commandName, loopContext)
296
+ return {
297
+ success: false,
298
+ error: escalation?.message,
299
+ escalation,
300
+ isLoopDetected: true,
301
+ suggestion: escalation?.suggestion,
302
+ }
303
+ }
304
+
305
+ return {
306
+ success: false,
307
+ error: (error as Error).message,
308
+ attemptNumber: attemptInfo.attemptNumber,
309
+ isLooping: attemptInfo.isLooping,
310
+ }
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Execute tool with permission check
316
+ */
317
+ async executeTool(toolName: string, args: unknown[], allowedTools: string[]): Promise<unknown> {
318
+ // Check if tool is allowed
319
+ if (!toolRegistry.isAllowed(toolName, allowedTools)) {
320
+ throw new Error(`Tool ${toolName} not allowed for this command`)
321
+ }
322
+
323
+ // Get tool function
324
+ const tool = toolRegistry.get(toolName)
325
+ if (!tool) {
326
+ throw new Error(`Tool ${toolName} not found`)
327
+ }
328
+
329
+ // Execute tool
330
+ return await tool(...args)
331
+ }
332
+
333
+ /**
334
+ * Simple execution for direct tool access (legacy migration helper)
335
+ */
336
+ async executeSimple(
337
+ commandName: string,
338
+ executionFn: ExecutionToolsFn,
339
+ projectPath: string
340
+ ): Promise<SimpleExecutionResult> {
341
+ try {
342
+ // Load template to get allowed tools
343
+ const template = await templateLoader.load(commandName)
344
+ const allowedTools = template.frontmatter['allowed-tools'] || []
345
+
346
+ // Build context
347
+ const context = await contextBuilder.build(projectPath)
348
+
349
+ // Create tools proxy that checks permissions
350
+ const tools = {
351
+ read: async (filePath: string) => this.executeTool('Read', [filePath], allowedTools),
352
+ write: async (filePath: string, content: string) => this.executeTool('Write', [filePath, content], allowedTools),
353
+ bash: async (command: string) => this.executeTool('Bash', [command], allowedTools),
354
+ }
355
+
356
+ // Execute user function with tools
357
+ const result = await executionFn(tools, context)
358
+
359
+ return {
360
+ success: true,
361
+ result,
362
+ }
363
+ } catch (error) {
364
+ return {
365
+ success: false,
366
+ error: (error as Error).message,
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ // =============================================================================
373
+ // Default Export
374
+ // =============================================================================
375
+
376
+ const commandExecutor = new CommandExecutor()
8
377
  export default commandExecutor
@@ -9,35 +9,15 @@
9
9
  import fs from 'fs/promises'
10
10
  import pathManager from '../infrastructure/path-manager'
11
11
  import configManager from '../infrastructure/config-manager'
12
+ import type { ContextPaths, ProjectContext, ContextState } from '../types'
12
13
 
13
- interface Paths {
14
- now: string
15
- next: string
16
- context: string
17
- shipped: string
18
- metrics: string
19
- ideas: string
20
- roadmap: string
21
- specs: string
22
- memory: string
23
- patterns: string
24
- analysis: string
25
- codePatterns: string
26
- }
27
-
28
- interface Context {
29
- projectId: string | null
30
- projectPath: string
31
- globalPath: string
32
- paths: Paths
33
- params: Record<string, unknown>
34
- timestamp: string
35
- date: string
36
- }
14
+ // Re-export types for convenience
15
+ export type { ContextPaths, ProjectContext, ContextState } from '../types'
37
16
 
38
- interface State {
39
- [key: string]: string | null
40
- }
17
+ // Local type aliases for backward compatibility
18
+ type Paths = ContextPaths
19
+ type Context = ProjectContext
20
+ type State = ContextState
41
21
 
42
22
  /**
43
23
  * Builds and caches project context for Claude decisions.