prjct-cli 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CHANGELOG.md +66 -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/agents/uxui.md +210 -0
  134. package/templates/agentic/subagent-generation.md +1 -1
  135. package/templates/commands/bug.md +219 -41
  136. package/templates/commands/feature.md +368 -80
  137. package/templates/commands/serve.md +118 -0
  138. package/templates/commands/ship.md +152 -14
  139. package/templates/commands/skill.md +110 -0
  140. package/templates/commands/sync.md +63 -4
  141. package/templates/commands/test.md +40 -188
  142. package/templates/mcp-config.json +0 -36
  143. package/templates/permissions/default.jsonc +60 -0
  144. package/templates/permissions/permissive.jsonc +49 -0
  145. package/templates/permissions/strict.jsonc +62 -0
  146. package/templates/skills/code-review.md +47 -0
  147. package/templates/skills/debug.md +61 -0
  148. package/templates/skills/refactor.md +47 -0
  149. package/templates/subagents/domain/devops.md +1 -1
  150. package/templates/subagents/domain/testing.md +6 -10
  151. package/templates/subagents/workflow/prjct-shipper.md +16 -7
  152. package/templates/tools/bash.txt +22 -0
  153. package/templates/tools/edit.txt +18 -0
  154. package/templates/tools/glob.txt +19 -0
  155. package/templates/tools/grep.txt +21 -0
  156. package/templates/tools/read.txt +14 -0
  157. package/templates/tools/task.txt +20 -0
  158. package/templates/tools/webfetch.txt +16 -0
  159. package/templates/tools/websearch.txt +18 -0
  160. package/templates/tools/write.txt +17 -0
  161. package/core/agentic/command-executor/command-executor.ts +0 -312
  162. package/core/agentic/command-executor/index.ts +0 -16
  163. package/core/agentic/command-executor/status-signal.ts +0 -38
  164. package/core/agentic/command-executor/types.ts +0 -79
  165. package/core/agentic/ground-truth/index.ts +0 -76
  166. package/core/agentic/ground-truth/types.ts +0 -33
  167. package/core/agentic/ground-truth/utils.ts +0 -48
  168. package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
  169. package/core/agentic/ground-truth/verifiers/done.ts +0 -75
  170. package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
  171. package/core/agentic/ground-truth/verifiers/index.ts +0 -37
  172. package/core/agentic/ground-truth/verifiers/init.ts +0 -52
  173. package/core/agentic/ground-truth/verifiers/now.ts +0 -57
  174. package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
  175. package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
  176. package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
  177. package/core/agentic/ground-truth/verifiers.ts +0 -6
  178. package/core/agentic/loop-detector/error-analysis.ts +0 -97
  179. package/core/agentic/loop-detector/hallucination.ts +0 -71
  180. package/core/agentic/loop-detector/index.ts +0 -41
  181. package/core/agentic/loop-detector/loop-detector.ts +0 -222
  182. package/core/agentic/loop-detector/types.ts +0 -66
  183. package/core/agentic/memory-system/history.ts +0 -53
  184. package/core/agentic/memory-system/index.ts +0 -192
  185. package/core/agentic/memory-system/patterns.ts +0 -156
  186. package/core/agentic/memory-system/semantic-memories.ts +0 -278
  187. package/core/agentic/memory-system/session.ts +0 -21
  188. package/core/agentic/plan-mode/approval.ts +0 -57
  189. package/core/agentic/plan-mode/constants.ts +0 -44
  190. package/core/agentic/plan-mode/index.ts +0 -28
  191. package/core/agentic/plan-mode/plan-mode.ts +0 -407
  192. package/core/agentic/plan-mode/types.ts +0 -193
  193. package/core/agents/types.ts +0 -126
  194. package/core/command-registry/categories.ts +0 -23
  195. package/core/command-registry/commands.ts +0 -15
  196. package/core/command-registry/core-commands.ts +0 -344
  197. package/core/command-registry/index.ts +0 -158
  198. package/core/command-registry/optional-commands.ts +0 -163
  199. package/core/command-registry/setup-commands.ts +0 -83
  200. package/core/command-registry/types.ts +0 -59
  201. package/core/command-registry.ts +0 -9
  202. package/core/commands/types.ts +0 -185
  203. package/core/commands.ts +0 -11
  204. package/core/constants/formats.ts +0 -187
  205. package/core/context-sync.ts +0 -18
  206. package/core/data/index.ts +0 -27
  207. package/core/data/md-base-manager.ts +0 -203
  208. package/core/data/md-ideas-manager.ts +0 -155
  209. package/core/data/md-queue-manager.ts +0 -180
  210. package/core/data/md-shipped-manager.ts +0 -90
  211. package/core/data/md-state-manager.ts +0 -137
  212. package/core/domain/task-stack/index.ts +0 -19
  213. package/core/domain/task-stack/parser.ts +0 -86
  214. package/core/domain/task-stack/storage.ts +0 -123
  215. package/core/domain/task-stack/task-stack.ts +0 -340
  216. package/core/domain/task-stack/types.ts +0 -51
  217. package/core/infrastructure/command-installer/command-installer.ts +0 -327
  218. package/core/infrastructure/command-installer/global-config.ts +0 -136
  219. package/core/infrastructure/command-installer/index.ts +0 -25
  220. package/core/infrastructure/command-installer/types.ts +0 -41
  221. package/core/infrastructure/session-manager/index.ts +0 -23
  222. package/core/infrastructure/session-manager/types.ts +0 -45
  223. package/core/infrastructure/session-manager.ts +0 -8
  224. package/core/serializers/ideas-serializer.ts +0 -187
  225. package/core/serializers/index.ts +0 -36
  226. package/core/serializers/queue-serializer.ts +0 -210
  227. package/core/serializers/shipped-serializer.ts +0 -108
  228. package/core/serializers/state-serializer.ts +0 -136
  229. package/core/session/types.ts +0 -29
  230. /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
