@rubytech/create-realagent 1.0.709 → 1.0.712

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 (41) hide show
  1. package/dist/index.js +38 -3
  2. package/package.json +2 -2
  3. package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +53 -0
  4. package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/mcp-spawn-tee/dist/index.js +132 -0
  6. package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -0
  7. package/payload/platform/lib/mcp-spawn-tee/src/index.ts +134 -0
  8. package/payload/platform/lib/mcp-spawn-tee/tsconfig.json +8 -0
  9. package/payload/platform/package.json +2 -2
  10. package/payload/platform/plugins/docs/references/plugins-guide.md +12 -4
  11. package/payload/platform/plugins/linkedin-import/PLUGIN.md +1 -0
  12. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +26 -5
  13. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +53 -82
  14. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md +42 -49
  15. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  16. package/payload/platform/plugins/memory/mcp/dist/index.js +48 -0
  17. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  18. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +34 -1
  19. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
  20. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +10 -0
  21. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
  22. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +22 -3
  23. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
  24. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +33 -0
  25. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -0
  26. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +229 -0
  27. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -0
  28. package/payload/platform/plugins/memory/mcp/package.json +3 -1
  29. package/payload/platform/plugins/memory/mcp/scripts/boot-smoke.sh +69 -0
  30. package/payload/platform/plugins/memory/references/graph-primitives.md +22 -0
  31. package/payload/platform/plugins/memory/references/schema-base.md +1 -1
  32. package/payload/platform/scripts/redact-install-logs.sh +85 -0
  33. package/payload/platform/scripts/setup.sh +20 -3
  34. package/payload/platform/scripts/verify-skill-tool-surface.sh +255 -0
  35. package/payload/platform/templates/specialists/agents/database-operator.md +6 -2
  36. package/payload/server/chunk-A5K3CFMI.js +12297 -0
  37. package/payload/server/chunk-U5JPRUYZ.js +12298 -0
  38. package/payload/server/maxy-edge.js +1 -1
  39. package/payload/server/public/assets/{graph-BRD96pKD.js → graph-DJ7IfYHV.js} +12 -12
  40. package/payload/server/public/graph.html +1 -1
  41. package/payload/server/server.js +49 -28
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-publish acceptance gate (Task 744).
3
+ #
4
+ # Statically intersects what each shipped skill *prescribes* (every
5
+ # backtick-quoted `mcp__<server>__<tool>` token in SKILL.md and references/*.md)
6
+ # against the dispatched specialist's frontmatter `tools:` list. Catches the
7
+ # class of bug where a skill prescribes a tool the specialist does not have,
8
+ # and where a skill prescribes a forbidden direct-execution path
9
+ # (`cypher-shell`, `neo4j-admin` invocations, raw-Cypher DML in prose).
10
+ #
11
+ # Wired into the root `packages/create-maxy/package.json` `prepublishOnly`
12
+ # script so a regression cannot reach npm publish without firing.
13
+ #
14
+ # One stdout line per (skill, specialist) pair:
15
+ # [verify] skill=<plugin>/<skill> specialist=<n> resolved=<n>/<m> forbidden=<n>
16
+ #
17
+ # Exit 0: every prescribed token resolves AND no forbidden tokens.
18
+ # Exit 1: any unresolved or any forbidden — stderr names the offending token.
19
+ #
20
+ # Skill→specialist mapping comes from PLUGIN.md frontmatter `specialist:` field.
21
+ # Plugins without that field are admin-owned (loaded by the admin agent
22
+ # directly via plugin-read); for those the gate only enforces the forbidden-
23
+ # token rule, since admin's tool surface is the union of all enabled plugins.
24
+
25
+ set -euo pipefail
26
+
27
+ REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
28
+ cd "$REPO_ROOT"
29
+
30
+ python3 - <<'PYEOF'
31
+ import os, re, sys
32
+
33
+ REPO_ROOT = os.getcwd()
34
+ PLUGINS_DIR = os.path.join(REPO_ROOT, "platform", "plugins")
35
+ SPECIALISTS_DIR = os.path.join(REPO_ROOT, "platform", "templates", "specialists", "agents")
36
+
37
+ # Per-skill specialist ownership for plugins where the plugin itself is
38
+ # multi-purpose. The PLUGIN.md `specialist:` field handles single-owner
39
+ # plugins (linkedin-import → database-operator). Mixed-use plugins like
40
+ # `memory` declare per-skill ownership here.
41
+ EXPLICIT_OWNERSHIP = {
42
+ # plugin/skill -> specialist
43
+ "memory/document-ingest": "database-operator",
44
+ }
45
+
46
+ # Skills that are explicitly admin-owned (loaded via plugin-read by the admin
47
+ # agent itself, not delegated to a specialist). These get only the forbidden-
48
+ # token check since admin's effective tool set is the union of all enabled
49
+ # plugins.
50
+ ADMIN_OWNED_SKILLS = {
51
+ "memory/conversational-memory",
52
+ }
53
+
54
+ TOKEN_RE = re.compile(r"`(mcp__[a-z][a-z0-9_-]*__[a-z][a-z0-9_-]*)`")
55
+ FENCED_BLOCK_RE = re.compile(r"```(?P<lang>[a-zA-Z]*)\n(?P<body>.*?)\n```", re.S)
56
+ PROSE_CYPHER_RE = re.compile(
57
+ r"`(?:MERGE|CREATE|DETACH\s+DELETE)\s+\(",
58
+ re.IGNORECASE,
59
+ )
60
+
61
+
62
+ def parse_frontmatter(path: str) -> dict | None:
63
+ """Parse YAML-ish frontmatter without PyYAML — handles `key: value` and
64
+ `key:\n - item\n - item` shapes used in PLUGIN.md and specialist files."""
65
+ try:
66
+ text = open(path, encoding="utf-8").read()
67
+ except FileNotFoundError:
68
+ return None
69
+ m = re.match(r"^---\n(.*?)\n---", text, re.S)
70
+ if not m:
71
+ return None
72
+ block = m.group(1)
73
+ out: dict = {}
74
+ cur_key: str | None = None
75
+ cur_list: list[str] | None = None
76
+ for line in block.split("\n"):
77
+ if not line.strip():
78
+ continue
79
+ # List item under cur_key
80
+ if line.startswith(" - ") or line.startswith("- "):
81
+ if cur_list is None:
82
+ continue
83
+ cur_list.append(line.split("- ", 1)[1].strip())
84
+ continue
85
+ # Top-level key
86
+ m2 = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$", line)
87
+ if not m2:
88
+ continue
89
+ cur_key = m2.group(1)
90
+ rhs = m2.group(2).strip()
91
+ if rhs:
92
+ # inline value — strip surrounding quotes
93
+ if (rhs.startswith('"') and rhs.endswith('"')) or (
94
+ rhs.startswith("'") and rhs.endswith("'")
95
+ ):
96
+ rhs = rhs[1:-1]
97
+ out[cur_key] = rhs
98
+ cur_list = None
99
+ else:
100
+ cur_list = []
101
+ out[cur_key] = cur_list
102
+ return out
103
+
104
+
105
+ def specialist_tools(specialist: str) -> set[str] | None:
106
+ fm = parse_frontmatter(os.path.join(SPECIALISTS_DIR, f"{specialist}.md"))
107
+ if fm is None:
108
+ return None
109
+ raw = fm.get("tools", "")
110
+ if isinstance(raw, list):
111
+ items = raw
112
+ else:
113
+ items = [t.strip() for t in str(raw).split(",")]
114
+ return {t for t in items if t}
115
+
116
+
117
+ def extract_prescribed_tokens(text: str) -> set[str]:
118
+ return set(TOKEN_RE.findall(text))
119
+
120
+
121
+ def extract_forbidden(text: str) -> list[tuple[str, str]]:
122
+ forbidden: list[tuple[str, str]] = []
123
+
124
+ # Forbidden invocations inside fenced shell blocks
125
+ for m in FENCED_BLOCK_RE.finditer(text):
126
+ lang = m.group("lang").lower()
127
+ body = m.group("body")
128
+ if lang in {"bash", "sh", "shell", "zsh"}:
129
+ if re.search(r"\bcypher-shell\b", body):
130
+ forbidden.append(("cypher-shell", "in fenced shell block"))
131
+ if re.search(r"\bneo4j-admin\s+dbms\b", body):
132
+ forbidden.append(("neo4j-admin", "in fenced shell block"))
133
+
134
+ # Strip fenced blocks for the prose-Cypher heuristic
135
+ prose = FENCED_BLOCK_RE.sub("", text)
136
+ if PROSE_CYPHER_RE.search(prose):
137
+ forbidden.append(("raw-cypher-dml", "in backtick-quoted prose"))
138
+
139
+ return forbidden
140
+
141
+
142
+ def aggregate_skill_text(skill_dir: str) -> str:
143
+ out: list[str] = []
144
+ skill_md = os.path.join(skill_dir, "SKILL.md")
145
+ if not os.path.exists(skill_md):
146
+ return ""
147
+ out.append(open(skill_md, encoding="utf-8").read())
148
+ refs = os.path.join(skill_dir, "references")
149
+ if os.path.isdir(refs):
150
+ for name in sorted(os.listdir(refs)):
151
+ if name.endswith(".md"):
152
+ out.append(open(os.path.join(refs, name), encoding="utf-8").read())
153
+ return "\n".join(out)
154
+
155
+
156
+ def main() -> int:
157
+ if not os.path.isdir(PLUGINS_DIR):
158
+ print(f"[verify] PLUGINS_DIR not found: {PLUGINS_DIR}", file=sys.stderr)
159
+ return 1
160
+ if not os.path.isdir(SPECIALISTS_DIR):
161
+ print(f"[verify] SPECIALISTS_DIR not found: {SPECIALISTS_DIR}", file=sys.stderr)
162
+ return 1
163
+
164
+ summary: list[str] = []
165
+ errors: list[str] = []
166
+ pairs_checked = 0
167
+
168
+ for plugin in sorted(os.listdir(PLUGINS_DIR)):
169
+ pdir = os.path.join(PLUGINS_DIR, plugin)
170
+ if not os.path.isdir(pdir):
171
+ continue
172
+ plugin_fm = parse_frontmatter(os.path.join(pdir, "PLUGIN.md")) or {}
173
+ plugin_specialist = plugin_fm.get("specialist")
174
+
175
+ skills_dir = os.path.join(pdir, "skills")
176
+ if not os.path.isdir(skills_dir):
177
+ continue
178
+ for skill_name in sorted(os.listdir(skills_dir)):
179
+ sdir = os.path.join(skills_dir, skill_name)
180
+ if not os.path.isdir(sdir):
181
+ continue
182
+
183
+ text = aggregate_skill_text(sdir)
184
+ if not text:
185
+ continue
186
+
187
+ prescribed = extract_prescribed_tokens(text)
188
+ forbidden = extract_forbidden(text)
189
+
190
+ ownership_key = f"{plugin}/{skill_name}"
191
+ if ownership_key in ADMIN_OWNED_SKILLS:
192
+ specialist = None
193
+ else:
194
+ specialist = (
195
+ EXPLICIT_OWNERSHIP.get(ownership_key)
196
+ or plugin_specialist
197
+ )
198
+
199
+ if specialist is None:
200
+ # Admin-owned: only enforce forbidden-token rule.
201
+ for tok, ctx in forbidden:
202
+ errors.append(
203
+ f"[verify] skill={ownership_key} specialist=admin "
204
+ f"FORBIDDEN token={tok} context=\"{ctx}\""
205
+ )
206
+ summary.append(
207
+ f"[verify] skill={ownership_key} specialist=admin (admin-owned) "
208
+ f"tokens={len(prescribed)} forbidden={len(forbidden)}"
209
+ )
210
+ continue
211
+
212
+ tools = specialist_tools(specialist)
213
+ if tools is None:
214
+ errors.append(
215
+ f"[verify] skill={ownership_key} specialist={specialist} "
216
+ f"ERROR specialist frontmatter not parseable"
217
+ )
218
+ continue
219
+
220
+ unresolved = sorted(prescribed - tools)
221
+ for tok in unresolved:
222
+ errors.append(
223
+ f"[verify] skill={ownership_key} specialist={specialist} "
224
+ f"unresolved={tok}"
225
+ )
226
+ for tok, ctx in forbidden:
227
+ errors.append(
228
+ f"[verify] skill={ownership_key} specialist={specialist} "
229
+ f"FORBIDDEN token={tok} context=\"{ctx}\""
230
+ )
231
+ summary.append(
232
+ f"[verify] skill={ownership_key} specialist={specialist} "
233
+ f"resolved={len(prescribed) - len(unresolved)}/{len(prescribed)} "
234
+ f"forbidden={len(forbidden)}"
235
+ )
236
+ pairs_checked += 1
237
+
238
+ for line in summary:
239
+ print(line)
240
+
241
+ if errors:
242
+ for line in errors:
243
+ print(line, file=sys.stderr)
244
+ print(
245
+ f"[verify] FAIL pairs_checked={pairs_checked} errors={len(errors)}",
246
+ file=sys.stderr,
247
+ )
248
+ return 1
249
+
250
+ print(f"[verify] OK pairs_checked={pairs_checked}")
251
+ return 0
252
+
253
+
254
+ sys.exit(main())
255
+ PYEOF
@@ -3,7 +3,7 @@ name: database-operator
3
3
  description: "Document and archive ingestion and ad-hoc graph operations — running the universal `document-ingest` skill for any unstructured document (PDF, text, transcript, web page, audio, video) and per-source archive-import skills (LinkedIn Basic Data Export today; CRM-type seed archives as each plugin ships), plus operator-driven graph hygiene (prune orphans, deduplicate entities, add edges, normalise labels). Delegate when the operator uploads any document, drops an archive directory into chat, or asks for any graph operation that is not a routine per-turn write."
