@ipation/specbridge 1.2.1 → 1.3.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 +56 -0
- package/dist/cli.js +704 -209
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +146 -13
- package/dist/index.js +153 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { Command as
|
|
5
|
-
import
|
|
4
|
+
import { Command as Command19 } from "commander";
|
|
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 join12 } from "path";
|
|
@@ -1345,11 +1345,12 @@ 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 chalk5 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";
|
|
1353
1354
|
|
|
1354
1355
|
// src/registry/loader.ts
|
|
1355
1356
|
import { join as join4 } from "path";
|
|
@@ -1382,6 +1383,10 @@ var ConstraintExceptionSchema = z2.object({
|
|
|
1382
1383
|
approvedBy: z2.string().optional(),
|
|
1383
1384
|
expiresAt: z2.string().datetime().optional()
|
|
1384
1385
|
});
|
|
1386
|
+
var ConstraintCheckSchema = z2.object({
|
|
1387
|
+
verifier: z2.string().min(1),
|
|
1388
|
+
params: z2.record(z2.unknown()).optional()
|
|
1389
|
+
});
|
|
1385
1390
|
var ConstraintSchema = z2.object({
|
|
1386
1391
|
id: z2.string().min(1).regex(/^[a-z0-9-]+$/, "Constraint ID must be lowercase alphanumeric with hyphens"),
|
|
1387
1392
|
type: ConstraintTypeSchema,
|
|
@@ -1389,6 +1394,7 @@ var ConstraintSchema = z2.object({
|
|
|
1389
1394
|
severity: SeveritySchema2,
|
|
1390
1395
|
scope: z2.string().min(1),
|
|
1391
1396
|
verifier: z2.string().optional(),
|
|
1397
|
+
check: ConstraintCheckSchema.optional(),
|
|
1392
1398
|
autofix: z2.boolean().optional(),
|
|
1393
1399
|
exceptions: z2.array(ConstraintExceptionSchema).optional()
|
|
1394
1400
|
});
|
|
@@ -2671,7 +2677,13 @@ function getVerifier(id) {
|
|
|
2671
2677
|
const factory = builtinVerifiers[id];
|
|
2672
2678
|
return factory ? factory() : null;
|
|
2673
2679
|
}
|
|
2674
|
-
function
|
|
2680
|
+
function getVerifierIds() {
|
|
2681
|
+
return Object.keys(builtinVerifiers);
|
|
2682
|
+
}
|
|
2683
|
+
function selectVerifierForConstraint(rule, specifiedVerifier, check) {
|
|
2684
|
+
if (check?.verifier) {
|
|
2685
|
+
return getVerifier(check.verifier);
|
|
2686
|
+
}
|
|
2675
2687
|
if (specifiedVerifier) {
|
|
2676
2688
|
return getVerifier(specifiedVerifier);
|
|
2677
2689
|
}
|
|
@@ -2802,10 +2814,14 @@ var VerificationEngine = class {
|
|
|
2802
2814
|
passed: 0,
|
|
2803
2815
|
failed: 0,
|
|
2804
2816
|
skipped: 0,
|
|
2805
|
-
duration: Date.now() - startTime
|
|
2817
|
+
duration: Date.now() - startTime,
|
|
2818
|
+
warnings: [],
|
|
2819
|
+
errors: []
|
|
2806
2820
|
};
|
|
2807
2821
|
}
|
|
2808
2822
|
const allViolations = [];
|
|
2823
|
+
const allWarnings = [];
|
|
2824
|
+
const allErrors = [];
|
|
2809
2825
|
let checked = 0;
|
|
2810
2826
|
let passed = 0;
|
|
2811
2827
|
let failed = 0;
|
|
@@ -2820,8 +2836,11 @@ var VerificationEngine = class {
|
|
|
2820
2836
|
decisions,
|
|
2821
2837
|
severityFilter,
|
|
2822
2838
|
cwd,
|
|
2823
|
-
|
|
2839
|
+
options.reporter,
|
|
2840
|
+
(violations, warnings, errors) => {
|
|
2824
2841
|
allViolations.push(...violations);
|
|
2842
|
+
allWarnings.push(...warnings);
|
|
2843
|
+
allErrors.push(...errors);
|
|
2825
2844
|
checked++;
|
|
2826
2845
|
if (violations.length > 0) {
|
|
2827
2846
|
failed++;
|
|
@@ -2841,7 +2860,9 @@ var VerificationEngine = class {
|
|
|
2841
2860
|
passed,
|
|
2842
2861
|
failed,
|
|
2843
2862
|
skipped: filesToVerify.length - checked,
|
|
2844
|
-
duration: timeout
|
|
2863
|
+
duration: timeout,
|
|
2864
|
+
warnings: allWarnings,
|
|
2865
|
+
errors: allErrors
|
|
2845
2866
|
};
|
|
2846
2867
|
}
|
|
2847
2868
|
} finally {
|
|
@@ -2866,23 +2887,64 @@ var VerificationEngine = class {
|
|
|
2866
2887
|
passed,
|
|
2867
2888
|
failed,
|
|
2868
2889
|
skipped,
|
|
2869
|
-
duration: Date.now() - startTime
|
|
2890
|
+
duration: Date.now() - startTime,
|
|
2891
|
+
warnings: allWarnings,
|
|
2892
|
+
errors: allErrors
|
|
2870
2893
|
};
|
|
2871
2894
|
}
|
|
2872
2895
|
/**
|
|
2873
2896
|
* Verify a single file
|
|
2874
2897
|
*/
|
|
2875
|
-
async verifyFile(filePath, decisions, severityFilter, cwd = process.cwd()) {
|
|
2898
|
+
async verifyFile(filePath, decisions, severityFilter, cwd = process.cwd(), reporter) {
|
|
2876
2899
|
const violations = [];
|
|
2900
|
+
const warnings = [];
|
|
2901
|
+
const errors = [];
|
|
2877
2902
|
const sourceFile = await this.astCache.get(filePath, this.project);
|
|
2878
|
-
if (!sourceFile) return violations;
|
|
2903
|
+
if (!sourceFile) return { violations, warnings, errors };
|
|
2879
2904
|
for (const decision of decisions) {
|
|
2880
2905
|
for (const constraint of decision.constraints) {
|
|
2881
2906
|
if (!shouldApplyConstraintToFile({ filePath, constraint, cwd, severityFilter })) {
|
|
2907
|
+
if (reporter) {
|
|
2908
|
+
reporter.add({
|
|
2909
|
+
file: filePath,
|
|
2910
|
+
decision,
|
|
2911
|
+
constraint,
|
|
2912
|
+
applied: false,
|
|
2913
|
+
reason: "File does not match scope pattern or severity filter"
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2882
2916
|
continue;
|
|
2883
2917
|
}
|
|
2884
|
-
const verifier = selectVerifierForConstraint(
|
|
2918
|
+
const verifier = selectVerifierForConstraint(
|
|
2919
|
+
constraint.rule,
|
|
2920
|
+
constraint.verifier,
|
|
2921
|
+
constraint.check
|
|
2922
|
+
);
|
|
2885
2923
|
if (!verifier) {
|
|
2924
|
+
const requestedVerifier = constraint.check?.verifier || constraint.verifier || "auto-detected";
|
|
2925
|
+
console.warn(
|
|
2926
|
+
chalk3.yellow(
|
|
2927
|
+
`Warning: No verifier found for ${decision.metadata.id}/${constraint.id}
|
|
2928
|
+
Requested: ${requestedVerifier}
|
|
2929
|
+
Available: ${getVerifierIds().join(", ")}`
|
|
2930
|
+
)
|
|
2931
|
+
);
|
|
2932
|
+
warnings.push({
|
|
2933
|
+
type: "missing_verifier",
|
|
2934
|
+
message: `No verifier found for constraint (requested: ${requestedVerifier})`,
|
|
2935
|
+
decisionId: decision.metadata.id,
|
|
2936
|
+
constraintId: constraint.id,
|
|
2937
|
+
file: filePath
|
|
2938
|
+
});
|
|
2939
|
+
if (reporter) {
|
|
2940
|
+
reporter.add({
|
|
2941
|
+
file: filePath,
|
|
2942
|
+
decision,
|
|
2943
|
+
constraint,
|
|
2944
|
+
applied: false,
|
|
2945
|
+
reason: `No verifier found (requested: ${requestedVerifier})`
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2886
2948
|
continue;
|
|
2887
2949
|
}
|
|
2888
2950
|
const ctx = {
|
|
@@ -2891,27 +2953,78 @@ var VerificationEngine = class {
|
|
|
2891
2953
|
constraint,
|
|
2892
2954
|
decisionId: decision.metadata.id
|
|
2893
2955
|
};
|
|
2956
|
+
const verificationStart = Date.now();
|
|
2894
2957
|
try {
|
|
2895
2958
|
const constraintViolations = await verifier.verify(ctx);
|
|
2896
2959
|
violations.push(...constraintViolations);
|
|
2897
|
-
|
|
2960
|
+
if (reporter) {
|
|
2961
|
+
reporter.add({
|
|
2962
|
+
file: filePath,
|
|
2963
|
+
decision,
|
|
2964
|
+
constraint,
|
|
2965
|
+
applied: true,
|
|
2966
|
+
reason: "Constraint matches file scope",
|
|
2967
|
+
selectedVerifier: verifier.id,
|
|
2968
|
+
verifierOutput: {
|
|
2969
|
+
violations: constraintViolations.length,
|
|
2970
|
+
duration: Date.now() - verificationStart
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
} catch (error) {
|
|
2975
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2976
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
2977
|
+
console.error(
|
|
2978
|
+
chalk3.red(
|
|
2979
|
+
`Error: Verifier '${verifier.id}' failed
|
|
2980
|
+
File: ${filePath}
|
|
2981
|
+
Decision: ${decision.metadata.id}/${constraint.id}
|
|
2982
|
+
Error: ${errorMessage}`
|
|
2983
|
+
)
|
|
2984
|
+
);
|
|
2985
|
+
if (errorStack) {
|
|
2986
|
+
console.error(chalk3.dim(errorStack));
|
|
2987
|
+
}
|
|
2988
|
+
errors.push({
|
|
2989
|
+
type: "verifier_exception",
|
|
2990
|
+
message: `Verifier '${verifier.id}' failed: ${errorMessage}`,
|
|
2991
|
+
decisionId: decision.metadata.id,
|
|
2992
|
+
constraintId: constraint.id,
|
|
2993
|
+
file: filePath,
|
|
2994
|
+
stack: errorStack
|
|
2995
|
+
});
|
|
2996
|
+
if (reporter) {
|
|
2997
|
+
reporter.add({
|
|
2998
|
+
file: filePath,
|
|
2999
|
+
decision,
|
|
3000
|
+
constraint,
|
|
3001
|
+
applied: true,
|
|
3002
|
+
reason: "Constraint matches file scope",
|
|
3003
|
+
selectedVerifier: verifier.id,
|
|
3004
|
+
verifierOutput: {
|
|
3005
|
+
violations: 0,
|
|
3006
|
+
duration: Date.now() - verificationStart,
|
|
3007
|
+
error: errorMessage
|
|
3008
|
+
}
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
2898
3011
|
}
|
|
2899
3012
|
}
|
|
2900
3013
|
}
|
|
2901
|
-
return violations;
|
|
3014
|
+
return { violations, warnings, errors };
|
|
2902
3015
|
}
|
|
2903
3016
|
/**
|
|
2904
3017
|
* Verify multiple files
|
|
2905
3018
|
*/
|
|
2906
|
-
async verifyFiles(files, decisions, severityFilter, cwd, onFileVerified) {
|
|
3019
|
+
async verifyFiles(files, decisions, severityFilter, cwd, reporter, onFileVerified) {
|
|
2907
3020
|
const BATCH_SIZE = 10;
|
|
2908
3021
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
2909
3022
|
const batch = files.slice(i, i + BATCH_SIZE);
|
|
2910
3023
|
const results = await Promise.all(
|
|
2911
|
-
batch.map((file) => this.verifyFile(file, decisions, severityFilter, cwd))
|
|
3024
|
+
batch.map((file) => this.verifyFile(file, decisions, severityFilter, cwd, reporter))
|
|
2912
3025
|
);
|
|
2913
|
-
for (const
|
|
2914
|
-
onFileVerified(violations);
|
|
3026
|
+
for (const result of results) {
|
|
3027
|
+
onFileVerified(result.violations, result.warnings, result.errors);
|
|
2915
3028
|
}
|
|
2916
3029
|
}
|
|
2917
3030
|
}
|
|
@@ -3029,8 +3142,77 @@ async function getChangedFiles(cwd) {
|
|
|
3029
3142
|
}
|
|
3030
3143
|
}
|
|
3031
3144
|
|
|
3145
|
+
// src/verification/explain.ts
|
|
3146
|
+
import chalk4 from "chalk";
|
|
3147
|
+
var ExplainReporter = class {
|
|
3148
|
+
entries = [];
|
|
3149
|
+
/**
|
|
3150
|
+
* Add an entry to the explanation trace
|
|
3151
|
+
*/
|
|
3152
|
+
add(entry) {
|
|
3153
|
+
this.entries.push(entry);
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Print the explanation trace
|
|
3157
|
+
*/
|
|
3158
|
+
print() {
|
|
3159
|
+
if (this.entries.length === 0) {
|
|
3160
|
+
console.log(chalk4.dim("No constraints were evaluated."));
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
console.log(chalk4.bold("\n=== Verification Explanation ===\n"));
|
|
3164
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
3165
|
+
for (const entry of this.entries) {
|
|
3166
|
+
const existing = byFile.get(entry.file) || [];
|
|
3167
|
+
existing.push(entry);
|
|
3168
|
+
byFile.set(entry.file, existing);
|
|
3169
|
+
}
|
|
3170
|
+
for (const [file, entries] of byFile) {
|
|
3171
|
+
console.log(chalk4.underline(file));
|
|
3172
|
+
for (const entry of entries) {
|
|
3173
|
+
const icon = entry.applied ? chalk4.green("\u2713") : chalk4.dim("\u2298");
|
|
3174
|
+
const constraintId = `${entry.decision.metadata.id}/${entry.constraint.id}`;
|
|
3175
|
+
console.log(` ${icon} ${constraintId}`);
|
|
3176
|
+
console.log(chalk4.dim(` ${entry.reason}`));
|
|
3177
|
+
if (entry.applied && entry.selectedVerifier) {
|
|
3178
|
+
console.log(chalk4.dim(` Verifier: ${entry.selectedVerifier}`));
|
|
3179
|
+
if (entry.verifierOutput) {
|
|
3180
|
+
if (entry.verifierOutput.error) {
|
|
3181
|
+
console.log(chalk4.red(` Error: ${entry.verifierOutput.error}`));
|
|
3182
|
+
} else {
|
|
3183
|
+
const violationText = entry.verifierOutput.violations === 1 ? "1 violation" : `${entry.verifierOutput.violations} violations`;
|
|
3184
|
+
const resultColor = entry.verifierOutput.violations > 0 ? chalk4.red : chalk4.green;
|
|
3185
|
+
console.log(
|
|
3186
|
+
chalk4.dim(` Result: `) + resultColor(violationText) + chalk4.dim(` in ${entry.verifierOutput.duration}ms`)
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
console.log("");
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
const applied = this.entries.filter((e) => e.applied).length;
|
|
3195
|
+
const skipped = this.entries.filter((e) => !e.applied).length;
|
|
3196
|
+
console.log(chalk4.bold("Summary:"));
|
|
3197
|
+
console.log(` Constraints Applied: ${chalk4.green(applied)}`);
|
|
3198
|
+
console.log(` Constraints Skipped: ${chalk4.dim(skipped)}`);
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Get all entries
|
|
3202
|
+
*/
|
|
3203
|
+
getEntries() {
|
|
3204
|
+
return [...this.entries];
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Clear all entries
|
|
3208
|
+
*/
|
|
3209
|
+
clear() {
|
|
3210
|
+
this.entries = [];
|
|
3211
|
+
}
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3032
3214
|
// src/cli/commands/verify.ts
|
|
3033
|
-
var verifyCommand = new Command3("verify").description("Verify code compliance against decisions").option("-l, --level <level>", "Verification level (commit, pr, full)", "full").option("-f, --files <patterns>", "Comma-separated file patterns to check").option("-d, --decisions <ids>", "Comma-separated decision IDs to check").option("-s, --severity <levels>", "Comma-separated severity levels (critical, high, medium, low)").option("--json", "Output as JSON").option("--incremental", "Only verify changed files (git diff --name-only --diff-filter=AM HEAD)").option("--fix", "Apply auto-fixes for supported violations").option("--dry-run", "Show what would be fixed without applying (requires --fix)").option("--interactive", "Confirm each fix interactively (requires --fix)").action(async (options) => {
|
|
3215
|
+
var verifyCommand = new Command3("verify").description("Verify code compliance against decisions").option("-l, --level <level>", "Verification level (commit, pr, full)", "full").option("-f, --files <patterns>", "Comma-separated file patterns to check").option("-d, --decisions <ids>", "Comma-separated decision IDs to check").option("-s, --severity <levels>", "Comma-separated severity levels (critical, high, medium, low)").option("--json", "Output as JSON").option("--incremental", "Only verify changed files (git diff --name-only --diff-filter=AM HEAD)").option("--explain", "Show detailed explanation of verification process").option("--fix", "Apply auto-fixes for supported violations").option("--dry-run", "Show what would be fixed without applying (requires --fix)").option("--interactive", "Confirm each fix interactively (requires --fix)").action(async (options) => {
|
|
3034
3216
|
const cwd = process.cwd();
|
|
3035
3217
|
if (!await pathExists(getSpecBridgeDir(cwd))) {
|
|
3036
3218
|
throw new NotInitializedError();
|
|
@@ -3047,13 +3229,15 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3047
3229
|
files = changed.length > 0 ? changed : [];
|
|
3048
3230
|
}
|
|
3049
3231
|
spinner.text = `Running ${level}-level verification...`;
|
|
3232
|
+
const reporter = options.explain ? new ExplainReporter() : void 0;
|
|
3050
3233
|
const engine = createVerificationEngine();
|
|
3051
3234
|
let result = await engine.verify(config, {
|
|
3052
3235
|
level,
|
|
3053
3236
|
files,
|
|
3054
3237
|
decisions,
|
|
3055
3238
|
severity,
|
|
3056
|
-
cwd
|
|
3239
|
+
cwd,
|
|
3240
|
+
reporter
|
|
3057
3241
|
});
|
|
3058
3242
|
let fixResult;
|
|
3059
3243
|
if (options.fix && result.violations.length > 0) {
|
|
@@ -3061,7 +3245,7 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3061
3245
|
if (fixableCount === 0) {
|
|
3062
3246
|
spinner.stop();
|
|
3063
3247
|
if (!options.json) {
|
|
3064
|
-
console.log(
|
|
3248
|
+
console.log(chalk5.yellow("No auto-fixable violations found"));
|
|
3065
3249
|
}
|
|
3066
3250
|
} else {
|
|
3067
3251
|
spinner.text = `Applying ${fixableCount} auto-fix(es)...`;
|
|
@@ -3085,14 +3269,41 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3085
3269
|
if (options.json) {
|
|
3086
3270
|
console.log(JSON.stringify({ ...result, autofix: fixResult }, null, 2));
|
|
3087
3271
|
} else {
|
|
3272
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
3273
|
+
console.log(chalk5.yellow.bold("\nWarnings:"));
|
|
3274
|
+
for (const warning of result.warnings) {
|
|
3275
|
+
console.log(chalk5.yellow(` \u26A0 ${warning.message}`));
|
|
3276
|
+
console.log(chalk5.dim(` ${warning.decisionId}/${warning.constraintId}`));
|
|
3277
|
+
if (warning.file) {
|
|
3278
|
+
console.log(chalk5.dim(` File: ${warning.file}`));
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
console.log("");
|
|
3282
|
+
}
|
|
3283
|
+
if (result.errors && result.errors.length > 0) {
|
|
3284
|
+
console.log(chalk5.red.bold("\nErrors:"));
|
|
3285
|
+
for (const error of result.errors) {
|
|
3286
|
+
console.log(chalk5.red(` \u2717 ${error.message}`));
|
|
3287
|
+
if (error.decisionId && error.constraintId) {
|
|
3288
|
+
console.log(chalk5.dim(` ${error.decisionId}/${error.constraintId}`));
|
|
3289
|
+
}
|
|
3290
|
+
if (error.file) {
|
|
3291
|
+
console.log(chalk5.dim(` File: ${error.file}`));
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
console.log("");
|
|
3295
|
+
}
|
|
3088
3296
|
printResult(result, level);
|
|
3089
3297
|
if (options.fix && fixResult) {
|
|
3090
|
-
console.log(
|
|
3298
|
+
console.log(chalk5.green(`\u2713 Applied ${fixResult.applied.length} fix(es)`));
|
|
3091
3299
|
if (fixResult.skipped > 0) {
|
|
3092
|
-
console.log(
|
|
3300
|
+
console.log(chalk5.yellow(`\u2298 Skipped ${fixResult.skipped} fix(es)`));
|
|
3093
3301
|
}
|
|
3094
3302
|
console.log("");
|
|
3095
3303
|
}
|
|
3304
|
+
if (options.explain && reporter) {
|
|
3305
|
+
reporter.print();
|
|
3306
|
+
}
|
|
3096
3307
|
}
|
|
3097
3308
|
if (!result.success) {
|
|
3098
3309
|
process.exit(1);
|
|
@@ -3105,8 +3316,8 @@ var verifyCommand = new Command3("verify").description("Verify code compliance a
|
|
|
3105
3316
|
function printResult(result, level) {
|
|
3106
3317
|
console.log("");
|
|
3107
3318
|
if (result.violations.length === 0) {
|
|
3108
|
-
console.log(
|
|
3109
|
-
console.log(
|
|
3319
|
+
console.log(chalk5.green("\u2713 All checks passed!"));
|
|
3320
|
+
console.log(chalk5.dim(` ${result.checked} files checked in ${result.duration}ms`));
|
|
3110
3321
|
return;
|
|
3111
3322
|
}
|
|
3112
3323
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -3116,7 +3327,7 @@ function printResult(result, level) {
|
|
|
3116
3327
|
byFile.set(violation.file, existing);
|
|
3117
3328
|
}
|
|
3118
3329
|
for (const [file, violations] of byFile) {
|
|
3119
|
-
console.log(
|
|
3330
|
+
console.log(chalk5.underline(file));
|
|
3120
3331
|
for (const v of violations) {
|
|
3121
3332
|
const typeIcon = getTypeIcon(v.type);
|
|
3122
3333
|
const severityColor = getSeverityColor(v.severity);
|
|
@@ -3124,9 +3335,9 @@ function printResult(result, level) {
|
|
|
3124
3335
|
console.log(
|
|
3125
3336
|
` ${typeIcon} ${severityColor(`[${v.severity}]`)} ${v.message}`
|
|
3126
3337
|
);
|
|
3127
|
-
console.log(
|
|
3338
|
+
console.log(chalk5.dim(` ${v.decisionId}/${v.constraintId}${location}`));
|
|
3128
3339
|
if (v.suggestion) {
|
|
3129
|
-
console.log(
|
|
3340
|
+
console.log(chalk5.cyan(` Suggestion: ${v.suggestion}`));
|
|
3130
3341
|
}
|
|
3131
3342
|
}
|
|
3132
3343
|
console.log("");
|
|
@@ -3135,29 +3346,29 @@ function printResult(result, level) {
|
|
|
3135
3346
|
const highCount = result.violations.filter((v) => v.severity === "high").length;
|
|
3136
3347
|
const mediumCount = result.violations.filter((v) => v.severity === "medium").length;
|
|
3137
3348
|
const lowCount = result.violations.filter((v) => v.severity === "low").length;
|
|
3138
|
-
console.log(
|
|
3349
|
+
console.log(chalk5.bold("Summary:"));
|
|
3139
3350
|
console.log(` Files: ${result.checked} checked, ${result.passed} passed, ${result.failed} failed`);
|
|
3140
3351
|
const violationParts = [];
|
|
3141
|
-
if (criticalCount > 0) violationParts.push(
|
|
3142
|
-
if (highCount > 0) violationParts.push(
|
|
3143
|
-
if (mediumCount > 0) violationParts.push(
|
|
3144
|
-
if (lowCount > 0) violationParts.push(
|
|
3352
|
+
if (criticalCount > 0) violationParts.push(chalk5.red(`${criticalCount} critical`));
|
|
3353
|
+
if (highCount > 0) violationParts.push(chalk5.yellow(`${highCount} high`));
|
|
3354
|
+
if (mediumCount > 0) violationParts.push(chalk5.cyan(`${mediumCount} medium`));
|
|
3355
|
+
if (lowCount > 0) violationParts.push(chalk5.dim(`${lowCount} low`));
|
|
3145
3356
|
console.log(` Violations: ${violationParts.join(", ")}`);
|
|
3146
3357
|
console.log(` Duration: ${result.duration}ms`);
|
|
3147
3358
|
if (!result.success) {
|
|
3148
3359
|
console.log("");
|
|
3149
3360
|
const blockingTypes = level === "commit" ? "invariant or critical" : level === "pr" ? "invariant, critical, or high" : "invariant";
|
|
3150
|
-
console.log(
|
|
3361
|
+
console.log(chalk5.red(`\u2717 Verification failed. ${blockingTypes} violations must be resolved.`));
|
|
3151
3362
|
}
|
|
3152
3363
|
}
|
|
3153
3364
|
function getTypeIcon(type) {
|
|
3154
3365
|
switch (type) {
|
|
3155
3366
|
case "invariant":
|
|
3156
|
-
return
|
|
3367
|
+
return chalk5.red("\u25CF");
|
|
3157
3368
|
case "convention":
|
|
3158
|
-
return
|
|
3369
|
+
return chalk5.yellow("\u25CF");
|
|
3159
3370
|
case "guideline":
|
|
3160
|
-
return
|
|
3371
|
+
return chalk5.green("\u25CF");
|
|
3161
3372
|
default:
|
|
3162
3373
|
return "\u25CB";
|
|
3163
3374
|
}
|
|
@@ -3165,15 +3376,15 @@ function getTypeIcon(type) {
|
|
|
3165
3376
|
function getSeverityColor(severity) {
|
|
3166
3377
|
switch (severity) {
|
|
3167
3378
|
case "critical":
|
|
3168
|
-
return
|
|
3379
|
+
return chalk5.red;
|
|
3169
3380
|
case "high":
|
|
3170
|
-
return
|
|
3381
|
+
return chalk5.yellow;
|
|
3171
3382
|
case "medium":
|
|
3172
|
-
return
|
|
3383
|
+
return chalk5.cyan;
|
|
3173
3384
|
case "low":
|
|
3174
|
-
return
|
|
3385
|
+
return chalk5.dim;
|
|
3175
3386
|
default:
|
|
3176
|
-
return
|
|
3387
|
+
return chalk5.white;
|
|
3177
3388
|
}
|
|
3178
3389
|
}
|
|
3179
3390
|
|
|
@@ -3182,15 +3393,15 @@ import { Command as Command8 } from "commander";
|
|
|
3182
3393
|
|
|
3183
3394
|
// src/cli/commands/decision/list.ts
|
|
3184
3395
|
import { Command as Command4 } from "commander";
|
|
3185
|
-
import
|
|
3396
|
+
import chalk6 from "chalk";
|
|
3186
3397
|
import { table } from "table";
|
|
3187
3398
|
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) => {
|
|
3188
3399
|
const registry = createRegistry();
|
|
3189
3400
|
const result = await registry.load();
|
|
3190
3401
|
if (result.errors.length > 0) {
|
|
3191
|
-
console.warn(
|
|
3402
|
+
console.warn(chalk6.yellow("\nWarnings:"));
|
|
3192
3403
|
for (const err of result.errors) {
|
|
3193
|
-
console.warn(
|
|
3404
|
+
console.warn(chalk6.yellow(` - ${err.filePath}: ${err.error}`));
|
|
3194
3405
|
}
|
|
3195
3406
|
console.log("");
|
|
3196
3407
|
}
|
|
@@ -3203,7 +3414,7 @@ var listDecisions = new Command4("list").description("List all architectural dec
|
|
|
3203
3414
|
}
|
|
3204
3415
|
const decisions = registry.getAll(filter);
|
|
3205
3416
|
if (decisions.length === 0) {
|
|
3206
|
-
console.log(
|
|
3417
|
+
console.log(chalk6.yellow("No decisions found."));
|
|
3207
3418
|
return;
|
|
3208
3419
|
}
|
|
3209
3420
|
if (options.json) {
|
|
@@ -3212,11 +3423,11 @@ var listDecisions = new Command4("list").description("List all architectural dec
|
|
|
3212
3423
|
}
|
|
3213
3424
|
const data = [
|
|
3214
3425
|
[
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3426
|
+
chalk6.bold("ID"),
|
|
3427
|
+
chalk6.bold("Title"),
|
|
3428
|
+
chalk6.bold("Status"),
|
|
3429
|
+
chalk6.bold("Constraints"),
|
|
3430
|
+
chalk6.bold("Tags")
|
|
3220
3431
|
]
|
|
3221
3432
|
];
|
|
3222
3433
|
for (const decision of decisions) {
|
|
@@ -3250,20 +3461,20 @@ var listDecisions = new Command4("list").description("List all architectural dec
|
|
|
3250
3461
|
},
|
|
3251
3462
|
drawHorizontalLine: (index) => index === 1
|
|
3252
3463
|
}));
|
|
3253
|
-
console.log(
|
|
3464
|
+
console.log(chalk6.dim(`Total: ${decisions.length} decision(s)`));
|
|
3254
3465
|
});
|
|
3255
3466
|
function getStatusColor(status) {
|
|
3256
3467
|
switch (status) {
|
|
3257
3468
|
case "active":
|
|
3258
|
-
return
|
|
3469
|
+
return chalk6.green;
|
|
3259
3470
|
case "draft":
|
|
3260
|
-
return
|
|
3471
|
+
return chalk6.yellow;
|
|
3261
3472
|
case "deprecated":
|
|
3262
|
-
return
|
|
3473
|
+
return chalk6.gray;
|
|
3263
3474
|
case "superseded":
|
|
3264
|
-
return
|
|
3475
|
+
return chalk6.blue;
|
|
3265
3476
|
default:
|
|
3266
|
-
return
|
|
3477
|
+
return chalk6.white;
|
|
3267
3478
|
}
|
|
3268
3479
|
}
|
|
3269
3480
|
function getConstraintTypeSummary(types) {
|
|
@@ -3276,9 +3487,9 @@ function getConstraintTypeSummary(types) {
|
|
|
3276
3487
|
counts[type]++;
|
|
3277
3488
|
}
|
|
3278
3489
|
const parts = [];
|
|
3279
|
-
if (counts.invariant > 0) parts.push(
|
|
3280
|
-
if (counts.convention > 0) parts.push(
|
|
3281
|
-
if (counts.guideline > 0) parts.push(
|
|
3490
|
+
if (counts.invariant > 0) parts.push(chalk6.red(`${counts.invariant}I`));
|
|
3491
|
+
if (counts.convention > 0) parts.push(chalk6.yellow(`${counts.convention}C`));
|
|
3492
|
+
if (counts.guideline > 0) parts.push(chalk6.green(`${counts.guideline}G`));
|
|
3282
3493
|
return parts.join(" ") || "-";
|
|
3283
3494
|
}
|
|
3284
3495
|
function truncate(str, length) {
|
|
@@ -3288,7 +3499,7 @@ function truncate(str, length) {
|
|
|
3288
3499
|
|
|
3289
3500
|
// src/cli/commands/decision/show.ts
|
|
3290
3501
|
import { Command as Command5 } from "commander";
|
|
3291
|
-
import
|
|
3502
|
+
import chalk7 from "chalk";
|
|
3292
3503
|
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) => {
|
|
3293
3504
|
const registry = createRegistry();
|
|
3294
3505
|
await registry.load();
|
|
@@ -3301,74 +3512,74 @@ var showDecision = new Command5("show").description("Show details of a specific
|
|
|
3301
3512
|
});
|
|
3302
3513
|
function printDecision(decision) {
|
|
3303
3514
|
const { metadata, decision: content, constraints } = decision;
|
|
3304
|
-
console.log(
|
|
3515
|
+
console.log(chalk7.bold.blue(`
|
|
3305
3516
|
${metadata.title}`));
|
|
3306
|
-
console.log(
|
|
3517
|
+
console.log(chalk7.dim(`ID: ${metadata.id}`));
|
|
3307
3518
|
console.log("");
|
|
3308
|
-
console.log(
|
|
3309
|
-
console.log(
|
|
3519
|
+
console.log(chalk7.bold("Status:"), getStatusBadge(metadata.status));
|
|
3520
|
+
console.log(chalk7.bold("Owners:"), metadata.owners.join(", "));
|
|
3310
3521
|
if (metadata.tags && metadata.tags.length > 0) {
|
|
3311
|
-
console.log(
|
|
3522
|
+
console.log(chalk7.bold("Tags:"), metadata.tags.map((t) => chalk7.cyan(t)).join(", "));
|
|
3312
3523
|
}
|
|
3313
3524
|
if (metadata.createdAt) {
|
|
3314
|
-
console.log(
|
|
3525
|
+
console.log(chalk7.bold("Created:"), metadata.createdAt);
|
|
3315
3526
|
}
|
|
3316
3527
|
if (metadata.supersededBy) {
|
|
3317
|
-
console.log(
|
|
3528
|
+
console.log(chalk7.bold("Superseded by:"), chalk7.yellow(metadata.supersededBy));
|
|
3318
3529
|
}
|
|
3319
3530
|
console.log("");
|
|
3320
|
-
console.log(
|
|
3531
|
+
console.log(chalk7.bold.underline("Summary"));
|
|
3321
3532
|
console.log(content.summary);
|
|
3322
3533
|
console.log("");
|
|
3323
|
-
console.log(
|
|
3534
|
+
console.log(chalk7.bold.underline("Rationale"));
|
|
3324
3535
|
console.log(content.rationale);
|
|
3325
3536
|
console.log("");
|
|
3326
3537
|
if (content.context) {
|
|
3327
|
-
console.log(
|
|
3538
|
+
console.log(chalk7.bold.underline("Context"));
|
|
3328
3539
|
console.log(content.context);
|
|
3329
3540
|
console.log("");
|
|
3330
3541
|
}
|
|
3331
3542
|
if (content.consequences && content.consequences.length > 0) {
|
|
3332
|
-
console.log(
|
|
3543
|
+
console.log(chalk7.bold.underline("Consequences"));
|
|
3333
3544
|
for (const consequence of content.consequences) {
|
|
3334
3545
|
console.log(` \u2022 ${consequence}`);
|
|
3335
3546
|
}
|
|
3336
3547
|
console.log("");
|
|
3337
3548
|
}
|
|
3338
|
-
console.log(
|
|
3549
|
+
console.log(chalk7.bold.underline(`Constraints (${constraints.length})`));
|
|
3339
3550
|
for (const constraint of constraints) {
|
|
3340
3551
|
const typeIcon = getTypeIcon2(constraint.type);
|
|
3341
3552
|
const severityBadge = getSeverityBadge(constraint.severity);
|
|
3342
3553
|
console.log(`
|
|
3343
|
-
${typeIcon} ${
|
|
3554
|
+
${typeIcon} ${chalk7.bold(constraint.id)} ${severityBadge}`);
|
|
3344
3555
|
console.log(` ${constraint.rule}`);
|
|
3345
|
-
console.log(
|
|
3556
|
+
console.log(chalk7.dim(` Scope: ${constraint.scope}`));
|
|
3346
3557
|
if (constraint.verifier) {
|
|
3347
|
-
console.log(
|
|
3558
|
+
console.log(chalk7.dim(` Verifier: ${constraint.verifier}`));
|
|
3348
3559
|
}
|
|
3349
3560
|
if (constraint.exceptions && constraint.exceptions.length > 0) {
|
|
3350
|
-
console.log(
|
|
3561
|
+
console.log(chalk7.dim(` Exceptions: ${constraint.exceptions.length}`));
|
|
3351
3562
|
}
|
|
3352
3563
|
}
|
|
3353
3564
|
console.log("");
|
|
3354
3565
|
if (decision.verification?.automated && decision.verification.automated.length > 0) {
|
|
3355
|
-
console.log(
|
|
3566
|
+
console.log(chalk7.bold.underline("Automated Verification"));
|
|
3356
3567
|
for (const check of decision.verification.automated) {
|
|
3357
3568
|
console.log(` \u2022 ${check.check} (${check.frequency})`);
|
|
3358
|
-
console.log(
|
|
3569
|
+
console.log(chalk7.dim(` Target: ${check.target}`));
|
|
3359
3570
|
}
|
|
3360
3571
|
console.log("");
|
|
3361
3572
|
}
|
|
3362
3573
|
if (decision.links) {
|
|
3363
3574
|
const { related, supersedes, references } = decision.links;
|
|
3364
3575
|
if (related && related.length > 0) {
|
|
3365
|
-
console.log(
|
|
3576
|
+
console.log(chalk7.bold("Related:"), related.join(", "));
|
|
3366
3577
|
}
|
|
3367
3578
|
if (supersedes && supersedes.length > 0) {
|
|
3368
|
-
console.log(
|
|
3579
|
+
console.log(chalk7.bold("Supersedes:"), supersedes.join(", "));
|
|
3369
3580
|
}
|
|
3370
3581
|
if (references && references.length > 0) {
|
|
3371
|
-
console.log(
|
|
3582
|
+
console.log(chalk7.bold("References:"));
|
|
3372
3583
|
for (const ref of references) {
|
|
3373
3584
|
console.log(` \u2022 ${ref}`);
|
|
3374
3585
|
}
|
|
@@ -3378,13 +3589,13 @@ ${metadata.title}`));
|
|
|
3378
3589
|
function getStatusBadge(status) {
|
|
3379
3590
|
switch (status) {
|
|
3380
3591
|
case "active":
|
|
3381
|
-
return
|
|
3592
|
+
return chalk7.bgGreen.black(" ACTIVE ");
|
|
3382
3593
|
case "draft":
|
|
3383
|
-
return
|
|
3594
|
+
return chalk7.bgYellow.black(" DRAFT ");
|
|
3384
3595
|
case "deprecated":
|
|
3385
|
-
return
|
|
3596
|
+
return chalk7.bgGray.white(" DEPRECATED ");
|
|
3386
3597
|
case "superseded":
|
|
3387
|
-
return
|
|
3598
|
+
return chalk7.bgBlue.white(" SUPERSEDED ");
|
|
3388
3599
|
default:
|
|
3389
3600
|
return status;
|
|
3390
3601
|
}
|
|
@@ -3392,11 +3603,11 @@ function getStatusBadge(status) {
|
|
|
3392
3603
|
function getTypeIcon2(type) {
|
|
3393
3604
|
switch (type) {
|
|
3394
3605
|
case "invariant":
|
|
3395
|
-
return
|
|
3606
|
+
return chalk7.red("\u25CF");
|
|
3396
3607
|
case "convention":
|
|
3397
|
-
return
|
|
3608
|
+
return chalk7.yellow("\u25CF");
|
|
3398
3609
|
case "guideline":
|
|
3399
|
-
return
|
|
3610
|
+
return chalk7.green("\u25CF");
|
|
3400
3611
|
default:
|
|
3401
3612
|
return "\u25CB";
|
|
3402
3613
|
}
|
|
@@ -3404,13 +3615,13 @@ function getTypeIcon2(type) {
|
|
|
3404
3615
|
function getSeverityBadge(severity) {
|
|
3405
3616
|
switch (severity) {
|
|
3406
3617
|
case "critical":
|
|
3407
|
-
return
|
|
3618
|
+
return chalk7.bgRed.white(" CRITICAL ");
|
|
3408
3619
|
case "high":
|
|
3409
|
-
return
|
|
3620
|
+
return chalk7.bgYellow.black(" HIGH ");
|
|
3410
3621
|
case "medium":
|
|
3411
|
-
return
|
|
3622
|
+
return chalk7.bgCyan.black(" MEDIUM ");
|
|
3412
3623
|
case "low":
|
|
3413
|
-
return
|
|
3624
|
+
return chalk7.bgGray.white(" LOW ");
|
|
3414
3625
|
default:
|
|
3415
3626
|
return severity;
|
|
3416
3627
|
}
|
|
@@ -3418,7 +3629,7 @@ function getSeverityBadge(severity) {
|
|
|
3418
3629
|
|
|
3419
3630
|
// src/cli/commands/decision/validate.ts
|
|
3420
3631
|
import { Command as Command6 } from "commander";
|
|
3421
|
-
import
|
|
3632
|
+
import chalk8 from "chalk";
|
|
3422
3633
|
import ora4 from "ora";
|
|
3423
3634
|
import { join as join5 } from "path";
|
|
3424
3635
|
var validateDecisions = new Command6("validate").description("Validate decision files").option("-f, --file <path>", "Validate a specific file").action(async (options) => {
|
|
@@ -3457,14 +3668,14 @@ var validateDecisions = new Command6("validate").description("Validate decision
|
|
|
3457
3668
|
}
|
|
3458
3669
|
spinner.stop();
|
|
3459
3670
|
if (invalid === 0) {
|
|
3460
|
-
console.log(
|
|
3671
|
+
console.log(chalk8.green(`\u2713 All ${valid} decision file(s) are valid.`));
|
|
3461
3672
|
} else {
|
|
3462
|
-
console.log(
|
|
3673
|
+
console.log(chalk8.red(`\u2717 ${invalid} of ${files.length} decision file(s) have errors.
|
|
3463
3674
|
`));
|
|
3464
3675
|
for (const { file, errors: fileErrors } of errors) {
|
|
3465
|
-
console.log(
|
|
3676
|
+
console.log(chalk8.red(`File: ${file}`));
|
|
3466
3677
|
for (const err of fileErrors) {
|
|
3467
|
-
console.log(
|
|
3678
|
+
console.log(chalk8.dim(` - ${err}`));
|
|
3468
3679
|
}
|
|
3469
3680
|
console.log("");
|
|
3470
3681
|
}
|
|
@@ -3478,7 +3689,7 @@ var validateDecisions = new Command6("validate").description("Validate decision
|
|
|
3478
3689
|
|
|
3479
3690
|
// src/cli/commands/decision/create.ts
|
|
3480
3691
|
import { Command as Command7 } from "commander";
|
|
3481
|
-
import
|
|
3692
|
+
import chalk9 from "chalk";
|
|
3482
3693
|
import { join as join6 } from "path";
|
|
3483
3694
|
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) => {
|
|
3484
3695
|
const cwd = process.cwd();
|
|
@@ -3486,13 +3697,13 @@ var createDecision = new Command7("create").description("Create a new decision f
|
|
|
3486
3697
|
throw new NotInitializedError();
|
|
3487
3698
|
}
|
|
3488
3699
|
if (!/^[a-z0-9-]+$/.test(id)) {
|
|
3489
|
-
console.error(
|
|
3700
|
+
console.error(chalk9.red("Error: Decision ID must be lowercase alphanumeric with hyphens only."));
|
|
3490
3701
|
process.exit(1);
|
|
3491
3702
|
}
|
|
3492
3703
|
const decisionsDir = getDecisionsDir(cwd);
|
|
3493
3704
|
const filePath = join6(decisionsDir, `${id}.decision.yaml`);
|
|
3494
3705
|
if (await pathExists(filePath)) {
|
|
3495
|
-
console.error(
|
|
3706
|
+
console.error(chalk9.red(`Error: Decision file already exists: ${filePath}`));
|
|
3496
3707
|
process.exit(1);
|
|
3497
3708
|
}
|
|
3498
3709
|
const decision = {
|
|
@@ -3528,13 +3739,13 @@ var createDecision = new Command7("create").description("Create a new decision f
|
|
|
3528
3739
|
}
|
|
3529
3740
|
};
|
|
3530
3741
|
await writeTextFile(filePath, stringifyYaml(decision));
|
|
3531
|
-
console.log(
|
|
3742
|
+
console.log(chalk9.green(`\u2713 Created decision: ${filePath}`));
|
|
3532
3743
|
console.log("");
|
|
3533
|
-
console.log(
|
|
3744
|
+
console.log(chalk9.cyan("Next steps:"));
|
|
3534
3745
|
console.log(` 1. Edit the file to add rationale, context, and consequences`);
|
|
3535
3746
|
console.log(` 2. Define constraints with appropriate scopes`);
|
|
3536
|
-
console.log(` 3. Run ${
|
|
3537
|
-
console.log(` 4. Change status from ${
|
|
3747
|
+
console.log(` 3. Run ${chalk9.bold("specbridge decision validate")} to check syntax`);
|
|
3748
|
+
console.log(` 4. Change status from ${chalk9.yellow("draft")} to ${chalk9.green("active")} when ready`);
|
|
3538
3749
|
});
|
|
3539
3750
|
|
|
3540
3751
|
// src/cli/commands/decision/index.ts
|
|
@@ -3542,7 +3753,7 @@ var decisionCommand = new Command8("decision").description("Manage architectural
|
|
|
3542
3753
|
|
|
3543
3754
|
// src/cli/commands/hook.ts
|
|
3544
3755
|
import { Command as Command9 } from "commander";
|
|
3545
|
-
import
|
|
3756
|
+
import chalk10 from "chalk";
|
|
3546
3757
|
import ora5 from "ora";
|
|
3547
3758
|
import { join as join7 } from "path";
|
|
3548
3759
|
var HOOK_SCRIPT = `#!/bin/sh
|
|
@@ -3574,9 +3785,9 @@ function createHookCommand() {
|
|
|
3574
3785
|
} else if (options.lefthook) {
|
|
3575
3786
|
spinner.succeed("Lefthook detected");
|
|
3576
3787
|
console.log("");
|
|
3577
|
-
console.log(
|
|
3788
|
+
console.log(chalk10.cyan("Add this to your lefthook.yml:"));
|
|
3578
3789
|
console.log("");
|
|
3579
|
-
console.log(
|
|
3790
|
+
console.log(chalk10.dim(`pre-commit:
|
|
3580
3791
|
commands:
|
|
3581
3792
|
specbridge:
|
|
3582
3793
|
glob: "*.{ts,tsx}"
|
|
@@ -3591,9 +3802,9 @@ function createHookCommand() {
|
|
|
3591
3802
|
} else if (await pathExists(join7(cwd, "lefthook.yml"))) {
|
|
3592
3803
|
spinner.succeed("Lefthook detected");
|
|
3593
3804
|
console.log("");
|
|
3594
|
-
console.log(
|
|
3805
|
+
console.log(chalk10.cyan("Add this to your lefthook.yml:"));
|
|
3595
3806
|
console.log("");
|
|
3596
|
-
console.log(
|
|
3807
|
+
console.log(chalk10.dim(`pre-commit:
|
|
3597
3808
|
commands:
|
|
3598
3809
|
specbridge:
|
|
3599
3810
|
glob: "*.{ts,tsx}"
|
|
@@ -3608,7 +3819,7 @@ function createHookCommand() {
|
|
|
3608
3819
|
}
|
|
3609
3820
|
if (await pathExists(hookPath) && !options.force) {
|
|
3610
3821
|
spinner.fail("Hook already exists");
|
|
3611
|
-
console.log(
|
|
3822
|
+
console.log(chalk10.yellow(`Use --force to overwrite: ${hookPath}`));
|
|
3612
3823
|
return;
|
|
3613
3824
|
}
|
|
3614
3825
|
await writeTextFile(hookPath, hookContent);
|
|
@@ -3618,9 +3829,9 @@ function createHookCommand() {
|
|
|
3618
3829
|
} catch {
|
|
3619
3830
|
}
|
|
3620
3831
|
spinner.succeed("Pre-commit hook installed");
|
|
3621
|
-
console.log(
|
|
3832
|
+
console.log(chalk10.dim(` Path: ${hookPath}`));
|
|
3622
3833
|
console.log("");
|
|
3623
|
-
console.log(
|
|
3834
|
+
console.log(chalk10.cyan("The hook will run on each commit and verify staged files."));
|
|
3624
3835
|
} catch (error) {
|
|
3625
3836
|
spinner.fail("Failed to install hook");
|
|
3626
3837
|
throw error;
|
|
@@ -3656,21 +3867,21 @@ function createHookCommand() {
|
|
|
3656
3867
|
cwd
|
|
3657
3868
|
});
|
|
3658
3869
|
if (result.violations.length === 0) {
|
|
3659
|
-
console.log(
|
|
3870
|
+
console.log(chalk10.green("\u2713 SpecBridge: All checks passed"));
|
|
3660
3871
|
process.exit(0);
|
|
3661
3872
|
}
|
|
3662
|
-
console.log(
|
|
3873
|
+
console.log(chalk10.red(`\u2717 SpecBridge: ${result.violations.length} violation(s) found`));
|
|
3663
3874
|
console.log("");
|
|
3664
3875
|
for (const v of result.violations) {
|
|
3665
3876
|
const location = v.line ? `:${v.line}` : "";
|
|
3666
3877
|
console.log(` ${v.file}${location}: ${v.message}`);
|
|
3667
|
-
console.log(
|
|
3878
|
+
console.log(chalk10.dim(` [${v.severity}] ${v.decisionId}/${v.constraintId}`));
|
|
3668
3879
|
}
|
|
3669
3880
|
console.log("");
|
|
3670
|
-
console.log(
|
|
3881
|
+
console.log(chalk10.yellow("Run `specbridge verify` for full details."));
|
|
3671
3882
|
process.exit(result.success ? 0 : 1);
|
|
3672
3883
|
} catch (error) {
|
|
3673
|
-
console.error(
|
|
3884
|
+
console.error(chalk10.red("SpecBridge verification failed"));
|
|
3674
3885
|
console.error(error instanceof Error ? error.message : String(error));
|
|
3675
3886
|
process.exit(1);
|
|
3676
3887
|
}
|
|
@@ -3709,7 +3920,7 @@ var hookCommand = createHookCommand();
|
|
|
3709
3920
|
|
|
3710
3921
|
// src/cli/commands/report.ts
|
|
3711
3922
|
import { Command as Command10 } from "commander";
|
|
3712
|
-
import
|
|
3923
|
+
import chalk12 from "chalk";
|
|
3713
3924
|
import ora6 from "ora";
|
|
3714
3925
|
import { join as join9 } from "path";
|
|
3715
3926
|
|
|
@@ -3764,54 +3975,54 @@ async function generateReport(config, options = {}) {
|
|
|
3764
3975
|
}
|
|
3765
3976
|
|
|
3766
3977
|
// src/reporting/formats/console.ts
|
|
3767
|
-
import
|
|
3978
|
+
import chalk11 from "chalk";
|
|
3768
3979
|
import { table as table2 } from "table";
|
|
3769
3980
|
function formatConsoleReport(report) {
|
|
3770
3981
|
const lines = [];
|
|
3771
3982
|
lines.push("");
|
|
3772
|
-
lines.push(
|
|
3773
|
-
lines.push(
|
|
3774
|
-
lines.push(
|
|
3983
|
+
lines.push(chalk11.bold.blue("SpecBridge Compliance Report"));
|
|
3984
|
+
lines.push(chalk11.dim(`Generated: ${new Date(report.timestamp).toLocaleString()}`));
|
|
3985
|
+
lines.push(chalk11.dim(`Project: ${report.project}`));
|
|
3775
3986
|
lines.push("");
|
|
3776
3987
|
const complianceColor = getComplianceColor(report.summary.compliance);
|
|
3777
|
-
lines.push(
|
|
3988
|
+
lines.push(chalk11.bold("Overall Compliance"));
|
|
3778
3989
|
lines.push(` ${complianceColor(formatComplianceBar(report.summary.compliance))} ${complianceColor(`${report.summary.compliance}%`)}`);
|
|
3779
3990
|
lines.push("");
|
|
3780
|
-
lines.push(
|
|
3991
|
+
lines.push(chalk11.bold("Summary"));
|
|
3781
3992
|
lines.push(` Decisions: ${report.summary.activeDecisions} active / ${report.summary.totalDecisions} total`);
|
|
3782
3993
|
lines.push(` Constraints: ${report.summary.totalConstraints}`);
|
|
3783
3994
|
lines.push("");
|
|
3784
|
-
lines.push(
|
|
3995
|
+
lines.push(chalk11.bold("Violations"));
|
|
3785
3996
|
const { violations } = report.summary;
|
|
3786
3997
|
const violationParts = [];
|
|
3787
3998
|
if (violations.critical > 0) {
|
|
3788
|
-
violationParts.push(
|
|
3999
|
+
violationParts.push(chalk11.red(`${violations.critical} critical`));
|
|
3789
4000
|
}
|
|
3790
4001
|
if (violations.high > 0) {
|
|
3791
|
-
violationParts.push(
|
|
4002
|
+
violationParts.push(chalk11.yellow(`${violations.high} high`));
|
|
3792
4003
|
}
|
|
3793
4004
|
if (violations.medium > 0) {
|
|
3794
|
-
violationParts.push(
|
|
4005
|
+
violationParts.push(chalk11.cyan(`${violations.medium} medium`));
|
|
3795
4006
|
}
|
|
3796
4007
|
if (violations.low > 0) {
|
|
3797
|
-
violationParts.push(
|
|
4008
|
+
violationParts.push(chalk11.dim(`${violations.low} low`));
|
|
3798
4009
|
}
|
|
3799
4010
|
if (violationParts.length > 0) {
|
|
3800
4011
|
lines.push(` ${violationParts.join(" | ")}`);
|
|
3801
4012
|
} else {
|
|
3802
|
-
lines.push(
|
|
4013
|
+
lines.push(chalk11.green(" No violations"));
|
|
3803
4014
|
}
|
|
3804
4015
|
lines.push("");
|
|
3805
4016
|
if (report.byDecision.length > 0) {
|
|
3806
|
-
lines.push(
|
|
4017
|
+
lines.push(chalk11.bold("By Decision"));
|
|
3807
4018
|
lines.push("");
|
|
3808
4019
|
const tableData = [
|
|
3809
4020
|
[
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
4021
|
+
chalk11.bold("Decision"),
|
|
4022
|
+
chalk11.bold("Status"),
|
|
4023
|
+
chalk11.bold("Constraints"),
|
|
4024
|
+
chalk11.bold("Violations"),
|
|
4025
|
+
chalk11.bold("Compliance")
|
|
3815
4026
|
]
|
|
3816
4027
|
];
|
|
3817
4028
|
for (const dec of report.byDecision) {
|
|
@@ -3821,7 +4032,7 @@ function formatConsoleReport(report) {
|
|
|
3821
4032
|
truncate2(dec.title, 40),
|
|
3822
4033
|
statusColor(dec.status),
|
|
3823
4034
|
String(dec.constraints),
|
|
3824
|
-
dec.violations > 0 ?
|
|
4035
|
+
dec.violations > 0 ? chalk11.red(String(dec.violations)) : chalk11.green("0"),
|
|
3825
4036
|
compColor(`${dec.compliance}%`)
|
|
3826
4037
|
]);
|
|
3827
4038
|
}
|
|
@@ -3855,23 +4066,23 @@ function formatComplianceBar(compliance) {
|
|
|
3855
4066
|
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
3856
4067
|
}
|
|
3857
4068
|
function getComplianceColor(compliance) {
|
|
3858
|
-
if (compliance >= 90) return
|
|
3859
|
-
if (compliance >= 70) return
|
|
3860
|
-
if (compliance >= 50) return
|
|
3861
|
-
return
|
|
4069
|
+
if (compliance >= 90) return chalk11.green;
|
|
4070
|
+
if (compliance >= 70) return chalk11.yellow;
|
|
4071
|
+
if (compliance >= 50) return chalk11.hex("#FFA500");
|
|
4072
|
+
return chalk11.red;
|
|
3862
4073
|
}
|
|
3863
4074
|
function getStatusColor2(status) {
|
|
3864
4075
|
switch (status) {
|
|
3865
4076
|
case "active":
|
|
3866
|
-
return
|
|
4077
|
+
return chalk11.green;
|
|
3867
4078
|
case "draft":
|
|
3868
|
-
return
|
|
4079
|
+
return chalk11.yellow;
|
|
3869
4080
|
case "deprecated":
|
|
3870
|
-
return
|
|
4081
|
+
return chalk11.gray;
|
|
3871
4082
|
case "superseded":
|
|
3872
|
-
return
|
|
4083
|
+
return chalk11.blue;
|
|
3873
4084
|
default:
|
|
3874
|
-
return
|
|
4085
|
+
return chalk11.white;
|
|
3875
4086
|
}
|
|
3876
4087
|
}
|
|
3877
4088
|
function truncate2(str, length) {
|
|
@@ -4243,29 +4454,29 @@ var reportCommand = new Command10("report").description("Generate compliance rep
|
|
|
4243
4454
|
const storage = new ReportStorage(cwd);
|
|
4244
4455
|
await storage.save(report);
|
|
4245
4456
|
if (options.trend) {
|
|
4246
|
-
console.log("\n" +
|
|
4457
|
+
console.log("\n" + chalk12.blue.bold("=== Compliance Trend Analysis ===\n"));
|
|
4247
4458
|
const days = parseInt(options.days || "30", 10);
|
|
4248
4459
|
const history = await storage.loadHistory(days);
|
|
4249
4460
|
if (history.length < 2) {
|
|
4250
|
-
console.log(
|
|
4461
|
+
console.log(chalk12.yellow(`Not enough data for trend analysis. Found ${history.length} report(s), need at least 2.`));
|
|
4251
4462
|
} else {
|
|
4252
4463
|
const trend = await analyzeTrend(history);
|
|
4253
|
-
console.log(
|
|
4464
|
+
console.log(chalk12.bold(`Period: ${trend.period.start} to ${trend.period.end} (${trend.period.days} days)`));
|
|
4254
4465
|
console.log(`
|
|
4255
4466
|
Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.endCompliance}% (${trend.overall.change > 0 ? "+" : ""}${trend.overall.change.toFixed(1)}%)`);
|
|
4256
4467
|
const trendEmoji = trend.overall.trend === "improving" ? "\u{1F4C8}" : trend.overall.trend === "degrading" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
4257
|
-
const trendColor = trend.overall.trend === "improving" ?
|
|
4468
|
+
const trendColor = trend.overall.trend === "improving" ? chalk12.green : trend.overall.trend === "degrading" ? chalk12.red : chalk12.yellow;
|
|
4258
4469
|
console.log(trendColor(`${trendEmoji} Trend: ${trend.overall.trend.toUpperCase()}`));
|
|
4259
4470
|
const degrading = trend.decisions.filter((d) => d.trend === "degrading").slice(0, 3);
|
|
4260
4471
|
if (degrading.length > 0) {
|
|
4261
|
-
console.log(
|
|
4472
|
+
console.log(chalk12.red("\n\u26A0\uFE0F Most Degraded Decisions:"));
|
|
4262
4473
|
degrading.forEach((d) => {
|
|
4263
4474
|
console.log(` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (${d.change.toFixed(1)}%)`);
|
|
4264
4475
|
});
|
|
4265
4476
|
}
|
|
4266
4477
|
const improving = trend.decisions.filter((d) => d.trend === "improving").slice(0, 3);
|
|
4267
4478
|
if (improving.length > 0) {
|
|
4268
|
-
console.log(
|
|
4479
|
+
console.log(chalk12.green("\n\u2705 Most Improved Decisions:"));
|
|
4269
4480
|
improving.forEach((d) => {
|
|
4270
4481
|
console.log(` \u2022 ${d.title}: ${d.startCompliance}% \u2192 ${d.endCompliance}% (+${d.change.toFixed(1)}%)`);
|
|
4271
4482
|
});
|
|
@@ -4274,26 +4485,26 @@ Overall Compliance: ${trend.overall.startCompliance}% \u2192 ${trend.overall.end
|
|
|
4274
4485
|
console.log("");
|
|
4275
4486
|
}
|
|
4276
4487
|
if (options.drift) {
|
|
4277
|
-
console.log("\n" +
|
|
4488
|
+
console.log("\n" + chalk12.blue.bold("=== Drift Analysis ===\n"));
|
|
4278
4489
|
const history = await storage.loadHistory(2);
|
|
4279
4490
|
if (history.length < 2) {
|
|
4280
|
-
console.log(
|
|
4491
|
+
console.log(chalk12.yellow("Not enough data for drift analysis. Need at least 2 reports."));
|
|
4281
4492
|
} else {
|
|
4282
4493
|
const currentEntry = history[0];
|
|
4283
4494
|
const previousEntry = history[1];
|
|
4284
4495
|
if (!currentEntry || !previousEntry) {
|
|
4285
|
-
console.log(
|
|
4496
|
+
console.log(chalk12.yellow("Invalid history data."));
|
|
4286
4497
|
return;
|
|
4287
4498
|
}
|
|
4288
4499
|
const drift = await detectDrift(currentEntry.report, previousEntry.report);
|
|
4289
|
-
console.log(
|
|
4500
|
+
console.log(chalk12.bold(`Comparing: ${previousEntry.timestamp} vs ${currentEntry.timestamp}`));
|
|
4290
4501
|
console.log(`
|
|
4291
4502
|
Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceChange.toFixed(1)}%`);
|
|
4292
4503
|
const driftEmoji = drift.trend === "improving" ? "\u{1F4C8}" : drift.trend === "degrading" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
4293
|
-
const driftColor = drift.trend === "improving" ?
|
|
4504
|
+
const driftColor = drift.trend === "improving" ? chalk12.green : drift.trend === "degrading" ? chalk12.red : chalk12.yellow;
|
|
4294
4505
|
console.log(driftColor(`${driftEmoji} Overall Trend: ${drift.trend.toUpperCase()}`));
|
|
4295
4506
|
if (drift.summary.newViolations.total > 0) {
|
|
4296
|
-
console.log(
|
|
4507
|
+
console.log(chalk12.red(`
|
|
4297
4508
|
\u26A0\uFE0F New Violations: ${drift.summary.newViolations.total}`));
|
|
4298
4509
|
if (drift.summary.newViolations.critical > 0) {
|
|
4299
4510
|
console.log(` \u2022 Critical: ${drift.summary.newViolations.critical}`);
|
|
@@ -4309,7 +4520,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4309
4520
|
}
|
|
4310
4521
|
}
|
|
4311
4522
|
if (drift.summary.fixedViolations.total > 0) {
|
|
4312
|
-
console.log(
|
|
4523
|
+
console.log(chalk12.green(`
|
|
4313
4524
|
\u2705 Fixed Violations: ${drift.summary.fixedViolations.total}`));
|
|
4314
4525
|
if (drift.summary.fixedViolations.critical > 0) {
|
|
4315
4526
|
console.log(` \u2022 Critical: ${drift.summary.fixedViolations.critical}`);
|
|
@@ -4325,7 +4536,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4325
4536
|
}
|
|
4326
4537
|
}
|
|
4327
4538
|
if (drift.mostDegraded.length > 0) {
|
|
4328
|
-
console.log(
|
|
4539
|
+
console.log(chalk12.red("\n\u{1F4C9} Most Degraded:"));
|
|
4329
4540
|
drift.mostDegraded.forEach((d) => {
|
|
4330
4541
|
console.log(` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (${d.complianceChange.toFixed(1)}%)`);
|
|
4331
4542
|
if (d.newViolations > 0) {
|
|
@@ -4334,7 +4545,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4334
4545
|
});
|
|
4335
4546
|
}
|
|
4336
4547
|
if (drift.mostImproved.length > 0) {
|
|
4337
|
-
console.log(
|
|
4548
|
+
console.log(chalk12.green("\n\u{1F4C8} Most Improved:"));
|
|
4338
4549
|
drift.mostImproved.forEach((d) => {
|
|
4339
4550
|
console.log(` \u2022 ${d.title}: ${d.previousCompliance}% \u2192 ${d.currentCompliance}% (+${d.complianceChange.toFixed(1)}%)`);
|
|
4340
4551
|
if (d.fixedViolations > 0) {
|
|
@@ -4374,7 +4585,7 @@ Compliance Change: ${drift.complianceChange > 0 ? "+" : ""}${drift.complianceCha
|
|
|
4374
4585
|
`health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${extension}`
|
|
4375
4586
|
);
|
|
4376
4587
|
await writeTextFile(outputPath, output);
|
|
4377
|
-
console.log(
|
|
4588
|
+
console.log(chalk12.green(`
|
|
4378
4589
|
Report saved to: ${outputPath}`));
|
|
4379
4590
|
if (options.save && !options.output) {
|
|
4380
4591
|
const latestPath = join9(getReportsDir(cwd), `health-latest.${extension}`);
|
|
@@ -4389,7 +4600,7 @@ Report saved to: ${outputPath}`));
|
|
|
4389
4600
|
|
|
4390
4601
|
// src/cli/commands/context.ts
|
|
4391
4602
|
import { Command as Command11 } from "commander";
|
|
4392
|
-
import
|
|
4603
|
+
import chalk13 from "chalk";
|
|
4393
4604
|
|
|
4394
4605
|
// src/agent/context.generator.ts
|
|
4395
4606
|
async function generateContext(filePath, config, options = {}) {
|
|
@@ -4509,12 +4720,12 @@ var contextCommand = new Command11("context").description("Generate architectura
|
|
|
4509
4720
|
});
|
|
4510
4721
|
if (options.output) {
|
|
4511
4722
|
await writeTextFile(options.output, output);
|
|
4512
|
-
console.log(
|
|
4723
|
+
console.log(chalk13.green(`Context saved to: ${options.output}`));
|
|
4513
4724
|
} else {
|
|
4514
4725
|
console.log(output);
|
|
4515
4726
|
}
|
|
4516
4727
|
} catch (error) {
|
|
4517
|
-
console.error(
|
|
4728
|
+
console.error(chalk13.red("Failed to generate context"));
|
|
4518
4729
|
throw error;
|
|
4519
4730
|
}
|
|
4520
4731
|
});
|
|
@@ -4528,7 +4739,7 @@ import { TextDocument } from "vscode-languageserver-textdocument";
|
|
|
4528
4739
|
import { fileURLToPath } from "url";
|
|
4529
4740
|
import path3 from "path";
|
|
4530
4741
|
import { Project as Project3 } from "ts-morph";
|
|
4531
|
-
import
|
|
4742
|
+
import chalk14 from "chalk";
|
|
4532
4743
|
function severityToDiagnostic(severity) {
|
|
4533
4744
|
switch (severity) {
|
|
4534
4745
|
case "critical":
|
|
@@ -4635,7 +4846,7 @@ var SpecBridgeLspServer = class {
|
|
|
4635
4846
|
if (!await pathExists(getSpecBridgeDir(this.cwd))) {
|
|
4636
4847
|
const err = new NotInitializedError();
|
|
4637
4848
|
this.initError = err.message;
|
|
4638
|
-
if (this.options.verbose) this.connection.console.error(
|
|
4849
|
+
if (this.options.verbose) this.connection.console.error(chalk14.red(this.initError));
|
|
4639
4850
|
return;
|
|
4640
4851
|
}
|
|
4641
4852
|
try {
|
|
@@ -4651,11 +4862,11 @@ var SpecBridgeLspServer = class {
|
|
|
4651
4862
|
}
|
|
4652
4863
|
}
|
|
4653
4864
|
if (this.options.verbose) {
|
|
4654
|
-
this.connection.console.log(
|
|
4865
|
+
this.connection.console.log(chalk14.dim(`Loaded ${this.decisions.length} active decision(s)`));
|
|
4655
4866
|
}
|
|
4656
4867
|
} catch (error) {
|
|
4657
4868
|
this.initError = error instanceof Error ? error.message : String(error);
|
|
4658
|
-
if (this.options.verbose) this.connection.console.error(
|
|
4869
|
+
if (this.options.verbose) this.connection.console.error(chalk14.red(this.initError));
|
|
4659
4870
|
}
|
|
4660
4871
|
}
|
|
4661
4872
|
async verifyTextDocument(doc) {
|
|
@@ -4727,7 +4938,7 @@ var lspCommand = new Command12("lsp").description("Start SpecBridge language ser
|
|
|
4727
4938
|
|
|
4728
4939
|
// src/cli/commands/watch.ts
|
|
4729
4940
|
import { Command as Command13 } from "commander";
|
|
4730
|
-
import
|
|
4941
|
+
import chalk15 from "chalk";
|
|
4731
4942
|
import chokidar from "chokidar";
|
|
4732
4943
|
import path4 from "path";
|
|
4733
4944
|
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) => {
|
|
@@ -4748,15 +4959,15 @@ var watchCommand = new Command13("watch").description("Watch for changes and ver
|
|
|
4748
4959
|
files: [absolutePath],
|
|
4749
4960
|
cwd
|
|
4750
4961
|
});
|
|
4751
|
-
const prefix = result.success ?
|
|
4962
|
+
const prefix = result.success ? chalk15.green("\u2713") : chalk15.red("\u2717");
|
|
4752
4963
|
const summary = `${prefix} ${path4.relative(cwd, absolutePath)}: ${result.violations.length} violation(s)`;
|
|
4753
4964
|
console.log(summary);
|
|
4754
4965
|
for (const v of result.violations.slice(0, 20)) {
|
|
4755
4966
|
const loc = v.line ? `:${v.line}${v.column ? `:${v.column}` : ""}` : "";
|
|
4756
|
-
console.log(
|
|
4967
|
+
console.log(chalk15.dim(` - ${v.file}${loc}: ${v.message} [${v.severity}]`));
|
|
4757
4968
|
}
|
|
4758
4969
|
if (result.violations.length > 20) {
|
|
4759
|
-
console.log(
|
|
4970
|
+
console.log(chalk15.dim(` \u2026 ${result.violations.length - 20} more`));
|
|
4760
4971
|
}
|
|
4761
4972
|
};
|
|
4762
4973
|
const watcher = chokidar.watch(config.project.sourceRoots, {
|
|
@@ -4765,7 +4976,7 @@ var watchCommand = new Command13("watch").description("Watch for changes and ver
|
|
|
4765
4976
|
ignoreInitial: true,
|
|
4766
4977
|
persistent: true
|
|
4767
4978
|
});
|
|
4768
|
-
console.log(
|
|
4979
|
+
console.log(chalk15.blue("Watching for changes..."));
|
|
4769
4980
|
watcher.on("change", (changedPath) => {
|
|
4770
4981
|
pendingPath = changedPath;
|
|
4771
4982
|
if (timer) clearTimeout(timer);
|
|
@@ -5057,7 +5268,7 @@ var promptCommand = new Command15("prompt").description("Generate AI agent promp
|
|
|
5057
5268
|
|
|
5058
5269
|
// src/cli/commands/analytics.ts
|
|
5059
5270
|
import { Command as Command16 } from "commander";
|
|
5060
|
-
import
|
|
5271
|
+
import chalk16 from "chalk";
|
|
5061
5272
|
import ora7 from "ora";
|
|
5062
5273
|
|
|
5063
5274
|
// src/analytics/engine.ts
|
|
@@ -5286,7 +5497,7 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5286
5497
|
const history = await storage.loadHistory(days);
|
|
5287
5498
|
if (history.length === 0) {
|
|
5288
5499
|
spinner.fail("No historical reports found");
|
|
5289
|
-
console.log(
|
|
5500
|
+
console.log(chalk16.yellow("\nGenerate reports with: specbridge report"));
|
|
5290
5501
|
return;
|
|
5291
5502
|
}
|
|
5292
5503
|
spinner.succeed(`Loaded ${history.length} historical report(s)`);
|
|
@@ -5303,17 +5514,17 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5303
5514
|
}
|
|
5304
5515
|
if (decisionId) {
|
|
5305
5516
|
const metrics = await engine.analyzeDecision(decisionId, history);
|
|
5306
|
-
console.log("\n" +
|
|
5517
|
+
console.log("\n" + chalk16.blue.bold(`=== Decision Analytics: ${metrics.title} ===
|
|
5307
5518
|
`));
|
|
5308
|
-
console.log(
|
|
5519
|
+
console.log(chalk16.bold("Overview:"));
|
|
5309
5520
|
console.log(` ID: ${metrics.decisionId}`);
|
|
5310
5521
|
console.log(` Current Violations: ${metrics.totalViolations}`);
|
|
5311
5522
|
console.log(` Average Compliance: ${metrics.averageComplianceScore.toFixed(1)}%`);
|
|
5312
5523
|
const trendEmoji = metrics.trendDirection === "up" ? "\u{1F4C8}" : metrics.trendDirection === "down" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
5313
|
-
const trendColor = metrics.trendDirection === "up" ?
|
|
5524
|
+
const trendColor = metrics.trendDirection === "up" ? chalk16.green : metrics.trendDirection === "down" ? chalk16.red : chalk16.yellow;
|
|
5314
5525
|
console.log(` ${trendColor(`${trendEmoji} Trend: ${metrics.trendDirection.toUpperCase()}`)}`);
|
|
5315
5526
|
if (metrics.history.length > 0) {
|
|
5316
|
-
console.log(
|
|
5527
|
+
console.log(chalk16.bold("\nCompliance History:"));
|
|
5317
5528
|
const recentHistory = metrics.history.slice(-10);
|
|
5318
5529
|
recentHistory.forEach((h) => {
|
|
5319
5530
|
const icon = h.violations === 0 ? "\u2705" : "\u26A0\uFE0F";
|
|
@@ -5322,58 +5533,58 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5322
5533
|
}
|
|
5323
5534
|
} else {
|
|
5324
5535
|
const summary = await engine.generateSummary(history);
|
|
5325
|
-
console.log("\n" +
|
|
5326
|
-
console.log(
|
|
5536
|
+
console.log("\n" + chalk16.blue.bold("=== Overall Analytics ===\n"));
|
|
5537
|
+
console.log(chalk16.bold("Summary:"));
|
|
5327
5538
|
console.log(` Total Decisions: ${summary.totalDecisions}`);
|
|
5328
5539
|
console.log(` Average Compliance: ${summary.averageCompliance}%`);
|
|
5329
5540
|
console.log(` Critical Issues: ${summary.criticalIssues}`);
|
|
5330
5541
|
const trendEmoji = summary.overallTrend === "up" ? "\u{1F4C8}" : summary.overallTrend === "down" ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
5331
|
-
const trendColor = summary.overallTrend === "up" ?
|
|
5542
|
+
const trendColor = summary.overallTrend === "up" ? chalk16.green : summary.overallTrend === "down" ? chalk16.red : chalk16.yellow;
|
|
5332
5543
|
console.log(` ${trendColor(`${trendEmoji} Overall Trend: ${summary.overallTrend.toUpperCase()}`)}`);
|
|
5333
5544
|
if (summary.topDecisions.length > 0) {
|
|
5334
|
-
console.log(
|
|
5545
|
+
console.log(chalk16.green("\n\u2705 Top Performing Decisions:"));
|
|
5335
5546
|
summary.topDecisions.forEach((d, i) => {
|
|
5336
5547
|
console.log(` ${i + 1}. ${d.title}: ${d.compliance}%`);
|
|
5337
5548
|
});
|
|
5338
5549
|
}
|
|
5339
5550
|
if (summary.bottomDecisions.length > 0) {
|
|
5340
|
-
console.log(
|
|
5551
|
+
console.log(chalk16.red("\n\u26A0\uFE0F Decisions Needing Attention:"));
|
|
5341
5552
|
summary.bottomDecisions.forEach((d, i) => {
|
|
5342
5553
|
console.log(` ${i + 1}. ${d.title}: ${d.compliance}%`);
|
|
5343
5554
|
});
|
|
5344
5555
|
}
|
|
5345
5556
|
if (options.insights || summary.criticalIssues > 0) {
|
|
5346
|
-
console.log(
|
|
5557
|
+
console.log(chalk16.blue.bold("\n=== Insights ===\n"));
|
|
5347
5558
|
const insights = summary.insights;
|
|
5348
5559
|
const warnings = insights.filter((i) => i.type === "warning");
|
|
5349
5560
|
const successes = insights.filter((i) => i.type === "success");
|
|
5350
5561
|
const infos = insights.filter((i) => i.type === "info");
|
|
5351
5562
|
if (warnings.length > 0) {
|
|
5352
|
-
console.log(
|
|
5563
|
+
console.log(chalk16.red("\u26A0\uFE0F Warnings:"));
|
|
5353
5564
|
warnings.forEach((i) => {
|
|
5354
5565
|
console.log(` \u2022 ${i.message}`);
|
|
5355
5566
|
if (i.details) {
|
|
5356
|
-
console.log(
|
|
5567
|
+
console.log(chalk16.gray(` ${i.details}`));
|
|
5357
5568
|
}
|
|
5358
5569
|
});
|
|
5359
5570
|
console.log("");
|
|
5360
5571
|
}
|
|
5361
5572
|
if (successes.length > 0) {
|
|
5362
|
-
console.log(
|
|
5573
|
+
console.log(chalk16.green("\u2705 Positive Trends:"));
|
|
5363
5574
|
successes.forEach((i) => {
|
|
5364
5575
|
console.log(` \u2022 ${i.message}`);
|
|
5365
5576
|
if (i.details) {
|
|
5366
|
-
console.log(
|
|
5577
|
+
console.log(chalk16.gray(` ${i.details}`));
|
|
5367
5578
|
}
|
|
5368
5579
|
});
|
|
5369
5580
|
console.log("");
|
|
5370
5581
|
}
|
|
5371
5582
|
if (infos.length > 0) {
|
|
5372
|
-
console.log(
|
|
5583
|
+
console.log(chalk16.blue("\u{1F4A1} Suggestions:"));
|
|
5373
5584
|
infos.forEach((i) => {
|
|
5374
5585
|
console.log(` \u2022 ${i.message}`);
|
|
5375
5586
|
if (i.details) {
|
|
5376
|
-
console.log(
|
|
5587
|
+
console.log(chalk16.gray(` ${i.details}`));
|
|
5377
5588
|
}
|
|
5378
5589
|
});
|
|
5379
5590
|
console.log("");
|
|
@@ -5383,10 +5594,10 @@ var analyticsCommand = new Command16("analytics").description("Analyze complianc
|
|
|
5383
5594
|
const latestEntry = history[history.length - 1];
|
|
5384
5595
|
const oldestEntry = history[0];
|
|
5385
5596
|
if (latestEntry && oldestEntry) {
|
|
5386
|
-
console.log(
|
|
5597
|
+
console.log(chalk16.gray(`
|
|
5387
5598
|
Data range: ${latestEntry.timestamp} to ${oldestEntry.timestamp}`));
|
|
5388
5599
|
}
|
|
5389
|
-
console.log(
|
|
5600
|
+
console.log(chalk16.gray(`Analyzing ${history.length} report(s) over ${days} days
|
|
5390
5601
|
`));
|
|
5391
5602
|
} catch (error) {
|
|
5392
5603
|
spinner.fail("Analytics failed");
|
|
@@ -5396,7 +5607,7 @@ Data range: ${latestEntry.timestamp} to ${oldestEntry.timestamp}`));
|
|
|
5396
5607
|
|
|
5397
5608
|
// src/cli/commands/dashboard.ts
|
|
5398
5609
|
import { Command as Command17 } from "commander";
|
|
5399
|
-
import
|
|
5610
|
+
import chalk17 from "chalk";
|
|
5400
5611
|
|
|
5401
5612
|
// src/dashboard/server.ts
|
|
5402
5613
|
import express from "express";
|
|
@@ -5611,42 +5822,325 @@ var dashboardCommand = new Command17("dashboard").description("Start compliance
|
|
|
5611
5822
|
if (!await pathExists(getSpecBridgeDir(cwd))) {
|
|
5612
5823
|
throw new NotInitializedError();
|
|
5613
5824
|
}
|
|
5614
|
-
console.log(
|
|
5825
|
+
console.log(chalk17.blue("Starting SpecBridge dashboard..."));
|
|
5615
5826
|
try {
|
|
5616
5827
|
const config = await loadConfig(cwd);
|
|
5617
5828
|
const app = createDashboardServer({ cwd, config });
|
|
5618
5829
|
const port = parseInt(options.port || "3000", 10);
|
|
5619
5830
|
const host = options.host || "localhost";
|
|
5620
5831
|
app.listen(port, host, () => {
|
|
5621
|
-
console.log(
|
|
5832
|
+
console.log(chalk17.green(`
|
|
5622
5833
|
\u2713 Dashboard running at http://${host}:${port}`));
|
|
5623
|
-
console.log(
|
|
5624
|
-
console.log(
|
|
5625
|
-
console.log(` ${
|
|
5626
|
-
console.log(` ${
|
|
5627
|
-
console.log(` ${
|
|
5628
|
-
console.log(` ${
|
|
5834
|
+
console.log(chalk17.gray(" Press Ctrl+C to stop\n"));
|
|
5835
|
+
console.log(chalk17.bold("API Endpoints:"));
|
|
5836
|
+
console.log(` ${chalk17.cyan(`http://${host}:${port}/api/health`)} - Health check`);
|
|
5837
|
+
console.log(` ${chalk17.cyan(`http://${host}:${port}/api/report/latest`)} - Latest report`);
|
|
5838
|
+
console.log(` ${chalk17.cyan(`http://${host}:${port}/api/decisions`)} - All decisions`);
|
|
5839
|
+
console.log(` ${chalk17.cyan(`http://${host}:${port}/api/analytics/summary`)} - Analytics`);
|
|
5629
5840
|
console.log("");
|
|
5630
5841
|
});
|
|
5631
5842
|
process.on("SIGINT", () => {
|
|
5632
|
-
console.log(
|
|
5843
|
+
console.log(chalk17.yellow("\n\nShutting down dashboard..."));
|
|
5633
5844
|
process.exit(0);
|
|
5634
5845
|
});
|
|
5635
5846
|
process.on("SIGTERM", () => {
|
|
5636
|
-
console.log(
|
|
5847
|
+
console.log(chalk17.yellow("\n\nShutting down dashboard..."));
|
|
5637
5848
|
process.exit(0);
|
|
5638
5849
|
});
|
|
5639
5850
|
} catch (error) {
|
|
5640
|
-
console.error(
|
|
5851
|
+
console.error(chalk17.red("Failed to start dashboard:"), error);
|
|
5852
|
+
throw error;
|
|
5853
|
+
}
|
|
5854
|
+
});
|
|
5855
|
+
|
|
5856
|
+
// src/cli/commands/impact.ts
|
|
5857
|
+
import { Command as Command18 } from "commander";
|
|
5858
|
+
import chalk18 from "chalk";
|
|
5859
|
+
import ora8 from "ora";
|
|
5860
|
+
|
|
5861
|
+
// src/propagation/graph.ts
|
|
5862
|
+
async function buildDependencyGraph2(decisions, files) {
|
|
5863
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
5864
|
+
const decisionToFiles = /* @__PURE__ */ new Map();
|
|
5865
|
+
const fileToDecisions = /* @__PURE__ */ new Map();
|
|
5866
|
+
for (const decision of decisions) {
|
|
5867
|
+
const decisionId = `decision:${decision.metadata.id}`;
|
|
5868
|
+
nodes.set(decisionId, {
|
|
5869
|
+
type: "decision",
|
|
5870
|
+
id: decision.metadata.id,
|
|
5871
|
+
edges: decision.constraints.map((c) => `constraint:${decision.metadata.id}/${c.id}`)
|
|
5872
|
+
});
|
|
5873
|
+
for (const constraint of decision.constraints) {
|
|
5874
|
+
const constraintId = `constraint:${decision.metadata.id}/${constraint.id}`;
|
|
5875
|
+
const matchingFiles = [];
|
|
5876
|
+
for (const file of files) {
|
|
5877
|
+
if (matchesPattern(file, constraint.scope)) {
|
|
5878
|
+
matchingFiles.push(`file:${file}`);
|
|
5879
|
+
const fileDecisions = fileToDecisions.get(file) || /* @__PURE__ */ new Set();
|
|
5880
|
+
fileDecisions.add(decision.metadata.id);
|
|
5881
|
+
fileToDecisions.set(file, fileDecisions);
|
|
5882
|
+
const decFiles = decisionToFiles.get(decision.metadata.id) || /* @__PURE__ */ new Set();
|
|
5883
|
+
decFiles.add(file);
|
|
5884
|
+
decisionToFiles.set(decision.metadata.id, decFiles);
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
nodes.set(constraintId, {
|
|
5888
|
+
type: "constraint",
|
|
5889
|
+
id: `${decision.metadata.id}/${constraint.id}`,
|
|
5890
|
+
edges: matchingFiles
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
for (const file of files) {
|
|
5895
|
+
const fileId = `file:${file}`;
|
|
5896
|
+
if (!nodes.has(fileId)) {
|
|
5897
|
+
nodes.set(fileId, {
|
|
5898
|
+
type: "file",
|
|
5899
|
+
id: file,
|
|
5900
|
+
edges: []
|
|
5901
|
+
});
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
return {
|
|
5905
|
+
nodes,
|
|
5906
|
+
decisionToFiles,
|
|
5907
|
+
fileToDecisions
|
|
5908
|
+
};
|
|
5909
|
+
}
|
|
5910
|
+
function getAffectedFiles(graph, decisionId) {
|
|
5911
|
+
const files = graph.decisionToFiles.get(decisionId);
|
|
5912
|
+
return files ? Array.from(files) : [];
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
// src/propagation/engine.ts
|
|
5916
|
+
var PropagationEngine = class {
|
|
5917
|
+
registry;
|
|
5918
|
+
graph = null;
|
|
5919
|
+
constructor(registry) {
|
|
5920
|
+
this.registry = registry || createRegistry();
|
|
5921
|
+
}
|
|
5922
|
+
/**
|
|
5923
|
+
* Initialize the engine with current state
|
|
5924
|
+
*/
|
|
5925
|
+
async initialize(config, options = {}) {
|
|
5926
|
+
const { cwd = process.cwd() } = options;
|
|
5927
|
+
await this.registry.load();
|
|
5928
|
+
const files = await glob(config.project.sourceRoots, {
|
|
5929
|
+
cwd,
|
|
5930
|
+
ignore: config.project.exclude,
|
|
5931
|
+
absolute: true
|
|
5932
|
+
});
|
|
5933
|
+
const decisions = this.registry.getActive();
|
|
5934
|
+
this.graph = await buildDependencyGraph2(decisions, files);
|
|
5935
|
+
}
|
|
5936
|
+
/**
|
|
5937
|
+
* Analyze impact of changing a decision
|
|
5938
|
+
*/
|
|
5939
|
+
async analyzeImpact(decisionId, change, config, options = {}) {
|
|
5940
|
+
const { cwd = process.cwd() } = options;
|
|
5941
|
+
if (!this.graph) {
|
|
5942
|
+
await this.initialize(config, options);
|
|
5943
|
+
}
|
|
5944
|
+
const affectedFilePaths = getAffectedFiles(this.graph, decisionId);
|
|
5945
|
+
const verificationEngine = createVerificationEngine(this.registry);
|
|
5946
|
+
const result = await verificationEngine.verify(config, {
|
|
5947
|
+
files: affectedFilePaths,
|
|
5948
|
+
decisions: [decisionId],
|
|
5949
|
+
cwd
|
|
5950
|
+
});
|
|
5951
|
+
const fileViolations = /* @__PURE__ */ new Map();
|
|
5952
|
+
for (const violation of result.violations) {
|
|
5953
|
+
const existing = fileViolations.get(violation.file) || { total: 0, autoFixable: 0 };
|
|
5954
|
+
existing.total++;
|
|
5955
|
+
if (violation.autofix) {
|
|
5956
|
+
existing.autoFixable++;
|
|
5957
|
+
}
|
|
5958
|
+
fileViolations.set(violation.file, existing);
|
|
5959
|
+
}
|
|
5960
|
+
const affectedFiles = affectedFilePaths.map((path5) => ({
|
|
5961
|
+
path: path5,
|
|
5962
|
+
violations: fileViolations.get(path5)?.total || 0,
|
|
5963
|
+
autoFixable: fileViolations.get(path5)?.autoFixable || 0
|
|
5964
|
+
}));
|
|
5965
|
+
affectedFiles.sort((a, b) => b.violations - a.violations);
|
|
5966
|
+
const totalViolations = result.violations.length;
|
|
5967
|
+
const totalAutoFixable = result.violations.filter((v) => v.autofix).length;
|
|
5968
|
+
const manualFixes = totalViolations - totalAutoFixable;
|
|
5969
|
+
let estimatedEffort;
|
|
5970
|
+
if (manualFixes === 0) {
|
|
5971
|
+
estimatedEffort = "low";
|
|
5972
|
+
} else if (manualFixes <= 10) {
|
|
5973
|
+
estimatedEffort = "medium";
|
|
5974
|
+
} else {
|
|
5975
|
+
estimatedEffort = "high";
|
|
5976
|
+
}
|
|
5977
|
+
const migrationSteps = this.generateMigrationSteps(
|
|
5978
|
+
affectedFiles,
|
|
5979
|
+
totalAutoFixable > 0
|
|
5980
|
+
);
|
|
5981
|
+
return {
|
|
5982
|
+
decision: decisionId,
|
|
5983
|
+
change,
|
|
5984
|
+
affectedFiles,
|
|
5985
|
+
estimatedEffort,
|
|
5986
|
+
migrationSteps
|
|
5987
|
+
};
|
|
5988
|
+
}
|
|
5989
|
+
/**
|
|
5990
|
+
* Generate migration steps
|
|
5991
|
+
*/
|
|
5992
|
+
generateMigrationSteps(affectedFiles, hasAutoFixes) {
|
|
5993
|
+
const steps = [];
|
|
5994
|
+
let order = 1;
|
|
5995
|
+
if (hasAutoFixes) {
|
|
5996
|
+
steps.push({
|
|
5997
|
+
order: order++,
|
|
5998
|
+
description: "Run auto-fix for mechanical violations",
|
|
5999
|
+
files: affectedFiles.filter((f) => f.autoFixable > 0).map((f) => f.path),
|
|
6000
|
+
automated: true
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
6003
|
+
const filesWithManualFixes = affectedFiles.filter(
|
|
6004
|
+
(f) => f.violations > f.autoFixable
|
|
6005
|
+
);
|
|
6006
|
+
if (filesWithManualFixes.length > 0) {
|
|
6007
|
+
const highPriority = filesWithManualFixes.filter((f) => f.violations > 5);
|
|
6008
|
+
const mediumPriority = filesWithManualFixes.filter(
|
|
6009
|
+
(f) => f.violations <= 5 && f.violations > 1
|
|
6010
|
+
);
|
|
6011
|
+
const lowPriority = filesWithManualFixes.filter((f) => f.violations === 1);
|
|
6012
|
+
if (highPriority.length > 0) {
|
|
6013
|
+
steps.push({
|
|
6014
|
+
order: order++,
|
|
6015
|
+
description: "Fix high-violation files first",
|
|
6016
|
+
files: highPriority.map((f) => f.path),
|
|
6017
|
+
automated: false
|
|
6018
|
+
});
|
|
6019
|
+
}
|
|
6020
|
+
if (mediumPriority.length > 0) {
|
|
6021
|
+
steps.push({
|
|
6022
|
+
order: order++,
|
|
6023
|
+
description: "Fix medium-violation files",
|
|
6024
|
+
files: mediumPriority.map((f) => f.path),
|
|
6025
|
+
automated: false
|
|
6026
|
+
});
|
|
6027
|
+
}
|
|
6028
|
+
if (lowPriority.length > 0) {
|
|
6029
|
+
steps.push({
|
|
6030
|
+
order: order++,
|
|
6031
|
+
description: "Fix remaining files",
|
|
6032
|
+
files: lowPriority.map((f) => f.path),
|
|
6033
|
+
automated: false
|
|
6034
|
+
});
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
steps.push({
|
|
6038
|
+
order: order++,
|
|
6039
|
+
description: "Run verification to confirm all violations resolved",
|
|
6040
|
+
files: [],
|
|
6041
|
+
automated: true
|
|
6042
|
+
});
|
|
6043
|
+
return steps;
|
|
6044
|
+
}
|
|
6045
|
+
/**
|
|
6046
|
+
* Get dependency graph
|
|
6047
|
+
*/
|
|
6048
|
+
getGraph() {
|
|
6049
|
+
return this.graph;
|
|
6050
|
+
}
|
|
6051
|
+
};
|
|
6052
|
+
function createPropagationEngine(registry) {
|
|
6053
|
+
return new PropagationEngine(registry);
|
|
6054
|
+
}
|
|
6055
|
+
|
|
6056
|
+
// src/cli/commands/impact.ts
|
|
6057
|
+
var impactCommand = new Command18("impact").description("Analyze impact of decision changes").argument("<decision-id>", "Decision ID to analyze").option("-c, --change <type>", "Type of change (created, modified, deprecated)", "modified").option("--json", "Output as JSON").option("--show-steps", "Show detailed migration steps", true).action(async (decisionId, options) => {
|
|
6058
|
+
const cwd = process.cwd();
|
|
6059
|
+
if (!await pathExists(getSpecBridgeDir(cwd))) {
|
|
6060
|
+
throw new NotInitializedError();
|
|
6061
|
+
}
|
|
6062
|
+
const spinner = ora8("Loading configuration...").start();
|
|
6063
|
+
try {
|
|
6064
|
+
const config = await loadConfig(cwd);
|
|
6065
|
+
const changeType = options.change || "modified";
|
|
6066
|
+
if (!["created", "modified", "deprecated"].includes(changeType)) {
|
|
6067
|
+
spinner.fail();
|
|
6068
|
+
console.error(chalk18.red(`Invalid change type: ${changeType}`));
|
|
6069
|
+
console.error(chalk18.dim("Valid types: created, modified, deprecated"));
|
|
6070
|
+
process.exit(1);
|
|
6071
|
+
}
|
|
6072
|
+
spinner.text = `Analyzing impact of ${changeType} decision ${decisionId}...`;
|
|
6073
|
+
const engine = createPropagationEngine();
|
|
6074
|
+
const analysis = await engine.analyzeImpact(decisionId, changeType, config, { cwd });
|
|
6075
|
+
spinner.stop();
|
|
6076
|
+
if (options.json) {
|
|
6077
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
6078
|
+
} else {
|
|
6079
|
+
printImpactAnalysis(analysis, options.showSteps !== false);
|
|
6080
|
+
}
|
|
6081
|
+
} catch (error) {
|
|
6082
|
+
spinner.fail("Impact analysis failed");
|
|
5641
6083
|
throw error;
|
|
5642
6084
|
}
|
|
5643
6085
|
});
|
|
6086
|
+
function printImpactAnalysis(analysis, showSteps) {
|
|
6087
|
+
console.log(chalk18.bold(`
|
|
6088
|
+
=== Impact Analysis: ${analysis.decision} ===
|
|
6089
|
+
`));
|
|
6090
|
+
const changeLabel = chalk18.cyan(analysis.change);
|
|
6091
|
+
console.log(`Change Type: ${changeLabel}`);
|
|
6092
|
+
const effortColor = analysis.estimatedEffort === "high" ? chalk18.red : analysis.estimatedEffort === "medium" ? chalk18.yellow : chalk18.green;
|
|
6093
|
+
console.log(`Estimated Effort: ${effortColor(analysis.estimatedEffort.toUpperCase())}
|
|
6094
|
+
`);
|
|
6095
|
+
console.log(chalk18.bold(`Affected Files: ${analysis.affectedFiles.length}`));
|
|
6096
|
+
if (analysis.affectedFiles.length > 0) {
|
|
6097
|
+
const displayCount = Math.min(analysis.affectedFiles.length, 10);
|
|
6098
|
+
for (let i = 0; i < displayCount; i++) {
|
|
6099
|
+
const file = analysis.affectedFiles[i];
|
|
6100
|
+
if (!file) continue;
|
|
6101
|
+
const violationText = file.violations === 1 ? "1 violation" : `${file.violations} violations`;
|
|
6102
|
+
const autoFixText = file.autoFixable > 0 ? chalk18.green(` (${file.autoFixable} auto-fixable)`) : "";
|
|
6103
|
+
console.log(` ${chalk18.red("\u25CF")} ${file.path} - ${violationText}${autoFixText}`);
|
|
6104
|
+
}
|
|
6105
|
+
if (analysis.affectedFiles.length > displayCount) {
|
|
6106
|
+
const remaining = analysis.affectedFiles.length - displayCount;
|
|
6107
|
+
console.log(chalk18.dim(` ... and ${remaining} more file(s)`));
|
|
6108
|
+
}
|
|
6109
|
+
} else {
|
|
6110
|
+
console.log(chalk18.green(" No violations found"));
|
|
6111
|
+
}
|
|
6112
|
+
if (showSteps && analysis.migrationSteps && analysis.migrationSteps.length > 0) {
|
|
6113
|
+
console.log(chalk18.bold("\nMigration Plan:"));
|
|
6114
|
+
for (const step of analysis.migrationSteps) {
|
|
6115
|
+
const icon = step.automated ? "\u{1F916}" : "\u{1F464}";
|
|
6116
|
+
const typeLabel = step.automated ? chalk18.green("[Automated]") : chalk18.yellow("[Manual]");
|
|
6117
|
+
console.log(` ${icon} Step ${step.order}: ${step.description} ${typeLabel}`);
|
|
6118
|
+
if (step.files.length > 0) {
|
|
6119
|
+
const displayFiles = Math.min(step.files.length, 3);
|
|
6120
|
+
for (let i = 0; i < displayFiles; i++) {
|
|
6121
|
+
console.log(chalk18.dim(` - ${step.files[i]}`));
|
|
6122
|
+
}
|
|
6123
|
+
if (step.files.length > displayFiles) {
|
|
6124
|
+
console.log(chalk18.dim(` ... and ${step.files.length - displayFiles} more file(s)`));
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
console.log("");
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
const totalViolations = analysis.affectedFiles.reduce((sum, f) => sum + f.violations, 0);
|
|
6131
|
+
const totalAutoFixable = analysis.affectedFiles.reduce((sum, f) => sum + f.autoFixable, 0);
|
|
6132
|
+
const manualFixes = totalViolations - totalAutoFixable;
|
|
6133
|
+
console.log(chalk18.bold("Summary:"));
|
|
6134
|
+
console.log(` Total Violations: ${totalViolations}`);
|
|
6135
|
+
console.log(` Auto-fixable: ${chalk18.green(totalAutoFixable)}`);
|
|
6136
|
+
console.log(` Manual Fixes Required: ${manualFixes > 0 ? chalk18.yellow(manualFixes) : chalk18.green(0)}`);
|
|
6137
|
+
}
|
|
5644
6138
|
|
|
5645
6139
|
// src/cli/index.ts
|
|
5646
6140
|
var __dirname2 = dirname5(fileURLToPath4(import.meta.url));
|
|
5647
6141
|
var packageJsonPath = join12(__dirname2, "../package.json");
|
|
5648
6142
|
var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
5649
|
-
var program = new
|
|
6143
|
+
var program = new Command19();
|
|
5650
6144
|
program.name("specbridge").description("Architecture Decision Runtime - Transform architectural decisions into executable, verifiable constraints").version(packageJson.version);
|
|
5651
6145
|
program.addCommand(initCommand);
|
|
5652
6146
|
program.addCommand(inferCommand);
|
|
@@ -5661,6 +6155,7 @@ program.addCommand(mcpServerCommand);
|
|
|
5661
6155
|
program.addCommand(promptCommand);
|
|
5662
6156
|
program.addCommand(analyticsCommand);
|
|
5663
6157
|
program.addCommand(dashboardCommand);
|
|
6158
|
+
program.addCommand(impactCommand);
|
|
5664
6159
|
program.exitOverride((err) => {
|
|
5665
6160
|
if (err.code === "commander.help" || err.code === "commander.helpDisplayed") {
|
|
5666
6161
|
process.exit(0);
|
|
@@ -5668,11 +6163,11 @@ program.exitOverride((err) => {
|
|
|
5668
6163
|
if (err.code === "commander.version") {
|
|
5669
6164
|
process.exit(0);
|
|
5670
6165
|
}
|
|
5671
|
-
console.error(
|
|
6166
|
+
console.error(chalk19.red(formatError(err)));
|
|
5672
6167
|
process.exit(1);
|
|
5673
6168
|
});
|
|
5674
6169
|
program.parseAsync(process.argv).catch((error) => {
|
|
5675
|
-
console.error(
|
|
6170
|
+
console.error(chalk19.red(formatError(error)));
|
|
5676
6171
|
process.exit(1);
|
|
5677
6172
|
});
|
|
5678
6173
|
//# sourceMappingURL=cli.js.map
|