prjct-cli 0.28.0 → 0.28.2

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 (46) hide show
  1. package/CHANGELOG.md +64 -12
  2. package/CLAUDE.md +48 -22
  3. package/core/agentic/agent-router.ts +15 -0
  4. package/core/agentic/command-executor.ts +53 -5
  5. package/core/agentic/prompt-builder.ts +83 -2
  6. package/core/agentic/template-loader.ts +107 -32
  7. package/core/commands/command-data.ts +0 -33
  8. package/core/commands/commands.ts +4 -12
  9. package/core/commands/registry.ts +0 -37
  10. package/core/domain/agent-loader.ts +7 -9
  11. package/core/domain/context-estimator.ts +15 -15
  12. package/core/index.ts +0 -2
  13. package/core/infrastructure/config-manager.ts +25 -4
  14. package/core/infrastructure/setup.ts +0 -99
  15. package/core/session/session-log-manager.ts +17 -0
  16. package/core/types/config.ts +1 -1
  17. package/core/types/index.ts +0 -2
  18. package/core/types/integrations.ts +2 -47
  19. package/core/types/storage.ts +0 -8
  20. package/core/types/task.ts +0 -4
  21. package/dist/bin/prjct.mjs +341 -316
  22. package/package.json +1 -1
  23. package/templates/agentic/subagent-generation.md +14 -1
  24. package/templates/commands/cleanup.md +15 -74
  25. package/templates/commands/init.md +1 -44
  26. package/templates/commands/ship.md +92 -12
  27. package/templates/commands/sync.md +25 -10
  28. package/templates/commands/task.md +41 -0
  29. package/templates/global/CLAUDE.md +196 -25
  30. package/templates/mcp-config.json +0 -28
  31. package/core/integrations/notion/client.ts +0 -413
  32. package/core/integrations/notion/index.ts +0 -46
  33. package/core/integrations/notion/setup.ts +0 -235
  34. package/core/integrations/notion/sync.ts +0 -818
  35. package/core/integrations/notion/templates.ts +0 -246
  36. package/core/plugin/builtin/notion.ts +0 -178
  37. package/templates/commands/feature.md +0 -46
  38. package/templates/commands/now.md +0 -53
  39. package/templates/hooks/prjct-session-start.sh +0 -50
  40. package/templates/skills/notion-push.md +0 -116
  41. package/templates/skills/notion-setup.md +0 -199
  42. package/templates/skills/notion-sync.md +0 -290
  43. package/templates/skills/prjct-done/SKILL.md +0 -97
  44. package/templates/skills/prjct-ship/SKILL.md +0 -150
  45. package/templates/skills/prjct-sync/SKILL.md +0 -108
  46. package/templates/skills/prjct-task/SKILL.md +0 -101
package/CHANGELOG.md CHANGED
@@ -1,21 +1,73 @@
1
1
  # Changelog
2
2
 
3
- ## [0.28.0] - 2026-01-10
3
+ ## [0.28.2] - 2026-01-10
4
4
 
5
- ### Feature: Claude Code Native Integration
5
+ ### Feature: Agent Mentions and Major Cleanup
6
6
 
7
- **Skills as First-Class Citizens**: prjct commands are now installed as Claude Code Skills for automatic discovery.
7
+ **Agent Mentions (p.agent.{name}):**
8
+ - New `agentId` field in agent frontmatter for identification
9
+ - Users can invoke agents in prompts: `p.agent.backend help me...`
10
+ - Format: `p.agent.workflow`, `p.agent.planner`, `p.agent.frontend`, etc.
8
11
 
