@a-company/paradigm 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp.js +286 -43
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -15940,6 +15940,7 @@ var import_sql2 = __toESM(require_sql_wasm(), 1);
|
|
|
15940
15940
|
import * as fs24 from "fs";
|
|
15941
15941
|
import * as path27 from "path";
|
|
15942
15942
|
import * as crypto5 from "crypto";
|
|
15943
|
+
import { execSync as execSync2 } from "child_process";
|
|
15943
15944
|
var cachedSQL = null;
|
|
15944
15945
|
async function getSqlJs() {
|
|
15945
15946
|
if (!cachedSQL) {
|
|
@@ -15967,6 +15968,8 @@ var SCHEMA_STATEMENTS = [
|
|
|
15967
15968
|
start_line INTEGER NOT NULL,
|
|
15968
15969
|
end_line INTEGER NOT NULL,
|
|
15969
15970
|
content_hash TEXT,
|
|
15971
|
+
normalized_hash TEXT,
|
|
15972
|
+
materialized_at_commit TEXT,
|
|
15970
15973
|
last_verified TEXT,
|
|
15971
15974
|
drifted INTEGER DEFAULT 0
|
|
15972
15975
|
)`,
|
|
@@ -16072,9 +16075,14 @@ function closeAspectGraph(db, rootDir) {
|
|
|
16072
16075
|
}
|
|
16073
16076
|
db.close();
|
|
16074
16077
|
}
|
|
16075
|
-
function materializeAspects(db, symbols) {
|
|
16078
|
+
function materializeAspects(db, symbols, rootDir) {
|
|
16076
16079
|
const aspects = symbols.filter((s) => s.type === "aspect");
|
|
16077
16080
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16081
|
+
let headCommit = null;
|
|
16082
|
+
try {
|
|
16083
|
+
headCommit = execSync2("git rev-parse HEAD", { cwd: rootDir, encoding: "utf8" }).trim();
|
|
16084
|
+
} catch {
|
|
16085
|
+
}
|
|
16078
16086
|
db.run("DELETE FROM anchors");
|
|
16079
16087
|
db.run("DELETE FROM edges");
|
|
16080
16088
|
db.run("DELETE FROM aspects");
|
|
@@ -16108,11 +16116,11 @@ function materializeAspects(db, symbols) {
|
|
|
16108
16116
|
if (entry.anchors) {
|
|
16109
16117
|
for (const anchor of entry.anchors) {
|
|
16110
16118
|
const { startLine, endLine } = resolveAnchorLines(anchor);
|
|
16111
|
-
const
|
|
16119
|
+
const hashes = computeAnchorHash(anchor, null);
|
|
16112
16120
|
db.run(
|
|
16113
|
-
`INSERT INTO anchors (aspect_id, file_path, start_line, end_line, content_hash, last_verified)
|
|
16114
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
16115
|
-
[entry.symbol, anchor.path, startLine, endLine,
|
|
16121
|
+
`INSERT INTO anchors (aspect_id, file_path, start_line, end_line, content_hash, normalized_hash, materialized_at_commit, last_verified)
|
|
16122
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
16123
|
+
[entry.symbol, anchor.path, startLine, endLine, hashes.exact, hashes.normalized, headCommit, now]
|
|
16116
16124
|
);
|
|
16117
16125
|
}
|
|
16118
16126
|
}
|
|
@@ -16195,7 +16203,7 @@ function getHeatmap(db, limit = 20, accessType) {
|
|
|
16195
16203
|
[limit]
|
|
16196
16204
|
);
|
|
16197
16205
|
}
|
|
16198
|
-
function checkDrift(db, rootDir, aspectId) {
|
|
16206
|
+
function checkDrift(db, rootDir, aspectId, autoHeal = true) {
|
|
16199
16207
|
const anchorRows = aspectId ? queryRows(db, "SELECT * FROM anchors WHERE aspect_id = ?", [aspectId]) : queryRows(db, "SELECT * FROM anchors");
|
|
16200
16208
|
const results = [];
|
|
16201
16209
|
for (const anchor of anchorRows) {
|
|
@@ -16206,8 +16214,10 @@ function checkDrift(db, rootDir, aspectId) {
|
|
|
16206
16214
|
path: anchor.file_path,
|
|
16207
16215
|
startLine: anchor.start_line,
|
|
16208
16216
|
endLine: anchor.end_line,
|
|
16209
|
-
|
|
16210
|
-
|
|
16217
|
+
status: "missing",
|
|
16218
|
+
resolvedBy: "none",
|
|
16219
|
+
exists: false,
|
|
16220
|
+
drifted: true
|
|
16211
16221
|
});
|
|
16212
16222
|
continue;
|
|
16213
16223
|
}
|
|
@@ -16217,30 +16227,174 @@ function checkDrift(db, rootDir, aspectId) {
|
|
|
16217
16227
|
const startIdx = Math.max(0, anchor.start_line - 1);
|
|
16218
16228
|
const endIdx = Math.min(lines.length, anchor.end_line);
|
|
16219
16229
|
const sliceContent = lines.slice(startIdx, endIdx).join("\n");
|
|
16220
|
-
const
|
|
16221
|
-
|
|
16230
|
+
const currentExactHash = crypto5.createHash("sha256").update(sliceContent).digest("hex");
|
|
16231
|
+
if (anchor.content_hash != null && currentExactHash === anchor.content_hash) {
|
|
16232
|
+
results.push({
|
|
16233
|
+
aspectId: anchor.aspect_id,
|
|
16234
|
+
path: anchor.file_path,
|
|
16235
|
+
startLine: anchor.start_line,
|
|
16236
|
+
endLine: anchor.end_line,
|
|
16237
|
+
status: "clean",
|
|
16238
|
+
resolvedBy: "exact-hash",
|
|
16239
|
+
exists: true,
|
|
16240
|
+
drifted: false
|
|
16241
|
+
});
|
|
16242
|
+
if (anchor.drifted === 1) {
|
|
16243
|
+
db.run("UPDATE anchors SET drifted = 0 WHERE id = ?", [anchor.id]);
|
|
16244
|
+
}
|
|
16245
|
+
continue;
|
|
16246
|
+
}
|
|
16247
|
+
const currentNormalizedHash = crypto5.createHash("sha256").update(normalizeForHash(sliceContent)).digest("hex");
|
|
16248
|
+
if (anchor.normalized_hash != null && currentNormalizedHash === anchor.normalized_hash) {
|
|
16249
|
+
db.run("UPDATE anchors SET content_hash = ?, drifted = 0 WHERE id = ?", [currentExactHash, anchor.id]);
|
|
16250
|
+
results.push({
|
|
16251
|
+
aspectId: anchor.aspect_id,
|
|
16252
|
+
path: anchor.file_path,
|
|
16253
|
+
startLine: anchor.start_line,
|
|
16254
|
+
endLine: anchor.end_line,
|
|
16255
|
+
status: "cosmetic",
|
|
16256
|
+
resolvedBy: "normalized-hash",
|
|
16257
|
+
exists: true,
|
|
16258
|
+
drifted: false
|
|
16259
|
+
});
|
|
16260
|
+
continue;
|
|
16261
|
+
}
|
|
16262
|
+
if (anchor.content_hash == null && anchor.normalized_hash == null) {
|
|
16263
|
+
db.run(
|
|
16264
|
+
"UPDATE anchors SET content_hash = ?, normalized_hash = ?, drifted = 0 WHERE id = ?",
|
|
16265
|
+
[currentExactHash, currentNormalizedHash, anchor.id]
|
|
16266
|
+
);
|
|
16267
|
+
results.push({
|
|
16268
|
+
aspectId: anchor.aspect_id,
|
|
16269
|
+
path: anchor.file_path,
|
|
16270
|
+
startLine: anchor.start_line,
|
|
16271
|
+
endLine: anchor.end_line,
|
|
16272
|
+
status: "clean",
|
|
16273
|
+
resolvedBy: "exact-hash",
|
|
16274
|
+
exists: true,
|
|
16275
|
+
drifted: false
|
|
16276
|
+
});
|
|
16277
|
+
continue;
|
|
16278
|
+
}
|
|
16279
|
+
let resolvedByGit = false;
|
|
16280
|
+
if (anchor.materialized_at_commit) {
|
|
16281
|
+
const mapping = computeLineShift(
|
|
16282
|
+
rootDir,
|
|
16283
|
+
anchor.file_path,
|
|
16284
|
+
anchor.materialized_at_commit,
|
|
16285
|
+
anchor.start_line,
|
|
16286
|
+
anchor.end_line
|
|
16287
|
+
);
|
|
16288
|
+
if (mapping) {
|
|
16289
|
+
const shiftedStartIdx = Math.max(0, mapping.currentStart - 1);
|
|
16290
|
+
const shiftedEndIdx = Math.min(lines.length, mapping.currentEnd);
|
|
16291
|
+
const shiftedContent = lines.slice(shiftedStartIdx, shiftedEndIdx).join("\n");
|
|
16292
|
+
const shiftedExactHash = crypto5.createHash("sha256").update(shiftedContent).digest("hex");
|
|
16293
|
+
if (anchor.content_hash != null && shiftedExactHash === anchor.content_hash) {
|
|
16294
|
+
const healed = autoHeal;
|
|
16295
|
+
if (healed) {
|
|
16296
|
+
db.run(
|
|
16297
|
+
"UPDATE anchors SET start_line = ?, end_line = ?, drifted = 0 WHERE id = ?",
|
|
16298
|
+
[mapping.currentStart, mapping.currentEnd, anchor.id]
|
|
16299
|
+
);
|
|
16300
|
+
const aspectRow = queryRows(
|
|
16301
|
+
db,
|
|
16302
|
+
"SELECT defined_in FROM aspects WHERE id = ?",
|
|
16303
|
+
[anchor.aspect_id]
|
|
16304
|
+
);
|
|
16305
|
+
if (aspectRow.length > 0) {
|
|
16306
|
+
healAnchorInPurposeFile(
|
|
16307
|
+
rootDir,
|
|
16308
|
+
aspectRow[0].defined_in,
|
|
16309
|
+
anchor.file_path,
|
|
16310
|
+
anchor.start_line,
|
|
16311
|
+
anchor.end_line,
|
|
16312
|
+
mapping.currentStart,
|
|
16313
|
+
mapping.currentEnd
|
|
16314
|
+
);
|
|
16315
|
+
}
|
|
16316
|
+
}
|
|
16317
|
+
results.push({
|
|
16318
|
+
aspectId: anchor.aspect_id,
|
|
16319
|
+
path: anchor.file_path,
|
|
16320
|
+
startLine: healed ? mapping.currentStart : anchor.start_line,
|
|
16321
|
+
endLine: healed ? mapping.currentEnd : anchor.end_line,
|
|
16322
|
+
status: "shifted",
|
|
16323
|
+
resolvedBy: "git-line-mapping",
|
|
16324
|
+
exists: true,
|
|
16325
|
+
drifted: false,
|
|
16326
|
+
suggestedStart: mapping.currentStart,
|
|
16327
|
+
suggestedEnd: mapping.currentEnd,
|
|
16328
|
+
autoHealed: healed
|
|
16329
|
+
});
|
|
16330
|
+
resolvedByGit = true;
|
|
16331
|
+
} else {
|
|
16332
|
+
const shiftedNormalized = crypto5.createHash("sha256").update(normalizeForHash(shiftedContent)).digest("hex");
|
|
16333
|
+
if (anchor.normalized_hash != null && shiftedNormalized === anchor.normalized_hash) {
|
|
16334
|
+
if (autoHeal) {
|
|
16335
|
+
const shiftedNewHash = crypto5.createHash("sha256").update(shiftedContent).digest("hex");
|
|
16336
|
+
db.run(
|
|
16337
|
+
"UPDATE anchors SET start_line = ?, end_line = ?, content_hash = ?, drifted = 0 WHERE id = ?",
|
|
16338
|
+
[mapping.currentStart, mapping.currentEnd, shiftedNewHash, anchor.id]
|
|
16339
|
+
);
|
|
16340
|
+
const aspectRow = queryRows(
|
|
16341
|
+
db,
|
|
16342
|
+
"SELECT defined_in FROM aspects WHERE id = ?",
|
|
16343
|
+
[anchor.aspect_id]
|
|
16344
|
+
);
|
|
16345
|
+
if (aspectRow.length > 0) {
|
|
16346
|
+
healAnchorInPurposeFile(
|
|
16347
|
+
rootDir,
|
|
16348
|
+
aspectRow[0].defined_in,
|
|
16349
|
+
anchor.file_path,
|
|
16350
|
+
anchor.start_line,
|
|
16351
|
+
anchor.end_line,
|
|
16352
|
+
mapping.currentStart,
|
|
16353
|
+
mapping.currentEnd
|
|
16354
|
+
);
|
|
16355
|
+
}
|
|
16356
|
+
}
|
|
16357
|
+
results.push({
|
|
16358
|
+
aspectId: anchor.aspect_id,
|
|
16359
|
+
path: anchor.file_path,
|
|
16360
|
+
startLine: autoHeal ? mapping.currentStart : anchor.start_line,
|
|
16361
|
+
endLine: autoHeal ? mapping.currentEnd : anchor.end_line,
|
|
16362
|
+
status: "shifted",
|
|
16363
|
+
resolvedBy: "git-line-mapping",
|
|
16364
|
+
exists: true,
|
|
16365
|
+
drifted: false,
|
|
16366
|
+
suggestedStart: mapping.currentStart,
|
|
16367
|
+
suggestedEnd: mapping.currentEnd,
|
|
16368
|
+
autoHealed: autoHeal
|
|
16369
|
+
});
|
|
16370
|
+
resolvedByGit = true;
|
|
16371
|
+
}
|
|
16372
|
+
}
|
|
16373
|
+
}
|
|
16374
|
+
}
|
|
16375
|
+
if (resolvedByGit) continue;
|
|
16376
|
+
db.run("UPDATE anchors SET drifted = 1 WHERE id = ?", [anchor.id]);
|
|
16222
16377
|
results.push({
|
|
16223
16378
|
aspectId: anchor.aspect_id,
|
|
16224
16379
|
path: anchor.file_path,
|
|
16225
16380
|
startLine: anchor.start_line,
|
|
16226
16381
|
endLine: anchor.end_line,
|
|
16227
|
-
|
|
16382
|
+
status: "modified",
|
|
16383
|
+
resolvedBy: "none",
|
|
16228
16384
|
exists: true,
|
|
16229
|
-
currentContent:
|
|
16385
|
+
currentContent: sliceContent,
|
|
16386
|
+
drifted: true
|
|
16230
16387
|
});
|
|
16231
|
-
if (drifted) {
|
|
16232
|
-
db.run("UPDATE anchors SET drifted = 1 WHERE id = ?", [anchor.id]);
|
|
16233
|
-
} else if (anchor.drifted === 1) {
|
|
16234
|
-
db.run("UPDATE anchors SET drifted = 0 WHERE id = ?", [anchor.id]);
|
|
16235
|
-
}
|
|
16236
16388
|
} catch {
|
|
16237
16389
|
results.push({
|
|
16238
16390
|
aspectId: anchor.aspect_id,
|
|
16239
16391
|
path: anchor.file_path,
|
|
16240
16392
|
startLine: anchor.start_line,
|
|
16241
16393
|
endLine: anchor.end_line,
|
|
16242
|
-
|
|
16243
|
-
|
|
16394
|
+
status: "modified",
|
|
16395
|
+
resolvedBy: "none",
|
|
16396
|
+
exists: true,
|
|
16397
|
+
drifted: true
|
|
16244
16398
|
});
|
|
16245
16399
|
}
|
|
16246
16400
|
}
|
|
@@ -16262,10 +16416,76 @@ function resolveAnchorLines(anchor) {
|
|
|
16262
16416
|
}
|
|
16263
16417
|
return { startLine: 1, endLine: 1 };
|
|
16264
16418
|
}
|
|
16419
|
+
function parseUnifiedDiffHunks(diffOutput) {
|
|
16420
|
+
const hunks = [];
|
|
16421
|
+
const hunkPattern = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gm;
|
|
16422
|
+
let match;
|
|
16423
|
+
while ((match = hunkPattern.exec(diffOutput)) !== null) {
|
|
16424
|
+
hunks.push({
|
|
16425
|
+
oldStart: parseInt(match[1], 10),
|
|
16426
|
+
oldCount: match[2] !== void 0 ? parseInt(match[2], 10) : 1,
|
|
16427
|
+
newStart: parseInt(match[3], 10),
|
|
16428
|
+
newCount: match[4] !== void 0 ? parseInt(match[4], 10) : 1
|
|
16429
|
+
});
|
|
16430
|
+
}
|
|
16431
|
+
return hunks;
|
|
16432
|
+
}
|
|
16433
|
+
function computeLineShift(rootDir, filePath, fromCommit, originalStart, originalEnd) {
|
|
16434
|
+
let diff;
|
|
16435
|
+
try {
|
|
16436
|
+
diff = execSync2(
|
|
16437
|
+
`git diff ${fromCommit}..HEAD --unified=0 -- "${filePath}"`,
|
|
16438
|
+
{ cwd: rootDir, encoding: "utf8", timeout: 5e3 }
|
|
16439
|
+
);
|
|
16440
|
+
} catch {
|
|
16441
|
+
return null;
|
|
16442
|
+
}
|
|
16443
|
+
if (!diff.trim()) {
|
|
16444
|
+
return { originalStart, originalEnd, currentStart: originalStart, currentEnd: originalEnd };
|
|
16445
|
+
}
|
|
16446
|
+
const hunks = parseUnifiedDiffHunks(diff);
|
|
16447
|
+
let offset = 0;
|
|
16448
|
+
for (const hunk of hunks) {
|
|
16449
|
+
const hunkOldEnd = hunk.oldStart + hunk.oldCount;
|
|
16450
|
+
if (hunkOldEnd <= originalStart) {
|
|
16451
|
+
offset += hunk.newCount - hunk.oldCount;
|
|
16452
|
+
continue;
|
|
16453
|
+
}
|
|
16454
|
+
if (hunk.oldStart < originalEnd) {
|
|
16455
|
+
return null;
|
|
16456
|
+
}
|
|
16457
|
+
break;
|
|
16458
|
+
}
|
|
16459
|
+
if (offset === 0) return null;
|
|
16460
|
+
return {
|
|
16461
|
+
originalStart,
|
|
16462
|
+
originalEnd,
|
|
16463
|
+
currentStart: originalStart + offset,
|
|
16464
|
+
currentEnd: originalEnd + offset
|
|
16465
|
+
};
|
|
16466
|
+
}
|
|
16467
|
+
function healAnchorInPurposeFile(rootDir, purposeFilePath, anchorFilePath, oldStart, oldEnd, newStart, newEnd) {
|
|
16468
|
+
const absolutePurpose = path27.isAbsolute(purposeFilePath) ? purposeFilePath : path27.join(rootDir, purposeFilePath);
|
|
16469
|
+
if (!fs24.existsSync(absolutePurpose)) return false;
|
|
16470
|
+
try {
|
|
16471
|
+
const content = fs24.readFileSync(absolutePurpose, "utf8");
|
|
16472
|
+
const oldAnchor = oldStart === oldEnd ? `${anchorFilePath}:${oldStart}` : `${anchorFilePath}:${oldStart}-${oldEnd}`;
|
|
16473
|
+
const newAnchor = newStart === newEnd ? `${anchorFilePath}:${newStart}` : `${anchorFilePath}:${newStart}-${newEnd}`;
|
|
16474
|
+
if (!content.includes(oldAnchor)) return false;
|
|
16475
|
+
const updated = content.replace(oldAnchor, newAnchor);
|
|
16476
|
+
fs24.writeFileSync(absolutePurpose, updated, "utf8");
|
|
16477
|
+
return true;
|
|
16478
|
+
} catch {
|
|
16479
|
+
return false;
|
|
16480
|
+
}
|
|
16481
|
+
}
|
|
16482
|
+
function normalizeForHash(content) {
|
|
16483
|
+
return content.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "").map((line) => line.replace(/\s+/g, " ")).join("\n");
|
|
16484
|
+
}
|
|
16265
16485
|
function computeAnchorHash(anchor, rootDir) {
|
|
16266
|
-
if (!rootDir) return null;
|
|
16486
|
+
if (!rootDir) return { exact: null, normalized: null };
|
|
16267
16487
|
const absolutePath = path27.isAbsolute(anchor.path) ? anchor.path : path27.join(rootDir, anchor.path);
|
|
16268
|
-
if (!fs24.existsSync(absolutePath)) return null;
|
|
16488
|
+
if (!fs24.existsSync(absolutePath)) return { exact: null, normalized: null };
|
|
16269
16489
|
try {
|
|
16270
16490
|
const fileContent = fs24.readFileSync(absolutePath, "utf8");
|
|
16271
16491
|
const lines = fileContent.split("\n");
|
|
@@ -16273,9 +16493,11 @@ function computeAnchorHash(anchor, rootDir) {
|
|
|
16273
16493
|
const startIdx = Math.max(0, startLine - 1);
|
|
16274
16494
|
const endIdx = Math.min(lines.length, endLine);
|
|
16275
16495
|
const sliceContent = lines.slice(startIdx, endIdx).join("\n");
|
|
16276
|
-
|
|
16496
|
+
const exact = crypto5.createHash("sha256").update(sliceContent).digest("hex");
|
|
16497
|
+
const normalized = crypto5.createHash("sha256").update(normalizeForHash(sliceContent)).digest("hex");
|
|
16498
|
+
return { exact, normalized };
|
|
16277
16499
|
} catch {
|
|
16278
|
-
return null;
|
|
16500
|
+
return { exact: null, normalized: null };
|
|
16279
16501
|
}
|
|
16280
16502
|
}
|
|
16281
16503
|
function inferCategory(data, entry) {
|
|
@@ -16878,7 +17100,7 @@ async function rebuildStaticFiles(rootDir, ctx) {
|
|
|
16878
17100
|
let aspectGraphStats;
|
|
16879
17101
|
try {
|
|
16880
17102
|
const db = await openAspectGraph(rootDir);
|
|
16881
|
-
materializeAspects(db, aggregation.symbols);
|
|
17103
|
+
materializeAspects(db, aggregation.symbols, rootDir);
|
|
16882
17104
|
const loreLinks = await materializeLoreLinks(db, rootDir);
|
|
16883
17105
|
const inferredEdges = await inferLoreEdges(db, rootDir);
|
|
16884
17106
|
const aspectCount = db.exec("SELECT COUNT(*) FROM aspects")[0]?.values[0]?.[0] ?? 0;
|
|
@@ -17593,7 +17815,7 @@ function summarizeEntry(entry) {
|
|
|
17593
17815
|
// ../paradigm-mcp/src/tools/habits.ts
|
|
17594
17816
|
import * as fs27 from "fs";
|
|
17595
17817
|
import * as path30 from "path";
|
|
17596
|
-
import { execSync as
|
|
17818
|
+
import { execSync as execSync3 } from "child_process";
|
|
17597
17819
|
function getHabitsToolsList() {
|
|
17598
17820
|
return [
|
|
17599
17821
|
{
|
|
@@ -17797,7 +18019,7 @@ async function handleHabitsCheck(args, ctx) {
|
|
|
17797
18019
|
].some((k) => taskLower.includes(k));
|
|
17798
18020
|
let gitClean;
|
|
17799
18021
|
try {
|
|
17800
|
-
const status =
|
|
18022
|
+
const status = execSync3("git status --porcelain", {
|
|
17801
18023
|
cwd: ctx.rootDir,
|
|
17802
18024
|
encoding: "utf8",
|
|
17803
18025
|
timeout: 5e3
|
|
@@ -18493,18 +18715,22 @@ function getAspectGraphToolsList() {
|
|
|
18493
18715
|
},
|
|
18494
18716
|
{
|
|
18495
18717
|
name: "paradigm_aspect_drift",
|
|
18496
|
-
description: "
|
|
18718
|
+
description: "Smart drift detection for code anchors. Layer 1: normalized hash (ignores formatting). Layer 2: git-aware line mapping (detects shifts, auto-heals .purpose files). ~200 tokens.",
|
|
18497
18719
|
inputSchema: {
|
|
18498
18720
|
type: "object",
|
|
18499
18721
|
properties: {
|
|
18500
18722
|
aspectId: {
|
|
18501
18723
|
type: "string",
|
|
18502
18724
|
description: "Optional: check a specific aspect. If omitted, checks all aspects."
|
|
18725
|
+
},
|
|
18726
|
+
autoHeal: {
|
|
18727
|
+
type: "boolean",
|
|
18728
|
+
description: "Auto-update anchors for high-confidence shifts (default: true)"
|
|
18503
18729
|
}
|
|
18504
18730
|
}
|
|
18505
18731
|
},
|
|
18506
18732
|
annotations: {
|
|
18507
|
-
readOnlyHint:
|
|
18733
|
+
readOnlyHint: false,
|
|
18508
18734
|
destructiveHint: false
|
|
18509
18735
|
}
|
|
18510
18736
|
},
|
|
@@ -18894,34 +19120,51 @@ async function handleAspectSuggestScan(args, ctx) {
|
|
|
18894
19120
|
}
|
|
18895
19121
|
}
|
|
18896
19122
|
async function handleAspectDrift(args, ctx) {
|
|
18897
|
-
const { aspectId } = args;
|
|
19123
|
+
const { aspectId, autoHeal: autoHealArg } = args;
|
|
19124
|
+
const autoHeal = autoHealArg !== false;
|
|
18898
19125
|
const normalizedId = aspectId ? aspectId.startsWith("~") ? aspectId.slice(1) : aspectId : void 0;
|
|
18899
19126
|
let db = null;
|
|
18900
19127
|
try {
|
|
18901
19128
|
db = await openAspectGraph(ctx.rootDir);
|
|
18902
|
-
const results = checkDrift(db, ctx.rootDir, normalizedId);
|
|
18903
|
-
const
|
|
18904
|
-
const
|
|
18905
|
-
const
|
|
19129
|
+
const results = checkDrift(db, ctx.rootDir, normalizedId, autoHeal);
|
|
19130
|
+
const cleanCount = results.filter((r) => r.status === "clean").length;
|
|
19131
|
+
const cosmeticCount = results.filter((r) => r.status === "cosmetic").length;
|
|
19132
|
+
const shiftedCount = results.filter((r) => r.status === "shifted").length;
|
|
19133
|
+
const modifiedCount = results.filter((r) => r.status === "modified").length;
|
|
19134
|
+
const missingCount = results.filter((r) => r.status === "missing").length;
|
|
19135
|
+
const hasIssues = modifiedCount > 0 || missingCount > 0;
|
|
19136
|
+
const hasHeals = cosmeticCount > 0 || shiftedCount > 0;
|
|
19137
|
+
const overallStatus = hasIssues ? "drift-detected" : hasHeals ? "clean-with-heals" : "clean";
|
|
18906
19138
|
const response = {
|
|
18907
19139
|
...normalizedId ? { aspectId: normalizedId } : { scope: "all" },
|
|
18908
19140
|
totalAnchors: results.length,
|
|
18909
|
-
|
|
18910
|
-
|
|
19141
|
+
clean: cleanCount,
|
|
19142
|
+
cosmetic: cosmeticCount,
|
|
19143
|
+
shifted: shiftedCount,
|
|
19144
|
+
modified: modifiedCount,
|
|
18911
19145
|
missing: missingCount,
|
|
18912
|
-
status:
|
|
19146
|
+
status: overallStatus,
|
|
18913
19147
|
results: results.map((r) => ({
|
|
18914
19148
|
aspectId: r.aspectId,
|
|
18915
19149
|
path: r.path,
|
|
18916
19150
|
startLine: r.startLine,
|
|
18917
19151
|
endLine: r.endLine,
|
|
18918
|
-
|
|
19152
|
+
status: r.status,
|
|
19153
|
+
resolvedBy: r.resolvedBy,
|
|
18919
19154
|
exists: r.exists,
|
|
18920
|
-
// Include
|
|
18921
|
-
...r.
|
|
19155
|
+
// Include shift details for shifted anchors
|
|
19156
|
+
...r.status === "shifted" ? { suggestedStart: r.suggestedStart, suggestedEnd: r.suggestedEnd, autoHealed: r.autoHealed } : {},
|
|
19157
|
+
// Include current content only for modified anchors (truncated)
|
|
19158
|
+
...r.status === "modified" && r.currentContent ? { currentContent: r.currentContent.slice(0, 500) } : {}
|
|
18922
19159
|
})),
|
|
18923
|
-
...
|
|
18924
|
-
|
|
19160
|
+
...cosmeticCount > 0 || shiftedCount > 0 ? {
|
|
19161
|
+
healed: [
|
|
19162
|
+
cosmeticCount > 0 ? `${cosmeticCount} cosmetic (whitespace/formatting \u2014 hashes updated)` : "",
|
|
19163
|
+
shiftedCount > 0 ? `${shiftedCount} shifted (line numbers updated via git diff${autoHeal ? " \u2014 .purpose files patched" : ""})` : ""
|
|
19164
|
+
].filter(Boolean).join(", ")
|
|
19165
|
+
} : {},
|
|
19166
|
+
...hasIssues ? {
|
|
19167
|
+
suggestion: "Review drifted anchors to ensure aspects still apply. Run `paradigm scan` to re-materialize after fixing."
|
|
18925
19168
|
} : {}
|
|
18926
19169
|
};
|
|
18927
19170
|
const text = JSON.stringify(response, null, 2);
|
|
@@ -19371,7 +19614,7 @@ async function handleAssessmentTool(name, args, ctx) {
|
|
|
19371
19614
|
|
|
19372
19615
|
// ../paradigm-mcp/src/tools/fallback-grep.ts
|
|
19373
19616
|
import * as path32 from "path";
|
|
19374
|
-
import { execSync as
|
|
19617
|
+
import { execSync as execSync4 } from "child_process";
|
|
19375
19618
|
function grepForReferences(rootDir, symbol, options = {}) {
|
|
19376
19619
|
const { maxResults = 20 } = options;
|
|
19377
19620
|
const results = [];
|
|
@@ -19385,7 +19628,7 @@ function grepForReferences(rootDir, symbol, options = {}) {
|
|
|
19385
19628
|
let output = "";
|
|
19386
19629
|
for (const cmd of grepCommands) {
|
|
19387
19630
|
try {
|
|
19388
|
-
output =
|
|
19631
|
+
output = execSync4(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
|
|
19389
19632
|
if (output.trim()) break;
|
|
19390
19633
|
} catch {
|
|
19391
19634
|
continue;
|