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 +8 -0
- package/package.json +1 -1
- package/src/cli.ts +63 -0
- package/src/config/defaults.ts +15 -1
- package/src/daemon/index.ts +27 -0
- package/src/ember/index.ts +21 -6
- package/src/ember/types.ts +3 -2
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.
|
|
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;
|
package/src/config/defaults.ts
CHANGED
|
@@ -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
|
package/src/daemon/index.ts
CHANGED
|
@@ -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");
|
package/src/ember/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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
|
}
|
package/src/ember/types.ts
CHANGED
|
@@ -17,10 +17,11 @@ export interface EmberSidecar {
|
|
|
17
17
|
|
|
18
18
|
export interface EmberConfig {
|
|
19
19
|
enabled: boolean;
|
|
20
|
-
sources
|
|
20
|
+
sources?: string[];
|
|
21
21
|
minConfidence: number;
|
|
22
22
|
backupDir: string;
|
|
23
|
-
excludePatterns
|
|
23
|
+
excludePatterns?: string[];
|
|
24
|
+
autoSquash?: boolean;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export type EnrichmentType = "tag" | "link" | "summary" | "metadata";
|