@getripple/cli 1.0.9 → 1.0.11
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 +175 -125
- package/dist/index.js.map +1 -1
- package/package.json +53 -53
package/dist/index.js
CHANGED
|
@@ -544,6 +544,19 @@ function parseCliArgs(argv) {
|
|
|
544
544
|
options.base = token.slice("--base=".length);
|
|
545
545
|
continue;
|
|
546
546
|
}
|
|
547
|
+
if (token === "--sha") {
|
|
548
|
+
const value = argv[i + 1];
|
|
549
|
+
if (!value || value.startsWith("-")) {
|
|
550
|
+
throw new Error("Missing value for --sha");
|
|
551
|
+
}
|
|
552
|
+
options.sha = value;
|
|
553
|
+
i++;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (token.startsWith("--sha=")) {
|
|
557
|
+
options.sha = token.slice("--sha=".length);
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
547
560
|
if (token === "--budget") {
|
|
548
561
|
const value = argv[i + 1];
|
|
549
562
|
if (!value || value.startsWith("-")) {
|
|
@@ -627,18 +640,21 @@ function rippleDirectRunnerHookLines() {
|
|
|
627
640
|
const GITHUB_ACTIONS_WORKFLOW_PATH = core_1.RIPPLE_CI_WORKFLOW_PATH;
|
|
628
641
|
function githubActionsWorkflow() {
|
|
629
642
|
return [
|
|
630
|
-
"name: Ripple",
|
|
643
|
+
"name: Ripple Enterprise Gate",
|
|
631
644
|
"",
|
|
632
645
|
"on:",
|
|
633
646
|
" pull_request:",
|
|
647
|
+
" push:",
|
|
648
|
+
" branches: [main, master]",
|
|
634
649
|
"",
|
|
635
650
|
"permissions:",
|
|
636
651
|
" contents: read",
|
|
637
652
|
" pull-requests: read",
|
|
653
|
+
" checks: write",
|
|
638
654
|
"",
|
|
639
655
|
"jobs:",
|
|
640
656
|
" ripple:",
|
|
641
|
-
" name: Ripple
|
|
657
|
+
" name: Ripple authorization gate",
|
|
642
658
|
" runs-on: ubuntu-latest",
|
|
643
659
|
" steps:",
|
|
644
660
|
" - name: Checkout",
|
|
@@ -649,8 +665,11 @@ function githubActionsWorkflow() {
|
|
|
649
665
|
" uses: actions/setup-node@v4",
|
|
650
666
|
" with:",
|
|
651
667
|
" node-version: 20",
|
|
652
|
-
" - name: Ripple CI",
|
|
653
|
-
` run: npx -y ${rippleCliPackageSpec()} ci --base origin/\${{ github.base_ref }} --github-annotations`,
|
|
668
|
+
" - name: Ripple CI gate",
|
|
669
|
+
` run: npx -y ${rippleCliPackageSpec()} ci --base origin/\${{ github.base_ref }} --github-annotations --sha \${{ github.sha }}`,
|
|
670
|
+
" env:",
|
|
671
|
+
" RIPPLE_API_KEY: ${{ secrets.RIPPLE_API_KEY }}",
|
|
672
|
+
" RIPPLE_CLOUD_URL: ${{ secrets.RIPPLE_CLOUD_URL }}",
|
|
654
673
|
"",
|
|
655
674
|
].join("\n");
|
|
656
675
|
}
|
|
@@ -3040,12 +3059,30 @@ function intentCommand(action, options) {
|
|
|
3040
3059
|
intentStatusCommand(options);
|
|
3041
3060
|
return;
|
|
3042
3061
|
}
|
|
3062
|
+
if (action === "push") {
|
|
3063
|
+
void intentPushCommand(options);
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3043
3066
|
if (action === "close") {
|
|
3044
3067
|
closeIntentCommand(options);
|
|
3045
3068
|
return;
|
|
3046
3069
|
}
|
|
3047
3070
|
throw new Error("Usage: ripple intent status [--intent latest|path] or ripple intent close --reason <text> [--intent latest|path]");
|
|
3048
3071
|
}
|
|
3072
|
+
async function intentPushCommand(options) {
|
|
3073
|
+
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
3074
|
+
const intentRef = options.intent ?? "latest";
|
|
3075
|
+
const intent = (0, core_1.loadChangeIntent)(workspaceRoot, intentRef);
|
|
3076
|
+
console.log(`[Ripple] Pushing active intent '${intent.id}' to Ripple Cloud...`);
|
|
3077
|
+
const result = await (0, core_1.pushIntentToCloud)(intent);
|
|
3078
|
+
if (result.sent) {
|
|
3079
|
+
console.log("✅ Intent pushed successfully. This is now the active boundary for CI checks.");
|
|
3080
|
+
}
|
|
3081
|
+
else {
|
|
3082
|
+
console.error(`❌ Failed to push intent: ${result.error}`);
|
|
3083
|
+
process.exitCode = 1;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3049
3086
|
function intentStatusCommand(options) {
|
|
3050
3087
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
3051
3088
|
const intentRef = options.intent ?? "latest";
|
|
@@ -3374,6 +3411,8 @@ async function auditCommand(options) {
|
|
|
3374
3411
|
}
|
|
3375
3412
|
applyStrictExit(options.strict && strictAuditShouldFail(audit));
|
|
3376
3413
|
}
|
|
3414
|
+
// in: ripple-main/packages/cli/src/index.ts
|
|
3415
|
+
// REPLACE THE ENTIRE OLD `gateCommand` FUNCTION WITH THIS:
|
|
3377
3416
|
async function gateCommand(options) {
|
|
3378
3417
|
if (selectedChangeModeCount(options) > 1) {
|
|
3379
3418
|
throw new Error("Choose one gate/audit mode: --staged, --worktree, or --changed --base <ref>");
|
|
@@ -3382,8 +3421,9 @@ async function gateCommand(options) {
|
|
|
3382
3421
|
const intentRef = options.intent ?? "latest";
|
|
3383
3422
|
const mode = selectedChangeMode(options);
|
|
3384
3423
|
const baseRef = options.base ?? "HEAD";
|
|
3424
|
+
const intentPath = resolveCliIntentPath(workspaceRoot, intentRef);
|
|
3385
3425
|
try {
|
|
3386
|
-
(0, core_1.loadChangeIntent)(workspaceRoot,
|
|
3426
|
+
(0, core_1.loadChangeIntent)(workspaceRoot, intentPath);
|
|
3387
3427
|
}
|
|
3388
3428
|
catch (err) {
|
|
3389
3429
|
const block = (0, core_1.buildRippleGateIntentBlockSummary)({
|
|
@@ -3405,7 +3445,7 @@ async function gateCommand(options) {
|
|
|
3405
3445
|
applyStrictExit(true);
|
|
3406
3446
|
return;
|
|
3407
3447
|
}
|
|
3408
|
-
const audit = await buildAuditFromCliOptions(options);
|
|
3448
|
+
const audit = await buildAuditFromCliOptions({ ...options, intent: intentPath });
|
|
3409
3449
|
const gate = (0, core_1.buildRippleGateSummary)(audit);
|
|
3410
3450
|
if (options.json) {
|
|
3411
3451
|
printJson(gate);
|
|
@@ -3416,6 +3456,60 @@ async function gateCommand(options) {
|
|
|
3416
3456
|
else {
|
|
3417
3457
|
printGateSummary(gate);
|
|
3418
3458
|
}
|
|
3459
|
+
// Consume intent on pass to prevent "ghost intents" if commit is aborted.
|
|
3460
|
+
if (gate.canContinue && intentRef === "latest" && mode === "staged") {
|
|
3461
|
+
try {
|
|
3462
|
+
const consumedIntentCachePath = path.join(workspaceRoot, ".ripple", ".cache", "consumed-intent.json");
|
|
3463
|
+
if (fs.existsSync(consumedIntentCachePath)) {
|
|
3464
|
+
fs.unlinkSync(consumedIntentCachePath);
|
|
3465
|
+
}
|
|
3466
|
+
fs.renameSync(intentPath, consumedIntentCachePath);
|
|
3467
|
+
console.log("\n[Ripple] Intent validated and consumed by gate. Ready for commit.");
|
|
3468
|
+
}
|
|
3469
|
+
catch (err) {
|
|
3470
|
+
const errorDetail = err instanceof Error ? err.message : String(err);
|
|
3471
|
+
console.warn(`[Ripple] CRITICAL WARNING: Could not consume intent file. To prevent a future 'ghost intent' block, manually delete '${intentPath}' after your commit. Error: ${errorDetail}`);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
// Sync a redacted, privacy-safe audit receipt to the cloud.
|
|
3475
|
+
if (process.env.RIPPLE_API_KEY) {
|
|
3476
|
+
const cloudDecision = gate.canContinue ? "continue" :
|
|
3477
|
+
gate.needsHuman ? "human-review" : "blocked";
|
|
3478
|
+
const commitSha = options.sha ?? (0, core_1.getCurrentCommitSha)(child_process_1.execSync);
|
|
3479
|
+
const branch = (0, core_1.getCurrentBranch)(child_process_1.execSync);
|
|
3480
|
+
const actor = (0, core_1.getCurrentActor)(child_process_1.execSync);
|
|
3481
|
+
const redactedPayload = {
|
|
3482
|
+
decision: gate.decision,
|
|
3483
|
+
status: gate.status,
|
|
3484
|
+
risk: {
|
|
3485
|
+
level: gate.risk.level,
|
|
3486
|
+
score: gate.risk.score,
|
|
3487
|
+
summary: gate.risk.summary,
|
|
3488
|
+
},
|
|
3489
|
+
auditStatus: audit.status,
|
|
3490
|
+
approvalStatus: audit.approvalStatus.status,
|
|
3491
|
+
intent: audit.intent,
|
|
3492
|
+
mode: audit.mode,
|
|
3493
|
+
baseRef: audit.baseRef,
|
|
3494
|
+
blockingReasons: audit.blockingReasons,
|
|
3495
|
+
};
|
|
3496
|
+
const cloudResult = await (0, core_1.syncAuditToCloud)({
|
|
3497
|
+
intentId: audit.intent.id,
|
|
3498
|
+
decision: cloudDecision,
|
|
3499
|
+
commitSha,
|
|
3500
|
+
branch,
|
|
3501
|
+
actor,
|
|
3502
|
+
source: "cli",
|
|
3503
|
+
payload: redactedPayload,
|
|
3504
|
+
});
|
|
3505
|
+
if (cloudResult.sent) {
|
|
3506
|
+
const icon = cloudDecision === "continue" ? "✓" : "⛔";
|
|
3507
|
+
console.log(`\n[Ripple Cloud] ${icon} Gate decision recorded: ${cloudDecision} (${commitSha.slice(0, 7)})`);
|
|
3508
|
+
}
|
|
3509
|
+
else if (cloudResult.error) {
|
|
3510
|
+
console.warn(`\n[Ripple Cloud] ⚠ Sync failed (non-blocking): ${cloudResult.error}`);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3419
3513
|
applyStrictExit(options.strict && !gate.canContinue);
|
|
3420
3514
|
}
|
|
3421
3515
|
function approveCommand(options) {
|
|
@@ -3620,14 +3714,21 @@ function approvalCommand(options) {
|
|
|
3620
3714
|
}
|
|
3621
3715
|
printApprovalStatus(status);
|
|
3622
3716
|
}
|
|
3717
|
+
// REPLACE WITH THIS NEW FUNCTION
|
|
3718
|
+
// in: ripple-main/packages/cli/src/index.ts
|
|
3719
|
+
// REPLACE THE ENTIRE OLD `ciCommand` FUNCTION WITH THIS:
|
|
3623
3720
|
async function ciCommand(options) {
|
|
3624
3721
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
3625
3722
|
const baseRef = options.base ?? defaultCiBaseRef();
|
|
3626
|
-
const hasExplicitIntent = Boolean(options.intent);
|
|
3627
|
-
const intentRef = options.intent ?? "latest";
|
|
3628
3723
|
const files = (0, core_1.listGitChangedFiles)(workspaceRoot, baseRef);
|
|
3629
3724
|
const emitGithubAnnotations = shouldEmitGithubAnnotations(options);
|
|
3630
|
-
|
|
3725
|
+
const commitSha = options.sha ?? (0, core_1.getCurrentCommitSha)(child_process_1.execSync);
|
|
3726
|
+
// FETCH THE AUTHORITATIVE INTENT FROM THE CLOUD.
|
|
3727
|
+
const intent = await (0, core_1.fetchActiveIntentForCommit)(commitSha);
|
|
3728
|
+
if (!intent) {
|
|
3729
|
+
// If no active intent is found in the cloud, run a policy-only audit.
|
|
3730
|
+
// This provides a baseline check without enforcing a specific boundary.
|
|
3731
|
+
console.log("[Ripple CI] No active cloud intent found. Running in policy-only audit mode.");
|
|
3631
3732
|
const summary = await buildCheckSummaryForFiles({
|
|
3632
3733
|
workspaceRoot,
|
|
3633
3734
|
files,
|
|
@@ -3636,105 +3737,20 @@ async function ciCommand(options) {
|
|
|
3636
3737
|
tokenBudget: options.budget,
|
|
3637
3738
|
});
|
|
3638
3739
|
const policySync = buildPolicySyncSummary(workspaceRoot);
|
|
3639
|
-
if (
|
|
3640
|
-
printJson({
|
|
3641
|
-
...summary,
|
|
3642
|
-
protocol: "ripple-ci-policy-audit",
|
|
3643
|
-
version: 1,
|
|
3644
|
-
auditMode: true,
|
|
3645
|
-
blocking: false,
|
|
3646
|
-
intentRequired: false,
|
|
3647
|
-
policySync,
|
|
3648
|
-
});
|
|
3649
|
-
}
|
|
3650
|
-
else if (options.agent) {
|
|
3651
|
-
printAgentStagedCheckSummary(summary);
|
|
3652
|
-
console.log("");
|
|
3653
|
-
console.log("ci_mode: policy-audit");
|
|
3654
|
-
console.log("blocking: false");
|
|
3655
|
-
console.log("intent_required: false");
|
|
3656
|
-
console.log(`policy_sync: ${policySync.status}`);
|
|
3657
|
-
if (policySync.missingRules.length > 0) {
|
|
3658
|
-
console.log("policy_sync_missing_rules:");
|
|
3659
|
-
policySync.missingRules.slice(0, 12).forEach((rule) => {
|
|
3660
|
-
console.log(`- ${rule.paths.join(", ")} risk=${rule.risk ?? "medium"}`);
|
|
3661
|
-
});
|
|
3662
|
-
}
|
|
3663
|
-
console.log("next_required_action: Review policy-risk findings and policy-sync warnings before merge. Use --intent latest --strict only when you want an intent-bound hard gate.");
|
|
3664
|
-
}
|
|
3665
|
-
else {
|
|
3666
|
-
console.log("Ripple CI policy audit");
|
|
3667
|
-
console.log("Status: audit");
|
|
3668
|
-
console.log("Blocking: false");
|
|
3669
|
-
console.log("Intent: none (local intents are not required in CI audit mode)");
|
|
3670
|
-
console.log(`Policy sync: ${policySync.status}`);
|
|
3671
|
-
if (policySync.missingRules.length > 0) {
|
|
3672
|
-
console.log("");
|
|
3673
|
-
console.log("Policy may be missing risky repo surfaces:");
|
|
3674
|
-
policySync.missingRules.slice(0, 12).forEach((rule) => {
|
|
3675
|
-
console.log(`- ${rule.paths.join(", ")} risk=${rule.risk ?? "medium"}`);
|
|
3676
|
-
});
|
|
3677
|
-
}
|
|
3678
|
-
console.log("");
|
|
3679
|
-
printStagedCheckSummary(summary);
|
|
3680
|
-
console.log("");
|
|
3681
|
-
console.log("Next action: Review policy-risk findings and policy-sync warnings before merge. Use --intent latest --strict only when you want an intent-bound hard gate.");
|
|
3682
|
-
}
|
|
3683
|
-
if (emitGithubAnnotations && !options.json) {
|
|
3740
|
+
if (emitGithubAnnotations) {
|
|
3684
3741
|
printGithubPolicyAuditAnnotations(summary, policySync);
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
}
|
|
3689
|
-
let intent;
|
|
3690
|
-
try {
|
|
3691
|
-
intent = (0, core_1.loadChangeIntent)(workspaceRoot, intentRef);
|
|
3692
|
-
}
|
|
3693
|
-
catch (err) {
|
|
3694
|
-
const summary = await buildCheckSummaryForFiles({
|
|
3695
|
-
workspaceRoot,
|
|
3696
|
-
files,
|
|
3697
|
-
mode: "changed",
|
|
3698
|
-
baseRef,
|
|
3699
|
-
tokenBudget: options.budget,
|
|
3700
|
-
});
|
|
3701
|
-
const message = intentLoadFailureMessage(intentRef, err);
|
|
3702
|
-
if (options.json) {
|
|
3703
|
-
printJson({
|
|
3704
|
-
...summary,
|
|
3705
|
-
nextRequiredPhase: MISSING_INTENT_NEXT_REQUIRED_PHASE,
|
|
3706
|
-
nextRequiredAction: MISSING_INTENT_NEXT_REQUIRED_ACTION,
|
|
3707
|
-
intentLoadError: {
|
|
3708
|
-
intent: intentRef,
|
|
3709
|
-
message,
|
|
3710
|
-
nextRequiredPhase: MISSING_INTENT_NEXT_REQUIRED_PHASE,
|
|
3711
|
-
nextRequiredAction: MISSING_INTENT_NEXT_REQUIRED_ACTION,
|
|
3712
|
-
},
|
|
3742
|
+
printGithubNoticeAnnotation({
|
|
3743
|
+
title: "Ripple: Policy Audit Mode",
|
|
3744
|
+
message: "No active intent was found in Ripple Cloud for this commit. To enable strict boundary checks, run `ripple intent push` locally after creating a plan.",
|
|
3713
3745
|
});
|
|
3714
3746
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
console.log(`next_required_phase: ${MISSING_INTENT_NEXT_REQUIRED_PHASE}`);
|
|
3719
|
-
console.log(`next_required_action: ${MISSING_INTENT_NEXT_REQUIRED_ACTION}`);
|
|
3720
|
-
console.log("");
|
|
3721
|
-
console.log("intent_error:");
|
|
3722
|
-
console.log(`- ${message}`);
|
|
3723
|
-
}
|
|
3724
|
-
else {
|
|
3725
|
-
printStagedCheckSummary(summary);
|
|
3726
|
-
console.log("");
|
|
3727
|
-
console.log(`Next required phase: ${MISSING_INTENT_NEXT_REQUIRED_PHASE}`);
|
|
3728
|
-
console.log(`Next required action: ${MISSING_INTENT_NEXT_REQUIRED_ACTION}`);
|
|
3729
|
-
console.log(`Intent error: ${message}`);
|
|
3730
|
-
}
|
|
3731
|
-
if (emitGithubAnnotations && !options.json) {
|
|
3732
|
-
printGithubIntentLoadError(message);
|
|
3733
|
-
}
|
|
3734
|
-
writeGithubStepSummary({ summary, intentLoadError: message });
|
|
3735
|
-
applyStrictExit(options.strict);
|
|
3747
|
+
writeGithubPolicyAuditStepSummary(summary, policySync);
|
|
3748
|
+
// Policy-only audits do not block the build unless strict mode is on.
|
|
3749
|
+
applyStrictExit(options.strict && summary.requiresAttention);
|
|
3736
3750
|
return;
|
|
3737
3751
|
}
|
|
3752
|
+
// An active intent was found. Run the full, strict audit against the cloud boundary.
|
|
3753
|
+
console.log(`[Ripple CI] Active intent '${intent.id}' found in cloud. Running full boundary check.`);
|
|
3738
3754
|
const audit = await buildAuditForFiles({
|
|
3739
3755
|
workspaceRoot,
|
|
3740
3756
|
files,
|
|
@@ -3742,13 +3758,10 @@ async function ciCommand(options) {
|
|
|
3742
3758
|
baseRef,
|
|
3743
3759
|
tokenBudget: options.budget,
|
|
3744
3760
|
intent,
|
|
3745
|
-
currentPolicyExplanation:
|
|
3761
|
+
currentPolicyExplanation: (0, core_1.explainRipplePolicyForIntent)((0, core_1.loadRipplePolicy)(workspaceRoot), intent),
|
|
3746
3762
|
});
|
|
3747
3763
|
if (options.json) {
|
|
3748
|
-
printJson({
|
|
3749
|
-
...audit,
|
|
3750
|
-
gate: (0, core_1.buildRippleGateSummary)(audit),
|
|
3751
|
-
});
|
|
3764
|
+
printJson({ ...audit, gate: (0, core_1.buildRippleGateSummary)(audit) });
|
|
3752
3765
|
}
|
|
3753
3766
|
else if (options.agent) {
|
|
3754
3767
|
printAgentAuditSummary(audit);
|
|
@@ -3760,7 +3773,46 @@ async function ciCommand(options) {
|
|
|
3760
3773
|
printGithubAuditAnnotations(audit);
|
|
3761
3774
|
}
|
|
3762
3775
|
writeGithubAuditStepSummary(audit);
|
|
3763
|
-
|
|
3776
|
+
// Sync the final audit result back to the cloud for the webhook to read.
|
|
3777
|
+
if (process.env.RIPPLE_API_KEY) {
|
|
3778
|
+
const gate = (0, core_1.buildRippleGateSummary)(audit);
|
|
3779
|
+
const cloudDecision = gate.canContinue ? "continue" :
|
|
3780
|
+
gate.needsHuman ? "human-review" : "blocked";
|
|
3781
|
+
const branch = (0, core_1.getCurrentBranch)(child_process_1.execSync);
|
|
3782
|
+
const actor = (0, core_1.getCurrentActor)(child_process_1.execSync);
|
|
3783
|
+
const redactedPayload = {
|
|
3784
|
+
decision: gate.decision,
|
|
3785
|
+
status: gate.status,
|
|
3786
|
+
risk: {
|
|
3787
|
+
level: gate.risk.level,
|
|
3788
|
+
score: gate.risk.score,
|
|
3789
|
+
summary: gate.risk.summary,
|
|
3790
|
+
},
|
|
3791
|
+
auditStatus: audit.status,
|
|
3792
|
+
approvalStatus: audit.approvalStatus.status,
|
|
3793
|
+
intent: audit.intent,
|
|
3794
|
+
mode: audit.mode,
|
|
3795
|
+
baseRef: audit.baseRef,
|
|
3796
|
+
blockingReasons: audit.blockingReasons,
|
|
3797
|
+
};
|
|
3798
|
+
const cloudResult = await (0, core_1.syncAuditToCloud)({
|
|
3799
|
+
intentId: intent.id,
|
|
3800
|
+
decision: cloudDecision,
|
|
3801
|
+
commitSha: commitSha,
|
|
3802
|
+
branch: branch,
|
|
3803
|
+
actor: actor,
|
|
3804
|
+
source: 'ci',
|
|
3805
|
+
payload: redactedPayload,
|
|
3806
|
+
});
|
|
3807
|
+
if (cloudResult.sent) {
|
|
3808
|
+
console.log(`[Ripple Cloud] ✓ Audit synced (${commitSha.slice(0, 7)})`);
|
|
3809
|
+
}
|
|
3810
|
+
else if (cloudResult.error) {
|
|
3811
|
+
console.warn(`[Ripple Cloud] ⚠ Sync failed (non-blocking): ${cloudResult.error}`);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
// Exit with a non-zero code if the audit did not pass, failing the CI job.
|
|
3815
|
+
applyStrictExit(!audit.canProceed);
|
|
3764
3816
|
}
|
|
3765
3817
|
async function doctorCommand(options) {
|
|
3766
3818
|
const workspaceRoot = resolveWorkspaceRoot(".");
|
|
@@ -4030,11 +4082,11 @@ fi
|
|
|
4030
4082
|
|
|
4031
4083
|
set +e
|
|
4032
4084
|
|
|
4033
|
-
ripple_run() {
|
|
4034
|
-
${rippleDirectRunnerHookLines().join("\n")}
|
|
4035
|
-
}
|
|
4036
|
-
|
|
4037
|
-
if [ -f ".ripple/intents/latest.json" ]; then
|
|
4085
|
+
ripple_run() {
|
|
4086
|
+
${rippleDirectRunnerHookLines().join("\n")}
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
if [ -f ".ripple/intents/latest.json" ]; then
|
|
4038
4090
|
echo "[Ripple] Active local intent found. Checking staged changes against approved boundary..."
|
|
4039
4091
|
ripple_run gate --staged --intent latest --agent --strict
|
|
4040
4092
|
status=$?
|
|
@@ -4083,22 +4135,20 @@ function ripplePreCommitHookScript() {
|
|
|
4083
4135
|
"",
|
|
4084
4136
|
].join("\n");
|
|
4085
4137
|
}
|
|
4138
|
+
// ripple-main/packages/cli/src/index.ts
|
|
4139
|
+
// ... (keep all existing functions)
|
|
4086
4140
|
function ripplePostCommitHookBlock() {
|
|
4087
4141
|
return [
|
|
4088
4142
|
RIPPLE_POST_COMMIT_HOOK_START,
|
|
4089
|
-
`#
|
|
4143
|
+
`# After a successful commit, this hook cleans up the consumed intent marker
|
|
4144
|
+
# left by the 'ripple gate' command. This prevents old intents from being
|
|
4145
|
+
# reused accidentally if a commit is amended or rebased.
|
|
4090
4146
|
set +e
|
|
4091
4147
|
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
cleared=1
|
|
4097
|
-
fi
|
|
4098
|
-
done
|
|
4099
|
-
|
|
4100
|
-
if [ "$cleared" -eq 1 ]; then
|
|
4101
|
-
echo "[Ripple] Consumed and cleared local intent."
|
|
4148
|
+
CONSUMED_INTENT_FILE=".ripple/.cache/consumed-intent.json"
|
|
4149
|
+
if [ -f "$CONSUMED_INTENT_FILE" ]; then
|
|
4150
|
+
rm "$CONSUMED_INTENT_FILE"
|
|
4151
|
+
echo "[Ripple] Cleaned up consumed intent marker."
|
|
4102
4152
|
fi`,
|
|
4103
4153
|
RIPPLE_POST_COMMIT_HOOK_END,
|
|
4104
4154
|
"",
|