@rubytech/create-maxy-code 0.1.61 → 0.1.63

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 (148) hide show
  1. package/dist/__tests__/installer-specialist-registration.test.js +112 -0
  2. package/dist/index.js +22 -0
  3. package/dist/specialist-registration.js +78 -0
  4. package/package.json +1 -1
  5. package/payload/platform/plugins/admin/PLUGIN.md +22 -0
  6. package/payload/platform/plugins/admin/hooks/__tests__/turn-completed-graph-write.test.sh +50 -13
  7. package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +22 -21
  8. package/payload/platform/plugins/contacts/PLUGIN.md +9 -0
  9. package/payload/platform/plugins/docs/references/admin-session.md +55 -7
  10. package/payload/platform/plugins/email/PLUGIN.md +9 -0
  11. package/payload/platform/plugins/epc/PLUGIN.md +5 -0
  12. package/payload/platform/plugins/loop/PLUGIN.md +27 -0
  13. package/payload/platform/plugins/memory/PLUGIN.md +37 -0
  14. package/payload/platform/plugins/memory/mcp/dist/index.js +3 -3
  15. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +137 -75
  17. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -1
  18. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +0 -17
  19. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -1
  20. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +32 -4
  21. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -1
  22. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +65 -0
  23. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -0
  24. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +182 -0
  25. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -0
  26. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js +8 -8
  27. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js.map +1 -1
  28. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.d.ts +2 -0
  29. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.d.ts.map +1 -0
  30. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.js +241 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.js.map +1 -0
  32. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
  33. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +9 -0
  34. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
  35. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts +1 -1
  36. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js +9 -1
  38. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js.map +1 -1
  39. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -1
  40. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +10 -2
  41. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -1
  42. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +5 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +1 -1
  47. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -1
  48. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +5 -1
  49. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -1
  50. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  51. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +4 -0
  52. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  53. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +1 -1
  54. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +4 -0
  55. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +1 -1
  56. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  57. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +4 -0
  58. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  59. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts.map +1 -1
  60. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js +4 -0
  61. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js.map +1 -1
  62. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
  63. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +4 -0
  64. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
  65. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts.map +1 -1
  66. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js +4 -0
  67. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js.map +1 -1
  68. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -1
  69. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +4 -0
  70. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  72. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +5 -0
  73. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  74. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
  75. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +4 -0
  76. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
  77. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.d.ts.map +1 -1
  78. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js +4 -0
  79. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js.map +1 -1
  80. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts.map +1 -1
  81. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +4 -0
  82. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -1
  83. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  84. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +5 -0
  85. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  86. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  87. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +6 -0
  88. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  89. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts.map +1 -1
  90. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js +4 -0
  91. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js.map +1 -1
  92. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  93. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +4 -0
  94. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  95. package/payload/platform/plugins/memory/mcp/vitest.config.ts +10 -3
  96. package/payload/platform/plugins/outlook/PLUGIN.md +7 -0
  97. package/payload/platform/plugins/preval/.claude-plugin/plugin.json +1 -1
  98. package/payload/platform/plugins/preval/PLUGIN.md +22 -12
  99. package/payload/platform/plugins/preval/hooks/preval-prompt-required.sh +140 -0
  100. package/payload/platform/plugins/preval/skills/property-preval/SKILL.md +77 -19
  101. package/payload/platform/plugins/preval/skills/property-preval/references/__tests__/adherence.test.py +373 -0
  102. package/payload/platform/plugins/preval/skills/property-preval/references/render.py +390 -47
  103. package/payload/platform/plugins/preval/skills/property-preval/references/template-inputs.schema.json +32 -21
  104. package/payload/platform/plugins/preval/skills/property-preval/references/template.html +0 -1
  105. package/payload/platform/plugins/property-data/PLUGIN.md +22 -0
  106. package/payload/platform/plugins/replicate/PLUGIN.md +3 -0
  107. package/payload/platform/plugins/scheduling/PLUGIN.md +8 -0
  108. package/payload/platform/plugins/tasks/PLUGIN.md +14 -0
  109. package/payload/platform/plugins/telegram/PLUGIN.md +3 -0
  110. package/payload/platform/plugins/waitlist/PLUGIN.md +7 -0
  111. package/payload/platform/plugins/whatsapp/PLUGIN.md +12 -0
  112. package/payload/platform/plugins/workflows/PLUGIN.md +8 -0
  113. package/payload/platform/services/claude-session-manager/dist/index.js +39 -1
  114. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  115. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +21 -0
  116. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  117. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +148 -41
  118. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  119. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +23 -0
  120. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -0
  121. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +141 -0
  122. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -0
  123. package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts +13 -2
  124. package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts.map +1 -1
  125. package/payload/platform/services/claude-session-manager/dist/tool-surface.js +135 -37
  126. package/payload/platform/services/claude-session-manager/dist/tool-surface.js.map +1 -1
  127. package/payload/premium-plugins/real-agent/agents/compliance.md +1 -1
  128. package/payload/premium-plugins/real-agent/plugins/epc/PLUGIN.md +5 -0
  129. package/payload/premium-plugins/real-agent/plugins/loop/PLUGIN.md +27 -0
  130. package/payload/premium-plugins/real-agent/plugins/preval/.claude-plugin/plugin.json +1 -1
  131. package/payload/premium-plugins/real-agent/plugins/preval/PLUGIN.md +22 -12
  132. package/payload/premium-plugins/real-agent/plugins/preval/hooks/preval-prompt-required.sh +140 -0
  133. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +77 -19
  134. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/__tests__/adherence.test.py +373 -0
  135. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +390 -47
  136. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +32 -21
  137. package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template.html +0 -1
  138. package/payload/premium-plugins/real-agent/plugins/property-data/PLUGIN.md +22 -0
  139. package/payload/server/public/assets/{admin-DhN3G8W7.js → admin-ymReS1mN.js} +1 -1
  140. package/payload/server/public/assets/{data-B2ZVXOcE.js → data-D6U2e85B.js} +1 -1
  141. package/payload/server/public/assets/{graph-BAMGPHrK.js → graph-C274PN27.js} +1 -1
  142. package/payload/server/public/assets/{graph-labels-D9eBbvxo.js → graph-labels-DQFzd1FY.js} +1 -1
  143. package/payload/server/public/assets/{page-DjdVMWCz.js → page-D-OLjort.js} +1 -1
  144. package/payload/server/public/assets/{page-CV27Al6Z.js → page-DFFt9_Q0.js} +1 -1
  145. package/payload/server/public/data.html +3 -3
  146. package/payload/server/public/graph.html +3 -3
  147. package/payload/server/public/index.html +4 -4
  148. package/payload/server/server.js +1 -1
