@rubytech/create-maxy-code 0.1.177 → 0.1.181

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 (62) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-trash/package.json +7 -0
  3. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +38 -3
  4. package/payload/platform/plugins/memory/mcp/dist/index.js +17 -7
  5. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  6. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.d.ts +8 -0
  7. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.d.ts.map +1 -0
  8. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.js +27 -0
  9. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.js.map +1 -0
  10. package/payload/platform/plugins/memory/mcp/dist/lib/section-types.d.ts +10 -6
  11. package/payload/platform/plugins/memory/mcp/dist/lib/section-types.d.ts.map +1 -1
  12. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js +13 -13
  13. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js.map +1 -1
  14. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.d.ts +2 -0
  15. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.d.ts.map +1 -0
  16. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.js +124 -0
  17. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.js.map +1 -0
  18. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.d.ts +2 -0
  19. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.d.ts.map +1 -0
  20. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js +104 -0
  21. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js.map +1 -0
  22. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts +12 -0
  23. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -1
  24. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +38 -13
  25. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -1
  26. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +23 -1
  27. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  28. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +175 -44
  29. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  30. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +7 -3
  31. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +9 -1
  32. package/payload/premium-plugins/writer-craft/PLUGIN.md +3 -3
  33. package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.d.ts +106 -0
  34. package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.d.ts.map +1 -0
  35. package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.js +340 -0
  36. package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.js.map +1 -0
  37. package/payload/premium-plugins/writer-craft/lib/graph-trash/package.json +7 -0
  38. package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
  39. package/payload/premium-plugins/writer-craft/mcp/dist/index.js +24 -10
  40. package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
  41. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts +15 -25
  42. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts.map +1 -1
  43. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js +17 -38
  44. package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js.map +1 -1
  45. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts +52 -12
  46. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
  47. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js +308 -2
  48. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js.map +1 -1
  49. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
  50. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js +26 -5
  51. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
  52. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts +2 -2
  53. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
  54. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js +14 -28
  55. package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js.map +1 -1
  56. package/payload/premium-plugins/writer-craft/mcp/scripts/smoke.mjs +21 -41
  57. package/payload/premium-plugins/writer-craft/mcp/src/index.ts +30 -12
  58. package/payload/premium-plugins/writer-craft/mcp/src/lib/voice-corpus.ts +18 -43
  59. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +419 -14
  60. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +27 -5
  61. package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-tag-content.ts +16 -31
  62. package/payload/premium-plugins/writer-craft/skills/voice-mirror/SKILL.md +37 -19
@@ -1,18 +1,16 @@
1
1
  /**
2
- * voice-tag-content — set `authorshipMode` (and optionally `format`) on one
3
- * or many content nodes.
2
+ * voice-tag-content — set `authorshipMode` and `format` on one or many
3
+ * content nodes.
4
4
  *
5
5
  * The tool routes through neo4j directly with an account-scoped MERGE so
6
6
  * the operator can only mutate their own nodes. We do not go through
7
7
  * `memory-update` because that tool re-embeds and re-validates on every
8
8
  * call — overkill for a single-property authorship stamp.
9
9
  *
10
- * Format stamping (Task 462):
11
- * - When `format` is provided: validated against FORMAT_VALUES and written
12
- * to `n.format` alongside `authorshipMode`.
13
- * - When `format` is omitted: a Cypher CASE expression derives format from
14
- * each node's labels and `source` property, matching the `deriveFormat()`
15
- * rule table in voice-corpus.ts. This keeps tag and ingest in lockstep.
10
+ * `format` is an editorial classification only the operator can set; it is
11
+ * therefore required. The sole hard-coded exception lives in
12
+ * `voice-ingest-session-text`, which stamps `format='text'` on auto-ingested
13
+ * `:Message` rows where the operator is not in the loop (Task 471).
16
14
  */
17
15
  import { getSession } from "../lib/neo4j.js";
