@rubytech/create-maxy-code 0.1.172 → 0.1.175

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 (59) hide show
  1. package/dist/index.js +12 -1
  2. package/package.json +1 -1
  3. package/payload/platform/neo4j/schema.cypher +49 -10
  4. package/payload/platform/plugins/docs/references/voice-mirror-guide.md +18 -16
  5. package/payload/platform/scripts/setup-account.sh +8 -0
  6. package/payload/platform/scripts/verify-skill-tool-surface.sh +117 -0
  7. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
  8. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +1 -0
  9. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
  10. package/payload/platform/templates/specialists/agents/content-producer.md +4 -4
  11. package/payload/platform/templates/specialists/agents/librarian.md +2 -2
  12. package/payload/platform/templates/specialists/agents/personal-assistant.md +4 -4
  13. package/payload/platform/templates/specialists/agents/project-manager.md +2 -2
  14. package/payload/platform/templates/specialists/agents/research-assistant.md +2 -2
  15. package/payload/premium-plugins/writer-craft/PLUGIN.md +11 -7
  16. package/payload/premium-plugins/writer-craft/hooks/__tests__/voice-session-end-text.test.sh +284 -0
  17. package/payload/premium-plugins/writer-craft/hooks/voice-session-end-text.sh +75 -0
  18. package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.d.ts +3 -0
  19. package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.d.ts.map +1 -0
  20. package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.js +58 -0
  21. package/payload/premium-plugins/writer-craft/mcp/dist/cli/ingest-session-text.js.map +1 -0
  22. package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
  23. package/payload/premium-plugins/writer-craft/mcp/dist/index.js +113 -36
  24. package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
  25. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts +47 -6
  26. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts.map +1 -1
  27. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js +69 -6
  28. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js.map +1 -1
  29. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts +9 -3
  30. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
  31. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js +86 -57
  32. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js.map +1 -1
  33. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.d.ts +23 -0
  34. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.d.ts.map +1 -0
  35. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.js +191 -0
  36. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-ingest-session-text.js.map +1 -0
  37. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.d.ts +7 -1
  38. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -1
  39. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.js +20 -12
  40. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-record-feedback.js.map +1 -1
  41. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts +13 -2
  42. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
  43. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js +26 -15
  44. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
  45. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts +3 -0
  46. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
  47. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js +33 -5
  48. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js.map +1 -1
  49. package/payload/premium-plugins/writer-craft/mcp/scripts/smoke.mjs +289 -28
  50. package/payload/premium-plugins/writer-craft/mcp/src/cli/ingest-session-text.ts +65 -0
  51. package/payload/premium-plugins/writer-craft/mcp/src/index.ts +126 -36
  52. package/payload/premium-plugins/writer-craft/mcp/src/lib/voice-corpus.ts +78 -6
  53. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +120 -61
  54. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-ingest-session-text.ts +229 -0
  55. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-record-feedback.ts +27 -13
  56. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +42 -17
  57. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-tag-content.ts +42 -5
  58. package/payload/premium-plugins/writer-craft/skills/voice-mirror/SKILL.md +65 -31
  59. package/payload/server/server.js +12 -3
