@shahmilsaari/memory-core 1.0.25 → 1.0.27

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.
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/classifier.ts
4
+ import { existsSync, readFileSync } from "fs";
5
+ import { join } from "path";
6
+ function globToRegex(pattern) {
7
+ const escaped = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "(?:.+/)?").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]+");
8
+ return new RegExp(`^${escaped}`);
9
+ }
10
+ function matchesGlob(filepath, pattern) {
11
+ return globToRegex(pattern).test(filepath);
12
+ }
13
+ var FileClassifier = class _FileClassifier {
14
+ constructor(layers) {
15
+ this.layers = layers;
16
+ }
17
+ layers;
18
+ classifyFile(filepath) {
19
+ const normalized = filepath.replace(/\\/g, "/");
20
+ for (const layer of this.layers) {
21
+ if (layer.paths.some((p) => matchesGlob(normalized, p))) {
22
+ return { file: filepath, layer: layer.name, isNew: false, isModified: false };
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ classifyDiff(diff) {
28
+ const result = [];
29
+ for (const file of diff.added) {
30
+ const c = this.classifyFile(file);
31
+ if (c) result.push({ ...c, isNew: true });
32
+ }
33
+ for (const file of diff.modified) {
34
+ const c = this.classifyFile(file);
35
+ if (c) result.push({ ...c, isModified: true });
36
+ }
37
+ return result;
38
+ }
39
+ static loadFromConfig(configDir) {
40
+ const layersPath = join(configDir, "layers.json");
41
+ if (!existsSync(layersPath)) {
42
+ return new _FileClassifier(defaultLayers());
43
+ }
44
+ const config = JSON.parse(readFileSync(layersPath, "utf-8"));
45
+ return new _FileClassifier(config.layers);
46
+ }
47
+ };
48
+ function defaultLayers() {
49
+ return [
50
+ { name: "domain", paths: ["src/domain/**", "src/core/domain/**"], description: "Business logic, entities, use cases" },
51
+ { name: "application", paths: ["src/application/**", "src/core/application/**"], description: "Use cases and orchestration" },
52
+ { name: "infrastructure", paths: ["src/infrastructure/**"], description: "Database, external APIs, low-level I/O" },
53
+ { name: "api", paths: ["src/api/**", "src/interfaces/**", "src/controllers/**"], description: "HTTP handlers, request/response" },
54
+ { name: "shared", paths: ["src/shared/**", "src/utils/**", "src/helpers/**"], description: "Utilities, helpers, constants" }
55
+ ];
56
+ }
57
+ export {
58
+ FileClassifier,
59
+ defaultLayers
60
+ };
package/dist/cli.js CHANGED
@@ -7,13 +7,11 @@ import {
7
7
  checkCommitMsg,
8
8
  checkFile,
9
9
  checkStaged,
10
- closePool,
11
10
  detectProject,
12
11
  findSchemaViolations,
13
12
  generate,
14
13
  getAllowPatterns,
15
14
  getDefaultApplicationContainer,
16
- getPool,
17
15
  inferProjectArchitectures,
18
16
  installHook,
19
17
  listProfiles,
@@ -25,12 +23,18 @@ import {
25
23
  readMemoryFileFromUrl,
26
24
  recordBypass,
27
25
  retrieveMemorySelection,
28
- runMigrations,
29
26
  seeds,
30
27
  toPortableMemory,
31
28
  uninstallHook,
32
29
  writeMemoryFile
33
- } from "./chunk-35ZWQFTO.js";
30
+ } from "./chunk-3XTHE74V.js";
31
+ import "./chunk-PQBWHAZN.js";
32
+ import {
33
+ closePool,
34
+ getPool,
35
+ runMigrations
36
+ } from "./chunk-M7NKSXFS.js";
37
+ import "./chunk-ZZBQEXEO.js";
34
38
 
35
39
  // src/cli.ts
36
40
  import { Command } from "commander";
@@ -40,6 +44,7 @@ import ora from "ora";
40
44
  import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, rmSync } from "fs";
41
45
  import { join, dirname, resolve } from "path";
42
46
  import { homedir } from "os";
47
+ import { spawnSync as spawnSync2 } from "child_process";
43
48
 
44
49
  // src/remote-install.ts
45
50
  import { spawnSync } from "child_process";
@@ -125,8 +130,15 @@ function printBanner(projectName, agentCount, status) {
125
130
  }
126
131
  var { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
127
132
  var CONFIG_FILE = ".memory-core.json";
128
- var LOCAL_GENERATED_FILES = [".memory-core-stats.json"];
133
+ var LOCAL_GENERATED_FILES = [".memory-core-stats.json", ".memory-core-db-version"];
129
134
  var LOCAL_STATE_FILES = [CONFIG_FILE, ".memory-core.env", ...LOCAL_GENERATED_FILES];
135
+ var ARCHMIND_RUNTIME_FILES = [
136
+ ".archmind/approval-queue.json",
137
+ ".archmind/watch-errors.json",
138
+ ".archmind/check.log",
139
+ ".archmind/cache/",
140
+ ".archmind/violation-history.json"
141
+ ];
130
142
  var CI_WORKFLOW_FILE = ".github/workflows/memory-core.yml";
131
143
  var GITIGNORE_HEADING = "# memory-core generated files";
132
144
  var DEFAULT_OLLAMA_URL = "http://localhost:11434";
@@ -134,10 +146,7 @@ var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
134
146
  var DEFAULT_CHAT_MODEL = "llama3.2";
135
147
  var phase1 = getDefaultApplicationContainer();
136
148
  function getEnvPath() {
137
- const memoryEnv = join(process.cwd(), ".memory-core.env");
138
- if (existsSync(memoryEnv)) return memoryEnv;
139
- const dotEnv = join(process.cwd(), ".env");
140
- return existsSync(dotEnv) ? dotEnv : memoryEnv;
149
+ return join(process.cwd(), ".memory-core.env");
141
150
  }
142
151
  function getWriteEnvPath() {
143
152
  return join(process.cwd(), ".memory-core.env");
@@ -718,7 +727,7 @@ program.command("init").description("Initialize memory-core in the current proje
718
727
  };
719
728
  writeRuntimeEnv(envValues, envPath);
720
729
  applyRuntimeEnv(envValues);
721
- appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
730
+ appendMissingGitignoreEntries([...LOCAL_STATE_FILES, ...ARCHMIND_RUNTIME_FILES], GITIGNORE_HEADING);
722
731
  console.log(chalk.green(" \u2713 .memory-core.env created with local defaults"));
723
732
  } else if (!hasEnv) {
724
733
  console.log(chalk.dim(" No .memory-core.env found \u2014 let's set up your connection.\n"));
@@ -866,7 +875,7 @@ program.command("init").description("Initialize memory-core in the current proje
866
875
  if (chatApiKey) envValues.CHAT_API_KEY = chatApiKey;
867
876
  writeRuntimeEnv(envValues, envPath);
868
877
  applyRuntimeEnv(envValues);
869
- appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
878
+ appendMissingGitignoreEntries([...LOCAL_STATE_FILES, ...ARCHMIND_RUNTIME_FILES], GITIGNORE_HEADING);
870
879
  console.log(chalk.green("\n \u2713 .memory-core.env created"));
871
880
  console.log(chalk.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
872
881
  } else {
@@ -1052,7 +1061,35 @@ program.command("init").description("Initialize memory-core in the current proje
1052
1061
  );
1053
1062
  writeProjectConfig(config);
1054
1063
  spinner.succeed(`Generated ${written.written.length} files`);
1055
- const gitignoreEntries = [...written.written, ...LOCAL_GENERATED_FILES];
1064
+ try {
1065
+ const archmindDir = join(process.cwd(), ".archmind");
1066
+ mkdirSync(archmindDir, { recursive: true });
1067
+ const layersPath = join(archmindDir, "layers.json");
1068
+ const rulesPath = join(archmindDir, "rules.json");
1069
+ const archmindGitignorePath = join(archmindDir, ".gitignore");
1070
+ if (!existsSync(layersPath)) {
1071
+ const arch2 = backendArchitecture ?? frontendFramework ?? "custom";
1072
+ const layersTemplate = buildLayersTemplate(arch2);
1073
+ writeFileSync(layersPath, JSON.stringify(layersTemplate, null, 2) + "\n", "utf-8");
1074
+ console.log(chalk.green(" \u2713 Created .archmind/layers.json"));
1075
+ }
1076
+ if (!existsSync(rulesPath)) {
1077
+ writeFileSync(rulesPath, JSON.stringify(DEFAULT_ARCH_RULES, null, 2) + "\n", "utf-8");
1078
+ console.log(chalk.green(" \u2713 Created .archmind/rules.json"));
1079
+ }
1080
+ if (!existsSync(archmindGitignorePath)) {
1081
+ writeFileSync(archmindGitignorePath, "approval-queue.json\nwatch-errors.json\n", "utf-8");
1082
+ }
1083
+ } catch {
1084
+ }
1085
+ try {
1086
+ const graphSpinner = ora(" Building dependency graph snapshot\u2026").start();
1087
+ const { snapshot } = await phase1.services.graphEngine.buildAndStoreSnapshot({ cwd: process.cwd() });
1088
+ graphSpinner.succeed(` Graph snapshot ready (${snapshot.nodes.length} nodes, ${snapshot.edges.length} edges)`);
1089
+ } catch {
1090
+ console.log(chalk.dim(" (graph build skipped \u2014 run `memory-core graph build` once manually)"));
1091
+ }
1092
+ const gitignoreEntries = [...written.written, ...LOCAL_GENERATED_FILES, ...ARCHMIND_RUNTIME_FILES];
1056
1093
  if (gitignoreEntries.length > 0) {
1057
1094
  const added = appendMissingGitignoreEntries(gitignoreEntries, GITIGNORE_HEADING);
1058
1095
  if (added > 0) {
@@ -1715,7 +1752,7 @@ program.command("dashboard").description("Start the live Svelte dashboard with W
1715
1752
  }
1716
1753
  return void 0;
1717
1754
  };
1718
- const { startDashboard } = await import("./dashboard-server-SSYZLQKB.js");
1755
+ const { startDashboard } = await import("./dashboard-server-TWLZQE2J.js");
1719
1756
  await startDashboard({
1720
1757
  port: parseInt(opts.port, 10),
1721
1758
  path: resolveDashboardPath(),
@@ -2182,7 +2219,241 @@ hook.command("bypass-prompt").description("Prompt developer for a bypass reason
2182
2219
  }
2183
2220
  await closePool();
2184
2221
  });
2185
- program.command("check").description("Check staged changes against architecture rules (used by pre-commit hook)").option("--staged", "Check git staged diff (default behaviour)").option("--ci", `Check CI diff using ${MEMORY_FILE}`).option("--all", "Check all tracked source files, including already-committed files").option("--path <dir>", "Directory to check for --all mode (default: current directory)").option("--file <path>", "Check a specific file retroactively against all rules").option("--commit-msg [file]", "Check commit message (defaults to .git/COMMIT_EDITMSG)").option("--verbose", "Show model and diff details").option("--debug", "Show prompt, diff, and raw model response").option("--fast", "Skip AI and memory retrieval; run deterministic checks only").option("--dry-run", "Show what would be flagged without blocking the commit").action(async (opts) => {
2222
+ program.command("check").description("Check staged changes against architecture rules (used by pre-commit hook)").option("--staged", "Check git staged diff (default behaviour)").option("--ci", `Check CI diff using ${MEMORY_FILE}`).option("--all", "Check all tracked source files, including already-committed files").option("--path <dir>", "Directory to check for --all mode (default: current directory)").option("--file <path>", "Check a specific file retroactively against all rules").option("--commit-msg [file]", "Check commit message (defaults to .git/COMMIT_EDITMSG)").option("--verbose", "Show model and diff details").option("--debug", "Show prompt, diff, and raw model response").option("--fast", "Skip AI and memory retrieval; run deterministic checks only").option("--dry-run", "Show what would be flagged without blocking the commit").option("--diff <ref>", "Check a git diff for architecture violations (evidence-based)").option("--second-opinion", "Run a second critique pass for uncertain decisions (use with --diff)").option("--json", "Output full JSON result (use with --diff)").action(async (opts) => {
2223
+ if (opts.diff !== void 0) {
2224
+ const { values: envValues } = readRuntimeEnv();
2225
+ applyRuntimeEnv(envValues);
2226
+ const startMs = Date.now();
2227
+ const diffRef = opts.diff || "HEAD~1";
2228
+ const diag = opts.json ? (msg) => process.stderr.write(msg + "\n") : (msg) => console.log(msg);
2229
+ const diagSpinner = (text) => ora({ text, stream: opts.json ? process.stderr : process.stdout });
2230
+ const { CheckRateLimiter } = await import("./rate-limiter-SLIPCXRF.js");
2231
+ const release = await new CheckRateLimiter(process.cwd()).acquire();
2232
+ process.on("exit", release);
2233
+ const { FileClassifier } = await import("./classifier-MZ65R7FK.js");
2234
+ const { ASTAnalyzer } = await import("./ast-analyzer-JM4CIOFY.js");
2235
+ const { GraphBuilder } = await import("./graph-TFNTB5OK.js");
2236
+ const { RuleMatcher } = await import("./rules-V3QMN3AR.js");
2237
+ const { EvidencePacketBuilder } = await import("./evidence-HVMSONTT.js");
2238
+ const { DeterministicValidator } = await import("./deterministic-validator-PP56B46I.js");
2239
+ const { OllamaJudge } = await import("./ollama-judge-D2LFK5PB.js");
2240
+ const { DeepSeekCritique } = await import("./deepseek-critique-MALVIYGF.js");
2241
+ const { ConfidenceGate, defaultGateConfig } = await import("./confidence-gate-ZQDAOS6P.js");
2242
+ const { CheckCache } = await import("./check-cache-6NWRTZJD.js");
2243
+ const { CheckLogger } = await import("./check-logger-5HYSWA3S.js");
2244
+ const configDir = getArchmindDir();
2245
+ const classifier = FileClassifier.loadFromConfig(configDir);
2246
+ const ruleMatcher = RuleMatcher.loadFromConfig(configDir);
2247
+ const astAnalyzer = new ASTAnalyzer(process.cwd());
2248
+ const graphBuilder = new GraphBuilder(astAnalyzer, classifier, process.cwd());
2249
+ const logger = new CheckLogger(process.cwd());
2250
+ let diffOutput = "";
2251
+ try {
2252
+ const result = spawnSync2("git", ["diff", "--name-status", diffRef], { encoding: "utf-8" });
2253
+ if (result.status !== 0) throw new Error(result.stderr);
2254
+ diffOutput = result.stdout;
2255
+ } catch {
2256
+ console.error("Could not run git diff against " + diffRef);
2257
+ process.exit(1);
2258
+ }
2259
+ const added = [];
2260
+ const modified = [];
2261
+ const deleted = [];
2262
+ for (const line of diffOutput.split("\n").filter(Boolean)) {
2263
+ const [status, ...rest] = line.split(" ");
2264
+ const file = rest.join(" ").trim();
2265
+ if (!file) continue;
2266
+ if (status.startsWith("A")) added.push(file);
2267
+ else if (status.startsWith("D")) deleted.push(file);
2268
+ else modified.push(file);
2269
+ }
2270
+ const allChanged = [...added, ...modified].filter((f) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
2271
+ const graph2 = await graphBuilder.buildFromFiles(allChanged);
2272
+ const builder = new EvidencePacketBuilder(process.cwd());
2273
+ const packet = await builder.build({ added, modified, deleted }, astAnalyzer, classifier, ruleMatcher, graph2);
2274
+ try {
2275
+ const fullGraph = await phase1.services.graphEngine.latest(process.cwd());
2276
+ if (fullGraph) {
2277
+ const snapshotAge = fullGraph.createdAt ? Date.now() - new Date(fullGraph.createdAt).getTime() : null;
2278
+ const STALE_MS = 24 * 60 * 60 * 1e3;
2279
+ if (snapshotAge !== null && snapshotAge > STALE_MS) {
2280
+ const hours = Math.round(snapshotAge / 36e5);
2281
+ diag(chalk.yellow(` \u26A0 Graph snapshot is ${hours}h old \u2014 run \`memory-core graph build\` to refresh`));
2282
+ }
2283
+ const changedSet = /* @__PURE__ */ new Set([...added, ...modified]);
2284
+ for (const edge of fullGraph.edges) {
2285
+ if (!changedSet.has(edge.from) || edge.to.startsWith("pkg:")) continue;
2286
+ const fromClass = classifier.classifyFile(edge.from);
2287
+ const toClass = classifier.classifyFile(edge.to);
2288
+ if (!fromClass || !toClass) continue;
2289
+ const violating = ruleMatcher.findViolatingRules(fromClass.layer, toClass.layer);
2290
+ for (const rule of violating) {
2291
+ const alreadyReported = packet.graphViolations.some(
2292
+ (v) => v.from === edge.from && v.to === edge.to && v.ruleName === rule.name
2293
+ );
2294
+ if (!alreadyReported) {
2295
+ packet.graphViolations.push({
2296
+ from: edge.from,
2297
+ fromLayer: fromClass.layer,
2298
+ to: edge.to,
2299
+ toLayer: toClass.layer,
2300
+ ruleName: rule.name,
2301
+ path: [edge.from, edge.to]
2302
+ });
2303
+ }
2304
+ }
2305
+ }
2306
+ } else {
2307
+ diag(chalk.yellow(" \u26A0 No graph snapshot \u2014 building now for full codebase enforcement\u2026"));
2308
+ const autoSpinner = diagSpinner(" Building dependency graph\u2026").start();
2309
+ try {
2310
+ const buildResult = await Promise.race([
2311
+ phase1.services.graphEngine.buildAndStoreSnapshot({ cwd: process.cwd() }),
2312
+ new Promise((_, reject) => setTimeout(() => reject(new Error("graph build timeout")), 15e3))
2313
+ ]);
2314
+ autoSpinner.succeed(` Graph snapshot ready (${buildResult.snapshot.nodes.length} nodes, ${buildResult.snapshot.edges.length} edges)`);
2315
+ const freshGraph = await phase1.services.graphEngine.latest(process.cwd());
2316
+ if (freshGraph) {
2317
+ const changedSet2 = /* @__PURE__ */ new Set([...added, ...modified]);
2318
+ for (const edge of freshGraph.edges) {
2319
+ if (!changedSet2.has(edge.from) || edge.to.startsWith("pkg:")) continue;
2320
+ const fromClass = classifier.classifyFile(edge.from);
2321
+ const toClass = classifier.classifyFile(edge.to);
2322
+ if (!fromClass || !toClass) continue;
2323
+ const violating = ruleMatcher.findViolatingRules(fromClass.layer, toClass.layer);
2324
+ for (const rule of violating) {
2325
+ const alreadyReported = packet.graphViolations.some(
2326
+ (v) => v.from === edge.from && v.to === edge.to && v.ruleName === rule.name
2327
+ );
2328
+ if (!alreadyReported) {
2329
+ packet.graphViolations.push({
2330
+ from: edge.from,
2331
+ fromLayer: fromClass.layer,
2332
+ to: edge.to,
2333
+ toLayer: toClass.layer,
2334
+ ruleName: rule.name,
2335
+ path: [edge.from, edge.to]
2336
+ });
2337
+ }
2338
+ }
2339
+ }
2340
+ }
2341
+ } catch (e) {
2342
+ autoSpinner.stop();
2343
+ const msg = e instanceof Error ? e.message : String(e);
2344
+ diag(chalk.dim(` (auto-build skipped: ${msg} \u2014 run \`memory-core graph build\` manually)`));
2345
+ }
2346
+ }
2347
+ } catch {
2348
+ }
2349
+ const det = new DeterministicValidator();
2350
+ if (det.validate(packet) === "block") {
2351
+ const out = {
2352
+ decision: "block",
2353
+ source: "deterministic",
2354
+ confidence: 1,
2355
+ violations: packet.violations.map((v) => ({ rule: v.rule.name, from: v.fromFile, to: v.toFile })),
2356
+ graphViolations: packet.graphViolations,
2357
+ ...opts.json ? { evidence: packet } : {}
2358
+ };
2359
+ console.log(JSON.stringify(out, null, 2));
2360
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: "block", source: "deterministic", confidence: 1, violationCount: packet.violations.length, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs, fast: opts.fast });
2361
+ process.exit(1);
2362
+ }
2363
+ if (opts.fast) {
2364
+ const out = { decision: "allow", source: "deterministic", confidence: 1, violations: [], graphViolations: packet.graphViolations };
2365
+ if (opts.json) {
2366
+ console.log(JSON.stringify(out, null, 2));
2367
+ } else {
2368
+ diag(chalk.green("\n Decision: ALLOW") + chalk.dim(" (source: deterministic+fast, confidence: 1.00)"));
2369
+ }
2370
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: "allow", source: "deterministic", confidence: 1, violationCount: 0, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs, fast: true });
2371
+ return;
2372
+ }
2373
+ let storedMemories = [];
2374
+ try {
2375
+ const { listMemories } = await import("./db-PRDHI2CN.js");
2376
+ const allMemories = await listMemories({ limit: 1e4 });
2377
+ const ranked = allMemories.filter((m) => ["rule", "pattern", "decision"].includes(m.type));
2378
+ const changedLayers = packet.layersAffected;
2379
+ const scored = ranked.map((m) => {
2380
+ const text = m.content.toLowerCase();
2381
+ const score = changedLayers.reduce((s, l) => s + (text.includes(l) ? 2 : 0), 0);
2382
+ return { m, score };
2383
+ });
2384
+ scored.sort((a, b) => b.score - a.score);
2385
+ storedMemories = scored.slice(0, 30).map(({ m }) => ({
2386
+ type: m.type,
2387
+ content: m.content,
2388
+ reason: m.reason ?? void 0
2389
+ }));
2390
+ } catch {
2391
+ }
2392
+ const cache = new CheckCache(process.cwd());
2393
+ const mHash = cache.memoriesHash(storedMemories);
2394
+ const rHash = cache.rulesHash(configDir);
2395
+ const cacheKey = cache.key(diffOutput, mHash, rHash);
2396
+ const cached = !opts.secondOpinion && cache.get(cacheKey);
2397
+ if (cached) {
2398
+ diag(chalk.dim(" (cached result)"));
2399
+ const output2 = {
2400
+ decision: cached.decision,
2401
+ source: cached.source + "+cache",
2402
+ violations: cached.violations,
2403
+ suggestedFix: cached.suggestedFix,
2404
+ reasoning: cached.reasoning,
2405
+ confidence: cached.confidence
2406
+ };
2407
+ if (opts.json) {
2408
+ console.log(JSON.stringify(output2, null, 2));
2409
+ } else {
2410
+ const color = cached.decision === "block" ? chalk.red : cached.decision === "warn" ? chalk.yellow : chalk.green;
2411
+ diag(color(`
2412
+ Decision: ${cached.decision.toUpperCase()}`) + chalk.dim(` (source: ${cached.source}+cache, confidence: ${cached.confidence.toFixed(2)})`));
2413
+ }
2414
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: cached.decision, source: cached.source + "+cache", confidence: cached.confidence, violationCount: cached.violations.length, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs, cached: true });
2415
+ if (cached.decision === "block") process.exit(1);
2416
+ return;
2417
+ }
2418
+ const chatModel = process.env.CHAT_MODEL ?? process.env.OLLAMA_CHAT_MODEL ?? "unknown";
2419
+ const provider2 = process.env.CHAT_PROVIDER ?? "ollama";
2420
+ const spinner = diagSpinner(` Analyzing with ${provider2}/${chatModel}\u2026`).start();
2421
+ const primaryDecision = await new OllamaJudge().judge(packet, storedMemories);
2422
+ spinner.stop();
2423
+ const critiqueEnabled = opts.secondOpinion ?? false;
2424
+ const critiqueFn = critiqueEnabled ? async (p) => new DeepSeekCritique().critique(p, primaryDecision) : async () => ({ decision: primaryDecision.decision, confidence: primaryDecision.confidence, reasoning: "skipped", override: false });
2425
+ const gateDecision = await new ConfidenceGate(defaultGateConfig(critiqueEnabled)).decide(packet, primaryDecision, critiqueFn);
2426
+ cache.set(cacheKey, {
2427
+ decision: gateDecision.final,
2428
+ source: gateDecision.source,
2429
+ confidence: primaryDecision.confidence,
2430
+ violations: primaryDecision.violations,
2431
+ reasoning: primaryDecision.reasoning,
2432
+ suggestedFix: primaryDecision.suggestedFix
2433
+ });
2434
+ const output = {
2435
+ decision: gateDecision.final,
2436
+ source: gateDecision.source,
2437
+ violations: primaryDecision.violations,
2438
+ suggestedFix: primaryDecision.suggestedFix,
2439
+ reasoning: primaryDecision.reasoning,
2440
+ confidence: primaryDecision.confidence,
2441
+ ...opts.json ? { judge: primaryDecision, critique: gateDecision.deepseek, selfReview: gateDecision.selfReview, evidence: packet } : {}
2442
+ };
2443
+ if (opts.json) {
2444
+ console.log(JSON.stringify(output, null, 2));
2445
+ } else {
2446
+ const color = gateDecision.final === "block" ? chalk.red : gateDecision.final === "warn" ? chalk.yellow : chalk.green;
2447
+ diag(color(`
2448
+ Decision: ${gateDecision.final.toUpperCase()}`) + chalk.dim(` (source: ${gateDecision.source}, confidence: ${primaryDecision.confidence.toFixed(2)})`));
2449
+ if (primaryDecision.violations.length > 0) diag(chalk.dim(` Violations: ${primaryDecision.violations.join(", ")}`));
2450
+ if (primaryDecision.suggestedFix) diag(chalk.dim(` Fix: ${primaryDecision.suggestedFix}`));
2451
+ diag("");
2452
+ }
2453
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: gateDecision.final, source: gateDecision.source, confidence: primaryDecision.confidence, violationCount: primaryDecision.violations.length, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs });
2454
+ if (gateDecision.final === "block") process.exit(1);
2455
+ return;
2456
+ }
2186
2457
  if (opts.file) {
2187
2458
  await checkFile(opts.file, { verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false, dryRun: opts.dryRun ?? false });
2188
2459
  await closePool();
@@ -2289,4 +2560,134 @@ program.command("watch").description("Watch source files and check violations in
2289
2560
  debug: opts.debug
2290
2561
  });
2291
2562
  });
2563
+ var ARCHMIND_DIR = join(process.cwd(), ".archmind");
2564
+ var DEFAULT_ARCH_RULES = {
2565
+ rules: [
2566
+ { id: "domain-no-infra", name: "Domain must not import infrastructure", fromLayer: "domain", toLayer: "infrastructure", allowed: false, severity: "critical", enforcement: "block" },
2567
+ { id: "domain-no-api", name: "Domain must not import API layer", fromLayer: "domain", toLayer: "api", allowed: false, severity: "critical", enforcement: "block" },
2568
+ { id: "api-no-infra-direct", name: "API should not import infrastructure directly", fromLayer: "api", toLayer: "infrastructure", allowed: false, severity: "medium", enforcement: "warn" },
2569
+ { id: "api-depends-on-domain", name: "API depends on domain", fromLayer: "api", toLayer: "domain", allowed: true, severity: "low", enforcement: "suggest" },
2570
+ { id: "no-circular", name: "No circular dependencies", fromLayer: "*", toLayer: "*", allowed: false, severity: "critical", enforcement: "block" },
2571
+ { id: "application-no-api", name: "Application must not import API layer", fromLayer: "application", toLayer: "api", allowed: false, severity: "critical", enforcement: "block" },
2572
+ { id: "application-uses-domain", name: "Application depends on domain", fromLayer: "application", toLayer: "domain", allowed: true, severity: "low", enforcement: "suggest" },
2573
+ { id: "shared-no-domain", name: "Shared must not import domain", fromLayer: "shared", toLayer: "domain", allowed: false, severity: "medium", enforcement: "warn" }
2574
+ ]
2575
+ };
2576
+ function buildLayersTemplate(arch2) {
2577
+ const mvc = ["nestjs", "mvc", "laravel", "go-api"].includes(arch2);
2578
+ const frontend = ["react", "vue", "angular", "svelte", "nuxt", "react-native"].includes(arch2);
2579
+ if (frontend) {
2580
+ return {
2581
+ layers: [
2582
+ { name: "domain", paths: ["src/domain/**", "src/store/**", "src/state/**"], description: "State, models, business logic" },
2583
+ { name: "application", paths: ["src/services/**", "src/hooks/**", "src/composables/**"], description: "App services and hooks" },
2584
+ { name: "infrastructure", paths: ["src/api/**", "src/lib/**", "src/clients/**"], description: "External API clients, adapters" },
2585
+ { name: "shared", paths: ["src/utils/**", "src/helpers/**", "src/constants/**"], description: "Shared utilities" }
2586
+ ]
2587
+ };
2588
+ }
2589
+ if (mvc) {
2590
+ return {
2591
+ layers: [
2592
+ { name: "domain", paths: ["src/models/**", "src/entities/**"], description: "Data models and entities" },
2593
+ { name: "application", paths: ["src/services/**"], description: "Business logic services" },
2594
+ { name: "infrastructure", paths: ["src/repositories/**", "src/database/**", "src/infrastructure/**"], description: "DB, external APIs" },
2595
+ { name: "api", paths: ["src/controllers/**", "src/routes/**", "src/handlers/**"], description: "HTTP handlers" },
2596
+ { name: "shared", paths: ["src/utils/**", "src/helpers/**", "src/shared/**"], description: "Shared utilities" }
2597
+ ]
2598
+ };
2599
+ }
2600
+ return {
2601
+ layers: [
2602
+ { name: "domain", paths: ["src/domain/**", "src/core/domain/**", "src/modules/*/domain/**"], description: "Business logic, entities, use cases" },
2603
+ { name: "application", paths: ["src/application/**", "src/core/application/**", "src/modules/*/application/**"], description: "Use cases and orchestration" },
2604
+ { name: "infrastructure", paths: ["src/infrastructure/**", "src/modules/*/infrastructure/**"], description: "Database, external APIs, low-level I/O" },
2605
+ { name: "api", paths: ["src/api/**", "src/interfaces/**", "src/controllers/**"], description: "HTTP handlers, request/response" },
2606
+ { name: "shared", paths: ["src/shared/**", "src/utils/**", "src/helpers/**"], description: "Utilities, helpers, constants" }
2607
+ ]
2608
+ };
2609
+ }
2610
+ function getArchmindDir() {
2611
+ return ARCHMIND_DIR;
2612
+ }
2613
+ var arch = program.command("arch").description("Architecture enforcement: check, review captured rules, watch for errors, log incidents");
2614
+ arch.command("review").description("Review and approve/reject auto-captured architecture rules").option("--approve-all", "Approve everything without prompting").option("--reject-all", "Reject everything without prompting").action(async (opts) => {
2615
+ const { ApprovalQueue } = await import("./approval-queue-YBYRGBHP.js");
2616
+ const { join: join2 } = await import("path");
2617
+ const configDir = getArchmindDir();
2618
+ const queue = new ApprovalQueue(configDir);
2619
+ const pending = queue.pending();
2620
+ if (pending.length === 0) {
2621
+ console.log(chalk.dim(" No pending rules."));
2622
+ return;
2623
+ }
2624
+ console.log(chalk.bold(`
2625
+ Pending rules (${pending.length}):
2626
+ `));
2627
+ if (opts.approveAll) {
2628
+ for (const item of pending) queue.approve(item.id);
2629
+ const count2 = queue.persistApproved(join2(configDir, "rules.json"));
2630
+ console.log(chalk.green(` \u2713 Approved and saved ${count2} rules`));
2631
+ return;
2632
+ }
2633
+ if (opts.rejectAll) {
2634
+ for (const item of pending) queue.reject(item.id);
2635
+ console.log(chalk.yellow(` \u2717 Rejected ${pending.length} rules`));
2636
+ return;
2637
+ }
2638
+ for (const item of pending) {
2639
+ console.log(chalk.cyan(` [${item.source.toUpperCase()}]`) + ` ${item.rule.name}`);
2640
+ console.log(chalk.dim(` ${item.rule.description}`));
2641
+ console.log(chalk.dim(` captured: ${item.capturedAt} confidence: ${item.confidence}`));
2642
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
2643
+ const accept = await confirm2({ message: " Keep this rule?" });
2644
+ accept ? queue.approve(item.id) : queue.reject(item.id);
2645
+ console.log(accept ? chalk.green(" \u2713 Kept") : chalk.yellow(" \u2717 Dropped"));
2646
+ console.log();
2647
+ }
2648
+ const count = queue.persistApproved(join2(configDir, "rules.json"));
2649
+ if (count > 0) console.log(chalk.green(` \u2713 Saved ${count} rules to .archmind/rules.json`));
2650
+ });
2651
+ arch.command("watch").description("Watch your dev processes and auto-draft rules from errors").action(async () => {
2652
+ const { WatchErrors, loadWatchConfig } = await import("./watch-errors-B3FA26N4.js");
2653
+ const configDir = getArchmindDir();
2654
+ const config = loadWatchConfig(configDir);
2655
+ console.log(chalk.cyan("\n Watching:"));
2656
+ for (const cmd of config.commands) console.log(chalk.dim(` \u2022 ${cmd}`));
2657
+ console.log(chalk.dim("\n Press Ctrl+C to stop.\n"));
2658
+ const watcher = new WatchErrors(config, configDir);
2659
+ watcher.start((rule) => {
2660
+ console.log(chalk.yellow(` [DRAFT] ${rule.name}`));
2661
+ console.log(chalk.dim(` ${rule.description.slice(0, 100)}`));
2662
+ console.log(chalk.dim(' Run "memory-core arch review" to approve.\n'));
2663
+ });
2664
+ process.on("SIGINT", () => {
2665
+ watcher.stop();
2666
+ console.log(chalk.dim("\n Stopped."));
2667
+ process.exit(0);
2668
+ });
2669
+ await new Promise(() => {
2670
+ });
2671
+ });
2672
+ arch.command("incident").description("Log a production incident and capture it as an architecture rule").action(async () => {
2673
+ const { input: input2, confirm: confirm2 } = await import("@inquirer/prompts");
2674
+ const { IncidentCaptureService } = await import("./incident-capture-RVPZULS7.js");
2675
+ const { ApprovalQueue } = await import("./approval-queue-YBYRGBHP.js");
2676
+ console.log(chalk.cyan("\n Incident capture\n"));
2677
+ const what = await input2({ message: "What broke?" });
2678
+ const why = await input2({ message: "Root cause?" });
2679
+ const where = await input2({ message: "Which file / layer / pattern?" });
2680
+ const rule = new IncidentCaptureService().draftRule({ what, why, where, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2681
+ console.log(chalk.dim(`
2682
+ Rule: ${rule.name}`));
2683
+ console.log(chalk.dim(` Description: ${rule.description}
2684
+ `));
2685
+ const save = await confirm2({ message: "Save to approval queue?" });
2686
+ if (save) {
2687
+ new ApprovalQueue(getArchmindDir()).add(rule, "incident", "high");
2688
+ console.log(chalk.green(' \u2713 Saved. Run "memory-core arch review" to approve.\n'));
2689
+ } else {
2690
+ console.log(chalk.dim(" Discarded.\n"));
2691
+ }
2692
+ });
2292
2693
  program.parseAsync(process.argv);
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/models/self-review.ts
4
+ var SelfReviewValidator = class {
5
+ validate(packet, decision) {
6
+ const issues = [];
7
+ const hasBlockViolations = packet.violations.some((v) => v.rule.enforcement === "block");
8
+ const hasGraphViolations = packet.graphViolations.length > 0;
9
+ if (decision === "allow" && hasBlockViolations) {
10
+ issues.push('Decision is "allow" but critical violations are present');
11
+ }
12
+ if (decision === "block" && !hasBlockViolations && !hasGraphViolations) {
13
+ issues.push("Block decision but no deterministic violations found \u2014 possible false positive");
14
+ }
15
+ if (decision === "allow" && hasGraphViolations) {
16
+ issues.push('Decision is "allow" but graph violations are present');
17
+ }
18
+ return {
19
+ valid: issues.length === 0,
20
+ reason: issues.length === 0 ? "Decision is supported by evidence" : `${issues.length} issue(s) found`,
21
+ issues
22
+ };
23
+ }
24
+ };
25
+
26
+ // src/models/confidence-gate.ts
27
+ var ConfidenceGate = class {
28
+ constructor(config) {
29
+ this.config = config;
30
+ }
31
+ config;
32
+ selfReview = new SelfReviewValidator();
33
+ async decide(packet, ollama, deepseek) {
34
+ if (packet.violations.some((v) => v.rule.enforcement === "block")) {
35
+ return { final: "block", source: "deterministic", ollama, selfReview: { valid: true, reason: "AST violation", issues: [] } };
36
+ }
37
+ if (packet.graphViolations.length > 0) {
38
+ return { final: "block", source: "deterministic", ollama, selfReview: { valid: true, reason: "Graph violation", issues: [] } };
39
+ }
40
+ if (ollama.decision === "block" && ollama.confidence >= this.config.blockConfidenceThreshold) {
41
+ const sr = this.selfReview.validate(packet, "block");
42
+ if (sr.valid) {
43
+ return { final: "block", source: "ollama", ollama, selfReview: sr };
44
+ }
45
+ return { final: "warn", source: "self-review", ollama, selfReview: sr };
46
+ }
47
+ if (this.config.escalateToDeepSeek && (ollama.confidence < this.config.warnConfidenceThreshold || ollama.decision === "warn")) {
48
+ const ds = await deepseek(packet);
49
+ return { final: ds.decision, source: "deepseek", ollama, deepseek: ds };
50
+ }
51
+ return { final: ollama.decision === "block" ? "warn" : ollama.decision, source: "ollama", ollama };
52
+ }
53
+ };
54
+ function defaultGateConfig(useDeepSeek = false) {
55
+ return {
56
+ blockConfidenceThreshold: 0.75,
57
+ warnConfidenceThreshold: 0.6,
58
+ escalateToDeepSeek: useDeepSeek
59
+ };
60
+ }
61
+ export {
62
+ ConfidenceGate,
63
+ defaultGateConfig
64
+ };