diff --git a/.changeset/eager-pandas-jump.md b/.changeset/eager-pandas-jump.md new file mode 100644 index 000000000..7d4f0f39b --- /dev/null +++ b/.changeset/eager-pandas-jump.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2464 +--- +**`/gsd-mempalace-capture` no longer crashes on first invocation** — the skill's own documented `rooms:` example wrote a flat list of bare strings, but mempalace's miner expects each entry as a dict with a `name` key, so following the example verbatim and running `mempalace mine` crashed with `TypeError: string indices must be integers, not 'str'`. Both `skills/gsd-mempalace-capture/SKILL.md` and `commands/gsd/mempalace-capture.md` now ship the corrected `- name: ` shape, so the documented example runs successfully end-to-end. diff --git a/commands/gsd/mempalace-capture.md b/commands/gsd/mempalace-capture.md index fdd61cc4a..218d13aeb 100644 --- a/commands/gsd/mempalace-capture.md +++ b/commands/gsd/mempalace-capture.md @@ -64,12 +64,16 @@ On any error or timeout, stop and let the phase continue -- capture is best-effo # One-time: declare the GSD room taxonomy so detect_room() recognizes these folders mkdir -p "$STAGE" [ -f "$STAGE/mempalace.yaml" ] || cat > "$STAGE/mempalace.yaml" <<'YAML' + # Each entry MUST be a dict with a `name` key (the miner's detect_room() + # indexes room["name"] — a bare-string list crashes _mine_impl with + # TypeError: string indices must be integers, not 'str'). Optional fields: + # `description`, `keywords` (matched against folder-path segments). rooms: - - decisions - - planning - - milestones - - problems - - general + - name: decisions + - name: planning + - name: milestones + - name: problems + - name: general YAML # Suppress MemPalace cache artifacts written into the scanned tree [ -f "$STAGE/.gitignore" ] || echo "mempalace_embedder.json" > "$STAGE/.gitignore" diff --git a/skills/gsd-mempalace-capture/SKILL.md b/skills/gsd-mempalace-capture/SKILL.md index 68db3da6e..37a3a7f10 100644 --- a/skills/gsd-mempalace-capture/SKILL.md +++ b/skills/gsd-mempalace-capture/SKILL.md @@ -64,12 +64,16 @@ On any error or timeout, stop and let the phase continue -- capture is best-effo # One-time: declare the GSD room taxonomy so detect_room() recognizes these folders mkdir -p "$STAGE" [ -f "$STAGE/mempalace.yaml" ] || cat > "$STAGE/mempalace.yaml" <<'YAML' + # Each entry MUST be a dict with a `name` key (the miner's detect_room() + # indexes room["name"] — a bare-string list crashes _mine_impl with + # TypeError: string indices must be integers, not 'str'). Optional fields: + # `description`, `keywords` (matched against folder-path segments). rooms: - - decisions - - planning - - milestones - - problems - - general + - name: decisions + - name: planning + - name: milestones + - name: problems + - name: general YAML # Suppress MemPalace cache artifacts written into the scanned tree [ -f "$STAGE/.gitignore" ] || echo "mempalace_embedder.json" > "$STAGE/.gitignore" diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index f463693b0..0abb7c60a 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -393,7 +393,7 @@ "skills/gsd-ingest-docs/SKILL.md": "f4e43968af317ed8", "skills/gsd-manager/SKILL.md": "6e51785860784d8c", "skills/gsd-map-codebase/SKILL.md": "552f914f43b2181c", - "skills/gsd-mempalace-capture/SKILL.md": "ece243a29d87cfca", + "skills/gsd-mempalace-capture/SKILL.md": "0f8eae8ef21ea89a", "skills/gsd-mempalace-recall/SKILL.md": "2d95e385fee26dee", "skills/gsd-milestone-summary/SKILL.md": "2955ab3e3a444ca3", "skills/gsd-mvp-phase/SKILL.md": "1cf7622d655840ac", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 12b39860e..c4d345260 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -63,7 +63,7 @@ "commands/gsd-ingest-docs.md": "6718b04c597a5428", "commands/gsd-manager.md": "8f4ae79774902293", "commands/gsd-map-codebase.md": "e4e170d4ef2d46c6", - "commands/gsd-mempalace-capture.md": "d57b339a2743f5a4", + "commands/gsd-mempalace-capture.md": "63c35058ba60209f", "commands/gsd-mempalace-recall.md": "38716c0983a3ef9c", "commands/gsd-milestone-summary.md": "ff5e11e6b33d4b5e", "commands/gsd-mvp-phase.md": "ffc5905aed8f450a", @@ -440,7 +440,7 @@ "skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "48846bf26ccaf0e7", "skills/gsd-ns-context/skills/graphify/SKILL.md": "3d6341614add6ccd", "skills/gsd-ns-context/skills/map-codebase/SKILL.md": "2017007dcd69d153", - "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "b3c600b306f179eb", + "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "9923f0164d27f27f", "skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "646e59e76acd548f", "skills/gsd-ns-ideate/SKILL.md": "d70360a5f7e8ac90", "skills/gsd-ns-ideate/skills/capture/SKILL.md": "74651f6959cfbbd3", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index c193e136d..b727c7397 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -62,7 +62,7 @@ "commands/gsd-ingest-docs.md": "ded9013d0de7e77b", "commands/gsd-manager.md": "72d5b31b88f77703", "commands/gsd-map-codebase.md": "ecd69887996ae561", - "commands/gsd-mempalace-capture.md": "3812fc95963f92d7", + "commands/gsd-mempalace-capture.md": "7ec09056410d8e63", "commands/gsd-mempalace-recall.md": "38716c0983a3ef9c", "commands/gsd-milestone-summary.md": "908509042caf5beb", "commands/gsd-mvp-phase.md": "1ef0d7c2871be49a", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 7ee2ce23a..7cf71afb9 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -391,7 +391,7 @@ "skills/gsd-ingest-docs/SKILL.md": "eb85e654917a503a", "skills/gsd-manager/SKILL.md": "1370d93741e47828", "skills/gsd-map-codebase/SKILL.md": "cf65e61890448285", - "skills/gsd-mempalace-capture/SKILL.md": "fa47f3e3fb1264eb", + "skills/gsd-mempalace-capture/SKILL.md": "175c5441158a0a9c", "skills/gsd-mempalace-recall/SKILL.md": "c3f412d5a64f3921", "skills/gsd-milestone-summary/SKILL.md": "33d51a325d314f3a", "skills/gsd-mvp-phase/SKILL.md": "f9a1348c6c297579", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 60928cf1e..46bdd874b 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -344,7 +344,7 @@ "skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "d39269bea995fabc", "skills/gsd-ns-context/skills/graphify/SKILL.md": "030050fcd08d129d", "skills/gsd-ns-context/skills/map-codebase/SKILL.md": "fa6a37402430af7d", - "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "ece243a29d87cfca", + "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "0f8eae8ef21ea89a", "skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "2d95e385fee26dee", "skills/gsd-ns-ideate/SKILL.md": "c10342345c01c91f", "skills/gsd-ns-ideate/skills/capture/SKILL.md": "fb53053848bb6695", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 0827b5852..f838c848b 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -63,7 +63,7 @@ "commands/gsd-ingest-docs.md": "ab3e58239c23d61d", "commands/gsd-manager.md": "bfe9e261fa918876", "commands/gsd-map-codebase.md": "5093093d42240325", - "commands/gsd-mempalace-capture.md": "c687b6d88b50f45c", + "commands/gsd-mempalace-capture.md": "abe0eb543cedb9fb", "commands/gsd-mempalace-recall.md": "6efef238d8c62569", "commands/gsd-milestone-summary.md": "e3a36fbd695a1833", "commands/gsd-mvp-phase.md": "671791ba61c4654a", @@ -463,7 +463,7 @@ "skills/gsd-ingest-docs/SKILL.md": "2df55e0ec95d57d9", "skills/gsd-manager/SKILL.md": "816db4d4ed618537", "skills/gsd-map-codebase/SKILL.md": "b304ec25e9a95c93", - "skills/gsd-mempalace-capture/SKILL.md": "cb6238896332cedf", + "skills/gsd-mempalace-capture/SKILL.md": "b6aee382ec1a6c2b", "skills/gsd-mempalace-recall/SKILL.md": "70c8491f53f4161b", "skills/gsd-milestone-summary/SKILL.md": "9eb030a1ac307fa1", "skills/gsd-mvp-phase/SKILL.md": "236fcdd69f47d101", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 1f1b1c656..f1ad01ccc 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -27,7 +27,7 @@ ".agents/skills/gsd-ingest-docs/SKILL.md": "5bd2838bf0b6dc1b", ".agents/skills/gsd-manager/SKILL.md": "cb1cf56f5d3f66d6", ".agents/skills/gsd-map-codebase/SKILL.md": "cd27dc028718426b", - ".agents/skills/gsd-mempalace-capture/SKILL.md": "361675ada00a54b6", + ".agents/skills/gsd-mempalace-capture/SKILL.md": "fb426a6700b98eed", ".agents/skills/gsd-mempalace-recall/SKILL.md": "7205b02250e89f25", ".agents/skills/gsd-milestone-summary/SKILL.md": "af84ecb400f23556", ".agents/skills/gsd-mvp-phase/SKILL.md": "72b65ae927b280fe", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index fc0eda5c8..b70be3f72 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -366,7 +366,7 @@ "skills/gsd-ingest-docs/SKILL.md": "10500de29525f6cf", "skills/gsd-manager/SKILL.md": "9946975bec15e737", "skills/gsd-map-codebase/SKILL.md": "39e7fd6bcc561f9d", - "skills/gsd-mempalace-capture/SKILL.md": "a9f9dd7fb904d09f", + "skills/gsd-mempalace-capture/SKILL.md": "1ccddd50b3d77a4a", "skills/gsd-mempalace-recall/SKILL.md": "89523db66f834b2f", "skills/gsd-milestone-summary/SKILL.md": "5ca1dabfe0cd85f0", "skills/gsd-mvp-phase/SKILL.md": "60ac3c3fa6a6a19b", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 17073cced..2e0a1b635 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -63,7 +63,7 @@ "commands/gsd-ingest-docs.md": "9e7f8757b9408c01", "commands/gsd-manager.md": "f300b708a5487c76", "commands/gsd-map-codebase.md": "12f705917eb202fb", - "commands/gsd-mempalace-capture.md": "06323113ec60df3e", + "commands/gsd-mempalace-capture.md": "8f5afec4dde75478", "commands/gsd-mempalace-recall.md": "08c910285469a394", "commands/gsd-milestone-summary.md": "adfad2cf43fc5aad", "commands/gsd-mvp-phase.md": "e8debeee627a0bf2", @@ -441,7 +441,7 @@ "skills/gsd-ingest-docs/SKILL.md": "1a2e87caa9011682", "skills/gsd-manager/SKILL.md": "8a506b1b8b3841c3", "skills/gsd-map-codebase/SKILL.md": "74a827b893d70dfa", - "skills/gsd-mempalace-capture/SKILL.md": "b5ad8d540b6320be", + "skills/gsd-mempalace-capture/SKILL.md": "ff7e3bcfa0d23d5f", "skills/gsd-mempalace-recall/SKILL.md": "04364d3012644204", "skills/gsd-milestone-summary/SKILL.md": "1d0b245b74176236", "skills/gsd-mvp-phase/SKILL.md": "485356f67c196015", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 6414dd920..a863088e8 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -370,7 +370,7 @@ "skills/gsd/gsd-ns-context/skills/extract-learnings/SKILL.md": "36e59d6e514dcaf0", "skills/gsd/gsd-ns-context/skills/graphify/SKILL.md": "169eefd42c444cde", "skills/gsd/gsd-ns-context/skills/map-codebase/SKILL.md": "9d36eee52586bb34", - "skills/gsd/gsd-ns-context/skills/mempalace-capture/SKILL.md": "4a2733dd32cdf737", + "skills/gsd/gsd-ns-context/skills/mempalace-capture/SKILL.md": "b55a640c7bae4c97", "skills/gsd/gsd-ns-context/skills/mempalace-recall/SKILL.md": "5d06922e0d7ab7d9", "skills/gsd/gsd-ns-ideate/SKILL.md": "b36bb4700ed6723a", "skills/gsd/gsd-ns-ideate/skills/capture/SKILL.md": "76fc7c1200124ece", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 9fe52959f..70760ac86 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -63,7 +63,7 @@ "command/gsd-ingest-docs.md": "e655ecf1d4433ba9", "command/gsd-manager.md": "1cdc133079833552", "command/gsd-map-codebase.md": "704cb63214f51523", - "command/gsd-mempalace-capture.md": "8b6bc24d7baf1e9b", + "command/gsd-mempalace-capture.md": "081236c25ce5b337", "command/gsd-mempalace-recall.md": "d358cdd89c935877", "command/gsd-milestone-summary.md": "4ab29b777f62f0bb", "command/gsd-mvp-phase.md": "273a1c26ca13274b", @@ -465,7 +465,7 @@ "skills/gsd-ingest-docs/SKILL.md": "f049f06a6ec7da43", "skills/gsd-manager/SKILL.md": "a37ecdbe32952262", "skills/gsd-map-codebase/SKILL.md": "e06d6ac1b7632cbe", - "skills/gsd-mempalace-capture/SKILL.md": "80ffe0ee9b833502", + "skills/gsd-mempalace-capture/SKILL.md": "ddb5c1322d6237ad", "skills/gsd-mempalace-recall/SKILL.md": "2091c48e6e986e28", "skills/gsd-milestone-summary/SKILL.md": "da645171ca2299d8", "skills/gsd-mvp-phase/SKILL.md": "eeb0ac0f88647475", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 58fcd9b05..c0e8667f6 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -428,7 +428,7 @@ "skills/gsd-ingest-docs/SKILL.md": "6366644e623ae9e4", "skills/gsd-manager/SKILL.md": "8a11a1beb8e35a9c", "skills/gsd-map-codebase/SKILL.md": "73ab0afb2398de2c", - "skills/gsd-mempalace-capture/SKILL.md": "5ed5ae9c018c89df", + "skills/gsd-mempalace-capture/SKILL.md": "aeb01fa8be356ba8", "skills/gsd-mempalace-recall/SKILL.md": "c6e0195e55cd934c", "skills/gsd-milestone-summary/SKILL.md": "3ea93ccaa0d4d967", "skills/gsd-mvp-phase/SKILL.md": "d2f98f1e955797b6", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 07a89c6b1..dc95698a7 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -63,7 +63,7 @@ "commands/gsd-ingest-docs.md": "b47c812c247e58a4", "commands/gsd-manager.md": "86ece154b34b6d1b", "commands/gsd-map-codebase.md": "9aba6b4eed565912", - "commands/gsd-mempalace-capture.md": "8b6bc24d7baf1e9b", + "commands/gsd-mempalace-capture.md": "081236c25ce5b337", "commands/gsd-mempalace-recall.md": "d358cdd89c935877", "commands/gsd-milestone-summary.md": "86a827271bc647c5", "commands/gsd-mvp-phase.md": "d45fd76cb9cff3ef", @@ -465,7 +465,7 @@ "skills/gsd-ingest-docs/SKILL.md": "8f294b4894f782a5", "skills/gsd-manager/SKILL.md": "cf40f237b4f20aa7", "skills/gsd-map-codebase/SKILL.md": "73f714297815f74c", - "skills/gsd-mempalace-capture/SKILL.md": "80ffe0ee9b833502", + "skills/gsd-mempalace-capture/SKILL.md": "ddb5c1322d6237ad", "skills/gsd-mempalace-recall/SKILL.md": "2091c48e6e986e28", "skills/gsd-milestone-summary/SKILL.md": "175366bb500b5e64", "skills/gsd-mvp-phase/SKILL.md": "0531d0e907af41ce", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 6c8e63b3d..f7cfaeb6b 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -369,7 +369,7 @@ "skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "8d38e55e5f8b774b", "skills/gsd-ns-context/skills/graphify/SKILL.md": "ac3661e6e576188b", "skills/gsd-ns-context/skills/map-codebase/SKILL.md": "cf65e61890448285", - "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "fa47f3e3fb1264eb", + "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "175c5441158a0a9c", "skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "c3f412d5a64f3921", "skills/gsd-ns-ideate/SKILL.md": "4a0d2691054b23e5", "skills/gsd-ns-ideate/skills/capture/SKILL.md": "05fa421c95fbad2d", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 0934ac518..a520d23ae 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -341,7 +341,7 @@ "skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "1fc6ba92fc18eb86", "skills/gsd-ns-context/skills/graphify/SKILL.md": "371ce22f2fe319bc", "skills/gsd-ns-context/skills/map-codebase/SKILL.md": "cfc5310b903d594a", - "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "211b03370a105fae", + "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "52cb3eacc890c44c", "skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "73b0b7a6301cb214", "skills/gsd-ns-ideate/SKILL.md": "aa399385b735c7c1", "skills/gsd-ns-ideate/skills/capture/SKILL.md": "3b7fc696b2107bcf", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index dd529949f..3c6d572f2 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -63,7 +63,7 @@ "commands/gsd-ingest-docs.md": "7a3c21ae5d5d133a", "commands/gsd-manager.md": "9f54653a7c589b00", "commands/gsd-map-codebase.md": "709e9572953accc6", - "commands/gsd-mempalace-capture.md": "be4316828baebe52", + "commands/gsd-mempalace-capture.md": "c0892e814b04350a", "commands/gsd-mempalace-recall.md": "5ac67fa4b7f2b03b", "commands/gsd-milestone-summary.md": "f8fa9c357c08b74b", "commands/gsd-mvp-phase.md": "cba67bcb55250e5f", @@ -412,7 +412,7 @@ "skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "6de7eeaaf4939e5d", "skills/gsd-ns-context/skills/graphify/SKILL.md": "d54c2b44e8059299", "skills/gsd-ns-context/skills/map-codebase/SKILL.md": "f48d48b43e2cc4bd", - "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "fa47f3e3fb1264eb", + "skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "175c5441158a0a9c", "skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "c3f412d5a64f3921", "skills/gsd-ns-ideate/SKILL.md": "4a0d2691054b23e5", "skills/gsd-ns-ideate/skills/capture/SKILL.md": "c848255c134a860f", diff --git a/tests/mempalace-capture-headless-invocation.test.cjs b/tests/mempalace-capture-headless-invocation.test.cjs index 6ece5f5e6..66b6efa9d 100644 --- a/tests/mempalace-capture-headless-invocation.test.cjs +++ b/tests/mempalace-capture-headless-invocation.test.cjs @@ -5,6 +5,12 @@ // --room flag (only `search` does). The fix replaced the flag with a // detect_room()-based staging approach. // +// #2414 extension: the staging block also embeds a `rooms:` YAML example. +// mempalace's miner (`detect_room()` and `_mine_impl`) requires each entry +// to be a DICT with at least a `name` key — a bare-string list crashes the +// first `mine` invocation with `TypeError: string indices must be integers, +// not 'str'`. The fix converts each `- ` to `- name: `. +// // Docs sources: // CLI reference: https://mempalaceofficial.com/reference/cli.html // Mining guide: https://mempalaceofficial.com/guide/mining.html @@ -14,6 +20,7 @@ const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); +const yaml = require('js-yaml'); const ROOT = path.resolve(__dirname, '..'); @@ -71,3 +78,73 @@ describe('#2220 — headless mempalace mine has no --room flag', () => { assert.ok(content.includes('.mempalace-stage/'), '.gitignore must exclude .planning/.mempalace-stage/'); }); }); + +// ─── #2414: rooms: entries MUST be dicts with a `name` key ─────────────────── +// +// The skill files embed a `rooms:` YAML example inside a bash heredoc. The +// miner's `detect_room()` and `_mine_impl` index `room["name"]` — a bare-string +// list crashes the first `mine` invocation with +// `TypeError: string indices must be integers, not 'str'`. +// +// This describe block extracts the YAML block from each file, parses it with +// js-yaml, and asserts every entry under `rooms:` is a dict carrying a `name` +// key. It also forbids the pre-fix shape (`- decisions` etc.) so a future +// reversion can't slip back in silently. + +describe('#2414 — rooms: entries are dicts with a name key (not bare strings)', () => { + // Extract the YAML `rooms:` block from a markdown file. The block lives + // inside a bash heredoc that ends with `YAML` on its own line. We capture + // from the line beginning with `rooms:` through the closing `YAML` sentinel. + function extractRoomsYaml(content) { + const lines = content.split(/\r?\n/); + const start = lines.findIndex((l) => /^\s*rooms:\s*$/.test(l)); + if (start === -1) return null; + let end = -1; + for (let i = start + 1; i < lines.length; i++) { + // The heredoc terminator in the embedded bash is `YAML` on its own line + // (possibly with leading whitespace from the markdown fence indentation). + if (/^\s*YAML\s*$/.test(lines[i])) { end = i; break; } + } + if (end === -1) return null; + return lines.slice(start, end).join('\n'); + } + + for (const rel of SKILL_FILES) { + test(`${rel}: every rooms: entry is a dict with a \`name\` key`, () => { + const content = fs.readFileSync(path.join(ROOT, rel), 'utf8'); + const yamlBlock = extractRoomsYaml(content); + assert.ok(yamlBlock, `${rel}: could not locate a rooms: YAML block`); + const parsed = yaml.load(yamlBlock); + assert.ok(parsed && Array.isArray(parsed.rooms), `${rel}: rooms: must parse to an array`); + assert.ok(parsed.rooms.length >= 5, `${rel}: expected the GSD room taxonomy (≥5 rooms), got ${parsed.rooms.length}`); + for (const [i, entry] of parsed.rooms.entries()) { + assert.ok( + entry !== null && typeof entry === 'object' && !Array.isArray(entry), + `${rel}: rooms:[${i}] must be a dict, got ${JSON.stringify(entry)} (a bare-string list crashes mempalace miner's detect_room with TypeError: string indices must be integers — #2414)`, + ); + assert.ok( + typeof entry.name === 'string' && entry.name.length > 0, + `${rel}: rooms:[${i}] must have a non-empty name string, got ${JSON.stringify(entry)}`, + ); + } + }); + + test(`${rel}: no bare-string rooms: entries remain (forbid the pre-fix shape)`, () => { + // Direct guard against the pre-fix shape — any line under rooms: that + // matches `^- $` (no colon) is the broken form. Belt-and-suspenders + // alongside the parse-and-shape check above; keeps the assertion readable + // when the YAML parser is removed/refactored in the future. + const content = fs.readFileSync(path.join(ROOT, rel), 'utf8'); + const yamlBlock = extractRoomsYaml(content); + assert.ok(yamlBlock, `${rel}: could not locate a rooms: YAML block`); + const bareStringEntries = yamlBlock + .split(/\r?\n/) + .filter((l) => /^\s*-\s+[A-Za-z][A-Za-z0-9_-]*\s*$/.test(l)); + assert.strictEqual( + bareStringEntries.length, + 0, + `${rel}: rooms: must not contain bare-string entries (crashes mempalace miner — #2414). Offenders: ${JSON.stringify(bareStringEntries)}`, + ); + }); + } +});