@balacode/mental 0.2.0 → 0.2.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mental",
3
3
  "displayName": "Mental",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Never reconstruct where you left off. Mental keeps the resume, the decisions, and what's still in the air — so you and your agents continue as if the last session never ended.",
6
6
  "author": {
7
7
  "name": "Ali Farahat",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mental",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Never reconstruct where you left off. Mental keeps the resume, the decisions, and what's still in the air — so you and your agents continue as if the last session never ended.",
5
5
  "author": {
6
6
  "name": "Ali Farahat",
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - `mental install` finds leftover **Balakit** Mental skill/rule copies (fingerprint: `npx balakit doctor`, `mentalDataPolicy`, …) and deletes them before writing the standalone skill. Mental-only `<!-- BEGIN balakit -->` blocks are stripped. Journals and `./.mental` data are not deleted. `mental doctor` warns if any remain.
15
+
16
+ ## [0.2.1] - 2026-08-26
17
+
18
+ ### Changed
19
+
20
+ - Agent Mental receipt wraps with `</br>`, uses a markdown hard break after `🧠 Mental`, and prints indented `Kind: Verb` lines so Cursor chat does not collapse the block onto one line.
21
+
10
22
  ## [0.2.0] - 2026-08-26
11
23
 
12
24
  ### Added
@@ -20,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
32
 
21
33
  ### Changed
22
34
 
23
- - README now shows the logo, explains the [Agent Plugins 1.0.0](https://agent-plugins.org/specification) packaging, and includes copy-paste install prompts for Cursor, Claude Code, VS Code, and GitHub Copilot.
35
+ - README now shows the logo, npm (`@balacode/mental`), [Agent Plugins 1.0.0](https://agent-plugins.org/specification) packaging, and copy-paste install prompts for Cursor, Claude Code, VS Code, and GitHub Copilot.
24
36
  - npm package is `@balacode/mental` (unscoped `mental` is taken). The CLI bin stays `mental`.
25
37
 
26
38
  ## [0.1.0] - 2026-08-26
@@ -37,6 +49,8 @@ First public release of the Mental CLI.
37
49
  - `install --mcp` registers `serve`; `decide` updates by title so an open decision can close.
38
50
  - Install, doctor, uninstall; hooks stay off by default.
39
51
 
40
- [Unreleased]: https://github.com/afaraha8403/mental/compare/v0.2.0...HEAD
52
+ [Unreleased]: https://github.com/afaraha8403/mental/compare/v0.2.2...HEAD
53
+ [0.2.2]: https://github.com/afaraha8403/mental/releases/tag/v0.2.2
54
+ [0.2.1]: https://github.com/afaraha8403/mental/releases/tag/v0.2.1
41
55
  [0.2.0]: https://github.com/afaraha8403/mental/releases/tag/v0.2.0
42
56
  [0.1.0]: https://github.com/afaraha8403/mental/releases/tag/v0.1.0
package/README.md CHANGED
@@ -4,15 +4,18 @@
4
4
 
5
5
  # Mental
6
6
 
7
+ [![npm](https://img.shields.io/npm/v/@balacode/mental.svg)](https://www.npmjs.com/package/@balacode/mental)
8
+
7
9
  Local-first continuity layer for you and your coding agents.
8
10
 
9
11
  Git records **what** changed. Mental records the small amount git cannot explain: where you left off, why a decision was made, what is still in the air after a hop, and the next exact action. **OKF markdown is the source of truth.** SQLite is a derived cache. Agents call `mental … --json` — they do not grep YAML.
10
12
 
13
+ - **npm:** [@balacode/mental](https://www.npmjs.com/package/@balacode/mental) — install this; the CLI binary is `mental`
11
14
  - **Repo:** https://github.com/afaraha8403/mental
12
15
  - **Spec:** [PLAN.md](./PLAN.md)
13
16
  - **Plugin standard:** [Agent Plugins 1.0.0](https://agent-plugins.org/specification)
14
17
 
15
- Mental is **not** a Balakit plugin. Default data lives in `~/.mental` (UUID bindings). Project `./.mental` only after `mental local`.
18
+ Mental is **not** a Balakit plugin. Default data lives in `~/.mental` (UUID bindings). Project `./.mental` only after `mental local`. The unscoped npm package [`mental`](https://www.npmjs.com/package/mental) is a different project.
16
19
 
17
20
  ## Install
18
21
 
@@ -73,7 +76,7 @@ npm i -g @balacode/mental
73
76
  mental install
74
77
  ```
75
78
 
76
- Last install wins. That puts `mental` on PATH (typically `~/.local/bin/mental`), copies the skill + tiny always-on rule into `~/.claude`, `~/.cursor`, `~/.agents`, and `~/.config/opencode`, and creates a `~/.mental` skeleton. It does **not** turn on hooks or MCP.
79
+ Last install wins. That puts `mental` on PATH (typically `~/.local/bin/mental`), copies the skill + tiny always-on rule into `~/.claude`, `~/.cursor`, `~/.agents`, and `~/.config/opencode`, and creates a `~/.mental` skeleton. It also **removes leftover Balakit Mental skill/rule copies** (the old `npx balakit doctor` pointer) so they cannot fight the new rule. It does **not** turn on hooks or MCP, and it does not delete journals.
77
80
 
78
81
  From a clone, without npm:
79
82
 
@@ -171,9 +174,9 @@ mental status --json
171
174
 
172
175
  Mid-chat, not just start/finish: search decisions before changing an approach, record attention the moment residue surfaces, and re-pulse `mental heartbeat --json` whenever other agents may have written — it is cheap and derives git live.
173
176
 
174
- Do not grep `.mental` or parse YAML. If `mental` is missing, continue the coding task (fail open) and mention install.
177
+ Do not grep `.mental` or parse YAML. If `mental` is missing, try `npx @balacode/mental …`. If that fails, continue the coding task (fail open) and mention `npm i -g @balacode/mental` then `mental install`.
175
178
 
176
- Turns that invoked `mental` end with `<br>`, then the title `🧠 Mental`, then one type line per write/read (see the skill).
179
+ Turns that invoked `mental` end with `</br>`, title `🧠 Mental ` (two trailing spaces so chat markdown does not join lines), indented `Kind: Verb` items, then `</br>` (see the skill).
177
180
 
178
181
  ## Identity (UUID, not the folder)
179
182
 
@@ -13,6 +13,7 @@ import { CMD } from "../lib/pkg.mjs";
13
13
  import { isOptedInLocal } from "../lib/import-legacy.mjs";
14
14
  import { findGitRoot } from "../lib/git.mjs";
15
15
  import { indexPath } from "../lib/index.mjs";
16
+ import { leftoverBalakitMentalCount, findBalakitMental } from "../lib/legacy-balakit.mjs";
16
17
 
17
18
  function check(id, ok, message, level = "error") {
18
19
  return { id, ok, level, message };
@@ -126,6 +127,27 @@ export function cmdDoctor(args, io = {}) {
126
127
  ),
127
128
  );
128
129
  }
130
+
131
+ const leftoverWiring = leftoverBalakitMentalCount({ home, projectDir: gitRoot || cwd });
132
+ if (leftoverWiring > 0) {
133
+ const found = findBalakitMental({ home, projectDir: gitRoot || cwd });
134
+ const sample = [
135
+ ...found.skills,
136
+ ...found.rules,
137
+ ...found.plugins,
138
+ ...found.blocks.map((b) => b.file),
139
+ ]
140
+ .slice(0, 4)
141
+ .join(", ");
142
+ checks.push(
143
+ check(
144
+ "legacy-balakit",
145
+ false,
146
+ `Balakit Mental skill/rule still present (${sample}). Run \`${CMD} install\` to remove it.`,
147
+ "warn",
148
+ ),
149
+ );
150
+ }
129
151
  }
130
152
 
131
153
  const problems = checks.filter((c) => !c.ok && c.level === "error");
@@ -11,6 +11,7 @@ import { enableHooks } from "../lib/hooks.mjs";
11
11
  import { enableMcp } from "../lib/mcp.mjs";
12
12
  import { CMD } from "../lib/pkg.mjs";
13
13
  import { printResult, brandLine } from "../lib/output.mjs";
14
+ import { purgeBalakitMental } from "../lib/legacy-balakit.mjs";
14
15
 
15
16
  export function cmdInstall(args, io = {}) {
16
17
  const stdout = io.stdout ?? process.stdout;
@@ -28,6 +29,10 @@ export function cmdInstall(args, io = {}) {
28
29
  const mcp = Boolean(args.flags?.mcp);
29
30
  const cwd = args.cwd ?? process.cwd();
30
31
 
32
+ const legacy = purgeBalakitMental({
33
+ home,
34
+ projectDir: cwd,
35
+ });
31
36
  const installed = installSkills({
32
37
  home,
33
38
  projectDir: project ? cwd : null,
@@ -61,6 +66,8 @@ export function cmdInstall(args, io = {}) {
61
66
  mcp: mcpResult,
62
67
  where: resolved.ok ? resolved.data : null,
63
68
  imported: imported || null,
69
+ legacyRemoved: legacy.removed,
70
+ legacyLeftover: legacy.leftover,
64
71
  };
65
72
  const importLine =
66
73
  imported?.copied?.length
@@ -73,6 +80,13 @@ export function cmdInstall(args, io = {}) {
73
80
  ? `\nMCP: ${CMD} serve registered in ${mcpResult.written.join(", ")}`
74
81
  : `\nMCP: config write failed (${mcpResult?.error?.message ?? "unknown"}) — add \`${CMD} serve\` manually`
75
82
  : "";
83
+ const legacyLine = legacy.removed.length
84
+ ? `\nremoved ${legacy.removed.length} Balakit Mental leftover(s)`
85
+ : "";
86
+ const leftoverLine =
87
+ legacy.leftover.length
88
+ ? `\nstill mixed Balakit block(s) (Mental text inside a kit block): ${legacy.leftover.join(", ")}`
89
+ : "";
76
90
  printResult(
77
91
  stdout,
78
92
  args.json,
@@ -80,7 +94,7 @@ export function cmdInstall(args, io = {}) {
80
94
  data,
81
95
  undefined,
82
96
  () =>
83
- `${brandLine(`installed skill + rule (${installed.written.length} paths)`)}\n~/.mental skeleton: ${personal}${cliLine}${hookLine}${mcpLine}${importLine}`,
97
+ `${brandLine(`installed skill + rule (${installed.written.length} paths)`)}\n~/.mental skeleton: ${personal}${cliLine}${hookLine}${mcpLine}${importLine}${legacyLine}${leftoverLine}`,
84
98
  );
85
99
  return 0;
86
100
  }
package/bin/lib/args.mjs CHANGED
@@ -22,7 +22,7 @@ Usage:
22
22
  ${CMD} remap List UUID bindings, or --to <id> / --from <id> for this clone
23
23
  ${CMD} split New UUID for this clone (--copy keeps OKF files)
24
24
  ${CMD} link Point this clone at --to <id>
25
- ${CMD} install Skill + rule + put ${CMD} on PATH (overrides previous)
25
+ ${CMD} install Skill + rule + PATH; removes leftover Balakit Mental skill/rule copies
26
26
  ${CMD} uninstall Remove installed skill/rule/hooks (OKF stays unless --delete-data DELETE)
27
27
  ${CMD} hooks on|off Optional session-start hooks (default off)
28
28
  ${CMD} serve Optional MCP stdio (heartbeat/where/status/search/list/show/journal/attention/decide/note)
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Find and remove Balakit-era Mental *tooling* (skill + always-on rule).
3
+ * Never deletes OKF data (`~/.mental` or `./.mental`).
4
+ *
5
+ * Balakit installed the personal Mental rule user-wide (`~/.cursor/rules/mental.mdc`,
6
+ * managed `BEGIN balakit` blocks) and the skill via skills.sh (`-g`) into agent
7
+ * skill dirs. Those copies still tell agents to `npx balakit doctor` and fight
8
+ * the standalone CLI.
9
+ */
10
+ import { existsSync, lstatSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+
13
+ const BALAKIT_BEGIN_RE = /<!-- BEGIN balakit\b[^>]*-->/;
14
+ const BALAKIT_BLOCK_RE =
15
+ /<!-- BEGIN balakit\b[^>]*-->\r?\n[\s\S]*?<!-- END balakit\b[^>]*-->/g;
16
+
17
+ /**
18
+ * Distinctive strings from the Balakit `mental` rule/skill (removed from the
19
+ * kit in 02bf1c2). Our current skill/rule never contain these.
20
+ * @param {string} text
21
+ */
22
+ export function isBalakitMentalText(text) {
23
+ if (!text) return false;
24
+ return (
25
+ /npx balakit doctor/i.test(text) ||
26
+ /mentalDataPolicy/.test(text) ||
27
+ /\.balakit\/installed\.json/.test(text) ||
28
+ /Respect the installed Mental data policy/.test(text) ||
29
+ /The `\.mental\/` Project Continuity Layer/.test(text) ||
30
+ /plugins\/balakit-mental/.test(text)
31
+ );
32
+ }
33
+
34
+ /** User-global skill dirs skills.sh used for a global `mental` install. */
35
+ export function userSkillCandidates(home) {
36
+ return [
37
+ join(home, ".claude", "skills", "mental"),
38
+ join(home, ".cursor", "skills", "mental"),
39
+ join(home, ".agents", "skills", "mental"),
40
+ join(home, ".codex", "skills", "mental"),
41
+ join(home, ".config", "opencode", "skills", "mental"),
42
+ join(home, ".gemini", "skills", "mental"),
43
+ join(home, ".codeium", "windsurf", "skills", "mental"),
44
+ join(home, ".kilocode", "skills", "mental"),
45
+ join(home, ".continue", "skills", "mental"),
46
+ join(home, ".pi", "agent", "skills", "mental"),
47
+ join(home, ".amp", "skills", "mental"),
48
+ join(home, ".roo", "skills", "mental"),
49
+ join(home, ".windsurf", "skills", "mental"),
50
+ ];
51
+ }
52
+
53
+ function projectSkillCandidates(root) {
54
+ if (!root) return [];
55
+ return [
56
+ join(root, ".claude", "skills", "mental"),
57
+ join(root, ".cursor", "skills", "mental"),
58
+ join(root, ".agents", "skills", "mental"),
59
+ join(root, ".github", "skills", "mental"),
60
+ join(root, "plugins", "balakit-mental"),
61
+ ];
62
+ }
63
+
64
+ function userRuleCandidates(home) {
65
+ return [join(home, ".cursor", "rules", "mental.mdc")];
66
+ }
67
+
68
+ function projectRuleCandidates(root) {
69
+ if (!root) return [];
70
+ return [
71
+ join(root, ".cursor", "rules", "mental.mdc"),
72
+ join(root, ".cursor", "rules", "balakit-mental.mdc"),
73
+ ];
74
+ }
75
+
76
+ function managedDocCandidates(home, projectDir) {
77
+ const files = [
78
+ join(home, ".claude", "CLAUDE.md"),
79
+ join(home, ".codex", "AGENTS.md"),
80
+ join(home, ".agents", "AGENTS.md"),
81
+ ];
82
+ if (projectDir) {
83
+ files.push(join(projectDir, "AGENTS.md"), join(projectDir, "CLAUDE.md"));
84
+ }
85
+ return files;
86
+ }
87
+
88
+ function pluginCandidates(home) {
89
+ return [join(home, ".cursor", "plugins", "local", "balakit-mental")];
90
+ }
91
+
92
+ function skillMarkdown(dir) {
93
+ try {
94
+ return readFileSync(join(dir, "SKILL.md"), "utf8");
95
+ } catch {
96
+ return "";
97
+ }
98
+ }
99
+
100
+ function readIfFile(file) {
101
+ try {
102
+ const st = lstatSync(file);
103
+ if (!st.isFile()) return "";
104
+ return readFileSync(file, "utf8");
105
+ } catch {
106
+ return "";
107
+ }
108
+ }
109
+
110
+ function isDir(p) {
111
+ try {
112
+ return lstatSync(p).isDirectory();
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * A Balakit managed block that is only the old Mental rule (safe to delete).
120
+ * Mixed kit blocks (base/testing + mental) are reported, not stripped.
121
+ * @param {string} block
122
+ */
123
+ export function isMentalOnlyBalakitBlock(block) {
124
+ if (!isBalakitMentalText(block)) return false;
125
+ const headings = block.match(/^# .+$/gm) || [];
126
+ return headings.length <= 1;
127
+ }
128
+
129
+ /**
130
+ * @param {{ home: string, projectDir?: string | null }} opts
131
+ * @returns {{
132
+ * skills: string[],
133
+ * rules: string[],
134
+ * plugins: string[],
135
+ * blocks: Array<{ file: string, strip: boolean }>,
136
+ * }}
137
+ */
138
+ export function findBalakitMental({ home, projectDir = null }) {
139
+ /** @type {string[]} */
140
+ const skills = [];
141
+ /** @type {string[]} */
142
+ const rules = [];
143
+ /** @type {string[]} */
144
+ const plugins = [];
145
+ /** @type {Array<{ file: string, strip: boolean }>} */
146
+ const blocks = [];
147
+
148
+ for (const dir of [...userSkillCandidates(home), ...projectSkillCandidates(projectDir)]) {
149
+ if (isDir(dir) && isBalakitMentalText(skillMarkdown(dir))) skills.push(dir);
150
+ }
151
+ for (const file of [...userRuleCandidates(home), ...projectRuleCandidates(projectDir)]) {
152
+ if (isBalakitMentalText(readIfFile(file))) rules.push(file);
153
+ }
154
+ for (const dir of pluginCandidates(home)) {
155
+ if (existsSync(dir)) plugins.push(dir);
156
+ }
157
+ for (const file of managedDocCandidates(home, projectDir)) {
158
+ const cur = readIfFile(file);
159
+ if (!BALAKIT_BEGIN_RE.test(cur) || !isBalakitMentalText(cur)) continue;
160
+ const m = cur.match(BALAKIT_BLOCK_RE);
161
+ const block = m ? m.join("\n") : cur;
162
+ blocks.push({ file, strip: isMentalOnlyBalakitBlock(block) });
163
+ }
164
+ return { skills, rules, plugins, blocks };
165
+ }
166
+
167
+ function rmPath(p) {
168
+ rmSync(p, { recursive: true, force: true });
169
+ }
170
+
171
+ /**
172
+ * Drop Mental-only `BEGIN balakit` blocks. Leave mixed kit blocks intact.
173
+ * @param {string} file
174
+ */
175
+ export function stripMentalOnlyBalakitBlock(file) {
176
+ const cur = readIfFile(file);
177
+ if (!cur) return false;
178
+ const next = cur.replace(BALAKIT_BLOCK_RE, (block) =>
179
+ isMentalOnlyBalakitBlock(block) ? "" : block,
180
+ );
181
+ if (next === cur) return false;
182
+ writeFileSync(file, next.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, ""));
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Remove Balakit Mental skill/rule copies. Call *before* writing the new ones.
188
+ * @param {{ home: string, projectDir?: string | null }} opts
189
+ */
190
+ export function purgeBalakitMental({ home, projectDir = null }) {
191
+ const found = findBalakitMental({ home, projectDir });
192
+ /** @type {string[]} */
193
+ const removed = [];
194
+ /** @type {string[]} */
195
+ const leftover = [];
196
+
197
+ for (const p of [...found.skills, ...found.rules, ...found.plugins]) {
198
+ try {
199
+ rmPath(p);
200
+ removed.push(p);
201
+ } catch {
202
+ leftover.push(p);
203
+ }
204
+ }
205
+ for (const b of found.blocks) {
206
+ if (b.strip) {
207
+ if (stripMentalOnlyBalakitBlock(b.file)) removed.push(b.file);
208
+ else leftover.push(b.file);
209
+ } else {
210
+ leftover.push(b.file);
211
+ }
212
+ }
213
+ return { removed, leftover, found };
214
+ }
215
+
216
+ /** @param {{ home: string, projectDir?: string | null }} opts */
217
+ export function leftoverBalakitMentalCount(opts) {
218
+ const f = findBalakitMental(opts);
219
+ return f.skills.length + f.rules.length + f.plugins.length + f.blocks.length;
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balacode/mental",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Never reconstruct where you left off. Mental keeps the resume, the decisions, and what's still in the air.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "mental",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Never reconstruct where you left off. Mental keeps the resume, the decisions, and what's still in the air — so you and your agents continue as if the last session never ended.",
6
6
  "author": {
7
7
  "name": "Ali Farahat",
package/rules/mental.mdc CHANGED
@@ -13,6 +13,6 @@ If `mental` is not on PATH, try `npx @balacode/mental …`. If that fails, conti
13
13
 
14
14
  Never commit Mental data. Never write secrets. Never edit gitignore; tell the user to run `mental doctor`.
15
15
 
16
- If you invoked `mental` this turn, end the user-visible reply with `<br>`, then the title `🧠 Mental`, then type lines under it (example in the Mental skill). Not a code fence.
16
+ If you invoked `mental` this turn, end the user-visible reply with `</br>`, then `🧠 Mental ` (two trailing spaces), then indented `Kind: Verb` lines, then `</br>`. Never one line. Example in the Mental skill. Not a code fence.
17
17
 
18
18
  On a pasted transcript or a plan-progress question ("where in the plan / what's left?"), use the Mental skill. Cheap reload is `mental heartbeat --json`, not a notes dump.
@@ -87,39 +87,45 @@ If you invoked `mental` this turn, end the **user-visible** reply with this bloc
87
87
  Last thing in the message. Not a code fence (so emojis render).
88
88
  Skip the whole block if you did not run `mental`.
89
89
 
90
- Separator is a literal `<br>` on its own line (not dashes, not a blank line).
91
- Next line is the title `🧠 Mental`. Then one item line per thing that happened,
92
- type-emoji first. At most four item lines; if more, keep the writes and end
93
- with `+N more`.
90
+ Cursor markdown joins adjacent lines into one paragraph. A lone `<br>`
91
+ becomes a break *inside* that paragraph, so `🧠 Mental` and the item
92
+ collapse to one line. Do **not** put the title and items on the same line.
93
+
94
+ Wrap with literal `</br>` (open and close, each on its own line).
95
+ Title line is `🧠 Mental` plus **two trailing spaces** (markdown hard break).
96
+ Each item is indented two spaces: `emoji Kind: Verb “title”`.
97
+ At most four item lines; if more, keep the writes and end with `+N more`.
94
98
 
95
99
  Titles only — no `file://` or markdown links. Mental files live in `~/.mental`,
96
100
  not this repo, so links would 404. The CLI tool card already has the path.
97
101
 
98
- **Writes (copy this shape):**
102
+ **Writes (copy this shape, including the two spaces after `Mental`):**
99
103
 
100
104
  ```text
101
- <br>
102
- 🧠 Mental
103
- 📓 journaledResolver landed
104
- 🚦 recorded attention Tom said ship
105
- 🎯 decided “Keep the JSON envelope”
105
+ </br>
106
+ 🧠 Mental
107
+ 🚦 Attention: Recorded Tom said ship
108
+ 📓 Journal: Recorded Resolver landed
109
+ 🎯 Decision: Decided “Keep the JSON envelope”
110
+ </br>
106
111
  ```
107
112
 
108
113
  **Read-only (heartbeat / search / show / list):**
109
114
 
110
115
  ```text
111
- <br>
112
- 🧠 Mental
113
- 🔍 heartbeat
116
+ </br>
117
+ 🧠 Mental
118
+ 🔍 Read: Heartbeat
119
+ </br>
114
120
  ```
115
121
 
116
122
  | Emoji | Kind | Item line |
117
123
  | --- | --- | --- |
118
- | 📓 | Journal | `📓 journaled “<title>”` |
119
- | 🚦 | Attention | `🚦 recorded attention “<title>”` or `🚦 resolved attention “<title>”` |
120
- | 🎯 | Decision | `🎯 decided “<title>”` or `🎯 opened decision “<title>”` |
121
- | 📝 | Note | `📝 noted “<title>”` |
122
- | 🔍 | Read | `🔍 heartbeat` / `🔍 searched` / `🔍 showed` / `🔍 listed` |
124
+ | 📓 | Journal | ` 📓 Journal: Recorded “<title>”` |
125
+ | 🚦 | Attention | ` 🚦 Attention: Recorded “<title>”` or ` 🚦 Attention: Resolved “<title>”` |
126
+ | 🎯 | Decision | ` 🎯 Decision: Decided “<title>”` or ` 🎯 Decision: Opened “<title>”` |
127
+ | 📝 | Note | ` 📝 Note: Recorded “<title>”` |
128
+ | 🔍 | Read | ` 🔍 Read: Heartbeat` / `Searched` / `Showed` / `Listed` |
123
129
 
124
130
  Mix writes and a read in one block if both happened. Never invent Mental activity.
125
131