prjct-cli 1.0.0 → 1.1.1
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 +81 -0
- package/core/commands/analysis.ts +17 -29
- package/core/services/doctor-service.ts +13 -16
- package/core/services/staleness-checker.ts +19 -7
- package/core/utils/output.ts +10 -0
- package/dist/bin/prjct.mjs +54 -39
- package/package.json +1 -1
- package/templates/commands/status.md +207 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.1] - 2026-02-06
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- visual grouping with boxes and tables for structured output (PRJ-134) (#110)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [1.1.1] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Improved
|
|
13
|
+
|
|
14
|
+
- **Visual grouping for structured output (PRJ-134)**: Added `out.section()` and integrated `out.box()`, `out.table()`, `out.list()` into sync, doctor, and status commands
|
|
15
|
+
|
|
16
|
+
### Implementation Details
|
|
17
|
+
|
|
18
|
+
Added `out.section(title)` method to the unified output system (`core/utils/output.ts`) — bold title with dim underline, chainable, quiet-mode aware.
|
|
19
|
+
|
|
20
|
+
Refactored three commands to use unified output helpers instead of raw `console.log`:
|
|
21
|
+
- **Sync** (`analysis.ts`): Summary metrics in `out.box()`, generated items via `out.section()` + `out.list()`
|
|
22
|
+
- **Doctor** (`doctor-service.ts`): Section headers via `out.section()`, recommendations via `out.list()`, summary via `out.done()`/`out.warn()`/`out.fail()`
|
|
23
|
+
- **Status** (`staleness-checker.ts`): Key-value details wrapped in box-drawing characters
|
|
24
|
+
|
|
25
|
+
### Learnings
|
|
26
|
+
|
|
27
|
+
- `staleness-checker.formatStatus()` returns a string (not direct output), so `out.box()` can't be used directly — used inline box-drawing chars instead
|
|
28
|
+
- Doctor service had its own icon logic for check results that was worth preserving alongside the new section headers
|
|
29
|
+
- Unified output helpers reduce code while maintaining the same visual style
|
|
30
|
+
|
|
31
|
+
### Test Plan
|
|
32
|
+
|
|
33
|
+
#### For QA
|
|
34
|
+
1. Run `prjct sync` — verify boxed "Sync Summary" with metrics, "Generated" section header with underline, `✓` bullet items
|
|
35
|
+
2. Run `prjct doctor` — verify bold+underline section headers for "System Tools", "Project Status", "Recommendations"
|
|
36
|
+
3. Run `prjct status` — verify key-value details in box-drawing characters
|
|
37
|
+
4. Run with `--quiet` flag — verify no visual output is printed
|
|
38
|
+
|
|
39
|
+
#### For Users
|
|
40
|
+
**What changed:** CLI output now uses visual grouping (boxes, section headers, structured lists) for better scannability
|
|
41
|
+
**How to use:** No changes needed — output is automatically improved
|
|
42
|
+
**Breaking changes:** None
|
|
43
|
+
|
|
44
|
+
## [1.1.0] - 2026-02-05
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
|
|
48
|
+
- visual workflow status command (PRJ-140) (#109)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## [1.1.0] - 2026-02-05
|
|
52
|
+
|
|
53
|
+
### Features
|
|
54
|
+
|
|
55
|
+
- **Workflow visualization (PRJ-140)**: New `p. status` command with visual workflow diagram
|
|
56
|
+
|
|
57
|
+
### Implementation Details
|
|
58
|
+
|
|
59
|
+
Added visual workflow status template showing:
|
|
60
|
+
- ASCII workflow diagram with current position indicator (sync → task → work → done → ship)
|
|
61
|
+
- Subtask tree visualization with status icons (✅/🔄/⬜)
|
|
62
|
+
- Progress bar for subtask completion
|
|
63
|
+
- Paused tasks, queue summary, and recent ships
|
|
64
|
+
- Context staleness indicator from `prjct status --json`
|
|
65
|
+
- Compact mode for single-line status output
|
|
66
|
+
|
|
67
|
+
### Learnings
|
|
68
|
+
|
|
69
|
+
- Template-first approach: Complex visualizations can be defined entirely in markdown templates without code changes
|
|
70
|
+
|
|
71
|
+
### Test Plan
|
|
72
|
+
|
|
73
|
+
#### For QA
|
|
74
|
+
1. Run `prjct sync` to install new status template
|
|
75
|
+
2. Run `p. status` - verify workflow diagram displays
|
|
76
|
+
3. Verify subtask tree shows correct status icons
|
|
77
|
+
4. Test `p. status compact` for single-line output
|
|
78
|
+
|
|
79
|
+
#### For Users
|
|
80
|
+
- New `p. status` command shows visual workflow overview
|
|
81
|
+
- No breaking changes
|
|
82
|
+
|
|
83
|
+
|
|
3
84
|
## [1.0.0] - 2026-02-05
|
|
4
85
|
|
|
5
86
|
### Features
|
|
@@ -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
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -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('')
|
|
@@ -195,23 +195,35 @@ export class StalenessChecker {
|
|
|
195
195
|
lines.push('CLAUDE.md status: ✓ Fresh')
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
// Build key-value table content
|
|
199
|
+
const details: string[] = []
|
|
200
200
|
if (status.lastSyncCommit) {
|
|
201
|
-
|
|
201
|
+
details.push(`Last sync: ${status.lastSyncCommit}`)
|
|
202
202
|
}
|
|
203
203
|
if (status.currentCommit) {
|
|
204
|
-
|
|
204
|
+
details.push(`Current: ${status.currentCommit}`)
|
|
205
205
|
}
|
|
206
206
|
if (status.commitsSinceSync > 0) {
|
|
207
|
-
|
|
207
|
+
details.push(`Commits since: ${status.commitsSinceSync}`)
|
|
208
208
|
}
|
|
209
209
|
if (status.daysSinceSync > 0) {
|
|
210
|
-
|
|
210
|
+
details.push(`Days since: ${status.daysSinceSync}`)
|
|
211
211
|
}
|
|
212
212
|
if (status.changedFiles.length > 0) {
|
|
213
|
-
|
|
213
|
+
details.push(`Files changed: ${status.changedFiles.length}`)
|
|
214
214
|
}
|
|
215
|
+
|
|
216
|
+
// Wrap details in a box
|
|
217
|
+
if (details.length > 0) {
|
|
218
|
+
const maxLen = Math.max(...details.map((l) => l.length))
|
|
219
|
+
const border = '─'.repeat(maxLen + 2)
|
|
220
|
+
lines.push(`┌${border}┐`)
|
|
221
|
+
for (const detail of details) {
|
|
222
|
+
lines.push(`│ ${detail.padEnd(maxLen)} │`)
|
|
223
|
+
}
|
|
224
|
+
lines.push(`└${border}┘`)
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
if (status.significantChanges.length > 0) {
|
|
216
228
|
lines.push(``)
|
|
217
229
|
lines.push(`Significant changes:`)
|
package/core/utils/output.ts
CHANGED
|
@@ -206,6 +206,7 @@ interface Output {
|
|
|
206
206
|
list(items: string[], options?: { bullet?: string; indent?: number }): Output
|
|
207
207
|
table(rows: Array<Record<string, string | number>>, options?: { header?: boolean }): Output
|
|
208
208
|
box(title: string, content: string): Output
|
|
209
|
+
section(title: string): Output
|
|
209
210
|
stop(): Output
|
|
210
211
|
step(current: number, total: number, msg: string): Output
|
|
211
212
|
progress(current: number, total: number, msg?: string): Output
|
|
@@ -369,6 +370,15 @@ const out: Output = {
|
|
|
369
370
|
return this
|
|
370
371
|
},
|
|
371
372
|
|
|
373
|
+
// Section header: bold title + underline
|
|
374
|
+
section(title: string) {
|
|
375
|
+
this.stop()
|
|
376
|
+
if (quietMode) return this
|
|
377
|
+
console.log(`\n${chalk.bold(title)}`)
|
|
378
|
+
console.log(chalk.dim('─'.repeat(title.length)))
|
|
379
|
+
return this
|
|
380
|
+
},
|
|
381
|
+
|
|
372
382
|
stop() {
|
|
373
383
|
if (interval) {
|
|
374
384
|
clearInterval(interval)
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -2495,6 +2495,15 @@ var init_output = __esm({
|
|
|
2495
2495
|
console.log(chalk2.dim(`\u2514${border}\u2518`));
|
|
2496
2496
|
return this;
|
|
2497
2497
|
},
|
|
2498
|
+
// Section header: bold title + underline
|
|
2499
|
+
section(title) {
|
|
2500
|
+
this.stop();
|
|
2501
|
+
if (quietMode) return this;
|
|
2502
|
+
console.log(`
|
|
2503
|
+
${chalk2.bold(title)}`);
|
|
2504
|
+
console.log(chalk2.dim("\u2500".repeat(title.length)));
|
|
2505
|
+
return this;
|
|
2506
|
+
},
|
|
2498
2507
|
stop() {
|
|
2499
2508
|
if (interval) {
|
|
2500
2509
|
clearInterval(interval);
|
|
@@ -5448,6 +5457,7 @@ var init_doctor_service = __esm({
|
|
|
5448
5457
|
"use strict";
|
|
5449
5458
|
init_config_manager();
|
|
5450
5459
|
init_path_manager();
|
|
5460
|
+
init_output();
|
|
5451
5461
|
init_version();
|
|
5452
5462
|
DoctorService = class {
|
|
5453
5463
|
static {
|
|
@@ -5702,37 +5712,34 @@ var init_doctor_service = __esm({
|
|
|
5702
5712
|
// OUTPUT
|
|
5703
5713
|
// ==========================================================================
|
|
5704
5714
|
printHeader() {
|
|
5705
|
-
|
|
5706
|
-
console.log(chalk3.bold(`prjct doctor v${VERSION}`));
|
|
5707
|
-
console.log(chalk3.dim("\u2500".repeat(40)));
|
|
5715
|
+
output_default.section(`prjct doctor v${VERSION}`);
|
|
5708
5716
|
}
|
|
5709
5717
|
printSection(title, checks) {
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
for (const check of checks) {
|
|
5718
|
+
output_default.section(title);
|
|
5719
|
+
const items = checks.map((check) => {
|
|
5713
5720
|
const icon = this.getStatusIcon(check.status, check.optional);
|
|
5714
5721
|
const name = check.name.padEnd(14);
|
|
5715
5722
|
const detail = check.version || check.message || "";
|
|
5716
5723
|
const optionalTag = check.optional && check.status === "error" ? chalk3.dim(" (optional)") : "";
|
|
5717
|
-
|
|
5724
|
+
return `${icon} ${name} ${chalk3.dim(detail)}${optionalTag}`;
|
|
5725
|
+
});
|
|
5726
|
+
for (const item of items) {
|
|
5727
|
+
console.log(` ${item}`);
|
|
5718
5728
|
}
|
|
5719
5729
|
}
|
|
5720
5730
|
printRecommendations(recommendations) {
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
for (const rec of recommendations) {
|
|
5724
|
-
console.log(` ${chalk3.yellow("\u2022")} ${rec}`);
|
|
5725
|
-
}
|
|
5731
|
+
output_default.section("Recommendations");
|
|
5732
|
+
output_default.list(recommendations, { bullet: chalk3.yellow("\u2022") });
|
|
5726
5733
|
}
|
|
5727
5734
|
printSummary(result) {
|
|
5728
5735
|
console.log("");
|
|
5729
5736
|
console.log(chalk3.dim("\u2500".repeat(40)));
|
|
5730
5737
|
if (result.hasErrors) {
|
|
5731
|
-
|
|
5738
|
+
output_default.fail("Some required checks failed");
|
|
5732
5739
|
} else if (result.hasWarnings) {
|
|
5733
|
-
|
|
5740
|
+
output_default.warn("All required checks passed (some warnings)");
|
|
5734
5741
|
} else {
|
|
5735
|
-
|
|
5742
|
+
output_default.done("All checks passed");
|
|
5736
5743
|
}
|
|
5737
5744
|
console.log("");
|
|
5738
5745
|
}
|
|
@@ -18383,38 +18390,37 @@ ${formatFullDiff(diff)}`);
|
|
|
18383
18390
|
const agentCount = result.agents.length;
|
|
18384
18391
|
const domainAgentCount = result.agents.filter((a) => a.type === "domain").length;
|
|
18385
18392
|
await command_installer_default.installGlobalConfig();
|
|
18386
|
-
|
|
18387
|
-
|
|
18393
|
+
output_default.done(`Synced ${result.stats.name || "project"} (${(elapsed / 1e3).toFixed(1)}s)`);
|
|
18394
|
+
console.log("");
|
|
18388
18395
|
const compressionPct = result.syncMetrics?.compressionRate ? Math.round(result.syncMetrics.compressionRate * 100) : 0;
|
|
18389
|
-
const metricsLine = [
|
|
18390
|
-
`${result.stats.fileCount} files \u2192 ${contextFilesCount} context`,
|
|
18391
|
-
`${agentCount} agents`,
|
|
18392
|
-
compressionPct > 10 ? `${compressionPct}% reduction` : null
|
|
18393
|
-
].filter(Boolean).join(" | ");
|
|
18394
|
-
console.log(metricsLine);
|
|
18395
18396
|
const framework = result.stats.frameworks.length > 0 ? ` (${result.stats.frameworks[0]})` : "";
|
|
18396
|
-
|
|
18397
|
-
`
|
|
18398
|
-
|
|
18397
|
+
const boxLines = [
|
|
18398
|
+
`${result.stats.fileCount} files \u2192 ${contextFilesCount} context | ${agentCount} agents${compressionPct > 10 ? ` | ${compressionPct}% reduction` : ""}`,
|
|
18399
|
+
`Stack: ${result.stats.ecosystem}${framework} | Branch: ${result.git.branch}`
|
|
18400
|
+
];
|
|
18401
|
+
output_default.box("Sync Summary", boxLines.join("\n"));
|
|
18402
|
+
const generatedItems = [];
|
|
18399
18403
|
if (result.contextFiles.length > 0) {
|
|
18400
|
-
|
|
18404
|
+
generatedItems.push(`${result.contextFiles.length} context files`);
|
|
18401
18405
|
}
|
|
18402
18406
|
const successTools = result.aiTools?.filter((t) => t.success) || [];
|
|
18403
18407
|
if (successTools.length > 0) {
|
|
18404
|
-
|
|
18405
|
-
console.log(` \u2713 AI tools: ${toolNames}`);
|
|
18408
|
+
generatedItems.push(`AI tools: ${successTools.map((t) => t.toolId).join(", ")}`);
|
|
18406
18409
|
}
|
|
18407
18410
|
if (agentCount > 0) {
|
|
18408
18411
|
const agentSummary = domainAgentCount > 0 ? `${agentCount} agents (${domainAgentCount} domain)` : `${agentCount} agents`;
|
|
18409
|
-
|
|
18412
|
+
generatedItems.push(agentSummary);
|
|
18410
18413
|
}
|
|
18411
18414
|
if (result.skills.length > 0) {
|
|
18412
18415
|
const skillWord = result.skills.length === 1 ? "skill" : "skills";
|
|
18413
|
-
|
|
18416
|
+
generatedItems.push(`${result.skills.length} ${skillWord}`);
|
|
18414
18417
|
}
|
|
18418
|
+
output_default.section("Generated");
|
|
18419
|
+
output_default.list(generatedItems, { bullet: "\u2713" });
|
|
18415
18420
|
console.log("");
|
|
18416
18421
|
if (result.git.hasChanges) {
|
|
18417
|
-
|
|
18422
|
+
output_default.warn("Uncommitted changes detected");
|
|
18423
|
+
console.log("");
|
|
18418
18424
|
}
|
|
18419
18425
|
showNextSteps("sync");
|
|
18420
18426
|
return {
|
|
@@ -19574,21 +19580,30 @@ var init_staleness_checker = __esm({
|
|
|
19574
19580
|
} else {
|
|
19575
19581
|
lines.push("CLAUDE.md status: \u2713 Fresh");
|
|
19576
19582
|
}
|
|
19577
|
-
|
|
19583
|
+
const details = [];
|
|
19578
19584
|
if (status.lastSyncCommit) {
|
|
19579
|
-
|
|
19585
|
+
details.push(`Last sync: ${status.lastSyncCommit}`);
|
|
19580
19586
|
}
|
|
19581
19587
|
if (status.currentCommit) {
|
|
19582
|
-
|
|
19588
|
+
details.push(`Current: ${status.currentCommit}`);
|
|
19583
19589
|
}
|
|
19584
19590
|
if (status.commitsSinceSync > 0) {
|
|
19585
|
-
|
|
19591
|
+
details.push(`Commits since: ${status.commitsSinceSync}`);
|
|
19586
19592
|
}
|
|
19587
19593
|
if (status.daysSinceSync > 0) {
|
|
19588
|
-
|
|
19594
|
+
details.push(`Days since: ${status.daysSinceSync}`);
|
|
19589
19595
|
}
|
|
19590
19596
|
if (status.changedFiles.length > 0) {
|
|
19591
|
-
|
|
19597
|
+
details.push(`Files changed: ${status.changedFiles.length}`);
|
|
19598
|
+
}
|
|
19599
|
+
if (details.length > 0) {
|
|
19600
|
+
const maxLen = Math.max(...details.map((l) => l.length));
|
|
19601
|
+
const border = "\u2500".repeat(maxLen + 2);
|
|
19602
|
+
lines.push(`\u250C${border}\u2510`);
|
|
19603
|
+
for (const detail of details) {
|
|
19604
|
+
lines.push(`\u2502 ${detail.padEnd(maxLen)} \u2502`);
|
|
19605
|
+
}
|
|
19606
|
+
lines.push(`\u2514${border}\u2518`);
|
|
19592
19607
|
}
|
|
19593
19608
|
if (status.significantChanges.length > 0) {
|
|
19594
19609
|
lines.push(``);
|
|
@@ -26859,7 +26874,7 @@ var require_package = __commonJS({
|
|
|
26859
26874
|
"package.json"(exports, module) {
|
|
26860
26875
|
module.exports = {
|
|
26861
26876
|
name: "prjct-cli",
|
|
26862
|
-
version: "1.
|
|
26877
|
+
version: "1.1.1",
|
|
26863
26878
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
26864
26879
|
main: "core/index.ts",
|
|
26865
26880
|
bin: {
|
package/package.json
CHANGED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
allowed-tools: [Read, Bash]
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# p. status
|
|
6
|
+
|
|
7
|
+
Visual workflow status showing current position in the prjct lifecycle.
|
|
8
|
+
|
|
9
|
+
## Step 1: Resolve Project Paths
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Get projectId from local config
|
|
13
|
+
cat .prjct/prjct.config.json | grep -o '"projectId"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Set `globalPath = ~/.prjct-cli/projects/{projectId}`
|
|
17
|
+
|
|
18
|
+
## Step 2: Read State and Context
|
|
19
|
+
|
|
20
|
+
READ:
|
|
21
|
+
- `{globalPath}/storage/state.json` → current task, paused, previous
|
|
22
|
+
- `{globalPath}/storage/queue.json` → upcoming tasks
|
|
23
|
+
- `{globalPath}/storage/shipped.json` → recent ships
|
|
24
|
+
- `{globalPath}/project.json` → lastSync timestamp
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Get staleness info
|
|
28
|
+
prjct status --json 2>/dev/null || echo '{"isStale": false}'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Step 3: Determine Workflow Position
|
|
32
|
+
|
|
33
|
+
Based on state.json, determine current position:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
IF no currentTask AND no previousTask:
|
|
37
|
+
position = "ready" # Ready to start (after sync)
|
|
38
|
+
ELSE IF currentTask.status == "active":
|
|
39
|
+
position = "working" # In task
|
|
40
|
+
ELSE IF currentTask.status == "in_review":
|
|
41
|
+
position = "reviewing" # PR open, waiting for merge
|
|
42
|
+
ELSE IF currentTask.status == "shipped":
|
|
43
|
+
position = "shipped" # Ready for next task
|
|
44
|
+
ELSE:
|
|
45
|
+
position = "idle"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Step 4: Calculate Progress
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
IF currentTask.subtasks exists:
|
|
52
|
+
completed = count where status == "completed"
|
|
53
|
+
total = subtasks.length
|
|
54
|
+
percent = (completed / total) * 100
|
|
55
|
+
progressBar = generateBar(percent, 10) # 10 chars wide
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Progress bar generation:
|
|
59
|
+
```
|
|
60
|
+
filled = floor(percent / 10)
|
|
61
|
+
empty = 10 - filled
|
|
62
|
+
bar = "█" × filled + "░" × empty
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Step 5: Format Subtask Tree
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
FOR EACH subtask in currentTask.subtasks:
|
|
69
|
+
IF index == currentSubtaskIndex:
|
|
70
|
+
prefix = "🔄" # Current
|
|
71
|
+
ELSE IF subtask.status == "completed":
|
|
72
|
+
prefix = "✅" # Done
|
|
73
|
+
ELSE:
|
|
74
|
+
prefix = "⬜" # Pending
|
|
75
|
+
|
|
76
|
+
connector = (index == last) ? "└─" : "├─"
|
|
77
|
+
OUTPUT: " {connector} {prefix} {subtask.description}"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Output: Workflow Diagram
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
📊 WORKFLOW STATUS
|
|
86
|
+
|
|
87
|
+
┌─────────────────────────────────────────────────────────┐
|
|
88
|
+
│ │
|
|
89
|
+
│ sync ──▶ task ──▶ [work] ──▶ done ──▶ ship │
|
|
90
|
+
│ ○ ○ ● ○ ○ │
|
|
91
|
+
│ ▲ │
|
|
92
|
+
│ YOU ARE HERE │
|
|
93
|
+
│ │
|
|
94
|
+
└─────────────────────────────────────────────────────────┘
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Position indicators:
|
|
98
|
+
- `○` = not active
|
|
99
|
+
- `●` = current position
|
|
100
|
+
- Arrow indicates flow direction
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Output: Full Status
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
📊 WORKFLOW STATUS
|
|
108
|
+
|
|
109
|
+
┌─────────────────────────────────────────────────────────┐
|
|
110
|
+
│ sync ──▶ task ──▶ work ──▶ done ──▶ ship │
|
|
111
|
+
│ {s} {t} {w} {d} {h} │
|
|
112
|
+
└─────────────────────────────────────────────────────────┘
|
|
113
|
+
|
|
114
|
+
🎯 Current: {currentTask.parentDescription}
|
|
115
|
+
Branch: {currentTask.branch}
|
|
116
|
+
Type: {currentTask.type} | Started: {elapsed}
|
|
117
|
+
{IF linearId: "Linear: {linearId}"}
|
|
118
|
+
|
|
119
|
+
Progress: {progressBar} {completed}/{total} subtasks
|
|
120
|
+
{subtask tree}
|
|
121
|
+
|
|
122
|
+
⏸️ Paused: {pausedTasks[0].description or "none"}
|
|
123
|
+
|
|
124
|
+
📋 Queue: {queueCount} tasks
|
|
125
|
+
{IF queueCount > 0:}
|
|
126
|
+
• {queue[0].description}
|
|
127
|
+
• {queue[1].description}
|
|
128
|
+
{... up to 3}
|
|
129
|
+
|
|
130
|
+
🚀 Last ship: {previousTask.description} ({daysSince})
|
|
131
|
+
{IF previousTask.prUrl: "PR: {prUrl}"}
|
|
132
|
+
|
|
133
|
+
📡 Context: {staleness status}
|
|
134
|
+
Last sync: {timeSinceSync}
|
|
135
|
+
{IF isStale: "⚠️ Run `p. sync` to refresh"}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Output: Compact (`p. status compact`)
|
|
141
|
+
|
|
142
|
+
Single-line summary:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
{position_emoji} {currentTask.description} │ {progressBar} {completed}/{total} │ 📋 {queueCount} │ {staleness_emoji}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Position emojis:
|
|
149
|
+
- 🔄 = working
|
|
150
|
+
- 👀 = reviewing
|
|
151
|
+
- ✅ = shipped
|
|
152
|
+
- 💤 = idle
|
|
153
|
+
|
|
154
|
+
Staleness emojis:
|
|
155
|
+
- ✅ = fresh
|
|
156
|
+
- ⚠️ = stale
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Output: No Active Task
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
📊 WORKFLOW STATUS
|
|
164
|
+
|
|
165
|
+
┌─────────────────────────────────────────────────────────┐
|
|
166
|
+
│ sync ──▶ task ──▶ work ──▶ done ──▶ ship │
|
|
167
|
+
│ ● ○ ○ ○ ○ │
|
|
168
|
+
└─────────────────────────────────────────────────────────┘
|
|
169
|
+
|
|
170
|
+
💤 No active task
|
|
171
|
+
|
|
172
|
+
📋 Queue: {queueCount} tasks
|
|
173
|
+
{IF queueCount > 0:}
|
|
174
|
+
• {queue[0].description}
|
|
175
|
+
|
|
176
|
+
🚀 Last ship: {previousTask.description} ({daysSince})
|
|
177
|
+
|
|
178
|
+
Next: `p. task "description"` or `p. task PRJ-XXX`
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Elapsed Time Formatting
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
IF minutes < 60: "{minutes}m"
|
|
187
|
+
ELSE IF hours < 24: "{hours}h {minutes}m"
|
|
188
|
+
ELSE: "{days}d {hours}h"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Context Staleness
|
|
194
|
+
|
|
195
|
+
From `prjct status --json`:
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"isStale": true,
|
|
199
|
+
"commitsSinceSync": 15,
|
|
200
|
+
"daysSinceSync": 3,
|
|
201
|
+
"significantChanges": ["package.json", "tsconfig.json"]
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Display:
|
|
206
|
+
- Fresh (< 10 commits, < 3 days): `✅ Fresh (synced {time} ago)`
|
|
207
|
+
- Stale: `⚠️ Stale ({commits} commits, {days}d) - run p. sync`
|