@getripple/cli 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +498 -38
- package/dist/index.js +261 -65
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ function usage() {
|
|
|
36
36
|
" ripple init [--force] [--json]",
|
|
37
37
|
" ripple doctor [--strict] [--agent]",
|
|
38
38
|
" ripple scan [path]",
|
|
39
|
+
" ripple workflow [--json]",
|
|
39
40
|
" ripple focus <file>",
|
|
40
41
|
" ripple blast <file>",
|
|
41
42
|
" ripple imports <file>",
|
|
@@ -82,6 +83,7 @@ function usage() {
|
|
|
82
83
|
"Examples:",
|
|
83
84
|
" ripple init",
|
|
84
85
|
" ripple doctor",
|
|
86
|
+
" ripple workflow",
|
|
85
87
|
" ripple doctor --agent",
|
|
86
88
|
" ripple agent",
|
|
87
89
|
" ripple agent --json",
|
|
@@ -420,12 +422,19 @@ function githubActionsWorkflow() {
|
|
|
420
422
|
"",
|
|
421
423
|
].join("\n");
|
|
422
424
|
}
|
|
425
|
+
function rippleGitIgnoreBlock() {
|
|
426
|
+
return [
|
|
427
|
+
"# Ripple machine cache - regenerated automatically",
|
|
428
|
+
core_1.RIPPLE_CACHE_GITIGNORE_ENTRY,
|
|
429
|
+
"",
|
|
430
|
+
].join("\n");
|
|
431
|
+
}
|
|
423
432
|
function defaultInitNextSteps(readiness) {
|
|
424
433
|
return uniqueLines([
|
|
425
434
|
...(readiness?.nextSteps ?? []),
|
|
426
435
|
"Run ripple plan --file <file> --task \"<task>\" --mode file --agent --save.",
|
|
427
436
|
"Run ripple doctor --agent --strict after saving the first intent.",
|
|
428
|
-
"Commit .ripple/policy.json, .
|
|
437
|
+
"Commit .ripple/policy.json, .ripple/intents/latest.json, approvals when needed, and .github/workflows/ripple.yml.",
|
|
429
438
|
]);
|
|
430
439
|
}
|
|
431
440
|
function uniqueLines(lines) {
|
|
@@ -933,13 +942,51 @@ async function runWithQuietEngine(task) {
|
|
|
933
942
|
console.log = originalLog;
|
|
934
943
|
}
|
|
935
944
|
}
|
|
945
|
+
function createCliEngine(workspaceRoot, contextMode = "lean") {
|
|
946
|
+
const engine = new core_1.GraphEngine(workspaceRoot);
|
|
947
|
+
engine.setContextGenerationMode(contextMode);
|
|
948
|
+
return engine;
|
|
949
|
+
}
|
|
950
|
+
function createFastCheckEngine(workspaceRoot) {
|
|
951
|
+
const engine = createCliEngine(workspaceRoot, "lean");
|
|
952
|
+
return engine;
|
|
953
|
+
}
|
|
954
|
+
function fastCheckCandidateFiles(files, intent) {
|
|
955
|
+
const intentSymbolFiles = (intent?.allowedSymbols ?? [])
|
|
956
|
+
.map((symbolId) => symbolId.split("::")[0])
|
|
957
|
+
.filter(Boolean);
|
|
958
|
+
return uniqueItems([
|
|
959
|
+
...files,
|
|
960
|
+
...(intent
|
|
961
|
+
? [
|
|
962
|
+
intent.targetFile,
|
|
963
|
+
...intent.editableFiles,
|
|
964
|
+
...intent.allowedFiles,
|
|
965
|
+
...intent.expectedFiles,
|
|
966
|
+
...intent.contextFiles,
|
|
967
|
+
...intentSymbolFiles,
|
|
968
|
+
]
|
|
969
|
+
: []),
|
|
970
|
+
].filter((file) => Boolean(file)));
|
|
971
|
+
}
|
|
936
972
|
function printScanSummary(summary) {
|
|
937
973
|
console.log("Ripple scan complete");
|
|
938
974
|
console.log(`Workspace: ${summary.workspace}`);
|
|
939
975
|
console.log(`Files: ${summary.files}`);
|
|
940
976
|
console.log(`Symbols: ${summary.symbols}`);
|
|
941
977
|
console.log(`Call edges: ${summary.callEdges}`);
|
|
942
|
-
console.log(`
|
|
978
|
+
console.log(`Mode: ${summary.contextMode}`);
|
|
979
|
+
console.log(`Graph cache: ${summary.cacheGenerated ? summary.cachePath : "not generated"}`);
|
|
980
|
+
console.log(`Context bundle: ${summary.contextGenerated ? summary.contextPath : "not generated by lean scan"}`);
|
|
981
|
+
}
|
|
982
|
+
function printWorkflowSummary(summary) {
|
|
983
|
+
console.log("Ripple workflow generated");
|
|
984
|
+
console.log(`Workspace: ${summary.workspace}`);
|
|
985
|
+
console.log(`Path: ${summary.path}`);
|
|
986
|
+
console.log(`Context bundle: ${summary.contextGenerated ? "generated" : "missing"}`);
|
|
987
|
+
console.log(`Focus files: ${summary.focusFileCount}`);
|
|
988
|
+
console.log("");
|
|
989
|
+
printHumanList("Next:", summary.nextSteps);
|
|
943
990
|
}
|
|
944
991
|
function printDoctorSummary(summary) {
|
|
945
992
|
console.log("Ripple doctor");
|
|
@@ -951,6 +998,7 @@ function printDoctorSummary(summary) {
|
|
|
951
998
|
console.log(`Agent trust: ${summary.adapterSupport.primaryAdapter.agentPolicy.canTrust.join(", ")}`);
|
|
952
999
|
console.log(`Graph: ${summary.checks.graph.ok ? "ok" : "missing"} - ${summary.checks.graph.detail}`);
|
|
953
1000
|
console.log(`Git: ${summary.checks.git.ok ? "ok" : "missing"} - ${summary.checks.git.detail}`);
|
|
1001
|
+
console.log(`Git ignore: ${summary.checks.gitIgnore.ok ? "ok" : "missing"} - ${summary.checks.gitIgnore.detail}`);
|
|
954
1002
|
console.log(`CI workflow: ${summary.checks.ciWorkflow.ok ? "ok" : "missing"} - ${summary.checks.ciWorkflow.detail}`);
|
|
955
1003
|
console.log(`Latest intent: ${summary.checks.latestIntent.ok ? "ok" : "missing"} - ${summary.checks.latestIntent.detail}`);
|
|
956
1004
|
console.log("");
|
|
@@ -993,7 +1041,10 @@ function printInitSummary(summary) {
|
|
|
993
1041
|
function printAgentDoctorSummary(summary) {
|
|
994
1042
|
console.log("RIPPLE_DOCTOR");
|
|
995
1043
|
console.log(`status: ${summary.status}`);
|
|
996
|
-
console.log(`
|
|
1044
|
+
console.log(`decision: ${summary.decision}`);
|
|
1045
|
+
console.log(`can_continue: ${summary.canContinue}`);
|
|
1046
|
+
console.log(`must_stop: ${summary.mustStop}`);
|
|
1047
|
+
console.log(`next_required_action: ${summary.nextRequiredAction}`);
|
|
997
1048
|
console.log(`workspace: ${summary.workspace}`);
|
|
998
1049
|
console.log(`adapter: ${summary.adapterSupport.primaryAdapter.id}`);
|
|
999
1050
|
console.log(`adapter_support: ${summary.adapterSupport.supportLevel}`);
|
|
@@ -1006,10 +1057,13 @@ function printAgentDoctorSummary(summary) {
|
|
|
1006
1057
|
console.log(`policy_detail: ${summary.enforcement.explicitPolicy.detail}`);
|
|
1007
1058
|
console.log(`graph: ${summary.checks.graph.ok ? "ok" : "missing"} - ${summary.checks.graph.detail}`);
|
|
1008
1059
|
console.log(`git: ${summary.checks.git.ok ? "ok" : "missing"} - ${summary.checks.git.detail}`);
|
|
1060
|
+
console.log(`git_ignore: ${summary.checks.gitIgnore.ok ? "ok" : "missing"} - ${summary.checks.gitIgnore.detail}`);
|
|
1009
1061
|
console.log(`ci_workflow: ${summary.checks.ciWorkflow.ok ? "ok" : "missing"} - ${summary.checks.ciWorkflow.detail}`);
|
|
1010
1062
|
console.log(`latest_intent: ${summary.checks.latestIntent.ok ? "ok" : "missing"} - ${summary.checks.latestIntent.detail}`);
|
|
1011
1063
|
console.log("");
|
|
1012
|
-
printAgentList("
|
|
1064
|
+
printAgentList("why", summary.why);
|
|
1065
|
+
console.log("");
|
|
1066
|
+
printAgentList("fix_now", summary.fixNow);
|
|
1013
1067
|
console.log("");
|
|
1014
1068
|
printAgentList("next_steps", summary.nextSteps);
|
|
1015
1069
|
}
|
|
@@ -1776,43 +1830,80 @@ function printAgentGateSummary(summary) {
|
|
|
1776
1830
|
printAgentList("commands_verify", summary.commands.verify);
|
|
1777
1831
|
}
|
|
1778
1832
|
function printGateSummary(summary) {
|
|
1779
|
-
|
|
1780
|
-
console.log(`
|
|
1833
|
+
const statusLabel = summary.canContinue ? "CONTINUE" : "STOP";
|
|
1834
|
+
console.log(`Ripple gate: ${statusLabel}`);
|
|
1835
|
+
console.log(gateHeadline(summary));
|
|
1836
|
+
console.log("");
|
|
1781
1837
|
console.log(`Decision: ${summary.decision}`);
|
|
1782
|
-
console.log(`Can continue: ${summary.canContinue}`);
|
|
1783
|
-
console.log(`Must stop: ${summary.mustStop}`);
|
|
1784
|
-
console.log(`Needs human: ${summary.needsHuman}`);
|
|
1785
|
-
console.log(`Next required phase: ${summary.nextRequiredPhase}`);
|
|
1786
|
-
console.log(`Next required action: ${summary.nextRequiredAction}`);
|
|
1787
|
-
console.log(`Summary: ${summary.summary}`);
|
|
1838
|
+
console.log(`Can continue: ${formatYesNo(summary.canContinue)}`);
|
|
1839
|
+
console.log(`Must stop: ${formatYesNo(summary.mustStop)}`);
|
|
1788
1840
|
console.log("");
|
|
1789
1841
|
console.log("Intent:");
|
|
1790
|
-
console.log(`
|
|
1791
|
-
console.log(`
|
|
1792
|
-
console.log(`
|
|
1793
|
-
console.log(`
|
|
1794
|
-
console.log(`
|
|
1795
|
-
console.log(
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1842
|
+
console.log(` Task: ${summary.intent.task}`);
|
|
1843
|
+
console.log(` Boundary: ${summary.intent.controlMode}`);
|
|
1844
|
+
console.log(` Target: ${summary.intent.targetFile}`);
|
|
1845
|
+
console.log(` Human gate: ${summary.intent.humanGate}`);
|
|
1846
|
+
console.log(` Approval: ${summary.approvalStatus}`);
|
|
1847
|
+
console.log("");
|
|
1848
|
+
printHumanList("Allowed:", gateAllowedItems(summary));
|
|
1849
|
+
const outsideBoundary = gateChangedOutsideItems(summary);
|
|
1850
|
+
printHumanList(outsideBoundary.length > 0 ? "Changed outside boundary:" : "Changed files:", outsideBoundary.length > 0 ? outsideBoundary : summary.changedFiles);
|
|
1851
|
+
printHumanList("Why:", compactGateReasons(summary));
|
|
1852
|
+
printHumanList("Fix now:", compactGateFixes(summary));
|
|
1853
|
+
if (summary.canContinue) {
|
|
1854
|
+
printHumanList("Verify:", summary.verificationTargets.slice(0, 8));
|
|
1855
|
+
}
|
|
1856
|
+
else {
|
|
1857
|
+
printHumanList("Commands:", compactGateCommands(summary));
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
function gateHeadline(summary) {
|
|
1861
|
+
if (summary.canContinue) {
|
|
1862
|
+
return "Agent may continue after running the listed verification targets.";
|
|
1863
|
+
}
|
|
1864
|
+
if (summary.decision === "restore-readiness") {
|
|
1865
|
+
return "Agent must stop because Ripple readiness is weaker than the saved intent.";
|
|
1866
|
+
}
|
|
1867
|
+
if (summary.needsHuman) {
|
|
1868
|
+
return "Agent must stop and ask the human before continuing.";
|
|
1869
|
+
}
|
|
1870
|
+
return "Agent must stop and repair the staged change before continuing.";
|
|
1871
|
+
}
|
|
1872
|
+
function gateAllowedItems(summary) {
|
|
1873
|
+
if (summary.intent.controlMode === "brainstorm") {
|
|
1874
|
+
return ["no file edits"];
|
|
1875
|
+
}
|
|
1876
|
+
if (summary.allowedSymbols.length > 0) {
|
|
1877
|
+
return summary.allowedSymbols;
|
|
1878
|
+
}
|
|
1879
|
+
if (summary.allowedFiles.length > 0) {
|
|
1880
|
+
return summary.allowedFiles;
|
|
1881
|
+
}
|
|
1882
|
+
return [summary.intent.targetFile];
|
|
1883
|
+
}
|
|
1884
|
+
function gateChangedOutsideItems(summary) {
|
|
1885
|
+
return [
|
|
1886
|
+
...summary.changedOutsideBoundaryFiles.map((file) => `file: ${file}`),
|
|
1887
|
+
...summary.changedOutsideBoundarySymbols.map((symbol) => `symbol: ${symbol}`),
|
|
1888
|
+
];
|
|
1889
|
+
}
|
|
1890
|
+
function compactGateReasons(summary) {
|
|
1891
|
+
return uniqueItems(summary.why).slice(0, 6);
|
|
1892
|
+
}
|
|
1893
|
+
function compactGateFixes(summary) {
|
|
1894
|
+
return uniqueItems(summary.fixNow).slice(0, 6);
|
|
1895
|
+
}
|
|
1896
|
+
function compactGateCommands(summary) {
|
|
1897
|
+
return uniqueItems([
|
|
1898
|
+
...summary.commands.unstage,
|
|
1811
1899
|
...summary.commands.repair,
|
|
1812
1900
|
...summary.commands.approve,
|
|
1813
|
-
...summary.commands.
|
|
1814
|
-
|
|
1815
|
-
|
|
1901
|
+
...summary.commands.plan,
|
|
1902
|
+
...summary.commands.doctor,
|
|
1903
|
+
]).slice(0, 6);
|
|
1904
|
+
}
|
|
1905
|
+
function formatYesNo(value) {
|
|
1906
|
+
return value ? "yes" : "no";
|
|
1816
1907
|
}
|
|
1817
1908
|
function printAuditSummary(summary) {
|
|
1818
1909
|
const validation = summary.stagedCheck.intentValidation;
|
|
@@ -2232,7 +2323,7 @@ async function planCommand(options) {
|
|
|
2232
2323
|
throw new Error("Missing target file. Usage: ripple plan --file <file> --task <task> [--budget N]");
|
|
2233
2324
|
}
|
|
2234
2325
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2235
|
-
const engine =
|
|
2326
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2236
2327
|
try {
|
|
2237
2328
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2238
2329
|
const summary = engine.planContext(options.task ?? "", options.file, options.budget);
|
|
@@ -2306,9 +2397,9 @@ function approvalStatusOutput(intent, status) {
|
|
|
2306
2397
|
};
|
|
2307
2398
|
}
|
|
2308
2399
|
async function buildAuditForFiles(input) {
|
|
2309
|
-
const engine =
|
|
2400
|
+
const engine = createFastCheckEngine(input.workspaceRoot);
|
|
2310
2401
|
try {
|
|
2311
|
-
await runWithQuietEngine(() => engine.
|
|
2402
|
+
await runWithQuietEngine(() => engine.fastCheckScan(fastCheckCandidateFiles(input.files, input.intent)));
|
|
2312
2403
|
const stagedCheck = (0, core_1.buildStagedCheckSummary)(engine, {
|
|
2313
2404
|
workspaceRoot: input.workspaceRoot,
|
|
2314
2405
|
stagedFiles: input.files,
|
|
@@ -2336,9 +2427,9 @@ async function buildAuditForFiles(input) {
|
|
|
2336
2427
|
}
|
|
2337
2428
|
}
|
|
2338
2429
|
async function buildCheckSummaryForFiles(input) {
|
|
2339
|
-
const engine =
|
|
2430
|
+
const engine = createFastCheckEngine(input.workspaceRoot);
|
|
2340
2431
|
try {
|
|
2341
|
-
await runWithQuietEngine(() => engine.
|
|
2432
|
+
await runWithQuietEngine(() => engine.fastCheckScan(fastCheckCandidateFiles(input.files)));
|
|
2342
2433
|
return (0, core_1.buildStagedCheckSummary)(engine, {
|
|
2343
2434
|
workspaceRoot: input.workspaceRoot,
|
|
2344
2435
|
stagedFiles: input.files,
|
|
@@ -2363,9 +2454,9 @@ async function checkCommand(options) {
|
|
|
2363
2454
|
const checkFiles = options.changed
|
|
2364
2455
|
? (0, core_1.listGitChangedFiles)(workspaceRoot, baseRef)
|
|
2365
2456
|
: (0, core_1.listGitStagedFiles)(workspaceRoot);
|
|
2366
|
-
const engine =
|
|
2457
|
+
const engine = createFastCheckEngine(workspaceRoot);
|
|
2367
2458
|
try {
|
|
2368
|
-
await runWithQuietEngine(() => engine.
|
|
2459
|
+
await runWithQuietEngine(() => engine.fastCheckScan(fastCheckCandidateFiles(checkFiles)));
|
|
2369
2460
|
const stagedSummary = (0, core_1.buildStagedCheckSummary)(engine, {
|
|
2370
2461
|
workspaceRoot,
|
|
2371
2462
|
stagedFiles: checkFiles,
|
|
@@ -2614,7 +2705,7 @@ async function ciCommand(options) {
|
|
|
2614
2705
|
}
|
|
2615
2706
|
async function doctorCommand(options) {
|
|
2616
2707
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2617
|
-
const engine =
|
|
2708
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2618
2709
|
try {
|
|
2619
2710
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2620
2711
|
const summary = (0, core_1.buildRippleReadinessSummary)(workspaceRoot, engine);
|
|
@@ -2638,6 +2729,7 @@ async function initCommand(options) {
|
|
|
2638
2729
|
const policy = (0, core_1.defaultRipplePolicy)();
|
|
2639
2730
|
const policyContents = (0, core_1.formatRipplePolicy)(policy);
|
|
2640
2731
|
const workflow = githubActionsWorkflow();
|
|
2732
|
+
const gitignoreBlock = rippleGitIgnoreBlock();
|
|
2641
2733
|
const files = [
|
|
2642
2734
|
{
|
|
2643
2735
|
path: core_1.RIPPLE_POLICY_PATH.split(path.sep).join("/"),
|
|
@@ -2649,6 +2741,12 @@ async function initCommand(options) {
|
|
|
2649
2741
|
absolutePath: path.join(workspaceRoot, GITHUB_ACTIONS_WORKFLOW_PATH),
|
|
2650
2742
|
content: workflow,
|
|
2651
2743
|
},
|
|
2744
|
+
{
|
|
2745
|
+
path: core_1.RIPPLE_GITIGNORE_PATH,
|
|
2746
|
+
absolutePath: path.join(workspaceRoot, core_1.RIPPLE_GITIGNORE_PATH),
|
|
2747
|
+
content: gitignoreBlock,
|
|
2748
|
+
merge: true,
|
|
2749
|
+
},
|
|
2652
2750
|
];
|
|
2653
2751
|
if (options.print) {
|
|
2654
2752
|
const summary = {
|
|
@@ -2668,18 +2766,15 @@ async function initCommand(options) {
|
|
|
2668
2766
|
printJson(summary);
|
|
2669
2767
|
return;
|
|
2670
2768
|
}
|
|
2671
|
-
process.stdout.write(
|
|
2672
|
-
`# ${
|
|
2673
|
-
|
|
2674
|
-
"",
|
|
2675
|
-
`# ${files[1].path}`,
|
|
2676
|
-
files[1].content.trimEnd(),
|
|
2677
|
-
"",
|
|
2678
|
-
].join("\n"));
|
|
2769
|
+
process.stdout.write(files
|
|
2770
|
+
.flatMap((file) => [`# ${file.path}`, file.content.trimEnd(), ""])
|
|
2771
|
+
.join("\n"));
|
|
2679
2772
|
return;
|
|
2680
2773
|
}
|
|
2681
|
-
const writtenFiles = files.map((file) =>
|
|
2682
|
-
|
|
2774
|
+
const writtenFiles = files.map((file) => file.merge
|
|
2775
|
+
? writeRippleGitIgnoreFile(file.absolutePath)
|
|
2776
|
+
: writeInitFile(file, options.force));
|
|
2777
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2683
2778
|
try {
|
|
2684
2779
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2685
2780
|
const readiness = (0, core_1.buildRippleReadinessSummary)(workspaceRoot, engine);
|
|
@@ -2721,6 +2816,44 @@ function writeInitFile(file, force) {
|
|
|
2721
2816
|
overwritten: existed,
|
|
2722
2817
|
};
|
|
2723
2818
|
}
|
|
2819
|
+
function writeRippleGitIgnoreFile(targetPath) {
|
|
2820
|
+
const content = rippleGitIgnoreBlock();
|
|
2821
|
+
const existed = fs.existsSync(targetPath);
|
|
2822
|
+
if (!existed) {
|
|
2823
|
+
fs.writeFileSync(targetPath, content, "utf8");
|
|
2824
|
+
return {
|
|
2825
|
+
path: core_1.RIPPLE_GITIGNORE_PATH,
|
|
2826
|
+
status: "written",
|
|
2827
|
+
written: true,
|
|
2828
|
+
overwritten: false,
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
const existing = fs.readFileSync(targetPath, "utf8");
|
|
2832
|
+
if (gitIgnoreContainsRippleCache(existing)) {
|
|
2833
|
+
return {
|
|
2834
|
+
path: core_1.RIPPLE_GITIGNORE_PATH,
|
|
2835
|
+
status: "exists",
|
|
2836
|
+
written: false,
|
|
2837
|
+
overwritten: false,
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
2841
|
+
fs.writeFileSync(targetPath, `${existing}${separator}${content}`, "utf8");
|
|
2842
|
+
return {
|
|
2843
|
+
path: core_1.RIPPLE_GITIGNORE_PATH,
|
|
2844
|
+
status: "updated",
|
|
2845
|
+
written: true,
|
|
2846
|
+
overwritten: false,
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
function gitIgnoreContainsRippleCache(contents) {
|
|
2850
|
+
return contents
|
|
2851
|
+
.split(/\r?\n/)
|
|
2852
|
+
.map((line) => line.trim().replace(/\\/g, "/").replace(/^\/+/, ""))
|
|
2853
|
+
.some((line) => line === core_1.RIPPLE_CACHE_GITIGNORE_ENTRY ||
|
|
2854
|
+
line === ".ripple/.cache" ||
|
|
2855
|
+
line === ".ripple/.cache/**");
|
|
2856
|
+
}
|
|
2724
2857
|
function initCiCommand(options) {
|
|
2725
2858
|
const workflow = githubActionsWorkflow();
|
|
2726
2859
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
@@ -2867,15 +3000,15 @@ function printAgentPolicyExplanation(explanation) {
|
|
|
2867
3000
|
async function repairCommand(options) {
|
|
2868
3001
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2869
3002
|
const stagedFiles = (0, core_1.listGitStagedFiles)(workspaceRoot);
|
|
2870
|
-
const
|
|
3003
|
+
const intent = (0, core_1.loadChangeIntent)(workspaceRoot, options.intent ?? "latest");
|
|
3004
|
+
const engine = createFastCheckEngine(workspaceRoot);
|
|
2871
3005
|
try {
|
|
2872
|
-
await runWithQuietEngine(() => engine.
|
|
3006
|
+
await runWithQuietEngine(() => engine.fastCheckScan(fastCheckCandidateFiles(stagedFiles, intent)));
|
|
2873
3007
|
const stagedSummary = (0, core_1.buildStagedCheckSummary)(engine, {
|
|
2874
3008
|
workspaceRoot,
|
|
2875
3009
|
stagedFiles,
|
|
2876
3010
|
tokenBudget: options.budget,
|
|
2877
3011
|
});
|
|
2878
|
-
const intent = (0, core_1.loadChangeIntent)(workspaceRoot, options.intent ?? "latest");
|
|
2879
3012
|
const summary = (0, core_1.validateStagedCheckAgainstIntent)(stagedSummary, intent, {
|
|
2880
3013
|
currentPolicyExplanation: currentPolicyExplanationForIntent(workspaceRoot, intent),
|
|
2881
3014
|
currentReadinessSnapshot: currentReadinessSnapshotForEngine(workspaceRoot, engine),
|
|
@@ -2898,7 +3031,7 @@ async function repairCommand(options) {
|
|
|
2898
3031
|
}
|
|
2899
3032
|
async function historyCommand(options) {
|
|
2900
3033
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2901
|
-
const engine =
|
|
3034
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2902
3035
|
try {
|
|
2903
3036
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2904
3037
|
const summary = engine.getRecentHistorySummary(options.last);
|
|
@@ -2921,7 +3054,7 @@ async function callersCommand(symbolId, options) {
|
|
|
2921
3054
|
throw new Error("Symbol id must use <file>::<symbol>, for example src/auth.ts::validateToken");
|
|
2922
3055
|
}
|
|
2923
3056
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2924
|
-
const engine =
|
|
3057
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2925
3058
|
try {
|
|
2926
3059
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2927
3060
|
const summary = engine.getSymbolCallersSummary(symbolId);
|
|
@@ -2941,16 +3074,20 @@ async function callersCommand(symbolId, options) {
|
|
|
2941
3074
|
}
|
|
2942
3075
|
async function scanCommand(targetPath, options) {
|
|
2943
3076
|
const workspaceRoot = resolveWorkspaceRoot(targetPath);
|
|
2944
|
-
const engine =
|
|
3077
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2945
3078
|
try {
|
|
2946
3079
|
await runWithQuietEngine(() => engine.initialScan());
|
|
3080
|
+
const cachePath = path.join(workspaceRoot, ".ripple", ".cache", "graph.cache.json");
|
|
2947
3081
|
const workflowPath = path.join(workspaceRoot, ".ripple", "WORKFLOW.md");
|
|
2948
3082
|
const summary = {
|
|
2949
3083
|
workspace: workspaceRoot,
|
|
2950
3084
|
files: engine.graph.files.size,
|
|
2951
3085
|
symbols: engine.graph.symbols.size,
|
|
2952
3086
|
callEdges: countCallEdges(engine),
|
|
2953
|
-
|
|
3087
|
+
contextMode: "lean",
|
|
3088
|
+
cacheGenerated: fs.existsSync(cachePath),
|
|
3089
|
+
cachePath,
|
|
3090
|
+
contextGenerated: false,
|
|
2954
3091
|
contextPath: workflowPath,
|
|
2955
3092
|
};
|
|
2956
3093
|
if (options.json) {
|
|
@@ -2964,12 +3101,66 @@ async function scanCommand(targetPath, options) {
|
|
|
2964
3101
|
engine.dispose();
|
|
2965
3102
|
}
|
|
2966
3103
|
}
|
|
3104
|
+
async function workflowCommand(options) {
|
|
3105
|
+
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
3106
|
+
const engine = createCliEngine(workspaceRoot, "full");
|
|
3107
|
+
try {
|
|
3108
|
+
await runWithQuietEngine(() => engine.initialScan());
|
|
3109
|
+
const workflowPath = path.join(workspaceRoot, ".ripple", "WORKFLOW.md");
|
|
3110
|
+
const contextDir = path.join(workspaceRoot, ".ripple", ".cache");
|
|
3111
|
+
const contextFiles = [
|
|
3112
|
+
".ripple/.cache/context.json",
|
|
3113
|
+
".ripple/.cache/context.files.json",
|
|
3114
|
+
".ripple/.cache/context.symbols.json",
|
|
3115
|
+
];
|
|
3116
|
+
const focusDir = path.join(contextDir, "focus");
|
|
3117
|
+
const focusFileCount = countJsonFiles(focusDir);
|
|
3118
|
+
const summary = {
|
|
3119
|
+
protocol: "ripple-workflow",
|
|
3120
|
+
version: 1,
|
|
3121
|
+
workspace: workspaceRoot,
|
|
3122
|
+
path: ".ripple/WORKFLOW.md",
|
|
3123
|
+
written: fs.existsSync(workflowPath),
|
|
3124
|
+
contextGenerated: contextFiles.every((filePath) => fs.existsSync(path.join(workspaceRoot, filePath))),
|
|
3125
|
+
contextFiles,
|
|
3126
|
+
focusFileCount,
|
|
3127
|
+
nextSteps: [
|
|
3128
|
+
"Copy .ripple/WORKFLOW.md into your agent instruction file if your agent does not use MCP.",
|
|
3129
|
+
"For MCP-capable agents, prefer ripple_plan_context and ripple_gate over reading generated files.",
|
|
3130
|
+
"Run ripple scan for a lean graph refresh when you do not need file-based agent instructions.",
|
|
3131
|
+
],
|
|
3132
|
+
};
|
|
3133
|
+
if (!summary.written) {
|
|
3134
|
+
throw new Error("WORKFLOW.md was not generated. Check that the workspace has supported source files.");
|
|
3135
|
+
}
|
|
3136
|
+
if (options.json) {
|
|
3137
|
+
printJson(summary);
|
|
3138
|
+
}
|
|
3139
|
+
else {
|
|
3140
|
+
printWorkflowSummary(summary);
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
finally {
|
|
3144
|
+
engine.dispose();
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
function countJsonFiles(directoryPath) {
|
|
3148
|
+
try {
|
|
3149
|
+
return fs
|
|
3150
|
+
.readdirSync(directoryPath)
|
|
3151
|
+
.filter((fileName) => fileName.endsWith(".json"))
|
|
3152
|
+
.length;
|
|
3153
|
+
}
|
|
3154
|
+
catch {
|
|
3155
|
+
return 0;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
2967
3158
|
async function symbolsCommand(filePath, options) {
|
|
2968
3159
|
if (!filePath) {
|
|
2969
3160
|
throw new Error("Missing file path. Usage: ripple symbols <file>");
|
|
2970
3161
|
}
|
|
2971
3162
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2972
|
-
const engine =
|
|
3163
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2973
3164
|
try {
|
|
2974
3165
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2975
3166
|
const summary = engine.getFileSymbolsSummary(filePath);
|
|
@@ -2992,7 +3183,7 @@ async function blastCommand(filePath, options) {
|
|
|
2992
3183
|
throw new Error("Missing file path. Usage: ripple blast <file>");
|
|
2993
3184
|
}
|
|
2994
3185
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
2995
|
-
const engine =
|
|
3186
|
+
const engine = createCliEngine(workspaceRoot);
|
|
2996
3187
|
try {
|
|
2997
3188
|
await runWithQuietEngine(() => engine.initialScan());
|
|
2998
3189
|
const summary = engine.getBlastRadiusSummary(filePath);
|
|
@@ -3015,7 +3206,7 @@ async function dependencyCommand(filePath, direction, options) {
|
|
|
3015
3206
|
throw new Error(`Missing file path. Usage: ripple ${direction} <file>`);
|
|
3016
3207
|
}
|
|
3017
3208
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
3018
|
-
const engine =
|
|
3209
|
+
const engine = createCliEngine(workspaceRoot);
|
|
3019
3210
|
try {
|
|
3020
3211
|
await runWithQuietEngine(() => engine.initialScan());
|
|
3021
3212
|
const summary = engine.getFileDependencySummary(filePath);
|
|
@@ -3041,13 +3232,14 @@ async function focusCommand(filePath, options) {
|
|
|
3041
3232
|
throw new Error("Missing file path. Usage: ripple focus <file>");
|
|
3042
3233
|
}
|
|
3043
3234
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
3044
|
-
const engine =
|
|
3235
|
+
const engine = createCliEngine(workspaceRoot, "on-demand");
|
|
3045
3236
|
try {
|
|
3046
3237
|
await runWithQuietEngine(() => engine.initialScan());
|
|
3047
3238
|
const summary = engine.getFileFocusSummary(filePath);
|
|
3048
3239
|
if (!summary) {
|
|
3049
3240
|
throw new Error(`File is not in the Ripple graph: ${filePath}`);
|
|
3050
3241
|
}
|
|
3242
|
+
engine.writeFileFocus(filePath);
|
|
3051
3243
|
if (options.json) {
|
|
3052
3244
|
printJson(summary);
|
|
3053
3245
|
}
|
|
@@ -3087,6 +3279,10 @@ async function main() {
|
|
|
3087
3279
|
await scanCommand(arg, options);
|
|
3088
3280
|
return;
|
|
3089
3281
|
}
|
|
3282
|
+
if (command === "workflow") {
|
|
3283
|
+
await workflowCommand(options);
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3090
3286
|
if (command === "doctor") {
|
|
3091
3287
|
await doctorCommand(options);
|
|
3092
3288
|
return;
|