package/dist/index.js CHANGED
@@ -3781,7 +3781,18 @@ try {
3781
3781
  console.log("");
3782
3782
  console.log("================================================================");
3783
3783
  console.log("");
3784
- console.log(` Open in your browser: http://${DEVICE_HOSTNAME}.local:${PORT}`);
3784
+ if (isLinux()) {
3785
+ console.log(` Same network (Pi / LAN): http://${DEVICE_HOSTNAME}.local:${PORT}`);
3786
+ console.log("");
3787
+ console.log(` Remote access (Hetzner / cloud) — on your local machine run:`);
3788
+ console.log(` ssh -L ${PORT}:localhost:${PORT} -L ${BRAND.websockifyPort}:localhost:${BRAND.websockifyPort} admin@<server-ipv4>`);
3789
+ console.log(` Then open:`);
3790
+ console.log(` Dashboard: http://localhost:${PORT}`);
3791
+ console.log(` VNC browser (Claude OAuth + Cloudflare): http://localhost:${BRAND.websockifyPort}/vnc.html`);
3792
+ }
3793
+ else {
3794
+ console.log(` Open in your browser: http://${DEVICE_HOSTNAME}.local:${PORT}`);
3795
+ }
3785
3796
  console.log("");
3786
3797
  console.log("================================================================");
3787
3798
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.172",
3
+ "version": "0.1.175",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -1279,13 +1279,17 @@ CREATE INDEX postal_address_postcode IF NOT EXISTS
1279
1279
  FOR (a:PostalAddress) ON (a.postalCode);
1280
1280
 
1281
1281
  // ----------------------------------------------------------
1282
- // Voice Mirror (Task 356) — authorial-voice capture + application
1282
+ // Voice Mirror (Task 356 + Task 462) — authorial-voice capture + application
1283
1283
  // ----------------------------------------------------------
1284
- // :VoiceProfile holds the distilled style card for one :AdminUser.
1285
- // One profile per AdminUser; the (accountId, adminUserId) pair is unique.
1284
+ // :VoiceProfile holds the distilled style card for one (AdminUser, format).
1285
+ // Task 462: one profile per (accountId, userId, format); format
1286
+ // {text, email, social-post, article, marketing-copy, note}.
1287
+ // The old (accountId, adminUserId) unique constraint is dropped and replaced.
1288
+ //
1286
1289
  // :VoiceEdit captures operator edits on agent drafts as feedback,
1287
1290
  // stamped with a Haiku-summarised intent; many edits feed back into
1288
- // the next distillation.
1291
+ // the next distillation. VoiceEdit now carries `format` to route
1292
+ // FEEDBACK_FOR edges to the matching per-format :VoiceProfile.
1289
1293
  //
1290
1294
  // `authorshipMode` is an enum property on content-bearing labels
1291
1295
  // (`:KnowledgeDocument`, `:ConversationArchive` (Task 397 — every chat
@@ -1293,30 +1297,62 @@ FOR (a:PostalAddress) ON (a.postalCode);
1293
1297
  // `:Message`, `:SocialPost`) with
1294
1298
  // values `human-only | human-led-agent-assisted | agent-led-human-reviewed
1295
1299
  // | agent-only | unknown`. Legacy nodes default to `unknown` until
1296
- // reviewed via the voice-mirror backfill flow. The index lets the
1297
- // distillation walk filter on `authorshipMode = 'human-only'` cheaply.
1300
+ // reviewed via the voice-mirror backfill flow. The per-label authorship
1301
+ // index lets the distillation walk filter cheaply.
1302
+ //
1303
+ // `format` is an enum property on corpus nodes (same label set) and on
1304
+ // :VoiceProfile nodes. The per-label format index lets per-format corpus
1305
+ // walks and profile reads avoid full-label scans.
1306
+
1307
+ // Task 462 — drop the old (accountId, adminUserId) constraint before
1308
+ // creating the new (accountId, userId, format) three-key constraint.
1309
+ // `IF EXISTS` makes the DROP idempotent on fresh installs.
1310
+ DROP CONSTRAINT voice_profile_admin_unique IF EXISTS;
1298
1311
 
1299
- CREATE CONSTRAINT voice_profile_admin_unique IF NOT EXISTS
1300
- FOR (v:VoiceProfile) REQUIRE (v.accountId, v.adminUserId) IS UNIQUE;
1312
+ CREATE CONSTRAINT voice_profile_format_unique IF NOT EXISTS
1313
+ FOR (v:VoiceProfile) REQUIRE (v.accountId, v.userId, v.format) IS UNIQUE;
1301
1314
 
1302
1315
  CREATE INDEX voice_profile_account IF NOT EXISTS
1303
1316
  FOR (v:VoiceProfile) ON (v.accountId);
1304
1317
 
1305
- CREATE INDEX voice_edit_admin IF NOT EXISTS
1306
- FOR (e:VoiceEdit) ON (e.adminUserId);
1318
+ CREATE INDEX voice_profile_format IF NOT EXISTS
1319
+ FOR (v:VoiceProfile) ON (v.format);
1320
+
1321
+ CREATE INDEX voice_edit_user IF NOT EXISTS
1322
+ FOR (e:VoiceEdit) ON (e.userId);
1307
1323
 
1308
1324
  CREATE INDEX knowledge_document_authorship IF NOT EXISTS
1309
1325
  FOR (k:KnowledgeDocument) ON (k.authorshipMode);
1310
1326
 
1327
+ CREATE INDEX knowledge_document_format IF NOT EXISTS
1328
+ FOR (k:KnowledgeDocument) ON (k.format);
1329
+
1311
1330
  CREATE INDEX conversation_archive_authorship IF NOT EXISTS
1312
1331
  FOR (c:ConversationArchive) ON (c.authorshipMode);
1313
1332
 
1333
+ CREATE INDEX conversation_archive_format IF NOT EXISTS
1334
+ FOR (c:ConversationArchive) ON (c.format);
1335
+
1314
1336
  CREATE INDEX message_authorship IF NOT EXISTS
1315
1337
  FOR (m:Message) ON (m.authorshipMode);
1316
1338
 
1339
+ CREATE INDEX message_format IF NOT EXISTS
1340
+ FOR (m:Message) ON (m.format);
1341
+
1342
+ // contentHash index — deduplication MERGE in voice-ingest-session-text uses
1343
+ // MERGE (n:Message {contentHash: $hash, accountId: $accountId}). Without this
1344
+ // index, each MERGE scans all :Message nodes for the account. The index on
1345
+ // contentHash alone lets neo4j narrow the lookup before applying the
1346
+ // accountId predicate.
1347
+ CREATE INDEX message_content_hash IF NOT EXISTS
1348
+ FOR (m:Message) ON (m.contentHash);
1349
+
1317
1350
  CREATE INDEX social_post_authorship IF NOT EXISTS
1318
1351
  FOR (s:SocialPost) ON (s.authorshipMode);
1319
1352
 
1353
+ CREATE INDEX social_post_format IF NOT EXISTS
1354
+ FOR (s:SocialPost) ON (s.format);
1355
+
1320
1356
  // :Section authorshipMode — the corpus walk includes conversation-archive
1321
1357
  // :Section chunks (parent :ConversationArchive) alongside ordinary
1322
1358
  // document sections under :KnowledgeDocument. Index on the :Section label
@@ -1325,6 +1361,9 @@ FOR (s:SocialPost) ON (s.authorshipMode);
1325
1361
  CREATE INDEX section_authorship IF NOT EXISTS
1326
1362
  FOR (s:Section) ON (s.authorshipMode);
1327
1363
 
1364
+ CREATE INDEX section_format IF NOT EXISTS
1365
+ FOR (s:Section) ON (s.format);
1366
+
1328
1367
  CREATE INDEX social_post_account IF NOT EXISTS
1329
1368
  FOR (s:SocialPost) ON (s.accountId);
1330
1369
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What It Does
4
4
 
5
- {{productName}} reads emails, posts, and documents that you wrote yourself, and uses them to make sure agent-drafted copy reads like you wrote it — not like generic AI prose.
5
+ {{productName}} reads emails, posts, documents, and your own chat messages, and uses them to make sure agent-drafted copy reads like you wrote it — not like generic AI prose.
6
6
 
7
7
  Anthropic models cannot be fine-tuned. Voice mirror works by feeding the model two things alongside every drafting task:
8
8
 
@@ -11,32 +11,34 @@ Anthropic models cannot be fine-tuned. Voice mirror works by feeding the model t
11
11
 
12
12
  The model conditions on both and produces copy that reads as yours.
13
13
 
14
- ## Setup
14
+ Voice mirror maintains a separate profile for each type of content you write: plain text, email, social posts, articles, notes, and marketing copy. The right profile is applied automatically based on what you're drafting.
15
15
 
16
- ### 1. Tell {{productName}} what you wrote
16
+ ## How Your Voice Is Captured
17
17
 
18
- Voice mirror only learns from content you genuinely authored. Everything else stays unknown until you say otherwise.
18
+ ### Automatically from chat
19
+
20
+ Every admin chat session feeds your writing into the `text` profile automatically. When the session ends, {{productName}} reads the transcript, filters out slash commands, system messages, and large paste blocks, and adds each genuine turn as a corpus entry. This happens in the background with no action needed from you.
21
+
22
+ ### Via backfill — from historical content
23
+
24
+ Use the backfill flow to teach {{productName}} your writing from emails, documents, and social posts you've already written.
19
25
 
20
26
  In the admin chat, ask: **"Start the voice-mirror backfill."**
21
27
 
22
28
  {{productName}} asks which stream to backfill first — discrete documents (emails, posts, PDFs) or chat archives (WhatsApp, Telegram, etc.). **Pick chat archives first if you have any imported.** Chat is where your unguarded voice lives; email is the same voice in dress clothes.
23
29
 
24
30
  - **Chat archives** — tag a whole conversation in one click. {{productName}} doesn't ask about individual messages because chunks within a conversation almost always share the same voice. Options per conversation: yours, mixed (multiple authors on your side — rare), or not yours (e.g. a Slack channel where you only forwarded other people's messages).
