@rubytech/create-maxy 1.0.705 → 1.0.707

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 (107) hide show
  1. package/dist/__tests__/apt-resolve.test.js +179 -0
  2. package/dist/apt-resolve.js +73 -0
  3. package/dist/index.js +48 -46
  4. package/package.json +3 -3
  5. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts +2 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts.map +1 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +89 -0
  8. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -0
  9. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +42 -0
  10. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -0
  11. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +87 -0
  12. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -0
  13. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +99 -0
  14. package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +84 -0
  15. package/payload/platform/neo4j/schema.cypher +23 -0
  16. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  17. package/payload/platform/plugins/admin/mcp/dist/index.js +30 -0
  18. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +2 -2
  20. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +47 -6
  21. package/payload/platform/plugins/docs/references/adherence.md +1 -1
  22. package/payload/platform/plugins/memory/PLUGIN.md +25 -16
  23. package/payload/platform/plugins/memory/mcp/dist/index.js +146 -38
  24. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts +2 -0
  26. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts.map +1 -0
  27. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +92 -0
  28. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -0
  29. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts +2 -0
  30. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts.map +1 -0
  31. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +51 -0
  32. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -0
  33. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts +2 -0
  34. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts.map +1 -0
  35. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +222 -0
  36. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -0
  37. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.d.ts +1 -7
  38. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.d.ts.map +1 -1
  39. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.js +27 -14
  40. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.js.map +1 -1
  41. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +16 -0
  42. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +38 -11
  44. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +136 -0
  46. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -0
  47. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +180 -0
  48. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -0
  49. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +126 -0
  50. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -0
  51. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +253 -0
  52. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -0
  53. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +11 -2
  54. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
  55. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +6 -3
  56. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
  57. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +44 -22
  58. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
  59. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +94 -57
  60. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
  61. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts +34 -0
  62. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +1 -0
  63. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +46 -0
  64. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +1 -0
  65. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts +1 -2
  66. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts.map +1 -1
  67. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js +8 -9
  68. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js.map +1 -1
  69. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts +5 -17
  70. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js +26 -49
  72. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -1
  74. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +4 -25
  75. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -1
  76. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +23 -14
  77. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  78. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +410 -164
  79. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  80. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +7 -5
  81. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  82. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +2 -2
  83. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  84. package/payload/platform/plugins/memory/references/schema-base.md +33 -0
  85. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +112 -0
  86. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -2
  87. package/payload/platform/templates/specialists/agents/content-producer.md +10 -77
  88. package/payload/platform/templates/specialists/agents/database-operator.md +21 -13
  89. package/payload/server/chunk-PE76FPYP.js +12040 -0
  90. package/payload/server/maxy-edge.js +1 -1
  91. package/payload/server/public/assets/{Checkbox-B2Lk8F4X.js → Checkbox-CjbS9JcG.js} +1 -1
  92. package/payload/server/public/assets/{admin-agtgi48Q.js → admin-Ce9DbUuu.js} +1 -1
  93. package/payload/server/public/assets/{data-B7nsyBTV.js → data-C-SxjLC9.js} +1 -1
  94. package/payload/server/public/assets/{file-DHWTu8LP.js → file-D4cbAAuo.js} +1 -1
  95. package/payload/server/public/assets/{graph-ChDwqqhJ.js → graph-BRD96pKD.js} +8 -8
  96. package/payload/server/public/assets/{house-CfjnRPO6.js → house-CYsVygEQ.js} +1 -1
  97. package/payload/server/public/assets/{jsx-runtime-81wg0w0Q.css → jsx-runtime-DPXE45W9.css} +1 -1
  98. package/payload/server/public/assets/{public-CE1kyVnz.js → public-BTOF98iO.js} +1 -1
  99. package/payload/server/public/assets/{share-2-CAd1beVT.js → share-2-B-sbkB36.js} +1 -1
  100. package/payload/server/public/assets/{useVoiceRecorder-LSAU68Eo.js → useVoiceRecorder-DLVFx3ms.js} +1 -1
  101. package/payload/server/public/assets/{x-B0xK3Aoq.js → x-BNidzSAn.js} +1 -1
  102. package/payload/server/public/data.html +6 -6
  103. package/payload/server/public/graph.html +7 -7
  104. package/payload/server/public/index.html +8 -8
  105. package/payload/server/public/public.html +5 -5
  106. package/payload/server/server.js +6 -10
  107. /package/payload/server/public/assets/{jsx-runtime-DhzH26q8.js → jsx-runtime-BUs3sHtV.js} +0 -0
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /**
3
+ * Shared parser for `platform/neo4j/schema.cypher`.
4
+ *
5
+ * Two consumers today:
6
+ * 1. The admin SCHEMA prompt block (`platform/ui/app/lib/admin-schema-block.ts`,
7
+ * Task 703) renders labels declared in schema.cypher into the agent's
8
+ * system prompt.
9
+ * 2. The memory-plugin schema validator (Task 736) treats schema.cypher
10
+ * declarations as the "promise" half of the recognised-label set —
11
+ * labels that exist as constraints/indexes but may not yet have a node
12
+ * in the graph (fresh-install bootstrap).
13
+ *
14
+ * The previous home was admin-schema-block.ts; that owner was wrong as
15
+ * soon as a second consumer arrived. Lifting here keeps a single source of
16
+ * truth and removes the cross-package shape coupling.
17
+ *
18
+ * Also exports a compact `levenshtein` for "did you mean?" suggestions on
19
+ * unknown labels — used by the memory-plugin validator. The graph-mcp
20
+ * cypher validator has its own copy in schema-cache.ts (private to the
21
+ * stale-miss heuristic); the duplication is intentional, since this
22
+ * exported one targets human-readable error UX, not refresh debouncing.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.parseLabelsFromSchemaCypher = parseLabelsFromSchemaCypher;
26
+ exports.levenshtein = levenshtein;
27
+ exports.nearestLabel = nearestLabel;
28
+ const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
29
+ /**
30
+ * Extract every Neo4j label declared in a `schema.cypher` file by scanning
31
+ * `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
32
+ * list. Pure function — no I/O.
33
+ */
34
+ function parseLabelsFromSchemaCypher(schemaCypher) {
35
+ const found = new Set();
36
+ for (const match of schemaCypher.matchAll(LABEL_PATTERN)) {
37
+ found.add(match[1]);
38
+ }
39
+ return [...found].sort();
40
+ }
41
+ /**
42
+ * Standard iterative-DP Levenshtein. Used to suggest a near-match when an
43
+ * agent submits an unknown label — small alphabets, short strings, runs in
44
+ * microseconds. Identical algorithm to the private copy in schema-cache.ts;
45
+ * exported here so callers outside graph-mcp don't have to roll their own.
46
+ */
47
+ function levenshtein(a, b) {
48
+ if (a === b)
49
+ return 0;
50
+ if (a.length === 0)
51
+ return b.length;
52
+ if (b.length === 0)
53
+ return a.length;
54
+ let prev = new Array(b.length + 1);
55
+ let curr = new Array(b.length + 1);
56
+ for (let j = 0; j <= b.length; j++)
57
+ prev[j] = j;
58
+ for (let i = 1; i <= a.length; i++) {
59
+ curr[0] = i;
60
+ for (let j = 1; j <= b.length; j++) {
61
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
62
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
63
+ }
64
+ [prev, curr] = [curr, prev];
65
+ }
66
+ return prev[b.length];
67
+ }
68
+ /**
69
+ * Find the closest label in `candidates` to `unknown` by edit distance.
70
+ * Returns null when the closest match is further than `maxDistance` (default
71
+ * 3) — beyond that a suggestion is more confusing than helpful.
72
+ */
73
+ function nearestLabel(unknown, candidates, maxDistance = 3) {
74
+ let best = null;
75
+ let bestDist = Infinity;
76
+ for (const candidate of candidates) {
77
+ const d = levenshtein(unknown, candidate);
78
+ if (d < bestDist) {
79
+ bestDist = d;
80
+ best = candidate;
81
+ }
82
+ }
83
+ if (best === null || bestDist > maxDistance)
84
+ return null;
85
+ return best;
86
+ }
87
+ //# sourceMappingURL=schema-cypher-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-cypher-parser.js","sourceRoot":"","sources":["../src/schema-cypher-parser.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AASH,kEAMC;AAQD,kCAgBC;AAOD,oCAgBC;AA5DD,MAAM,aAAa,GAAG,kCAAkC,CAAC;AAEzD;;;;GAIG;AACH,SAAgB,2BAA2B,CAAC,YAAoB;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACvE,CAAC;QACD,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA4B,EAC5B,WAAW,GAAG,CAAC;IAEf,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACjB,QAAQ,GAAG,CAAC,CAAC;YACb,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,WAAW;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,99 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ parseLabelsFromSchemaCypher,
5
+ levenshtein,
6
+ nearestLabel,
7
+ } from "../schema-cypher-parser.js";
8
+
9
+ test("parseLabelsFromSchemaCypher: extracts labels from constraint and index forms", () => {
10
+ const text = `
11
+ CREATE CONSTRAINT person_email IF NOT EXISTS
12
+ FOR (p:Person) REQUIRE p.email IS UNIQUE;
13
+
14
+ CREATE CONSTRAINT business_account IF NOT EXISTS
15
+ FOR (b:LocalBusiness) REQUIRE b.accountId IS UNIQUE;
16
+
17
+ CREATE INDEX admin_user_account IF NOT EXISTS
18
+ FOR (au:AdminUser) ON (au.accountId);
19
+
20
+ CREATE INDEX knowledge_doc IF NOT EXISTS
21
+ FOR (k:KnowledgeDocument) ON (k.embedding) OPTIONS { ... };
22
+ `;
23
+ const labels = parseLabelsFromSchemaCypher(text);
24
+ assert.deepEqual(labels, [
25
+ "AdminUser",
26
+ "KnowledgeDocument",
27
+ "LocalBusiness",
28
+ "Person",
29
+ ]);
30
+ });
31
+
32
+ test("parseLabelsFromSchemaCypher: deduplicates labels declared multiple times", () => {
33
+ const text = `
34
+ FOR (p:Person) REQUIRE p.email IS UNIQUE;
35
+ FOR (p:Person) REQUIRE p.telephone IS UNIQUE;
36
+ FOR (p:Person) ON (p.status);
37
+ `;
38
+ assert.deepEqual(parseLabelsFromSchemaCypher(text), ["Person"]);
39
+ });
40
+
41
+ test("parseLabelsFromSchemaCypher: returns sorted output", () => {
42
+ const text = `FOR (z:Zebra) ON (z.x); FOR (a:Aardvark) ON (a.x); FOR (m:Mongoose) ON (m.x);`;
43
+ assert.deepEqual(parseLabelsFromSchemaCypher(text), [
44
+ "Aardvark",
45
+ "Mongoose",
46
+ "Zebra",
47
+ ]);
48
+ });
49
+
50
+ test("parseLabelsFromSchemaCypher: ignores non-constraint cypher", () => {
51
+ const text = `
52
+ MATCH (n:Person) RETURN n;
53
+ CREATE (b:Business { name: 'X' });
54
+ FOR (p:Person) REQUIRE p.email IS UNIQUE;
55
+ `;
56
+ // MATCH/CREATE patterns don't include FOR, so they're ignored.
57
+ assert.deepEqual(parseLabelsFromSchemaCypher(text), ["Person"]);
58
+ });
59
+
60
+ test("parseLabelsFromSchemaCypher: empty input returns empty array", () => {
61
+ assert.deepEqual(parseLabelsFromSchemaCypher(""), []);
62
+ });
63
+
64
+ test("levenshtein: identical strings", () => {
65
+ assert.equal(levenshtein("Person", "Person"), 0);
66
+ });
67
+
68
+ test("levenshtein: substitution distance", () => {
69
+ // LocaIBusiness vs LocalBusiness — capital-I-as-l typo from the incident.
70
+ assert.equal(levenshtein("LocaIBusiness", "LocalBusiness"), 1);
71
+ });
72
+
73
+ test("levenshtein: insertion + deletion", () => {
74
+ assert.equal(levenshtein("Person", "Persons"), 1);
75
+ assert.equal(levenshtein("Persons", "Person"), 1);
76
+ });
77
+
78
+ test("levenshtein: handles empty strings", () => {
79
+ assert.equal(levenshtein("", "abc"), 3);
80
+ assert.equal(levenshtein("abc", ""), 3);
81
+ assert.equal(levenshtein("", ""), 0);
82
+ });
83
+
84
+ test("nearestLabel: finds the closest match", () => {
85
+ const candidates = ["Person", "LocalBusiness", "AdminUser"];
86
+ assert.equal(nearestLabel("LocaIBusiness", candidates), "LocalBusiness");
87
+ assert.equal(nearestLabel("AdminUsr", candidates), "AdminUser");
88
+ });
89
+
90
+ test("nearestLabel: returns null when no match within maxDistance", () => {
91
+ const candidates = ["Person", "LocalBusiness"];
92
+ assert.equal(nearestLabel("Quokka", candidates), null);
93
+ // Default maxDistance is 3; "abcde" is too far from any candidate.
94
+ assert.equal(nearestLabel("abcde", candidates, 1), null);
95
+ });
96
+
97
+ test("nearestLabel: empty candidates returns null", () => {
98
+ assert.equal(nearestLabel("Person", []), null);
99
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared parser for `platform/neo4j/schema.cypher`.
3
+ *
4
+ * Two consumers today:
5
+ * 1. The admin SCHEMA prompt block (`platform/ui/app/lib/admin-schema-block.ts`,
6
+ * Task 703) renders labels declared in schema.cypher into the agent's
7
+ * system prompt.
8
+ * 2. The memory-plugin schema validator (Task 736) treats schema.cypher
9
+ * declarations as the "promise" half of the recognised-label set —
10
+ * labels that exist as constraints/indexes but may not yet have a node
11
+ * in the graph (fresh-install bootstrap).
12
+ *
13
+ * The previous home was admin-schema-block.ts; that owner was wrong as
14
+ * soon as a second consumer arrived. Lifting here keeps a single source of
15
+ * truth and removes the cross-package shape coupling.
16
+ *
17
+ * Also exports a compact `levenshtein` for "did you mean?" suggestions on
18
+ * unknown labels — used by the memory-plugin validator. The graph-mcp
19
+ * cypher validator has its own copy in schema-cache.ts (private to the
20
+ * stale-miss heuristic); the duplication is intentional, since this
21
+ * exported one targets human-readable error UX, not refresh debouncing.
22
+ */
23
+
24
+ const LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
25
+
26
+ /**
27
+ * Extract every Neo4j label declared in a `schema.cypher` file by scanning
28
+ * `FOR (alias:Label)` constraint and index forms. Returns a sorted, de-duped
29
+ * list. Pure function — no I/O.
30
+ */
31
+ export function parseLabelsFromSchemaCypher(schemaCypher: string): string[] {
32
+ const found = new Set<string>();
33
+ for (const match of schemaCypher.matchAll(LABEL_PATTERN)) {
34
+ found.add(match[1]);
35
+ }
36
+ return [...found].sort();
37
+ }
38
+
39
+ /**
40
+ * Standard iterative-DP Levenshtein. Used to suggest a near-match when an
41
+ * agent submits an unknown label — small alphabets, short strings, runs in
42
+ * microseconds. Identical algorithm to the private copy in schema-cache.ts;
43
+ * exported here so callers outside graph-mcp don't have to roll their own.
44
+ */
45
+ export function levenshtein(a: string, b: string): number {
46
+ if (a === b) return 0;
47
+ if (a.length === 0) return b.length;
48
+ if (b.length === 0) return a.length;
49
+ let prev = new Array<number>(b.length + 1);
50
+ let curr = new Array<number>(b.length + 1);
51
+ for (let j = 0; j <= b.length; j++) prev[j] = j;
52
+ for (let i = 1; i <= a.length; i++) {
53
+ curr[0] = i;
54
+ for (let j = 1; j <= b.length; j++) {
55
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
56
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
57
+ }
58
+ [prev, curr] = [curr, prev];
59
+ }
60
+ return prev[b.length];
61
+ }
62
+
63
+ /**
64
+ * Find the closest label in `candidates` to `unknown` by edit distance.
65
+ * Returns null when the closest match is further than `maxDistance` (default
66
+ * 3) — beyond that a suggestion is more confusing than helpful.
67
+ */
68
+ export function nearestLabel(
69
+ unknown: string,
70
+ candidates: Iterable<string>,
71
+ maxDistance = 3,
72
+ ): string | null {
73
+ let best: string | null = null;
74
+ let bestDist = Infinity;
75
+ for (const candidate of candidates) {
76
+ const d = levenshtein(unknown, candidate);
77
+ if (d < bestDist) {
78
+ bestDist = d;
79
+ best = candidate;
80
+ }
81
+ }
82
+ if (best === null || bestDist > maxDistance) return null;
83
+ return best;
84
+ }
@@ -257,6 +257,29 @@ CREATE FULLTEXT INDEX knowledge_fulltext IF NOT EXISTS
257
257
  FOR (k:KnowledgeDocument|Section|Chunk)
258
258
  ON EACH [k.summary, k.content];
259
259
 
260
+ // ----------------------------------------------------------
261
+ // Position node — a role held by a UserProfile at an Organization.
262
+ // Each role is its own node so it can carry its own title, dates, and
263
+ // achievement bullets. Connected via:
264
+ // (:UserProfile)-[:HAS_POSITION]->(:Position)-[:AT]->(:Organization)
265
+ // Multiple Positions per UserProfile (career history); one Organization
266
+ // may host many Positions across many UserProfiles. No unique constraint.
267
+ // Written by the document-ingest skill when a CV section classifies as
268
+ // `Position` (Task 737).
269
+ // ----------------------------------------------------------
270
+
271
+ CREATE INDEX position_account IF NOT EXISTS
272
+ FOR (p:Position) ON (p.accountId);
273
+
274
+ CREATE VECTOR INDEX position_embedding IF NOT EXISTS
275
+ FOR (p:Position) ON (p.embedding)
276
+ OPTIONS {
277
+ indexConfig: {
278
+ `vector.dimensions`: 768,
279
+ `vector.similarity_function`: 'cosine'
280
+ }
281
+ };
282
+
260
283
  // ----------------------------------------------------------
261
284
  // Conversation node — persistent conversation sessions
262
285
  // One Conversation per sessionKey. Account scope is encoded by the
@@ -36,6 +36,7 @@ hidden:
36
36
  - api-key-verify
37
37
  - onboarding-get
38
38
  - onboarding-complete-step
39
+ - onboarding-step9-mode
39
40
  - qr-generate
40
41
  - wifi
41
42
  - review-digest-compose
@@ -1709,6 +1709,36 @@ server.tool("onboarding-complete-step", "Mark an onboarding step as completed. S
1709
1709
  };
1710
1710
  }
1711
1711
  });