@@ -1,22 +1,36 @@
1
1
  // Tool-surface resolver.
2
2
  //
3
3
  // Loaded once at manager boot from a directory walk of every
4
- // platform/plugins/<name>/PLUGIN.md. The frontmatter of each PLUGIN.md
5
- // declares a list of tools; every tool entry must specify both `name` and
6
- // `publicAllowlist: <bool>`. The resolver produces two sets of fully
7
- // qualified tool names (`mcp__<plugin-dir>__<tool>`):
4
+ // platform/plugins/<name>/PLUGIN.md (plus premium-plugins/<bundle>/PLUGIN.md
5
+ // and premium-plugins/<bundle>/plugins/<sub>/PLUGIN.md). The frontmatter of
6
+ // each PLUGIN.md declares a list of tools; every tool entry must specify
7
+ // `name`, `publicAllowlist: <bool>`, and `adminAllowlist: <bool>`. The
8
+ // resolver produces three sets of fully qualified tool names
9
+ // (`mcp__<plugin-dir>__<tool>`):
8
10
  //
9
- // - admin: every tool, every plugin.
11
+ // - admin: subset where adminAllowlist === true.
10
12
  // - public: subset where publicAllowlist === true.
13
+ // - all: every declared tool across every plugin (both `tools:`
14
+ // entries and `hidden:` names). Used by the boot-time drift
15
+ // assertion that catches specialists naming tools no plugin
16
+ // produces.
11
17
  //
12
- // A tool missing the publicAllowlist field is a refusal-to-boot condition,
18
+ // The third surface specialists is declared on each specialist's
19
+ // agent `.md` frontmatter (`tools: <comma-list>`), not here. That list
20
+ // is consumed by Claude Code per-subagent; this resolver does not gate
21
+ // it. The drift assertion (see manager boot in `index.ts`) verifies the
22
+ // listed tools resolve to something — either an `all`-member tool, a
23
+ // CC-native tool, or a known MCP-bridge namespace.
24
+ //
25
+ // A tool missing either allowlist field is a refusal-to-boot condition,
13
26
  // not a silent default. The decision belongs at the tool's declaration
14
27
  // site; defaulting either way hides the question from the writer.
15
28
  //
16
29
  // The parser uses a hand-rolled YAML-ish walker over the frontmatter block
17
30
  // — there is no js-yaml dependency in this service today and the schema
18
31
  // is intentionally narrow (a sequence of `- name: <s>` + `publicAllowlist:
19
- // <bool>` pairs). Anything outside that shape is rejected loudly.
32
+ // <bool>` + `adminAllowlist: <bool>` triples). Anything outside that
33
+ // shape is rejected loudly.
20
34
  import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
21
35
  import { join } from 'node:path';
22
36
  export class ToolSurfaceError extends Error {
@@ -40,7 +54,7 @@ function extractFrontmatter(raw, pluginDir) {
40
54
  /** Parse the tools: block out of a PLUGIN.md frontmatter body. Returns an
41
55
  * empty array when the frontmatter has no tools: key or declares `tools: []`.
42
56
  * Throws ToolSurfaceError on any malformed entry (missing name, missing or
43
- * non-boolean publicAllowlist). */
57
+ * non-boolean publicAllowlist or adminAllowlist). */
44
58
  export function parseToolsBlock(frontmatter, pluginDir) {
45
59
  const lines = frontmatter.split(/\r?\n/);
46
60
  let i = 0;
@@ -71,6 +85,12 @@ export function parseToolsBlock(frontmatter, pluginDir) {
71
85
  }
72
86
  throw new ToolSurfaceError('public-allowlist-not-boolean', `${pluginDir}/${current.name} — publicAllowlist must be a YAML boolean (true|false), got ${JSON.stringify(current.publicAllowlist)}`);
73
87
  }
88
+ if (typeof current.adminAllowlist !== 'boolean') {
89
+ if (current.adminAllowlist === undefined) {
90
+ throw new ToolSurfaceError('admin-allowlist-missing', `${pluginDir}/${current.name} — every tool must declare adminAllowlist: <true|false>`);
91
+ }
92
+ throw new ToolSurfaceError('admin-allowlist-not-boolean', `${pluginDir}/${current.name} — adminAllowlist must be a YAML boolean (true|false), got ${JSON.stringify(current.adminAllowlist)}`);
93
+ }
74
94
  out.push(current);
75
95
  current = null;
76
96
  };
