@open-code-review/cli 1.3.0 → 1.4.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/dist/index.js +865 -327
- package/dist/package.json +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -289,6 +289,28 @@ sessions/
|
|
|
289
289
|
} catch {
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
+
const reviewersDir = join(ocrSkillsDest, "references", "reviewers");
|
|
293
|
+
const existingReviewers = /* @__PURE__ */ new Map();
|
|
294
|
+
const warnings = [];
|
|
295
|
+
if (existsSync(reviewersDir)) {
|
|
296
|
+
try {
|
|
297
|
+
const reviewerFiles = readdirSync(reviewersDir).filter(
|
|
298
|
+
(f) => f.endsWith(".md")
|
|
299
|
+
);
|
|
300
|
+
for (const file of reviewerFiles) {
|
|
301
|
+
const filePath = join(reviewersDir, file);
|
|
302
|
+
try {
|
|
303
|
+
existingReviewers.set(file, readFileSync(filePath));
|
|
304
|
+
} catch (err) {
|
|
305
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
306
|
+
warnings.push(`Could not read reviewer ${file}: ${msg}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
311
|
+
warnings.push(`Could not read reviewers directory: ${msg}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
292
314
|
const skillsOk = copyDirSafe(ocrSkillsSource, ocrSkillsDest);
|
|
293
315
|
if (!skillsOk) {
|
|
294
316
|
return {
|
|
@@ -313,6 +335,17 @@ sessions/
|
|
|
313
335
|
} catch {
|
|
314
336
|
}
|
|
315
337
|
}
|
|
338
|
+
if (existingReviewers.size > 0) {
|
|
339
|
+
ensureDir(reviewersDir);
|
|
340
|
+
for (const [file, content] of existingReviewers) {
|
|
341
|
+
try {
|
|
342
|
+
writeFileSync(join(reviewersDir, file), content);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
345
|
+
warnings.push(`Could not restore reviewer ${file}: ${msg}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
316
349
|
const commandsOk = installCommandsForTool(tool, commandsSource, targetDir);
|
|
317
350
|
if (!commandsOk) {
|
|
318
351
|
return {
|
|
@@ -323,7 +356,8 @@ sessions/
|
|
|
323
356
|
}
|
|
324
357
|
return {
|
|
325
358
|
tool,
|
|
326
|
-
success: true
|
|
359
|
+
success: true,
|
|
360
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
327
361
|
};
|
|
328
362
|
}
|
|
329
363
|
function detectInstalledTools(targetDir, tools) {
|
|
@@ -357,9 +391,11 @@ Always open \`.ocr/skills/SKILL.md\` when the request:
|
|
|
357
391
|
- Asks for code review, PR review, or feedback on changes
|
|
358
392
|
- Mentions "review my code" or similar phrases
|
|
359
393
|
- Wants multi-perspective analysis of code quality
|
|
394
|
+
- Asks to map, organize, or navigate a large changeset
|
|
360
395
|
|
|
361
396
|
Use \`.ocr/skills/SKILL.md\` to learn:
|
|
362
397
|
- How to run the 8-phase review workflow
|
|
398
|
+
- How to generate a Code Review Map for large changesets
|
|
363
399
|
- Available reviewer personas and their focus areas
|
|
364
400
|
- Session management and output format
|
|
365
401
|
|
|
@@ -545,6 +581,14 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
545
581
|
console.log(` ${chalk2.red("\u2717")} ${result.tool.name}: ${result.error}`);
|
|
546
582
|
}
|
|
547
583
|
}
|
|
584
|
+
const allWarnings = results.flatMap((r) => r.warnings ?? []);
|
|
585
|
+
if (allWarnings.length > 0) {
|
|
586
|
+
console.log();
|
|
587
|
+
console.log(chalk2.yellow("\u26A0 Warnings:"));
|
|
588
|
+
for (const warning of allWarnings) {
|
|
589
|
+
console.log(` ${chalk2.yellow("\u26A0")} ${warning}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
548
592
|
if (options.inject && successful.length > 0) {
|
|
549
593
|
console.log();
|
|
550
594
|
const injectSpinner = ora(
|
|
@@ -582,11 +626,11 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
582
626
|
|
|
583
627
|
// packages/cli/src/commands/progress.ts
|
|
584
628
|
import { Command as Command2 } from "commander";
|
|
585
|
-
import
|
|
629
|
+
import chalk7 from "chalk";
|
|
586
630
|
import { watch } from "chokidar";
|
|
587
|
-
import { existsSync as
|
|
588
|
-
import { join as
|
|
589
|
-
import
|
|
631
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, statSync } from "node:fs";
|
|
632
|
+
import { join as join8, basename as basename3 } from "node:path";
|
|
633
|
+
import logUpdate4 from "log-update";
|
|
590
634
|
|
|
591
635
|
// packages/cli/src/lib/guards.ts
|
|
592
636
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
|
|
@@ -641,30 +685,66 @@ function ensureSessionsDir(targetDir) {
|
|
|
641
685
|
return sessionsDir;
|
|
642
686
|
}
|
|
643
687
|
|
|
644
|
-
// packages/cli/src/
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
688
|
+
// packages/cli/src/lib/progress/strategy.ts
|
|
689
|
+
var strategies = /* @__PURE__ */ new Map();
|
|
690
|
+
function registerStrategy(strategy) {
|
|
691
|
+
strategies.set(strategy.workflowType, strategy);
|
|
692
|
+
}
|
|
693
|
+
function getStrategy(workflowType) {
|
|
694
|
+
return strategies.get(workflowType);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// packages/cli/src/lib/progress/detector.ts
|
|
698
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "node:fs";
|
|
699
|
+
import { join as join5 } from "node:path";
|
|
700
|
+
function detectWorkflowType(sessionPath, explicitType) {
|
|
701
|
+
if (explicitType) {
|
|
702
|
+
return explicitType;
|
|
703
|
+
}
|
|
704
|
+
const statePath = join5(sessionPath, "state.json");
|
|
705
|
+
if (existsSync5(statePath)) {
|
|
706
|
+
try {
|
|
707
|
+
const content = readFileSync4(statePath, "utf-8");
|
|
708
|
+
const state = JSON.parse(content);
|
|
709
|
+
if (state.workflow_type) {
|
|
710
|
+
return state.workflow_type;
|
|
711
|
+
}
|
|
712
|
+
} catch {
|
|
650
713
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
714
|
+
}
|
|
715
|
+
const hasMapDir = existsSync5(join5(sessionPath, "map"));
|
|
716
|
+
const hasRoundsDir = existsSync5(join5(sessionPath, "rounds"));
|
|
717
|
+
if (hasMapDir && !hasRoundsDir) {
|
|
718
|
+
return "map";
|
|
719
|
+
}
|
|
720
|
+
if (hasRoundsDir && !hasMapDir) {
|
|
721
|
+
return "review";
|
|
722
|
+
}
|
|
723
|
+
if (hasMapDir && hasRoundsDir) {
|
|
724
|
+
if (existsSync5(statePath)) {
|
|
725
|
+
try {
|
|
726
|
+
const content = readFileSync4(statePath, "utf-8");
|
|
727
|
+
const state = JSON.parse(content);
|
|
728
|
+
const phase = state.current_phase;
|
|
729
|
+
if (phase.startsWith("map-") || phase === "topology" || phase === "flow-analysis" || phase === "requirements-mapping") {
|
|
730
|
+
return "map";
|
|
731
|
+
}
|
|
732
|
+
return "review";
|
|
733
|
+
} catch {
|
|
734
|
+
return "review";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return "review";
|
|
656
739
|
}
|
|
657
|
-
var lastRenderType = null;
|
|
658
|
-
var lastLineCount = 0;
|
|
659
|
-
var TOTAL_PHASES = 8;
|
|
660
740
|
function isSessionActive(sessionPath) {
|
|
661
741
|
const statePath = join5(sessionPath, "state.json");
|
|
662
742
|
if (!existsSync5(statePath)) {
|
|
663
743
|
return true;
|
|
664
744
|
}
|
|
665
745
|
try {
|
|
666
|
-
const
|
|
667
|
-
const state = JSON.parse(
|
|
746
|
+
const content = readFileSync4(statePath, "utf-8");
|
|
747
|
+
const state = JSON.parse(content);
|
|
668
748
|
if (state.status === "closed" || state.current_phase === "complete") {
|
|
669
749
|
return false;
|
|
670
750
|
}
|
|
@@ -673,27 +753,119 @@ function isSessionActive(sessionPath) {
|
|
|
673
753
|
return true;
|
|
674
754
|
}
|
|
675
755
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
678
|
-
|
|
756
|
+
function detectActiveWorkflows(sessionPath) {
|
|
757
|
+
const activeWorkflows = [];
|
|
758
|
+
const hasRoundsDir = existsSync5(join5(sessionPath, "rounds"));
|
|
759
|
+
if (hasRoundsDir) {
|
|
760
|
+
const roundsDir = join5(sessionPath, "rounds");
|
|
761
|
+
const rounds = existsSync5(roundsDir) ? readdirSync2(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort() : [];
|
|
762
|
+
if (rounds.length > 0) {
|
|
763
|
+
const latestRound = rounds[rounds.length - 1];
|
|
764
|
+
const finalPath = join5(roundsDir, latestRound, "final.md");
|
|
765
|
+
if (!existsSync5(finalPath)) {
|
|
766
|
+
activeWorkflows.push("review");
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
activeWorkflows.push("review");
|
|
770
|
+
}
|
|
679
771
|
}
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
772
|
+
const hasMapDir = existsSync5(join5(sessionPath, "map"));
|
|
773
|
+
if (hasMapDir) {
|
|
774
|
+
const runsDir = join5(sessionPath, "map", "runs");
|
|
775
|
+
const runs = existsSync5(runsDir) ? readdirSync2(runsDir).filter((d) => d.match(/^run-\d+$/)).sort() : [];
|
|
776
|
+
if (runs.length > 0) {
|
|
777
|
+
const latestRun = runs[runs.length - 1];
|
|
778
|
+
const mapPath = join5(runsDir, latestRun, "map.md");
|
|
779
|
+
if (!existsSync5(mapPath)) {
|
|
780
|
+
activeWorkflows.push("map");
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
activeWorkflows.push("map");
|
|
688
784
|
}
|
|
689
785
|
}
|
|
690
|
-
|
|
786
|
+
if (activeWorkflows.length === 0) {
|
|
787
|
+
const statePath = join5(sessionPath, "state.json");
|
|
788
|
+
if (existsSync5(statePath)) {
|
|
789
|
+
try {
|
|
790
|
+
const content = readFileSync4(statePath, "utf-8");
|
|
791
|
+
const state = JSON.parse(content);
|
|
792
|
+
if (state.workflow_type && state.current_phase !== "complete") {
|
|
793
|
+
activeWorkflows.push(state.workflow_type);
|
|
794
|
+
}
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return activeWorkflows;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// packages/cli/src/lib/progress/render-utils.ts
|
|
803
|
+
import chalk4 from "chalk";
|
|
804
|
+
import logUpdate from "log-update";
|
|
805
|
+
var lastRenderType = null;
|
|
806
|
+
var lastLineCount = 0;
|
|
807
|
+
function formatDuration(ms) {
|
|
808
|
+
const clampedMs = Math.max(0, ms);
|
|
809
|
+
const totalSeconds = Math.floor(clampedMs / 1e3);
|
|
810
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
811
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
812
|
+
const seconds = totalSeconds % 60;
|
|
813
|
+
if (hours > 0) {
|
|
814
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
815
|
+
} else if (minutes > 0) {
|
|
816
|
+
return `${minutes}m ${seconds}s`;
|
|
817
|
+
} else {
|
|
818
|
+
return `${seconds}s`;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function renderProgressBar(current, total, label) {
|
|
822
|
+
const width = 24;
|
|
823
|
+
const filled = Math.round(current / total * width);
|
|
824
|
+
const empty = width - filled;
|
|
825
|
+
const bar = chalk4.green("\u2501".repeat(filled)) + chalk4.dim("\u2500".repeat(empty));
|
|
826
|
+
const percent = Math.round(current / total * 100);
|
|
827
|
+
const percentStr = chalk4.bold.white(`${percent}%`);
|
|
828
|
+
return label ? `${bar} ${percentStr} ${chalk4.dim("\xB7")} ${chalk4.cyan(label)}` : `${bar} ${percentStr}`;
|
|
829
|
+
}
|
|
830
|
+
function getPhaseStatus(isComplete, isCurrent) {
|
|
831
|
+
if (isComplete) return chalk4.green("\u2713");
|
|
832
|
+
if (isCurrent) return chalk4.cyan("\u25B8");
|
|
833
|
+
return chalk4.dim("\xB7");
|
|
834
|
+
}
|
|
835
|
+
function clearForRenderType(renderType) {
|
|
836
|
+
if (lastRenderType !== renderType) {
|
|
837
|
+
logUpdate.clear();
|
|
838
|
+
}
|
|
839
|
+
lastRenderType = renderType;
|
|
840
|
+
}
|
|
841
|
+
function padLines(lines) {
|
|
842
|
+
while (lines.length < lastLineCount) {
|
|
843
|
+
lines.push("");
|
|
844
|
+
}
|
|
845
|
+
lastLineCount = lines.length;
|
|
846
|
+
return lines;
|
|
691
847
|
}
|
|
848
|
+
|
|
849
|
+
// packages/cli/src/lib/progress/review-strategy.ts
|
|
850
|
+
import chalk5 from "chalk";
|
|
851
|
+
import logUpdate2 from "log-update";
|
|
852
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
853
|
+
import { join as join6, basename } from "node:path";
|
|
854
|
+
var REVIEW_PHASES = [
|
|
855
|
+
{ key: "context", label: "Context Discovery" },
|
|
856
|
+
{ key: "change-context", label: "Change Context" },
|
|
857
|
+
{ key: "analysis", label: "Tech Lead Analysis" },
|
|
858
|
+
{ key: "reviews", label: "Parallel Reviews" },
|
|
859
|
+
{ key: "aggregation", label: "Aggregate Findings" },
|
|
860
|
+
{ key: "discourse", label: "Reviewer Discourse" },
|
|
861
|
+
{ key: "synthesis", label: "Final Synthesis" },
|
|
862
|
+
{ key: "complete", label: "Complete" }
|
|
863
|
+
];
|
|
692
864
|
function countFindings(filePath) {
|
|
693
|
-
if (!
|
|
865
|
+
if (!existsSync6(filePath)) {
|
|
694
866
|
return 0;
|
|
695
867
|
}
|
|
696
|
-
const content =
|
|
868
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
697
869
|
const findingMatches = content.match(/^##\s+(Finding|Issue|Suggestion)/gm);
|
|
698
870
|
return findingMatches?.length ?? 0;
|
|
699
871
|
}
|
|
@@ -706,346 +878,622 @@ function formatReviewerName(filename) {
|
|
|
706
878
|
}
|
|
707
879
|
return base.charAt(0).toUpperCase() + base.slice(1);
|
|
708
880
|
}
|
|
709
|
-
function parseSessionState(sessionPath, preservedStartTime) {
|
|
710
|
-
const session = basename(sessionPath);
|
|
711
|
-
const statePath = join5(sessionPath, "state.json");
|
|
712
|
-
if (!existsSync5(statePath)) {
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
|
-
try {
|
|
716
|
-
const stateContent = readFileSync4(statePath, "utf-8");
|
|
717
|
-
const state = JSON.parse(stateContent);
|
|
718
|
-
return parseFromStateJson(session, state, sessionPath, preservedStartTime);
|
|
719
|
-
} catch {
|
|
720
|
-
return null;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
function parseFromStateJson(session, state, sessionPath, preservedStartTime) {
|
|
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");
|
|
733
|
-
const reviewers = [];
|
|
734
|
-
if (existsSync5(reviewsDir)) {
|
|
735
|
-
const entries = readdirSync2(reviewsDir);
|
|
736
|
-
const reviewFiles = entries.filter((f) => f.endsWith(".md"));
|
|
737
|
-
for (const file of reviewFiles) {
|
|
738
|
-
const reviewPath = join5(reviewsDir, file);
|
|
739
|
-
const findings = countFindings(reviewPath);
|
|
740
|
-
reviewers.push({
|
|
741
|
-
name: file.replace(".md", ""),
|
|
742
|
-
displayName: formatReviewerName(file),
|
|
743
|
-
status: "complete",
|
|
744
|
-
findings
|
|
745
|
-
});
|
|
746
|
-
}
|
|
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"));
|
|
756
|
-
return {
|
|
757
|
-
session,
|
|
758
|
-
phase: state.current_phase,
|
|
759
|
-
phaseNumber: state.phase_number,
|
|
760
|
-
totalPhases: TOTAL_PHASES,
|
|
761
|
-
contextComplete,
|
|
762
|
-
changeContextComplete,
|
|
763
|
-
analysisComplete,
|
|
764
|
-
reviewsComplete,
|
|
765
|
-
aggregationComplete: reviewsComplete,
|
|
766
|
-
// Aggregation is inline
|
|
767
|
-
discourseComplete,
|
|
768
|
-
synthesisComplete,
|
|
769
|
-
currentRound,
|
|
770
|
-
rounds,
|
|
771
|
-
reviewers,
|
|
772
|
-
startTime,
|
|
773
|
-
complete: state.current_phase === "complete"
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
881
|
function deriveRoundsFromFilesystem(roundsDir) {
|
|
777
|
-
if (!
|
|
882
|
+
if (!existsSync6(roundsDir)) {
|
|
778
883
|
return [];
|
|
779
884
|
}
|
|
780
|
-
const roundDirs =
|
|
885
|
+
const roundDirs = readdirSync3(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort((a, b) => {
|
|
781
886
|
const numA = parseInt(a.replace("round-", ""));
|
|
782
887
|
const numB = parseInt(b.replace("round-", ""));
|
|
783
888
|
return numA - numB;
|
|
784
889
|
});
|
|
785
890
|
return roundDirs.map((dir) => {
|
|
786
891
|
const roundNum = parseInt(dir.replace("round-", ""));
|
|
787
|
-
const roundPath =
|
|
788
|
-
const reviewsPath =
|
|
789
|
-
const finalPath =
|
|
892
|
+
const roundPath = join6(roundsDir, dir);
|
|
893
|
+
const reviewsPath = join6(roundPath, "reviews");
|
|
894
|
+
const finalPath = join6(roundPath, "final.md");
|
|
790
895
|
const reviewers = [];
|
|
791
|
-
if (
|
|
792
|
-
const files =
|
|
896
|
+
if (existsSync6(reviewsPath)) {
|
|
897
|
+
const files = readdirSync3(reviewsPath).filter((f) => f.endsWith(".md"));
|
|
793
898
|
reviewers.push(...files.map((f) => f.replace(".md", "")));
|
|
794
899
|
}
|
|
795
900
|
return {
|
|
796
901
|
round: roundNum,
|
|
797
|
-
isComplete:
|
|
902
|
+
isComplete: existsSync6(finalPath),
|
|
798
903
|
reviewers
|
|
799
904
|
};
|
|
800
905
|
});
|
|
801
906
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
907
|
+
var ReviewProgressStrategy = class {
|
|
908
|
+
workflowType = "review";
|
|
909
|
+
phases = REVIEW_PHASES;
|
|
910
|
+
totalPhases = 8;
|
|
911
|
+
parseState(sessionPath, preservedStartTime) {
|
|
912
|
+
const session = basename(sessionPath);
|
|
913
|
+
const statePath = join6(sessionPath, "state.json");
|
|
914
|
+
if (!existsSync6(statePath)) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
try {
|
|
918
|
+
const stateContent = readFileSync5(statePath, "utf-8");
|
|
919
|
+
const state = JSON.parse(stateContent);
|
|
920
|
+
return this.parseFromStateJson(
|
|
921
|
+
session,
|
|
922
|
+
state,
|
|
923
|
+
sessionPath,
|
|
924
|
+
preservedStartTime
|
|
925
|
+
);
|
|
926
|
+
} catch {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
815
929
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
930
|
+
parseFromStateJson(session, state, sessionPath, preservedStartTime) {
|
|
931
|
+
const effectiveStartTime = state.round_started_at ?? state.started_at;
|
|
932
|
+
const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
|
|
933
|
+
const roundsDir = join6(sessionPath, "rounds");
|
|
934
|
+
const rounds = deriveRoundsFromFilesystem(roundsDir);
|
|
935
|
+
const highestExistingRound = rounds.length > 0 ? Math.max(...rounds.map((r) => r.round)) : 1;
|
|
936
|
+
const stateRound = state.current_round ?? 1;
|
|
937
|
+
const currentRound = Math.min(stateRound, highestExistingRound);
|
|
938
|
+
const currentRoundDir = join6(roundsDir, `round-${currentRound}`);
|
|
939
|
+
const reviewsDir = join6(currentRoundDir, "reviews");
|
|
940
|
+
const reviewers = [];
|
|
941
|
+
if (existsSync6(reviewsDir)) {
|
|
942
|
+
const entries = readdirSync3(reviewsDir);
|
|
943
|
+
const reviewFiles = entries.filter((f) => f.endsWith(".md"));
|
|
944
|
+
for (const file of reviewFiles) {
|
|
945
|
+
const reviewPath = join6(reviewsDir, file);
|
|
946
|
+
const findings = countFindings(reviewPath);
|
|
947
|
+
reviewers.push({
|
|
948
|
+
name: file.replace(".md", ""),
|
|
949
|
+
displayName: formatReviewerName(file),
|
|
950
|
+
status: "complete",
|
|
951
|
+
findings
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const contextComplete = existsSync6(
|
|
956
|
+
join6(sessionPath, "discovered-standards.md")
|
|
957
|
+
);
|
|
958
|
+
const changeContextComplete = existsSync6(join6(sessionPath, "context.md"));
|
|
959
|
+
const analysisComplete = changeContextComplete;
|
|
960
|
+
const reviewsComplete = state.phase_number > 4;
|
|
961
|
+
const discourseComplete = existsSync6(join6(currentRoundDir, "discourse.md"));
|
|
962
|
+
const synthesisComplete = existsSync6(join6(currentRoundDir, "final.md"));
|
|
963
|
+
return {
|
|
964
|
+
workflowType: "review",
|
|
965
|
+
session,
|
|
966
|
+
phase: state.current_phase,
|
|
967
|
+
phaseNumber: state.phase_number,
|
|
968
|
+
totalPhases: this.totalPhases,
|
|
969
|
+
contextComplete,
|
|
970
|
+
changeContextComplete,
|
|
971
|
+
analysisComplete,
|
|
972
|
+
reviewsComplete,
|
|
973
|
+
aggregationComplete: reviewsComplete,
|
|
974
|
+
discourseComplete,
|
|
975
|
+
synthesisComplete,
|
|
976
|
+
currentRound,
|
|
977
|
+
rounds,
|
|
978
|
+
reviewers,
|
|
979
|
+
startTime,
|
|
980
|
+
complete: state.current_phase === "complete"
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
render(state) {
|
|
984
|
+
const lines = [];
|
|
985
|
+
const log = (line = "") => lines.push(line);
|
|
986
|
+
log();
|
|
987
|
+
log(chalk5.bold.white(" Open Code Review"));
|
|
988
|
+
log();
|
|
989
|
+
const elapsed = Math.max(0, Date.now() - state.startTime);
|
|
990
|
+
const roundInfo = state.currentRound > 1 ? chalk5.cyan(` Round ${state.currentRound}`) + chalk5.dim(" \xB7 ") : "";
|
|
991
|
+
log(
|
|
992
|
+
chalk5.dim(" ") + chalk5.white(state.session) + chalk5.dim(" \xB7 ") + roundInfo + chalk5.white(formatDuration(elapsed))
|
|
993
|
+
);
|
|
994
|
+
log();
|
|
995
|
+
const progressPhases = state.complete ? 8 : state.phaseNumber;
|
|
996
|
+
const currentPhase = this.phases.find((p) => p.key === state.phase);
|
|
997
|
+
const phaseLabel = state.complete ? "Done" : currentPhase?.label;
|
|
998
|
+
log(` ${renderProgressBar(progressPhases, 8, phaseLabel)}`);
|
|
999
|
+
log();
|
|
1000
|
+
const phaseCompletion = {
|
|
1001
|
+
context: state.contextComplete,
|
|
1002
|
+
"change-context": state.changeContextComplete,
|
|
1003
|
+
analysis: state.analysisComplete,
|
|
1004
|
+
reviews: state.reviewsComplete,
|
|
1005
|
+
aggregation: state.aggregationComplete,
|
|
1006
|
+
discourse: state.discourseComplete,
|
|
1007
|
+
synthesis: state.synthesisComplete,
|
|
1008
|
+
complete: state.complete
|
|
1009
|
+
};
|
|
1010
|
+
for (const phase of this.phases) {
|
|
1011
|
+
const isComplete = phaseCompletion[phase.key] ?? false;
|
|
1012
|
+
const isCurrent = state.phase === phase.key && !state.complete;
|
|
1013
|
+
const status = getPhaseStatus(isComplete, isCurrent);
|
|
1014
|
+
let label;
|
|
1015
|
+
if (isCurrent) {
|
|
1016
|
+
label = chalk5.cyan.bold(phase.label);
|
|
1017
|
+
} else if (isComplete) {
|
|
1018
|
+
label = chalk5.white(phase.label);
|
|
1019
|
+
} else {
|
|
1020
|
+
label = chalk5.dim(phase.label);
|
|
1021
|
+
}
|
|
1022
|
+
log(` ${status} ${label}`);
|
|
1023
|
+
if (phase.key === "reviews" && state.reviewers.length > 0) {
|
|
1024
|
+
if (state.currentRound > 1) {
|
|
1025
|
+
log(chalk5.dim(" ") + chalk5.cyan(`Round ${state.currentRound}`));
|
|
1026
|
+
}
|
|
1027
|
+
const reviewerLine = state.reviewers.map((r) => {
|
|
1028
|
+
const icon = r.status === "complete" ? chalk5.green("\u2713") : chalk5.dim("\u25CB");
|
|
1029
|
+
const name = chalk5.dim(r.displayName);
|
|
1030
|
+
const count = r.findings > 0 ? chalk5.cyan(` ${r.findings}`) : chalk5.dim(" 0");
|
|
1031
|
+
return `${icon} ${name}${count}`;
|
|
1032
|
+
}).join(chalk5.dim(" \u2502 "));
|
|
1033
|
+
log(chalk5.dim(" ") + reviewerLine);
|
|
1034
|
+
if (state.rounds.length > 1) {
|
|
1035
|
+
const prevRounds = state.rounds.slice(0, -1);
|
|
1036
|
+
for (const round of prevRounds) {
|
|
1037
|
+
const roundLabel = chalk5.dim(` Round ${round.round}`);
|
|
1038
|
+
const reviewerCount = round.reviewers.length;
|
|
1039
|
+
const rstatus = round.isComplete ? chalk5.green("\u2713") : chalk5.dim("\u25CB");
|
|
1040
|
+
log(
|
|
1041
|
+
`${roundLabel} ${rstatus} ${chalk5.dim(`${reviewerCount} reviewers`)}`
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
log();
|
|
1048
|
+
if (state.complete) {
|
|
1049
|
+
const totalFindings = state.reviewers.reduce(
|
|
1050
|
+
(sum, r) => sum + r.findings,
|
|
1051
|
+
0
|
|
1052
|
+
);
|
|
1053
|
+
log(
|
|
1054
|
+
chalk5.green.bold(" \u2713 Complete") + chalk5.dim(" \xB7 ") + chalk5.white(
|
|
1055
|
+
`${totalFindings} finding${totalFindings !== 1 ? "s" : ""}`
|
|
1056
|
+
)
|
|
1057
|
+
);
|
|
1058
|
+
log(
|
|
1059
|
+
chalk5.dim(" ") + chalk5.dim("\u2192 ") + chalk5.white(
|
|
1060
|
+
`.ocr/sessions/${state.session}/rounds/round-${state.currentRound}/final.md`
|
|
1061
|
+
)
|
|
1062
|
+
);
|
|
1063
|
+
} else {
|
|
1064
|
+
log(chalk5.dim(" Ctrl+C to exit"));
|
|
1065
|
+
}
|
|
1066
|
+
log();
|
|
1067
|
+
clearForRenderType("review-progress");
|
|
1068
|
+
logUpdate2(padLines(lines).join("\n"));
|
|
1069
|
+
}
|
|
1070
|
+
renderWaiting() {
|
|
1071
|
+
const lines = [];
|
|
1072
|
+
const log = (line = "") => lines.push(line);
|
|
1073
|
+
log();
|
|
1074
|
+
log(chalk5.bold.white(" Open Code Review"));
|
|
1075
|
+
log();
|
|
1076
|
+
log(chalk5.dim(" Waiting for session..."));
|
|
1077
|
+
log();
|
|
1078
|
+
const bar = chalk5.dim("\u2500".repeat(24));
|
|
1079
|
+
log(` ${bar} ${chalk5.dim("0%")}`);
|
|
1080
|
+
log();
|
|
1081
|
+
log(
|
|
1082
|
+
chalk5.dim(" Run ") + chalk5.white("/ocr-review") + chalk5.dim(" to start")
|
|
1083
|
+
);
|
|
1084
|
+
log();
|
|
1085
|
+
log(chalk5.dim(" Ctrl+C to exit"));
|
|
1086
|
+
log();
|
|
1087
|
+
clearForRenderType("review-waiting");
|
|
1088
|
+
logUpdate2(padLines(lines).join("\n"));
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
var reviewStrategy = new ReviewProgressStrategy();
|
|
1092
|
+
|
|
1093
|
+
// packages/cli/src/lib/progress/map-strategy.ts
|
|
1094
|
+
import chalk6 from "chalk";
|
|
1095
|
+
import logUpdate3 from "log-update";
|
|
1096
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "node:fs";
|
|
1097
|
+
import { join as join7, basename as basename2 } from "node:path";
|
|
1098
|
+
var MAP_PHASES = [
|
|
1099
|
+
{ key: "map-context", label: "Context Discovery" },
|
|
1100
|
+
{ key: "topology", label: "Topology Analysis" },
|
|
1101
|
+
{ key: "flow-analysis", label: "Flow Tracing" },
|
|
1102
|
+
{ key: "requirements-mapping", label: "Requirements Mapping" },
|
|
1103
|
+
{ key: "synthesis", label: "Map Synthesis" },
|
|
826
1104
|
{ key: "complete", label: "Complete" }
|
|
827
1105
|
];
|
|
828
|
-
function
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1106
|
+
function deriveRunsFromFilesystem(mapDir) {
|
|
1107
|
+
const runsDir = join7(mapDir, "runs");
|
|
1108
|
+
if (!existsSync7(runsDir)) {
|
|
1109
|
+
return [];
|
|
1110
|
+
}
|
|
1111
|
+
const runDirs = readdirSync4(runsDir).filter((d) => d.match(/^run-\d+$/)).sort((a, b) => {
|
|
1112
|
+
const numA = parseInt(a.replace("run-", ""));
|
|
1113
|
+
const numB = parseInt(b.replace("run-", ""));
|
|
1114
|
+
return numA - numB;
|
|
1115
|
+
});
|
|
1116
|
+
return runDirs.map((dir) => {
|
|
1117
|
+
const runNum = parseInt(dir.replace("run-", ""));
|
|
1118
|
+
const runPath = join7(runsDir, dir);
|
|
1119
|
+
const mapPath = join7(runPath, "map.md");
|
|
1120
|
+
let fileCount = 0;
|
|
1121
|
+
const topologyPath = join7(runPath, "topology.md");
|
|
1122
|
+
if (existsSync7(topologyPath)) {
|
|
1123
|
+
const content = readFileSync6(topologyPath, "utf-8");
|
|
1124
|
+
const fileListMatch = content.match(
|
|
1125
|
+
/## Canonical File List[\s\S]*?```([\s\S]*?)```/
|
|
1126
|
+
);
|
|
1127
|
+
if (fileListMatch && fileListMatch[1]) {
|
|
1128
|
+
fileCount = fileListMatch[1].trim().split("\n").filter(Boolean).length;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
run: runNum,
|
|
1133
|
+
isComplete: existsSync7(mapPath),
|
|
1134
|
+
fileCount
|
|
1135
|
+
};
|
|
1136
|
+
});
|
|
841
1137
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
chalk4.dim(" ") + chalk4.white(state.session) + chalk4.dim(" \xB7 ") + roundInfo + chalk4.white(formatDuration(elapsed))
|
|
852
|
-
);
|
|
853
|
-
log();
|
|
854
|
-
const progressPhases = state.complete ? 8 : state.phaseNumber;
|
|
855
|
-
const currentPhase = PHASE_INFO.find((p) => p.key === state.phase);
|
|
856
|
-
const phaseLabel = state.complete ? "Done" : currentPhase?.label;
|
|
857
|
-
log(` ${renderProgressBar(progressPhases, 8, phaseLabel)}`);
|
|
858
|
-
log();
|
|
859
|
-
const phaseCompletion = {
|
|
860
|
-
waiting: false,
|
|
861
|
-
context: state.contextComplete,
|
|
862
|
-
"change-context": state.changeContextComplete,
|
|
863
|
-
analysis: state.analysisComplete,
|
|
864
|
-
reviews: state.reviewsComplete,
|
|
865
|
-
aggregation: state.aggregationComplete,
|
|
866
|
-
discourse: state.discourseComplete,
|
|
867
|
-
synthesis: state.synthesisComplete,
|
|
868
|
-
complete: state.complete
|
|
869
|
-
};
|
|
870
|
-
for (const phase of PHASE_INFO) {
|
|
871
|
-
const isComplete = phaseCompletion[phase.key];
|
|
872
|
-
const isCurrent = state.phase === phase.key && !state.complete;
|
|
873
|
-
const status = getPhaseStatus(isComplete, isCurrent);
|
|
874
|
-
let label;
|
|
875
|
-
if (isCurrent) {
|
|
876
|
-
label = chalk4.cyan.bold(phase.label);
|
|
877
|
-
} else if (isComplete) {
|
|
878
|
-
label = chalk4.white(phase.label);
|
|
879
|
-
} else {
|
|
880
|
-
label = chalk4.dim(phase.label);
|
|
1138
|
+
var MapProgressStrategy = class {
|
|
1139
|
+
workflowType = "map";
|
|
1140
|
+
phases = MAP_PHASES;
|
|
1141
|
+
totalPhases = 6;
|
|
1142
|
+
parseState(sessionPath, preservedStartTime) {
|
|
1143
|
+
const session = basename2(sessionPath);
|
|
1144
|
+
const statePath = join7(sessionPath, "state.json");
|
|
1145
|
+
if (!existsSync7(statePath)) {
|
|
1146
|
+
return null;
|
|
881
1147
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const reviewerLine = state.reviewers.map((r) => {
|
|
888
|
-
const icon = r.status === "complete" ? chalk4.green("\u2713") : chalk4.dim("\u25CB");
|
|
889
|
-
const name = chalk4.dim(r.displayName);
|
|
890
|
-
const count = r.findings > 0 ? chalk4.cyan(` ${r.findings}`) : chalk4.dim(" 0");
|
|
891
|
-
return `${icon} ${name}${count}`;
|
|
892
|
-
}).join(chalk4.dim(" \u2502 "));
|
|
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
|
-
}
|
|
1148
|
+
try {
|
|
1149
|
+
const stateContent = readFileSync6(statePath, "utf-8");
|
|
1150
|
+
const state = JSON.parse(stateContent);
|
|
1151
|
+
if (state.workflow_type !== "map") {
|
|
1152
|
+
return null;
|
|
904
1153
|
}
|
|
1154
|
+
return this.parseFromStateJson(
|
|
1155
|
+
session,
|
|
1156
|
+
state,
|
|
1157
|
+
sessionPath,
|
|
1158
|
+
preservedStartTime
|
|
1159
|
+
);
|
|
1160
|
+
} catch {
|
|
1161
|
+
return null;
|
|
905
1162
|
}
|
|
906
1163
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
1164
|
+
parseFromStateJson(session, state, sessionPath, preservedStartTime) {
|
|
1165
|
+
const effectiveStartTime = state.map_started_at ?? state.started_at;
|
|
1166
|
+
const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
|
|
1167
|
+
const mapDir = join7(sessionPath, "map");
|
|
1168
|
+
const runs = deriveRunsFromFilesystem(mapDir);
|
|
1169
|
+
const highestExistingRun = runs.length > 0 ? Math.max(...runs.map((r) => r.run)) : 1;
|
|
1170
|
+
const stateRun = state.current_map_run ?? 1;
|
|
1171
|
+
const currentRun = Math.min(stateRun, highestExistingRun);
|
|
1172
|
+
const currentRunDir = join7(mapDir, "runs", `run-${currentRun}`);
|
|
1173
|
+
const contextComplete = existsSync7(
|
|
1174
|
+
join7(sessionPath, "discovered-standards.md")
|
|
912
1175
|
);
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
)
|
|
1176
|
+
const topologyComplete = existsSync7(join7(currentRunDir, "topology.md"));
|
|
1177
|
+
const flowAnalysisComplete = existsSync7(
|
|
1178
|
+
join7(currentRunDir, "flow-analysis.md")
|
|
917
1179
|
);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
`.ocr/sessions/${state.session}/rounds/round-${state.currentRound}/final.md`
|
|
921
|
-
)
|
|
1180
|
+
const requirementsMappingComplete = existsSync7(
|
|
1181
|
+
join7(currentRunDir, "requirements-mapping.md")
|
|
922
1182
|
);
|
|
923
|
-
|
|
924
|
-
|
|
1183
|
+
const synthesisComplete = existsSync7(join7(currentRunDir, "map.md"));
|
|
1184
|
+
const hasRequirements = existsSync7(join7(sessionPath, "requirements.md"));
|
|
1185
|
+
const flowAnalysts = flowAnalysisComplete ? [
|
|
1186
|
+
{
|
|
1187
|
+
name: "flow-analyst",
|
|
1188
|
+
displayName: "Flow Analysts",
|
|
1189
|
+
status: "complete"
|
|
1190
|
+
}
|
|
1191
|
+
] : [];
|
|
1192
|
+
const requirementsMappers = requirementsMappingComplete && hasRequirements ? [
|
|
1193
|
+
{
|
|
1194
|
+
name: "req-mapper",
|
|
1195
|
+
displayName: "Requirements Mappers",
|
|
1196
|
+
status: "complete"
|
|
1197
|
+
}
|
|
1198
|
+
] : [];
|
|
1199
|
+
return {
|
|
1200
|
+
workflowType: "map",
|
|
1201
|
+
session,
|
|
1202
|
+
phase: state.current_phase,
|
|
1203
|
+
phaseNumber: state.phase_number,
|
|
1204
|
+
totalPhases: this.totalPhases,
|
|
1205
|
+
contextComplete,
|
|
1206
|
+
topologyComplete,
|
|
1207
|
+
flowAnalysisComplete,
|
|
1208
|
+
requirementsMappingComplete,
|
|
1209
|
+
synthesisComplete,
|
|
1210
|
+
currentRun,
|
|
1211
|
+
runs,
|
|
1212
|
+
flowAnalysts,
|
|
1213
|
+
requirementsMappers,
|
|
1214
|
+
hasRequirements,
|
|
1215
|
+
startTime,
|
|
1216
|
+
complete: state.current_phase === "complete"
|
|
1217
|
+
};
|
|
925
1218
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1219
|
+
render(state) {
|
|
1220
|
+
const lines = [];
|
|
1221
|
+
const log = (line = "") => lines.push(line);
|
|
1222
|
+
log();
|
|
1223
|
+
log(chalk6.bold.white(" Open Code Review") + chalk6.cyan(" \xB7 Map"));
|
|
1224
|
+
log();
|
|
1225
|
+
const elapsed = Math.max(0, Date.now() - state.startTime);
|
|
1226
|
+
const runInfo = state.currentRun > 1 ? chalk6.cyan(` Run ${state.currentRun}`) + chalk6.dim(" \xB7 ") : "";
|
|
1227
|
+
log(
|
|
1228
|
+
chalk6.dim(" ") + chalk6.white(state.session) + chalk6.dim(" \xB7 ") + runInfo + chalk6.white(formatDuration(elapsed))
|
|
1229
|
+
);
|
|
1230
|
+
log();
|
|
1231
|
+
const currentRunInfo = state.runs.find((r) => r.run === state.currentRun);
|
|
1232
|
+
if (currentRunInfo && currentRunInfo.fileCount > 0) {
|
|
1233
|
+
log(
|
|
1234
|
+
chalk6.dim(" ") + chalk6.white(`${currentRunInfo.fileCount} files`) + chalk6.dim(" in changeset")
|
|
1235
|
+
);
|
|
1236
|
+
log();
|
|
1237
|
+
}
|
|
1238
|
+
const progressPhases = state.complete ? 6 : state.phaseNumber;
|
|
1239
|
+
const currentPhase = this.phases.find((p) => p.key === state.phase);
|
|
1240
|
+
const phaseLabel = state.complete ? "Done" : currentPhase?.label;
|
|
1241
|
+
log(` ${renderProgressBar(progressPhases, 6, phaseLabel)}`);
|
|
1242
|
+
log();
|
|
1243
|
+
const phaseCompletion = {
|
|
1244
|
+
"map-context": state.contextComplete,
|
|
1245
|
+
topology: state.topologyComplete,
|
|
1246
|
+
"flow-analysis": state.flowAnalysisComplete,
|
|
1247
|
+
"requirements-mapping": state.requirementsMappingComplete,
|
|
1248
|
+
synthesis: state.synthesisComplete,
|
|
1249
|
+
complete: state.complete
|
|
1250
|
+
};
|
|
1251
|
+
for (const phase of this.phases) {
|
|
1252
|
+
if (phase.key === "requirements-mapping" && !state.hasRequirements) {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
const isComplete = phaseCompletion[phase.key] ?? false;
|
|
1256
|
+
const isCurrent = state.phase === phase.key && !state.complete;
|
|
1257
|
+
const status = getPhaseStatus(isComplete, isCurrent);
|
|
1258
|
+
let label;
|
|
1259
|
+
if (isCurrent) {
|
|
1260
|
+
label = chalk6.cyan.bold(phase.label);
|
|
1261
|
+
} else if (isComplete) {
|
|
1262
|
+
label = chalk6.white(phase.label);
|
|
1263
|
+
} else {
|
|
1264
|
+
label = chalk6.dim(phase.label);
|
|
1265
|
+
}
|
|
1266
|
+
log(` ${status} ${label}`);
|
|
1267
|
+
if (phase.key === "flow-analysis" && state.flowAnalysts.length > 0) {
|
|
1268
|
+
const agentLine = state.flowAnalysts.map((a) => {
|
|
1269
|
+
const icon = a.status === "complete" ? chalk6.green("\u2713") : chalk6.dim("\u25CB");
|
|
1270
|
+
return `${icon} ${chalk6.dim(a.displayName)}`;
|
|
1271
|
+
}).join(chalk6.dim(" \u2502 "));
|
|
1272
|
+
log(chalk6.dim(" ") + agentLine);
|
|
1273
|
+
}
|
|
1274
|
+
if (phase.key === "requirements-mapping" && state.requirementsMappers.length > 0) {
|
|
1275
|
+
const agentLine = state.requirementsMappers.map((a) => {
|
|
1276
|
+
const icon = a.status === "complete" ? chalk6.green("\u2713") : chalk6.dim("\u25CB");
|
|
1277
|
+
return `${icon} ${chalk6.dim(a.displayName)}`;
|
|
1278
|
+
}).join(chalk6.dim(" \u2502 "));
|
|
1279
|
+
log(chalk6.dim(" ") + agentLine);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
log();
|
|
1283
|
+
if (state.complete) {
|
|
1284
|
+
log(chalk6.green.bold(" \u2713 Map Complete"));
|
|
1285
|
+
log(
|
|
1286
|
+
chalk6.dim(" ") + chalk6.dim("\u2192 ") + chalk6.white(
|
|
1287
|
+
`.ocr/sessions/${state.session}/map/runs/run-${state.currentRun}/map.md`
|
|
1288
|
+
)
|
|
1289
|
+
);
|
|
1290
|
+
} else {
|
|
1291
|
+
log(chalk6.dim(" Ctrl+C to exit"));
|
|
1292
|
+
}
|
|
1293
|
+
log();
|
|
1294
|
+
clearForRenderType("map-progress");
|
|
1295
|
+
logUpdate3(padLines(lines).join("\n"));
|
|
929
1296
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
lines.push(
|
|
1297
|
+
renderWaiting() {
|
|
1298
|
+
const lines = [];
|
|
1299
|
+
const log = (line = "") => lines.push(line);
|
|
1300
|
+
log();
|
|
1301
|
+
log(chalk6.bold.white(" Open Code Review") + chalk6.cyan(" \xB7 Map"));
|
|
1302
|
+
log();
|
|
1303
|
+
log(chalk6.dim(" Waiting for session..."));
|
|
1304
|
+
log();
|
|
1305
|
+
const bar = chalk6.dim("\u2500".repeat(24));
|
|
1306
|
+
log(` ${bar} ${chalk6.dim("0%")}`);
|
|
1307
|
+
log();
|
|
1308
|
+
log(chalk6.dim(" Run ") + chalk6.white("/ocr-map") + chalk6.dim(" to start"));
|
|
1309
|
+
log();
|
|
1310
|
+
log(chalk6.dim(" Ctrl+C to exit"));
|
|
1311
|
+
log();
|
|
1312
|
+
clearForRenderType("map-waiting");
|
|
1313
|
+
logUpdate3(padLines(lines).join("\n"));
|
|
933
1314
|
}
|
|
934
|
-
|
|
935
|
-
|
|
1315
|
+
};
|
|
1316
|
+
var mapStrategy = new MapProgressStrategy();
|
|
1317
|
+
|
|
1318
|
+
// packages/cli/src/lib/progress/index.ts
|
|
1319
|
+
registerStrategy(reviewStrategy);
|
|
1320
|
+
registerStrategy(mapStrategy);
|
|
1321
|
+
|
|
1322
|
+
// packages/cli/src/commands/progress.ts
|
|
1323
|
+
function debounce(fn, delay) {
|
|
1324
|
+
let timeoutId = null;
|
|
1325
|
+
return (...args) => {
|
|
1326
|
+
if (timeoutId) {
|
|
1327
|
+
clearTimeout(timeoutId);
|
|
1328
|
+
}
|
|
1329
|
+
timeoutId = setTimeout(() => {
|
|
1330
|
+
fn(...args);
|
|
1331
|
+
timeoutId = null;
|
|
1332
|
+
}, delay);
|
|
1333
|
+
};
|
|
936
1334
|
}
|
|
937
|
-
function
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
log();
|
|
941
|
-
log(chalk4.bold.white(" Open Code Review"));
|
|
942
|
-
log();
|
|
943
|
-
log(chalk4.dim(" Waiting for session..."));
|
|
944
|
-
log();
|
|
945
|
-
const bar = chalk4.dim("\u2500".repeat(24));
|
|
946
|
-
log(` ${bar} ${chalk4.dim("0%")}`);
|
|
947
|
-
log();
|
|
948
|
-
log(
|
|
949
|
-
chalk4.dim(" Run ") + chalk4.white("/ocr-review") + chalk4.dim(" to start")
|
|
950
|
-
);
|
|
951
|
-
log();
|
|
952
|
-
log(chalk4.dim(" Ctrl+C to exit"));
|
|
953
|
-
log();
|
|
954
|
-
if (lastRenderType !== "waiting") {
|
|
955
|
-
logUpdate.clear();
|
|
1335
|
+
function findLatestActiveSession(sessionsDir) {
|
|
1336
|
+
if (!existsSync8(sessionsDir)) {
|
|
1337
|
+
return null;
|
|
956
1338
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1339
|
+
const sessions = readdirSync5(sessionsDir).filter((name) => {
|
|
1340
|
+
const sessionPath = join8(sessionsDir, name);
|
|
1341
|
+
return statSync(sessionPath).isDirectory();
|
|
1342
|
+
}).sort().reverse();
|
|
1343
|
+
for (const session of sessions) {
|
|
1344
|
+
const sessionPath = join8(sessionsDir, session);
|
|
1345
|
+
if (isSessionActive(sessionPath)) {
|
|
1346
|
+
return session;
|
|
1347
|
+
}
|
|
960
1348
|
}
|
|
961
|
-
|
|
962
|
-
logUpdate(lines.join("\n"));
|
|
1349
|
+
return null;
|
|
963
1350
|
}
|
|
964
|
-
|
|
1351
|
+
function getStrategyForSession(sessionPath, explicitWorkflow) {
|
|
1352
|
+
const workflowType = detectWorkflowType(sessionPath, explicitWorkflow);
|
|
1353
|
+
if (!workflowType) {
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
return getStrategy(workflowType) ?? null;
|
|
1357
|
+
}
|
|
1358
|
+
var progressCommand = new Command2("progress").description("Watch real-time progress of a code review or map session").option("-s, --session <name>", "Specify session name").option(
|
|
1359
|
+
"-w, --workflow <type>",
|
|
1360
|
+
"Specify workflow type (review or map)",
|
|
1361
|
+
(value) => {
|
|
1362
|
+
if (value !== "review" && value !== "map") {
|
|
1363
|
+
throw new Error(
|
|
1364
|
+
`Invalid workflow type: ${value}. Use 'review' or 'map'.`
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
).action(async (options) => {
|
|
965
1370
|
const targetDir = process.cwd();
|
|
966
1371
|
requireOcrSetup(targetDir);
|
|
967
1372
|
const sessionsDir = ensureSessionsDir(targetDir);
|
|
968
|
-
const ocrDir =
|
|
1373
|
+
const ocrDir = join8(targetDir, ".ocr");
|
|
969
1374
|
if (options.session) {
|
|
970
|
-
const sessionPath =
|
|
971
|
-
if (!
|
|
972
|
-
console.log(
|
|
1375
|
+
const sessionPath = join8(sessionsDir, options.session);
|
|
1376
|
+
if (!existsSync8(sessionPath)) {
|
|
1377
|
+
console.log(chalk7.red(`Session not found: ${options.session}`));
|
|
1378
|
+
process.exit(1);
|
|
1379
|
+
}
|
|
1380
|
+
const strategy = getStrategyForSession(sessionPath, options.workflow);
|
|
1381
|
+
if (!strategy) {
|
|
1382
|
+
console.log(
|
|
1383
|
+
chalk7.red(
|
|
1384
|
+
`Cannot determine workflow type for session ${options.session}`
|
|
1385
|
+
)
|
|
1386
|
+
);
|
|
1387
|
+
console.log(
|
|
1388
|
+
chalk7.dim(`Try specifying --workflow review or --workflow map`)
|
|
1389
|
+
);
|
|
973
1390
|
process.exit(1);
|
|
974
1391
|
}
|
|
975
|
-
let state =
|
|
1392
|
+
let state = strategy.parseState(sessionPath);
|
|
976
1393
|
if (!state) {
|
|
977
1394
|
console.log(
|
|
978
|
-
|
|
1395
|
+
chalk7.red(
|
|
979
1396
|
`Session ${options.session} has no state.json - cannot track progress`
|
|
980
1397
|
)
|
|
981
1398
|
);
|
|
982
1399
|
console.log(
|
|
983
|
-
|
|
1400
|
+
chalk7.dim(
|
|
984
1401
|
`The orchestrating agent must create state.json for progress tracking.`
|
|
985
1402
|
)
|
|
986
1403
|
);
|
|
987
1404
|
process.exit(1);
|
|
988
1405
|
}
|
|
989
|
-
let
|
|
990
|
-
|
|
1406
|
+
let preservedStartTime = state.startTime;
|
|
1407
|
+
strategy.render(state);
|
|
991
1408
|
const timerInterval2 = setInterval(() => {
|
|
992
|
-
const newState =
|
|
1409
|
+
const newState = strategy.parseState(sessionPath, preservedStartTime);
|
|
993
1410
|
if (newState) {
|
|
994
1411
|
state = newState;
|
|
995
|
-
|
|
1412
|
+
strategy.render(state);
|
|
996
1413
|
}
|
|
997
1414
|
}, 1e3);
|
|
998
1415
|
const watcher = watch(sessionPath, {
|
|
999
1416
|
persistent: true,
|
|
1000
1417
|
ignoreInitial: true,
|
|
1001
|
-
depth:
|
|
1418
|
+
depth: 4
|
|
1419
|
+
// map/runs/run-{n}/*.md
|
|
1002
1420
|
});
|
|
1003
1421
|
watcher.on("all", () => {
|
|
1004
|
-
const newState =
|
|
1422
|
+
const newState = strategy.parseState(sessionPath, preservedStartTime);
|
|
1005
1423
|
if (newState) {
|
|
1006
1424
|
state = newState;
|
|
1007
|
-
|
|
1425
|
+
strategy.render(state);
|
|
1008
1426
|
}
|
|
1009
1427
|
});
|
|
1010
1428
|
process.on("SIGINT", () => {
|
|
1011
1429
|
clearInterval(timerInterval2);
|
|
1012
1430
|
watcher.close();
|
|
1013
|
-
|
|
1431
|
+
logUpdate4.done();
|
|
1014
1432
|
process.exit(0);
|
|
1015
1433
|
});
|
|
1016
1434
|
return;
|
|
1017
1435
|
}
|
|
1018
1436
|
let currentSession = findLatestActiveSession(sessionsDir);
|
|
1019
|
-
let currentSessionPath = currentSession ?
|
|
1437
|
+
let currentSessionPath = currentSession ? join8(sessionsDir, currentSession) : null;
|
|
1020
1438
|
let sessionWatcher = null;
|
|
1021
|
-
|
|
1439
|
+
const preservedStartTimes = {
|
|
1440
|
+
review: void 0,
|
|
1441
|
+
map: void 0
|
|
1442
|
+
};
|
|
1443
|
+
let currentStrategy = null;
|
|
1022
1444
|
const updateDisplayImpl = () => {
|
|
1023
|
-
if (!currentSessionPath || !
|
|
1445
|
+
if (!currentSessionPath || !existsSync8(currentSessionPath) || !isSessionActive(currentSessionPath)) {
|
|
1024
1446
|
const latestActive = findLatestActiveSession(sessionsDir);
|
|
1025
1447
|
if (latestActive && latestActive !== currentSession) {
|
|
1026
1448
|
currentSession = latestActive;
|
|
1027
|
-
currentSessionPath =
|
|
1028
|
-
|
|
1449
|
+
currentSessionPath = join8(sessionsDir, latestActive);
|
|
1450
|
+
preservedStartTimes.review = void 0;
|
|
1451
|
+
preservedStartTimes.map = void 0;
|
|
1452
|
+
currentStrategy = null;
|
|
1029
1453
|
watchSession(currentSessionPath);
|
|
1030
1454
|
} else if (!latestActive) {
|
|
1031
1455
|
currentSession = null;
|
|
1032
1456
|
currentSessionPath = null;
|
|
1033
|
-
|
|
1457
|
+
preservedStartTimes.review = void 0;
|
|
1458
|
+
preservedStartTimes.map = void 0;
|
|
1459
|
+
currentStrategy = null;
|
|
1034
1460
|
}
|
|
1035
1461
|
}
|
|
1036
|
-
if (currentSessionPath &&
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
if (
|
|
1040
|
-
|
|
1462
|
+
if (currentSessionPath && existsSync8(currentSessionPath)) {
|
|
1463
|
+
if (!options.workflow) {
|
|
1464
|
+
const activeWorkflows = detectActiveWorkflows(currentSessionPath);
|
|
1465
|
+
if (activeWorkflows.length > 1) {
|
|
1466
|
+
renderCombinedProgress(currentSessionPath, preservedStartTimes);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
if (!currentStrategy) {
|
|
1471
|
+
currentStrategy = getStrategyForSession(
|
|
1472
|
+
currentSessionPath,
|
|
1473
|
+
options.workflow
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
if (currentStrategy) {
|
|
1477
|
+
const workflowType = currentStrategy.workflowType;
|
|
1478
|
+
const state = currentStrategy.parseState(
|
|
1479
|
+
currentSessionPath,
|
|
1480
|
+
preservedStartTimes[workflowType]
|
|
1481
|
+
);
|
|
1482
|
+
if (state) {
|
|
1483
|
+
if (!preservedStartTimes[workflowType]) {
|
|
1484
|
+
preservedStartTimes[workflowType] = state.startTime;
|
|
1485
|
+
}
|
|
1486
|
+
currentStrategy.render(state);
|
|
1487
|
+
} else {
|
|
1488
|
+
currentStrategy.renderWaiting();
|
|
1041
1489
|
}
|
|
1042
|
-
renderProgress(state);
|
|
1043
1490
|
} else {
|
|
1044
|
-
|
|
1491
|
+
renderGenericWaiting();
|
|
1045
1492
|
}
|
|
1046
1493
|
} else {
|
|
1047
|
-
|
|
1048
|
-
|
|
1494
|
+
preservedStartTimes.review = void 0;
|
|
1495
|
+
preservedStartTimes.map = void 0;
|
|
1496
|
+
renderGenericWaiting();
|
|
1049
1497
|
}
|
|
1050
1498
|
};
|
|
1051
1499
|
const updateDisplay = debounce(updateDisplayImpl, 50);
|
|
@@ -1056,7 +1504,7 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
1056
1504
|
sessionWatcher = watch(sessionPath, {
|
|
1057
1505
|
persistent: true,
|
|
1058
1506
|
ignoreInitial: true,
|
|
1059
|
-
depth:
|
|
1507
|
+
depth: 4
|
|
1060
1508
|
});
|
|
1061
1509
|
sessionWatcher.on("all", updateDisplay);
|
|
1062
1510
|
};
|
|
@@ -1065,20 +1513,22 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
1065
1513
|
watchSession(currentSessionPath);
|
|
1066
1514
|
}
|
|
1067
1515
|
const timerInterval = setInterval(updateDisplay, 1e3);
|
|
1068
|
-
const watchDir =
|
|
1516
|
+
const watchDir = existsSync8(ocrDir) ? ocrDir : targetDir;
|
|
1069
1517
|
const dirWatcher = watch(watchDir, {
|
|
1070
1518
|
persistent: true,
|
|
1071
1519
|
ignoreInitial: true,
|
|
1072
1520
|
depth: 3
|
|
1073
1521
|
});
|
|
1074
1522
|
dirWatcher.on("addDir", (dirPath) => {
|
|
1075
|
-
const parentDir =
|
|
1076
|
-
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(
|
|
1523
|
+
const parentDir = join8(dirPath, "..");
|
|
1524
|
+
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join8(".ocr", "sessions"));
|
|
1077
1525
|
if (isDirectChild && !dirPath.endsWith("sessions")) {
|
|
1078
|
-
const newSession =
|
|
1526
|
+
const newSession = basename3(dirPath);
|
|
1079
1527
|
currentSession = newSession;
|
|
1080
1528
|
currentSessionPath = dirPath;
|
|
1081
|
-
|
|
1529
|
+
preservedStartTimes.review = void 0;
|
|
1530
|
+
preservedStartTimes.map = void 0;
|
|
1531
|
+
currentStrategy = null;
|
|
1082
1532
|
watchSession(dirPath);
|
|
1083
1533
|
updateDisplay();
|
|
1084
1534
|
}
|
|
@@ -1089,25 +1539,103 @@ var progressCommand = new Command2("progress").description("Watch real-time prog
|
|
|
1089
1539
|
clearInterval(timerInterval);
|
|
1090
1540
|
dirWatcher.close();
|
|
1091
1541
|
if (sessionWatcher) sessionWatcher.close();
|
|
1092
|
-
|
|
1542
|
+
logUpdate4.done();
|
|
1093
1543
|
process.exit(0);
|
|
1094
1544
|
});
|
|
1095
1545
|
});
|
|
1546
|
+
function renderGenericWaiting() {
|
|
1547
|
+
const lines = [];
|
|
1548
|
+
lines.push("");
|
|
1549
|
+
lines.push(chalk7.bold.white(" Open Code Review"));
|
|
1550
|
+
lines.push("");
|
|
1551
|
+
lines.push(chalk7.dim(" Waiting for session..."));
|
|
1552
|
+
lines.push("");
|
|
1553
|
+
lines.push(` ${chalk7.dim("\u2500".repeat(24))} ${chalk7.dim("0%")}`);
|
|
1554
|
+
lines.push("");
|
|
1555
|
+
lines.push(
|
|
1556
|
+
chalk7.dim(" Run ") + chalk7.white("/ocr-review") + chalk7.dim(" or ") + chalk7.white("/ocr-map") + chalk7.dim(" to start")
|
|
1557
|
+
);
|
|
1558
|
+
lines.push("");
|
|
1559
|
+
lines.push(chalk7.dim(" Ctrl+C to exit"));
|
|
1560
|
+
lines.push("");
|
|
1561
|
+
logUpdate4(lines.join("\n"));
|
|
1562
|
+
}
|
|
1563
|
+
function renderCombinedProgress(sessionPath, preservedStartTimes) {
|
|
1564
|
+
const lines = [];
|
|
1565
|
+
const session = basename3(sessionPath);
|
|
1566
|
+
lines.push("");
|
|
1567
|
+
lines.push(
|
|
1568
|
+
chalk7.bold.white(" Open Code Review") + chalk7.yellow(" \xB7 Parallel Workflows")
|
|
1569
|
+
);
|
|
1570
|
+
lines.push("");
|
|
1571
|
+
lines.push(chalk7.dim(" ") + chalk7.white(session));
|
|
1572
|
+
lines.push("");
|
|
1573
|
+
const reviewStrategy2 = getStrategy("review");
|
|
1574
|
+
const mapStrategy2 = getStrategy("map");
|
|
1575
|
+
if (reviewStrategy2) {
|
|
1576
|
+
const reviewState = reviewStrategy2.parseState(
|
|
1577
|
+
sessionPath,
|
|
1578
|
+
preservedStartTimes.review
|
|
1579
|
+
);
|
|
1580
|
+
if (reviewState) {
|
|
1581
|
+
const reviewPercent = Math.round(
|
|
1582
|
+
reviewState.phaseNumber / reviewStrategy2.totalPhases * 100
|
|
1583
|
+
);
|
|
1584
|
+
const reviewBar = chalk7.blue("\u2501".repeat(Math.round(reviewPercent / 10))) + chalk7.dim("\u2500".repeat(10 - Math.round(reviewPercent / 10)));
|
|
1585
|
+
const currentPhase = reviewStrategy2.phases.find(
|
|
1586
|
+
(p) => p.key === reviewState.phase
|
|
1587
|
+
);
|
|
1588
|
+
lines.push(
|
|
1589
|
+
chalk7.blue(" \u25C9 Review") + chalk7.dim(" ") + reviewBar + chalk7.dim(" ") + chalk7.white(`${reviewPercent}%`) + chalk7.dim(" \xB7 ") + chalk7.cyan(currentPhase?.label ?? reviewState.phase)
|
|
1590
|
+
);
|
|
1591
|
+
} else {
|
|
1592
|
+
lines.push(chalk7.blue(" \u25C9 Review") + chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 0%"));
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
if (mapStrategy2) {
|
|
1596
|
+
const mapState = mapStrategy2.parseState(
|
|
1597
|
+
sessionPath,
|
|
1598
|
+
preservedStartTimes.map
|
|
1599
|
+
);
|
|
1600
|
+
if (mapState) {
|
|
1601
|
+
const mapPercent = Math.round(
|
|
1602
|
+
mapState.phaseNumber / mapStrategy2.totalPhases * 100
|
|
1603
|
+
);
|
|
1604
|
+
const mapBar = chalk7.green("\u2501".repeat(Math.round(mapPercent / 10))) + chalk7.dim("\u2500".repeat(10 - Math.round(mapPercent / 10)));
|
|
1605
|
+
const currentPhase = mapStrategy2.phases.find(
|
|
1606
|
+
(p) => p.key === mapState.phase
|
|
1607
|
+
);
|
|
1608
|
+
lines.push(
|
|
1609
|
+
chalk7.green(" \u25C9 Map") + chalk7.dim(" ") + mapBar + chalk7.dim(" ") + chalk7.white(`${mapPercent}%`) + chalk7.dim(" \xB7 ") + chalk7.cyan(currentPhase?.label ?? mapState.phase)
|
|
1610
|
+
);
|
|
1611
|
+
} else {
|
|
1612
|
+
lines.push(chalk7.green(" \u25C9 Map") + chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 0%"));
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
lines.push("");
|
|
1616
|
+
lines.push(
|
|
1617
|
+
chalk7.dim(" Use ") + chalk7.white("--workflow review") + chalk7.dim(" or ") + chalk7.white("--workflow map") + chalk7.dim(" for details")
|
|
1618
|
+
);
|
|
1619
|
+
lines.push("");
|
|
1620
|
+
lines.push(chalk7.dim(" Ctrl+C to exit"));
|
|
1621
|
+
lines.push("");
|
|
1622
|
+
logUpdate4(lines.join("\n"));
|
|
1623
|
+
}
|
|
1096
1624
|
|
|
1097
1625
|
// packages/cli/src/commands/update.ts
|
|
1098
1626
|
import { Command as Command3 } from "commander";
|
|
1099
|
-
import
|
|
1627
|
+
import chalk8 from "chalk";
|
|
1100
1628
|
import ora2 from "ora";
|
|
1101
|
-
import { existsSync as
|
|
1102
|
-
import { join as
|
|
1629
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1630
|
+
import { join as join9 } from "node:path";
|
|
1103
1631
|
function detectConfiguredTools(targetDir) {
|
|
1104
1632
|
return AI_TOOLS.filter((tool) => {
|
|
1105
1633
|
if (tool.commandStrategy === "subdirectory") {
|
|
1106
|
-
const ocrDir =
|
|
1107
|
-
return
|
|
1634
|
+
const ocrDir = join9(targetDir, tool.commandsDir, "ocr");
|
|
1635
|
+
return existsSync9(ocrDir);
|
|
1108
1636
|
} else {
|
|
1109
|
-
const reviewCmd =
|
|
1110
|
-
return
|
|
1637
|
+
const reviewCmd = join9(targetDir, tool.commandsDir, "ocr-review.md");
|
|
1638
|
+
return existsSync9(reviewCmd);
|
|
1111
1639
|
}
|
|
1112
1640
|
});
|
|
1113
1641
|
}
|
|
@@ -1118,7 +1646,7 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
1118
1646
|
const targetDir = process.cwd();
|
|
1119
1647
|
requireOcrSetup(targetDir);
|
|
1120
1648
|
console.log();
|
|
1121
|
-
console.log(
|
|
1649
|
+
console.log(chalk8.bold.cyan(" Open Code Review - Update"));
|
|
1122
1650
|
console.log();
|
|
1123
1651
|
const savedToolIds = getConfiguredToolIds(targetDir);
|
|
1124
1652
|
const configuredTools = detectConfiguredTools(targetDir);
|
|
@@ -1132,8 +1660,8 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
1132
1660
|
);
|
|
1133
1661
|
}
|
|
1134
1662
|
if (toolsToUpdate.length === 0) {
|
|
1135
|
-
console.log(
|
|
1136
|
-
console.log(
|
|
1663
|
+
console.log(chalk8.yellow(" No configured AI tools found."));
|
|
1664
|
+
console.log(chalk8.dim(" Run `ocr init` to set up OCR first."));
|
|
1137
1665
|
console.log();
|
|
1138
1666
|
process.exit(1);
|
|
1139
1667
|
}
|
|
@@ -1142,31 +1670,33 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
1142
1670
|
const updateSkills = options.skills || !hasSpecificFlag;
|
|
1143
1671
|
const updateInject = options.inject || !hasSpecificFlag;
|
|
1144
1672
|
if (options.dryRun) {
|
|
1145
|
-
console.log(
|
|
1673
|
+
console.log(chalk8.yellow(" Dry run mode - no files will be modified"));
|
|
1146
1674
|
console.log();
|
|
1147
1675
|
}
|
|
1148
|
-
console.log(
|
|
1676
|
+
console.log(chalk8.dim(" Detected tools:"));
|
|
1149
1677
|
for (const tool of toolsToUpdate) {
|
|
1150
1678
|
console.log(` \u2022 ${tool.name}`);
|
|
1151
1679
|
}
|
|
1152
1680
|
console.log();
|
|
1153
1681
|
if (updateCommands || updateSkills) {
|
|
1154
1682
|
if (options.dryRun) {
|
|
1155
|
-
console.log(
|
|
1156
|
-
console.log(
|
|
1683
|
+
console.log(chalk8.dim(" Would update:"));
|
|
1684
|
+
console.log(chalk8.dim(" \u2022 .ocr/skills/SKILL.md (main skill)"));
|
|
1157
1685
|
console.log(
|
|
1158
|
-
|
|
1686
|
+
chalk8.dim(" \u2022 .ocr/skills/references/ (except reviewers/)")
|
|
1159
1687
|
);
|
|
1160
|
-
console.log(
|
|
1688
|
+
console.log(chalk8.dim(" \u2022 .ocr/skills/assets/reviewer-template.md"));
|
|
1689
|
+
console.log(chalk8.dim(" Preserved (not modified):"));
|
|
1690
|
+
console.log(chalk8.dim(" \u2022 .ocr/config.yaml"));
|
|
1161
1691
|
console.log(
|
|
1162
|
-
|
|
1692
|
+
chalk8.dim(" \u2022 .ocr/skills/references/reviewers/ (all reviewers)")
|
|
1163
1693
|
);
|
|
1164
1694
|
for (const tool of toolsToUpdate) {
|
|
1165
1695
|
if (tool.commandStrategy === "subdirectory") {
|
|
1166
|
-
console.log(
|
|
1696
|
+
console.log(chalk8.dim(` \u2022 ${tool.commandsDir}/ocr/ (commands)`));
|
|
1167
1697
|
} else {
|
|
1168
1698
|
console.log(
|
|
1169
|
-
|
|
1699
|
+
chalk8.dim(` \u2022 ${tool.commandsDir}/ocr-*.md (commands)`)
|
|
1170
1700
|
);
|
|
1171
1701
|
}
|
|
1172
1702
|
}
|
|
@@ -1183,34 +1713,42 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
1183
1713
|
const successful = results.filter((r) => r.success);
|
|
1184
1714
|
const failed = results.filter((r) => !r.success);
|
|
1185
1715
|
if (successful.length > 0) {
|
|
1186
|
-
console.log(
|
|
1716
|
+
console.log(chalk8.green(" \u2713 Commands and skills updated"));
|
|
1187
1717
|
console.log(
|
|
1188
|
-
|
|
1718
|
+
chalk8.dim(" Including: SKILL.md, references/, assets/")
|
|
1189
1719
|
);
|
|
1190
1720
|
for (const result of successful) {
|
|
1191
|
-
console.log(` ${
|
|
1721
|
+
console.log(` ${chalk8.green("\u2713")} ${result.tool.name}`);
|
|
1192
1722
|
}
|
|
1193
1723
|
}
|
|
1194
1724
|
if (failed.length > 0) {
|
|
1195
1725
|
console.log();
|
|
1196
|
-
console.log(
|
|
1726
|
+
console.log(chalk8.red(" \u2717 Some updates failed:"));
|
|
1197
1727
|
for (const result of failed) {
|
|
1198
1728
|
console.log(
|
|
1199
|
-
` ${
|
|
1729
|
+
` ${chalk8.red("\u2717")} ${result.tool.name}: ${result.error}`
|
|
1200
1730
|
);
|
|
1201
1731
|
}
|
|
1202
1732
|
}
|
|
1733
|
+
const allWarnings = results.flatMap((r) => r.warnings ?? []);
|
|
1734
|
+
if (allWarnings.length > 0) {
|
|
1735
|
+
console.log();
|
|
1736
|
+
console.log(chalk8.yellow(" \u26A0 Warnings:"));
|
|
1737
|
+
for (const warning of allWarnings) {
|
|
1738
|
+
console.log(` ${chalk8.yellow("\u26A0")} ${warning}`);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1203
1741
|
console.log();
|
|
1204
1742
|
}
|
|
1205
1743
|
}
|
|
1206
1744
|
if (updateInject) {
|
|
1207
1745
|
if (options.dryRun) {
|
|
1208
|
-
console.log(
|
|
1209
|
-
if (
|
|
1210
|
-
console.log(
|
|
1746
|
+
console.log(chalk8.dim(" Would update:"));
|
|
1747
|
+
if (existsSync9(join9(targetDir, "AGENTS.md"))) {
|
|
1748
|
+
console.log(chalk8.dim(" \u2022 AGENTS.md (OCR managed block)"));
|
|
1211
1749
|
}
|
|
1212
|
-
if (
|
|
1213
|
-
console.log(
|
|
1750
|
+
if (existsSync9(join9(targetDir, "CLAUDE.md"))) {
|
|
1751
|
+
console.log(chalk8.dim(" \u2022 CLAUDE.md (OCR managed block)"));
|
|
1214
1752
|
}
|
|
1215
1753
|
console.log();
|
|
1216
1754
|
} else {
|
|
@@ -1218,23 +1756,23 @@ var updateCommand = new Command3("update").description("Update OCR assets after
|
|
|
1218
1756
|
const injectResults = injectIntoProjectFiles(targetDir);
|
|
1219
1757
|
spinner.stop();
|
|
1220
1758
|
if (injectResults.agentsMd || injectResults.claudeMd) {
|
|
1221
|
-
console.log(
|
|
1759
|
+
console.log(chalk8.green(" \u2713 Instructions updated"));
|
|
1222
1760
|
if (injectResults.agentsMd) {
|
|
1223
|
-
console.log(` ${
|
|
1761
|
+
console.log(` ${chalk8.green("\u2713")} AGENTS.md`);
|
|
1224
1762
|
}
|
|
1225
1763
|
if (injectResults.claudeMd) {
|
|
1226
|
-
console.log(` ${
|
|
1764
|
+
console.log(` ${chalk8.green("\u2713")} CLAUDE.md`);
|
|
1227
1765
|
}
|
|
1228
1766
|
} else {
|
|
1229
|
-
console.log(
|
|
1767
|
+
console.log(chalk8.dim(" No instruction files to update"));
|
|
1230
1768
|
}
|
|
1231
1769
|
console.log();
|
|
1232
1770
|
}
|
|
1233
1771
|
}
|
|
1234
1772
|
if (options.dryRun) {
|
|
1235
|
-
console.log(
|
|
1773
|
+
console.log(chalk8.dim(" Run without --dry-run to apply changes."));
|
|
1236
1774
|
} else {
|
|
1237
|
-
console.log(
|
|
1775
|
+
console.log(chalk8.green(" \u2713 Update complete"));
|
|
1238
1776
|
}
|
|
1239
1777
|
console.log();
|
|
1240
1778
|
});
|