@rubytech/create-maxy-code 0.1.417 → 0.1.423

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 (165) hide show
  1. package/dist/__tests__/brand-port-uniqueness.test.js +74 -0
  2. package/dist/known-brands.js +1 -0
  3. package/package.json +1 -1
  4. package/payload/platform/config/brand-registry.json +8 -0
  5. package/payload/platform/lib/active-rules/dist/index.d.ts +35 -0
  6. package/payload/platform/lib/active-rules/dist/index.d.ts.map +1 -0
  7. package/payload/platform/lib/active-rules/dist/index.js +94 -0
  8. package/payload/platform/lib/active-rules/dist/index.js.map +1 -0
  9. package/payload/platform/lib/active-rules/package.json +7 -0
  10. package/payload/platform/lib/active-rules/src/index.test.ts +89 -0
  11. package/payload/platform/lib/active-rules/src/index.ts +133 -0
  12. package/payload/platform/lib/active-rules/tsconfig.json +9 -0
  13. package/payload/platform/package-lock.json +5158 -2922
  14. package/payload/platform/package.json +4 -3
  15. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +22 -0
  16. package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +14 -1
  17. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +16 -2
  18. package/payload/platform/plugins/docs/references/internals.md +10 -0
  19. package/payload/platform/plugins/docs/references/memory-guide.md +4 -0
  20. package/payload/platform/plugins/docs/references/outlook-guide.md +1 -1
  21. package/payload/platform/plugins/memory/PLUGIN.md +3 -0
  22. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.d.ts +2 -0
  23. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.d.ts.map +1 -0
  24. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js +49 -0
  25. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js.map +1 -0
  26. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.d.ts +2 -0
  27. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.d.ts.map +1 -0
  28. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.js +99 -0
  29. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.js.map +1 -0
  30. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts +3 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -1
  32. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +11 -3
  33. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
  34. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +21 -0
  35. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  36. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +106 -14
  37. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  38. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +30 -1
  39. package/payload/platform/plugins/memory/skills/preference-audit/SKILL.md +50 -0
  40. package/payload/platform/plugins/outlook/mcp/dist/__tests__/calendar-write.test.js +24 -0
  41. package/payload/platform/plugins/outlook/mcp/dist/__tests__/calendar-write.test.js.map +1 -1
  42. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.d.ts +15 -2
  43. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.d.ts.map +1 -1
  44. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.js +16 -7
  45. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-create.js.map +1 -1
  46. package/payload/platform/plugins/whatsapp/PLUGIN.md +3 -1
  47. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.d.ts +2 -0
  48. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.d.ts.map +1 -0
  49. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.js +15 -0
  50. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.js.map +1 -0
  51. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +14 -8
  52. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  53. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.d.ts +7 -0
  54. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.d.ts.map +1 -0
  55. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.js +13 -0
  56. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.js.map +1 -0
  57. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +9 -1
  58. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.d.ts +2 -0
  59. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.d.ts.map +1 -0
  60. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.js +66 -0
  61. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.js.map +1 -0
  62. package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts.map +1 -1
  63. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js +8 -1
  64. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js.map +1 -1
  65. package/payload/platform/scripts/__tests__/provision-role-stamp.test.sh +3 -0
  66. package/payload/platform/scripts/lib/provision-account-dir.sh +9 -1
  67. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts +1 -1
  68. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts.map +1 -1
  69. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js +5 -1
  70. package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js.map +1 -1
  71. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +1 -0
  72. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/http-server.js +65 -4
  74. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/index.js +53 -0
  76. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.d.ts +37 -0
  78. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.d.ts.map +1 -0
  79. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.js +80 -0
  80. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.js.map +1 -0
  81. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.d.ts +70 -0
  82. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.d.ts.map +1 -0
  83. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.js +129 -0
  84. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.js.map +1 -0
  85. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts +46 -0
  86. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts.map +1 -0
  87. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js +96 -0
  88. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js.map +1 -0
  89. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +39 -0
  90. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  91. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +66 -5
  92. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  93. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
  94. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +5 -1
  95. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
  96. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +6 -0
  97. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  98. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +20 -0
  99. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  100. package/payload/platform/services/telegram-channel/dist/instructions.d.ts +9 -0
  101. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -0
  102. package/payload/platform/services/telegram-channel/dist/instructions.js +80 -0
  103. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -0
  104. package/payload/platform/services/telegram-channel/dist/notification.d.ts +93 -0
  105. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -0
  106. package/payload/platform/services/telegram-channel/dist/notification.js +119 -0
  107. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -0
  108. package/payload/platform/services/telegram-channel/dist/server.d.ts +40 -0
  109. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -0
  110. package/payload/platform/services/telegram-channel/dist/server.js +291 -0
  111. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -0
  112. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +28 -0
  113. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -0
  114. package/payload/platform/services/telegram-channel/dist/turn-follow.js +188 -0
  115. package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -0
  116. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  117. package/payload/platform/services/webchat-channel/dist/instructions.js +37 -78
  118. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  119. package/payload/platform/services/webchat-channel/dist/notification.d.ts +5 -0
  120. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  121. package/payload/platform/services/webchat-channel/dist/notification.js +6 -1
  122. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  123. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +20 -36
  124. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  125. package/payload/platform/services/whatsapp-channel/dist/notification.js +43 -81
  126. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  127. package/payload/platform/templates/agents/admin/IDENTITY.md +2 -1
  128. package/payload/platform/templates/agents/passive/IDENTITY.md +21 -0
  129. package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
  130. package/payload/server/public/assets/{AdminLoginScreens-CBTx7ZPK.js → AdminLoginScreens-B1P6PrZ3.js} +1 -1
  131. package/payload/server/public/assets/AdminShell-Y1fHQApK.js +2 -0
  132. package/payload/server/public/assets/{Checkbox-BHTxekRB.js → Checkbox-CgCqOslz.js} +1 -1
  133. package/payload/server/public/assets/{Transcript-5kLbBInN.js → Transcript-DZzFjhoq.js} +1 -1
  134. package/payload/server/public/assets/{admin-BWCLE32N.js → admin-BRBjJEci.js} +1 -1
  135. package/payload/server/public/assets/{browser-CRvCPsvV.js → browser-ID7iqIi4.js} +1 -1
  136. package/payload/server/public/assets/{calendar-BI-kd-QP.js → calendar-BEQPMTtd.js} +1 -1
  137. package/payload/server/public/assets/chat-D0GDSWM-.js +1 -0
  138. package/payload/server/public/assets/chevron-left-A0HYKwGY.js +1 -0
  139. package/payload/server/public/assets/data-CV38dClT.js +1 -0
  140. package/payload/server/public/assets/{graph-BFSyW_5i.js → graph-BK0MjKRy.js} +1 -1
  141. package/payload/server/public/assets/{graph-labels-DEjD_qJh.js → graph-labels-BfG6FO25.js} +1 -1
  142. package/payload/server/public/assets/{maximize-2-BcMJNeCB.js → maximize-2-DrBaKD3z.js} +1 -1
  143. package/payload/server/public/assets/{operator-Dtvm9ero.js → operator-CnpNyIJ8.js} +1 -1
  144. package/payload/server/public/assets/page-DC_L-IS1.js +32 -0
  145. package/payload/server/public/assets/{page-TXi9dmaB.js → page-DKHLflh7.js} +1 -1
  146. package/payload/server/public/assets/{public-DBx3U0r8.js → public-ByUJr48E.js} +1 -1
  147. package/payload/server/public/assets/{rotate-ccw-8ti3QkeY.js → rotate-ccw-D45TbeAI.js} +1 -1
  148. package/payload/server/public/assets/useSubAccountSwitcher-CCn22f98.css +1 -0
  149. package/payload/server/public/assets/useSubAccountSwitcher-CprHkBnZ.js +9 -0
  150. package/payload/server/public/browser.html +4 -4
  151. package/payload/server/public/calendar.html +5 -5
  152. package/payload/server/public/chat.html +9 -9
  153. package/payload/server/public/data.html +8 -8
  154. package/payload/server/public/graph.html +9 -9
  155. package/payload/server/public/index.html +11 -11
  156. package/payload/server/public/operator.html +11 -11
  157. package/payload/server/public/public.html +9 -9
  158. package/payload/server/server.js +406 -99
  159. package/payload/server/public/assets/AdminShell-DPHUeXea.js +0 -2
  160. package/payload/server/public/assets/chat-DFy8hb1y.js +0 -1
  161. package/payload/server/public/assets/chevron-left-Bggm5B5U.js +0 -1
  162. package/payload/server/public/assets/data-HvZ69Ya1.js +0 -1
  163. package/payload/server/public/assets/page-C_pm3hl3.js +0 -32
  164. package/payload/server/public/assets/useSubAccountSwitcher-BUvlETHN.css +0 -1
  165. package/payload/server/public/assets/useSubAccountSwitcher-Bc5dOBdI.js +0 -9