@@ -1,45 +0,0 @@
1
- /**
2
- * Spec Command Verifier
3
- * Verify spec can be created
4
- */
5
-
6
- import fs from 'fs/promises'
7
- import type { Context, VerificationResult } from '../types'
8
-
9
- export async function verifySpec(context: Context): Promise<VerificationResult> {
10
- const warnings: string[] = []
11
- const recommendations: string[] = []
12
- const actual: Record<string, unknown> = {}
13
-
14
- // 1. Check specs directory exists
15
- const specsPath = context.paths.specs
16
- try {
17
- await fs.access(specsPath)
18
- actual.specsExists = true
19
-
20
- // List existing specs
21
- const files = await fs.readdir(specsPath)
22
- actual.existingSpecs = files.filter((f) => f.endsWith('.md'))
23
- actual.specCount = (actual.existingSpecs as string[]).length
24
- } catch {
25
- actual.specsExists = false
26
- actual.specCount = 0
27
- }
28
-
29
- // 2. Check for duplicate spec name
30
- const featureName = context.params.feature || context.params.name || context.params.description
31
- if (featureName && actual.existingSpecs) {
32
- const slug = featureName.toLowerCase().replace(/\s+/g, '-')
33
- if ((actual.existingSpecs as string[]).includes(`${slug}.md`)) {
34
- warnings.push(`Spec "${featureName}" already exists`)
35
- recommendations.push('Use a different name or edit existing spec')
36
- }
37
- }
38
-
39
- return {
40
- verified: warnings.length === 0,
41
- actual,
42
- warnings,
43
- recommendations,
44
- }
45
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * Sync Command Verifier
3
- * Verify sync can proceed
4
- */
5
-
6
- import fs from 'fs/promises'
7
- import path from 'path'
8
- import os from 'os'
9
- import type { Context, VerificationResult } from '../types'
10
-
11
- export async function verifySync(context: Context): Promise<VerificationResult> {
12
- const warnings: string[] = []
13
- const recommendations: string[] = []
14
- const actual: Record<string, unknown> = {}
15
-
16
- // 1. Check if project is initialized
17
- const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
18
- try {
19
- const configContent = await fs.readFile(configPath, 'utf-8')
20
- actual.hasConfig = true
21
- actual.config = JSON.parse(configContent)
22
- } catch {
23
- actual.hasConfig = false
24
- warnings.push('Project not initialized')
25
- recommendations.push('Run /p:init first')
26
- return { verified: false, actual, warnings, recommendations }
27
- }
28
-
29
- // 2. Check if global storage exists
30
- const projectId = (actual.config as { projectId?: string })?.projectId
31
- const globalProjectPath = path.join(os.homedir(), '.prjct-cli/projects', projectId || '')
32
- try {
33
- await fs.access(globalProjectPath)
34
- actual.globalStorageExists = true
35
- } catch {
36
- actual.globalStorageExists = false
37
- warnings.push('Global storage missing')
38
- recommendations.push('Run /p:init to recreate')
39
- }
40
-
41
- return {
42
- verified: warnings.length === 0,
43
- actual,
44
- warnings,
45
- recommendations,
46
- }
47
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Ground Truth Verifiers
3
- * Re-exports from verifiers/index.ts for backwards compatibility.
4
- */
5
-
6
- export * from './verifiers/index'
@@ -1,97 +0,0 @@
1
- /**
2
- * Error Analysis
3
- * Pattern detection and suggestion generation for errors
4
- */
5
-
6
- import type { ErrorEntry, ErrorPattern } from './types'
7
-
8
- /**
9
- * Check if two errors are similar
10
- */
11
- export function isSimilarError(error1: string, error2: string): boolean {
12
- if (!error1 || !error2) return false
13
-
14
- // Normalize errors
15
- const normalize = (e: string) =>
16
- e
17
- .toLowerCase()
18
- .replace(/[0-9]+/g, 'N') // Replace numbers
19
- .replace(/['"`]/g, '') // Remove quotes
20
- .replace(/\s+/g, ' ') // Normalize whitespace
21
- .trim()
22
-
23
- return normalize(error1) === normalize(error2)
24
- }
25
-
26
- /**
27
- * Analyze error pattern from history
28
- */
29
- export function analyzeErrorPattern(errors: ErrorEntry[]): ErrorPattern {
30
- if (!errors || errors.length === 0) {
31
- return { type: 'unknown', description: 'No error information' }
32
- }
33
-
34
- const lastError = errors[errors.length - 1]?.message?.toLowerCase() || ''
35
-
36
- // Detect common patterns (ORDER MATTERS - more specific patterns first)
37
- if (lastError.includes('permission') || lastError.includes('access denied')) {
38
- return { type: 'permission', description: 'File or directory permission issue' }
39
- }
40
- if (lastError.includes('not found') || lastError.includes('no such file')) {
41
- return { type: 'not_found', description: 'File or resource not found' }
42
- }
43
- if (lastError.includes('syntax') || lastError.includes('parse')) {
44
- return { type: 'syntax', description: 'Syntax or parsing error' }
45
- }
46
- if (lastError.includes('timeout') || lastError.includes('timed out')) {
47
- return { type: 'timeout', description: 'Operation timed out' }
48
- }
49
- if (lastError.includes('network') || lastError.includes('connection')) {
50
- return { type: 'network', description: 'Network or connection issue' }
51
- }
52
- // Config pattern MUST be checked before validation (since "invalid config" contains both)
53
- if (lastError.includes('config') || lastError.includes('configuration')) {
54
- return { type: 'config', description: 'Configuration issue' }
55
- }
56
- if (lastError.includes('validation') || lastError.includes('invalid')) {
57
- return { type: 'validation', description: 'Validation failed' }
58
- }
59
-
60
- return { type: 'unknown', description: 'Unrecognized error pattern' }
61
- }
62
-
63
- /**
64
- * Generate user-friendly escalation message
65
- */
66
- export function generateEscalationMessage(command: string, errorPattern: ErrorPattern, maxAttempts: number): string {
67
- const messages: Record<string, string> = {
68
- permission: `I've tried ${command} ${maxAttempts} times but keep hitting permission issues.`,
69
- not_found: `After ${maxAttempts} attempts, I still can't find the required file or resource.`,
70
- syntax: `I'm encountering repeated syntax errors with ${command}.`,
71
- timeout: `The operation keeps timing out after ${maxAttempts} attempts.`,
72
- network: `Network issues are preventing ${command} from completing.`,
73
- validation: `Validation keeps failing for ${command}.`,
74
- config: `There seems to be a configuration issue affecting ${command}.`,
75
- unknown: `I've tried ${command} ${maxAttempts} times without success.`,
76
- }
77
-
78
- return messages[errorPattern.type] || messages.unknown
79
- }
80
-
81
- /**
82
- * Generate actionable suggestion based on error pattern
83
- */
84
- export function generateSuggestion(errorPattern: ErrorPattern): string {
85
- const suggestions: Record<string, string> = {
86
- permission: 'Check file permissions. Try: chmod -R u+w ~/.prjct-cli/',
87
- not_found: 'Verify the file path exists. Run /p:init if project not initialized.',
88
- syntax: 'Check the file format. There may be invalid JSON or markdown.',
89
- timeout: 'Check your network connection or try again in a moment.',
90
- network: 'Verify internet connection and try again.',
91
- validation: 'Review the input parameters and try with different values.',
92
- config: 'Check .prjct/prjct.config.json for issues. Try /p:init to reinitialize.',
93
- unknown: 'Can you check the issue manually and provide more context?',
94
- }
95
-
96
- return suggestions[errorPattern.type] || suggestions.unknown
97
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * Hallucination Detection
3
- * ANTI-HALLUCINATION: Patterns that indicate Claude may be hallucinating
4
- */
5
-
6
- import type { HallucinationPattern, HallucinationResult } from './types'
7
-
8
- /**
9
- * ANTI-HALLUCINATION: Patterns that indicate Claude may be hallucinating
10
- * These patterns detect contradictory or impossible statements
11
- */
12
- export const HALLUCINATION_PATTERNS: HallucinationPattern[] = [
13
- // Contradictory file operations
14
- { pattern: /file.*not found.*created/i, type: 'contradiction', description: 'Claims file created but also not found' },
15
- { pattern: /created.*but.*error/i, type: 'contradiction', description: 'Claims success but also error' },
16
- { pattern: /successfully.*failed/i, type: 'contradiction', description: 'Contradictory success/failure' },
17
-
18
- // Impossible task states
19
- {
20
- pattern: /already.*completed.*completing/i,
21
- type: 'state',
22
- description: 'Completing already-completed task',
23
- },
24
- { pattern: /no task.*marking complete/i, type: 'state', description: 'Completing non-existent task' },
25
- { pattern: /no.*active.*done with/i, type: 'state', description: 'Finishing task that doesnt exist' },
26
-
27
- // Invented data
28
- {
29
- pattern: /version.*updated.*no package/i,
30
- type: 'invented',
31
- description: 'Version update without package.json',
32
- },
33
- { pattern: /committed.*nothing to commit/i, type: 'invented', description: 'Commit without changes' },
34
- { pattern: /pushed.*no remote/i, type: 'invented', description: 'Push without remote' },
35
- ]
36
-
37
- /**
38
- * Get suggestion for handling detected hallucination
39
- */
40
- export function getHallucinationSuggestion(type: string): string {
41
- const suggestions: Record<string, string> = {
42
- contradiction: 'Verify file/resource state before reporting. Use Read tool to check actual state.',
43
- state: 'Check current task state from now.md before assuming completion.',
44
- invented: 'Verify prerequisites exist (package.json, git remote) before claiming actions.',
45
- }
46
- return suggestions[type] || 'Verify actual state before proceeding.'
47
- }
48
-
49
- /**
50
- * Detect potential hallucination patterns in output
51
- */
52
- export function detectHallucination(output: string): HallucinationResult {
53
- if (!output || typeof output !== 'string') {
54
- return { detected: false }
55
- }
56
-
57
- for (const { pattern, type, description } of HALLUCINATION_PATTERNS) {
58
- if (pattern.test(output)) {
59
- return {
60
- detected: true,
61
- type,
62
- pattern: pattern.source,
63
- description,
64
- message: `Potential hallucination detected: ${description}`,
65
- suggestion: getHallucinationSuggestion(type),
66
- }
67
- }
68
- }
69
-
70
- return { detected: false }
71
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Loop Detection & User Escalation
3
- *
4
- * Detects when commands are failing repeatedly and escalates to user
5
- * instead of continuing in infinite loops.
6
- *
7
- * OPTIMIZATION (P2.4): Loop Detection
8
- * - Track attempt counts per command/error type
9
- * - Auto-escalate after 3 failed attempts
10
- * - Provide specific help based on error patterns
11
- *
12
- * ANTI-HALLUCINATION: Detects contradictory/impossible outputs
13
- * - Patterns that indicate Claude is hallucinating (saying file exists when it doesn't)
14
- * - Contradictory statements in same response
15
- * - Completing tasks that were never started
16
- *
17
- * Source: Augment Code pattern
18
- * "If you notice yourself going around in circles... ask the user for help"
19
- */
20
-
21
- export type {
22
- ErrorEntry,
23
- AttemptRecord,
24
- ErrorPattern,
25
- EscalationInfo,
26
- AttemptResult,
27
- AttemptInfo,
28
- HallucinationPattern,
29
- HallucinationResult,
30
- OutputAnalysis,
31
- } from './types'
32
-
33
- export { HALLUCINATION_PATTERNS, detectHallucination, getHallucinationSuggestion } from './hallucination'
34
- export { isSimilarError, analyzeErrorPattern, generateEscalationMessage, generateSuggestion } from './error-analysis'
35
- export { LoopDetector } from './loop-detector'
36
-
37
- import { LoopDetector } from './loop-detector'
38
-
39
- // Singleton instance
40
- const loopDetector = new LoopDetector()
41
- export default loopDetector
@@ -1,222 +0,0 @@
1
- /**
2
- * Loop Detector Class
3
- * Core loop detection and user escalation functionality
4
- */
5
-
6
- import type { AttemptRecord, EscalationInfo, AttemptResult, AttemptInfo, OutputAnalysis } from './types'
7
- import { isSimilarError, analyzeErrorPattern, generateEscalationMessage, generateSuggestion } from './error-analysis'
8
- import { detectHallucination } from './hallucination'
9
-
10
- export class LoopDetector {
11
- private _attempts: Map<string, AttemptRecord>
12
- private _errorPatterns: Map<string, unknown>
13
- maxAttempts: number
14
- sessionTimeout: number
15
-
16
- constructor() {
17
- // Track attempts per command session
18
- this._attempts = new Map()
19
-
20
- // Track error patterns
21
- this._errorPatterns = new Map()
22
-
23
- // Configuration
24
- this.maxAttempts = 3
25
- this.sessionTimeout = 5 * 60 * 1000 // 5 minutes
26
- }
27
-
28
- /**
29
- * Generate a unique key for tracking attempts
30
- */
31
- private _getKey(command: string, context: string = ''): string {
32
- return `${command}:${context}`.toLowerCase()
33
- }
34
-
35
- /**
36
- * Record an attempt for a command
37
- */
38
- recordAttempt(command: string, context: string = '', result: AttemptResult = {}): AttemptInfo {
39
- const key = this._getKey(command, context)
40
- const now = Date.now()
41
-
42
- // Get or create attempt record
43
- let record = this._attempts.get(key)
44
-
45
- if (!record || now - record.lastAttempt > this.sessionTimeout) {
46
- // New session or timed out
47
- record = {
48
- command,
49
- context,
50
- attempts: 0,
51
- errors: [],
52
- firstAttempt: now,
53
- lastAttempt: now,
54
- success: false,
55
- }
56
- }
57
-
58
- // Update record
59
- record.attempts++
60
- record.lastAttempt = now
61
- record.success = result.success || false
62
-
63
- if (result.error) {
64
- record.errors.push({
65
- message: result.error,
66
- timestamp: now,
67
- })
68
- }
69
-
70
- this._attempts.set(key, record)
71
-
72
- return {
73
- attemptNumber: record.attempts,
74
- isLooping: this.isLooping(command, context),
75
- shouldEscalate: this.shouldEscalate(command, context),
76
- }
77
- }
78
-
79
- /**
80
- * Check if a command is in a loop (repeated failures)
81
- */
82
- isLooping(command: string, context: string = ''): boolean {
83
- const key = this._getKey(command, context)
84
- const record = this._attempts.get(key)
85
-
86
- if (!record) return false
87
-
88
- // Check if multiple failures with same error
89
- if (record.attempts >= 2 && !record.success) {
90
- const recentErrors = record.errors.slice(-3)
91
- if (recentErrors.length >= 2) {
92
- // Check if errors are similar
93
- const firstError = recentErrors[0]?.message || ''
94
- const sameError = recentErrors.every((e) => isSimilarError(e.message, firstError))
95
- return sameError
96
- }
97
- }
98
-
99
- return false
100
- }
101
-
102
- /**
103
- * Check if we should escalate to user
104
- */
105
- shouldEscalate(command: string, context: string = ''): boolean {
106
- const key = this._getKey(command, context)
107
- const record = this._attempts.get(key)
108
-
109
- if (!record) return false
110
-
111
- // Escalate after max attempts without success
112
- return record.attempts >= this.maxAttempts && !record.success
113
- }
114
-
115
- /**
116
- * Get escalation message for user
117
- */
118
- getEscalationInfo(command: string, context: string = ''): EscalationInfo | null {
119
- const key = this._getKey(command, context)
120
- const record = this._attempts.get(key)
121
-
122
- if (!record) {
123
- return null
124
- }
125
-
126
- // Analyze error pattern
127
- const errorPattern = analyzeErrorPattern(record.errors)
128
-
129
- return {
130
- status: 'BLOCKED',
131
- command,
132
- context,
133
- attempts: record.attempts,
134
- duration: record.lastAttempt - record.firstAttempt,
135
- errorPattern,
136
- message: generateEscalationMessage(command, errorPattern, this.maxAttempts),
137
- suggestion: generateSuggestion(errorPattern),
138
- lastError: record.errors[record.errors.length - 1]?.message || null,
139
- }
140
- }
141
-
142
- /**
143
- * Mark a command as successful (resets tracking)
144
- */
145
- recordSuccess(command: string, context: string = ''): void {
146
- const key = this._getKey(command, context)
147
- const record = this._attempts.get(key)
148
-
149
- if (record) {
150
- record.success = true
151
- record.attempts = 0
152
- record.errors = []
153
- this._attempts.set(key, record)
154
- }
155
- }
156
-
157
- /**
158
- * Clear all tracking for a command
159
- */
160
- clearTracking(command: string, context: string = ''): void {
161
- const key = this._getKey(command, context)
162
- this._attempts.delete(key)
163
- }
164
-
165
- /**
166
- * Clear all tracking data
167
- */
168
- clearAll(): void {
169
- this._attempts.clear()
170
- this._errorPatterns.clear()
171
- }
172
-
173
- /**
174
- * Get statistics for debugging
175
- */
176
- getStats(): { activeTracking: number; commands: Record<string, unknown> } {
177
- const stats: { activeTracking: number; commands: Record<string, unknown> } = {
178
- activeTracking: this._attempts.size,
179
- commands: {},
180
- }
181
-
182
- for (const [key, record] of this._attempts) {
183
- stats.commands[key] = {
184
- attempts: record.attempts,
185
- success: record.success,
186
- errorCount: record.errors.length,
187
- }
188
- }
189
-
190
- return stats
191
- }
192
-
193
- /**
194
- * ANTI-HALLUCINATION: Detect potential hallucination patterns in output
195
- */
196
- detectHallucination(output: string) {
197
- return detectHallucination(output)
198
- }
199
-
200
- /**
201
- * Analyze output and record if hallucination detected
202
- */
203
- analyzeOutput(command: string, output: string): OutputAnalysis {
204
- const hallucination = this.detectHallucination(output)
205
-
206
- if (hallucination.detected) {
207
- // Record as a special type of error
208
- this.recordAttempt(command, 'hallucination', {
209
- success: false,
210
- error: `HALLUCINATION: ${hallucination.description}`,
211
- })
212
-
213
- return {
214
- ...hallucination,
215
- shouldBlock: true,
216
- action: 'VERIFY_STATE',
217
- }
218
- }
219
-
220
- return { detected: false, shouldBlock: false }
221
- }
222
- }
@@ -1,66 +0,0 @@
1
- /**
2
- * Loop Detector Types
3
- */
4
-
5
- export interface ErrorEntry {
6
- message: string
7
- timestamp: number
8
- }
9
-
10
- export interface AttemptRecord {
11
- command: string
12
- context: string
13
- attempts: number
14
- errors: ErrorEntry[]
15
- firstAttempt: number
16
- lastAttempt: number
17
- success: boolean
18
- }
19
-
20
- export interface ErrorPattern {
21
- type: string
22
- description: string
23
- }
24
-
25
- export interface EscalationInfo {
26
- status: string
27
- command: string
28
- context: string
29
- attempts: number
30
- duration: number
31
- errorPattern: ErrorPattern
32
- message: string
33
- suggestion: string
34
- lastError: string | null
35
- }
36
-
37
- export interface AttemptResult {
38
- success?: boolean
39
- error?: string
40
- }
41
-
42
- export interface AttemptInfo {
43
- attemptNumber: number
44
- isLooping: boolean
45
- shouldEscalate: boolean
46
- }
47
-
48
- export interface HallucinationPattern {
49
- pattern: RegExp
50
- type: string
51
- description: string
52
- }
53
-
54
- export interface HallucinationResult {
55
- detected: boolean
56
- type?: string
57
- pattern?: string
58
- description?: string
59
- message?: string
60
- suggestion?: string
61
- }
62
-
63
- export interface OutputAnalysis extends HallucinationResult {
64
- shouldBlock: boolean
65
- action?: string
66
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * History - Tier 3
3
- * Append-only JSONL audit log.
4
- */
5
-
6
- import fs from 'fs/promises'
7
- import path from 'path'
8
- import pathManager from '../../infrastructure/path-manager'
9
- import type { HistoryEntry } from './types'
10
-
11
- export class HistoryStore {
12
- private _getSessionPath(projectId: string): string {
13
- const now = new Date()
14
- const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
15
- const day = now.toISOString().split('T')[0]
16
-
17
- return path.join(pathManager.getGlobalProjectPath(projectId), 'memory', 'sessions', yearMonth, `${day}.jsonl`)
18
- }
19
-
20
- async appendHistory(projectId: string, entry: Record<string, unknown>): Promise<void> {
21
- const sessionPath = this._getSessionPath(projectId)
22
- await fs.mkdir(path.dirname(sessionPath), { recursive: true })
23
-
24
- const logEntry: HistoryEntry = {
25
- ts: new Date().toISOString(),
26
- type: entry.type as string,
27
- ...entry,
28
- }
29
-
30
- await fs.appendFile(sessionPath, JSON.stringify(logEntry) + '\n', 'utf-8')
31
- }
32
-
33
- async getRecentHistory(projectId: string, limit: number = 20): Promise<HistoryEntry[]> {
34
- try {
35
- const sessionPath = this._getSessionPath(projectId)
36
- const content = await fs.readFile(sessionPath, 'utf-8')
37
- const lines = content.trim().split('\n').filter(Boolean)
38
-
39
- return lines
40
- .slice(-limit)
41
- .map((line) => {
42
- try {
43
- return JSON.parse(line)
44
- } catch {
45
- return null
46
- }
47
- })
48
- .filter((entry): entry is HistoryEntry => entry !== null)
49
- } catch {
50
- return []
51
- }
52
- }
53
- }