@rubytech/create-maxy-code 0.1.383 → 0.1.384

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 (172) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/mcp/dist/index.js +37 -9
  3. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  4. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +48 -0
  5. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
  6. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +51 -0
  7. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +25 -17
  9. package/payload/platform/plugins/cloudflare/references/hosting-sites.md +7 -4
  10. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +9 -6
  11. package/payload/platform/plugins/docs/references/admin-ui.md +24 -16
  12. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +1 -1
  13. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +2 -0
  14. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +2 -0
  15. package/payload/platform/scripts/__tests__/check-plugin-references.test.sh +83 -0
  16. package/payload/platform/scripts/check-plugin-references.mjs +60 -11
  17. package/payload/platform/services/claude-session-manager/dist/index.js +27 -2
  18. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  19. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.d.ts +35 -0
  20. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.d.ts.map +1 -0
  21. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.js +114 -0
  22. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.js.map +1 -0
  23. package/payload/platform/services/claude-session-manager/package.json +2 -1
  24. package/payload/server/public/assets/{AdminLoginScreens-C32Kg8HN.js → AdminLoginScreens-C_h8V0Xs.js} +1 -1
  25. package/payload/server/public/assets/AdminShell-Dqnxpn7U.js +1 -0
  26. package/payload/server/public/assets/{Checkbox-C-dvkjKb.js → Checkbox-Bl1WRVGb.js} +1 -1
  27. package/payload/server/public/assets/Transcript-C93Lifnb.js +1 -0
  28. package/payload/server/public/assets/admin-ByoqNaSf.js +1 -0
  29. package/payload/server/public/assets/{arc-JgkiMDpu.js → arc-C6-nr5UV.js} +1 -1
  30. package/payload/server/public/assets/architecture-YZFGNWBL-DdgmZ93B.js +1 -0
  31. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CyIbl1bD.js → architectureDiagram-Q4EWVU46-DIdho_T6.js} +1 -1
  32. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CkcZVblf.js → blockDiagram-DXYQGD6D-D5rTTBSy.js} +1 -1
  33. package/payload/server/public/assets/{browser-BUuA2PPl.js → browser-BXY1sNxv.js} +1 -1
  34. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-VByNuobb.js → c4Diagram-AHTNJAMY-pfUVHdmy.js} +1 -1
  35. package/payload/server/public/assets/calendar-CObRTPpU.js +1 -0
  36. package/payload/server/public/assets/channel-Da-UUyqx.js +1 -0
  37. package/payload/server/public/assets/chat-Dq-VHp7a.js +1 -0
  38. package/payload/server/public/assets/chevron-left-DjbK_wdQ.js +1 -0
  39. package/payload/server/public/assets/{chunk-2KRD3SAO-BFwiGcqo.js → chunk-2KRD3SAO-EF6cdj8U.js} +1 -1
  40. package/payload/server/public/assets/{chunk-336JU56O-BMCBU-vf.js → chunk-336JU56O-DE-lcZAc.js} +2 -2
  41. package/payload/server/public/assets/chunk-426QAEUC-B1kFAxo1.js +1 -0
  42. package/payload/server/public/assets/{chunk-4BX2VUAB-RfOeo8_F.js → chunk-4BX2VUAB--ANCzzyn.js} +1 -1
  43. package/payload/server/public/assets/{chunk-4TB4RGXK-B5xbgonc.js → chunk-4TB4RGXK-DTO_X80J.js} +1 -1
  44. package/payload/server/public/assets/{chunk-55IACEB6-K8U55ikJ.js → chunk-55IACEB6-4J3nrz44.js} +1 -1
  45. package/payload/server/public/assets/{chunk-5FUZZQ4R-DgmmsrAc.js → chunk-5FUZZQ4R-jH_eqAHL.js} +1 -1
  46. package/payload/server/public/assets/{chunk-5PVQY5BW-DQ6rtehF.js → chunk-5PVQY5BW-C-qU6p6k.js} +1 -1
  47. package/payload/server/public/assets/{chunk-67CJDMHE-CC2kfpmQ.js → chunk-67CJDMHE-DXxH9Snb.js} +1 -1
  48. package/payload/server/public/assets/{chunk-7N4EOEYR-BClZ5bzw.js → chunk-7N4EOEYR-CCkxuP7A.js} +1 -1
  49. package/payload/server/public/assets/{chunk-AA7GKIK3-adtwAFKX.js → chunk-AA7GKIK3-CyItuM8d.js} +1 -1
  50. package/payload/server/public/assets/{chunk-BSJP7CBP-fKY1UWEd.js → chunk-BSJP7CBP-Bg7gxYNf.js} +1 -1
  51. package/payload/server/public/assets/{chunk-CIAEETIT-knPAMmjG.js → chunk-CIAEETIT-BAXbOqv5.js} +1 -1
  52. package/payload/server/public/assets/{chunk-EDXVE4YY-C7FRKZ5e.js → chunk-EDXVE4YY-vdoErQoc.js} +1 -1
  53. package/payload/server/public/assets/{chunk-ENJZ2VHE-0cgFfv3z.js → chunk-ENJZ2VHE-Cz_z_6GK.js} +1 -1
  54. package/payload/server/public/assets/{chunk-FMBD7UC4-Dm_Hcblb.js → chunk-FMBD7UC4-Ck2jvOgW.js} +1 -1
  55. package/payload/server/public/assets/{chunk-FOC6F5B3-CfCJNXmK.js → chunk-FOC6F5B3-DucR0Qo8.js} +1 -1
  56. package/payload/server/public/assets/{chunk-ICPOFSXX-xeu6qrZj.js → chunk-ICPOFSXX-DolYDD7g.js} +2 -2
  57. package/payload/server/public/assets/{chunk-K5T4RW27-BQDCSuTh.js → chunk-K5T4RW27-FRzx7E5I.js} +1 -1
  58. package/payload/server/public/assets/{chunk-KGLVRYIC-D6fFAgbM.js → chunk-KGLVRYIC-Bb5S4Bmc.js} +1 -1
  59. package/payload/server/public/assets/{chunk-LIHQZDEY-Dskwlc-K.js → chunk-LIHQZDEY-c8VZJ-lU.js} +1 -1
  60. package/payload/server/public/assets/{chunk-ORNJ4GCN-CnHIrJeA.js → chunk-ORNJ4GCN-BAeh-LZ1.js} +1 -1
  61. package/payload/server/public/assets/{chunk-OYMX7WX6-B4bnsPkv.js → chunk-OYMX7WX6-BUg7Kysh.js} +1 -1
  62. package/payload/server/public/assets/chunk-QZHKN3VN-BjE-Glwf.js +1 -0
  63. package/payload/server/public/assets/{chunk-U2HBQHQK-BeEXzOqV.js → chunk-U2HBQHQK-K4r17UTk.js} +1 -1
  64. package/payload/server/public/assets/{chunk-X2U36JSP-D2rpgOqd.js → chunk-X2U36JSP-Byh-CyxC.js} +1 -1
  65. package/payload/server/public/assets/{chunk-XPW4576I-D-yT82Xf.js → chunk-XPW4576I-DC7lhr93.js} +1 -1
  66. package/payload/server/public/assets/{chunk-YZCP3GAM-DqxlLBm1.js → chunk-YZCP3GAM-B2cu7AEP.js} +1 -1
  67. package/payload/server/public/assets/{chunk-ZZ45TVLE-CA_Ep0OM.js → chunk-ZZ45TVLE-DH_p8sFX.js} +1 -1
  68. package/payload/server/public/assets/classDiagram-6PBFFD2Q-Dol3Ogeh.js +1 -0
  69. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-VUMj7gWH.js +1 -0
  70. package/payload/server/public/assets/clone-m7oGrdBC.js +1 -0
  71. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-DWb9mxTa.js → cose-bilkent-S5V4N54A-DeM6JI00.js} +1 -1
  72. package/payload/server/public/assets/{dagre-B3K7jxOZ.js → dagre-3drs3e4F.js} +1 -1
  73. package/payload/server/public/assets/{dagre-KV5264BT-BhiTISRq.js → dagre-KV5264BT-C6S-VUtN.js} +1 -1
  74. package/payload/server/public/assets/data-BXOoK8Ci.js +1 -0
  75. package/payload/server/public/assets/{diagram-5BDNPKRD-D5yBIDpm.js → diagram-5BDNPKRD-BkvA32I3.js} +1 -1
  76. package/payload/server/public/assets/{diagram-G4DWMVQ6-CSTE1S4d.js → diagram-G4DWMVQ6-CVwtRMPB.js} +1 -1
  77. package/payload/server/public/assets/{diagram-MMDJMWI5-D8llv4XY.js → diagram-MMDJMWI5-DJ1JVMbd.js} +1 -1
  78. package/payload/server/public/assets/{diagram-TYMM5635-CITQ7aDM.js → diagram-TYMM5635-rfj-crtX.js} +1 -1
  79. package/payload/server/public/assets/{erDiagram-SMLLAGMA-BjR5nlmv.js → erDiagram-SMLLAGMA-TJPuweS5.js} +1 -1
  80. package/payload/server/public/assets/{flatten-DhYYI_lo.js → flatten-BZkoyJ4e.js} +1 -1
  81. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-Dl6e-E0f.js → flowDiagram-DWJPFMVM-LQWEUjbV.js} +1 -1
  82. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CppkrFAl.js → ganttDiagram-T4ZO3ILL-DdYsxKRw.js} +1 -1
  83. package/payload/server/public/assets/gitGraph-7Q5UKJZL-cJEaiQq4.js +1 -0
  84. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-clV_tnwa.js → gitGraphDiagram-UUTBAWPF-sx8-fokM.js} +1 -1
  85. package/payload/server/public/assets/{graph-CvQZVxwZ.js → graph-BCL0fVDH.js} +3 -3
  86. package/payload/server/public/assets/{graph-labels-C7bJMcXs.js → graph-labels-C0nn9nQi.js} +1 -1
  87. package/payload/server/public/assets/{graphlib-Cnk6WL-8.js → graphlib-DixeJdCf.js} +1 -1
  88. package/payload/server/public/assets/info-OMHHGYJF-BUxfOTKa.js +1 -0
  89. package/payload/server/public/assets/infoDiagram-42DDH7IO-CD-d3Rgf.js +2 -0
  90. package/payload/server/public/assets/{isEmpty-DGhq_DBP.js → isEmpty-VRKFml5R.js} +1 -1
  91. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DUqusmP1.js → ishikawaDiagram-UXIWVN3A-BKUCuKhZ.js} +1 -1
  92. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-RMEvGtDJ.js → journeyDiagram-VCZTEJTY-BafZP6pM.js} +1 -1
  93. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BxkwQPCP.js → kanban-definition-6JOO6SKY-DQ4c6wRv.js} +1 -1
  94. package/payload/server/public/assets/{line-CiOuSBlJ.js → line-0_x1Wivu.js} +1 -1
  95. package/payload/server/public/assets/{linear-D22sItNm.js → linear-CZrU-J2V.js} +1 -1
  96. package/payload/server/public/assets/{mermaid-parser.core-kvN8ajT-.js → mermaid-parser.core-DwJ1cyfO.js} +2 -2
  97. package/payload/server/public/assets/{mermaid.core-DKHTTwbg.js → mermaid.core-DnUst0He.js} +3 -3
  98. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CKUJxwdG.js → mindmap-definition-QFDTVHPH-oc-GwerE.js} +1 -1
  99. package/payload/server/public/assets/operator-B_1qYO7m.js +1 -0
  100. package/payload/server/public/assets/{ordinal-CibZU3PM.js → ordinal-v4prQcuq.js} +1 -1
  101. package/payload/server/public/assets/packet-4T2RLAQJ-B9U_2639.js +1 -0
  102. package/payload/server/public/assets/page-BQEl7aZO.js +1 -0
  103. package/payload/server/public/assets/page-Dck23z9U.js +32 -0
  104. package/payload/server/public/assets/pdf-render-NQmflZ7K.js +11 -0
  105. package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
  106. package/payload/server/public/assets/pie-ZZUOXDRM-Glh-lJOT.js +1 -0
  107. package/payload/server/public/assets/{pieDiagram-DEJITSTG-CRMabeJN.js → pieDiagram-DEJITSTG-BnBtQNHM.js} +1 -1
  108. package/payload/server/public/assets/public-BzvwPHM0.js +1 -0
  109. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DcrlhDuj.js → quadrantDiagram-34T5L4WZ-CkNqtzHI.js} +1 -1
  110. package/payload/server/public/assets/radar-PYXPWWZC-CTSgob8w.js +1 -0
  111. package/payload/server/public/assets/{reduce-Da0RBva0.js → reduce-0Mm1v1Rr.js} +1 -1
  112. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DP231rM_.js → requirementDiagram-MS252O5E-D7qc27KT.js} +1 -1
  113. package/payload/server/public/assets/{rotate-ccw-DPCP-Lmz.js → rotate-ccw-CHep9vl5.js} +1 -1
  114. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6--HM5_dC8.js → sankeyDiagram-XADWPNL6-2Y4sqRY7.js} +1 -1
  115. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BsiVmDR9.js → sequenceDiagram-FGHM5R23-hXOiSHuT.js} +1 -1
  116. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Cjdhqdjh.js → stateDiagram-FHFEXIEX-Dq4hhkLA.js} +1 -1
  117. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DTj9o2vZ.js +1 -0
  118. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Cf5xPmSA.js → timeline-definition-GMOUNBTQ-Dn9wBUEw.js} +1 -1
  119. package/payload/server/public/assets/treeView-SZITEDCU-CXApkW1l.js +1 -0
  120. package/payload/server/public/assets/treemap-W4RFUUIX-DzvTXAcT.js +1 -0
  121. package/payload/server/public/assets/useSubAccountSwitcher-B1Kf32uG.js +9 -0
  122. package/payload/server/public/assets/{useSubAccountSwitcher-DqxG4I5C.css → useSubAccountSwitcher-CfI3J2IX.css} +1 -1
  123. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-PCor55Sw.js → vennDiagram-DHZGUBPP-BKVHJiIt.js} +1 -1
  124. package/payload/server/public/assets/wardley-RL74JXVD-BExXV4oI.js +1 -0
  125. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-bokCHsim.js → wardleyDiagram-NUSXRM2D-Bn9vvpdK.js} +1 -1
  126. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-B2YnsUoQ.js → xychartDiagram-5P7HB3ND-FIoMdPB3.js} +1 -1
  127. package/payload/server/public/browser.html +5 -4
  128. package/payload/server/public/calendar.html +6 -4
  129. package/payload/server/public/chat.html +10 -7
  130. package/payload/server/public/data.html +8 -5
  131. package/payload/server/public/graph.html +9 -7
  132. package/payload/server/public/index.html +10 -8
  133. package/payload/server/public/operator.html +12 -9
  134. package/payload/server/public/public.html +9 -7
  135. package/payload/server/public/assets/AdminShell-DizHgGBB.js +0 -1
  136. package/payload/server/public/assets/admin-CSBsndZ4.js +0 -1
  137. package/payload/server/public/assets/architecture-YZFGNWBL-Dh5BzVzk.js +0 -1
  138. package/payload/server/public/assets/calendar-CGlyx54B.js +0 -1
  139. package/payload/server/public/assets/channel-CFHc3PQ7.js +0 -1
  140. package/payload/server/public/assets/chat-DT1PbRkD.js +0 -1
  141. package/payload/server/public/assets/chunk-426QAEUC-De9_O1hi.js +0 -1
  142. package/payload/server/public/assets/chunk-QZHKN3VN-COwe7oXR.js +0 -1
  143. package/payload/server/public/assets/classDiagram-6PBFFD2Q-wjL65qaU.js +0 -1
  144. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D_EIplVd.js +0 -1
  145. package/payload/server/public/assets/clone-CEczy_Ef.js +0 -1
  146. package/payload/server/public/assets/data-BC4KTIX4.js +0 -1
  147. package/payload/server/public/assets/gitGraph-7Q5UKJZL-KHAc7IxG.js +0 -1
  148. package/payload/server/public/assets/info-OMHHGYJF-C6nm3sYf.js +0 -1
  149. package/payload/server/public/assets/infoDiagram-42DDH7IO-DhwCejsr.js +0 -2
  150. package/payload/server/public/assets/operator-4skjNtEa.js +0 -1
  151. package/payload/server/public/assets/packet-4T2RLAQJ-z-APhyNH.js +0 -1
  152. package/payload/server/public/assets/page-B0zvkWaG.js +0 -1
  153. package/payload/server/public/assets/page-BYlBTwqJ.js +0 -32
  154. package/payload/server/public/assets/pie-ZZUOXDRM-D_HVZjir.js +0 -1
  155. package/payload/server/public/assets/public-ClYyeEA1.js +0 -1
  156. package/payload/server/public/assets/radar-PYXPWWZC-DtKBIb3o.js +0 -1
  157. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-6v4alr7n.js +0 -1
  158. package/payload/server/public/assets/treeView-SZITEDCU-DbXWs029.js +0 -1
  159. package/payload/server/public/assets/treemap-W4RFUUIX-DUGgqAs9.js +0 -1
  160. package/payload/server/public/assets/useSubAccountSwitcher-B8FEcQNM.js +0 -9
  161. package/payload/server/public/assets/wardley-RL74JXVD-Cjn85RON.js +0 -1
  162. /package/payload/server/public/assets/{_baseFor-BFYpZGkN.js → _baseFor-C0Y1mc3g.js} +0 -0
  163. /package/payload/server/public/assets/{array-CYkMkqnU.js → array-DqLCdDFv.js} +0 -0
  164. /package/payload/server/public/assets/{cytoscape.esm-DqlHsaog.js → cytoscape.esm-Cn5b4Cdu.js} +0 -0
  165. /package/payload/server/public/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CrJzLgRD.js} +0 -0
  166. /package/payload/server/public/assets/{dist-BMqkacmt.js → dist-Dlks0Bfw.js} +0 -0
  167. /package/payload/server/public/assets/{init-Rr1s_RiX.js → init-C0r9Gk5G.js} +0 -0
  168. /package/payload/server/public/assets/{katex-pVJiI_rc.js → katex-BFwFoEts.js} +0 -0
  169. /package/payload/server/public/assets/{path-BAQ3hXlG.js → path-DIKpVbHL.js} +0 -0
  170. /package/payload/server/public/assets/{preload-helper-B_l5jfgx.js → preload-helper-C5gCWwwF.js} +0 -0
  171. /package/payload/server/public/assets/{rough.esm-nHaDi0Kw.js → rough.esm-DsHEmXUZ.js} +0 -0
  172. /package/payload/server/public/assets/{src-CsEKjNXD.js → src-Bv7ZWAzF.js} +0 -0
