@open-code-review/cli 1.1.1 → 1.3.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/README.md +46 -2
- package/dist/index.js +243 -71
- package/dist/package.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,16 +57,60 @@ ocr progress
|
|
|
57
57
|
ocr progress --session 2026-01-26-feature-auth
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Shows: current phase, elapsed time, reviewer status, finding counts, completion percentage
|
|
60
|
+
Shows: current phase, elapsed time, reviewer status, finding counts, completion percentage, and **current round**.
|
|
61
|
+
|
|
62
|
+
**Multi-round support**: The progress display shows which round is active and tracks completion across rounds. When a round completes, running `/ocr-review` again starts a new round (`round-2/`, `round-3/`, etc.).
|
|
61
63
|
|
|
62
64
|
### `ocr update`
|
|
63
65
|
|
|
64
|
-
Update OCR skills and commands to the latest version.
|
|
66
|
+
Update OCR skills and commands to the latest version after upgrading the package.
|
|
65
67
|
|
|
66
68
|
```bash
|
|
69
|
+
# Update everything
|
|
67
70
|
ocr update
|
|
71
|
+
|
|
72
|
+
# Preview changes first
|
|
73
|
+
ocr update --dry-run
|
|
74
|
+
|
|
75
|
+
# Update specific components
|
|
76
|
+
ocr update --commands # Commands only
|
|
77
|
+
ocr update --skills # Skills and references only
|
|
78
|
+
ocr update --inject # AGENTS.md/CLAUDE.md only
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**What it does:**
|
|
82
|
+
|
|
83
|
+
1. Detects which AI tools you configured during `ocr init`
|
|
84
|
+
2. Updates `.ocr/skills/` (SKILL.md, workflow, discourse rules)
|
|
85
|
+
3. Updates tool-specific commands (`.windsurf/workflows/`, etc.)
|
|
86
|
+
4. Updates managed blocks in `AGENTS.md` / `CLAUDE.md`
|
|
87
|
+
|
|
88
|
+
**What is NOT modified:**
|
|
89
|
+
|
|
90
|
+
- `.ocr/config.yaml` — Your team composition and context are preserved
|
|
91
|
+
- `.ocr/skills/references/reviewers/` — All reviewers preserved (default and custom)
|
|
92
|
+
- `.ocr/sessions/` — Review history remains untouched
|
|
93
|
+
|
|
94
|
+
## Session Storage
|
|
95
|
+
|
|
96
|
+
The CLI reads session state from `.ocr/sessions/{date}-{branch}/`:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
.ocr/sessions/2026-01-26-feature-auth/
|
|
100
|
+
├── state.json # Workflow state (read by progress command)
|
|
101
|
+
└── rounds/
|
|
102
|
+
├── round-1/
|
|
103
|
+
│ ├── reviews/*.md # Individual reviewer outputs
|
|
104
|
+
│ ├── discourse.md
|
|
105
|
+
│ └── final.md # Completion indicator
|
|
106
|
+
└── round-2/ # Additional rounds if re-reviewed
|
|
68
107
|
```
|
|
69
108
|
|
|
109
|
+
The CLI derives round information from the filesystem:
|
|
110
|
+
- **Round count**: Enumerated from `rounds/round-*/` directories
|
|
111
|
+
- **Round completion**: Determined by `final.md` presence
|
|
112
|
+
- **Reviewer progress**: Listed from `rounds/round-{n}/reviews/*.md`
|
|
113
|
+
|
|
70
114
|
## Supported AI Tools
|
|
71
115
|
|
|
72
116
|
| Tool | Config Directory |
|
package/dist/index.js
CHANGED
|
@@ -329,6 +329,16 @@ sessions/
|
|
|
329
329
|
function detectInstalledTools(targetDir, tools) {
|
|
330
330
|
return tools.filter((tool) => {
|
|
331
331
|
const configPath = join(targetDir, tool.configDir);
|
|
332
|
+
if (tool.id === "github-copilot") {
|
|
333
|
+
const copilotInstructions = join(
|
|
334
|
+
targetDir,
|
|
335
|
+
".github",
|
|
336
|
+
"copilot-instructions.md"
|
|
337
|
+
);
|
|
338
|
+
const copilotDir = join(targetDir, ".github", "copilot");
|
|
339
|
+
const copilotCommands = join(targetDir, ".github", "commands");
|
|
340
|
+
return existsSync(copilotInstructions) || existsSync(copilotDir) || existsSync(copilotCommands);
|
|
341
|
+
}
|
|
332
342
|
return existsSync(configPath);
|
|
333
343
|
});
|
|
334
344
|
}
|
|
@@ -417,6 +427,50 @@ function printBanner() {
|
|
|
417
427
|
console.log();
|
|
418
428
|
}
|
|
419
429
|
|
|
430
|
+
// packages/cli/src/lib/cli-config.ts
|
|
431
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
432
|
+
import { join as join3 } from "node:path";
|
|
433
|
+
var CLI_CONFIG_FILE = "cli-config.json";
|
|
434
|
+
function getCliConfigPath(targetDir) {
|
|
435
|
+
return join3(targetDir, ".ocr", CLI_CONFIG_FILE);
|
|
436
|
+
}
|
|
437
|
+
function loadCliConfig(targetDir) {
|
|
438
|
+
const configPath = getCliConfigPath(targetDir);
|
|
439
|
+
if (!existsSync3(configPath)) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
444
|
+
return JSON.parse(content);
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function saveCliConfig(targetDir, config) {
|
|
450
|
+
const configPath = getCliConfigPath(targetDir);
|
|
451
|
+
try {
|
|
452
|
+
const configWithMeta = {
|
|
453
|
+
...config,
|
|
454
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
455
|
+
};
|
|
456
|
+
writeFileSync3(configPath, JSON.stringify(configWithMeta, null, 2) + "\n");
|
|
457
|
+
return true;
|
|
458
|
+
} catch {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function getConfiguredToolIds(targetDir) {
|
|
463
|
+
const config = loadCliConfig(targetDir);
|
|
464
|
+
return config?.configuredTools ?? [];
|
|
465
|
+
}
|
|
466
|
+
function setConfiguredToolIds(targetDir, toolIds) {
|
|
467
|
+
const existing = loadCliConfig(targetDir) ?? { configuredTools: [] };
|
|
468
|
+
return saveCliConfig(targetDir, {
|
|
469
|
+
...existing,
|
|
470
|
+
configuredTools: toolIds
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
420
474
|
// packages/cli/src/commands/init.ts
|
|
421
475
|
var initCommand = new Command("init").description("Set up OCR for AI coding environments").option("-t, --tools <tools>", 'Comma-separated tool IDs or "all"').option("--no-inject", "Skip injecting instructions into AGENTS.md/CLAUDE.md").action(async (options) => {
|
|
422
476
|
printBanner();
|
|
@@ -481,6 +535,8 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
481
535
|
for (const result of successful) {
|
|
482
536
|
console.log(` ${chalk2.green("\u2713")} ${result.tool.name}`);
|
|
483
537
|
}
|
|
538
|
+
const successfulToolIds = successful.map((r) => r.tool.id);
|
|
539
|
+
setConfiguredToolIds(targetDir, successfulToolIds);
|
|
484
540
|
}
|
|
485
541
|
if (failed.length > 0) {
|
|
486
542
|
console.log();
|
|
@@ -528,21 +584,21 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
528
584
|
import { Command as Command2 } from "commander";
|
|
529
585
|
import chalk4 from "chalk";
|
|
530
586
|
import { watch } from "chokidar";
|
|
531
|
-
import { existsSync as
|
|
532
|
-
import { join as
|
|
587
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
588
|
+
import { join as join5, basename } from "node:path";
|
|
533
589
|
import logUpdate from "log-update";
|
|
534
590
|
|
|
535
591
|
// packages/cli/src/lib/guards.ts
|
|
536
|
-
import { existsSync as
|
|
537
|
-
import { join as
|
|
592
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
|
|
593
|
+
import { join as join4 } from "node:path";
|
|
538
594
|
import chalk3 from "chalk";
|
|
539
595
|
function checkOcrSetup(targetDir) {
|
|
540
|
-
const ocrDir =
|
|
541
|
-
const skillsDir =
|
|
542
|
-
const sessionsDir =
|
|
543
|
-
const hasOcrDir =
|
|
544
|
-
const hasSkills =
|
|
545
|
-
const hasSessions =
|
|
596
|
+
const ocrDir = join4(targetDir, ".ocr");
|
|
597
|
+
const skillsDir = join4(ocrDir, "skills");
|
|
598
|
+
const sessionsDir = join4(ocrDir, "sessions");
|
|
599
|
+
const hasOcrDir = existsSync4(ocrDir);
|
|
600
|
+
const hasSkills = existsSync4(skillsDir);
|
|
601
|
+
const hasSessions = existsSync4(sessionsDir);
|
|
546
602
|
return {
|
|
547
603
|
valid: hasOcrDir && hasSkills,
|
|
548
604
|
ocrDir,
|
|
@@ -558,7 +614,7 @@ function requireOcrSetup(targetDir) {
|
|
|
558
614
|
console.log();
|
|
559
615
|
console.log(chalk3.red.bold(" \u2717 OCR is not set up in this directory"));
|
|
560
616
|
console.log();
|
|
561
|
-
if (!
|
|
617
|
+
if (!existsSync4(status.ocrDir)) {
|
|
562
618
|
console.log(chalk3.dim(" The .ocr directory was not found."));
|
|
563
619
|
} else if (!status.hasSkills) {
|
|
564
620
|
console.log(chalk3.dim(" The .ocr/skills directory is missing."));
|
|
@@ -578,38 +634,55 @@ function requireOcrSetup(targetDir) {
|
|
|
578
634
|
return status;
|
|
579
635
|
}
|
|
580
636
|
function ensureSessionsDir(targetDir) {
|
|
581
|
-
const sessionsDir =
|
|
582
|
-
if (!
|
|
637
|
+
const sessionsDir = join4(targetDir, ".ocr", "sessions");
|
|
638
|
+
if (!existsSync4(sessionsDir)) {
|
|
583
639
|
mkdirSync2(sessionsDir, { recursive: true });
|
|
584
640
|
}
|
|
585
641
|
return sessionsDir;
|
|
586
642
|
}
|
|
587
643
|
|
|
588
644
|
// packages/cli/src/commands/progress.ts
|
|
645
|
+
function debounce(fn, delay) {
|
|
646
|
+
let timeoutId = null;
|
|
647
|
+
return (...args) => {
|
|
648
|
+
if (timeoutId) {
|
|
649
|
+
clearTimeout(timeoutId);
|
|
650
|
+
}
|
|
651
|
+
timeoutId = setTimeout(() => {
|
|
652
|
+
fn(...args);
|
|
653
|
+
timeoutId = null;
|
|
654
|
+
}, delay);
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
var lastRenderType = null;
|
|
658
|
+
var lastLineCount = 0;
|
|
589
659
|
var TOTAL_PHASES = 8;
|
|
590
660
|
function isSessionActive(sessionPath) {
|
|
591
|
-
const statePath =
|
|
592
|
-
if (!
|
|
661
|
+
const statePath = join5(sessionPath, "state.json");
|
|
662
|
+
if (!existsSync5(statePath)) {
|
|
593
663
|
return true;
|
|
594
664
|
}
|
|
595
665
|
try {
|
|
596
|
-
const stateContent =
|
|
666
|
+
const stateContent = readFileSync4(statePath, "utf-8");
|
|
597
667
|
const state = JSON.parse(stateContent);
|
|
598
|
-
|
|
668
|
+
if (state.status === "closed" || state.current_phase === "complete") {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
return true;
|
|
599
672
|
} catch {
|
|
600
673
|
return true;
|
|
601
674
|
}
|
|
602
675
|
}
|
|
603
676
|
function findLatestActiveSession(sessionsDir) {
|
|
604
|
-
if (!
|
|
677
|
+
if (!existsSync5(sessionsDir)) {
|
|
605
678
|
return null;
|
|
606
679
|
}
|
|
607
680
|
const sessions = readdirSync2(sessionsDir).filter((name) => {
|
|
608
|
-
const sessionPath =
|
|
681
|
+
const sessionPath = join5(sessionsDir, name);
|
|
609
682
|
return statSync(sessionPath).isDirectory();
|
|
610
683
|
}).sort().reverse();
|
|
611
684
|
for (const session of sessions) {
|
|
612
|
-
const sessionPath =
|
|
685
|
+
const sessionPath = join5(sessionsDir, session);
|
|
613
686
|
if (isSessionActive(sessionPath)) {
|
|
614
687
|
return session;
|
|
615
688
|
}
|
|
@@ -617,10 +690,10 @@ function findLatestActiveSession(sessionsDir) {
|
|
|
617
690
|
return null;
|
|
618
691
|
}
|
|
619
692
|
function countFindings(filePath) {
|
|
620
|
-
if (!
|
|
693
|
+
if (!existsSync5(filePath)) {
|
|
621
694
|
return 0;
|
|
622
695
|
}
|
|
623
|
-
const content =
|
|
696
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
624
697
|
const findingMatches = content.match(/^##\s+(Finding|Issue|Suggestion)/gm);
|
|
625
698
|
return findingMatches?.length ?? 0;
|
|
626
699
|
}
|
|
@@ -635,12 +708,12 @@ function formatReviewerName(filename) {
|
|
|
635
708
|
}
|
|
636
709
|
function parseSessionState(sessionPath, preservedStartTime) {
|
|
637
710
|
const session = basename(sessionPath);
|
|
638
|
-
const statePath =
|
|
639
|
-
if (!
|
|
711
|
+
const statePath = join5(sessionPath, "state.json");
|
|
712
|
+
if (!existsSync5(statePath)) {
|
|
640
713
|
return null;
|
|
641
714
|
}
|
|
642
715
|
try {
|
|
643
|
-
const stateContent =
|
|
716
|
+
const stateContent = readFileSync4(statePath, "utf-8");
|
|
644
717
|
const state = JSON.parse(stateContent);
|
|
645
718
|
return parseFromStateJson(session, state, sessionPath, preservedStartTime);
|
|
646
719
|
} catch {
|
|
@@ -648,16 +721,21 @@ function parseSessionState(sessionPath, preservedStartTime) {
|
|
|
648
721
|
}
|
|
649
722
|
}
|
|
650
723
|
function parseFromStateJson(session, state, sessionPath, preservedStartTime) {
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const
|
|
724
|
+
const effectiveStartTime = state.round_started_at ?? state.started_at;
|
|
725
|
+
const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
|
|
726
|
+
const roundsDir = join5(sessionPath, "rounds");
|
|
727
|
+
const rounds = deriveRoundsFromFilesystem(roundsDir);
|
|
728
|
+
const highestExistingRound = rounds.length > 0 ? Math.max(...rounds.map((r) => r.round)) : 1;
|
|
729
|
+
const stateRound = state.current_round ?? 1;
|
|
730
|
+
const currentRound = Math.min(stateRound, highestExistingRound);
|
|
731
|
+
const currentRoundDir = join5(roundsDir, `round-${currentRound}`);
|
|
732
|
+
const reviewsDir = join5(currentRoundDir, "reviews");
|
|
654
733
|
const reviewers = [];
|
|
655
|
-
if (
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
);
|
|
734
|
+
if (existsSync5(reviewsDir)) {
|
|
735
|
+
const entries = readdirSync2(reviewsDir);
|
|
736
|
+
const reviewFiles = entries.filter((f) => f.endsWith(".md"));
|
|
659
737
|
for (const file of reviewFiles) {
|
|
660
|
-
const reviewPath =
|
|
738
|
+
const reviewPath = join5(reviewsDir, file);
|
|
661
739
|
const findings = countFindings(reviewPath);
|
|
662
740
|
reviewers.push({
|
|
663
741
|
name: file.replace(".md", ""),
|
|
@@ -667,38 +745,79 @@ function parseFromStateJson(session, state, sessionPath, preservedStartTime) {
|
|
|
667
745
|
});
|
|
668
746
|
}
|
|
669
747
|
}
|
|
748
|
+
const contextComplete = existsSync5(
|
|
749
|
+
join5(sessionPath, "discovered-standards.md")
|
|
750
|
+
);
|
|
751
|
+
const changeContextComplete = existsSync5(join5(sessionPath, "context.md"));
|
|
752
|
+
const analysisComplete = changeContextComplete;
|
|
753
|
+
const reviewsComplete = state.phase_number > 4;
|
|
754
|
+
const discourseComplete = existsSync5(join5(currentRoundDir, "discourse.md"));
|
|
755
|
+
const synthesisComplete = existsSync5(join5(currentRoundDir, "final.md"));
|
|
670
756
|
return {
|
|
671
757
|
session,
|
|
672
758
|
phase: state.current_phase,
|
|
673
759
|
phaseNumber: state.phase_number,
|
|
674
760
|
totalPhases: TOTAL_PHASES,
|
|
675
|
-
contextComplete
|
|
676
|
-
|
|
677
|
-
analysisComplete
|
|
678
|
-
reviewsComplete
|
|
679
|
-
aggregationComplete:
|
|
680
|
-
|
|
681
|
-
|
|
761
|
+
contextComplete,
|
|
762
|
+
changeContextComplete,
|
|
763
|
+
analysisComplete,
|
|
764
|
+
reviewsComplete,
|
|
765
|
+
aggregationComplete: reviewsComplete,
|
|
766
|
+
// Aggregation is inline
|
|
767
|
+
discourseComplete,
|
|
768
|
+
synthesisComplete,
|
|
769
|
+
currentRound,
|
|
770
|
+
rounds,
|
|
682
771
|
reviewers,
|
|
683
772
|
startTime,
|
|
684
773
|
complete: state.current_phase === "complete"
|
|
685
774
|
};
|
|
686
775
|
}
|
|
776
|
+
function deriveRoundsFromFilesystem(roundsDir) {
|
|
777
|
+
if (!existsSync5(roundsDir)) {
|
|
778
|
+
return [];
|
|
779
|
+
}
|
|
780
|
+
const roundDirs = readdirSync2(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort((a, b) => {
|
|
781
|
+
const numA = parseInt(a.replace("round-", ""));
|
|
782
|
+
const numB = parseInt(b.replace("round-", ""));
|
|
783
|
+
return numA - numB;
|
|
784
|
+
});
|
|
785
|
+
return roundDirs.map((dir) => {
|
|
786
|
+
const roundNum = parseInt(dir.replace("round-", ""));
|
|
787
|
+
const roundPath = join5(roundsDir, dir);
|
|
788
|
+
const reviewsPath = join5(roundPath, "reviews");
|
|
789
|
+
const finalPath = join5(roundPath, "final.md");
|
|
790
|
+
const reviewers = [];
|
|
791
|
+
if (existsSync5(reviewsPath)) {
|
|
792
|
+
const files = readdirSync2(reviewsPath).filter((f) => f.endsWith(".md"));
|
|
793
|
+
reviewers.push(...files.map((f) => f.replace(".md", "")));
|
|
794
|
+
}
|
|
795
|
+
return {
|
|
796
|
+
round: roundNum,
|
|
797
|
+
isComplete: existsSync5(finalPath),
|
|
798
|
+
reviewers
|
|
799
|
+
};
|
|
800
|
+
});
|
|
801
|
+
}
|
|
687
802
|
function formatDuration(ms) {
|
|
688
|
-
const
|
|
803
|
+
const absMs = Math.abs(ms);
|
|
804
|
+
const totalSeconds = Math.floor(absMs / 1e3);
|
|
689
805
|
const hours = Math.floor(totalSeconds / 3600);
|
|
690
806
|
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
691
807
|
const seconds = totalSeconds % 60;
|
|
808
|
+
let duration;
|
|
692
809
|
if (hours > 0) {
|
|
693
|
-
|
|
810
|
+
duration = `${hours}h ${minutes}m ${seconds}s`;
|
|
694
811
|
} else if (minutes > 0) {
|
|
695
|
-
|
|
812
|
+
duration = `${minutes}m ${seconds}s`;
|
|
813
|
+
} else {
|
|
814
|
+
duration = `${seconds}s`;
|
|
696
815
|
}
|
|
697
|
-
return
|
|
816
|
+
return duration;
|
|
698
817
|
}
|
|
699
818
|
var PHASE_INFO = [
|
|
700
819
|
{ key: "context", label: "Context Discovery" },
|
|
701
|
-
{ key: "
|
|
820
|
+
{ key: "change-context", label: "Change Context" },
|
|
702
821
|
{ key: "analysis", label: "Tech Lead Analysis" },
|
|
703
822
|
{ key: "reviews", label: "Parallel Reviews" },
|
|
704
823
|
{ key: "aggregation", label: "Aggregate Findings" },
|
|
@@ -727,8 +846,9 @@ function renderProgress(state) {
|
|
|
727
846
|
log(chalk4.bold.white(" Open Code Review"));
|
|
728
847
|
log();
|
|
729
848
|
const elapsed = Date.now() - state.startTime;
|
|
849
|
+
const roundInfo = state.currentRound > 1 ? chalk4.cyan(` Round ${state.currentRound}`) + chalk4.dim(" \xB7 ") : "";
|
|
730
850
|
log(
|
|
731
|
-
chalk4.dim(" ") + chalk4.white(state.session) + chalk4.dim(" \xB7 ") + chalk4.white(formatDuration(elapsed))
|
|
851
|
+
chalk4.dim(" ") + chalk4.white(state.session) + chalk4.dim(" \xB7 ") + roundInfo + chalk4.white(formatDuration(elapsed))
|
|
732
852
|
);
|
|
733
853
|
log();
|
|
734
854
|
const progressPhases = state.complete ? 8 : state.phaseNumber;
|
|
@@ -739,7 +859,7 @@ function renderProgress(state) {
|
|
|
739
859
|
const phaseCompletion = {
|
|
740
860
|
waiting: false,
|
|
741
861
|
context: state.contextComplete,
|
|
742
|
-
|
|
862
|
+
"change-context": state.changeContextComplete,
|
|
743
863
|
analysis: state.analysisComplete,
|
|
744
864
|
reviews: state.reviewsComplete,
|
|
745
865
|
aggregation: state.aggregationComplete,
|
|
@@ -761,6 +881,9 @@ function renderProgress(state) {
|
|
|
761
881
|
}
|
|
762
882
|
log(` ${status} ${label}`);
|
|
763
883
|
if (phase.key === "reviews" && state.reviewers.length > 0) {
|
|
884
|
+
if (state.currentRound > 1) {
|
|
885
|
+
log(chalk4.dim(" ") + chalk4.cyan(`Round ${state.currentRound}`));
|
|
886
|
+
}
|
|
764
887
|
const reviewerLine = state.reviewers.map((r) => {
|
|
765
888
|
const icon = r.status === "complete" ? chalk4.green("\u2713") : chalk4.dim("\u25CB");
|
|
766
889
|
const name = chalk4.dim(r.displayName);
|
|
@@ -768,6 +891,17 @@ function renderProgress(state) {
|
|
|
768
891
|
return `${icon} ${name}${count}`;
|
|
769
892
|
}).join(chalk4.dim(" \u2502 "));
|
|
770
893
|
log(chalk4.dim(" ") + reviewerLine);
|
|
894
|
+
if (state.rounds.length > 1) {
|
|
895
|
+
const prevRounds = state.rounds.slice(0, -1);
|
|
896
|
+
for (const round of prevRounds) {
|
|
897
|
+
const roundLabel = chalk4.dim(` Round ${round.round}`);
|
|
898
|
+
const reviewerCount = round.reviewers.length;
|
|
899
|
+
const status2 = round.isComplete ? chalk4.green("\u2713") : chalk4.dim("\u25CB");
|
|
900
|
+
log(
|
|
901
|
+
`${roundLabel} ${status2} ${chalk4.dim(`${reviewerCount} reviewers`)}`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
771
905
|
}
|
|
772
906
|
}
|
|
773
907
|
log();
|
|
@@ -782,12 +916,22 @@ function renderProgress(state) {
|
|
|
782
916
|
)
|
|
783
917
|
);
|
|
784
918
|
log(
|
|
785
|
-
chalk4.dim(" ") + chalk4.dim("\u2192 ") + chalk4.white(
|
|
919
|
+
chalk4.dim(" ") + chalk4.dim("\u2192 ") + chalk4.white(
|
|
920
|
+
`.ocr/sessions/${state.session}/rounds/round-${state.currentRound}/final.md`
|
|
921
|
+
)
|
|
786
922
|
);
|
|
787
923
|
} else {
|
|
788
924
|
log(chalk4.dim(" Ctrl+C to exit"));
|
|
789
925
|
}
|
|
790
926
|
log();
|
|
927
|
+
if (lastRenderType !== "progress") {
|
|
928
|
+
logUpdate.clear();
|
|
929
|
+
}
|
|
930
|
+
lastRenderType = "progress";
|
|
931
|
+
while (lines.length < lastLineCount) {
|
|
932
|
+
lines.push("");
|
|
933
|
+
}
|
|
934
|
+
lastLineCount = lines.length;
|
|
791
935
|
logUpdate(lines.join("\n"));
|
|
792
936
|
}
|
|
793
937
|
function renderWaiting() {
|
|
@@ -807,16 +951,24 @@ function renderWaiting() {
|
|
|
807
951
|
log();
|
|
808
952
|
log(chalk4.dim(" Ctrl+C to exit"));
|
|
809
953
|
log();
|
|
954
|
+
if (lastRenderType !== "waiting") {
|
|
955
|
+
logUpdate.clear();
|
|
956
|
+
}
|
|
957
|
+
lastRenderType = "waiting";
|
|
958
|
+
while (lines.length < lastLineCount) {
|
|
959
|
+
lines.push("");
|
|
960
|
+
}
|
|
961
|
+
lastLineCount = lines.length;
|
|
810
962
|
logUpdate(lines.join("\n"));
|
|
811
963
|
}
|
|
812
964
|
var progressCommand = new Command2("progress").description("Watch real-time progress of a code review session").option("-s, --session <name>", "Specify session name").action(async (options) => {
|
|
813
965
|
const targetDir = process.cwd();
|
|
814
966
|
requireOcrSetup(targetDir);
|
|
815
967
|
const sessionsDir = ensureSessionsDir(targetDir);
|
|
816
|
-
const ocrDir =
|
|
968
|
+
const ocrDir = join5(targetDir, ".ocr");
|
|
817
969
|
if (options.session) {
|
|
818
|
-
const sessionPath =
|
|
819
|
-
if (!
|
|
970
|
+
const sessionPath = join5(sessionsDir, options.session);
|
|
971
|
+
if (!existsSync5(sessionPath)) {
|
|
820
972
|
console.log(chalk4.red(`Session not found: ${options.session}`));
|
|
821
973
|
process.exit(1);
|
|
822
974
|
}
|
|
@@ -846,7 +998,7 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
846
998
|
const watcher = watch(sessionPath, {
|
|
847
999
|
persistent: true,
|
|
848
1000
|
ignoreInitial: true,
|
|
849
|
-
depth:
|
|
1001
|
+
depth: 3
|
|
850
1002
|
});
|
|
851
1003
|
watcher.on("all", () => {
|
|
852
1004
|
const newState = parseSessionState(sessionPath, preservedStartTime2);
|
|
@@ -864,11 +1016,24 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
864
1016
|
return;
|
|
865
1017
|
}
|
|
866
1018
|
let currentSession = findLatestActiveSession(sessionsDir);
|
|
867
|
-
let currentSessionPath = currentSession ?
|
|
1019
|
+
let currentSessionPath = currentSession ? join5(sessionsDir, currentSession) : null;
|
|
868
1020
|
let sessionWatcher = null;
|
|
869
1021
|
let preservedStartTime;
|
|
870
|
-
const
|
|
871
|
-
if (currentSessionPath
|
|
1022
|
+
const updateDisplayImpl = () => {
|
|
1023
|
+
if (!currentSessionPath || !existsSync5(currentSessionPath) || !isSessionActive(currentSessionPath)) {
|
|
1024
|
+
const latestActive = findLatestActiveSession(sessionsDir);
|
|
1025
|
+
if (latestActive && latestActive !== currentSession) {
|
|
1026
|
+
currentSession = latestActive;
|
|
1027
|
+
currentSessionPath = join5(sessionsDir, latestActive);
|
|
1028
|
+
preservedStartTime = void 0;
|
|
1029
|
+
watchSession(currentSessionPath);
|
|
1030
|
+
} else if (!latestActive) {
|
|
1031
|
+
currentSession = null;
|
|
1032
|
+
currentSessionPath = null;
|
|
1033
|
+
preservedStartTime = void 0;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (currentSessionPath && existsSync5(currentSessionPath)) {
|
|
872
1037
|
const state = parseSessionState(currentSessionPath, preservedStartTime);
|
|
873
1038
|
if (state) {
|
|
874
1039
|
if (!preservedStartTime) {
|
|
@@ -883,6 +1048,7 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
883
1048
|
renderWaiting();
|
|
884
1049
|
}
|
|
885
1050
|
};
|
|
1051
|
+
const updateDisplay = debounce(updateDisplayImpl, 50);
|
|
886
1052
|
const watchSession = (sessionPath) => {
|
|
887
1053
|
if (sessionWatcher) {
|
|
888
1054
|
sessionWatcher.close();
|
|
@@ -890,24 +1056,24 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
890
1056
|
sessionWatcher = watch(sessionPath, {
|
|
891
1057
|
persistent: true,
|
|
892
1058
|
ignoreInitial: true,
|
|
893
|
-
depth:
|
|
1059
|
+
depth: 3
|
|
894
1060
|
});
|
|
895
1061
|
sessionWatcher.on("all", updateDisplay);
|
|
896
1062
|
};
|
|
897
|
-
|
|
1063
|
+
updateDisplayImpl();
|
|
898
1064
|
if (currentSessionPath) {
|
|
899
1065
|
watchSession(currentSessionPath);
|
|
900
1066
|
}
|
|
901
1067
|
const timerInterval = setInterval(updateDisplay, 1e3);
|
|
902
|
-
const watchDir =
|
|
1068
|
+
const watchDir = existsSync5(ocrDir) ? ocrDir : targetDir;
|
|
903
1069
|
const dirWatcher = watch(watchDir, {
|
|
904
1070
|
persistent: true,
|
|
905
1071
|
ignoreInitial: true,
|
|
906
1072
|
depth: 3
|
|
907
1073
|
});
|
|
908
1074
|
dirWatcher.on("addDir", (dirPath) => {
|
|
909
|
-
const parentDir =
|
|
910
|
-
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(
|
|
1075
|
+
const parentDir = join5(dirPath, "..");
|
|
1076
|
+
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join5(".ocr", "sessions"));
|
|
911
1077
|
if (isDirectChild && !dirPath.endsWith("sessions")) {
|
|
912
1078
|
const newSession = basename(dirPath);
|
|
913
1079
|
currentSession = newSession;
|
|
@@ -932,16 +1098,16 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
932
1098
|
import { Command as Command3 } from "commander";
|
|
933
1099
|
import chalk5 from "chalk";
|
|
934
1100
|
import ora2 from "ora";
|
|
935
|
-
import { existsSync as
|
|
936
|
-
import { join as
|
|
1101
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1102
|
+
import { join as join6 } from "node:path";
|
|
937
1103
|
function detectConfiguredTools(targetDir) {
|
|
938
1104
|
return AI_TOOLS.filter((tool) => {
|
|
939
1105
|
if (tool.commandStrategy === "subdirectory") {
|
|
940
|
-
const ocrDir =
|
|
941
|
-
return
|
|
1106
|
+
const ocrDir = join6(targetDir, tool.commandsDir, "ocr");
|
|
1107
|
+
return existsSync6(ocrDir);
|
|
942
1108
|
} else {
|
|
943
|
-
const reviewCmd =
|
|
944
|
-
return
|
|
1109
|
+
const reviewCmd = join6(targetDir, tool.commandsDir, "ocr-review.md");
|
|
1110
|
+
return existsSync6(reviewCmd);
|
|
945
1111
|
}
|
|
946
1112
|
});
|
|
947
1113
|
}
|
|
@@ -954,11 +1120,17 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
954
1120
|
console.log();
|
|
955
1121
|
console.log(chalk5.bold.cyan(" Open Code Review - Update"));
|
|
956
1122
|
console.log();
|
|
1123
|
+
const savedToolIds = getConfiguredToolIds(targetDir);
|
|
957
1124
|
const configuredTools = detectConfiguredTools(targetDir);
|
|
958
1125
|
const installedTools = detectInstalledTools(targetDir, AI_TOOLS);
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1126
|
+
let toolsToUpdate;
|
|
1127
|
+
if (savedToolIds.length > 0) {
|
|
1128
|
+
toolsToUpdate = AI_TOOLS.filter((tool) => savedToolIds.includes(tool.id));
|
|
1129
|
+
} else {
|
|
1130
|
+
toolsToUpdate = AI_TOOLS.filter(
|
|
1131
|
+
(tool) => configuredTools.some((t) => t.id === tool.id) || installedTools.some((t) => t.id === tool.id)
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
962
1134
|
if (toolsToUpdate.length === 0) {
|
|
963
1135
|
console.log(chalk5.yellow(" No configured AI tools found."));
|
|
964
1136
|
console.log(chalk5.dim(" Run `ocr init` to set up OCR first."));
|
|
@@ -1034,10 +1206,10 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
1034
1206
|
if (updateInject) {
|
|
1035
1207
|
if (options.dryRun) {
|
|
1036
1208
|
console.log(chalk5.dim(" Would update:"));
|
|
1037
|
-
if (
|
|
1209
|
+
if (existsSync6(join6(targetDir, "AGENTS.md"))) {
|
|
1038
1210
|
console.log(chalk5.dim(" \u2022 AGENTS.md (OCR managed block)"));
|
|
1039
1211
|
}
|
|
1040
|
-
if (
|
|
1212
|
+
if (existsSync6(join6(targetDir, "CLAUDE.md"))) {
|
|
1041
1213
|
console.log(chalk5.dim(" \u2022 CLAUDE.md (OCR managed block)"));
|
|
1042
1214
|
}
|
|
1043
1215
|
console.log();
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-code-review/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "CLI for Open Code Review - Multi-environment setup and progress tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"commander": "^13.0.0",
|
|
35
35
|
"log-update": "^7.0.2",
|
|
36
36
|
"ora": "^8.1.1",
|
|
37
|
-
"@open-code-review/agents": "1.
|
|
37
|
+
"@open-code-review/agents": "1.3.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|