prjct-cli 0.28.2 → 0.28.4

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 (33) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/core/agentic/index.ts +11 -1
  3. package/core/agentic/memory-system.ts +44 -5
  4. package/core/agentic/smart-context.ts +36 -64
  5. package/core/agentic/token-estimator.ts +264 -0
  6. package/core/infrastructure/path-manager.ts +7 -7
  7. package/core/infrastructure/setup.ts +28 -0
  8. package/core/infrastructure/slash-command-registry.ts +176 -0
  9. package/core/types/integrations.ts +28 -1
  10. package/package.json +1 -1
  11. package/templates/agentic/subagent-generation.md +237 -90
  12. package/templates/commands/bug.md +51 -392
  13. package/templates/commands/done.md +53 -232
  14. package/templates/commands/setup-statusline.md +138 -0
  15. package/templates/commands/ship.md +86 -668
  16. package/templates/commands/sync.md +189 -552
  17. package/templates/commands/task.md +50 -276
  18. package/templates/global/CLAUDE.md +101 -161
  19. package/templates/guides/agent-generation.md +164 -0
  20. package/templates/guides/claude-code-ux.md +232 -0
  21. package/templates/guides/integrations.md +149 -0
  22. package/templates/mcp-config.json +23 -18
  23. package/templates/shared/git-operations.md +68 -0
  24. package/templates/shared/io-patterns.md +72 -0
  25. package/templates/shared/standard.md +70 -0
  26. package/templates/shared/validation.md +75 -0
  27. package/CLAUDE.md +0 -204
  28. package/templates/agentic/agents/uxui.md +0 -218
  29. package/templates/subagents/domain/backend.md +0 -106
  30. package/templates/subagents/domain/database.md +0 -118
  31. package/templates/subagents/domain/devops.md +0 -149
  32. package/templates/subagents/domain/frontend.md +0 -100
  33. package/templates/subagents/domain/testing.md +0 -166