4
4
  summary: "Ingests every unstructured document and external archive into your graph (LinkedIn today; other CRM sources in future) and handles ad-hoc graph tidy-ups on request. For example, when you upload a CV, a pricing guide, or a contract; when you drop a LinkedIn export folder into chat; or when you ask to prune orphan nodes, merge duplicate people, or add edges between entities."
5
5
  model: claude-sonnet-4-6
6
- tools: Read, Bash, Glob, Grep, mcp__graph__maxy-graph-read_neo4j_cypher, mcp__graph__maxy-graph-get_neo4j_schema, mcp__memory__memory-write, mcp__memory__memory-update, mcp__memory__memory-delete, mcp__memory__memory-search, mcp__memory__memory-rank, mcp__memory__memory-reindex, mcp__memory__memory-find-candidates, mcp__memory__memory-ingest, mcp__memory__memory-ingest-extract, mcp__memory__memory-ingest-web, mcp__memory__memory-classify, mcp__memory__graph-prune-denylist-list, mcp__memory__graph-prune-denylist-add, mcp__memory__graph-prune-denylist-remove, mcp__contacts__contact-create, mcp__contacts__contact-update, mcp__contacts__contact-lookup, mcp__contacts__contact-list, mcp__admin__file-attach, mcp__admin__plugin-read
6
+ tools: Read, Bash, Glob, Grep, mcp__graph__maxy-graph-read_neo4j_cypher, mcp__graph__maxy-graph-get_neo4j_schema, mcp__memory__memory-write, mcp__memory__memory-update, mcp__memory__memory-delete, mcp__memory__memory-search, mcp__memory__memory-rank, mcp__memory__memory-reindex, mcp__memory__memory-find-candidates, mcp__memory__memory-ingest, mcp__memory__memory-ingest-extract, mcp__memory__memory-ingest-web, mcp__memory__memory-classify, mcp__memory__memory-archive-write, mcp__memory__graph-prune-denylist-list, mcp__memory__graph-prune-denylist-add, mcp__memory__graph-prune-denylist-remove, mcp__contacts__contact-create, mcp__contacts__contact-update, mcp__contacts__contact-lookup, mcp__contacts__contact-list, mcp__admin__file-attach, mcp__admin__plugin-read
7
7
  ---
