mirror-mind 0.33.3 → 0.33.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.
@@ -12,6 +12,7 @@ Future release notes should use the structure defined in [Release Notes](../proc
12
12
 
13
13
  ## Releases
14
14
 
15
+ - [v0.33.4 — A Name Is a Key](v0.33.4.md) — patch release of three first-run corrections: two from a contributor's PR #43 and CR141, which its review opened. `init --help` created a home named `--help`, `init ../escaped` created one outside `.mirror-minds`, and `init "My User"` put a space into `MIRROR_USER`; a user name is now a kebab-case key like a journey slug or a persona id, refused otherwise with a name that would work and nothing created. `identity edit` on Windows opens Notepad when no editor is named. Existing homes untouched. No migration.
15
16
  - [v0.33.3 — Mirror Finds Itself Under Any Path](v0.33.3.md) — patch release of one Change Request, CR140, carried by a contributor's PR #42: Mirror read its own install path from `URL.pathname`, so a checkout or package under a path with a space or a non-ASCII character read as `Install: unknown`, the welcome card showed no update, and `runtime update` refused. The path is now a filesystem path, a guard keeps the idiom out, and an `unknown` install names the path it looked up from. On Windows, npm's global layout is recognized and npm runs with no shell, verified on Windows in CI for the two files they touch; there is still no Windows product. Affected installs update once by hand. No migration.
16
17
  - [v0.33.2 — Git Runs Where Mirror Lives](v0.33.2.md) — patch release of one Change Request, CR138: on a clone install, `runtime status`, `version`, and `diagnose` describe the install from any directory, and `diagnose` grades Mirror's own tree. Git now runs only in the install's own checkout, so a checkout someone else made can no longer run a `core.fsmonitor` command through these commands; `version --start PATH` is the one explicit exception. Package installs were never exposed. No migration.
17
18
  - [v0.33.1 — The Install Answers For Itself](v0.33.1.md) — patch release of six RS010 Change Requests: the welcome card, the status line, and both `release-notes` readers answer for the install that is running, never for the cwd or the repository around the install. A package asks its own tree and the npm registry, a clone its own checkout and remote; `pending` says `unknown` when it cannot tell, and a `--ref` value can no longer run a command through git. No migration.
@@ -0,0 +1,104 @@
1
+ ---
2
+ digest: >
3
+ v0.33.4 is a maintenance release of three first-run corrections, two from a
4
+ contributor's PR #43 and one Change Request, CR141, that its review opened.
5
+ `mirror init --help` created a home named `--help`; `init ../escaped` created
6
+ one outside `.mirror-minds`; `init "My User"` put a space into `MIRROR_USER`
7
+ for every wiring command. A user name is now a kebab-case key, like a journey
8
+ slug or a persona id, refused otherwise with a name that would work and
9
+ nothing created. And `identity edit` on Windows opens Notepad when no editor
10
+ is named, instead of failing to start nano. Existing homes are untouched; no
11
+ migration.
12
+ ---
13
+
14
+ # v0.33.4 — A Name Is a Key
15
+
16
+ **Date:** 2026-10-17
17
+
18
+ ## Highlights
19
+
20
+ - **`init` takes a kebab-case key for a user name, and nothing else** (CR141, from the
21
+ review of PR #43). `init ../escaped` created `~/escaped`, outside the directory the
22
+ product owns; `init "My User"` put a space into `MIRROR_USER`, which every wiring
23
+ command `init` prints then carried; `init a/b` nested. Each is refused before anything
24
+ is created, with the sentence `identity set` already uses: the consequence, the rule,
25
+ and a key that would work.
26
+ - **`init --help` no longer creates a home named `--help`** (PR #43 by
27
+ @rodrigoimmaginario). The command has no flags; a word that starts with `-` gets the
28
+ usage line and exit 2.
29
+ - **`identity edit` opens Notepad on Windows when no editor is named** (PR #43). The
30
+ order is unchanged, `EDITOR`, then `VISUAL`, then the platform's default: `notepad` on
31
+ Windows, where nano exists only inside Git Bash, `nano` elsewhere.
32
+ - **Existing homes are untouched.** `init` only creates; a home with another kind of name
33
+ keeps it.
34
+
35
+ ## Where We Started
36
+
37
+ A contributor's first run on Windows met two small things and sent a PR for each:
38
+ `init --help` created a home, and `identity edit` with no `EDITOR` set failed to start
39
+ nano. The first was not a Windows bug at all; it reproduced on macOS in a second.
40
+
41
+ Its review asked what else `init` accepted. The answer was anything. The name becomes
42
+ `~/.mirror-minds/<user>` and the value of `MIRROR_USER`, and the repository had already
43
+ decided, in CR104, that a key which goes into paths and shell commands must be one plain
44
+ word a shell cannot split, substitute, or read as an option. Journey slugs and persona
45
+ ids had met that rule since `v0.32.0`. The user name was the last key of its kind that
46
+ did not.
47
+
48
+ ## What Changed
49
+
50
+ **One sentence for every key.** `newUserNameProblem` joins `newIdentityKeyProblem` in
51
+ `ts/src/identity/identityKey.ts`, both over one builder, so `init`, `identity set`,
52
+ `identity edit`, and `seed` refuse a non-key the same way. The key is printed escaped: a
53
+ name with a newline or a `;` in it cannot forge output or a command. `runInit` asks before
54
+ `initUserHome`, so a refusal leaves `$HOME` as it was: no home, no identity directory, no
55
+ config file.
56
+
57
+ **The test is the PR's, widened.** `../escaped`, `My User`, `a/b`, and an accented name go
58
+ through the real front door; each asserts the exit, the sentence with its suggestion, and
59
+ an empty home afterwards. PR #43's own `init --help` test stands beside it.
60
+
61
+ **Notepad, with its evidence still owed.** The platform is a dependency of
62
+ `resolveEditor`, so both defaults are tested on every OS. The PR verified the Windows
63
+ round trip on an unchanged buffer, which prints `No changes detected.` whether Notepad
64
+ blocked or returned at once; early Windows 11 Notepad did return at once. A round trip
65
+ that changes the buffer is asked for on the PR, and the default ships as the better of
66
+ the two available answers: nano could not start at all.
67
+
68
+ ### What you will notice
69
+
70
+ - A first `mirror init` with a name that is not a plain lowercase word is refused with
71
+ a name that would work. `mirror init your-name`, as Getting Started has always said,
72
+ is unchanged.
73
+ - On Windows, `mirror identity edit …` opens Notepad instead of failing.
74
+ - Nothing else.
75
+
76
+ ## Upgrading
77
+
78
+ ```bash
79
+ mirror runtime update # or: npm install -g mirror-mind
80
+ ```
81
+
82
+ There is no database migration and no runtime wiring change. If `v0.33.3`'s note applied
83
+ to you, a Mirror under a path with a space or an accent, and you have not yet taken its
84
+ one manual update, take it first; from `v0.33.3` on, `runtime update` works.
85
+
86
+ ## Conscious Exclusions
87
+
88
+ - **Selecting an existing home by name.** `MIRROR_USER` pointing at a home created
89
+ before this rule, under whatever name, still selects it. Only creation is constrained.
90
+ - **`init`'s wiring lines on Windows.** They print `ln -s`, which Windows has no answer
91
+ to; that waits for the runtimes to be verified there, as the contributor's PR says.
92
+ - **Counting verdicts as front-door errors** (CR139), unchanged since `v0.33.2`.
93
+
94
+ ## What We Learned
95
+
96
+ A fix for an instance invites the question "what else?", and the cheapest moment to ask
97
+ it is the review. The `-` check was right; the class was one line away, and the rule
98
+ already existed with its sentence, its escaping, and its tests. When a repository has
99
+ decided something once, the second instance should cost a reference, not a decision.
100
+
101
+ ## Next Horizon
102
+
103
+ CR139, the verdict loop, is the visible next step in RS010. The contributor's Windows
104
+ series, if it comes, begins with the suite on `windows-latest`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.33.3",
3
+ "version": "0.33.4",
4
4
  "description": "Mirror Mind: a local-first memory and identity framework for agentic AI runtimes (Pi, Claude Code, Gemini CLI, Codex)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.33.3",
3
+ "version": "0.33.4",
4
4
  "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
5
5
  "author": {
6
6
  "name": "Mirror Mind"
@@ -43,7 +43,7 @@ import { type Database, openDatabaseReadOnly, type WritableDatabase } from "#db/
43
43
  import { ensureDatabaseReady } from "#db/readyOnOpen.ts";
44
44
  import { assertSchemaState, SchemaStateError } from "#db/schemaState.ts";
45
45
  import { allDescriptors, descriptorsByLayer } from "#descriptor/descriptorRead.ts";
46
- import { newIdentityKeyProblem } from "#identity/identityKey.ts";
46
+ import { newIdentityKeyProblem, newUserNameProblem } from "#identity/identityKey.ts";
47
47
  import { identityRowExists, listIdentityByLayer } from "#identity/identityRead.ts";
48
48
  import { listJourneysForListCommand } from "#identity/journeyListing.ts";
49
49
  import { listPersonas } from "#identity/personaListing.ts";
@@ -885,10 +885,20 @@ function isInit(argv: readonly string[]): boolean {
885
885
  */
886
886
  function runInit(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): number {
887
887
  const user = argv[1];
888
- if (!user) {
888
+ // A word that starts with `-` is a flag this command does not have, not a
889
+ // user: `init --help` once created a home named `--help`.
890
+ if (!user || user.startsWith("-")) {
889
891
  console.error("Usage: init <user>");
890
892
  return 2;
891
893
  }
894
+ // And any other word is a key: the home's directory name and the value of
895
+ // MIRROR_USER in the wiring commands printed below. CR104's rule for slugs
896
+ // and persona ids, asked before anything is created (CR141).
897
+ const problem = newUserNameProblem(user);
898
+ if (problem !== null) {
899
+ console.error(`Error: ${problem}`);
900
+ return 2;
901
+ }
892
902
  try {
893
903
  const identityRoot = initUserHome(user);
894
904
  const mirrorHome = dirname(identityRoot);
@@ -34,6 +34,8 @@ export interface IdentityEditDeps {
34
34
  /** The editor argv[0], resolved the way Python resolves it. */
35
35
  editor?: string | undefined;
36
36
  visual?: string | undefined;
37
+ /** Decides the editor when neither variable is set. Default: this process's. */
38
+ platform?: NodeJS.Platform;
37
39
  nowIso: () => string;
38
40
  /** Injected so a test can watch the buffer without driving a real editor. */
39
41
  spawn?: typeof spawnSync;
@@ -42,9 +44,16 @@ export interface IdentityEditDeps {
42
44
  newId: () => string;
43
45
  }
44
46
 
45
- /** Python's `os.environ.get("EDITOR") or os.environ.get("VISUAL") or "nano"`. */
47
+ /**
48
+ * Python's `os.environ.get("EDITOR") or os.environ.get("VISUAL") or "nano"`,
49
+ * with one platform default: Windows has no nano outside Git Bash, so a
50
+ * PowerShell user with neither variable set saw `Editor 'nano' could not be
51
+ * started.` Notepad ships with every Windows and blocks until it is closed,
52
+ * which is what the edit needs. Either variable still wins when set.
53
+ */
46
54
  export function resolveEditor(deps: IdentityEditDeps): string {
47
- return deps.editor || deps.visual || "nano";
55
+ const platform = deps.platform ?? process.platform;
56
+ return deps.editor || deps.visual || (platform === "win32" ? "notepad" : "nano");
48
57
  }
49
58
 
50
59
  /**
@@ -44,11 +44,27 @@ export function identityKeyBreaksGrammar(layer: string, key: string): boolean {
44
44
  */
45
45
  export function newIdentityKeyProblem(layer: string, key: string): string | null {
46
46
  const keyName = KEY_NAMES.get(layer);
47
- if (keyName === undefined || isKebabSlug(key)) return null;
47
+ if (keyName === undefined) return null;
48
+ return keyProblem(layer, keyName, key);
49
+ }
50
+
51
+ /**
52
+ * Why `init` cannot take `user`, or null when it can (CR141). A user name is
53
+ * a key of the same kind: it becomes `~/.mirror-minds/<user>` and the value of
54
+ * `MIRROR_USER` in every wiring command `init` prints. `init ../escaped` once
55
+ * created `~/escaped`, outside the directory the product owns.
56
+ */
57
+ export function newUserNameProblem(user: string): string | null {
58
+ return keyProblem("user home", "user name", user);
59
+ }
60
+
61
+ /** The one sentence every key refusal is, or null when `key` is a kebab-case slug. */
62
+ function keyProblem(created: string, keyName: string, key: string): string | null {
63
+ if (isKebabSlug(key)) return null;
48
64
  const suggestion = kebabSlug(key);
49
65
  const example = suggestion ? `, for example ${pyRepr(suggestion)}` : "";
50
66
  return (
51
- `no ${layer} was created: ${pyRepr(key)} is not a ${keyName}. ` +
67
+ `no ${created} was created: ${pyRepr(key)} is not a ${keyName}. ` +
52
68
  `Use lowercase letters, digits, and single hyphens, up to 80 characters${example}.`
53
69
  );
54
70
  }