package/CHANGELOG.md CHANGED
@@ -1,5 +1,110 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.28.4] - 2026-01-11
4
+
5
+ ### Feature: Claude Code UX Enhancements
6
+
7
+ Rich terminal experience for Claude Code - 100% ToS compliant.
8
+
9
+ **Status Line System:**
10
+ - Custom status line script showing prjct task, git branch, lines changed
11
+ - Context usage bar with color-coded warnings (green/yellow/red)
12
+ - Model icons (🎭 Opus, 📝 Sonnet, 🍃 Haiku)
13
+ - Installed at `~/.prjct-cli/statusline/statusline.sh`
14
+
15
+ **Theme System:**
16
+ - Three built-in themes: default (prjct), gentleman, minimal
17
+ - JSON-based theme configuration
18
+ - ANSI 256 color support
19
+ - Located at `~/.prjct-cli/statusline/themes/`
20
+
21
+ **Output Styles:**
22
+ - `prjct.md` - Concise, actionable output (default)
23
+ - `verbose.md` - Detailed explanations for learning
24
+ - `ship-fast.md` - Minimal output for max velocity
25
+ - Located at `~/.prjct-cli/output-styles/`
26
+
27
+ **Hooks Integration:**
28
+ - `update-metrics.sh` - Tracks tool usage per session
29
+ - `session-summary.sh` - Shows stats when Claude stops
30
+ - Located at `~/.prjct-cli/hooks/`
31
+
32
+ **Visual Theme (tweakcc):**
33
+ - Full Claude Code color theme
34
+ - Spanish thinking verbs
35
+ - Located at `~/.prjct-cli/tweakcc-theme.json`
36
+
37
+ **New Templates:**
38
+ - `templates/commands/setup-statusline.md` - Interactive setup
39
+ - `templates/guides/claude-code-ux.md` - Full documentation
40
+ - Updated `templates/global/CLAUDE.md` with UX section
41
+
42
+ **Template Optimization:**
43
+ - Further reduced template sizes
44
+ - New shared patterns in `templates/shared/`
45
+
46
+ ## [0.28.3] - 2026-01-11
47
+
48
+ ### Feature: Claude Code Synergy Optimization
49
+
50
+ Major improvements to maximize prjct + Claude Code integration efficiency.
51
+
52
+ **Skill Auto-Invocation (Phase 4 of /p:task):**
53
+ - Skills are now automatically invoked when loading agents
54
+ - Agent frontmatter `skills: [skill-name]` triggers `Skill("skill-name")`
55
+ - Example: Loading `frontend.md` auto-activates `/frontend-design`
56
+
57
+ **MCP Auto-Usage:**
58
+ - Agents with `mcp: [context7]` auto-query documentation during tasks
59
+ - Per-project MCP config at `{globalPath}/config/mcp-servers.json`
60
+ - Seamless library documentation lookup during implementation
61
+
62
+ **Think Blocks for Destructive Commands:**
63
+ - `/p:ship` now includes mandatory `<think>` block
64
+ - Pre-ship verification checklist (completeness, quality, git state)
65
+ - Prevents shipping incomplete or broken code
66
+
67
+ **Agent Auto-Refresh in /p:sync:**
68
+ - Detects when dependencies change (package.json, etc.)
69
+ - Regenerates stale agents (>7 days old)
70
+ - Versions previous agents as `.backup` files
71
+ - Logs refresh events to memory
72
+
73
+ **Slash Command Registration:**
74
+ - New `slash-commands.json` config for Claude Code integration
75
+ - Enables command discovery and validation
76
+ - Path: `{globalPath}/config/slash-commands.json`
77
+
78
+ **Token Budget Analysis:**
79
+ - Estimates context token usage during sync
80
+ - Warns if approaching 80% of budget (160k tokens)
81
+ - Auto-summarizes large agents if needed
82
+
83
+ **Client Migration on Update:**
84
+ - Setup now creates missing config files for existing projects
85
+ - `slash-commands.json` auto-created for existing installations
86
+ - Seamless upgrade experience for npm update users
87
+
88
+ **New TypeScript Modules:**
89
+ - `core/infrastructure/slash-command-registry.ts` - Command validation
90
+ - `core/agentic/token-estimator.ts` - Token budget estimation
91
+
92
+ **Template Context Optimization:**
93
+ - Reduced main templates from 3,197 to 2,214 lines (-31%)
94
+ - `sync.md`: 1,602 → 835 lines (-48%)
95
+ - `global/CLAUDE.md`: 429 → 363 lines (-15%)
96
+ - `task.md`: 397 → 336 lines (-15%)
97
+ - `ship.md`: 769 → 680 lines (-12%)
98
+ - New `templates/guides/` for on-demand documentation
99
+ - New `templates/shared/validation.md` for reusable validation
100
+
101
+ **Files Removed (Agentic Generation):**
102
+ - Deleted hardcoded domain agents from `templates/subagents/domain/`
103
+ - Deleted `.mcp.json` (now generated per-project)
104
+ - Deleted `CLAUDE.md` from root (moved to templates/global/)
105
+
106
+ ---
107
+
3
108
  ## [0.28.2] - 2026-01-10
4
109
 
5
110
  ### Feature: Agent Mentions and Major Cleanup
@@ -90,9 +90,19 @@ export { default as toolRegistry } from './tool-registry'
90
90
  export { default as templateLoader } from './template-loader'
91
91
 
92
92
  // ============ Utilities ============
93
- // Chain of thought, services
93
+ // Chain of thought, services, token estimation
94
94
  export { default as chainOfThought } from './chain-of-thought'
95
95
  export { default as services } from './services'
96
+ export {
97
+ default as tokenEstimator,
98
+ estimateTokens,
99
+ getTokenBudget,
100
+ estimateContext,
101
+ filterContext,
102
+ summarizeForTokens,
103
+ createContextSections,
104
+ formatEstimate,
105
+ } from './token-estimator'
96
106
 
97
107
  // ============ Types ============
98
108
  // All types re-exported from ../types (canonical source)
