mirror-mind 0.34.0 → 0.36.0

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.
Files changed (58) hide show
  1. package/.pi/skills/mm-build/SKILL.md +1 -1
  2. package/.pi/skills/mm-explore/SKILL.md +1 -1
  3. package/.pi/skills/mm-help/SKILL.md +2 -2
  4. package/.pi/skills/mm-soul/SKILL.md +1 -1
  5. package/AGENTS.md +7 -6
  6. package/README.md +5 -6
  7. package/docs/releases/index.md +2 -0
  8. package/docs/releases/v0.35.0.md +199 -0
  9. package/docs/releases/v0.36.0.md +222 -0
  10. package/package.json +2 -4
  11. package/plugins/mirror-mind/.claude-plugin/plugin.json +1 -1
  12. package/plugins/mirror-mind/agy/pre-invocation.sh +92 -0
  13. package/plugins/mirror-mind/agy/session-start.sh +92 -0
  14. package/plugins/mirror-mind/agy/stop.sh +92 -0
  15. package/plugins/mirror-mind/hooks.json +26 -0
  16. package/plugins/mirror-mind/mcp_config.json +11 -0
  17. package/plugins/mirror-mind/plugin.json +14 -0
  18. package/plugins/mirror-mind/rules/AGENTS.md +189 -0
  19. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1 -1
  20. package/ts/src/conversation/agyTranscript.ts +105 -0
  21. package/ts/src/conversation/commandTurn.ts +1 -1
  22. package/ts/src/conversation/deferredCloseTail.ts +2 -2
  23. package/ts/src/conversation/logger.ts +1 -1
  24. package/ts/src/conversation/runtimePolicy.ts +36 -4
  25. package/ts/src/docs/docsLint.ts +14 -1
  26. package/ts/src/extensions/bindings.ts +53 -2
  27. package/ts/src/extensions/catalogCommands.ts +82 -23
  28. package/ts/src/extensions/catalogWrites.ts +12 -0
  29. package/ts/src/extensions/contextRuntime.ts +36 -1
  30. package/ts/src/extensions/dispatch.ts +43 -10
  31. package/ts/src/extensions/extensionPlugin.ts +325 -0
  32. package/ts/src/extensions/manifest.ts +7 -1
  33. package/ts/src/extensions/settings.ts +147 -0
  34. package/ts/src/frontDoor/cli.ts +16 -1
  35. package/ts/src/frontDoor/extensionCatalogRoute.ts +8 -2
  36. package/ts/src/frontDoor/runtimeRoute.ts +11 -0
  37. package/ts/src/frontDoor/stdinSource.ts +2 -2
  38. package/ts/src/guards/claudePlugin.ts +91 -0
  39. package/ts/src/guards/packContents.ts +3 -2
  40. package/ts/src/guards/piSourcedSkills.ts +1 -1
  41. package/ts/src/guards/retiredSurfaces.ts +31 -0
  42. package/ts/src/hooks/agy.ts +229 -0
  43. package/ts/src/hooks/claude.ts +6 -0
  44. package/ts/src/hooks/main.ts +23 -16
  45. package/ts/src/hooks/payload.ts +8 -15
  46. package/ts/src/hooks/runtime.ts +11 -10
  47. package/ts/src/runtime/brokenPipe.ts +52 -0
  48. package/ts/src/runtime/diagnose.ts +122 -13
  49. package/ts/src/runtime/extensionPluginWiring.ts +222 -0
  50. package/ts/src/runtime/pluginWiring.ts +168 -29
  51. package/ts/src/runtime/status.ts +28 -1
  52. package/ts/src/runtime/update.ts +25 -11
  53. package/ts/src/runtime/wiring.ts +149 -35
  54. package/.gemini/hooks/log-assistant.sh +0 -85
  55. package/.gemini/hooks/log-user.sh +0 -85
  56. package/.gemini/hooks/session-end.sh +0 -85
  57. package/.gemini/hooks/session-start.sh +0 -85
  58. package/ts/src/hooks/gemini.ts +0 -128
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every agy wrapper shares these bytes
4
+ # and differs only in the hook name and its stdin gate (CV21.DS7.TS2).
5
+ set -uo pipefail
6
+ HOOK="agy:pre-invocation"
7
+ GATE='"invocationNum"[[:space:]]*:[[:space:]]*-?[0-9]*[1-9]'
8
+
9
+ # agy sends the payload on stdin and blocks its agent loop on this process.
10
+ # Read the payload once; when the gate says this invocation is not ours,
11
+ # answer the empty object without resolving Node at all.
12
+ PAYLOAD="$(cat 2>/dev/null || true)"
13
+ if [ -n "$GATE" ] && printf '%s' "$PAYLOAD" | grep -qE "$GATE"; then
14
+ printf '{}\n'
15
+ exit 0
16
+ fi
17
+
18
+ # This wrapper is the Claude plugin's, and a plugin is copied into Claude
19
+ # Code's own cache: no relative path from here reaches the Mirror tree. It
20
+ # runs the installed `mirror-hook` bin instead, and reads no tree .env.
21
+ REPO_ROOT=""
22
+
23
+ # A hook never fails the user's turn, and never fails in silence: when it
24
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits
25
+ # 0 — after answering agy's contract, so the agent loop never stalls.
26
+ env_file_value() {
27
+ [ -f "$2" ] || return 0
28
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
29
+ | tail -n 1 | tr -d "\"'\r"
30
+ }
31
+ env_value() {
32
+ local value=""
33
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
34
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
35
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
36
+ printf '%s' "$value"
37
+ }
38
+ note() {
39
+ local home user
40
+ home="$(env_value MIRROR_HOME)"
41
+ user="$(env_value MIRROR_USER)"
42
+ if [ -n "$home" ]; then
43
+ home="${home/#\~/$HOME}"
44
+ elif [ -n "$user" ]; then
45
+ home="$HOME/.mirror-minds/$user"
46
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
47
+ else
48
+ return 0
49
+ fi
50
+ mkdir -p "$home" 2>/dev/null \
51
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
52
+ }
53
+
54
+ # Find the bin. MIRROR_BIN names the directory holding `mirror` and
55
+ # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
56
+ # then the PATH; then the global bin directories a GUI launch leaves off it.
57
+ ENTRY=""
58
+ [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
59
+ [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
60
+ for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
61
+ [ -n "$ENTRY" ] && break
62
+ [ -x "$candidate/mirror-hook" ] && ENTRY="$candidate/mirror-hook"
63
+ done
64
+
65
+ if [ -z "$ENTRY" ]; then
66
+ note "mirror-hook not found on PATH; hook skipped. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory."
67
+ exit 0
68
+ fi
69
+
70
+ NODE="${MIRROR_NODE:-}"
71
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
72
+ NODE="$(command -v node 2>/dev/null || true)"
73
+ fi
74
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
75
+ [ -n "$NODE" ] && break
76
+ [ -x "$candidate" ] && NODE="$candidate"
77
+ done
78
+
79
+ if [ -z "$NODE" ]; then
80
+ printf '{}\n'
81
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
82
+ exit 0
83
+ fi
84
+
85
+ # The payload was consumed by the gate read; re-feed it to the entry.
86
+ printf '%s' "$PAYLOAD" | "$NODE" "$ENTRY" "$HOOK" "$@"
87
+ status=$?
88
+ if [ "$status" -ne 0 ]; then
89
+ printf '{}\n'
90
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
91
+ fi
92
+ exit 0
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every agy wrapper shares these bytes
4
+ # and differs only in the hook name and its stdin gate (CV21.DS7.TS2).
5
+ set -uo pipefail
6
+ HOOK="agy:session-start"
7
+ GATE=''
8
+
9
+ # agy sends the payload on stdin and blocks its agent loop on this process.
10
+ # Read the payload once; when the gate says this invocation is not ours,
11
+ # answer the empty object without resolving Node at all.
12
+ PAYLOAD="$(cat 2>/dev/null || true)"
13
+ if [ -n "$GATE" ] && printf '%s' "$PAYLOAD" | grep -qE "$GATE"; then
14
+ printf '{}\n'
15
+ exit 0
16
+ fi
17
+
18
+ # This wrapper is the Claude plugin's, and a plugin is copied into Claude
19
+ # Code's own cache: no relative path from here reaches the Mirror tree. It
20
+ # runs the installed `mirror-hook` bin instead, and reads no tree .env.
21
+ REPO_ROOT=""
22
+
23
+ # A hook never fails the user's turn, and never fails in silence: when it
24
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits
25
+ # 0 — after answering agy's contract, so the agent loop never stalls.
26
+ env_file_value() {
27
+ [ -f "$2" ] || return 0
28
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
29
+ | tail -n 1 | tr -d "\"'\r"
30
+ }
31
+ env_value() {
32
+ local value=""
33
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
34
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
35
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
36
+ printf '%s' "$value"
37
+ }
38
+ note() {
39
+ local home user
40
+ home="$(env_value MIRROR_HOME)"
41
+ user="$(env_value MIRROR_USER)"
42
+ if [ -n "$home" ]; then
43
+ home="${home/#\~/$HOME}"
44
+ elif [ -n "$user" ]; then
45
+ home="$HOME/.mirror-minds/$user"
46
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
47
+ else
48
+ return 0
49
+ fi
50
+ mkdir -p "$home" 2>/dev/null \
51
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
52
+ }
53
+
54
+ # Find the bin. MIRROR_BIN names the directory holding `mirror` and
55
+ # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
56
+ # then the PATH; then the global bin directories a GUI launch leaves off it.
57
+ ENTRY=""
58
+ [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
59
+ [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
60
+ for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
61
+ [ -n "$ENTRY" ] && break
62
+ [ -x "$candidate/mirror-hook" ] && ENTRY="$candidate/mirror-hook"
63
+ done
64
+
65
+ if [ -z "$ENTRY" ]; then
66
+ note "mirror-hook not found on PATH; hook skipped. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory."
67
+ exit 0
68
+ fi
69
+
70
+ NODE="${MIRROR_NODE:-}"
71
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
72
+ NODE="$(command -v node 2>/dev/null || true)"
73
+ fi
74
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
75
+ [ -n "$NODE" ] && break
76
+ [ -x "$candidate" ] && NODE="$candidate"
77
+ done
78
+
79
+ if [ -z "$NODE" ]; then
80
+ printf '{}\n'
81
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
82
+ exit 0
83
+ fi
84
+
85
+ # The payload was consumed by the gate read; re-feed it to the entry.
86
+ printf '%s' "$PAYLOAD" | "$NODE" "$ENTRY" "$HOOK" "$@"
87
+ status=$?
88
+ if [ "$status" -ne 0 ]; then
89
+ printf '{}\n'
90
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
91
+ fi
92
+ exit 0
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every agy wrapper shares these bytes
4
+ # and differs only in the hook name and its stdin gate (CV21.DS7.TS2).
5
+ set -uo pipefail
6
+ HOOK="agy:stop"
7
+ GATE=''
8
+
9
+ # agy sends the payload on stdin and blocks its agent loop on this process.
10
+ # Read the payload once; when the gate says this invocation is not ours,
11
+ # answer the empty object without resolving Node at all.
12
+ PAYLOAD="$(cat 2>/dev/null || true)"
13
+ if [ -n "$GATE" ] && printf '%s' "$PAYLOAD" | grep -qE "$GATE"; then
14
+ printf '{}\n'
15
+ exit 0
16
+ fi
17
+
18
+ # This wrapper is the Claude plugin's, and a plugin is copied into Claude
19
+ # Code's own cache: no relative path from here reaches the Mirror tree. It
20
+ # runs the installed `mirror-hook` bin instead, and reads no tree .env.
21
+ REPO_ROOT=""
22
+
23
+ # A hook never fails the user's turn, and never fails in silence: when it
24
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits
25
+ # 0 — after answering agy's contract, so the agent loop never stalls.
26
+ env_file_value() {
27
+ [ -f "$2" ] || return 0
28
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
29
+ | tail -n 1 | tr -d "\"'\r"
30
+ }
31
+ env_value() {
32
+ local value=""
33
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
34
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
35
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
36
+ printf '%s' "$value"
37
+ }
38
+ note() {
39
+ local home user
40
+ home="$(env_value MIRROR_HOME)"
41
+ user="$(env_value MIRROR_USER)"
42
+ if [ -n "$home" ]; then
43
+ home="${home/#\~/$HOME}"
44
+ elif [ -n "$user" ]; then
45
+ home="$HOME/.mirror-minds/$user"
46
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
47
+ else
48
+ return 0
49
+ fi
50
+ mkdir -p "$home" 2>/dev/null \
51
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
52
+ }
53
+
54
+ # Find the bin. MIRROR_BIN names the directory holding `mirror` and
55
+ # `mirror-hook` (`$(npm prefix -g)/bin`), the same trust class as MIRROR_NODE;
56
+ # then the PATH; then the global bin directories a GUI launch leaves off it.
57
+ ENTRY=""
58
+ [ -n "${MIRROR_BIN:-}" ] && [ -x "$MIRROR_BIN/mirror-hook" ] && ENTRY="$MIRROR_BIN/mirror-hook"
59
+ [ -n "$ENTRY" ] || ENTRY="$(command -v mirror-hook 2>/dev/null || true)"
60
+ for candidate in "$HOME/.nvm/current/bin" /opt/homebrew/bin /usr/local/bin; do
61
+ [ -n "$ENTRY" ] && break
62
+ [ -x "$candidate/mirror-hook" ] && ENTRY="$candidate/mirror-hook"
63
+ done
64
+
65
+ if [ -z "$ENTRY" ]; then
66
+ note "mirror-hook not found on PATH; hook skipped. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory."
67
+ exit 0
68
+ fi
69
+
70
+ NODE="${MIRROR_NODE:-}"
71
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
72
+ NODE="$(command -v node 2>/dev/null || true)"
73
+ fi
74
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
75
+ [ -n "$NODE" ] && break
76
+ [ -x "$candidate" ] && NODE="$candidate"
77
+ done
78
+
79
+ if [ -z "$NODE" ]; then
80
+ printf '{}\n'
81
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
82
+ exit 0
83
+ fi
84
+
85
+ # The payload was consumed by the gate read; re-feed it to the entry.
86
+ printf '%s' "$PAYLOAD" | "$NODE" "$ENTRY" "$HOOK" "$@"
87
+ status=$?
88
+ if [ "$status" -ne 0 ]; then
89
+ printf '{}\n'
90
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
91
+ fi
92
+ exit 0
@@ -0,0 +1,26 @@
1
+ {
2
+ "mirror-mind": {
3
+ "enabled": true,
4
+ "SessionStart": [
5
+ {
6
+ "type": "command",
7
+ "command": "agy/session-start.sh",
8
+ "timeout": 60
9
+ }
10
+ ],
11
+ "PreInvocation": [
12
+ {
13
+ "type": "command",
14
+ "command": "agy/pre-invocation.sh",
15
+ "timeout": 15
16
+ }
17
+ ],
18
+ "Stop": [
19
+ {
20
+ "type": "command",
21
+ "command": "agy/stop.sh",
22
+ "timeout": 30
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "mirror-mind": {
4
+ "command": "/bin/sh",
5
+ "args": [
6
+ "-c",
7
+ "mmbin=\"\"; [ -n \"$MIRROR_BIN\" ] && [ -x \"$MIRROR_BIN/mirror\" ] && mmbin=\"$MIRROR_BIN/mirror\"; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmnode=\"$MIRROR_NODE\"; [ -n \"$mmnode\" ] && [ -x \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
8
+ ]
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "mirror-mind",
3
+ "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
4
+ "version": "0.36.0",
5
+ "mcpServers": {
6
+ "mirror-mind": {
7
+ "command": "/bin/sh",
8
+ "args": [
9
+ "-c",
10
+ "mmbin=\"\"; [ -n \"$MIRROR_BIN\" ] && [ -x \"$MIRROR_BIN/mirror\" ] && mmbin=\"$MIRROR_BIN/mirror\"; [ -n \"$mmbin\" ] || mmbin=\"$(command -v mirror 2>/dev/null)\"; for mmdir in \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmbin\" ] && break; [ -x \"$mmdir/mirror\" ] && mmbin=\"$mmdir/mirror\"; done; [ -n \"$mmbin\" ] || { echo \"mirror-mind: mirror not found on PATH; the MCP server cannot start. Install mirror-mind (npm install -g mirror-mind) or set MIRROR_BIN to its bin directory.\" >&2; exit 1; }; mmnode=\"$MIRROR_NODE\"; [ -n \"$mmnode\" ] && [ -x \"$mmnode\" ] || mmnode=\"$(command -v node 2>/dev/null)\"; for mmdir in \"$(dirname \"$mmbin\")\" \"$HOME/.nvm/current/bin\" /opt/homebrew/bin /usr/local/bin; do [ -n \"$mmnode\" ] && break; [ -x \"$mmdir/node\" ] && mmnode=\"$mmdir/node\"; done; [ -n \"$mmnode\" ] || { echo \"mirror-mind: node not found on PATH; the MCP server cannot start. Set MIRROR_NODE.\" >&2; exit 1; }; exec \"$mmnode\" \"$mmbin\" mcp"
11
+ ]
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,189 @@
1
+ # Mirror Mind — Operating Instructions
2
+
3
+ <!-- This file ships in the mirror-mind package and is read by every runtime:
4
+ Pi and Codex read it from the project root natively, Claude Code through
5
+ the @AGENTS.md import in CLAUDE.md, Antigravity as the plugin's rules, and
6
+ an installed package's Pi session receives it from the extension
7
+ (CV22.DS10.US3, D13).
8
+ It holds nothing about this repository; that is CLAUDE.md's part. -->
9
+
10
+ ---
11
+
12
+ ## Mirror Operating Instructions
13
+
14
+ Applies to all sessions, regardless of project.
15
+
16
+ ### Operating Modes
17
+
18
+ The mirror operates in four modes, chosen automatically based on context:
19
+ Mirror, Builder, Explorer, and Soul.
20
+
21
+ Each skill below is named three ways: `/mm-<skill>` in Pi and Grok Build,
22
+ `$mm-<skill>` in Codex, `/mm:<skill>` in Claude Code. A **plugin** install prefixes the plugin's
23
+ name — `/mirror-mind:mm:<skill>` in Claude Code's menu and in Antigravity,
24
+ `$mirror-mind:mm:<skill>` in Codex, `mirror-mind:mm-<skill>` as the skill's own
25
+ name — and the forms below refer to the same skill.
26
+
27
+ **Mirror Mode** — activate for: life decisions, feelings, business strategy,
28
+ writing, mentoring, health, existential questions, sensemaking, psychological
29
+ tensions, class preparation, product launches, or any topic asking for personal
30
+ reflection or positioning.
31
+
32
+ How to operate: load the mirror skill (`/mm-mirror`, `$mm-mirror`, or
33
+ `/mm:mirror`). Load identity, route persona, search attachments, answer in first
34
+ person, and record the response.
35
+
36
+ **Builder Mode** — activate for: code, project structure, YAML editing, bugs,
37
+ implementation, architecture, or any software engineering task.
38
+
39
+ How to operate: read code, edit files, run commands, propose technical
40
+ solutions, keep docs updated when code changes. For a journey, use `/mm-build
41
+ <slug>` / `$mm-build <slug>` / `/mm:build <slug>` — loads journey context and
42
+ project docs.
43
+
44
+ **Explorer Mode** — activate for the uncertain middle before construction:
45
+ exploring a possibility space, holding uncertainty before committing to build,
46
+ developing an Exploratory Story, surfacing attractors, proposing small
47
+ experiments, or preparing a Builder handoff.
48
+
49
+ How to operate: use `/mm-explore <slug>` / `$mm-explore <slug>` / `/mm:explore
50
+ <slug>` to activate Explorer Mode for a journey and resume the active Exploratory
51
+ Story when one exists. Follow `.pi/skills/mm-explore/SKILL.md`: render required
52
+ story surfaces before interpretation, thicken the story as the narrative changes,
53
+ and keep exploration durable. Explorer Mode preserves uncertainty — it does not
54
+ implement. For operational mutation requests, name the `△ EXPLORER → BUILDER
55
+ BOUNDARY` and route them through Builder Mode via an explicit handoff. Deactivate
56
+ with `/mm-explore deactivate`, which returns to Mirror Mode while preserving
57
+ sticky journey context.
58
+
59
+ **Soul Mode** — activate when the user asks to enter Soul Mode, open Soul Mode,
60
+ or continue an active Soul Mode ritual.
61
+
62
+ How to operate: use `/mm-soul [slug]` / `$mm-soul [slug]` / `/mm:soul [slug]`
63
+ to render the entry surface. While Soul Mode is active, follow
64
+ `.pi/skills/mm-soul/SKILL.md`: listen to the user's answer as living field,
65
+ withhold Possible Listenings when the material is thin, and when living matter
66
+ appears call the front door's `soul listen ...` (the exact invocation is in the
67
+ skill) to render the Possible Listenings surface at the end of the response. This renderer call is required
68
+ Soul Mode behavior, not optional tool use. Soul Mode must not mutate files,
69
+ implement stories, run implementation commands, package releases, or change
70
+ project state. For operational requests, name the `☾ SOUL → BUILDER BOUNDARY`
71
+ and ask whether to switch to Builder Mode for the same journey.
72
+
73
+ Builder activation boundary: activating Builder Mode or loading a journey is
74
+ context setup only. After loading the context and required docs, stop and ask
75
+ what work should be done next. Do not edit files, create tests, run
76
+ implementation, start TDD, or mutate project state until the user gives an
77
+ explicit implementation or documentation instruction, such as implement, fix,
78
+ edit, create, run tests, or names a specific story to execute. Context
79
+ activation is not execution consent.
80
+
81
+ Ariad surface transport invariant: when command output contains marked Ariad
82
+ surface blocks (`<<<ARIAD:...>>>` through `<<<END:...>>>`), preserve every marked
83
+ block verbatim in the assistant response before commentary. Do not summarize,
84
+ translate, reorder, trim, rewrap, re-indent, reformat, or replace marked surfaces
85
+ with prose. This is a general rendering rule for Ariad surfaces, not a
86
+ phase-specific behavior.
87
+
88
+ **Ambiguity:** if the mode is unclear, ask whether the user wants personal
89
+ reflection or project construction.
90
+
91
+ **Journey Status** — shortcut within Mirror Mode. Activate when the user asks
92
+ "How are we doing?", "What's the status of X?", or any question about progress
93
+ or roadmap. Dispatch to `/mm-journey` or `/mm-journeys`. When the user asks for
94
+ the journey list in natural language, use `/mm-journeys` and preserve its
95
+ hierarchical rendering without flattening or reformatting it.
96
+
97
+ **Commits:** use descriptive English commit messages. Explain the WHY, not just
98
+ what was done. Prefer small commits with clear review boundaries.
99
+
100
+ ### Ego-Persona Model
101
+
102
+ The mirror has one voice: the ego. Personas are specialized lenses activated by
103
+ the ego according to context.
104
+
105
+ **Automatic routing:** activate a persona when the topic clearly belongs to a
106
+ specialized domain, the depth required exceeds the generic ego repertoire, or
107
+ the user explicitly asks for a persona.
108
+
109
+ **Routing protocol:** persona routing is data-driven. Each persona in the
110
+ database carries `routing_keywords` and a routing descriptor. At runtime,
111
+ `detectPersona` (`ts/src/persona/detectPersona.ts`) scores the query against
112
+ those keywords. If no persona scores above threshold, the ego answers alone. To
113
+ inspect active routing:
114
+ `mirror detect-persona "<query>"`.
115
+
116
+ **Signature format:**
117
+
118
+ When the ego answers alone — no signature.
119
+
120
+ When a persona is active:
121
+ ```text
122
+ ◇ persona-name
123
+
124
+ [first-person answer, unified voice]
125
+ ```
126
+
127
+ When switching personas:
128
+ ```text
129
+ ◇ product-designer
130
+
131
+ [analysis...]
132
+
133
+ ◇ therapist
134
+
135
+ [reflection...]
136
+ ```
137
+
138
+ Rules: `◇` plus persona name on its own line; voice stays first person and unified.
139
+
140
+ ### Hard Constraints
141
+
142
+ - **Truth:** do not invent data. If uncertain, say so.
143
+ - **Service:** intellectual partner, not task executor. Question, refine, align —
144
+ do not execute without thinking.
145
+
146
+ ### Available Skills
147
+
148
+ **Core modes:**
149
+ - `mm-mirror` — activates Mirror Mode — `.pi/skills/mm-mirror/SKILL.md`
150
+ - `mm-build` — activates Builder Mode for a journey — `.pi/skills/mm-build/SKILL.md`
151
+ - `mm-explore` — activates Explorer Mode for a journey — `.pi/skills/mm-explore/SKILL.md`
152
+ - `mm-soul` — activates Soul Mode ritual entry — `.pi/skills/mm-soul/SKILL.md`
153
+
154
+ **Journeys and tasks:**
155
+ - `mm-journeys` — compact journey list — `.pi/skills/mm-journeys/SKILL.md`
156
+ - `mm-journey` — detailed journey status — `.pi/skills/mm-journey/SKILL.md`
157
+ - `mm-tasks` — task management by journey — `.pi/skills/mm-tasks/SKILL.md`
158
+ - `mm-week` — weekly planning — `.pi/skills/mm-week/SKILL.md`
159
+
160
+ **Memory and inspection:**
161
+ - `mm-memories` — recorded memories — `.pi/skills/mm-memories/SKILL.md`
162
+ - `mm-conversations` — recent conversations list — `.pi/skills/mm-conversations/SKILL.md`
163
+ - `mm-recall` — load messages from a previous conversation — `.pi/skills/mm-recall/SKILL.md`
164
+
165
+ **Content:**
166
+ - `mm-journal` — personal journal entry — `.pi/skills/mm-journal/SKILL.md`
167
+
168
+ **Identity:**
169
+ - `mm-seed` — seed identity YAML files into the database — `.pi/skills/mm-seed/SKILL.md`
170
+ - `mm-identity` — read and update identity in the database — `.pi/skills/mm-identity/SKILL.md`
171
+
172
+ **Session control:**
173
+ - `mm-mute` — toggle conversation logging — `.pi/skills/mm-mute/SKILL.md`
174
+ - `mm-new` — start a new conversation — `.pi/skills/mm-new/SKILL.md`
175
+ - `mm-discard` — discard the current conversation before quitting — `.pi/skills/mm-discard/SKILL.md`
176
+
177
+ **Memory cultivation:**
178
+ - `mm-consolidate` — scan memories for patterns and propose consolidation — `.pi/skills/mm-consolidate/SKILL.md`
179
+ - `mm-shadow` — surface and promote shadow-layer observations — `.pi/skills/mm-shadow/SKILL.md`
180
+
181
+ **Utilities:**
182
+ - `mm-consult` — ask other LLMs through OpenRouter — `.pi/skills/mm-consult/SKILL.md`
183
+ - `mm-backup` — memory database backup — `.pi/skills/mm-backup/SKILL.md`
184
+ - `mm-welcome` — render the state-aware welcome card on demand — `.pi/skills/mm-welcome/SKILL.md`
185
+ - `mm-release-notes` — show Mirror Mind release notes — `.pi/skills/mm-release-notes/SKILL.md`
186
+ - `mm-update` — update the local Mirror runtime — `.pi/skills/mm-update/SKILL.md`
187
+ - `mm-help` — list available commands — `.pi/skills/mm-help/SKILL.md`
188
+
189
+ Full command reference: [REFERENCE.md](REFERENCE.md)
@@ -10,7 +10,7 @@ Activates Builder Mode for a specific journey. Loads identity context and projec
10
10
 
11
11
  ## Usage
12
12
 
13
- Pi and Gemini CLI:
13
+ Pi:
14
14
 
15
15
  ```
16
16
  /mm-build <journey-slug>
@@ -0,0 +1,105 @@
1
+ // The agy transcript reader (CV21.DS7.TS3).
2
+ //
3
+ // Antigravity's hook payloads carry no prompt or reply text (DS12.TS3 L7),
4
+ // so logging reads the JSONL transcript agy writes. The step shapes are
5
+ // probed from real agy 1.4.2 transcripts (the US1/TS2/TS3 probe discipline;
6
+ // evidence/transcript-shape-20261018.md):
7
+ //
8
+ // * a user turn is a `USER_INPUT` step (`source: USER_EXPLICIT`) whose
9
+ // content string wraps the request in `<USER_REQUEST>…</USER_REQUEST>`,
10
+ // followed by `<ADDITIONAL_METADATA>…`;
11
+ // * the model's answer is the `PLANNER_RESPONSE` step(s) (`source: MODEL`).
12
+ // MOST of these are EMPTY — intermediate tool steps — so the reply is
13
+ // the non-empty response text in the turn, not every PLANNER_RESPONSE.
14
+ // (The plan guessed `AGENT_MESSAGE`; the probe corrected both the type
15
+ // and the emptiness.)
16
+ //
17
+ // A turn runs from one `USER_INPUT` to just before the next. `step_index` is
18
+ // a monotonic per-conversation ordinal; `lastStepIndex` is the turn's
19
+ // high-water mark, which the Stop handler stores so a re-fired Stop logs
20
+ // nothing new.
21
+
22
+ import { parseJsonl } from "#conversation/transcriptBackfill.ts";
23
+
24
+ export interface AgyTurn {
25
+ readonly userText: string;
26
+ readonly replyText: string;
27
+ /** The highest step_index this turn logged: the idempotency high-water mark. */
28
+ readonly lastStepIndex: number;
29
+ }
30
+
31
+ function stringField(entry: unknown, key: string): string {
32
+ if (typeof entry !== "object" || entry === null) return "";
33
+ const value = (entry as Record<string, unknown>)[key];
34
+ return typeof value === "string" ? value : "";
35
+ }
36
+
37
+ function numberField(entry: unknown, key: string): number {
38
+ if (typeof entry !== "object" || entry === null) return -1;
39
+ const value = (entry as Record<string, unknown>)[key];
40
+ return typeof value === "number" ? value : -1;
41
+ }
42
+
43
+ const USER_REQUEST = /<USER_REQUEST>\s*([\s\S]*?)\s*<\/USER_REQUEST>/;
44
+
45
+ /** The request body agy wraps the user's words in, or the whole string. */
46
+ export function extractUserRequest(content: string): string {
47
+ const match = USER_REQUEST.exec(content);
48
+ return (match ? match[1] : content).trim();
49
+ }
50
+
51
+ /**
52
+ * Join the turn's non-empty reply parts into one string, the same rule the
53
+ * Claude reader's `assistantText` uses (trimmed, blank-line separated).
54
+ * Shared so "how parts become one reply" lives in one place.
55
+ */
56
+ export function joinReplyParts(parts: readonly string[]): string {
57
+ return parts
58
+ .map((part) => part.trim())
59
+ .filter((part) => part.length > 0)
60
+ .join("\n\n");
61
+ }
62
+
63
+ /**
64
+ * The transcript's completed turns, in order. Each pairs the user's request
65
+ * with the model's reply and carries the turn's high-water `step_index`.
66
+ * A turn with no reply text yet (the model has not answered) is omitted:
67
+ * the Stop handler logs it on the Stop that follows the answer.
68
+ */
69
+ export function readAgyTurns(transcriptPath: string): AgyTurn[] {
70
+ let entries: unknown[];
71
+ try {
72
+ entries = parseJsonl(transcriptPath);
73
+ } catch {
74
+ return [];
75
+ }
76
+
77
+ const turns: AgyTurn[] = [];
78
+ let userText: string | null = null;
79
+ let replyParts: string[] = [];
80
+ let lastStepIndex = -1;
81
+
82
+ const flush = () => {
83
+ if (userText === null) return;
84
+ const replyText = joinReplyParts(replyParts);
85
+ if (replyText) turns.push({ userText, replyText, lastStepIndex });
86
+ userText = null;
87
+ replyParts = [];
88
+ };
89
+
90
+ for (const entry of entries) {
91
+ const type = stringField(entry, "type");
92
+ if (type === "USER_INPUT") {
93
+ flush();
94
+ userText = extractUserRequest(stringField(entry, "content"));
95
+ lastStepIndex = numberField(entry, "step_index");
96
+ } else if (type === "PLANNER_RESPONSE" && userText !== null) {
97
+ const content = stringField(entry, "content").trim();
98
+ const stepIndex = numberField(entry, "step_index");
99
+ if (stepIndex > lastStepIndex) lastStepIndex = stepIndex;
100
+ if (content) replyParts.push(content);
101
+ }
102
+ }
103
+ flush();
104
+ return turns;
105
+ }
@@ -29,7 +29,7 @@
29
29
  // One rule for every runtime: a Claude Code user who types `$mm-journeys`,
30
30
  // which Claude Code does not understand, is still invoking a skill, and that
31
31
  // turn is not conversation either. The rule has one home for every hook that
32
- // runs in our process (the Claude-family logger, the Gemini hook); the Pi
32
+ // runs in our process (the Claude-family logger, agy's Stop); the Pi
33
33
  // extension, a separate process in Pi's file, keeps its own `/` check.
34
34
 
35
35
  const SKILL_SEGMENT = "[a-z][a-z0-9_-]*";
@@ -5,8 +5,8 @@
5
5
  // (extraction, then title/summary/tags). Some runtimes give a SessionEnd hook
6
6
  // less time than one model call: Codex runs it synchronously under a 1 s
7
7
  // default (DS12.TS2 L2; DS2's live `/hooks` table), Grok under 1.5 s
8
- // (DS12.TS4). Pi and Gemini CLI already close without extraction at exit and
9
- // let the next session start extract (`session-end-pi`). On a runtime that
8
+ // (DS12.TS4). Pi already closes without extraction at exit and
9
+ // lets the next session start extract (`session-end-pi`). On a runtime that
10
10
  // bounds the hook, the shared Claude-family `session-end` does the same, and a
11
11
  // little more: it also skips the metadata finalization, and flags the
12
12
  // conversation so the next start's maintenance finishes it.