prjct-cli 0.45.0 → 0.45.3

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 (207) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/bin/prjct.ts +117 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +58 -39
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +28 -4
  43. package/core/commands/commands.ts +57 -24
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +13 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +18 -19
  59. package/core/context-tools/imports-tool.ts +13 -33
  60. package/core/context-tools/index.ts +29 -54
  61. package/core/context-tools/recent-tool.ts +16 -22
  62. package/core/context-tools/signatures-tool.ts +17 -26
  63. package/core/context-tools/summary-tool.ts +20 -22
  64. package/core/context-tools/token-counter.ts +25 -20
  65. package/core/context-tools/types.ts +5 -5
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -16
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +25 -25
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +87 -345
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -17
  157. package/core/storage/metrics-storage.ts +39 -34
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -305
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +14 -14
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18755 -15574
  194. package/dist/core/infrastructure/command-installer.js +86 -79
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +246 -225
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
package/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.45.3] - 2026-01-29
4
+
5
+ ### Bug Fixes
6
+
7
+ - remove --provenance (requires public repo) - PRJ-147
8
+
9
+
10
+ ## [0.45.2] - 2026-01-29
11
+
12
+ ### Bug Fixes
13
+
14
+ - use Production environment for npm secrets - PRJ-147
15
+
16
+
17
+ ## [0.45.1] - 2026-01-29
18
+
19
+ ### Bug Fixes
20
+
21
+ - use semver-sorted tags for version detection - PRJ-147
22
+ - npm auth configuration for CI publish - PRJ-147
23
+
24
+
25
+ ## [0.42.1] - 2026-01-29
26
+
27
+ ### Bug Fixes
28
+
29
+ - npm auth configuration for CI publish - PRJ-147
30
+
31
+
32
+ ## [0.42.0] - 2026-01-29
33
+
34
+ ### Features
35
+
36
+ - automated release pipeline - PRJ-147 (#68)
37
+ - add project indexing and analysis services (PRJ-85, PRJ-87) (#66)
38
+ - metrics display in output (PRJ-68, PRJ-69) (#54)
39
+ - add prjct uninstall command (PRJ-146) (#65)
40
+ - add prjct doctor command (PRJ-117) (#62)
41
+ - smart watch mode - auto-sync on file changes (PRJ-123) (#61)
42
+ - add --quiet flag for silent output (PRJ-97) (#60)
43
+ - interactive onboarding wizard (PRJ-124) (#58)
44
+ - smart context filtering tools for AI agents (PRJ-127) (#57)
45
+ - workflow state machine + bidirectional Linear sync (#55)
46
+ - progress indicators for long-running operations (PRJ-129) (#52)
47
+ - agent activity stream for real-time visibility (PRJ-135) (#51)
48
+ - show explicit next steps after each command (PRJ-136) (#50)
49
+ - multi-agent output Phase 3 - Auto-detect + Continue.dev (PRJ-126) (#49)
50
+ - multi-agent output Phase 2 - Copilot + Windsurf (PRJ-126) (#48)
51
+ - multi-agent context output - Phase 1 (PRJ-126) (#47)
52
+ - bidirectional sync Linear ↔ prjct (PRJ-142) (#46)
53
+
54
+ ### Bug Fixes
55
+
56
+ - add Bun setup for test runner in CI - PRJ-147
57
+ - use npm instead of bun for CI tests - PRJ-147
58
+ - remove IDE files from repo - PRJ-144, PRJ-145
59
+ - add --help handler in CLI entry point for CI compatibility
60
+ - update tests for CI compatibility
61
+ - CI workflow - remove unsupported bun reporter flag and fix verification
62
+ - enforce workflow steps in templates (PRJ-143) (#56)
63
+ - add $PRJCT_CLI prefix to relative paths in templates (PRJ-143)
64
+ - use stderr for Linear connection log to not break JSON output
65
+
66
+ ### Performance
67
+
68
+ - parallelize sync operations for 30-50% speedup (PRJ-116) (#59)
69
+
70
+ ### Refactoring
71
+
72
+ - migrate from ESLint + Prettier to Biome
73
+ - consolidate and optimize CI workflows
74
+ - extract StackDetector from sync-service (PRJ-86) (#64)
75
+ - extract ContextFileGenerator from sync-service (PRJ-88) (#63)
76
+
77
+
3
78
  ## [0.45.0] - 2026-01-29
4
79
 
5
80
  ### Feature: Smart Context Filtering Tools (PRJ-127)
package/bin/prjct.ts CHANGED
@@ -8,14 +8,14 @@
8
8
  * auto-install on first CLI use. This is the reliable path.
9
9
  */
10
10
 
11
- import fs from 'fs'
12
- import path from 'path'
13
- import os from 'os'
14
- import { VERSION } from '../core/utils/version'
15
- import editorsConfig from '../core/infrastructure/editors-config'
16
- import { startServer, DEFAULT_PORT } from '../core/server/server'
17
- import configManager from '../core/infrastructure/config-manager'
11
+ import fs from 'node:fs'
12
+ import os from 'node:os'
13
+ import path from 'node:path'
18
14
  import { detectAllProviders } from '../core/infrastructure/ai-provider'
15
+ import configManager from '../core/infrastructure/config-manager'
16
+ import editorsConfig from '../core/infrastructure/editors-config'
17
+ import { DEFAULT_PORT, startServer } from '../core/server/server'
18
+ import { VERSION } from '../core/utils/version'
19
19
 
20
20
  /**
21
21
  * Check if routers are installed for detected providers
@@ -52,6 +52,15 @@ function checkRoutersInstalled(): boolean {
52
52
  // Check for special subcommands that bypass normal CLI
53
53
  const args = process.argv.slice(2)
54
54
 
55
+ // Parse --quiet / -q flag (must be done early, before any output)
56
+ const quietIndex = args.findIndex((arg) => arg === '--quiet' || arg === '-q')
57
+ const isQuietMode = quietIndex !== -1
58
+ if (isQuietMode) {
59
+ args.splice(quietIndex, 1) // Remove flag from args
60
+ const { setQuietMode } = await import('../core/utils/output')
61
+ setQuietMode(true)
62
+ }
63
+
55
64
  // Colors for output
56
65
  const CYAN = '\x1b[36m'
57
66
  const YELLOW = '\x1b[33m'
@@ -78,7 +87,7 @@ if (args[0] === 'start' || args[0] === 'setup') {
78
87
  console.error('No prjct project found. Run "prjct init" first.')
79
88
  process.exitCode = 1
80
89
  } else {
81
- const port = parseInt(args[1]) || DEFAULT_PORT
90
+ const port = parseInt(args[1], 10) || DEFAULT_PORT
82
91
  await startServer(projectId, projectPath, port)
83
92
  }
84
93
  } catch (error) {
@@ -99,9 +108,57 @@ if (args[0] === 'start' || args[0] === 'setup') {
99
108
  console.log(JSON.stringify(result, null, 2))
100
109
  process.exitCode = result.tool === 'error' ? 1 : 0
101
110
  }
111
+ } else if (args[0] === 'doctor') {
112
+ // Health check command
113
+ const { doctorService } = await import('../core/services/doctor-service')
114
+ const exitCode = await doctorService.run(process.cwd())
115
+ process.exitCode = exitCode
116
+ } else if (args[0] === 'uninstall') {
117
+ // Complete system removal
118
+ const { uninstall } = await import('../core/commands/uninstall')
119
+
120
+ // Parse flags
121
+ const force = args.includes('--force') || args.includes('-f')
122
+ const backup = args.includes('--backup') || args.includes('-b')
123
+ const dryRun = args.includes('--dry-run') || args.includes('-n')
124
+ const keepPackage = args.includes('--keep-package')
125
+
126
+ const result = await uninstall({ force, backup, dryRun, keepPackage })
127
+ process.exitCode = result.success ? 0 : 1
128
+ } else if (args[0] === 'watch') {
129
+ // Watch mode - auto-sync on file changes
130
+ const projectPath = process.cwd()
131
+ const projectId = await configManager.getProjectId(projectPath)
132
+
133
+ if (!projectId) {
134
+ console.error('No prjct project found. Run "prjct init" first.')
135
+ process.exitCode = 1
136
+ } else {
137
+ const { watchService } = await import('../core/services/watch-service')
138
+
139
+ // Parse options
140
+ const verbose = args.includes('--verbose') || args.includes('-v')
141
+ const debounceArg = args.find((a) => a.startsWith('--debounce='))
142
+ const debounceMs = debounceArg ? parseInt(debounceArg.split('=')[1], 10) : undefined
143
+ const intervalArg = args.find((a) => a.startsWith('--interval='))
144
+ const minIntervalMs = intervalArg ? parseInt(intervalArg.split('=')[1], 10) * 1000 : undefined
145
+
146
+ const result = await watchService.start(projectPath, {
147
+ verbose,
148
+ quiet: isQuietMode,
149
+ debounceMs,
150
+ minIntervalMs,
151
+ })
152
+
153
+ if (!result.success) {
154
+ console.error(result.error)
155
+ process.exitCode = 1
156
+ }
157
+ // Watch mode runs indefinitely until Ctrl+C
158
+ }
102
159
  } else if (args[0] === 'linear') {
103
160
  // Linear CLI subcommand - direct access to Linear SDK
104
- const { spawn } = await import('child_process')
161
+ const { spawn } = await import('node:child_process')
105
162
  const projectPath = process.cwd()
106
163
  const projectId = await configManager.getProjectId(projectPath)
107
164
 
@@ -125,6 +182,56 @@ if (args[0] === 'start' || args[0] === 'setup') {
125
182
  process.exitCode = code || 0
126
183
  })
127
184
  }
185
+ } else if (args[0] === 'help' || args[0] === '-h' || args[0] === '--help') {
186
+ // Show help - bypass setup check to always show help
187
+ console.log(`
188
+ prjct - Context layer for AI coding agents
189
+ Works with Claude Code, Gemini CLI, Antigravity, Cursor IDE, and more.
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
+ `)
234
+ process.exitCode = 0
128
235
  } else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
129
236
  // Show version with provider status
130
237
  const detection = detectAllProviders()
@@ -211,7 +318,7 @@ ${CYAN}${BOLD} Welcome to prjct!${RESET}
211
318
  const { default: setup } = await import('../core/infrastructure/setup')
212
319
  await setup.run()
213
320
  }
214
- } catch (error) {
321
+ } catch (_error) {
215
322
  // Silent fail on version check
216
323
  }
217
324
 
@@ -3,11 +3,11 @@
3
3
  * P3.3: Semantic Memory Database
4
4
  */
5
5
 
6
- import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'bun:test'
6
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test'
7
+ import fs from 'node:fs/promises'
8
+ import path from 'node:path'
7
9
  import memorySystem from '../../agentic/memory-system'
8
10
  import pathManager from '../../infrastructure/path-manager'
9
- import fs from 'fs/promises'
10
- import path from 'path'
11
11
 
12
12
  let testCounter = 0
13
13
  const getTestProjectId = () => `test-memory-${Date.now()}-${++testCounter}`
@@ -33,7 +33,7 @@ describe('MemorySystem P3.3', () => {
33
33
  title: 'Test Memory',
34
34
  content: 'This is test content',
35
35
  tags: ['code_style', 'naming_convention'],
36
- userTriggered: true
36
+ userTriggered: true,
37
37
  })
38
38
 
39
39
  // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
@@ -52,19 +52,19 @@ describe('MemorySystem P3.3', () => {
52
52
  const memoryId = await memorySystem.createMemory(TEST_PROJECT_ID, {
53
53
  title: 'Original Title',
54
54
  content: 'Original content',
55
- tags: ['code_style']
55
+ tags: ['code_style'],
56
56
  })
57
57
 
58
58
  const updated = await memorySystem.updateMemory(TEST_PROJECT_ID, memoryId, {
59
59
  title: 'Updated Title',
60
60
  content: 'Updated content',
61
- tags: ['naming_convention', 'architecture']
61
+ tags: ['naming_convention', 'architecture'],
62
62
  })
63
63
 
64
64
  expect(updated).toBe(true)
65
65
 
66
66
  const memories = await memorySystem.getAllMemories(TEST_PROJECT_ID)
67
- const memory = memories.find(m => m.id === memoryId)
67
+ const memory = memories.find((m) => m.id === memoryId)
68
68
 
69
69
  expect(memory!.title).toBe('Updated Title')
70
70
  expect(memory!.content).toBe('Updated content')
@@ -74,7 +74,7 @@ describe('MemorySystem P3.3', () => {
74
74
 
75
75
  it('should return false for non-existent memory', async () => {
76
76
  const result = await memorySystem.updateMemory(TEST_PROJECT_ID, 'non_existent_id', {
77
- title: 'New Title'
77
+ title: 'New Title',
78
78
  })
79
79
  expect(result).toBe(false)
80
80
  })
@@ -85,14 +85,14 @@ describe('MemorySystem P3.3', () => {
85
85
  const memoryId = await memorySystem.createMemory(TEST_PROJECT_ID, {
86
86
  title: 'To Delete',
87
87
  content: 'Will be deleted',
88
- tags: ['test']
88
+ tags: ['test'],
89
89
  })
90
90
 
91
91
  const deleted = await memorySystem.deleteMemory(TEST_PROJECT_ID, memoryId)
92
92
  expect(deleted).toBe(true)
93
93
 
94
94
  const memories = await memorySystem.getAllMemories(TEST_PROJECT_ID)
95
- expect(memories.find(m => m.id === memoryId)).toBeUndefined()
95
+ expect(memories.find((m) => m.id === memoryId)).toBeUndefined()
96
96
  })
97
97
  })
98
98
 
@@ -101,27 +101,35 @@ describe('MemorySystem P3.3', () => {
101
101
  await memorySystem.createMemory(TEST_PROJECT_ID, {
102
102
  title: 'Memory 1',
103
103
  content: 'Content 1',
104
- tags: ['code_style', 'naming_convention']
104
+ tags: ['code_style', 'naming_convention'],
105
105
  })
106
106
  await memorySystem.createMemory(TEST_PROJECT_ID, {
107
107
  title: 'Memory 2',
108
108
  content: 'Content 2',
109
- tags: ['architecture', 'naming_convention']
109
+ tags: ['architecture', 'naming_convention'],
110
110
  })
111
111
  await memorySystem.createMemory(TEST_PROJECT_ID, {
112
112
  title: 'Memory 3',
113
113
  content: 'Content 3',
114
- tags: ['commit_style']
114
+ tags: ['commit_style'],
115
115
  })
116
116
  })
117
117
 
118
118
  it('should find memories with ANY tag (OR)', async () => {
119
- const results = await memorySystem.findByTags(TEST_PROJECT_ID, ['code_style', 'architecture'], false)
119
+ const results = await memorySystem.findByTags(
120
+ TEST_PROJECT_ID,
121
+ ['code_style', 'architecture'],
122
+ false
123
+ )
120
124
  expect(results.length).toBe(2)
121
125
  })
122
126
 
123
127
  it('should find memories with ALL tags (AND)', async () => {
124
- const results = await memorySystem.findByTags(TEST_PROJECT_ID, ['naming_convention', 'architecture'], true)
128
+ const results = await memorySystem.findByTags(
129
+ TEST_PROJECT_ID,
130
+ ['naming_convention', 'architecture'],
131
+ true
132
+ )
125
133
  expect(results.length).toBe(1)
126
134
  expect(results[0].title).toBe('Memory 2')
127
135
  })
@@ -132,12 +140,12 @@ describe('MemorySystem P3.3', () => {
132
140
  await memorySystem.createMemory(TEST_PROJECT_ID, {
133
141
  title: 'React Hooks Pattern',
134
142
  content: 'Use custom hooks for reusable logic',
135
- tags: ['code_style']
143
+ tags: ['code_style'],
136
144
  })
137
145
  await memorySystem.createMemory(TEST_PROJECT_ID, {
138
146
  title: 'API Design',
139
147
  content: 'REST endpoints follow /api/v1 pattern',
140
- tags: ['architecture']
148
+ tags: ['architecture'],
141
149
  })
142
150
  })
143
151
 
@@ -165,17 +173,17 @@ describe('MemorySystem P3.3', () => {
165
173
  title: 'Commit Style',
166
174
  content: 'Use conventional commits',
167
175
  tags: ['commit_style', 'ship_workflow'],
168
- userTriggered: true
176
+ userTriggered: true,
169
177
  })
170
178
  await memorySystem.createMemory(TEST_PROJECT_ID, {
171
179
  title: 'Test Behavior',
172
180
  content: 'Run tests before shipping',
173
- tags: ['test_behavior', 'ship_workflow']
181
+ tags: ['test_behavior', 'ship_workflow'],
174
182
  })
175
183
  await memorySystem.createMemory(TEST_PROJECT_ID, {
176
184
  title: 'Code Style',
177
185
  content: 'Use TypeScript strict mode',
178
- tags: ['code_style']
186
+ tags: ['code_style'],
179
187
  })
180
188
  })
181
189
 
@@ -184,8 +192,8 @@ describe('MemorySystem P3.3', () => {
184
192
  const results = await memorySystem.getRelevantMemories(TEST_PROJECT_ID, context, 5)
185
193
 
186
194
  expect(results.length).toBeGreaterThan(0)
187
- const hasRelevantTags = results.some(m =>
188
- m.tags.includes('commit_style') || m.tags.includes('ship_workflow')
195
+ const hasRelevantTags = results.some(
196
+ (m) => m.tags.includes('commit_style') || m.tags.includes('ship_workflow')
189
197
  )
190
198
  expect(hasRelevantTags).toBe(true)
191
199
  })
@@ -194,7 +202,7 @@ describe('MemorySystem P3.3', () => {
194
202
  const context = { commandName: 'ship', params: {} }
195
203
  const results = await memorySystem.getRelevantMemories(TEST_PROJECT_ID, context, 5)
196
204
 
197
- const userTriggeredIndex = results.findIndex(m => m.userTriggered)
205
+ const userTriggeredIndex = results.findIndex((m) => m.userTriggered)
198
206
  expect(userTriggeredIndex).toBeLessThanOrEqual(1)
199
207
  })
200
208
 
@@ -207,7 +215,12 @@ describe('MemorySystem P3.3', () => {
207
215
 
208
216
  describe('autoRemember', () => {
209
217
  it('should create memory from user decision', async () => {
210
- await memorySystem.autoRemember(TEST_PROJECT_ID, 'commit_footer', 'prjct', 'User chose prjct footer')
218
+ await memorySystem.autoRemember(
219
+ TEST_PROJECT_ID,
220
+ 'commit_footer',
221
+ 'prjct',
222
+ 'User chose prjct footer'
223
+ )
211
224
 
212
225
  const memories = await memorySystem.getAllMemories(TEST_PROJECT_ID)
213
226
  expect(memories.length).toBe(1)
@@ -232,12 +245,12 @@ describe('MemorySystem P3.3', () => {
232
245
  title: 'Memory 1',
233
246
  content: 'Content 1',
234
247
  tags: ['code_style'],
235
- userTriggered: true
248
+ userTriggered: true,
236
249
  })
237
250
  await memorySystem.createMemory(TEST_PROJECT_ID, {
238
251
  title: 'Memory 2',
239
252
  content: 'Content 2',
240
- tags: ['code_style', 'architecture']
253
+ tags: ['code_style', 'architecture'],
241
254
  })
242
255
 
243
256
  const stats = await memorySystem.getMemoryStats(TEST_PROJECT_ID)
@@ -3,14 +3,9 @@
3
3
  * P3.4: Plan Mode + Approval Flow
4
4
  */
5
5
 
6
- import { describe, it, expect, beforeEach } from 'bun:test'
7
- import planMode, {
8
- PLAN_STATUS,
9
- PLAN_REQUIRED_COMMANDS,
10
- DESTRUCTIVE_COMMANDS,
11
- PLANNING_TOOLS
12
- } from '../../agentic/plan-mode'
13
- import type { ProposedPlan, ApprovalContext, ChangedFile } from '../../types'
6
+ import { beforeEach, describe, expect, it } from 'bun:test'
7
+ import planMode, { PLAN_STATUS } from '../../agentic/plan-mode'
8
+ import type { ApprovalContext, ProposedPlan } from '../../types'
14
9
 
15
10
  // Helper to create complete ProposedPlan objects
16
11
  const createPlan = (overrides: Partial<ProposedPlan> = {}): ProposedPlan => ({
@@ -109,7 +104,9 @@ describe('PlanMode P3.4', () => {
109
104
 
110
105
  describe('startPlanning', () => {
111
106
  it('should create a new plan with correct initial state', () => {
112
- const plan = planMode.startPlanning(TEST_PROJECT_ID, 'feature', { description: 'Add dark mode' })
107
+ const plan = planMode.startPlanning(TEST_PROJECT_ID, 'feature', {
108
+ description: 'Add dark mode',
109
+ })
113
110
 
114
111
  // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
115
112
  expect(plan.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
@@ -161,7 +158,7 @@ describe('PlanMode P3.4', () => {
161
158
  type: 'file_content',
162
159
  source: 'src/index.js',
163
160
  data: 'content',
164
- gatheredAt: new Date().toISOString()
161
+ gatheredAt: new Date().toISOString(),
165
162
  })
166
163
 
167
164
  const plan = planMode.getActivePlan(TEST_PROJECT_ID)
@@ -173,11 +170,14 @@ describe('PlanMode P3.4', () => {
173
170
  describe('proposePlan', () => {
174
171
  it('should set status to pending approval', () => {
175
172
  planMode.startPlanning(TEST_PROJECT_ID, 'feature', {})
176
- planMode.proposePlan(TEST_PROJECT_ID, createPlan({
177
- summary: 'Add dark mode feature',
178
- approach: 'CSS variables with theme context',
179
- steps: [{ description: 'Create theme context' }, { description: 'Add toggle' }]
180
- }))
173
+ planMode.proposePlan(
174
+ TEST_PROJECT_ID,
175
+ createPlan({
176
+ summary: 'Add dark mode feature',
177
+ approach: 'CSS variables with theme context',
178
+ steps: [{ description: 'Create theme context' }, { description: 'Add toggle' }],
179
+ })
180
+ )
181
181
 
182
182
  const plan = planMode.getActivePlan(TEST_PROJECT_ID)
183
183
  expect(plan!.status).toBe(PLAN_STATUS.PENDING_APPROVAL)
@@ -185,11 +185,14 @@ describe('PlanMode P3.4', () => {
185
185
 
186
186
  it('should return formatted plan for display', () => {
187
187
  planMode.startPlanning(TEST_PROJECT_ID, 'feature', {})
188
- const formatted = planMode.proposePlan(TEST_PROJECT_ID, createPlan({
189
- summary: 'Test plan',
190
- approach: 'Test approach',
191
- steps: [{ description: 'Step 1' }]
192
- }))
188
+ const formatted = planMode.proposePlan(
189
+ TEST_PROJECT_ID,
190
+ createPlan({
191
+ summary: 'Test plan',
192
+ approach: 'Test approach',
193
+ steps: [{ description: 'Step 1' }],
194
+ })
195
+ )
193
196
 
194
197
  expect(formatted!.summary).toBe('Test plan')
195
198
  expect(formatted!.approach).toBe('Test approach')
@@ -210,9 +213,12 @@ describe('PlanMode P3.4', () => {
210
213
 
211
214
  it('should convert proposed steps to executable steps', () => {
212
215
  planMode.startPlanning(TEST_PROJECT_ID, 'feature', {})
213
- planMode.proposePlan(TEST_PROJECT_ID, createPlan({
214
- steps: [{ description: 'Step 1' }, { description: 'Step 2' }]
215
- }))
216
+ planMode.proposePlan(
217
+ TEST_PROJECT_ID,
218
+ createPlan({
219
+ steps: [{ description: 'Step 1' }, { description: 'Step 2' }],
220
+ })
221
+ )
216
222
  const result = planMode.approvePlan(TEST_PROJECT_ID)
217
223
 
218
224
  expect(result!.steps.length).toBe(2)
@@ -249,12 +255,15 @@ describe('PlanMode P3.4', () => {
249
255
  describe('execution flow', () => {
250
256
  beforeEach(() => {
251
257
  planMode.startPlanning(TEST_PROJECT_ID, 'feature', {})
252
- planMode.proposePlan(TEST_PROJECT_ID, createPlan({
253
- steps: [
254
- { description: 'Step 1', tool: 'Write' },
255
- { description: 'Step 2', tool: 'Bash' }
256
- ]
257
- }))
258
+ planMode.proposePlan(
259
+ TEST_PROJECT_ID,
260
+ createPlan({
261
+ steps: [
262
+ { description: 'Step 1', tool: 'Write' },
263
+ { description: 'Step 2', tool: 'Bash' },
264
+ ],
265
+ })
266
+ )
258
267
  planMode.approvePlan(TEST_PROJECT_ID)
259
268
  })
260
269
 
@@ -301,15 +310,18 @@ describe('PlanMode P3.4', () => {
301
310
 
302
311
  describe('generateApprovalPrompt', () => {
303
312
  it('should generate ship approval prompt', () => {
304
- const prompt = planMode.generateApprovalPrompt('ship', createApprovalContext({
305
- branch: 'feature/dark-mode',
306
- changedFiles: [
307
- { path: 'a.js', action: 'modify' },
308
- { path: 'b.js', action: 'modify' }
309
- ],
310
- commitMessage: 'Add dark mode',
311
- operation: 'git_push'
312
- }))
313
+ const prompt = planMode.generateApprovalPrompt(
314
+ 'ship',
315
+ createApprovalContext({
316
+ branch: 'feature/dark-mode',
317
+ changedFiles: [
318
+ { path: 'a.js', action: 'modify' },
319
+ { path: 'b.js', action: 'modify' },
320
+ ],
321
+ commitMessage: 'Add dark mode',
322
+ operation: 'git_push',
323
+ })
324
+ )
313
325
 
314
326
  expect(prompt.title).toBe('Ship Confirmation')
315
327
  expect(prompt.details).toContain('Branch: feature/dark-mode')
@@ -317,11 +329,14 @@ describe('PlanMode P3.4', () => {
317
329
  })
318
330
 
319
331
  it('should generate cleanup approval prompt', () => {
320
- const prompt = planMode.generateApprovalPrompt('cleanup', createApprovalContext({
321
- filesToDelete: ['temp.js'],
322
- linesOfCode: 50,
323
- operation: 'delete_files'
324
- }))
332
+ const prompt = planMode.generateApprovalPrompt(
333
+ 'cleanup',
334
+ createApprovalContext({
335
+ filesToDelete: ['temp.js'],
336
+ linesOfCode: 50,
337
+ operation: 'delete_files',
338
+ })
339
+ )
325
340
 
326
341
  expect(prompt.title).toBe('Cleanup Confirmation')
327
342
  expect(prompt.message).toContain('delete')
@@ -347,9 +362,12 @@ describe('PlanMode P3.4', () => {
347
362
 
348
363
  it('should show progress during execution', () => {
349
364
  planMode.startPlanning(TEST_PROJECT_ID, 'feature', {})
350
- planMode.proposePlan(TEST_PROJECT_ID, createPlan({
351
- steps: [{ description: 'Step 1' }, { description: 'Step 2' }]
352
- }))
365
+ planMode.proposePlan(
366
+ TEST_PROJECT_ID,
367
+ createPlan({
368
+ steps: [{ description: 'Step 1' }, { description: 'Step 2' }],
369
+ })
370
+ )
353
371
  planMode.approvePlan(TEST_PROJECT_ID)
354
372
  planMode.startExecution(TEST_PROJECT_ID)
355
373