prjct-cli 1.1.0 → 1.2.0
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 +96 -0
- package/bin/prjct.ts +6 -0
- package/core/commands/analysis.ts +17 -29
- package/core/commands/planning.ts +1 -0
- package/core/index.ts +1 -0
- package/core/services/doctor-service.ts +13 -16
- package/core/services/hooks-service.ts +676 -0
- package/core/services/staleness-checker.ts +19 -7
- package/core/utils/help.ts +6 -0
- package/core/utils/output.ts +10 -0
- package/dist/bin/prjct.mjs +1350 -843
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-02-06
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- git hooks integration for auto-sync (PRJ-128) (#112)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [1.2.0] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Git hooks integration (PRJ-128)**: New `prjct hooks` command for auto-syncing context on commit and branch checkout
|
|
15
|
+
|
|
16
|
+
### Implementation Details
|
|
17
|
+
|
|
18
|
+
New `prjct hooks` CLI subcommand with three operations:
|
|
19
|
+
- `prjct hooks install` — auto-detects hook manager (lefthook > husky > direct `.git/hooks/`) and installs post-commit + post-checkout hooks
|
|
20
|
+
- `prjct hooks uninstall` — cleanly removes only prjct hooks, preserving existing hooks
|
|
21
|
+
- `prjct hooks status` — shows active hooks, strategy, and available managers
|
|
22
|
+
|
|
23
|
+
Hook scripts feature:
|
|
24
|
+
- **Rate limiting** — 30-second lockfile prevents over-syncing on rapid commits
|
|
25
|
+
- **Background execution** — hooks run `prjct sync` in background, never blocking git
|
|
26
|
+
- **Branch-only checkout** — post-checkout only fires on branch switch, not file checkout
|
|
27
|
+
- **Cross-platform** — handles macOS/Linux differences in `stat` and `md5` commands
|
|
28
|
+
|
|
29
|
+
Supports three installation strategies:
|
|
30
|
+
- **Lefthook** — adds `prjct-sync-*` commands to existing `lefthook.yml`
|
|
31
|
+
- **Husky** — appends to existing `.husky/` hook scripts
|
|
32
|
+
- **Direct** — writes to `.git/hooks/` as fallback
|
|
33
|
+
|
|
34
|
+
Hook configuration saved to `project.json` for persistence across sessions.
|
|
35
|
+
|
|
36
|
+
### Learnings
|
|
37
|
+
|
|
38
|
+
- Strategy pattern works well for hook manager abstraction (detect → select → install)
|
|
39
|
+
- `stat -f%m` (macOS) vs `stat -c%Y` (Linux) for file modification time
|
|
40
|
+
- Lefthook section merging needs careful regex to avoid duplicates
|
|
41
|
+
- `$3` parameter in post-checkout distinguishes branch checkout (1) from file checkout (0)
|
|
42
|
+
|
|
43
|
+
### Test Plan
|
|
44
|
+
|
|
45
|
+
#### For QA
|
|
46
|
+
1. Run `prjct hooks status` — verify shows "Not installed" with available managers
|
|
47
|
+
2. Run `prjct hooks install` — verify detects manager and installs hooks
|
|
48
|
+
3. Run `prjct hooks status` — verify shows "Active"
|
|
49
|
+
4. Make a git commit — verify sync runs in background
|
|
50
|
+
5. Switch branches — verify post-checkout triggers sync
|
|
51
|
+
6. Run `prjct hooks uninstall` — verify clean removal
|
|
52
|
+
7. Run `bun run build && bun run typecheck` — zero errors
|
|
53
|
+
|
|
54
|
+
#### For Users
|
|
55
|
+
**What changed:** New `prjct hooks` command for automatic context syncing
|
|
56
|
+
**How to use:** Run `prjct hooks install` in any prjct project
|
|
57
|
+
**Breaking changes:** None
|
|
58
|
+
|
|
59
|
+
## [1.1.1] - 2026-02-06
|
|
60
|
+
|
|
61
|
+
### Bug Fixes
|
|
62
|
+
|
|
63
|
+
- visual grouping with boxes and tables for structured output (PRJ-134) (#110)
|
|
64
|
+
|
|
65
|
+
## [1.1.1] - 2026-02-05
|
|
66
|
+
|
|
67
|
+
### Improved
|
|
68
|
+
|
|
69
|
+
- **Visual grouping for structured output (PRJ-134)**: Added `out.section()` and integrated `out.box()`, `out.table()`, `out.list()` into sync, doctor, and status commands
|
|
70
|
+
|
|
71
|
+
### Implementation Details
|
|
72
|
+
|
|
73
|
+
Added `out.section(title)` method to the unified output system (`core/utils/output.ts`) — bold title with dim underline, chainable, quiet-mode aware.
|
|
74
|
+
|
|
75
|
+
Refactored three commands to use unified output helpers instead of raw `console.log`:
|
|
76
|
+
- **Sync** (`analysis.ts`): Summary metrics in `out.box()`, generated items via `out.section()` + `out.list()`
|
|
77
|
+
- **Doctor** (`doctor-service.ts`): Section headers via `out.section()`, recommendations via `out.list()`, summary via `out.done()`/`out.warn()`/`out.fail()`
|
|
78
|
+
- **Status** (`staleness-checker.ts`): Key-value details wrapped in box-drawing characters
|
|
79
|
+
|
|
80
|
+
### Learnings
|
|
81
|
+
|
|
82
|
+
- `staleness-checker.formatStatus()` returns a string (not direct output), so `out.box()` can't be used directly — used inline box-drawing chars instead
|
|
83
|
+
- Doctor service had its own icon logic for check results that was worth preserving alongside the new section headers
|
|
84
|
+
- Unified output helpers reduce code while maintaining the same visual style
|
|
85
|
+
|
|
86
|
+
### Test Plan
|
|
87
|
+
|
|
88
|
+
#### For QA
|
|
89
|
+
1. Run `prjct sync` — verify boxed "Sync Summary" with metrics, "Generated" section header with underline, `✓` bullet items
|
|
90
|
+
2. Run `prjct doctor` — verify bold+underline section headers for "System Tools", "Project Status", "Recommendations"
|
|
91
|
+
3. Run `prjct status` — verify key-value details in box-drawing characters
|
|
92
|
+
4. Run with `--quiet` flag — verify no visual output is printed
|
|
93
|
+
|
|
94
|
+
#### For Users
|
|
95
|
+
**What changed:** CLI output now uses visual grouping (boxes, section headers, structured lists) for better scannability
|
|
96
|
+
**How to use:** No changes needed — output is automatically improved
|
|
97
|
+
**Breaking changes:** None
|
|
98
|
+
|
|
3
99
|
## [1.1.0] - 2026-02-05
|
|
4
100
|
|
|
5
101
|
### Features
|
package/bin/prjct.ts
CHANGED
|
@@ -108,6 +108,12 @@ if (args[0] === 'start' || args[0] === 'setup') {
|
|
|
108
108
|
console.log(JSON.stringify(result, null, 2))
|
|
109
109
|
process.exitCode = result.tool === 'error' ? 1 : 0
|
|
110
110
|
}
|
|
111
|
+
} else if (args[0] === 'hooks') {
|
|
112
|
+
// Git hooks management
|
|
113
|
+
const { hooksService } = await import('../core/services/hooks-service')
|
|
114
|
+
const subcommand = args[1] || 'status'
|
|
115
|
+
const exitCode = await hooksService.run(process.cwd(), subcommand)
|
|
116
|
+
process.exitCode = exitCode
|
|
111
117
|
} else if (args[0] === 'doctor') {
|
|
112
118
|
// Health check command
|
|
113
119
|
const { doctorService } = await import('../core/services/doctor-service')
|
|
@@ -478,67 +478,55 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
478
478
|
// ═══════════════════════════════════════════════════════════════════════
|
|
479
479
|
// SUCCESS LINE - Immediate confirmation with timing
|
|
480
480
|
// ═══════════════════════════════════════════════════════════════════════
|
|
481
|
-
|
|
481
|
+
out.done(`Synced ${result.stats.name || 'project'} (${(elapsed / 1000).toFixed(1)}s)`)
|
|
482
|
+
console.log('')
|
|
482
483
|
|
|
483
484
|
// ═══════════════════════════════════════════════════════════════════════
|
|
484
|
-
//
|
|
485
|
+
// SUMMARY BOX - Key metrics grouped visually
|
|
485
486
|
// ═══════════════════════════════════════════════════════════════════════
|
|
486
|
-
// Only show compression rate if meaningful (> 10%)
|
|
487
487
|
const compressionPct = result.syncMetrics?.compressionRate
|
|
488
488
|
? Math.round(result.syncMetrics.compressionRate * 100)
|
|
489
489
|
: 0
|
|
490
|
-
const metricsLine = [
|
|
491
|
-
`${result.stats.fileCount} files → ${contextFilesCount} context`,
|
|
492
|
-
`${agentCount} agents`,
|
|
493
|
-
compressionPct > 10 ? `${compressionPct}% reduction` : null,
|
|
494
|
-
]
|
|
495
|
-
.filter(Boolean)
|
|
496
|
-
.join(' | ')
|
|
497
|
-
console.log(metricsLine)
|
|
498
|
-
|
|
499
|
-
// Stack and branch info
|
|
500
490
|
const framework = result.stats.frameworks.length > 0 ? ` (${result.stats.frameworks[0]})` : ''
|
|
501
|
-
|
|
491
|
+
const boxLines = [
|
|
492
|
+
`${result.stats.fileCount} files → ${contextFilesCount} context | ${agentCount} agents${compressionPct > 10 ? ` | ${compressionPct}% reduction` : ''}`,
|
|
493
|
+
`Stack: ${result.stats.ecosystem}${framework} | Branch: ${result.git.branch}`,
|
|
494
|
+
]
|
|
495
|
+
out.box('Sync Summary', boxLines.join('\n'))
|
|
502
496
|
|
|
503
497
|
// ═══════════════════════════════════════════════════════════════════════
|
|
504
498
|
// CHANGES SECTION - What was generated/updated
|
|
505
499
|
// ═══════════════════════════════════════════════════════════════════════
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// Context files (condensed)
|
|
500
|
+
const generatedItems: string[] = []
|
|
509
501
|
if (result.contextFiles.length > 0) {
|
|
510
|
-
|
|
502
|
+
generatedItems.push(`${result.contextFiles.length} context files`)
|
|
511
503
|
}
|
|
512
|
-
|
|
513
|
-
// AI tools
|
|
514
504
|
const successTools = result.aiTools?.filter((t) => t.success) || []
|
|
515
505
|
if (successTools.length > 0) {
|
|
516
|
-
|
|
517
|
-
console.log(` ✓ AI tools: ${toolNames}`)
|
|
506
|
+
generatedItems.push(`AI tools: ${successTools.map((t) => t.toolId).join(', ')}`)
|
|
518
507
|
}
|
|
519
|
-
|
|
520
|
-
// Agents (show count with breakdown)
|
|
521
508
|
if (agentCount > 0) {
|
|
522
509
|
const agentSummary =
|
|
523
510
|
domainAgentCount > 0
|
|
524
511
|
? `${agentCount} agents (${domainAgentCount} domain)`
|
|
525
512
|
: `${agentCount} agents`
|
|
526
|
-
|
|
513
|
+
generatedItems.push(agentSummary)
|
|
527
514
|
}
|
|
528
|
-
|
|
529
|
-
// Skills
|
|
530
515
|
if (result.skills.length > 0) {
|
|
531
516
|
const skillWord = result.skills.length === 1 ? 'skill' : 'skills'
|
|
532
|
-
|
|
517
|
+
generatedItems.push(`${result.skills.length} ${skillWord}`)
|
|
533
518
|
}
|
|
534
519
|
|
|
520
|
+
out.section('Generated')
|
|
521
|
+
out.list(generatedItems, { bullet: '✓' })
|
|
535
522
|
console.log('')
|
|
536
523
|
|
|
537
524
|
// ═══════════════════════════════════════════════════════════════════════
|
|
538
525
|
// STATUS INDICATOR - Repository state
|
|
539
526
|
// ═══════════════════════════════════════════════════════════════════════
|
|
540
527
|
if (result.git.hasChanges) {
|
|
541
|
-
|
|
528
|
+
out.warn('Uncommitted changes detected')
|
|
529
|
+
console.log('')
|
|
542
530
|
}
|
|
543
531
|
|
|
544
532
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -207,6 +207,7 @@ export class PlanningCommands extends PrjctCommandsBase {
|
|
|
207
207
|
console.log(' Quick start:')
|
|
208
208
|
console.log(' prjct sync Update context after changes')
|
|
209
209
|
console.log(' prjct task Start working on a task')
|
|
210
|
+
console.log(' prjct hooks Auto-sync on commit/checkout')
|
|
210
211
|
console.log('')
|
|
211
212
|
|
|
212
213
|
if (wizardResult) {
|
package/core/index.ts
CHANGED
|
@@ -306,6 +306,7 @@ TERMINAL COMMANDS (this CLI)
|
|
|
306
306
|
prjct setup Reconfigure installations
|
|
307
307
|
prjct sync Sync project state
|
|
308
308
|
prjct watch Auto-sync on file changes (Ctrl+C to stop)
|
|
309
|
+
prjct hooks Manage git hooks for auto-sync
|
|
309
310
|
prjct doctor Check system health and dependencies
|
|
310
311
|
|
|
311
312
|
EXAMPLES
|
|
@@ -15,6 +15,7 @@ import path from 'node:path'
|
|
|
15
15
|
import chalk from 'chalk'
|
|
16
16
|
import configManager from '../infrastructure/config-manager'
|
|
17
17
|
import pathManager from '../infrastructure/path-manager'
|
|
18
|
+
import out from '../utils/output'
|
|
18
19
|
import { VERSION } from '../utils/version'
|
|
19
20
|
|
|
20
21
|
// ============================================================================
|
|
@@ -367,32 +368,28 @@ class DoctorService {
|
|
|
367
368
|
// ==========================================================================
|
|
368
369
|
|
|
369
370
|
private printHeader(): void {
|
|
370
|
-
|
|
371
|
-
console.log(chalk.bold(`prjct doctor v${VERSION}`))
|
|
372
|
-
console.log(chalk.dim('─'.repeat(40)))
|
|
371
|
+
out.section(`prjct doctor v${VERSION}`)
|
|
373
372
|
}
|
|
374
373
|
|
|
375
374
|
private printSection(title: string, checks: CheckResult[]): void {
|
|
376
|
-
|
|
377
|
-
console.log(chalk.bold(title))
|
|
375
|
+
out.section(title)
|
|
378
376
|
|
|
379
|
-
|
|
377
|
+
const items = checks.map((check) => {
|
|
380
378
|
const icon = this.getStatusIcon(check.status, check.optional)
|
|
381
379
|
const name = check.name.padEnd(14)
|
|
382
380
|
const detail = check.version || check.message || ''
|
|
383
381
|
const optionalTag = check.optional && check.status === 'error' ? chalk.dim(' (optional)') : ''
|
|
382
|
+
return `${icon} ${name} ${chalk.dim(detail)}${optionalTag}`
|
|
383
|
+
})
|
|
384
384
|
|
|
385
|
-
|
|
385
|
+
for (const item of items) {
|
|
386
|
+
console.log(` ${item}`)
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
|
|
389
390
|
private printRecommendations(recommendations: string[]): void {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
for (const rec of recommendations) {
|
|
394
|
-
console.log(` ${chalk.yellow('•')} ${rec}`)
|
|
395
|
-
}
|
|
391
|
+
out.section('Recommendations')
|
|
392
|
+
out.list(recommendations, { bullet: chalk.yellow('•') })
|
|
396
393
|
}
|
|
397
394
|
|
|
398
395
|
private printSummary(result: DoctorResult): void {
|
|
@@ -400,11 +397,11 @@ class DoctorService {
|
|
|
400
397
|
console.log(chalk.dim('─'.repeat(40)))
|
|
401
398
|
|
|
402
399
|
if (result.hasErrors) {
|
|
403
|
-
|
|
400
|
+
out.fail('Some required checks failed')
|
|
404
401
|
} else if (result.hasWarnings) {
|
|
405
|
-
|
|
402
|
+
out.warn('All required checks passed (some warnings)')
|
|
406
403
|
} else {
|
|
407
|
-
|
|
404
|
+
out.done('All checks passed')
|
|
408
405
|
}
|
|
409
406
|
|
|
410
407
|
console.log('')
|