prjct-cli 0.20.0 → 0.21.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.
Files changed (236) 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/integrations/notion/client.ts +323 -0
  58. package/core/integrations/notion/index.ts +43 -0
  59. package/core/integrations/notion/setup.ts +230 -0
  60. package/core/integrations/notion/sync.ts +311 -0
  61. package/core/integrations/notion/templates.ts +234 -0
  62. package/core/outcomes/analyzer.ts +7 -41
  63. package/core/outcomes/index.ts +1 -1
  64. package/core/outcomes/recorder.ts +1 -1
  65. package/core/plugin/builtin/notion.ts +178 -0
  66. package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
  67. package/core/plugin/loader.ts +5 -5
  68. package/core/plugin/registry.ts +2 -2
  69. package/core/schemas/ideas.ts +85 -54
  70. package/core/schemas/index.ts +14 -33
  71. package/core/schemas/permissions.ts +177 -0
  72. package/core/schemas/project.ts +39 -12
  73. package/core/schemas/roadmap.ts +94 -59
  74. package/core/schemas/schemas.ts +39 -0
  75. package/core/schemas/shipped.ts +87 -60
  76. package/core/schemas/state.ts +110 -70
  77. package/core/server/index.ts +21 -0
  78. package/core/server/routes.ts +165 -0
  79. package/core/server/server.ts +136 -0
  80. package/core/server/sse.ts +135 -0
  81. package/core/services/agent-service.ts +170 -0
  82. package/core/services/breakdown-service.ts +126 -0
  83. package/core/services/index.ts +21 -0
  84. package/core/services/memory-service.ts +108 -0
  85. package/core/services/project-service.ts +146 -0
  86. package/core/services/skill-service.ts +253 -0
  87. package/core/session/compaction.ts +257 -0
  88. package/core/session/index.ts +20 -8
  89. package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
  90. package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
  91. package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
  92. package/core/session/utils.ts +1 -1
  93. package/core/storage/ideas-storage.ts +10 -26
  94. package/core/storage/index.ts +14 -162
  95. package/core/storage/queue-storage.ts +13 -11
  96. package/core/storage/shipped-storage.ts +4 -17
  97. package/core/storage/state-storage.ts +35 -43
  98. package/core/storage/storage-manager.ts +42 -52
  99. package/core/storage/storage.ts +160 -0
  100. package/core/sync/auth-config.ts +1 -8
  101. package/core/sync/index.ts +17 -10
  102. package/core/sync/oauth-handler.ts +1 -6
  103. package/core/sync/sync-client.ts +6 -34
  104. package/core/sync/sync-manager.ts +11 -40
  105. package/core/types/agentic.ts +577 -0
  106. package/core/types/agents.ts +145 -0
  107. package/core/types/bus.ts +82 -0
  108. package/core/types/commands.ts +366 -0
  109. package/core/types/config.ts +70 -0
  110. package/core/types/core.ts +96 -0
  111. package/core/types/domain.ts +71 -0
  112. package/core/types/events.ts +42 -0
  113. package/core/types/fs.ts +56 -0
  114. package/core/types/index.ts +396 -500
  115. package/core/types/infrastructure.ts +196 -0
  116. package/core/types/integrations.ts +57 -0
  117. package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
  118. package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
  119. package/core/types/plugin.ts +25 -0
  120. package/core/types/server.ts +54 -0
  121. package/core/types/services.ts +65 -0
  122. package/core/types/session.ts +135 -0
  123. package/core/types/storage.ts +148 -0
  124. package/core/types/sync.ts +121 -0
  125. package/core/types/task.ts +72 -0
  126. package/core/types/template.ts +24 -0
  127. package/core/types/utils.ts +90 -0
  128. package/core/utils/cache.ts +195 -0
  129. package/core/utils/collection-filters.ts +245 -0
  130. package/core/utils/date-helper.ts +1 -5
  131. package/core/utils/file-helper.ts +20 -10
  132. package/core/utils/jsonl-helper.ts +5 -8
  133. package/core/utils/markdown-builder.ts +277 -0
  134. package/core/utils/project-commands.ts +132 -0
  135. package/core/utils/runtime.ts +119 -0
  136. package/dist/bin/prjct.mjs +12568 -0
  137. package/package.json +13 -8
  138. package/scripts/build.js +106 -0
  139. package/scripts/postinstall.js +50 -8
  140. package/templates/agentic/subagent-generation.md +1 -1
  141. package/templates/commands/init.md +43 -0
  142. package/templates/commands/notion-setup.md +191 -0
  143. package/templates/commands/serve.md +118 -0
  144. package/templates/commands/ship.md +13 -2
  145. package/templates/commands/skill.md +110 -0
  146. package/templates/commands/sync.md +1 -1
  147. package/templates/commands/test.md +23 -4
  148. package/templates/mcp-config.json +28 -0
  149. package/templates/permissions/default.jsonc +60 -0
  150. package/templates/permissions/permissive.jsonc +49 -0
  151. package/templates/permissions/strict.jsonc +62 -0
  152. package/templates/skills/code-review.md +47 -0
  153. package/templates/skills/debug.md +61 -0
  154. package/templates/skills/refactor.md +47 -0
  155. package/templates/subagents/domain/devops.md +1 -1
  156. package/templates/subagents/domain/testing.md +6 -10
  157. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  158. package/templates/tools/bash.txt +22 -0
  159. package/templates/tools/edit.txt +18 -0
  160. package/templates/tools/glob.txt +19 -0
  161. package/templates/tools/grep.txt +21 -0
  162. package/templates/tools/read.txt +14 -0
  163. package/templates/tools/task.txt +20 -0
  164. package/templates/tools/webfetch.txt +16 -0
  165. package/templates/tools/websearch.txt +18 -0
  166. package/templates/tools/write.txt +17 -0
  167. package/core/agentic/command-executor/command-executor.ts +0 -312
  168. package/core/agentic/command-executor/index.ts +0 -16
  169. package/core/agentic/command-executor/status-signal.ts +0 -38
  170. package/core/agentic/command-executor/types.ts +0 -79
  171. package/core/agentic/ground-truth/index.ts +0 -76
  172. package/core/agentic/ground-truth/types.ts +0 -33
  173. package/core/agentic/ground-truth/utils.ts +0 -48
  174. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  175. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  176. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  177. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  178. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  179. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  180. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  181. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  182. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  183. package/core/agentic/ground-truth/verifiers.ts +0 -6
  184. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  185. package/core/agentic/loop-detector/hallucination.ts +0 -71
  186. package/core/agentic/loop-detector/index.ts +0 -41
  187. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  188. package/core/agentic/loop-detector/types.ts +0 -66
  189. package/core/agentic/memory-system/history.ts +0 -53
  190. package/core/agentic/memory-system/index.ts +0 -192
  191. package/core/agentic/memory-system/patterns.ts +0 -156
  192. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  193. package/core/agentic/memory-system/session.ts +0 -21
  194. package/core/agentic/plan-mode/approval.ts +0 -57
  195. package/core/agentic/plan-mode/constants.ts +0 -44
  196. package/core/agentic/plan-mode/index.ts +0 -28
  197. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  198. package/core/agentic/plan-mode/types.ts +0 -193
  199. package/core/agents/types.ts +0 -126
  200. package/core/command-registry/categories.ts +0 -23
  201. package/core/command-registry/commands.ts +0 -15
  202. package/core/command-registry/core-commands.ts +0 -344
  203. package/core/command-registry/index.ts +0 -158
  204. package/core/command-registry/optional-commands.ts +0 -163
  205. package/core/command-registry/setup-commands.ts +0 -83
  206. package/core/command-registry/types.ts +0 -59
  207. package/core/command-registry.ts +0 -9
  208. package/core/commands/types.ts +0 -185
  209. package/core/commands.ts +0 -11
  210. package/core/constants/formats.ts +0 -187
  211. package/core/context-sync.ts +0 -18
  212. package/core/data/index.ts +0 -27
  213. package/core/data/md-base-manager.ts +0 -203
  214. package/core/data/md-ideas-manager.ts +0 -155
  215. package/core/data/md-queue-manager.ts +0 -180
  216. package/core/data/md-shipped-manager.ts +0 -90
  217. package/core/data/md-state-manager.ts +0 -137
  218. package/core/domain/task-stack/index.ts +0 -19
  219. package/core/domain/task-stack/parser.ts +0 -86
  220. package/core/domain/task-stack/storage.ts +0 -123
  221. package/core/domain/task-stack/task-stack.ts +0 -340
  222. package/core/domain/task-stack/types.ts +0 -51
  223. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  224. package/core/infrastructure/command-installer/global-config.ts +0 -136
  225. package/core/infrastructure/command-installer/index.ts +0 -25
  226. package/core/infrastructure/command-installer/types.ts +0 -41
  227. package/core/infrastructure/session-manager/index.ts +0 -23
  228. package/core/infrastructure/session-manager/types.ts +0 -45
  229. package/core/infrastructure/session-manager.ts +0 -8
  230. package/core/serializers/ideas-serializer.ts +0 -187
  231. package/core/serializers/index.ts +0 -36
  232. package/core/serializers/queue-serializer.ts +0 -210
  233. package/core/serializers/shipped-serializer.ts +0 -108
  234. package/core/serializers/state-serializer.ts +0 -136
  235. package/core/session/types.ts +0 -29
  236. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -1,8 +1,607 @@