@@ -43,7 +43,6 @@ import type {
43
43
  MemoryDatabase,
44
44
  HistoryEntry,
45
45
  HistoryEventType,
46
- Decision,
47
46
  Workflow,
48
47
  Preference,
49
48
  Patterns,
@@ -269,8 +268,13 @@ export class HistoryStore {
269
268
  /**
270
269
  * Patterns - Tier 2
271
270
  * Persistent learned preferences and decisions.
271
+ * OPTIMIZED: Debounced saves to reduce I/O
272
272
  */
273
273
  export class PatternStore extends CachedStore<Patterns> {
274
+ private _saveTimeout: ReturnType<typeof setTimeout> | null = null
275
+ private _pendingSave: string | null = null
276
+ private static readonly SAVE_DEBOUNCE_MS = 500 // Batch saves within 500ms
277
+
274
278
  protected getFilename(): string {
275
279
  return 'patterns.json'
276
280
  }
@@ -285,6 +289,41 @@ export class PatternStore extends CachedStore<Patterns> {
285
289
  }
286
290
  }
287
291
 
292
+ /**
293
+ * OPTIMIZED: Debounced save - batches multiple updates
294
+ */
295
+ private async debouncedSave(projectId: string): Promise<void> {
296
+ this._pendingSave = projectId
297
+
298
+ if (this._saveTimeout) {
299
+ clearTimeout(this._saveTimeout)
300
+ }
301
+
302
+ return new Promise((resolve) => {
303
+ this._saveTimeout = setTimeout(async () => {
304
+ if (this._pendingSave) {
305
+ await this.save(this._pendingSave)
306
+ this._pendingSave = null
307
+ }
308
+ resolve()
309
+ }, PatternStore.SAVE_DEBOUNCE_MS)
310
+ })
311
+ }
312
+
313
+ /**
314
+ * Force flush any pending saves (call before process exit)
315
+ */
316
+ async flush(projectId: string): Promise<void> {
317
+ if (this._saveTimeout) {
318
+ clearTimeout(this._saveTimeout)
319
+ this._saveTimeout = null
320
+ }
321
+ if (this._pendingSave) {
322
+ await this.save(projectId)
323
+ this._pendingSave = null
324
+ }
325
+ }
326
+
288
327
  // Convenience alias for backward compatibility
289
328
  async loadPatterns(projectId: string): Promise<Patterns> {
290
329
  return this.load(projectId)
@@ -330,7 +369,8 @@ export class PatternStore extends CachedStore<Patterns> {
330
369
  }
331
370
  }
332
371
 
333
- await this.save(projectId)
372
+ // OPTIMIZED: Debounced save instead of immediate
373
+ await this.debouncedSave(projectId)
334
374
  }
335
375
 
336
376
  async getDecision(projectId: string, key: string): Promise<{ value: string; confidence: string } | null> {
@@ -364,13 +404,12 @@ export class PatternStore extends CachedStore<Patterns> {
364
404
  patterns.workflows[workflowName].lastSeen = now
365
405
  }
366
406
 
367
- await this.save(projectId)
407
+ await this.debouncedSave(projectId)
368
408
  }
369
409
 
370
410
  async getWorkflow(projectId: string, workflowName: string): Promise<Workflow | null> {
371
411
  const patterns = await this.load(projectId)
372
412
  const workflow = patterns.workflows[workflowName]
373
-
374
413
  if (!workflow || workflow.count < 3) return null
375
414
  return workflow
376
415
  }
@@ -378,7 +417,7 @@ export class PatternStore extends CachedStore<Patterns> {
378
417
  async setPreference(projectId: string, key: string, value: Preference['value']): Promise<void> {
379
418
  const patterns = await this.load(projectId)
380
419
  patterns.preferences[key] = { value, updatedAt: getTimestamp() }
381
- await this.save(projectId)
420
+ await this.debouncedSave(projectId)
382
421
  }
383
422
 
384
423
  async getPreference(projectId: string, key: string, defaultValue: unknown = null): Promise<unknown> {
@@ -13,7 +13,6 @@ import { outcomeAnalyzer } from '../outcomes'
13
13
  import type { TaskType } from '../types'
14
14
  import type {
15
15
  ContextDomain,
16
- SmartContextProjectState,
17
16
  FullContext,
18
17
  FilteredContext,
19
18
  StackInfo,
@@ -32,8 +31,22 @@ export type {
32
31
  FilterMetrics,
33
32
  } from '../types'
34
33
 
35
- // Local type alias for backward compatibility
36
- type ProjectState = SmartContextProjectState
34
+ /**
35
+ * PERFORMANCE: Pre-built keyword → domain map for O(1) lookup
36
+ * Instead of iterating 5 arrays of keywords, we do a single pass
37
+ */
38
+ const KEYWORD_DOMAIN_MAP: Map<string, ContextDomain> = new Map([
39
+ // Frontend
40
+ ...['ui', 'component', 'react', 'vue', 'angular', 'css', 'style', 'button', 'form', 'modal', 'layout', 'responsive', 'animation', 'dom', 'html', 'frontend', 'fe', 'client', 'browser', 'jsx', 'tsx'].map(k => [k, 'frontend' as ContextDomain] as const),
41
+ // Backend
42
+ ...['api', 'server', 'database', 'db', 'endpoint', 'route', 'handler', 'controller', 'service', 'repository', 'model', 'query', 'backend', 'be', 'rest', 'graphql', 'prisma', 'sql', 'redis', 'auth'].map(k => [k, 'backend' as ContextDomain] as const),
43
+ // DevOps
44
+ ...['deploy', 'docker', 'kubernetes', 'k8s', 'ci', 'cd', 'pipeline', 'terraform', 'ansible', 'aws', 'gcp', 'azure', 'nginx', 'devops', 'infrastructure', 'monitoring', 'logging'].map(k => [k, 'devops' as ContextDomain] as const),
45
+ // Docs
46
+ ...['document', 'docs', 'readme', 'changelog', 'comment', 'jsdoc', 'tutorial', 'guide', 'explain', 'describe', 'markdown'].map(k => [k, 'docs' as ContextDomain] as const),
47
+ // Testing
48
+ ...['test', 'spec', 'jest', 'mocha', 'cypress', 'playwright', 'pytest', 'unittest', 'e2e', 'unit', 'integration', 'coverage', 'mock', 'fixture'].map(k => [k, 'testing' as ContextDomain] as const),
49
+ ])
37
50
 
38
51
  /**
39
52
  * SmartContext - Intelligent context filtering.
@@ -41,63 +54,20 @@ type ProjectState = SmartContextProjectState
41
54
  class SmartContext {
42
55
  /**
43
56
  * Detect the domain of a task from its description.
57
+ * OPTIMIZED: Single-pass O(n) where n = words in description
44
58
  */
45
59
  detectDomain(taskDescription: string): DomainAnalysis {
46
- const lower = taskDescription.toLowerCase()
47
-
48
- // Frontend indicators
49
- const frontendKeywords = [
50
- 'ui', 'component', 'react', 'vue', 'angular', 'css', 'style',
51
- 'button', 'form', 'modal', 'layout', 'responsive', 'animation',
52
- 'dom', 'html', 'frontend', 'fe', 'client', 'browser', 'jsx', 'tsx'
53
- ]
54
-
55
- // Backend indicators
56
- const backendKeywords = [
57
- 'api', 'server', 'database', 'db', 'endpoint', 'route', 'handler',
58
- 'controller', 'service', 'repository', 'model', 'query', 'backend',
59
- 'be', 'rest', 'graphql', 'prisma', 'sql', 'redis', 'auth'
60
- ]
61
-
62
- // DevOps indicators
63
- const devopsKeywords = [
64
- 'deploy', 'docker', 'kubernetes', 'k8s', 'ci', 'cd', 'pipeline',
65
- 'terraform', 'ansible', 'aws', 'gcp', 'azure', 'config', 'nginx',
66
- 'devops', 'infrastructure', 'monitoring', 'logging', 'build'
67
- ]
68
-
69
- // Docs indicators
70
- const docsKeywords = [
71
- 'document', 'docs', 'readme', 'changelog', 'comment', 'jsdoc',
72
- 'tutorial', 'guide', 'explain', 'describe', 'markdown'
73
- ]
74
-
75
- // Testing indicators
76
- const testingKeywords = [
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',
90
- 'e2e', 'unit', 'integration', 'coverage', 'mock', 'fixture'
91
- ]
92
-
93
- // Count matches
60
+ const words = taskDescription.toLowerCase().split(/\s+/)
94
61
  const scores: Record<ContextDomain, number> = {
95
- frontend: frontendKeywords.filter(k => lower.includes(k)).length,
96
- backend: backendKeywords.filter(k => lower.includes(k)).length,
97
- devops: devopsKeywords.filter(k => lower.includes(k)).length,
98
- docs: docsKeywords.filter(k => lower.includes(k)).length,
99
- testing: testingKeywords.filter(k => lower.includes(k)).length,
100
- general: 0,
62
+ frontend: 0, backend: 0, devops: 0, docs: 0, testing: 0, general: 0
63
+ }
64
+
65
+ // Single pass: check each word against the map
66
+ for (const word of words) {
67
+ const domain = KEYWORD_DOMAIN_MAP.get(word)
68
+ if (domain) {
69
+ scores[domain]++
70
+ }
101
71
  }
102
72
 
103
73
  // Find primary and secondary domains
@@ -139,14 +109,16 @@ class SmartContext {
139
109
  agent => relevantDomains.includes(agent.domain)
140
110
  )
141
111
 
142
- // Enrich with performance data
143
- for (const agent of filteredAgents) {
144
- const perf = await agentPerformanceTracker.getAgentPerformance(
145
- projectId,
146
- agent.name
147
- )
112
+ // OPTIMIZED: Enrich with performance data in parallel
113
+ const perfPromises = filteredAgents.map(agent =>
114
+ agentPerformanceTracker.getAgentPerformance(projectId, agent.name)
115
+ )
116
+ const perfResults = await Promise.all(perfPromises)
117
+
118
+ for (let i = 0; i < filteredAgents.length; i++) {
119
+ const perf = perfResults[i]
148
120
  if (perf) {
149
- agent.successRate = perf.successRate
121
+ filteredAgents[i].successRate = perf.successRate
150
122
  }
151
123
  }
152
124
 
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Token Budget Estimator
3
+ * Estimates token usage and provides context filtering for large projects.
4
+ *
5
+ * This prevents context overflow by:
6
+ * - Estimating token count before sending to Claude
7
+ * - Prioritizing critical context over nice-to-have
8
+ * - Truncating or summarizing when needed
9
+ *
10
+ * @version 1.0.0
11
+ */
12
+
13
+ export interface TokenEstimate {
14
+ total: number
15
+ breakdown: {
16
+ projectContext: number
17
+ agentContext: number
18
+ skillContext: number
19
+ userPrompt: number
20
+ systemPrompt: number
21
+ }
22
+ withinBudget: boolean
23
+ recommendations: string[]
24
+ }
25
+
26
+ export interface ContextSection {
27
+ name: string
28
+ content: string
29
+ priority: 'critical' | 'high' | 'medium' | 'low'
30
+ tokens: number
31
+ }
32
+
33
+ /**
34
+ * Approximate tokens per character ratio
35
+ * Claude uses ~4 characters per token on average for English text
36
+ * Code tends to be slightly more efficient (~3.5 chars/token)
37
+ */
38
+ const CHARS_PER_TOKEN = 3.8
39
+
40
+ /**
41
+ * Default token budgets by model
42
+ */
43
+ const TOKEN_BUDGETS = {
44
+ 'claude-3-opus': 200000,
45
+ 'claude-3-sonnet': 200000,
46
+ 'claude-3-haiku': 200000,
47
+ 'claude-3.5-sonnet': 200000,
48
+ 'claude-opus-4': 200000,
49
+ default: 100000, // Conservative default
50
+ }
51
+
52
+ /**
53
+ * Estimate tokens from string content
54
+ */
55
+ export function estimateTokens(content: string): number {
56
+ if (!content) return 0
57
+ return Math.ceil(content.length / CHARS_PER_TOKEN)
58
+ }
59
+
60
+ /**
61
+ * Get token budget for a model
62
+ */
63
+ export function getTokenBudget(model: string = 'default'): number {
64
+ return TOKEN_BUDGETS[model as keyof typeof TOKEN_BUDGETS] || TOKEN_BUDGETS.default
65
+ }
66
+
67
+ /**
68
+ * Estimate total context tokens
69
+ */
70
+ export function estimateContext(sections: ContextSection[]): TokenEstimate {
71
+ const breakdown = {
72
+ projectContext: 0,
73
+ agentContext: 0,
74
+ skillContext: 0,
75
+ userPrompt: 0,
76
+ systemPrompt: 0,
77
+ }
78
+
79
+ let total = 0
80
+
81
+ for (const section of sections) {
82
+ const tokens = estimateTokens(section.content)
83
+ section.tokens = tokens
84
+ total += tokens
85
+
86
+ // Categorize for breakdown
87
+ if (section.name.includes('agent')) {
88
+ breakdown.agentContext += tokens
89
+ } else if (section.name.includes('skill')) {
90
+ breakdown.skillContext += tokens
91
+ } else if (section.name.includes('user') || section.name.includes('prompt')) {
92
+ breakdown.userPrompt += tokens
93
+ } else if (section.name.includes('system')) {
94
+ breakdown.systemPrompt += tokens
95
+ } else {
96
+ breakdown.projectContext += tokens
97
+ }
98
+ }
99
+
100
+ const budget = getTokenBudget()
101
+ const withinBudget = total < budget * 0.8 // Leave 20% buffer for response
102
+
103
+ const recommendations: string[] = []
104
+
105
+ if (!withinBudget) {
106
+ recommendations.push(`Context exceeds safe limit (${total} tokens vs ${Math.floor(budget * 0.8)} budget)`)
107
+
108
+ // Find sections that can be reduced
109
+ const lowPriority = sections.filter(s => s.priority === 'low')
110
+ if (lowPriority.length > 0) {
111
+ const lowTokens = lowPriority.reduce((sum, s) => sum + s.tokens, 0)
112
+ recommendations.push(`Remove low-priority sections to save ~${lowTokens} tokens`)
113
+ }
114
+
115
+ const mediumPriority = sections.filter(s => s.priority === 'medium')
116
+ if (mediumPriority.length > 0) {
117
+ const medTokens = mediumPriority.reduce((sum, s) => sum + s.tokens, 0)
118
+ recommendations.push(`Consider summarizing medium-priority sections (~${medTokens} tokens)`)
119
+ }
120
+ }
121
+
122
+ return {
123
+ total,
124
+ breakdown,
125
+ withinBudget,
126
+ recommendations,
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Filter context to fit within budget
132
+ */
133
+ export function filterContext(
134
+ sections: ContextSection[],
135
+ maxTokens?: number
136
+ ): { filtered: ContextSection[]; removed: string[]; totalTokens: number } {
137
+ const budget = maxTokens || getTokenBudget() * 0.8
138
+
139
+ // Sort by priority (critical first)
140
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }
141
+ const sorted = [...sections].sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority])
142
+
143
+ const filtered: ContextSection[] = []
144
+ const removed: string[] = []
145
+ let totalTokens = 0
146
+
147
+ for (const section of sorted) {
148
+ const sectionTokens = estimateTokens(section.content)
149
+
150
+ if (totalTokens + sectionTokens <= budget) {
151
+ filtered.push({ ...section, tokens: sectionTokens })
152
+ totalTokens += sectionTokens
153
+ } else if (section.priority === 'critical') {
154
+ // Critical sections are always included, even if over budget
155
+ filtered.push({ ...section, tokens: sectionTokens })
156
+ totalTokens += sectionTokens
157
+ } else {
158
+ removed.push(section.name)
159
+ }
160
+ }
161
+
162
+ return { filtered, removed, totalTokens }
163
+ }
164
+
165
+ /**
166
+ * Summarize content to reduce tokens
167
+ */
168
+ export function summarizeForTokens(content: string, targetTokens: number): string {
169
+ const currentTokens = estimateTokens(content)
170
+
171
+ if (currentTokens <= targetTokens) {
172
+ return content
173
+ }
174
+
175
+ // Calculate target character count
176
+ const targetChars = targetTokens * CHARS_PER_TOKEN
177
+
178
+ // Split into lines and take priority lines
179
+ const lines = content.split('\n')
180
+
181
+ // Keep headers and first lines of sections
182
+ const priorityLines: string[] = []
183
+ let charCount = 0
184
+
185
+ for (const line of lines) {
186
+ const isHeader = line.startsWith('#') || line.startsWith('**')
187
+ const isImportant = line.includes('CRITICAL') || line.includes('IMPORTANT') || line.includes('TODO')
188
+
189
+ if (isHeader || isImportant) {
190
+ priorityLines.push(line)
191
+ charCount += line.length + 1
192
+ } else if (charCount < targetChars * 0.8) {
193
+ priorityLines.push(line)
194
+ charCount += line.length + 1
195
+ }
196
+
197
+ if (charCount >= targetChars) {
198
+ break
199
+ }
200
+ }
201
+
202
+ if (priorityLines.length < lines.length) {
203
+ priorityLines.push('')
204
+ priorityLines.push(`[... ${lines.length - priorityLines.length} lines truncated for context limit ...]`)
205
+ }
206
+
207
+ return priorityLines.join('\n')
208
+ }
209
+
210
+ /**
211
+ * Create context sections from project state
212
+ */
213
+ export function createContextSections(
214
+ projectContext: string,
215
+ agentContext: string,
216
+ skillContext: string,
217
+ userPrompt: string
218
+ ): ContextSection[] {
219
+ return [
220
+ { name: 'user-prompt', content: userPrompt, priority: 'critical', tokens: 0 },
221
+ { name: 'agent-context', content: agentContext, priority: 'high', tokens: 0 },
222
+ { name: 'project-context', content: projectContext, priority: 'medium', tokens: 0 },
223
+ { name: 'skill-context', content: skillContext, priority: 'medium', tokens: 0 },
224
+ ]
225
+ }
226
+
227
+ /**
228
+ * Format token estimate for display
229
+ */
230
+ export function formatEstimate(estimate: TokenEstimate): string {
231
+ const lines = [
232
+ '📊 Token Budget',
233
+ '',
234
+ `Total: ${estimate.total.toLocaleString()} tokens`,
235
+ '',
236
+ 'Breakdown:',
237
+ ` Project: ${estimate.breakdown.projectContext.toLocaleString()}`,
238
+ ` Agents: ${estimate.breakdown.agentContext.toLocaleString()}`,
239
+ ` Skills: ${estimate.breakdown.skillContext.toLocaleString()}`,
240
+ ` Prompt: ${estimate.breakdown.userPrompt.toLocaleString()}`,
241
+ '',
242
+ `Status: ${estimate.withinBudget ? '✅ Within budget' : '⚠️ Over budget'}`,
243
+ ]
244
+
245
+ if (estimate.recommendations.length > 0) {
246
+ lines.push('')
247
+ lines.push('Recommendations:')
248
+ for (const rec of estimate.recommendations) {
249
+ lines.push(` - ${rec}`)
250
+ }
251
+ }
252
+
253
+ return lines.join('\n')
254
+ }
255
+
256
+ export default {
257
+ estimateTokens,
258
+ getTokenBudget,
259
+ estimateContext,
260
+ filterContext,
261
+ summarizeForTokens,
262
+ createContextSections,
263
+ formatEstimate,
264
+ }
@@ -115,20 +115,20 @@ class PathManager {
115
115
  /**
116
116
  * Ensure the project-specific global structure exists
117
117
  * Creates the layered directory structure for a project
118
+ * OPTIMIZED: Parallel directory creation
118
119
  */
119
120
  async ensureProjectStructure(projectId: string): Promise<string> {
120
121
  await this.ensureGlobalStructure()
121
122
 
122
123
  const projectPath = this.getGlobalProjectPath(projectId)
123
124
 
124
- const layers = ['core', 'progress', 'planning', 'analysis', 'memory', 'agents']
125
+ // OPTIMIZED: Create all directories in parallel
126
+ const dirs = [
127
+ 'core', 'progress', 'planning', 'analysis', 'memory', 'agents',
128
+ 'planning/tasks', 'sessions', 'storage', 'context', 'sync'
129
+ ].map(dir => path.join(projectPath, dir))
125
130
 
126
- for (const layer of layers) {
127
- await fileHelper.ensureDir(path.join(projectPath, layer))
128
- }
129
-
130
- await fileHelper.ensureDir(path.join(projectPath, 'planning', 'tasks'))
131
- await fileHelper.ensureDir(path.join(projectPath, 'sessions'))
131
+ await Promise.all(dirs.map(dir => fileHelper.ensureDir(dir)))
132
132
 
133
133
  return projectPath
134
134
  }