@shahmilsaari/memory-core 1.0.25 → 1.0.26
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/README.md +75 -659
- package/dist/approval-queue-YBYRGBHP.js +7 -0
- package/dist/ast-analyzer-JM4CIOFY.js +44 -0
- package/dist/check-cache-6NWRTZJD.js +52 -0
- package/dist/check-logger-5HYSWA3S.js +21 -0
- package/dist/{chunk-35ZWQFTO.js → chunk-3XTHE74V.js} +239 -830
- package/dist/chunk-M7NKSXFS.js +301 -0
- package/dist/chunk-PQBWHAZN.js +156 -0
- package/dist/chunk-W6WEAV3S.js +69 -0
- package/dist/chunk-ZZBQEXEO.js +183 -0
- package/dist/classifier-MZ65R7FK.js +60 -0
- package/dist/cli.js +404 -10
- package/dist/confidence-gate-ZQDAOS6P.js +64 -0
- package/dist/dashboard/assets/index-CE3AMEOD.js +2 -0
- package/dist/dashboard/assets/{index-CHgjllWU.css → index-CNc2vvZF.css} +1 -1
- package/dist/dashboard/index.html +2 -2
- package/dist/{dashboard-server-SSYZLQKB.js → dashboard-server-EEFNE6NX.js} +95 -11
- package/dist/db-PRDHI2CN.js +29 -0
- package/dist/deepseek-critique-MALVIYGF.js +82 -0
- package/dist/deterministic-validator-PP56B46I.js +18 -0
- package/dist/evidence-HVMSONTT.js +65 -0
- package/dist/graph-TFNTB5OK.js +98 -0
- package/dist/incident-capture-RVPZULS7.js +20 -0
- package/dist/ollama-judge-D2LFK5PB.js +137 -0
- package/dist/rate-limiter-SLIPCXRF.js +41 -0
- package/dist/rules-V3QMN3AR.js +95 -0
- package/dist/watch-errors-B3FA26N4.js +99 -0
- package/package.json +1 -1
- package/dist/dashboard/assets/index-B7gd4JQc.js +0 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
callChatModel
|
|
4
|
+
} from "./chunk-PQBWHAZN.js";
|
|
5
|
+
|
|
6
|
+
// src/models/deepseek-critique.ts
|
|
7
|
+
function loadDeepSeekConfig(_configDir) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
var DeepSeekCritique = class {
|
|
11
|
+
constructor(_config) {
|
|
12
|
+
}
|
|
13
|
+
async critique(packet, primaryDecision) {
|
|
14
|
+
try {
|
|
15
|
+
const result = await callChatModel([
|
|
16
|
+
{
|
|
17
|
+
role: "system",
|
|
18
|
+
content: "You are an expert architecture reviewer. Validate or override model decisions based only on the provided evidence. Return ONLY valid JSON."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
role: "user",
|
|
22
|
+
content: buildCritiquePrompt(packet, primaryDecision)
|
|
23
|
+
}
|
|
24
|
+
]);
|
|
25
|
+
return parseDecision(result.content, primaryDecision);
|
|
26
|
+
} catch {
|
|
27
|
+
return {
|
|
28
|
+
decision: primaryDecision.decision,
|
|
29
|
+
confidence: primaryDecision.confidence,
|
|
30
|
+
reasoning: "Chat model unavailable for critique \u2014 using primary decision",
|
|
31
|
+
override: false
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function buildCritiquePrompt(packet, primary) {
|
|
37
|
+
return `Validate this architecture review decision.
|
|
38
|
+
|
|
39
|
+
PRIMARY DECISION:
|
|
40
|
+
- Decision: ${primary.decision}
|
|
41
|
+
- Confidence: ${primary.confidence}
|
|
42
|
+
- Reasoning: ${primary.reasoning}
|
|
43
|
+
- Violations cited: ${primary.violations.join(", ") || "none"}
|
|
44
|
+
|
|
45
|
+
EVIDENCE SUMMARY:
|
|
46
|
+
- Changed files: ${packet.changedFiles.map((f) => `${f.file} (${f.layer})`).join(", ") || "none"}
|
|
47
|
+
- Violations: ${packet.violations.map((v) => `${v.rule.name}: ${v.fromFile} \u2192 ${v.toFile}`).join(", ") || "none"}
|
|
48
|
+
- Graph violations: ${packet.graphViolations.map((v) => v.ruleName).join(", ") || "none"}
|
|
49
|
+
- Matched rules: ${packet.matchedRules.map((r) => r.name).join(", ") || "none"}
|
|
50
|
+
|
|
51
|
+
TASK:
|
|
52
|
+
1. Is the primary decision supported by the evidence?
|
|
53
|
+
2. Were any violations missed?
|
|
54
|
+
3. Should the decision be overridden?
|
|
55
|
+
|
|
56
|
+
Return ONLY this JSON, no other text:
|
|
57
|
+
{
|
|
58
|
+
"decision": "allow",
|
|
59
|
+
"confidence": 0.9,
|
|
60
|
+
"reasoning": "",
|
|
61
|
+
"override": false
|
|
62
|
+
}`;
|
|
63
|
+
}
|
|
64
|
+
function parseDecision(raw, fallback) {
|
|
65
|
+
try {
|
|
66
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
67
|
+
if (!match) throw new Error("no JSON");
|
|
68
|
+
const parsed = JSON.parse(match[0]);
|
|
69
|
+
return {
|
|
70
|
+
decision: ["allow", "warn", "block"].includes(parsed.decision) ? parsed.decision : fallback.decision,
|
|
71
|
+
confidence: typeof parsed.confidence === "number" ? Math.max(0, Math.min(1, parsed.confidence)) : fallback.confidence,
|
|
72
|
+
reasoning: parsed.reasoning ?? "",
|
|
73
|
+
override: parsed.override ?? false
|
|
74
|
+
};
|
|
75
|
+
} catch {
|
|
76
|
+
return { decision: fallback.decision, confidence: fallback.confidence, reasoning: "Parse error", override: false };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
DeepSeekCritique,
|
|
81
|
+
loadDeepSeekConfig
|
|
82
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/models/deterministic-validator.ts
|
|
4
|
+
var DeterministicValidator = class {
|
|
5
|
+
validate(packet) {
|
|
6
|
+
if (packet.violations.some((v) => v.rule.severity === "critical")) return "block";
|
|
7
|
+
if (packet.graphViolations.length > 0) return "block";
|
|
8
|
+
const newInRestricted = packet.changedFiles.filter(
|
|
9
|
+
(f) => f.isNew && ["infrastructure", "config"].includes(f.layer)
|
|
10
|
+
);
|
|
11
|
+
if (newInRestricted.length > 0) return "warn";
|
|
12
|
+
if (packet.violations.some((v) => v.rule.severity === "medium")) return "warn";
|
|
13
|
+
return "allow";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
DeterministicValidator
|
|
18
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/evidence.ts
|
|
4
|
+
import { dirname, resolve } from "path";
|
|
5
|
+
var EvidencePacketBuilder = class {
|
|
6
|
+
constructor(cwd = process.cwd()) {
|
|
7
|
+
this.cwd = cwd;
|
|
8
|
+
}
|
|
9
|
+
cwd;
|
|
10
|
+
async build(diff, astAnalyzer, classifier, ruleMatcher, graph) {
|
|
11
|
+
const changedFiles = classifier.classifyDiff(diff);
|
|
12
|
+
const layersAffected = [...new Set(changedFiles.map((f) => f.layer))];
|
|
13
|
+
const importsMap = [];
|
|
14
|
+
const violations = [];
|
|
15
|
+
for (const file of [...diff.added, ...diff.modified]) {
|
|
16
|
+
const astInfo = astAnalyzer.analyze(file);
|
|
17
|
+
importsMap.push({ file, imports: astInfo.imports });
|
|
18
|
+
const fromClassified = classifier.classifyFile(file);
|
|
19
|
+
if (!fromClassified) continue;
|
|
20
|
+
for (const imp of astInfo.imports) {
|
|
21
|
+
if (imp.isExternal) continue;
|
|
22
|
+
const importedFile = this.resolveImport(file, imp.module);
|
|
23
|
+
const toClassified = classifier.classifyFile(importedFile);
|
|
24
|
+
if (!toClassified) continue;
|
|
25
|
+
const violatingRules = ruleMatcher.findViolatingRules(fromClassified.layer, toClassified.layer);
|
|
26
|
+
for (const rule of violatingRules) {
|
|
27
|
+
violations.push({ rule, fromFile: file, toFile: importedFile, imports: [imp] });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const graphViolations = graph.findViolations(ruleMatcher.getAllRules());
|
|
32
|
+
const matchedRules = ruleMatcher.getRulesForLayers(layersAffected);
|
|
33
|
+
return {
|
|
34
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35
|
+
changedFiles,
|
|
36
|
+
layersAffected,
|
|
37
|
+
imports: importsMap,
|
|
38
|
+
matchedRules,
|
|
39
|
+
violations,
|
|
40
|
+
graphViolations,
|
|
41
|
+
diffSummary: this.generateDiffSummary(changedFiles),
|
|
42
|
+
metadata: {
|
|
43
|
+
totalFilesChanged: diff.added.length + diff.modified.length,
|
|
44
|
+
newFiles: diff.added.length,
|
|
45
|
+
layerTransitions: violations.length
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
generateDiffSummary(files) {
|
|
50
|
+
const layers = [...new Set(files.map((f) => f.layer))];
|
|
51
|
+
const newCount = files.filter((f) => f.isNew).length;
|
|
52
|
+
return `Modified ${files.length} files across ${layers.length} layer(s) (${newCount} new)`;
|
|
53
|
+
}
|
|
54
|
+
resolveImport(fromFile, importPath) {
|
|
55
|
+
if (importPath.startsWith(".")) {
|
|
56
|
+
const abs = resolve(this.cwd, dirname(fromFile), importPath).replace(/\\/g, "/");
|
|
57
|
+
const base = this.cwd.replace(/\\/g, "/").replace(/\/?$/, "/");
|
|
58
|
+
return abs.startsWith(base) ? abs.slice(base.length) : abs;
|
|
59
|
+
}
|
|
60
|
+
return importPath;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
EvidencePacketBuilder
|
|
65
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/graph.ts
|
|
4
|
+
import { dirname, resolve } from "path";
|
|
5
|
+
var DependencyGraph = class {
|
|
6
|
+
nodes = /* @__PURE__ */ new Map();
|
|
7
|
+
edges = [];
|
|
8
|
+
addNode(file, layer, imports) {
|
|
9
|
+
this.nodes.set(file, { id: file, layer, imports });
|
|
10
|
+
}
|
|
11
|
+
addEdge(from, to) {
|
|
12
|
+
this.edges.push({ from, to, type: "import" });
|
|
13
|
+
}
|
|
14
|
+
validateDependency(from, to, rules) {
|
|
15
|
+
const fromNode = this.nodes.get(from);
|
|
16
|
+
const toNode = this.nodes.get(to);
|
|
17
|
+
if (!fromNode || !toNode) return null;
|
|
18
|
+
for (const rule of rules) {
|
|
19
|
+
const fromMatches = rule.fromLayer === fromNode.layer || rule.fromLayer === "*";
|
|
20
|
+
const toMatches = rule.toLayer === toNode.layer || rule.toLayer === "*";
|
|
21
|
+
if (fromMatches && toMatches && !rule.allowed) {
|
|
22
|
+
return {
|
|
23
|
+
from,
|
|
24
|
+
fromLayer: fromNode.layer,
|
|
25
|
+
to,
|
|
26
|
+
toLayer: toNode.layer,
|
|
27
|
+
ruleName: rule.name,
|
|
28
|
+
path: [from, to]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
findViolations(rules) {
|
|
35
|
+
const violations = [];
|
|
36
|
+
for (const edge of this.edges) {
|
|
37
|
+
const v = this.validateDependency(edge.from, edge.to, rules);
|
|
38
|
+
if (v) violations.push(v);
|
|
39
|
+
}
|
|
40
|
+
return violations;
|
|
41
|
+
}
|
|
42
|
+
findPath(from, to) {
|
|
43
|
+
const visited = /* @__PURE__ */ new Set();
|
|
44
|
+
const queue = [[from, [from]]];
|
|
45
|
+
while (queue.length > 0) {
|
|
46
|
+
const [current, path] = queue.shift();
|
|
47
|
+
if (current === to) return path;
|
|
48
|
+
if (visited.has(current)) continue;
|
|
49
|
+
visited.add(current);
|
|
50
|
+
const node = this.nodes.get(current);
|
|
51
|
+
if (!node) continue;
|
|
52
|
+
for (const neighbor of node.imports) {
|
|
53
|
+
if (!visited.has(neighbor)) queue.push([neighbor, [...path, neighbor]]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
getNodes() {
|
|
59
|
+
return [...this.nodes.values()];
|
|
60
|
+
}
|
|
61
|
+
getEdges() {
|
|
62
|
+
return this.edges;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var GraphBuilder = class {
|
|
66
|
+
constructor(astAnalyzer, classifier, cwd = process.cwd()) {
|
|
67
|
+
this.astAnalyzer = astAnalyzer;
|
|
68
|
+
this.classifier = classifier;
|
|
69
|
+
this.cwd = cwd;
|
|
70
|
+
}
|
|
71
|
+
astAnalyzer;
|
|
72
|
+
classifier;
|
|
73
|
+
cwd;
|
|
74
|
+
async buildFromFiles(files) {
|
|
75
|
+
const graph = new DependencyGraph();
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const classified = this.classifier.classifyFile(file);
|
|
78
|
+
if (!classified) continue;
|
|
79
|
+
const astInfo = this.astAnalyzer.analyze(file);
|
|
80
|
+
const resolvedImports = astInfo.imports.filter((imp) => !imp.isExternal).map((imp) => this.resolveImport(file, imp.module));
|
|
81
|
+
graph.addNode(file, classified.layer, resolvedImports);
|
|
82
|
+
for (const resolved of resolvedImports) {
|
|
83
|
+
graph.addEdge(file, resolved);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return graph;
|
|
87
|
+
}
|
|
88
|
+
resolveImport(fromFile, importPath) {
|
|
89
|
+
if (importPath.startsWith(".")) {
|
|
90
|
+
return resolve(this.cwd, dirname(fromFile), importPath).replace(/\\/g, "/");
|
|
91
|
+
}
|
|
92
|
+
return importPath;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export {
|
|
96
|
+
DependencyGraph,
|
|
97
|
+
GraphBuilder
|
|
98
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/automation/incident-capture.ts
|
|
4
|
+
var IncidentCaptureService = class {
|
|
5
|
+
draftRule(incident) {
|
|
6
|
+
return {
|
|
7
|
+
id: `auto-incident-${Date.now()}`,
|
|
8
|
+
name: `[INCIDENT] ${incident.what.slice(0, 60)}`,
|
|
9
|
+
description: `Root cause: ${incident.why}. Applies to: ${incident.where}`,
|
|
10
|
+
fromLayer: "*",
|
|
11
|
+
toLayer: "*",
|
|
12
|
+
allowed: false,
|
|
13
|
+
severity: "critical",
|
|
14
|
+
enforcement: "block"
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
IncidentCaptureService
|
|
20
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
callChatModel
|
|
4
|
+
} from "./chunk-PQBWHAZN.js";
|
|
5
|
+
|
|
6
|
+
// src/models/ollama-judge.ts
|
|
7
|
+
function loadOllamaConfig(_configDir) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
var MAX_RETRIES = 3;
|
|
11
|
+
var BASE_DELAY_MS = 800;
|
|
12
|
+
function isRetryable(err) {
|
|
13
|
+
if (!(err instanceof Error)) return false;
|
|
14
|
+
const msg = err.message.toLowerCase();
|
|
15
|
+
if (msg.includes("timeout") || msg.includes("etimedout") || msg.includes("econnrefused") || msg.includes("fetch failed") || msg.includes("enotfound")) return true;
|
|
16
|
+
const code = err.cause?.code;
|
|
17
|
+
if (code && /^(ECONNREFUSED|ETIMEDOUT|ENOTFOUND|ECONNRESET|EPIPE)$/.test(code)) return true;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
async function withRetry(fn, retries = MAX_RETRIES) {
|
|
21
|
+
let lastErr;
|
|
22
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
|
23
|
+
try {
|
|
24
|
+
return await fn();
|
|
25
|
+
} catch (err) {
|
|
26
|
+
lastErr = err;
|
|
27
|
+
if (!isRetryable(err) || attempt === retries - 1) throw err;
|
|
28
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * (attempt + 1)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw lastErr;
|
|
32
|
+
}
|
|
33
|
+
var OllamaJudge = class {
|
|
34
|
+
constructor(_config) {
|
|
35
|
+
}
|
|
36
|
+
async judge(packet, memories = []) {
|
|
37
|
+
try {
|
|
38
|
+
const result = await withRetry(() => callChatModel([
|
|
39
|
+
{
|
|
40
|
+
role: "system",
|
|
41
|
+
content: "You are an architecture review system. Analyze evidence and return ONLY valid JSON with no extra text."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
role: "user",
|
|
45
|
+
content: buildPrompt(packet, memories)
|
|
46
|
+
}
|
|
47
|
+
]));
|
|
48
|
+
return calibrateConfidence(parseDecision(result.content), packet);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return fallbackToDeterministic(packet, err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function buildPrompt(packet, memories = []) {
|
|
55
|
+
const memoriesSection = memories.length > 0 ? `
|
|
56
|
+
PROJECT RULES FROM MEMORY:
|
|
57
|
+
${memories.map((m) => `- [${m.type}] ${m.content}${m.reason ? ` (reason: ${m.reason})` : ""}`).join("\n")}
|
|
58
|
+
` : "";
|
|
59
|
+
return `Analyze the following architecture evidence and return ONLY valid JSON.
|
|
60
|
+
|
|
61
|
+
RULES THAT APPLY:
|
|
62
|
+
${packet.matchedRules.map((r) => `- ${r.name} (${r.severity}): ${r.description}`).join("\n") || "None"}
|
|
63
|
+
${memoriesSection}
|
|
64
|
+
CHANGED FILES:
|
|
65
|
+
${packet.changedFiles.map((f) => `- ${f.file} (layer: ${f.layer})`).join("\n") || "None"}
|
|
66
|
+
|
|
67
|
+
IMPORTS ANALYZED:
|
|
68
|
+
${packet.imports.map((i) => `${i.file}:
|
|
69
|
+
${i.imports.map((imp) => ` - "${imp.module}"${imp.isExternal ? " [external]" : ""}`).join("\n")}`).join("\n") || "None"}
|
|
70
|
+
|
|
71
|
+
VIOLATIONS DETECTED:
|
|
72
|
+
${packet.violations.length > 0 ? packet.violations.map((v) => `- ${v.rule.name} (${v.rule.severity}): ${v.fromFile} \u2192 ${v.toFile}`).join("\n") : "None"}
|
|
73
|
+
|
|
74
|
+
GRAPH VIOLATIONS:
|
|
75
|
+
${packet.graphViolations.length > 0 ? packet.graphViolations.map((v) => `- ${v.ruleName}: ${v.fromLayer} \u2192 ${v.toLayer}`).join("\n") : "None"}
|
|
76
|
+
|
|
77
|
+
TASK:
|
|
78
|
+
1. Do the violations represent real rule breaches?
|
|
79
|
+
2. Are there hidden violations not yet detected?
|
|
80
|
+
3. Is this change safe to merge?
|
|
81
|
+
|
|
82
|
+
CONSTRAINTS:
|
|
83
|
+
- Only cite evidence from the packet above \u2014 do NOT invent project conventions
|
|
84
|
+
- If uncertain, return "warn" not "block"
|
|
85
|
+
- Return ONLY this JSON structure, no other text:
|
|
86
|
+
{
|
|
87
|
+
"decision": "allow",
|
|
88
|
+
"confidence": 0.9,
|
|
89
|
+
"violations": [],
|
|
90
|
+
"evidence": [],
|
|
91
|
+
"suggestedFix": "",
|
|
92
|
+
"reasoning": ""
|
|
93
|
+
}`;
|
|
94
|
+
}
|
|
95
|
+
function parseDecision(raw) {
|
|
96
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
97
|
+
if (!match) throw new Error("Model did not return valid JSON");
|
|
98
|
+
const parsed = JSON.parse(match[0]);
|
|
99
|
+
return {
|
|
100
|
+
decision: ["allow", "warn", "block"].includes(parsed.decision) ? parsed.decision : "warn",
|
|
101
|
+
confidence: typeof parsed.confidence === "number" ? Math.max(0, Math.min(1, parsed.confidence)) : 0.5,
|
|
102
|
+
violations: Array.isArray(parsed.violations) ? parsed.violations : [],
|
|
103
|
+
evidence: Array.isArray(parsed.evidence) ? parsed.evidence : [],
|
|
104
|
+
suggestedFix: parsed.suggestedFix ?? "",
|
|
105
|
+
reasoning: parsed.reasoning ?? ""
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function calibrateConfidence(raw, packet) {
|
|
109
|
+
const blockViolations = packet.violations.filter((v) => v.rule.enforcement === "block").length;
|
|
110
|
+
const warnViolations = packet.violations.filter((v) => v.rule.enforcement === "warn").length;
|
|
111
|
+
const graphViolationCount = packet.graphViolations.length;
|
|
112
|
+
const totalDeterministicViolations = blockViolations + warnViolations + graphViolationCount;
|
|
113
|
+
let adjusted = raw.confidence;
|
|
114
|
+
if (blockViolations > 0 || graphViolationCount > 0) {
|
|
115
|
+
adjusted = Math.max(adjusted, 0.85 + Math.min(blockViolations + graphViolationCount, 5) * 0.02);
|
|
116
|
+
} else if (totalDeterministicViolations === 0 && raw.decision === "block") {
|
|
117
|
+
adjusted = Math.min(adjusted, 0.6);
|
|
118
|
+
} else if (totalDeterministicViolations === 0 && raw.decision === "allow") {
|
|
119
|
+
adjusted = Math.max(adjusted, 0.75);
|
|
120
|
+
}
|
|
121
|
+
return { ...raw, confidence: Math.max(0, Math.min(1, adjusted)) };
|
|
122
|
+
}
|
|
123
|
+
function fallbackToDeterministic(packet, err) {
|
|
124
|
+
const hasBlock = packet.violations.some((v) => v.rule.enforcement === "block") || packet.graphViolations.length > 0;
|
|
125
|
+
return {
|
|
126
|
+
decision: hasBlock ? "block" : "warn",
|
|
127
|
+
confidence: 1,
|
|
128
|
+
violations: packet.violations.map((v) => v.rule.name),
|
|
129
|
+
evidence: ["Deterministic fallback (chat model unavailable)"],
|
|
130
|
+
suggestedFix: "Remove imports from blocked layers",
|
|
131
|
+
reasoning: `Chat model unavailable (${err instanceof Error ? err.message : String(err)}); used AST/graph analysis`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
OllamaJudge,
|
|
136
|
+
loadOllamaConfig
|
|
137
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/rate-limiter.ts
|
|
4
|
+
import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var LOCK_TIMEOUT_MS = 3e4;
|
|
7
|
+
var POLL_MS = 200;
|
|
8
|
+
var CheckRateLimiter = class {
|
|
9
|
+
lockPath;
|
|
10
|
+
constructor(rootPath) {
|
|
11
|
+
const dir = join(rootPath, ".archmind", "cache");
|
|
12
|
+
mkdirSync(dir, { recursive: true });
|
|
13
|
+
this.lockPath = join(dir, "check.lock");
|
|
14
|
+
}
|
|
15
|
+
async acquire() {
|
|
16
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
17
|
+
while (existsSync(this.lockPath)) {
|
|
18
|
+
if (Date.now() > deadline) {
|
|
19
|
+
try {
|
|
20
|
+
unlinkSync(this.lockPath);
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
writeFileSync(this.lockPath, String(process.pid), "utf-8");
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
return () => {
|
|
32
|
+
try {
|
|
33
|
+
unlinkSync(this.lockPath);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export {
|
|
40
|
+
CheckRateLimiter
|
|
41
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/rules.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var RuleMatcher = class _RuleMatcher {
|
|
7
|
+
constructor(rules) {
|
|
8
|
+
this.rules = rules;
|
|
9
|
+
}
|
|
10
|
+
rules;
|
|
11
|
+
findApplicableRules(fromLayer, toLayer) {
|
|
12
|
+
return this.rules.filter(
|
|
13
|
+
(rule) => (rule.fromLayer === fromLayer || rule.fromLayer === "*") && (rule.toLayer === toLayer || rule.toLayer === "*")
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
findViolatingRules(fromLayer, toLayer) {
|
|
17
|
+
const hasExplicitAllow = this.rules.some(
|
|
18
|
+
(r) => r.fromLayer === fromLayer && r.toLayer === toLayer && r.allowed
|
|
19
|
+
);
|
|
20
|
+
return this.findApplicableRules(fromLayer, toLayer).filter((rule) => {
|
|
21
|
+
if (rule.allowed) return false;
|
|
22
|
+
if (rule.fromLayer === "*" || rule.toLayer === "*") return !hasExplicitAllow;
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
scoreViolations(violations) {
|
|
27
|
+
if (violations.some((v) => v.severity === "critical")) return "block";
|
|
28
|
+
if (violations.some((v) => v.severity === "medium")) return "warn";
|
|
29
|
+
return "suggest";
|
|
30
|
+
}
|
|
31
|
+
getRulesForLayers(layers) {
|
|
32
|
+
return this.rules.filter(
|
|
33
|
+
(rule) => layers.includes(rule.fromLayer) || rule.fromLayer === "*"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
getAllRules() {
|
|
37
|
+
return this.rules;
|
|
38
|
+
}
|
|
39
|
+
static loadFromConfig(configDir) {
|
|
40
|
+
const rulesPath = join(configDir, "rules.json");
|
|
41
|
+
if (!existsSync(rulesPath)) {
|
|
42
|
+
return new _RuleMatcher(defaultRules());
|
|
43
|
+
}
|
|
44
|
+
const config = JSON.parse(readFileSync(rulesPath, "utf-8"));
|
|
45
|
+
return new _RuleMatcher(config.rules);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function defaultRules() {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
id: "domain-no-infra",
|
|
52
|
+
name: "Domain layer must not import infrastructure",
|
|
53
|
+
description: "Domain layer should depend only on itself and shared utilities",
|
|
54
|
+
fromLayer: "domain",
|
|
55
|
+
toLayer: "infrastructure",
|
|
56
|
+
allowed: false,
|
|
57
|
+
severity: "critical",
|
|
58
|
+
enforcement: "block"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "domain-no-api",
|
|
62
|
+
name: "Domain layer must not import API layer",
|
|
63
|
+
description: "Domain must remain framework-agnostic",
|
|
64
|
+
fromLayer: "domain",
|
|
65
|
+
toLayer: "api",
|
|
66
|
+
allowed: false,
|
|
67
|
+
severity: "critical",
|
|
68
|
+
enforcement: "block"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "api-no-infra-direct",
|
|
72
|
+
name: "API layer should not import infrastructure directly",
|
|
73
|
+
description: "HTTP handlers should use application/domain services, not infra directly",
|
|
74
|
+
fromLayer: "api",
|
|
75
|
+
toLayer: "infrastructure",
|
|
76
|
+
allowed: false,
|
|
77
|
+
severity: "medium",
|
|
78
|
+
enforcement: "warn"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "api-depends-on-domain",
|
|
82
|
+
name: "API layer should depend on domain layer",
|
|
83
|
+
description: "HTTP handlers should use domain services",
|
|
84
|
+
fromLayer: "api",
|
|
85
|
+
toLayer: "domain",
|
|
86
|
+
allowed: true,
|
|
87
|
+
severity: "low",
|
|
88
|
+
enforcement: "suggest"
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
RuleMatcher,
|
|
94
|
+
defaultRules
|
|
95
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ApprovalQueue
|
|
4
|
+
} from "./chunk-W6WEAV3S.js";
|
|
5
|
+
|
|
6
|
+
// src/automation/watch-errors.ts
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
function loadWatchConfig(configDir) {
|
|
11
|
+
const cfgPath = join(configDir, "watch-errors.json");
|
|
12
|
+
if (existsSync(cfgPath)) {
|
|
13
|
+
return JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
14
|
+
}
|
|
15
|
+
return defaultWatchConfig(configDir);
|
|
16
|
+
}
|
|
17
|
+
function defaultWatchConfig(configDir) {
|
|
18
|
+
return {
|
|
19
|
+
commands: ["npm test", "tsc --noEmit"],
|
|
20
|
+
patterns: {
|
|
21
|
+
"Cannot find module": { regex: "Cannot find module '([^']+)'", template: "Cannot find module" },
|
|
22
|
+
"Circular dependency": { regex: "Circular dependency detected: ([^ ]+)", template: "Circular dependency" },
|
|
23
|
+
"Type error": { regex: "error TS\\d+: (.+)", template: "TypeScript type error" }
|
|
24
|
+
},
|
|
25
|
+
outputDir: join(configDir, "auto-rules")
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var WatchErrors = class {
|
|
29
|
+
constructor(config, configDir) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.configDir = configDir;
|
|
32
|
+
this.queue = new ApprovalQueue(configDir);
|
|
33
|
+
if (!existsSync(config.outputDir)) {
|
|
34
|
+
mkdirSync(config.outputDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
config;
|
|
38
|
+
configDir;
|
|
39
|
+
processes = /* @__PURE__ */ new Map();
|
|
40
|
+
queue;
|
|
41
|
+
start(onDraft) {
|
|
42
|
+
for (const cmd of this.config.commands) {
|
|
43
|
+
const [bin, ...args] = cmd.split(" ");
|
|
44
|
+
const proc = spawn(bin, args, { shell: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
45
|
+
const handleLine = (line) => {
|
|
46
|
+
const rule = this.analyzeErrorLine(line);
|
|
47
|
+
if (rule) {
|
|
48
|
+
this.queue.add(rule, "watch", "low");
|
|
49
|
+
this.saveRuleToDisk(rule);
|
|
50
|
+
onDraft?.(rule);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
let buffer = "";
|
|
54
|
+
const flush = (chunk) => {
|
|
55
|
+
buffer += chunk.toString();
|
|
56
|
+
const lines = buffer.split("\n");
|
|
57
|
+
buffer = lines.pop() ?? "";
|
|
58
|
+
lines.forEach(handleLine);
|
|
59
|
+
};
|
|
60
|
+
proc.stdout?.on("data", flush);
|
|
61
|
+
proc.stderr?.on("data", flush);
|
|
62
|
+
proc.on("exit", () => this.processes.delete(cmd));
|
|
63
|
+
this.processes.set(cmd, proc);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
stop() {
|
|
67
|
+
for (const proc of this.processes.values()) {
|
|
68
|
+
proc.kill();
|
|
69
|
+
}
|
|
70
|
+
this.processes.clear();
|
|
71
|
+
}
|
|
72
|
+
analyzeErrorLine(line) {
|
|
73
|
+
for (const [key, pattern] of Object.entries(this.config.patterns)) {
|
|
74
|
+
const match = line.match(new RegExp(pattern.regex));
|
|
75
|
+
if (match) {
|
|
76
|
+
return {
|
|
77
|
+
id: `auto-watch-${Date.now()}`,
|
|
78
|
+
name: `[WATCH] ${key}: ${(match[1] ?? "").slice(0, 50)}`,
|
|
79
|
+
description: line.trim().slice(0, 200),
|
|
80
|
+
fromLayer: "*",
|
|
81
|
+
toLayer: "*",
|
|
82
|
+
allowed: true,
|
|
83
|
+
severity: "low",
|
|
84
|
+
enforcement: "suggest"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
saveRuleToDisk(rule) {
|
|
91
|
+
const filename = join(this.config.outputDir, `${rule.id}.json`);
|
|
92
|
+
writeFileSync(filename, JSON.stringify(rule, null, 2));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export {
|
|
96
|
+
WatchErrors,
|
|
97
|
+
defaultWatchConfig,
|
|
98
|
+
loadWatchConfig
|
|
99
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahmilsaari/memory-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
|
|
5
5
|
"homepage": "https://memory-core.shahmilsaari.my/",
|
|
6
6
|
"type": "module",
|