New:Socket for Asana Is Now Available.Learn more
Get Started

stateark

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

stateark - npm Package Compare versions

Comparing version
0.5.4
to
0.5.5
+32
-0
CHANGELOG.md
# Changelog
## v0.5.5 — A store written on Windows opens everywhere
Found by inspecting a real user's store, not by a test. Includes everything from
0.5.4, which never reached npm.
- **Manifest paths are now platform-independent.** `path.join` on Windows produced
`artifacts\name`, and that separator was written into `manifest.json` as permanent
metadata. On macOS and Linux it does not resolve — `artifacts\README.md` is not a
path there, it is a filename containing a backslash. Every artifact of a
Windows-written store was therefore unreachable on any other platform. Paths are
now always written POSIX-style and both separators are accepted on read, so
existing stores repair themselves at the next savepoint.
- **A file that cannot be carried forward is never dropped silently.** The failure
path swallowed the error and left the artifact out of the manifest without a word
— the exact silent loss StateArk exists to prevent, and it fired on every
cross-platform store. It now raises `carry_forward_failed`, naming the file and
the reason.
- **`state.md` opens with YAML frontmatter** in the shape the Open Knowledge Format
asks for: `type`, `title`, `description`, `timestamp`, `project`, `version`,
`tags`. Additive only — the Markdown body is unchanged and StateArk does not
depend on it, but Obsidian vaults, RAG pipelines and other agents can now filter
savepoints without a StateArk-specific parser. The store layout is deliberately
not restructured around OKF: the spec is at v0.1 and will still move.
- **The store README warns against hand-written savepoints** (from 0.5.4). A client
that could not reach the MCP tools invented a "documented manual fallback",
wrote the files itself and corrupted a timestamp in the process. There is no such
procedure. Writing by hand bypasses the lock, the atomic publish, the checksums
and every integrity check.
- Smoke suite: 10 new checks. Five rebuild a Windows-written store and prove the
artifacts survive; four cover the frontmatter, including a title full of YAML
metacharacters.
## v0.5.3 — Several agents, one store

@@ -4,0 +36,0 @@

+2
-2
import { createClient } from "@supabase/supabase-js";
import { readFile } from "node:fs/promises";
import { resolveInside } from "./local-store.js";
import { resolveInside, normalizeStoredPath } from "./local-store.js";
const TEXT_KINDS = /^(code|prompt|schema|config)$/;

@@ -70,3 +70,3 @@ const TEXT_EXT = /\.(md|txt|py|js|ts|tsx|jsx|json|sql|yaml|yml|toml|csv|xml|html|css|sh|ps1)$/i;

if (a.status === "stored" && a.relative_path) {
const full = resolveInside(sp.dir, a.relative_path);
const full = resolveInside(sp.dir, normalizeStoredPath(a.relative_path));
if (!full)

@@ -73,0 +73,0 @@ throw new Error(`Refusing to read artifact outside the savepoint: ${a.name}`);

@@ -128,2 +128,19 @@ import { createHash, randomUUID } from "node:crypto";

}
/**
* Manifest paths are always written POSIX-style.
*
* `path.join` on Windows produces `artifacts\name`, and that separator used to be
* persisted into manifest.json. Opening such a store on macOS or Linux then failed
* to resolve a single artifact: `artifacts\README.md` is not a path there, it is a
* filename containing a backslash. A store is meant to survive the move between
* machines, so the separator inside it cannot depend on the machine that wrote it.
*/
const artifactRelPath = (name) => path.posix.join("artifacts", name);
/**
* Read a manifest path written by any platform, including stores from before 0.5.5
* that still contain backslashes. Tolerant on read, strict on write.
*/
export function normalizeStoredPath(rel) {
return String(rel ?? "").replace(/\\/g, "/");
}
/** Resolve `rel` inside `base`, refusing anything that escapes it. */

