prjct-cli 0.28.3 → 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 +43 -0
- package/core/agentic/memory-system.ts +44 -5
- package/core/agentic/smart-context.ts +36 -64
- package/core/infrastructure/path-manager.ts +7 -7
- package/package.json +1 -1
- 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 +82 -614
- package/templates/commands/sync.md +153 -253
- package/templates/commands/task.md +50 -259
- package/templates/global/CLAUDE.md +92 -283
- package/templates/guides/claude-code-ux.md +232 -0
- package/templates/shared/git-operations.md +68 -0
- package/templates/shared/io-patterns.md +72 -0
- package/templates/shared/standard.md +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
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
|
+
|
|
3
46
|
## [0.28.3] - 2026-01-11
|
|
4
47
|
|
|
5
48
|
### Feature: Claude Code Synergy Optimization
|
|
@@ -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
|
|
|
@@ -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
|
}
|