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
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Skill Loader for Agentic System
3
+ *
4
+ * Integrates skills into the prompt builder and context system.
5
+ * Formats skills for Claude consumption.
6
+ *
7
+ * @version 1.0.0
8
+ */
9
+
10
+ import skillService from '../services/skill-service'
11
+ import type { Skill, FormattedSkill, SkillContext } from '../types'
12
+
13
+ /**
14
+ * Format a skill for inclusion in prompts
15
+ */
16
+ function formatSkillForPrompt(skill: Skill): FormattedSkill {
17
+ return {
18
+ id: skill.id,
19
+ name: skill.name,
20
+ description: skill.description,
21
+ prompt: skill.content,
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Generate markdown listing of available skills
27
+ */
28
+ function generateSkillsMarkdown(skills: Skill[]): string {
29
+ if (skills.length === 0) {
30
+ return ''
31
+ }
32
+
33
+ const lines: string[] = [
34
+ '## Available Skills',
35
+ '',
36
+ 'The following skills can be invoked:',
37
+ '',
38
+ ]
39
+
40
+ for (const skill of skills) {
41
+ lines.push(`- **${skill.name}** (\`${skill.id}\`): ${skill.description || 'No description'}`)
42
+ if (skill.metadata.agent) {
43
+ lines.push(` - Agent: ${skill.metadata.agent}`)
44
+ }
45
+ if (skill.metadata.tags?.length) {
46
+ lines.push(` - Tags: ${skill.metadata.tags.join(', ')}`)
47
+ }
48
+ }
49
+
50
+ return lines.join('\n')
51
+ }
52
+
53
+ /**
54
+ * Load skills and prepare context for prompts
55
+ */
56
+ export async function loadSkillContext(projectPath?: string): Promise<SkillContext> {
57
+ const skills = await skillService.getAll(projectPath)
58
+
59
+ return {
60
+ availableSkills: skills.map(formatSkillForPrompt),
61
+ skillsMarkdown: generateSkillsMarkdown(skills),
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Get a specific skill's prompt content
67
+ */
68
+ export async function getSkillPrompt(skillId: string, projectPath?: string): Promise<string | null> {
69
+ const skill = await skillService.get(skillId, projectPath)
70
+ return skill?.content || null
71
+ }
72
+
73
+ /**
74
+ * Find best matching skill for a query
75
+ */
76
+ export async function findBestSkill(query: string, projectPath?: string): Promise<Skill | null> {
77
+ const results = await skillService.search(query, projectPath)
78
+ return results[0]?.skill || null
79
+ }
80
+
81
+ /**
82
+ * Format skill invocation result
83
+ */
84
+ export function formatSkillResult(skill: Skill, result: string): string {
85
+ return [
86
+ `## Skill Executed: ${skill.name}`,
87
+ '',
88
+ result,
89
+ '',
90
+ `---`,
91
+ `*Skill: ${skill.id} | Source: ${skill.source}*`,
92
+ ].join('\n')
93
+ }
94
+
95
+ /**
96
+ * Build skill section for system prompt
97
+ */
98
+ export async function buildSkillSystemPrompt(projectPath?: string): Promise<string> {
99
+ const { availableSkills, skillsMarkdown } = await loadSkillContext(projectPath)
100
+
101
+ if (availableSkills.length === 0) {
102
+ return ''
103
+ }
104
+
105
+ return [
106
+ '<skills>',
107
+ skillsMarkdown,
108
+ '',
109
+ 'To invoke a skill, use the skill ID.',
110
+ '</skills>',
111
+ ].join('\n')
112
+ }
@@ -10,127 +10,30 @@
10
10
 
11
11
  import { agentPerformanceTracker } from '../agents'
12
12
  import { outcomeAnalyzer } from '../outcomes'
13
- import type { TaskType } from '../agents/types'
14
-
15
- // Local interface for context state
16
- interface ProjectState {
17
- projectId: string
18
- currentTask: { description: string; startedAt: string } | null
19
- queue: { description: string; priority: string }[]
20
- }
21
-
22
- /**
23
- * Context domain for filtering.
24
- */
25
- export type ContextDomain = 'frontend' | 'backend' | 'devops' | 'docs' | 'testing' | 'general'
26
-
27
- /**
28
- * Full context available before filtering.
29
- */
30
- export interface FullContext {
31
- /** Project state */
32
- state: ProjectState | null
33
-
34
- /** Available agents */
35
- agents: AgentInfo[]
36
-
37
- /** Roadmap features */
38
- roadmap: FeatureInfo[]
39
-
40
- /** Detected patterns */
41
- patterns: PatternInfo[]
42
-
43
- /** Tech stack info */
44
- stack: StackInfo
45
-
46
- /** File list */
47
- files: string[]
48
-
49
- /** Project path */
50
- projectPath: string
51
- }
52
-
53
- /**
54
- * Filtered context optimized for a task domain.
55
- */
56
- export interface FilteredContext {
57
- /** Filtered agents (domain-relevant only) */
58
- agents: AgentInfo[]
59
-
60
- /** Filtered roadmap (related features only) */
61
- roadmap: FeatureInfo[]
62
-
63
- /** Filtered patterns (domain-relevant only) */
64
- patterns: PatternInfo[]
65
-
66
- /** Filtered stack info */
67
- stack: Partial<StackInfo>
68
-
69
- /** Filtered files */
70
- files: string[]
71
-
72
- /** Filtering metrics */
73
- metrics: FilterMetrics
74
- }
75
-
76
- /**
77
- * Agent info for context.
78
- */
79
- export interface AgentInfo {
80
- name: string
81
- domain: ContextDomain
82
- skills: string[]
83
- successRate?: number
84
- }
85
-
86
- /**
87
- * Feature info for context.
88
- */
89
- export interface FeatureInfo {
90
- id: string
91
- name: string
92
- relatedTo: ContextDomain[]
93
- status: string
94
- }
95
-
96
- /**
97
- * Pattern info for context.
98
- */
99
- export interface PatternInfo {
100
- description: string
101
- domain: ContextDomain
102
- confidence: number
103
- }
104
-
105
- /**
106
- * Stack info for context.
107
- */
108
- export interface StackInfo {
109
- frontend: string[]
110
- backend: string[]
111
- devops: string[]
112
- database: string[]
113
- testing: string[]
114
- }
115
-
116
- /**
117
- * Filtering metrics.
118
- */
119
- export interface FilterMetrics {
120
- originalSize: number
121
- filteredSize: number
122
- reductionPercent: number
123
- domain: ContextDomain
124
- }
125
-
126
- /**
127
- * Domain detection result.
128
- */
129
- interface DomainAnalysis {
130
- primary: ContextDomain
131
- secondary: ContextDomain[]
132
- confidence: number
133
- }
13
+ import type { TaskType } from '../types'
14
+ import type {
15
+ ContextDomain,
16
+ SmartContextProjectState,
17
+ FullContext,
18
+ FilteredContext,
19
+ StackInfo,
20
+ DomainAnalysis,
21
+ } from '../types'
22
+
23
+ // Re-export types for convenience
24
+ export type {
25
+ ContextDomain,
26
+ FullContext,
27
+ FilteredContext,
28
+ AgentInfo,
29
+ FeatureInfo,
30
+ PatternInfo,
31
+ StackInfo,
32
+ FilterMetrics,
33
+ } from '../types'
34
+
35
+ // Local type alias for backward compatibility
36
+ type ProjectState = SmartContextProjectState
134
37
 
135
38
  /**
136
39
  * SmartContext - Intelligent context filtering.
@@ -171,7 +74,19 @@ class SmartContext {
171
74
 
172
75
  // Testing indicators
173
76
  const testingKeywords = [
174
- 'test', 'spec', 'jest', 'vitest', 'mocha', 'cypress', 'playwright',
77
+ 'test', 'spec',
78
+ // JS/TS
79
+ 'bun', 'bun test', 'jest', 'mocha', 'cypress', 'playwright',
80
+ // Python
81
+ 'pytest', 'unittest',
82
+ // Go
83
+ 'go test',
84
+ // Rust
85
+ 'cargo test',
86
+ // .NET
87
+ 'dotnet test',
88
+ // Java
89
+ 'mvn test', 'gradle test', 'gradlew test',
175
90
  'e2e', 'unit', 'integration', 'coverage', 'mock', 'fixture'
176
91
  ]
177
92
 
@@ -3,153 +3,110 @@
3
3
  * Loads and parses command templates with frontmatter.
4
4
  *
5
5
  * @module agentic/template-loader
6
- * @version 1.0.0
7
6
  */
8
7
 
9
8
  import fs from 'fs/promises'
10
9
  import path from 'path'
11
10
  import { TemplateError } from '../errors'
11
+ import type { Frontmatter, ParsedTemplate } from '../types'
12
12
 
13
- interface Frontmatter {
14
- name?: string
15
- description?: string
16
- 'allowed-tools'?: string[]
17
- [key: string]: string | string[] | undefined
18
- }
13
+ // ============ Module State (LRU Cache) ============
14
+
15
+ const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates', 'commands')
16
+ const MAX_CACHE_SIZE = 50
17
+
18
+ const cache = new Map<string, ParsedTemplate>()
19
+ const cacheOrder: string[] = []
20
+
21
+ // ============ Cache Helpers ============
19
22
 
20
- interface ParsedTemplate {
21
- frontmatter: Frontmatter
22
- content: string
23
+ function updateLruOrder(key: string): void {
24
+ const index = cacheOrder.indexOf(key)
25
+ if (index > -1) cacheOrder.splice(index, 1)
26
+ cacheOrder.push(key)
23
27
  }
24
28
 
25
- /**
26
- * Loads command templates from templates/commands/ with caching.
27
- * Parses YAML-like frontmatter for metadata extraction.
28
- * Uses LRU cache with size limit to prevent memory leaks.
29
- */
30
- class TemplateLoader {
31
- templatesDir: string
32
- cache: Map<string, ParsedTemplate>
33
- cacheOrder: string[] // Track access order for LRU eviction
34
- maxCacheSize: number
35
-
36
- constructor() {
37
- this.templatesDir = path.join(__dirname, '..', '..', 'templates', 'commands')
38
- this.cache = new Map()
39
- this.cacheOrder = []
40
- this.maxCacheSize = 50 // More than enough for all commands
29
+ function evictLru(): void {
30
+ while (cache.size >= MAX_CACHE_SIZE && cacheOrder.length > 0) {
31
+ const oldest = cacheOrder.shift()
32
+ if (oldest) cache.delete(oldest)
41
33
  }
34
+ }
42
35
 
43
- /**
44
- * Update LRU order - move key to end (most recently used)
45
- */
46
- private updateLruOrder(key: string): void {
47
- const index = this.cacheOrder.indexOf(key)
48
- if (index > -1) {
49
- this.cacheOrder.splice(index, 1)
50
- }
51
- this.cacheOrder.push(key)
52
- }
36
+ // ============ Parsing Functions ============
53
37
 
54
- /**
55
- * Evict least recently used entry if cache exceeds max size
56
- */
57
- private evictLru(): void {
58
- while (this.cache.size >= this.maxCacheSize && this.cacheOrder.length > 0) {
59
- const oldest = this.cacheOrder.shift()
60
- if (oldest) {
61
- this.cache.delete(oldest)
62
- }
63
- }
38
+ export function parseFrontmatter(content: string): ParsedTemplate {
39
+ const frontmatterRegex = /^---\n([\s\S]+?)\n---\n([\s\S]*)$/
40
+ const match = content.match(frontmatterRegex)
41
+
42
+ if (!match) {
43
+ return { frontmatter: {}, content: content.trim() }
64
44
  }
65
45
 
66
- /**
67
- * Load template with frontmatter
68
- */
69
- async load(commandName: string): Promise<ParsedTemplate> {
70
- // Check cache first
71
- if (this.cache.has(commandName)) {
72
- this.updateLruOrder(commandName)
73
- return this.cache.get(commandName)!
74
- }
46
+ const [, frontmatterText, mainContent] = match
47
+ const frontmatter: Frontmatter = {}
75
48
 
76
- const templatePath = path.join(this.templatesDir, `${commandName}.md`)
49
+ frontmatterText.split('\n').forEach((line) => {
50
+ const [key, ...valueParts] = line.split(':')
51
+ if (key && valueParts.length > 0) {
52
+ const value = valueParts.join(':').trim()
77
53
 
78
- try {
79
- const rawContent = await fs.readFile(templatePath, 'utf-8')
80
- const parsed = this.parseFrontmatter(rawContent)
54
+ // Parse arrays
55
+ if (value.startsWith('[') && value.endsWith(']')) {
56
+ frontmatter[key.trim()] = value.slice(1, -1).split(',').map((v) => v.trim())
57
+ } else {
58
+ // Remove quotes if present
59
+ frontmatter[key.trim()] = value.replace(/^["']|["']$/g, '')
60
+ }
61
+ }
62
+ })
81
63
 
82
- // Evict LRU if needed before adding
83
- this.evictLru()
64
+ return { frontmatter, content: mainContent.trim() }
65
+ }
84
66
 
85
- // Cache result
86
- this.cache.set(commandName, parsed)
87
- this.cacheOrder.push(commandName)
67
+ // ============ Main Functions ============
88
68
 
89
- return parsed
90
- } catch {
91
- throw TemplateError.notFound(commandName)
92
- }
69
+ export async function load(commandName: string): Promise<ParsedTemplate> {
70
+ // Check cache first
71
+ if (cache.has(commandName)) {
72
+ updateLruOrder(commandName)
73
+ return cache.get(commandName)!
93
74
  }
94
75
 
95
- /**
96
- * Parse frontmatter from markdown
97
- */
98
- parseFrontmatter(content: string): ParsedTemplate {
99
- const frontmatterRegex = /^---\n([\s\S]+?)\n---\n([\s\S]*)$/
100
- const match = content.match(frontmatterRegex)
101
-
102
- if (!match) {
103
- return {
104
- frontmatter: {},
105
- content: content.trim(),
106
- }
107
- }
76
+ const templatePath = path.join(TEMPLATES_DIR, `${commandName}.md`)
108
77
 
109
- const [, frontmatterText, mainContent] = match
110
- const frontmatter: Frontmatter = {}
111
-
112
- // Parse frontmatter lines
113
- frontmatterText.split('\n').forEach((line) => {
114
- const [key, ...valueParts] = line.split(':')
115
- if (key && valueParts.length > 0) {
116
- const value = valueParts.join(':').trim()
117
-
118
- // Parse arrays
119
- if (value.startsWith('[') && value.endsWith(']')) {
120
- frontmatter[key.trim()] = value
121
- .slice(1, -1)
122
- .split(',')
123
- .map((v) => v.trim())
124
- } else {
125
- // Remove quotes if present
126
- frontmatter[key.trim()] = value.replace(/^["']|["']$/g, '')
127
- }
128
- }
129
- })
78
+ try {
79
+ const rawContent = await fs.readFile(templatePath, 'utf-8')
80
+ const parsed = parseFrontmatter(rawContent)
130
81
 
131
- return {
132
- frontmatter,
133
- content: mainContent.trim(),
134
- }
135
- }
82
+ // Evict LRU if needed before adding
83
+ evictLru()
136
84
 
137
- /**
138
- * Get allowed tools for a command
139
- */
140
- async getAllowedTools(commandName: string): Promise<string[]> {
141
- const template = await this.load(commandName)
142
- return template.frontmatter['allowed-tools'] || []
143
- }
85
+ // Cache result
86
+ cache.set(commandName, parsed)
87
+ cacheOrder.push(commandName)
144
88
 
145
- /**
146
- * Clear cache (useful for testing)
147
- */
148
- clearCache(): void {
149
- this.cache.clear()
89
+ return parsed
90
+ } catch {
91
+ throw TemplateError.notFound(commandName)
150
92
  }
151
93
  }
152
94
 
153
- const templateLoader = new TemplateLoader()
154
- export default templateLoader
155
- export { TemplateLoader }
95
+ export async function getAllowedTools(commandName: string): Promise<string[]> {
96
+ const template = await load(commandName)
97
+ return template.frontmatter['allowed-tools'] || []
98
+ }
99
+
100
+ export function clearCache(): void {
101
+ cache.clear()
102
+ cacheOrder.length = 0
103
+ }
104
+
105
+ // ============ Default Export (backwards compat) ============
106
+
107
+ export default {
108
+ load,
109
+ parseFrontmatter,
110
+ getAllowedTools,
111
+ clearCache
112
+ }
@@ -9,20 +9,14 @@
9
9
  import fs from 'fs/promises'
10
10
  import { exec } from 'child_process'
11
11
  import { promisify } from 'util'
12
+ import type { ToolFunction, ToolRegistryInterface } from '../types'
12
13
 
13
- const execAsync = promisify(exec)
14
-
15
- type ToolFunction = (...args: unknown[]) => Promise<unknown>
14
+ // Re-export types for convenience
15
+ export type { ToolFunction, ToolRegistryInterface } from '../types'
16
16
 
17
- interface ToolRegistry {
18
- tools: Map<string, ToolFunction>
19
- register(name: string, fn: ToolFunction): void
20
- get(name: string): ToolFunction | undefined
21
- isAllowed(name: string, allowedTools: string[]): boolean
22
- list(): string[]
23
- }
17
+ const execAsync = promisify(exec)
24
18
 
25
- const toolRegistry: ToolRegistry = {
19
+ const toolRegistry: ToolRegistryInterface = {
26
20
  tools: new Map(),
27
21
 
28
22
  /**
@@ -25,4 +25,4 @@
25
25
  */
26
26
 
27
27
  export { AgentPerformanceTracker, default as agentPerformanceTracker } from './performance'
28
- export * from './types'
28
+ export * from '../types'
@@ -14,7 +14,7 @@ import type {
14
14
  AgentSuggestion,
15
15
  AgentPerformanceSummary,
16
16
  TaskType,
17
- } from './types'
17
+ } from '../types'
18
18
 
19
19
  const PERFORMANCE_DIR = 'analysis'
20
20
  const PERFORMANCE_FILE = 'agent-performance.json'
@@ -98,7 +98,7 @@ export class AgentPerformanceTracker {
98
98
  perfPath,
99
99
  { agents: [] }
100
100
  )
101
- return data.agents
101
+ return data?.agents ?? []
102
102
  }
103
103
 
104
104
  /**
@@ -127,6 +127,8 @@ export class AgentPerformanceTracker {
127
127
  { agents: [] }
128
128
  )
129
129
 
130
+ if (!data) return
131
+
130
132
  // Find or create agent performance
131
133
  let agentPerf = data.agents.find((a) => a.agentName === record.agentName)
132
134