@@ -518,3 +535,3 @@ export function resolveInside(base, rel) {

if (bytes) {
rel = path.join("artifacts", name);
rel = artifactRelPath(name);
const full = resolveInside(tmpDir, rel);

@@ -566,4 +583,4 @@ if (!full)

continue;
const from = resolveInside(prev.dir, old.relative_path);
const rel = path.join("artifacts", old.name);
const from = resolveInside(prev.dir, normalizeStoredPath(old.relative_path));
const rel = artifactRelPath(old.name);
const to = resolveInside(tmpDir, rel);

@@ -581,3 +598,12 @@ if (!from || !to)

}
catch { /* source unreadable: nothing to carry, leave it out */ }
catch (e) {
// Never drop a file silently - that is the exact loss StateArk exists to
// prevent. Most likely cause: a store written on another platform, or an
// artifact deleted from the previous version by hand.
warnings.push({
code: "carry_forward_failed",
artifact: old.name,
message: `"${old.name}" existed in ${prev.meta.version} but could not be carried forward (${e instanceof Error ? e.message : String(e)}). It is NOT part of this savepoint. Re-submit it if you still need it.`,
});
}
}

@@ -763,3 +789,3 @@ // ---- compare against the previous savepoint --------------------------

}
const full = resolveInside(sp.dir, a.relative_path);
const full = resolveInside(sp.dir, normalizeStoredPath(a.relative_path));
if (!full)

@@ -787,3 +813,3 @@ return null;

const name = safeFilename(filename);
const rel = path.join("artifacts", name);
const rel = artifactRelPath(name);
const full = resolveInside(sp.dir, rel);

@@ -899,4 +925,39 @@ if (!full)

}
/** Quote a value so it is a valid YAML scalar whatever the model put in it. */
function yamlString(v) {
return `"${String(v ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n]+/g, " ").trim()}"`;
}
/**
* YAML frontmatter, in the shape the Open Knowledge Format asks for.
*
* OKF (Google Cloud, v0.1) requires exactly one field, `type`, and leaves the rest
* to the producer. Adding it is additive: humans read the Markdown as before,
* StateArk does not depend on it, and Obsidian, RAG pipelines and other agents can
* suddenly filter savepoints by project, version or date without a custom parser.
*
* Deliberately only this. The OKF spec is young and will move; the store layout
* stays StateArk's own rather than being restructured around a v0.1 standard.
*/
function frontmatter(project, version, title, state, createdAt, warnings) {
const summary = (state.executive_summary ?? "").split(/(?<=[.!?])\s/)[0] ?? "";
return [
"---",
"type: project-state",
`title: ${yamlString(title ? `${project} ${version} — ${title}` : `${project} ${version}`)}`,
`description: ${yamlString(summary)}`,
`timestamp: ${yamlString(createdAt)}`,
`project: ${yamlString(project)}`,
`version: ${yamlString(version)}`,
`generated: ${yamlString("stateark")}`,
"tags:",
` - ${yamlString("stateark")}`,
` - ${yamlString(slugify(project))}`,
...(warnings.length ? [` - ${yamlString("has-integrity-warnings")}`] : []),
"---",
"",
];
}
export function renderMarkdown(project, version, title, state, artifacts, createdAt, warnings = []) {
const l = [
...frontmatter(project, version, title, state, createdAt, warnings),
`# ${project} — ${version}`, "",

@@ -903,0 +964,0 @@ `> StateArk Savepoint · ${createdAt}${title ? ` · ${title}` : ""}`, "",

{
"name": "stateark",
"version": "0.5.4",
"version": "0.5.5",
"mcpName": "io.github.Askrion/stateark",

@@ -5,0 +5,0 @@ "type": "module",

@@ -98,2 +98,26 @@ # StateArk

Stores are platform-independent: a savepoint written on Windows opens on macOS and
Linux and the other way round.
## Readable by other tools
Every `state.md` opens with YAML frontmatter in the shape the
[Open Knowledge Format](https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing)
asks for — `type`, `title`, `description`, `timestamp`, `project`, `version`, `tags`:
```yaml
---
type: project-state
title: "Harbour Pricing v1.4 — pricing tiers final"
timestamp: "2026-08-28T09:00:00.000Z"
project: "Harbour Pricing"
version: "v1.4"
tags: ["stateark", "harbour-pricing"]
---
```
Drop your store into an Obsidian vault or point a RAG pipeline at it and savepoints
are filterable by project, version or date without a StateArk-specific parser. The
Markdown body is unchanged, and StateArk does not depend on the frontmatter.
## Licence

@@ -151,3 +175,3 @@

npm run typecheck
npm test # 97 checks against a throwaway store — run this first
npm test # 107 checks against a throwaway store — run this first
npm start

@@ -154,0 +178,0 @@ ```