@@ -0,0 +1,74 @@
1
+ // Standing check: no two brands may fight for the same host resource when
2
+ // co-resident on one device. Two namespaces are at stake:
3
+ //
4
+ // 1. Host TCP ports. neo4jPort / ttydPort / rfbPort / websockifyPort /
5
+ // cdpPort / claudeSessionManagerPort all bind TCP on the same host, so they
6
+ // must be globally unique across every (brand, field) pair — not merely
7
+ // unique within each field. A future brand whose ttydPort equals another
8
+ // brand's neo4jPort would EADDRINUSE at runtime with no build event, even
9
+ // though each field is individually unique.
10
+ // 2. X display numbers. vncDisplay is an Xvnc display index (:NN and its
11
+ // /tmp/.X<NN>-lock), a namespace distinct from TCP ports, so it must be
12
+ // unique across brands on its own.
13
+ //
14
+ // This reconciles all brands/*/brand.json on every CI run. Path resolution
15
+ // mirrors known-brand-hostnames.test.ts.
16
+ import test from "node:test";
17
+ import assert from "node:assert/strict";
18
+ import { readdirSync, readFileSync, existsSync } from "node:fs";
19
+ import { fileURLToPath } from "node:url";
20
+ import { dirname, resolve, join } from "node:path";
21
+ // dist/__tests__/brand-port-uniqueness.test.js → maxy-code/brands
22
+ const here = dirname(fileURLToPath(import.meta.url));
23
+ const BRANDS_DIR = resolve(here, "../../../../brands");
24
+ // Fields that bind a host TCP port (one shared namespace across all fields).
25
+ const TCP_PORT_FIELDS = [
26
+ "neo4jPort",
27
+ "ttydPort",
28
+ "rfbPort",
29
+ "websockifyPort",
30
+ "cdpPort",
31
+ "claudeSessionManagerPort",
32
+ ];
33
+ // X display index — its own namespace, unique per brand.
34
+ const DISPLAY_FIELD = "vncDisplay";
35
+ const ALL_FIELDS = [...TCP_PORT_FIELDS, DISPLAY_FIELD];
36
+ function brandManifests() {
37
+ return readdirSync(BRANDS_DIR, { withFileTypes: true })
38
+ .filter((e) => e.isDirectory())
39
+ .map((e) => ({ name: e.name, path: join(BRANDS_DIR, e.name, "brand.json") }))
40
+ .filter((b) => existsSync(b.path))
41
+ .map((b) => ({ name: b.name, manifest: JSON.parse(readFileSync(b.path, "utf-8")) }));
42
+ }
43
+ test("brands/ is discoverable and every brand declares all port fields", () => {
44
+ const brands = brandManifests();
45
+ assert.ok(brands.length > 0, `no brand.json found under ${BRANDS_DIR}`);
46
+ for (const { name, manifest } of brands) {
47
+ for (const field of ALL_FIELDS) {
48
+ assert.equal(typeof manifest[field], "number", `brands/${name}/brand.json is missing numeric ${field}`);
49
+ }
50
+ }
51
+ });
52
+ test("host TCP ports are globally unique across every (brand, field)", () => {
53
+ const brands = brandManifests();
54
+ const seen = new Map(); // port -> "brand.field" that claimed it
55
+ for (const { name, manifest } of brands) {
56
+ for (const field of TCP_PORT_FIELDS) {
57
+ const value = manifest[field];
58
+ const owner = `${name}.${field}`;
59
+ const prior = seen.get(value);
60
+ assert.equal(prior, undefined, `TCP port collision: ${prior} and ${owner} both bind ${value} — they would EADDRINUSE when co-resident`);
61
+ seen.set(value, owner);
62
+ }
63
+ }
64
+ });
65
+ test("vncDisplay is unique across brands", () => {
66
+ const brands = brandManifests();
67
+ const seen = new Map();
68
+ for (const { name, manifest } of brands) {
69
+ const value = manifest[DISPLAY_FIELD];
70
+ const prior = seen.get(value);
71
+ assert.equal(prior, undefined, `vncDisplay collision: brands "${prior}" and "${name}" both use :${value}`);
72
+ seen.set(value, name);
73
+ }
74
+ });
@@ -18,6 +18,7 @@ export const KNOWN_BRAND_HOSTNAMES = [
18
18
  "realagent",
19
19
  "realagent-code",
20
20
  "sitedesk-code",
21
+ "property-administrators",
21
22
  "maxy-2",
22
23
  "maxy-3",
23
24
  "maxy-4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.417",
3
+ "version": "0.1.423",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -8,6 +8,14 @@
8
8
  "websockifyPort": 6080,
9
9
  "cdpPort": 9222
10
10
  },
