@miadi/stateloom-mcp 0.3.3 → 0.3.4

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.
@@ -1 +1,13 @@
1
+ /**
2
+ * Create the folder a document is about to be written into, one level only.
3
+ *
4
+ * Earned 2026-10-01 (Episode 550, L1): `miadi-chronicle://550/screenwalk`
5
+ * resolves to `<episode>/diagrams/screenwalk.smdf.json`, and naming a missing
6
+ * file is how an episode's first diagram is created — but a fresh episode has
7
+ * no `diagrams/` yet, so the first write failed with ENOENT. The folder is
8
+ * created only when ITS parent exists (an episode's `diagrams/` room, a new
9
+ * folder beside existing work); a path whose ancestors are missing is a typo,
10
+ * and it still fails loudly instead of growing a tree.
11
+ */
12
+ export declare function ensureParentFolder(path: string): void;
1
13
  export declare function writeFileAtomic(path: string, text: string): void;
@@ -6,9 +6,29 @@
6
6
  * replace it regardless of its mode, and a read-only document is a person's
7
7
  * word that it should not change — and the new file keeps the old one's mode.
8
8
  */
9
- import { accessSync, chmodSync, constants, existsSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
9
+ import { accessSync, chmodSync, constants, existsSync, mkdirSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
10
10
  import { basename, dirname, join } from "path";
11
+ /**
12
+ * Create the folder a document is about to be written into, one level only.
13
+ *
14
+ * Earned 2026-10-01 (Episode 550, L1): `miadi-chronicle://550/screenwalk`
15
+ * resolves to `<episode>/diagrams/screenwalk.smdf.json`, and naming a missing
16
+ * file is how an episode's first diagram is created — but a fresh episode has
17
+ * no `diagrams/` yet, so the first write failed with ENOENT. The folder is
18
+ * created only when ITS parent exists (an episode's `diagrams/` room, a new
19
+ * folder beside existing work); a path whose ancestors are missing is a typo,
20
+ * and it still fails loudly instead of growing a tree.
21
+ */
22
+ export function ensureParentFolder(path) {
23
+ const dir = dirname(path);
24
+ if (existsSync(dir))
25
+ return;
26
+ if (!existsSync(dirname(dir)))
27
+ return;
28
+ mkdirSync(dir);
29
+ }
11
30
  export function writeFileAtomic(path, text) {
31
+ ensureParentFolder(path);
12
32
  let mode;
13
33
  if (existsSync(path)) {
14
34
  accessSync(path, constants.W_OK);
package/dist/erd.js CHANGED
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, readdirSync, writeFileSync } from "fs";
3
3
  import { dirname, join, resolve } from "path";
4
4
  import { addAttribute, addEntity, addRelationship, checkLinks, checkStateOf, collectNotes, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, isSqdfPath, isSysdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, updateEntity, updateErdSettings, summarizeErd, validateErd, normalizeNotes } from "@miadi/stateloom-protocol";
5
5
  import { stampedOutputPath } from "@miadi/stateloom-cli/render";
6
+ import { writeFileAtomic } from "./atomicWrite.js";
6
7
  const ok = (text) => ({ content: [{ type: "text", text }] });
7
8
  const fail = (text) => ({ content: [{ type: "text", text }], isError: true });
8
9
  export function readErd(path) {
@@ -17,7 +18,7 @@ export function readErd(path) {
17
18
  }
18
19
  }
19
20
  function writeErd(path, def) {
20
- writeFileSync(path, JSON.stringify(def, null, 2) + "\n", "utf8");
21
+ writeFileAtomic(path, JSON.stringify(def, null, 2) + "\n");
21
22
  }
22
23
  /** One line for set_project_file / get_project_file when the active document is an ERDF. */
23
24
  export function erdSummary(path) {
package/dist/server.js CHANGED
@@ -72,6 +72,7 @@ import { registerErdTools, erdSummary, erdGetDefinition, erdLoadDefinition, erdR
72
72
  import { registerSequenceTools, sequenceSummary, sequenceGetDefinition, sequenceLoadDefinition, sequenceRender, sequenceGetNotes, sequenceSetNotes, } from "./sequence.js";
73
73
  import { registerSystemTools, checkSystemReport, reconcileAfterEdit, reconcileModeLine, systemSummary, systemGetDefinition, systemLoadDefinition, systemRender, systemGetNotes, systemSetNotes, systemRispec, } from "./system.js";
74
74
  import { liveBridge } from "./live.js";
75
+ import { writeFileAtomic } from "./atomicWrite.js";
75
76
  // STATELOOM_* read first, SMCRAFT_* legacy twin honored — the live MCP
76
77
  // registration bakes SMCRAFT_PROJECT_FILE and must keep working (2026-07-27
77
78
  // rename brief). Resolved once at boot, like the direct reads they replace.
@@ -130,7 +131,7 @@ function writeDef(def) {
130
131
  catch {
131
132
  // A new or unreadable file is written in the loom's own shape.
132
133
  }
133
- writeFileSync(PROJECT_FILE, JSON.stringify(bare ? def : { stateMachine: def }, null, 2) + (bare ? "\n" : ""), "utf8");
134
+ writeFileAtomic(PROJECT_FILE, JSON.stringify(bare ? def : { stateMachine: def }, null, 2) + (bare ? "\n" : ""));
134
135
  }
135
136
  // ─── Optional real-time design bridge (env-gated, best-effort) ───────
136
137
  // When STATELOOM_BRIDGE_URL (or legacy SMCRAFT_BRIDGE_URL) is set, each
@@ -784,6 +785,22 @@ function buildServer() {
784
785
  content: [{ type: "text", text: `Added state '${name}' under '${parentName}' (${kind ?? "normal"}).` }],
785
786
  };
786
787
  });
788
+ server.tool("add_object", "Declare an object the machine is constructed with: `instance` is the name guards use (`walk` in `walk.state`), `class` is its type — an ERD entity when it is data. Fixes L001/L003 without rewriting the machine. Refused when the instance already exists.", { instance: z.string(), class: z.string() }, async ({ instance, class: className }) => {
789
+ const def = readDef();
790
+ if (!def)
791
+ return { content: [{ type: "text", text: noMachine() }], isError: true };
792
+ if (!instance.trim() || !className.trim())
793
+ return { content: [{ type: "text", text: "An object needs an instance and a class." }], isError: true };
794
+ const objects = [...(def.settings.objects ?? [])];
795
+ if (objects.some((o) => o.instance === instance)) {
796
+ return { content: [{ type: "text", text: `The machine already has an object '${instance}'.` }], isError: true };
797
+ }
798
+ objects.push({ instance, class: className });
799
+ def.settings.objects = objects;
800
+ writeDef(def);
801
+ bridgeEmitPatch([{ op: "settings.update", patch: { objects } }]);
802
+ return { content: [{ type: "text", text: `Object '${instance}' : ${className} declared on '${def.settings.name}'.` }] };
803
+ });
787
804
  server.tool("update_state", "Change what a state says about itself without rewriting the machine: its description, its kind (normal, final, history) or its prompt. Omitted fields stay as they are; an empty description or prompt clears it. Use it after a scenario built states in auto mode, to say what each one is.", {
788
805
  name: z.string(),
789
806
  description: z.string().optional(),
@@ -1081,7 +1098,11 @@ function buildServer() {
1081
1098
  : "bridge not configured (STATELOOM_BRIDGE_URL unset)";
1082
1099
  return {
1083
1100
  content: [
1084
- { type: "text", text: `Active document: ${PROJECT_FILE}\n${summary}\n${bridgeNote}\nCanvas: ${canvasLink(PROJECT_FILE)}` },
1101
+ {
1102
+ type: "text",
1103
+ // The version is said so an agent can tell when its pin is behind (Episode 550, L4).
1104
+ text: `Active document: ${PROJECT_FILE}\n${summary}\n${bridgeNote}\nCanvas: ${canvasLink(PROJECT_FILE)}\nLoom: @miadi/stateloom-mcp ${PACKAGE_VERSION} (\`npm view @miadi/stateloom-mcp version\` names the latest)`,
1105
+ },
1085
1106
  ],
1086
1107
  };
1087
1108
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miadi/stateloom-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "MCP server for miadi-stateloom (formerly smcraft-mcp) — lets LLM agents design state machines through conversation",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "dependencies": {
41
41
  "@miadi/stateloom-cli": "^0.1.4",
42
42
  "@miadi/stateloom-client": "^0.1.3",
43
- "@miadi/stateloom-protocol": "^0.1.12",
43
+ "@miadi/stateloom-protocol": "^0.1.13",
44
44
  "@modelcontextprotocol/sdk": "^1.12.1",
45
45
  "zod": "^4.3.6"
46
46
  },