9
- - **SessionStart Hook**: Injects fresh project context at session start via `~/.claude/hooks/prjct-session-start.sh`
10
- - **Skills Auto-Install**: 4 skills installed to `~/.claude/skills/` during `npm install`:
11
- - `prjct-task` - Start tasks with classification
12
- - `prjct-sync` - Analyze codebase, generate agents
13
- - `prjct-done` - Complete current subtask
14
- - `prjct-ship` - Ship with PR + version bump
15
- - **Setup Integration**: `core/infrastructure/setup.ts` now installs hooks and skills automatically
16
- - **Simplified CLAUDE.md**: Reduced global template from 221 to ~50 lines - hooks handle fresh context
12
+ **Deprecated Commands Removed:**
13
+ - `/p:now` - Use `/p:task` instead
14
+ - `/p:feature` - Use `/p:task` instead
15
+ - `/p:work` - Use `/p:task` instead
17
16
 
18
- **Upgrade Impact**: Run `npm install -g prjct-cli` to install hooks and skills.
17
+ **Notion Integration Removed:**
18
+ - Removed entire `core/integrations/notion/` directory
19
+ - Removed Notion skills and MCP config
20
+ - Cleaned up type references
21
+
22
+ **Code Cleanup:**
23
+ - Removed `executeWithoutProject()` deprecated method
24
+ - Cleaned dead code in prompt-builder arrays
25
+ - Removed ARCHITECTURE.md (outdated)
26
+
27
+ **Ship Template Enhancement:**
28
+ - Version bump now REQUIRED before PR creation
29
+ - CHANGELOG.md update now REQUIRED before PR creation
30
+ - Better categorization of changes (Added/Fixed/Changed)
31
+
32
+ ---
33
+
34
+ ## [0.28.1] - 2026-01-10
35
+
36
+ ### Feature: @ Agent Mentions
37
+
38
+ Direct agent invocation via @ notation in tasks.
39
+
40
+ **New Capabilities:**
41
+
42
+ 1. **@ Agent Mentions**
43
+ - `p. task @frontend add button` - Loads frontend specialist directly
44
+ - `p. task @frontend @uxui dark mode` - Loads multiple agents
45
+ - Supports all prjct agents: `@frontend`, `@backend`, `@database`, `@uxui`, `@testing`, `@devops`
46
+
47
+ 2. **@ Claude Subagents**
48
+ - `@explore` - Fast codebase search (Task tool with subagent_type=Explore)
49
+ - `@general` - Complex multi-step research
50
+ - `@plan` - Architecture design and planning
51
+ - Example: `p. task @frontend @explore add button like existing ones`
52
+
53
+ 3. **Performance Improvements**
54
+ - Template cache: O(1) LRU using ES6 Map insertion order
55
+ - Agent loading: Parallel Promise.all() for faster startup
56
+ - Glob searches: Parallel execution in context estimation
57
+
58
+ 4. **Tool Permissions**
59
+ - Templates can specify `tool-permissions` in frontmatter
60
+ - Supports `allow`, `ask`, and `deny` rules per tool
61
+ - Example: `ship.md` now declares safe vs dangerous git commands
62
+
63
+ **Files Changed:**
64
+ - `core/agentic/command-executor.ts` - Parse @ mentions, load mentioned agents
65
+ - `core/agentic/prompt-builder.ts` - Build subagent instructions, tool permissions
66
+ - `core/agentic/template-loader.ts` - LRU cache optimization, tool-permissions parsing
67
+ - `core/agentic/agent-router.ts` - `loadAgentsByMention()` method
68
+ - `core/domain/agent-loader.ts` - Parallel agent loading
69
+ - `core/domain/context-estimator.ts` - Parallel glob execution
70
+ - `templates/commands/task.md` - @ mention documentation
19
71
 
20
72
  ---
21
73
 
package/CLAUDE.md CHANGED
@@ -145,34 +145,60 @@ Next: [suggested action]
145
145
 
146
146
  ---
147
147
 
148
- ## CLAUDE CODE INTEGRATION (v0.28)
148
+ ## AGENT MENTIONS (v0.28)
149
149
 
150
- prjct-cli uses Claude Code's native features for robust integration:
150
+ Invoke or reference project agents using the `p.agent.{name}` syntax.
151
151
 
152
- ### SessionStart Hook
152
+ ### Available Agent IDs
153
153
 