11
+ {
12
+ "hostname": "property-administrators",
13
+ "configDir": ".property-administrators",
14
+ "vncDisplay": 102,
15
+ "rfbPort": 5903,
16
+ "websockifyPort": 6083,
17
+ "cdpPort": 9225
18
+ },
11
19
  {
12
20
  "hostname": "realagent-code",
13
21
  "configDir": ".realagent-code",
@@ -0,0 +1,35 @@
1
+ export declare const SAME_SUBJECT_COSINE = 0.85;
2
+ export declare const INJECTION_RULE_CAP = 12;
3
+ export declare const ACTIVE_CANDIDATE_FLOOR = 0.4;
4
+ export declare const ACTIVE_CANDIDATE_LIMIT = 30;
5
+ export interface RuleCandidate {
6
+ preferenceId: string;
7
+ category: string;
8
+ key: string;
9
+ value: string;
10
+ confidence: number;
11
+ observedAt: string;
12
+ embedding: number[] | null;
13
+ }
14
+ export interface ActiveRule {
15
+ preferenceId: string;
16
+ category: string;
17
+ key: string;
18
+ value: string;
19
+ confidence: number;
20
+ observedAt: string;
21
+ }
22
+ export declare function cosine(a: number[], b: number[]): number;
23
+ export declare function deconflictBySubject(records: RuleCandidate[], cosineThreshold?: number): ActiveRule[];
24
+ export declare function formatStandingRulesBlock(rules: ActiveRule[], cap?: number): string;
25
+ interface MinimalSession {
26
+ run: (q: string, p: Record<string, unknown>) => Promise<{
27
+ records: Array<{
28
+ get(k: string): unknown;
29
+ }>;
30
+ }>;
31
+ }
32
+ export declare function queryActiveRuleCandidates(session: MinimalSession, accountId: string): Promise<RuleCandidate[]>;
33
+ export declare function activeRuleSet(session: MinimalSession, accountId: string, cap?: number): Promise<ActiveRule[]>;
34
+ export {};
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAYvD;AAOD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,aAAa,EAAE,EACxB,eAAe,GAAE,MAA4B,GAC5C,UAAU,EAAE,CAgBd;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,EAAE,EACnB,GAAG,GAAE,MAA2B,GAC/B,MAAM,CAIR;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,CACH,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACvB,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAC/D;AAOD,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,EAAE,CAAC,CA6B1B;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,MAA2B,GAC/B,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ // The one definition of "this account's active, de-conflicted, bounded
3
+ // rule-set" (Task 1486). Consumed by the memory plugin's profile-read
4
+ // (agent-callable) and by the three inbound gateways (per-turn injection). Kept
5
+ // free of neo4j-driver types so both an MCP process and the UI can import the
6
+ // built dist.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ACTIVE_CANDIDATE_LIMIT = exports.ACTIVE_CANDIDATE_FLOOR = exports.INJECTION_RULE_CAP = exports.SAME_SUBJECT_COSINE = void 0;
9
+ exports.cosine = cosine;
10
+ exports.deconflictBySubject = deconflictBySubject;
11
+ exports.formatStandingRulesBlock = formatStandingRulesBlock;
12
+ exports.queryActiveRuleCandidates = queryActiveRuleCandidates;
13
+ exports.activeRuleSet = activeRuleSet;
14
+ exports.SAME_SUBJECT_COSINE = 0.85;
15
+ exports.INJECTION_RULE_CAP = 12;
16
+ exports.ACTIVE_CANDIDATE_FLOOR = 0.4;
17
+ exports.ACTIVE_CANDIDATE_LIMIT = 30;
18
+ function cosine(a, b) {
19
+ if (!a.length || !b.length || a.length !== b.length)
20
+ return 0;
21
+ let dot = 0;
22
+ let na = 0;
23
+ let nb = 0;
24
+ for (let i = 0; i < a.length; i++) {
25
+ dot += a[i] * b[i];
26
+ na += a[i] * a[i];
27
+ nb += b[i] * b[i];
28
+ }
29
+ if (na === 0 || nb === 0)
30
+ return 0;
31
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
32
+ }
33
+ // records MUST be confidence-desc on entry. Greedy: the first record of a
34
+ // cluster is the winner; any later record within cosineThreshold of an
35
+ // already-kept winner is a rephrasing and is dropped. A record with no usable
36
+ // embedding is never folded — it is kept as its own subject (we cannot prove it
37
+ // is a duplicate).
38
+ function deconflictBySubject(records, cosineThreshold = exports.SAME_SUBJECT_COSINE) {
39
+ const winners = [];
40
+ for (const r of records) {
41
+ const emb = r.embedding;
42
+ const isDup = Array.isArray(emb) &&
43
+ emb.length > 0 &&
44
+ winners.some((w) => Array.isArray(w.embedding) &&
45
+ w.embedding.length > 0 &&
46
+ cosine(emb, w.embedding) >= cosineThreshold);
47
+ if (!isDup)
48
+ winners.push(r);
49
+ }
50
+ return winners.map(({ embedding: _embedding, ...rest }) => rest);
51
+ }
52
+ function formatStandingRulesBlock(rules, cap = exports.INJECTION_RULE_CAP) {
53
+ if (rules.length === 0)
54
+ return "";
55
+ const lines = rules.slice(0, cap).map((r) => `- ${r.value}`);
56
+ return `## Standing rules\n${lines.join("\n")}`;
57
+ }
58
+ // The candidate query: this account's admin-scope preferences, not trashed, not
59
+ // a "does not apply" declination, above the injection floor, ordered so the
60
+ // highest-confidence / most-recent wins de-confliction. Returns `embedding` for
61
+ // the clustering step. The trashed predicate mirrors graph-trash's
62
+ // `notTrashed()` exactly (label + deletedAt) so this lib stays dependency-free.
63
+ async function queryActiveRuleCandidates(session, accountId) {
64
+ // ACTIVE_CANDIDATE_LIMIT is inlined as a Cypher literal rather than bound as a
65
+ // param: a plain JS number bound to `LIMIT $p` serializes as a FLOAT and Neo4j
66
+ // rejects it ("not a valid value. Must be a non-negative integer"). This lib
67
+ // is dependency-free (no neo4j-driver), so it cannot call `neo4j.int()`; the
68
+ // constant is a hardcoded integer, so inlining it is safe and gate-clean.
69
+ const result = await session.run(`
70
+ MATCH (pref:Preference {accountId: $accountId, scope: 'admin'})
71
+ WHERE NOT pref:Trashed AND pref.deletedAt IS NULL
72
+ AND coalesce(pref.notApplicable, false) = false
73
+ AND pref.confidence >= $floor
74
+ RETURN pref.preferenceId AS preferenceId, pref.category AS category,
75
+ pref.key AS key, pref.value AS value, pref.confidence AS confidence,
76
+ pref.observedAt AS observedAt, pref.embedding AS embedding
77
+ ORDER BY pref.confidence DESC, pref.observedAt DESC
78
+ LIMIT ${exports.ACTIVE_CANDIDATE_LIMIT}
79
+ `, { accountId, floor: exports.ACTIVE_CANDIDATE_FLOOR });
80
+ return result.records.map((r) => ({
81
+ preferenceId: r.get("preferenceId"),
82
+ category: r.get("category"),
83
+ key: r.get("key"),
84
+ value: r.get("value"),
85
+ confidence: r.get("confidence"),
86
+ observedAt: r.get("observedAt") ?? "",
87
+ embedding: r.get("embedding") ?? null,
88
+ }));
89
+ }
90
+ async function activeRuleSet(session, accountId, cap = exports.INJECTION_RULE_CAP) {
91
+ const candidates = await queryActiveRuleCandidates(session, accountId);
92
+ return deconflictBySubject(candidates).slice(0, cap);
93
+ }
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@maxy/active-rules",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "commonjs",
6
+ "main": "dist/index.js"
7
+ }
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ cosine,
4
+ deconflictBySubject,
5
+ formatStandingRulesBlock,
6
+ INJECTION_RULE_CAP,
7
+ type RuleCandidate,
8
+ } from "./index.js";
9
+
10
+ const vec = (n: number, fill: number): number[] => Array(n).fill(fill);
11
+
12
+ function cand(over: Partial<RuleCandidate>): RuleCandidate {
13
+ return {
14
+ preferenceId: "p",
15
+ category: "communication",
16
+ key: "k",
17
+ value: "v",
18
+ confidence: 0.5,
19
+ observedAt: "2026-07-01T00:00:00Z",
20
+ embedding: vec(4, 1),
21
+ ...over,
22
+ };
23
+ }
24
+
25
+ describe("cosine", () => {
26
+ it("is 1 for identical vectors and 0 for an empty one", () => {
27
+ expect(cosine([1, 0, 0], [1, 0, 0])).toBeCloseTo(1, 6);
28
+ expect(cosine([], [1, 2, 3])).toBe(0);
29
+ });
30
+ });
31
+
32
+ describe("deconflictBySubject", () => {
33
+ it("folds a near-duplicate into the higher-confidence winner", () => {
34
+ const a = cand({
35
+ preferenceId: "a",
36
+ key: "header_rule",
37
+ value: "use header A",
38
+ confidence: 0.8,
39
+ embedding: [1, 0, 0, 0],
40
+ });
41
+ const b = cand({
42
+ preferenceId: "b",
43
+ key: "doc_header",
44
+ value: "use header A restated",
45
+ confidence: 0.5,
46
+ embedding: [0.99, 0.01, 0, 0],
47
+ });
48
+ const out = deconflictBySubject([a, b], 0.85);
49
+ expect(out.map((r) => r.preferenceId)).toEqual(["a"]);
50
+ });
51
+
52
+ it("keeps genuinely different subjects", () => {
53
+ const a = cand({ preferenceId: "a", embedding: [1, 0, 0, 0] });
54
+ const b = cand({ preferenceId: "b", embedding: [0, 1, 0, 0] });
55
+ expect(deconflictBySubject([a, b], 0.85).map((r) => r.preferenceId)).toEqual([
56
+ "a",
57
+ "b",
58
+ ]);
59
+ });
60
+
61
+ it("never folds a candidate with no embedding", () => {
62
+ const a = cand({ preferenceId: "a", embedding: [1, 0, 0, 0] });
63
+ const b = cand({ preferenceId: "b", embedding: null });
64
+ expect(deconflictBySubject([a, b], 0.85).map((r) => r.preferenceId)).toEqual([
65
+ "a",
66
+ "b",
67
+ ]);
68
+ });
69
+ });
70
+
71
+ describe("formatStandingRulesBlock", () => {
72
+ it("returns empty string for no rules", () => {
73
+ expect(formatStandingRulesBlock([], INJECTION_RULE_CAP)).toBe("");
74
+ });
75
+
76
+ it("caps the number of rendered rules", () => {
77
+ const rules = Array.from({ length: 20 }, (_, i) => ({
78
+ preferenceId: `p${i}`,
79
+ category: "communication",
80
+ key: `k${i}`,
81
+ value: `rule ${i}`,
82
+ confidence: 1 - i * 0.01,
83
+ observedAt: "2026-07-01T00:00:00Z",
84
+ }));
85
+ const block = formatStandingRulesBlock(rules, 12);
86
+ expect(block).toContain("## Standing rules");
87
+ expect((block.match(/^- /gm) ?? []).length).toBe(12);
88
+ });
89
+ });
@@ -0,0 +1,133 @@
1
+ // The one definition of "this account's active, de-conflicted, bounded
2
+ // rule-set" (Task 1486). Consumed by the memory plugin's profile-read
3
+ // (agent-callable) and by the three inbound gateways (per-turn injection). Kept
4
+ // free of neo4j-driver types so both an MCP process and the UI can import the
5
+ // built dist.
6
+
7
+ export const SAME_SUBJECT_COSINE = 0.85;
8
+ export const INJECTION_RULE_CAP = 12;
9
+ export const ACTIVE_CANDIDATE_FLOOR = 0.4;
10
+ export const ACTIVE_CANDIDATE_LIMIT = 30;
11
+
12
+ export interface RuleCandidate {
13
+ preferenceId: string;
14
+ category: string;
15
+ key: string;
16
+ value: string;
17
+ confidence: number;
18
+ observedAt: string;
19
+ embedding: number[] | null;
20
+ }
21
+
22
+ export interface ActiveRule {
23
+ preferenceId: string;
24
+ category: string;
25
+ key: string;
26
+ value: string;
27
+ confidence: number;
28
+ observedAt: string;
29
+ }
30
+
31
+ export function cosine(a: number[], b: number[]): number {
32
+ if (!a.length || !b.length || a.length !== b.length) return 0;
33
+ let dot = 0;
34
+ let na = 0;
35
+ let nb = 0;
36
+ for (let i = 0; i < a.length; i++) {
37
+ dot += a[i] * b[i];
38
+ na += a[i] * a[i];
39
+ nb += b[i] * b[i];
40
+ }
41
+ if (na === 0 || nb === 0) return 0;
42
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
43
+ }
44
+
45
+ // records MUST be confidence-desc on entry. Greedy: the first record of a
46
+ // cluster is the winner; any later record within cosineThreshold of an
47
+ // already-kept winner is a rephrasing and is dropped. A record with no usable
48
+ // embedding is never folded — it is kept as its own subject (we cannot prove it
49
+ // is a duplicate).
50
+ export function deconflictBySubject(
51
+ records: RuleCandidate[],
52
+ cosineThreshold: number = SAME_SUBJECT_COSINE,
53
+ ): ActiveRule[] {
54
+ const winners: RuleCandidate[] = [];
55
+ for (const r of records) {
56
+ const emb = r.embedding;
57
+ const isDup =
58
+ Array.isArray(emb) &&
59
+ emb.length > 0 &&
60
+ winners.some(
61
+ (w) =>
62
+ Array.isArray(w.embedding) &&
63
+ w.embedding.length > 0 &&
64
+ cosine(emb, w.embedding) >= cosineThreshold,
65
+ );
66
+ if (!isDup) winners.push(r);
67
+ }
68
+ return winners.map(({ embedding: _embedding, ...rest }) => rest);
69
+ }
70
+
71
+ export function formatStandingRulesBlock(
72
+ rules: ActiveRule[],
73
+ cap: number = INJECTION_RULE_CAP,
74
+ ): string {
75
+ if (rules.length === 0) return "";
76
+ const lines = rules.slice(0, cap).map((r) => `- ${r.value}`);
77
+ return `## Standing rules\n${lines.join("\n")}`;
78
+ }
79
+
80
+ interface MinimalSession {
81
+ run: (
82
+ q: string,
83
+ p: Record<string, unknown>,
84
+ ) => Promise<{ records: Array<{ get(k: string): unknown }> }>;
85
+ }
86
+
87
+ // The candidate query: this account's admin-scope preferences, not trashed, not
88
+ // a "does not apply" declination, above the injection floor, ordered so the
89
+ // highest-confidence / most-recent wins de-confliction. Returns `embedding` for
90
+ // the clustering step. The trashed predicate mirrors graph-trash's
91
+ // `notTrashed()` exactly (label + deletedAt) so this lib stays dependency-free.
92
+ export async function queryActiveRuleCandidates(
93
+ session: MinimalSession,
94
+ accountId: string,
95
+ ): Promise<RuleCandidate[]> {
96
+ // ACTIVE_CANDIDATE_LIMIT is inlined as a Cypher literal rather than bound as a
97
+ // param: a plain JS number bound to `LIMIT $p` serializes as a FLOAT and Neo4j
98
+ // rejects it ("not a valid value. Must be a non-negative integer"). This lib
99
+ // is dependency-free (no neo4j-driver), so it cannot call `neo4j.int()`; the
100
+ // constant is a hardcoded integer, so inlining it is safe and gate-clean.
101
+ const result = await session.run(
102
+ `
103
+ MATCH (pref:Preference {accountId: $accountId, scope: 'admin'})
104
+ WHERE NOT pref:Trashed AND pref.deletedAt IS NULL
105
+ AND coalesce(pref.notApplicable, false) = false
106
+ AND pref.confidence >= $floor
107
+ RETURN pref.preferenceId AS preferenceId, pref.category AS category,
108
+ pref.key AS key, pref.value AS value, pref.confidence AS confidence,
109
+ pref.observedAt AS observedAt, pref.embedding AS embedding
110
+ ORDER BY pref.confidence DESC, pref.observedAt DESC
111
+ LIMIT ${ACTIVE_CANDIDATE_LIMIT}
112
+ `,
113
+ { accountId, floor: ACTIVE_CANDIDATE_FLOOR },
114
+ );
115
+ return result.records.map((r) => ({
116
+ preferenceId: r.get("preferenceId") as string,
117
+ category: r.get("category") as string,
118
+ key: r.get("key") as string,
119
+ value: r.get("value") as string,
120
+ confidence: r.get("confidence") as number,
121
+ observedAt: (r.get("observedAt") as string) ?? "",
122
+ embedding: (r.get("embedding") as number[] | null) ?? null,
123
+ }));
124
+ }
125
+
126
+ export async function activeRuleSet(
127
+ session: MinimalSession,
128
+ accountId: string,
129
+ cap: number = INJECTION_RULE_CAP,
130
+ ): Promise<ActiveRule[]> {
131
+ const candidates = await queryActiveRuleCandidates(session, accountId);
132
+ return deconflictBySubject(candidates).slice(0, cap);
133
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/**/*.test.ts"]
9
+ }