@rubytech/create-maxy 1.0.753 → 1.0.756

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 (27) hide show
  1. package/package.json +1 -1
  2. package/payload/premium-plugins/real-agency/plugins/loop/PLUGIN.md +11 -2
  3. package/payload/premium-plugins/real-agency/plugins/loop/mcp/package-lock.json +1273 -34
  4. package/payload/premium-plugins/real-agency/plugins/loop/mcp/package.json +4 -2
  5. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/__tests__/loop-swagger.snapshot.json +26467 -0
  6. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/__tests__/swagger-write-coverage.test.ts +153 -0
  7. package/payload/premium-plugins/real-agency/plugins/loop/mcp/tsconfig.json +2 -1
  8. package/payload/premium-plugins/real-agency/plugins/loop/mcp/vitest.config.ts +9 -0
  9. package/payload/server/chunk-26QT2HEQ.js +9483 -0
  10. package/payload/server/maxy-edge.js +1 -1
  11. package/payload/server/public/assets/{Checkbox-CoHxu6iQ.js → Checkbox-C24nM41g.js} +1 -1
  12. package/payload/server/public/assets/{admin-B-dILI0e.js → admin-CTbpNMNG.js} +2 -2
  13. package/payload/server/public/assets/{data-D3SSQe8g.js → data-Bfj5UBbk.js} +1 -1
  14. package/payload/server/public/assets/{file-Bsc6G_ml.js → file-CLa5WeSl.js} +1 -1
  15. package/payload/server/public/assets/{graph-DNYh-ftf.js → graph-B6YWFq_S.js} +1 -1
  16. package/payload/server/public/assets/{house-7AA8vK65.js → house-BSa2PlqY.js} +1 -1
  17. package/payload/server/public/assets/{jsx-runtime-Bf6eMlTM.css → jsx-runtime-CW8OiWT9.css} +1 -1
  18. package/payload/server/public/assets/{public-ChayZXZ8.js → public-C_mAXOnw.js} +1 -1
  19. package/payload/server/public/assets/{share-2-CYBOKnMb.js → share-2-Sy-qhrFk.js} +1 -1
  20. package/payload/server/public/assets/{useVoiceRecorder-Blz894s4.js → useVoiceRecorder-CAf6yMpK.js} +1 -1
  21. package/payload/server/public/assets/{x-BnOIJSto.js → x-BMWxX7y6.js} +1 -1
  22. package/payload/server/public/data.html +6 -6
  23. package/payload/server/public/graph.html +7 -7
  24. package/payload/server/public/index.html +8 -8
  25. package/payload/server/public/public.html +5 -5
  26. package/payload/server/server.js +45 -16
  27. /package/payload/server/public/assets/{jsx-runtime-2IoeIcNG.js → jsx-runtime-CFleUYzT.js} +0 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Anti-regression: every Loop V2 swagger write endpoint maps to a