25
- - **Documents and posts** — paginated 10 at a time. Tag the whole batch, a subset by number, or per-item if you want a precise label like `human-led-agent-assisted` (you wrote the content, {{productName}} polished).
31
+ - **Documents and posts** — paginated 10 at a time. {{productName}} shows each item with its detected format (email, article, note, social-post). Tag the whole batch, a subset by number, or per-item if you want a precise label like `human-led-agent-assisted` (you wrote the content, {{productName}} polished). Override the detected format if one is wrong.
26
32
 
27
33
  Skip a batch if none qualify, or stop at any time — the next session resumes where you left off.
28
34
 
29
- ### 2. Distil the profile
30
-
31
- Once you have at least ~20 `human-only` items tagged, ask: **"Build my voice profile."**
32
-
33
- {{productName}} reads the corpus, summarises your style as a YAML card, and saves it to the graph. It picks up your sentence rhythms, the constructions you reach for, the words you avoid.
35
+ ## Distilling Your Profile
34
36
 
35
- The profile re-runs automatically when your corpus grows by ≥20% or every 30 days, whichever comes first.
37
+ Once you have corpus entries tagged (or after automatic PTY ingestion fills the `text` profile), ask: **"Build my voice profile."**
36
38
 
37
- ### 3. Done
39
+ {{productName}} reads the corpus for each format, summarises your style as a YAML card, and saves it to the graph. It picks up your sentence rhythms, the constructions you reach for, the words you avoid — separately for email, articles, social posts, and so on.
38
40
 
39
- That's it. Every agent-drafted email, social post, brochure paragraph, and prospectus section from this point conditions on your voice. You don't need to ask for it.
41
+ The profile re-runs automatically when your corpus grows by ≥20% for any format or every 30 days, whichever comes first.
40
42
 
41
43
  ## Feedback Loop
42
44
 
@@ -50,11 +52,11 @@ Voice mirror is on by default for every drafting skill. To opt out for one, set
50
52
 
51
53
  ## What It Won't Do
52
54
 
53
- - **Blend voices** — one profile per person, no "Joel + Neo combined".
55
+ - **Blend voices** — one profile set per person, no "Joel + Neo combined".
54
56
  - **Copy public figures** — voice mirror only learns from your own writing.
55
57
  - **Clone audio** — text only, no speech synthesis.
56
- - **Guess** — content stays `unknown` until you mark it. {{productName}} never auto-classifies your historical writing.
58
+ - **Guess** — historical content stays `unknown` until you mark it. {{productName}} never auto-classifies your writing. (Automatic ingestion applies only to your live PTY sessions where authorship is certain.)
57
59
 
58
60
  ## Status
59
61
 
60
- Voice mirror shipped end-to-end. The schema, the four tools (`voice-tag-content`, `voice-distil-profile`, `voice-retrieve-conditioning`, `voice-record-feedback`), and wiring into the three live drafting surfaces (email composition, property brochures, investor data room) are all live. When no voice profile exists on the account, every drafting skill degrades gracefully — the output matches what it was before voice mirror was installed.
62
+ Voice mirror is live end-to-end. Six corpus formats (text, email, social-post, article, note, marketing-copy), five tools (`voice-tag-content`, `voice-distil-profile`, `voice-retrieve-conditioning`, `voice-record-feedback`, `voice-ingest-session-text`), automatic PTY-turn ingestion via SessionEnd hook, and wiring into the three live drafting surfaces (email composition, property brochures, investor data room) are all live. When no voice profile exists on the account for a given format, every drafting skill degrades gracefully — the output matches what it was before voice mirror was installed.
@@ -58,6 +58,7 @@ mkdir -p "$ACCOUNT_DIR/agents/admin" "$ACCOUNT_DIR/agents/public" "$ACCOUNT_DIR/
58
58
  # to locate scripts relative to the platform installation, not the account dir.
59
59
  ACCOUNT_SETTINGS="$ACCOUNT_DIR/.claude/settings.json"
60
60
  HOOKS_PATH="\$PLATFORM_ROOT/plugins/admin/hooks"
61
+ WRITER_CRAFT_HOOKS_PATH="\$PLATFORM_ROOT/../premium-plugins/writer-craft/hooks"
61
62
  cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
62
63
  {
63
64
  "hooks": {
@@ -146,6 +147,13 @@ cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
146
147
  { "type": "command", "command": "bash $HOOKS_PATH/session-end-retrospective.sh" }
147
148
  ]
148
149
  }
150
+ ],
151
+ "SessionEnd": [
152
+ {
153
+ "hooks": [
154
+ { "type": "command", "command": "bash $WRITER_CRAFT_HOOKS_PATH/voice-session-end-text.sh" }
155
+ ]
156
+ }
149
157
  ]
150
158
  }
151
159
  }
@@ -68,6 +68,13 @@ ADMIN_OWNED_SKILLS = {
68
68
  "memory/conversational-memory",
69
69
  }
70
70
 
71
+ # Regex for skill-name tokens in the prompt body that require `Skill` in tools:
72
+ # 1. `skill-load skillName=<name>` pattern (how templates reference Skill calls)
73
+ # 2. `Skill <name>` pattern (explicit Skill tool invocation in enforcement directives)
74
+ # Only hyphenated lowercase names are skill names.
75
+ SKILL_LOAD_RE = re.compile(r"skillName=([a-z][a-z0-9-]+)")
76
+ SKILL_INVOKE_RE = re.compile(r"`Skill\s+([a-z][a-z0-9-]+)`")
77
+
71
78
  TOKEN_RE = re.compile(r"`(mcp__[a-z][a-z0-9_-]*__[a-z][a-z0-9_-]*)`")
72
79
  FENCED_BLOCK_RE = re.compile(r"```(?P<lang>[a-zA-Z]*)\n(?P<body>.*?)\n```", re.S)
