@vibecheck-ai/mcp 24.5.8 → 24.6.2
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/LICENSE +21 -0
- package/dist/index.js +1121 -972
- package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
- package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
- package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
- package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
- package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
- package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
- package/package.json +25 -24
package/dist/index.js
CHANGED
|
@@ -5752,7 +5752,7 @@ var require_util = __commonJS({
|
|
|
5752
5752
|
return path10;
|
|
5753
5753
|
}
|
|
5754
5754
|
exports2.normalize = normalize4;
|
|
5755
|
-
function
|
|
5755
|
+
function join10(aRoot, aPath) {
|
|
5756
5756
|
if (aRoot === "") {
|
|
5757
5757
|
aRoot = ".";
|
|
5758
5758
|
}
|
|
@@ -5784,7 +5784,7 @@ var require_util = __commonJS({
|
|
|
5784
5784
|
}
|
|
5785
5785
|
return joined;
|
|
5786
5786
|
}
|
|
5787
|
-
exports2.join =
|
|
5787
|
+
exports2.join = join10;
|
|
5788
5788
|
exports2.isAbsolute = function(aPath) {
|
|
5789
5789
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
5790
5790
|
};
|
|
@@ -5957,7 +5957,7 @@ var require_util = __commonJS({
|
|
|
5957
5957
|
parsed.path = parsed.path.substring(0, index2 + 1);
|
|
5958
5958
|
}
|
|
5959
5959
|
}
|
|
5960
|
-
sourceURL =
|
|
5960
|
+
sourceURL = join10(urlGenerate(parsed), sourceURL);
|
|
5961
5961
|
}
|
|
5962
5962
|
return normalize4(sourceURL);
|
|
5963
5963
|
}
|
|
@@ -219066,27 +219066,29 @@ var require_has_flag = __commonJS({
|
|
|
219066
219066
|
}
|
|
219067
219067
|
});
|
|
219068
219068
|
|
|
219069
|
-
// ../../node_modules/.pnpm/supports-color@
|
|
219069
|
+
// ../../node_modules/.pnpm/supports-color@8.1.1/node_modules/supports-color/index.js
|
|
219070
219070
|
var require_supports_color = __commonJS({
|
|
219071
|
-
"../../node_modules/.pnpm/supports-color@
|
|
219071
|
+
"../../node_modules/.pnpm/supports-color@8.1.1/node_modules/supports-color/index.js"(exports2, module2) {
|
|
219072
219072
|
"use strict";
|
|
219073
219073
|
var os2 = __require("os");
|
|
219074
219074
|
var tty = __require("tty");
|
|
219075
219075
|
var hasFlag = require_has_flag();
|
|
219076
219076
|
var { env: env3 } = process;
|
|
219077
|
-
var
|
|
219077
|
+
var flagForceColor;
|
|
219078
219078
|
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
219079
|
-
|
|
219079
|
+
flagForceColor = 0;
|
|
219080
219080
|
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
219081
|
-
|
|
219081
|
+
flagForceColor = 1;
|
|
219082
219082
|
}
|
|
219083
|
-
|
|
219084
|
-
if (
|
|
219085
|
-
|
|
219086
|
-
|
|
219087
|
-
|
|
219088
|
-
|
|
219089
|
-
|
|
219083
|
+
function envForceColor() {
|
|
219084
|
+
if ("FORCE_COLOR" in env3) {
|
|
219085
|
+
if (env3.FORCE_COLOR === "true") {
|
|
219086
|
+
return 1;
|
|
219087
|
+
}
|
|
219088
|
+
if (env3.FORCE_COLOR === "false") {
|
|
219089
|
+
return 0;
|
|
219090
|
+
}
|
|
219091
|
+
return env3.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env3.FORCE_COLOR, 10), 3);
|
|
219090
219092
|
}
|
|
219091
219093
|
}
|
|
219092
219094
|
function translateLevel(level) {
|
|
@@ -219100,15 +219102,22 @@ var require_supports_color = __commonJS({
|
|
|
219100
219102
|
has16m: level >= 3
|
|
219101
219103
|
};
|
|
219102
219104
|
}
|
|
219103
|
-
function supportsColor(haveStream, streamIsTTY) {
|
|
219105
|
+
function supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
219106
|
+
const noFlagForceColor = envForceColor();
|
|
219107
|
+
if (noFlagForceColor !== void 0) {
|
|
219108
|
+
flagForceColor = noFlagForceColor;
|
|
219109
|
+
}
|
|
219110
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
219104
219111
|
if (forceColor === 0) {
|
|
219105
219112
|
return 0;
|
|
219106
219113
|
}
|
|
219107
|
-
if (
|
|
219108
|
-
|
|
219109
|
-
|
|
219110
|
-
|
|
219111
|
-
|
|
219114
|
+
if (sniffFlags) {
|
|
219115
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
219116
|
+
return 3;
|
|
219117
|
+
}
|
|
219118
|
+
if (hasFlag("color=256")) {
|
|
219119
|
+
return 2;
|
|
219120
|
+
}
|
|
219112
219121
|
}
|
|
219113
219122
|
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
219114
219123
|
return 0;
|
|
@@ -219125,7 +219134,7 @@ var require_supports_color = __commonJS({
|
|
|
219125
219134
|
return 1;
|
|
219126
219135
|
}
|
|
219127
219136
|
if ("CI" in env3) {
|
|
219128
|
-
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env3) || env3.CI_NAME === "codeship") {
|
|
219137
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE", "DRONE"].some((sign) => sign in env3) || env3.CI_NAME === "codeship") {
|
|
219129
219138
|
return 1;
|
|
219130
219139
|
}
|
|
219131
219140
|
return min2;
|
|
@@ -219137,7 +219146,7 @@ var require_supports_color = __commonJS({
|
|
|
219137
219146
|
return 3;
|
|
219138
219147
|
}
|
|
219139
219148
|
if ("TERM_PROGRAM" in env3) {
|
|
219140
|
-
const version3 = parseInt((env3.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
219149
|
+
const version3 = Number.parseInt((env3.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
219141
219150
|
switch (env3.TERM_PROGRAM) {
|
|
219142
219151
|
case "iTerm.app":
|
|
219143
219152
|
return version3 >= 3 ? 3 : 2;
|
|
@@ -219156,14 +219165,17 @@ var require_supports_color = __commonJS({
|
|
|
219156
219165
|
}
|
|
219157
219166
|
return min2;
|
|
219158
219167
|
}
|
|
219159
|
-
function getSupportLevel(stream) {
|
|
219160
|
-
const level = supportsColor(stream,
|
|
219168
|
+
function getSupportLevel(stream, options = {}) {
|
|
219169
|
+
const level = supportsColor(stream, {
|
|
219170
|
+
streamIsTTY: stream && stream.isTTY,
|
|
219171
|
+
...options
|
|
219172
|
+
});
|
|
219161
219173
|
return translateLevel(level);
|
|
219162
219174
|
}
|
|
219163
219175
|
module2.exports = {
|
|
219164
219176
|
supportsColor: getSupportLevel,
|
|
219165
|
-
stdout:
|
|
219166
|
-
stderr:
|
|
219177
|
+
stdout: getSupportLevel({ isTTY: tty.isatty(1) }),
|
|
219178
|
+
stderr: getSupportLevel({ isTTY: tty.isatty(2) })
|
|
219167
219179
|
};
|
|
219168
219180
|
}
|
|
219169
219181
|
});
|
|
@@ -219532,7 +219544,7 @@ var require_bindings = __commonJS({
|
|
|
219532
219544
|
var fs7 = __require("fs");
|
|
219533
219545
|
var path10 = __require("path");
|
|
219534
219546
|
var fileURLToPath2 = require_file_uri_to_path();
|
|
219535
|
-
var
|
|
219547
|
+
var join10 = path10.join;
|
|
219536
219548
|
var dirname8 = path10.dirname;
|
|
219537
219549
|
var exists2 = fs7.accessSync && function(path11) {
|
|
219538
219550
|
try {
|
|
@@ -219592,7 +219604,7 @@ var require_bindings = __commonJS({
|
|
|
219592
219604
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
219593
219605
|
var tries = [], i2 = 0, l = opts.try.length, n, b, err2;
|
|
219594
219606
|
for (; i2 < l; i2++) {
|
|
219595
|
-
n =
|
|
219607
|
+
n = join10.apply(
|
|
219596
219608
|
null,
|
|
219597
219609
|
opts.try[i2].map(function(p) {
|
|
219598
219610
|
return opts[p] || p;
|
|
@@ -219653,7 +219665,7 @@ var require_bindings = __commonJS({
|
|
|
219653
219665
|
if (dir === ".") {
|
|
219654
219666
|
dir = process.cwd();
|
|
219655
219667
|
}
|
|
219656
|
-
if (exists2(
|
|
219668
|
+
if (exists2(join10(dir, "package.json")) || exists2(join10(dir, "node_modules"))) {
|
|
219657
219669
|
return dir;
|
|
219658
219670
|
}
|
|
219659
219671
|
if (prev === dir) {
|
|
@@ -219662,7 +219674,7 @@ var require_bindings = __commonJS({
|
|
|
219662
219674
|
);
|
|
219663
219675
|
}
|
|
219664
219676
|
prev = dir;
|
|
219665
|
-
dir =
|
|
219677
|
+
dir = join10(dir, "..");
|
|
219666
219678
|
}
|
|
219667
219679
|
};
|
|
219668
219680
|
}
|
|
@@ -219676,7 +219688,7 @@ var require_wrappers = __commonJS({
|
|
|
219676
219688
|
exports2.prepare = function prepare(sql) {
|
|
219677
219689
|
return this[cppdb].prepare(sql, this, false);
|
|
219678
219690
|
};
|
|
219679
|
-
exports2.exec = function
|
|
219691
|
+
exports2.exec = function exec(sql) {
|
|
219680
219692
|
this[cppdb].exec(sql);
|
|
219681
219693
|
return this;
|
|
219682
219694
|
};
|
|
@@ -221643,7 +221655,7 @@ ${JSON.stringify(t2, null, 2)}`);
|
|
|
221643
221655
|
|
|
221644
221656
|
// ../context-engine/dist/chunk-3ZE34DAJ.js
|
|
221645
221657
|
import { readFile as readFile2 } from "fs/promises";
|
|
221646
|
-
import { join as join3, dirname as
|
|
221658
|
+
import { join as join3, dirname as dirname5 } from "path";
|
|
221647
221659
|
import { fileURLToPath } from "url";
|
|
221648
221660
|
async function initParser() {
|
|
221649
221661
|
if (ParserClass) return ParserClass;
|
|
@@ -221783,7 +221795,7 @@ var init_chunk_3ZE34DAJ = __esm({
|
|
|
221783
221795
|
SymbolKind2["Export"] = "export";
|
|
221784
221796
|
return SymbolKind2;
|
|
221785
221797
|
})(SymbolKind || {});
|
|
221786
|
-
GRAMMAR_DIR = join3(
|
|
221798
|
+
GRAMMAR_DIR = join3(dirname5(fileURLToPath(import.meta.url)), "../../../../node_modules/tree-sitter-wasms/out");
|
|
221787
221799
|
EXT_TO_GRAMMAR = {
|
|
221788
221800
|
".ts": "typescript",
|
|
221789
221801
|
".tsx": "tsx",
|
|
@@ -278734,14 +278746,11 @@ import { pathToFileURL } from "url";
|
|
|
278734
278746
|
// src/server.ts
|
|
278735
278747
|
import * as path9 from "path";
|
|
278736
278748
|
import * as fs6 from "fs";
|
|
278737
|
-
import {
|
|
278749
|
+
import { execFile } from "child_process";
|
|
278738
278750
|
import { promisify } from "util";
|
|
278739
278751
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
278740
278752
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
278741
|
-
import {
|
|
278742
|
-
ListToolsRequestSchema,
|
|
278743
|
-
CallToolRequestSchema
|
|
278744
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
278753
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
278745
278754
|
import { runOnFiles } from "@vibecheck-ai/cli/runner";
|
|
278746
278755
|
import {
|
|
278747
278756
|
computeTrustScore,
|
|
@@ -289076,6 +289085,845 @@ rules:
|
|
|
289076
289085
|
match: "src/repositories/**"
|
|
289077
289086
|
`;
|
|
289078
289087
|
|
|
289088
|
+
// ../context-engine/dist/chunk-INBCP46U.js
|
|
289089
|
+
import * as path5 from "path";
|
|
289090
|
+
var ContextSynthesizer = class {
|
|
289091
|
+
rootPath;
|
|
289092
|
+
data;
|
|
289093
|
+
dna;
|
|
289094
|
+
graph;
|
|
289095
|
+
ruleResult;
|
|
289096
|
+
constructor(rootPath, data, dna, graph, ruleResult) {
|
|
289097
|
+
this.rootPath = rootPath;
|
|
289098
|
+
this.data = data;
|
|
289099
|
+
this.dna = dna;
|
|
289100
|
+
this.graph = graph;
|
|
289101
|
+
this.ruleResult = ruleResult || null;
|
|
289102
|
+
}
|
|
289103
|
+
/**
|
|
289104
|
+
* Synthesize the full context — the complete brain dump for AI agents.
|
|
289105
|
+
*/
|
|
289106
|
+
synthesize() {
|
|
289107
|
+
const projectIdentity = this.buildProjectIdentity();
|
|
289108
|
+
const archRules = this.buildArchRuleSummary();
|
|
289109
|
+
const activeViolations = this.ruleResult?.violations || [];
|
|
289110
|
+
const codebaseDNA = this.buildDNASummary();
|
|
289111
|
+
const fileContexts = this.buildFileContexts();
|
|
289112
|
+
const taskPlaybooks = this.buildTaskPlaybooks();
|
|
289113
|
+
const verificationSteps = this.buildVerificationSteps();
|
|
289114
|
+
const riskBriefing = this.buildRiskBriefing();
|
|
289115
|
+
return {
|
|
289116
|
+
version: "2.0.0",
|
|
289117
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
289118
|
+
projectIdentity,
|
|
289119
|
+
architecturalRules: archRules,
|
|
289120
|
+
activeViolations,
|
|
289121
|
+
codebaseDNA,
|
|
289122
|
+
fileContexts,
|
|
289123
|
+
taskPlaybooks,
|
|
289124
|
+
verificationSteps,
|
|
289125
|
+
riskBriefing
|
|
289126
|
+
};
|
|
289127
|
+
}
|
|
289128
|
+
/**
|
|
289129
|
+
* Generate context for a specific file — what an AI agent needs to know
|
|
289130
|
+
* before editing this file.
|
|
289131
|
+
*/
|
|
289132
|
+
synthesizeForFile(filePath) {
|
|
289133
|
+
const rel = this.toRelative(filePath);
|
|
289134
|
+
const file = this.data.files.find((f) => f.relativePath === rel || f.path === filePath);
|
|
289135
|
+
if (!file) return null;
|
|
289136
|
+
return this.buildSingleFileContext(file);
|
|
289137
|
+
}
|
|
289138
|
+
/**
|
|
289139
|
+
* Generate a compact markdown context document for IDE rules.
|
|
289140
|
+
* This replaces the old static rule generation with intelligence-driven context.
|
|
289141
|
+
*/
|
|
289142
|
+
generateContextDocument() {
|
|
289143
|
+
const ctx = this.synthesize();
|
|
289144
|
+
const lines = [];
|
|
289145
|
+
lines.push(`# ${ctx.projectIdentity.name} \u2014 AI Context`);
|
|
289146
|
+
lines.push(`<!-- Generated by @repo/context-engine at ${ctx.generatedAt} -->`);
|
|
289147
|
+
lines.push("");
|
|
289148
|
+
lines.push("## Project Identity");
|
|
289149
|
+
lines.push(`- **Stack**: ${ctx.projectIdentity.stack}`);
|
|
289150
|
+
lines.push(`- **Architecture**: ${ctx.projectIdentity.architecture}`);
|
|
289151
|
+
if (ctx.projectIdentity.keyPatterns.length > 0) {
|
|
289152
|
+
lines.push(`- **Key Patterns**: ${ctx.projectIdentity.keyPatterns.join(", ")}`);
|
|
289153
|
+
}
|
|
289154
|
+
lines.push("");
|
|
289155
|
+
if (ctx.codebaseDNA.conventions.length > 0) {
|
|
289156
|
+
lines.push("## Conventions (Auto-Discovered)");
|
|
289157
|
+
for (const conv of ctx.codebaseDNA.conventions) {
|
|
289158
|
+
lines.push(`- ${conv}`);
|
|
289159
|
+
}
|
|
289160
|
+
lines.push("");
|
|
289161
|
+
}
|
|
289162
|
+
if (ctx.architecturalRules.length > 0) {
|
|
289163
|
+
lines.push("## Architecture Rules");
|
|
289164
|
+
for (const rule of ctx.architecturalRules) {
|
|
289165
|
+
const icon = rule.severity === "error" ? "MUST" : rule.severity === "warning" ? "SHOULD" : "MAY";
|
|
289166
|
+
lines.push(`- **[${icon}]** ${rule.name}: ${rule.description}`);
|
|
289167
|
+
if (rule.violationCount > 0) {
|
|
289168
|
+
lines.push(` - ${rule.violationCount} active violations`);
|
|
289169
|
+
}
|
|
289170
|
+
}
|
|
289171
|
+
lines.push("");
|
|
289172
|
+
}
|
|
289173
|
+
if (ctx.codebaseDNA.boundaries.length > 0) {
|
|
289174
|
+
lines.push("## Module Boundaries");
|
|
289175
|
+
for (const boundary of ctx.codebaseDNA.boundaries) {
|
|
289176
|
+
lines.push(`- ${boundary}`);
|
|
289177
|
+
}
|
|
289178
|
+
lines.push("");
|
|
289179
|
+
}
|
|
289180
|
+
if (ctx.codebaseDNA.hotFiles.length > 0) {
|
|
289181
|
+
lines.push("## High-Impact Files (Edit With Care)");
|
|
289182
|
+
for (const file of ctx.codebaseDNA.hotFiles.slice(0, 10)) {
|
|
289183
|
+
lines.push(`- \`${file}\``);
|
|
289184
|
+
}
|
|
289185
|
+
lines.push("");
|
|
289186
|
+
}
|
|
289187
|
+
if (ctx.riskBriefing.securityConcerns.length > 0 || ctx.riskBriefing.testGaps.length > 0) {
|
|
289188
|
+
lines.push("## Risk Areas");
|
|
289189
|
+
for (const concern of ctx.riskBriefing.securityConcerns) {
|
|
289190
|
+
lines.push(`- ${concern}`);
|
|
289191
|
+
}
|
|
289192
|
+
for (const gap of ctx.riskBriefing.testGaps.slice(0, 5)) {
|
|
289193
|
+
lines.push(`- ${gap}`);
|
|
289194
|
+
}
|
|
289195
|
+
lines.push("");
|
|
289196
|
+
}
|
|
289197
|
+
if (ctx.projectIdentity.noGoZones.length > 0) {
|
|
289198
|
+
lines.push("## No-Go Zones");
|
|
289199
|
+
for (const zone of ctx.projectIdentity.noGoZones) {
|
|
289200
|
+
lines.push(`- ${zone}`);
|
|
289201
|
+
}
|
|
289202
|
+
lines.push("");
|
|
289203
|
+
}
|
|
289204
|
+
if (ctx.taskPlaybooks.length > 0) {
|
|
289205
|
+
lines.push("## Task Playbooks");
|
|
289206
|
+
for (const playbook of ctx.taskPlaybooks) {
|
|
289207
|
+
lines.push(`### ${playbook.taskType}`);
|
|
289208
|
+
for (const step of playbook.steps) {
|
|
289209
|
+
lines.push(`1. ${step}`);
|
|
289210
|
+
}
|
|
289211
|
+
if (playbook.mustVerify.length > 0) {
|
|
289212
|
+
lines.push(`**Verify**: ${playbook.mustVerify.join(", ")}`);
|
|
289213
|
+
}
|
|
289214
|
+
lines.push("");
|
|
289215
|
+
}
|
|
289216
|
+
}
|
|
289217
|
+
if (ctx.verificationSteps.length > 0) {
|
|
289218
|
+
lines.push("## Verification Protocol");
|
|
289219
|
+
for (const step of ctx.verificationSteps) {
|
|
289220
|
+
lines.push(`### On ${step.trigger}`);
|
|
289221
|
+
for (const check of step.checks) {
|
|
289222
|
+
lines.push(`- ${check}`);
|
|
289223
|
+
}
|
|
289224
|
+
if (step.commands.length > 0) {
|
|
289225
|
+
lines.push(`**Run**: \`${step.commands.join(" && ")}\``);
|
|
289226
|
+
}
|
|
289227
|
+
lines.push("");
|
|
289228
|
+
}
|
|
289229
|
+
}
|
|
289230
|
+
lines.push("## Codebase Health");
|
|
289231
|
+
lines.push(`- **Overall**: ${this.dna.healthScore.overall}/100`);
|
|
289232
|
+
const dims = this.dna.healthScore.dimensions;
|
|
289233
|
+
lines.push(`- Architecture: ${dims.architecture} | Tests: ${dims.testCoverage} | Conventions: ${dims.conventions} | Dependencies: ${dims.dependencies}`);
|
|
289234
|
+
lines.push("");
|
|
289235
|
+
lines.push("---");
|
|
289236
|
+
lines.push("<!-- context-engine:v2 -->");
|
|
289237
|
+
return lines.join("\n");
|
|
289238
|
+
}
|
|
289239
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289240
|
+
// IDENTITY
|
|
289241
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289242
|
+
buildProjectIdentity() {
|
|
289243
|
+
const fp = this.dna.fingerprint;
|
|
289244
|
+
const stack = [fp.framework, fp.language, fp.orm, fp.validator, fp.authLib, fp.router].filter(Boolean).join(" | ");
|
|
289245
|
+
const keyPatterns = this.dna.patterns.map((p) => p.name);
|
|
289246
|
+
const criticalPaths = this.dna.hotspots.slice(0, 5).map((h) => h.file);
|
|
289247
|
+
const noGoZones = [];
|
|
289248
|
+
if (this.ruleResult) {
|
|
289249
|
+
const errorRules = this.ruleResult.violations.filter((v) => v.severity === "error");
|
|
289250
|
+
const uniqueMessages = [...new Set(errorRules.map((v) => v.message))];
|
|
289251
|
+
noGoZones.push(...uniqueMessages.slice(0, 5));
|
|
289252
|
+
}
|
|
289253
|
+
for (const cycle of this.graph.cycles) {
|
|
289254
|
+
noGoZones.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}...`);
|
|
289255
|
+
}
|
|
289256
|
+
const architecture = this.dna.conventions.filter((c) => c.area === "structure").map((c) => c.description).join("; ") || fp.framework;
|
|
289257
|
+
return {
|
|
289258
|
+
name: fp.name,
|
|
289259
|
+
stack,
|
|
289260
|
+
architecture,
|
|
289261
|
+
keyPatterns,
|
|
289262
|
+
criticalPaths,
|
|
289263
|
+
noGoZones: noGoZones.slice(0, 10)
|
|
289264
|
+
};
|
|
289265
|
+
}
|
|
289266
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289267
|
+
// RULES SUMMARY
|
|
289268
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289269
|
+
buildArchRuleSummary() {
|
|
289270
|
+
if (!this.ruleResult) return [];
|
|
289271
|
+
const breakdown = this.ruleResult.ruleBreakdown;
|
|
289272
|
+
return Object.entries(breakdown).map(([ruleId, count]) => {
|
|
289273
|
+
const violation = this.ruleResult.violations.find((v) => v.ruleId === ruleId);
|
|
289274
|
+
return {
|
|
289275
|
+
id: ruleId,
|
|
289276
|
+
name: violation?.ruleName || ruleId,
|
|
289277
|
+
type: "import_forbidden",
|
|
289278
|
+
severity: violation?.severity || "warning",
|
|
289279
|
+
scope: violation?.sourceSymbol.filePath || "",
|
|
289280
|
+
description: violation?.message || "",
|
|
289281
|
+
violationCount: count
|
|
289282
|
+
};
|
|
289283
|
+
});
|
|
289284
|
+
}
|
|
289285
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289286
|
+
// DNA SUMMARY
|
|
289287
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289288
|
+
buildDNASummary() {
|
|
289289
|
+
return {
|
|
289290
|
+
conventions: this.dna.conventions.filter((c) => c.confidence > 0.5).map((c) => c.description),
|
|
289291
|
+
patterns: this.dna.patterns.map((p) => `${p.name}: ${p.description}`),
|
|
289292
|
+
boundaries: this.dna.boundaries.filter((b) => b.importCount > 3).map((b) => `${b.from} \u2192 ${b.to} (${b.importCount} imports${b.isCircular ? ", CIRCULAR" : ""})`),
|
|
289293
|
+
hotFiles: this.dna.hotspots.slice(0, 10).map((h) => h.file),
|
|
289294
|
+
riskAreas: this.dna.riskMap.filter((r) => r.riskLevel === "critical" || r.riskLevel === "high").map((r) => `${r.file}: ${r.factors[0]}`)
|
|
289295
|
+
};
|
|
289296
|
+
}
|
|
289297
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289298
|
+
// FILE CONTEXTS
|
|
289299
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289300
|
+
buildFileContexts() {
|
|
289301
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
289302
|
+
for (const file of this.data.files) {
|
|
289303
|
+
contexts.set(file.relativePath, this.buildSingleFileContext(file));
|
|
289304
|
+
}
|
|
289305
|
+
return contexts;
|
|
289306
|
+
}
|
|
289307
|
+
buildSingleFileContext(file) {
|
|
289308
|
+
const rel = file.relativePath;
|
|
289309
|
+
const role = this.classifyRole(file);
|
|
289310
|
+
const graphNode = this.graph.nodes.find((n) => n.relativePath === rel);
|
|
289311
|
+
const layer = graphNode?.layer;
|
|
289312
|
+
const dependsOn = this.graph.edges.filter((e) => e.from === rel).map((e) => e.to);
|
|
289313
|
+
const dependedOnBy = this.graph.edges.filter((e) => e.to === rel).map((e) => e.from);
|
|
289314
|
+
const applicableRules = [];
|
|
289315
|
+
if (this.ruleResult) {
|
|
289316
|
+
for (const v of this.ruleResult.violations) {
|
|
289317
|
+
if (v.sourceSymbol.filePath.includes(rel) || v.targetSymbol && v.targetSymbol.filePath.includes(rel)) {
|
|
289318
|
+
if (!applicableRules.includes(v.ruleId)) applicableRules.push(v.ruleId);
|
|
289319
|
+
}
|
|
289320
|
+
}
|
|
289321
|
+
}
|
|
289322
|
+
const conventions = this.dna.conventions.filter((c) => this.conventionAppliesToFile(c.area, file)).map((c) => c.description);
|
|
289323
|
+
const patterns = this.dna.patterns.filter((p) => p.fileMatches.some((m) => m === rel)).map((p) => p.name);
|
|
289324
|
+
const riskEntry = this.dna.riskMap.find((r) => r.file === rel);
|
|
289325
|
+
const riskLevel = riskEntry?.riskLevel || "low";
|
|
289326
|
+
const relatedFiles = this.findRelatedFiles(file, role).slice(0, 8);
|
|
289327
|
+
const editGuidance = this.generateEditGuidance(file, role, layer, dependedOnBy, conventions);
|
|
289328
|
+
return {
|
|
289329
|
+
filePath: file.path,
|
|
289330
|
+
role,
|
|
289331
|
+
layer,
|
|
289332
|
+
dependsOn,
|
|
289333
|
+
dependedOnBy,
|
|
289334
|
+
applicableRules,
|
|
289335
|
+
conventions,
|
|
289336
|
+
patterns,
|
|
289337
|
+
riskLevel,
|
|
289338
|
+
relatedFiles,
|
|
289339
|
+
editGuidance
|
|
289340
|
+
};
|
|
289341
|
+
}
|
|
289342
|
+
classifyRole(file) {
|
|
289343
|
+
const rel = file.relativePath.toLowerCase();
|
|
289344
|
+
if (rel.includes(".test.") || rel.includes(".spec.") || rel.includes("__tests__")) return "test";
|
|
289345
|
+
if (rel.includes("fixture") || rel.includes("mock")) return "fixture";
|
|
289346
|
+
if (rel.includes("migration")) return "migration";
|
|
289347
|
+
if (rel.match(/\.(css|scss|less|styl)$/)) return "style";
|
|
289348
|
+
if (rel.includes(".config.") || rel.includes("config/") || rel === "tsconfig.json") return "config";
|
|
289349
|
+
if (rel.includes("middleware")) return "middleware";
|
|
289350
|
+
if (rel.includes("/api/") || rel.includes("route")) return "route-handler";
|
|
289351
|
+
if (rel.includes("service") || rel.includes("Service")) return "service";
|
|
289352
|
+
if (rel.includes("repositor") || rel.includes("Repositor")) return "repository";
|
|
289353
|
+
if (rel.endsWith(".tsx") && !rel.includes("page.")) return "component";
|
|
289354
|
+
if (rel.includes("/types") || rel.endsWith(".d.ts")) return "type";
|
|
289355
|
+
if (rel.includes("util") || rel.includes("helper") || rel.includes("lib/")) return "util";
|
|
289356
|
+
if (rel.includes("script") || rel.includes("bin/")) return "script";
|
|
289357
|
+
if (rel.match(/^(src\/)?index\.|^(src\/)?main\.|^(src\/)?app\./)) return "entry";
|
|
289358
|
+
return "unknown";
|
|
289359
|
+
}
|
|
289360
|
+
conventionAppliesToFile(area, file) {
|
|
289361
|
+
switch (area) {
|
|
289362
|
+
case "naming":
|
|
289363
|
+
return true;
|
|
289364
|
+
case "imports":
|
|
289365
|
+
return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
|
|
289366
|
+
case "exports":
|
|
289367
|
+
return file.exports.length > 0;
|
|
289368
|
+
case "testing":
|
|
289369
|
+
return file.relativePath.includes(".test.") || file.relativePath.includes(".spec.");
|
|
289370
|
+
case "error-handling":
|
|
289371
|
+
return !file.relativePath.includes(".test.");
|
|
289372
|
+
case "types":
|
|
289373
|
+
return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
|
|
289374
|
+
default:
|
|
289375
|
+
return true;
|
|
289376
|
+
}
|
|
289377
|
+
}
|
|
289378
|
+
findRelatedFiles(file, role) {
|
|
289379
|
+
const related = [];
|
|
289380
|
+
const dir = path5.dirname(file.relativePath);
|
|
289381
|
+
for (const other of this.data.files) {
|
|
289382
|
+
if (other.path === file.path) continue;
|
|
289383
|
+
if (path5.dirname(other.relativePath) === dir) {
|
|
289384
|
+
related.push(other.relativePath);
|
|
289385
|
+
}
|
|
289386
|
+
}
|
|
289387
|
+
if (related.length < 5) {
|
|
289388
|
+
for (const other of this.data.files) {
|
|
289389
|
+
if (other.path === file.path) continue;
|
|
289390
|
+
if (related.includes(other.relativePath)) continue;
|
|
289391
|
+
if (this.classifyRole(other) === role) {
|
|
289392
|
+
related.push(other.relativePath);
|
|
289393
|
+
if (related.length >= 8) break;
|
|
289394
|
+
}
|
|
289395
|
+
}
|
|
289396
|
+
}
|
|
289397
|
+
return related;
|
|
289398
|
+
}
|
|
289399
|
+
generateEditGuidance(file, role, layer, dependedOnBy, conventions) {
|
|
289400
|
+
const guidance = [];
|
|
289401
|
+
if (dependedOnBy.length > 10) {
|
|
289402
|
+
guidance.push(`HIGH IMPACT: ${dependedOnBy.length} files depend on this. Changes have wide blast radius.`);
|
|
289403
|
+
}
|
|
289404
|
+
switch (role) {
|
|
289405
|
+
case "route-handler":
|
|
289406
|
+
guidance.push("Validate all inputs with schemas before processing.");
|
|
289407
|
+
guidance.push("Return consistent response shapes ({ success, data } or { success, error }).");
|
|
289408
|
+
guidance.push("Ensure authentication middleware is applied to protected endpoints.");
|
|
289409
|
+
break;
|
|
289410
|
+
case "service":
|
|
289411
|
+
guidance.push("Keep business logic here, not in controllers/routes.");
|
|
289412
|
+
guidance.push("Use dependency injection for testability.");
|
|
289413
|
+
if (layer) guidance.push(`This is in the ${layer} layer \u2014 only import from lower layers.`);
|
|
289414
|
+
break;
|
|
289415
|
+
case "repository":
|
|
289416
|
+
guidance.push("Only data access logic belongs here \u2014 no business rules.");
|
|
289417
|
+
guidance.push("Return domain objects, not raw database rows.");
|
|
289418
|
+
break;
|
|
289419
|
+
case "component":
|
|
289420
|
+
guidance.push("Keep components focused and composable.");
|
|
289421
|
+
guidance.push("Extract complex logic to custom hooks.");
|
|
289422
|
+
break;
|
|
289423
|
+
case "middleware":
|
|
289424
|
+
guidance.push("Middleware must call next() or return a response \u2014 never leave the request hanging.");
|
|
289425
|
+
guidance.push("Keep middleware focused on a single concern.");
|
|
289426
|
+
break;
|
|
289427
|
+
case "test":
|
|
289428
|
+
guidance.push("Follow Arrange-Act-Assert pattern.");
|
|
289429
|
+
guidance.push("Test edge cases and error conditions, not just happy path.");
|
|
289430
|
+
break;
|
|
289431
|
+
}
|
|
289432
|
+
for (const conv of conventions.slice(0, 3)) {
|
|
289433
|
+
guidance.push(`Convention: ${conv}`);
|
|
289434
|
+
}
|
|
289435
|
+
return guidance;
|
|
289436
|
+
}
|
|
289437
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289438
|
+
// TASK PLAYBOOKS
|
|
289439
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289440
|
+
buildTaskPlaybooks() {
|
|
289441
|
+
const fp = this.dna.fingerprint;
|
|
289442
|
+
const playbooks = [];
|
|
289443
|
+
playbooks.push({
|
|
289444
|
+
taskType: "Bug Fix",
|
|
289445
|
+
steps: [
|
|
289446
|
+
"Reproduce the bug and understand the expected vs actual behavior",
|
|
289447
|
+
"Identify the root cause file(s) using the dependency graph",
|
|
289448
|
+
"Write a failing test that reproduces the bug",
|
|
289449
|
+
"Apply the minimal fix at the root cause",
|
|
289450
|
+
"Verify the fix passes the test and does not break existing tests",
|
|
289451
|
+
"Check that the fix does not violate any architecture rules"
|
|
289452
|
+
],
|
|
289453
|
+
mustRead: this.dna.hotspots.slice(0, 3).map((h) => h.file),
|
|
289454
|
+
mustUpdate: ["The buggy file", "Related test file"],
|
|
289455
|
+
mustVerify: ["All existing tests pass", "New regression test passes", "No new arch rule violations"],
|
|
289456
|
+
stopConditions: ["Never modify tests to make them pass \u2014 fix the code", "Do not change public API signatures without discussion"]
|
|
289457
|
+
});
|
|
289458
|
+
if (fp.router) {
|
|
289459
|
+
playbooks.push({
|
|
289460
|
+
taskType: "Add API Endpoint",
|
|
289461
|
+
steps: [
|
|
289462
|
+
"Check existing routes in truthpack to avoid duplicates",
|
|
289463
|
+
"Create the route handler following existing patterns",
|
|
289464
|
+
"Add input validation using the project validator",
|
|
289465
|
+
"Add authentication middleware if the route is protected",
|
|
289466
|
+
"Write tests for success, validation failure, and auth failure",
|
|
289467
|
+
"Update the truthpack (run vibecheck scan)"
|
|
289468
|
+
],
|
|
289469
|
+
mustRead: ["truthpack/routes.json", ...this.dna.patterns.filter((p) => p.category === "api").map((p) => p.exemplar)],
|
|
289470
|
+
mustUpdate: ["Route file", "Test file", "Truthpack"],
|
|
289471
|
+
mustVerify: ["Route responds correctly", "Input validation works", "Auth is enforced", "Test passes"],
|
|
289472
|
+
stopConditions: ["Do not create duplicate routes", "Do not hardcode mock data in handlers"]
|
|
289473
|
+
});
|
|
289474
|
+
}
|
|
289475
|
+
if (fp.framework.includes("Next") || fp.framework.includes("React")) {
|
|
289476
|
+
playbooks.push({
|
|
289477
|
+
taskType: "Add UI Component",
|
|
289478
|
+
steps: [
|
|
289479
|
+
"Check if a similar component already exists",
|
|
289480
|
+
"Create the component following existing naming and structure patterns",
|
|
289481
|
+
"Add TypeScript props interface",
|
|
289482
|
+
"Add unit test for the component",
|
|
289483
|
+
"If using state, determine if it should be a client component"
|
|
289484
|
+
],
|
|
289485
|
+
mustRead: this.dna.patterns.filter((p) => p.category === "ui" || p.category === "state").map((p) => p.exemplar),
|
|
289486
|
+
mustUpdate: ["Component file", "Test file", "Parent component that uses it"],
|
|
289487
|
+
mustVerify: ["Component renders correctly", "Props are typed", "Test passes"],
|
|
289488
|
+
stopConditions: ['Do not use "any" type for props', 'Do not add useState in server components without "use client"']
|
|
289489
|
+
});
|
|
289490
|
+
}
|
|
289491
|
+
playbooks.push({
|
|
289492
|
+
taskType: "Refactor",
|
|
289493
|
+
steps: [
|
|
289494
|
+
"Identify all callers/dependents of the code being refactored",
|
|
289495
|
+
"Ensure comprehensive tests exist before refactoring",
|
|
289496
|
+
"Apply changes incrementally, testing after each step",
|
|
289497
|
+
"Update all dependents to use the new API",
|
|
289498
|
+
"Remove old code only after all dependents are migrated",
|
|
289499
|
+
"Verify no architecture rules are violated"
|
|
289500
|
+
],
|
|
289501
|
+
mustRead: ["Dependency graph for affected files"],
|
|
289502
|
+
mustUpdate: ["Refactored file", "All dependent files", "Tests"],
|
|
289503
|
+
mustVerify: ["All tests pass", "No new violations", "No regressions"],
|
|
289504
|
+
stopConditions: ["Never break existing public APIs without migration path", "Do not refactor and add features in the same change"]
|
|
289505
|
+
});
|
|
289506
|
+
return playbooks;
|
|
289507
|
+
}
|
|
289508
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289509
|
+
// VERIFICATION STEPS
|
|
289510
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289511
|
+
buildVerificationSteps() {
|
|
289512
|
+
const fp = this.dna.fingerprint;
|
|
289513
|
+
const steps = [];
|
|
289514
|
+
if (fp.language === "TypeScript") {
|
|
289515
|
+
steps.push({
|
|
289516
|
+
trigger: "Any TypeScript file change",
|
|
289517
|
+
checks: ["TypeScript compilation succeeds", "No new type errors introduced"],
|
|
289518
|
+
commands: [fp.packageManager === "pnpm" ? "pnpm run check-types" : "npm run check-types"],
|
|
289519
|
+
artifacts: []
|
|
289520
|
+
});
|
|
289521
|
+
}
|
|
289522
|
+
if (fp.testRunner) {
|
|
289523
|
+
steps.push({
|
|
289524
|
+
trigger: "Any source file change",
|
|
289525
|
+
checks: ["Related tests pass", "No test regressions"],
|
|
289526
|
+
commands: [`${fp.packageManager} run test`],
|
|
289527
|
+
artifacts: ["test-results.json"]
|
|
289528
|
+
});
|
|
289529
|
+
}
|
|
289530
|
+
if (fp.router) {
|
|
289531
|
+
steps.push({
|
|
289532
|
+
trigger: "Route handler added or modified",
|
|
289533
|
+
checks: ["Route responds with correct status", "Auth middleware is applied", "Input validation works"],
|
|
289534
|
+
commands: ["vibecheck scan"],
|
|
289535
|
+
artifacts: ["truthpack/routes.json"]
|
|
289536
|
+
});
|
|
289537
|
+
}
|
|
289538
|
+
steps.push({
|
|
289539
|
+
trigger: "Any source file change",
|
|
289540
|
+
checks: ["No new architecture rule violations", "No new circular dependencies"],
|
|
289541
|
+
commands: ["vibecheck arch-rules"],
|
|
289542
|
+
artifacts: []
|
|
289543
|
+
});
|
|
289544
|
+
return steps;
|
|
289545
|
+
}
|
|
289546
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289547
|
+
// RISK BRIEFING
|
|
289548
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289549
|
+
buildRiskBriefing() {
|
|
289550
|
+
const criticalFiles = this.dna.hotspots.filter((h) => h.score > 30).slice(0, 10).map((h) => h.file);
|
|
289551
|
+
const recentViolations = this.ruleResult?.violations.filter((v) => v.severity === "error").slice(0, 10) || [];
|
|
289552
|
+
const securityConcerns = [];
|
|
289553
|
+
for (const risk of this.dna.riskMap) {
|
|
289554
|
+
if (risk.riskLevel === "critical") {
|
|
289555
|
+
securityConcerns.push(`${risk.file}: ${risk.factors.join(", ")}`);
|
|
289556
|
+
}
|
|
289557
|
+
}
|
|
289558
|
+
const testGaps = [];
|
|
289559
|
+
const sourceFiles = this.data.files.filter(
|
|
289560
|
+
(f) => !f.relativePath.includes(".test.") && !f.relativePath.includes(".spec.") && (f.relativePath.endsWith(".ts") || f.relativePath.endsWith(".tsx")) && !f.relativePath.includes("config") && !f.relativePath.includes(".d.ts")
|
|
289561
|
+
);
|
|
289562
|
+
const testFiles = this.data.files.filter(
|
|
289563
|
+
(f) => f.relativePath.includes(".test.") || f.relativePath.includes(".spec.")
|
|
289564
|
+
);
|
|
289565
|
+
const testedBases = new Set(testFiles.map(
|
|
289566
|
+
(f) => path5.basename(f.relativePath).replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "")
|
|
289567
|
+
));
|
|
289568
|
+
for (const file of sourceFiles) {
|
|
289569
|
+
const baseName = path5.basename(file.relativePath).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
289570
|
+
if (!testedBases.has(baseName) && file.exports.length > 0) {
|
|
289571
|
+
testGaps.push(`${file.relativePath} has exports but no test file`);
|
|
289572
|
+
}
|
|
289573
|
+
}
|
|
289574
|
+
const driftWarnings = [];
|
|
289575
|
+
for (const cycle of this.graph.cycles) {
|
|
289576
|
+
driftWarnings.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}${cycle.nodes.length > 3 ? "..." : ""}`);
|
|
289577
|
+
}
|
|
289578
|
+
return {
|
|
289579
|
+
criticalFiles,
|
|
289580
|
+
recentViolations,
|
|
289581
|
+
securityConcerns: securityConcerns.slice(0, 5),
|
|
289582
|
+
testGaps: testGaps.slice(0, 10),
|
|
289583
|
+
driftWarnings: driftWarnings.slice(0, 5)
|
|
289584
|
+
};
|
|
289585
|
+
}
|
|
289586
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289587
|
+
// HELPERS
|
|
289588
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289589
|
+
toRelative(filePath) {
|
|
289590
|
+
if (filePath.startsWith(this.rootPath)) {
|
|
289591
|
+
return filePath.slice(this.rootPath.length + 1).replace(/\\/g, "/");
|
|
289592
|
+
}
|
|
289593
|
+
return filePath.replace(/\\/g, "/");
|
|
289594
|
+
}
|
|
289595
|
+
};
|
|
289596
|
+
var ContextExplainer = class {
|
|
289597
|
+
/**
|
|
289598
|
+
* Generate a rich explanation for a file.
|
|
289599
|
+
*/
|
|
289600
|
+
explain(filePath, ctx) {
|
|
289601
|
+
const fc = ctx.fileContext;
|
|
289602
|
+
const paragraphs = [];
|
|
289603
|
+
const quickFacts = [];
|
|
289604
|
+
const warnings = [];
|
|
289605
|
+
const relatedFiles = [];
|
|
289606
|
+
const overlays = [];
|
|
289607
|
+
const role = fc?.role ?? "unknown";
|
|
289608
|
+
const roleSummary = this.buildRoleSummary(filePath, role, ctx);
|
|
289609
|
+
if (fc) {
|
|
289610
|
+
const depCount = fc.dependedOnBy.length;
|
|
289611
|
+
const depsOnCount = fc.dependsOn.length;
|
|
289612
|
+
if (depCount > 0 || depsOnCount > 0) {
|
|
289613
|
+
const parts2 = [];
|
|
289614
|
+
if (depCount > 0) {
|
|
289615
|
+
const critical = depCount > 10 ? " \u2014 changes here have wide blast radius" : "";
|
|
289616
|
+
parts2.push(`${depCount} file${depCount > 1 ? "s" : ""} depend on this${critical}`);
|
|
289617
|
+
}
|
|
289618
|
+
if (depsOnCount > 0) {
|
|
289619
|
+
parts2.push(`it imports from ${depsOnCount} file${depsOnCount > 1 ? "s" : ""}`);
|
|
289620
|
+
}
|
|
289621
|
+
paragraphs.push({
|
|
289622
|
+
heading: "Dependencies",
|
|
289623
|
+
text: parts2.join(". ") + ".",
|
|
289624
|
+
importance: depCount > 10 ? "critical" : depCount > 5 ? "high" : "medium"
|
|
289625
|
+
});
|
|
289626
|
+
}
|
|
289627
|
+
quickFacts.push({ label: "Role", value: role, icon: "layer" });
|
|
289628
|
+
if (fc.layer) quickFacts.push({ label: "Layer", value: fc.layer, icon: "layer" });
|
|
289629
|
+
quickFacts.push({ label: "Dependents", value: String(depCount), icon: "dependency" });
|
|
289630
|
+
quickFacts.push({ label: "Dependencies", value: String(depsOnCount), icon: "dependency" });
|
|
289631
|
+
if (fc.riskLevel === "critical" || fc.riskLevel === "high") {
|
|
289632
|
+
quickFacts.push({ label: "Risk", value: fc.riskLevel.toUpperCase(), icon: "warning" });
|
|
289633
|
+
warnings.push({
|
|
289634
|
+
message: `This file is classified as ${fc.riskLevel} risk`,
|
|
289635
|
+
severity: fc.riskLevel === "critical" ? "error" : "warning",
|
|
289636
|
+
action: "Add comprehensive tests and review carefully before merging changes"
|
|
289637
|
+
});
|
|
289638
|
+
}
|
|
289639
|
+
for (const dep of fc.dependedOnBy.slice(0, 5)) {
|
|
289640
|
+
relatedFiles.push({
|
|
289641
|
+
filePath: dep,
|
|
289642
|
+
reason: `Imports from this file`,
|
|
289643
|
+
relationship: "depended-by",
|
|
289644
|
+
confidence: 0.9
|
|
289645
|
+
});
|
|
289646
|
+
}
|
|
289647
|
+
overlays.push({
|
|
289648
|
+
type: "code-lens",
|
|
289649
|
+
line: 1,
|
|
289650
|
+
text: `${depCount} dependent${depCount !== 1 ? "s" : ""} \xB7 ${depsOnCount} import${depsOnCount !== 1 ? "s" : ""} \xB7 ${role}`,
|
|
289651
|
+
tooltip: `This ${role} file has ${depCount} files that depend on it and imports from ${depsOnCount} files`
|
|
289652
|
+
});
|
|
289653
|
+
}
|
|
289654
|
+
if (fc && fc.conventions.length > 0) {
|
|
289655
|
+
paragraphs.push({
|
|
289656
|
+
heading: "Conventions",
|
|
289657
|
+
text: `Follow these discovered conventions: ${fc.conventions.slice(0, 3).join("; ")}.`,
|
|
289658
|
+
importance: "medium"
|
|
289659
|
+
});
|
|
289660
|
+
}
|
|
289661
|
+
if (ctx.rules) {
|
|
289662
|
+
const fileViolations = ctx.rules.violations.filter(
|
|
289663
|
+
(v) => v.sourceSymbol.filePath.includes(shortName(filePath)) || v.targetSymbol && v.targetSymbol.filePath.includes(shortName(filePath))
|
|
289664
|
+
);
|
|
289665
|
+
if (fileViolations.length > 0) {
|
|
289666
|
+
const errors = fileViolations.filter((v) => v.severity === "error");
|
|
289667
|
+
const warns = fileViolations.filter((v) => v.severity === "warning");
|
|
289668
|
+
paragraphs.push({
|
|
289669
|
+
heading: "Architecture Violations",
|
|
289670
|
+
text: `${errors.length} error${errors.length !== 1 ? "s" : ""} and ${warns.length} warning${warns.length !== 1 ? "s" : ""} from architecture rules.`,
|
|
289671
|
+
importance: errors.length > 0 ? "critical" : "high"
|
|
289672
|
+
});
|
|
289673
|
+
for (const v of fileViolations.slice(0, 5)) {
|
|
289674
|
+
warnings.push({
|
|
289675
|
+
message: v.message,
|
|
289676
|
+
severity: v.severity === "error" ? "error" : "warning",
|
|
289677
|
+
action: v.suggestedFix ?? "Review and fix the violation",
|
|
289678
|
+
ruleId: v.ruleId
|
|
289679
|
+
});
|
|
289680
|
+
overlays.push({
|
|
289681
|
+
type: "diagnostic",
|
|
289682
|
+
line: v.sourceSymbol.line,
|
|
289683
|
+
text: v.message,
|
|
289684
|
+
severity: v.severity === "error" ? "error" : "warning",
|
|
289685
|
+
tooltip: v.suggestedFix
|
|
289686
|
+
});
|
|
289687
|
+
}
|
|
289688
|
+
}
|
|
289689
|
+
}
|
|
289690
|
+
if (ctx.callGraph) {
|
|
289691
|
+
const fileNodes = ctx.callGraph.nodes.filter((n) => n.filePath.includes(shortName(filePath)));
|
|
289692
|
+
const hotFunctions = fileNodes.filter((n) => n.callerCount > 5);
|
|
289693
|
+
if (hotFunctions.length > 0) {
|
|
289694
|
+
paragraphs.push({
|
|
289695
|
+
heading: "Hot Functions",
|
|
289696
|
+
text: hotFunctions.map(
|
|
289697
|
+
(f) => `\`${f.name}\` is called by ${f.callerCount} function${f.callerCount !== 1 ? "s" : ""}${f.calleeCount > 0 ? ` and calls ${f.calleeCount}` : ""}`
|
|
289698
|
+
).join(". ") + ".",
|
|
289699
|
+
importance: "high"
|
|
289700
|
+
});
|
|
289701
|
+
for (const fn of hotFunctions) {
|
|
289702
|
+
overlays.push({
|
|
289703
|
+
type: "code-lens",
|
|
289704
|
+
line: 0,
|
|
289705
|
+
// Would need symbol line mapping
|
|
289706
|
+
text: `${fn.callerCount} callers \xB7 ${fn.calleeCount} callees`,
|
|
289707
|
+
tooltip: `Function ${fn.name} has ${fn.callerCount} callers and ${fn.calleeCount} callees`
|
|
289708
|
+
});
|
|
289709
|
+
}
|
|
289710
|
+
}
|
|
289711
|
+
const deadInFile = ctx.callGraph.stats.deadFunctions.filter(
|
|
289712
|
+
(d) => d.filePath.includes(shortName(filePath))
|
|
289713
|
+
);
|
|
289714
|
+
if (deadInFile.length > 0) {
|
|
289715
|
+
for (const dead of deadInFile) {
|
|
289716
|
+
warnings.push({
|
|
289717
|
+
message: `\`${dead.name}\` appears to be dead code (exported but never called)`,
|
|
289718
|
+
severity: "info",
|
|
289719
|
+
action: "Verify this function is not called via dynamic dispatch or external consumers, then consider removing it"
|
|
289720
|
+
});
|
|
289721
|
+
}
|
|
289722
|
+
}
|
|
289723
|
+
}
|
|
289724
|
+
if (ctx.temporal) {
|
|
289725
|
+
const hotspot = ctx.temporal.changeHotspots.find((h) => filePath.includes(h.file) || h.file.includes(shortName(filePath)));
|
|
289726
|
+
if (hotspot) {
|
|
289727
|
+
const daysSince = Math.round((Date.now() - new Date(hotspot.lastChanged).getTime()) / 864e5);
|
|
289728
|
+
paragraphs.push({
|
|
289729
|
+
heading: "Recent Activity",
|
|
289730
|
+
text: `Changed ${hotspot.commits} times in the last ${ctx.temporal.stats.analysisWindowDays} days by ${hotspot.authors} author${hotspot.authors !== 1 ? "s" : ""}. Last modified ${daysSince} day${daysSince !== 1 ? "s" : ""} ago.`,
|
|
289731
|
+
importance: hotspot.commits > 10 ? "high" : "medium"
|
|
289732
|
+
});
|
|
289733
|
+
quickFacts.push({ label: "Recent commits", value: String(hotspot.commits), icon: "git" });
|
|
289734
|
+
quickFacts.push({ label: "Last changed", value: `${daysSince}d ago`, icon: "git" });
|
|
289735
|
+
quickFacts.push({ label: "Authors", value: String(hotspot.authors), icon: "git" });
|
|
289736
|
+
}
|
|
289737
|
+
const churn = ctx.temporal.churnFiles.find((c) => filePath.includes(c.file) || c.file.includes(shortName(filePath)));
|
|
289738
|
+
if (churn && churn.severity !== "low") {
|
|
289739
|
+
warnings.push({
|
|
289740
|
+
message: churn.reason,
|
|
289741
|
+
severity: churn.severity === "high" ? "warning" : "info",
|
|
289742
|
+
action: "Consider whether this file needs refactoring to reduce change frequency"
|
|
289743
|
+
});
|
|
289744
|
+
}
|
|
289745
|
+
const expertise = ctx.temporal.authorExpertise.find(
|
|
289746
|
+
(e) => filePath.startsWith(e.area) || filePath.includes(e.area)
|
|
289747
|
+
);
|
|
289748
|
+
if (expertise && expertise.busFactor === 1) {
|
|
289749
|
+
warnings.push({
|
|
289750
|
+
message: `Bus factor of 1 \u2014 ${expertise.primaryAuthor} has made ${expertise.authors[0]?.percentage}% of changes to this area`,
|
|
289751
|
+
severity: "info",
|
|
289752
|
+
action: "Consider knowledge sharing or pair programming for this area"
|
|
289753
|
+
});
|
|
289754
|
+
}
|
|
289755
|
+
}
|
|
289756
|
+
if (ctx.learned) {
|
|
289757
|
+
const coEdits = ctx.learned.coEdits.filter((p) => p.files[0].includes(shortName(filePath)) || p.files[1].includes(shortName(filePath))).slice(0, 3);
|
|
289758
|
+
if (coEdits.length > 0) {
|
|
289759
|
+
for (const pair of coEdits) {
|
|
289760
|
+
const other = pair.files[0].includes(shortName(filePath)) ? pair.files[1] : pair.files[0];
|
|
289761
|
+
relatedFiles.push({
|
|
289762
|
+
filePath: other,
|
|
289763
|
+
reason: `Often edited together (${pair.count} times)`,
|
|
289764
|
+
relationship: "co-edited",
|
|
289765
|
+
confidence: pair.weight
|
|
289766
|
+
});
|
|
289767
|
+
}
|
|
289768
|
+
}
|
|
289769
|
+
}
|
|
289770
|
+
if (fc && fc.editGuidance.length > 0) {
|
|
289771
|
+
paragraphs.push({
|
|
289772
|
+
heading: "Edit Guidance",
|
|
289773
|
+
text: fc.editGuidance.join(" "),
|
|
289774
|
+
importance: "medium"
|
|
289775
|
+
});
|
|
289776
|
+
}
|
|
289777
|
+
return {
|
|
289778
|
+
filePath,
|
|
289779
|
+
roleSummary,
|
|
289780
|
+
paragraphs,
|
|
289781
|
+
quickFacts,
|
|
289782
|
+
warnings,
|
|
289783
|
+
relatedFiles,
|
|
289784
|
+
overlays
|
|
289785
|
+
};
|
|
289786
|
+
}
|
|
289787
|
+
/**
|
|
289788
|
+
* Generate a compact markdown explanation for AI agent consumption.
|
|
289789
|
+
*/
|
|
289790
|
+
explainForAgent(filePath, ctx) {
|
|
289791
|
+
const explanation = this.explain(filePath, ctx);
|
|
289792
|
+
const lines = [];
|
|
289793
|
+
lines.push(`## ${shortName(filePath)}: ${explanation.roleSummary}`);
|
|
289794
|
+
lines.push("");
|
|
289795
|
+
if (explanation.quickFacts.length > 0) {
|
|
289796
|
+
lines.push(explanation.quickFacts.map((f) => `**${f.label}**: ${f.value}`).join(" \xB7 "));
|
|
289797
|
+
lines.push("");
|
|
289798
|
+
}
|
|
289799
|
+
for (const p of explanation.paragraphs.filter((p2) => p2.importance === "critical" || p2.importance === "high")) {
|
|
289800
|
+
lines.push(`### ${p.heading}`);
|
|
289801
|
+
lines.push(p.text);
|
|
289802
|
+
lines.push("");
|
|
289803
|
+
}
|
|
289804
|
+
if (explanation.warnings.length > 0) {
|
|
289805
|
+
lines.push("### Warnings");
|
|
289806
|
+
for (const w of explanation.warnings) {
|
|
289807
|
+
const icon = w.severity === "error" ? "MUST FIX" : w.severity === "warning" ? "SHOULD FIX" : "NOTE";
|
|
289808
|
+
lines.push(`- **[${icon}]** ${w.message} \u2014 ${w.action}`);
|
|
289809
|
+
}
|
|
289810
|
+
lines.push("");
|
|
289811
|
+
}
|
|
289812
|
+
if (explanation.relatedFiles.length > 0) {
|
|
289813
|
+
lines.push("### Related Files");
|
|
289814
|
+
for (const rf of explanation.relatedFiles.slice(0, 5)) {
|
|
289815
|
+
lines.push(`- \`${rf.filePath}\` \u2014 ${rf.reason}`);
|
|
289816
|
+
}
|
|
289817
|
+
lines.push("");
|
|
289818
|
+
}
|
|
289819
|
+
return lines.join("\n");
|
|
289820
|
+
}
|
|
289821
|
+
/**
|
|
289822
|
+
* Generate IDE overlay data for the VS Code extension to consume.
|
|
289823
|
+
*/
|
|
289824
|
+
getIDEOverlays(filePath, ctx) {
|
|
289825
|
+
const explanation = this.explain(filePath, ctx);
|
|
289826
|
+
return explanation.overlays;
|
|
289827
|
+
}
|
|
289828
|
+
/**
|
|
289829
|
+
* Generate a health report explanation.
|
|
289830
|
+
*/
|
|
289831
|
+
explainHealth(health, dna) {
|
|
289832
|
+
const lines = [];
|
|
289833
|
+
const dims = health.dimensions;
|
|
289834
|
+
lines.push(`## Codebase Health: ${health.overall}/100`);
|
|
289835
|
+
lines.push("");
|
|
289836
|
+
const entries = [
|
|
289837
|
+
{ name: "Architecture", score: dims.architecture, explain: this.explainArchScore(dims.architecture, dna) },
|
|
289838
|
+
{ name: "Test Coverage", score: dims.testCoverage, explain: this.explainTestScore(dims.testCoverage) },
|
|
289839
|
+
{ name: "Conventions", score: dims.conventions, explain: this.explainConventionScore(dims.conventions, dna) },
|
|
289840
|
+
{ name: "Dependencies", score: dims.dependencies, explain: this.explainDependencyScore(dims.dependencies, dna) },
|
|
289841
|
+
{ name: "Security", score: dims.security, explain: dims.security >= 80 ? "No critical security concerns detected" : "Critical risk areas identified" },
|
|
289842
|
+
{ name: "Complexity", score: dims.complexity, explain: dims.complexity >= 80 ? "Complexity is well-managed" : "High-complexity hotspots detected" }
|
|
289843
|
+
];
|
|
289844
|
+
for (const entry of entries) {
|
|
289845
|
+
const bar = this.renderBar(entry.score);
|
|
289846
|
+
lines.push(`${bar} **${entry.name}**: ${entry.score}/100 \u2014 ${entry.explain}`);
|
|
289847
|
+
}
|
|
289848
|
+
return lines.join("\n");
|
|
289849
|
+
}
|
|
289850
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289851
|
+
// PRIVATE
|
|
289852
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
289853
|
+
buildRoleSummary(filePath, role, ctx) {
|
|
289854
|
+
const parts2 = [];
|
|
289855
|
+
switch (role) {
|
|
289856
|
+
case "service":
|
|
289857
|
+
parts2.push("Business logic service");
|
|
289858
|
+
break;
|
|
289859
|
+
case "route-handler":
|
|
289860
|
+
parts2.push("API route handler");
|
|
289861
|
+
break;
|
|
289862
|
+
case "component":
|
|
289863
|
+
parts2.push("UI component");
|
|
289864
|
+
break;
|
|
289865
|
+
case "repository":
|
|
289866
|
+
parts2.push("Data access layer");
|
|
289867
|
+
break;
|
|
289868
|
+
case "middleware":
|
|
289869
|
+
parts2.push("Request middleware");
|
|
289870
|
+
break;
|
|
289871
|
+
case "test":
|
|
289872
|
+
parts2.push("Test file");
|
|
289873
|
+
break;
|
|
289874
|
+
case "config":
|
|
289875
|
+
parts2.push("Configuration");
|
|
289876
|
+
break;
|
|
289877
|
+
case "type":
|
|
289878
|
+
parts2.push("Type definitions");
|
|
289879
|
+
break;
|
|
289880
|
+
case "util":
|
|
289881
|
+
parts2.push("Utility module");
|
|
289882
|
+
break;
|
|
289883
|
+
case "entry":
|
|
289884
|
+
parts2.push("Entry point");
|
|
289885
|
+
break;
|
|
289886
|
+
default:
|
|
289887
|
+
parts2.push("Source file");
|
|
289888
|
+
}
|
|
289889
|
+
if (ctx.fileContext) {
|
|
289890
|
+
if (ctx.fileContext.layer) parts2.push(`in ${ctx.fileContext.layer} layer`);
|
|
289891
|
+
if (ctx.fileContext.dependedOnBy.length > 10) parts2.push("(high-impact)");
|
|
289892
|
+
}
|
|
289893
|
+
return parts2.join(" ");
|
|
289894
|
+
}
|
|
289895
|
+
explainArchScore(score, dna) {
|
|
289896
|
+
if (score >= 80) return `Strong architecture with ${dna.patterns.length} recognized patterns`;
|
|
289897
|
+
if (score >= 50) return `Moderate architecture \u2014 ${dna.patterns.length} patterns detected, room to strengthen boundaries`;
|
|
289898
|
+
return "Architecture needs attention \u2014 few recognized patterns or boundaries";
|
|
289899
|
+
}
|
|
289900
|
+
explainTestScore(score) {
|
|
289901
|
+
if (score >= 80) return "Good test coverage across source files";
|
|
289902
|
+
if (score >= 50) return "Moderate coverage \u2014 some source files lack tests";
|
|
289903
|
+
return "Low test coverage \u2014 many exported modules have no test files";
|
|
289904
|
+
}
|
|
289905
|
+
explainConventionScore(score, dna) {
|
|
289906
|
+
const strong = dna.conventions.filter((c) => c.confidence > 0.6).length;
|
|
289907
|
+
if (score >= 80) return `${strong} strong conventions enforced consistently`;
|
|
289908
|
+
if (score >= 50) return `${strong} conventions detected but inconsistently applied`;
|
|
289909
|
+
return "Few consistent conventions \u2014 codebase style varies across files";
|
|
289910
|
+
}
|
|
289911
|
+
explainDependencyScore(score, dna) {
|
|
289912
|
+
const circular = dna.boundaries.filter((b) => b.isCircular).length;
|
|
289913
|
+
if (score >= 80) return "Clean dependency graph with no circular dependencies";
|
|
289914
|
+
if (circular > 0) return `${circular} circular dependency${circular > 1 ? "ies" : "y"} detected \u2014 these increase coupling and make code harder to reason about`;
|
|
289915
|
+
return "Dependency health needs improvement";
|
|
289916
|
+
}
|
|
289917
|
+
renderBar(score) {
|
|
289918
|
+
const filled = Math.round(score / 10);
|
|
289919
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
289920
|
+
}
|
|
289921
|
+
};
|
|
289922
|
+
function shortName(filePath) {
|
|
289923
|
+
const parts2 = filePath.split("/");
|
|
289924
|
+
return parts2[parts2.length - 1] ?? filePath;
|
|
289925
|
+
}
|
|
289926
|
+
|
|
289079
289927
|
// ../context-engine/dist/chunk-HMKLYBWJ.js
|
|
289080
289928
|
var import_better_sqlite3 = __toESM(require_lib(), 1);
|
|
289081
289929
|
var import_fast_glob2 = __toESM(require_out4(), 1);
|
|
@@ -289085,7 +289933,7 @@ import { mkdirSync } from "fs";
|
|
|
289085
289933
|
import { join as join4 } from "path";
|
|
289086
289934
|
import { readFile as readFile22 } from "fs/promises";
|
|
289087
289935
|
import { accessSync } from "fs";
|
|
289088
|
-
import { extname as extname2, resolve as resolve2, dirname as
|
|
289936
|
+
import { extname as extname2, resolve as resolve2, dirname as dirname6 } from "path";
|
|
289089
289937
|
import { createHash as createHash22 } from "crypto";
|
|
289090
289938
|
var SCHEMA_VERSION = 1;
|
|
289091
289939
|
var PersistentIndex = class {
|
|
@@ -289983,7 +290831,7 @@ var DefaultFileParser = class {
|
|
|
289983
290831
|
}
|
|
289984
290832
|
resolveImportPath(sourcePath, fromFile) {
|
|
289985
290833
|
if (!sourcePath.startsWith(".")) return sourcePath;
|
|
289986
|
-
const dir =
|
|
290834
|
+
const dir = dirname6(fromFile);
|
|
289987
290835
|
const resolved = resolve2(dir, sourcePath);
|
|
289988
290836
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
289989
290837
|
for (const ext2 of extensions) {
|
|
@@ -290093,845 +290941,6 @@ function flattenCodeSymbols(symbols) {
|
|
|
290093
290941
|
return flat;
|
|
290094
290942
|
}
|
|
290095
290943
|
|
|
290096
|
-
// ../context-engine/dist/chunk-INBCP46U.js
|
|
290097
|
-
import * as path5 from "path";
|
|
290098
|
-
var ContextSynthesizer = class {
|
|
290099
|
-
rootPath;
|
|
290100
|
-
data;
|
|
290101
|
-
dna;
|
|
290102
|
-
graph;
|
|
290103
|
-
ruleResult;
|
|
290104
|
-
constructor(rootPath, data, dna, graph, ruleResult) {
|
|
290105
|
-
this.rootPath = rootPath;
|
|
290106
|
-
this.data = data;
|
|
290107
|
-
this.dna = dna;
|
|
290108
|
-
this.graph = graph;
|
|
290109
|
-
this.ruleResult = ruleResult || null;
|
|
290110
|
-
}
|
|
290111
|
-
/**
|
|
290112
|
-
* Synthesize the full context — the complete brain dump for AI agents.
|
|
290113
|
-
*/
|
|
290114
|
-
synthesize() {
|
|
290115
|
-
const projectIdentity = this.buildProjectIdentity();
|
|
290116
|
-
const archRules = this.buildArchRuleSummary();
|
|
290117
|
-
const activeViolations = this.ruleResult?.violations || [];
|
|
290118
|
-
const codebaseDNA = this.buildDNASummary();
|
|
290119
|
-
const fileContexts = this.buildFileContexts();
|
|
290120
|
-
const taskPlaybooks = this.buildTaskPlaybooks();
|
|
290121
|
-
const verificationSteps = this.buildVerificationSteps();
|
|
290122
|
-
const riskBriefing = this.buildRiskBriefing();
|
|
290123
|
-
return {
|
|
290124
|
-
version: "2.0.0",
|
|
290125
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
290126
|
-
projectIdentity,
|
|
290127
|
-
architecturalRules: archRules,
|
|
290128
|
-
activeViolations,
|
|
290129
|
-
codebaseDNA,
|
|
290130
|
-
fileContexts,
|
|
290131
|
-
taskPlaybooks,
|
|
290132
|
-
verificationSteps,
|
|
290133
|
-
riskBriefing
|
|
290134
|
-
};
|
|
290135
|
-
}
|
|
290136
|
-
/**
|
|
290137
|
-
* Generate context for a specific file — what an AI agent needs to know
|
|
290138
|
-
* before editing this file.
|
|
290139
|
-
*/
|
|
290140
|
-
synthesizeForFile(filePath) {
|
|
290141
|
-
const rel = this.toRelative(filePath);
|
|
290142
|
-
const file = this.data.files.find((f) => f.relativePath === rel || f.path === filePath);
|
|
290143
|
-
if (!file) return null;
|
|
290144
|
-
return this.buildSingleFileContext(file);
|
|
290145
|
-
}
|
|
290146
|
-
/**
|
|
290147
|
-
* Generate a compact markdown context document for IDE rules.
|
|
290148
|
-
* This replaces the old static rule generation with intelligence-driven context.
|
|
290149
|
-
*/
|
|
290150
|
-
generateContextDocument() {
|
|
290151
|
-
const ctx = this.synthesize();
|
|
290152
|
-
const lines = [];
|
|
290153
|
-
lines.push(`# ${ctx.projectIdentity.name} \u2014 AI Context`);
|
|
290154
|
-
lines.push(`<!-- Generated by @repo/context-engine at ${ctx.generatedAt} -->`);
|
|
290155
|
-
lines.push("");
|
|
290156
|
-
lines.push("## Project Identity");
|
|
290157
|
-
lines.push(`- **Stack**: ${ctx.projectIdentity.stack}`);
|
|
290158
|
-
lines.push(`- **Architecture**: ${ctx.projectIdentity.architecture}`);
|
|
290159
|
-
if (ctx.projectIdentity.keyPatterns.length > 0) {
|
|
290160
|
-
lines.push(`- **Key Patterns**: ${ctx.projectIdentity.keyPatterns.join(", ")}`);
|
|
290161
|
-
}
|
|
290162
|
-
lines.push("");
|
|
290163
|
-
if (ctx.codebaseDNA.conventions.length > 0) {
|
|
290164
|
-
lines.push("## Conventions (Auto-Discovered)");
|
|
290165
|
-
for (const conv of ctx.codebaseDNA.conventions) {
|
|
290166
|
-
lines.push(`- ${conv}`);
|
|
290167
|
-
}
|
|
290168
|
-
lines.push("");
|
|
290169
|
-
}
|
|
290170
|
-
if (ctx.architecturalRules.length > 0) {
|
|
290171
|
-
lines.push("## Architecture Rules");
|
|
290172
|
-
for (const rule of ctx.architecturalRules) {
|
|
290173
|
-
const icon = rule.severity === "error" ? "MUST" : rule.severity === "warning" ? "SHOULD" : "MAY";
|
|
290174
|
-
lines.push(`- **[${icon}]** ${rule.name}: ${rule.description}`);
|
|
290175
|
-
if (rule.violationCount > 0) {
|
|
290176
|
-
lines.push(` - ${rule.violationCount} active violations`);
|
|
290177
|
-
}
|
|
290178
|
-
}
|
|
290179
|
-
lines.push("");
|
|
290180
|
-
}
|
|
290181
|
-
if (ctx.codebaseDNA.boundaries.length > 0) {
|
|
290182
|
-
lines.push("## Module Boundaries");
|
|
290183
|
-
for (const boundary of ctx.codebaseDNA.boundaries) {
|
|
290184
|
-
lines.push(`- ${boundary}`);
|
|
290185
|
-
}
|
|
290186
|
-
lines.push("");
|
|
290187
|
-
}
|
|
290188
|
-
if (ctx.codebaseDNA.hotFiles.length > 0) {
|
|
290189
|
-
lines.push("## High-Impact Files (Edit With Care)");
|
|
290190
|
-
for (const file of ctx.codebaseDNA.hotFiles.slice(0, 10)) {
|
|
290191
|
-
lines.push(`- \`${file}\``);
|
|
290192
|
-
}
|
|
290193
|
-
lines.push("");
|
|
290194
|
-
}
|
|
290195
|
-
if (ctx.riskBriefing.securityConcerns.length > 0 || ctx.riskBriefing.testGaps.length > 0) {
|
|
290196
|
-
lines.push("## Risk Areas");
|
|
290197
|
-
for (const concern of ctx.riskBriefing.securityConcerns) {
|
|
290198
|
-
lines.push(`- ${concern}`);
|
|
290199
|
-
}
|
|
290200
|
-
for (const gap of ctx.riskBriefing.testGaps.slice(0, 5)) {
|
|
290201
|
-
lines.push(`- ${gap}`);
|
|
290202
|
-
}
|
|
290203
|
-
lines.push("");
|
|
290204
|
-
}
|
|
290205
|
-
if (ctx.projectIdentity.noGoZones.length > 0) {
|
|
290206
|
-
lines.push("## No-Go Zones");
|
|
290207
|
-
for (const zone of ctx.projectIdentity.noGoZones) {
|
|
290208
|
-
lines.push(`- ${zone}`);
|
|
290209
|
-
}
|
|
290210
|
-
lines.push("");
|
|
290211
|
-
}
|
|
290212
|
-
if (ctx.taskPlaybooks.length > 0) {
|
|
290213
|
-
lines.push("## Task Playbooks");
|
|
290214
|
-
for (const playbook of ctx.taskPlaybooks) {
|
|
290215
|
-
lines.push(`### ${playbook.taskType}`);
|
|
290216
|
-
for (const step of playbook.steps) {
|
|
290217
|
-
lines.push(`1. ${step}`);
|
|
290218
|
-
}
|
|
290219
|
-
if (playbook.mustVerify.length > 0) {
|
|
290220
|
-
lines.push(`**Verify**: ${playbook.mustVerify.join(", ")}`);
|
|
290221
|
-
}
|
|
290222
|
-
lines.push("");
|
|
290223
|
-
}
|
|
290224
|
-
}
|
|
290225
|
-
if (ctx.verificationSteps.length > 0) {
|
|
290226
|
-
lines.push("## Verification Protocol");
|
|
290227
|
-
for (const step of ctx.verificationSteps) {
|
|
290228
|
-
lines.push(`### On ${step.trigger}`);
|
|
290229
|
-
for (const check of step.checks) {
|
|
290230
|
-
lines.push(`- ${check}`);
|
|
290231
|
-
}
|
|
290232
|
-
if (step.commands.length > 0) {
|
|
290233
|
-
lines.push(`**Run**: \`${step.commands.join(" && ")}\``);
|
|
290234
|
-
}
|
|
290235
|
-
lines.push("");
|
|
290236
|
-
}
|
|
290237
|
-
}
|
|
290238
|
-
lines.push("## Codebase Health");
|
|
290239
|
-
lines.push(`- **Overall**: ${this.dna.healthScore.overall}/100`);
|
|
290240
|
-
const dims = this.dna.healthScore.dimensions;
|
|
290241
|
-
lines.push(`- Architecture: ${dims.architecture} | Tests: ${dims.testCoverage} | Conventions: ${dims.conventions} | Dependencies: ${dims.dependencies}`);
|
|
290242
|
-
lines.push("");
|
|
290243
|
-
lines.push("---");
|
|
290244
|
-
lines.push("<!-- context-engine:v2 -->");
|
|
290245
|
-
return lines.join("\n");
|
|
290246
|
-
}
|
|
290247
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290248
|
-
// IDENTITY
|
|
290249
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290250
|
-
buildProjectIdentity() {
|
|
290251
|
-
const fp = this.dna.fingerprint;
|
|
290252
|
-
const stack = [fp.framework, fp.language, fp.orm, fp.validator, fp.authLib, fp.router].filter(Boolean).join(" | ");
|
|
290253
|
-
const keyPatterns = this.dna.patterns.map((p) => p.name);
|
|
290254
|
-
const criticalPaths = this.dna.hotspots.slice(0, 5).map((h) => h.file);
|
|
290255
|
-
const noGoZones = [];
|
|
290256
|
-
if (this.ruleResult) {
|
|
290257
|
-
const errorRules = this.ruleResult.violations.filter((v) => v.severity === "error");
|
|
290258
|
-
const uniqueMessages = [...new Set(errorRules.map((v) => v.message))];
|
|
290259
|
-
noGoZones.push(...uniqueMessages.slice(0, 5));
|
|
290260
|
-
}
|
|
290261
|
-
for (const cycle of this.graph.cycles) {
|
|
290262
|
-
noGoZones.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}...`);
|
|
290263
|
-
}
|
|
290264
|
-
const architecture = this.dna.conventions.filter((c) => c.area === "structure").map((c) => c.description).join("; ") || fp.framework;
|
|
290265
|
-
return {
|
|
290266
|
-
name: fp.name,
|
|
290267
|
-
stack,
|
|
290268
|
-
architecture,
|
|
290269
|
-
keyPatterns,
|
|
290270
|
-
criticalPaths,
|
|
290271
|
-
noGoZones: noGoZones.slice(0, 10)
|
|
290272
|
-
};
|
|
290273
|
-
}
|
|
290274
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290275
|
-
// RULES SUMMARY
|
|
290276
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290277
|
-
buildArchRuleSummary() {
|
|
290278
|
-
if (!this.ruleResult) return [];
|
|
290279
|
-
const breakdown = this.ruleResult.ruleBreakdown;
|
|
290280
|
-
return Object.entries(breakdown).map(([ruleId, count]) => {
|
|
290281
|
-
const violation = this.ruleResult.violations.find((v) => v.ruleId === ruleId);
|
|
290282
|
-
return {
|
|
290283
|
-
id: ruleId,
|
|
290284
|
-
name: violation?.ruleName || ruleId,
|
|
290285
|
-
type: "import_forbidden",
|
|
290286
|
-
severity: violation?.severity || "warning",
|
|
290287
|
-
scope: violation?.sourceSymbol.filePath || "",
|
|
290288
|
-
description: violation?.message || "",
|
|
290289
|
-
violationCount: count
|
|
290290
|
-
};
|
|
290291
|
-
});
|
|
290292
|
-
}
|
|
290293
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290294
|
-
// DNA SUMMARY
|
|
290295
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290296
|
-
buildDNASummary() {
|
|
290297
|
-
return {
|
|
290298
|
-
conventions: this.dna.conventions.filter((c) => c.confidence > 0.5).map((c) => c.description),
|
|
290299
|
-
patterns: this.dna.patterns.map((p) => `${p.name}: ${p.description}`),
|
|
290300
|
-
boundaries: this.dna.boundaries.filter((b) => b.importCount > 3).map((b) => `${b.from} \u2192 ${b.to} (${b.importCount} imports${b.isCircular ? ", CIRCULAR" : ""})`),
|
|
290301
|
-
hotFiles: this.dna.hotspots.slice(0, 10).map((h) => h.file),
|
|
290302
|
-
riskAreas: this.dna.riskMap.filter((r) => r.riskLevel === "critical" || r.riskLevel === "high").map((r) => `${r.file}: ${r.factors[0]}`)
|
|
290303
|
-
};
|
|
290304
|
-
}
|
|
290305
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290306
|
-
// FILE CONTEXTS
|
|
290307
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290308
|
-
buildFileContexts() {
|
|
290309
|
-
const contexts = /* @__PURE__ */ new Map();
|
|
290310
|
-
for (const file of this.data.files) {
|
|
290311
|
-
contexts.set(file.relativePath, this.buildSingleFileContext(file));
|
|
290312
|
-
}
|
|
290313
|
-
return contexts;
|
|
290314
|
-
}
|
|
290315
|
-
buildSingleFileContext(file) {
|
|
290316
|
-
const rel = file.relativePath;
|
|
290317
|
-
const role = this.classifyRole(file);
|
|
290318
|
-
const graphNode = this.graph.nodes.find((n) => n.relativePath === rel);
|
|
290319
|
-
const layer = graphNode?.layer;
|
|
290320
|
-
const dependsOn = this.graph.edges.filter((e) => e.from === rel).map((e) => e.to);
|
|
290321
|
-
const dependedOnBy = this.graph.edges.filter((e) => e.to === rel).map((e) => e.from);
|
|
290322
|
-
const applicableRules = [];
|
|
290323
|
-
if (this.ruleResult) {
|
|
290324
|
-
for (const v of this.ruleResult.violations) {
|
|
290325
|
-
if (v.sourceSymbol.filePath.includes(rel) || v.targetSymbol && v.targetSymbol.filePath.includes(rel)) {
|
|
290326
|
-
if (!applicableRules.includes(v.ruleId)) applicableRules.push(v.ruleId);
|
|
290327
|
-
}
|
|
290328
|
-
}
|
|
290329
|
-
}
|
|
290330
|
-
const conventions = this.dna.conventions.filter((c) => this.conventionAppliesToFile(c.area, file)).map((c) => c.description);
|
|
290331
|
-
const patterns = this.dna.patterns.filter((p) => p.fileMatches.some((m) => m === rel)).map((p) => p.name);
|
|
290332
|
-
const riskEntry = this.dna.riskMap.find((r) => r.file === rel);
|
|
290333
|
-
const riskLevel = riskEntry?.riskLevel || "low";
|
|
290334
|
-
const relatedFiles = this.findRelatedFiles(file, role).slice(0, 8);
|
|
290335
|
-
const editGuidance = this.generateEditGuidance(file, role, layer, dependedOnBy, conventions);
|
|
290336
|
-
return {
|
|
290337
|
-
filePath: file.path,
|
|
290338
|
-
role,
|
|
290339
|
-
layer,
|
|
290340
|
-
dependsOn,
|
|
290341
|
-
dependedOnBy,
|
|
290342
|
-
applicableRules,
|
|
290343
|
-
conventions,
|
|
290344
|
-
patterns,
|
|
290345
|
-
riskLevel,
|
|
290346
|
-
relatedFiles,
|
|
290347
|
-
editGuidance
|
|
290348
|
-
};
|
|
290349
|
-
}
|
|
290350
|
-
classifyRole(file) {
|
|
290351
|
-
const rel = file.relativePath.toLowerCase();
|
|
290352
|
-
if (rel.includes(".test.") || rel.includes(".spec.") || rel.includes("__tests__")) return "test";
|
|
290353
|
-
if (rel.includes("fixture") || rel.includes("mock")) return "fixture";
|
|
290354
|
-
if (rel.includes("migration")) return "migration";
|
|
290355
|
-
if (rel.match(/\.(css|scss|less|styl)$/)) return "style";
|
|
290356
|
-
if (rel.includes(".config.") || rel.includes("config/") || rel === "tsconfig.json") return "config";
|
|
290357
|
-
if (rel.includes("middleware")) return "middleware";
|
|
290358
|
-
if (rel.includes("/api/") || rel.includes("route")) return "route-handler";
|
|
290359
|
-
if (rel.includes("service") || rel.includes("Service")) return "service";
|
|
290360
|
-
if (rel.includes("repositor") || rel.includes("Repositor")) return "repository";
|
|
290361
|
-
if (rel.endsWith(".tsx") && !rel.includes("page.")) return "component";
|
|
290362
|
-
if (rel.includes("/types") || rel.endsWith(".d.ts")) return "type";
|
|
290363
|
-
if (rel.includes("util") || rel.includes("helper") || rel.includes("lib/")) return "util";
|
|
290364
|
-
if (rel.includes("script") || rel.includes("bin/")) return "script";
|
|
290365
|
-
if (rel.match(/^(src\/)?index\.|^(src\/)?main\.|^(src\/)?app\./)) return "entry";
|
|
290366
|
-
return "unknown";
|
|
290367
|
-
}
|
|
290368
|
-
conventionAppliesToFile(area, file) {
|
|
290369
|
-
switch (area) {
|
|
290370
|
-
case "naming":
|
|
290371
|
-
return true;
|
|
290372
|
-
case "imports":
|
|
290373
|
-
return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
|
|
290374
|
-
case "exports":
|
|
290375
|
-
return file.exports.length > 0;
|
|
290376
|
-
case "testing":
|
|
290377
|
-
return file.relativePath.includes(".test.") || file.relativePath.includes(".spec.");
|
|
290378
|
-
case "error-handling":
|
|
290379
|
-
return !file.relativePath.includes(".test.");
|
|
290380
|
-
case "types":
|
|
290381
|
-
return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
|
|
290382
|
-
default:
|
|
290383
|
-
return true;
|
|
290384
|
-
}
|
|
290385
|
-
}
|
|
290386
|
-
findRelatedFiles(file, role) {
|
|
290387
|
-
const related = [];
|
|
290388
|
-
const dir = path5.dirname(file.relativePath);
|
|
290389
|
-
for (const other of this.data.files) {
|
|
290390
|
-
if (other.path === file.path) continue;
|
|
290391
|
-
if (path5.dirname(other.relativePath) === dir) {
|
|
290392
|
-
related.push(other.relativePath);
|
|
290393
|
-
}
|
|
290394
|
-
}
|
|
290395
|
-
if (related.length < 5) {
|
|
290396
|
-
for (const other of this.data.files) {
|
|
290397
|
-
if (other.path === file.path) continue;
|
|
290398
|
-
if (related.includes(other.relativePath)) continue;
|
|
290399
|
-
if (this.classifyRole(other) === role) {
|
|
290400
|
-
related.push(other.relativePath);
|
|
290401
|
-
if (related.length >= 8) break;
|
|
290402
|
-
}
|
|
290403
|
-
}
|
|
290404
|
-
}
|
|
290405
|
-
return related;
|
|
290406
|
-
}
|
|
290407
|
-
generateEditGuidance(file, role, layer, dependedOnBy, conventions) {
|
|
290408
|
-
const guidance = [];
|
|
290409
|
-
if (dependedOnBy.length > 10) {
|
|
290410
|
-
guidance.push(`HIGH IMPACT: ${dependedOnBy.length} files depend on this. Changes have wide blast radius.`);
|
|
290411
|
-
}
|
|
290412
|
-
switch (role) {
|
|
290413
|
-
case "route-handler":
|
|
290414
|
-
guidance.push("Validate all inputs with schemas before processing.");
|
|
290415
|
-
guidance.push("Return consistent response shapes ({ success, data } or { success, error }).");
|
|
290416
|
-
guidance.push("Ensure authentication middleware is applied to protected endpoints.");
|
|
290417
|
-
break;
|
|
290418
|
-
case "service":
|
|
290419
|
-
guidance.push("Keep business logic here, not in controllers/routes.");
|
|
290420
|
-
guidance.push("Use dependency injection for testability.");
|
|
290421
|
-
if (layer) guidance.push(`This is in the ${layer} layer \u2014 only import from lower layers.`);
|
|
290422
|
-
break;
|
|
290423
|
-
case "repository":
|
|
290424
|
-
guidance.push("Only data access logic belongs here \u2014 no business rules.");
|
|
290425
|
-
guidance.push("Return domain objects, not raw database rows.");
|
|
290426
|
-
break;
|
|
290427
|
-
case "component":
|
|
290428
|
-
guidance.push("Keep components focused and composable.");
|
|
290429
|
-
guidance.push("Extract complex logic to custom hooks.");
|
|
290430
|
-
break;
|
|
290431
|
-
case "middleware":
|
|
290432
|
-
guidance.push("Middleware must call next() or return a response \u2014 never leave the request hanging.");
|
|
290433
|
-
guidance.push("Keep middleware focused on a single concern.");
|
|
290434
|
-
break;
|
|
290435
|
-
case "test":
|
|
290436
|
-
guidance.push("Follow Arrange-Act-Assert pattern.");
|
|
290437
|
-
guidance.push("Test edge cases and error conditions, not just happy path.");
|
|
290438
|
-
break;
|
|
290439
|
-
}
|
|
290440
|
-
for (const conv of conventions.slice(0, 3)) {
|
|
290441
|
-
guidance.push(`Convention: ${conv}`);
|
|
290442
|
-
}
|
|
290443
|
-
return guidance;
|
|
290444
|
-
}
|
|
290445
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290446
|
-
// TASK PLAYBOOKS
|
|
290447
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290448
|
-
buildTaskPlaybooks() {
|
|
290449
|
-
const fp = this.dna.fingerprint;
|
|
290450
|
-
const playbooks = [];
|
|
290451
|
-
playbooks.push({
|
|
290452
|
-
taskType: "Bug Fix",
|
|
290453
|
-
steps: [
|
|
290454
|
-
"Reproduce the bug and understand the expected vs actual behavior",
|
|
290455
|
-
"Identify the root cause file(s) using the dependency graph",
|
|
290456
|
-
"Write a failing test that reproduces the bug",
|
|
290457
|
-
"Apply the minimal fix at the root cause",
|
|
290458
|
-
"Verify the fix passes the test and does not break existing tests",
|
|
290459
|
-
"Check that the fix does not violate any architecture rules"
|
|
290460
|
-
],
|
|
290461
|
-
mustRead: this.dna.hotspots.slice(0, 3).map((h) => h.file),
|
|
290462
|
-
mustUpdate: ["The buggy file", "Related test file"],
|
|
290463
|
-
mustVerify: ["All existing tests pass", "New regression test passes", "No new arch rule violations"],
|
|
290464
|
-
stopConditions: ["Never modify tests to make them pass \u2014 fix the code", "Do not change public API signatures without discussion"]
|
|
290465
|
-
});
|
|
290466
|
-
if (fp.router) {
|
|
290467
|
-
playbooks.push({
|
|
290468
|
-
taskType: "Add API Endpoint",
|
|
290469
|
-
steps: [
|
|
290470
|
-
"Check existing routes in truthpack to avoid duplicates",
|
|
290471
|
-
"Create the route handler following existing patterns",
|
|
290472
|
-
"Add input validation using the project validator",
|
|
290473
|
-
"Add authentication middleware if the route is protected",
|
|
290474
|
-
"Write tests for success, validation failure, and auth failure",
|
|
290475
|
-
"Update the truthpack (run vibecheck scan)"
|
|
290476
|
-
],
|
|
290477
|
-
mustRead: ["truthpack/routes.json", ...this.dna.patterns.filter((p) => p.category === "api").map((p) => p.exemplar)],
|
|
290478
|
-
mustUpdate: ["Route file", "Test file", "Truthpack"],
|
|
290479
|
-
mustVerify: ["Route responds correctly", "Input validation works", "Auth is enforced", "Test passes"],
|
|
290480
|
-
stopConditions: ["Do not create duplicate routes", "Do not hardcode mock data in handlers"]
|
|
290481
|
-
});
|
|
290482
|
-
}
|
|
290483
|
-
if (fp.framework.includes("Next") || fp.framework.includes("React")) {
|
|
290484
|
-
playbooks.push({
|
|
290485
|
-
taskType: "Add UI Component",
|
|
290486
|
-
steps: [
|
|
290487
|
-
"Check if a similar component already exists",
|
|
290488
|
-
"Create the component following existing naming and structure patterns",
|
|
290489
|
-
"Add TypeScript props interface",
|
|
290490
|
-
"Add unit test for the component",
|
|
290491
|
-
"If using state, determine if it should be a client component"
|
|
290492
|
-
],
|
|
290493
|
-
mustRead: this.dna.patterns.filter((p) => p.category === "ui" || p.category === "state").map((p) => p.exemplar),
|
|
290494
|
-
mustUpdate: ["Component file", "Test file", "Parent component that uses it"],
|
|
290495
|
-
mustVerify: ["Component renders correctly", "Props are typed", "Test passes"],
|
|
290496
|
-
stopConditions: ['Do not use "any" type for props', 'Do not add useState in server components without "use client"']
|
|
290497
|
-
});
|
|
290498
|
-
}
|
|
290499
|
-
playbooks.push({
|
|
290500
|
-
taskType: "Refactor",
|
|
290501
|
-
steps: [
|
|
290502
|
-
"Identify all callers/dependents of the code being refactored",
|
|
290503
|
-
"Ensure comprehensive tests exist before refactoring",
|
|
290504
|
-
"Apply changes incrementally, testing after each step",
|
|
290505
|
-
"Update all dependents to use the new API",
|
|
290506
|
-
"Remove old code only after all dependents are migrated",
|
|
290507
|
-
"Verify no architecture rules are violated"
|
|
290508
|
-
],
|
|
290509
|
-
mustRead: ["Dependency graph for affected files"],
|
|
290510
|
-
mustUpdate: ["Refactored file", "All dependent files", "Tests"],
|
|
290511
|
-
mustVerify: ["All tests pass", "No new violations", "No regressions"],
|
|
290512
|
-
stopConditions: ["Never break existing public APIs without migration path", "Do not refactor and add features in the same change"]
|
|
290513
|
-
});
|
|
290514
|
-
return playbooks;
|
|
290515
|
-
}
|
|
290516
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290517
|
-
// VERIFICATION STEPS
|
|
290518
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290519
|
-
buildVerificationSteps() {
|
|
290520
|
-
const fp = this.dna.fingerprint;
|
|
290521
|
-
const steps = [];
|
|
290522
|
-
if (fp.language === "TypeScript") {
|
|
290523
|
-
steps.push({
|
|
290524
|
-
trigger: "Any TypeScript file change",
|
|
290525
|
-
checks: ["TypeScript compilation succeeds", "No new type errors introduced"],
|
|
290526
|
-
commands: [fp.packageManager === "pnpm" ? "pnpm run check-types" : "npm run check-types"],
|
|
290527
|
-
artifacts: []
|
|
290528
|
-
});
|
|
290529
|
-
}
|
|
290530
|
-
if (fp.testRunner) {
|
|
290531
|
-
steps.push({
|
|
290532
|
-
trigger: "Any source file change",
|
|
290533
|
-
checks: ["Related tests pass", "No test regressions"],
|
|
290534
|
-
commands: [`${fp.packageManager} run test`],
|
|
290535
|
-
artifacts: ["test-results.json"]
|
|
290536
|
-
});
|
|
290537
|
-
}
|
|
290538
|
-
if (fp.router) {
|
|
290539
|
-
steps.push({
|
|
290540
|
-
trigger: "Route handler added or modified",
|
|
290541
|
-
checks: ["Route responds with correct status", "Auth middleware is applied", "Input validation works"],
|
|
290542
|
-
commands: ["vibecheck scan"],
|
|
290543
|
-
artifacts: ["truthpack/routes.json"]
|
|
290544
|
-
});
|
|
290545
|
-
}
|
|
290546
|
-
steps.push({
|
|
290547
|
-
trigger: "Any source file change",
|
|
290548
|
-
checks: ["No new architecture rule violations", "No new circular dependencies"],
|
|
290549
|
-
commands: ["vibecheck arch-rules"],
|
|
290550
|
-
artifacts: []
|
|
290551
|
-
});
|
|
290552
|
-
return steps;
|
|
290553
|
-
}
|
|
290554
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290555
|
-
// RISK BRIEFING
|
|
290556
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290557
|
-
buildRiskBriefing() {
|
|
290558
|
-
const criticalFiles = this.dna.hotspots.filter((h) => h.score > 30).slice(0, 10).map((h) => h.file);
|
|
290559
|
-
const recentViolations = this.ruleResult?.violations.filter((v) => v.severity === "error").slice(0, 10) || [];
|
|
290560
|
-
const securityConcerns = [];
|
|
290561
|
-
for (const risk of this.dna.riskMap) {
|
|
290562
|
-
if (risk.riskLevel === "critical") {
|
|
290563
|
-
securityConcerns.push(`${risk.file}: ${risk.factors.join(", ")}`);
|
|
290564
|
-
}
|
|
290565
|
-
}
|
|
290566
|
-
const testGaps = [];
|
|
290567
|
-
const sourceFiles = this.data.files.filter(
|
|
290568
|
-
(f) => !f.relativePath.includes(".test.") && !f.relativePath.includes(".spec.") && (f.relativePath.endsWith(".ts") || f.relativePath.endsWith(".tsx")) && !f.relativePath.includes("config") && !f.relativePath.includes(".d.ts")
|
|
290569
|
-
);
|
|
290570
|
-
const testFiles = this.data.files.filter(
|
|
290571
|
-
(f) => f.relativePath.includes(".test.") || f.relativePath.includes(".spec.")
|
|
290572
|
-
);
|
|
290573
|
-
const testedBases = new Set(testFiles.map(
|
|
290574
|
-
(f) => path5.basename(f.relativePath).replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "")
|
|
290575
|
-
));
|
|
290576
|
-
for (const file of sourceFiles) {
|
|
290577
|
-
const baseName = path5.basename(file.relativePath).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
290578
|
-
if (!testedBases.has(baseName) && file.exports.length > 0) {
|
|
290579
|
-
testGaps.push(`${file.relativePath} has exports but no test file`);
|
|
290580
|
-
}
|
|
290581
|
-
}
|
|
290582
|
-
const driftWarnings = [];
|
|
290583
|
-
for (const cycle of this.graph.cycles) {
|
|
290584
|
-
driftWarnings.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}${cycle.nodes.length > 3 ? "..." : ""}`);
|
|
290585
|
-
}
|
|
290586
|
-
return {
|
|
290587
|
-
criticalFiles,
|
|
290588
|
-
recentViolations,
|
|
290589
|
-
securityConcerns: securityConcerns.slice(0, 5),
|
|
290590
|
-
testGaps: testGaps.slice(0, 10),
|
|
290591
|
-
driftWarnings: driftWarnings.slice(0, 5)
|
|
290592
|
-
};
|
|
290593
|
-
}
|
|
290594
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290595
|
-
// HELPERS
|
|
290596
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290597
|
-
toRelative(filePath) {
|
|
290598
|
-
if (filePath.startsWith(this.rootPath)) {
|
|
290599
|
-
return filePath.slice(this.rootPath.length + 1).replace(/\\/g, "/");
|
|
290600
|
-
}
|
|
290601
|
-
return filePath.replace(/\\/g, "/");
|
|
290602
|
-
}
|
|
290603
|
-
};
|
|
290604
|
-
var ContextExplainer = class {
|
|
290605
|
-
/**
|
|
290606
|
-
* Generate a rich explanation for a file.
|
|
290607
|
-
*/
|
|
290608
|
-
explain(filePath, ctx) {
|
|
290609
|
-
const fc = ctx.fileContext;
|
|
290610
|
-
const paragraphs = [];
|
|
290611
|
-
const quickFacts = [];
|
|
290612
|
-
const warnings = [];
|
|
290613
|
-
const relatedFiles = [];
|
|
290614
|
-
const overlays = [];
|
|
290615
|
-
const role = fc?.role ?? "unknown";
|
|
290616
|
-
const roleSummary = this.buildRoleSummary(filePath, role, ctx);
|
|
290617
|
-
if (fc) {
|
|
290618
|
-
const depCount = fc.dependedOnBy.length;
|
|
290619
|
-
const depsOnCount = fc.dependsOn.length;
|
|
290620
|
-
if (depCount > 0 || depsOnCount > 0) {
|
|
290621
|
-
const parts2 = [];
|
|
290622
|
-
if (depCount > 0) {
|
|
290623
|
-
const critical = depCount > 10 ? " \u2014 changes here have wide blast radius" : "";
|
|
290624
|
-
parts2.push(`${depCount} file${depCount > 1 ? "s" : ""} depend on this${critical}`);
|
|
290625
|
-
}
|
|
290626
|
-
if (depsOnCount > 0) {
|
|
290627
|
-
parts2.push(`it imports from ${depsOnCount} file${depsOnCount > 1 ? "s" : ""}`);
|
|
290628
|
-
}
|
|
290629
|
-
paragraphs.push({
|
|
290630
|
-
heading: "Dependencies",
|
|
290631
|
-
text: parts2.join(". ") + ".",
|
|
290632
|
-
importance: depCount > 10 ? "critical" : depCount > 5 ? "high" : "medium"
|
|
290633
|
-
});
|
|
290634
|
-
}
|
|
290635
|
-
quickFacts.push({ label: "Role", value: role, icon: "layer" });
|
|
290636
|
-
if (fc.layer) quickFacts.push({ label: "Layer", value: fc.layer, icon: "layer" });
|
|
290637
|
-
quickFacts.push({ label: "Dependents", value: String(depCount), icon: "dependency" });
|
|
290638
|
-
quickFacts.push({ label: "Dependencies", value: String(depsOnCount), icon: "dependency" });
|
|
290639
|
-
if (fc.riskLevel === "critical" || fc.riskLevel === "high") {
|
|
290640
|
-
quickFacts.push({ label: "Risk", value: fc.riskLevel.toUpperCase(), icon: "warning" });
|
|
290641
|
-
warnings.push({
|
|
290642
|
-
message: `This file is classified as ${fc.riskLevel} risk`,
|
|
290643
|
-
severity: fc.riskLevel === "critical" ? "error" : "warning",
|
|
290644
|
-
action: "Add comprehensive tests and review carefully before merging changes"
|
|
290645
|
-
});
|
|
290646
|
-
}
|
|
290647
|
-
for (const dep of fc.dependedOnBy.slice(0, 5)) {
|
|
290648
|
-
relatedFiles.push({
|
|
290649
|
-
filePath: dep,
|
|
290650
|
-
reason: `Imports from this file`,
|
|
290651
|
-
relationship: "depended-by",
|
|
290652
|
-
confidence: 0.9
|
|
290653
|
-
});
|
|
290654
|
-
}
|
|
290655
|
-
overlays.push({
|
|
290656
|
-
type: "code-lens",
|
|
290657
|
-
line: 1,
|
|
290658
|
-
text: `${depCount} dependent${depCount !== 1 ? "s" : ""} \xB7 ${depsOnCount} import${depsOnCount !== 1 ? "s" : ""} \xB7 ${role}`,
|
|
290659
|
-
tooltip: `This ${role} file has ${depCount} files that depend on it and imports from ${depsOnCount} files`
|
|
290660
|
-
});
|
|
290661
|
-
}
|
|
290662
|
-
if (fc && fc.conventions.length > 0) {
|
|
290663
|
-
paragraphs.push({
|
|
290664
|
-
heading: "Conventions",
|
|
290665
|
-
text: `Follow these discovered conventions: ${fc.conventions.slice(0, 3).join("; ")}.`,
|
|
290666
|
-
importance: "medium"
|
|
290667
|
-
});
|
|
290668
|
-
}
|
|
290669
|
-
if (ctx.rules) {
|
|
290670
|
-
const fileViolations = ctx.rules.violations.filter(
|
|
290671
|
-
(v) => v.sourceSymbol.filePath.includes(shortName(filePath)) || v.targetSymbol && v.targetSymbol.filePath.includes(shortName(filePath))
|
|
290672
|
-
);
|
|
290673
|
-
if (fileViolations.length > 0) {
|
|
290674
|
-
const errors = fileViolations.filter((v) => v.severity === "error");
|
|
290675
|
-
const warns = fileViolations.filter((v) => v.severity === "warning");
|
|
290676
|
-
paragraphs.push({
|
|
290677
|
-
heading: "Architecture Violations",
|
|
290678
|
-
text: `${errors.length} error${errors.length !== 1 ? "s" : ""} and ${warns.length} warning${warns.length !== 1 ? "s" : ""} from architecture rules.`,
|
|
290679
|
-
importance: errors.length > 0 ? "critical" : "high"
|
|
290680
|
-
});
|
|
290681
|
-
for (const v of fileViolations.slice(0, 5)) {
|
|
290682
|
-
warnings.push({
|
|
290683
|
-
message: v.message,
|
|
290684
|
-
severity: v.severity === "error" ? "error" : "warning",
|
|
290685
|
-
action: v.suggestedFix ?? "Review and fix the violation",
|
|
290686
|
-
ruleId: v.ruleId
|
|
290687
|
-
});
|
|
290688
|
-
overlays.push({
|
|
290689
|
-
type: "diagnostic",
|
|
290690
|
-
line: v.sourceSymbol.line,
|
|
290691
|
-
text: v.message,
|
|
290692
|
-
severity: v.severity === "error" ? "error" : "warning",
|
|
290693
|
-
tooltip: v.suggestedFix
|
|
290694
|
-
});
|
|
290695
|
-
}
|
|
290696
|
-
}
|
|
290697
|
-
}
|
|
290698
|
-
if (ctx.callGraph) {
|
|
290699
|
-
const fileNodes = ctx.callGraph.nodes.filter((n) => n.filePath.includes(shortName(filePath)));
|
|
290700
|
-
const hotFunctions = fileNodes.filter((n) => n.callerCount > 5);
|
|
290701
|
-
if (hotFunctions.length > 0) {
|
|
290702
|
-
paragraphs.push({
|
|
290703
|
-
heading: "Hot Functions",
|
|
290704
|
-
text: hotFunctions.map(
|
|
290705
|
-
(f) => `\`${f.name}\` is called by ${f.callerCount} function${f.callerCount !== 1 ? "s" : ""}${f.calleeCount > 0 ? ` and calls ${f.calleeCount}` : ""}`
|
|
290706
|
-
).join(". ") + ".",
|
|
290707
|
-
importance: "high"
|
|
290708
|
-
});
|
|
290709
|
-
for (const fn of hotFunctions) {
|
|
290710
|
-
overlays.push({
|
|
290711
|
-
type: "code-lens",
|
|
290712
|
-
line: 0,
|
|
290713
|
-
// Would need symbol line mapping
|
|
290714
|
-
text: `${fn.callerCount} callers \xB7 ${fn.calleeCount} callees`,
|
|
290715
|
-
tooltip: `Function ${fn.name} has ${fn.callerCount} callers and ${fn.calleeCount} callees`
|
|
290716
|
-
});
|
|
290717
|
-
}
|
|
290718
|
-
}
|
|
290719
|
-
const deadInFile = ctx.callGraph.stats.deadFunctions.filter(
|
|
290720
|
-
(d) => d.filePath.includes(shortName(filePath))
|
|
290721
|
-
);
|
|
290722
|
-
if (deadInFile.length > 0) {
|
|
290723
|
-
for (const dead of deadInFile) {
|
|
290724
|
-
warnings.push({
|
|
290725
|
-
message: `\`${dead.name}\` appears to be dead code (exported but never called)`,
|
|
290726
|
-
severity: "info",
|
|
290727
|
-
action: "Verify this function is not called via dynamic dispatch or external consumers, then consider removing it"
|
|
290728
|
-
});
|
|
290729
|
-
}
|
|
290730
|
-
}
|
|
290731
|
-
}
|
|
290732
|
-
if (ctx.temporal) {
|
|
290733
|
-
const hotspot = ctx.temporal.changeHotspots.find((h) => filePath.includes(h.file) || h.file.includes(shortName(filePath)));
|
|
290734
|
-
if (hotspot) {
|
|
290735
|
-
const daysSince = Math.round((Date.now() - new Date(hotspot.lastChanged).getTime()) / 864e5);
|
|
290736
|
-
paragraphs.push({
|
|
290737
|
-
heading: "Recent Activity",
|
|
290738
|
-
text: `Changed ${hotspot.commits} times in the last ${ctx.temporal.stats.analysisWindowDays} days by ${hotspot.authors} author${hotspot.authors !== 1 ? "s" : ""}. Last modified ${daysSince} day${daysSince !== 1 ? "s" : ""} ago.`,
|
|
290739
|
-
importance: hotspot.commits > 10 ? "high" : "medium"
|
|
290740
|
-
});
|
|
290741
|
-
quickFacts.push({ label: "Recent commits", value: String(hotspot.commits), icon: "git" });
|
|
290742
|
-
quickFacts.push({ label: "Last changed", value: `${daysSince}d ago`, icon: "git" });
|
|
290743
|
-
quickFacts.push({ label: "Authors", value: String(hotspot.authors), icon: "git" });
|
|
290744
|
-
}
|
|
290745
|
-
const churn = ctx.temporal.churnFiles.find((c) => filePath.includes(c.file) || c.file.includes(shortName(filePath)));
|
|
290746
|
-
if (churn && churn.severity !== "low") {
|
|
290747
|
-
warnings.push({
|
|
290748
|
-
message: churn.reason,
|
|
290749
|
-
severity: churn.severity === "high" ? "warning" : "info",
|
|
290750
|
-
action: "Consider whether this file needs refactoring to reduce change frequency"
|
|
290751
|
-
});
|
|
290752
|
-
}
|
|
290753
|
-
const expertise = ctx.temporal.authorExpertise.find(
|
|
290754
|
-
(e) => filePath.startsWith(e.area) || filePath.includes(e.area)
|
|
290755
|
-
);
|
|
290756
|
-
if (expertise && expertise.busFactor === 1) {
|
|
290757
|
-
warnings.push({
|
|
290758
|
-
message: `Bus factor of 1 \u2014 ${expertise.primaryAuthor} has made ${expertise.authors[0]?.percentage}% of changes to this area`,
|
|
290759
|
-
severity: "info",
|
|
290760
|
-
action: "Consider knowledge sharing or pair programming for this area"
|
|
290761
|
-
});
|
|
290762
|
-
}
|
|
290763
|
-
}
|
|
290764
|
-
if (ctx.learned) {
|
|
290765
|
-
const coEdits = ctx.learned.coEdits.filter((p) => p.files[0].includes(shortName(filePath)) || p.files[1].includes(shortName(filePath))).slice(0, 3);
|
|
290766
|
-
if (coEdits.length > 0) {
|
|
290767
|
-
for (const pair of coEdits) {
|
|
290768
|
-
const other = pair.files[0].includes(shortName(filePath)) ? pair.files[1] : pair.files[0];
|
|
290769
|
-
relatedFiles.push({
|
|
290770
|
-
filePath: other,
|
|
290771
|
-
reason: `Often edited together (${pair.count} times)`,
|
|
290772
|
-
relationship: "co-edited",
|
|
290773
|
-
confidence: pair.weight
|
|
290774
|
-
});
|
|
290775
|
-
}
|
|
290776
|
-
}
|
|
290777
|
-
}
|
|
290778
|
-
if (fc && fc.editGuidance.length > 0) {
|
|
290779
|
-
paragraphs.push({
|
|
290780
|
-
heading: "Edit Guidance",
|
|
290781
|
-
text: fc.editGuidance.join(" "),
|
|
290782
|
-
importance: "medium"
|
|
290783
|
-
});
|
|
290784
|
-
}
|
|
290785
|
-
return {
|
|
290786
|
-
filePath,
|
|
290787
|
-
roleSummary,
|
|
290788
|
-
paragraphs,
|
|
290789
|
-
quickFacts,
|
|
290790
|
-
warnings,
|
|
290791
|
-
relatedFiles,
|
|
290792
|
-
overlays
|
|
290793
|
-
};
|
|
290794
|
-
}
|
|
290795
|
-
/**
|
|
290796
|
-
* Generate a compact markdown explanation for AI agent consumption.
|
|
290797
|
-
*/
|
|
290798
|
-
explainForAgent(filePath, ctx) {
|
|
290799
|
-
const explanation = this.explain(filePath, ctx);
|
|
290800
|
-
const lines = [];
|
|
290801
|
-
lines.push(`## ${shortName(filePath)}: ${explanation.roleSummary}`);
|
|
290802
|
-
lines.push("");
|
|
290803
|
-
if (explanation.quickFacts.length > 0) {
|
|
290804
|
-
lines.push(explanation.quickFacts.map((f) => `**${f.label}**: ${f.value}`).join(" \xB7 "));
|
|
290805
|
-
lines.push("");
|
|
290806
|
-
}
|
|
290807
|
-
for (const p of explanation.paragraphs.filter((p2) => p2.importance === "critical" || p2.importance === "high")) {
|
|
290808
|
-
lines.push(`### ${p.heading}`);
|
|
290809
|
-
lines.push(p.text);
|
|
290810
|
-
lines.push("");
|
|
290811
|
-
}
|
|
290812
|
-
if (explanation.warnings.length > 0) {
|
|
290813
|
-
lines.push("### Warnings");
|
|
290814
|
-
for (const w of explanation.warnings) {
|
|
290815
|
-
const icon = w.severity === "error" ? "MUST FIX" : w.severity === "warning" ? "SHOULD FIX" : "NOTE";
|
|
290816
|
-
lines.push(`- **[${icon}]** ${w.message} \u2014 ${w.action}`);
|
|
290817
|
-
}
|
|
290818
|
-
lines.push("");
|
|
290819
|
-
}
|
|
290820
|
-
if (explanation.relatedFiles.length > 0) {
|
|
290821
|
-
lines.push("### Related Files");
|
|
290822
|
-
for (const rf of explanation.relatedFiles.slice(0, 5)) {
|
|
290823
|
-
lines.push(`- \`${rf.filePath}\` \u2014 ${rf.reason}`);
|
|
290824
|
-
}
|
|
290825
|
-
lines.push("");
|
|
290826
|
-
}
|
|
290827
|
-
return lines.join("\n");
|
|
290828
|
-
}
|
|
290829
|
-
/**
|
|
290830
|
-
* Generate IDE overlay data for the VS Code extension to consume.
|
|
290831
|
-
*/
|
|
290832
|
-
getIDEOverlays(filePath, ctx) {
|
|
290833
|
-
const explanation = this.explain(filePath, ctx);
|
|
290834
|
-
return explanation.overlays;
|
|
290835
|
-
}
|
|
290836
|
-
/**
|
|
290837
|
-
* Generate a health report explanation.
|
|
290838
|
-
*/
|
|
290839
|
-
explainHealth(health, dna) {
|
|
290840
|
-
const lines = [];
|
|
290841
|
-
const dims = health.dimensions;
|
|
290842
|
-
lines.push(`## Codebase Health: ${health.overall}/100`);
|
|
290843
|
-
lines.push("");
|
|
290844
|
-
const entries = [
|
|
290845
|
-
{ name: "Architecture", score: dims.architecture, explain: this.explainArchScore(dims.architecture, dna) },
|
|
290846
|
-
{ name: "Test Coverage", score: dims.testCoverage, explain: this.explainTestScore(dims.testCoverage) },
|
|
290847
|
-
{ name: "Conventions", score: dims.conventions, explain: this.explainConventionScore(dims.conventions, dna) },
|
|
290848
|
-
{ name: "Dependencies", score: dims.dependencies, explain: this.explainDependencyScore(dims.dependencies, dna) },
|
|
290849
|
-
{ name: "Security", score: dims.security, explain: dims.security >= 80 ? "No critical security concerns detected" : "Critical risk areas identified" },
|
|
290850
|
-
{ name: "Complexity", score: dims.complexity, explain: dims.complexity >= 80 ? "Complexity is well-managed" : "High-complexity hotspots detected" }
|
|
290851
|
-
];
|
|
290852
|
-
for (const entry of entries) {
|
|
290853
|
-
const bar = this.renderBar(entry.score);
|
|
290854
|
-
lines.push(`${bar} **${entry.name}**: ${entry.score}/100 \u2014 ${entry.explain}`);
|
|
290855
|
-
}
|
|
290856
|
-
return lines.join("\n");
|
|
290857
|
-
}
|
|
290858
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290859
|
-
// PRIVATE
|
|
290860
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
290861
|
-
buildRoleSummary(filePath, role, ctx) {
|
|
290862
|
-
const parts2 = [];
|
|
290863
|
-
switch (role) {
|
|
290864
|
-
case "service":
|
|
290865
|
-
parts2.push("Business logic service");
|
|
290866
|
-
break;
|
|
290867
|
-
case "route-handler":
|
|
290868
|
-
parts2.push("API route handler");
|
|
290869
|
-
break;
|
|
290870
|
-
case "component":
|
|
290871
|
-
parts2.push("UI component");
|
|
290872
|
-
break;
|
|
290873
|
-
case "repository":
|
|
290874
|
-
parts2.push("Data access layer");
|
|
290875
|
-
break;
|
|
290876
|
-
case "middleware":
|
|
290877
|
-
parts2.push("Request middleware");
|
|
290878
|
-
break;
|
|
290879
|
-
case "test":
|
|
290880
|
-
parts2.push("Test file");
|
|
290881
|
-
break;
|
|
290882
|
-
case "config":
|
|
290883
|
-
parts2.push("Configuration");
|
|
290884
|
-
break;
|
|
290885
|
-
case "type":
|
|
290886
|
-
parts2.push("Type definitions");
|
|
290887
|
-
break;
|
|
290888
|
-
case "util":
|
|
290889
|
-
parts2.push("Utility module");
|
|
290890
|
-
break;
|
|
290891
|
-
case "entry":
|
|
290892
|
-
parts2.push("Entry point");
|
|
290893
|
-
break;
|
|
290894
|
-
default:
|
|
290895
|
-
parts2.push("Source file");
|
|
290896
|
-
}
|
|
290897
|
-
if (ctx.fileContext) {
|
|
290898
|
-
if (ctx.fileContext.layer) parts2.push(`in ${ctx.fileContext.layer} layer`);
|
|
290899
|
-
if (ctx.fileContext.dependedOnBy.length > 10) parts2.push("(high-impact)");
|
|
290900
|
-
}
|
|
290901
|
-
return parts2.join(" ");
|
|
290902
|
-
}
|
|
290903
|
-
explainArchScore(score, dna) {
|
|
290904
|
-
if (score >= 80) return `Strong architecture with ${dna.patterns.length} recognized patterns`;
|
|
290905
|
-
if (score >= 50) return `Moderate architecture \u2014 ${dna.patterns.length} patterns detected, room to strengthen boundaries`;
|
|
290906
|
-
return "Architecture needs attention \u2014 few recognized patterns or boundaries";
|
|
290907
|
-
}
|
|
290908
|
-
explainTestScore(score) {
|
|
290909
|
-
if (score >= 80) return "Good test coverage across source files";
|
|
290910
|
-
if (score >= 50) return "Moderate coverage \u2014 some source files lack tests";
|
|
290911
|
-
return "Low test coverage \u2014 many exported modules have no test files";
|
|
290912
|
-
}
|
|
290913
|
-
explainConventionScore(score, dna) {
|
|
290914
|
-
const strong = dna.conventions.filter((c) => c.confidence > 0.6).length;
|
|
290915
|
-
if (score >= 80) return `${strong} strong conventions enforced consistently`;
|
|
290916
|
-
if (score >= 50) return `${strong} conventions detected but inconsistently applied`;
|
|
290917
|
-
return "Few consistent conventions \u2014 codebase style varies across files";
|
|
290918
|
-
}
|
|
290919
|
-
explainDependencyScore(score, dna) {
|
|
290920
|
-
const circular = dna.boundaries.filter((b) => b.isCircular).length;
|
|
290921
|
-
if (score >= 80) return "Clean dependency graph with no circular dependencies";
|
|
290922
|
-
if (circular > 0) return `${circular} circular dependency${circular > 1 ? "ies" : "y"} detected \u2014 these increase coupling and make code harder to reason about`;
|
|
290923
|
-
return "Dependency health needs improvement";
|
|
290924
|
-
}
|
|
290925
|
-
renderBar(score) {
|
|
290926
|
-
const filled = Math.round(score / 10);
|
|
290927
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
290928
|
-
}
|
|
290929
|
-
};
|
|
290930
|
-
function shortName(filePath) {
|
|
290931
|
-
const parts2 = filePath.split("/");
|
|
290932
|
-
return parts2[parts2.length - 1] ?? filePath;
|
|
290933
|
-
}
|
|
290934
|
-
|
|
290935
290944
|
// ../context-engine/dist/index.js
|
|
290936
290945
|
import * as fs5 from "fs";
|
|
290937
290946
|
import * as path6 from "path";
|
|
@@ -291813,7 +291822,7 @@ var ContextEngine = class {
|
|
|
291813
291822
|
// src/server.ts
|
|
291814
291823
|
init_dist();
|
|
291815
291824
|
|
|
291816
|
-
// ../subscriptions/dist/chunk-
|
|
291825
|
+
// ../subscriptions/dist/chunk-IRYOMNQT.js
|
|
291817
291826
|
var PLAN_IDS = ["free", "pro", "team", "enterprise"];
|
|
291818
291827
|
var PLAN_RANK = Object.fromEntries(
|
|
291819
291828
|
PLAN_IDS.map((id, index2) => [id, index2])
|
|
@@ -291898,9 +291907,9 @@ var PLAN_DEFINITIONS = {
|
|
|
291898
291907
|
},
|
|
291899
291908
|
pro: {
|
|
291900
291909
|
displayName: "Pro",
|
|
291901
|
-
tagline: "$
|
|
291902
|
-
monthlyPriceUsd:
|
|
291903
|
-
priceLabel: "$
|
|
291910
|
+
tagline: "$19.00/mo or $189.99/yr (save 17%).",
|
|
291911
|
+
monthlyPriceUsd: 19,
|
|
291912
|
+
priceLabel: "$19.00/mo",
|
|
291904
291913
|
billingInterval: "month",
|
|
291905
291914
|
badgeToken: "tier-pro",
|
|
291906
291915
|
highlights: [
|
|
@@ -293661,20 +293670,11 @@ var InputValidator = class {
|
|
|
293661
293670
|
errors.push("Path traversal detected");
|
|
293662
293671
|
logger.warn("Path traversal attempt blocked", { input, normalized });
|
|
293663
293672
|
}
|
|
293664
|
-
if (path7.isAbsolute(input)) {
|
|
293665
|
-
errors.push("Absolute paths not allowed");
|
|
293666
|
-
}
|
|
293667
293673
|
if (input.includes("\0")) {
|
|
293668
293674
|
errors.push("Null byte detected in path");
|
|
293669
293675
|
logger.warn("Null byte injection attempt blocked", { input });
|
|
293670
293676
|
}
|
|
293671
|
-
const suspiciousPatterns = [
|
|
293672
|
-
/\.\.\//,
|
|
293673
|
-
/\.\.\\/,
|
|
293674
|
-
/%2e%2e/i,
|
|
293675
|
-
/%252e/i,
|
|
293676
|
-
/\.\./
|
|
293677
|
-
];
|
|
293677
|
+
const suspiciousPatterns = [/\.\.\//, /\.\.\\/, /%2e%2e/i, /%252e/i, /\.\./];
|
|
293678
293678
|
for (const pattern of suspiciousPatterns) {
|
|
293679
293679
|
if (pattern.test(input)) {
|
|
293680
293680
|
errors.push(`Suspicious pattern detected: ${pattern}`);
|
|
@@ -293723,12 +293723,7 @@ var InputValidator = class {
|
|
|
293723
293723
|
break;
|
|
293724
293724
|
}
|
|
293725
293725
|
}
|
|
293726
|
-
const scriptPatterns = [
|
|
293727
|
-
/<script/i,
|
|
293728
|
-
/javascript:/i,
|
|
293729
|
-
/on\w+\s*=/i,
|
|
293730
|
-
/<iframe/i
|
|
293731
|
-
];
|
|
293726
|
+
const scriptPatterns = [/<script/i, /javascript:/i, /on\w+\s*=/i, /<iframe/i];
|
|
293732
293727
|
for (const pattern of scriptPatterns) {
|
|
293733
293728
|
if (pattern.test(input)) {
|
|
293734
293729
|
logger.warn("Potential script injection attempt blocked", { input });
|
|
@@ -293876,10 +293871,7 @@ var InputValidator = class {
|
|
|
293876
293871
|
}
|
|
293877
293872
|
}
|
|
293878
293873
|
if (args2.outcome !== void 0) {
|
|
293879
|
-
const outcomeResult = this.validateEnum(args2.outcome, [
|
|
293880
|
-
"helpful",
|
|
293881
|
-
"not_helpful"
|
|
293882
|
-
]);
|
|
293874
|
+
const outcomeResult = this.validateEnum(args2.outcome, ["helpful", "not_helpful"]);
|
|
293883
293875
|
if (!outcomeResult.valid) {
|
|
293884
293876
|
errors.push(...outcomeResult.errors.map((e) => `outcome: ${e}`));
|
|
293885
293877
|
}
|
|
@@ -293901,7 +293893,9 @@ var InputValidator = class {
|
|
|
293901
293893
|
}
|
|
293902
293894
|
}
|
|
293903
293895
|
if (args2.enginePreset !== void 0) {
|
|
293904
|
-
const presetResult = this.validateEnum(args2.enginePreset, [
|
|
293896
|
+
const presetResult = this.validateEnum(args2.enginePreset, [
|
|
293897
|
+
...VIBECHECK_SCAN_ENGINE_PRESETS
|
|
293898
|
+
]);
|
|
293905
293899
|
if (!presetResult.valid) {
|
|
293906
293900
|
errors.push(...presetResult.errors.map((e) => `enginePreset: ${e}`));
|
|
293907
293901
|
}
|
|
@@ -293919,6 +293913,21 @@ var InputValidator = class {
|
|
|
293919
293913
|
}
|
|
293920
293914
|
}
|
|
293921
293915
|
break;
|
|
293916
|
+
case "vibecheck_ghost": {
|
|
293917
|
+
if (args2.file !== void 0) {
|
|
293918
|
+
const fileResult = this.validatePath(args2.file);
|
|
293919
|
+
if (!fileResult.valid) {
|
|
293920
|
+
errors.push(...fileResult.errors.map((e) => `file: ${e}`));
|
|
293921
|
+
}
|
|
293922
|
+
}
|
|
293923
|
+
if (args2.path !== void 0) {
|
|
293924
|
+
const pathResult = this.validatePath(args2.path);
|
|
293925
|
+
if (!pathResult.valid) {
|
|
293926
|
+
errors.push(...pathResult.errors.map((e) => `path: ${e}`));
|
|
293927
|
+
}
|
|
293928
|
+
}
|
|
293929
|
+
break;
|
|
293930
|
+
}
|
|
293922
293931
|
}
|
|
293923
293932
|
if (errors.length > 0) {
|
|
293924
293933
|
logger.warn("Tool argument validation failed", {
|
|
@@ -293992,7 +294001,10 @@ var MCP_TOOLS = [
|
|
|
293992
294001
|
path: { type: "string", description: "Workspace root. Defaults to cwd." },
|
|
293993
294002
|
query: { type: "string", description: "Natural language query." },
|
|
293994
294003
|
limit: { type: "number", description: "Max results. Default 10." },
|
|
293995
|
-
useSemantic: {
|
|
294004
|
+
useSemantic: {
|
|
294005
|
+
type: "boolean",
|
|
294006
|
+
description: "Use semantic search (embeddings) when available. Slower but finds conceptually related code."
|
|
294007
|
+
}
|
|
293996
294008
|
},
|
|
293997
294009
|
required: ["query"]
|
|
293998
294010
|
}
|
|
@@ -294015,7 +294027,11 @@ var MCP_TOOLS = [
|
|
|
294015
294027
|
properties: {
|
|
294016
294028
|
path: { type: "string", description: "Workspace root. Defaults to cwd." },
|
|
294017
294029
|
file: { type: "string", description: "File path that was (or was not) helpful." },
|
|
294018
|
-
outcome: {
|
|
294030
|
+
outcome: {
|
|
294031
|
+
type: "string",
|
|
294032
|
+
enum: ["helpful", "not_helpful"],
|
|
294033
|
+
description: "User feedback."
|
|
294034
|
+
}
|
|
294019
294035
|
},
|
|
294020
294036
|
required: ["file", "outcome"]
|
|
294021
294037
|
}
|
|
@@ -294089,7 +294105,11 @@ var MCP_TOOLS = [
|
|
|
294089
294105
|
type: "object",
|
|
294090
294106
|
properties: {
|
|
294091
294107
|
path: { type: "string", description: "Destination path." },
|
|
294092
|
-
type: {
|
|
294108
|
+
type: {
|
|
294109
|
+
type: "string",
|
|
294110
|
+
enum: ["component", "api", "hook", "test"],
|
|
294111
|
+
description: "Type of code to forge."
|
|
294112
|
+
},
|
|
294093
294113
|
name: { type: "string", description: "Name of the component/api/hook." }
|
|
294094
294114
|
},
|
|
294095
294115
|
required: ["type", "name"]
|
|
@@ -294101,7 +294121,10 @@ var MCP_TOOLS = [
|
|
|
294101
294121
|
inputSchema: {
|
|
294102
294122
|
type: "object",
|
|
294103
294123
|
properties: {
|
|
294104
|
-
path: {
|
|
294124
|
+
path: {
|
|
294125
|
+
type: "string",
|
|
294126
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294127
|
+
},
|
|
294105
294128
|
targetUrl: { type: "string", description: "Base URL to test." }
|
|
294106
294129
|
}
|
|
294107
294130
|
}
|
|
@@ -294112,7 +294135,10 @@ var MCP_TOOLS = [
|
|
|
294112
294135
|
inputSchema: {
|
|
294113
294136
|
type: "object",
|
|
294114
294137
|
properties: {
|
|
294115
|
-
path: {
|
|
294138
|
+
path: {
|
|
294139
|
+
type: "string",
|
|
294140
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294141
|
+
}
|
|
294116
294142
|
}
|
|
294117
294143
|
}
|
|
294118
294144
|
},
|
|
@@ -294122,8 +294148,15 @@ var MCP_TOOLS = [
|
|
|
294122
294148
|
inputSchema: {
|
|
294123
294149
|
type: "object",
|
|
294124
294150
|
properties: {
|
|
294125
|
-
path: {
|
|
294126
|
-
|
|
294151
|
+
path: {
|
|
294152
|
+
type: "string",
|
|
294153
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294154
|
+
},
|
|
294155
|
+
mode: {
|
|
294156
|
+
type: "string",
|
|
294157
|
+
enum: ["enforce", "observe", "off"],
|
|
294158
|
+
description: "Firewall mode to set."
|
|
294159
|
+
}
|
|
294127
294160
|
},
|
|
294128
294161
|
required: ["mode"]
|
|
294129
294162
|
}
|
|
@@ -294140,13 +294173,30 @@ var MCP_TOOLS = [
|
|
|
294140
294173
|
required: ["action"]
|
|
294141
294174
|
}
|
|
294142
294175
|
},
|
|
294176
|
+
{
|
|
294177
|
+
name: "vibecheck_ghost",
|
|
294178
|
+
description: "Ghost Mode \u2014 symbolic execution trace showing how AI-generated code will break at runtime without running it. Returns a line-by-line trace with verdicts.",
|
|
294179
|
+
inputSchema: {
|
|
294180
|
+
type: "object",
|
|
294181
|
+
properties: {
|
|
294182
|
+
file: {
|
|
294183
|
+
type: "string",
|
|
294184
|
+
description: "File path to trace (relative to workspace or absolute)."
|
|
294185
|
+
}
|
|
294186
|
+
},
|
|
294187
|
+
required: ["file"]
|
|
294188
|
+
}
|
|
294189
|
+
},
|
|
294143
294190
|
{
|
|
294144
294191
|
name: "vibecheck_docguard",
|
|
294145
294192
|
description: "Documentation quality analysis (orphaned, stale, or duplicate docs).",
|
|
294146
294193
|
inputSchema: {
|
|
294147
294194
|
type: "object",
|
|
294148
294195
|
properties: {
|
|
294149
|
-
path: {
|
|
294196
|
+
path: {
|
|
294197
|
+
type: "string",
|
|
294198
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294199
|
+
}
|
|
294150
294200
|
}
|
|
294151
294201
|
}
|
|
294152
294202
|
},
|
|
@@ -294156,7 +294206,10 @@ var MCP_TOOLS = [
|
|
|
294156
294206
|
inputSchema: {
|
|
294157
294207
|
type: "object",
|
|
294158
294208
|
properties: {
|
|
294159
|
-
path: {
|
|
294209
|
+
path: {
|
|
294210
|
+
type: "string",
|
|
294211
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294212
|
+
}
|
|
294160
294213
|
}
|
|
294161
294214
|
}
|
|
294162
294215
|
},
|
|
@@ -294166,7 +294219,10 @@ var MCP_TOOLS = [
|
|
|
294166
294219
|
inputSchema: {
|
|
294167
294220
|
type: "object",
|
|
294168
294221
|
properties: {
|
|
294169
|
-
path: {
|
|
294222
|
+
path: {
|
|
294223
|
+
type: "string",
|
|
294224
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294225
|
+
}
|
|
294170
294226
|
}
|
|
294171
294227
|
}
|
|
294172
294228
|
},
|
|
@@ -294176,7 +294232,10 @@ var MCP_TOOLS = [
|
|
|
294176
294232
|
inputSchema: {
|
|
294177
294233
|
type: "object",
|
|
294178
294234
|
properties: {
|
|
294179
|
-
path: {
|
|
294235
|
+
path: {
|
|
294236
|
+
type: "string",
|
|
294237
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294238
|
+
},
|
|
294180
294239
|
out: { type: "string", description: "Output path for the truthpack." }
|
|
294181
294240
|
}
|
|
294182
294241
|
}
|
|
@@ -294187,7 +294246,10 @@ var MCP_TOOLS = [
|
|
|
294187
294246
|
inputSchema: {
|
|
294188
294247
|
type: "object",
|
|
294189
294248
|
properties: {
|
|
294190
|
-
path: {
|
|
294249
|
+
path: {
|
|
294250
|
+
type: "string",
|
|
294251
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294252
|
+
},
|
|
294191
294253
|
branch: { type: "string", description: "Branch to compare against." }
|
|
294192
294254
|
}
|
|
294193
294255
|
}
|
|
@@ -294198,7 +294260,10 @@ var MCP_TOOLS = [
|
|
|
294198
294260
|
inputSchema: {
|
|
294199
294261
|
type: "object",
|
|
294200
294262
|
properties: {
|
|
294201
|
-
path: {
|
|
294263
|
+
path: {
|
|
294264
|
+
type: "string",
|
|
294265
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294266
|
+
}
|
|
294202
294267
|
}
|
|
294203
294268
|
}
|
|
294204
294269
|
},
|
|
@@ -294209,7 +294274,10 @@ var MCP_TOOLS = [
|
|
|
294209
294274
|
type: "object",
|
|
294210
294275
|
properties: {
|
|
294211
294276
|
intent: { type: "string", description: "Description of the feature to build." },
|
|
294212
|
-
path: {
|
|
294277
|
+
path: {
|
|
294278
|
+
type: "string",
|
|
294279
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294280
|
+
}
|
|
294213
294281
|
},
|
|
294214
294282
|
required: ["intent"]
|
|
294215
294283
|
}
|
|
@@ -294220,7 +294288,10 @@ var MCP_TOOLS = [
|
|
|
294220
294288
|
inputSchema: {
|
|
294221
294289
|
type: "object",
|
|
294222
294290
|
properties: {
|
|
294223
|
-
path: {
|
|
294291
|
+
path: {
|
|
294292
|
+
type: "string",
|
|
294293
|
+
description: "Workspace or project root path. Defaults to current directory."
|
|
294294
|
+
}
|
|
294224
294295
|
}
|
|
294225
294296
|
}
|
|
294226
294297
|
},
|
|
@@ -294254,9 +294325,19 @@ var MCP_TOOLS = [
|
|
|
294254
294325
|
type: "object",
|
|
294255
294326
|
properties: {
|
|
294256
294327
|
path: { type: "string", description: "Project path. Defaults to current directory." },
|
|
294257
|
-
severity: {
|
|
294258
|
-
|
|
294259
|
-
|
|
294328
|
+
severity: {
|
|
294329
|
+
type: "string",
|
|
294330
|
+
enum: ["critical", "high", "medium", "low", "info"],
|
|
294331
|
+
description: "Filter by severity."
|
|
294332
|
+
},
|
|
294333
|
+
engine: {
|
|
294334
|
+
type: "string",
|
|
294335
|
+
description: 'Filter by engine ID (e.g. "phantom_dep", "ghost_route").'
|
|
294336
|
+
},
|
|
294337
|
+
file: {
|
|
294338
|
+
type: "string",
|
|
294339
|
+
description: "Filter findings to a specific file or directory path."
|
|
294340
|
+
}
|
|
294260
294341
|
}
|
|
294261
294342
|
}
|
|
294262
294343
|
},
|
|
@@ -294310,7 +294391,10 @@ var MCP_TOOLS = [
|
|
|
294310
294391
|
inputSchema: {
|
|
294311
294392
|
type: "object",
|
|
294312
294393
|
properties: {
|
|
294313
|
-
path: {
|
|
294394
|
+
path: {
|
|
294395
|
+
type: "string",
|
|
294396
|
+
description: "Workspace root path. Defaults to current directory."
|
|
294397
|
+
}
|
|
294314
294398
|
}
|
|
294315
294399
|
}
|
|
294316
294400
|
}
|
|
@@ -294461,12 +294545,12 @@ function createScanIdempotencyKey(prefix) {
|
|
|
294461
294545
|
// src/mcp-scan-meter-client.ts
|
|
294462
294546
|
var MCP_SCAN_METER_CLIENT = {
|
|
294463
294547
|
type: "mcp",
|
|
294464
|
-
version: "24.
|
|
294548
|
+
version: "24.6.2"
|
|
294465
294549
|
};
|
|
294466
294550
|
|
|
294467
294551
|
// src/server.ts
|
|
294468
294552
|
import { uploadScanToApi } from "@repo/shared/sync/upload-scan";
|
|
294469
|
-
var
|
|
294553
|
+
var execFileAsync = promisify(execFile);
|
|
294470
294554
|
async function executeScan(targetPath, engineToggles = null) {
|
|
294471
294555
|
const resolved = path9.resolve(targetPath);
|
|
294472
294556
|
const stat4 = fs6.statSync(resolved);
|
|
@@ -294617,10 +294701,7 @@ async function runGuard(targetPath) {
|
|
|
294617
294701
|
var MCP_TOOL_TIMEOUT_MS = 18e4;
|
|
294618
294702
|
function withTimeout(promise, ms, label) {
|
|
294619
294703
|
return new Promise((resolve6, reject) => {
|
|
294620
|
-
const timer = setTimeout(
|
|
294621
|
-
() => reject(new Error(`${label} timed out after ${ms / 1e3}s`)),
|
|
294622
|
-
ms
|
|
294623
|
-
);
|
|
294704
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1e3}s`)), ms);
|
|
294624
294705
|
promise.then(
|
|
294625
294706
|
(value) => {
|
|
294626
294707
|
clearTimeout(timer);
|
|
@@ -294749,10 +294830,8 @@ async function handleCallToolRequest(request, runtimeOverrides = {}) {
|
|
|
294749
294830
|
}
|
|
294750
294831
|
const validation = InputValidator.validateToolArgs(name2, args2);
|
|
294751
294832
|
if (!validation.valid) {
|
|
294752
|
-
return buildErrorResponse(
|
|
294753
|
-
|
|
294754
|
-
${validation.errors.join("\n")}`
|
|
294755
|
-
);
|
|
294833
|
+
return buildErrorResponse(`Invalid arguments for ${name2}:
|
|
294834
|
+
${validation.errors.join("\n")}`);
|
|
294756
294835
|
}
|
|
294757
294836
|
const BILLABLE_ENGINE_TOOLS = /* @__PURE__ */ new Set(["vibecheck_scan", "vibecheck_score"]);
|
|
294758
294837
|
if (BILLABLE_ENGINE_TOOLS.has(name2)) {
|
|
@@ -294795,9 +294874,14 @@ ${validation.errors.join("\n")}`
|
|
|
294795
294874
|
const recordMcpBillableUsage = async (toolKey) => {
|
|
294796
294875
|
const tok = process.env.VIBECHECK_TOKEN?.trim();
|
|
294797
294876
|
if (!tok || shouldSkipServerScanMetering()) return { ok: true };
|
|
294798
|
-
const r = await postServerScanRecord(
|
|
294799
|
-
|
|
294800
|
-
|
|
294877
|
+
const r = await postServerScanRecord(
|
|
294878
|
+
tok,
|
|
294879
|
+
MCP_SCAN_METER_CLIENT,
|
|
294880
|
+
createScanIdempotencyKey(toolKey),
|
|
294881
|
+
{
|
|
294882
|
+
scanType: toolKey.replace(/^mcp-/, "")
|
|
294883
|
+
}
|
|
294884
|
+
);
|
|
294801
294885
|
if (!r.ok) {
|
|
294802
294886
|
return { ok: false, message: r.error };
|
|
294803
294887
|
}
|
|
@@ -294819,9 +294903,18 @@ ${validation.errors.join("\n")}`
|
|
|
294819
294903
|
await engine.initialize(data);
|
|
294820
294904
|
return engine.getProactiveContext({ focusedFile });
|
|
294821
294905
|
};
|
|
294822
|
-
const context = await withTimeout(
|
|
294906
|
+
const context = await withTimeout(
|
|
294907
|
+
run2(),
|
|
294908
|
+
MCP_TOOL_TIMEOUT_MS,
|
|
294909
|
+
"vibecheck_context_proactive"
|
|
294910
|
+
);
|
|
294823
294911
|
return {
|
|
294824
|
-
content: [
|
|
294912
|
+
content: [
|
|
294913
|
+
{
|
|
294914
|
+
type: "text",
|
|
294915
|
+
text: typeof context === "string" ? context : JSON.stringify(context, null, 2)
|
|
294916
|
+
}
|
|
294917
|
+
]
|
|
294825
294918
|
};
|
|
294826
294919
|
}
|
|
294827
294920
|
case "vibecheck_context_intent": {
|
|
@@ -294868,12 +294961,16 @@ ${validation.errors.join("\n")}`
|
|
|
294868
294961
|
const file = args2?.file;
|
|
294869
294962
|
const outcome = args2?.outcome;
|
|
294870
294963
|
if (!file || !outcome) {
|
|
294871
|
-
return buildErrorResponse(
|
|
294964
|
+
return buildErrorResponse(
|
|
294965
|
+
'vibecheck_context_feedback requires "file" and "outcome" (helpful|not_helpful)'
|
|
294966
|
+
);
|
|
294872
294967
|
}
|
|
294873
294968
|
const feedbackFile = normalizeRelativeWorkspaceFilePath(file, "file");
|
|
294874
294969
|
await runtime.recordFeedback(targetPath, feedbackFile, outcome);
|
|
294875
294970
|
return {
|
|
294876
|
-
content: [
|
|
294971
|
+
content: [
|
|
294972
|
+
{ type: "text", text: JSON.stringify({ ok: true, file: feedbackFile, outcome }) }
|
|
294973
|
+
]
|
|
294877
294974
|
};
|
|
294878
294975
|
}
|
|
294879
294976
|
case "vibecheck_roast": {
|
|
@@ -294993,6 +295090,31 @@ ${validation.errors.join("\n")}`
|
|
|
294993
295090
|
]
|
|
294994
295091
|
};
|
|
294995
295092
|
}
|
|
295093
|
+
case "vibecheck_ghost": {
|
|
295094
|
+
const fileArg = args2.file;
|
|
295095
|
+
if (!fileArg || typeof fileArg !== "string") {
|
|
295096
|
+
return buildErrorResponse('vibecheck_ghost requires "file"');
|
|
295097
|
+
}
|
|
295098
|
+
let relFile;
|
|
295099
|
+
try {
|
|
295100
|
+
relFile = normalizeRelativeWorkspaceFilePath(fileArg, "file");
|
|
295101
|
+
} catch (e) {
|
|
295102
|
+
return buildErrorResponse(e instanceof Error ? e.message : "Invalid file path");
|
|
295103
|
+
}
|
|
295104
|
+
const absFile = path9.join(targetPath, relFile);
|
|
295105
|
+
if (!fs6.existsSync(absFile) || !fs6.statSync(absFile).isFile()) {
|
|
295106
|
+
return buildErrorResponse(`Not a file: ${relFile}`);
|
|
295107
|
+
}
|
|
295108
|
+
const { runGhostTrace } = await import("@vibecheck/engines");
|
|
295109
|
+
const trace = await withTimeout(
|
|
295110
|
+
runGhostTrace({ workspaceRoot: targetPath, filePath: absFile }),
|
|
295111
|
+
MCP_TOOL_TIMEOUT_MS,
|
|
295112
|
+
"vibecheck_ghost"
|
|
295113
|
+
);
|
|
295114
|
+
return {
|
|
295115
|
+
content: [{ type: "text", text: JSON.stringify(trace, null, 2) }]
|
|
295116
|
+
};
|
|
295117
|
+
}
|
|
294996
295118
|
// ── Platform Unification Tool Handlers ──────────────────────────────
|
|
294997
295119
|
case "vibecheck_trust_score": {
|
|
294998
295120
|
const result = await withTimeout(
|
|
@@ -295031,10 +295153,7 @@ ${validation.errors.join("\n")}`
|
|
|
295031
295153
|
const fileFilter = args2.file;
|
|
295032
295154
|
filtered = filtered.filter((f) => f.file?.includes(fileFilter));
|
|
295033
295155
|
}
|
|
295034
|
-
const lines = [
|
|
295035
|
-
`## Findings (${filtered.length} of ${gatedReport.summary.total})`,
|
|
295036
|
-
""
|
|
295037
|
-
];
|
|
295156
|
+
const lines = [`## Findings (${filtered.length} of ${gatedReport.summary.total})`, ""];
|
|
295038
295157
|
for (const f of filtered.slice(0, 50)) {
|
|
295039
295158
|
const loc = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : "unknown";
|
|
295040
295159
|
lines.push(`- **[${f.severity.toUpperCase()}]** ${f.message}`);
|
|
@@ -295086,16 +295205,22 @@ ${validation.errors.join("\n")}`
|
|
|
295086
295205
|
const token = process.env.VIBECHECK_TOKEN?.trim();
|
|
295087
295206
|
if (!token) {
|
|
295088
295207
|
return {
|
|
295089
|
-
content: [
|
|
295090
|
-
|
|
295091
|
-
|
|
295092
|
-
|
|
295093
|
-
|
|
295094
|
-
|
|
295095
|
-
|
|
295096
|
-
|
|
295097
|
-
|
|
295098
|
-
|
|
295208
|
+
content: [
|
|
295209
|
+
{
|
|
295210
|
+
type: "text",
|
|
295211
|
+
text: JSON.stringify(
|
|
295212
|
+
{
|
|
295213
|
+
ok: false,
|
|
295214
|
+
message: "Authentication required to dismiss findings across surfaces. Set VIBECHECK_TOKEN or run `vibecheck auth login`.",
|
|
295215
|
+
localOnly: true,
|
|
295216
|
+
findingId,
|
|
295217
|
+
reason
|
|
295218
|
+
},
|
|
295219
|
+
null,
|
|
295220
|
+
2
|
|
295221
|
+
)
|
|
295222
|
+
}
|
|
295223
|
+
]
|
|
295099
295224
|
};
|
|
295100
295225
|
}
|
|
295101
295226
|
try {
|
|
@@ -295103,7 +295228,7 @@ ${validation.errors.join("\n")}`
|
|
|
295103
295228
|
const resp = await fetch(`${apiBase}/api/v1/findings/${findingId}`, {
|
|
295104
295229
|
method: "PATCH",
|
|
295105
295230
|
headers: {
|
|
295106
|
-
|
|
295231
|
+
Authorization: `Bearer ${token}`,
|
|
295107
295232
|
"Content-Type": "application/json"
|
|
295108
295233
|
},
|
|
295109
295234
|
body: JSON.stringify({ resolved: true })
|
|
@@ -295112,31 +295237,37 @@ ${validation.errors.join("\n")}`
|
|
|
295112
295237
|
return buildErrorResponse(`Failed to dismiss finding: HTTP ${resp.status}`);
|
|
295113
295238
|
}
|
|
295114
295239
|
return {
|
|
295115
|
-
content: [
|
|
295116
|
-
|
|
295117
|
-
|
|
295240
|
+
content: [
|
|
295241
|
+
{
|
|
295242
|
+
type: "text",
|
|
295243
|
+
text: `Finding \`${findingId}\` dismissed. Reason: ${reason}
|
|
295118
295244
|
This change is synced across all surfaces.`
|
|
295119
|
-
|
|
295245
|
+
}
|
|
295246
|
+
]
|
|
295120
295247
|
};
|
|
295121
295248
|
} catch (err2) {
|
|
295122
|
-
return buildErrorResponse(
|
|
295249
|
+
return buildErrorResponse(
|
|
295250
|
+
`Failed to dismiss finding: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
295251
|
+
);
|
|
295123
295252
|
}
|
|
295124
295253
|
}
|
|
295125
295254
|
case "vibecheck_history": {
|
|
295126
295255
|
const token = process.env.VIBECHECK_TOKEN?.trim();
|
|
295127
295256
|
if (!token) {
|
|
295128
295257
|
return {
|
|
295129
|
-
content: [
|
|
295130
|
-
|
|
295131
|
-
|
|
295132
|
-
|
|
295258
|
+
content: [
|
|
295259
|
+
{
|
|
295260
|
+
type: "text",
|
|
295261
|
+
text: "Scan history requires authentication. Set VIBECHECK_TOKEN or run `vibecheck auth login`."
|
|
295262
|
+
}
|
|
295263
|
+
]
|
|
295133
295264
|
};
|
|
295134
295265
|
}
|
|
295135
295266
|
try {
|
|
295136
295267
|
const apiBase = process.env.VIBECHECK_API_URL || "https://api.vibecheckai.dev";
|
|
295137
295268
|
const limit = args2.limit ?? 10;
|
|
295138
295269
|
const resp = await fetch(`${apiBase}/api/v1/scans/recent?limit=${limit}`, {
|
|
295139
|
-
headers: {
|
|
295270
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
295140
295271
|
});
|
|
295141
295272
|
if (!resp.ok) {
|
|
295142
295273
|
return buildErrorResponse(`Failed to fetch scan history: HTTP ${resp.status}`);
|
|
@@ -295144,7 +295275,14 @@ This change is synced across all surfaces.`
|
|
|
295144
295275
|
const body2 = await resp.json();
|
|
295145
295276
|
const scans = body2.data ?? [];
|
|
295146
295277
|
if (scans.length === 0) {
|
|
295147
|
-
return {
|
|
295278
|
+
return {
|
|
295279
|
+
content: [
|
|
295280
|
+
{
|
|
295281
|
+
type: "text",
|
|
295282
|
+
text: "No scan history found. Run `vibecheck scan .` to create your first scan."
|
|
295283
|
+
}
|
|
295284
|
+
]
|
|
295285
|
+
};
|
|
295148
295286
|
}
|
|
295149
295287
|
const lines = [`## Scan History (${scans.length} most recent)`, ""];
|
|
295150
295288
|
for (const s of scans) {
|
|
@@ -295155,7 +295293,9 @@ This change is synced across all surfaces.`
|
|
|
295155
295293
|
}
|
|
295156
295294
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
295157
295295
|
} catch (err2) {
|
|
295158
|
-
return buildErrorResponse(
|
|
295296
|
+
return buildErrorResponse(
|
|
295297
|
+
`Failed to fetch history: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
295298
|
+
);
|
|
295159
295299
|
}
|
|
295160
295300
|
}
|
|
295161
295301
|
case "vibecheck_engines": {
|
|
@@ -295218,10 +295358,16 @@ This change is synced across all surfaces.`
|
|
|
295218
295358
|
case "vibecheck_explain_file":
|
|
295219
295359
|
case "vibecheck_review": {
|
|
295220
295360
|
const cmdName = name2.replace("vibecheck_", "");
|
|
295221
|
-
const
|
|
295361
|
+
const cliArgv = [cmdName];
|
|
295362
|
+
for (const [k, v] of Object.entries(args2)) {
|
|
295363
|
+
if (k === "path") continue;
|
|
295364
|
+
cliArgv.push(`--${k}`, String(v));
|
|
295365
|
+
}
|
|
295222
295366
|
const workDir = targetPath || workspaceRoot;
|
|
295367
|
+
const vibecheckBin = new URL("../node_modules/.bin/vibecheck", import.meta.url).pathname;
|
|
295368
|
+
const binPath = fs6.existsSync(vibecheckBin) ? vibecheckBin : "vibecheck";
|
|
295223
295369
|
try {
|
|
295224
|
-
const { stdout, stderr } = await
|
|
295370
|
+
const { stdout, stderr } = await execFileAsync(binPath, cliArgv, { cwd: workDir });
|
|
295225
295371
|
return {
|
|
295226
295372
|
content: [
|
|
295227
295373
|
{
|
|
@@ -295231,7 +295377,10 @@ This change is synced across all surfaces.`
|
|
|
295231
295377
|
]
|
|
295232
295378
|
};
|
|
295233
295379
|
} catch (error) {
|
|
295234
|
-
|
|
295380
|
+
const err2 = error;
|
|
295381
|
+
return buildErrorResponse(
|
|
295382
|
+
`CLI Execution Error: ${err2.message ?? err2.stdout ?? err2.stderr ?? String(error)}`
|
|
295383
|
+
);
|
|
295235
295384
|
}
|
|
295236
295385
|
}
|
|
295237
295386
|
default:
|
|
@@ -295247,7 +295396,7 @@ function createMcpServer(runtimeOverrides = {}) {
|
|
|
295247
295396
|
const server2 = new Server(
|
|
295248
295397
|
{
|
|
295249
295398
|
name: "vibecheck-mcp",
|
|
295250
|
-
version: "
|
|
295399
|
+
version: "24.6.2"
|
|
295251
295400
|
},
|
|
295252
295401
|
{
|
|
295253
295402
|
capabilities: {
|