3
+ * registered MCP tool whose source file references the path's stable
4
+ * prefix. Catches three drift modes pre-publish:
5
+ *
6
+ * 1. Loop adds a new POST/PUT/PATCH/DELETE → no mapping → test fails
7
+ * and names the orphan endpoint.
8
+ * 2. A tool's `server.tool("…")` registration is renamed or removed →
9
+ * the name lookup against `index.ts` source fails.
10
+ * 3. A tool's source file is refactored such that the swagger path
11
+ * family no longer appears literally → the path-fragment lookup
12
+ * against the source file fails.
13
+ *
14
+ * The vendored snapshot at `loop-swagger.snapshot.json` was fetched on
15
+ * 2026-04-28 from `https://api.loop.software/swagger/v2/swagger.json`
16
+ * (32 write endpoints across 8 tags). Refresh by re-downloading and
17
+ * extending ENDPOINT_MAP — the test is the single source of truth for
18
+ * what coverage means.
19
+ */
20
+
21
+ import { describe, expect, it } from 'vitest';
22
+ import { readFileSync } from 'node:fs';
23
+ import { resolve, dirname } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ const PLUGIN_SRC = resolve(__dirname, '..');
28
+ const SWAGGER = JSON.parse(
29
+ readFileSync(resolve(__dirname, 'loop-swagger.snapshot.json'), 'utf-8'),
30
+ ) as { paths: Record<string, Record<string, unknown>> };
31
+
32
+ const WRITE_METHODS = ['post', 'put', 'patch', 'delete'] as const;
33
+
34
+ interface EndpointMapping {
35
+ method: typeof WRITE_METHODS[number];
36
+ path: string;
37
+ tool: string;
38
+ source: string;
39
+ fragment: string;
40
+ }
41
+
42
+ // One entry per swagger write op. `fragment` is the literal substring
43
+ // (typically the stable prefix before any `${...}` template variable)
44
+ // that must appear in the source file — the path's family signature.
45
+ const ENDPOINT_MAP: EndpointMapping[] = [
46
+ // Customer (1)
47
+ { method: 'post', path: '/customer/preferences/{personCode}', tool: 'loop-customer-preferences', source: 'tools/customer-preferences.ts', fragment: '/customer/preferences/' },
48
+ // Feedback (2)
49
+ { method: 'post', path: '/feedback/residential/sales/viewings/{id}/feedback', tool: 'loop-feedback-submit', source: 'tools/feedback.ts', fragment: '/feedback/residential/' },
50
+ { method: 'post', path: '/feedback/residential/lettings/viewings/{id}/feedback', tool: 'loop-feedback-submit', source: 'tools/feedback.ts', fragment: '/feedback/residential/' },
51
+ // Marketing — match-batch PUT (2)
52
+ { method: 'put', path: '/marketing/matching/other-matches', tool: 'loop-marketing-match-batch', source: 'tools/marketing-match-batch.ts', fragment: '/matching/other-matches' },
53
+ { method: 'put', path: '/marketing/rentals/matching/other-matches', tool: 'loop-marketing-match-batch', source: 'tools/marketing-match-batch.ts', fragment: '/matching/other-matches' },
54
+ // Marketing — match-request POST × {sales,lettings} × {viewing,information,callback} (6)
55
+ { method: 'post', path: '/marketing/matching/{id}/viewing', tool: 'loop-marketing-match-request', source: 'tools/marketing-match-request.ts', fragment: '/matching/' },
56
+ { method: 'post', path: '/marketing/rentals/matching/{id}/viewing', tool: 'loop-marketing-match-request', source: 'tools/marketing-match-request.ts', fragment: '/marketing/rentals' },
57
+ { method: 'post', path: '/marketing/matching/{id}/information', tool: 'loop-marketing-match-request', source: 'tools/marketing-match-request.ts', fragment: '/matching/' },
58
+ { method: 'post', path: '/marketing/rentals/matching/{id}/information', tool: 'loop-marketing-match-request', source: 'tools/marketing-match-request.ts', fragment: '/marketing/rentals' },
59
+ { method: 'post', path: '/marketing/matching/{id}/callback', tool: 'loop-marketing-match-request', source: 'tools/marketing-match-request.ts', fragment: '/matching/' },
60
+ { method: 'post', path: '/marketing/rentals/matching/{id}/callback', tool: 'loop-marketing-match-request', source: 'tools/marketing-match-request.ts', fragment: '/marketing/rentals' },
61
+ // Marketing — enquiries (4)
62
+ { method: 'post', path: '/marketing/enquiries/seller', tool: 'loop-marketing-enquiry', source: 'tools/marketing-enquiry.ts', fragment: '/marketing/enquiries/seller' },
63
+ { method: 'put', path: '/marketing/enquiries/auto-responder/{id}/answers/{key}', tool: 'loop-marketing-enquiry', source: 'tools/marketing-enquiry.ts', fragment: '/marketing/enquiries/auto-responder/' },
64
+ { method: 'put', path: '/marketing/enquiries/auto-responder/{id}/details/{key}', tool: 'loop-marketing-enquiry', source: 'tools/marketing-enquiry.ts', fragment: '/marketing/enquiries/auto-responder/' },
65
+ { method: 'put', path: '/marketing/enquiries/auto-responder/{id}/refer/{key}', tool: 'loop-marketing-enquiry', source: 'tools/marketing-enquiry.ts', fragment: '/marketing/enquiries/auto-responder/' },
66
+ // Property (6)
67
+ { method: 'post', path: '/property/residential/sales/{id}/viewing', tool: 'loop-property-request', source: 'tools/property-request.ts', fragment: '/property/residential/' },
68
+ { method: 'post', path: '/property/residential/sales/{id}/call-back', tool: 'loop-property-request', source: 'tools/property-request.ts', fragment: '/property/residential/' },
69
+ { method: 'post', path: '/property/residential/sales/{id}/information', tool: 'loop-property-request', source: 'tools/property-request.ts', fragment: '/property/residential/' },
70
+ { method: 'post', path: '/property/residential/lettings/{id}/viewing', tool: 'loop-property-request', source: 'tools/property-request.ts', fragment: '/property/residential/' },
71
+ { method: 'post', path: '/property/residential/lettings/{id}/call-back', tool: 'loop-property-request', source: 'tools/property-request.ts', fragment: '/property/residential/' },
72
+ { method: 'post', path: '/property/residential/lettings/{id}/information', tool: 'loop-property-request', source: 'tools/property-request.ts', fragment: '/property/residential/' },
73
+ // Supplier (3)
74
+ { method: 'post', path: '/supplier/maintenance/{code}/{jobid}/complete', tool: 'loop-supplier', source: 'tools/supplier.ts', fragment: '/supplier/maintenance/' },
75
+ { method: 'put', path: '/supplier/maintenance/{code}/{quoteid}/quote-for-job', tool: 'loop-supplier', source: 'tools/supplier.ts', fragment: '/quote-for-job' },
76
+ { method: 'post', path: '/supplier/board-contractor/{code}/{jobid}/complete', tool: 'loop-supplier', source: 'tools/supplier.ts', fragment: '/supplier/board-contractor/' },
77
+ // Viewing — sales (4)
78
+ { method: 'post', path: '/residential/sales/viewings', tool: 'loop-viewing-create', source: 'tools/viewing-create.ts', fragment: '/residential/' },
79
+ { method: 'post', path: '/residential/sales/viewings/{id}/buyer-feedback', tool: 'loop-viewing-update', source: 'tools/viewing-update.ts', fragment: '/residential/' },
80
+ { method: 'post', path: '/residential/sales/viewings/{id}/note', tool: 'loop-viewing-update', source: 'tools/viewing-update.ts', fragment: '/residential/' },
81
+ { method: 'post', path: '/residential/sales/viewings/{id}/seller-feedback', tool: 'loop-viewing-update', source: 'tools/viewing-update.ts', fragment: '/residential/' },
82
+ // RentalViewing — lettings (4)
83
+ { method: 'post', path: '/residential/lettings/viewings', tool: 'loop-viewing-create', source: 'tools/viewing-create.ts', fragment: '/residential/' },
84
+ { method: 'post', path: '/residential/lettings/viewings/{id}/renter-feedback', tool: 'loop-viewing-update', source: 'tools/viewing-update.ts', fragment: '/residential/' },
85
+ { method: 'post', path: '/residential/lettings/viewings/{id}/note', tool: 'loop-viewing-update', source: 'tools/viewing-update.ts', fragment: '/residential/' },
86
+ { method: 'post', path: '/residential/lettings/viewings/{id}/landlord-feedback', tool: 'loop-viewing-update', source: 'tools/viewing-update.ts', fragment: '/residential/' },
87
+ ];
88
+
89
+ // Lazily cache source files so multiple per-endpoint tests don't re-read.
90
+ const sourceCache = new Map<string, string>();
91
+ function readSource(rel: string): string {
92
+ let cached = sourceCache.get(rel);
93
+ if (cached) return cached;
94
+ cached = readFileSync(resolve(PLUGIN_SRC, rel), 'utf-8');
95
+ sourceCache.set(rel, cached);
96
+ return cached;
97
+ }
98
+
99
+ const indexSrc = readSource('index.ts');
100
+
101
+ describe('Loop swagger write coverage', () => {
102
+ // 1. Every write op in the snapshot has a mapping (catches new endpoints).
103
+ it('every swagger write op has a mapping in ENDPOINT_MAP', () => {
104
+ const swaggerOps: { method: string; path: string }[] = [];
105
+ for (const [path, item] of Object.entries(SWAGGER.paths)) {
106
+ for (const m of WRITE_METHODS) {
107
+ if (item[m]) swaggerOps.push({ method: m, path });
108
+ }
109
+ }
110
+ const mapped = new Set(ENDPOINT_MAP.map(e => `${e.method.toUpperCase()} ${e.path}`));
111
+ const orphans = swaggerOps.filter(o => !mapped.has(`${o.method.toUpperCase()} ${o.path}`));
112
+ if (orphans.length > 0) {
113
+ const lines = orphans.map(o => ` ${o.method.toUpperCase()} ${o.path}`).join('\n');
114
+ throw new Error(
115
+ `${orphans.length} swagger write endpoint(s) lack a mapping in ENDPOINT_MAP — Loop has added new write ops since the last snapshot:\n${lines}\n\nUpdate ENDPOINT_MAP to map each new endpoint to its tool, source, and path fragment.`,
116
+ );
117
+ }
118
+ expect(swaggerOps.length).toBe(ENDPOINT_MAP.length);
119
+ });
120
+
121
+ // 2. Every mapping's tool is registered in index.ts (catches deregistration).
122
+ // Match by quoted tool name in proximity to a `server.tool(` call —
123
+ // each registration is `server.tool(\n "<name>", ...)` or single-line.
124
+ // A tool name that appears only in a comment or unrelated string doesn't
125
+ // match because we require it to be the first non-whitespace argument
126
+ // of a server.tool( invocation within ~32 chars.
127
+ it.each(ENDPOINT_MAP)('$method $path → tool $tool registered in index.ts', ({ tool }) => {
128
+ const registrationRe = new RegExp(`server\\.tool\\(\\s*"${tool.replace(/[.*+?^${}()|[\\\]\\\\]/g, '\\\\$&')}"`);
129
+ const isRegistered = registrationRe.test(indexSrc);
130
+ expect(isRegistered, `tool "${tool}" not registered as server.tool("${tool}", ...) in src/index.ts`).toBe(true);
131
+ });
132
+
133
+ // 3. Every mapping's path fragment appears in the named source file
134
+ // (catches refactor that drops the path family).
135
+ it.each(ENDPOINT_MAP)('$method $path → fragment "$fragment" present in $source', ({ source, fragment }) => {
136
+ const src = readSource(source);
137
+ expect(
138
+ src.includes(fragment),
139
+ `expected fragment "${fragment}" in ${source} (path family for the swagger op)`,
140
+ ).toBe(true);
141
+ });
142
+
143
+ // 4. Reverse coverage: every distinct tool in the mapping appears
144
+ // registered (catches dead mappings).
145
+ it('every tool referenced by ENDPOINT_MAP is registered', () => {
146
+ const tools = new Set(ENDPOINT_MAP.map(e => e.tool));
147
+ const missing: string[] = [];
148
+ for (const tool of tools) {
149
+ if (!indexSrc.includes(`"${tool}"`)) missing.push(tool);
150
+ }
151
+ expect(missing).toEqual([]);
152
+ });
153
+ });
@@ -15,5 +15,6 @@
15
15
  "declarationMap": true,
16
16
  "sourceMap": true
17
17
  },
18
- "include": ["src"]
18
+ "include": ["src"],
19
+ "exclude": ["src/**/__tests__/**", "src/**/*.test.ts"]
19
20
  }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ include: ["src/**/__tests__/**/*.test.ts"],
7
+ testTimeout: 10_000,
8
+ },
9
+ });