73
80
  PROSE_CYPHER_RE = re.compile(
@@ -170,6 +177,73 @@ def aggregate_skill_text(skill_dir: str) -> str:
170
177
  return "\n".join(out)
171
178
 
172
179
 
180
+ def collect_skill_names() -> set[str]:
181
+ """Enumerate all installed skill directory names from platform/plugins/*/skills/."""
182
+ names: set[str] = set()
183
+ if not os.path.isdir(PLUGINS_DIR):
184
+ return names
185
+ for plugin in os.listdir(PLUGINS_DIR):
186
+ skills_dir = os.path.join(PLUGINS_DIR, plugin, "skills")
187
+ if not os.path.isdir(skills_dir):
188
+ continue
189
+ for entry in os.listdir(skills_dir):
190
+ if os.path.isdir(os.path.join(skills_dir, entry)):
191
+ names.add(entry)
192
+ return names
193
+
194
+
195
+ def specialist_body(specialist_path: str) -> str:
196
+ """Return the prompt body of a specialist template (content below frontmatter)."""
197
+ try:
198
+ text = open(specialist_path, encoding="utf-8").read()
199
+ except FileNotFoundError:
200
+ return ""
201
+ m = re.match(r"^---\n.*?\n---\n(.*)", text, re.S)
202
+ return m.group(1) if m else text
203
+
204
+
205
+ def check_builtin_tool_rule(
206
+ specialist: str,
207
+ body: str,
208
+ tools: set[str],
209
+ known_skills: set[str],
210
+ ) -> list[str]:
211
+ """Built-in-tool rule: if the specialist prompt references a skill load that
212
+ matches an installed skill directory, `Skill` must appear in tools:."""
213
+ referenced: set[str] = set()
214
+ # Pattern 1: skill-load skillName=<name> (template invocation form)
215
+ referenced |= set(SKILL_LOAD_RE.findall(body)) & known_skills
216
+ # Pattern 2: `Skill <name>` (enforcement directive form)
217
+ referenced |= set(SKILL_INVOKE_RE.findall(body)) & known_skills
218
+ if not referenced or "Skill" in tools:
219
+ return []
220
+ return [
221
+ f"[verify] specialist={specialist} claims-loads={name} "
222
+ f"but Skill missing from tools"
223
+ for name in sorted(referenced)
224
+ ]
225
+
226
+
227
+ def check_webfetch_rule(
228
+ specialist: str,
229
+ body: str,
230
+ tools: set[str],
231
+ ) -> list[str]:
232
+ """WebFetch rule: if the specialist prompt names WebFetch explicitly or
233
+ directs dereferencing an operator-provided URL, WebFetch must be in tools:."""
234
+ if "WebFetch" in tools:
235
+ return []
236
+ has_webfetch = bool(re.search(r"\bWebFetch\b", body))
237
+ has_fetch_url = bool(re.search(r"\bfetch\b.*\bURL\b", body, re.IGNORECASE))
238
+ if not (has_webfetch or has_fetch_url):
239
+ return []
240
+ reason = "WebFetch" if has_webfetch else r"\bfetch\b.*\bURL\b"
241
+ return [
242
+ f"[verify] specialist={specialist} directs WebFetch but tool missing "
243
+ f"(matched: {reason})"
244
+ ]
245
+
246
+
173
247
  def main() -> int:
174
248
  if not os.path.isdir(PLUGINS_DIR):
175
249
  print(f"[verify] PLUGINS_DIR not found: {PLUGINS_DIR}", file=sys.stderr)
@@ -252,6 +326,49 @@ def main() -> int:
252
326
  )
253
327
  pairs_checked += 1
254
328
 
329
+ # Built-in-tool rule + WebFetch rule: sweep all agent template .md files.
330
+ # Covers platform specialists (SPECIALISTS_DIR) and premium-plugin agent
331
+ # templates (premium-plugins/*/agents/). These rules are orthogonal to the
332
+ # skill-pairing loop above — they check each agent's own prompt body for
333
+ # directives that require built-in CC tools.
334
+ known_skills = collect_skill_names()
335
+ builtin_checked = 0
336
+
337
+ agent_template_dirs: list[str] = []
338
+ if os.path.isdir(SPECIALISTS_DIR):
339
+ agent_template_dirs.append(SPECIALISTS_DIR)
340
+ pp_root = os.path.join(REPO_ROOT, "premium-plugins")
341
+ if os.path.isdir(pp_root):
342
+ for plugin in sorted(os.listdir(pp_root)):
343
+ adir = os.path.join(pp_root, plugin, "agents")
344
+ if os.path.isdir(adir):
345
+ agent_template_dirs.append(adir)
346
+
347
+ for adir in agent_template_dirs:
348
+ for fname in sorted(os.listdir(adir)):
349
+ if not fname.endswith(".md"):
350
+ continue
351
+ spath = os.path.join(adir, fname)
352
+ fm = parse_frontmatter(spath)
353
+ if fm is None:
354
+ continue
355
+ specialist = fm.get("name") or fname[:-3]
356
+ raw = fm.get("tools", "")
357
+ if isinstance(raw, list):
358
+ tools = {t for t in raw if t}
359
+ else:
360
+ tools = {t.strip() for t in str(raw).split(",") if t.strip()}
361
+ body = specialist_body(spath)
362
+ for err in check_builtin_tool_rule(specialist, body, tools, known_skills):
363
+ errors.append(err)
364
+ for err in check_webfetch_rule(specialist, body, tools):
365
+ errors.append(err)
366
+ builtin_checked += 1
367
+
368
+ summary.append(
369
+ f"[verify] builtin-tool-check specialists_checked={builtin_checked}"
370
+ )
371
+
255
372
  for line in summary:
256
373
  print(line)
257
374
 
