mercury-agent 0.16.0 → 0.16.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.
Files changed (80) hide show
  1. package/README.md +6 -0
  2. package/container/Dockerfile +6 -1
  3. package/container/Dockerfile.base +6 -1
  4. package/docs/README.md +50 -0
  5. package/docs/authoring-profiles.md +8 -0
  6. package/docs/behavior-layers.md +193 -0
  7. package/docs/extensions.md +2 -0
  8. package/docs/goals/football-reporter-profile/decisions.md +479 -0
  9. package/docs/goals/football-reporter-profile/goal.md +91 -0
  10. package/docs/goals/football-reporter-profile/roadmap.md +196 -0
  11. package/docs/goals/whatsapp-bot-hardening/README.md +43 -0
  12. package/docs/goals/whatsapp-bot-hardening/archive/ambient-group-context.md +211 -0
  13. package/docs/goals/whatsapp-bot-hardening/archive/command-routing-consistency.md +210 -0
  14. package/docs/goals/whatsapp-bot-hardening/archive/destructive-command-guards.md +218 -0
  15. package/docs/goals/whatsapp-bot-hardening/archive/docs-extension.md +182 -0
  16. package/docs/goals/whatsapp-bot-hardening/archive/handoff-2026-08-09.md +588 -0
  17. package/docs/goals/whatsapp-bot-hardening/archive/media-size-and-silent-drop.md +178 -0
  18. package/docs/goals/whatsapp-bot-hardening/archive/member-memory.md +199 -0
  19. package/docs/goals/whatsapp-bot-hardening/archive/message-author-attribution.md +211 -0
  20. package/docs/goals/whatsapp-bot-hardening/archive/run-from-source-switchover.md +212 -0
  21. package/docs/goals/whatsapp-bot-hardening/archive/setup-plan-2026-08-06.md +1117 -0
  22. package/docs/goals/whatsapp-bot-hardening/decisions.md +200 -0
  23. package/docs/goals/whatsapp-bot-hardening/goal.md +75 -0
  24. package/docs/goals/whatsapp-bot-hardening/open-threads.md +396 -0
  25. package/docs/goals/whatsapp-bot-hardening/roadmap.md +245 -0
  26. package/docs/memory.md +30 -0
  27. package/docs/profile-guide.md +483 -0
  28. package/docs/refactor/.gitkeep +0 -0
  29. package/docs/refactor/archive/.gitkeep +0 -0
  30. package/docs/refactor/audits/.gitkeep +0 -0
  31. package/docs/refactor/backlog/.gitkeep +0 -0
  32. package/docs/skills-guide.md +154 -0
  33. package/examples/extensions/README.md +1 -0
  34. package/examples/extensions/feed-watch/config.ts +354 -0
  35. package/examples/extensions/feed-watch/digest.ts +344 -0
  36. package/examples/extensions/feed-watch/feeds.ts +341 -0
  37. package/examples/extensions/feed-watch/index.ts +302 -0
  38. package/examples/extensions/feed-watch/items.ts +171 -0
  39. package/examples/extensions/feed-watch/match.ts +107 -0
  40. package/examples/extensions/feed-watch/prompts/verify.md +35 -0
  41. package/examples/extensions/feed-watch/skill/SKILL.md +128 -0
  42. package/examples/extensions/feed-watch/watch.ts +548 -0
  43. package/examples/extensions/longview/hook.ts +179 -16
  44. package/examples/extensions/longview/index.ts +13 -2
  45. package/examples/extensions/longview/prompts/summarize.md +3 -2
  46. package/examples/extensions/longview/render/telegraph-nodes.ts +21 -1
  47. package/examples/extensions/longview/summarize.ts +57 -14
  48. package/examples/extensions/napkin/index.ts +334 -86
  49. package/examples/extensions/napkin/pi-spawn.ts +196 -0
  50. package/examples/extensions/pinchtab/index.ts +37 -4
  51. package/examples/extensions/pinchtab/lib/session-injector.ts +31 -9
  52. package/examples/extensions/pinchtab/skill/SKILL.md +29 -0
  53. package/examples/profiles/_template/AGENTS.md +138 -0
  54. package/examples/profiles/_template/README.md +65 -0
  55. package/examples/profiles/_template/config.yaml +57 -0
  56. package/examples/profiles/_template/tasks/daily.md +32 -0
  57. package/examples/profiles/football-reporter/AGENTS.md +219 -0
  58. package/examples/profiles/football-reporter/README.md +138 -0
  59. package/examples/profiles/football-reporter/config.yaml +174 -0
  60. package/examples/profiles/football-reporter/seed/MEMORY.md +42 -0
  61. package/examples/profiles/football-reporter/seed/episodes/barcelona-2026-27.md +25 -0
  62. package/examples/profiles/football-reporter/seed/episodes/beitar-jerusalem-2026-27.md +25 -0
  63. package/examples/profiles/football-reporter/seed/episodes/maccabi-haifa-2026-27.md +24 -0
  64. package/examples/profiles/football-reporter/seed/episodes/man-united-2026-27.md +26 -0
  65. package/examples/profiles/football-reporter/seed/episodes/real-madrid-2026-27.md +25 -0
  66. package/examples/profiles/football-reporter/seed/napkin-distill.md +55 -0
  67. package/examples/profiles/football-reporter/tasks/daily-article.md +142 -0
  68. package/package.json +8 -5
  69. package/src/adapters/whatsapp.ts +75 -13
  70. package/src/bridges/whatsapp.ts +6 -4
  71. package/src/cli/mrctl.ts +9 -1
  72. package/src/core/handler.ts +51 -6
  73. package/src/core/routes/dashboard.ts +106 -5
  74. package/src/core/routes/tasks.ts +28 -0
  75. package/src/core/runtime.ts +63 -2
  76. package/src/core/task-scheduler.ts +103 -12
  77. package/src/extensions/catalog.ts +9 -0
  78. package/src/profile/space-profile.ts +780 -0
  79. package/src/storage/db.ts +215 -0
  80. package/src/types.ts +56 -0