@@ -1,3 +1,4 @@
1
+ import type { CreatedBy } from "../../../../../lib/graph-write/dist/index.js";
1
2
  export interface AccountSummary {
2
3
  accountId: string;
3
4
  role: string;
@@ -43,6 +44,53 @@ export declare function provisionClientAccount(opts: {
43
44
  accountId: string;
44
45
  usersFile: string;
45
46
  }): string;
47
+ /** Minimal Neo4j session surface the root seed needs — the admin plugin's
48
+ * `getSession()` satisfies it; tests pass an in-memory fake. */
49
+ export interface SeedSessionLike {
50
+ run(query: string, params?: Record<string, unknown>): Promise<{
51
+ records: Array<{
52
+ get(key: string): unknown;
53
+ }>;
54
+ }>;
55
+ }
56
+ /** The provenance the root seed represents — matches the properties the cypher
57
+ * below stamps (`createdByAgent='system'`, `createdBySource='installer'`), the
58
+ * same shape `seed-neo4j.sh` writes for the house root. `agent:"system"` is the
59
+ * convention `checkLabelOriginGate` exempts. The seed runs as raw cypher through
60
+ * the admin session (not the memory-write chokepoint), so the gate never
61
+ * actually evaluates it in production; this constant is not the write's
62
+ * provenance carrier — it exists so a unit test can assert the gate *would*
63
+ * permit this origin. */
64
+ export declare const SEED_ORIGIN: CreatedBy;
65
+ /** Task 1359 — seed a client account's graph root: a bare :LocalBusiness{accountId}
66
+ * plus its owner :AdminUser (fresh per-account userId) joined by :ADMIN_OF, in
67
+ * one statement, then read both node existences back. Without this a created
68
+ * client account is rootless: `checkGraphWriteGate` refuses every non-root
69
+ * write and `checkLabelOriginGate` refuses the agent's attempt to create the
70
+ * root, so the graph is unpopulatable.
71
+ *
72
+ * Raw cypher through the admin session bypasses the memory-write gates by
73
+ * construction (they guard only the memory-write chokepoint), mirroring
74
+ * `seed-neo4j.sh`. The owner AdminUser carries a fresh `ownerUserId` because
75
+ * AdminUser is MERGE-keyed on `userId` and COALESCEs `accountId`; reusing the
76
+ * install owner's userId would keep `accountId=house` and leave the gate's
77
+ * `MATCH (au:AdminUser {accountId: <client>})` empty. Single-shot per account:
78
+ * MERGE on `LocalBusiness.accountId` (a UNIQUE key) makes a replay against the
79
+ * same accountId safe, but it is NOT replay-idempotent under a different
80
+ * `ownerUserId` (that would MERGE a second owner AdminUser); the sole caller
81
+ * mints a new accountId per create and never replays. Returns the two
82
+ * read-back booleans; the caller treats `businessSeeded && adminSeeded` as the
83
+ * verified post-condition. */
84
+ export declare function seedAccountGraphRoot(args: {
85
+ session: SeedSessionLike;
86
+ accountId: string;
87
+ ownerUserId: string;
88
+ ownerName: string;
89
+ createdAt: string;
90
+ }): Promise<{
91
+ businessSeeded: boolean;
92
+ adminSeeded: boolean;
93
+ }>;
46
94
  /** Cypher for the account-scoped node count and the irreversible purge. The
47
95
  * purge deletes every node carrying the accountId (account scope is a property,
48
96
  * never an edge — see CLAUDE.md), detaching its relationships. */
@@ -1 +1 @@
1
- {"version":3,"file":"account-lifecycle.d.ts","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;oCAEoC;AACpC,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAoBzE;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED;;+DAE+D;AAC/D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE3E;AAED;;;;;+DAK+D;AAC/D,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED;;;;0EAI0E;AAC1E,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAO7F;AAED;;iFAEiF;AACjF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAO3C;AAED;;;yBAGyB;AACzB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAoBT;AAED;;mEAEmE;AACnE,eAAO,MAAM,yBAAyB,2DAA2D,CAAC;AAClG,eAAO,MAAM,oBAAoB,sDAAsD,CAAC"}
1
+ {"version":3,"file":"account-lifecycle.d.ts","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAC;AAI9E,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;oCAEoC;AACpC,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAoBzE;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED;;+DAE+D;AAC/D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE3E;AAED;;;;;+DAK+D;AAC/D,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED;;;;0EAI0E;AAC1E,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAO7F;AAED;;iFAEiF;AACjF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAO3C;AAED;;;yBAGyB;AACzB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAoBT;AAED;iEACiE;AACjE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAClH;AAED;;;;;;;0BAO0B;AAC1B,eAAO,MAAM,WAAW,EAAE,SAAoD,CAAC;AAE/E;;;;;;;;;;;;;;;;;;+BAkB+B;AAC/B,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAyB7D;AAED;;mEAEmE;AACnE,eAAO,MAAM,yBAAyB,2DAA2D,CAAC;AAClG,eAAO,MAAM,oBAAoB,sDAAsD,CAAC"}
@@ -109,6 +109,57 @@ export function provisionClientAccount(opts) {
109
109
  });