1712
+ // Task 704: pin the operator's persona (personal/business-owner/employee) at
1713
+ // step 9 BEFORE any graph write happens. The agent calls this immediately on
1714
+ // the user's single-select submission. The tool's job is twofold:
1715
+ // 1. Emit a deterministic [onboarding-step9-mode] log line for the
1716
+ // diagnostic-grep path on the Pi (matches [onboarding-step-complete]
1717
+ // format — same accountId-prefix slicing, same surrounding code).
1718
+ // 2. Return the next-action prose so the agent's branching is anchored to
1719
+ // the tool surface, not free-text inference.
1720
+ // No persistence — the graph state (LocalBusiness exists vs Person with
1721
+ // role=admin-personal exists) remains the source of truth for gate
1722
+ // satisfaction. Re-calling this tool with a different mode on the same
1723
+ // session is allowed; the most recent log line wins for diagnostic purposes.
1724
+ server.tool("onboarding-step9-mode", "Step 9 onboarding fork: record the operator's persona (personal, business-owner, " +
1725
+ "or employee) before any graph write. Logs the choice for diagnostic grep and " +
1726
+ "returns the deterministic next-action prose for the agent.", {
1727
+ mode: z
1728
+ .enum(["personal", "business-owner", "employee"])
1729
+ .describe("The operator's persona: 'personal' for an individual user with no business, 'business-owner' for someone setting up Maxy as the operations agent for their company, or 'employee' for an employee whose employer is NOT being registered."),
1730
+ }, async ({ mode }) => {
1731
+ console.log(`[onboarding-step9-mode] accountId=${ACCOUNT_ID.slice(0, 8)}… mode=${mode}`);
1732
+ const nextAction = mode === "business-owner"
1733
+ ? "Invoke the `business-profile` skill to create AdminUser + LocalBusiness and capture the business identity. Mark step 9 complete only after both nodes exist in the graph."
1734
+ : "Ask the user for their email (one sentence). Use their name from `admin-identity` in the system prompt; split into givenName + familyName. Call `memory-write` to create the AdminUser node, then call `memory-write` to create a Person node with `role: \"admin-personal\"`, the givenName, familyName, email, and an OWNS edge from the AdminUser. Then call `onboarding-complete-step` with step 9. Do not invoke the `business-profile` skill — personal/employee mode does not register a LocalBusiness.";
1735
+ return {
1736
+ content: [{
1737
+ type: "text",
1738
+ text: JSON.stringify({ mode, nextAction }, null, 2),
1739
+ }],
1740
+ };
1741
+ });
1712
1742
  // ===================================================================
1713
1743
  // Utility tools
1714
1744
  // ===================================================================