@ipation/specbridge 2.3.0 → 2.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/CHANGELOG.md +28 -0
- package/README.md +2 -0
- package/dist/cli.js +334 -283
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +120 -64
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command as Command20 } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk19 from "chalk";
|
|
6
6
|
import { readFileSync as readFileSync2 } from "fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8
8
|
import { dirname as dirname5, join as join14 } from "path";
|
|
@@ -1345,12 +1345,11 @@ Detected ${patterns.length} pattern(s):
|
|
|
1345
1345
|
|
|
1346
1346
|
// src/cli/commands/verify.ts
|
|
1347
1347
|
import { Command as Command3 } from "commander";
|
|
1348
|
-
import
|
|
1348
|
+
import chalk4 from "chalk";
|
|
1349
1349
|
import ora3 from "ora";
|
|
1350
1350
|
|
|
1351
1351
|
// src/verification/engine.ts
|
|
1352
1352
|
import { Project as Project2 } from "ts-morph";
|
|
1353
|
-
import chalk3 from "chalk";
|
|
1354
1353
|
|
|
1355
1354
|
// src/registry/loader.ts
|
|
1356
1355
|
import { join as join4 } from "path";
|
|
@@ -2244,7 +2243,8 @@ var DependencyVerifier = class {
|
|
|
2244
2243
|
const sccs = tarjanScc(graph);
|
|
2245
2244
|
const current = projectFilePath;
|
|
2246
2245
|
for (const scc of sccs) {
|
|
2247
|
-
const
|
|
2246
|
+
const first = scc[0];
|
|
2247
|
+
const hasSelfLoop = first !== void 0 && scc.length === 1 && (graph.get(first)?.has(first) ?? false);
|
|
2248
2248
|
const isCycle = scc.length > 1 || hasSelfLoop;
|
|
2249
2249
|
if (!isCycle) continue;
|
|
2250
2250
|
if (!scc.includes(current)) continue;
|
|
@@ -2685,10 +2685,36 @@ import { existsSync } from "fs";
|
|
|
2685
2685
|
import { join as join5 } from "path";
|
|
2686
2686
|
import { pathToFileURL } from "url";
|
|
2687
2687
|
import fg2 from "fast-glob";
|
|
2688
|
+
|
|
2689
|
+
// src/utils/logger.ts
|
|
2690
|
+
import pino from "pino";
|
|
2691
|
+
var defaultOptions = {
|
|
2692
|
+
level: process.env.SPECBRIDGE_LOG_LEVEL || "info",
|
|
2693
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
2694
|
+
base: {
|
|
2695
|
+
service: "specbridge"
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
var destination = pino.destination({
|
|
2699
|
+
fd: 2,
|
|
2700
|
+
// stderr
|
|
2701
|
+
sync: false
|
|
2702
|
+
});
|
|
2703
|
+
var rootLogger = pino(defaultOptions, destination);
|
|
2704
|
+
function getLogger(bindings) {
|
|
2705
|
+
if (!bindings) {
|
|
2706
|
+
return rootLogger;
|
|
2707
|
+
}
|
|
2708
|
+
return rootLogger.child(bindings);
|
|
2709
|
+
}
|
|
2710
|
+
var logger = getLogger();
|
|
2711
|
+
|
|
2712
|
+
// src/verification/plugins/loader.ts
|
|
2688
2713
|
var PluginLoader = class {
|
|
2689
2714
|
plugins = /* @__PURE__ */ new Map();
|
|
2690
2715
|
loaded = false;
|
|
2691
2716
|
loadErrors = [];
|
|
2717
|
+
logger = getLogger({ module: "verification.plugins.loader" });
|
|
2692
2718
|
/**
|
|
2693
2719
|
* Load all plugins from the specified base path
|
|
2694
2720
|
*
|
|
@@ -2711,15 +2737,15 @@ var PluginLoader = class {
|
|
|
2711
2737
|
} catch (error) {
|
|
2712
2738
|
const message = error instanceof Error ? error.message : String(error);
|
|
2713
2739
|
this.loadErrors.push({ file, error: message });
|
|
2714
|
-
|
|
2740
|
+
this.logger.warn({ file, error: message }, "Failed to load plugin");
|
|
2715
2741
|
}
|
|
2716
2742
|
}
|
|
2717
2743
|
this.loaded = true;
|
|
2718
2744
|
if (this.plugins.size > 0) {
|
|
2719
|
-
|
|
2745
|
+
this.logger.info({ count: this.plugins.size }, "Loaded custom verifier plugins");
|
|
2720
2746
|
}
|
|
2721
2747
|
if (this.loadErrors.length > 0) {
|
|
2722
|
-
|
|
2748
|
+
this.logger.warn({ count: this.loadErrors.length }, "Plugin load failures");
|
|
2723
2749
|
}
|
|
2724
2750
|
}
|
|
2725
2751
|
/**
|
|
@@ -2895,8 +2921,9 @@ var builtinVerifiers = {
|
|
|
2895
2921
|
};
|
|
2896
2922
|
var verifierInstances = /* @__PURE__ */ new Map();
|
|
2897
2923
|
function getVerifier(id) {
|
|
2898
|
-
|
|
2899
|
-
|
|
2924
|
+
const pooled = verifierInstances.get(id);
|
|
2925
|
+
if (pooled) {
|
|
2926
|
+
return pooled;
|
|
2900
2927
|
}
|
|
2901
2928
|
const pluginLoader2 = getPluginLoader();
|
|
2902
2929
|
const customVerifier = pluginLoader2.getVerifier(id);
|
|
@@ -3098,6 +3125,7 @@ var VerificationEngine = class {
|
|
|
3098
3125
|
astCache;
|
|
3099
3126
|
resultsCache;
|
|
3100
3127
|
pluginsLoaded = false;
|
|
3128
|
+
logger = getLogger({ module: "verification.engine" });
|
|
3101
3129
|
constructor(registry) {
|
|
3102
3130
|
this.registry = registry || createRegistry();
|
|
3103
3131
|
this.project = new Project2({
|
|
@@ -3271,13 +3299,12 @@ var VerificationEngine = class {
|
|
|
3271
3299
|
);
|
|
3272
3300
|
if (!verifier) {
|
|
3273
3301
|
const requestedVerifier = constraint.check?.verifier || constraint.verifier || "auto-detected";
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
);
|
|
3302
|
+
this.logger.warn({
|
|
3303
|
+
decisionId: decision.metadata.id,
|
|
3304
|
+
constraintId: constraint.id,
|
|
3305
|
+
requestedVerifier,
|
|
3306
|
+
availableVerifiers: getVerifierIds()
|
|
3307
|
+
}, "No verifier found for constraint");
|
|
3281
3308
|
warnings.push({
|
|
3282
3309
|
type: "missing_verifier",
|
|
3283
3310
|
message: `No verifier found for constraint (requested: ${requestedVerifier})`,
|
|
@@ -3383,17 +3410,14 @@ var VerificationEngine = class {
|
|
|
3383
3410
|
} catch (error) {
|
|
3384
3411
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3385
3412
|
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
);
|
|
3394
|
-
if (errorStack) {
|
|
3395
|
-
console.error(chalk3.dim(errorStack));
|
|
3396
|
-
}
|
|
3413
|
+
this.logger.error({
|
|
3414
|
+
verifierId: verifier.id,
|
|
3415
|
+
filePath,
|
|
3416
|
+
decisionId: decision.metadata.id,
|
|
3417
|
+
constraintId: constraint.id,
|
|
3418
|
+
error: errorMessage,
|
|
3419
|
+
stack: errorStack
|
|
3420
|
+
}, "Verifier execution failed");
|
|
3397
3421
|
errors.push({
|
|
3398
3422
|
type: "verifier_exception",
|
|
3399
3423
|
message: `Verifier '${verifier.id}' failed: ${errorMessage}`,
|
|
@@ -3509,6 +3533,10 @@ var AutofixEngine = class {
|
|
|
3509
3533
|
const edits = [];
|
|
3510
3534
|
for (const violation of fileViolations) {
|
|
3511
3535
|
const fix = violation.autofix;
|
|
3536
|
+
if (!fix) {
|
|
3537
|
+
skippedViolations++;
|
|
3538
|
+
continue;
|
|
3539
|
+
}
|
|
3512
3540
|
if (options.interactive) {
|
|
3513
3541
|
const ok = await confirmFix(`Apply fix: ${fix.description} (${filePath}:${violation.line ?? 1})?`);
|
|
3514
3542
|
if (!ok) {
|
|
@@ -3555,7 +3583,7 @@ async function getChangedFiles(cwd) {
|
|
|
3555
3583
|
}
|
|
3556
3584
|
|
|
3557
3585
|
// src/verification/explain.ts
|
|
3558
|
-
import
|
|
3586
|
+
import chalk3 from "chalk";
|
|
3559
3587
|
var ExplainReporter = class {
|
|
3560
3588
|
entries = [];
|
|
3561
3589
|
/**
|
|
@@ -3569,10 +3597,10 @@ var ExplainReporter = class {
|
|
|
3569
3597
|
*/
|
|
3570
3598
|
print() {
|
|
3571
3599
|
if (this.entries.length === 0) {
|
|
3572
|
-
console.log(
|
|
3600
|
+
console.log(chalk3.dim("No constraints were evaluated."));
|
|
3573
3601
|
return;
|
|
3574
3602
|
}
|
|
3575
|
-
console.log(
|
|
3603
|
+
console.log(chalk3.bold("\n=== Verification Explanation ===\n"));
|
|
3576
3604
|
const byFile = /* @__PURE__ */ new Map();
|
|
3577
3605
|
for (const entry of this.entries) {
|
|
3578
3606
|
const existing = byFile.get(entry.file) || [];
|
|
@@ -3580,22 +3608,22 @@ var ExplainReporter = class {
|
|
|
3580
3608
|
byFile.set(entry.file, existing);
|
|
3581
3609
|
}
|
|
3582
3610
|
for (const [file, entries] of byFile) {
|
|
3583
|
-
console.log(
|
|
3611
|
+
console.log(chalk3.underline(file));
|
|
3584
3612
|
for (const entry of entries) {
|
|
3585
|
-
const icon = entry.applied ?
|
|
3613
|
+
const icon = entry.applied ? chalk3.green("\u2713") : chalk3.dim("\u2298");
|
|
3586
3614
|
const constraintId = `${entry.decision.metadata.id}/${entry.constraint.id}`;
|
|
3587
3615
|
console.log(` ${icon} ${constraintId}`);
|
|
3588
|
-
console.log(
|
|
3616
|
+
console.log(chalk3.dim(` ${entry.reason}`));
|
|
3589
3617
|
if (entry.applied && entry.selectedVerifier) {
|
|
3590
|
-
console.log(
|
|
3618
|
+
console.log(chalk3.dim(` Verifier: ${entry.selectedVerifier}`));
|
|
3591
3619
|
if (entry.verifierOutput) {
|
|
3592
3620
|
if (entry.verifierOutput.error) {
|
|
3593
|
-
console.log(
|
|
3621
|
+
console.log(chalk3.red(` Error: ${entry.verifierOutput.error}`));
|
|
3594
3622
|
} else {
|
|
3595
3623
|
const violationText = entry.verifierOutput.violations === 1 ? "1 violation" : `${entry.verifierOutput.violations} violations`;
|
|
3596
|
-
const resultColor = entry.verifierOutput.violations > 0 ?
|
|
3624
|
+
const resultColor = entry.verifierOutput.violations > 0 ? chalk3.red : chalk3.green;
|
|
3597
3625
|
console.log(
|
|
3598
|
-
|
|
3626
|
+
chalk3.dim(` Result: `) + resultColor(violationText) + chalk3.dim(` in ${entry.verifierOutput.duration}ms`)
|
|
3599
3627
|
);
|
|
3600
3628
|
}
|
|
3601
3629
|
}
|
|
@@ -3605,9 +3633,9 @@ var ExplainReporter = class {
|
|
|
3605
3633
|
}
|
|
3606
3634
|
const applied = this.entries.filter((e) => e.applied).length;
|
|
3607
3635
|
const skipped = this.entries.filter((e) => !e.applied).length;
|
|
3608
|
-
console.log(
|
|
3609
|
-
console.log(` Constraints Applied: ${
|
|
3610
|
-
console.log(` Constraints Skipped: ${
|
|
3636
|
+
console.log(chalk3.bold("Summary:"));
|
|
3637
|
+
console.log(` Constraints Applied: ${chalk3.green(applied)}`);
|
|
3638
|
+
console.log(` Constraints Skipped: ${chalk3.dim(skipped)}`);
|
|
3611
3639
|
}
|
|
3612
3640
|
/**
|
|
3613
3641
|
* Get all entries
|
|
@@ -3657,7 +3685,7 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3657
3685
|
if (fixableCount === 0) {
|
|
3658
3686
|
spinner.stop();
|
|
3659
3687
|
if (!options.json) {
|
|
3660
|
-
console.log(
|
|
3688
|
+
console.log(chalk4.yellow("No auto-fixable violations found"));
|
|
3661
3689
|
}
|
|
3662
3690
|
} else {
|
|
3663
3691
|
spinner.text = `Applying ${fixableCount} auto-fix(es)...`;
|
|
@@ -3682,34 +3710,34 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3682
3710
|
console.log(JSON.stringify({ ...result, autofix: fixResult }, null, 2));
|
|
3683
3711
|
} else {
|
|
3684
3712
|
if (result.warnings && result.warnings.length > 0) {
|
|
3685
|
-
console.log(
|
|
3713
|
+
console.log(chalk4.yellow.bold("\nWarnings:"));
|
|
3686
3714
|
for (const warning of result.warnings) {
|
|
3687
|
-
console.log(
|
|
3688
|
-
console.log(
|
|
3715
|
+
console.log(chalk4.yellow(` \u26A0 ${warning.message}`));
|
|
3716
|
+
console.log(chalk4.dim(` ${warning.decisionId}/${warning.constraintId}`));
|
|
3689
3717
|
if (warning.file) {
|
|
3690
|
-
console.log(
|
|
3718
|
+
console.log(chalk4.dim(` File: ${warning.file}`));
|
|
3691
3719
|
}
|
|
3692
3720
|
}
|
|
3693
3721
|
console.log("");
|
|
3694
3722
|
}
|
|
3695
3723
|
if (result.errors && result.errors.length > 0) {
|
|
3696
|
-
console.log(
|
|
3724
|
+
console.log(chalk4.red.bold("\nErrors:"));
|
|
3697
3725
|
for (const error of result.errors) {
|
|
3698
|
-
console.log(
|
|
3726
|
+
console.log(chalk4.red(` \u2717 ${error.message}`));
|
|
3699
3727
|
if (error.decisionId && error.constraintId) {
|
|
3700
|
-
console.log(
|
|
3728
|
+
console.log(chalk4.dim(` ${error.decisionId}/${error.constraintId}`));
|
|
3701
3729
|
}
|
|
3702
3730
|
if (error.file) {
|
|
3703
|
-
console.log(
|
|
3731
|
+
console.log(chalk4.dim(` File: ${error.file}`));
|
|
3704
3732
|
}
|
|
3705
3733
|
}
|
|
3706
3734
|
console.log("");
|
|
3707
3735
|
}
|
|
3708
3736
|
printResult(result, level);
|
|
3709
3737
|
if (options.fix && fixResult) {
|
|
3710
|
-
console.log(
|
|
3738
|
+
console.log(chalk4.green(`\u2713 Applied ${fixResult.applied.length} fix(es)`));
|
|
3711
3739
|
if (fixResult.skipped > 0) {
|
|
3712
|
-
console.log(
|
|
3740
|
+
console.log(chalk4.yellow(`\u2298 Skipped ${fixResult.skipped} fix(es)`));
|
|
3713
3741
|
}
|
|
3714
3742
|
console.log("");
|
|
3715
3743
|
}
|
|
@@ -3728,8 +3756,8 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3728
3756
|
function printResult(result, level) {
|
|
3729
3757
|
console.log("");
|
|
3730
3758
|
if (result.violations.length === 0) {
|
|
3731
|
-
console.log(
|
|
3732
|
-
console.log(
|
|
3759
|
+
console.log(chalk4.green("\u2713 All checks passed!"));
|
|
3760
|
+
console.log(chalk4.dim(` ${result.checked} files checked in ${result.duration}ms`));
|
|
3733
3761
|
return;
|
|
3734
3762
|
}
|
|
3735
3763
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -3739,7 +3767,7 @@ function printResult(result, level) {
|
|
|
3739
3767
|
byFile.set(violation.file, existing);
|
|
3740
3768
|
}
|
|
3741
3769
|
for (const [file, violations] of byFile) {
|
|
3742
|
-
console.log(
|
|
3770
|
+
console.log(chalk4.underline(file));
|
|
3743
3771
|
for (const v of violations) {
|
|
3744
3772
|
const typeIcon = getTypeIcon(v.type);
|
|
3745
3773
|
const severityColor = getSeverityColor(v.severity);
|
|
@@ -3747,9 +3775,9 @@ function printResult(result, level) {
|
|
|
3747
3775
|
console.log(
|
|
3748
3776
|
` ${typeIcon} ${severityColor(`[${v.severity}]`)} ${v.message}`
|
|
3749
3777
|
);
|
|
3750
|
-
console.log(
|
|
3778
|
+
console.log(chalk4.dim(` ${v.decisionId}/${v.constraintId}${location}`));
|
|
3751
3779
|
if (v.suggestion) {
|
|
3752
|
-
console.log(
|
|
3780
|
+
console.log(chalk4.cyan(` Suggestion: ${v.suggestion}`));
|
|
3753
3781
|
}
|
|
3754
3782
|
}
|
|
3755
3783
|
console.log("");
|
|
@@ -3758,29 +3786,29 @@ function printResult(result, level) {
|
|
|
3758
3786
|
const highCount = result.violations.filter((v) => v.severity === "high").length;
|
|
3759
3787
|
const mediumCount = result.violations.filter((v) => v.severity === "medium").length;
|
|
3760
3788
|
const lowCount = result.violations.filter((v) => v.severity === "low").length;
|
|
3761
|
-
console.log(
|
|
3789
|
+
console.log(chalk4.bold("Summary:"));
|
|
3762
3790
|
console.log(` Files: ${result.checked} checked, ${result.passed} passed, ${result.failed} failed`);
|
|
3763
3791
|
const violationParts = [];
|
|
3764
|
-
if (criticalCount > 0) violationParts.push(
|
|
3765
|
-
if (highCount > 0) violationParts.push(
|
|
3766
|
-
if (mediumCount > 0) violationParts.push(
|
|
3767
|
-
if (lowCount > 0) violationParts.push(
|
|
3792
|
+
if (criticalCount > 0) violationParts.push(chalk4.red(`${criticalCount} critical`));
|
|
3793
|
+
if (highCount > 0) violationParts.push(chalk4.yellow(`${highCount} high`));
|
|
3794
|
+
if (mediumCount > 0) violationParts.push(chalk4.cyan(`${mediumCount} medium`));
|
|
3795
|
+
if (lowCount > 0) violationParts.push(chalk4.dim(`${lowCount} low`));
|
|
3768
3796
|
console.log(` Violations: ${violationParts.join(", ")}`);
|
|
3769
3797
|
console.log(` Duration: ${result.duration}ms`);
|
|
3770
3798
|
if (!result.success) {
|
|
3771
3799
|
console.log("");
|
|
3772
3800
|
const blockingTypes = level === "commit" ? "invariant or critical" : level === "pr" ? "invariant, critical, or high" : "invariant";
|
|
3773
|
-
console.log(
|
|
3801
|
+
console.log(chalk4.red(`\u2717 Verification failed. ${blockingTypes} violations must be resolved.`));
|
|
3774
3802
|
}
|
|
3775
3803
|
}
|
|
3776
3804
|
function getTypeIcon(type) {
|
|
3777
3805
|
switch (type) {
|
|
3778
3806
|
case "invariant":
|
|
3779
|
-
return
|
|
3807
|
+
return chalk4.red("\u25CF");
|
|
3780
3808
|
case "convention":
|
|
3781
|
-
return
|
|
3809
|
+
return chalk4.yellow("\u25CF");
|
|
3782
3810
|
case "guideline":
|
|
3783
|
-
return
|
|
3811
|
+
return chalk4.green("\u25CF");
|
|
3784
3812
|
default:
|
|
3785
3813
|
return "\u25CB";
|
|
3786
3814
|
}
|
|
@@ -3788,15 +3816,15 @@ function getTypeIcon(type) {
|
|
|
3788
3816
|
function getSeverityColor(severity) {
|
|
3789
3817
|
switch (severity) {
|
|
3790
3818
|
case "critical":
|
|
3791
|
-
return
|
|
3819
|
+
return chalk4.red;
|
|
3792
3820
|
case "high":
|
|
3793
|
-
return
|
|
3821
|
+
return chalk4.yellow;
|
|
3794
3822
|
case "medium":
|
|
3795
|
-
return
|
|
3823
|
+
return chalk4.cyan;
|
|
3796
3824
|
case "low":
|
|
3797
|
-
return
|
|
3825
|
+
return chalk4.dim;
|
|
3798
3826
|
default:
|
|
3799
|
-
return
|
|
3827
|
+
return chalk4.white;
|
|
3800
3828
|
}
|
|
3801
3829
|
}
|
|
3802
3830
|
|
|
@@ -3805,15 +3833,15 @@ import { Command as Command8 } from "commander";
|
|
|
3805
3833
|
|
|
3806
3834
|
// src/cli/commands/decision/list.ts
|
|
3807
3835
|
import { Command as Command4 } from "commander";
|
|
3808
|
-
import
|
|
3836
|
+
import chalk5 from "chalk";
|
|
3809
3837
|
import { table } from "table";
|
|
3810
3838
|
var listDecisions = new Command4("list").description("List all architectural decisions").option("-s, --status <status>", "Filter by status (draft, active, deprecated, superseded)").option("-t, --tag <tag>", "Filter by tag").option("--json", "Output as JSON").action(async (options) => {
|
|
3811
3839
|
const registry = createRegistry();
|
|
3812
3840
|
const result = await registry.load();
|
|
3813
3841
|
if (result.errors.length > 0) {
|
|
3814
|
-
console.warn(
|
|
3842
|
+
console.warn(chalk5.yellow("\nWarnings:"));
|
|
3815
3843
|
for (const err of result.errors) {
|
|
3816
|
-
console.warn(
|
|
3844
|
+
console.warn(chalk5.yellow(` - ${err.filePath}: ${err.error}`));
|
|
3817
3845
|
}
|
|
3818
3846
|
console.log("");
|
|
3819
3847
|
}
|
|
@@ -3826,7 +3854,7 @@ var listDecisions = new Command4("list").description("List all architectural dec
|
|
|
3826
3854
|
}
|
|
3827
3855
|
const decisions = registry.getAll(filter);
|
|
3828
3856
|
if (decisions.length === 0) {
|
|
3829
|
-
console.log(
|
|
3857
|
+
console.log(chalk5.yellow("No decisions found."));
|
|
3830
3858
|
return;
|
|
3831
3859
|
}
|
|
3832
3860
|
if (options.json) {
|
|
@@ -3835,11 +3863,11 @@ var listDecisions = new Command4("list").description("List all architectural dec
|
|
|
3835
3863
|
}
|
|
3836
3864
|
const data = [
|
|
3837
3865
|
[
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3866
|
+
chalk5.bold("ID"),
|
|
3867
|
+
chalk5.bold("Title"),
|
|
3868
|
+
chalk5.bold("Status"),
|
|
3869
|
+
chalk5.bold("Constraints"),
|
|
3870
|
+
chalk5.bold("Tags")
|
|
3843
3871
|
]
|
|
3844
3872
|
];
|
|
3845
3873
|
for (const decision of decisions) {
|
|
@@ -3873,20 +3901,20 @@ var listDecisions = new Command4("list").description("List all architectural dec
|
|
|
3873
3901
|
},
|
|
3874
3902
|
drawHorizontalLine: (index) => index === 1
|
|
3875
3903
|
}));
|
|
3876
|
-
console.log(
|
|
3904
|
+
console.log(chalk5.dim(`Total: ${decisions.length} decision(s)`));
|
|
3877
3905
|
});
|
|
3878
3906
|
function getStatusColor(status) {
|
|
3879
3907
|
switch (status) {
|
|
3880
3908
|
case "active":
|
|
3881
|
-
return
|
|
3909
|
+
return chalk5.green;
|
|
3882
3910
|
case "draft":
|
|
3883
|
-
return
|
|
3911
|
+
return chalk5.yellow;
|
|
3884
3912
|
case "deprecated":
|
|
3885
|
-
return
|
|
3913
|
+
return chalk5.gray;
|
|
3886
3914
|
case "superseded":
|
|
3887
|
-
return
|
|
3915
|
+
return chalk5.blue;
|
|
3888
3916
|
default:
|
|
3889
|
-
return
|
|
3917
|
+
return chalk5.white;
|
|
3890
3918
|
}
|
|
3891
3919
|
}
|
|
3892
3920
|
function getConstraintTypeSummary(types) {
|
|
@@ -3899,9 +3927,9 @@ function getConstraintTypeSummary(types) {
|
|
|
3899
3927
|
counts[type]++;
|
|
3900
3928
|
}
|
|
3901
3929
|
const parts = [];
|
|
3902
|
-
if (counts.invariant > 0) parts.push(
|
|
3903
|
-
if (counts.convention > 0) parts.push(
|
|
3904
|
-
if (counts.guideline > 0) parts.push(
|
|
3930
|
+
if (counts.invariant > 0) parts.push(chalk5.red(`${counts.invariant}I`));
|
|
3931
|
+
if (counts.convention > 0) parts.push(chalk5.yellow(`${counts.convention}C`));
|
|
3932
|
+
if (counts.guideline > 0) parts.push(chalk5.green(`${counts.guideline}G`));
|
|
3905
3933
|
return parts.join(" ") || "-";
|
|
3906
3934
|
}
|
|
3907
3935
|
function truncate(str, length) {
|
|
@@ -3911,7 +3939,7 @@ function truncate(str, length) {
|
|
|
3911
3939
|
|
|
3912
3940
|
// src/cli/commands/decision/show.ts
|
|
3913
3941
|
import { Command as Command5 } from "commander";
|
|
3914
|
-
import
|
|
3942
|
+
import chalk6 from "chalk";
|
|
3915
3943
|
var showDecision = new Command5("show").description("Show details of a specific decision").argument("<id>", "Decision ID").option("--json", "Output as JSON").action(async (id, options) => {
|
|
3916
3944
|
const registry = createRegistry();
|
|
3917
3945
|
await registry.load();
|
|
@@ -3924,74 +3952,74 @@ var showDecision = new Command5("show").description("Show details of a specific
|
|
|
3924
3952
|
});
|
|
3925
3953
|
function printDecision(decision) {
|
|
3926
3954
|
const { metadata, decision: content, constraints } = decision;
|
|
3927
|
-
console.log(
|
|
3955
|
+
console.log(chalk6.bold.blue(`
|
|
3928
3956
|
${metadata.title}`));
|
|
3929
|
-
console.log(
|
|
3957
|
+
console.log(chalk6.dim(`ID: ${metadata.id}`));
|
|
3930
3958
|
console.log("");
|
|
3931
|
-
console.log(
|
|
3932
|
-
console.log(
|
|
3959
|
+
console.log(chalk6.bold("Status:"), getStatusBadge(metadata.status));
|
|
3960
|
+
console.log(chalk6.bold("Owners:"), metadata.owners.join(", "));
|
|
3933
3961
|
if (metadata.tags && metadata.tags.length > 0) {
|
|
3934
|
-
console.log(
|
|
3962
|
+
console.log(chalk6.bold("Tags:"), metadata.tags.map((t) => chalk6.cyan(t)).join(", "));
|
|
3935
3963
|
}
|
|
3936
3964
|
if (metadata.createdAt) {
|
|
3937
|
-
console.log(
|
|
3965
|
+
console.log(chalk6.bold("Created:"), metadata.createdAt);
|
|
3938
3966
|
}
|
|
3939
3967
|
if (metadata.supersededBy) {
|
|
3940
|
-
console.log(
|
|
3968
|
+
console.log(chalk6.bold("Superseded by:"), chalk6.yellow(metadata.supersededBy));
|
|
3941
3969
|
}
|
|
3942
3970
|
console.log("");
|
|
3943
|
-
console.log(
|
|
3971
|
+
console.log(chalk6.bold.underline("Summary"));
|
|
3944
3972
|
console.log(content.summary);
|
|
3945
3973
|
console.log("");
|
|
3946
|
-
console.log(
|
|
3974
|
+
console.log(chalk6.bold.underline("Rationale"));
|
|
3947
3975
|
console.log(content.rationale);
|
|
3948
3976
|
console.log("");
|
|
3949
3977
|
if (content.context) {
|
|
3950
|
-
console.log(
|
|
3978
|
+
console.log(chalk6.bold.underline("Context"));
|
|
3951
3979
|
console.log(content.context);
|
|
3952
3980
|
console.log("");
|
|
3953
3981
|
}
|
|
3954
3982
|
if (content.consequences && content.consequences.length > 0) {
|
|
3955
|
-
console.log(
|
|
3983
|
+
console.log(chalk6.bold.underline("Consequences"));
|
|
3956
3984
|
for (const consequence of content.consequences) {
|
|
3957
3985
|
console.log(` \u2022 ${consequence}`);
|
|
3958
3986
|
}
|
|
3959
3987
|
console.log("");
|
|
3960
3988
|
}
|
|
3961
|
-
console.log(
|
|
3989
|
+
console.log(chalk6.bold.underline(`Constraints (${constraints.length})`));
|
|
3962
3990
|
for (const constraint of constraints) {
|
|
3963
3991
|
const typeIcon = getTypeIcon2(constraint.type);
|
|
3964
3992
|
const severityBadge = getSeverityBadge(constraint.severity);
|
|
3965
3993
|
console.log(`
|
|
3966
|
-
${typeIcon} ${
|
|
3994
|
+
${typeIcon} ${chalk6.bold(constraint.id)} ${severityBadge}`);
|
|
3967
3995
|
console.log(` ${constraint.rule}`);
|
|
3968
|
-
console.log(
|
|
3996
|
+
console.log(chalk6.dim(` Scope: ${constraint.scope}`));
|
|
3969
3997
|
if (constraint.verifier) {
|
|
3970
|
-
console.log(
|
|
3998
|
+
console.log(chalk6.dim(` Verifier: ${constraint.verifier}`));
|
|
3971
3999
|
}
|
|
3972
4000
|
if (constraint.exceptions && constraint.exceptions.length > 0) {
|
|
3973
|
-
console.log(
|
|
4001
|
+
console.log(chalk6.dim(` Exceptions: ${constraint.exceptions.length}`));
|
|
3974
4002
|
}
|
|
3975
4003
|
}
|
|
3976
4004
|
console.log("");
|
|
3977
4005
|
if (decision.verification?.automated && decision.verification.automated.length > 0) {
|
|
3978
|
-
console.log(
|
|
4006
|
+
console.log(chalk6.bold.underline("Automated Verification"));
|
|
3979
4007
|
for (const check of decision.verification.automated) {
|
|
3980
4008
|
console.log(` \u2022 ${check.check} (${check.frequency})`);
|
|
3981
|
-
console.log(
|
|
4009
|
+
console.log(chalk6.dim(` Target: ${check.target}`));
|
|
3982
4010
|
}
|
|
3983
4011
|
console.log("");
|
|
3984
4012
|
}
|
|
3985
4013
|
if (decision.links) {
|
|
3986
4014
|
const { related, supersedes, references } = decision.links;
|
|
3987
4015
|
if (related && related.length > 0) {
|
|
3988
|
-
console.log(
|
|
4016
|
+
console.log(chalk6.bold("Related:"), related.join(", "));
|
|
3989
4017
|
}
|
|
3990
4018
|
if (supersedes && supersedes.length > 0) {
|
|
3991
|
-
console.log(
|
|
4019
|
+
console.log(chalk6.bold("Supersedes:"), supersedes.join(", "));
|
|
3992
4020
|
}
|
|
3993
4021
|
if (references && references.length > 0) {
|
|
3994
|
-
console.log(
|
|
4022
|
+
console.log(chalk6.bold("References:"));
|
|
3995
4023
|
for (const ref of references) {
|
|
3996
4024
|
console.log(` \u2022 ${ref}`);
|
|
3997
4025
|
}
|
|
@@ -4001,13 +4029,13 @@ ${metadata.title}`));
|
|
|
4001
4029
|
function getStatusBadge(status) {
|
|
4002
4030
|
switch (status) {
|
|
4003
4031
|
case "active":
|
|
4004
|
-
return
|
|
4032
|
+
return chalk6.bgGreen.black(" ACTIVE ");
|
|
4005
4033
|
case "draft":
|
|
4006
|
-
return
|
|
4034
|
+
return chalk6.bgYellow.black(" DRAFT ");
|
|
4007
4035
|
case "deprecated":
|
|
4008
|
-
return
|
|
4036
|
+
return chalk6.bgGray.white(" DEPRECATED ");
|
|
4009
4037
|
case "superseded":
|
|
4010
|
-
return
|
|
4038
|
+
return chalk6.bgBlue.white(" SUPERSEDED ");
|
|
4011
4039
|
default:
|
|
4012
4040
|
return status;
|
|
4013
4041
|
}
|
|
@@ -4015,11 +4043,11 @@ function getStatusBadge(status) {
|
|
|
4015
4043
|
function getTypeIcon2(type) {
|
|
4016
4044
|
switch (type) {
|
|
4017
4045
|
case "invariant":
|
|
4018
|
-
return
|
|
4046
|
+
return chalk6.red("\u25CF");
|
|
4019
4047
|
case "convention":
|
|
4020
|
-
return
|
|
4048
|
+
return chalk6.yellow("\u25CF");
|
|
4021
4049
|
case "guideline":
|
|
4022
|
-
return
|
|
4050
|
+
return chalk6.green("\u25CF");
|
|
4023
4051
|
default:
|
|
4024
4052
|
return "\u25CB";
|
|
4025
4053
|
}
|
|
@@ -4027,13 +4055,13 @@ function getTypeIcon2(type) {
|
|
|
4027
4055
|
function getSeverityBadge(severity) {
|
|
4028
4056
|
switch (severity) {
|
|
4029
4057
|
case "critical":
|
|
4030
|
-
return
|
|
4058
|
+
return chalk6.bgRed.white(" CRITICAL ");
|
|
4031
4059
|
case "high":
|
|
4032
|
-
return
|
|
4060
|
+
return chalk6.bgYellow.black(" HIGH ");
|
|
4033
4061
|
case "medium":
|
|
4034
|
-
return
|
|
4062
|
+
return chalk6.bgCyan.black(" MEDIUM ");
|
|
4035
4063
|
case "low":
|
|
4036
|
-
return
|
|
4064
|
+
return chalk6.bgGray.white(" LOW ");
|
|
4037
4065
|
default:
|
|
4038
4066
|
return severity;
|
|
4039
4067
|
}
|
|
@@ -4041,7 +4069,7 @@ function getSeverityBadge(severity) {
|
|
|
4041
4069
|
|
|
4042
4070
|
// src/cli/commands/decision/validate.ts
|
|
4043
4071
|
import { Command as Command6 } from "commander";
|
|
4044
|
-
import
|
|
4072
|
+
import chalk7 from "chalk";
|
|
4045
4073
|
import ora4 from "ora";
|
|
4046
4074
|
import { join as join6 } from "path";
|
|
4047
4075
|
var validateDecisions = new Command6("validate").description("Validate decision files").option("-f, --file <path>", "Validate a specific file").action(async (options) => {
|
|
@@ -4080,14 +4108,14 @@ var validateDecisions = new Command6("validate").description("Validate decision
|
|
|
4080
4108
|
}
|
|
4081
4109
|
spinner.stop();
|
|
4082
4110
|
if (invalid === 0) {
|
|
4083
|
-
console.log(
|
|
4111
|
+
console.log(chalk7.green(`\u2713 All ${valid} decision file(s) are valid.`));
|
|
4084
4112
|
} else {
|
|
4085
|
-
console.log(
|
|
4113
|
+
console.log(chalk7.red(`\u2717 ${invalid} of ${files.length} decision file(s) have errors.
|
|
4086
4114
|
`));
|
|
4087
4115
|
for (const { file, errors: fileErrors } of errors) {
|
|
4088
|
-
console.log(
|
|
4116
|
+
console.log(chalk7.red(`File: ${file}`));
|
|
4089
4117
|
for (const err of fileErrors) {
|
|
4090
|
-
console.log(
|
|
4118
|
+
console.log(chalk7.dim(` - ${err}`));
|
|
4091
4119
|
}
|
|
4092
4120
|
console.log("");
|
|
4093
4121
|
}
|
|
@@ -4101,7 +4129,7 @@ var validateDecisions = new Command6("validate").description("Validate decision
|
|
|
4101
4129
|
|
|
4102
4130
|
// src/cli/commands/decision/create.ts
|
|
4103
4131
|
import { Command as Command7 } from "commander";
|
|
4104
|
-
import
|
|
4132
|
+
import chalk8 from "chalk";
|
|
4105
4133
|
import { join as join7 } from "path";
|
|
4106
4134
|
var createDecision = new Command7("create").description("Create a new decision file").argument("<id>", "Decision ID (e.g., auth-001)").requiredOption("-t, --title <title>", "Decision title").requiredOption("-s, --summary <summary>", "One-sentence summary").option("--type <type>", "Default constraint type (invariant, convention, guideline)", "convention").option("--severity <severity>", "Default constraint severity (critical, high, medium, low)", "medium").option("--scope <scope>", "Default constraint scope (glob pattern)", "src/**/*.ts").option("-o, --owner <owner>", "Owner name", "team").action(async (id, options) => {
|
|
4107
4135
|
const cwd = process.cwd();
|
|
@@ -4109,13 +4137,13 @@ var createDecision = new Command7("create").description("Create a new decision f
|
|
|
4109
4137
|
throw new NotInitializedError();
|
|
4110
4138
|
}
|
|
4111
4139
|
if (!/^[a-z0-9-]+$/.test(id)) {
|
|
4112
|
-
console.error(
|
|
4140
|
+
console.error(chalk8.red("Error: Decision ID must be lowercase alphanumeric with hyphens only."));
|
|
4113
4141
|
process.exit(1);
|
|
4114
4142
|
}
|
|
4115
4143
|
const decisionsDir = getDecisionsDir(cwd);
|
|
4116
4144
|
const filePath = join7(decisionsDir, `${id}.decision.yaml`);
|
|
4117
4145
|
if (await pathExists(filePath)) {
|
|
4118
|
-
console.error(
|
|
4146
|
+
console.error(chalk8.red(`Error: Decision file already exists: ${filePath}`));
|
|
4119
4147
|
process.exit(1);
|
|
4120
4148
|
}
|
|
4121
4149
|
const decision = {
|
|
@@ -4151,13 +4179,13 @@ var createDecision = new Command7("create").description("Create a new decision f
|
|
|
4151
4179
|
}
|
|
4152
4180
|
};
|
|
4153
4181
|
await writeTextFile(filePath, stringifyYaml(decision));
|
|
4154
|
-
console.log(
|
|
4182
|
+
console.log(chalk8.green(`\u2713 Created decision: ${filePath}`));
|
|
4155
4183
|
console.log("");
|
|
4156
|
-
console.log(
|
|
4184
|
+
console.log(chalk8.cyan("Next steps:"));
|
|
4157
4185
|
console.log(` 1. Edit the file to add rationale, context, and consequences`);
|
|
4158
4186
|
console.log(` 2. Define constraints with appropriate scopes`);
|
|
4159
|
-
console.log(` 3. Run ${
|
|
4160
|
-
console.log(` 4. Change status from ${
|
|
4187
|
+
console.log(` 3. Run ${chalk8.bold("specbridge decision validate")} to check syntax`);
|
|
4188
|
+
console.log(` 4. Change status from ${chalk8.yellow("draft")} to ${chalk8.green("active")} when ready`);
|
|
4161
4189
|
});
|
|
4162
4190
|
|
|
4163
4191
|
// src/cli/commands/decision/index.ts
|
|
@@ -4165,7 +4193,7 @@ var decisionCommand = new Command8("decision").description("Manage architectural
|
|
|
4165
4193
|
|
|
4166
4194
|
// src/cli/commands/hook.ts
|
|
4167
4195
|
import { Command as Command9 } from "commander";
|
|
4168
|
-
import
|
|
4196
|
+
import chalk9 from "chalk";
|
|
4169
4197
|
import ora5 from "ora";
|
|
4170
4198
|
import { join as join8 } from "path";
|
|
4171
4199
|
var HOOK_SCRIPT = `#!/bin/sh
|
|
@@ -4197,9 +4225,9 @@ function createHookCommand() {
|
|
|
4197
4225
|
} else if (options.lefthook) {
|
|
4198
4226
|
spinner.succeed("Lefthook detected");
|
|
4199
4227
|
console.log("");
|
|
4200
|
-
console.log(
|
|
4228
|
+
console.log(chalk9.cyan("Add this to your lefthook.yml:"));
|
|
4201
4229
|
console.log("");
|
|
4202
|
-
console.log(
|
|
4230
|
+
console.log(chalk9.dim(`pre-commit:
|
|
4203
4231
|
commands:
|
|
4204
4232
|
specbridge:
|
|
4205
4233
|
glob: "*.{ts,tsx}"
|
|
@@ -4214,9 +4242,9 @@ function createHookCommand() {
|
|
|
4214
4242
|
} else if (await pathExists(join8(cwd, "lefthook.yml"))) {
|
|
4215
4243
|
spinner.succeed("Lefthook detected");
|
|
4216
4244
|
console.log("");
|
|
4217
|
-
console.log(
|
|
4245
|
+
console.log(chalk9.cyan("Add this to your lefthook.yml:"));
|
|
4218
4246
|
console.log("");
|
|
4219
|
-
console.log(
|
|
4247
|
+
console.log(chalk9.dim(`pre-commit:
|
|
4220
4248
|
commands:
|
|
4221
4249
|
specbridge:
|
|
4222
4250
|
glob: "*.{ts,tsx}"
|
|
@@ -4231,7 +4259,7 @@ function createHookCommand() {
|
|
|
4231
4259
|
}
|
|
4232
4260
|
if (await pathExists(hookPath) && !options.force) {
|
|
4233
4261
|
spinner.fail("Hook already exists");
|
|
4234
|
-
console.log(
|
|
4262
|
+
console.log(chalk9.yellow(`Use --force to overwrite: ${hookPath}`));
|
|
4235
4263
|
return;
|
|
4236
4264
|
}
|
|
4237
4265
|
await writeTextFile(hookPath, hookContent);
|
|
@@ -4241,9 +4269,9 @@ function createHookCommand() {
|
|
|
4241
4269
|
} catch {
|
|
4242
4270
|
}
|
|
4243
4271
|
spinner.succeed("Pre-commit hook installed");
|
|
4244
|
-
console.log(
|
|
4272
|
+
console.log(chalk9.dim(` Path: ${hookPath}`));
|
|
4245
4273
|
console.log("");
|
|
4246
|
-
console.log(
|
|
4274
|
+
console.log(chalk9.cyan("The hook will run on each commit and verify staged files."));
|
|
4247
4275
|
} catch (error) {
|
|
4248
4276
|
spinner.fail("Failed to install hook");
|
|
4249
4277
|
throw error;
|
|
@@ -4279,21 +4307,21 @@ function createHookCommand() {
|
|
|
4279
4307
|
cwd
|
|
4280
4308
|
});
|
|
4281
4309
|
if (result.violations.length === 0) {
|
|
4282
|
-
console.log(
|
|
4310
|
+
console.log(chalk9.green("\u2713 SpecBridge: All checks passed"));
|
|
4283
4311
|
process.exit(0);
|
|
4284
4312
|
}
|
|
4285
|
-
console.log(
|
|
4313
|
+
console.log(chalk9.red(`\u2717 SpecBridge: ${result.violations.length} violation(s) found`));
|
|
4286
4314
|
console.log("");
|
|
4287
4315
|
for (const v of result.violations) {
|
|
4288
4316
|
const location = v.line ? `:${v.line}` : "";
|
|
4289
4317
|
console.log(` ${v.file}${location}: ${v.message}`);
|
|
4290
|
-
console.log(
|
|
4318
|
+
console.log(chalk9.dim(` [${v.severity}] ${v.decisionId}/${v.constraintId}`));
|
|
4291
4319
|
}
|
|
4292
4320
|
console.log("");
|
|
4293
|
-
console.log(
|
|
4321
|
+
console.log(chalk9.yellow("Run `specbridge verify` for full details."));
|
|
4294
4322
|
process.exit(result.success ? 0 : 1);
|
|
4295
4323
|
} catch (error) {
|
|
4296
|
-
console.error(
|
|
4324
|
+
console.error(chalk9.red("SpecBridge verification failed"));
|
|
4297
4325
|
console.error(error instanceof Error ? error.message : String(error));
|
|
4298
4326
|
process.exit(1);
|
|
4299
4327
|
}
|
|
@@ -4332,7 +4360,7 @@ var hookCommand = createHookCommand();
|
|
|
4332
4360
|
|
|
4333
4361
|
// src/cli/commands/report.ts
|
|
4334
4362
|
import { Command as Command10 } from "commander";
|
|
4335
|
-
import
|
|
4363
|
+
import chalk11 from "chalk";
|
|
4336
4364
|
import ora6 from "ora";
|
|
4337
4365
|
import { join as join10 } from "path";
|
|
4338
4366
|
|
|
@@ -4416,54 +4444,54 @@ async function generateReport(config, options = {}) {
|
|
|
4416
4444
|
}
|
|
4417
4445
|
|
|
4418
4446
|
// src/reporting/formats/console.ts
|
|
4419
|
-
import
|
|
4447
|
+
import chalk10 from "chalk";
|
|
4420
4448
|
import { table as table2 } from "table";
|
|
4421
4449
|
function formatConsoleReport(report) {
|
|
4422
4450
|
const lines = [];
|
|
4423
4451
|
lines.push("");
|
|
4424
|
-
lines.push(
|
|
4425
|
-
lines.push(
|
|
4426
|
-
lines.push(
|
|
4452
|
+
lines.push(chalk10.bold.blue("SpecBridge Compliance Report"));
|
|
4453
|
+
lines.push(chalk10.dim(`Generated: ${new Date(report.timestamp).toLocaleString()}`));
|
|
4454
|
+
lines.push(chalk10.dim(`Project: ${report.project}`));
|
|
4427
4455
|
lines.push("");
|
|
4428
4456
|
const complianceColor = getComplianceColor(report.summary.compliance);
|
|
4429
|
-
lines.push(
|
|
4457
|
+
lines.push(chalk10.bold("Overall Compliance"));
|
|
4430
4458
|
lines.push(` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`);
|
|
4431
4459
|
lines.push("");
|
|
4432
|
-
lines.push(
|
|
4460
|
+
lines.push(chalk10.bold("Summary"));
|
|
4433
4461
|
lines.push(` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`);
|
|
4434
4462
|
lines.push(` Constraints: ${report.summary.totalConstraints}`);
|
|
4435
4463
|
lines.push("");
|
|
4436
|
-
lines.push(
|
|
4464
|
+
lines.push(chalk10.bold("Violations"));
|
|
4437
4465
|
const { violations } = report.summary;
|
|
4438
4466
|
const violationParts = [];
|
|
4439
4467
|
if (violations.critical > 0) {
|
|
4440
|
-
violationParts.push(
|
|
4468
|
+
violationParts.push(chalk10.red(`${violations.critical} critical`));
|
|
4441
4469
|
}
|
|
4442
4470
|
if (violations.high > 0) {
|
|
4443
|
-
violationParts.push(
|
|
4471
|
+
violationParts.push(chalk10.yellow(`${violations.high} high`));
|
|
4444
4472
|
}
|
|
4445
4473
|
if (violations.medium > 0) {
|
|
4446
|
-
violationParts.push(
|
|
4474
|
+
violationParts.push(chalk10.cyan(`${violations.medium} medium`));
|
|
4447
4475
|
}
|
|
4448
4476
|
if (violations.low > 0) {
|
|
4449
|
-
violationParts.push(
|
|
4477
|
+
violationParts.push(chalk10.dim(`${violations.low} low`));
|
|
4450
4478
|
}
|
|
4451
4479
|
if (violationParts.length > 0) {
|
|
4452
4480
|
lines.push(` ${violationParts.join(" | ")}`);
|
|
4453
4481
|
} else {
|
|
4454
|
-
lines.push(
|
|
4482
|
+
lines.push(chalk10.green(" No violations"));
|
|
4455
4483
|
}
|
|
4456
4484
|
lines.push("");
|
|
4457
4485
|
if (report.byDecision.length > 0) {
|
|
4458
|
-
lines.push(
|
|
4486
|
+
lines.push(chalk10.bold("By Decision"));
|
|
4459
4487
|
lines.push("");
|
|
4460
4488
|
const tableData = [
|
|
4461
4489
|
[
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4490
|
+
chalk10.bold("Decision"),
|
|
4491
|
+
chalk10.bold("Status"),
|
|
4492
|
+
chalk10.bold("Constraints"),
|
|
4493
|
+
chalk10.bold("Violations"),
|
|
4494
|
+
chalk10.bold("Compliance")
|
|
4467
4495
|
]
|
|
4468
4496
|
];
|
|
4469
4497
|
for (const dec of report.byDecision) {
|
|
@@ -4473,7 +4501,7 @@ function formatConsoleReport(report) {
|
|
|
4473
4501
|
truncate2(dec.title, 40),
|
|
4474
4502
|
statusColor(dec.status),
|
|
4475
4503
|
String(dec.constraints),
|
|
4476
|
-
dec.violations > 0 ?
|
|
4504
|
+
dec.violations > 0 ? chalk10.red(String(dec.violations)) : chalk10.green("0"),
|
|
4477
4505
|
compColor(`${dec.compliance}%`)
|
|
4478
4506
|
]);
|
|
4479
4507
|
}
|
|
@@ -4507,23 +4535,23 @@ function formatComplianceBar(compliance) {
|
|
|
4507
4535
|
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
4508
4536
|
}
|
|
4509
4537
|
function getComplianceColor(compliance) {
|
|
4510
|
-
if (compliance >= 90) return
|
|
4511
|
-
if (compliance >= 70) return
|
|
4512
|
-
if (compliance >= 50) return
|
|
4513
|
-
return
|
|
4538
|
+
if (compliance >= 90) return chalk10.green;
|
|
4539
|
+
if (compliance >= 70) return chalk10.yellow;
|
|
4540
|
+
if (compliance >= 50) return chalk10.hex("#FFA500");
|
|
4541
|
+
return chalk10.red;
|
|
4514
4542
|
}
|
|
4515
4543
|
function getStatusColor2(status) {
|
|
4516
4544
|
switch (status) {
|
|
4517
4545
|
case "active":
|
|
4518
|
-
return
|
|
4546
|
+
return chalk10.green;
|
|
4519
4547
|
case "draft":
|
|
4520
|
-
return
|
|
4548
|
+
return chalk10.yellow;
|
|
4521
4549
|
case "deprecated":
|
|
4522
|
-
return
|
|
4550
|
+
return chalk10.gray;
|
|
4523
4551
|
case "superseded":
|
|
4524
|
-
return
|
|
4552
|
+
return chalk10.blue;
|
|
4525
4553
|
default:
|
|
4526
|
-
return
|
|
4554
|
+
return chalk10.white;
|
|
4527
4555
|
}
|
|
4528
4556
|
}
|
|
4529
4557
|
function truncate2(str, length) {
|
|
@@ -4597,6 +4625,7 @@ function formatProgressBar(percentage) {
|
|
|
4597
4625
|
import { join as join9 } from "path";
|
|
4598
4626
|
var ReportStorage = class {
|
|
4599
4627
|
storageDir;
|
|
4628
|
+
logger = getLogger({ module: "reporting.storage" });
|
|
4600
4629
|
constructor(basePath) {
|
|
4601
4630
|
this.storageDir = join9(getSpecBridgeDir(basePath), "reports", "history");
|
|
4602
4631
|
}
|
|
@@ -4655,7 +4684,7 @@ var ReportStorage = class {
|
|
|
4655
4684
|
const timestamp = file.replace("report-", "").replace(".json", "");
|
|
4656
4685
|
return { timestamp, report };
|
|
4657
4686
|
} catch (error) {
|
|
4658
|
-
|
|
4687
|
+
this.logger.warn({ file, error }, "Failed to load report file");
|
|
4659
4688
|
return null;
|
|
4660
4689
|
}
|
|
4661
4690
|
});
|
|
@@ -4699,7 +4728,7 @@ var ReportStorage = class {
|
|
|
4699
4728
|
const fs = await import("fs/promises");
|
|
4700
4729
|
await fs.unlink(filepath);
|
|
4701
4730
|
} catch (error) {
|
|
4702
|
-
|
|
4731
|
+
this.logger.warn({ file, error }, "Failed to delete old report file");
|
|
4703
4732
|
}
|
|
4704
4733
|
}
|
|
4705
4734
|
return filesToDelete.length;
|
|
@@ -4901,29 +4930,29 @@ var reportCommand = new Command10("report").description("Generate compliance rep
|
|
|
4901
4930
|
const storage = new ReportStorage(cwd);
|
|
4902
4931
|
await storage.save(report);
|
|
4903
4932
|
if (options.trend) {
|
|
4904
|
-
console.log("\n" +
|
|
4933
|
+
console.log("\n" + chalk11.blue.bold("=== Compliance Trend Analysis ===\n"));
|
|
4905
4934
|
const days = parseInt(options.days || "30", 10);
|
|
4906
4935
|
const history = await storage.loadHistory(days);
|
|
4907
4936
|
if (history.length < 2) {
|
|
4908
|
-
console.log(
|
|
4937
|
+
console.log(chalk11.yellow(`Not enough data for trend analysis. Found ${history.length} report(s), need at least 2.`));
|
|
4909
4938
|
} else {
|
|
4910
4939
|
const trend = await analyzeTrend(history);
|
|
4911
|
-
console.log(
|
|
4940
|
+
console.log(chalk11.bold(`Period: ${trend.period.start} to ${trend.period.end} (${trend.period.days} days)`));
|
|
4912
4941
|
console.log(`
|
|
4913
4942
|
Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.endCompliance}% (${trend.overall.change > 0 ? "+" : ""}${trend.overall.change.toFixed(1)}%)`);
|
|
4914
4943
|
const trendEmoji = trend.overall.trend === "improving" ? "\u{1F4C8}" : trend.overall.trend === "degrading" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
4915
|
-
const trendColor = trend.overall.trend === "improving" ?
|
|
4944
|
+
const trendColor = trend.overall.trend === "improving" ? chalk11.green : trend.overall.trend === "degrading" ? chalk11.red : chalk11.yellow;
|
|
4916
4945
|
console.log(trendColor(`${trendEmoji} Trend: ${trend.overall.trend.toUpperCase()}`));
|
|
4917
4946
|
const degrading = trend.decisions.filter((d) => d.trend === "degrading").slice(0, 3);
|
|
4918
4947
|
if (degrading.length > 0) {
|
|
4919
|
-
console.log(
|
|
4948
|
+
console.log(chalk11.red("\n\u26A0\uFE0F Most Degraded Decisions:"));
|
|
4920
4949
|
degrading.forEach((d) => {
|
|
4921
4950
|
console.log(` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (${d.change.toFixed(1)}%)`);
|
|
4922
4951
|
});
|
|
4923
4952
|
}
|
|
4924
4953
|
const improving = trend.decisions.filter((d) => d.trend === "improving").slice(0, 3);
|
|
4925
4954
|
if (improving.length > 0) {
|
|
4926
|
-
console.log(
|
|
4955
|
+
console.log(chalk11.green("\n\u2705 Most Improved Decisions:"));
|
|
4927
4956
|
improving.forEach((d) => {
|
|
4928
4957
|
console.log(` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (+${d.change.toFixed(1)}%)`);
|
|
4929
4958
|
});
|
|
@@ -4932,26 +4961,26 @@ Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.end
|
|
|
4932
4961
|
console.log("");
|
|
4933
4962
|
}
|
|
4934
4963
|
if (options.drift) {
|
|
4935
|
-
console.log("\n" +
|
|
4964
|
+
console.log("\n" + chalk11.blue.bold("=== Drift Analysis ===\n"));
|
|
4936
4965
|
const history = await storage.loadHistory(2);
|
|
4937
4966
|
if (history.length < 2) {
|
|
4938
|
-
console.log(
|
|
4967
|
+
console.log(chalk11.yellow("Not enough data for drift analysis. Need at least 2 reports."));
|
|
4939
4968
|
} else {
|
|
4940
4969
|
const currentEntry = history[0];
|
|
4941
4970
|
const previousEntry = history[1];
|
|
4942
4971
|
if (!currentEntry || !previousEntry) {
|
|
4943
|
-
console.log(
|
|
4972
|
+
console.log(chalk11.yellow("Invalid history data."));
|
|
4944
4973
|
return;
|
|
4945
4974
|
}
|
|
4946
4975
|
const drift = await detectDrift(currentEntry.report, previousEntry.report);
|
|
4947
|
-
console.log(
|
|
4976
|
+
console.log(chalk11.bold(`Comparing: ${previousEntry.timestamp} vs ${currentEntry.timestamp}`));
|
|
4948
4977
|
console.log(`
|
|
4949
4978
|
Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceChange.toFixed(1)}%`);
|
|
4950
4979
|
const driftEmoji = drift.trend === "improving" ? "\u{1F4C8}" : drift.trend === "degrading" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
4951
|
-
const driftColor = drift.trend === "improving" ?
|
|
4980
|
+
const driftColor = drift.trend === "improving" ? chalk11.green : drift.trend === "degrading" ? chalk11.red : chalk11.yellow;
|
|
4952
4981
|
console.log(driftColor(`${driftEmoji} Overall Trend: ${drift.trend.toUpperCase()}`));
|
|
4953
4982
|
if (drift.summary.newViolations.total > 0) {
|
|
4954
|
-
console.log(
|
|
4983
|
+
console.log(chalk11.red(`
|
|
4955
4984
|
\u26A0\uFE0F New Violations: ${drift.summary.newViolations.total}`));
|
|
4956
4985
|
if (drift.summary.newViolations.critical > 0) {
|
|
4957
4986
|
console.log(` \u2022 Critical: ${drift.summary.newViolations.critical}`);
|
|
@@ -4967,7 +4996,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4967
4996
|
}
|
|
4968
4997
|
}
|
|
4969
4998
|
if (drift.summary.fixedViolations.total > 0) {
|
|
4970
|
-
console.log(
|
|
4999
|
+
console.log(chalk11.green(`
|
|
4971
5000
|
\u2705 Fixed Violations: ${drift.summary.fixedViolations.total}`));
|
|
4972
5001
|
if (drift.summary.fixedViolations.critical > 0) {
|
|
4973
5002
|
console.log(` \u2022 Critical: ${drift.summary.fixedViolations.critical}`);
|
|
@@ -4983,7 +5012,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4983
5012
|
}
|
|
4984
5013
|
}
|
|
4985
5014
|
if (drift.mostDegraded.length > 0) {
|
|
4986
|
-
console.log(
|
|
5015
|
+
console.log(chalk11.red("\n\u{1F4C9} Most Degraded:"));
|
|
4987
5016
|
drift.mostDegraded.forEach((d) => {
|
|
4988
5017
|
console.log(` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (${d.complianceChange.toFixed(1)}%)`);
|
|
4989
5018
|
if (d.newViolations > 0) {
|
|
@@ -4992,7 +5021,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4992
5021
|
});
|
|
4993
5022
|
}
|
|
4994
5023
|
if (drift.mostImproved.length > 0) {
|
|
4995
|
-
console.log(
|
|
5024
|
+
console.log(chalk11.green("\n\u{1F4C8} Most Improved:"));
|
|
4996
5025
|
drift.mostImproved.forEach((d) => {
|
|
4997
5026
|
console.log(` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (+${d.complianceChange.toFixed(1)}%)`);
|
|
4998
5027
|
if (d.fixedViolations > 0) {
|
|
@@ -5032,7 +5061,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
5032
5061
|
`health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${extension}`
|
|
5033
5062
|
);
|
|
5034
5063
|
await writeTextFile(outputPath, output);
|
|
5035
|
-
console.log(
|
|
5064
|
+
console.log(chalk11.green(`
|
|
5036
5065
|
Report saved to: ${outputPath}`));
|
|
5037
5066
|
if (options.save && !options.output) {
|
|
5038
5067
|
const latestPath = join10(getReportsDir(cwd), `health-latest.${extension}`);
|
|
@@ -5047,7 +5076,7 @@ Report saved to: ${outputPath}`));
|
|
|
5047
5076
|
|
|
5048
5077
|
// src/cli/commands/context.ts
|
|
5049
5078
|
import { Command as Command11 } from "commander";
|
|
5050
|
-
import
|
|
5079
|
+
import chalk12 from "chalk";
|
|
5051
5080
|
|
|
5052
5081
|
// src/agent/context.generator.ts
|
|
5053
5082
|
async function generateContext(filePath, config, options = {}) {
|
|
@@ -5167,12 +5196,12 @@ var contextCommand = new Command11("context").description("Generate architectura
|
|
|
5167
5196
|
});
|
|
5168
5197
|
if (options.output) {
|
|
5169
5198
|
await writeTextFile(options.output, output);
|
|
5170
|
-
console.log(
|
|
5199
|
+
console.log(chalk12.green(`Context saved to: ${options.output}`));
|
|
5171
5200
|
} else {
|
|
5172
5201
|
console.log(output);
|
|
5173
5202
|
}
|
|
5174
5203
|
} catch (error) {
|
|
5175
|
-
console.error(
|
|
5204
|
+
console.error(chalk12.red("Failed to generate context"));
|
|
5176
5205
|
throw error;
|
|
5177
5206
|
}
|
|
5178
5207
|
});
|
|
@@ -5186,7 +5215,7 @@ import { TextDocument } from "vscode-languageserver-textdocument";
|
|
|
5186
5215
|
import { fileURLToPath } from "url";
|
|
5187
5216
|
import path3 from "path";
|
|
5188
5217
|
import { Project as Project3 } from "ts-morph";
|
|
5189
|
-
import
|
|
5218
|
+
import chalk13 from "chalk";
|
|
5190
5219
|
function severityToDiagnostic(severity) {
|
|
5191
5220
|
switch (severity) {
|
|
5192
5221
|
case "critical":
|
|
@@ -5297,7 +5326,7 @@ var SpecBridgeLspServer = class {
|
|
|
5297
5326
|
if (!await pathExists(getSpecBridgeDir(this.cwd))) {
|
|
5298
5327
|
const err = new NotInitializedError();
|
|
5299
5328
|
this.initError = err.message;
|
|
5300
|
-
if (this.options.verbose) this.connection.console.error(
|
|
5329
|
+
if (this.options.verbose) this.connection.console.error(chalk13.red(this.initError));
|
|
5301
5330
|
return;
|
|
5302
5331
|
}
|
|
5303
5332
|
try {
|
|
@@ -5306,7 +5335,7 @@ var SpecBridgeLspServer = class {
|
|
|
5306
5335
|
await getPluginLoader().loadPlugins(this.cwd);
|
|
5307
5336
|
} catch (error) {
|
|
5308
5337
|
const msg = error instanceof Error ? error.message : String(error);
|
|
5309
|
-
if (this.options.verbose) this.connection.console.error(
|
|
5338
|
+
if (this.options.verbose) this.connection.console.error(chalk13.red(`Plugin load failed: ${msg}`));
|
|
5310
5339
|
}
|
|
5311
5340
|
this.registry = createRegistry({ basePath: this.cwd });
|
|
5312
5341
|
await this.registry.load();
|
|
@@ -5319,11 +5348,11 @@ var SpecBridgeLspServer = class {
|
|
|
5319
5348
|
}
|
|
5320
5349
|
}
|
|
5321
5350
|
if (this.options.verbose) {
|
|
5322
|
-
this.connection.console.log(
|
|
5351
|
+
this.connection.console.log(chalk13.dim(`Loaded ${this.decisions.length} active decision(s)`));
|
|
5323
5352
|
}
|
|
5324
5353
|
} catch (error) {
|
|
5325
5354
|
this.initError = error instanceof Error ? error.message : String(error);
|
|
5326
|
-
if (this.options.verbose) this.connection.console.error(
|
|
5355
|
+
if (this.options.verbose) this.connection.console.error(chalk13.red(this.initError));
|
|
5327
5356
|
}
|
|
5328
5357
|
}
|
|
5329
5358
|
async verifyTextDocument(doc) {
|
|
@@ -5395,7 +5424,7 @@ var lspCommand = new Command12("lsp").description("Start SpecBridge language ser
|
|
|
5395
5424
|
|
|
5396
5425
|
// src/cli/commands/watch.ts
|
|
5397
5426
|
import { Command as Command13 } from "commander";
|
|
5398
|
-
import
|
|
5427
|
+
import chalk14 from "chalk";
|
|
5399
5428
|
import chokidar from "chokidar";
|
|
5400
5429
|
import path4 from "path";
|
|
5401
5430
|
var watchCommand = new Command13("watch").description("Watch for changes and verify files continuously").option("-l, --level <level>", "Verification level (commit, pr, full)", "full").option("--debounce <ms>", "Debounce verify on rapid changes", "150").action(async (options) => {
|
|
@@ -5416,15 +5445,15 @@ var watchCommand = new Command13("watch").description("Watch for changes and ver
|
|
|
5416
5445
|
files: [absolutePath],
|
|
5417
5446
|
cwd
|
|
5418
5447
|
});
|
|
5419
|
-
const prefix = result.success ?
|
|
5448
|
+
const prefix = result.success ? chalk14.green("\u2713") : chalk14.red("\u2717");
|
|
5420
5449
|
const summary = `${prefix} ${path4.relative(cwd, absolutePath)}: ${result.violations.length} violation(s)`;
|
|
5421
5450
|
console.log(summary);
|
|
5422
5451
|
for (const v of result.violations.slice(0, 20)) {
|
|
5423
5452
|
const loc = v.line ? `:${v.line}${v.column ? `:${v.column}` : ""}` : "";
|
|
5424
|
-
console.log(
|
|
5453
|
+
console.log(chalk14.dim(` - ${v.file}${loc}: ${v.message} [${v.severity}]`));
|
|
5425
5454
|
}
|
|
5426
5455
|
if (result.violations.length > 20) {
|
|
5427
|
-
console.log(
|
|
5456
|
+
console.log(chalk14.dim(` \u2026 ${result.violations.length - 20} more`));
|
|
5428
5457
|
}
|
|
5429
5458
|
};
|
|
5430
5459
|
const watcher = chokidar.watch(config.project.sourceRoots, {
|
|
@@ -5433,7 +5462,7 @@ var watchCommand = new Command13("watch").description("Watch for changes and ver
|
|
|
5433
5462
|
ignoreInitial: true,
|
|
5434
5463
|
persistent: true
|
|
5435
5464
|
});
|
|
5436
|
-
console.log(
|
|
5465
|
+
console.log(chalk14.blue("Watching for changes..."));
|
|
5437
5466
|
watcher.on("change", (changedPath) => {
|
|
5438
5467
|
pendingPath = changedPath;
|
|
5439
5468
|
if (timer) clearTimeout(timer);
|
|
@@ -5725,7 +5754,7 @@ var promptCommand = new Command15("prompt").description("Generate AI agent promp
|
|
|
5725
5754
|
|
|
5726
5755
|
// src/cli/commands/analytics.ts
|
|
5727
5756
|
import { Command as Command16 } from "commander";
|
|
5728
|
-
import
|
|
5757
|
+
import chalk15 from "chalk";
|
|
5729
5758
|
import ora7 from "ora";
|
|
5730
5759
|
|
|
5731
5760
|
// src/analytics/engine.ts
|
|
@@ -5954,7 +5983,7 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5954
5983
|
const history = await storage.loadHistory(days);
|
|
5955
5984
|
if (history.length === 0) {
|
|
5956
5985
|
spinner.fail("No historical reports found");
|
|
5957
|
-
console.log(
|
|
5986
|
+
console.log(chalk15.yellow("\nGenerate reports with: specbridge report"));
|
|
5958
5987
|
return;
|
|
5959
5988
|
}
|
|
5960
5989
|
spinner.succeed(`Loaded ${history.length} historical report(s)`);
|
|
@@ -5971,17 +6000,17 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5971
6000
|
}
|
|
5972
6001
|
if (decisionId) {
|
|
5973
6002
|
const metrics = await engine.analyzeDecision(decisionId, history);
|
|
5974
|
-
console.log("\n" +
|
|
6003
|
+
console.log("\n" + chalk15.blue.bold(`=== Decision Analytics: ${metrics.title} ===
|
|
5975
6004
|
`));
|
|
5976
|
-
console.log(
|
|
6005
|
+
console.log(chalk15.bold("Overview:"));
|
|
5977
6006
|
console.log(` ID: ${metrics.decisionId}`);
|
|
5978
6007
|
console.log(` Current Violations: ${metrics.totalViolations}`);
|
|
5979
6008
|
console.log(` Average Compliance: ${metrics.averageComplianceScore.toFixed(1)}%`);
|
|
5980
6009
|
const trendEmoji = metrics.trendDirection === "up" ? "\u{1F4C8}" : metrics.trendDirection === "down" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
5981
|
-
const trendColor = metrics.trendDirection === "up" ?
|
|
6010
|
+
const trendColor = metrics.trendDirection === "up" ? chalk15.green : metrics.trendDirection === "down" ? chalk15.red : chalk15.yellow;
|
|
5982
6011
|
console.log(` ${trendColor(`${trendEmoji} Trend: ${metrics.trendDirection.toUpperCase()}`)}`);
|
|
5983
6012
|
if (metrics.history.length > 0) {
|
|
5984
|
-
console.log(
|
|
6013
|
+
console.log(chalk15.bold("\nCompliance History:"));
|
|
5985
6014
|
const recentHistory = metrics.history.slice(-10);
|
|
5986
6015
|
recentHistory.forEach((h) => {
|
|
5987
6016
|
const icon = h.violations === 0 ? "\u2705" : "\u26A0\uFE0F";
|
|
@@ -5990,58 +6019,58 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5990
6019
|
}
|
|
5991
6020
|
} else {
|
|
5992
6021
|
const summary = await engine.generateSummary(history);
|
|
5993
|
-
console.log("\n" +
|
|
5994
|
-
console.log(
|
|
6022
|
+
console.log("\n" + chalk15.blue.bold("=== Overall Analytics ===\n"));
|
|
6023
|
+
console.log(chalk15.bold("Summary:"));
|
|
5995
6024
|
console.log(` Total Decisions: ${summary.totalDecisions}`);
|
|
5996
6025
|
console.log(` Average Compliance: ${summary.averageCompliance}%`);
|
|
5997
6026
|
console.log(` Critical Issues: ${summary.criticalIssues}`);
|
|
5998
6027
|
const trendEmoji = summary.overallTrend === "up" ? "\u{1F4C8}" : summary.overallTrend === "down" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
5999
|
-
const trendColor = summary.overallTrend === "up" ?
|
|
6028
|
+
const trendColor = summary.overallTrend === "up" ? chalk15.green : summary.overallTrend === "down" ? chalk15.red : chalk15.yellow;
|
|
6000
6029
|
console.log(` ${trendColor(`${trendEmoji} Overall Trend: ${summary.overallTrend.toUpperCase()}`)}`);
|
|
6001
6030
|
if (summary.topDecisions.length > 0) {
|
|
6002
|
-
console.log(
|
|
6031
|
+
console.log(chalk15.green("\n\u2705 Top Performing Decisions:"));
|
|
6003
6032
|
summary.topDecisions.forEach((d, i) => {
|
|
6004
6033
|
console.log(` ${i + 1}. ${d.title}: ${d.compliance}%`);
|
|
6005
6034
|
});
|
|
6006
6035
|
}
|
|
6007
6036
|
if (summary.bottomDecisions.length > 0) {
|
|
6008
|
-
console.log(
|
|
6037
|
+
console.log(chalk15.red("\n\u26A0\uFE0F Decisions Needing Attention:"));
|
|
6009
6038
|
summary.bottomDecisions.forEach((d, i) => {
|
|
6010
6039
|
console.log(` ${i + 1}. ${d.title}: ${d.compliance}%`);
|
|
6011
6040
|
});
|
|
6012
6041
|
}
|
|
6013
6042
|
if (options.insights || summary.criticalIssues > 0) {
|
|
6014
|
-
console.log(
|
|
6043
|
+
console.log(chalk15.blue.bold("\n=== Insights ===\n"));
|
|
6015
6044
|
const insights = summary.insights;
|
|
6016
6045
|
const warnings = insights.filter((i) => i.type === "warning");
|
|
6017
6046
|
const successes = insights.filter((i) => i.type === "success");
|
|
6018
6047
|
const infos = insights.filter((i) => i.type === "info");
|
|
6019
6048
|
if (warnings.length > 0) {
|
|
6020
|
-
console.log(
|
|
6049
|
+
console.log(chalk15.red("\u26A0\uFE0F Warnings:"));
|
|
6021
6050
|
warnings.forEach((i) => {
|
|
6022
6051
|
console.log(` \u2022 ${i.message}`);
|
|
6023
6052
|
if (i.details) {
|
|
6024
|
-
console.log(
|
|
6053
|
+
console.log(chalk15.gray(` ${i.details}`));
|
|
6025
6054
|
}
|
|
6026
6055
|
});
|
|
6027
6056
|
console.log("");
|
|
6028
6057
|
}
|
|
6029
6058
|
if (successes.length > 0) {
|
|
6030
|
-
console.log(
|
|
6059
|
+
console.log(chalk15.green("\u2705 Positive Trends:"));
|
|
6031
6060
|
successes.forEach((i) => {
|
|
6032
6061
|
console.log(` \u2022 ${i.message}`);
|
|
6033
6062
|
if (i.details) {
|
|
6034
|
-
console.log(
|
|
6063
|
+
console.log(chalk15.gray(` ${i.details}`));
|
|
6035
6064
|
}
|
|
6036
6065
|
});
|
|
6037
6066
|
console.log("");
|
|
6038
6067
|
}
|
|
6039
6068
|
if (infos.length > 0) {
|
|
6040
|
-
console.log(
|
|
6069
|
+
console.log(chalk15.blue("\u{1F4A1} Suggestions:"));
|
|
6041
6070
|
infos.forEach((i) => {
|
|
6042
6071
|
console.log(` \u2022 ${i.message}`);
|
|
6043
6072
|
if (i.details) {
|
|
6044
|
-
console.log(
|
|
6073
|
+
console.log(chalk15.gray(` ${i.details}`));
|
|
6045
6074
|
}
|
|
6046
6075
|
});
|
|
6047
6076
|
console.log("");
|
|
@@ -6051,10 +6080,10 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
6051
6080
|
const latestEntry = history[history.length - 1];
|
|
6052
6081
|
const oldestEntry = history[0];
|
|
6053
6082
|
if (latestEntry && oldestEntry) {
|
|
6054
|
-
console.log(
|
|
6083
|
+
console.log(chalk15.gray(`
|
|
6055
6084
|
Data range: ${latestEntry.timestamp} to ${oldestEntry.timestamp}`));
|
|
6056
6085
|
}
|
|
6057
|
-
console.log(
|
|
6086
|
+
console.log(chalk15.gray(`Analyzing ${history.length} report(s) over ${days} days
|
|
6058
6087
|
`));
|
|
6059
6088
|
} catch (error) {
|
|
6060
6089
|
spinner.fail("Analytics failed");
|
|
@@ -6064,7 +6093,7 @@ Data range: ${latestEntry.timestamp} to ${oldestEntry.timestamp}`));
|
|
|
6064
6093
|
|
|
6065
6094
|
// src/cli/commands/dashboard.ts
|
|
6066
6095
|
import { Command as Command17 } from "commander";
|
|
6067
|
-
import
|
|
6096
|
+
import chalk16 from "chalk";
|
|
6068
6097
|
|
|
6069
6098
|
// src/dashboard/server.ts
|
|
6070
6099
|
import express from "express";
|
|
@@ -6083,6 +6112,7 @@ var DashboardServer = class {
|
|
|
6083
6112
|
CACHE_TTL = 6e4;
|
|
6084
6113
|
// 1 minute
|
|
6085
6114
|
refreshInterval = null;
|
|
6115
|
+
logger = getLogger({ module: "dashboard.server" });
|
|
6086
6116
|
constructor(options) {
|
|
6087
6117
|
this.cwd = options.cwd;
|
|
6088
6118
|
this.config = options.config;
|
|
@@ -6099,7 +6129,11 @@ var DashboardServer = class {
|
|
|
6099
6129
|
await this.registry.load();
|
|
6100
6130
|
await this.refreshCache();
|
|
6101
6131
|
this.refreshInterval = setInterval(
|
|
6102
|
-
() =>
|
|
6132
|
+
() => {
|
|
6133
|
+
void this.refreshCache().catch((error) => {
|
|
6134
|
+
this.logger.error({ error }, "Background cache refresh failed");
|
|
6135
|
+
});
|
|
6136
|
+
},
|
|
6103
6137
|
this.CACHE_TTL
|
|
6104
6138
|
);
|
|
6105
6139
|
}
|
|
@@ -6122,7 +6156,7 @@ var DashboardServer = class {
|
|
|
6122
6156
|
this.cacheTimestamp = Date.now();
|
|
6123
6157
|
await this.reportStorage.save(report);
|
|
6124
6158
|
} catch (error) {
|
|
6125
|
-
|
|
6159
|
+
this.logger.error({ error }, "Cache refresh failed");
|
|
6126
6160
|
if (!this.cachedReport) {
|
|
6127
6161
|
try {
|
|
6128
6162
|
const stored = await this.reportStorage.loadLatest();
|
|
@@ -6130,7 +6164,7 @@ var DashboardServer = class {
|
|
|
6130
6164
|
this.cachedReport = stored.report;
|
|
6131
6165
|
}
|
|
6132
6166
|
} catch (fallbackError) {
|
|
6133
|
-
|
|
6167
|
+
this.logger.error({ error: fallbackError }, "Failed to load fallback report");
|
|
6134
6168
|
}
|
|
6135
6169
|
}
|
|
6136
6170
|
}
|
|
@@ -6393,7 +6427,7 @@ var dashboardCommand = new Command17("dashboard").description("Start compliance
|
|
|
6393
6427
|
if (!await pathExists(getSpecBridgeDir(cwd))) {
|
|
6394
6428
|
throw new NotInitializedError();
|
|
6395
6429
|
}
|
|
6396
|
-
console.log(
|
|
6430
|
+
console.log(chalk16.blue("Starting SpecBridge dashboard..."));
|
|
6397
6431
|
try {
|
|
6398
6432
|
const config = await loadConfig(cwd);
|
|
6399
6433
|
const server = createDashboardServer({ cwd, config });
|
|
@@ -6401,32 +6435,32 @@ var dashboardCommand = new Command17("dashboard").description("Start compliance
|
|
|
6401
6435
|
const port = parseInt(options.port || "3000", 10);
|
|
6402
6436
|
const host = options.host || "localhost";
|
|
6403
6437
|
server.getApp().listen(port, host, () => {
|
|
6404
|
-
console.log(
|
|
6438
|
+
console.log(chalk16.green(`
|
|
6405
6439
|
\u2713 Dashboard running at http://${host}:${port}`));
|
|
6406
|
-
console.log(
|
|
6407
|
-
console.log(
|
|
6408
|
-
console.log(` ${
|
|
6409
|
-
console.log(` ${
|
|
6410
|
-
console.log(` ${
|
|
6411
|
-
console.log(` ${
|
|
6440
|
+
console.log(chalk16.gray(" Press Ctrl+C to stop\n"));
|
|
6441
|
+
console.log(chalk16.bold("API Endpoints:"));
|
|
6442
|
+
console.log(` ${chalk16.cyan(`http://${host}:${port}/api/health`)} - Health check`);
|
|
6443
|
+
console.log(` ${chalk16.cyan(`http://${host}:${port}/api/report/latest`)} - Latest report (cached)`);
|
|
6444
|
+
console.log(` ${chalk16.cyan(`http://${host}:${port}/api/decisions`)} - All decisions`);
|
|
6445
|
+
console.log(` ${chalk16.cyan(`http://${host}:${port}/api/analytics/summary`)} - Analytics`);
|
|
6412
6446
|
console.log("");
|
|
6413
6447
|
});
|
|
6414
6448
|
const shutdown = () => {
|
|
6415
|
-
console.log(
|
|
6449
|
+
console.log(chalk16.yellow("\n\nShutting down dashboard..."));
|
|
6416
6450
|
server.stop();
|
|
6417
6451
|
process.exit(0);
|
|
6418
6452
|
};
|
|
6419
6453
|
process.on("SIGINT", shutdown);
|
|
6420
6454
|
process.on("SIGTERM", shutdown);
|
|
6421
6455
|
} catch (error) {
|
|
6422
|
-
console.error(
|
|
6456
|
+
console.error(chalk16.red("Failed to start dashboard:"), error);
|
|
6423
6457
|
throw error;
|
|
6424
6458
|
}
|
|
6425
6459
|
});
|
|
6426
6460
|
|
|
6427
6461
|
// src/cli/commands/impact.ts
|
|
6428
6462
|
import { Command as Command18 } from "commander";
|
|
6429
|
-
import
|
|
6463
|
+
import chalk17 from "chalk";
|
|
6430
6464
|
import ora8 from "ora";
|
|
6431
6465
|
|
|
6432
6466
|
// src/propagation/graph.ts
|
|
@@ -6513,7 +6547,24 @@ var PropagationEngine = class {
|
|
|
6513
6547
|
if (!this.graph) {
|
|
6514
6548
|
await this.initialize(config, options);
|
|
6515
6549
|
}
|
|
6516
|
-
const
|
|
6550
|
+
const graph = this.graph;
|
|
6551
|
+
if (!graph) {
|
|
6552
|
+
return {
|
|
6553
|
+
decision: decisionId,
|
|
6554
|
+
change,
|
|
6555
|
+
affectedFiles: [],
|
|
6556
|
+
estimatedEffort: "low",
|
|
6557
|
+
migrationSteps: [
|
|
6558
|
+
{
|
|
6559
|
+
order: 1,
|
|
6560
|
+
description: "Run verification to confirm all violations resolved",
|
|
6561
|
+
files: [],
|
|
6562
|
+
automated: true
|
|
6563
|
+
}
|
|
6564
|
+
]
|
|
6565
|
+
};
|
|
6566
|
+
}
|
|
6567
|
+
const affectedFilePaths = getAffectedFiles(graph, decisionId);
|
|
6517
6568
|
const verificationEngine = createVerificationEngine(this.registry);
|
|
6518
6569
|
const result = await verificationEngine.verify(config, {
|
|
6519
6570
|
files: affectedFilePaths,
|
|
@@ -6637,8 +6688,8 @@ var impactCommand = new Command18("impact").description("Analyze impact of decis
|
|
|
6637
6688
|
const changeType = options.change || "modified";
|
|
6638
6689
|
if (!["created", "modified", "deprecated"].includes(changeType)) {
|
|
6639
6690
|
spinner.fail();
|
|
6640
|
-
console.error(
|
|
6641
|
-
console.error(
|
|
6691
|
+
console.error(chalk17.red(`Invalid change type: ${changeType}`));
|
|
6692
|
+
console.error(chalk17.dim("Valid types: created, modified, deprecated"));
|
|
6642
6693
|
process.exit(1);
|
|
6643
6694
|
}
|
|
6644
6695
|
spinner.text = `Analyzing impact of ${changeType} decision ${decisionId}...`;
|
|
@@ -6656,44 +6707,44 @@ var impactCommand = new Command18("impact").description("Analyze impact of decis
|
|
|
6656
6707
|
}
|
|
6657
6708
|
});
|
|
6658
6709
|
function printImpactAnalysis(analysis, showSteps) {
|
|
6659
|
-
console.log(
|
|
6710
|
+
console.log(chalk17.bold(`
|
|
6660
6711
|
=== Impact Analysis: ${analysis.decision} ===
|
|
6661
6712
|
`));
|
|
6662
|
-
const changeLabel =
|
|
6713
|
+
const changeLabel = chalk17.cyan(analysis.change);
|
|
6663
6714
|
console.log(`Change Type: ${changeLabel}`);
|
|
6664
|
-
const effortColor = analysis.estimatedEffort === "high" ?
|
|
6715
|
+
const effortColor = analysis.estimatedEffort === "high" ? chalk17.red : analysis.estimatedEffort === "medium" ? chalk17.yellow : chalk17.green;
|
|
6665
6716
|
console.log(`Estimated Effort: ${effortColor(analysis.estimatedEffort.toUpperCase())}
|
|
6666
6717
|
`);
|
|
6667
|
-
console.log(
|
|
6718
|
+
console.log(chalk17.bold(`Affected Files: ${analysis.affectedFiles.length}`));
|
|
6668
6719
|
if (analysis.affectedFiles.length > 0) {
|
|
6669
6720
|
const displayCount = Math.min(analysis.affectedFiles.length, 10);
|
|
6670
6721
|
for (let i = 0; i < displayCount; i++) {
|
|
6671
6722
|
const file = analysis.affectedFiles[i];
|
|
6672
6723
|
if (!file) continue;
|
|
6673
6724
|
const violationText = file.violations === 1 ? "1 violation" : `${file.violations} violations`;
|
|
6674
|
-
const autoFixText = file.autoFixable > 0 ?
|
|
6675
|
-
console.log(` ${
|
|
6725
|
+
const autoFixText = file.autoFixable > 0 ? chalk17.green(` (${file.autoFixable} auto-fixable)`) : "";
|
|
6726
|
+
console.log(` ${chalk17.red("\u25CF")} ${file.path} - ${violationText}${autoFixText}`);
|
|
6676
6727
|
}
|
|
6677
6728
|
if (analysis.affectedFiles.length > displayCount) {
|
|
6678
6729
|
const remaining = analysis.affectedFiles.length - displayCount;
|
|
6679
|
-
console.log(
|
|
6730
|
+
console.log(chalk17.dim(` ... and ${remaining} more file(s)`));
|
|
6680
6731
|
}
|
|
6681
6732
|
} else {
|
|
6682
|
-
console.log(
|
|
6733
|
+
console.log(chalk17.green(" No violations found"));
|
|
6683
6734
|
}
|
|
6684
6735
|
if (showSteps && analysis.migrationSteps && analysis.migrationSteps.length > 0) {
|
|
6685
|
-
console.log(
|
|
6736
|
+
console.log(chalk17.bold("\nMigration Plan:"));
|
|
6686
6737
|
for (const step of analysis.migrationSteps) {
|
|
6687
6738
|
const icon = step.automated ? "\u{1F916}" : "\u{1F464}";
|
|
6688
|
-
const typeLabel = step.automated ?
|
|
6739
|
+
const typeLabel = step.automated ? chalk17.green("[Automated]") : chalk17.yellow("[Manual]");
|
|
6689
6740
|
console.log(` ${icon} Step ${step.order}: ${step.description} ${typeLabel}`);
|
|
6690
6741
|
if (step.files.length > 0) {
|
|
6691
6742
|
const displayFiles = Math.min(step.files.length, 3);
|
|
6692
6743
|
for (let i = 0; i < displayFiles; i++) {
|
|
6693
|
-
console.log(
|
|
6744
|
+
console.log(chalk17.dim(` - ${step.files[i]}`));
|
|
6694
6745
|
}
|
|
6695
6746
|
if (step.files.length > displayFiles) {
|
|
6696
|
-
console.log(
|
|
6747
|
+
console.log(chalk17.dim(` ... and ${step.files.length - displayFiles} more file(s)`));
|
|
6697
6748
|
}
|
|
6698
6749
|
}
|
|
6699
6750
|
console.log("");
|
|
@@ -6702,15 +6753,15 @@ function printImpactAnalysis(analysis, showSteps) {
|
|
|
6702
6753
|
const totalViolations = analysis.affectedFiles.reduce((sum, f) => sum + f.violations, 0);
|
|
6703
6754
|
const totalAutoFixable = analysis.affectedFiles.reduce((sum, f) => sum + f.autoFixable, 0);
|
|
6704
6755
|
const manualFixes = totalViolations - totalAutoFixable;
|
|
6705
|
-
console.log(
|
|
6756
|
+
console.log(chalk17.bold("Summary:"));
|
|
6706
6757
|
console.log(` Total Violations: ${totalViolations}`);
|
|
6707
|
-
console.log(` Auto-fixable: ${
|
|
6708
|
-
console.log(` Manual Fixes Required: ${manualFixes > 0 ?
|
|
6758
|
+
console.log(` Auto-fixable: ${chalk17.green(totalAutoFixable)}`);
|
|
6759
|
+
console.log(` Manual Fixes Required: ${manualFixes > 0 ? chalk17.yellow(manualFixes) : chalk17.green(0)}`);
|
|
6709
6760
|
}
|
|
6710
6761
|
|
|
6711
6762
|
// src/cli/commands/migrate.ts
|
|
6712
6763
|
import { Command as Command19 } from "commander";
|
|
6713
|
-
import
|
|
6764
|
+
import chalk18 from "chalk";
|
|
6714
6765
|
import ora9 from "ora";
|
|
6715
6766
|
import { join as join13 } from "path";
|
|
6716
6767
|
import { readdir as readdir2, copyFile, mkdir as mkdir2, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -6721,17 +6772,17 @@ var migrateCommand = new Command19("migrate").description("Migrate SpecBridge co
|
|
|
6721
6772
|
}
|
|
6722
6773
|
const from = options.from || "v1";
|
|
6723
6774
|
const to = options.to || "v2";
|
|
6724
|
-
console.log(
|
|
6775
|
+
console.log(chalk18.blue.bold(`
|
|
6725
6776
|
=== SpecBridge Migration: ${from} \u2192 ${to} ===
|
|
6726
6777
|
`));
|
|
6727
6778
|
if (from !== "v1" && from !== "v1.3") {
|
|
6728
|
-
console.error(
|
|
6729
|
-
console.log(
|
|
6779
|
+
console.error(chalk18.red(`Unsupported source version: ${from}`));
|
|
6780
|
+
console.log(chalk18.gray("Supported: v1, v1.3"));
|
|
6730
6781
|
process.exit(1);
|
|
6731
6782
|
}
|
|
6732
6783
|
if (to !== "v2" && to !== "v2.0") {
|
|
6733
|
-
console.error(
|
|
6734
|
-
console.log(
|
|
6784
|
+
console.error(chalk18.red(`Unsupported target version: ${to}`));
|
|
6785
|
+
console.log(chalk18.gray("Supported: v2, v2.0"));
|
|
6735
6786
|
process.exit(1);
|
|
6736
6787
|
}
|
|
6737
6788
|
const spinner = ora9("Analyzing current configuration...").start();
|
|
@@ -6751,7 +6802,7 @@ var migrateCommand = new Command19("migrate").description("Migrate SpecBridge co
|
|
|
6751
6802
|
const config = await loadConfig(cwd);
|
|
6752
6803
|
const v1Report = await generateReport(config, { cwd, legacyCompliance: true });
|
|
6753
6804
|
v1Compliance = v1Report.summary.compliance;
|
|
6754
|
-
} catch
|
|
6805
|
+
} catch {
|
|
6755
6806
|
spinner.warn("Could not generate v1 baseline report");
|
|
6756
6807
|
}
|
|
6757
6808
|
spinner.text = "Migrating decision files...";
|
|
@@ -6773,7 +6824,7 @@ var migrateCommand = new Command19("migrate").description("Migrate SpecBridge co
|
|
|
6773
6824
|
v2: v2Compliance,
|
|
6774
6825
|
difference: v2Compliance - v1Compliance
|
|
6775
6826
|
};
|
|
6776
|
-
} catch
|
|
6827
|
+
} catch {
|
|
6777
6828
|
spinner.warn("Could not generate v2 comparison report");
|
|
6778
6829
|
}
|
|
6779
6830
|
}
|
|
@@ -6782,41 +6833,41 @@ var migrateCommand = new Command19("migrate").description("Migrate SpecBridge co
|
|
|
6782
6833
|
report.changes.push("All decisions validated successfully");
|
|
6783
6834
|
}
|
|
6784
6835
|
spinner.succeed(options.dryRun ? "Migration preview complete" : "Migration complete");
|
|
6785
|
-
console.log(
|
|
6786
|
-
console.log(
|
|
6836
|
+
console.log(chalk18.green.bold("\n\u2713 Migration Summary:\n"));
|
|
6837
|
+
console.log(chalk18.bold("Backup:"));
|
|
6787
6838
|
console.log(` ${report.backupPath}`);
|
|
6788
6839
|
console.log("");
|
|
6789
|
-
console.log(
|
|
6840
|
+
console.log(chalk18.bold("Changes:"));
|
|
6790
6841
|
for (const change of report.changes) {
|
|
6791
6842
|
console.log(` \u2022 ${change}`);
|
|
6792
6843
|
}
|
|
6793
6844
|
console.log("");
|
|
6794
6845
|
if (report.complianceComparison) {
|
|
6795
|
-
console.log(
|
|
6846
|
+
console.log(chalk18.bold("Compliance Comparison:"));
|
|
6796
6847
|
console.log(` v1.3 formula: ${report.complianceComparison.v1}%`);
|
|
6797
6848
|
console.log(` v2.0 formula: ${report.complianceComparison.v2}%`);
|
|
6798
6849
|
const diff = report.complianceComparison.difference;
|
|
6799
|
-
const diffColor = diff > 0 ?
|
|
6850
|
+
const diffColor = diff > 0 ? chalk18.green : diff < 0 ? chalk18.red : chalk18.yellow;
|
|
6800
6851
|
console.log(` Difference: ${diffColor(`${diff > 0 ? "+" : ""}${diff.toFixed(1)}%`)}`);
|
|
6801
6852
|
console.log("");
|
|
6802
6853
|
if (Math.abs(diff) > 10) {
|
|
6803
|
-
console.log(
|
|
6804
|
-
console.log(
|
|
6854
|
+
console.log(chalk18.yellow("\u26A0\uFE0F Note: Compliance score changed significantly due to severity weighting."));
|
|
6855
|
+
console.log(chalk18.gray(" Consider adjusting CI thresholds if needed.\n"));
|
|
6805
6856
|
}
|
|
6806
6857
|
}
|
|
6807
6858
|
if (options.dryRun) {
|
|
6808
|
-
console.log(
|
|
6809
|
-
console.log(
|
|
6859
|
+
console.log(chalk18.yellow("This was a dry run. No changes were applied."));
|
|
6860
|
+
console.log(chalk18.gray("Run without --dry-run to apply changes.\n"));
|
|
6810
6861
|
} else {
|
|
6811
|
-
console.log(
|
|
6812
|
-
console.log(
|
|
6862
|
+
console.log(chalk18.green("\u2713 Migration successful!"));
|
|
6863
|
+
console.log(chalk18.gray(`
|
|
6813
6864
|
Rollback: Copy files from ${report.backupPath} back to .specbridge/decisions/
|
|
6814
6865
|
`));
|
|
6815
6866
|
}
|
|
6816
6867
|
} catch (error) {
|
|
6817
6868
|
spinner.fail("Migration failed");
|
|
6818
|
-
console.error(
|
|
6819
|
-
console.log(
|
|
6869
|
+
console.error(chalk18.red("\nError:"), error instanceof Error ? error.message : error);
|
|
6870
|
+
console.log(chalk18.gray("\nNo changes were applied."));
|
|
6820
6871
|
throw error;
|
|
6821
6872
|
}
|
|
6822
6873
|
});
|
|
@@ -6863,7 +6914,7 @@ async function migrateDecisions(cwd, dryRun) {
|
|
|
6863
6914
|
"$1check:\n$1 verifier: $2"
|
|
6864
6915
|
);
|
|
6865
6916
|
if (dryRun) {
|
|
6866
|
-
console.log(
|
|
6917
|
+
console.log(chalk18.gray(` Would migrate: ${file}`));
|
|
6867
6918
|
updatedCount++;
|
|
6868
6919
|
} else {
|
|
6869
6920
|
await writeFile3(filePath, migratedContent, "utf-8");
|
|
@@ -6901,11 +6952,11 @@ program.exitOverride((err) => {
|
|
|
6901
6952
|
if (err.code === "commander.version") {
|
|
6902
6953
|
process.exit(0);
|
|
6903
6954
|
}
|
|
6904
|
-
console.error(
|
|
6955
|
+
console.error(chalk19.red(formatError(err)));
|
|
6905
6956
|
process.exit(1);
|
|
6906
6957
|
});
|
|
6907
6958
|
program.parseAsync(process.argv).catch((error) => {
|
|
6908
|
-
console.error(
|
|
6959
|
+
console.error(chalk19.red(formatError(error)));
|
|
6909
6960
|
process.exit(1);
|
|
6910
6961
|
});
|
|
6911
6962
|
//# sourceMappingURL=cli.js.map
|