0xray 4.0.40 → 4.0.42

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 (190) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/agents/api-design.yml +31 -0
  3. package/.opencode/agents/architect.yml +15 -0
  4. package/.opencode/agents/architecture-patterns.yml +14 -0
  5. package/.opencode/agents/auto-format.yml +14 -0
  6. package/.opencode/agents/backend-engineer.yml +12 -0
  7. package/.opencode/agents/boot-orchestrator.yml +14 -0
  8. package/.opencode/agents/bug-triage-specialist.yml +15 -0
  9. package/.opencode/agents/code-analyzer.yml +12 -0
  10. package/.opencode/agents/code-reviewer.yml +14 -0
  11. package/.opencode/agents/content-creator.yml +12 -0
  12. package/.opencode/agents/database-engineer.yml +12 -0
  13. package/.opencode/agents/devops-engineer.yml +12 -0
  14. package/.opencode/agents/framework-compliance-audit.yml +14 -0
  15. package/.opencode/agents/frontend-engineer.yml +12 -0
  16. package/.opencode/agents/frontend-ui-ux-engineer.yml +12 -0
  17. package/.opencode/agents/git-workflow.yml +15 -0
  18. package/.opencode/agents/growth-strategist.yml +12 -0
  19. package/.opencode/agents/hermes-agent.yml +12 -0
  20. package/.opencode/agents/inference-improve.yml +12 -0
  21. package/.opencode/agents/lint.yml +14 -0
  22. package/.opencode/agents/log-monitor.yml +12 -0
  23. package/.opencode/agents/mobile-developer.yml +12 -0
  24. package/.opencode/agents/model-health-check.yml +12 -0
  25. package/.opencode/agents/multimodal-looker.yml +12 -0
  26. package/.opencode/agents/performance-analysis.yml +12 -0
  27. package/.opencode/agents/performance-engineer.yml +12 -0
  28. package/.opencode/agents/performance-optimization.yml +12 -0
  29. package/.opencode/agents/processor-pipeline.yml +14 -0
  30. package/.opencode/agents/project-analysis.yml +12 -0
  31. package/.opencode/agents/refactorer.yml +12 -0
  32. package/.opencode/agents/researcher.yml +12 -0
  33. package/.opencode/agents/security-auditor.yml +12 -0
  34. package/.opencode/agents/security-scan.yml +12 -0
  35. package/.opencode/agents/seo-consultant.yml +12 -0
  36. package/.opencode/agents/session-management.yml +12 -0
  37. package/.opencode/agents/state-manager.yml +12 -0
  38. package/.opencode/agents/storyteller.yml +12 -0
  39. package/.opencode/agents/strategist.yml +12 -0
  40. package/.opencode/agents/tech-writer.yml +12 -0
  41. package/.opencode/agents/testing-best-practices.yml +12 -0
  42. package/.opencode/agents/testing-lead.yml +12 -0
  43. package/.opencode/agents/ui-ux-design.yml +12 -0
  44. package/.opencode/codex.codex +8 -0
  45. package/.opencode/commands/auto-format.md +99 -0
  46. package/.opencode/commands/auto-summary-capture.md +90 -0
  47. package/.opencode/commands/dependency-audit.md +184 -0
  48. package/.opencode/commands/enforcer-daily-scan.md +137 -0
  49. package/.opencode/commands/framework-compliance-audit.md +205 -0
  50. package/.opencode/commands/interactive-validator.md +75 -0
  51. package/.opencode/commands/job-summary-logger.md +68 -0
  52. package/.opencode/commands/lint.md +11 -0
  53. package/.opencode/commands/mode-switch.md +95 -0
  54. package/.opencode/commands/model-health-check.md +186 -0
  55. package/.opencode/commands/performance-analysis.md +144 -0
  56. package/.opencode/commands/pre-commit-introspection.md +185 -0
  57. package/.opencode/commands/pre-commit-introspection.sh +133 -0
  58. package/.opencode/commands/security-scan.md +157 -0
  59. package/.opencode/commands/sisyphus-validation.md +128 -0
  60. package/.opencode/commands/summary-logger.md +83 -0
  61. package/.opencode/enforcer-config.json +282 -0
  62. package/.opencode/skills/orchestrator/SKILL.md +12 -0
  63. package/.opencode/workflows/post-deployment-audit.yml +123 -0
  64. package/.xray/features.json +1 -1
  65. package/.xray/features.schema.json +1 -1
  66. package/AGENTS-consumer.md +2 -0
  67. package/AGENTS.md +2 -0
  68. package/README.md +52 -12
  69. package/SKILLS.md +2 -0
  70. package/dist/AGENTS.md +2 -0
  71. package/dist/CHANGELOG.md +29 -0
  72. package/dist/README.md +52 -12
  73. package/dist/SKILLS.md +2 -0
  74. package/dist/cli/commands/openclaw-install.js +1 -1
  75. package/dist/cli/index.js +12 -0
  76. package/dist/enforcement/index.d.ts +4 -0
  77. package/dist/enforcement/index.js +4 -0
  78. package/dist/enforcement/validators/code-quality-validators.js +6 -4
  79. package/dist/governance/llm-governance-provider.d.ts +7 -1
  80. package/dist/governance/llm-governance-provider.js +72 -14
  81. package/dist/governance/local-confer.d.ts +4 -1
  82. package/dist/governance/local-confer.js +22 -1
  83. package/dist/integrations/grok/grok-cli.js +10 -37
  84. package/dist/integrations/grok/hooks/grok-hook-utils.js +29 -2
  85. package/dist/integrations/grok/hooks/session-start.js +2 -20
  86. package/dist/integrations/hooks/confer-hook-runtime.mjs +23 -1
  87. package/dist/integrations/hooks/goggles-mcp.mjs +36 -4
  88. package/dist/integrations/hooks/goggles-pipeline.mjs +55 -15
  89. package/dist/integrations/hooks/goggles-planes.json +13 -4
  90. package/dist/integrations/hooks/plates.cjs +22 -0
  91. package/dist/integrations/hooks/session-note-index.cjs +298 -0
  92. package/dist/integrations/hooks/session-note-index.mjs +22 -0
  93. package/dist/integrations/hooks/sleeve.cjs +335 -0
  94. package/dist/integrations/hooks/sleeve.mjs +18 -0
  95. package/dist/integrations/hooks/station-hook-runtime.cjs +205 -9
  96. package/dist/integrations/hooks/wake-cascade.cjs +102 -0
  97. package/dist/integrations/hooks/wake-cascade.mjs +16 -0
  98. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  99. package/dist/mcps/architect-tools.server.d.ts +6 -0
  100. package/dist/mcps/architect-tools.server.js +19 -2
  101. package/dist/mcps/config/server-config-registry.js +8 -6
  102. package/dist/mcps/governance.server.d.ts +1 -0
  103. package/dist/mcps/governance.server.js +46 -0
  104. package/dist/mcps/knowledge-skills/code-review.server.js +7 -0
  105. package/dist/mcps/knowledge-skills/security-audit.server.js +5 -3
  106. package/dist/mcps/mcp-client.js +1 -1
  107. package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
  108. package/dist/mcps/researcher.server.js +12 -3
  109. package/dist/memory-routing/plates.d.ts +1 -1
  110. package/dist/memory-routing/plates.js +1 -0
  111. package/dist/nucleus/autonomy-kernel.d.ts +2 -0
  112. package/dist/nucleus/autonomy-kernel.js +17 -13
  113. package/dist/nucleus/confer-host.d.ts +35 -0
  114. package/dist/nucleus/confer-host.js +55 -0
  115. package/dist/nucleus/confer.d.ts +19 -3
  116. package/dist/nucleus/confer.js +111 -55
  117. package/dist/nucleus/lead-dev-plan-persistence.js +4 -2
  118. package/dist/nucleus/suit-temperament.d.ts +1 -1
  119. package/dist/nucleus/suit-temperament.js +2 -2
  120. package/dist/nucleus/synthesis-consult-receipt.d.ts +7 -1
  121. package/dist/nucleus/synthesis-consult-receipt.js +86 -35
  122. package/dist/reporting/framework-reporting-system.js +2 -5
  123. package/dist/reporting/log-parser.d.ts +2 -0
  124. package/dist/reporting/log-parser.js +7 -6
  125. package/dist/skills/orchestrator/SKILL.md +12 -0
  126. package/docs-site/docs/plates/activity-log.md +1 -1
  127. package/docs-site/docs/plates/boot.md +2 -0
  128. package/docs-site/docs/plates/centrifuge.md +30 -0
  129. package/docs-site/docs/plates/cursor-compact.md +1 -1
  130. package/docs-site/docs/plates/glossary.md +1 -2
  131. package/docs-site/docs/plates/goggles.md +4 -0
  132. package/docs-site/docs/plates/governance.md +2 -0
  133. package/docs-site/docs/plates/grok-compact.md +1 -1
  134. package/docs-site/docs/plates/grokbot.md +1 -1
  135. package/docs-site/docs/plates/host-pack.md +2 -0
  136. package/docs-site/docs/plates/house.md +1 -1
  137. package/docs-site/docs/plates/index.md +3 -1
  138. package/docs-site/docs/plates/inference-files.md +1 -1
  139. package/docs-site/docs/plates/kept-line.md +1 -1
  140. package/docs-site/docs/plates/kits.md +2 -0
  141. package/docs-site/docs/plates/lens-gate.md +1 -1
  142. package/docs-site/docs/plates/lens-page.md +1 -1
  143. package/docs-site/docs/plates/live-mesh.md +1 -1
  144. package/docs-site/docs/plates/memory-recall.md +4 -0
  145. package/docs-site/docs/plates/notes-page.md +2 -1
  146. package/docs-site/docs/plates/orchestration.md +2 -0
  147. package/docs-site/docs/plates/package-face.md +1 -1
  148. package/docs-site/docs/plates/payload-heat.md +1 -1
  149. package/docs-site/docs/plates/pickup-stamp.md +1 -1
  150. package/docs-site/docs/plates/pre-tool.md +1 -1
  151. package/docs-site/docs/plates/processor.md +2 -0
  152. package/docs-site/docs/plates/record-map.md +2 -1
  153. package/docs-site/docs/plates/reflection-page.md +1 -1
  154. package/docs-site/docs/plates/reporting.md +2 -0
  155. package/docs-site/docs/plates/review.md +1 -1
  156. package/docs-site/docs/plates/routing.md +2 -0
  157. package/docs-site/docs/plates/session-capture.md +1 -1
  158. package/docs-site/docs/plates/site-manual.md +1 -1
  159. package/docs-site/docs/plates/stamp-plate.md +1 -1
  160. package/docs-site/docs/plates/station-card.md +4 -1
  161. package/docs-site/docs/plates/station-heat.md +1 -1
  162. package/docs-site/docs/plates/suit-organs.md +1 -1
  163. package/docs-site/docs/plates/suit-settings.md +1 -1
  164. package/docs-site/docs/plates/suit-wear.md +3 -1
  165. package/docs-site/docs/plates/suit.md +2 -0
  166. package/docs-site/docs/plates/trail-state.md +1 -1
  167. package/docs-site/docs/plates/work-fresh.md +1 -1
  168. package/docs-site/docs/plates/write-home.md +1 -1
  169. package/llms.txt +2 -0
  170. package/package.json +2 -2
  171. package/scripts/mjs/verify-confer-core.mjs +153 -29
  172. package/scripts/mjs/verify-user-aside-core.mjs +3 -4
  173. package/scripts/node/bridge-mcp-wiring.cjs +273 -9
  174. package/scripts/node/confer-switch.cjs +66 -0
  175. package/scripts/node/install-bridges.cjs +188 -60
  176. package/scripts/node/setup.cjs +2 -46
  177. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  178. package/src/mcps/architect-tools.server.ts +26 -2
  179. package/src/mcps/config/__tests__/server-config-registry.test.ts +15 -6
  180. package/src/mcps/config/server-config-registry.ts +9 -6
  181. package/src/mcps/governance.server.ts +49 -0
  182. package/src/mcps/knowledge-skills/code-review.server.ts +8 -0
  183. package/src/mcps/knowledge-skills/security-audit.server.ts +11 -12
  184. package/src/mcps/mcp-client.ts +1 -1
  185. package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
  186. package/src/mcps/researcher.server.ts +19 -10
  187. package/src/skills/orchestrator/SKILL.md +12 -0
  188. package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +5 -51
  189. package/xray/features.json +1 -1
  190. package/xray/features.schema.json +1 -1
