amalfa 1.0.39 โ†’ 1.0.40

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/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to AMALFA will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.40] - 2026-01-09
9
+
10
+ ### Added
11
+ - **Ember Phase 2**: Integrated `EmberService` into the `AmalfaDaemon`.
12
+ - **Optimization**: Changes to file content now trigger immediate sidecar generation (if applicable).
13
+ - **Configuration**: Added `tests` to default `excludePatterns`.
14
+ - **CLI**: Added `amalfa stop-all` (alias `kill`) to stop all running services.
15
+
8
16
  ## [1.0.39] - 2026-01-09
9
17
 
10
18
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalfa",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "Local-first knowledge graph engine for AI agents. Transforms markdown into searchable memory with MCP protocol.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/pjsvis/amalfa#readme",
package/src/cli.ts CHANGED
@@ -53,6 +53,7 @@ Commands:
53
53
  ember <action> Manage Ember enrichment service (scan|squash)
54
54
  scripts list List available scripts and their descriptions
55
55
  servers [--dot] Show status of all AMALFA services (--dot for graph)
56
+ stop-all (kill) Stop all running AMALFA services
56
57
 
57
58
  Options:
58
59
  --force Override pre-flight warnings (errors still block)
@@ -738,6 +739,63 @@ async function cmdServers() {
738
739
  );
739
740
  }
740
741
 
