@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.
- package/dist/__tests__/installer-specialist-registration.test.js +112 -0
- package/dist/index.js +22 -0
- package/dist/specialist-registration.js +78 -0
- package/package.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +22 -0
- package/payload/platform/plugins/admin/hooks/__tests__/turn-completed-graph-write.test.sh +50 -13
- package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +22 -21
- package/payload/platform/plugins/contacts/PLUGIN.md +9 -0
- package/payload/platform/plugins/docs/references/admin-session.md +55 -7
- package/payload/platform/plugins/email/PLUGIN.md +9 -0
- package/payload/platform/plugins/epc/PLUGIN.md +5 -0
- package/payload/platform/plugins/loop/PLUGIN.md +27 -0
- package/payload/platform/plugins/memory/PLUGIN.md +37 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +3 -3
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +137 -75
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +0 -17
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +32 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +65 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +182 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js +8 -8
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.js +241 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js +9 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +10 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +5 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +5 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +5 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +5 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +10 -3
- package/payload/platform/plugins/outlook/PLUGIN.md +7 -0
- package/payload/platform/plugins/preval/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/preval/PLUGIN.md +22 -12
- package/payload/platform/plugins/preval/hooks/preval-prompt-required.sh +140 -0
- package/payload/platform/plugins/preval/skills/property-preval/SKILL.md +77 -19
- package/payload/platform/plugins/preval/skills/property-preval/references/__tests__/adherence.test.py +373 -0
- package/payload/platform/plugins/preval/skills/property-preval/references/render.py +390 -47
- package/payload/platform/plugins/preval/skills/property-preval/references/template-inputs.schema.json +32 -21
- package/payload/platform/plugins/preval/skills/property-preval/references/template.html +0 -1
- package/payload/platform/plugins/property-data/PLUGIN.md +22 -0
- package/payload/platform/plugins/replicate/PLUGIN.md +3 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +8 -0
- package/payload/platform/plugins/tasks/PLUGIN.md +14 -0
- package/payload/platform/plugins/telegram/PLUGIN.md +3 -0
- package/payload/platform/plugins/waitlist/PLUGIN.md +7 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +12 -0
- package/payload/platform/plugins/workflows/PLUGIN.md +8 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +39 -1
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +21 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +148 -41
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +23 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +141 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts +13 -2
- package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/tool-surface.js +135 -37
- package/payload/platform/services/claude-session-manager/dist/tool-surface.js.map +1 -1
- package/payload/premium-plugins/real-agent/agents/compliance.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/epc/PLUGIN.md +5 -0
- package/payload/premium-plugins/real-agent/plugins/loop/PLUGIN.md +27 -0
- package/payload/premium-plugins/real-agent/plugins/preval/.claude-plugin/plugin.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/preval/PLUGIN.md +22 -12
- package/payload/premium-plugins/real-agent/plugins/preval/hooks/preval-prompt-required.sh +140 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +77 -19
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/__tests__/adherence.test.py +373 -0
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +390 -47
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +32 -21
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template.html +0 -1
- package/payload/premium-plugins/real-agent/plugins/property-data/PLUGIN.md +22 -0
- package/payload/server/public/assets/{admin-DhN3G8W7.js → admin-ymReS1mN.js} +1 -1
- package/payload/server/public/assets/{data-B2ZVXOcE.js → data-D6U2e85B.js} +1 -1
- package/payload/server/public/assets/{graph-BAMGPHrK.js → graph-C274PN27.js} +1 -1
- package/payload/server/public/assets/{graph-labels-D9eBbvxo.js → graph-labels-DQFzd1FY.js} +1 -1
- package/payload/server/public/assets/{page-DjdVMWCz.js → page-D-OLjort.js} +1 -1
- package/payload/server/public/assets/{page-CV27Al6Z.js → page-DFFt9_Q0.js} +1 -1
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- 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
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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:
|
|
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
|
-
//
|
|
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>`
|
|
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
|
|
97
|
-
if (
|
|
98
|
-
const v =
|
|
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
|
|
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(
|
|
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
|
|
225
|
-
|
|
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":"AAAA,yBAAyB;AACzB,EAAE;AACF,6DAA6D;AAC7D,uEAAuE;AACvE,
|
|
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
|
|
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
|
|
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
|
|
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 = `
|
|
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 **
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- **PropertyData http-
|
|
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
|
|
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
|