@plumpslabs/kuma 2.4.5 → 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 +688 -213
- package/hooks/kuma-agy-hooks.js +78 -25
- package/hooks.json +2 -2
- 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
|
});
|
|
@@ -5339,15 +5466,17 @@ async function addGotcha(entry) {
|
|
|
5339
5466
|
const severity = entry.severity || "medium";
|
|
5340
5467
|
const status = entry.status || "active";
|
|
5341
5468
|
const contentHash = hashFileContent(entry.filePath);
|
|
5469
|
+
const trap = entry.description;
|
|
5470
|
+
const ruleFix = entry.workaround || "Review implementation carefully before modifying.";
|
|
5342
5471
|
const formatted = [
|
|
5343
|
-
`### ${entry.filePath} \u2014 ${
|
|
5344
|
-
`- **
|
|
5345
|
-
`- **
|
|
5346
|
-
|
|
5347
|
-
entry.triggerCommand ? `- **Trigger**: when running \`${entry.triggerCommand}\`` : "",
|
|
5348
|
-
entry.status ? `- **Status**: ${
|
|
5349
|
-
entry.scopePackage ? `- **Scope Package**: ${entry.scopePackage}` : "",
|
|
5350
|
-
`- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
5472
|
+
`### ${entry.filePath} \u2014 ${trap}`,
|
|
5473
|
+
`- \u{1FAA4} **Trap**: ${trap}`,
|
|
5474
|
+
`- \u{1F6E1}\uFE0F **Rule/Fix**: ${ruleFix}`,
|
|
5475
|
+
`- \u26A1 **Severity**: ${severity}`,
|
|
5476
|
+
entry.triggerCommand ? `- \u2328\uFE0F **Trigger**: when running \`${entry.triggerCommand}\`` : "",
|
|
5477
|
+
entry.status ? `- \u{1F3F7}\uFE0F **Status**: ${status}` : "",
|
|
5478
|
+
entry.scopePackage ? `- \u{1F4E6} **Scope Package**: ${entry.scopePackage}` : "",
|
|
5479
|
+
`- \u{1F4C5} **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
5351
5480
|
].filter(Boolean).join("\n");
|
|
5352
5481
|
const checkStmt = db.prepare(
|
|
5353
5482
|
`SELECT id FROM known_gotchas WHERE file_path = ? AND description LIKE ?`
|
|
@@ -5533,13 +5662,14 @@ async function resolveGotchasForScope(scope) {
|
|
|
5533
5662
|
const db = await getDb();
|
|
5534
5663
|
const stmt = db.prepare(
|
|
5535
5664
|
`SELECT id, file_path, content_hash FROM known_gotchas
|
|
5536
|
-
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`
|
|
5537
5666
|
);
|
|
5538
|
-
stmt.bind([`%${scope}
|
|
5667
|
+
stmt.bind([`%${scope}%`, scope]);
|
|
5539
5668
|
const rows = [];
|
|
5540
5669
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
5541
5670
|
stmt.free();
|
|
5542
5671
|
let resolved = 0;
|
|
5672
|
+
const resolvedPaths = [];
|
|
5543
5673
|
for (const r of rows) {
|
|
5544
5674
|
const current = hashFileContent(r.file_path);
|
|
5545
5675
|
if (current && current !== r.content_hash) {
|
|
@@ -5548,9 +5678,13 @@ async function resolveGotchasForScope(scope) {
|
|
|
5548
5678
|
[r.id]
|
|
5549
5679
|
);
|
|
5550
5680
|
resolved++;
|
|
5681
|
+
resolvedPaths.push(r.file_path);
|
|
5551
5682
|
}
|
|
5552
5683
|
}
|
|
5553
|
-
if (resolved > 0)
|
|
5684
|
+
if (resolved > 0) {
|
|
5685
|
+
saveDb();
|
|
5686
|
+
syncGotchaStatusToMarkdown([scope, ...resolvedPaths], "resolved", "auto-verified");
|
|
5687
|
+
}
|
|
5554
5688
|
return { resolved };
|
|
5555
5689
|
} catch {
|
|
5556
5690
|
return { resolved: 0 };
|
|
@@ -5656,14 +5790,73 @@ async function getGotchasByStatus(status, limit = 20) {
|
|
|
5656
5790
|
return [];
|
|
5657
5791
|
}
|
|
5658
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
|
+
}
|
|
5659
5845
|
async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent") {
|
|
5660
5846
|
try {
|
|
5661
5847
|
await ensureGotchasSchema();
|
|
5662
5848
|
const db = await getDb();
|
|
5663
5849
|
let resolved = 0;
|
|
5664
5850
|
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5851
|
+
const targetPaths = [];
|
|
5665
5852
|
if (isId) {
|
|
5666
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();
|
|
5667
5860
|
db.run(
|
|
5668
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'`,
|
|
5669
5862
|
[verifiedBy, id]
|
|
@@ -5671,12 +5864,22 @@ async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent")
|
|
|
5671
5864
|
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5672
5865
|
} else {
|
|
5673
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();
|
|
5674
5874
|
db.run(
|
|
5675
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'`,
|
|
5676
5876
|
[verifiedBy, scope, `%${scope}%`]
|
|
5677
5877
|
);
|
|
5678
5878
|
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5679
5879
|
}
|
|
5880
|
+
if (targetPaths.length > 0) {
|
|
5881
|
+
syncGotchaStatusToMarkdown(targetPaths, "resolved", resolutionReason);
|
|
5882
|
+
}
|
|
5680
5883
|
if (resolved > 0) {
|
|
5681
5884
|
saveDb();
|
|
5682
5885
|
rebuildFtsIndex();
|
|
@@ -5685,6 +5888,13 @@ async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent")
|
|
|
5685
5888
|
message: `\u2705 Resolved ${resolved} gotcha(s) for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5686
5889
|
};
|
|
5687
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
|
+
}
|
|
5688
5898
|
return { resolved: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5689
5899
|
} catch (err) {
|
|
5690
5900
|
return { resolved: 0, message: `\u274C Failed to resolve gotcha: ${err}` };
|
|
@@ -5696,8 +5906,15 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5696
5906
|
const db = await getDb();
|
|
5697
5907
|
let deprecated = 0;
|
|
5698
5908
|
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5909
|
+
const targetPaths = [];
|
|
5699
5910
|
if (isId) {
|
|
5700
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();
|
|
5701
5918
|
db.run(
|
|
5702
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'`,
|
|
5703
5920
|
[id]
|
|
@@ -5705,12 +5922,22 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5705
5922
|
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5706
5923
|
} else {
|
|
5707
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();
|
|
5708
5932
|
db.run(
|
|
5709
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'`,
|
|
5710
5934
|
[scope, `%${scope}%`]
|
|
5711
5935
|
);
|
|
5712
5936
|
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5713
5937
|
}
|
|
5938
|
+
if (targetPaths.length > 0) {
|
|
5939
|
+
syncGotchaStatusToMarkdown(targetPaths, "deprecated", reason);
|
|
5940
|
+
}
|
|
5714
5941
|
if (deprecated > 0) {
|
|
5715
5942
|
saveDb();
|
|
5716
5943
|
rebuildFtsIndex();
|
|
@@ -5719,6 +5946,13 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5719
5946
|
message: `\u{1F9F9} Deprecated ${deprecated} gotcha(s) for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5720
5947
|
};
|
|
5721
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
|
+
}
|
|
5722
5956
|
return { deprecated: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5723
5957
|
} catch (err) {
|
|
5724
5958
|
return { deprecated: 0, message: `\u274C Failed to deprecate gotcha: ${err}` };
|
|
@@ -6477,7 +6711,8 @@ var kumaFlowCache_exports = {};
|
|
|
6477
6711
|
__export(kumaFlowCache_exports, {
|
|
6478
6712
|
deriveHopsFromImports: () => deriveHopsFromImports,
|
|
6479
6713
|
getFlowFreshness: () => getFlowFreshness,
|
|
6480
|
-
getFreshDomainFlow: () => getFreshDomainFlow
|
|
6714
|
+
getFreshDomainFlow: () => getFreshDomainFlow,
|
|
6715
|
+
getHopsForDomain: () => getHopsForDomain
|
|
6481
6716
|
});
|
|
6482
6717
|
import fs15 from "fs";
|
|
6483
6718
|
import path16 from "path";
|
|
@@ -6568,9 +6803,86 @@ function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
|
6568
6803
|
}
|
|
6569
6804
|
return hops;
|
|
6570
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
|
+
}
|
|
6571
6882
|
async function getFreshDomainFlow(domain) {
|
|
6572
6883
|
const freshness = await getFlowFreshness(domain);
|
|
6573
|
-
|
|
6884
|
+
const hops = await getHopsForDomain(domain);
|
|
6885
|
+
if (!freshness && hops.length === 0) {
|
|
6574
6886
|
return `\u2139\uFE0F No arch_flow recorded for domain "${domain}".
|
|
6575
6887
|
Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} | hops: a.ts \u2192 b.ts \u2192 c.ts' }).`;
|
|
6576
6888
|
}
|
|
@@ -6579,33 +6891,55 @@ Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} |
|
|
|
6579
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`,
|
|
6580
6892
|
""
|
|
6581
6893
|
];
|
|
6582
|
-
if (freshness
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
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}`);
|
|
6599
6916
|
}
|
|
6600
|
-
} catch (err) {
|
|
6601
|
-
lines.push(`\u26A0\uFE0F Re-derivation failed: ${err}`);
|
|
6602
6917
|
}
|
|
6918
|
+
} else {
|
|
6919
|
+
lines.push(`\u2139\uFE0F Serving flow from markdown definition.`);
|
|
6603
6920
|
}
|
|
6604
6921
|
lines.push("");
|
|
6605
|
-
|
|
6606
|
-
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("");
|
|
6607
6934
|
}
|
|
6608
|
-
|
|
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();
|
|
6609
6943
|
}
|
|
6610
6944
|
var init_kumaFlowCache = __esm({
|
|
6611
6945
|
"src/engine/kumaFlowCache.ts"() {
|
|
@@ -7168,25 +7502,66 @@ async function createCheckpoint(label, description) {
|
|
|
7168
7502
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
7169
7503
|
}
|
|
7170
7504
|
fs22.mkdirSync(cpDir, { recursive: true });
|
|
7171
|
-
const
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
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
|
+
}
|
|
7175
7549
|
const files = [];
|
|
7176
|
-
|
|
7177
|
-
const row = filesStmt.getAsObject();
|
|
7178
|
-
const fp = row.file_path;
|
|
7550
|
+
for (const fp of candidatePaths) {
|
|
7179
7551
|
const fullPath = path23.resolve(root, fp);
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
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 {
|
|
7187
7562
|
}
|
|
7188
7563
|
}
|
|
7189
|
-
|
|
7564
|
+
const db = await getDb();
|
|
7190
7565
|
const dbData = db.export();
|
|
7191
7566
|
fs22.writeFileSync(path23.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
7192
7567
|
const manifest = {
|
|
@@ -10478,11 +10853,32 @@ async function handleHistory(params) {
|
|
|
10478
10853
|
try {
|
|
10479
10854
|
const { getFileTrace: getFileTrace2, formatFileTrace: formatFileTrace2, getFreshGotchasForFile: getFreshGotchasForFile2, getDecisionsForFile: getDecisionsForFile2 } = await Promise.resolve().then(() => (init_kumaInject(), kumaInject_exports));
|
|
10480
10855
|
const trace = await getFileTrace2(target, 8);
|
|
10481
|
-
|
|
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
|
+
}
|
|
10482
10878
|
lines.push("");
|
|
10483
|
-
const gotchas = await getFreshGotchasForFile2(target, 5);
|
|
10879
|
+
const gotchas = await getFreshGotchasForFile2(target, 5, true);
|
|
10484
10880
|
if (gotchas.length > 0) {
|
|
10485
|
-
lines.push("\u26A0\uFE0F **Active gotchas
|
|
10881
|
+
lines.push("\u26A0\uFE0F **Active gotchas:**");
|
|
10486
10882
|
for (const g of gotchas) {
|
|
10487
10883
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10488
10884
|
lines.push(` ${icon} [${g.severity}] ${g.description}`);
|
|
@@ -10497,11 +10893,34 @@ async function handleHistory(params) {
|
|
|
10497
10893
|
try {
|
|
10498
10894
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
10499
10895
|
const db = await getDb2();
|
|
10500
|
-
const
|
|
10501
|
-
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}%`]);
|
|
10502
10903
|
const resolved = [];
|
|
10503
10904
|
while (stmt.step()) resolved.push(stmt.getAsObject());
|
|
10504
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
|
+
}
|
|
10505
10924
|
if (resolved.length > 0) {
|
|
10506
10925
|
lines.push("\u2705 **Resolved gotchas** (fixed):");
|
|
10507
10926
|
for (const r of resolved) {
|
|
@@ -10514,7 +10933,7 @@ async function handleHistory(params) {
|
|
|
10514
10933
|
}
|
|
10515
10934
|
const decisions = await getDecisionsForFile2(target, 3);
|
|
10516
10935
|
if (decisions.length > 0) {
|
|
10517
|
-
lines.push("\u{1F4CC} **Relevant decisions
|
|
10936
|
+
lines.push("\u{1F4CC} **Relevant decisions:**");
|
|
10518
10937
|
for (const d of decisions) lines.push(` ${d}`);
|
|
10519
10938
|
lines.push("");
|
|
10520
10939
|
}
|
|
@@ -10589,6 +11008,8 @@ var MEMORY_ALIASES = {
|
|
|
10589
11008
|
"resolve_gotcha": "gotcha",
|
|
10590
11009
|
"resolve-gotcha": "gotcha",
|
|
10591
11010
|
"resolve": "gotcha",
|
|
11011
|
+
"gotcha_resolve": "gotcha",
|
|
11012
|
+
"gotcha-resolve": "gotcha",
|
|
10592
11013
|
"deprecate_gotcha": "gotcha",
|
|
10593
11014
|
"deprecate-gotcha": "gotcha",
|
|
10594
11015
|
"deprecate": "gotcha"
|
|
@@ -10784,7 +11205,7 @@ async function handleArchFlow(params) {
|
|
|
10784
11205
|
async function handleGotchaAction(params) {
|
|
10785
11206
|
const rawStatus = (params.status || "").toLowerCase().trim();
|
|
10786
11207
|
const rawAction = (params.action || "").toLowerCase().trim();
|
|
10787
|
-
const isResolution = rawStatus === "resolved" || rawStatus === "resolve" || rawAction === "resolve_gotcha" || rawAction === "resolve";
|
|
11208
|
+
const isResolution = rawStatus === "resolved" || rawStatus === "resolve" || rawAction === "resolve_gotcha" || rawAction === "gotcha_resolve" || rawAction === "gotcha-resolve" || rawAction === "resolve";
|
|
10788
11209
|
const isDeprecation = rawStatus === "deprecated" || rawStatus === "deprecate" || rawAction === "deprecate_gotcha" || rawAction === "deprecate";
|
|
10789
11210
|
const target = params.target || params.id || params.scope;
|
|
10790
11211
|
if (isResolution && target) {
|
|
@@ -11449,127 +11870,123 @@ async function doInit() {
|
|
|
11449
11870
|
// src/manifest.ts
|
|
11450
11871
|
var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
|
|
11451
11872
|
function registerAllTools(server) {
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
} catch (err) {
|
|
11474
|
-
return { content: [{ type: "text", text: `Error in kuma_context: ${err}` }], isError: true };
|
|
11475
|
-
}
|
|
11873
|
+
const contextDesc = "Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow), `map` (repository topology), `impact` (blast radius analysis).";
|
|
11874
|
+
const contextSchema = {
|
|
11875
|
+
action: z.enum(["init", "research", "history", "flow", "map", "impact"]).describe(
|
|
11876
|
+
"init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow, map=repo topology, impact=blast radius"
|
|
11877
|
+
),
|
|
11878
|
+
scope: z.string().optional().describe("Research scope or target for impact"),
|
|
11879
|
+
target: z.string().optional().describe("Target symbol/file for history/flow/impact"),
|
|
11880
|
+
goal: z.string().optional().describe("Current goal (for init)")
|
|
11881
|
+
};
|
|
11882
|
+
const contextHandler = async (params) => {
|
|
11883
|
+
try {
|
|
11884
|
+
await ensureInitialized();
|
|
11885
|
+
const text = await handleContext({
|
|
11886
|
+
action: params.action,
|
|
11887
|
+
scope: params.scope,
|
|
11888
|
+
target: params.target,
|
|
11889
|
+
goal: params.goal
|
|
11890
|
+
});
|
|
11891
|
+
return { content: [{ type: "text", text }] };
|
|
11892
|
+
} catch (err) {
|
|
11893
|
+
return { content: [{ type: "text", text: `Error in kuma_context: ${err}` }], isError: true };
|
|
11476
11894
|
}
|
|
11477
|
-
|
|
11478
|
-
server.tool(
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
}
|
|
11895
|
+
};
|
|
11896
|
+
server.tool("kuma_context", contextDesc, contextSchema, contextHandler);
|
|
11897
|
+
server.tool("kuma_kuma_context", contextDesc, contextSchema, contextHandler);
|
|
11898
|
+
const memoryDesc = "Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area), `search` (quick lookup of memory + knowledge graph), `gotcha_resolve` (archive fixed bugs).";
|
|
11899
|
+
const memorySchema = {
|
|
11900
|
+
action: z.enum(["gotcha", "decision", "arch_flow", "research_save", "search", "gotcha_resolve"]).describe(
|
|
11901
|
+
"gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup, gotcha_resolve=archive fixed bug"
|
|
11902
|
+
),
|
|
11903
|
+
scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
|
|
11904
|
+
target: z.string().optional().describe("Target file, gotcha ID, or component"),
|
|
11905
|
+
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
|
|
11906
|
+
query: z.string().optional().describe("Search query for search action"),
|
|
11907
|
+
content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
|
|
11908
|
+
record: z.string().optional().describe("JSON record string for research_save"),
|
|
11909
|
+
confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
|
|
11910
|
+
title: z.string().optional().describe("Decision title (optional if content/target provided)"),
|
|
11911
|
+
context: z.string().optional().describe("Decision context"),
|
|
11912
|
+
rationale: z.string().optional().describe("Decision rationale (optional if content provided)"),
|
|
11913
|
+
outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
|
|
11914
|
+
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical) or status (resolved|deprecated)"),
|
|
11915
|
+
severity: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11916
|
+
description: z.string().optional().describe("Gotcha workaround / rationale"),
|
|
11917
|
+
id: z.union([z.number(), z.string()]).optional().describe("Gotcha ID for resolution or deprecation"),
|
|
11918
|
+
limit: z.number().min(1).max(100).optional().describe("Result limit for search")
|
|
11919
|
+
};
|
|
11920
|
+
const memoryHandler = async (params) => {
|
|
11921
|
+
try {
|
|
11922
|
+
await ensureInitialized();
|
|
11923
|
+
const text = await handleMemory({
|
|
11924
|
+
action: params.action,
|
|
11925
|
+
scope: params.scope,
|
|
11926
|
+
target: params.target,
|
|
11927
|
+
query: params.query,
|
|
11928
|
+
content: params.content,
|
|
11929
|
+
record: params.record,
|
|
11930
|
+
confidence: params.confidence,
|
|
11931
|
+
title: params.title,
|
|
11932
|
+
context: params.context,
|
|
11933
|
+
rationale: params.rationale,
|
|
11934
|
+
outcome: params.outcome,
|
|
11935
|
+
limit: params.limit,
|
|
11936
|
+
status: params.status,
|
|
11937
|
+
severity: params.severity,
|
|
11938
|
+
description: params.description,
|
|
11939
|
+
trigger_command: params.trigger_command,
|
|
11940
|
+
id: params.id
|
|
11941
|
+
});
|
|
11942
|
+
return { content: [{ type: "text", text }] };
|
|
11943
|
+
} catch (err) {
|
|
11944
|
+
return { content: [{ type: "text", text: `Error in kuma_memory: ${err}` }], isError: true };
|
|
11528
11945
|
}
|
|
11529
|
-
|
|
11530
|
-
server.tool(
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
}
|
|
11946
|
+
};
|
|
11947
|
+
server.tool("kuma_memory", memoryDesc, memorySchema, memoryHandler);
|
|
11948
|
+
server.tool("kuma_kuma_memory", memoryDesc, memorySchema, memoryHandler);
|
|
11949
|
+
const safetyDesc = "Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation), `checkpoint` + `rollback_label` (the one rollback mechanism: snapshot before risky work, restore after).";
|
|
11950
|
+
const safetySchema = {
|
|
11951
|
+
action: z.enum(["guard", "verify", "checkpoint", "rollback_label"]).describe(
|
|
11952
|
+
"guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
|
|
11953
|
+
),
|
|
11954
|
+
scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
|
|
11955
|
+
target: z.string().optional().describe("Target test file or scope for verify"),
|
|
11956
|
+
command: z.string().optional().describe("Custom test command to run during verify"),
|
|
11957
|
+
timeoutMs: z.number().optional().describe("Custom timeout in milliseconds for verify (default: 60000)"),
|
|
11958
|
+
timeout: z.number().optional().describe("Custom timeout in seconds for verify"),
|
|
11959
|
+
force: z.boolean().optional().describe("Force re-run even if cache is fresh or rate limited (verify)"),
|
|
11960
|
+
guardGoal: z.string().optional().describe("Goal for guard check"),
|
|
11961
|
+
guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
|
|
11962
|
+
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
|
|
11963
|
+
description: z.string().optional().describe("Description for checkpoint")
|
|
11964
|
+
};
|
|
11965
|
+
const safetyHandler = async (params) => {
|
|
11966
|
+
try {
|
|
11967
|
+
await ensureInitialized();
|
|
11968
|
+
const text = await handleSafety({
|
|
11969
|
+
action: params.action,
|
|
11970
|
+
guardGoal: params.guardGoal,
|
|
11971
|
+
guardCheck: params.guardCheck,
|
|
11972
|
+
scope: params.scope,
|
|
11973
|
+
target: params.target,
|
|
11974
|
+
command: params.command,
|
|
11975
|
+
timeoutMs: params.timeoutMs,
|
|
11976
|
+
timeout: params.timeout,
|
|
11977
|
+
force: params.force,
|
|
11978
|
+
label: params.label,
|
|
11979
|
+
description: params.description
|
|
11980
|
+
});
|
|
11981
|
+
return { content: [{ type: "text", text }] };
|
|
11982
|
+
} catch (err) {
|
|
11983
|
+
return { content: [{ type: "text", text: `Error in kuma_safety: ${err}` }], isError: true };
|
|
11568
11984
|
}
|
|
11569
|
-
|
|
11570
|
-
|
|
11985
|
+
};
|
|
11986
|
+
server.tool("kuma_safety", safetyDesc, safetySchema, safetyHandler);
|
|
11987
|
+
server.tool("kuma_kuma_safety", safetyDesc, safetySchema, safetyHandler);
|
|
11988
|
+
console.error("[Manifest] Registered 3 core tools with universal dual-prefix aliases (kuma_* and kuma_kuma_*).");
|
|
11571
11989
|
console.error("[Manifest] Auto-init hooks installed on all tools.");
|
|
11572
|
-
console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
|
|
11573
11990
|
}
|
|
11574
11991
|
|
|
11575
11992
|
// src/index.ts
|
|
@@ -11624,6 +12041,64 @@ async function main() {
|
|
|
11624
12041
|
printHelp();
|
|
11625
12042
|
process.exit(0);
|
|
11626
12043
|
}
|
|
12044
|
+
if (args[0] === "status") {
|
|
12045
|
+
console.log(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Health & Memory Status`);
|
|
12046
|
+
console.log(`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
12047
|
+
try {
|
|
12048
|
+
const { getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_pathValidator(), pathValidator_exports));
|
|
12049
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
12050
|
+
const root = getProjectRoot2();
|
|
12051
|
+
console.log(`\u{1F4C1} Project Root: ${root}`);
|
|
12052
|
+
const db = await getDb2();
|
|
12053
|
+
let decisionsCount = 0;
|
|
12054
|
+
try {
|
|
12055
|
+
const dRes = db.exec("SELECT COUNT(*) FROM nodes WHERE type = 'decision'");
|
|
12056
|
+
decisionsCount = dRes[0]?.values[0]?.[0] || 0;
|
|
12057
|
+
} catch {
|
|
12058
|
+
}
|
|
12059
|
+
let activeGotchas = 0;
|
|
12060
|
+
let resolvedGotchas = 0;
|
|
12061
|
+
let candidateGotchas = 0;
|
|
12062
|
+
try {
|
|
12063
|
+
const gRes = db.exec("SELECT status, COUNT(*) FROM known_gotchas GROUP BY status");
|
|
12064
|
+
for (const row of gRes[0]?.values || []) {
|
|
12065
|
+
const status = String(row[0]);
|
|
12066
|
+
const cnt = Number(row[1]);
|
|
12067
|
+
if (status === "active" || status === "verified") activeGotchas += cnt;
|
|
12068
|
+
else if (status === "resolved") resolvedGotchas += cnt;
|
|
12069
|
+
else if (status === "candidate") candidateGotchas += cnt;
|
|
12070
|
+
}
|
|
12071
|
+
} catch {
|
|
12072
|
+
}
|
|
12073
|
+
let shadowInjections = 0;
|
|
12074
|
+
let savedMinutes = 0;
|
|
12075
|
+
try {
|
|
12076
|
+
const { getInjectionStats: getInjectionStats2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
12077
|
+
const stats = getInjectionStats2(24);
|
|
12078
|
+
shadowInjections = stats.count;
|
|
12079
|
+
savedMinutes = Math.round(stats.savedMs / 6e4);
|
|
12080
|
+
} catch {
|
|
12081
|
+
}
|
|
12082
|
+
let workspaceDesc = "Single project";
|
|
12083
|
+
try {
|
|
12084
|
+
const { getWorkspaceInfo: getWorkspaceInfo2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
12085
|
+
const wsInfo = await getWorkspaceInfo2(root);
|
|
12086
|
+
if (wsInfo.isWorkspace) {
|
|
12087
|
+
workspaceDesc = `${wsInfo.packages.length} package(s) (${wsInfo.type.toUpperCase()})`;
|
|
12088
|
+
}
|
|
12089
|
+
} catch {
|
|
12090
|
+
}
|
|
12091
|
+
console.log(`\u{1F3DB}\uFE0F Decisions Recorded: ${decisionsCount}`);
|
|
12092
|
+
console.log(`\u26A0\uFE0F Active Gotchas: ${activeGotchas} active \xB7 ${resolvedGotchas} resolved \xB7 ${candidateGotchas} candidate`);
|
|
12093
|
+
console.log(`\u{1FA84} Shadow Memory: ${shadowInjections} prevention(s) in last 24h (~${savedMinutes} min saved)`);
|
|
12094
|
+
console.log(`\u{1F4E6} Workspace: ${workspaceDesc}`);
|
|
12095
|
+
console.log(`\u{1F6E1}\uFE0F Safety Hooks: Active`);
|
|
12096
|
+
console.log(`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
12097
|
+
} catch (err) {
|
|
12098
|
+
console.error(`\u274C Failed to retrieve status: ${err}`);
|
|
12099
|
+
}
|
|
12100
|
+
process.exit(0);
|
|
12101
|
+
}
|
|
11627
12102
|
if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
|
|
11628
12103
|
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
11629
12104
|
console.error("");
|