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.
- package/CHANGELOG.md +64 -12
- package/CLAUDE.md +48 -22
- package/core/agentic/agent-router.ts +15 -0
- package/core/agentic/command-executor.ts +53 -5
- package/core/agentic/prompt-builder.ts +83 -2
- package/core/agentic/template-loader.ts +107 -32
- package/core/commands/command-data.ts +0 -33
- package/core/commands/commands.ts +4 -12
- package/core/commands/registry.ts +0 -37
- package/core/domain/agent-loader.ts +7 -9
- package/core/domain/context-estimator.ts +15 -15
- package/core/index.ts +0 -2
- package/core/infrastructure/config-manager.ts +25 -4
- package/core/infrastructure/setup.ts +0 -99
- package/core/session/session-log-manager.ts +17 -0
- package/core/types/config.ts +1 -1
- package/core/types/index.ts +0 -2
- package/core/types/integrations.ts +2 -47
- package/core/types/storage.ts +0 -8
- package/core/types/task.ts +0 -4
- package/dist/bin/prjct.mjs +341 -316
- package/package.json +1 -1
- package/templates/agentic/subagent-generation.md +14 -1
- package/templates/commands/cleanup.md +15 -74
- package/templates/commands/init.md +1 -44
- package/templates/commands/ship.md +92 -12
- package/templates/commands/sync.md +25 -10
- package/templates/commands/task.md +41 -0
- package/templates/global/CLAUDE.md +196 -25
- package/templates/mcp-config.json +0 -28
- package/core/integrations/notion/client.ts +0 -413
- package/core/integrations/notion/index.ts +0 -46
- package/core/integrations/notion/setup.ts +0 -235
- package/core/integrations/notion/sync.ts +0 -818
- package/core/integrations/notion/templates.ts +0 -246
- package/core/plugin/builtin/notion.ts +0 -178
- package/templates/commands/feature.md +0 -46
- package/templates/commands/now.md +0 -53
- package/templates/hooks/prjct-session-start.sh +0 -50
- package/templates/skills/notion-push.md +0 -116
- package/templates/skills/notion-setup.md +0 -199
- package/templates/skills/notion-sync.md +0 -290
- package/templates/skills/prjct-done/SKILL.md +0 -97
- package/templates/skills/prjct-ship/SKILL.md +0 -150
- package/templates/skills/prjct-sync/SKILL.md +0 -108
- 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.
|
|
3
|
+
## [0.28.2] - 2026-01-10
|
|
4
4
|
|
|
5
|
-
### Feature:
|
|
5
|
+
### Feature: Agent Mentions and Major Cleanup
|
|
6
6
|
|
|
7
|
-
**
|
|
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
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
**
|
|
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
|
-
##
|
|
148
|
+
## AGENT MENTIONS (v0.28)
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
Invoke or reference project agents using the `p.agent.{name}` syntax.
|
|
151
151
|
|
|
152
|
-
###
|
|
152
|
+
### Available Agent IDs
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
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
|
-
###
|
|
166
|
+
### Usage in Prompts
|
|
160
167
|
|
|
161
|
-
|
|
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
|
-
|
|
|
164
|
-
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
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
|
-
|
|
198
|
+
### Usage
|
|
171
199
|
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = ['
|
|
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 = ['
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
const oldest =
|
|
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
|
-
|
|
116
|
+
for (let i = 0; i < lines.length; i++) {
|
|
117
|
+
const line = lines[i]
|
|
50
118
|
const [key, ...valueParts] = line.split(':')
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
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
|
-
//
|
|
83
|
-
|
|
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 (
|
|
7
|
-
* - Workflow (
|
|
8
|
-
* - Planning (
|
|
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 (
|
|
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
|
}
|