@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.
- package/package.json +1 -1
- package/payload/platform/lib/graph-trash/package.json +7 -0
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +38 -3
- package/payload/platform/plugins/memory/mcp/dist/index.js +17 -7
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.d.ts +8 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/turn-text-cache-path.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/section-types.d.ts +10 -6
- package/payload/platform/plugins/memory/mcp/dist/lib/section-types.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js +13 -13
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.js +124 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-chat-body-server-sliced.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js +104 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-section-properties-strip.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts +12 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +38 -13
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +23 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +175 -44
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +7 -3
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +9 -1
- package/payload/premium-plugins/writer-craft/PLUGIN.md +3 -3
- package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.d.ts +106 -0
- package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.d.ts.map +1 -0
- package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.js +340 -0
- package/payload/premium-plugins/writer-craft/lib/graph-trash/dist/index.js.map +1 -0
- package/payload/premium-plugins/writer-craft/lib/graph-trash/package.json +7 -0
- package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js +24 -10
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts +15 -25
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js +17 -38
- package/payload/premium-plugins/writer-craft/mcp/dist/lib/voice-corpus.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts +52 -12
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js +308 -2
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js +26 -5
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts +2 -2
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js +14 -28
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-tag-content.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/scripts/smoke.mjs +21 -41
- package/payload/premium-plugins/writer-craft/mcp/src/index.ts +30 -12
- package/payload/premium-plugins/writer-craft/mcp/src/lib/voice-corpus.ts +18 -43
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +419 -14
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-retrieve-conditioning.ts +27 -5
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-tag-content.ts +16 -31
- 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`
|
|
3
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
/**
|
|
44
|
-
format
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 |
|
|
41
|
-
|
|
42
|
-
| `text` | `:Message` nodes
|
|
43
|
-
| `email` | `:KnowledgeDocument {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`
|
|
46
|
-
| `note` | `:KnowledgeDocument`
|
|
47
|
-
| `marketing-copy` |
|
|
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
|
|
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).
|
|
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
|
|
69
|
-
- `1
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
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
|
-
|
|
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. |
|