1
1
  /**
2
- * Ground Truth Verification
3
- * Re-exports from ground-truth/index.ts for backwards compatibility.
2
+ * Ground Truth
3
+ * Verifies actual state before critical operations.
4
+ *
5
+ * OPTIMIZATION (P1.3): Anti-Hallucination Pattern
6
+ * - Reads actual files before assuming state
7
+ * - Compares expected vs actual state
8
+ * - Provides specific warnings for mismatches
9
+ * - Logs verification results for debugging
10
+ *
11
+ * Source: Devin, Cursor, Augment Code patterns
4
12
  */
5
13
 
6
- import groundTruth from './ground-truth/index'
7
- export * from './ground-truth/index'
8
- export default groundTruth
14
+ import fs from 'fs/promises'
15
+ import path from 'path'
16
+ import os from 'os'
17
+ import { execSync } from 'child_process'
18
+
19
+ import type { GroundTruthContext, VerificationResult, Verifier } from '../types'
20
+
21
+ // =============================================================================
22
+ // Utilities
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Format duration from milliseconds to human-readable string
27
+ */
28
+ export function formatDuration(ms: number): string {
29
+ const hours = Math.floor(ms / (1000 * 60 * 60))
30
+ const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60))
31
+
32
+ if (hours > 0) {
33
+ return `${hours}h ${minutes}m`
34
+ }
35
+ return `${minutes}m`
36
+ }
37
+
38
+ /**
39
+ * Escape special regex characters in a string
40
+ */
41
+ export function escapeRegex(string: string): string {
42
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
43
+ }
44
+
45
+ /**
46
+ * Format verification warnings for display
47
+ */
48
+ export function formatWarnings(result: VerificationResult): string | null {
49
+ if (result.verified || result.warnings.length === 0) {
50
+ return null
51
+ }
52
+
53
+ let output = ' Ground Truth Warnings:\n'
54
+ result.warnings.forEach((w) => {
55
+ output += ` - ${w}\n`
56
+ })
57
+
58
+ if (result.recommendations.length > 0) {
59
+ output += '\nRecommendations:\n'
60
+ result.recommendations.forEach((r) => {
61
+ output += ` -> ${r}\n`
62
+ })
63
+ }
64
+
65
+ return output
66
+ }
67
+
68
+ // =============================================================================
69
+ // Verifiers
70
+ // =============================================================================
71
+
72
+ /**
73
+ * Done Command Verifier
74
+ * Verify task is actually complete-able
75
+ */
76
+ export async function verifyDone(context: GroundTruthContext): Promise<VerificationResult> {
77
+ const warnings: string[] = []
78
+ const recommendations: string[] = []
79
+ const actual: Record<string, unknown> = {}
80
+
81
+ // 1. Verify now.md exists and has real content
82
+ const nowPath = context.paths.now
83
+ try {
84
+ const nowContent = await fs.readFile(nowPath, 'utf-8')
85
+ actual.nowExists = true
86
+ actual.nowContent = nowContent.trim()
87
+ actual.nowLength = nowContent.length
88
+
89
+ // Check for placeholder content
90
+ if (nowContent.includes('No current task') || nowContent.match(/^#\s*NOW\s*$/m)) {
91
+ warnings.push('now.md appears to be empty or placeholder')
92
+ recommendations.push('Start a task first with /p:now "task"')
93
+ }
94
+
95
+ // Check for task metadata (started time)
96
+ const startedMatch = nowContent.match(/Started:\s*(.+)/i)
97
+ if (startedMatch) {
98
+ actual.startedAt = startedMatch[1]
99
+ // Calculate duration
100
+ const startTime = new Date(startedMatch[1])
101
+ if (!isNaN(startTime.getTime())) {
102
+ actual.durationMs = Date.now() - startTime.getTime()
103
+ actual.durationFormatted = formatDuration(actual.durationMs as number)
104
+ }
105
+ }
106
+ } catch {
107
+ actual.nowExists = false
108
+ warnings.push('now.md does not exist')
109
+ recommendations.push('Create a task with /p:now "task"')
110
+ }
111
+
112
+ // 2. Verify next.md for auto-start
113
+ const nextPath = context.paths.next
114
+ try {
115
+ const nextContent = await fs.readFile(nextPath, 'utf-8')
116
+ actual.nextExists = true
117
+ const tasks = nextContent.match(/- \[ \]/g) || []
118
+ actual.pendingTasks = tasks.length
119
+ } catch {
120
+ actual.nextExists = false
121
+ actual.pendingTasks = 0
122
+ }
123
+
124
+ // 3. Verify metrics.md is writable
125
+ const metricsPath = context.paths.metrics
126
+ try {
127
+ await fs.access(path.dirname(metricsPath), fs.constants.W_OK)
128
+ actual.metricsWritable = true
129
+ } catch {
130
+ actual.metricsWritable = false
131
+ warnings.push('Cannot write to metrics directory')
132
+ }
133
+
134
+ return {
135
+ verified: warnings.length === 0,
136
+ actual,
137
+ warnings,
138
+ recommendations,
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Ship Command Verifier
144
+ * Verify feature is ready to ship
145
+ */
146
+ export async function verifyShip(context: GroundTruthContext): Promise<VerificationResult> {
147
+ const warnings: string[] = []
148
+ const recommendations: string[] = []
149
+ const actual: Record<string, unknown> = {}
150
+
151
+ // 1. Check for uncommitted changes
152
+ try {
153
+ const gitStatus = execSync('git status --porcelain', {
154
+ cwd: context.projectPath,
155
+ encoding: 'utf-8',
156
+ })
157
+ actual.hasUncommittedChanges = gitStatus.trim().length > 0
158
+ actual.uncommittedFiles = gitStatus.trim().split('\n').filter(Boolean).length
159
+
160
+ if (actual.hasUncommittedChanges) {
161
+ warnings.push(`${actual.uncommittedFiles} uncommitted file(s)`)
162
+ recommendations.push('Commit changes before shipping')
163
+ }
164
+ } catch {
165
+ actual.gitAvailable = false
166
+ // Not a git repo or git not available - not a blocker
167
+ }
168
+
169
+ // 2. Check for package.json version (if exists)
170
+ const pkgPath = path.join(context.projectPath, 'package.json')
171
+ try {
172
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8')
173
+ const pkg = JSON.parse(pkgContent)
174
+ actual.currentVersion = pkg.version
175
+ actual.hasPackageJson = true
176
+ } catch {
177
+ actual.hasPackageJson = false
178
+ }
179
+
180
+ // 3. Check shipped.md for duplicate feature names
181
+ const shippedPath = context.paths.shipped
182
+ try {
183
+ const shippedContent = await fs.readFile(shippedPath, 'utf-8')
184
+ actual.shippedExists = true
185
+
186
+ // Check if feature name already shipped today
187
+ const featureName = context.params.feature || context.params.description
188
+ if (featureName) {
189
+ const today = new Date().toISOString().split('T')[0]
190
+ const todayPattern = new RegExp(`${today}.*${escapeRegex(featureName)}`, 'i')
191
+ if (todayPattern.test(shippedContent)) {
192
+ warnings.push(`Feature "${featureName}" already shipped today`)
193
+ recommendations.push('Use a different feature name or skip /p:ship')
194
+ }
195
+ }
196
+ } catch {
197
+ actual.shippedExists = false
198
+ }
199
+
200
+ // 4. Check for test failures (if test script exists)
201
+ if (actual.hasPackageJson) {
202
+ try {
203
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8')
204
+ const pkg = JSON.parse(pkgContent)
205
+ actual.hasTestScript = !!pkg.scripts?.test
206
+ // Note: We don't run tests here, just check if they exist
207
+ // Running tests is the user's responsibility
208
+ } catch {
209
+ actual.hasTestScript = false
210
+ }
211
+ }
212
+
213
+ return {
214
+ verified: warnings.length === 0,
215
+ actual,
216
+ warnings,
217
+ recommendations,
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Feature Command Verifier
223
+ * Verify feature can be added
224
+ */
225
+ export async function verifyFeature(context: GroundTruthContext): Promise<VerificationResult> {
226
+ const warnings: string[] = []
227
+ const recommendations: string[] = []
228
+ const actual: Record<string, unknown> = {}
229
+
230
+ // 1. Check next.md capacity
231
+ const nextPath = context.paths.next
232
+ try {
233
+ const nextContent = await fs.readFile(nextPath, 'utf-8')
234
+ actual.nextExists = true
235
+ const tasks = nextContent.match(/- \[[ x]\]/g) || []
236
+ actual.taskCount = tasks.length
237
+ actual.pendingTasks = (nextContent.match(/- \[ \]/g) || []).length
238
+
239
+ if ((actual.taskCount as number) >= 90) {
240
+ warnings.push(`Queue nearly full (${actual.taskCount}/100 tasks)`)
241
+ recommendations.push('Complete some tasks before adding more')
242
+ }
243
+ } catch {
244
+ actual.nextExists = false
245
+ actual.taskCount = 0
246
+ }
247
+
248
+ // 2. Check roadmap.md for duplicate features
249
+ const roadmapPath = context.paths.roadmap
250
+ try {
251
+ const roadmapContent = await fs.readFile(roadmapPath, 'utf-8')
252
+ actual.roadmapExists = true
253
+
254
+ const featureName = context.params.description || context.params.feature
255
+ if (featureName) {
256
+ const featurePattern = new RegExp(escapeRegex(featureName), 'i')
257
+ if (featurePattern.test(roadmapContent)) {
258
+ warnings.push(`Feature "${featureName}" may already exist in roadmap`)
259
+ recommendations.push('Check roadmap for duplicates with /p:roadmap')
260
+ }
261
+ }
262
+ } catch {
263
+ actual.roadmapExists = false
264
+ }
265
+
266
+ // 3. Check if there's an active task (should complete first?)
267
+ const nowPath = context.paths.now
268
+ try {
269
+ const nowContent = await fs.readFile(nowPath, 'utf-8')
270
+ actual.hasActiveTask = nowContent.trim().length > 0 && !nowContent.includes('No current task')
271
+
272
+ if (actual.hasActiveTask) {
273
+ recommendations.push('Consider completing current task first with /p:done')
274
+ }
275
+ } catch {
276
+ actual.hasActiveTask = false
277
+ }
278
+
279
+ return {
280
+ verified: warnings.length === 0,
281
+ actual,
282
+ warnings,
283
+ recommendations,
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Now Command Verifier
289
+ * Verify task can be set (anti-hallucination: warn if replacing)
290
+ */
291
+ export async function verifyNow(context: GroundTruthContext): Promise<VerificationResult> {
292
+ const warnings: string[] = []
293
+ const recommendations: string[] = []
294
+ const actual: Record<string, unknown> = {}
295
+
296
+ // 1. Check if there's already an active task
297
+ const nowPath = context.paths.now
298
+ try {
299
+ const nowContent = await fs.readFile(nowPath, 'utf-8')
300
+ actual.nowExists = true
301
+ actual.nowContent = nowContent.trim()
302
+
303
+ const hasRealTask =
304
+ nowContent.trim().length > 0 && !nowContent.includes('No current task') && !nowContent.match(/^#\s*NOW\s*$/m)
305
+
306
+ actual.hasActiveTask = hasRealTask
307
+
308
+ // ANTI-HALLUCINATION: Warn if replacing existing task
309
+ if (hasRealTask && context.params.task) {
310
+ const taskPreview = nowContent.substring(0, 50).replace(/\n/g, ' ')
311
+ warnings.push(`Replacing existing task: "${taskPreview}..."`)
312
+ recommendations.push('Use /p:done first to track completion')
313
+ }
314
+ } catch {
315
+ actual.nowExists = false
316
+ actual.hasActiveTask = false
317
+ }
318
+
319
+ // 2. Check next.md for available tasks
320
+ const nextPath = context.paths.next
321
+ try {
322
+ const nextContent = await fs.readFile(nextPath, 'utf-8')
323
+ const pendingTasks = (nextContent.match(/- \[ \]/g) || []).length
324
+ actual.pendingTasks = pendingTasks
325
+
326
+ if (!context.params.task && pendingTasks > 0) {
327
+ recommendations.push(`${pendingTasks} tasks available in queue`)
328
+ }
329
+ } catch {
330
+ actual.pendingTasks = 0
331
+ }
332
+
333
+ return {
334
+ verified: warnings.length === 0,
335
+ actual,
336
+ warnings,
337
+ recommendations,
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Init Command Verifier
343
+ * Verify project can be initialized
344
+ */
345
+ export async function verifyInit(context: GroundTruthContext): Promise<VerificationResult> {
346
+ const warnings: string[] = []
347
+ const recommendations: string[] = []
348
+ const actual: Record<string, unknown> = {}
349
+
350
+ // 1. Check if already initialized
351
+ const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
352
+ try {
353
+ const configContent = await fs.readFile(configPath, 'utf-8')
354
+ actual.alreadyInitialized = true
355
+ actual.existingConfig = JSON.parse(configContent)
356
+ warnings.push('Project already initialized')
357
+ recommendations.push('Use /p:analyze to refresh analysis or delete .prjct/ to reinitialize')
358
+ } catch {
359
+ actual.alreadyInitialized = false
360
+ }
361
+
362
+ // 2. Check if global storage path is writable
363
+ const globalPath = path.join(os.homedir(), '.prjct-cli')
364
+ try {
365
+ await fs.access(globalPath, fs.constants.W_OK)
366
+ actual.globalPathWritable = true
367
+ } catch {
368
+ try {
369
+ // Try to create it
370
+ await fs.mkdir(globalPath, { recursive: true })
371
+ actual.globalPathWritable = true
372
+ actual.globalPathCreated = true
373
+ } catch {
374
+ actual.globalPathWritable = false
375
+ warnings.push('Cannot write to ~/.prjct-cli')
376
+ recommendations.push('Check directory permissions')
377
+ }
378
+ }
379
+
380
+ return {
381
+ verified: warnings.length === 0,
382
+ actual,
383
+ warnings,
384
+ recommendations,
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Sync Command Verifier
390
+ * Verify sync can proceed
391
+ */
392
+ export async function verifySync(context: GroundTruthContext): Promise<VerificationResult> {
393
+ const warnings: string[] = []
394
+ const recommendations: string[] = []
395
+ const actual: Record<string, unknown> = {}
396
+
397
+ // 1. Check if project is initialized
398
+ const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
399
+ try {
400
+ const configContent = await fs.readFile(configPath, 'utf-8')
401
+ actual.hasConfig = true
402
+ actual.config = JSON.parse(configContent)
403
+ } catch {
404
+ actual.hasConfig = false
405
+ warnings.push('Project not initialized')
406
+ recommendations.push('Run /p:init first')
407
+ return { verified: false, actual, warnings, recommendations }
408
+ }
409
+
410
+ // 2. Check if global storage exists
411
+ const projectId = (actual.config as { projectId?: string })?.projectId
412
+ const globalProjectPath = path.join(os.homedir(), '.prjct-cli/projects', projectId || '')
413
+ try {
414
+ await fs.access(globalProjectPath)
415
+ actual.globalStorageExists = true
416
+ } catch {
417
+ actual.globalStorageExists = false
418
+ warnings.push('Global storage missing')
419
+ recommendations.push('Run /p:init to recreate')
420
+ }
421
+
422
+ return {
423
+ verified: warnings.length === 0,
424
+ actual,
425
+ warnings,
426
+ recommendations,
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Analyze Command Verifier
432
+ * Verify analysis can proceed
433
+ */
434
+ export async function verifyAnalyze(context: GroundTruthContext): Promise<VerificationResult> {
435
+ const warnings: string[] = []
436
+ const recommendations: string[] = []
437
+ const actual: Record<string, unknown> = {}
438
+
439
+ // 1. Check if project has recognizable structure
440
+ const files = ['package.json', 'Cargo.toml', 'go.mod', 'requirements.txt', 'Gemfile', 'pom.xml']
441
+ actual.detectedFiles = []
442
+
443
+ for (const file of files) {
444
+ try {
445
+ await fs.access(path.join(context.projectPath, file))
446
+ ;(actual.detectedFiles as string[]).push(file)
447
+ } catch {
448
+ // File doesn't exist
449
+ }
450
+ }
451
+
452
+ if ((actual.detectedFiles as string[]).length === 0) {
453
+ warnings.push('No recognizable project files detected')
454
+ recommendations.push('Analysis may be limited without package.json or similar')
455
+ }
456
+
457
+ // 2. Check for source directories
458
+ const srcDirs = ['src', 'lib', 'app', 'core', 'components']
459
+ actual.detectedSrcDirs = []
460
+
461
+ for (const dir of srcDirs) {
462
+ try {
463
+ const stat = await fs.stat(path.join(context.projectPath, dir))
464
+ if (stat.isDirectory()) {
465
+ ;(actual.detectedSrcDirs as string[]).push(dir)
466
+ }
467
+ } catch {
468
+ // Directory doesn't exist
469
+ }
470
+ }
471
+
472
+ return {
473
+ verified: true, // Analysis can always proceed, even with warnings
474
+ actual,
475
+ warnings,
476
+ recommendations,
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Spec Command Verifier
482
+ * Verify spec can be created
483
+ */
484
+ export async function verifySpec(context: GroundTruthContext): Promise<VerificationResult> {
485
+ const warnings: string[] = []
486
+ const recommendations: string[] = []
487
+ const actual: Record<string, unknown> = {}
488
+
489
+ // 1. Check specs directory exists
490
+ const specsPath = context.paths.specs
491
+ try {
492
+ await fs.access(specsPath)
493
+ actual.specsExists = true
494
+
495
+ // List existing specs
496
+ const files = await fs.readdir(specsPath)
497
+ actual.existingSpecs = files.filter((f) => f.endsWith('.md'))
498
+ actual.specCount = (actual.existingSpecs as string[]).length
499
+ } catch {
500
+ actual.specsExists = false
501
+ actual.specCount = 0
502
+ }
503
+
504
+ // 2. Check for duplicate spec name
505
+ const featureName = context.params.feature || context.params.name || context.params.description
506
+ if (featureName && actual.existingSpecs) {
507
+ const slug = featureName.toLowerCase().replace(/\s+/g, '-')
508
+ if ((actual.existingSpecs as string[]).includes(`${slug}.md`)) {
509
+ warnings.push(`Spec "${featureName}" already exists`)
510
+ recommendations.push('Use a different name or edit existing spec')
511
+ }
512
+ }
513
+
514
+ return {
515
+ verified: warnings.length === 0,
516
+ actual,
517
+ warnings,
518
+ recommendations,
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Command-specific ground truth verifiers
524
+ */
525
+ export const verifiers: Record<string, Verifier> = {
526
+ done: verifyDone,
527
+ ship: verifyShip,
528
+ feature: verifyFeature,
529
+ now: verifyNow,
530
+ init: verifyInit,
531
+ sync: verifySync,
532
+ analyze: verifyAnalyze,
533
+ spec: verifySpec,
534
+ }
535
+
536
+ // =============================================================================
537
+ // Main Functions
538
+ // =============================================================================
539
+
540
+ /**
541
+ * Verify ground truth before command execution
542
+ */
543
+ export async function verify(
544
+ commandName: string,
545
+ context: GroundTruthContext,
546
+ state: unknown
547
+ ): Promise<VerificationResult> {
548
+ const verifier = verifiers[commandName]
549
+
550
+ if (!verifier) {
551
+ // No specific verification needed
552
+ return {
553
+ verified: true,
554
+ actual: {},
555
+ warnings: [],
556
+ recommendations: [],
557
+ }
558
+ }
559
+
560
+ try {
561
+ return await verifier(context, state)
562
+ } catch (error) {
563
+ return {
564
+ verified: false,
565
+ actual: {},
566
+ warnings: [`Verification error: ${(error as Error).message}`],
567
+ recommendations: ['Check file permissions and project configuration'],
568
+ }
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Prepare command by verifying ground truth
574
+ * Returns enhanced context with verification results
575
+ */
576
+ export async function prepareCommand(commandName: string, context: GroundTruthContext, state: unknown) {
577
+ const verification = await verify(commandName, context, state)
578
+
579
+ return {
580
+ ...context,
581
+ groundTruth: {
582
+ ...verification,
583
+ verifiedAt: new Date().toISOString(),
584
+ command: commandName,
585
+ },
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Check if command requires ground truth verification
591
+ */
592
+ export function requiresVerification(commandName: string): boolean {
593
+ // ANTI-HALLUCINATION: Expanded verification for more commands
594
+ return ['done', 'ship', 'feature', 'spec', 'now', 'init', 'sync', 'analyze'].includes(commandName)
595
+ }
596
+
597
+ // =============================================================================
598
+ // Default Export
599
+ // =============================================================================
600
+
601
+ export default {
602
+ verify,
603
+ prepareCommand,
604
+ requiresVerification,
605
+ verifiers,
606
+ formatWarnings,
607
+ }