742
+ async function cmdStopAll() {
743
+ console.log("๐Ÿ›‘ Stopping ALL Amalfa Services...\n");
744
+
745
+ // We reuse the service definitions from cmdServers logic,
746
+ // but abstracted slightly or duplicated for simplicity since they are inside cmdServers
747
+ const { AMALFA_DIRS } = await import("./config/defaults");
748
+ const { join } = await import("node:path");
749
+ const { existsSync, readFileSync, unlinkSync } = await import("node:fs");
750
+
751
+ const SERVICES = [
752
+ {
753
+ name: "Vector Daemon",
754
+ pidFile: join(AMALFA_DIRS.runtime, "vector-daemon.pid"),
755
+ },
756
+ { name: "File Watcher", pidFile: join(AMALFA_DIRS.runtime, "daemon.pid") },
757
+ { name: "Sonar Agent", pidFile: join(AMALFA_DIRS.runtime, "sonar.pid") },
758
+ // MCP usually runs as stdio, but if we track a PID file for it:
759
+ { name: "MCP Server", pidFile: join(AMALFA_DIRS.runtime, "mcp.pid") },
760
+ ];
761
+
762
+ let stoppedCount = 0;
763
+
764
+ for (const svc of SERVICES) {
765
+ if (existsSync(svc.pidFile)) {
766
+ try {
767
+ const pidStr = readFileSync(svc.pidFile, "utf-8").trim();
768
+ const pid = Number.parseInt(pidStr, 10);
769
+
770
+ if (!Number.isNaN(pid)) {
771
+ // Check if running
772
+ try {
773
+ process.kill(pid, 0); // Check existence
774
+ process.kill(pid, "SIGTERM");
775
+ console.log(`โœ… Sent SIGTERM to ${svc.name} (PID: ${pid})`);
776
+ stoppedCount++;
777
+ } catch {
778
+ // Not running, just stale
779
+ console.log(`๐Ÿงน Cleaning stale PID file for ${svc.name}`);
780
+ }
781
+ }
782
+ } catch (e) {
783
+ console.warn(`โš ๏ธ Failed to stop ${svc.name}:`, e);
784
+ }
785
+ // Always clean up PID file
786
+ try {
787
+ unlinkSync(svc.pidFile);
788
+ } catch {}
789
+ }
790
+ }
791
+
792
+ if (stoppedCount === 0) {
793
+ console.log("โœจ No active services found.");
794
+ } else {
795
+ console.log(`\nโœ… Stopped ${stoppedCount} service(s).`);
796
+ }
797
+ }
798
+
741
799
  async function cmdValidate() {
742
800
  console.log("๐Ÿ›ก๏ธ AMALFA Database Validation\n");
743
801
 
@@ -1002,6 +1060,11 @@ async function main() {
1002
1060
  await cmdServers();
1003
1061
  break;
1004
1062
 
1063
+ case "stop-all":
1064
+ case "kill":
1065
+ await cmdStopAll();
1066
+ break;
1067
+
1005
1068
  case "sonar":
1006
1069
  await cmdSonar();
1007
1070
  break;
@@ -44,6 +44,8 @@ export function initAmalfaDirs(): void {
44
44
  }
45
45
  }
46
46
 
47
+ import type { EmberConfig } from "@src/ember/types";
48
+
47
49
  export interface AmalfaConfig {
48
50
  /** @deprecated Use sources array instead */
49
51
  source?: string;
@@ -77,6 +79,8 @@ export interface AmalfaConfig {
77
79
  sonar: SonarConfig;
78
80
  /** @deprecated Use sonar instead */
79
81
  phi3?: SonarConfig;
82
+ /** Ember automated enrichment configuration */
83
+ ember: EmberConfig;
80
84
  }
81
85
 
82
86
  export interface SonarConfig {
@@ -142,12 +146,18 @@ export const DEFAULT_CONFIG: AmalfaConfig = {
142
146
  model: "BAAI/bge-small-en-v1.5",
143
147
  dimensions: 384,
144
148
  },
149
+ ember: {
150
+ enabled: true,
151
+ minConfidence: 0.8,
152
+ autoSquash: false,
153
+ backupDir: ".amalfa/backups/ember",
154
+ },
145
155
  watch: {
146
156
  enabled: true,
147
157
  debounce: 1000,
148
158
  notifications: true,
149
159
  },
150
- excludePatterns: ["node_modules", ".git", ".amalfa"],
160
+ excludePatterns: ["node_modules", ".git", ".amalfa", "tests"],
151
161
  // Optional graph tuning (for advanced use)
152
162
  graph: {
153
163
  tuning: {
@@ -276,6 +286,10 @@ export async function loadConfig(): Promise<AmalfaConfig> {
276
286
  },
277
287
  },
278
288
  } as SonarConfig,
289
+ ember: {
290
+ ...DEFAULT_CONFIG.ember,
291
+ ...(userConfig.ember || {}),
292
+ },
279
293
  };
280
294
 
281
295
  // Normalize: Convert legacy 'source' to 'sources' array
@@ -12,6 +12,7 @@ import {
12
12
  type AmalfaConfig,
13
13
  loadConfig,
14
14
  } from "@src/config/defaults";
15
+ import { EmberService } from "@src/ember";
15
16
  import { AmalfaIngestor } from "@src/pipeline/AmalfaIngestor";
16
17
  import { ResonanceDB } from "@src/resonance/db";
17
18
  import { getLogger } from "@src/utils/Logger";
@@ -157,6 +158,32 @@ function triggerIngestion(debounceMs: number) {
157
158
  // For now, we'll re-run full ingestion (hash checking prevents duplicates)
158
159
  await ingestor.ingest();
159
160
 
161
+ // --- EMBER INTEGRATION ---
162
+ if (config.ember?.enabled) {
163
+ log.info("๐Ÿ”ฅ Running Ember Analysis...");
164
+ const ember = new EmberService(db, config.ember);
165
+ let enrichedCount = 0;
166
+
167
+ // Analyze changed files only
168
+ // Note: Reading again from disk is safer than passing content from ingestor for now
169
+ for (const file of batch) {
170
+ try {
171
+ const content = await Bun.file(file).text();
172
+ const sidecar = await ember.analyze(file, content);
173
+ if (sidecar) {
174
+ await ember.generate(sidecar);
175
+ enrichedCount++;
176
+ }
177
+ } catch (err) {
178
+ log.warn({ file, err }, "Failed to analyze with Ember");
179
+ }
180
+ }
181
+ if (enrichedCount > 0) {
182
+ log.info({ enrichedCount }, "๐Ÿ”ฅ Ember enriched files");
183
+ }
184
+ }
185
+ // -------------------------
186
+
160
187
  db.close();
161
188
 
162
189
  log.info({ files: batchSize }, "โœ… Update complete");
@@ -5,7 +5,7 @@ import { Glob } from "bun";
5
5
  import { EmberAnalyzer } from "./analyzer";
6
6
  import { EmberGenerator } from "./generator";
7
7
  import { EmberSquasher } from "./squasher";
8
- import type { EmberConfig } from "./types";
8
+ import type { EmberConfig, EmberSidecar } from "./types";
9
9
 
10
10
  export class EmberService {
11
11
  private analyzer: EmberAnalyzer;
@@ -50,6 +50,20 @@ export class EmberService {
50
50
  return enrichedCount;
51
51
  }
52
52
 
53
+ /**
54
+ * Analyze a single file
55
+ */
56
+ async analyze(filePath: string, content: string) {
57
+ return this.analyzer.analyze(filePath, content);
58
+ }
59
+
60
+ /**
61
+ * Generate a sidecar file
62
+ */
63
+ async generate(sidecar: EmberSidecar) {
64
+ return this.generator.generate(sidecar);
65
+ }
66
+
53
67
  /**
54
68
  * Squash all pending sidecars
55
69
  */
@@ -71,8 +85,9 @@ export class EmberService {
71
85
  private async findSidecars(): Promise<string[]> {
72
86
  const sidecars: string[] = [];
73
87
  const glob = new Glob("**/*.ember.json");
88
+ const sources = this.config.sources || ["./docs"];
74
89
  // Scan sources
75
- for (const source of this.config.sources) {
90
+ for (const source of sources) {
76
91
  // Assuming source is like "./docs"
77
92
  const sourcePath = join(process.cwd(), source);
78
93
  for (const file of glob.scanSync({ cwd: sourcePath })) {
@@ -85,14 +100,14 @@ export class EmberService {
85
100
  private async discoverFiles(): Promise<string[]> {
86
101
  const files: string[] = [];
87
102
  const glob = new Glob("**/*.{md,mdx}"); // Only markdown for now
103
+ const sources = this.config.sources || ["./docs"];
104
+ const excludes = this.config.excludePatterns || [];
88
105
 
89
- for (const source of this.config.sources) {
106
+ for (const source of sources) {
90
107
  const sourcePath = join(process.cwd(), source);
91
108
  try {
92
109
  for (const file of glob.scanSync({ cwd: sourcePath })) {
93
- const shouldExclude = this.config.excludePatterns.some((p) =>
94
- file.includes(p),
95
- );
110
+ const shouldExclude = excludes.some((p) => file.includes(p));
96
111
  if (!shouldExclude) {
97
112
  files.push(join(sourcePath, file));
98
113
  }
@@ -17,10 +17,11 @@ export interface EmberSidecar {
17
17
 
18
18
  export interface EmberConfig {
19
19
  enabled: boolean;
20
- sources: string[];
20
+ sources?: string[];
21
21
  minConfidence: number;
22
22
  backupDir: string;
23
- excludePatterns: string[];
23
+ excludePatterns?: string[];
24
+ autoSquash?: boolean;
24
25
  }
25
26
 
26
27
  export type EnrichmentType = "tag" | "link" | "summary" | "metadata";