@@ -17,7 +17,6 @@
17
17
  import { frameworkLogger } from '../../core/framework-logger.js';
18
18
  import fs from 'fs';
19
19
  import path from 'path';
20
- import { execSync } from 'child_process';
21
20
  import { createRequire } from 'module';
22
21
  import { fileURLToPath } from 'node:url';
23
22
  import { syncBuiltinSkills } from '../../cli/commands/skill-install.js';
@@ -27,27 +26,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
26
  const packageRoot = path.resolve(__dirname, '..', '..', '..');
28
27
  const requireCjs = createRequire(import.meta.url);
29
28
  const { resolveConsumerTargetDir, patchGrokHooks } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
30
- const { XRAY_MCP_SERVERS, resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
31
- function registerGrokMcpServers(targetDir) {
32
- try {
33
- execSync('which grok', { stdio: 'ignore' });
34
- }
35
- catch {
36
- console.log('[Grok] grok CLI not on PATH — plugin .mcp.json still configured');
37
- return;
38
- }
39
- for (const s of XRAY_MCP_SERVERS) {
40
- try {
41
- const envEntries = { ...s.env, XRAY_ROOT: targetDir };
42
- const envFlags = Object.entries(envEntries)
43
- .map(([k, v]) => `--env "${k}=${v}"`)
44
- .join(' ');
45
- execSync(`grok mcp add ${s.name} --command npx --args "-y" "0xray" "mcp" "${s.mcpCmd}" ${envFlags}`, { stdio: 'pipe' });
46
- }
47
- catch {
48
- // already registered or grok config conflict — non-blocking
49
- }
50
- }
29
+ const { resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
30
+ function registerGrokMcpServers(targetDir, env) {
31
+ const wiring = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
32
+ const tomlPath = wiring.writeProjectSuitMcp(targetDir);
33
+ const trustPath = wiring.trustGrokFolder(targetDir, env);
34
+ process.stdout.write(`Project Grok MCP written to ${tomlPath}\n`);
35
+ process.stdout.write(`Grok folder trust written to ${trustPath}\n`);
51
36
  }
52
37
  export async function installForGrokCLI(options = {}) {
53
38
  frameworkLogger.log('grok-integration', 'install-start', 'info', {
@@ -115,21 +100,9 @@ export async function installForGrokCLI(options = {}) {
115
100
  writeProjectRepertoireMcp(targetDir);
116
101
  writeProjectGogglesMcp(targetDir);
117
102
  mintAfterWear(targetDir);
118
- const primary = dests[0];
119
- try {
120
- execSync(`grok plugins trust "${primary}"`, { stdio: 'ignore' });
121
- console.log('\x1b[32m✓ Auto-trusted the 0xray plugin with Grok CLI\x1b[0m');
122
- }
123
- catch {
124
- console.log('\nPlease run this command to fully trust the plugin:');
125
- console.log(` grok plugins trust "${primary}"`);
126
- }
127
- if (isolated) {
128
- registerGrokMcpServers(targetDir);
129
- console.log('\x1b[32m✓ Registered 7 xray MCP servers with Grok CLI (npx)\x1b[0m');
130
- }
131
- else {
132
- console.log('\x1b[32m✓ Project-scoped Grok plugin worn (machine ~/.grok/plugins/0xray not written)\x1b[0m');
103
+ registerGrokMcpServers(targetDir, env);
104
+ if (!isolated) {
105
+ process.stdout.write('Project-scoped Grok plugin worn (machine ~/.grok/plugins/0xray not written)\n');
133
106
  }
134
107
  console.log('\n✅ 0xRay is now installed as a first-class Grok CLI plugin!');
135
108
  console.log('Restart Grok or run `grok` to load the new hooks and MCP servers.');
@@ -19,7 +19,7 @@ import {
19
19
  updatePlanTodoStatusInPlace,
20
20
  loadDelegationGateFeatures,
21
21
  } from '../../hooks/delegation-gate-runtime.mjs';
22
- import { isConferPendingForSession } from '../../hooks/confer-hook-runtime.mjs';
22
+ import { isConferPendingForSession, readConferReviewHint } from '../../hooks/confer-hook-runtime.mjs';
23
23
  import { getActiveUserAsideBoot } from '../../hooks/user-aside-hook-runtime.mjs';
24
24
  import {
25
25
  applyStationHeat,
@@ -282,6 +282,25 @@ export function loadConferPending(root = workspaceRoot(), sessionId = null) {
282
282
  }
283
283
  }
284
284
 
285
+ export function resolveGrokHookEvent(event = {}, argv = process.argv, env = process.env) {
286
+ if (env && env.GROK_HOOK_EVENT) return env.GROK_HOOK_EVENT;
287
+ const flag = (argv || []).find((arg) => String(arg).startsWith('--hook-event='));
288
+ if (flag) return String(flag).slice('--hook-event='.length);
289
+ const named = String((event && (event.hookEventName || event.hook)) || '')
290
+ .toLowerCase()
291
+ .replace(/[-_\s]/g, '');
292
+ if (named === 'precompact') return 'pre_compact';
293
+ if (named === 'postcompact') return 'post_compact';
294
+ if (named === 'sessionstart') return 'session_start';
295
+ if (
296
+ (event && (event.prompt != null || event.userMessage != null || event.user_prompt != null)) ||
297
+ named === 'userpromptsubmit'
298
+ ) {
299
+ return 'user_prompt_submit';
300
+ }
301
+ return 'session_start';
302
+ }
303
+
285
304
  export function buildSessionBootPayload(root, source = '0xray/grok-session-start', extra = {}) {
286
305
  const features = loadFeatures(root);
287
306
  const blockingTerms = loadBlockingCodexTerms();
@@ -289,6 +308,7 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
289
308
  const sessionId =
290
309
  extra.sessionId || process.env.GROK_SESSION_ID || process.env.GROK_SESSION || null;
291
310
  const conferPending = loadConferPending(root, sessionId);
311
+ const conferReview = readConferReviewHint(root);
292
312
  const userAsideBoot = getActiveUserAsideBoot(root, sessionId);
293
313
  const gateFeatures = loadDelegationGateFeatures(root, 'grok');
294
314
  const frontier = gateFeatures.ceremony === 'lite';
@@ -313,11 +333,18 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
313
333
  ...heat,
314
334
  ...(siblingRoots.length > 0 ? { siblingWorkspaceRoots: siblingRoots } : {}),
315
335
  ...(conferPending ? { conferPending: true, conferTrigger: 'analyze-complexity at synthesis checkpoint' } : {}),
336
+ ...(conferReview
337
+ ? {
338
+ conferReview,
339
+ conferReviewHint: 'UNREVIEWED — abstain without a governance LLM blocks the consult todo',
340
+ }
341
+ : {}),
316
342
  ...(userAsideBoot ?? {}),
317
343
  sessionId,
318
344
  ...extra,
319
345
  ...heat,
320
346
  ...compactHold,
347
+ arrivedHook: extra && extra.hookEvent ? String(extra.hookEvent) : "",
321
348
  hook: source,
322
349
  source,
323
350
  timestamp: new Date().toISOString(),
@@ -328,8 +355,8 @@ export function writeSessionBoot(root, payload) {
328
355
  try {
329
356
  const stateDir = path.join(root, '.xray', 'state');
330
357
  fs.mkdirSync(stateDir, { recursive: true });
331
- fs.writeFileSync(sessionBootPath(root), JSON.stringify(payload, null, 2));
332
358
  writeStationMarkdown(root, payload);
359
+ fs.writeFileSync(sessionBootPath(root), JSON.stringify(payload, null, 2));
333
360
  const hook = String((payload && (payload.hookEvent || payload.source)) || "");
334
361
  if (!/compact/i.test(hook)) {
335
362
  try {
@@ -9,6 +9,7 @@ import {
9
9
  clearPendingDelegationsForSessionChange,
10
10
  ensureSessionBoot,
11
11
  readStdinJson,
12
+ resolveGrokHookEvent,
12
13
  resolveSessionId,
13
14
  normalizeWorkspaceRoot,
14
15
  workspaceRoot,
@@ -25,25 +26,6 @@ import {
25
26
  import { maintainLens } from '../../hooks/goggles-pipeline.mjs';
26
27
  import { readSavedFreshness, refreshFreshness } from '../../../nucleus/work-freshness.mjs';
27
28
 
28
- function resolveHookEvent(event) {
29
- if (process.env.GROK_HOOK_EVENT) return process.env.GROK_HOOK_EVENT;
30
- const flag = process.argv.find((a) => a.startsWith('--hook-event='));
31
- if (flag) return flag.slice('--hook-event='.length);
32
- const named = event.hookEventName || event.hook;
33
- if (named === 'PreCompact' || named === 'pre_compact') return 'pre_compact';
34
- if (named === 'PostCompact' || named === 'post_compact') return 'post_compact';
35
- if (
36
- event.prompt != null ||
37
- event.userMessage != null ||
38
- event.user_prompt != null ||
39
- named === 'UserPromptSubmit' ||
40
- named === 'user_prompt_submit'
41
- ) {
42
- return 'user_prompt_submit';
43
- }
44
- return 'session_start';
45
- }
46
-
47
29
  function extractIntent(event) {
48
30
  return event.prompt || event.userMessage || event.user_prompt || event.compactContext || null;
49
31
  }
@@ -117,7 +99,7 @@ async function main() {
117
99
  let HOOK_EVENT = 'session_start';
118
100
  try {
119
101
  const event = await readStdinJson();
120
- HOOK_EVENT = resolveHookEvent(event);
102
+ HOOK_EVENT = resolveGrokHookEvent(event);
121
103
  const eventRoot = normalizeWorkspaceRoot(event.workspaceRoot || event.cwd || root);
122
104
  const sessionId = resolveSessionId(event);
123
105
  if (HOOK_EVENT === 'user_prompt_submit' && sessionId) {
@@ -3,7 +3,7 @@
3
3
  * Lazy-load confer SSOT for Grok session-boot hints.
4
4
  */
5
5
  import { createRequire } from 'node:module';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
7
7
  import { dirname, join, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
 
@@ -46,4 +46,26 @@ export function isConferPendingForSession(projectRoot, sessionId = null) {
46
46
  const mod = loadConfer();
47
47
  if (!mod?.isConferPending) return false;
48
48
  return mod.isConferPending(projectRoot, sessionId);
49
+ }
50
+
51
+ /** 'UNREVIEWED' when a consult receipt recorded abstain-without-model. */
52
+ export function readConferReviewHint(projectRoot) {
53
+ const stateDir = join(projectRoot, '.xray', 'state');
54
+ if (!existsSync(stateDir)) return null;
55
+ let names;
56
+ try {
57
+ names = readdirSync(stateDir);
58
+ } catch {
59
+ return null;
60
+ }
61
+ for (const name of names) {
62
+ if (!name.startsWith('synthesis-consult-') || !name.endsWith('.json')) continue;
63
+ try {
64
+ const receipt = JSON.parse(readFileSync(join(stateDir, name), 'utf8'));
65
+ if (receipt && receipt.verdict === 'UNREVIEWED') return 'UNREVIEWED';
66
+ } catch {
67
+ /* unreadable receipt is not a hint */
68
+ }
69
+ }
70
+ return null;
49
71
  }
@@ -2,8 +2,8 @@
2
2
  * Goggles MCP. Stdio. Server name `goggles`.
3
3
  * Tools: look, status_lens. Same organ as the CLI.
4
4
  */
5
- import { existsSync, readFileSync, realpathSync } from 'node:fs';
6
- import { basename, join, resolve } from 'node:path';
5
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
6
+ import { basename, dirname, join, resolve } from 'node:path';
7
7
  import { pathToFileURL, fileURLToPath } from 'node:url';
8
8
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -162,6 +162,29 @@ function publicCard(view) {
162
162
  if (view.exam) notes.push(view.exam);
163
163
  }
164
164
  const skills = view.skills ? basename(String(view.skills)) : '';
165
+ const cascade = view.cascade
166
+ ? {
167
+ plane: view.cascade.plane,
168
+ station: view.cascade.station,
169
+ notesPage: view.cascade.notesPage,
170
+ index: view.cascade.index,
171
+ laws: Array.isArray(view.cascade.laws) ? view.cascade.laws : [],
172
+ notes: Array.isArray(view.cascade.notes) ? view.cascade.notes : [],
173
+ }
174
+ : null;
175
+ const sleeve = view.sleeve
176
+ ? {
177
+ on: Boolean(view.sleeve.on),
178
+ missing: Array.isArray(view.sleeve.missing) ? view.sleeve.missing : [],
179
+ stitches: Array.isArray(view.sleeve.stitches)
180
+ ? view.sleeve.stitches.map((row) => ({
181
+ name: String(row && row.name || ''),
182
+ on: Boolean(row && row.on),
183
+ at: Array.isArray(row && row.at) ? row.at.map((item) => String(item)) : [],
184
+ }))
185
+ : [],
186
+ }
187
+ : null;
165
188
  if (view.narrow) {
166
189
  return {
167
190
  plane: view.plane,
@@ -169,6 +192,8 @@ function publicCard(view) {
169
192
  digest: view.digest || '',
170
193
  files: Array.isArray(view.files) ? view.files : [],
171
194
  notes,
195
+ ...(cascade ? { cascade } : {}),
196
+ ...(sleeve ? { sleeve } : {}),
172
197
  };
173
198
  }
174
199
  return {
@@ -185,6 +210,8 @@ function publicCard(view) {
185
210
  teardown: view.teardown || '',
186
211
  worn: view.worn || '',
187
212
  notes,
213
+ ...(cascade ? { cascade } : {}),
214
+ ...(sleeve ? { sleeve } : {}),
188
215
  };
189
216
  }
190
217
 
@@ -205,11 +232,16 @@ export async function answerLook(raw, root) {
205
232
  return { ok: true, quiet: false, mode: 'kind0', content: line };
206
233
  }
207
234
  const argv = argvFrom(args);
208
- const text = api.look(argv).text;
235
+ const text = api.look(argv, here).text;
209
236
  if (text === 'Name one plane.') return deny('name_one', 'Name one plane.');
210
- const cards = api.lookCards(argv);
237
+ const cards = api.lookCards(argv, here);
211
238
  if (Array.isArray(cards) && cards.length) {
212
239
  const shown = cards.map(publicCard);
240
+ if (shown.length === 1 && shown[0] && typeof shown[0].plane === 'string') {
241
+ const dest = join(here, '.xray', 'state', 'goggles-plane.json');
242
+ mkdirSync(dirname(dest), { recursive: true });
243
+ writeFileSync(dest, `${JSON.stringify({ plane: shown[0].plane })}\n`);
244
+ }
213
245
  return {
214
246
  ok: true,
215
247
  quiet: false,
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'node:url';
18
18
  const FIELD_ORDER = ['plate', 'entry', 'exit', 'files', 'skills', 'setup', 'teardown', 'worn'];
19
19
  const HERE = dirname(fileURLToPath(import.meta.url));
20
20
  const WORN_PLANES = ['dichotomy', 'syncopate', 'synthesis', 'digest', 'triage', 'loop'];
21
- const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory-recall', 'orchestration', 'processor', 'reporting', 'stamp-plate', 'record-map', 'write-home', 'activity-log', 'session-capture', 'suit-wear', 'suit-organs', 'station-card', 'notes-page', 'reflection-page', 'site-manual', 'package-face', 'suit-settings', 'trail-state', 'inference-files', 'grok-compact', 'payload-heat', 'station-heat', 'pickup-stamp', 'cursor-compact', 'work-fresh', 'kept-line', 'lens-gate', 'pre-tool', 'lens-page'];
21
+ const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory-recall', 'orchestration', 'processor', 'reporting', 'stamp-plate', 'record-map', 'write-home', 'activity-log', 'session-capture', 'suit-wear', 'suit-organs', 'station-card', 'notes-page', 'reflection-page', 'site-manual', 'package-face', 'suit-settings', 'trail-state', 'inference-files', 'grok-compact', 'payload-heat', 'station-heat', 'pickup-stamp', 'cursor-compact', 'work-fresh', 'kept-line', 'lens-gate', 'pre-tool', 'lens-page', 'centrifuge'];
22
22
  const CARD_FLAVORS = ['digest', 'triage'];
23
23
  const SCOPE_ZOOM = ['ecosystem', 'part', 'one flow', 'one artifact'];
24
24
 
@@ -211,7 +211,7 @@ function namesTheSet(lower) {
211
211
  return planes.length === 1 && planes[0] === 'loop' && findScopes(lower).length === 0;
212
212
  }
213
213
 
214
- function gogglesAnswer(argv) {
214
+ function gogglesAnswer(argv, projectRoot) {
215
215
  const words = [];
216
216
  for (const raw of argv || []) {
217
217
  const arg = String(raw || '').trim();
@@ -234,14 +234,14 @@ function gogglesAnswer(argv) {
234
234
  if (flavors.length === 1) {
235
235
  const others = acceptedPlanes().filter((name) => name !== flavors[0] && lower.includes(name));
236
236
  if (others.length) return none('');
237
- const cards = collectCards(flavors[0], lower);
237
+ const cards = collectCards(flavors[0], lower, projectRoot);
238
238
  if (!cards) return none('');
239
239
  return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
240
240
  }
241
241
  const planes = acceptedPlanes().filter((name) => lower.includes(name));
242
242
  if (planes.length !== 1) {
243
243
  if (!planes.length && cardPlanesNamed(lower).length) {
244
- const cards = collectCards('digest', lower);
244
+ const cards = collectCards('digest', lower, projectRoot);
245
245
  if (cards && cards.length) {
246
246
  return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
247
247
  }
@@ -255,12 +255,12 @@ function gogglesAnswer(argv) {
255
255
  return none(file ? `${line} File: ${file}` : line);
256
256
  }
257
257
 
258
- export function readGoggles(argv) {
259
- return ok(gogglesAnswer(argv).text);
258
+ export function readGoggles(argv, projectRoot) {
259
+ return ok(gogglesAnswer(argv, projectRoot).text);
260
260
  }
261
261
 
262
- export function lookCards(argv) {
263
- return gogglesAnswer(argv).cards;
262
+ export function lookCards(argv, projectRoot) {
263
+ return gogglesAnswer(argv, projectRoot).cards;
264
264
  }
265
265
 
266
266
  function cardPlanesNamed(lower) {
@@ -271,7 +271,22 @@ function listedFiles(plane) {
271
271
  return Array.isArray(plane.files) ? plane.files.map(String) : [];
272
272
  }
273
273
 
274
- function cardView(plane, scope, flavor, root) {
274
+ function cascadeApi() {
275
+ return createRequire(import.meta.url)('./wake-cascade.cjs');
276
+ }
277
+
278
+ function sleeveApi() {
279
+ return createRequire(import.meta.url)('./sleeve.cjs');
280
+ }
281
+
282
+ function appendLining(text, view) {
283
+ let next = text;
284
+ if (view.cascade) next = `${next}\n${cascadeApi().formatCascadeReading(view.cascade)}`;
285
+ if (view.sleeve) next = `${next}\n${sleeveApi().formatSleeveReading(view.sleeve)}`;
286
+ return next;
287
+ }
288
+
289
+ function cardView(plane, scope, flavor, root, cascadeRoot) {
275
290
  const listed = listedFiles(plane);
276
291
  const view = {
277
292
  plane: plane.id,
@@ -290,6 +305,20 @@ function cardView(plane, scope, flavor, root) {
290
305
  worn: plane.worn || '',
291
306
  };
292
307
  view.exam = examinePlane(plane, root).text;
308
+ if (cascadeApi().isCascadePlane(plane.id)) {
309
+ try {
310
+ view.cascade = cascadeApi().readWakeCascade(cascadeRoot || stateRoot());
311
+ } catch {
312
+ view.cascade = null;
313
+ }
314
+ }
315
+ if (sleeveApi().isSleevePlane(plane.id)) {
316
+ try {
317
+ view.sleeve = sleeveApi().readSleeve(cascadeRoot || stateRoot());
318
+ } catch {
319
+ view.sleeve = null;
320
+ }
321
+ }
293
322
  if (flavor === 'triage') {
294
323
  const filled = new Set(filledOf(plane));
295
324
  view.empty = FIELD_ORDER.filter((name) => !filled.has(name));
@@ -328,7 +357,7 @@ function formatCardText(view) {
328
357
  } else if (typeof view.exam === 'string' && view.exam.startsWith('Drift:')) {
329
358
  text = `${text}\n${view.exam}`;
330
359
  }
331
- return text;
360
+ return appendLining(text, view);
332
361
  }
333
362
 
334
363
  export function formatCardPane(view) {
@@ -345,10 +374,11 @@ export function formatCardPane(view) {
345
374
  const top = `┌ ${title} ${'─'.repeat(Math.max(0, width - title.length - 1))}┐`;
346
375
  const mid = body.map((line) => `│ ${line.padEnd(width)} │`);
347
376
  const bot = `└${'─'.repeat(width + 2)}┘`;
348
- return [top, ...mid, bot].join('\n');
377
+ const pane = [top, ...mid, bot].join('\n');
378
+ return appendLining(pane, view);
349
379
  }
350
380
 
351
- function collectCards(flavor, lower) {
381
+ function collectCards(flavor, lower, projectRoot) {
352
382
  const named = cardPlanesNamed(lower);
353
383
  const scopes = findScopes(lower.filter((word) => word !== flavor && !named.includes(word)));
354
384
  if (scopes.length > 1) return null;
@@ -359,7 +389,7 @@ function collectCards(flavor, lower) {
359
389
  const cards = [];
360
390
  for (const id of ids) {
361
391
  const plane = growPlane(id, platesDir, stateRoot());
362
- cards.push(cardView(plane, scope, flavor, root));
392
+ cards.push(cardView(plane, scope, flavor, root, projectRoot));
363
393
  }
364
394
  return cards;
365
395
  }
@@ -929,6 +959,15 @@ function lensPage(root, names) {
929
959
  return parts.join('\n\n');
930
960
  }
931
961
 
962
+
963
+ export function holdPlane(root, id) {
964
+ const plane = String(id || "").trim();
965
+ if (!plane || plane.includes(" ")) return;
966
+ const dest = join(root, ".xray", "state", "goggles-plane.json");
967
+ mkdirSync(dirname(dest), { recursive: true });
968
+ writeFileSync(dest, `${JSON.stringify({ plane })}\n`);
969
+ }
970
+
932
971
  function stampPlane(root, id) {
933
972
  try {
934
973
  const plates = createRequire(import.meta.url)('./plates.cjs');
@@ -953,6 +992,7 @@ export function lensBeforeResearch(root, toolName, text) {
953
992
  if (!research) return null;
954
993
  const names = CARD_PLANES.filter((name) => intentWords(spoken).includes(name));
955
994
  if (names.length === 1) {
995
+ holdPlane(root, names[0]);
956
996
  stampPlane(root, names[0]);
957
997
  rememberOpened(root, spoken);
958
998
  return null;
@@ -1095,8 +1135,8 @@ export function examinePlane(plane, root) {
1095
1135
  }
1096
1136
 
1097
1137
 
1098
- export function look(argv) {
1099
- return readGoggles(argv);
1138
+ export function look(argv, projectRoot) {
1139
+ return readGoggles(argv, projectRoot);
1100
1140
  }
1101
1141
 
1102
1142
 
@@ -38,7 +38,10 @@
38
38
  "plates": ["suit-wear"]
39
39
  },
40
40
  "governance": {
41
- "files": ["src/governance/governance-service.ts"],
41
+ "files": [
42
+ "src/governance/governance-service.ts",
43
+ "src/governance/llm-governance-provider.ts"
44
+ ],
42
45
  "skills": ".agents/skills/governance/SKILL.md"
43
46
  },
44
47
  "memory-recall": {
@@ -68,7 +71,7 @@
68
71
  "plates": ["review"]
69
72
  },
70
73
  "record-map": {
71
- "files": ["docs-site/docs/plates/record-map.md"],
74
+ "files": ["docs-site/docs/plates/record-map.md", "src/integrations/hooks/wake-cascade.cjs", "src/integrations/hooks/session-note-index.cjs"],
72
75
  "skills": ".agents/skills/record-map/SKILL.md",
73
76
  "plates": ["station-card", "notes-page", "reflection-page", "site-manual", "package-face", "write-home", "activity-log", "session-capture", "lens-page"]
74
77
  },
@@ -103,13 +106,13 @@
103
106
  "plates": ["activity-log", "session-capture", "inference-files", "trail-state"]
104
107
  },
105
108
  "station-card": {
106
- "files": ["src/integrations/hooks/station-hook-runtime.cjs"],
109
+ "files": ["src/integrations/hooks/station-hook-runtime.cjs", "src/integrations/hooks/wake-cascade.cjs"],
107
110
  "mark": "formatStationMarkdown",
108
111
  "skills": ".agents/skills/station-card/SKILL.md",
109
112
  "plates": ["notes-page", "record-map", "kept-line"]
110
113
  },
111
114
  "notes-page": {
112
- "files": ["src/integrations/hooks/station-hook-runtime.cjs"],
115
+ "files": ["src/integrations/hooks/station-hook-runtime.cjs", "src/integrations/hooks/session-note-index.cjs"],
113
116
  "mark": "stampNotesPickup",
114
117
  "skills": ".agents/skills/notes-page/SKILL.md",
115
118
  "plates": ["station-card", "record-map"]
@@ -210,6 +213,12 @@
210
213
  "mark": "export function formatCardPane",
211
214
  "skills": ".agents/skills/lens-page/SKILL.md",
212
215
  "plates": ["record-map", "lens-gate"]
216
+ },
217
+ "centrifuge": {
218
+ "files": ["src/integrations/hooks/station-hook-runtime.cjs"],
219
+ "mark": "function wakeOrder",
220
+ "skills": ".agents/skills/centrifuge/SKILL.md",
221
+ "plates": ["station-card", "goggles"]
213
222
  }
214
223
  }
215
224
 
@@ -46,6 +46,7 @@ const PLATE_IDS = Object.freeze([
46
46
  "lens-gate",
47
47
  "pre-tool",
48
48
  "lens-page",
49
+ "centrifuge",
49
50
  ]);
50
51
 
51
52
  /** Id token, then extra phrases. Equal top scores recall nothing. */
@@ -229,6 +230,11 @@ const CUES = {
229
230
  [/lens page/i, 6],
230
231
  [/plates\/lens-page/i, 6],
231
232
  ],
233
+ centrifuge: [
234
+ [/centrifuge plate/i, 6],
235
+ [/the centrifuge/i, 5],
236
+ [/plates\/centrifuge/i, 6],
237
+ ],
232
238
  };
233
239
 
234
240
  function platesDir() {
@@ -317,6 +323,21 @@ function stampPlateIfMissing(projectRoot, id) {
317
323
  return { id, path, written: true };
318
324
  }
319
325
 
326
+ function ensureWornPlate(projectRoot, id) {
327
+ const path = wornPlatePath(projectRoot, id);
328
+ const source = readFileSync(plateSourcePath(id), "utf8");
329
+ if (existsSync(path)) {
330
+ try {
331
+ if (readFileSync(path, "utf8") === source) return { id, path, written: false };
332
+ } catch {
333
+ /* rewrite a plate the process cannot keep */
334
+ }
335
+ }
336
+ mkdirSync(dirname(path), { recursive: true });
337
+ writeFileSync(path, source);
338
+ return { id, path, written: true };
339
+ }
340
+
320
341
  function organFile(intent) {
321
342
  try {
322
343
  const { suitHint } = require("./goggles-pipeline.mjs");
@@ -342,6 +363,7 @@ module.exports = {
342
363
  loadPlate,
343
364
  recallPlate,
344
365
  stampPlateIfMissing,
366
+ ensureWornPlate,
345
367
  plateStockLine,
346
368
  wornPlatePath,
347
369
  };