@open-code-review/cli 1.1.1 → 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/README.md CHANGED
@@ -57,7 +57,9 @@ 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
 
@@ -67,6 +69,26 @@ Update OCR skills and commands to the latest version.
67
69
  ocr update
68
70
  ```
69
71
 
72
+ ## Session Storage
73
+
74
+ The CLI reads session state from `.ocr/sessions/{date}-{branch}/`:
75
+
76
+ ```
77
+ .ocr/sessions/2026-01-26-feature-auth/
78
+ ├── state.json # Workflow state (read by progress command)
79
+ └── rounds/
80
+ ├── round-1/
81
+ │ ├── reviews/*.md # Individual reviewer outputs
82
+ │ ├── discourse.md
83
+ │ └── final.md # Completion indicator
84
+ └── round-2/ # Additional rounds if re-reviewed
85
+ ```
86
+
87
+ The CLI derives round information from the filesystem:
88
+ - **Round count**: Enumerated from `rounds/round-*/` directories
89
+ - **Round completion**: Determined by `final.md` presence
90
+ - **Reviewer progress**: Listed from `rounds/round-{n}/reviews/*.md`
91
+
70
92
  ## Supported AI Tools
71
93
 
72
94
  | 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 existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
532
- import { join as join4, basename } from "node:path";
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 existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
537
- import { join as join3 } from "node:path";
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 = join3(targetDir, ".ocr");
541
- const skillsDir = join3(ocrDir, "skills");
542
- const sessionsDir = join3(ocrDir, "sessions");
543
- const hasOcrDir = existsSync3(ocrDir);
544
- const hasSkills = existsSync3(skillsDir);
545
- const hasSessions = existsSync3(sessionsDir);
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 (!existsSync3(status.ocrDir)) {
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 = join3(targetDir, ".ocr", "sessions");
582
- if (!existsSync3(sessionsDir)) {
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 = join4(sessionPath, "state.json");
592
- if (!existsSync4(statePath)) {
661
+ const statePath = join5(sessionPath, "state.json");
662
+ if (!existsSync5(statePath)) {
593
663
  return true;
594
664
  }
595
665
  try {
596
- const stateContent = readFileSync3(statePath, "utf-8");
666
+ const stateContent = readFileSync4(statePath, "utf-8");
597
667
  const state = JSON.parse(stateContent);
598
- return state.status !== "closed";
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 (!existsSync4(sessionsDir)) {
677
+ if (!existsSync5(sessionsDir)) {
605
678
  return null;
606
679
  }
607
680
  const sessions = readdirSync2(sessionsDir).filter((name) => {
608
- const sessionPath = join4(sessionsDir, name);
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 = join4(sessionsDir, session);
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 (!existsSync4(filePath)) {
693
+ if (!existsSync5(filePath)) {
621
694
  return 0;
622
695
  }
623
- const content = readFileSync3(filePath, "utf-8");
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 = join4(sessionPath, "state.json");
639
- if (!existsSync4(statePath)) {
711
+ const statePath = join5(sessionPath, "state.json");
712
+ if (!existsSync5(statePath)) {
640
713
  return null;
641
714
  }
642
715
  try {
643
- const stateContent = readFileSync3(statePath, "utf-8");
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 startTime = preservedStartTime ?? (state.started_at ? new Date(state.started_at).getTime() : Date.now());
652
- const completed = new Set(state.completed_phases);
653
- const reviewsDir = join4(sessionPath, "reviews");
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 (existsSync4(reviewsDir)) {
656
- const reviewFiles = readdirSync2(reviewsDir).filter(
657
- (f) => f.endsWith(".md")
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 = join4(reviewsDir, file);
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: completed.has("context"),
676
- requirementsComplete: completed.has("requirements"),
677
- analysisComplete: completed.has("analysis"),
678
- reviewsComplete: completed.has("reviews"),
679
- aggregationComplete: completed.has("aggregation"),
680
- discourseComplete: completed.has("discourse"),
681
- synthesisComplete: completed.has("synthesis"),
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 totalSeconds = Math.floor(ms / 1e3);
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
- return `${hours}h ${minutes}m ${seconds}s`;
810
+ duration = `${hours}h ${minutes}m ${seconds}s`;
694
811
  } else if (minutes > 0) {
695
- return `${minutes}m ${seconds}s`;
812
+ duration = `${minutes}m ${seconds}s`;
813
+ } else {
814
+ duration = `${seconds}s`;
696
815
  }
697
- return `${seconds}s`;
816
+ return duration;
698
817
  }
699
818
  var PHASE_INFO = [
700
819
  { key: "context", label: "Context Discovery" },
701
- { key: "requirements", label: "Requirements Gathering" },
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
- requirements: state.requirementsComplete,
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(`.ocr/sessions/${state.session}/final.md`)
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 = join4(targetDir, ".ocr");
968
+ const ocrDir = join5(targetDir, ".ocr");
817
969
  if (options.session) {
818
- const sessionPath = join4(sessionsDir, options.session);
819
- if (!existsSync4(sessionPath)) {
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: 2
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 ? join4(sessionsDir, currentSession) : null;
1019
+ let currentSessionPath = currentSession ? join5(sessionsDir, currentSession) : null;
868
1020
  let sessionWatcher = null;
869
1021
  let preservedStartTime;
870
- const updateDisplay = () => {
871
- if (currentSessionPath && existsSync4(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: 2
1059
+ depth: 3
894
1060
  });
895
1061
  sessionWatcher.on("all", updateDisplay);
896
1062
  };
897
- updateDisplay();
1063
+ updateDisplayImpl();
898
1064
  if (currentSessionPath) {
899
1065
  watchSession(currentSessionPath);
900
1066
  }
901
1067
  const timerInterval = setInterval(updateDisplay, 1e3);
902
- const watchDir = existsSync4(ocrDir) ? ocrDir : targetDir;
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 = join4(dirPath, "..");
910
- const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join4(".ocr", "sessions"));
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 existsSync5 } from "node:fs";
936
- import { join as join5 } from "node:path";
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 = join5(targetDir, tool.commandsDir, "ocr");
941
- return existsSync5(ocrDir);
1106
+ const ocrDir = join6(targetDir, tool.commandsDir, "ocr");
1107
+ return existsSync6(ocrDir);
942
1108
  } else {
943
- const reviewCmd = join5(targetDir, tool.commandsDir, "ocr-review.md");
944
- return existsSync5(reviewCmd);
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
- const toolsToUpdate = AI_TOOLS.filter(
960
- (tool) => configuredTools.some((t) => t.id === tool.id) || installedTools.some((t) => t.id === tool.id)
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 (existsSync5(join5(targetDir, "AGENTS.md"))) {
1209
+ if (existsSync6(join6(targetDir, "AGENTS.md"))) {
1038
1210
  console.log(chalk5.dim(" \u2022 AGENTS.md (OCR managed block)"));
1039
1211
  }
1040
- if (existsSync5(join5(targetDir, "CLAUDE.md"))) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-code-review/cli",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "CLI for Open Code Review - Multi-environment setup and progress tracking",
5
5
  "type": "module",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-code-review/cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.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.1.1"
37
+ "@open-code-review/agents": "1.2.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"