@creator-notes/cnotes 0.33.0 → 0.35.0

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.
@@ -83,6 +83,181 @@ export function lintMentionMarkdown(markdown, allowAtKey) {
83
83
  }
84
84
  return issues;
85
85
  }
86
+ /** Parse a wiki-link body (`[[inner]]`): target + optional type, either order
87
+ * (`[[blocks::DEC-3]]` and `[[DEC-3::blocks]]` both bind — mirror of
88
+ * lib/tiptap/markdown-extensions.ts). Returns null for ordinary wiki prose. */
89
+ function parseWikiInner(inner) {
90
+ const noAlias = inner.split("|")[0].trim();
91
+ const parts = noAlias.split("::").map((s) => s.trim());
92
+ const isTarget = (s) => DISPLAY_ID_RE.test(s) || AT_KEY_RE.test(s);
93
+ if (parts.length === 1) {
94
+ return isTarget(parts[0]) ? { target: parts[0], type: null } : null;
95
+ }
96
+ if (parts.length === 2) {
97
+ if (isTarget(parts[1]))
98
+ return { target: parts[1], type: parts[0].toLowerCase() || null };
99
+ if (isTarget(parts[0]))
100
+ return { target: parts[0], type: parts[1].toLowerCase() || null };
101
+ }
102
+ return null;
103
+ }
104
+ const WIKI_RE = /\[\[([^\]]+)\]\]/g;
105
+ /** Collect every mention on one line, both `[T](relationship:t)` and `[[T]]`. */
106
+ function findMentions(line) {
107
+ const found = [];
108
+ for (const m of line.matchAll(MENTION_RE)) {
109
+ const linkText = m[1];
110
+ found.push({
111
+ raw: m[0],
112
+ index: m.index ?? 0,
113
+ effectiveType: m[2].trim().toLowerCase(),
114
+ retype: (t) => `[${linkText}](relationship:${t})`,
115
+ });
116
+ }
117
+ for (const m of line.matchAll(WIKI_RE)) {
118
+ const parsed = parseWikiInner(m[1]);
119
+ if (!parsed)
120
+ continue;
121
+ found.push({
122
+ raw: m[0],
123
+ index: m.index ?? 0,
124
+ effectiveType: parsed.type ?? "references",
125
+ retype: (t) => `[[${t}::${parsed.target}]]`,
126
+ });
127
+ }
128
+ return found.sort((a, b) => a.index - b.index);
129
+ }
130
+ /** Prose forms for each type: the label and the de-hyphenated slug. The
131
+ * `references` type is excluded — it's what generic mentions already are. */
132
+ function buildTypePhrases(types) {
133
+ const phrases = [];
134
+ const seen = new Set();
135
+ for (const t of types) {
136
+ const value = t.value.trim().toLowerCase();
137
+ if (!value || value === "references")
138
+ continue;
139
+ for (const phrase of new Set([
140
+ t.label.trim().toLowerCase(),
141
+ value.replace(/-/g, " "),
142
+ ])) {
143
+ if (!phrase || seen.has(phrase))
144
+ continue;
145
+ seen.add(phrase);
146
+ phrases.push({ phrase, value, label: t.label });
147
+ }
148
+ }
149
+ // Longest first so "depends on" wins over a hypothetical "depends".
150
+ return phrases.sort((a, b) => b.phrase.length - a.phrase.length);
151
+ }
152
+ /** Strip list/quote/heading markers and opening emphasis from a line start. */
153
+ function stripLineDecoration(line) {
154
+ return line
155
+ .replace(/^[\s>]*/, "")
156
+ .replace(/^(?:[-*+]\s+|\d+[.)]\s+|#{1,6}\s+)/, "")
157
+ .replace(/^[*_]{1,3}/, "");
158
+ }
159
+ /**
160
+ * Lint one markdown body for relationship verbs narrated in prose next to a
161
+ * GENERIC mention (`relationship:references` or untyped `[[NOTE-12]]`).
162
+ * Fires when a line STARTS with a type's verb, or when the verb sits
163
+ * immediately before the mention. Mentions that already carry a specific type
164
+ * are never flagged, even if the prose disagrees — that's a judgment call, not
165
+ * a lint. Fenced code blocks are skipped.
166
+ *
167
+ * @param types workspace relationship types (fetch via
168
+ * `fetchRelationshipTypeOptions`); pass [] to disable.
169
+ */
170
+ export function lintNarratedVerbMentions(markdown, types) {
171
+ const typePhrases = buildTypePhrases(types);
172
+ if (typePhrases.length === 0)
173
+ return [];
174
+ const warnings = [];
175
+ let inFence = false;
176
+ markdown.split(/\r?\n/).forEach((line, i) => {
177
+ if (/^\s*(```|~~~)/.test(line)) {
178
+ inFence = !inFence;
179
+ return;
180
+ }
181
+ if (inFence)
182
+ return;
183
+ const generics = findMentions(line).filter((m) => m.effectiveType === "references");
184
+ if (generics.length === 0)
185
+ return;
186
+ // Rule B: verb immediately before a generic mention ("this blocks [[X]]").
187
+ const claimed = new Set();
188
+ for (const mention of generics) {
189
+ const before = line.slice(0, mention.index).trimEnd().toLowerCase();
190
+ for (const p of typePhrases) {
191
+ if (!before.endsWith(p.phrase))
192
+ continue;
193
+ const pre = before.slice(0, before.length - p.phrase.length);
194
+ if (pre !== "" && !/[^a-z0-9]$/.test(pre))
195
+ continue;
196
+ const start = line.toLowerCase().lastIndexOf(p.phrase, mention.index);
197
+ warnings.push(makeWarning(i + 1, line.slice(start, start + p.phrase.length), p, mention));
198
+ claimed.add(mention);
199
+ break;
200
+ }
201
+ }
202
+ // Rule A: line leads with a verb, generic mention anywhere after it
203
+ // ("Blocks clean filmstrip of [[X]]."). First unclaimed mention gets it.
204
+ const stripped = stripLineDecoration(line);
205
+ const strippedLower = stripped.toLowerCase();
206
+ const target = generics.find((m) => !claimed.has(m));
207
+ if (!target)
208
+ return;
209
+ for (const p of typePhrases) {
210
+ if (!strippedLower.startsWith(p.phrase))
211
+ continue;
212
+ if (!/^[\s:,.!*_—-]/.test(strippedLower.slice(p.phrase.length)))
213
+ continue;
214
+ warnings.push(makeWarning(i + 1, stripped.slice(0, p.phrase.length), p, target));
215
+ break;
216
+ }
217
+ });
218
+ return warnings;
219
+ }
220
+ function makeWarning(line, verb, p, mention) {
221
+ const rewrite = mention.retype(p.value);
222
+ return {
223
+ line,
224
+ verb,
225
+ suggestedType: p.value,
226
+ mention: mention.raw,
227
+ rewrite,
228
+ message: `prose "${verb}" narrates a relationship the graph can't see — ` +
229
+ `${mention.raw} files a plain reference. If the claim is real, retype ` +
230
+ `the mention: ${rewrite} (workspace type "${p.label}") and drop the ` +
231
+ `now-redundant verb. If "${verb}" isn't asserting a relationship here, ` +
232
+ `ignore this warning.`,
233
+ };
234
+ }
235
+ /** Cheap gate: does this body contain any generic mention at all? Used to
236
+ * skip the relationship-types fetch when no warning could possibly fire. */
237
+ export function hasGenericMention(markdown) {
238
+ for (const m of markdown.matchAll(MENTION_RE)) {
239
+ if (m[2].trim().toLowerCase() === "references")
240
+ return true;
241
+ }
242
+ for (const m of markdown.matchAll(WIKI_RE)) {
243
+ const parsed = parseWikiInner(m[1]);
244
+ if (parsed && parsed.type === null)
245
+ return true;
246
+ }
247
+ return false;
248
+ }
249
+ /** Fetch the workspace's relationship types for the narrated-verb lint.
250
+ * Best-effort: returns null on any failure — the lint is advisory and must
251
+ * never fail (or slow-fail) a write. */
252
+ export async function fetchRelationshipTypeOptions(api, workspaceId) {
253
+ try {
254
+ const data = await api.get("/api/relationship-types", { workspaceId });
255
+ return data.relationshipTypes ?? [];
256
+ }
257
+ catch {
258
+ return null;
259
+ }
260
+ }
86
261
  /**
87
262
  * Format the server's `unresolvedMentions` into an actionable block. Returns
88
263
  * null when there's nothing to report.
@@ -1 +1 @@
1
- {"version":3,"file":"mention-lint.js","sourceRoot":"","sources":["../../src/lib/mention-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;4EAE4E;AAC5E,MAAM,aAAa,GAAG,uCAAuC,CAAC;AAC9D,sEAAsE;AACtE,MAAM,SAAS,GAAG,mBAAmB,CAAC;AACtC,6DAA6D;AAC7D,MAAM,UAAU,GAAG,uCAAuC,CAAC;AAa3D,sFAAsF;AACtF,SAAS,QAAQ,CAAC,QAAgB;IAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACrE,CAAC;AAED,SAAS,aAAa,CAAC,MAAc,EAAE,UAAmB;IACxD,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,UAAU,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,UAAmB;IAEnB,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACjB,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAClC,IAAI,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC;YAAE,SAAS;QAEhD,IAAI,OAAe,CAAC;QACpB,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YAChD,6DAA6D;YAC7D,OAAO;gBACL,cAAc,MAAM,iDAAiD;oBACrE,oDAAoD;oBACpD,sEAAsE;oBACtE,SAAS,IAAI,uCAAuC,IAAI,KAAK,CAAC;QAClE,CAAC;aAAM,IAAI,UAAU,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1E,OAAO;gBACL,cAAc,MAAM,sDAAsD;oBAC1E,qEAAqE,IAAI,IAAI;oBAC7E,qCAAqC,IAAI,IAAI,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,OAAO;gBACL,cAAc,MAAM,4CAA4C;oBAChE,qBAAqB,UAAU,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC,EAAE,KAAK;oBACpF,+DAA+D,IAAI,IAAI,CAAC;QAC5E,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAQD;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,UAAmB;IAEnB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvE,MAAM,KAAK,GAAI,UAAkC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC1D,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,IAAI,WAAW,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,gBAAgB,IAAI,YAAY,CAAC;QAChD,MAAM,aAAa,GACjB,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;YAC/B,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,aAAa;YACvB,CAAC,CAAC,qCAAqC,IAAI,kEAAkE;YAC7G,CAAC,CAAC,YAAY,MAAM,oEAAoE,CAAC;QAC3F,OAAO,QAAQ,MAAM,kBAAkB,IAAI,MAAM,GAAG,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"mention-lint.js","sourceRoot":"","sources":["../../src/lib/mention-lint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Type-rubric helpers for the authoring loop.
3
+ *
4
+ * Rubrics (`validationPrompt` on each supertag) are the quality standard for
5
+ * a note type. Agents habitually filter create responses down to
6
+ * `.results[].displayId`, so a sibling top-level `rubrics` array is invisible
7
+ * unless the agent already knows to look. These helpers:
8
+ *
9
+ * 1. Load the workspace's type → rubric map (non-fatal on failure).
10
+ * 2. Attach `type` + `rubric` onto each create result item so a `.results[]`
11
+ * extraction carries the standard along with the displayId.
12
+ * 3. Build a compact `{ type, rubric }[]` list for pre-write surfaces
13
+ * (`operations begin`), so the text is in context before authoring starts.
14
+ */
15
+ import type { ApiClient } from "./api-client.js";
16
+ export type TypeRubric = {
17
+ type: string;
18
+ /** Null when the type exists but has no validationPrompt. */
19
+ rubric: string | null;
20
+ };
21
+ /** Case-insensitive map of type display name → validationPrompt text. */
22
+ export type RubricMap = Map<string, string>;
23
+ /**
24
+ * Fetch every workspace type that has a validationPrompt.
25
+ * Returns an empty map (never throws) when the request fails — rubric echo
26
+ * must never fail a successful create / begin.
27
+ */
28
+ export declare function loadRubricMap(api: ApiClient, workspaceId: string): Promise<RubricMap>;
29
+ /**
30
+ * Compact list of `{ type, rubric }` for the distinct types in `typeNames`
31
+ * that have a validationPrompt. Preserves first-seen casing from `typeNames`.
32
+ */
33
+ export declare function rubricsForTypes(typeNames: string[], map: RubricMap): Array<{
34
+ type: string;
35
+ rubric: string;
36
+ }>;
37
+ /**
38
+ * Every type in the workspace that has a validationPrompt, for pre-write
39
+ * surfaces (`operations begin`). Sorted by type name for stable output.
40
+ */
41
+ export declare function allWorkspaceRubrics(map: RubricMap): Array<{
42
+ type: string;
43
+ rubric: string;
44
+ }>;
45
+ type CreateResult = Record<string, unknown> & {
46
+ key?: string;
47
+ displayId?: string;
48
+ noteId?: string;
49
+ title?: string;
50
+ };
51
+ /**
52
+ * Attach `type` + `rubric` onto each create result.
53
+ *
54
+ * `typeByKey` maps the batch item key → type string from the create request.
55
+ * Types without a validationPrompt get `rubric: null` so the gap is visible
56
+ * (several default types ship without one).
57
+ */
58
+ export declare function attachRubricsToResults<T extends CreateResult>(results: T[], typeByKey: Map<string, string>, map: RubricMap): Array<T & TypeRubric>;
59
+ export {};
60
+ //# sourceMappingURL=rubrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rubrics.d.ts","sourceRoot":"","sources":["../../src/lib/rubrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5C;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAiBpB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EAAE,EACnB,GAAG,EAAE,SAAS,GACb,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAWzC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAI3F;AAED,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,YAAY,EAC3D,OAAO,EAAE,CAAC,EAAE,EACZ,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,GAAG,EAAE,SAAS,GACb,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC,CAMvB"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Fetch every workspace type that has a validationPrompt.
3
+ * Returns an empty map (never throws) when the request fails — rubric echo
4
+ * must never fail a successful create / begin.
5
+ */
6
+ export async function loadRubricMap(api, workspaceId) {
7
+ try {
8
+ const supertags = await api.get("/api/supertags", {
9
+ workspaceId,
10
+ });
11
+ const byName = new Map();
12
+ for (const s of Array.isArray(supertags) ? supertags : []) {
13
+ const nm = String(s.name || "").toLowerCase();
14
+ const prompt = s.validationPrompt;
15
+ if (nm && typeof prompt === "string" && prompt.trim()) {
16
+ byName.set(nm, prompt);
17
+ }
18
+ }
19
+ return byName;
20
+ }
21
+ catch {
22
+ return new Map();
23
+ }
24
+ }
25
+ /**
26
+ * Compact list of `{ type, rubric }` for the distinct types in `typeNames`
27
+ * that have a validationPrompt. Preserves first-seen casing from `typeNames`.
28
+ */
29
+ export function rubricsForTypes(typeNames, map) {
30
+ const seen = new Set();
31
+ const out = [];
32
+ for (const raw of typeNames) {
33
+ const key = raw.toLowerCase();
34
+ if (seen.has(key))
35
+ continue;
36
+ seen.add(key);
37
+ const rubric = map.get(key);
38
+ if (rubric)
39
+ out.push({ type: raw, rubric });
40
+ }
41
+ return out;
42
+ }
43
+ /**
44
+ * Every type in the workspace that has a validationPrompt, for pre-write
45
+ * surfaces (`operations begin`). Sorted by type name for stable output.
46
+ */
47
+ export function allWorkspaceRubrics(map) {
48
+ return Array.from(map.entries())
49
+ .map(([type, rubric]) => ({ type, rubric }))
50
+ .sort((a, b) => a.type.localeCompare(b.type));
51
+ }
52
+ /**
53
+ * Attach `type` + `rubric` onto each create result.
54
+ *
55
+ * `typeByKey` maps the batch item key → type string from the create request.
56
+ * Types without a validationPrompt get `rubric: null` so the gap is visible
57
+ * (several default types ship without one).
58
+ */
59
+ export function attachRubricsToResults(results, typeByKey, map) {
60
+ return results.map((r) => {
61
+ const type = (r.key ? typeByKey.get(String(r.key)) : undefined) ?? String(r.type ?? "");
62
+ const rubric = type ? map.get(type.toLowerCase()) ?? null : null;
63
+ return { ...r, type, rubric };
64
+ });
65
+ }
66
+ //# sourceMappingURL=rubrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rubrics.js","sourceRoot":"","sources":["../../src/lib/rubrics.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,8 @@ import { resolveNoteId } from "./lib/resolve-note.js";
29
29
  import { readCanvasMarkdown } from "./lib/canvas-read.js";
30
30
  import { buildSchema } from "./lib/build-schema.js";
31
31
  import { assertMcpLocalFileReadable, getMcpLocalFileRoots, } from "./lib/mcp-local-file.js";
32
+ import { attachRubricsToResults, loadRubricMap, rubricsForTypes, } from "./lib/rubrics.js";
33
+ import { lintNarratedVerbMentions, hasGenericMention, fetchRelationshipTypeOptions, } from "./lib/mention-lint.js";
32
34
  // ---------------------------------------------------------------------------
33
35
  // Environment
34
36
  // ---------------------------------------------------------------------------
@@ -233,41 +235,40 @@ const NoteCreateItem = z.object({
233
235
  markdown: z.string().describe("Note content as markdown (the first # h1 heading becomes the title)"),
234
236
  tags: z.array(z.string()).optional().describe("Tags for this note"),
235
237
  });
236
- server.tool("cnotes_notes_create", "Create one or more interlinked notes in a single atomic transaction. FIRST read the target type's rubric (cnotes_types_show) and author to it. Pass `notes` as one item object or an array; each note's title is its first # h1 heading. Cross-reference in markdown with [@key: Title](relationship:type) (same-batch — @key resolves to the new display ID) or [NOTE-12: Title](relationship:references) (existing note); ALWAYS include the ': Title' part, and the relationship name is free-form (use 'references' if unsure). The response echoes each type's rubric under `rubrics`; on validation failure, `details.items` pinpoints the bad item by index + key.", {
238
+ server.tool("cnotes_notes_create", "Create one or more interlinked notes in a single atomic transaction. FIRST call cnotes_types_show for each target type and author to that rubric — do not skip this. Pass `notes` as one item object or an array; each note's title is its first # h1 heading. Cross-reference in markdown with [@key: Title](relationship:type) (same-batch — @key resolves to the new display ID) or [NOTE-12: Title](relationship:type) (existing note); ALWAYS include the ': Title' part. Pick the relationship type from the verb your own prose uses ('blocks', 'depends-on', …; free-form — unknown types auto-create); reserve 'references' for pure citations — a verb narrated in prose around a 'references' chip is invisible to backlinks. A `mentionWarnings` array in the result flags narrated verbs it detects; treat each as a retype instruction unless the prose genuinely isn't asserting a relationship. Each result item carries `type` + `rubric` (null when the type has no validationPrompt); a top-level `rubrics` array is also present. On validation failure, `details.items` pinpoints the bad item by index + key.", {
237
239
  notes: z
238
240
  .union([NoteCreateItem, z.array(NoteCreateItem).min(1)])
239
241
  .describe("Either a single item object or a non-empty array of items."),
240
242
  }, async ({ notes }) => withErrorHandling(async () => {
241
243
  const items = Array.isArray(notes) ? notes : [notes];
244
+ // Narrated-verb lint (warn-only, pre-send): MCP has no stderr channel,
245
+ // so `mentionWarnings` in the result is the only way the model sees it.
246
+ let mentionWarnings = [];
247
+ if (items.some((n) => hasGenericMention(String(n.markdown ?? "")))) {
248
+ const relTypes = await fetchRelationshipTypeOptions(api, workspaceId);
249
+ if (relTypes) {
250
+ mentionWarnings = items.flatMap((n) => lintNarratedVerbMentions(String(n.markdown ?? ""), relTypes).map((w) => ({ item: n.key, ...w })));
251
+ }
252
+ }
242
253
  const result = await api.post("/api/notes/bulk", {
243
254
  action: "bulkCreate",
244
255
  workspaceId,
245
256
  notes: items,
246
257
  });
247
- // A1 — echo the rubric for each type used so the agent sees the standard
248
- // for what it just wrote and can revise in-loop. Zero LLM. Non-fatal: the
249
- // create already succeeded if we got here.
250
- try {
251
- const supertags = await api.get("/api/supertags", {
252
- workspaceId,
253
- });
254
- const byName = new Map();
255
- for (const s of Array.isArray(supertags) ? supertags : []) {
256
- const nm = String(s.name || "").toLowerCase();
257
- if (nm && s.validationPrompt)
258
- byName.set(nm, String(s.validationPrompt));
259
- }
260
- const rubrics = Array.from(new Set(items.map((n) => String(n.type))))
261
- .map((t) => ({ type: t, rubric: byName.get(t.toLowerCase()) || "" }))
262
- .filter((r) => r.rubric);
263
- if (rubrics.length > 0 && result && typeof result === "object") {
264
- result.rubrics = rubrics;
265
- }
266
- }
267
- catch {
268
- // Skip the rubric echo if supertags can't be fetched.
269
- }
270
- return result;
258
+ // Attach rubrics ON EACH result so filtering to results[] still carries
259
+ // the standard. Top-level rubrics kept for backward compatibility.
260
+ // Non-fatal: create already succeeded if we got here.
261
+ const rubricMap = await loadRubricMap(api, workspaceId);
262
+ const typeByKey = new Map(items.map((n) => [n.key, n.type]));
263
+ const rawResults = (result.results || []);
264
+ const enriched = attachRubricsToResults(rawResults, typeByKey, rubricMap);
265
+ const rubrics = rubricsForTypes(items.map((n) => String(n.type)), rubricMap);
266
+ return {
267
+ ...result,
268
+ results: enriched,
269
+ ...(rubrics.length > 0 ? { rubrics } : {}),
270
+ ...(mentionWarnings.length > 0 ? { mentionWarnings } : {}),
271
+ };
271
272
  }));
272
273
  server.tool("cnotes_notes_retype", "Change a single note's type. Atomic operation: creates a new note with the target type, archives the old one, and cross-links both.", {
273
274
  noteId: z.string().describe("Display ID of the note to retype (e.g. NOTE-1)"),
@@ -590,7 +591,7 @@ server.tool("cnotes_canvas_bulk_remove", "Remove multiple nodes from a canvas in
590
591
  action: "bulkRemoveNodes",
591
592
  nodeIds,
592
593
  })));
593
- server.tool("cnotes_canvas_add_edge", "Connect two nodes on a canvas with an edge. Defaults to an arrow at the target end and a solid line.", {
594
+ server.tool("cnotes_canvas_add_edge", "Connect two nodes on a canvas with an edge (any node type, including section frames). Defaults to an arrow at the target end and a solid line.", {
594
595
  canvasId: z.string().describe("Canvas ID"),
595
596
  sourceNodeId: z.string().describe("Source node ID"),
596
597
  targetNodeId: z.string().describe("Target node ID"),
@@ -852,16 +853,28 @@ server.tool("cnotes_versions_list", "List versions of a note.", {
852
853
  throw new Error(`Note not found: ${noteId}`);
853
854
  return note.versions || [];
854
855
  }));
855
- server.tool("cnotes_versions_create", "Create a new version of a note. Content is markdown. To link to other notes, use [NOTE-123: Title](relationship:references) inside the markdown — always include the title (the part after the colon) or the chip renders as 'Untitled'. The relationship name is free-form (e.g. references, triggers, caused-by); use 'references' if unsure.", {
856
+ server.tool("cnotes_versions_create", "Create a new version of a note. Content is markdown. To link to other notes, use [NOTE-123: Title](relationship:type) inside the markdown — always include the title (the part after the colon) or the chip renders as 'Untitled'. Pick the relationship type from the verb your own prose uses ('blocks', 'depends-on', 'caused-by', …; free-form — unknown types auto-create); reserve 'references' for pure citations — a verb narrated in prose around a 'references' chip is invisible to backlinks. A `mentionWarnings` array in the result flags narrated verbs it detects; treat each as a retype instruction unless the prose genuinely isn't asserting a relationship.", {
856
857
  noteId: z.string().describe("Note display ID or Convex ID"),
857
858
  markdown: z.string().describe("New content as markdown"),
858
859
  description: z.string().describe("Change description (required)"),
859
860
  }, async ({ noteId, markdown, description }) => withErrorHandling(async () => {
861
+ // Narrated-verb lint (warn-only): same contract as cnotes_notes_create.
862
+ let mentionWarnings = [];
863
+ if (hasGenericMention(markdown)) {
864
+ const relTypes = await fetchRelationshipTypeOptions(api, workspaceId);
865
+ if (relTypes) {
866
+ mentionWarnings = lintNarratedVerbMentions(markdown, relTypes).map((w) => ({ ...w }));
867
+ }
868
+ }
860
869
  const resolvedId = await resolveNoteId(api, workspaceId, noteId);
861
- return api.post(`/api/notes/${resolvedId}/versions`, {
870
+ const data = await api.post(`/api/notes/${resolvedId}/versions`, {
862
871
  content: markdown,
863
872
  description,
864
873
  });
874
+ return {
875
+ ...data,
876
+ ...(mentionWarnings.length > 0 ? { mentionWarnings } : {}),
877
+ };
865
878
  }));
866
879
  // ---------------------------------------------------------------------------
867
880
  // Workspace