@plumpslabs/kuma 2.4.6 → 2.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +504 -87
- package/package.json +1 -1
- package/plugin.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1329,25 +1329,31 @@ var init_sessionMemory = __esm({
|
|
|
1329
1329
|
const parsed = JSON.parse(raw);
|
|
1330
1330
|
const storedBranch = parsed.currentBranch;
|
|
1331
1331
|
const branchSwitched = Boolean(storedBranch && currentGitBranch && storedBranch !== currentGitBranch);
|
|
1332
|
+
const lastActive = parsed.lastActiveAt || parsed.startTime || 0;
|
|
1333
|
+
const isIdle = lastActive > 0 && Date.now() - lastActive > 2 * 3600 * 1e3;
|
|
1334
|
+
const startTime = isIdle ? Date.now() : parsed.startTime || config.startTime;
|
|
1335
|
+
const metrics = isIdle ? { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 } : parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 };
|
|
1336
|
+
const toolCalls = isIdle ? [] : parsed.toolCalls || [];
|
|
1332
1337
|
this.state = {
|
|
1333
1338
|
projectRoot: parsed.projectRoot || config.projectRoot,
|
|
1334
|
-
startTime
|
|
1339
|
+
startTime,
|
|
1340
|
+
lastActiveAt: Date.now(),
|
|
1335
1341
|
currentGoal: parsed.currentGoal || "",
|
|
1336
1342
|
currentBranch: currentGitBranch || storedBranch || void 0,
|
|
1337
1343
|
previousBranch: branchSwitched ? storedBranch : parsed.previousBranch,
|
|
1338
1344
|
branchSwitched,
|
|
1339
|
-
completedSteps: parsed.completedSteps || [],
|
|
1345
|
+
completedSteps: isIdle ? [] : parsed.completedSteps || [],
|
|
1340
1346
|
modifiedFiles: new Map(parsed.modifiedFiles || []),
|
|
1341
1347
|
failedFiles: new Map(parsed.failedFiles || []),
|
|
1342
1348
|
searchResults: new Map(parsed.searchResults || []),
|
|
1343
1349
|
dependencyGraph: new Map(parsed.dependencyGraph || []),
|
|
1344
|
-
toolCalls
|
|
1350
|
+
toolCalls,
|
|
1345
1351
|
conventions: parsed.conventions,
|
|
1346
1352
|
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
|
|
1347
|
-
metrics
|
|
1353
|
+
metrics
|
|
1348
1354
|
};
|
|
1349
1355
|
this.initialized = true;
|
|
1350
|
-
console.error(`[SessionMemory] Loaded persistent session memory.json`);
|
|
1356
|
+
console.error(`[SessionMemory] Loaded persistent session memory.json${isIdle ? " (idle reset applied)" : ""}`);
|
|
1351
1357
|
return;
|
|
1352
1358
|
} catch (err) {
|
|
1353
1359
|
console.error(`[SessionMemory] Failed to load persistent session: ${err}. Re-initializing.`);
|
|
@@ -1508,6 +1514,7 @@ No unresolved issues.`;
|
|
|
1508
1514
|
const serialized = {
|
|
1509
1515
|
projectRoot: this.state.projectRoot,
|
|
1510
1516
|
startTime: this.state.startTime,
|
|
1517
|
+
lastActiveAt: this.state.lastActiveAt || Date.now(),
|
|
1511
1518
|
currentGoal: this.state.currentGoal,
|
|
1512
1519
|
currentBranch: this.state.currentBranch,
|
|
1513
1520
|
previousBranch: this.state.previousBranch,
|
|
@@ -1908,6 +1915,32 @@ No unresolved issues.`;
|
|
|
1908
1915
|
}
|
|
1909
1916
|
getModifiedFiles() {
|
|
1910
1917
|
this.ensureInit();
|
|
1918
|
+
try {
|
|
1919
|
+
const root = this.state.projectRoot;
|
|
1920
|
+
const gitOut = execSync("git status --porcelain", {
|
|
1921
|
+
cwd: root,
|
|
1922
|
+
encoding: "utf-8",
|
|
1923
|
+
timeout: 3e3,
|
|
1924
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1925
|
+
}).trim();
|
|
1926
|
+
if (gitOut) {
|
|
1927
|
+
for (const line of gitOut.split("\n")) {
|
|
1928
|
+
const trimmed = line.trim();
|
|
1929
|
+
if (!trimmed) continue;
|
|
1930
|
+
const rawPath = trimmed.slice(2).trim();
|
|
1931
|
+
const cleanPath = rawPath.replace(/^["']|["']$/g, "").split(" -> ").pop();
|
|
1932
|
+
if (cleanPath.startsWith(".kuma/") || cleanPath.startsWith(".git/")) continue;
|
|
1933
|
+
if (!this.state.modifiedFiles.has(cleanPath)) {
|
|
1934
|
+
this.state.modifiedFiles.set(cleanPath, {
|
|
1935
|
+
filePath: cleanPath,
|
|
1936
|
+
modifiedAt: Date.now(),
|
|
1937
|
+
status: trimmed.startsWith("??") || trimmed.startsWith("A") ? "created" : "modified"
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1911
1944
|
return Array.from(this.state.modifiedFiles.values());
|
|
1912
1945
|
}
|
|
1913
1946
|
getFailedFiles() {
|
|
@@ -2130,6 +2163,14 @@ No unresolved issues.`;
|
|
|
2130
2163
|
projectRoot: process.cwd(),
|
|
2131
2164
|
startTime: Date.now()
|
|
2132
2165
|
});
|
|
2166
|
+
} else if (this.state) {
|
|
2167
|
+
const lastActive = this.state.lastActiveAt || this.state.startTime || 0;
|
|
2168
|
+
if (lastActive > 0 && Date.now() - lastActive > 2 * 3600 * 1e3) {
|
|
2169
|
+
this.state.startTime = Date.now();
|
|
2170
|
+
this.state.metrics = { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 };
|
|
2171
|
+
this.state.toolCalls = [];
|
|
2172
|
+
}
|
|
2173
|
+
this.state.lastActiveAt = Date.now();
|
|
2133
2174
|
}
|
|
2134
2175
|
}
|
|
2135
2176
|
};
|
|
@@ -3481,7 +3522,8 @@ async function recordDomainFlow(params) {
|
|
|
3481
3522
|
type: "feature_domain",
|
|
3482
3523
|
name: params.domain,
|
|
3483
3524
|
metadata: {
|
|
3484
|
-
hops: params.hops
|
|
3525
|
+
hops: params.hops,
|
|
3526
|
+
hopsCount: params.hops.length,
|
|
3485
3527
|
gotchas: params.gotchas?.length || 0,
|
|
3486
3528
|
decisions: params.decisions?.length || 0,
|
|
3487
3529
|
filePaths: params.filePaths || [],
|
|
@@ -4554,7 +4596,7 @@ function scoreMemoryRelevance(context, limit = 5) {
|
|
|
4554
4596
|
const results = [];
|
|
4555
4597
|
const kumaDir = path9.join(getProjectRoot(), MEMORY_DIR);
|
|
4556
4598
|
if (!fs8.existsSync(kumaDir)) return [];
|
|
4557
|
-
const terms = context.toLowerCase().split(
|
|
4599
|
+
const terms = Array.from(new Set(context.toLowerCase().split(/[\s/._-]+/).filter((w) => w.length > 2)));
|
|
4558
4600
|
if (terms.length === 0) return [];
|
|
4559
4601
|
try {
|
|
4560
4602
|
const files = fs8.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
@@ -4781,21 +4823,21 @@ function getActiveGotchas() {
|
|
|
4781
4823
|
let currentSeverity = "";
|
|
4782
4824
|
let currentStatus = "active";
|
|
4783
4825
|
for (const line of lines) {
|
|
4784
|
-
const fileMatch = line.match(/^###\s
|
|
4826
|
+
const fileMatch = line.match(/^###\s+\[?(.+?)\]?\s*(?:[—–-]+\s*(.+))?$/);
|
|
4785
4827
|
if (fileMatch) {
|
|
4786
4828
|
if (currentFilePath && currentStatus !== "resolved" && currentStatus !== "deprecated") {
|
|
4787
4829
|
results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
|
|
4788
4830
|
}
|
|
4789
4831
|
currentFilePath = fileMatch[1].trim();
|
|
4790
|
-
currentDesc = fileMatch[2].trim();
|
|
4832
|
+
currentDesc = (fileMatch[2] || fileMatch[1]).trim();
|
|
4791
4833
|
currentSeverity = "medium";
|
|
4792
4834
|
currentStatus = "active";
|
|
4793
4835
|
currentSection = line + "\n";
|
|
4794
4836
|
continue;
|
|
4795
4837
|
}
|
|
4796
|
-
const sevMatch = line.match(
|
|
4838
|
+
const sevMatch = line.match(/[-*]\s*(?:⚡\s*)?\*\*Severity\*\*:\s*(\w+)/i);
|
|
4797
4839
|
if (sevMatch) currentSeverity = sevMatch[1].toLowerCase();
|
|
4798
|
-
const statusMatch = line.match(
|
|
4840
|
+
const statusMatch = line.match(/[-*]\s*(?:🏷️\s*)?\*\*Status\*\*:\s*(\w+)/i);
|
|
4799
4841
|
if (statusMatch) currentStatus = statusMatch[1].toLowerCase();
|
|
4800
4842
|
currentSection += line + "\n";
|
|
4801
4843
|
}
|
|
@@ -4809,8 +4851,10 @@ function getActiveGotchas() {
|
|
|
4809
4851
|
function checkFileGotchas(filePath) {
|
|
4810
4852
|
const gotchas = getActiveGotchas();
|
|
4811
4853
|
const warnings = [];
|
|
4854
|
+
const base = path10.basename(filePath);
|
|
4812
4855
|
for (const g of gotchas) {
|
|
4813
|
-
|
|
4856
|
+
const gBase = path10.basename(g.filePath);
|
|
4857
|
+
if (filePath.includes(g.filePath) || g.filePath.includes(filePath) || base && gBase && base === gBase) {
|
|
4814
4858
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
4815
4859
|
warnings.push(`${icon} **Gotcha**: ${g.description} (${g.severity})`);
|
|
4816
4860
|
}
|
|
@@ -4919,8 +4963,9 @@ function isGotchaFresh(storedHash, currentHash) {
|
|
|
4919
4963
|
if (!currentHash) return true;
|
|
4920
4964
|
return storedHash === currentHash;
|
|
4921
4965
|
}
|
|
4922
|
-
async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
4966
|
+
async function getFreshGotchasForFile(filePath, limit = 5, includeStale = false) {
|
|
4923
4967
|
const root = getProjectRoot();
|
|
4968
|
+
const base = path11.basename(filePath);
|
|
4924
4969
|
try {
|
|
4925
4970
|
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
4926
4971
|
if (fs10.existsSync(dbFile)) {
|
|
@@ -4930,44 +4975,58 @@ async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
|
4930
4975
|
SELECT id, file_path, description, severity, workaround, content_hash
|
|
4931
4976
|
FROM known_gotchas
|
|
4932
4977
|
WHERE status IN ('active', 'verified')
|
|
4933
|
-
AND (
|
|
4978
|
+
AND (
|
|
4979
|
+
file_path = ?
|
|
4980
|
+
OR file_path LIKE ?
|
|
4981
|
+
OR ? LIKE ('%' || file_path || '%')
|
|
4982
|
+
OR file_path LIKE ?
|
|
4983
|
+
)
|
|
4934
4984
|
ORDER BY
|
|
4935
|
-
CASE WHEN file_path = ? THEN 0
|
|
4985
|
+
CASE WHEN file_path = ? THEN 0 WHEN file_path LIKE ? THEN 1 ELSE 2 END,
|
|
4936
4986
|
CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
|
|
4937
4987
|
updated_at DESC
|
|
4938
4988
|
LIMIT ?
|
|
4939
4989
|
`);
|
|
4940
|
-
stmt.bind([filePath, filePath, filePath, limit]);
|
|
4990
|
+
stmt.bind([filePath, `%${filePath}%`, filePath, `%${base}%`, filePath, `%${filePath}%`, limit * 2]);
|
|
4941
4991
|
const rows = [];
|
|
4942
4992
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
4943
4993
|
stmt.free();
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4994
|
+
if (rows.length > 0) {
|
|
4995
|
+
const currentHash = hashFileContent(filePath);
|
|
4996
|
+
const mapped = rows.map((r) => ({
|
|
4997
|
+
id: r.id,
|
|
4998
|
+
filePath: r.file_path,
|
|
4999
|
+
description: r.description,
|
|
5000
|
+
severity: r.severity,
|
|
5001
|
+
workaround: r.workaround ?? null,
|
|
5002
|
+
stale: !isGotchaFresh(r.content_hash ?? null, currentHash)
|
|
5003
|
+
}));
|
|
5004
|
+
const filtered = includeStale ? mapped : mapped.filter((g) => !g.stale);
|
|
5005
|
+
return filtered.slice(0, limit);
|
|
5006
|
+
}
|
|
4953
5007
|
}
|
|
4954
5008
|
} catch {
|
|
4955
5009
|
}
|
|
4956
5010
|
try {
|
|
4957
5011
|
if (!fs10.existsSync(path11.join(root, ".kuma"))) return [];
|
|
4958
|
-
const {
|
|
4959
|
-
const
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
5012
|
+
const { getActiveGotchas: getActiveGotchas2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
5013
|
+
const allGotchas = getActiveGotchas2();
|
|
5014
|
+
const matched = allGotchas.filter(
|
|
5015
|
+
(g) => g.filePath.includes(filePath) || filePath.includes(g.filePath) || base && path11.basename(g.filePath) === base
|
|
5016
|
+
);
|
|
5017
|
+
if (matched.length > 0) {
|
|
5018
|
+
return matched.slice(0, limit).map((g) => ({
|
|
5019
|
+
filePath: g.filePath,
|
|
5020
|
+
description: g.description,
|
|
5021
|
+
severity: g.severity,
|
|
5022
|
+
workaround: null,
|
|
5023
|
+
stale: false
|
|
5024
|
+
}));
|
|
5025
|
+
}
|
|
4968
5026
|
} catch {
|
|
4969
5027
|
return [];
|
|
4970
5028
|
}
|
|
5029
|
+
return [];
|
|
4971
5030
|
}
|
|
4972
5031
|
async function getFileTrace(filePath, limit = 8) {
|
|
4973
5032
|
try {
|
|
@@ -5015,14 +5074,81 @@ function formatFileTrace(entries, filePath) {
|
|
|
5015
5074
|
return lines.join("\n");
|
|
5016
5075
|
}
|
|
5017
5076
|
async function getDecisionsForFile(filePath, limit = 3) {
|
|
5077
|
+
const decisions = [];
|
|
5078
|
+
const root = getProjectRoot();
|
|
5079
|
+
const base = path11.basename(filePath);
|
|
5080
|
+
const baseWithoutExt = base.replace(/\.[^.]+$/, "");
|
|
5081
|
+
try {
|
|
5082
|
+
const decisionsMdPath = path11.join(root, ".kuma", "memories", "decisions.md");
|
|
5083
|
+
if (fs10.existsSync(decisionsMdPath)) {
|
|
5084
|
+
const content = fs10.readFileSync(decisionsMdPath, "utf-8");
|
|
5085
|
+
const sections = content.split(/^##\s+/m);
|
|
5086
|
+
for (const section of sections) {
|
|
5087
|
+
if (!section.trim() || section.startsWith("# ")) continue;
|
|
5088
|
+
const sectionLines = section.split("\n");
|
|
5089
|
+
const title = sectionLines[0].trim();
|
|
5090
|
+
const sectionText = section.toLowerCase();
|
|
5091
|
+
const matches = sectionText.includes(filePath.toLowerCase()) || sectionText.includes(base.toLowerCase()) || baseWithoutExt.length > 3 && sectionText.includes(baseWithoutExt.toLowerCase());
|
|
5092
|
+
if (matches) {
|
|
5093
|
+
let rationale = "";
|
|
5094
|
+
for (const line of sectionLines) {
|
|
5095
|
+
const rMatch = line.match(/^[-*]\s*\*\*Rationale:\*\*\s*(.+)$/i);
|
|
5096
|
+
if (rMatch) {
|
|
5097
|
+
rationale = rMatch[1].trim();
|
|
5098
|
+
break;
|
|
5099
|
+
}
|
|
5100
|
+
const oMatch = line.match(/^[-*]\s*\*\*Outcome:\*\*\s*(.+)$/i);
|
|
5101
|
+
if (!rationale && oMatch) {
|
|
5102
|
+
rationale = oMatch[1].trim();
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
decisions.push(`\u{1F4CC} ${title}${rationale ? ` \u2014 *${rationale}*` : ""}`);
|
|
5106
|
+
if (decisions.length >= limit) return decisions;
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
} catch {
|
|
5111
|
+
}
|
|
5112
|
+
try {
|
|
5113
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
5114
|
+
const db = await getDb2();
|
|
5115
|
+
const stmt = db.prepare(`
|
|
5116
|
+
SELECT name, metadata FROM nodes
|
|
5117
|
+
WHERE type = 'decision'
|
|
5118
|
+
AND (name LIKE ? OR metadata LIKE ? OR name LIKE ? OR metadata LIKE ?)
|
|
5119
|
+
LIMIT ?
|
|
5120
|
+
`);
|
|
5121
|
+
stmt.bind([`%${base}%`, `%${base}%`, `%${baseWithoutExt}%`, `%${baseWithoutExt}%`, limit]);
|
|
5122
|
+
while (stmt.step()) {
|
|
5123
|
+
const row = stmt.getAsObject();
|
|
5124
|
+
try {
|
|
5125
|
+
const meta = JSON.parse(row.metadata || "{}");
|
|
5126
|
+
const title = row.name.replace(/^ADR:\s*/, "");
|
|
5127
|
+
const reason = meta.rationale || meta.context || meta.outcome || "";
|
|
5128
|
+
const formatted = `\u{1F4CC} ${title}${reason ? ` \u2014 *${reason.substring(0, 100)}*` : ""}`;
|
|
5129
|
+
if (!decisions.includes(formatted)) {
|
|
5130
|
+
decisions.push(formatted);
|
|
5131
|
+
}
|
|
5132
|
+
} catch {
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
stmt.free();
|
|
5136
|
+
if (decisions.length >= limit) return decisions.slice(0, limit);
|
|
5137
|
+
} catch {
|
|
5138
|
+
}
|
|
5018
5139
|
try {
|
|
5019
5140
|
const { scoreMemoryRelevance: scoreMemoryRelevance2 } = await Promise.resolve().then(() => (init_kumaMemory(), kumaMemory_exports));
|
|
5020
|
-
const
|
|
5021
|
-
const scored = scoreMemoryRelevance2(
|
|
5022
|
-
|
|
5141
|
+
const searchTerms = `${filePath} ${base} ${baseWithoutExt}`.replace(/[\/._-]/g, " ");
|
|
5142
|
+
const scored = scoreMemoryRelevance2(searchTerms, limit);
|
|
5143
|
+
for (const m of scored) {
|
|
5144
|
+
const formatted = `\u{1F4CC} ${m.topic} \u2014 ${m.content ? m.content.substring(0, 80) : `${m.score}% match`}`;
|
|
5145
|
+
if (!decisions.includes(formatted)) {
|
|
5146
|
+
decisions.push(formatted);
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5023
5149
|
} catch {
|
|
5024
|
-
return [];
|
|
5025
5150
|
}
|
|
5151
|
+
return decisions.slice(0, limit);
|
|
5026
5152
|
}
|
|
5027
5153
|
async function getRelevantContext(filePath, goal) {
|
|
5028
5154
|
const parts = [];
|
|
@@ -5253,6 +5379,7 @@ __export(kumaGotchas_exports, {
|
|
|
5253
5379
|
resolveGotcha: () => resolveGotcha,
|
|
5254
5380
|
resolveGotchasForScope: () => resolveGotchasForScope,
|
|
5255
5381
|
setGotchaStatus: () => setGotchaStatus,
|
|
5382
|
+
syncGotchaStatusToMarkdown: () => syncGotchaStatusToMarkdown,
|
|
5256
5383
|
syncGotchasGraph: () => syncGotchasGraph,
|
|
5257
5384
|
syncGotchasToDb: () => syncGotchasToDb
|
|
5258
5385
|
});
|
|
@@ -5535,13 +5662,14 @@ async function resolveGotchasForScope(scope) {
|
|
|
5535
5662
|
const db = await getDb();
|
|
5536
5663
|
const stmt = db.prepare(
|
|
5537
5664
|
`SELECT id, file_path, content_hash FROM known_gotchas
|
|
5538
|
-
WHERE status = 'active' AND file_path LIKE ? AND content_hash IS NOT NULL`
|
|
5665
|
+
WHERE status = 'active' AND (file_path LIKE ? OR file_path = ?) AND content_hash IS NOT NULL`
|
|
5539
5666
|
);
|
|
5540
|
-
stmt.bind([`%${scope}
|
|
5667
|
+
stmt.bind([`%${scope}%`, scope]);
|
|
5541
5668
|
const rows = [];
|
|
5542
5669
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
5543
5670
|
stmt.free();
|
|
5544
5671
|
let resolved = 0;
|
|
5672
|
+
const resolvedPaths = [];
|
|
5545
5673
|
for (const r of rows) {
|
|
5546
5674
|
const current = hashFileContent(r.file_path);
|
|
5547
5675
|
if (current && current !== r.content_hash) {
|
|
@@ -5550,9 +5678,13 @@ async function resolveGotchasForScope(scope) {
|
|
|
5550
5678
|
[r.id]
|
|
5551
5679
|
);
|
|
5552
5680
|
resolved++;
|
|
5681
|
+
resolvedPaths.push(r.file_path);
|
|
5553
5682
|
}
|
|
5554
5683
|
}
|
|
5555
|
-
if (resolved > 0)
|
|
5684
|
+
if (resolved > 0) {
|
|
5685
|
+
saveDb();
|
|
5686
|
+
syncGotchaStatusToMarkdown([scope, ...resolvedPaths], "resolved", "auto-verified");
|
|
5687
|
+
}
|
|
5556
5688
|
return { resolved };
|
|
5557
5689
|
} catch {
|
|
5558
5690
|
return { resolved: 0 };
|
|
@@ -5658,14 +5790,73 @@ async function getGotchasByStatus(status, limit = 20) {
|
|
|
5658
5790
|
return [];
|
|
5659
5791
|
}
|
|
5660
5792
|
}
|
|
5793
|
+
function syncGotchaStatusToMarkdown(targetPaths, newStatus, reason) {
|
|
5794
|
+
try {
|
|
5795
|
+
const mdPath = path12.join(getProjectRoot(), ".kuma", "KNOWN_GOTCHAS.md");
|
|
5796
|
+
if (!fs11.existsSync(mdPath)) return false;
|
|
5797
|
+
const content = fs11.readFileSync(mdPath, "utf-8");
|
|
5798
|
+
const lines = content.split("\n");
|
|
5799
|
+
const normalizedTargets = targetPaths.map((p) => p.trim().toLowerCase()).filter(Boolean);
|
|
5800
|
+
if (normalizedTargets.length === 0) return false;
|
|
5801
|
+
let modified = false;
|
|
5802
|
+
const outputLines = [];
|
|
5803
|
+
let inMatchingSection = false;
|
|
5804
|
+
let sectionHasStatus = false;
|
|
5805
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5806
|
+
const line = lines[i];
|
|
5807
|
+
const headingMatch = line.match(/^###\s+\[?(.+?)\]?\s*(?:[—–-]+\s*(.+))?$/);
|
|
5808
|
+
if (headingMatch) {
|
|
5809
|
+
if (inMatchingSection && !sectionHasStatus) {
|
|
5810
|
+
outputLines.push(`- \u{1F3F7}\uFE0F **Status**: ${newStatus}${reason ? ` (${reason})` : ""}`);
|
|
5811
|
+
modified = true;
|
|
5812
|
+
}
|
|
5813
|
+
const headingFile = headingMatch[1].trim().toLowerCase();
|
|
5814
|
+
const headingBase = path12.basename(headingFile);
|
|
5815
|
+
inMatchingSection = normalizedTargets.some(
|
|
5816
|
+
(t) => headingFile.includes(t) || t.includes(headingFile) || headingBase === t || headingBase === path12.basename(t)
|
|
5817
|
+
);
|
|
5818
|
+
sectionHasStatus = false;
|
|
5819
|
+
outputLines.push(line);
|
|
5820
|
+
continue;
|
|
5821
|
+
}
|
|
5822
|
+
if (inMatchingSection) {
|
|
5823
|
+
const statusMatch = line.match(/^(\s*[-*]\s*(?:🏷️\s*)?\*\*Status\*\*:\s*)(.*)$/i);
|
|
5824
|
+
if (statusMatch) {
|
|
5825
|
+
outputLines.push(`- \u{1F3F7}\uFE0F **Status**: ${newStatus}${reason ? ` (${reason})` : ""}`);
|
|
5826
|
+
sectionHasStatus = true;
|
|
5827
|
+
modified = true;
|
|
5828
|
+
continue;
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
outputLines.push(line);
|
|
5832
|
+
}
|
|
5833
|
+
if (inMatchingSection && !sectionHasStatus) {
|
|
5834
|
+
outputLines.push(`- \u{1F3F7}\uFE0F **Status**: ${newStatus}${reason ? ` (${reason})` : ""}`);
|
|
5835
|
+
modified = true;
|
|
5836
|
+
}
|
|
5837
|
+
if (modified) {
|
|
5838
|
+
fs11.writeFileSync(mdPath, outputLines.join("\n"), "utf-8");
|
|
5839
|
+
}
|
|
5840
|
+
return modified;
|
|
5841
|
+
} catch {
|
|
5842
|
+
return false;
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5661
5845
|
async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent") {
|
|
5662
5846
|
try {
|
|
5663
5847
|
await ensureGotchasSchema();
|
|
5664
5848
|
const db = await getDb();
|
|
5665
5849
|
let resolved = 0;
|
|
5666
5850
|
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5851
|
+
const targetPaths = [];
|
|
5667
5852
|
if (isId) {
|
|
5668
5853
|
const id = Number(idOrScope);
|
|
5854
|
+
const findStmt = db.prepare(`SELECT file_path FROM known_gotchas WHERE id = ?`);
|
|
5855
|
+
findStmt.bind([id]);
|
|
5856
|
+
if (findStmt.step()) {
|
|
5857
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5858
|
+
}
|
|
5859
|
+
findStmt.free();
|
|
5669
5860
|
db.run(
|
|
5670
5861
|
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'resolved'`,
|
|
5671
5862
|
[verifiedBy, id]
|
|
@@ -5673,12 +5864,22 @@ async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent")
|
|
|
5673
5864
|
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5674
5865
|
} else {
|
|
5675
5866
|
const scope = String(idOrScope).trim();
|
|
5867
|
+
targetPaths.push(scope);
|
|
5868
|
+
const findStmt = db.prepare(`SELECT DISTINCT file_path FROM known_gotchas WHERE (file_path = ? OR file_path LIKE ?)`);
|
|
5869
|
+
findStmt.bind([scope, `%${scope}%`]);
|
|
5870
|
+
while (findStmt.step()) {
|
|
5871
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5872
|
+
}
|
|
5873
|
+
findStmt.free();
|
|
5676
5874
|
db.run(
|
|
5677
5875
|
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE (file_path = ? OR file_path LIKE ?) AND status != 'resolved'`,
|
|
5678
5876
|
[verifiedBy, scope, `%${scope}%`]
|
|
5679
5877
|
);
|
|
5680
5878
|
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5681
5879
|
}
|
|
5880
|
+
if (targetPaths.length > 0) {
|
|
5881
|
+
syncGotchaStatusToMarkdown(targetPaths, "resolved", resolutionReason);
|
|
5882
|
+
}
|
|
5682
5883
|
if (resolved > 0) {
|
|
5683
5884
|
saveDb();
|
|
5684
5885
|
rebuildFtsIndex();
|
|
@@ -5687,6 +5888,13 @@ async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent")
|
|
|
5687
5888
|
message: `\u2705 Resolved ${resolved} gotcha(s) for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5688
5889
|
};
|
|
5689
5890
|
}
|
|
5891
|
+
const mdUpdated = syncGotchaStatusToMarkdown(targetPaths.length > 0 ? targetPaths : [String(idOrScope)], "resolved", resolutionReason);
|
|
5892
|
+
if (mdUpdated) {
|
|
5893
|
+
return {
|
|
5894
|
+
resolved: 1,
|
|
5895
|
+
message: `\u2705 Resolved gotcha in KNOWN_GOTCHAS.md for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5896
|
+
};
|
|
5897
|
+
}
|
|
5690
5898
|
return { resolved: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5691
5899
|
} catch (err) {
|
|
5692
5900
|
return { resolved: 0, message: `\u274C Failed to resolve gotcha: ${err}` };
|
|
@@ -5698,8 +5906,15 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5698
5906
|
const db = await getDb();
|
|
5699
5907
|
let deprecated = 0;
|
|
5700
5908
|
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5909
|
+
const targetPaths = [];
|
|
5701
5910
|
if (isId) {
|
|
5702
5911
|
const id = Number(idOrScope);
|
|
5912
|
+
const findStmt = db.prepare(`SELECT file_path FROM known_gotchas WHERE id = ?`);
|
|
5913
|
+
findStmt.bind([id]);
|
|
5914
|
+
if (findStmt.step()) {
|
|
5915
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5916
|
+
}
|
|
5917
|
+
findStmt.free();
|
|
5703
5918
|
db.run(
|
|
5704
5919
|
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'deprecation', last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'deprecated'`,
|
|
5705
5920
|
[id]
|
|
@@ -5707,12 +5922,22 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5707
5922
|
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5708
5923
|
} else {
|
|
5709
5924
|
const scope = String(idOrScope).trim();
|
|
5925
|
+
targetPaths.push(scope);
|
|
5926
|
+
const findStmt = db.prepare(`SELECT DISTINCT file_path FROM known_gotchas WHERE (file_path = ? OR file_path LIKE ?)`);
|
|
5927
|
+
findStmt.bind([scope, `%${scope}%`]);
|
|
5928
|
+
while (findStmt.step()) {
|
|
5929
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5930
|
+
}
|
|
5931
|
+
findStmt.free();
|
|
5710
5932
|
db.run(
|
|
5711
5933
|
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'deprecation', last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE (file_path = ? OR file_path LIKE ?) AND status != 'deprecated'`,
|
|
5712
5934
|
[scope, `%${scope}%`]
|
|
5713
5935
|
);
|
|
5714
5936
|
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5715
5937
|
}
|
|
5938
|
+
if (targetPaths.length > 0) {
|
|
5939
|
+
syncGotchaStatusToMarkdown(targetPaths, "deprecated", reason);
|
|
5940
|
+
}
|
|
5716
5941
|
if (deprecated > 0) {
|
|
5717
5942
|
saveDb();
|
|
5718
5943
|
rebuildFtsIndex();
|
|
@@ -5721,6 +5946,13 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5721
5946
|
message: `\u{1F9F9} Deprecated ${deprecated} gotcha(s) for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5722
5947
|
};
|
|
5723
5948
|
}
|
|
5949
|
+
const mdUpdated = syncGotchaStatusToMarkdown(targetPaths.length > 0 ? targetPaths : [String(idOrScope)], "deprecated", reason);
|
|
5950
|
+
if (mdUpdated) {
|
|
5951
|
+
return {
|
|
5952
|
+
deprecated: 1,
|
|
5953
|
+
message: `\u{1F9F9} Deprecated gotcha in KNOWN_GOTCHAS.md for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5954
|
+
};
|
|
5955
|
+
}
|
|
5724
5956
|
return { deprecated: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5725
5957
|
} catch (err) {
|
|
5726
5958
|
return { deprecated: 0, message: `\u274C Failed to deprecate gotcha: ${err}` };
|
|
@@ -6479,7 +6711,8 @@ var kumaFlowCache_exports = {};
|
|
|
6479
6711
|
__export(kumaFlowCache_exports, {
|
|
6480
6712
|
deriveHopsFromImports: () => deriveHopsFromImports,
|
|
6481
6713
|
getFlowFreshness: () => getFlowFreshness,
|
|
6482
|
-
getFreshDomainFlow: () => getFreshDomainFlow
|
|
6714
|
+
getFreshDomainFlow: () => getFreshDomainFlow,
|
|
6715
|
+
getHopsForDomain: () => getHopsForDomain
|
|
6483
6716
|
});
|
|
6484
6717
|
import fs15 from "fs";
|
|
6485
6718
|
import path16 from "path";
|
|
@@ -6570,9 +6803,86 @@ function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
|
6570
6803
|
}
|
|
6571
6804
|
return hops;
|
|
6572
6805
|
}
|
|
6806
|
+
function escapeRegex(str) {
|
|
6807
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6808
|
+
}
|
|
6809
|
+
async function getHopsForDomain(domain) {
|
|
6810
|
+
const hops = [];
|
|
6811
|
+
try {
|
|
6812
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
6813
|
+
const db = await getDb2();
|
|
6814
|
+
const stmt = db.prepare(`SELECT metadata FROM nodes WHERE type = 'feature_domain' AND name = ? LIMIT 1`);
|
|
6815
|
+
stmt.bind([domain]);
|
|
6816
|
+
if (stmt.step()) {
|
|
6817
|
+
const meta = JSON.parse(stmt.getAsObject().metadata || "{}");
|
|
6818
|
+
if (Array.isArray(meta.hops) && meta.hops.length > 0 && typeof meta.hops[0] === "object") {
|
|
6819
|
+
stmt.free();
|
|
6820
|
+
return meta.hops;
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
stmt.free();
|
|
6824
|
+
const nodeStmt = db.prepare(`
|
|
6825
|
+
SELECT name, metadata FROM nodes
|
|
6826
|
+
WHERE type = 'cross_service_link' AND (metadata LIKE ? OR id LIKE ?)
|
|
6827
|
+
LIMIT 25
|
|
6828
|
+
`);
|
|
6829
|
+
nodeStmt.bind([`%"domain":"${domain}"%`, `%${domain}::%`]);
|
|
6830
|
+
const rawNodes = [];
|
|
6831
|
+
while (nodeStmt.step()) {
|
|
6832
|
+
const row = nodeStmt.getAsObject();
|
|
6833
|
+
try {
|
|
6834
|
+
const meta = JSON.parse(row.metadata || "{}");
|
|
6835
|
+
rawNodes.push({
|
|
6836
|
+
name: row.name,
|
|
6837
|
+
hopIndex: typeof meta.hopIndex === "number" ? meta.hopIndex : 999,
|
|
6838
|
+
relation: meta.relation,
|
|
6839
|
+
description: meta.description
|
|
6840
|
+
});
|
|
6841
|
+
} catch {
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
nodeStmt.free();
|
|
6845
|
+
if (rawNodes.length >= 2) {
|
|
6846
|
+
rawNodes.sort((a, b) => a.hopIndex - b.hopIndex);
|
|
6847
|
+
const uniqueSteps = [];
|
|
6848
|
+
for (const n of rawNodes) {
|
|
6849
|
+
if (uniqueSteps.length === 0 || uniqueSteps[uniqueSteps.length - 1] !== n.name) {
|
|
6850
|
+
uniqueSteps.push(n.name);
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
for (let i = 0; i < uniqueSteps.length - 1; i++) {
|
|
6854
|
+
hops.push({ from: uniqueSteps[i], to: uniqueSteps[i + 1] });
|
|
6855
|
+
}
|
|
6856
|
+
if (hops.length > 0) return hops;
|
|
6857
|
+
}
|
|
6858
|
+
} catch {
|
|
6859
|
+
}
|
|
6860
|
+
try {
|
|
6861
|
+
const root = getProjectRoot();
|
|
6862
|
+
const archPath = path16.join(root, ".kuma", "ARCHITECTURE_FLOW.md");
|
|
6863
|
+
if (fs15.existsSync(archPath)) {
|
|
6864
|
+
const content = fs15.readFileSync(archPath, "utf-8");
|
|
6865
|
+
const lines = content.split("\n");
|
|
6866
|
+
for (const line of lines) {
|
|
6867
|
+
const domMatch = line.match(new RegExp(`domain:\\s*${escapeRegex(domain)}\\s*\\|\\s*hops:\\s*(.+)`, "i"));
|
|
6868
|
+
if (domMatch) {
|
|
6869
|
+
const hopsPart = domMatch[1].trim();
|
|
6870
|
+
const arrowSteps = hopsPart.split(/→|->/).map((s) => s.trim()).filter(Boolean);
|
|
6871
|
+
for (let i = 0; i < arrowSteps.length - 1; i++) {
|
|
6872
|
+
hops.push({ from: arrowSteps[i], to: arrowSteps[i + 1] });
|
|
6873
|
+
}
|
|
6874
|
+
if (hops.length > 0) return hops;
|
|
6875
|
+
}
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
} catch {
|
|
6879
|
+
}
|
|
6880
|
+
return hops;
|
|
6881
|
+
}
|
|
6573
6882
|
async function getFreshDomainFlow(domain) {
|
|
6574
6883
|
const freshness = await getFlowFreshness(domain);
|
|
6575
|
-
|
|
6884
|
+
const hops = await getHopsForDomain(domain);
|
|
6885
|
+
if (!freshness && hops.length === 0) {
|
|
6576
6886
|
return `\u2139\uFE0F No arch_flow recorded for domain "${domain}".
|
|
6577
6887
|
Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} | hops: a.ts \u2192 b.ts \u2192 c.ts' }).`;
|
|
6578
6888
|
}
|
|
@@ -6581,33 +6891,55 @@ Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} |
|
|
|
6581
6891
|
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
6582
6892
|
""
|
|
6583
6893
|
];
|
|
6584
|
-
if (freshness
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6894
|
+
if (freshness) {
|
|
6895
|
+
if (freshness.fresh) {
|
|
6896
|
+
lines.push(`\u2705 Cache FRESH \u2014 served as-is (${freshness.filePaths.length} file(s) tracked).`);
|
|
6897
|
+
} else {
|
|
6898
|
+
lines.push(`\u{1F501} Cache STALE (${freshness.staleFiles.length} file(s) changed) \u2014 re-deriving from imports...`);
|
|
6899
|
+
try {
|
|
6900
|
+
const entry = freshness.filePaths[0];
|
|
6901
|
+
const derivedHops = deriveHopsFromImports(entry);
|
|
6902
|
+
if (derivedHops.length > 0) {
|
|
6903
|
+
const { recordDomainFlow: recordDomainFlow2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
6904
|
+
await recordDomainFlow2({
|
|
6905
|
+
domain,
|
|
6906
|
+
hops: derivedHops,
|
|
6907
|
+
filePaths: freshness.filePaths
|
|
6908
|
+
});
|
|
6909
|
+
lines.push(`\u2705 Re-derived ${derivedHops.length} hop(s) from ${path16.basename(entry)} \u2014 cache refreshed.`);
|
|
6910
|
+
hops.splice(0, hops.length, ...derivedHops);
|
|
6911
|
+
} else {
|
|
6912
|
+
lines.push("\u26A0\uFE0F No imports to follow \u2014 cache left as-is (record hops manually if needed).");
|
|
6913
|
+
}
|
|
6914
|
+
} catch (err) {
|
|
6915
|
+
lines.push(`\u26A0\uFE0F Re-derivation failed: ${err}`);
|
|
6601
6916
|
}
|
|
6602
|
-
} catch (err) {
|
|
6603
|
-
lines.push(`\u26A0\uFE0F Re-derivation failed: ${err}`);
|
|
6604
6917
|
}
|
|
6918
|
+
} else {
|
|
6919
|
+
lines.push(`\u2139\uFE0F Serving flow from markdown definition.`);
|
|
6605
6920
|
}
|
|
6606
6921
|
lines.push("");
|
|
6607
|
-
|
|
6608
|
-
lines.push(
|
|
6922
|
+
if (hops.length > 0) {
|
|
6923
|
+
lines.push("\u{1F504} **Flow Sequence (Hops):**");
|
|
6924
|
+
for (let i = 0; i < hops.length; i++) {
|
|
6925
|
+
const h = hops[i];
|
|
6926
|
+
const detail = h.relation && h.relation !== "flows" ? ` (${h.relation})` : "";
|
|
6927
|
+
if (i === 0) {
|
|
6928
|
+
lines.push(` 1. ${h.from} \u2192`);
|
|
6929
|
+
}
|
|
6930
|
+
const isLast = i === hops.length - 1;
|
|
6931
|
+
lines.push(` ${i + 2}. ${h.to}${detail}${isLast ? "" : " \u2192"}`);
|
|
6932
|
+
}
|
|
6933
|
+
lines.push("");
|
|
6609
6934
|
}
|
|
6610
|
-
|
|
6935
|
+
if (freshness && freshness.filePaths.length > 0) {
|
|
6936
|
+
lines.push("\u{1F4C1} **Tracked Files:**");
|
|
6937
|
+
for (const fp of freshness.filePaths) {
|
|
6938
|
+
lines.push(` \u{1F4C4} ${fp}${freshness.staleFiles.includes(fp) ? " \u26A0\uFE0F changed" : ""}`);
|
|
6939
|
+
}
|
|
6940
|
+
lines.push("");
|
|
6941
|
+
}
|
|
6942
|
+
return lines.join("\n").trimEnd();
|
|
6611
6943
|
}
|
|
6612
6944
|
var init_kumaFlowCache = __esm({
|
|
6613
6945
|
"src/engine/kumaFlowCache.ts"() {
|
|
@@ -7170,25 +7502,66 @@ async function createCheckpoint(label, description) {
|
|
|
7170
7502
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
7171
7503
|
}
|
|
7172
7504
|
fs22.mkdirSync(cpDir, { recursive: true });
|
|
7173
|
-
const
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7505
|
+
const candidatePaths = /* @__PURE__ */ new Set();
|
|
7506
|
+
try {
|
|
7507
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
7508
|
+
const gitOut = execSync6("git status --porcelain", {
|
|
7509
|
+
cwd: root,
|
|
7510
|
+
encoding: "utf-8",
|
|
7511
|
+
timeout: 4e3,
|
|
7512
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7513
|
+
}).trim();
|
|
7514
|
+
if (gitOut) {
|
|
7515
|
+
for (const line of gitOut.split("\n")) {
|
|
7516
|
+
const trimmed = line.trim();
|
|
7517
|
+
if (!trimmed) continue;
|
|
7518
|
+
const rawPath = trimmed.slice(2).trim();
|
|
7519
|
+
const cleanPath = rawPath.replace(/^["']|["']$/g, "").split(" -> ").pop();
|
|
7520
|
+
if (cleanPath.startsWith(".kuma/") || cleanPath.startsWith(".git/")) continue;
|
|
7521
|
+
candidatePaths.add(cleanPath);
|
|
7522
|
+
}
|
|
7523
|
+
}
|
|
7524
|
+
} catch {
|
|
7525
|
+
}
|
|
7526
|
+
try {
|
|
7527
|
+
for (const m of sessionMemory.getModifiedFiles()) {
|
|
7528
|
+
if (!m.filePath.startsWith(".kuma/") && !m.filePath.startsWith(".git/")) {
|
|
7529
|
+
candidatePaths.add(m.filePath);
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
} catch {
|
|
7533
|
+
}
|
|
7534
|
+
try {
|
|
7535
|
+
const db2 = await getDb();
|
|
7536
|
+
const filesStmt = db2.prepare(
|
|
7537
|
+
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
7538
|
+
);
|
|
7539
|
+
while (filesStmt.step()) {
|
|
7540
|
+
const row = filesStmt.getAsObject();
|
|
7541
|
+
const fp = row.file_path;
|
|
7542
|
+
if (fp && !fp.startsWith(".kuma/") && !fp.startsWith(".git/")) {
|
|
7543
|
+
candidatePaths.add(fp);
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7546
|
+
filesStmt.free();
|
|
7547
|
+
} catch {
|
|
7548
|
+
}
|
|
7177
7549
|
const files = [];
|
|
7178
|
-
|
|
7179
|
-
const row = filesStmt.getAsObject();
|
|
7180
|
-
const fp = row.file_path;
|
|
7550
|
+
for (const fp of candidatePaths) {
|
|
7181
7551
|
const fullPath = path23.resolve(root, fp);
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7552
|
+
try {
|
|
7553
|
+
if (fs22.existsSync(fullPath) && fs22.statSync(fullPath).isFile()) {
|
|
7554
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
7555
|
+
const hash = simpleHash2(content);
|
|
7556
|
+
const fileDir = path23.dirname(path23.join(cpDir, "files", fp));
|
|
7557
|
+
if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
|
|
7558
|
+
fs22.writeFileSync(path23.join(cpDir, "files", fp), content, "utf-8");
|
|
7559
|
+
files.push({ path: fp, hash });
|
|
7560
|
+
}
|
|
7561
|
+
} catch {
|
|
7189
7562
|
}
|
|
7190
7563
|
}
|
|
7191
|
-
|
|
7564
|
+
const db = await getDb();
|
|
7192
7565
|
const dbData = db.export();
|
|
7193
7566
|
fs22.writeFileSync(path23.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
7194
7567
|
const manifest = {
|
|
@@ -10480,11 +10853,32 @@ async function handleHistory(params) {
|
|
|
10480
10853
|
try {
|
|
10481
10854
|
const { getFileTrace: getFileTrace2, formatFileTrace: formatFileTrace2, getFreshGotchasForFile: getFreshGotchasForFile2, getDecisionsForFile: getDecisionsForFile2 } = await Promise.resolve().then(() => (init_kumaInject(), kumaInject_exports));
|
|
10482
10855
|
const trace = await getFileTrace2(target, 8);
|
|
10483
|
-
|
|
10856
|
+
const traceFormatted = formatFileTrace2(trace, target);
|
|
10857
|
+
if (traceFormatted) {
|
|
10858
|
+
lines.push(traceFormatted);
|
|
10859
|
+
} else {
|
|
10860
|
+
try {
|
|
10861
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
10862
|
+
const { getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_pathValidator(), pathValidator_exports));
|
|
10863
|
+
const root = getProjectRoot2();
|
|
10864
|
+
const gitHistory = execSync6(
|
|
10865
|
+
`git log -n 5 --pretty=format:" \u2022 %h (%ad) %s" --date=short -- "${target}"`,
|
|
10866
|
+
{ cwd: root, encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
10867
|
+
).trim();
|
|
10868
|
+
if (gitHistory) {
|
|
10869
|
+
lines.push(`\u{1F570}\uFE0F **Git Commit History (latest):**
|
|
10870
|
+
${gitHistory}`);
|
|
10871
|
+
} else {
|
|
10872
|
+
lines.push(" (no changes recorded for this file yet)");
|
|
10873
|
+
}
|
|
10874
|
+
} catch {
|
|
10875
|
+
lines.push(" (no changes recorded for this file yet)");
|
|
10876
|
+
}
|
|
10877
|
+
}
|
|
10484
10878
|
lines.push("");
|
|
10485
|
-
const gotchas = await getFreshGotchasForFile2(target, 5);
|
|
10879
|
+
const gotchas = await getFreshGotchasForFile2(target, 5, true);
|
|
10486
10880
|
if (gotchas.length > 0) {
|
|
10487
|
-
lines.push("\u26A0\uFE0F **Active gotchas
|
|
10881
|
+
lines.push("\u26A0\uFE0F **Active gotchas:**");
|
|
10488
10882
|
for (const g of gotchas) {
|
|
10489
10883
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10490
10884
|
lines.push(` ${icon} [${g.severity}] ${g.description}`);
|
|
@@ -10499,11 +10893,34 @@ async function handleHistory(params) {
|
|
|
10499
10893
|
try {
|
|
10500
10894
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
10501
10895
|
const db = await getDb2();
|
|
10502
|
-
const
|
|
10503
|
-
stmt.
|
|
10896
|
+
const base = path17.basename(target);
|
|
10897
|
+
const stmt = db.prepare(`
|
|
10898
|
+
SELECT description, last_verified_at FROM known_gotchas
|
|
10899
|
+
WHERE status = 'resolved' AND (file_path LIKE ? OR file_path LIKE ?)
|
|
10900
|
+
ORDER BY last_verified_at DESC LIMIT 5
|
|
10901
|
+
`);
|
|
10902
|
+
stmt.bind([`%${base}%`, `%${target}%`]);
|
|
10504
10903
|
const resolved = [];
|
|
10505
10904
|
while (stmt.step()) resolved.push(stmt.getAsObject());
|
|
10506
10905
|
stmt.free();
|
|
10906
|
+
if (resolved.length === 0) {
|
|
10907
|
+
try {
|
|
10908
|
+
const { getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_pathValidator(), pathValidator_exports));
|
|
10909
|
+
const mdPath = path17.join(getProjectRoot2(), ".kuma", "KNOWN_GOTCHAS.md");
|
|
10910
|
+
if (fs16.existsSync(mdPath)) {
|
|
10911
|
+
const mdContent = fs16.readFileSync(mdPath, "utf-8");
|
|
10912
|
+
const sections = mdContent.split(/^###\s+/m);
|
|
10913
|
+
for (const sec of sections) {
|
|
10914
|
+
const secLower = sec.toLowerCase();
|
|
10915
|
+
if ((secLower.includes(base.toLowerCase()) || secLower.includes(target.toLowerCase())) && secLower.includes("status**: resolved")) {
|
|
10916
|
+
const descLine = sec.split("\n")[0].trim();
|
|
10917
|
+
resolved.push({ description: descLine, last_verified_at: null });
|
|
10918
|
+
}
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10921
|
+
} catch {
|
|
10922
|
+
}
|
|
10923
|
+
}
|
|
10507
10924
|
if (resolved.length > 0) {
|
|
10508
10925
|
lines.push("\u2705 **Resolved gotchas** (fixed):");
|
|
10509
10926
|
for (const r of resolved) {
|
|
@@ -10516,7 +10933,7 @@ async function handleHistory(params) {
|
|
|
10516
10933
|
}
|
|
10517
10934
|
const decisions = await getDecisionsForFile2(target, 3);
|
|
10518
10935
|
if (decisions.length > 0) {
|
|
10519
|
-
lines.push("\u{1F4CC} **Relevant decisions
|
|
10936
|
+
lines.push("\u{1F4CC} **Relevant decisions:**");
|
|
10520
10937
|
for (const d of decisions) lines.push(` ${d}`);
|
|
10521
10938
|
lines.push("");
|
|
10522
10939
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7",
|
|
4
4
|
"description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://antigravity.google/schemas/v1/plugin.json",
|
|
3
3
|
"name": "kuma",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.7",
|
|
5
5
|
"description": "Safety-first context & orchestration engine for AI coding agents. Shadow memory, gotcha lifecycle, blast radius impact analysis, and test verification.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "plumpslabs",
|