154
- A hook runs at the start of every Claude Code session to inject fresh context:
155
- - Located at: `~/.claude/hooks/prjct-session-start.sh`
156
- - Automatically reads project state and injects into session
157
- - Bypasses CLAUDE.md caching issues
154
+ | Agent ID | Agent File | Purpose |
155
+ |----------|------------|---------|
156
+ | `p.agent.workflow` | `prjct-workflow.md` | Task lifecycle: /p:now, /p:done, /p:next |
157
+ | `p.agent.planner` | `prjct-planner.md` | Planning: /p:feature, /p:idea, /p:spec |
158
+ | `p.agent.shipper` | `prjct-shipper.md` | Shipping: /p:ship |
159
+ | `p.agent.frontend` | `frontend.md` | Frontend/UI work |
160
+ | `p.agent.backend` | `backend.md` | Backend/API work |
161
+ | `p.agent.database` | `database.md` | Database work |
162
+ | `p.agent.testing` | `testing.md` | Testing work |
163
+ | `p.agent.devops` | `devops.md` | DevOps/CI work |
164
+ | `p.agent.uxui` | `uxui.md` | UX/UI design |
158
165
 
159
- ### Skills (Auto-Discovery)
166
+ ### Usage in Prompts
160
167
 
161
- Skills are auto-discovered by Claude Code when relevant:
168
+ ```
169
+ "p.agent.backend help me create a REST endpoint"
170
+ "p.agent.frontend build a login form"
171
+ "p.agent.testing write tests for the auth module"
172
+ ```
173
+
174
+ ### How It Works
175
+
176
+ 1. Each agent has `agentId: p.agent.{name}` in frontmatter
177
+ 2. When user mentions an agent ID, load that agent's context
178
+ 3. Agent provides domain expertise for the task
179
+
180
+ ---
181
+
182
+ ## SKILL INTEGRATION (v0.27)
183
+
184
+ Agents are linked to Claude Code skills from claude-plugins.dev.
185
+
186
+ ### Agent → Skill Mapping
162
187
 
163
- | Skill | Trigger |
164
- |-------|---------|
165
- | `prjct-task` | "p. task", starting work, features/bugs |
166
- | `prjct-sync` | "p. sync", analyze codebase |
167
- | `prjct-done` | "p. done", completing work |
168
- | `prjct-ship` | "p. ship", releasing features |
188
+ | Agent | Skill |
189
+ |-------|-------|
190
+ | `frontend.md` | `frontend-design` |
191
+ | `uxui.md` | `frontend-design` |
192
+ | `backend.md` | `javascript-typescript` |
193
+ | `testing.md` | `developer-kit` |
194
+ | `devops.md` | `developer-kit` |
195
+ | `prjct-planner.md` | `feature-dev` |
196
+ | `prjct-shipper.md` | `code-review` |
169
197
 
170
- Skills location: `~/.claude/skills/prjct-*/SKILL.md`
198
+ ### Usage
171
199
 
172
- ### Setup
200
+ - `p. sync` installs required skills automatically
201
+ - `p. task` invokes skills based on task type
202
+ - Skills config: `{globalPath}/config/skills.json`
173
203
 
174
- All integration is installed automatically via `npm install -g prjct-cli`:
175
- 1. SessionStart hook → `~/.claude/hooks/`
176
- 2. Skills → `~/.claude/skills/`
177
- 3. Commands → `~/.claude/commands/p/`
178
- 4. Settings → `~/.claude/settings.json`
204
+ See `templates/agentic/skill-integration.md` for details.
@@ -104,6 +104,21 @@ class AgentRouter {
104
104
  }
105
105
  }
106
106
 
107
+ /**
108
+ * Load multiple agents by @ mention names
109
+ * @example loadAgentsByMention(["frontend", "uxui"]) -> [Agent, Agent]
110
+ */
111
+ async loadAgentsByMention(mentions: string[]): Promise<Agent[]> {
112
+ const agents: Agent[] = []
113
+ for (const name of mentions) {
114
+ const agent = await this.loadAgent(name)
115
+ if (agent) {
116
+ agents.push(agent)
117
+ }
118
+ }
119
+ return agents
120
+ }
121
+
107
122
  /**
108
123
  * Log agent usage to JSONL file
109
124
  */
