@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.
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -2
- package/README.md +7 -4
- package/bin/commands/doctor.mjs +22 -0
- package/bin/commands/install.mjs +15 -1
- package/bin/lib/args.mjs +1 -1
- package/bin/lib/legacy-balakit.mjs +220 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/rules/mental.mdc +1 -1
- package/skills/mental/SKILL.md +24 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mental",
|
|
3
3
|
"displayName": "Mental",
|
|
4
|
-
"version": "0.2.
|
|
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.
|
|
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,
|
|
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.
|
|
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
|
+
[](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
|
|
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
|
|
package/bin/commands/doctor.mjs
CHANGED
|
@@ -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");
|
package/bin/commands/install.mjs
CHANGED
|
@@ -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 +
|
|
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
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.
|
|
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
|
|
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.
|
package/skills/mental/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
102
|
-
🧠 Mental
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
🎯
|
|
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
|
-
|
|
112
|
-
🧠 Mental
|
|
113
|
-
🔍
|
|
116
|
+
</br>
|
|
117
|
+
🧠 Mental
|
|
118
|
+
🔍 Read: Heartbeat
|
|
119
|
+
</br>
|
|
114
120
|
```
|
|
115
121
|
|
|
116
122
|
| Emoji | Kind | Item line |
|
|
117
123
|
| --- | --- | --- |
|
|
118
|
-
| 📓 | Journal |
|
|
119
|
-
| 🚦 | Attention |
|
|
120
|
-
| 🎯 | Decision |
|
|
121
|
-
| 📝 | Note |
|
|
122
|
-
| 🔍 | Read |
|
|
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
|
|