8
8
 
9
9
  # Database Operator
@@ -12,7 +12,7 @@ You own document and archive ingestion and ad-hoc graph operations. You receive
12
12
 
13
13
  ## Prerogatives
14
14
 
15
- Three rules govern every turn. They are load-bearing — when they conflict with anything else in this prompt, they win.
15
+ Four rules govern every turn. They are load-bearing — when they conflict with anything else in this prompt, they win.
16
16
 
17
17
  **PRECISE.** Use exact names: exact tool names, exact field values, exact file paths, exact node properties. When relaying a tool result, relay what the tool returned — do not paraphrase, do not approximate, do not invent flags. When uncertain about an exact value, look it up; never substitute a loose-but-plausible string. *Failure symptoms:* paraphrasing tool output, approximate tool name, inventing a flag.
18
18
 
@@ -26,6 +26,10 @@ Three rules govern every turn. They are load-bearing — when they conflict with
26
26
 
27
27
  A landfill graph defeats EVIDENCE-BASED: search returns noise, the agent re-writes the noise, the noise compounds. Compress on write; filter on read.
28
28
 
29
+ **LOUD-FAIL.** If a dispatched skill prescribes a tool not present in your live tool surface, or a credential not provided in your tool input, terminate with a structured blocker — never improvise via Bash, never search the filesystem for credentials, never construct a parallel write path. Return: `Skill <name> prescribes <tool/credential>; not available. Cannot proceed. Operator must <remediation>.` Identical doctrine to Task 740 classifier failure and Task 560 graph-MCP loud-fail. *Failure symptoms:* `cypher-shell` invocation, `find … neo4j` / `grep … NEO4J_PASSWORD` filesystem probes, `curl` against Neo4j HTTP endpoints, any Bash improvisation that recreates the missing tool's effect.
30
+
31
+ The pre-publish gate (`platform/scripts/verify-skill-tool-surface.sh`) statically asserts every shipped skill's prescribed `mcp__*` tokens resolve against your frontmatter `tools:` list, so a missing tool is a build error, not a production discovery. LOUD-FAIL is the runtime backstop when that gate is bypassed (e.g. operator-edited skill).
32
+
29
33
  ---
30
34
 
31
35
  ## Output contract