@@ -18,6 +18,7 @@ import chainOfThought from './chain-of-thought'
18
18
  import memorySystem from './memory-system'
19
19
  import groundTruth from './ground-truth'
20
20
  import planMode from './plan-mode'
21
+ import AgentRouter from './agent-router'
21
22
 
22
23
  import type {
23
24
  ExecutionResult,
@@ -107,6 +108,18 @@ export class CommandExecutor {
107
108
  }
108
109
 
109
110
  try {
111
+ // 0. Parse @ agent mentions from task input
112
+ const taskInput = (params.task as string) || (params.description as string) || ''
113
+ const { prjctAgents, claudeSubagents, cleanInput } = promptBuilder.parseAgentMentions(taskInput)
114
+
115
+ // Update params with clean input (without @ mentions)
116
+ if (prjctAgents.length > 0 || claudeSubagents.length > 0) {
117
+ if (params.task) params.task = cleanInput
118
+ if (params.description) params.description = cleanInput
119
+ params._mentionedAgents = prjctAgents
120
+ params._claudeSubagents = claudeSubagents
121
+ }
122
+
110
123
  // 1. Load template
111
124
  const template = await templateLoader.load(commandName)
112
125
 
@@ -195,27 +208,62 @@ export class CommandExecutor {
195
208
  )
196
209
  }
197
210
 
198
- // 9. Build prompt - NO agent assignment here, Claude decides via templates
211
+ // 8.5. Load @ mentioned agents if any
212
+ let primaryAgent = null
213
+ if (prjctAgents.length > 0) {
214
+ const agentRouter = new AgentRouter()
215
+ await agentRouter.initialize(projectPath)
216
+ const loadedAgents = await agentRouter.loadAgentsByMention(prjctAgents)
217
+
218
+ if (loadedAgents.length > 0) {
219
+ primaryAgent = {
220
+ name: loadedAgents[0].name,
221
+ content: loadedAgents[0].content,
222
+ role: undefined,
223
+ skills: [],
224
+ }
225
+ console.log(`🤖 Loading agents: ${loadedAgents.map((a) => `@${a.name}`).join(', ')}`)
226
+ }
227
+ }
228
+
229
+ // 9. Build prompt - use mentioned agent or let Claude decide via templates
199
230
  const planInfo = {
200
231
  isPlanning: requiresPlanning || isInPlanningMode,
201
232
  requiresApproval: isDestructive && !params.approved,
202
233
  active: activePlan,
203
234
  allowedTools: planMode.getAllowedTools(isInPlanningMode, template.frontmatter['allowed-tools'] || []),
204
235
  }
205
- // Agent is null - Claude assigns via Task tool using agent-routing.md
206
- const prompt = promptBuilder.build(
236
+
237
+ // Build base prompt
238
+ let prompt = promptBuilder.build(
207
239
  template,
208
240
  context as Parameters<typeof promptBuilder.build>[1],
209
241
  state,
210
- null,
242
+ primaryAgent,
211
243
  learnedPatterns,
212
244
  null,
213
245
  relevantMemories,
214
246
  planInfo
215
247
  )
216
248
 
249
+ // Add Claude subagent instructions if @ mentioned
250
+ if (claudeSubagents.length > 0) {
251
+ const subagentInstructions = promptBuilder.buildSubagentInstructions(claudeSubagents)
252
+ prompt = prompt + subagentInstructions
253
+ console.log(`🔧 Claude subagents requested: ${claudeSubagents.map((s) => `@${s}`).join(', ')}`)
254
+ }
255
+
256
+ // Add tool permissions from template if present
257
+ const toolPermissions = template.frontmatter['tool-permissions'] as Record<string, { allow?: string[]; ask?: string[]; deny?: string[] }> | undefined
258
+ if (toolPermissions) {
259
+ const permissionsSection = promptBuilder.buildToolPermissions(toolPermissions)
260
+ prompt = prompt + permissionsSection
261
+ }
262
+
217
263
  // Log agentic mode
218
- console.log(`🤖 Agentic delegation enabled - Claude will assign agent via Task tool`)
264
+ if (!primaryAgent) {
265
+ console.log(`🤖 Agentic delegation enabled - Claude will assign agent via Task tool`)
266
+ }
219
267
 
220
268
  // Record successful attempt
221
269
  loopDetector.recordSuccess(commandName, loopContext)
@@ -283,7 +283,7 @@ class PromptBuilder {
283
283
 
284
284
  // Agent assignment (CONDITIONAL - only for code-modifying commands)
285
285
  const commandName = template.frontmatter?.name?.replace('p:', '') || ''
286
- const agentCommands = ['now', 'build', 'feature', 'design', 'fix', 'bug', 'test', 'work', 'cleanup', 'spec']
286
+ const agentCommands = ['task', 'design', 'fix', 'bug', 'test', 'cleanup', 'spec']
287
287
  const needsAgent = agentCommands.includes(commandName)
288
288
 
289
289
  if (agent && needsAgent) {
@@ -332,7 +332,7 @@ class PromptBuilder {
332
332
  }
333
333
 
334
334
  // OPTIMIZED: Only include patterns for code-modifying commands
335
- const codeCommands = ['now', 'build', 'feature', 'design', 'cleanup', 'fix', 'bug', 'test', 'init', 'spec', 'work']
335
+ const codeCommands = ['task', 'design', 'cleanup', 'fix', 'bug', 'test', 'init', 'spec']
336
336
  const needsPatterns = codeCommands.includes(commandName)
337
337
 
338
338
  // Include code patterns analysis for code-modifying commands
@@ -498,6 +498,87 @@ class PromptBuilder {
498
498
  return result || null
499
499
  }
500
500
 
501
+ /**
502
+ * Claude Code subagents that can be invoked via @ notation
503
+ */
504
+ private readonly CLAUDE_SUBAGENTS = ['explore', 'general', 'plan']
505
+
506
+ /**
507
+ * Parse @ agent mentions from input
508
+ * Separates prjct agents from Claude Code subagents
509
+ * @example "@frontend @explore add dark mode" -> { prjctAgents: ["frontend"], claudeSubagents: ["explore"], cleanInput: "add dark mode" }
510
+ */
511
+ parseAgentMentions(input: string): {
512
+ prjctAgents: string[]
513
+ claudeSubagents: string[]
514
+ cleanInput: string
515
+ } {
516
+ if (!input) return { prjctAgents: [], claudeSubagents: [], cleanInput: '' }
517
+
518
+ const mentionPattern = /@(\w+)/g
519
+ const prjctAgents: string[] = []
520
+ const claudeSubagents: string[] = []
521
+ let match
522
+
523
+ while ((match = mentionPattern.exec(input)) !== null) {
524
+ const name = match[1].toLowerCase()
525
+ if (this.CLAUDE_SUBAGENTS.includes(name)) {
526
+ claudeSubagents.push(name)
527
+ } else {
528
+ prjctAgents.push(name)
529
+ }
530
+ }
531
+
532
+ const cleanInput = input.replace(mentionPattern, '').trim()
533
+ return { prjctAgents, claudeSubagents, cleanInput }
534
+ }
535
+
536
+ /**
537
+ * Build Claude subagent instructions for the prompt
538
+ */
539
+ buildSubagentInstructions(claudeSubagents: string[]): string {
540
+ if (!claudeSubagents.length) return ''
541
+
542
+ const instructions = claudeSubagents.map((sub) => {
543
+ switch (sub) {
544
+ case 'explore':
545
+ return '- USE Task tool with subagent_type=Explore for fast codebase exploration'
546
+ case 'general':
547
+ return '- USE Task tool with subagent_type=general-purpose for complex multi-step research'
548
+ case 'plan':
549
+ return '- USE Task tool with subagent_type=Plan for architecture design'
550
+ default:
551
+ return null
552
+ }
553
+ }).filter(Boolean)
554
+
555
+ if (!instructions.length) return ''
556
+
557
+ return `
558
+ ## CLAUDE SUBAGENT INSTRUCTIONS
559
+ ${instructions.join('\n')}
560
+ `
561
+ }
562
+
563
+ /**
564
+ * Build tool permissions section from template frontmatter
565
+ */
566
+ buildToolPermissions(toolPermissions: Record<string, { allow?: string[]; ask?: string[]; deny?: string[] }> | null): string {
567
+ if (!toolPermissions) return ''
568
+
569
+ const parts: string[] = ['\n## TOOL PERMISSIONS\n', 'Check these BEFORE executing:\n']
570
+
571
+ for (const [tool, rules] of Object.entries(toolPermissions)) {
572
+ parts.push(`\n**${tool}:**`)
573
+ if (rules.allow?.length) parts.push(`- ALLOW: ${rules.allow.join(', ')}`)
574
+ if (rules.ask?.length) parts.push(`- ASK USER: ${rules.ask.join(', ')}`)
575
+ if (rules.deny?.length) parts.push(`- NEVER: ${rules.deny.join(', ')}`)
576
+ }
577
+
578
+ parts.push('\n⚠️ DENY = Never execute. ASK = Confirm with user first.\n')
579
+ return parts.join('\n')
580
+ }
581
+
501
582
  /**
502
583
  * Build critical anti-hallucination rules section
503
584
  */
@@ -15,26 +15,92 @@ import type { Frontmatter, ParsedTemplate } from '../types'
15
15
  const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates', 'commands')
16
16
  const MAX_CACHE_SIZE = 50
17
17
 
18
+ // Use single Map for O(1) LRU operations (ES6 Maps maintain insertion order)
18
19
  const cache = new Map<string, ParsedTemplate>()
19
- const cacheOrder: string[] = []
20
20
 
21
21
  // ============ Cache Helpers ============
22
22
 
23
- function updateLruOrder(key: string): void {
24
- const index = cacheOrder.indexOf(key)
25
- if (index > -1) cacheOrder.splice(index, 1)
26
- cacheOrder.push(key)
27
- }
23
+ function setWithLru(key: string, value: ParsedTemplate): void {
24
+ // Delete first to move to end when re-adding (most recently used)
25
+ cache.delete(key)
26
+ cache.set(key, value)
28
27
 
29
- function evictLru(): void {
30
- while (cache.size >= MAX_CACHE_SIZE && cacheOrder.length > 0) {
31
- const oldest = cacheOrder.shift()
28
+ // Evict oldest (first item) if over limit
29
+ if (cache.size > MAX_CACHE_SIZE) {
30
+ const oldest = cache.keys().next().value
32
31
  if (oldest) cache.delete(oldest)
33
32
  }
34
33
  }
35
34
 
35
+ function getWithLru(key: string): ParsedTemplate | undefined {
36
+ const value = cache.get(key)
37
+ if (value !== undefined) {
38
+ // Move to end (most recently used)
39
+ cache.delete(key)
40
+ cache.set(key, value)
41
+ }
42
+ return value
43
+ }
44
+
36
45
  // ============ Parsing Functions ============
37
46
 
47
+ /**
48
+ * Parse tool-permissions YAML block
49
+ * Handles nested structure like:
50
+ * tool-permissions:
51
+ * bash:
52
+ * allow: ["git *"]
53
+ * deny: ["rm -rf"]
54
+ */
55
+ function parseToolPermissions(lines: string[], startIndex: number): {
56
+ permissions: Record<string, { allow?: string[]; ask?: string[]; deny?: string[] }>
57
+ endIndex: number
58
+ } {
59
+ const permissions: Record<string, { allow?: string[]; ask?: string[]; deny?: string[] }> = {}
60
+ let i = startIndex
61
+ let currentTool: string | null = null
62
+
63
+ while (i < lines.length) {
64
+ const line = lines[i]
65
+ const trimmed = line.trim()
66
+
67
+ // End if we hit a new top-level key (no leading spaces)
68
+ if (line.length > 0 && !line.startsWith(' ') && !line.startsWith('\t')) {
69
+ break
70
+ }
71
+
72
+ // Tool name (2 spaces indent)
73
+ const toolMatch = line.match(/^ {2}(\w+):$/)
74
+ if (toolMatch) {
75
+ currentTool = toolMatch[1]
76
+ permissions[currentTool] = {}
77
+ i++
78
+ continue
79
+ }
80
+
81
+ // Permission array (4 spaces indent)
82
+ const permMatch = line.match(/^ {4}(allow|ask|deny):\s*\[(.+)\]/)
83
+ if (permMatch && currentTool) {
84
+ const [, permType, arrayContent] = permMatch
85
+ permissions[currentTool][permType as 'allow' | 'ask' | 'deny'] = arrayContent
86
+ .split(',')
87
+ .map((v) => v.trim().replace(/^["']|["']$/g, ''))
88
+ i++
89
+ continue
90
+ }
91
+
92
+ // Skip empty lines within block
93
+ if (trimmed === '') {
94
+ i++
95
+ continue
96
+ }
97
+
98
+ i++
99
+ }
100
+
101
+ return { permissions, endIndex: i }
102
+ }
103
+
38
104
  export function parseFrontmatter(content: string): ParsedTemplate {
39
105
  const frontmatterRegex = /^---\n([\s\S]+?)\n---\n([\s\S]*)$/
40
106
  const match = content.match(frontmatterRegex)
@@ -45,21 +111,35 @@ export function parseFrontmatter(content: string): ParsedTemplate {
45
111
 
46
112
  const [, frontmatterText, mainContent] = match
47
113
  const frontmatter: Frontmatter = {}
114
+ const lines = frontmatterText.split('\n')
48
115
 
49
- frontmatterText.split('\n').forEach((line) => {
116
+ for (let i = 0; i < lines.length; i++) {
117
+ const line = lines[i]
50
118
  const [key, ...valueParts] = line.split(':')
51
- if (key && valueParts.length > 0) {
52
- const value = valueParts.join(':').trim()
53
-
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
- }
119
+
120
+ if (!key || line.startsWith(' ') || line.startsWith('\t')) {
121
+ continue
61
122
  }
62
- })
123
+
124
+ const keyTrimmed = key.trim()
125
+ const value = valueParts.join(':').trim()
126
+
127
+ // Handle tool-permissions nested block
128
+ if (keyTrimmed === 'tool-permissions' && value === '') {
129
+ const { permissions, endIndex } = parseToolPermissions(lines, i + 1)
130
+ frontmatter['tool-permissions'] = permissions
131
+ i = endIndex - 1
132
+ continue
133
+ }
134
+
135
+ // Parse arrays
136
+ if (value.startsWith('[') && value.endsWith(']')) {
137
+ frontmatter[keyTrimmed] = value.slice(1, -1).split(',').map((v) => v.trim())
138
+ } else if (value) {
139
+ // Remove quotes if present
140
+ frontmatter[keyTrimmed] = value.replace(/^["']|["']$/g, '')
141
+ }
142
+ }
63
143
 
64
144
  return { frontmatter, content: mainContent.trim() }
65
145
  }
@@ -67,10 +147,10 @@ export function parseFrontmatter(content: string): ParsedTemplate {
67
147
  // ============ Main Functions ============
68
148
 
69
149
  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)!
150
+ // Check cache first with LRU update
151
+ const cached = getWithLru(commandName)
152
+ if (cached) {
153
+ return cached
74
154
  }
75
155
 
76
156
  const templatePath = path.join(TEMPLATES_DIR, `${commandName}.md`)
@@ -79,12 +159,8 @@ export async function load(commandName: string): Promise<ParsedTemplate> {
79
159
  const rawContent = await fs.readFile(templatePath, 'utf-8')
80
160
  const parsed = parseFrontmatter(rawContent)
81
161
 
82
- // Evict LRU if needed before adding
83
- evictLru()
84
-
85
- // Cache result
86
- cache.set(commandName, parsed)
87
- cacheOrder.push(commandName)
162
+ // Cache with LRU management
163
+ setWithLru(commandName, parsed)
88
164
 
89
165
  return parsed
90
166
  } catch {
@@ -99,7 +175,6 @@ export async function getAllowedTools(commandName: string): Promise<string[]> {
99
175
 
100
176
  export function clearCache(): void {
101
177
  cache.clear()
102
- cacheOrder.length = 0
103
178
  }
104
179
 
105
180
  // ============ Default Export (backwards compat) ============
@@ -62,17 +62,6 @@ export const COMMANDS: CommandMeta[] = [
62
62
  requiresProject: true,
63
63
  features: ['Agentic type classification', '7-phase workflow', 'Git branch management', 'Task breakdown'],
64
64
  },
65
- {
66
- name: 'feature',
67
- group: 'core',
68
- description: 'DEPRECATED - Use /p:task instead',
69
- usage: { claude: '/p:task "<description>"', terminal: 'prjct task "<description>"' },
70
- params: '<description>',
71
- implemented: true,
72
- hasTemplate: true,
73
- requiresProject: true,
74
- deprecated: true,
75
- },
76
65
  {
77
66
  name: 'spec',
78
67
  group: 'core',
@@ -83,28 +72,6 @@ export const COMMANDS: CommandMeta[] = [
83
72
  hasTemplate: true,
84
73
  requiresProject: true,
85
74
  },
86
- {
87
- name: 'now',
88
- group: 'core',
89
- description: 'DEPRECATED - Use /p:task instead',
90
- usage: { claude: '/p:task "<description>"', terminal: 'prjct task "<description>"' },
91
- params: '[task]',
92
- implemented: true,
93
- hasTemplate: true,
94
- requiresProject: true,
95
- deprecated: true,
96
- },
97
- {
98
- name: 'work',
99
- group: 'core',
100
- description: 'DEPRECATED - Use /p:task instead',
101
- usage: { claude: '/p:task "<description>"', terminal: 'prjct task "<description>"' },
102
- params: '[task]',
103
- implemented: true,
104
- hasTemplate: true,
105
- requiresProject: true,
106
- deprecated: true,
107
- },
108
75
  {
109
76
  name: 'pause',
110
77
  group: 'core',
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * MD-First Architecture - All state in Markdown files.
5
5
  *
6
- * COMMANDS (22 total):
7
- * - Workflow (5): work, done, next, pause, resume
8
- * - Planning (5): init, feature, bug, idea, spec
6
+ * COMMANDS (20 total):
7
+ * - Workflow (4): done, next, pause, resume
8
+ * - Planning (4): init, bug, idea, spec
9
9
  * - Shipping (1): ship
10
10
  * - Analytics (2): dash, help
11
- * - Maintenance (5): cleanup, design, recover, undo, redo, history
11
+ * - Maintenance (6): cleanup, design, recover, undo, redo, history
12
12
  * - Analysis (2): analyze, sync
13
13
  * - Setup (3): start, setup, migrateAll
14
14
  */
@@ -69,10 +69,6 @@ class PrjctCommands {
69
69
 
70
70
  // ========== Workflow Commands ==========
71
71
 
72
- async work(task: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
73
- return this.workflow.now(task, projectPath)
74
- }
75
-
76
72
  async done(projectPath: string = process.cwd()): Promise<CommandResult> {
77
73
  return this.workflow.done(projectPath)
78
74
  }
@@ -95,10 +91,6 @@ class PrjctCommands {
95
91
  return this.planning.init(idea, projectPath)
96
92
  }
97
93
 
98
- async feature(description: string, projectPath: string = process.cwd()): Promise<CommandResult> {
99
- return this.planning.feature(description, projectPath)
100
- }
101
-
102
94
  async bug(description: string, projectPath: string = process.cwd()): Promise<CommandResult> {
103
95
  return this.planning.bug(description, projectPath)
104
96
  }