18
16
  import {
@@ -40,8 +38,8 @@ export interface VoiceTagContentParams {
40
38
  nodeIds: string[];
41
39
  mode: AuthorshipMode;
42
40
  accountId: string;
43
- /** Optional format. When omitted, derived from each node's labels. */
44
- format?: VoiceFormat;
41
+ /** Writing format (required). Editorial classification operator-supplied. */
42
+ format: VoiceFormat;
45
43
  }
46
44
 
47
45
  export interface VoiceTagContentResult {
@@ -50,18 +48,6 @@ export interface VoiceTagContentResult {
50
48
  skippedReasons: Record<string, number>;
51
49
  }
52
50
 
53
- // Cypher CASE expression that mirrors deriveFormat() for in-query derivation.
54
- // Used when the caller does not supply an explicit format.
55
- const FORMAT_DERIVE_CASE = `CASE
56
- WHEN 'Message' IN labels(n) THEN 'text'
57
- WHEN 'SocialPost' IN labels(n) THEN 'social-post'
58
- WHEN ('KnowledgeDocument' IN labels(n) OR 'ConversationArchive' IN labels(n))
59
- AND n.source = 'email' THEN 'email'
60
- WHEN 'KnowledgeDocument' IN labels(n) AND size(coalesce(n.body, '')) > 1500 THEN 'article'
61
- WHEN 'KnowledgeDocument' IN labels(n) THEN 'note'
62
- ELSE 'text'
63
- END`;
64
-
65
51
  export async function voiceTagContent(
66
52
  params: VoiceTagContentParams,
67
53
  ): Promise<VoiceTagContentResult> {
@@ -77,7 +63,10 @@ export async function voiceTagContent(
77
63
  if (!accountId) {
78
64
  throw new Error("voice-tag-content: accountId is required");
79
65
  }
80
- if (format !== undefined && !(FORMAT_VALUES as readonly string[]).includes(format)) {
66
+ if (format === undefined || format === null) {
67
+ throw new Error("voice-tag-content: format is required");
68
+ }
69
+ if (!(FORMAT_VALUES as readonly string[]).includes(format)) {
81
70
  throw new Error(
82
71
  `voice-tag-content: invalid format '${format}'. Valid values: ${FORMAT_VALUES.join(", ")}`,
83
72
  );
@@ -97,16 +86,12 @@ export async function voiceTagContent(
97
86
  for (let i = 0; i < nodeIds.length; i += BATCH) {
98
87
  const batch = nodeIds.slice(i, i + BATCH);
99
88
  const result = await session.run(
100
- // Tag any of the four primary content labels, or a conversation-
89
+ // Tag any of the three primary content labels, or a conversation-
101
90
  // document :Section chunk (parent :KnowledgeDocument carries
102
91
  // `conversationIdentity`). The EXISTS sub-pattern guards against
103
92
  // accidentally tagging document-path :Section rows AND parent
104
93
  // `:Conversation` / `:AdminConversation` nodes (which are not
105
94
  // body-bearing content).
106
- //
107
- // Format is either the caller-supplied constant ($format is non-null)
108
- // or derived per-node via the CASE expression that mirrors
109
- // deriveFormat() in voice-corpus.ts.
110
95
  `UNWIND $ids AS id
111
96
  MATCH (n) WHERE elementId(n) = id AND n.accountId = $accountId
112
97
  WITH n
@@ -117,14 +102,14 @@ export async function voiceTagContent(
117
102
  })
118
103
  SET n.authorshipMode = $mode,
119
104
  n.authorshipTaggedAt = $now,
120
- n.format = CASE WHEN $format IS NOT NULL THEN $format ELSE ${FORMAT_DERIVE_CASE} END
105
+ n.format = $format
121
106
  RETURN elementId(n) AS id`,
122
107
  {
123
108
  ids: batch,
124
109
  accountId,
125
110
  mode,
126
111
  now,
127
- format: format ?? null,
112
+ format,
128
113
  primaryLabels: [...TAGGABLE_PRIMARY_LABELS],
129
114
  },
130
115
  );
@@ -141,7 +126,7 @@ export async function voiceTagContent(
141
126
  }
142
127
 
143
128
  process.stderr.write(
144
- `[voice-tag] mode=${mode} format=${format ?? "derived"} accountId=${accountId} updated=${updated} skipped=${
129
+ `[voice-tag] mode=${mode} format=${format} accountId=${accountId} updated=${updated} skipped=${
145
130
  nodeIds.length - updated
146
131
  }\n`,
147
132
  );
@@ -11,7 +11,7 @@ The skill is built on five deterministic MCP tools served by the `writer-craft`
11
11
 
12
12
  | Tool | Purpose |
13
13
  |------|---------|
14
- | `voice-tag-content` | Stamp historical or new content nodes with `authorshipMode` and derived `format`. Bulk-tags arrays of node ids. |
14
+ | `voice-tag-content` | Stamp historical or new content nodes with `authorshipMode` and operator-supplied `format`. Bulk-tags arrays of node ids. |
15
15
  | `voice-distil-profile` | Walk the operator's `human-only` corpus for a given `format` and produce a `:VoiceProfile {styleCard, generatedAt, corpusSize, feedbackEntries, format}` node. Without a `format`, enumerates all formats present in the corpus and distils each. |
16
16
  | `voice-retrieve-conditioning` | Return `{styleCard, exemplars[]}` for a drafting brief. Requires `brief.format` (one of the six corpus formats). K=5 for short-form, K=15 for long-form. Token-budget bounded. |
17
17
  | `voice-record-feedback` | When the operator edits an agent draft, write a `:VoiceEdit {originalText, editedText, intent, occurredAt, format}-[:FEEDBACK_FOR]->(:VoiceProfile {format})` node. The `intent` field is a Haiku-summarised diff. |
@@ -37,16 +37,16 @@ Do **not** activate for:
37
37
 
38
38
  Voice profiles are segmented by content format. The closed enum is:
39
39
 
40
- | Format | Derived from |
41
- |--------|-------------|
42
- | `text` | `:Message` nodes, operator PTY turns (auto-ingested via SessionEnd hook) |
43
- | `email` | `:KnowledgeDocument {source:'email'}`, `:ConversationArchive {source:'email'}` |
40
+ | Format | Operator picks for |
41
+ |--------|--------------------|
42
+ | `text` | `:Message` nodes; the SessionEnd hook stamps this automatically on operator PTY turns (the sole hard-coded path chat turns are unambiguously text and the operator is not in the loop on session end) |
43
+ | `email` | Email threads stored as `:KnowledgeDocument {source:'email'}` or `:ConversationArchive {source:'email'}` |
44
44
  | `social-post` | `:SocialPost` nodes |
45
- | `article` | `:KnowledgeDocument` with body > 1,500 chars |
46
- | `note` | `:KnowledgeDocument` with body 1,500 chars |
47
- | `marketing-copy` | No automatic derivation path tag explicitly via `voice-tag-content` |
45
+ | `article` | Long-form `:KnowledgeDocument` the operator considers an article |
46
+ | `note` | Short-form `:KnowledgeDocument` the operator considers a note |
47
+ | `marketing-copy` | Any node the operator considers marketing copy |
48
48
 
49
- Format is derived automatically when tagging via `voice-tag-content`. Pass `format` explicitly to override the derivation.
49
+ Format is editorial the operator picks it per node when calling `voice-tag-content`. The tool does not infer it.
50
50
 
51
51
  ## Flow — backfill review (one-shot session)
52
52
 
@@ -63,19 +63,16 @@ Ask the operator which stream to backfill first. Default to chat archives when t
63
63
 
64
64
  ### 2a. Discrete documents — paginate in batches of 10
65
65
 
66
- Query for `:KnowledgeDocument | :SocialPost | :Message` nodes belonging to this operator's account whose `authorshipMode` is `unknown`. Order by `dateSent | datePublished | dateCreated` descending — most recent first. For each batch, surface a numbered list with: source label, date, recipient or topic, and a one-line preview (first ~120 chars of body). Offer bulk shortcuts:
66
+ Query for `:KnowledgeDocument | :SocialPost | :Message` nodes belonging to this operator's account whose `authorshipMode` is `unknown`. Order by `dateSent | datePublished | dateCreated` descending — most recent first. For each batch, surface a numbered list with: source label, date, recipient or topic, and a one-line preview (first ~120 chars of body). Operator commands name both mode and format:
67
67
 
68
- - `1-10 human-only` — tag every item in the batch as `human-only` with auto-derived format.
69
- - `1-10 human-only article` — tag the whole batch as `human-only` and force format to `article`.
70
- - `1,3,7 human-only` — tag a subset.
71
- - `2 human-led-agent-assisted` — single-item mode override.
72
- - `5 agent-led-human-reviewed marketing-copy` — single-item mode and format override.
68
+ - `1-10 human-only article` — tag every item in the batch as `human-only` with format `article`.
69
+ - `1,3,7 human-only email` — tag a subset with mode + format.
70
+ - `2 human-led-agent-assisted note` — single-item.
71
+ - `5 agent-led-human-reviewed marketing-copy` — single-item.
73
72
  - `skip` — leave the batch as `unknown` and advance.
74
73
  - `stop` — exit the backfill; the next session resumes from where the operator left off.
75
74
 
76
- The tool derives `format` automatically from node labels and properties. Show the derived format alongside each item's preview so the operator can spot misclassifications. Append a format token to any batch or single-item command to override the derivation.
77
-
78
- Bulk-tag via `voice-tag-content` once per operator response. Emit `[voice-tag] mode=<mode> format=<format> count=<n>` per write.
75
+ `format` is required on every command; the tool will not infer it. If the operator omits the format, ask before tagging. Bulk-tag via `voice-tag-content` once per operator response. Emit `[voice-tag] mode=<mode> format=<format> count=<n>` per write.
79
76
 
80
77
  ### 2b. Chat archives — paginate per conversation-document parent
81
78
 
@@ -86,7 +83,7 @@ For each conversation-document `:KnowledgeDocument` (with `conversationIdentity`
86
83
  - `not me` — tag every chunk as `agent-only` (e.g. a Slack channel where the operator only forwarded other people's messages).
87
84
  - `skip` / `stop` — same semantics as 2a.
88
85
 
89
- Bulk-tag via `voice-tag-content` with the list of `:Section` element IDs the conversation-document contains. The skill emits `[voice-tag] mode=<mode> format=<format> count=<chunk-count>` once per conversation.
86
+ Bulk-tag via `voice-tag-content` with the list of `:Section` element IDs the conversation-document contains, passing `format='text'` (chat archives are unambiguously text-format). The skill emits `[voice-tag] mode=<mode> format=<format> count=<chunk-count>` once per conversation.
90
87
 
91
88
  Authorship at conversation granularity is a deliberate UX trade-off: chunks within a conversation almost always share the same operator's voice, and per-chunk tagging at archive scale would burn the operator's attention. The `mixed` opt-in covers the rare case where it matters.
92
89
 
@@ -156,6 +153,25 @@ A re-run within the 30-day window with <20% growth is a no-op — the tool retur
156
153
 
157
154
  The triggering surface is the skill, not a scheduled job. Distillation runs on demand: at the end of a backfill batch, when the email composer notices stale-profile narration, or when the operator says "rebuild my voice profile".
158
155
 
156
+ ### Amend mode (single-document update)
157
+
158
+ Activate when the operator names one or more specific documents and wants them folded into their voice profile right now — e.g. **"I just published this new article, take that into account"**, **"that thread to Sarah is a good example of how I write — fold it in"**, or **"add this essay to my voice profile"**. The full-corpus distil cadence is wrong for "this specific document, now" — the operator's signal is the document choice, not the cadence guard.
159
+
160
+ Call `voice-distil-profile` with:
161
+
162
+ - `mode: 'amend'`
163
+ - `format: <VoiceFormat>` — required; the operator's per-format profile
164
+ - `nodeIds: [<elementId>, ...]` — the documents the operator picked (one or more)
165
+
166
+ The tool returns `{existingStyleCard, exemplars[], feedbackIntents[], eligible, ineligible}`. Compare the named exemplars against the prior card and the recent edit intents, then choose:
167
+
168
+ - **Material delta** — compose an updated YAML and call `mode: 'write'` with `styleCardYaml` and `amendedFromNodeIds: [<elementId>, ...]` listing the documents that actually moved the card. The write bypasses the cadence guard and adds `LEARNED_FROM` edges to those documents (pre-existing edges remain).
169
+ - **No material delta** — reply to the operator in-turn ("nothing in that article shifts your voice card; the prior profile still applies"). Do not call `mode: 'write'`. `generatedAt` only advances on a write.
170
+
171
+ When `eligible=0` (every named document failed the predicate) surface the `ineligible[]` payload to the operator naming the per-id reason (`node-not-found`, `wrong-account`, `ineligible-authorship-mode`, `trashed`, `ineligible-label`) rather than silently no-op. Most commonly this means the documents are tagged as `agent-only` and need re-tagging via `voice-tag-content` before they can amend a profile.
172
+
173
+ Amend mode does not apply to the SessionEnd auto-ingest path. Session-text ingest stays on the full-corpus path; amend is operator-initiated only.
174
+
159
175
  ## Per-operator scope
160
176
 
161
177
  One `:VoiceProfile` per `(accountId, userId, format)`. Multi-author accounts (an agency with several operators) get one profile per person per format. Identity is the `(accountId, userId, format)` triple, enforced by the schema constraint.
@@ -167,6 +183,8 @@ One `:VoiceProfile` per `(accountId, userId, format)`. Multi-author accounts (an
167
183
  | `[voice-tag] mode=<mode> format=<format> nodeId=<id> count=<n>` | Every tag write. |
168
184
  | `[voice-distil] userId=<id> format=<format> corpusSize=<n> generatedAt=<iso> feedbackEntries=<n>` | Each distillation. |
169
185
  | `[voice-distil] skip reason=<below-threshold\|recent> format=<format>` | When the cadence guard fires. |
186
+ | `[voice-distil] mode=amend userId=<id> format=<format> nodeIds=<count> existingProfile=<elementId\|none> eligible=<n> ineligible=<n> exemplars=<n>` | Each amend-mode call. `eligible=0` means surface `ineligible[]` to the operator, never silently no-op. |
187
+ | `[voice-distil] mode=write userId=<id> format=<format> amended=true amendedFromNodeIds=<count> ...` | Amend-write fired (operator-initiated update persisted). |
170
188
  | `[voice-retrieve] task=<topic> format=<format> styleCardBytes=<n> exemplarCount=<k> tokenBudget=<n>` | Each retrieval. |
171
189
  | `[voice-record-feedback] userId=<id> format=<format> intent="<haiku-summary>" diffBytes=<n>` | Each feedback write. |
172
190
  | `[voice-session-end-hook] trigger sessionId=<id>` | Hook fires for an eligible session. |