@@ -1 +1 @@
1
- {"version":3,"file":"specialist-drift.d.ts","sourceRoot":"","sources":["../src/specialist-drift.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAmCpD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EACF,0BAA0B,GAC1B,wBAAwB,GACxB,gBAAgB,GAChB,uBAAuB,CAAA;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;yCAGqC;IACrC,EAAE,EAAE,OAAO,CAAA;IACX;;wCAEoC;IACpC,OAAO,EAAE,qBAAqB,EAAE,CAAA;IAChC,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;IACjB;;;;6CAIyC;IACzC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;CACpD;AAED;;;kEAGkE;AAClE,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3E;AAmCD;;;;;;;;;wDASwD;AACxD,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,WAAW,EAAE,WAAW,EACxB,eAAe,GAAE,WAAW,CAAC,MAAM,CAAa,GAC/C,qBAAqB,CAgDvB"}
1
+ {"version":3,"file":"specialist-drift.d.ts","sourceRoot":"","sources":["../src/specialist-drift.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAoCpD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EACF,0BAA0B,GAC1B,wBAAwB,GACxB,gBAAgB,GAChB,uBAAuB,CAAA;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;yCAGqC;IACrC,EAAE,EAAE,OAAO,CAAA;IACX;;wCAEoC;IACpC,OAAO,EAAE,qBAAqB,EAAE,CAAA;IAChC,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;IACjB;;;;6CAIyC;IACzC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;CACpD;AAED;;;kEAGkE;AAClE,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3E;AAmCD;;;;;;;;;wDASwD;AACxD,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,WAAW,EAAE,WAAW,EACxB,eAAe,GAAE,WAAW,CAAC,MAAM,CAAa,GAC/C,qBAAqB,CAgDvB"}
@@ -37,6 +37,7 @@ const CC_NATIVE_TOOLS = new Set([
37
37
  'Bash',
38
38
  'BashOutput',
39
39
  'KillShell',
40
+ 'Skill',
40
41
  'WebSearch',
41
42
  'WebFetch',
42
43
  'Task',
@@ -1 +1 @@
1
- {"version":3,"file":"specialist-drift.js","sourceRoot":"","sources":["../src/specialist-drift.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specialist-drift.js","sourceRoot":"","sources":["../src/specialist-drift.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ name: content-producer
3
3
  description: "Visual production and static-site hosting. Reads from the populated graph to produce visual artifacts (image generation, PDF rendering, component delivery) and hosts already-prepared static sites by extracting attached archives via unzip-attachment then placing the tree under <accountDir>/sites/<slug>/ via publish-site. Delegate for: generating images, saving rendered pages as PDF, or any 'host this website' / 'publish this site' / 'put this online' intent carrying an HTML+assets archive. Not document ingestion: graph ingestion of any kind routes to specialists:database-operator. Static-site zips are extracted to disk for publication, never written to the graph."
4
4
  summary: "Produces visual output from your graph: generates images, renders pages to PDF, and hosts static websites you upload as a zip."
5
5
  model: claude-sonnet-4-6
6
- tools: Bash, mcp__memory__memory-search, mcp__replicate__image-generate, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_take_screenshot, mcp__plugin_playwright_playwright__browser_pdf_save, mcp__admin__file-attach, mcp__admin__plugin-read, mcp__admin__public-hostname, mcp__admin__publish-site
6
+ tools: Bash, Skill, mcp__memory__memory-search, mcp__replicate__image-generate, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_take_screenshot, mcp__plugin_playwright_playwright__browser_pdf_save, mcp__admin__file-attach, mcp__admin__plugin-read, mcp__admin__public-hostname, mcp__admin__publish-site
7
7
  ---
8
8
 
9
9
  # Content Producer
@@ -32,13 +32,13 @@ Three models via `image-generate`. Pick by output need: `recraft-v4` for design-
32
32
 
33
33
  ## PDF output
34
34
 
35
- Generate the HTML, navigate the Playwright browser to it, and capture with `browser_pdf_save`. For A4 print, load `skill-load skillName=a4-print-documents`: the skill carries the print-CSS constraints (page margins, glassmorphism fallback, page-break rules, dark-background colour-adjust). Do not paraphrase those rules; load and follow.
35
+ Generate the HTML, navigate the Playwright browser to it, and capture with `browser_pdf_save`. For A4 print, **the first tool call of this workflow branch MUST be `Skill a4-print-documents`. Any browser tool call before the skill content is loaded into context is a contract violation.** Load `skill-load skillName=a4-print-documents`: the skill carries the print-CSS constraints (page margins, glassmorphism fallback, page-break rules, dark-background colour-adjust). Do not paraphrase those rules; load and follow.
36
36
 
37
37
  `file://` URLs are rewritten transparently by the `playwright-file-guard` PreToolUse hook. Pass them directly to `browser_navigate`.
38
38
 
39
39
  ## Hosting websites
40
40
 
41
- When a brief carries a "host this website" / "publish this site" / "put this online" intent with a `.zip` of HTML and assets, run `skill-load skillName=unzip-attachment` to extract, then call `mcp__admin__publish-site` directly. The tool owns slug validation, the symlink scan, the `mv`, the refusal taxonomy, and the post-publish llms.txt refresh — load `skill-load skillName=publish-site` only if you need the intent-router context. Do not improvise a fallback server.
41
+ When a brief carries a "host this website" / "publish this site" / "put this online" intent with a `.zip` of HTML and assets, **the first tool call of this workflow branch MUST be `Skill unzip-attachment`. Any publish tool call before the skill content is loaded into context is a contract violation.** Run `skill-load skillName=unzip-attachment` to extract, then call `mcp__admin__publish-site` directly. The tool owns slug validation, the symlink scan, the `mv`, the refusal taxonomy, and the post-publish llms.txt refresh — load `skill-load skillName=publish-site` only if you need the intent-router context. Do not improvise a fallback server.
42
42
 
43
43
  Confirm the slug with the operator before publishing. The slug is one or more `/`-separated segments under `<accountDir>/sites/`. Refusals are loud-fail; relay the tool's `Operator action:` line verbatim and stop. On success, take the `pathSlug` from the tool response, call `mcp__admin__public-hostname`, and surface `https://<hostname><pathSlug>` to the operator as one line.
44
44
 
@@ -60,4 +60,4 @@ Return to the admin agent: what you did (images generated, PDFs produced, compon
60
60
 
61
61
  ## Plain English
62
62
 
63
- Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin (captions, brochure prose, summaries, the "What you did" lines). It does not apply to `image-generate` prompts: those are agent-to-machine payloads where technical descriptors are required vocabulary, not jargon to strip.
63
+ **The first tool call MUST be `Skill plainly`. Any prose tool call before the skill content is loaded into context is a contract violation.** Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin (captions, brochure prose, summaries, the "What you did" lines). It does not apply to `image-generate` prompts: those are agent-to-machine payloads where technical descriptors are required vocabulary, not jargon to strip.
@@ -3,7 +3,7 @@ name: librarian
3
3
  description: "Foreground ingester for the memory graph. Owns external-archive and document ingestion — PDFs, web pages, plain text, conversation transcripts (WhatsApp / Telegram / Signal / iMessage / Slack / LinkedIn DMs / Zoom / meeting minutes), LinkedIn Basic Data Exports, and post-unzip trees the admin agent forwards. Loads `document-ingest`, `conversation-archive`, `conversation-archive-enrich`, and `linkedin-import` skills. Returns to the admin agent with a structured outcome line; never interacts with the operator directly except through the dispatching agent."
4
4
  summary: "Owns foreground ingest of documents, conversation transcripts, and external archives into the memory graph."
5
5
  model: claude-sonnet-4-5
6
- tools: mcp__plugin_memory_memory__memory-ingest-extract, mcp__plugin_memory_memory__memory-ingest, mcp__plugin_memory_memory__memory-ingest-web, mcp__plugin_memory_memory__memory-archive-write, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-search, mcp__plugin_memory_memory__conversation-archive-list-chunks, mcp__plugin_memory_memory__conversation-archive-derive-insights, mcp__plugin_memory_memory__conversation-archive-enrich-rejection, mcp__plugin_contacts_contacts__contact-create, mcp__plugin_work_work__work-create, Read, Bash
6
+ tools: mcp__plugin_memory_memory__memory-ingest-extract, mcp__plugin_memory_memory__memory-ingest, mcp__plugin_memory_memory__memory-ingest-web, mcp__plugin_memory_memory__memory-archive-write, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-search, mcp__plugin_memory_memory__conversation-archive-list-chunks, mcp__plugin_memory_memory__conversation-archive-derive-insights, mcp__plugin_memory_memory__conversation-archive-enrich-rejection, mcp__plugin_contacts_contacts__contact-create, mcp__plugin_work_work__work-create, Read, Bash, Skill, WebFetch
7
7
  ---
8
8
 
9
9
  # Librarian
@@ -28,7 +28,7 @@ These three rules win when anything else in this prompt conflicts with them.
28
28
 
29
29
  ## Skill routing
30
30
 
31
- The dispatch brief from the admin agent names the input class. Map it to one skill:
31
+ The dispatch brief from the admin agent names the input class. Map it to one skill. **After routing, the first tool call MUST be `Skill <skill-name>`. Any `memory-*` write tool called before the skill content is loaded into context is a contract violation.**
32
32
 
33
33
  | Input | Skill | Parent label |
34
34
  |---|---|---|
@@ -3,7 +3,7 @@ name: personal-assistant
3
3
  description: "Your personal assistant. Scheduling, platform administration, messaging channels (Telegram, WhatsApp, email, Outlook), system health, Cloudflare tunnel setup, and browser automation. Delegate when a task involves managing your calendar, configuring the platform, operating messaging channels, setting up a tunnel or domain, or completing interactive browser tasks."
4
4
  summary: "Handles the operational tasks you'd give a personal assistant: scheduling meetings, managing your platform settings, connecting messaging channels, and completing browser-based tasks on your behalf."
5
5
  model: claude-sonnet-4-6
6
- tools: mcp__admin__system-status, mcp__admin__brand-settings, mcp__admin__account-manage, mcp__admin__account-update, mcp__admin__logs-read, mcp__admin__plugin-read, mcp__admin__file-attach, mcp__admin__wifi, mcp__contacts__contact-create, mcp__contacts__contact-lookup, mcp__contacts__contact-update, mcp__contacts__contact-delete, mcp__contacts__contact-list, mcp__contacts__contact-export, mcp__contacts__contact-erase, mcp__contacts__group-create, mcp__contacts__group-manage, mcp__telegram__message, mcp__telegram__message-history, mcp__telegram__telegram-webhook-register, mcp__whatsapp__whatsapp-login-start, mcp__whatsapp__whatsapp-login-wait, mcp__whatsapp__whatsapp-status, mcp__whatsapp__whatsapp-disconnect, mcp__whatsapp__whatsapp-send, mcp__whatsapp__whatsapp-send-document, mcp__whatsapp__whatsapp-config, mcp__whatsapp__whatsapp-activity, mcp__whatsapp__whatsapp-conversations, mcp__whatsapp__whatsapp-messages, mcp__whatsapp__whatsapp-conversation-graph-state, mcp__whatsapp__whatsapp-group-info, mcp__email__email-setup, mcp__email__email-read, mcp__email__email-send, mcp__email__email-reply, mcp__email__email-search, mcp__email__email-graph-query, mcp__email__email-otp-extract, mcp__email__email-status, mcp__email__email-fetch, mcp__email__email-ingest, mcp__outlook__outlook-account-register, mcp__outlook__outlook-mail-list, mcp__outlook__outlook-mail-search, mcp__outlook__outlook-calendar-list, mcp__outlook__outlook-calendar-event, mcp__outlook__outlook-contacts-list, mcp__outlook__outlook-mailbox-info, mcp__scheduling__schedule-event, mcp__scheduling__schedule-list, mcp__scheduling__schedule-get, mcp__scheduling__schedule-update, mcp__scheduling__schedule-cancel, mcp__scheduling__schedule-export-ics, mcp__scheduling__schedule-import-ics, mcp__scheduling__time-resolve, mcp__memory__memory-search, mcp__memory__profile-update, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_navigate_back, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_click, mcp__plugin_playwright_playwright__browser_fill, mcp__plugin_playwright_playwright__browser_fill_form, mcp__plugin_playwright_playwright__browser_type, mcp__plugin_playwright_playwright__browser_press_key, mcp__plugin_playwright_playwright__browser_hover, mcp__plugin_playwright_playwright__browser_select_option, mcp__plugin_playwright_playwright__browser_wait_for, mcp__plugin_playwright_playwright__browser_handle_dialog, mcp__plugin_playwright_playwright__browser_evaluate, mcp__plugin_playwright_playwright__browser_console_messages, mcp__plugin_playwright_playwright__browser_resize, mcp__plugin_playwright_playwright__browser_tabs, mcp__plugin_playwright_playwright__browser_close
6
+ tools: Skill, mcp__admin__system-status, mcp__admin__brand-settings, mcp__admin__account-manage, mcp__admin__account-update, mcp__admin__logs-read, mcp__admin__plugin-read, mcp__admin__file-attach, mcp__admin__wifi, mcp__contacts__contact-create, mcp__contacts__contact-lookup, mcp__contacts__contact-update, mcp__contacts__contact-delete, mcp__contacts__contact-list, mcp__contacts__contact-export, mcp__contacts__contact-erase, mcp__contacts__group-create, mcp__contacts__group-manage, mcp__telegram__message, mcp__telegram__message-history, mcp__telegram__telegram-webhook-register, mcp__whatsapp__whatsapp-login-start, mcp__whatsapp__whatsapp-login-wait, mcp__whatsapp__whatsapp-status, mcp__whatsapp__whatsapp-disconnect, mcp__whatsapp__whatsapp-send, mcp__whatsapp__whatsapp-send-document, mcp__whatsapp__whatsapp-config, mcp__whatsapp__whatsapp-activity, mcp__whatsapp__whatsapp-conversations, mcp__whatsapp__whatsapp-messages, mcp__whatsapp__whatsapp-conversation-graph-state, mcp__whatsapp__whatsapp-group-info, mcp__email__email-setup, mcp__email__email-read, mcp__email__email-send, mcp__email__email-reply, mcp__email__email-search, mcp__email__email-graph-query, mcp__email__email-otp-extract, mcp__email__email-status, mcp__email__email-fetch, mcp__email__email-ingest, mcp__outlook__outlook-account-register, mcp__outlook__outlook-mail-list, mcp__outlook__outlook-mail-search, mcp__outlook__outlook-calendar-list, mcp__outlook__outlook-calendar-event, mcp__outlook__outlook-contacts-list, mcp__outlook__outlook-mailbox-info, mcp__scheduling__schedule-event, mcp__scheduling__schedule-list, mcp__scheduling__schedule-get, mcp__scheduling__schedule-update, mcp__scheduling__schedule-cancel, mcp__scheduling__schedule-export-ics, mcp__scheduling__schedule-import-ics, mcp__scheduling__time-resolve, mcp__memory__memory-search, mcp__memory__profile-update, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_navigate_back, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_click, mcp__plugin_playwright_playwright__browser_fill, mcp__plugin_playwright_playwright__browser_fill_form, mcp__plugin_playwright_playwright__browser_type, mcp__plugin_playwright_playwright__browser_press_key, mcp__plugin_playwright_playwright__browser_hover, mcp__plugin_playwright_playwright__browser_select_option, mcp__plugin_playwright_playwright__browser_wait_for, mcp__plugin_playwright_playwright__browser_handle_dialog, mcp__plugin_playwright_playwright__browser_evaluate, mcp__plugin_playwright_playwright__browser_console_messages, mcp__plugin_playwright_playwright__browser_resize, mcp__plugin_playwright_playwright__browser_tabs, mcp__plugin_playwright_playwright__browser_close
7
7
  ---
8
8
 
9
9
  # Personal Assistant
@@ -22,8 +22,8 @@ These three rules win when anything else in this prompt conflicts with them.
22
22
 
23
23
  Each domain has a small set of tools and, where it exists, a skill that drives the multi-step flow. Match the brief to the domain, load the skill if one is named, and run the tools the skill prescribes.
24
24
 
25
- - **WhatsApp setup or config:** load `skill-load skillName=connect-whatsapp` for QR pairing and admin-phone setup; load `skill-load skillName=manage-whatsapp-config` for DM/group policies and admin-phone management. The skills carry the per-phase flow.
26
- - **Cloudflare tunnel:** load `skill-load skillName=setup-tunnel`. The skill drives `cloudflared` directly via Bash following `references/manual-setup.md`, `references/reset-guide.md`, and `references/dashboard-guide.md`. There is no shell-script wrapper; PTY stdout is the only surface.
25
+ - **WhatsApp setup or config:** **After routing, the first tool call MUST be `Skill connect-whatsapp` (for QR pairing and admin-phone setup) or `Skill manage-whatsapp-config` (for DM/group policies). Any channel tool call before the routed skill's content is loaded into context is a contract violation.** Load `skill-load skillName=connect-whatsapp` for QR pairing and admin-phone setup; load `skill-load skillName=manage-whatsapp-config` for DM/group policies and admin-phone management. The skills carry the per-phase flow.
26
+ - **Cloudflare tunnel:** **After routing, the first tool call MUST be `Skill setup-tunnel`. Any Bash call before the skill content is loaded into context is a contract violation.** Load `skill-load skillName=setup-tunnel`. The skill drives `cloudflared` directly via Bash following `references/manual-setup.md`, `references/reset-guide.md`, and `references/dashboard-guide.md`. There is no shell-script wrapper; PTY stdout is the only surface.
27
27
  - **Every other domain** (scheduling, Telegram, email, Outlook, contacts, browser, platform admin) runs through the tool descriptions injected into your system prompt. The rules below apply across these domains regardless of which tool is invoked.
28
28
 
29
29
  ## Cross-domain rules
@@ -56,4 +56,4 @@ Name the tool, what you tried, and what the `[tool-failure-diag]` line shows. Do
56
56
 
57
57
  ## Plain English
58
58
 
59
- Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin and to every message body destined for a human reader (email bodies, WhatsApp text, status summaries). It does not apply to structured tool arguments (cron expressions, scheduling enums, browser selectors).
59
+ **The first tool call MUST be `Skill plainly`. Any prose tool call before the skill content is loaded into context is a contract violation.** Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin and to every message body destined for a human reader (email bodies, WhatsApp text, status summaries). It does not apply to structured tool arguments (cron expressions, scheduling enums, browser selectors).
@@ -3,7 +3,7 @@ name: project-manager
3
3
  description: "Project and task management. Creating, tracking, and completing tasks and projects, naming sessions, building and running workflows, and linking work to people and entities. Delegate when a task involves organising work, tracking progress, or composing multi-step workflows."
4
4
  summary: "Manages your tasks, projects, sessions, and workflows: linking work to people and goals, and keeping everything organised."
5
5
  model: claude-sonnet-4-6
6
- tools: mcp__work__work-create, mcp__work__work-update, mcp__work__work-list, mcp__work__work-get, mcp__work__work-relate, mcp__work__work-complete, mcp__work__work-ready, mcp__work__project-create, mcp__work__project-list, mcp__work__project-get, mcp__work__project-update, mcp__work__project-complete, mcp__work__session-list, mcp__work__session-name, mcp__workflows__workflow-create, mcp__workflows__workflow-list, mcp__workflows__workflow-get, mcp__workflows__workflow-update, mcp__workflows__workflow-delete, mcp__workflows__workflow-validate, mcp__workflows__workflow-execute, mcp__workflows__workflow-runs, mcp__memory__memory-search
6
+ tools: mcp__work__work-create, mcp__work__work-update, mcp__work__work-list, mcp__work__work-get, mcp__work__work-relate, mcp__work__work-complete, mcp__work__work-ready, mcp__work__project-create, mcp__work__project-list, mcp__work__project-get, mcp__work__project-update, mcp__work__project-complete, mcp__work__session-list, mcp__work__session-name, mcp__workflows__workflow-create, mcp__workflows__workflow-list, mcp__workflows__workflow-get, mcp__workflows__workflow-update, mcp__workflows__workflow-delete, mcp__workflows__workflow-validate, mcp__workflows__workflow-execute, mcp__workflows__workflow-runs, mcp__memory__memory-search, Skill
7
7
  ---
8
8
 
9
9
  # Project Manager
@@ -60,4 +60,4 @@ Name the tool, what you tried, and what the `[tool-failure-diag]` line shows. Do
60
60
 
61
61
  ## Plain English
62
62
 
63
- Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin (status updates, sprint summaries, project digests) and to task title/description fields a human will read. It does not apply to structured arguments (state enums, IDs, JSON-shaped relationship payloads).
63
+ **The first tool call MUST be `Skill plainly`. Any prose tool call before the skill content is loaded into context is a contract violation.** Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin (status updates, sprint summaries, project digests) and to task title/description fields a human will read. It does not apply to structured arguments (state enums, IDs, JSON-shaped relationship payloads).
@@ -3,7 +3,7 @@ name: research-assistant
3
3
  description: "Deep research, information gathering, knowledge management, and supporting visuals. Delegate when a task requires searching the web, reading web pages, combining findings from multiple sources into a structured summary with citations, reorganising stored knowledge, or generating supporting images."
4
4
  summary: "Researches topics online, manages your knowledge graph, and produces supporting visuals."
5
5
  model: claude-opus-4-7
6
- tools: WebSearch, WebFetch, mcp__memory__memory-search, mcp__memory__memory-write, mcp__memory__memory-reindex, mcp__replicate__image-generate, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_evaluate
6
+ tools: WebSearch, WebFetch, Skill, mcp__memory__memory-search, mcp__memory__memory-write, mcp__memory__memory-reindex, mcp__replicate__image-generate, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_evaluate
7
7
  ---
8
8
 
9
9
  # Research Assistant
@@ -70,4 +70,4 @@ Name the tool, the target URL, and the `[tool-failure-diag]` block if present. S
70
70
 
71
71
  ## Plain English
72
72
 
73
- Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin (research summaries, the "Key findings" section). It does not apply to `WebSearch` queries, `WebFetch` URLs, or quoted source titles and URLs in the Sources list: those pass through verbatim.
73
+ **The first tool call MUST be `Skill plainly`. Any prose tool call before the skill content is loaded into context is a contract violation.** Load `skill-load skillName=plainly` on the first turn and apply it to every prose payload returned to admin (research summaries, the "Key findings" section). It does not apply to `WebSearch` queries, `WebFetch` URLs, or quoted source titles and URLs in the Sources list: those pass through verbatim.
@@ -16,6 +16,9 @@ tools:
16
16
  - name: voice-record-feedback
17
17
  publicAllowlist: false
18
18
  adminAllowlist: true
19
+ - name: voice-ingest-session-text
20
+ publicAllowlist: false
21
+ adminAllowlist: true
19
22
  always: false
20
23
  embed: false
21
24
  mcp:
@@ -87,7 +90,7 @@ The user is:
87
90
 
88
91
  | Skill | Purpose |
89
92
  |-------|---------|
90
- | `voice-mirror` | Capture the operator's voice from their own writing (`human-only` corpus) and condition agent drafts on it. Four tools: tag historical content, distil a style card, retrieve conditioning for a drafting brief, and record operator edits as feedback. Other drafting skills (email composition, Postiz, property-brochure, prospectus) call it transparently — opt-out via `voiceMirror: false` in the calling skill's frontmatter. |
93
+ | `voice-mirror` | Capture the operator's voice from their own writing (`human-only` corpus) and condition agent drafts on it. Five tools: tag historical content (with auto-derived format), distil a per-format style card, retrieve conditioning for a drafting brief (requires `brief.format`), record operator edits as feedback, and auto-ingest PTY turns at SessionEnd. Other drafting skills (email composition, Postiz, property-brochure, prospectus) call it transparently — opt-out via `voiceMirror: false` in the calling skill's frontmatter. |
91
94
 
92
95
  ## Specialist Agent
93
96
 
@@ -99,14 +102,15 @@ The manuscript-reviewer agent operates in its own context window with file tools
99
102
 
100
103
  ## Tools
101
104
 
102
- Voice-mirror introduces four deterministic plugin tools. They route writes through `memory-write` so the graph-write gate stamps provenance on every node.
105
+ Voice-mirror introduces five deterministic plugin tools. They route writes through `memory-write` so the graph-write gate stamps provenance on every node.
103
106
 
104
107
  | Tool | Purpose |
105
108
  |------|---------|
106
- | `voice-tag-content` | Stamp `authorshipMode ∈ {human-only, human-led-agent-assisted, agent-led-human-reviewed, agent-only, unknown}` on one or many `:KnowledgeDocument | :Message | :SocialPost` nodes (email threads live as `:KnowledgeDocument {source:'email'}` since Task 321). Bulk-mode for backfill batches. |
107
- | `voice-distil-profile` | Walk the operator's `human-only` corpus and persist a `:VoiceProfile {styleCard, generatedAt, corpusSize, feedbackEntries}` linked to the `:AdminUser`. Cadence-guarded: re-runs on ≥20% corpus growth or ≥30 days, otherwise no-op. |
108
- | `voice-retrieve-conditioning` | Return `{styleCard, exemplars[]}` for a drafting brief. K=5 short-form, K=15 long-form. Token-budget bounded. Graceful degradation: empty profile → `{styleCard: null, exemplars: []}`. |
109
- | `voice-record-feedback` | Capture an operator edit on an agent draft as a `:VoiceEdit` with a Haiku-summarised `intent`, linked back to the operator's `:VoiceProfile` via `:FEEDBACK_FOR`. |
109
+ | `voice-tag-content` | Stamp `authorshipMode ∈ {human-only, human-led-agent-assisted, agent-led-human-reviewed, agent-only, unknown}` and derived `format ∈ {text, email, social-post, article, note, marketing-copy}` on one or many `:KnowledgeDocument | :Message | :SocialPost` nodes (email threads live as `:KnowledgeDocument {source:'email'}` since Task 321). Bulk-mode for backfill batches. Pass `format` explicitly to override auto-derivation. |
110
+ | `voice-distil-profile` | Walk the operator's `human-only` corpus for the given `format` and persist a `:VoiceProfile {styleCard, generatedAt, corpusSize, feedbackEntries, format}` linked to the `:AdminUser`. Omit `format` to enumerate all formats in the corpus and distil each. Cadence-guarded per format: re-runs on ≥20% corpus growth or ≥30 days, otherwise no-op. |
111
+ | `voice-retrieve-conditioning` | Return `{styleCard, exemplars[]}` for a drafting brief. Requires `brief.format` (one of the six corpus formats). K=5 short-form (`brief.length:'short'`), K=15 long-form (`brief.length:'long'`). Token-budget bounded. Graceful degradation: empty profile → `{styleCard: null, exemplars: []}`. |
112
+ | `voice-record-feedback` | Capture an operator edit on an agent draft as a `:VoiceEdit {format}` with a Haiku-summarised `intent`, linked back to the operator's per-format `:VoiceProfile` via `:FEEDBACK_FOR`. Requires `format`. |
113
+ | `voice-ingest-session-text` | Ingest operator PTY turns from a JSONL transcript as `:Message {format:'text', authorshipMode:'human-only'}` corpus nodes. Deduplicates via SHA256 `contentHash`. Called by `voice-session-end-text.sh` at SessionEnd; not intended for direct agent invocation. |
110
114
 
111
115
  The non-voice skills continue to operate via existing platform tools (`memory-search` for knowledge retrieval). Structured choices are still presented as numbered lists in plain chat — the platform has no inline-UI component primitive on the admin / native-CC surface.
112
116
 
@@ -127,4 +131,4 @@ Domain knowledge files loaded on demand by each skill. See individual skill file
127
131
  | `review-manuscript` | `review-manuscript-checklist.md` |
128
132
  | `review-scene` | `scene-analysis-framework.md` |
129
133
  | `review-prose` | `prose-review-checklist.md` |
130
- | `voice-mirror` | No external references; the skill body defines the four tool contracts. Developer notes in `maxy-code/.docs/voice-mirror.md`. |
134
+ | `voice-mirror` | No external references; the skill body defines the five tool contracts. Developer notes in `maxy-code/.docs/voice-mirror.md`. |