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.
- package/CHANGELOG.md +105 -0
- package/core/agentic/index.ts +11 -1
- package/core/agentic/memory-system.ts +44 -5
- package/core/agentic/smart-context.ts +36 -64
- package/core/agentic/token-estimator.ts +264 -0
- package/core/infrastructure/path-manager.ts +7 -7
- package/core/infrastructure/setup.ts +28 -0
- package/core/infrastructure/slash-command-registry.ts +176 -0
- package/core/types/integrations.ts +28 -1
- package/package.json +1 -1
- package/templates/agentic/subagent-generation.md +237 -90
- package/templates/commands/bug.md +51 -392
- package/templates/commands/done.md +53 -232
- package/templates/commands/setup-statusline.md +138 -0
- package/templates/commands/ship.md +86 -668
- package/templates/commands/sync.md +189 -552
- package/templates/commands/task.md +50 -276
- package/templates/global/CLAUDE.md +101 -161
- package/templates/guides/agent-generation.md +164 -0
- package/templates/guides/claude-code-ux.md +232 -0
- package/templates/guides/integrations.md +149 -0
- package/templates/mcp-config.json +23 -18
- package/templates/shared/git-operations.md +68 -0
- package/templates/shared/io-patterns.md +72 -0
- package/templates/shared/standard.md +70 -0
- package/templates/shared/validation.md +75 -0
- package/CLAUDE.md +0 -204
- package/templates/agentic/agents/uxui.md +0 -218
- package/templates/subagents/domain/backend.md +0 -106
- package/templates/subagents/domain/database.md +0 -118
- package/templates/subagents/domain/devops.md +0 -149
- package/templates/subagents/domain/frontend.md +0 -100
- 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
|
package/core/agentic/index.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|