@@ -0,0 +1,218 @@
1
+ # Destructive Command Guards
2
+
3
+ **Status**: Done
4
+ **Slug**: destructive-command-guards
5
+ **Goal**: whatsapp-bot-hardening
6
+ **Milestone**: 3
7
+ **Created**: 2026-08-09
8
+ **Last updated**: 2026-08-09
9
+
10
+ ---
11
+
12
+ ## Goal
13
+
14
+ > Part of goal [whatsapp-bot-hardening](../goals/whatsapp-bot-hardening/goal.md).
15
+ > HANDOFF Bugs 5.1, 5.4–5.5, 6a, 6c — the defects that destroy data, lock you
16
+ > out, or disable the emergency brake.
17
+
18
+ Four small, high-severity fixes: (1) `mrctl spaces delete <id>` silently
19
+ ignores its argument and always deletes the **current** space — this is what
20
+ actually destroyed the baduk space (559 messages, recovered from the WAL);
21
+ (2) the chat delete-confirmation can never match a WhatsApp *reply* because the
22
+ adapter appends a `<reply_to>` block, and pending state is cleared before
23
+ matching; (3) `/stop`, `/compact`, `/clear` with a slash route to the model
24
+ instead of executing — so the abort command *queues another run*; (4)
25
+ `/spaces unlink` in a group is an unrecoverable self-lockout, also reachable
26
+ without any command via `onGroupRemoval` + re-add.
27
+
28
+ ## User Stories
29
+
30
+ - As an admin, I want `mrctl spaces delete <id>` to refuse loudly rather than
31
+ delete a different space than the one I named.
32
+ - As an admin, I want to confirm a cross-space delete by *replying* "yes" —
33
+ the literal instruction the prompt gives — and have it delete the named space.
34
+ - As any user, I want `/stop` to actually abort a runaway agent.
35
+ - As an admin, I want a group that got unlinked (by command or by bot
36
+ removal/re-add) to be recoverable from chat.
37
+
38
+ ## MVP Scope
39
+
40
+ **In scope:** mrctl argument rejection + usage text; shared
41
+ confirmation-text normalization (strip `<reply_to>` etc.); pending-state
42
+ lifecycle fix; `/`-tolerant `CHAT_COMMANDS` lookup + honest help wording;
43
+ unlinked conversations stay eligible for link-restoring commands; re-link (or
44
+ prompt) on group re-join; removal of `/spaces unlink` from chat (D-009).
45
+
46
+ **Out of scope:** a `DELETE /api/spaces/:id` route — explicitly forbidden
47
+ (D-006); requiring the confirmation to echo the target id — recommended,
48
+ tracked as an open question; the rest of the command-surface audit (M3.2).
49
+
50
+ ---
51
+
52
+ ## Context for Claude
53
+
54
+ - `src/cli/mrctl.ts:384` (the argument-ignoring delete) and `:85` (usage line).
55
+ - `src/core/runtime.ts:911-933` (chat delete flow — correct),
56
+ `:953` (`handlePendingSpaceDelete`, the `text === "yes"` match),
57
+ `:981-982` (pending state cleared before matching).
58
+ - `src/adapters/whatsapp.ts:609` — where `<reply_to …>` is appended to text.
59
+ - `src/core/router.ts:39-43` (`CHAT_COMMANDS` whole-prompt match) and
60
+ `src/core/commands.ts:15-67` (`SLASH_COMMANDS`).
61
+ - `src/core/conversation.ts:127` (unlink lockout) and `src/core/handler.ts:190-197`
62
+ (drop gate upstream of routing); `main.ts` `onGroupRemoval`.
63
+ - `resources/skills/spaces/SKILL.md` — agent-facing docs to make emphatic.
64
+ - `HANDOFF.md` Bugs 5, 6a, 6c — full analysis; 6b's correction of the incident
65
+ account (the agent's `mrctl` call, not the chat command, killed baduk).
66
+
67
+ ---
68
+
69
+ ## Architecture & Data
70
+
71
+ ### Data Models
72
+
73
+ None.
74
+
75
+ ### API Contracts
76
+
77
+ Unchanged by design — the `/current`-only API is a security boundary (D-006).
78
+
79
+ ### File & Folder Structure
80
+
81
+ | Path | New / Modified | Purpose |
82
+ |------|---------------|---------|
83
+ | `src/cli/mrctl.ts` | Modified | Reject `spaces delete` argument with explanatory error; fix usage line |
84
+ | `src/core/runtime.ts` | Modified | Confirmation normalization + pending-state lifecycle reorder |
85
+ | `src/core/` (new helper) | New | Shared `normalizeConfirmationText()` — strip `<reply_to>` block/appended context before compare |
86
+ | `src/core/router.ts` | Modified | Strip leading `/` before `CHAT_COMMANDS` lookup |
87
+ | `src/core/commands.ts` | Modified | Remove `spaces unlink` from chat surface; help wording for bare/slash commands |
88
+ | `src/core/conversation.ts` + `src/core/handler.ts` | Modified | Unlinked conversations route link-restoring commands only |
89
+ | `main.ts` | Modified | Re-link or prompt on group re-join |
90
+ | `resources/skills/spaces/SKILL.md` | Modified | State mrctl is current-space-only, cross-space delete is chat-admin-only |
91
+
92
+ ### Implementation Sequence
93
+
94
+ 1. **mrctl argument rejection** (the load-bearing fix per HANDOFF 6b):
95
+ `mrctl spaces delete <arg>` → exit non-zero with
96
+ "only deletes the CURRENT space and takes no argument; use chat
97
+ `/spaces delete <id>` as an admin" — and issue **no** HTTP request. Fix the
98
+ usage line to `spaces list|name [<name>]|delete` with "(current space only)".
99
+ - Read first: `src/cli/mrctl.ts:80-90, 380-390`
100
+ - Verify: unit — with arg: non-zero, no request; without arg: still works.
101
+ 2. **Confirmation normalization helper** — strip `<reply_to …>…</reply_to>`
102
+ (and any appended context) before the `yes`/`no` compare; use it in
103
+ `handlePendingSpaceDelete`. Build as a shared helper for future flows.
104
+ - Read first: `src/core/runtime.ts:940-990`, `src/adapters/whatsapp.ts:600-615`
105
+ - Verify: unit — "yes" with an appended `<reply_to>` block matches (**the
106
+ baduk regression test**).
107
+ 3. **Pending-state lifecycle** — clear pending only on match, explicit `no`,
108
+ or expiry; an unrecognized message leaves it pending and falls through.
109
+ - Read first: `src/core/runtime.ts:953-990`
110
+ - Verify: unit — unrelated message does not clear pending; `no` and expiry do;
111
+ deleting space A from space B deletes A, not B.
112
+ 4. **`/stop`/`/compact`/`/clear` routing** — strip a leading `/` before the
113
+ `CHAT_COMMANDS` lookup (or register leaf entries in `SLASH_COMMANDS`);
114
+ reword `/help`'s "Bare commands" section to say the slash form works.
115
+ - Read first: `src/core/router.ts:35-50`, `src/core/commands.ts:15-67`
116
+ - Verify: unit — `/stop`, `/compact`, `/clear` route to `type: "command"`
117
+ and never reach the assistant path.
118
+ 5. **Unlink lockout** — unlinked conversations remain routable for
119
+ link-restoring commands only (`/spaces switch`, `/spaces list`); ordinary
120
+ messages still dropped. Remove `/spaces unlink` from the chat surface
121
+ (D-009). On group re-join (`onGroupRemoval` counterpart), re-link or prompt.
122
+ - Read first: `src/core/conversation.ts:110-135`, `src/core/handler.ts:185-200`,
123
+ `main.ts` (group membership handlers)
124
+ - Verify: unit — after `unlinkConversation`, `/spaces switch <id>` routes
125
+ and re-links; an ordinary message is still dropped.
126
+ 6. **Docs** — make `resources/skills/spaces/SKILL.md` emphatic that `mrctl` is
127
+ current-space-only (so the *agent* never repeats the baduk mistake).
128
+ - Read first: that SKILL.md
129
+ - Verify: text review; no behaviour to test.
130
+
131
+ Steps are independent except 2→3 (same function).
132
+
133
+ ---
134
+
135
+ ## Non-Negotiable Rules
136
+
137
+ - **Never** add a cross-space delete route to the API (D-006).
138
+ - The mrctl fix must fail **closed**: argument present → no request sent at all.
139
+ - A stray message must never silently cancel a pending delete.
140
+ - All manual destructive testing happens in **throwaway** spaces/groups only.
141
+
142
+ ---
143
+
144
+ ## Edge Cases & Risks
145
+
146
+ | Scenario | Handling |
147
+ |----------|---------|
148
+ | Reply "yes" carries other appended context beyond `<reply_to>` | Normalization strips known appended blocks, then trims — covered by unit tests per adapter format |
149
+ | Pending delete never confirmed | Expiry clears it (existing timeout retained) |
150
+ | `/stop` while nothing is running | Command executes as today's bare `stop` does — no model call |
151
+ | Group re-added but user declines re-link | Conversation stays unlinked but commands still route — no lockout |
152
+ | Agent (not human) types the destructive mrctl call | Step 1 makes it error; step 6 teaches it not to try |
153
+
154
+ ---
155
+
156
+ ## Implementation Checklist
157
+
158
+ ### Phase 1 — Goal
159
+ - [x] Goal, User Stories, and MVP Scope written and reviewed
160
+
161
+ ### Phase 2 — Architecture
162
+ - [x] Architecture & Data section complete
163
+ - [x] Non-Negotiable Rules defined
164
+ - [x] Edge Cases covered
165
+ - [x] Context for Claude pointers filled in
166
+
167
+ ### Implementation (commit `c44720f`)
168
+ - [x] mrctl rejection + usage (step 1)
169
+ - [x] Confirmation normalization (step 2)
170
+ - [x] Pending-state lifecycle (step 3)
171
+ - [x] Slash-tolerant CHAT_COMMANDS + help wording (step 4)
172
+ - [x] Unlink recovery + removal from chat (step 5; re-join handling deferred —
173
+ see retrospective)
174
+ - [x] SKILL.md wording (step 6)
175
+ - [x] 21 unit tests green, incl. the baduk regression; `bun run check` passes
176
+ - [ ] **Manual (needs a phone, throwaway space):** `/spaces delete <other-id>`
177
+ → **reply** "yes" → named space dies, current survives
178
+ - [ ] **Manual (needs a phone, throwaway group):** unlink then re-link from chat
179
+ - [x] No secrets or `.env` files committed
180
+ - [x] No unrelated files modified
181
+
182
+ ---
183
+
184
+ ## Open Questions
185
+
186
+ - [ ] Require the confirmation to echo the target id (`yes cmp-opus`) for
187
+ cross-space deletes? Recommended in HANDOFF — owner: user
188
+
189
+ ---
190
+
191
+ ## Retrospective
192
+
193
+ **Residual risks / follow-ups:**
194
+ - Re-linking on group re-join is **not** automatic. It was in the spec, but
195
+ chat recovery makes the lockout recoverable, which was the actual defect —
196
+ an admin types `/spaces switch <id>` and the group is back. Auto-relink on
197
+ rejoin would silently re-attach a group the bot was removed from, which is a
198
+ worse default. Accepted, deliberately.
199
+
200
+ **What changed from the plan:**
201
+ - The confirmation fix became a small shared module (`src/core/confirmation.ts`)
202
+ rather than an inline strip, because every future confirmation flow has the
203
+ same problem — the adapter appends context to *all* inbound text.
204
+ - Normalization **removes** the `<reply_to>` block instead of searching inside
205
+ it. Searching would have let a quoted message containing "yes" confirm a
206
+ delete on the user's behalf; a test pins that it cannot.
207
+
208
+ **Key decisions made during implementation:**
209
+ - The mrctl guard fails *before* the HTTP call, and the test asserts zero
210
+ requests reached a stub server. "Warns and then deletes anyway" would have
211
+ looked identical in a log-only test.
212
+ - `/spaces unlink` returns an explanation rather than disappearing, so someone
213
+ with the muscle memory learns why instead of getting "unknown verb".
214
+ - Recovery while unlinked is restricted to `list` and `switch`. Nothing that
215
+ mutates a space is reachable from a conversation that has no space.
216
+
217
+ **Architecture impact** (update `docs/ARCHITECTURE.md` if any of these apply):
218
+ - [ ] none expected
@@ -0,0 +1,182 @@
1
+ # `docs` Extension — Real Document Creation
2
+
3
+ **Status**: Done
4
+ **Slug**: docs-extension
5
+ **Goal**: whatsapp-bot-hardening
6
+ **Milestone**: 4
7
+ **Created**: 2026-08-09
8
+ **Last updated**: 2026-08-09
9
+
10
+ ---
11
+
12
+ ## Goal
13
+
14
+ > Part of goal [whatsapp-bot-hardening](../goals/whatsapp-bot-hardening/goal.md).
15
+ > HANDOFF Bug 4.
16
+
17
+ Asked to make a document, the bot attaches HTML + markdown and tells the user
18
+ to convert it themselves — because no document-writing capability exists: the
19
+ container has python3/node/bun but no `python-docx` and no `pandoc`. Egress
20
+ already works (the HTML attachment proves it); only *producing* office files is
21
+ missing. Ship a `docs` extension modeled directly on the existing `pdf`
22
+ extension (`index.ts` + skill dir; its `install` string is compiled into the
23
+ derived image by `extensions/image-builder.ts`) — D-005.
24
+
25
+ ## User Stories
26
+
27
+ - As a group member (not just admin), I want to say "make me a doc with X" and
28
+ receive a real `.docx` in WhatsApp that opens in Word/Google Docs.
29
+ - As a user, I want spreadsheets (`openpyxl`) and ODF output available for the
30
+ same flow.
31
+
32
+ ## MVP Scope
33
+
34
+ **In scope:** the extension (`index.ts` with install string + permission +
35
+ skill), `SKILL.md` teaching the agent to build documents and drop them in
36
+ `outbox/`, derived-image rebuild, member-role access.
37
+
38
+ **Out of scope:** Google Docs *links* (Workspace extension + OAuth — separate
39
+ project, per HANDOFF); templates/branding for generated documents.
40
+
41
+ ---
42
+
43
+ ## Context for Claude
44
+
45
+ - `examples/extensions/pdf/index.ts` + `examples/extensions/pdf/skill/SKILL.md`
46
+ — the model to copy (structure, install-string idiom, outbox flow).
47
+ - `src/extensions/image-builder.ts` — how `install` strings are grouped and
48
+ compiled into the derived image.
49
+ - `container/Dockerfile` — base image contents (python3 + pip present).
50
+ - `PLAN.md` "Web browsing" section — hard-won install-string traps: **never put
51
+ a backtick inside the TS template literal**, and validate with
52
+ `mercury extensions validate <name>` before restarting.
53
+ - `HANDOFF.md` Bug 4 — the exact `install` string to use.
54
+
55
+ ---
56
+
57
+ ## Architecture & Data
58
+
59
+ ### Data Models
60
+
61
+ None.
62
+
63
+ ### API Contracts
64
+
65
+ None (extension surface only).
66
+
67
+ ### File & Folder Structure
68
+
69
+ | Path | New / Modified | Purpose |
70
+ |------|---------------|---------|
71
+ | `examples/extensions/docs/index.ts` | New | CLI registration + install string + permissions |
72
+ | `examples/extensions/docs/skill/SKILL.md` | New | Teach: compose → build `.docx`/`.xlsx`/`.odt` → write to `outbox/` |
73
+
74
+ ```ts
75
+ // index.ts core (from HANDOFF):
76
+ mercury.cli({
77
+ name: "docs",
78
+ install: "apt-get update && apt-get install -y --no-install-recommends pandoc && "
79
+ + "python3 -m pip install --break-system-packages python-docx openpyxl odfpy && "
80
+ + "rm -rf /var/lib/apt/lists/*",
81
+ });
82
+ mercury.permission({ defaultRoles: ["admin", "member"] });
83
+ mercury.skill("./skill");
84
+ ```
85
+
86
+ ### Implementation Sequence
87
+
88
+ 1. Create the extension from the `pdf` template with the install string above.
89
+ No backticks anywhere inside the template literal.
90
+ - Read first: `examples/extensions/pdf/index.ts`, `pdf/skill/SKILL.md`
91
+ - Verify: `mercury extensions validate docs` passes.
92
+ 2. Write `SKILL.md`: when asked for a document, build it with
93
+ python-docx/pandoc, save to `outbox/`, confirm delivery; prefer `.docx` for
94
+ "doc", `.xlsx` for tables, pandoc for markdown→docx conversion.
95
+ - Read first: `pdf/skill/SKILL.md` (tone + outbox conventions)
96
+ - Verify: skill loads; text review.
97
+ 3. Install into the live project (path install, like pinchtab in PLAN.md) and
98
+ let the derived image rebuild (~3-4 min, background).
99
+ - Read first: PLAN.md extension-install trap notes
100
+ - Verify: image build succeeds; `docker run --rm --entrypoint python3
101
+ mercury-agent-ext:<hash> -c "import docx, openpyxl"` works and
102
+ `pandoc --version` runs in the derived image.
103
+
104
+ ---
105
+
106
+ ## Non-Negotiable Rules
107
+
108
+ - No backticks inside the install template literal (breaks the whole extension
109
+ load — symptom: extension shows "uninstalled" in dashboard).
110
+ - Validate with `mercury extensions validate docs` before any restart.
111
+ - Generated files go through `outbox/` — no new egress path.
112
+
113
+ ---
114
+
115
+ ## Edge Cases & Risks
116
+
117
+ | Scenario | Handling |
118
+ |----------|---------|
119
+ | Derived image rebuild slow first boot | Expected; background build, note in test plan |
120
+ | `pip --break-system-packages` conflicts | Pinned minimal set; same idiom other extensions use |
121
+ | Member permission not applied | Explicit manual test: member (not admin) triggers doc creation |
122
+ | `npm update -g` reverting extensions | Moot after M1.1 — we run from source |
123
+ | Hebrew/RTL content in docx | Include an RTL paragraph in the manual test (real user language) |
124
+
125
+ ---
126
+
127
+ ## Implementation Checklist
128
+
129
+ ### Phase 1 — Goal
130
+ - [x] Goal, User Stories, and MVP Scope written and reviewed
131
+
132
+ ### Phase 2 — Architecture
133
+ - [x] Architecture & Data section complete
134
+ - [x] Non-Negotiable Rules defined
135
+ - [x] Edge Cases covered
136
+ - [x] Context for Claude pointers filled in
137
+
138
+ ### Implementation (commit `e1208b8`)
139
+ - [x] Extension created + `mercury extensions validate docs` passes
140
+ - [x] SKILL.md written (step 2)
141
+ - [x] Installed; derived image builds; `import docx, openpyxl, odf` and
142
+ `pandoc 3.1.3` verified **inside** the image
143
+ - [x] `bun run check` passes
144
+ - [x] **Verified end to end:** asked the bot for a packing list → produced a
145
+ real "Microsoft Word 2007+" file, parsed back to a `Heading 1` and three
146
+ `List Bullet` paragraphs
147
+ - [ ] **Manual (needs a phone):** a member (not admin) triggers it over WhatsApp
148
+ - [x] No secrets or `.env` files committed
149
+ - [x] No unrelated files modified
150
+
151
+ ---
152
+
153
+ ## Open Questions
154
+
155
+ - [ ] none
156
+
157
+ ---
158
+
159
+ ## Retrospective
160
+
161
+ **Residual risks / follow-ups:**
162
+ - Google Workspace links extension — ideas/gws-docs-links.md (create if wanted)
163
+
164
+ **What changed from the plan:**
165
+ - Nothing structural — the `pdf` extension was a faithful model and the
166
+ install string from HANDOFF worked as written.
167
+
168
+ **Key decisions made during implementation:**
169
+ - The skill spends as much space on what *not* to do as on how: no markdown or
170
+ HTML as a substitute for a requested document, no claiming a file was saved
171
+ without writing it, no promising a Google Docs link. Those are the exact
172
+ behaviours this replaces, and a skill that only shows the happy path would
173
+ not have displaced them.
174
+ - RTL guidance included up front. The documents actually requested here are
175
+ frequently Hebrew, and Word mis-aligns them unless the paragraph is marked —
176
+ a "working" file that looks broken would have read as the feature failing.
177
+ - Verified by parsing the produced file, not by trusting the bot's report that
178
+ it had saved one. The whole class of bug in this goal is a confident report
179
+ that does not match what happened.
180
+
181
+ **Architecture impact** (update `docs/ARCHITECTURE.md` if any of these apply):
182
+ - [ ] New package/module added (`docs` extension)