@@ -93,9 +113,9 @@ export function parseToolsBlock(frontmatter, pluginDir) {
93
113
  if (orphanListItem) {
94
114
  throw new ToolSurfaceError('tool-name-missing', `${pluginDir}/PLUGIN.md tool entry lacks a name field`);
95
115
  }
96
- const allowlist = line.match(/^\s+publicAllowlist:\s*(.+?)\s*$/);
97
- if (allowlist && current) {
98
- const v = allowlist[1].trim();
116
+ const publicAllow = line.match(/^\s+publicAllowlist:\s*(.+?)\s*$/);
117
+ if (publicAllow && current) {
118
+ const v = publicAllow[1].trim();
99
119
  if (v === 'true')
100
120
  current.publicAllowlist = true;
101
121
  else if (v === 'false')
@@ -104,13 +124,54 @@ export function parseToolsBlock(frontmatter, pluginDir) {
104
124
  current.publicAllowlist = v;
105
125
  continue;
106
126
  }
127
+ const adminAllow = line.match(/^\s+adminAllowlist:\s*(.+?)\s*$/);
128
+ if (adminAllow && current) {
129
+ const v = adminAllow[1].trim();
130
+ if (v === 'true')
131
+ current.adminAllowlist = true;
132
+ else if (v === 'false')
133
+ current.adminAllowlist = false;
134
+ else
135
+ current.adminAllowlist = v;
136
+ continue;
137
+ }
107
138
  // Other per-entry fields (e.g. nested name on its own line, or unknown
108
- // keys we don't care about) are ignored — only name and publicAllowlist
109
- // affect the resolver.
139
+ // keys we don't care about) are ignored — only name, publicAllowlist,
140
+ // and adminAllowlist affect the resolver.
110
141
  }
111
142
  flush();
112
143
  return out;
113
144
  }
145
+ /** Parse the `hidden:` block out of a PLUGIN.md frontmatter body. Returns
146
+ * the list of tool names declared as hidden — these tools are served by
147
+ * the plugin's MCP server but excluded from agent-visible enumerations.
148
+ * They count for the `all` surface so specialists may still name them
149
+ * in their `tools:` frontmatter without tripping the drift assertion. */
150
+ export function parseHiddenBlock(frontmatter) {
151
+ const lines = frontmatter.split(/\r?\n/);
152
+ let i = 0;
153
+ while (i < lines.length) {
154
+ if (/^hidden:\s*$/.test(lines[i])) {
155
+ i++;
156
+ break;
157
+ }
158
+ i++;
159
+ }
160
+ if (i >= lines.length)
161
+ return [];
162
+ const out = [];
163
+ for (; i < lines.length; i++) {
164
+ const line = lines[i];
165
+ if (line.length === 0)
166
+ continue;
167
+ if (/^[A-Za-z_]/.test(line))
168
+ break;
169
+ const item = line.match(/^\s+-\s+(.+?)\s*$/);
170
+ if (item)
171
+ out.push(item[1]);
172
+ }
173
+ return out;
174
+ }
114
175
  /** Parse the optional `mcp:` block out of a PLUGIN.md frontmatter body.
115
176
  * Returns null when the frontmatter has no `mcp:` key. Throws on a malformed
116
177
  * shape (missing command, args not a list, env not key:value pairs). The
@@ -192,7 +253,7 @@ export function parseMcpBlock(frontmatter, pluginDir) {
192
253
  return desc;
193
254
  }
194
255
  /** Walk a plugins root directory, parse every <name>/PLUGIN.md, and return
195
- * the {admin, public, mcpServers} sets. Throws on the first malformed
256
+ * the {admin, public, all, mcpServers} sets. Throws on the first malformed
196
257
  * manifest — the caller (the manager boot) must exit non-zero so the
197
258
  * operator sees the gap.
198
259
  *
@@ -209,42 +270,79 @@ export function loadToolSurface(pluginsRoots) {
209
270
  }
210
271
  const admin = new Set();
211
272
  const pub = new Set();
273
+ const all = new Set();
212
274
  const mcpServers = new Map();
275
+ /** Process one candidate plugin directory. The plugin's MCP namespace
276
+ * is the directory basename; sub-plugins under a premium bundle use
277
+ * their own basename, not the bundle's. */
278
+ const loadOne = (pluginsRoot, plugin) => {
279
+ const pluginDir = join(pluginsRoot, plugin);
280
+ try {
281
+ if (!statSync(pluginDir).isDirectory())
282
+ return;
283
+ }
284
+ catch {
285
+ return;
286
+ }
287
+ const manifestPath = join(pluginDir, 'PLUGIN.md');
288
+ if (!existsSync(manifestPath))
289
+ return;
290
+ const raw = readFileSync(manifestPath, 'utf-8');
291
+ const frontmatter = extractFrontmatter(raw, plugin);
292
+ const tools = parseToolsBlock(frontmatter, plugin);
293
+ if (tools.length === 0)
294
+ return;
295
+ const mcp = parseMcpBlock(frontmatter, plugin);
296
+ if (!mcp) {
297
+ throw new ToolSurfaceError('mcp-block-missing', `${plugin}/PLUGIN.md declares tools but no mcp: block; spawns would --allowed-tools mcp__${plugin}__* with no registered server`);
298
+ }
299
+ if (mcpServers.has(plugin)) {
300
+ throw new ToolSurfaceError('mcp-plugin-duplicate', `${plugin} declared by more than one plugins root`);
301
+ }
302
+ mcpServers.set(plugin, mcp);
303
+ for (const t of tools) {
304
+ const qualified = `mcp__${plugin}__${t.name}`;
305
+ all.add(qualified);
306
+ if (t.adminAllowlist)
307
+ admin.add(qualified);
308
+ if (t.publicAllowlist)
309
+ pub.add(qualified);
310
+ }
311
+ // Hidden tools share the plugin's MCP server but stay out of the
312
+ // admin/public allowlists. They count for `all` so specialists may
313
+ // name them without tripping the drift assertion.
314
+ for (const h of parseHiddenBlock(frontmatter)) {
315
+ all.add(`mcp__${plugin}__${h}`);
316
+ }
317
+ };
213
318
  for (const root of roots) {
214
319
  const entries = readdirSync(root).sort();
215
320
  for (const plugin of entries) {
216
321
  const pluginDir = join(root, plugin);
322
+ // Walk the top-level plugin manifest (platform/plugins/<name>/PLUGIN.md
323
+ // or premium-plugins/<bundle>/PLUGIN.md).
324
+ loadOne(root, plugin);
325
+ // A premium bundle may also nest sibling plugins under
326
+ // `<bundle>/plugins/<sub>/PLUGIN.md`. The bundle itself is often a
327
+ // wrapper without tools while the sub-plugins carry real namespaces
328
+ // (loop, property-data, epc under real-agent). The MCP namespace
329
+ // ends up as `mcp__<sub>__*` — the bundle name does not appear in
330
+ // tool names.
331
+ const subRoot = join(pluginDir, 'plugins');
332
+ let subEntries;
217
333
  try {
218
- if (!statSync(pluginDir).isDirectory())
334
+ if (!statSync(subRoot).isDirectory())
219
335
  continue;
336
+ subEntries = readdirSync(subRoot).sort();
220
337
  }
221
338
  catch {
222
339
  continue;
223
340
  }
224
- const manifestPath = join(pluginDir, 'PLUGIN.md');
225
- if (!existsSync(manifestPath))
226
- continue;
227
- const raw = readFileSync(manifestPath, 'utf-8');
228
- const frontmatter = extractFrontmatter(raw, plugin);
229
- const tools = parseToolsBlock(frontmatter, plugin);
230
- if (tools.length === 0)
231
- continue;
232
- const mcp = parseMcpBlock(frontmatter, plugin);
233
- if (!mcp) {
234
- throw new ToolSurfaceError('mcp-block-missing', `${plugin}/PLUGIN.md declares tools but no mcp: block; spawns would --allowed-tools mcp__${plugin}__* with no registered server`);
235
- }
236
- if (mcpServers.has(plugin)) {
237
- throw new ToolSurfaceError('mcp-plugin-duplicate', `${plugin} declared by more than one plugins root`);
238
- }
239
- mcpServers.set(plugin, mcp);
240
- for (const t of tools) {
241
- const qualified = `mcp__${plugin}__${t.name}`;
242
- admin.add(qualified);
243
- if (t.publicAllowlist)
244
- pub.add(qualified);
341
+ for (const sub of subEntries) {
342
+ loadOne(subRoot, sub);
245
343
  }
246
344
  }
247
345
  }
248
- return { admin, public: pub, mcpServers };
346
+ return { admin, public: pub, all, mcpServers };
249
347
  }
250
348
  //# sourceMappingURL=tool-surface.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-surface.js","sourceRoot":"","sources":["../src/tool-surface.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tool-surface.js","sourceRoot":"","sources":["../src/tool-surface.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ name: compliance
3
3
  description: "UK estate agency legal and compliance guidance — legislation, regulation, codes of practice, AML obligations, consumer protection, data protection, material information, advertising standards, and lettings law. Delegate when the task involves regulatory compliance, legal obligations, listing audit against legislative requirements, AML/KYC procedures, data handling questions, advertising rules, or any question about what the law requires of an estate agent or letting agent."
4
4
  summary: "Your compliance officer — advises on UK estate agency law, audits listings against regulatory requirements, and cites current legislation."
5
5
  model: claude-opus-4-7
6
- tools: mcp__loop__loop-key-register, mcp__loop__loop-key-deregister, mcp__loop__loop-key-list, mcp__loop__loop-people-search, mcp__loop__loop-people-detail, mcp__loop__loop-property-search, mcp__loop__loop-property-detail, mcp__loop__loop-property-listed, mcp__loop__loop-property-sold, mcp__loop__loop-property-viewing, mcp__loop__loop-property-callback, mcp__loop__loop-property-information, mcp__loop__loop-viewing-search, mcp__loop__loop-viewing-detail, mcp__loop__loop-viewing-create, mcp__loop__loop-viewing-update, mcp__loop__loop-feedback-get, mcp__loop__loop-feedback-submit, mcp__loop__loop-team-info, mcp__loop__loop-team-availability, mcp__loop__loop-marketing-match, mcp__loop__loop-marketing-match-batch, mcp__loop__loop-marketing-match-request, mcp__loop__loop-marketing-enquiry, mcp__loop__loop-auto-responder, mcp__loop__loop-customer-preferences, mcp__loop__loop-supplier, mcp__memory__memory-search, mcp__deep-research__perplexity-search
6
+ tools: mcp__loop__loop-key-register, mcp__loop__loop-key-deregister, mcp__loop__loop-key-list, mcp__loop__loop-people-search, mcp__loop__loop-people-detail, mcp__loop__loop-property-search, mcp__loop__loop-property-detail, mcp__loop__loop-property-listed, mcp__loop__loop-property-sold, mcp__loop__loop-property-viewing, mcp__loop__loop-property-callback, mcp__loop__loop-property-information, mcp__loop__loop-viewing-search, mcp__loop__loop-viewing-detail, mcp__loop__loop-viewing-create, mcp__loop__loop-viewing-update, mcp__loop__loop-feedback-get, mcp__loop__loop-feedback-submit, mcp__loop__loop-team-info, mcp__loop__loop-team-availability, mcp__loop__loop-marketing-match, mcp__loop__loop-marketing-match-batch, mcp__loop__loop-marketing-match-request, mcp__loop__loop-marketing-enquiry, mcp__loop__loop-auto-responder, mcp__loop__loop-customer-preferences, mcp__loop__loop-supplier, mcp__memory__memory-search
7
7
  ---
8
8
 
9
9
  # Compliance
@@ -4,14 +4,19 @@ description: "gov.uk EPC register — by-address and by-UPRN lookup of the most
4
4
  tools:
5
5
  - name: epc-key-register
6
6
  publicAllowlist: false
7
+ adminAllowlist: false
7
8
  - name: epc-key-deregister
8
9
  publicAllowlist: false
10
+ adminAllowlist: false
9
11
  - name: epc-key-list
10
12
  publicAllowlist: false
13
+ adminAllowlist: false
11
14
  - name: epc-lookup-by-address
12
15
  publicAllowlist: false
16
+ adminAllowlist: false
13
17
  - name: epc-lookup-by-uprn
14
18
  publicAllowlist: false
19
+ adminAllowlist: false
15
20
  always: false
16
21
  metadata: {"platform":{"optional":true,"embed":["admin"]}}
17
22
  mcp:
@@ -4,58 +4,85 @@ description: "Loop CRM integration — full read/write access to all 8 resource
4
4
  tools:
5
5
  - name: loop-key-register
6
6
  publicAllowlist: false
7
+ adminAllowlist: false
7
8
  - name: loop-key-deregister
8
9
  publicAllowlist: false
10
+ adminAllowlist: false
9
11
  - name: loop-key-list
10
12
  publicAllowlist: false
13
+ adminAllowlist: false
11
14
  - name: loop-people-search
12
15
  publicAllowlist: false
16
+ adminAllowlist: false
13
17
  - name: loop-people-detail
14
18
  publicAllowlist: false
19
+ adminAllowlist: false
15
20
  - name: loop-property-search
16
21
  publicAllowlist: false
22
+ adminAllowlist: false
17
23
  - name: loop-property-detail
18
24
  publicAllowlist: false
25
+ adminAllowlist: false
19
26
  - name: loop-property-listed
20
27
  publicAllowlist: false
28
+ adminAllowlist: false
21
29
  - name: loop-property-sold
22
30
  publicAllowlist: false
31
+ adminAllowlist: false
23
32
  - name: loop-property-viewing
24
33
  publicAllowlist: false
34
+ adminAllowlist: false
25
35
  - name: loop-property-callback
26
36
  publicAllowlist: false
37
+ adminAllowlist: false
27
38
  - name: loop-property-information
28
39
  publicAllowlist: false
40
+ adminAllowlist: false
29
41
  - name: loop-viewing-search
30
42
  publicAllowlist: false
43
+ adminAllowlist: false
31
44
  - name: loop-viewing-detail
32
45
  publicAllowlist: false
46
+ adminAllowlist: false
33
47
  - name: loop-viewing-create
34
48
  publicAllowlist: false
49
+ adminAllowlist: false
35
50
  - name: loop-viewing-update
36
51
  publicAllowlist: false
52
+ adminAllowlist: false
37
53
  - name: loop-feedback-get
38
54
  publicAllowlist: false
55
+ adminAllowlist: false
39
56
  - name: loop-feedback-submit
40
57
  publicAllowlist: false
58
+ adminAllowlist: false
41
59
  - name: loop-team-info
42
60
  publicAllowlist: false
61
+ adminAllowlist: false
43
62
  - name: loop-team-availability
44
63
  publicAllowlist: false
64
+ adminAllowlist: false
45
65
  - name: loop-marketing-match
46
66
  publicAllowlist: false
67
+ adminAllowlist: false
47
68
  - name: loop-marketing-match-batch
48
69
  publicAllowlist: false
70
+ adminAllowlist: false
49
71
  - name: loop-marketing-match-request
50
72
  publicAllowlist: false
73
+ adminAllowlist: false
51
74
  - name: loop-marketing-enquiry
52
75
  publicAllowlist: false
76
+ adminAllowlist: false
53
77
  - name: loop-auto-responder
54
78
  publicAllowlist: false
79
+ adminAllowlist: false
55
80
  - name: loop-customer-preferences
56
81
  publicAllowlist: false
82
+ adminAllowlist: false
57
83
  - name: loop-supplier
58
84
  publicAllowlist: false
85
+ adminAllowlist: false
59
86
  skills:
60
87
  - skills/tone-matched-drafter/SKILL.md
61
88
  - skills/priority-ranker/SKILL.md
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "real-agent-preval",
3
- "description": "Single-address pre-valuation pitch pack. One full UK address in, 5-page A4 PDF out (cover · subject valuation · market context · comparables · recently with <brand>). Consumes the real-agent-property-data and real-agent-epc MCPs — no MCP tools of its own.",
3
+ "description": "Single-address pre-valuation pitch pack. One full UK address in, 5-page A4 PDF out (cover · subject valuation · market context · comparables · recently with <brand>). Consumes the real-agent-property-data MCP — no MCP tools of its own. EPC certificate fields (total_floor_area_m2, current_energy_rating, lodgement_date) are pasted by the operator into inputs.json before invocation.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,8 +1,12 @@
1
1
  ---
2
2
  name: real-agent-preval
3
- description: "Single-address pre-valuation pitch pack. One full UK address in, 5-page A4 PDF out (cover · subject valuation · market context · comparables · recently with <brand>). Consumes the real-agent-property-data and real-agent-epc MCPs — no MCP tools of its own."
3
+ description: "Single-address pre-valuation pitch pack. One full UK address in, 5-page A4 PDF out (cover · subject valuation · market context · comparables · recently with <brand>). Consumes the real-agent-property-data MCP — no MCP tools of its own. EPC certificate fields (total_floor_area_m2, current_energy_rating, lodgement_date) are pasted by the operator into inputs.json before invocation."
4
4
  skills:
5
5
  - skills/property-preval/SKILL.md
6
+ hooks:
7
+ PreToolUse:
8
+ - matcher: "Write|Edit|MultiEdit"
9
+ command: "${CLAUDE_PLUGIN_ROOT}/hooks/preval-prompt-required.sh"
6
10
  always: false
7
11
  metadata: {"platform":{"optional":true,"embed":["admin"]}}
8
12
  ---
@@ -16,30 +20,36 @@ Consumer sub-plugin. No MCP server, no new tools — only a skill (`property-pre
16
20
  A 5-page A4 PDF and the source HTML, written to `~/Documents/Maxy/preval/<postcode-compact>-<YYYY-MM-DD>/`:
17
21
 
18
22
  - **Page 1 — Cover.** Full-bleed subject hero photo with gradient scrim. Light brand wordmark top-left. Address in display-italic. Postcode chip. Four-up meta strip (Report · Address · Postcode · Generated). Lifted from the `property-market-report` cover contract.
19
- - **Page 2 — Subject valuation.** KPI strip (floor area, bedrooms, EPC rating, type). Valuation panel: headline range = `subject.sqft × sold £/sqft 70pc band` (Land Registry), asking £/sqft cross-check, operator adjustments (`subject.adjustments[]`) shown as a stacked list with colour-coded percentages, final indicative range pill. Falls back to "Estimate requires floor area" benchmark-only mode when sqft is missing.
23
+ - **Page 2 — Subject valuation.** KPI strip (floor area, bedrooms, EPC rating, type). Valuation panel: headline range = `derived sqft × sold £/sqft 70pc band` (Land Registry), asking £/sqft cross-check, operator adjustments (`subject.adjustments[]`) shown as a stacked list with colour-coded percentages, final indicative range pill. Floor area is derived from `subject.total_floor_area_m2` (operator-pasted from the EPC certificate) — a null value aborts the run with `epc-certificate-not-supplied`.
20
24
  - **Page 3 — Market context.** Market summary headline + paragraphs with drop cap. KPI strip (avg asking £, avg sold £, £/sqft). Price-growth panel (YoY) + demand pill with gauge. 7-year sparkline of £ and £/sqft. Same patterns as `property-market-report`.
21
25
  - **Page 4 — Comparables.** Full-width Land Registry sold-transactions table (≤ 6 rows), stacked above a full-width live-asking-comparables tile grid (≤ 4 tiles). Never side-by-side — corrects the cramped two-column layout in v1 preval.
22
26
  - **Page 5 — Recently with `<brand>` + sources.** Operator-curated `agent_listings[]` rendered as a clickable tile grid with status badges (Sold / Under offer / For sale), then the sources & methodology block. The vendor reads this page and knows who to call.
23
27
 
24
28
  ## Tool dependencies
25
29
 
26
- The skill fans out to **13** MCP calls in parallel:
27
-
28
- - **12 PropertyData** tools (postcode-keyed): `prices`, `prices-per-sqf`, `sold-prices`, `sold-prices-per-sqf`, `growth`, `growth-psf`, `demand`, `demand-rent`, `property-types`, `crime`, `flood-risk`, `council-tax`.
29
- - **1 EPC** tool (address-keyed): `epc-lookup-by-address` for subject floor area + energy rating + lodgement date.
30
+ The skill fans out to **12** PropertyData MCP calls in parallel (postcode-keyed): `prices`, `prices-per-sqf`, `sold-prices`, `sold-prices-per-sqf`, `growth`, `growth-psf`, `demand`, `demand-rent`, `property-types`, `crime`, `flood-risk`, `council-tax`.
30
31
 
31
32
  `property-data-address-match-uprn` and `property-data-planning-applications` are deliberately not called (CRM hygiene and dev-precedent reports, respectively).
32
33
 
34
+ The three EPC certificate fields the page-2 KPI strip needs — `total_floor_area_m2`, `current_energy_rating`, `lodgement_date` — are pasted by the operator into `subject` in `inputs.json` before invocation. There is no MCP fan-out for EPC; the PreToolUse hook and `render.py` both abort with `epc-certificate-not-supplied` if any of the three fields is null.
35
+
33
36
  ## Failure handling
34
37
 
35
- - **PropertyData key-not-registered** abort with the `property-data-key-register` prompt. Market data is essential; the pack cannot render without it.
36
- - **EPC unavailable** — any of `key-not-registered`, `http-404 (no record)`, `http-429`, `http-500`, network failure, or EPC MCP not configured — is treated as a single class. The skill prompts the operator with the specific cause and offers two paths:
37
- 1. Paste `subject.sqft` directly pack renders with EPC KPI cells showing "—" + cause sub-label, but the valuation maths still runs on the pasted sqft.
38
- 2. Declinevaluation panel switches to "Estimate requires floor area" benchmark-only mode; the pack still renders all five pages.
39
- - **PropertyData http-429** back off and retry only the throttled tool (4 calls per 10 seconds upstream throttle).
38
+ Every failure is fail-loud. The skill emits `[preval] aborted reason=<cause> …` and exits 2; no partial `index.html` or PDF is left on disk.
39
+
40
+ - **PropertyData `key-not-registered`**abort `reason=property-data-key-not-registered` with the `property-data-key-register` remediation hint.
41
+ - **PropertyData `http-429`** back off and retry the single throttled tool once. A second `http-429` aborts `reason=property-data-rate-limited`.
42
+ - **PropertyData `http-5xx` / network failure** abort `reason=property-data-unavailable`.
43
+ - **Any of the 12 PropertyData log lines missing** within 60s of `[preval] start` → abort `reason=observability-missing tool=<name>`. A silent tool drop must never proceed to render.
44
+ - **Subject EPC certificate field missing** — any of `subject.total_floor_area_m2`, `subject.current_energy_rating`, `subject.lodgement_date` is null → abort `reason=epc-certificate-not-supplied`. The operator pastes all three from the EPC certificate into `inputs.json` before re-invoking. There is no mid-run prompt and no MCP fallback.
45
+ - See `skills/property-preval/SKILL.md` § Hard breakpoints for the full table of abort causes.
40
46
 
41
47
  ## External dependencies
42
48
 
43
49
  - Requires `real-agent-property-data` enabled on the account with a registered PropertyData API key (`property-data-key-register`).
44
- - Requires `real-agent-epc` enabled with a registered EPC API key (`epc-key-register`) failure modes above; not strictly blocking thanks to the paste-sqft fallback.
50
+ - Requires the operator to paste the three EPC certificate fields (`total_floor_area_m2`, `current_energy_rating`, `lodgement_date`) into `subject` in `inputs.json` before invocation. There is no automated EPC lookup.
45
51
  - Requires Google Chrome at `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` for headless `--print-to-pdf`. Same Chrome dependency as `property-market-report`.
52
+
53
+ ## Hooks
54
+
55
+ - `hooks/preval-prompt-required.sh` — PreToolUse gate on `Write|Edit|MultiEdit` of `**/preval/**/inputs.json`. Blocks (exit 2) before the file lands on disk if any of `epc-certificate-not-supplied`, `agent-listings-invented`, `market-summary-invented`, or `brand-invented` applies. The same five causes (plus `cover-hero-not-subject`) are re-checked by `render.py`'s `_assert_inputs` gate — defence in depth against silent fallback completions. Paths outside `preval/inputs.json` pass through.
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook — defence-in-depth gate for property-preval's inputs.json.
3
+ #
4
+ # Fires on Write/Edit when the target path ends in `/preval/.../inputs.json`.
5
+ # Blocks (exit 2) before the payload reaches disk if any of the five named
6
+ # adherence violations is present. The render.py validator catches the same
7
+ # violations downstream — this hook stops the bad payload one step earlier,
8
+ # while the agent still has the opportunity to prompt the operator.
9
+ #
10
+ # Aborts named here mirror the AdherenceError causes raised by render.py:
11
+ # - epc-certificate-not-supplied
12
+ # - agent-listings-invented
13
+ # - market-summary-invented
14
+ # - brand-invented
15
+ #
16
+ # (cover-hero-not-subject is checked by render.py only — the slug heuristic
17
+ # needs the on-disk path to be inspectable, which isn't reliable from the
18
+ # tool args alone.)
19
+ #
20
+ # Error philosophy: fail closed on the four named contract violations; fail
21
+ # open on anything else (missing python3, malformed stdin, file not under
22
+ # preval/). Failing open lets unrelated tool calls proceed; the named
23
+ # violations are the only ones the hook owns.
24
+
25
+ set -u
26
+
27
+ if [ -t 0 ]; then
28
+ exit 0
29
+ fi
30
+
31
+ if ! command -v python3 >/dev/null 2>&1; then
32
+ cat >/dev/null
33
+ echo "[preval-prompt-required] action=fail reason=python3-missing" >&2
34
+ exit 0
35
+ fi
36
+
37
+ INPUT=$(cat)
38
+
39
+ INPUT="$INPUT" python3 <<'PY'
40
+ import json, os, sys
41
+
42
+ INPUT = os.environ.get("INPUT", "")
43
+
44
+ def passthrough(reason):
45
+ print(f"[preval-prompt-required] action=passthrough reason={reason}", file=sys.stderr)
46
+ sys.exit(0)
47
+
48
+ def block(cause, remediation):
49
+ print(f"[preval-prompt-required] action=block cause={cause}", file=sys.stderr)
50
+ print(f"[preval] {cause}: {remediation}", file=sys.stderr)
51
+ sys.exit(2)
52
+
53
+ try:
54
+ payload = json.loads(INPUT) if INPUT else {}
55
+ except Exception:
56
+ passthrough("malformed-stdin")
57
+
58
+ tool_name = payload.get("tool_name") or ""
59
+ if tool_name not in ("Write", "Edit", "MultiEdit"):
60
+ passthrough(f"tool={tool_name}")
61
+
62
+ tool_input = payload.get("tool_input") or {}
63
+ file_path = tool_input.get("file_path") or ""
64
+ norm = file_path.replace("\\", "/").lower()
65
+ if "/preval/" not in norm or not norm.endswith("/inputs.json"):
66
+ passthrough("path-not-preval-inputs")
67
+
68
+ if tool_name == "Write":
69
+ content = tool_input.get("content") or ""
70
+ elif tool_name == "Edit":
71
+ content = tool_input.get("new_string") or ""
72
+ else: # MultiEdit
73
+ edits = tool_input.get("edits") or []
74
+ content = edits[-1].get("new_string", "") if edits else ""
75
+
76
+ try:
77
+ data = json.loads(content)
78
+ except Exception:
79
+ passthrough("content-not-json")
80
+
81
+ if not isinstance(data, dict):
82
+ passthrough("content-not-object")
83
+
84
+ overrides = data.get("agent_overrides") or {}
85
+
86
+ subject = data.get("subject") or {}
87
+ missing_epc = [
88
+ f for f in ("total_floor_area_m2", "current_energy_rating", "lodgement_date")
89
+ if not subject.get(f)
90
+ ]
91
+ if missing_epc:
92
+ block(
93
+ "epc-certificate-not-supplied",
94
+ f"subject is missing EPC certificate field(s) {missing_epc}. "
95
+ "Paste total_floor_area_m2, current_energy_rating, and lodgement_date "
96
+ "from the EPC certificate into subject before re-invoking. "
97
+ "No mid-run prompt — the operator supplies the certificate before invocation.",
98
+ )
99
+
100
+ if not overrides.get("listings_status_curated"):
101
+ for listing in data.get("agent_listings") or []:
102
+ origin = (listing or {}).get("origin")
103
+ if not origin or origin == "operator":
104
+ continue
105
+ try:
106
+ src_path = os.path.expanduser(str(origin))
107
+ if not os.path.isfile(src_path):
108
+ continue
109
+ with open(src_path) as f:
110
+ source = json.load(f)
111
+ except Exception:
112
+ continue
113
+ if source.get("status") in (None, "") and listing.get("status"):
114
+ block(
115
+ "agent-listings-invented",
116
+ f"agent_listings entry {listing.get('slug')} has status "
117
+ f"'{listing['status']}' but source {src_path} carries status:null. "
118
+ "Prompt the operator and set agent_overrides.listings_status_curated: true.",
119
+ )
120
+
121
+ ms = data.get("market_summary") or {}
122
+ if ms.get("origin") not in ("operator", "operator-edited-llm-stub"):
123
+ block(
124
+ "market-summary-invented",
125
+ "market_summary.origin must be 'operator' or 'operator-edited-llm-stub'. "
126
+ "Have the operator draft or accept the paragraphs before writing inputs.json.",
127
+ )
128
+
129
+ brand = data.get("brand") or {}
130
+ if brand.get("name") and not overrides.get("brand_selected_by_operator"):
131
+ block(
132
+ "brand-invented",
133
+ f"brand.name ('{brand['name']}') was set without "
134
+ "agent_overrides.brand_selected_by_operator: true. Ask the operator which "
135
+ "estate agent is pitching; presence of a DESIGN.md on disk does not authorise selection.",
136
+ )
137
+
138
+ print("[preval-prompt-required] action=allow", file=sys.stderr)
139
+ sys.exit(0)
140
+ PY