110
110
  return accountDir;
111
111
  }
112
+ /** The provenance the root seed represents — matches the properties the cypher
113
+ * below stamps (`createdByAgent='system'`, `createdBySource='installer'`), the
114
+ * same shape `seed-neo4j.sh` writes for the house root. `agent:"system"` is the
115
+ * convention `checkLabelOriginGate` exempts. The seed runs as raw cypher through
116
+ * the admin session (not the memory-write chokepoint), so the gate never
117
+ * actually evaluates it in production; this constant is not the write's
118
+ * provenance carrier — it exists so a unit test can assert the gate *would*
119
+ * permit this origin. */
120
+ export const SEED_ORIGIN = { agent: "system", source: "installer" };
121
+ /** Task 1359 — seed a client account's graph root: a bare :LocalBusiness{accountId}
122
+ * plus its owner :AdminUser (fresh per-account userId) joined by :ADMIN_OF, in
123
+ * one statement, then read both node existences back. Without this a created
124
+ * client account is rootless: `checkGraphWriteGate` refuses every non-root
125
+ * write and `checkLabelOriginGate` refuses the agent's attempt to create the
126
+ * root, so the graph is unpopulatable.
127
+ *
128
+ * Raw cypher through the admin session bypasses the memory-write gates by
129
+ * construction (they guard only the memory-write chokepoint), mirroring
130
+ * `seed-neo4j.sh`. The owner AdminUser carries a fresh `ownerUserId` because
131
+ * AdminUser is MERGE-keyed on `userId` and COALESCEs `accountId`; reusing the
132
+ * install owner's userId would keep `accountId=house` and leave the gate's
133
+ * `MATCH (au:AdminUser {accountId: <client>})` empty. Single-shot per account:
134
+ * MERGE on `LocalBusiness.accountId` (a UNIQUE key) makes a replay against the
135
+ * same accountId safe, but it is NOT replay-idempotent under a different
136
+ * `ownerUserId` (that would MERGE a second owner AdminUser); the sole caller
137
+ * mints a new accountId per create and never replays. Returns the two
138
+ * read-back booleans; the caller treats `businessSeeded && adminSeeded` as the
139
+ * verified post-condition. */
140
+ export async function seedAccountGraphRoot(args) {
141
+ const { session, accountId, ownerUserId, ownerName, createdAt } = args;
142
+ const res = await session.run(`MERGE (lb:LocalBusiness {accountId: $accountId})
143
+ ON CREATE SET lb.createdBySource = 'installer',
144
+ lb.createdByAgent = 'system',
145
+ lb.createdAt = datetime()
146
+ MERGE (au:AdminUser {userId: $ownerUserId})
147
+ ON CREATE SET au.accountId = $accountId,
148
+ au.role = 'owner',
149
+ au.name = $ownerName,
150
+ au.createdAt = $createdAt
151
+ ON MATCH SET au.accountId = COALESCE(au.accountId, $accountId)
152
+ MERGE (au)-[r:ADMIN_OF]->(lb)
153
+ ON CREATE SET r.role = 'owner', r.grantedAt = $createdAt
154
+ WITH lb.accountId AS acct
155
+ RETURN COUNT { MATCH (b:LocalBusiness {accountId: acct}) } > 0 AS hasBusiness,
156
+ COUNT { MATCH (a:AdminUser {accountId: acct}) } > 0 AS hasAdmin`, { accountId, ownerUserId, ownerName, createdAt });
157
+ const rec = res.records[0];
158
+ return {
159
+ businessSeeded: Boolean(rec?.get("hasBusiness")),
160
+ adminSeeded: Boolean(rec?.get("hasAdmin")),
161
+ };
162
+ }
112
163
  /** Cypher for the account-scoped node count and the irreversible purge. The
113
164
  * purge deletes every node carrying the accountId (account scope is a property,
114
165
  * never an edge — see CLAUDE.md), detaching its relationships. */
