@danielsimonjr/memoryjs 2.1.1 → 2.1.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # MemoryJS
2
2
 
3
- [![Version](https://img.shields.io/badge/version-2.1.1-blue.svg)](https://github.com/danielsimonjr/memoryjs)
3
+ [![Version](https://img.shields.io/badge/version-2.1.2-blue.svg)](https://github.com/danielsimonjr/memoryjs)
4
4
  [![NPM](https://img.shields.io/npm/v/@danielsimonjr/memoryjs.svg)](https://www.npmjs.com/package/@danielsimonjr/memoryjs)
5
5
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
package/dist/cli/index.js CHANGED
@@ -36896,6 +36896,354 @@ function registerToolAffordanceCommands(program2) {
36896
36896
  });
36897
36897
  }
36898
36898
 
36899
+ // src/cli/commands/smoke.ts
36900
+ init_esm_shims();
36901
+ init_ManagerContext();
36902
+ import { promises as fs17 } from "fs";
36903
+ import { join as join8 } from "path";
36904
+ import { tmpdir } from "os";
36905
+ import { performance } from "perf_hooks";
36906
+ async function runSteps(steps, verbose) {
36907
+ const results = [];
36908
+ for (const step of steps) {
36909
+ const start = performance.now();
36910
+ try {
36911
+ await step.run();
36912
+ const durationMs = performance.now() - start;
36913
+ results.push({ name: step.name, ok: true, durationMs });
36914
+ if (verbose) {
36915
+ console.log(` ${formatSuccess("\u2713")} ${step.name} (${durationMs.toFixed(1)} ms)`);
36916
+ }
36917
+ } catch (error) {
36918
+ const durationMs = performance.now() - start;
36919
+ const message = error instanceof Error ? error.message : String(error);
36920
+ results.push({ name: step.name, ok: false, durationMs, error: message });
36921
+ if (verbose) {
36922
+ console.log(` ${formatError("\u2717")} ${step.name} (${durationMs.toFixed(1)} ms) \u2014 ${message}`);
36923
+ }
36924
+ }
36925
+ }
36926
+ return results;
36927
+ }
36928
+ function buildSteps(ctx) {
36929
+ return [
36930
+ // ---- Entity (CRUD) ----
36931
+ {
36932
+ name: "entity:create",
36933
+ run: async () => {
36934
+ const [created] = await ctx.entityManager.createEntities([{
36935
+ name: "Alpha",
36936
+ entityType: "service",
36937
+ observations: ["root node"]
36938
+ }]);
36939
+ if (created.name !== "Alpha") throw new Error(`expected Alpha got ${created.name}`);
36940
+ }
36941
+ },
36942
+ {
36943
+ name: "entity:open_nodes",
36944
+ run: async () => {
36945
+ const result = await ctx.searchManager.openNodes(["Alpha"]);
36946
+ if (result.entities.length !== 1) throw new Error(`expected 1 entity got ${result.entities.length}`);
36947
+ }
36948
+ },
36949
+ {
36950
+ name: "entity:update",
36951
+ run: async () => {
36952
+ await ctx.entityManager.updateEntity("Alpha", { importance: 7 });
36953
+ const after = ctx.storage.getEntityByName("Alpha");
36954
+ if (after?.importance !== 7) throw new Error(`importance not persisted: ${after?.importance}`);
36955
+ }
36956
+ },
36957
+ // ---- Relation ----
36958
+ {
36959
+ name: "relation:create_endpoints",
36960
+ run: async () => {
36961
+ await ctx.entityManager.createEntities([
36962
+ { name: "Beta", entityType: "service", observations: ["second node"] },
36963
+ { name: "Gamma", entityType: "service", observations: ["third node"] }
36964
+ ]);
36965
+ }
36966
+ },
36967
+ {
36968
+ name: "relation:create",
36969
+ run: async () => {
36970
+ await ctx.relationManager.createRelations([{ from: "Alpha", to: "Beta", relationType: "depends_on" }]);
36971
+ const graph = await ctx.storage.loadGraph();
36972
+ if (!graph.relations.some((r) => r.from === "Alpha" && r.to === "Beta")) {
36973
+ throw new Error("relation Alpha\u2192Beta not found in graph");
36974
+ }
36975
+ }
36976
+ },
36977
+ // ---- Observation ----
36978
+ {
36979
+ name: "observation:add",
36980
+ run: async () => {
36981
+ await ctx.observationManager.addObservations([
36982
+ { entityName: "Alpha", contents: ["extra note for search"] }
36983
+ ]);
36984
+ const obs = await ctx.observationManager.getObservationsFor("Alpha");
36985
+ if (!obs.some((o) => o.includes("extra note"))) throw new Error("observation not persisted");
36986
+ }
36987
+ },
36988
+ {
36989
+ name: "observation:delete",
36990
+ run: async () => {
36991
+ await ctx.observationManager.deleteObservations([
36992
+ { entityName: "Alpha", observations: ["root node"] }
36993
+ ]);
36994
+ const obs = await ctx.observationManager.getObservationsFor("Alpha");
36995
+ if (obs.includes("root node")) throw new Error("observation not deleted");
36996
+ }
36997
+ },
36998
+ // ---- Search ----
36999
+ {
37000
+ name: "search:basic",
37001
+ run: async () => {
37002
+ const result = await ctx.searchManager.searchNodes("extra note");
37003
+ if (result.entities.length === 0) throw new Error("basic search returned no entities");
37004
+ }
37005
+ },
37006
+ {
37007
+ name: "search:boolean",
37008
+ run: async () => {
37009
+ const result = await ctx.searchManager.booleanSearch("extra AND note");
37010
+ if (result.entities.length === 0) throw new Error("boolean search returned no entities");
37011
+ }
37012
+ },
37013
+ {
37014
+ name: "search:ranked",
37015
+ run: async () => {
37016
+ const result = await ctx.rankedSearch.searchNodesRanked("extra note", void 0, void 0, void 0, 5);
37017
+ if (result.length === 0) throw new Error("ranked search returned no results");
37018
+ }
37019
+ },
37020
+ // ---- Tag ----
37021
+ {
37022
+ name: "tag:add",
37023
+ run: async () => {
37024
+ await ctx.entityManager.addTags("Alpha", ["critical", "core"]);
37025
+ const e = ctx.storage.getEntityByName("Alpha");
37026
+ if (!e?.tags?.includes("critical")) throw new Error("tag not added");
37027
+ }
37028
+ },
37029
+ {
37030
+ name: "tag:remove",
37031
+ run: async () => {
37032
+ await ctx.entityManager.removeTags("Alpha", ["core"]);
37033
+ const e = ctx.storage.getEntityByName("Alpha");
37034
+ if (e?.tags?.includes("core")) throw new Error("tag not removed");
37035
+ }
37036
+ },
37037
+ // ---- Hierarchy ----
37038
+ {
37039
+ name: "hierarchy:set_parent",
37040
+ run: async () => {
37041
+ await ctx.hierarchyManager.setEntityParent("Beta", "Alpha");
37042
+ const e = ctx.storage.getEntityByName("Beta");
37043
+ if (e?.parentId !== "Alpha") throw new Error(`parent not set: ${e?.parentId}`);
37044
+ }
37045
+ },
37046
+ {
37047
+ name: "hierarchy:ancestors",
37048
+ run: async () => {
37049
+ const ancestors = await ctx.hierarchyManager.getAncestors("Beta");
37050
+ if (!ancestors.some((a) => a.name === "Alpha")) throw new Error("Alpha not in Beta ancestors");
37051
+ }
37052
+ },
37053
+ // ---- Graph algorithms ----
37054
+ {
37055
+ name: "graph:shortest_path",
37056
+ run: async () => {
37057
+ const result = await ctx.graphTraversal.findShortestPath("Alpha", "Beta");
37058
+ if (!result || result.path.length === 0) throw new Error("no path found Alpha\u2192Beta");
37059
+ }
37060
+ },
37061
+ {
37062
+ name: "graph:connected_components",
37063
+ run: async () => {
37064
+ const result = await ctx.graphTraversal.findConnectedComponents();
37065
+ if (result.components.length === 0) throw new Error("no connected components");
37066
+ }
37067
+ },
37068
+ // ---- IO ----
37069
+ {
37070
+ name: "io:export_json",
37071
+ run: async () => {
37072
+ const graph = await ctx.storage.loadGraph();
37073
+ const exported = ctx.ioManager.exportGraph(graph, "json");
37074
+ const parsed = JSON.parse(exported);
37075
+ if (!Array.isArray(parsed.entities) || parsed.entities.length < 3) {
37076
+ throw new Error(`export json had ${parsed.entities?.length} entities`);
37077
+ }
37078
+ }
37079
+ },
37080
+ // ---- Maintenance / Analytics ----
37081
+ {
37082
+ name: "analytics:stats",
37083
+ run: async () => {
37084
+ const stats = await ctx.analyticsManager.getGraphStats();
37085
+ if (stats.totalEntities < 3) throw new Error(`stats.totalEntities=${stats.totalEntities}`);
37086
+ }
37087
+ },
37088
+ {
37089
+ name: "maintenance:validate",
37090
+ run: async () => {
37091
+ const report = await ctx.analyticsManager.validateGraph();
37092
+ if (!report) throw new Error("validate returned nothing");
37093
+ }
37094
+ },
37095
+ // ---- Decision Rationale (v2.1.0) ----
37096
+ {
37097
+ name: "decision:propose",
37098
+ run: async () => {
37099
+ const r = await ctx.decisionManager.propose({
37100
+ context: "choosing storage",
37101
+ decision: "use SQLite",
37102
+ alternatives: ["JSONL"],
37103
+ consequences: ["ACID"]
37104
+ });
37105
+ if (!r.id) throw new Error("propose returned no id");
37106
+ }
37107
+ },
37108
+ {
37109
+ name: "decision:accept",
37110
+ run: async () => {
37111
+ const list = await ctx.decisionManager.list({ status: "proposed" });
37112
+ if (list.length === 0) throw new Error("no proposed decision to accept");
37113
+ const result = await ctx.decisionManager.accept(list[0].id);
37114
+ if (result !== "accepted") throw new Error(`accept returned ${result}`);
37115
+ }
37116
+ },
37117
+ {
37118
+ name: "decision:list",
37119
+ run: async () => {
37120
+ const list = await ctx.decisionManager.list({ status: "accepted" });
37121
+ if (list.length === 0) throw new Error("accepted decision missing from list");
37122
+ }
37123
+ },
37124
+ // ---- Heuristic Guidelines (v2.1.0) ----
37125
+ {
37126
+ name: "heuristic:add",
37127
+ run: async () => {
37128
+ const id = await ctx.heuristicManager.add({
37129
+ condition: "editing TypeScript",
37130
+ action: "run typecheck before commit"
37131
+ });
37132
+ if (!id) throw new Error("add_heuristic returned no id");
37133
+ }
37134
+ },
37135
+ {
37136
+ name: "heuristic:match",
37137
+ run: async () => {
37138
+ const matches2 = await ctx.heuristicManager.match("editing TypeScript file");
37139
+ if (matches2.length === 0) throw new Error("match returned no heuristics");
37140
+ }
37141
+ },
37142
+ // ---- Project Context (v2.1.0) ----
37143
+ {
37144
+ name: "project_context:upsert",
37145
+ run: async () => {
37146
+ const rec = await ctx.projectContextManager.upsert("smoke", {
37147
+ facts: ["smoke test project"]
37148
+ });
37149
+ if (!rec) throw new Error("upsert returned nothing");
37150
+ }
37151
+ },
37152
+ {
37153
+ name: "project_context:get",
37154
+ run: async () => {
37155
+ const rec = ctx.projectContextManager.get("smoke");
37156
+ if (!rec) throw new Error("project context not found after upsert");
37157
+ }
37158
+ },
37159
+ // ---- Tool Affordance (v2.1.0) ----
37160
+ {
37161
+ name: "tool_affordance:record + stats",
37162
+ run: async () => {
37163
+ await ctx.toolAffordanceManager.recordOutcome("test_tool", {
37164
+ outcome: "success",
37165
+ durationMs: 10
37166
+ });
37167
+ const stats = ctx.toolAffordanceManager.rollingStats("test_tool");
37168
+ if (!stats) throw new Error("rollingStats returned null after recording");
37169
+ }
37170
+ },
37171
+ // ---- Exclusion (do_not_remember) (v2.1.0) ----
37172
+ {
37173
+ name: "exclusion:add + check",
37174
+ run: async () => {
37175
+ await ctx.exclusionManager.add({ pattern: "secret-token" });
37176
+ const verdict = await ctx.exclusionManager.check("this contains secret-token");
37177
+ if (!verdict.blocked) throw new Error("exclusion check failed to block matching content");
37178
+ }
37179
+ },
37180
+ // ---- Observation Dedup (v2.1.0) ----
37181
+ {
37182
+ name: "observation_dedup:find",
37183
+ run: async () => {
37184
+ await ctx.entityManager.createEntities([
37185
+ { name: "DupA", entityType: "note", observations: ["shared dedup fact"] },
37186
+ { name: "DupB", entityType: "note", observations: ["shared dedup fact"] }
37187
+ ]);
37188
+ const groups = await ctx.observationDedupManager.findDuplicateObservations({});
37189
+ if (groups.length === 0) throw new Error("dedup found 0 groups despite seeded duplicates");
37190
+ }
37191
+ },
37192
+ // ---- Spell Correction (v2.1.0) ----
37193
+ {
37194
+ name: "spell:rebuild + suggest",
37195
+ run: async () => {
37196
+ await ctx.spellChecker.rebuild();
37197
+ const suggestions = await ctx.spellChecker.suggest("Alpa", { limit: 3 });
37198
+ if (!Array.isArray(suggestions)) throw new Error("suggest did not return an array");
37199
+ }
37200
+ }
37201
+ ];
37202
+ }
37203
+ function printSummary(results, totalMs, verbose) {
37204
+ const passed = results.filter((r) => r.ok).length;
37205
+ const failed = results.length - passed;
37206
+ if (!verbose) {
37207
+ for (const r of results) {
37208
+ const marker = r.ok ? formatSuccess("\u2713") : formatError("\u2717");
37209
+ const tail = r.ok ? "" : ` \u2014 ${r.error}`;
37210
+ console.log(` ${marker} ${r.name}${tail}`);
37211
+ }
37212
+ }
37213
+ console.log("");
37214
+ if (failed === 0) {
37215
+ console.log(formatSuccess(`Smoke test passed: ${passed}/${results.length} steps in ${totalMs.toFixed(0)} ms`));
37216
+ } else {
37217
+ console.log(formatError(`Smoke test FAILED: ${failed} failing / ${results.length} total in ${totalMs.toFixed(0)} ms`));
37218
+ }
37219
+ }
37220
+ function registerSmokeCommand(program2) {
37221
+ program2.command("smoke").description("Run a per-category end-to-end smoke test (~30 ops) against a fresh temp graph").option("-s, --storage <path>", "Storage path for the smoke run (default: temp dir)").option("-k, --keep", "Preserve the smoke graph after the run and print its path (default: cleanup)").option("-v, --verbose", "Print each step as it runs (default: print summary only)").action(async (opts) => {
37222
+ const verbose = Boolean(opts.verbose);
37223
+ const keep = Boolean(opts.keep);
37224
+ const storagePath = opts.storage ? opts.storage : await fs17.mkdtemp(join8(tmpdir(), "memoryjs-smoke-"));
37225
+ const graphPath = opts.storage ? storagePath : join8(storagePath, "graph.jsonl");
37226
+ if (verbose) console.log(`Smoke storage: ${graphPath}`);
37227
+ const ctx = new ManagerContext(graphPath);
37228
+ const steps = buildSteps(ctx);
37229
+ const start = performance.now();
37230
+ const results = await runSteps(steps, verbose);
37231
+ const totalMs = performance.now() - start;
37232
+ printSummary(results, totalMs, verbose);
37233
+ if (keep) {
37234
+ console.log(`
37235
+ Keeping smoke graph at: ${graphPath}`);
37236
+ } else if (!opts.storage) {
37237
+ try {
37238
+ await fs17.rm(storagePath, { recursive: true, force: true });
37239
+ } catch {
37240
+ }
37241
+ }
37242
+ const failed = results.filter((r) => !r.ok).length;
37243
+ if (failed > 0) process.exit(1);
37244
+ });
37245
+ }
37246
+
36899
37247
  // src/cli/commands/index.ts
36900
37248
  function registerCommands(program2) {
36901
37249
  registerEntityCommands(program2);
@@ -36911,6 +37259,7 @@ function registerCommands(program2) {
36911
37259
  registerDecisionCommands(program2);
36912
37260
  registerProjectContextCommands(program2);
36913
37261
  registerToolAffordanceCommands(program2);
37262
+ registerSmokeCommand(program2);
36914
37263
  }
36915
37264
 
36916
37265
  // src/cli/index.ts
@@ -36918,7 +37267,7 @@ init_logger();
36918
37267
  import { readFileSync as readFileSync4 } from "fs";
36919
37268
  import { createInterface as createInterface2 } from "readline";
36920
37269
  import { fileURLToPath as fileURLToPath3 } from "url";
36921
- import { dirname as dirname9, join as join8 } from "path";
37270
+ import { dirname as dirname9, join as join9 } from "path";
36922
37271
  process.on("unhandledRejection", (reason) => {
36923
37272
  logger.error("Unhandled promise rejection:", reason);
36924
37273
  });
@@ -36929,7 +37278,7 @@ function getVersion() {
36929
37278
  try {
36930
37279
  const __filename2 = fileURLToPath3(import.meta.url);
36931
37280
  const __dirname2 = dirname9(__filename2);
36932
- const pkgPath = join8(__dirname2, "..", "..", "package.json");
37281
+ const pkgPath = join9(__dirname2, "..", "..", "package.json");
36933
37282
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
36934
37283
  return pkg.version;
36935
37284
  } catch {