@@ -1 +1 @@
1
- {"version":3,"file":"account-lifecycle.js","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,sEAAsE;AACtE,gFAAgF;AAChF,+EAA+E;AAC/E,uEAAuE;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAQlF;;oCAEoC;AACpC,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAuB,CAAC;YAChF,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;QAChE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,EAAU;IAC5D,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AACvF,CAAC;AAED;;+DAE+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;AACzE,CAAC;AAED;;;;;+DAK+D;AAC/D,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACzE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA0C,CAAC;QACnG,OAAO,OAAO,GAAG,CAAC,uBAAuB,KAAK,QAAQ,IAAI,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7H,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;0EAI0E;AAC1E,MAAM,UAAU,0BAA0B,CAAC,WAAmB,EAAE,eAAuB;IACrF,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAClF,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA4B,CAAC;IACrF,GAAG,CAAC,uBAAuB,GAAG,eAAe,CAAC;IAC9C,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;iFAEiF;AACjF,MAAM,UAAU,iBAAiB,CAC/B,WAAmB,EACnB,EAAU,EACV,EAAU;IAEV,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC9C,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACjD,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC/B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;AACxD,CAAC;AAED;;;yBAGyB;AACzB,MAAM,UAAU,sBAAsB,CAAC,IAKtC;IACC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1D,YAAY,CACV,MAAM,EACN,CAAC,IAAI,EAAE,mGAAmG,CAAC,EAC3G;QACE,GAAG,EAAE;YACH,GAAG,OAAO,CAAC,GAAG;YACd,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC;YACnD,WAAW,EAAE,IAAI,CAAC,YAAY;YAC9B,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;YAC9C,UAAU,EAAE,IAAI,CAAC,SAAS;YAC1B,QAAQ,EAAE,UAAU;YACpB,OAAO,EAAE,IAAI,CAAC,SAAS;SACxB;QACD,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;KAClC,CACF,CAAC;IACF,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;mEAEmE;AACnE,MAAM,CAAC,MAAM,yBAAyB,GAAG,wDAAwD,CAAC;AAClG,MAAM,CAAC,MAAM,oBAAoB,GAAG,mDAAmD,CAAC"}
1
+ {"version":3,"file":"account-lifecycle.js","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:86a7d5decfa1f569dcb4a23a7506fe36f23947c44dc68d4c43277be01c6418e2
4
+ content-hash: sha256:6ab87c68af715d29cae2a3fa1273c434fb8c88cdbe96eb86e0c7216895625085
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -2803,13 +2803,19 @@ authoritative. This section names the surfaces and what backs each.
2803
2803
  > **Admin variant only.** Every surface below belongs to the `admin` shell
2804
2804
  > variant. The **operator** variant (`operator.<host>`) mounts no sidebar at
2805
2805
  > all: its dashboard is the persistent admin webchat at `/` and the data
2806
- > browser at `/data`, plus a read-only **Conversations** reader reached from a
2807
- > dynamic burger item — header order Chat → Conversations → Data → Log out, in
2808
- > a single-column `.platform.platform-operator` grid. The operator never sees
2809
- > the sessions list or the nav rows; the Conversations reader lists the agent's
2810
- > inbound channel conversations (WhatsApp, Telegram, public webchat) read-only
2811
- > via the same admin-gated reader routes, never the operator's own session. The
2812
- > operator surface is documented in full in the internal doc
2806
+ > browser at `/data`, plus a **Conversations** item reached from a dynamic
2807
+ > burger item — header order Chat → Conversations → Data → Log out, in a
2808
+ > single-column `.platform.platform-operator` grid. On the operator **chat**
2809
+ > surface the Conversations item opens the same in-chat session
2810
+ > switcher the admin `/chat` surface uses (`ChatSurface`'s `SessionList` flyout):
2811
+ > the operator's own admin webchat conversations, newest-first, each resuming
2812
+ > live via `/chat?session=<id>`, with rename / archive / delete / end / new — not
2813
+ > a read-only reader. The item gates on the flyout handler being wired, exactly
2814
+ > like the admin and lite items. The read-only inbound-channel **Conversations**
2815
+ > reader (WhatsApp, Telegram, public webchat, via the admin-gated
2816
+ > `/api/whatsapp-reader/conversations` route) remains on `/data`'s Conversations
2817
+ > item and in the admin Sidebar; it is no longer reachable from the operator chat
2818
+ > surface. The operator surface is documented in full in the internal doc
2813
2819
  > `maxy-code/.docs/operator-dashboard.md`.
2814
2820
  >
2815
2821
  > **Operator `/chat`.** The reduced operator chrome renders on
@@ -2833,15 +2839,17 @@ authoritative. This section names the surfaces and what backs each.
2833
2839
  > surface; the rest of the admin shell stays edge-to-edge. The public webchat
2834
2840
  > (`.public-surface`) inherits the same value.
2835
2841
  >
2836
- > **Conversations item.** The dynamic Conversations burger item appears on
2837
- > **both** the operator chat surface and `/data` whenever ≥1 reviewable
2838
- > conversation exists (the count/channels are fetched per surface from
2839
- > `/whatsapp-reader/conversations` with the operator's session key). On `/data`
2840
- > selecting it renders the same read-only reader in place (the file browser is
2841
- > swapped for it; Back returns). The flyout-open signal
2842
- > `[operator-ui] op=conversations-menu present=<bool> channels=[…] count=<n>`
2843
- > fires on `/data` too `present=false` there while the chat surface shows the
2844
- > item is the props-not-wired signature.
2842
+ > **Conversations item.** The dynamic Conversations burger item gates on the
2843
+ > host wiring its open-handler (like the admin and lite items), not on a channel
2844
+ > count. On the operator **chat** surface the handler is always wired, so the
2845
+ > item is always present and opens the live session switcher (above). On `/data`
2846
+ > the handler is wired only when ≥1 reviewable inbound conversation exists
2847
+ > (fetched from `/whatsapp-reader/conversations` with the operator's session
2848
+ > key), and selecting it renders the read-only reader in place (the file browser
2849
+ > is swapped for it; Back returns) so `/data` keeps its hide-when-empty
2850
+ > behaviour. The flyout-open signal `[operator-ui] op=conversations-menu
2851
+ > present=<bool>` fires for the operator variant on both surfaces; `present=false`
2852
+ > while the operator expects the item is the handler-not-wired signature.
2845
2853
 
2846
2854
  | Surface | What it does | Backed by |
2847
2855
  |---|---|---|
@@ -33,15 +33,18 @@ If the site captures form data, add the `[[d1_databases]]` binding from `d1-data
33
33
 
34
34
  ## 3. Load the reused deploy token
35
35
 
36
- A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). Load the stable per-scope token minting it once only if absent per `api.md` § Provisioning and reusing a stable per-scope token:
36
+ A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). The `pages` scope resolves the stable `<brand>-pages-d1` token, which carries both. Resolve it with the deterministic helper, which mints it once only if absent and reuses it thereafter. This is the one command; do not source the secrets file and hand the resulting `CLOUDFLARE_API_TOKEN` to `wrangler` — that variable holds the **master**, not the per-scope token, and `wrangler pages` then returns `Authentication error [code: 10000]`:
37
37
 
38
38
  ```bash
39
+ KEY=$(bash platform/plugins/cloudflare/bin/cf-token.sh pages "${SECRETS_DIR}/cloudflare.env")
39
40
  set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
40
- : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
41
- # PAGES_D1 = the reused per-scope token (Pages Edit, + D1 Edit if the site uses D1),
42
- # loaded from cloudflare.env and minted once if absent see api.md § Provisioning and reusing.
41
+ : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
42
+ PAGES_D1="${!KEY}" # the active Pages(+D1) per-scope token NOT the sourced CLOUDFLARE_API_TOKEN (that is the master)
43
+ : "${PAGES_D1:?per-scope token not resolvedre-run cf-token.sh and read its output}"
43
44
  ```
44
45
 
46
+ A `10000` here is a token-selection symptom, not a permissions wall: it means the master reached `wrangler`. Re-run the helper line and retry with `${PAGES_D1}`; never fall back to a manual step on the strength of a `10000`. The mint-once, reuse, prefix-routing, and redaction details live in `api.md` § Provisioning and reusing a stable per-scope token; this command is self-contained and does not depend on reading that section first.
47
+
45
48
  ## 4. Deploy
46
49
 
47
50
  ```bash
@@ -9,7 +9,9 @@ Run by the admin/main session, the operator-infra surface that holds the `Bash`
9
9
 
10
10
  This is the entry point for every Cloudflare task on the install. Pick the operation class, read the matching reference, run the command via Bash, relay the literal output, and prove the outcome with a live signal. There are no Cloudflare MCP tools; the plugin registers none. The agent never browser-automates the dashboard — the operator clicks where a click is genuinely required.
11
11
 
12
- | You want to… | Read | Outcome contract |
12
+ **How to open every `references/…` file named in this skill.** These reference files live at the plugin root (`platform/plugins/cloudflare/references/`), one directory above this skill. They are not children of the directory this skill announces as its base. Read each one with the `plugin-read` tool, which resolves the path plugin-root-relative: `plugin-read` with `file="references/api.md"` returns the file. A plain `Read` of `references/api.md` against this skill's announced base directory resolves to `.../skills/cloudflare/references/api.md`, which does not exist and returns "File does not exist". That miss is not a reason to skip the reference, to substitute a non-canonical file, or to improvise the command from memory. Use `plugin-read` for every `references/…` mention below, the same way the sibling `site-deploy` and `calendar-site` skills do.
13
+
14
+ | You want to… | Read via `plugin-read` | Outcome contract |
13
15
  |---|---|---|
14
16
  | Stand up / diagnose / reset a tunnel so a hostname is reachable | `references/manual-setup.md` | external `curl -I https://<hostname>` → `200` |
15
17
  | Switch accounts, edit an apex CNAME by hand, or other dashboard-only clicks | `references/dashboard-guide.md` | the click-path completed in the operator's browser |
@@ -27,19 +29,20 @@ This is the entry point for every Cloudflare task on the install. Pick the opera
27
29
 
28
30
  Neither the master token nor any per-scope token is ever written into a project tree, committed, or echoed into chat — the per-scope tokens persist only in the account-scoped secrets file, never in a deployable project tree.
29
31
 
30
- ### Token operations: run the helper first (binding)
32
+ ### Resolve the per-scope token before any `wrangler` / API call (binding)
31
33
 
32
- Before **any** Cloudflare API token operation (verify, list, mint, reuse), resolve the per-scope token with the deterministic helper, never a hand-built `curl`:
34
+ This binding governs **every** Pages, D1, DNS, or Access operation, not only calls you think of as "token operations." Before the first `wrangler pages …`, `wrangler d1 …`, or Cloudflare-API `curl` of a task, resolve the per-scope token with the deterministic helper and pass **that** token to the command:
33
35
 
34
36
  ```bash
35
37
  KEY=$(bash platform/plugins/cloudflare/bin/cf-token.sh <scope> <secrets-file>) # scope: pages | d1 | dns | access
36
38
  set -a; . <secrets-file>; set +a
37
- # the active, persisted per-scope token is now in the variable named by $KEY
39
+ # ${!KEY} now holds the active, persisted per-scope token. Pass it explicitly:
40
+ CLOUDFLARE_API_TOKEN="${!KEY}" wrangler pages project list
38
41
  ```
39
42
 
40
- The helper owns the one decision the false-negative failure turns on: it reads the master's `cfat_`/`cfut_` prefix and routes verify, `permission_groups`, and mint to the matching endpoint family, mints once if absent (no expiry), verifies through the transient post-mint window, persists to the secrets file, and prints only the secrets key name. It never prints a token value.
43
+ **The antipattern this exists to stop.** Sourcing the secrets file loads the **master** token into `CLOUDFLARE_API_TOKEN`. If you then run `wrangler pages …` (or a Cloudflare-API `curl`) with the environment as-sourced, you hand the **master** to the tool, not the per-scope token. The master is the wrong token for these endpoints, so Cloudflare rejects the call, and the rejection reads like a permissions wall when it is really a token-selection mistake. `. <secrets-file>` immediately followed by `wrangler pages …` is the exact sequence that trips this; the helper line above is what prevents it. Never let `CLOUDFLARE_API_TOKEN` reach `wrangler`/`curl` still holding the sourced master.
41
44
 
42
- A raw `curl` to a token endpoint chosen **before** the master's prefix is established is out of bounds. The wrong endpoint returns a stable false-negative that reads as "cannot mint" but is not: a `cfat_` master at `/user/tokens/verify` returns `Invalid API Token`, and a `cfut_` master at any `/accounts/{id}/...` endpoint returns `9109`. If you see either from a token probe, the cause is the endpoint, not the token. Stop and run the helper. Doing the verify or mint by hand is permitted only after reading the `references/api.md` section on routing token endpoints by prefix, and routing by the established prefix.
45
+ **A `10000` / `Invalid API Token` / `9109` from a `wrangler` or API call is a token-selection symptom, never "this token lacks permission."** The recovery is always the same: run `cf-token.sh <scope> <secrets-file>` and re-issue the command with the resolved per-scope token. Concretely: a just-sourced master handed to `wrangler pages …` returns `Authentication error [code: 10000]`; a `cfat_` master at `/user/tokens/verify` returns `Invalid API Token`; a `cfut_` master at any `/accounts/{id}/...` endpoint returns `9109`. None of these means "cannot deploy" or "lacks Pages/DNS permission." Do not fall back to a manual DNS record, a tunnel workaround, or an operator ask on the strength of one of these codes — run the helper and retry. The helper reads the master's `cfat_`/`cfut_` prefix, routes verify/`permission_groups`/mint to the matching endpoint family, mints once if absent (no expiry), verifies through the transient post-mint window, persists to the secrets file, and prints only the secrets key name; it never prints a token value. Doing verify or mint by hand is permitted only after reading the `references/api.md` section on routing token endpoints by prefix, and routing by the established prefix.
43
46
 
44
47
  ## Outcome contracts — what "done" means
45
48
 
@@ -323,13 +323,19 @@ authoritative. This section names the surfaces and what backs each.
323
323
  > **Admin variant only.** Every surface below belongs to the `admin` shell
324
324
  > variant. The **operator** variant (`operator.<host>`) mounts no sidebar at
325
325
  > all: its dashboard is the persistent admin webchat at `/` and the data
326
- > browser at `/data`, plus a read-only **Conversations** reader reached from a
327
- > dynamic burger item — header order Chat → Conversations → Data → Log out, in
328
- > a single-column `.platform.platform-operator` grid. The operator never sees
329
- > the sessions list or the nav rows; the Conversations reader lists the agent's
330
- > inbound channel conversations (WhatsApp, Telegram, public webchat) read-only
331
- > via the same admin-gated reader routes, never the operator's own session. The
332
- > operator surface is documented in full in the internal doc
326
+ > browser at `/data`, plus a **Conversations** item reached from a dynamic
327
+ > burger item — header order Chat → Conversations → Data → Log out, in a
328
+ > single-column `.platform.platform-operator` grid. On the operator **chat**
329
+ > surface the Conversations item opens the same in-chat session
330
+ > switcher the admin `/chat` surface uses (`ChatSurface`'s `SessionList` flyout):
331
+ > the operator's own admin webchat conversations, newest-first, each resuming
332
+ > live via `/chat?session=<id>`, with rename / archive / delete / end / new — not
333
+ > a read-only reader. The item gates on the flyout handler being wired, exactly
334
+ > like the admin and lite items. The read-only inbound-channel **Conversations**
335
+ > reader (WhatsApp, Telegram, public webchat, via the admin-gated
336
+ > `/api/whatsapp-reader/conversations` route) remains on `/data`'s Conversations
337
+ > item and in the admin Sidebar; it is no longer reachable from the operator chat
338
+ > surface. The operator surface is documented in full in the internal doc
333
339
  > `maxy-code/.docs/operator-dashboard.md`.
334
340
  >
335
341
  > **Operator `/chat`.** The reduced operator chrome renders on
@@ -353,15 +359,17 @@ authoritative. This section names the surfaces and what backs each.
353
359
  > surface; the rest of the admin shell stays edge-to-edge. The public webchat
354
360
  > (`.public-surface`) inherits the same value.
355
361
  >
356
- > **Conversations item.** The dynamic Conversations burger item appears on
357
- > **both** the operator chat surface and `/data` whenever ≥1 reviewable
358
- > conversation exists (the count/channels are fetched per surface from
359
- > `/whatsapp-reader/conversations` with the operator's session key). On `/data`
360
- > selecting it renders the same read-only reader in place (the file browser is
361
- > swapped for it; Back returns). The flyout-open signal
362
- > `[operator-ui] op=conversations-menu present=<bool> channels=[…] count=<n>`
363
- > fires on `/data` too `present=false` there while the chat surface shows the
364
- > item is the props-not-wired signature.
362
+ > **Conversations item.** The dynamic Conversations burger item gates on the
363
+ > host wiring its open-handler (like the admin and lite items), not on a channel
364
+ > count. On the operator **chat** surface the handler is always wired, so the
365
+ > item is always present and opens the live session switcher (above). On `/data`
366
+ > the handler is wired only when ≥1 reviewable inbound conversation exists
367
+ > (fetched from `/whatsapp-reader/conversations` with the operator's session
368
+ > key), and selecting it renders the read-only reader in place (the file browser
369
+ > is swapped for it; Back returns) so `/data` keeps its hide-when-empty
370
+ > behaviour. The flyout-open signal `[operator-ui] op=conversations-menu
371
+ > present=<bool>` fires for the operator variant on both surfaces; `present=false`
372
+ > while the operator expects the item is the handler-not-wired signature.
365
373
 
366
374
  | Surface | What it does | Backed by |
367
375
  |---|---|---|
@@ -21,7 +21,7 @@ Activate when the operator asks for any of:
21
21
  Do **not** activate for:
22
22
 
23
23
  - Listing or searching emails as the end-goal (no draft intent) — let `email-search` / `email-graph-query` answer directly.
24
- - Setup, status checks, OTP extraction — those are direct tool calls. (For Google/Workspace setup the agent must require an app password, never the regular password — see the email plugin's `references/email-reference.md` § Setup → Google. For Microsoft / Microsoft 365 addresses there is no email-plugin path at all — register via the `outlook` Graph plugin's `outlook-account-register`, not `email-setup` — see § Setup → Microsoft.)
24
+ - Setup, status checks, OTP extraction — those are direct tool calls. (For Google/Workspace setup the agent must require an app password, never the regular password — see the email plugin's `references/email-reference.md` § Setup → Google, opened with the `plugin-read` tool (`file="references/email-reference.md"`), which resolves it at the plugin root; a plain `Read` against this skill's base directory misses. For Microsoft / Microsoft 365 addresses there is no email-plugin path at all — register via the `outlook` Graph plugin's `outlook-account-register`, not `email-setup` — see § Setup → Microsoft.)
25
25
  - Any automated send loop. If the operator asks for drip / scheduled / broadcast sends, decline and explain that this skill is human-in-the-loop only.
26
26
 
27
27
  ## Flow — reply to an existing thread
@@ -63,5 +63,7 @@ Subsequent tool calls use the persisted refresh token to mint access tokens tran
63
63
 
64
64
  ## Reference files
65
65
 
66
+ Open each with the `plugin-read` tool (`file="references/auth.md"`), which resolves the path at the plugin root. A plain `Read` of `references/auth.md` against this skill's base directory misses, because these files live one level up at the plugin root, not under the skill.
67
+
66
68
  - [`references/auth.md`](references/auth.md) — Entra app registration + PKCE flow + re-vendor procedure
67
69
  - [`references/graph-surfaces.md`](references/graph-surfaces.md) — Graph endpoint and response shape per tool
@@ -22,6 +22,8 @@ A finished Telegram setup has all of these, and the skill is not done until each
22
22
 
23
23
  ## Flow
24
24
 
25
+ Read `references/setup-guide.md` (cited throughout this flow) with the `plugin-read` tool (`file="references/setup-guide.md"`), which resolves it at the plugin root. A plain `Read` against this skill's base directory misses, because the file lives one level up at the plugin root, not under the skill.
26
+
25
27
  ### Step 1 — Get the bot token
26
28
 
27
29
  Follow `references/setup-guide.md` "Creating a Bot": the operator creates the bot via @BotFather and gives you the token (e.g. `123456:ABC-DEF…`). Confirm you have it before continuing.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ # Task 1362 — the reference-reachability guard.
3
+ # A SKILL.md's plugin-root-only bare `references/…` citation is reachable ONLY
4
+ # when the SKILL.md declares the `plugin-read` convention. Without it, a plain
5
+ # Read against the announced skill base dir misses, so the citation is a
6
+ # violation. Skill-local references, full `plugins/<name>/references/…` paths,
7
+ # and PLUGIN.md plugin-root references are unaffected.
8
+ set -u
9
+ GATE="$(cd "$(dirname "$0")/.." && pwd)/check-plugin-references.mjs"
10
+ [[ -f "$GATE" ]] || { echo "FAIL: gate script missing at $GATE" >&2; exit 1; }
11
+ PASS=0; FAIL=0
12
+
13
+ ROOT=$(mktemp -d)
14
+ PREMIUM="$ROOT/premium-empty"
15
+ mkdir -p "$PREMIUM"
16
+ trap 'rm -rf "$ROOT"' EXIT
17
+
18
+ reset_root() { rm -rf "$ROOT/platform"; }
19
+
20
+ # Write a SKILL.md for plugin "$1" (skill slug == plugin name) with body "$2".
21
+ make_skill() {
22
+ local plugin="$1" body="$2"
23
+ local dir="$ROOT/platform/plugins/$plugin/skills/$plugin"
24
+ mkdir -p "$dir"
25
+ { printf -- '---\nname: %s\ndescription: test skill\n---\n\n' "$plugin"; printf '%s\n' "$body"; } > "$dir/SKILL.md"
26
+ }
27
+
28
+ # Create a reference file at an arbitrary path under $ROOT.
29
+ make_ref() { local rel="$1"; mkdir -p "$ROOT/$(dirname "$rel")"; printf 'ref body\n' > "$ROOT/$rel"; }
30
+
31
+ check() {
32
+ local desc="$1" expected="$2"
33
+ node "$GATE" "$ROOT/platform" "$PREMIUM" >/dev/null 2>&1
34
+ local got=$?
35
+ if [[ "$got" -eq "$expected" ]]; then echo "PASS: $desc"; ((PASS++))
36
+ else echo "FAIL: $desc (expected exit $expected, got $got)"; ((FAIL++)); fi
37
+ }
38
+
39
+ # CASE 1: plugin-root-only ref, NO plugin-read declared → violation.
40
+ reset_root
41
+ make_ref "platform/plugins/foo/references/api.md"
42
+ make_skill "foo" 'Read \`references/api.md\` before the API call.'
43
+ check "plugin-root-only ref without plugin-read fails" 1
44
+
45
+ # CASE 2: same ref, plugin-read declared → passes.
46
+ reset_root
47
+ make_ref "platform/plugins/foo/references/api.md"
48
+ make_skill "foo" 'Open \`references/api.md\` with the \`plugin-read\` tool before the API call.'
49
+ check "plugin-root-only ref with plugin-read passes" 0
50
+
51
+ # CASE 3: skill-local ref, NO plugin-read → passes (plain Read resolves it).
52
+ reset_root
53
+ make_ref "platform/plugins/foo/skills/foo/references/local.md"
54
+ make_skill "foo" 'Read \`references/local.md\` for the details.'
55
+ check "skill-local ref without plugin-read passes" 0
56
+
57
+ # CASE 4: full plugins/<name>/references/... path, NO plugin-read → passes.
58
+ reset_root
59
+ make_ref "platform/plugins/foo/references/api.md"
60
+ make_skill "foo" 'Read \`plugins/foo/references/api.md\` before the API call.'
61
+ check "full plugin-path ref without plugin-read passes" 0
62
+
63
+ # CASE 5: genuinely missing ref (neither skill-local nor plugin-root) → violation.
64
+ reset_root
65
+ make_skill "foo" 'Open \`references/ghost.md\` with \`plugin-read\`.'
66
+ check "missing ref fails even with plugin-read" 1
67
+
68
+ # CASE 6: a MISSING full plugins/<name>/references/... path from a SKILL.md without
69
+ # plugin-read → violation, and the plugin-root "declare plugin-read" hint must NOT
70
+ # fire (the hint is scoped to the bare references/ shape only; a full path is not
71
+ # reachability-limited by the missing convention). Guards the Task 1362 hint-scope fix.
72
+ reset_root
73
+ make_skill "foo" 'Read \`plugins/foo/references/ghost.md\` before the API call.'
74
+ OUT=$(node "$GATE" "$ROOT/platform" "$PREMIUM" 2>&1); GOT=$?
75
+ if [[ "$GOT" -eq 1 ]] && ! grep -q "exists at plugin root" <<<"$OUT"; then
76
+ echo "PASS: missing full-path ref fails without a false plugin-read hint"; ((PASS++))
77
+ else
78
+ echo "FAIL: missing full-path ref (exit=$GOT, hint present=$(grep -qc 'exists at plugin root' <<<"$OUT" && echo yes || echo no))"; ((FAIL++))
79
+ fi
80
+
81
+ echo ""
82
+ echo "Results: $PASS passed, $FAIL failed"
83
+ [[ "$FAIL" -eq 0 ]] && exit 0 || exit 1
@@ -17,6 +17,21 @@
17
17
  // 2. `plugins/<name>/references/<file>.md` (optionally prefixed by
18
18
  // `platform/`) — resolves at `<platformRoot>/plugins/<name>/references/
19
19
  // <file>.md`.
20
+ //
21
+ // Task 1362 — access-method tightening for SKILL.md bare references.
22
+ // A SKILL.md announces its own skill directory as the base dir. An agent that
23
+ // reads "references/foo.md" with a plain file `Read` resolves it against that
24
+ // base dir → `<skill-dir>/references/foo.md`. When the reference actually lives
25
+ // at the plugin root (`<plugin-root>/references/foo.md`, the shared layout the
26
+ // cloudflare/outlook/telegram/email plugins use) that plain read MISSES, and the
27
+ // agent gets "File does not exist" — the reliability trap Task 1362 removes.
28
+ // The plugin-root path is only reachable if the SKILL.md tells the agent to read
29
+ // via the `plugin-read` tool (which resolves plugin-root-relative). So for a bare
30
+ // reference cited from a SKILL.md, the plugin-root candidate counts ONLY when that
31
+ // SKILL.md declares the `plugin-read` convention; without it, the skill-local path
32
+ // is the sole candidate and a plugin-root-only reference is a violation. PLUGIN.md
33
+ // is itself a plugin-root doc always read via plugin-read, so its plugin-root
34
+ // candidate is unconditional (unchanged).
20
35
 
21
36
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
22
37
  import { resolve, relative, join, dirname } from "node:path";
@@ -85,23 +100,25 @@ function owningPluginRoot(filePath) {
85
100
 
86
101
  // Directories to resolve a bare `references/<file>.md` citation against.
87
102
  // Returns an array of candidate directories; the citation passes if the file
88
- // exists under ANY of them. Both candidate paths for SKILL.md are reachable
89
- // via `plugin-read` — the agent may construct `file=references/foo.md`
90
- // (plugin-root form) or `file=skills/<slug>/references/foo.md` (skill-local
91
- // form), depending on how the plugin organises its references.
103
+ // exists under ANY of them.
92
104
  //
93
105
  // PLUGIN.md — plugin-root only. → [<plugin-root>]
94
- // SKILL.md — skill-local OR plugin-root.
95
- // [<skill-dir>, <plugin-root>]
106
+ // SKILL.md — skill-local always; plugin-root ONLY when the SKILL.md
107
+ // declares the `plugin-read` convention (Task 1362). A plain
108
+ // `Read` from a SKILL.md resolves against the announced skill
109
+ // dir, so a plugin-root-only reference is unreachable unless
110
+ // the SKILL.md routes the read through `plugin-read`.
111
+ // → [<skill-dir>] or
112
+ // [<skill-dir>, <plugin-root>]
96
113
  // specialist template — no owning context. → []
97
- function bareReferenceContextDirs(filePath) {
114
+ function bareReferenceContextDirs(filePath, declaresPluginRead) {
98
115
  if (filePath.endsWith("/PLUGIN.md")) {
99
116
  return [dirname(filePath)];
100
117
  }
101
118
  if (filePath.endsWith("/SKILL.md")) {
102
119
  const skillDir = dirname(filePath);
103
120
  const pluginRoot = owningPluginRoot(filePath);
104
- return pluginRoot ? [skillDir, pluginRoot] : [skillDir];
121
+ return pluginRoot && declaresPluginRead ? [skillDir, pluginRoot] : [skillDir];
105
122
  }
106
123
  return [];
107
124
  }
@@ -110,7 +127,7 @@ function bareReferenceContextDirs(filePath) {
110
127
  // citation passes the existence check if ANY candidate exists. Returns an
111
128
  // empty array if the citation has no resolvable shape (bare reference cited
112
129
  // from a non-plugin file).
113
- function candidateTargets({ raw, sourcePath, platformRoot }) {
130
+ function candidateTargets({ raw, sourcePath, platformRoot, declaresPluginRead }) {
114
131
  // Strip an optional leading `platform/` so `platform/plugins/<name>/...`
115
132
  // and `plugins/<name>/...` resolve identically.
116
133
  const normalised = raw.replace(/^platform\//, "");
@@ -130,7 +147,7 @@ function candidateTargets({ raw, sourcePath, platformRoot }) {
130
147
  }
131
148
 
132
149
  // Bare `references/<file>.md`.
133
- const ctxDirs = bareReferenceContextDirs(sourcePath);
150
+ const ctxDirs = bareReferenceContextDirs(sourcePath, declaresPluginRead);
134
151
  return ctxDirs.map(d => resolve(d, normalised));
135
152
  }
136
153
 
@@ -141,6 +158,10 @@ function scanFile(filePath, { platformRoot, repoRoot }) {
141
158
  } catch {
142
159
  return { violations: [], citations: 0 };
143
160
  }
161
+ // Whether this file routes its bare references through the `plugin-read` tool.
162
+ // Governs whether a SKILL.md's plugin-root reference candidate is reachable
163
+ // (Task 1362 — see bareReferenceContextDirs). PLUGIN.md ignores this.
164
+ const declaresPluginRead = /plugin-read/.test(body);
144
165
  const violations = [];
145
166
  let citations = 0;
146
167
  const lines = body.split("\n");
@@ -153,7 +174,7 @@ function scanFile(filePath, { platformRoot, repoRoot }) {
153
174
  // 1-based column, pointing at the start of the captured citation.
154
175
  const column = (m.index + m[0].indexOf(raw)) + 1;
155
176
  citations++;
156
- const candidates = candidateTargets({ raw, sourcePath: filePath, platformRoot });
177
+ const candidates = candidateTargets({ raw, sourcePath: filePath, platformRoot, declaresPluginRead });
157
178
  if (candidates.length === 0) {
158
179
  violations.push({
159
180
  source: relative(repoRoot, filePath),
@@ -171,12 +192,39 @@ function scanFile(filePath, { platformRoot, repoRoot }) {
171
192
  const targetMsg = candidates.length === 1
172
193
  ? candidates[0]
173
194
  : `none of [${candidates.join(", ")}]`;
195
+ // Task 1362 hint: a SKILL.md BARE `references/<file>.md` citation that
196
+ // misses skill-local but DOES exist at the plugin root is an access-method
197
+ // defect, not a missing file. The fix is to declare `plugin-read` (or write
198
+ // the full `plugins/<name>/references/...` path), not to create a skill-local
199
+ // copy. Restrict the hint to the bare shape: a citation already written as a
200
+ // full `plugins/.../references/...` (or `skills/.../references/...`) path is
201
+ // resolved elsewhere and is not reachability-limited by a missing convention,
202
+ // so it gets no hint (and its `raw` would resolve to a bogus nested path here).
203
+ let hint;
204
+ const isBareRef = raw.replace(/^platform\//, "").startsWith("references/");
205
+ if (
206
+ isBareRef &&
207
+ filePath.endsWith("/SKILL.md") &&
208
+ !declaresPluginRead &&
209
+ owningPluginRoot(filePath)
210
+ ) {
211
+ const rootCandidate = resolve(
212
+ owningPluginRoot(filePath),
213
+ raw.replace(/^platform\//, ""),
214
+ );
215
+ if (existsSync(rootCandidate)) {
216
+ hint =
217
+ `exists at plugin root (${relative(repoRoot, rootCandidate)}) but this SKILL.md does not declare the \`plugin-read\` convention, ` +
218
+ `so a plain Read misses it — declare \`plugin-read\` or cite the full \`plugins/<name>/references/...\` path`;
219
+ }
220
+ }
174
221
  violations.push({
175
222
  source: relative(repoRoot, filePath),
176
223
  line: i + 1,
177
224
  column,
178
225
  target: targetMsg,
179
226
  raw,
227
+ ...(hint ? { hint } : {}),
180
228
  });
181
229
  }
182
230
  }
@@ -244,6 +292,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
244
292
  console.error(` source: ${v.source}:${v.line}:${v.column}`);
245
293
  console.error(` cites: ${v.raw}`);
246
294
  console.error(` target: ${v.target}`);
295
+ if (v.hint) console.error(` hint: ${v.hint}`);
247
296
  }
248
297
  console.error(
249
298
  `FATAL: ${r.violations.length} broken reference citation(s) across ${r.scanned} scanned file(s) (${r.citations} citations).`,