0xray 4.0.21 → 4.0.22

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 (56) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/hermes-agent/SKILL.md +1 -1
  3. package/.opencode/skills/orchestrator/SKILL.md +22 -0
  4. package/.xray/features.json +1 -1
  5. package/AGENTS-consumer.md +11 -11
  6. package/AGENTS.md +8 -8
  7. package/README.md +27 -27
  8. package/SKILLS.md +1 -1
  9. package/dist/AGENTS.md +8 -8
  10. package/dist/CHANGELOG.md +75 -0
  11. package/dist/README.md +27 -27
  12. package/dist/SKILLS.md +1 -1
  13. package/dist/governance/governance-core.d.ts +2 -1
  14. package/dist/governance/governance-core.js +20 -11
  15. package/dist/governance/governance-service.js +9 -7
  16. package/dist/inference/inference-cycle.js +22 -5
  17. package/dist/integrations/cursor/README.md +1 -1
  18. package/dist/integrations/cursor/hooks/cursor-hook-utils.js +19 -0
  19. package/dist/integrations/cursor/hooks/pre-tool-use.js +2 -1
  20. package/dist/integrations/hooks/station-hook-runtime.cjs +90 -30
  21. package/dist/integrations/hooks/station-hook-runtime.mjs +1 -0
  22. package/dist/integrations/hooks/station-memory-ingest.mjs +4 -2
  23. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  24. package/dist/memory-routing/provider-loader.d.ts +3 -1
  25. package/dist/memory-routing/provider-loader.js +3 -1
  26. package/dist/nucleus/thin-dispatch.js +11 -2
  27. package/dist/processors/implementations/agents-md-validation-processor.js +1 -1
  28. package/dist/skills/hermes-agent/SKILL.md +1 -1
  29. package/dist/skills/orchestrator/SKILL.md +22 -0
  30. package/package.json +2 -2
  31. package/scripts/foundry/CHANGELOG.md +2 -0
  32. package/scripts/foundry/README.md +2 -2
  33. package/scripts/foundry/cli.mjs +1 -1
  34. package/scripts/foundry/inspect.mjs +160 -5
  35. package/scripts/foundry/plant/skills/inspect/SKILL.md +2 -0
  36. package/scripts/foundry/validate-release-docs.mjs +24 -20
  37. package/scripts/foundry/version-manager.mjs +127 -7
  38. package/scripts/mjs/test-consumer-readiness.mjs +10 -3
  39. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  40. package/src/skills/hermes-agent/SKILL.md +1 -1
  41. package/src/skills/orchestrator/SKILL.md +22 -0
  42. package/vendor/@0xray/repertoire/data/stack-overlay.json +154 -1
  43. package/vendor/@0xray/repertoire/dist/RepertoireService.js +7 -6
  44. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +10 -11
  45. package/vendor/@0xray/repertoire/dist/paths.js +66 -4
  46. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +54 -14
  47. package/xray/agents_template.md +10 -5
  48. package/xray/features.json +1 -1
  49. package/dist/mcps/shared/prompt-security-validator.d.ts +0 -44
  50. package/dist/mcps/shared/prompt-security-validator.js +0 -148
  51. package/dist/mcps/shared/security-scanner.d.ts +0 -119
  52. package/dist/mcps/shared/security-scanner.js +0 -429
  53. package/dist/scripts/activate-kernel-pipeline.d.ts +0 -7
  54. package/dist/scripts/activate-kernel-pipeline.js +0 -101
  55. package/src/mcps/shared/prompt-security-validator.ts +0 -199
  56. package/src/mcps/shared/security-scanner.ts +0 -599
@@ -182,6 +182,25 @@ export function cursorHeatRoots(event = {}) {
182
182
  return out;
183
183
  }
184
184
 
185
+ function packageNameAt(root) {
186
+ try {
187
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
188
+ return typeof pkg.name === 'string' ? pkg.name : '';
189
+ } catch {
190
+ return '';
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Constitution follows the suit mill when that package is in the heat set.
196
+ * A sibling repo's leftover synthesis checkpoint must not deny the host.
197
+ */
198
+ export function cursorGateRoot(heatRoots, eventRoot) {
199
+ const mill = heatRoots.find((root) => packageNameAt(root) === '0xray');
200
+ if (mill) return mill;
201
+ return heatRoots[0] || eventRoot;
202
+ }
203
+
185
204
  export function cursorSessionId(event = {}) {
186
205
  return (
187
206
  event.conversation_id ||
@@ -9,6 +9,7 @@ import {
9
9
  appendHookActivity,
10
10
  checkCodexPatterns,
11
11
  checkFullTestSuite,
12
+ cursorGateRoot,
12
13
  cursorHeatRoots,
13
14
  cursorSessionId,
14
15
  cursorToolContext,
@@ -53,7 +54,7 @@ async function main() {
53
54
  for (const root of heatRoots) {
54
55
  ensureCursorSessionBoot(root, '0xray/cursor-pre-tool-use-boot', { sessionId });
55
56
  }
56
- const gateRoot = heatRoots[0] || eventRoot;
57
+ const gateRoot = cursorGateRoot(heatRoots, eventRoot);
57
58
 
58
59
  const features = loadFeatures(gateRoot);
59
60
  const gateFeatures = loadDelegationGateFeatures(gateRoot, 'cursor');
@@ -395,6 +395,30 @@ function pruneKeywordDest(root) {
395
395
  return { removed, kept: kept.length };
396
396
  }
397
397
 
398
+ const STACK_LAW_FIELDS = [
399
+ "definition",
400
+ "tags",
401
+ "priority",
402
+ "evaluation_criteria",
403
+ "validation_experiment",
404
+ "example_inference_snippet",
405
+ "implementation_notes",
406
+ ];
407
+
408
+ /** Stack text cadences onto an existing project law. Observation stats stay. */
409
+ function refreshStackLaw(existing, signal) {
410
+ let dirty = false;
411
+ for (const field of STACK_LAW_FIELDS) {
412
+ if (signal[field] == null) continue;
413
+ const next = signal[field];
414
+ const prev = existing[field];
415
+ if (JSON.stringify(prev) === JSON.stringify(next)) continue;
416
+ existing[field] = next;
417
+ dirty = true;
418
+ }
419
+ return dirty;
420
+ }
421
+
398
422
  function mergeMissingSignals(destPath, incoming) {
399
423
  if (!incoming.length || !existsSync(destPath)) return 0;
400
424
  let data;
@@ -404,24 +428,35 @@ function mergeMissingSignals(destPath, incoming) {
404
428
  return 0;
405
429
  }
406
430
  if (!Array.isArray(data.signals)) return 0;
407
- const have = new Set(
408
- data.signals.map((signal) => String((signal && signal.name) || "").trim()).filter(Boolean),
409
- );
410
- let added = 0;
431
+ const byName = new Map();
432
+ for (const signal of data.signals) {
433
+ const name = String((signal && signal.name) || "").trim();
434
+ if (name) byName.set(name, signal);
435
+ }
436
+ let changed = 0;
411
437
  for (const signal of incoming) {
412
438
  const name = String(signal.name).trim();
413
- if (!name || have.has(name)) continue;
414
- data.signals.push({
415
- ...signal,
416
- name,
417
- tags: Array.isArray(signal.tags) ? signal.tags : [],
418
- definition: typeof signal.definition === "string" ? signal.definition : name,
419
- });
420
- have.add(name);
421
- added += 1;
439
+ if (!name) continue;
440
+ const existing = byName.get(name);
441
+ if (!existing) {
442
+ const created = {
443
+ ...signal,
444
+ name,
445
+ tags: Array.isArray(signal.tags) ? signal.tags : [],
446
+ definition: typeof signal.definition === "string" ? signal.definition : name,
447
+ };
448
+ data.signals.push(created);
449
+ byName.set(name, created);
450
+ changed += 1;
451
+ continue;
452
+ }
453
+ if (refreshStackLaw(existing, signal)) changed += 1;
454
+ }
455
+ if (changed) {
456
+ data.last_updated = new Date().toISOString();
457
+ writeFileSync(destPath, `${JSON.stringify(data, null, 2)}\n`);
422
458
  }
423
- if (added) writeFileSync(destPath, `${JSON.stringify(data, null, 2)}\n`);
424
- return added;
459
+ return changed;
425
460
  }
426
461
 
427
462
  /** Hydrate project dest from seed + stack. Subject repo-* stay off dest. */
@@ -661,6 +696,7 @@ function maybeCaptureSessionOnHeadMove(root) {
661
696
  from: stamp && stamp.head ? stamp.head : commits[commits.length - 1] ? commits[commits.length - 1].hash : git.head,
662
697
  to: git.head,
663
698
  };
699
+ const patterns = patternsFromGit(root, commits, span);
664
700
  const session = {
665
701
  sessionId,
666
702
  timestamp: new Date().toISOString(),
@@ -669,8 +705,8 @@ function maybeCaptureSessionOnHeadMove(root) {
669
705
  approaches,
670
706
  wrongTurns: [],
671
707
  solutions: [],
672
- patterns: patternsFromGit(root, commits, span),
673
- matched_primitives: [],
708
+ patterns,
709
+ matched_primitives: preferLawHits(patterns.map((row) => row && row.name)),
674
710
  metrics: { commits: commits.length },
675
711
  };
676
712
  const outDir = join(root, "docs", "inference");
@@ -754,8 +790,16 @@ function growDestOnWake(root) {
754
790
  const line = String(out).trim().split("\n").filter(Boolean).at(-1) || "{}";
755
791
  const parsed = JSON.parse(line);
756
792
  const pruned = pruneKeywordDest(root);
757
- if (!parsed || typeof parsed !== "object") return { pruned };
758
- return { ...parsed, pruned: pruned.removed, destCount: countCuratedSignals(destSignalsPath(root)) };
793
+ const destCount = countCuratedSignals(destSignalsPath(root)) || 0;
794
+ if (!parsed || typeof parsed !== "object") return { pruned: pruned.removed, destCount };
795
+ const heated = Array.isArray(parsed.heated) ? parsed.heated.length : 0;
796
+ return {
797
+ ...parsed,
798
+ observed: heated,
799
+ pruned: pruned.removed,
800
+ destCount,
801
+ after: destCount,
802
+ };
759
803
  } catch {
760
804
  return null;
761
805
  }
@@ -811,6 +855,28 @@ function retainCompactFields(existing, extra = {}) {
811
855
  return kept;
812
856
  }
813
857
 
858
+ /** Persist heated length. A number stays a number. An array is a count. Never drop a number to 0. */
859
+ function workingGrowSnapshot(grow) {
860
+ if (!grow || typeof grow !== "object") return null;
861
+ if (typeof grow.after !== "number") {
862
+ return typeof grow.skipped === "string" ? grow.skipped : null;
863
+ }
864
+ const observed =
865
+ typeof grow.observed === "number"
866
+ ? grow.observed
867
+ : Array.isArray(grow.observed)
868
+ ? grow.observed.length
869
+ : Array.isArray(grow.heated)
870
+ ? grow.heated.length
871
+ : 0;
872
+ return {
873
+ before: grow.before,
874
+ after: grow.after,
875
+ imported: grow.imported,
876
+ observed,
877
+ };
878
+ }
879
+
814
880
  function formatWorkingLine(working) {
815
881
  if (!working || typeof working !== "object") return null;
816
882
  const matched = stationSafeSignals(working.matchedSignals);
@@ -826,7 +892,7 @@ function buildRepertoireResume(root) {
826
892
  const modulePath = resolveRepertoireProviderModule(root);
827
893
  const mr = readMemoryRoutingConfig(root);
828
894
  if (!modulePath) {
829
- return "Repertoire: not installed (memory_routing stays off)";
895
+ return "Repertoire: module unresolved";
830
896
  }
831
897
  const destPath = destSignalsPath(root);
832
898
  let signalsPath = existsSync(destPath) ? destPath : mr.config && mr.config.signalsPath;
@@ -932,15 +998,8 @@ function applyStationHeat(root, host, extra = {}, existing = {}) {
932
998
  if (pickup) workingSnapshot.pickup = pickup;
933
999
  if (matchText) workingSnapshot.matchText = matchText;
934
1000
  if (captured) workingSnapshot.sessionCapture = captured;
935
- if (grow && grow.skipped) workingSnapshot.grow = grow.skipped;
936
- if (grow && typeof grow.after === "number") {
937
- workingSnapshot.grow = {
938
- before: grow.before,
939
- after: grow.after,
940
- imported: grow.imported,
941
- observed: Array.isArray(grow.observed) ? grow.observed.length : 0,
942
- };
943
- }
1001
+ const growReceipt = workingGrowSnapshot(grow);
1002
+ if (growReceipt) workingSnapshot.grow = growReceipt;
944
1003
  if (matchedSignals.length) workingSnapshot.matchedSignals = matchedSignals.slice(0, 8);
945
1004
  const opProcNames = readOpProcNames(root);
946
1005
  if (opProcNames.length) workingSnapshot.opProcNames = opProcNames;
@@ -1108,7 +1167,7 @@ function formatStationMarkdown(fields) {
1108
1167
  } else {
1109
1168
  lines.push("Git: n/a");
1110
1169
  }
1111
- lines.push(fields.repertoireResume || "Repertoire: not installed (memory_routing stays off)");
1170
+ lines.push(fields.repertoireResume || "Repertoire: module unresolved");
1112
1171
  if (fields.workingLine) {
1113
1172
  lines.push(fields.workingLine);
1114
1173
  }
@@ -1165,6 +1224,7 @@ module.exports = {
1165
1224
  readNotesPickup,
1166
1225
  maybeCaptureSessionOnHeadMove,
1167
1226
  formatWorkingLine,
1227
+ workingGrowSnapshot,
1168
1228
  applyStationHeat,
1169
1229
  isStockTicket,
1170
1230
  readStationTicketField,
@@ -28,6 +28,7 @@ export const patternsFromGit = impl.patternsFromGit;
28
28
  export const readNotesPickup = impl.readNotesPickup;
29
29
  export const maybeCaptureSessionOnHeadMove = impl.maybeCaptureSessionOnHeadMove;
30
30
  export const formatWorkingLine = impl.formatWorkingLine;
31
+ export const workingGrowSnapshot = impl.workingGrowSnapshot;
31
32
  export const applyStationHeat = impl.applyStationHeat;
32
33
  export const isStockTicket = impl.isStockTicket;
33
34
  export const readStationTicketField = impl.readStationTicketField;
@@ -128,13 +128,15 @@ async function growDest(projectRoot) {
128
128
  sources: [...(kernel.sources || []), ...cleanup.sources],
129
129
  });
130
130
  const after = service.signalsManager.load().signals.length;
131
+ const heatedNames = Array.isArray(diary.heated) ? diary.heated : [];
131
132
  process.stdout.write(
132
133
  `${JSON.stringify({
133
134
  before,
134
135
  after,
135
136
  imported: sessions.imported,
136
- observed: 0,
137
- heated: diary.heated,
137
+ observed: heatedNames.length,
138
+ heated: heatedNames,
139
+ destCount: after,
138
140
  })}\n`,
139
141
  );
140
142
  return;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.21",
3
+ "version": "4.0.22",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -1,6 +1,8 @@
1
1
  import type { MemoryRoutingConfig, MemoryRoutingProvider } from './types.js';
2
2
  export declare function isExplicitMemoryRoutingOptOut(config: MemoryRoutingConfig): boolean;
3
- /** Leftover shipped default: enabled false + provider null. Not an explicit opt-out. */
3
+ /** Leftover shipped default: enabled false + provider null. Not an explicit opt-out.
4
+ * resolveLeftoverEnabledConfig migrates old tarballs when the organ is on disk.
5
+ * Explicit opt-out is enabled: false + provider: repertoire. */
4
6
  export declare function isLeftoverMemoryRoutingOff(config: MemoryRoutingConfig): boolean;
5
7
  export declare function resolveLeftoverEnabledConfig(config: MemoryRoutingConfig, cwd: string): MemoryRoutingConfig | null;
6
8
  export declare function loadMemoryRoutingProvider(config: MemoryRoutingConfig, cwd?: string): Promise<MemoryRoutingProvider>;
@@ -59,7 +59,9 @@ function defaultPathForProvider(provider, cwd) {
59
59
  export function isExplicitMemoryRoutingOptOut(config) {
60
60
  return config.enabled === false && config.provider === 'repertoire';
61
61
  }
62
- /** Leftover shipped default: enabled false + provider null. Not an explicit opt-out. */
62
+ /** Leftover shipped default: enabled false + provider null. Not an explicit opt-out.
63
+ * resolveLeftoverEnabledConfig migrates old tarballs when the organ is on disk.
64
+ * Explicit opt-out is enabled: false + provider: repertoire. */
63
65
  export function isLeftoverMemoryRoutingOff(config) {
64
66
  if (config.enabled === true)
65
67
  return false;
@@ -7,7 +7,7 @@
7
7
  * Import from here or from delegation/index.ts (which re-exports these).
8
8
  */
9
9
  import { frameworkLogger } from "../core/framework-logger.js";
10
- import { getLevelFromScore, getAgentForTier, levelToTier, } from "../delegation/complexity-core.js";
10
+ import { getLevelFromScore, getAgentForTier, getAgentCountForLevel, getStrategyForLevel, levelToTier, } from "../delegation/complexity-core.js";
11
11
  import { ComplexityAnalyzer } from "../delegation/complexity-analyzer.js";
12
12
  import { getMemoryRoutingProviderSync, initializeMemoryRouting, } from "../memory-routing/index.js";
13
13
  initializeMemoryRouting();
@@ -18,6 +18,9 @@ export const NUCLEUS_THIN_DISPATCH_VERSION = "0.1.0";
18
18
  */
19
19
  export function scoreComplexity(operation, context, thresholds) {
20
20
  const analyzer = new ComplexityAnalyzer();
21
+ if (thresholds) {
22
+ analyzer.setThresholds(thresholds);
23
+ }
21
24
  const metrics = analyzer.analyzeComplexity(operation, context);
22
25
  const score = analyzer.calculateComplexityScore(metrics);
23
26
  frameworkLogger.log("nucleus-thin-dispatch", "score-complexity", "info", {
@@ -76,7 +79,13 @@ export function scoreAndRoute(operation, context, thresholds) {
76
79
  signals: resolved.context.matchedSignals,
77
80
  });
78
81
  return {
79
- score: { ...score, score: resolved.adjustedScore, level: adjustedLevel },
82
+ score: {
83
+ ...score,
84
+ score: resolved.adjustedScore,
85
+ level: adjustedLevel,
86
+ recommendedStrategy: getStrategyForLevel(adjustedLevel),
87
+ estimatedAgents: getAgentCountForLevel(adjustedLevel),
88
+ },
80
89
  agent,
81
90
  memoryRouting,
82
91
  };
@@ -224,7 +224,7 @@ export class AgentsMdValidationProcessor {
224
224
  "",
225
225
  "## Codex",
226
226
  "",
227
- `0xRay enforces Universal Development Codex (60 terms) for systematic error prevention.`,
227
+ `0xRay enforces Universal Development Codex (69 terms) for systematic error prevention.`,
228
228
  "",
229
229
  ].join("\n"),
230
230
  "## CLI Commands": [
@@ -64,7 +64,7 @@ Returns: pass/fail with per-file results and violations.
64
64
 
65
65
  ### xray_codex_check
66
66
 
67
- Validate code against the 60-term Universal Development Codex. Checks error-handling, type-safety, performance, security, architecture.
67
+ Validate code against the 69-term Universal Development Codex. Checks error-handling, type-safety, performance, security, architecture.
68
68
 
69
69
  ```
70
70
  xray_codex_check(code="const x: any = foo()", operation="create")
@@ -64,6 +64,10 @@ Config: `features.json` → `multi_agent_orchestration.lead_dev_mode`
64
64
 
65
65
  The seven rules are the engine. The cadence is the time signature. Disk SSOT: `grok-bot/ops/LEAD-CADENCE.md`.
66
66
 
67
+ Jargon is not the job. Review and cadence ask whether the change works. Do not spend the loop reciting dest names, mill liturgy, or host mantras.
68
+
69
+ - **Feat rebase:** when the mandated work turns into mantra, or the job has split across planes that no longer share one present, stop. Go up a level and answer the original message again from this seat. Do not continue the side thread and call that the job.
70
+
67
71
  - **Peer test:** another instance with only worn 0xRay continues the ticket. No command stream.
68
72
  - **Wear first:** Read `.xray/state/STATION.md`. Same cloud. Same critic (resume only). Dummy Reads Station.
69
73
  - **Dummy tests `SKILLS.md`:** a suited dummy proves the worn catalog. Same-sess bodies keep context and hold a Station card.
@@ -73,6 +77,24 @@ The seven rules are the engine. The cadence is the time signature. Disk SSOT: `g
73
77
  - **Live tick, not pacer:** `/loop` runs work now. Keep workstreams moving. Do not idle-wait 12 minutes. **Clean ticks every cycle** — unsubscribe then resubscribe so the prompt matches metal. Name-dedupe does not rewrite. A stale prompt (waiting URL after live, Hold after strip) is dirty: rewrite, do not act. **If the card and WAVEBOARD are idle, unsubscribe and do not resubscribe.**
74
78
  - **Board clock:** PR events + resume the same critic. Not two idle pacers. Merge after PASS+CI (gate A). Publish only after mill-gate D + `foundry gate`.
75
79
  - **Dispatch:** ticket + PR URLs + “Wear, then review.” The critic Reads `SEATS.md` Reviewer. Do not paste a command novel. When a law is spoken twice, encode it this wake.
80
+ - **Cascade:** Chat is not the brain. A cascade that must survive compaction is a named repertoire signal on the stack overlay, hydrated onto the project law list. NOTES holds the same cascade for the second Read. Station stays the ticket. A reflection is not this capture. Capture priority: a law is an invariant you would otherwise re-read the source to relearn. One law per subsystem bone. Heat touches last_seen and does not append a confidence sample, so more hits do not raise conviction. Heat touches last_seen only when the diary contains that signal's name; two definition words are not a hit; a weak session score is not lifted to 0.55 and a sample below 0.55 is not recorded. Constructing the organ does not append workspace samples. Hero names: `heat-is-not-conviction`, `inference-cycle-invariant`, `governance-vote-invariant`, `thin-dispatch-invariant`. Repertoire mergeStackOverlay refreshes a changed stack definition and keeps observation stats.
81
+ - **Cold brief:** a new critic or any new subagent that does not already have this thread gets the latest commit SHA on the branch under review (fetch it; do not reuse an earlier SHA from a previous pass) and the Station card (`.xray/state/STATION.md`: intent, plan, git). If the card is missing, give branch + HEAD + what just changed. If the branch moves after you brief them, send the new SHA before they verdict.
82
+ - **Control:** The lead keeps control. Do not assume the subagent has the context. Give the duty, the latest SHA, the Station card, and the laws that apply. The subagent wears the applicable 0xRay suit before it works: the tree under review, built and installed. An older published package is not that suit. The lead checks the result.
83
+
84
+ ### Engine cadence
85
+
86
+ This suite is the engine that builds the engine. When a seat sees OP-PROC, write it onto these existing surfaces in the same wake. Do not open a new skill.
87
+
88
+ 1. Change the engine with precision.
89
+ 2. Ship it local: build and install the tree. A live npm version stays live. Once a version is live, the stamper strips every present-tense patch pin from shipped guides and shipped OP-PROC, or reconcile advances the next cut. The stamper does not edit Station, NOTES, dest, or node_modules. It does not publish.
90
+ 3. Test the change.
91
+ 4. Run another deep review of the diff you just made.
92
+ 5. Monitor long-term memory (dest name count and whether observations are the only growth) and short-term memory (Station: intent, plan, git).
93
+ 6. Ship the PR.
94
+
95
+ ### Planes
96
+
97
+ Operating planes: code, OP-PROC, model, suit, mill, host, and test/ship. Test/ship is the critical OP-PROC plane. Test then ship is the hero. Ship without that proof is the catastrophe. Long-term memory is the project law list. Each wake refreshes a changed stack definition and keeps observation stats. Heat writes the matched names onto Station, the short-term card that survives compaction. Reactive decisions move out of TypeScript into OP-PROC as the model improves. That move is why the mill and the suit exist. It is not fully proved. Once a version is live, the stamper strips every present-tense patch pin from shipped guides and shipped OP-PROC, or reconcile advances the next cut. The stamper does not edit Station, NOTES, dest, or node_modules. It does not publish. Do not freeze the live cut.
76
98
  - **Close:** loop until Station is done. Compact: Station survives the cut. Overlay dest names are OP-PROC — reload `repertoire-working.json` `opProcNames`, not Station.
77
99
  - **Board:** critic Strict comments; forge implements; lead stays main thread.
78
100
  - **Codex 69:** rewire this skill and existing ops. Do not add a new `SKILL.md` to hold the cadence.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.21",
3
+ "version": "4.0.22",
4
4
  "description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
5
5
  "readme": "README.md",
6
6
  "license": "MIT",
@@ -106,7 +106,7 @@
106
106
  "debug:inference:force": "XRAY_FORCE_MCP_GOVERNANCE=true XRAY_DEV_PATH=dist node dist/cli/index.js inference:run --force",
107
107
  "validate:full": "npm run validate",
108
108
  "test:unified": "node scripts/test/test-unified-framework.mjs",
109
- "test:consumer": "node scripts/test/test-consumer-readiness.cjs",
109
+ "test:consumer": "node scripts/mjs/test-consumer-readiness.mjs",
110
110
  "config:setup": "node scripts/config/utils.js setup-dev",
111
111
  "monitoring:start": "node scripts/monitoring/daemon.js start",
112
112
  "monitoring:stop": "node scripts/monitoring/daemon.js stop",
@@ -4,6 +4,8 @@
4
4
 
5
5
  Shim `sound-voice.cjs` → `@0xray/blip/sound-voice`. Depend on published `@0xray/blip@^0.1.2`. Foundry stamp stays 0.1.12. Do not vendor mill drawers.
6
6
 
7
+ Inspect `--go` emits a shop-style forge GO receipt (harness profile: bare / suited / partial). Observational by default: Cursor/Grok `hooks.json` bound, Repertoire fastened (`node_modules` or `.xray/state/repertoire`), Station present. `--require-harness=` gates dual-cloud A/B. Vendor Repertoire source and `memory_routing` declaration are not fasten. Not an 8th MCP. Not PPE. Not 5.0.
8
+
7
9
  ## [0.1.12] - 2026-09-18
8
10
 
9
11
  Fasten is foundry-plant/0: mill packages declare plants in package.json (`mill.protocol`, prefix selection when skills/agents are omitted) and foundry copies organs from that package. Builtin catalog is mill+inspect only; foundry does not catalog mill organs. `foundry.json` `"plant": "blip"|"sound"` are package aliases for `@0xray/blip`, not skill lists. Unknown mill packages fail closed (`npm i`). Inventory records `millPackage` and `millProtocol` when a mill package was used.
@@ -23,7 +23,7 @@ npx @0xray/foundry release [patch|minor|major] --i-mean-it
23
23
  npx @0xray/foundry release --publish-only --dry-run
24
24
  npx @0xray/foundry release --publish-only --i-mean-it
25
25
  npx @0xray/foundry mint
26
- npx @0xray/foundry inspect [--skip-live]
26
+ npx @0xray/foundry inspect [--skip-live] [--go] [--go-out PATH] [--require-harness=bare|suited|partial]
27
27
  npx @0xray/foundry sound render [--brief TEXT] [--genre ambient|techno|jazz]
28
28
  npx @0xray/foundry sound inspect
29
29
  npx @0xray/foundry sound mix
@@ -39,7 +39,7 @@ npx @0xray/foundry hooks
39
39
 
40
40
  `FOUNDRY_ROOT` overrides cwd (the repo being milled).
41
41
 
42
- `gate` is build + test + docs-check. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `inspect` runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME). Mint fails on a costume dump. Isolated HOME skips machine `~/.grok`. Shared HOME last-mile dest is project `.grok/plugins/0xray` (no last-wins machine plugin). `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
42
+ `gate` is build + test + docs-check. `docs-build` runs Docusaurus on the 0xray exo (`docs-site/`); stranger mills skip. `inspect` runs the six mill checks (diff, plant vs worn, receipt, CI, live tarball GET, isolated HOME) plus observational harness (Cursor/Grok hooks bound, Repertoire fastened, Station present). `--go` emits a shop-style forge GO receipt; `--require-harness=bare|suited|partial` turns that observation into a gate. Mint fails on a costume dump. Isolated HOME skips machine `~/.grok`. Shared HOME last-mile dest is project `.grok/plugins/0xray` (no last-wins machine plugin). `ci` reports GitHub Actions (no auto-push). `hooks` installs git pre/post hooks. GitHub `0xRay CI/CD` is the mill gate on `main`. `Deploy Docs` is the mill Pages put.
43
43
 
44
44
  `docs-check` and `gate` run **full** 0xRay corpus only when the milled `package.json` name is `0xray` and `docs-site/` exists. Otherwise **light**: `package.json` + CHANGELOG.
45
45
 
@@ -29,7 +29,7 @@ const HELP =
29
29
  "Mill, not exo. FOUNDRY_ROOT overrides cwd (the repo being milled).\n" +
30
30
  "release bumps/commits/pushes/publishes the milled cwd — pass --dry-run or --i-mean-it (or FOUNDRY_RELEASE=1).\n" +
31
31
  "Mint fastens the requested factory plant (default mill; foundry.json plant: sound|blip turns mill off), then inspect.\n" +
32
- "inspect runs mill checks plus, on a sound seat, last bed receipt PASS/FAIL; on a blip seat, last blip receipt.\n" +
32
+ "inspect runs mill checks plus, on a sound seat, last bed receipt PASS/FAIL; on a blip seat, last blip receipt. Observational harness (hooks / Repertoire / Station). --go emits a forge GO receipt. Not an 8th MCP.\n" +
33
33
  "sound render|inspect|mix is the factory-sound plant (brief → seed → genre → wav → metrics; mix levels every variation). Sibling to mill, not a mill copy.\n" +
34
34
  "blip render|inspect|vibe|look is the factory-blip plant (brief → seed → still|motion:<id> → 4.44s mp4; vibe is stamp density, look is the friend hitting replay, ship bar 8). Sibling to mill + sound.\n" +
35
35
  "gate is build+test. docs-build is Docusaurus on the 0xray exo. ci reports GitHub Actions (no auto-push). hooks installs git pre/post hooks.\n";
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Mill inspect organ. Runs the six checks. Not an 8th MCP. Not PPE.
3
+ * Mill inspect organ. Runs the six plant checks plus observational harness.
4
+ * Not an 8th MCP. Not PPE. Does not fasten hooks or Repertoire.
4
5
  *
5
- * npx @0xray/foundry inspect [--skip-live]
6
+ * npx @0xray/foundry inspect [--skip-live] [--go] [--go-out PATH]
7
+ * [--require-harness=bare|suited|partial] [--goal TEXT]
6
8
  */
7
9
 
8
10
  import { spawnSync } from "node:child_process";
@@ -63,6 +65,140 @@ function readJson(file) {
63
65
  }
64
66
  }
65
67
 
68
+ const HARNESS_PROFILES = new Set(["bare", "suited", "partial"]);
69
+
70
+ export function parseInspectArgs(argv = process.argv.slice(2)) {
71
+ const out = {
72
+ skipLive: false,
73
+ go: false,
74
+ goOut: null,
75
+ requireHarness: null,
76
+ goal: null,
77
+ };
78
+ for (let i = 0; i < argv.length; i += 1) {
79
+ const arg = argv[i];
80
+ if (arg === "--skip-live") out.skipLive = true;
81
+ else if (arg === "--go") out.go = true;
82
+ else if (arg === "--go-out") out.goOut = argv[++i] || null;
83
+ else if (arg.startsWith("--go-out=")) out.goOut = arg.slice("--go-out=".length);
84
+ else if (arg === "--require-harness") out.requireHarness = argv[++i] || null;
85
+ else if (arg.startsWith("--require-harness=")) {
86
+ out.requireHarness = arg.slice("--require-harness=".length);
87
+ } else if (arg === "--goal") out.goal = argv[++i] || null;
88
+ else if (arg.startsWith("--goal=")) out.goal = arg.slice("--goal=".length);
89
+ }
90
+ if (out.goOut) out.go = true;
91
+ return out;
92
+ }
93
+
94
+ function hooksFileBound(file) {
95
+ if (!fs.existsSync(file)) return false;
96
+ const json = readJson(file);
97
+ const hooks = json?.hooks;
98
+ if (!hooks || typeof hooks !== "object") return false;
99
+ return Object.values(hooks).some(
100
+ (list) =>
101
+ Array.isArray(list) &&
102
+ list.some((entry) => typeof entry?.command === "string" && entry.command.trim()),
103
+ );
104
+ }
105
+
106
+ export function classifyHarnessProfile({ hooksBound, repertoireFastened, stationPresent }) {
107
+ if (!hooksBound && !repertoireFastened && !stationPresent) return "bare";
108
+ if (hooksBound && repertoireFastened) return "suited";
109
+ return "partial";
110
+ }
111
+
112
+ export function probeHarness(root) {
113
+ const cursorHooks = path.join(root, ".cursor", "hooks.json");
114
+ const grokHooks = path.join(root, ".grok", "plugins", "0xray", "hooks", "hooks.json");
115
+ const grokPluginHooks = path.join(root, ".grok-plugin", "hooks", "hooks.json");
116
+ const hooks = {
117
+ cursor: hooksFileBound(cursorHooks),
118
+ grok: hooksFileBound(grokHooks) || hooksFileBound(grokPluginHooks),
119
+ };
120
+ hooks.bound = Boolean(hooks.cursor || hooks.grok);
121
+
122
+ const repertoire = {
123
+ nodeModules: fs.existsSync(path.join(root, "node_modules", "@0xray", "repertoire")),
124
+ state: fs.existsSync(path.join(root, ".xray", "state", "repertoire")),
125
+ vendorSource: fs.existsSync(path.join(root, "vendor", "@0xray", "repertoire")),
126
+ };
127
+ repertoire.fastened = Boolean(repertoire.nodeModules || repertoire.state);
128
+
129
+ const features =
130
+ readJson(path.join(root, ".xray", "features.json")) ||
131
+ readJson(path.join(root, "xray", "features.json"));
132
+ repertoire.memoryRoutingDeclared = features?.memory_routing?.provider === "repertoire";
133
+
134
+ const stationPresent = fs.existsSync(path.join(root, ".xray", "state", "STATION.md"));
135
+ const profile = classifyHarnessProfile({
136
+ hooksBound: hooks.bound,
137
+ repertoireFastened: repertoire.fastened,
138
+ stationPresent,
139
+ });
140
+
141
+ return {
142
+ id: "harness",
143
+ hooks,
144
+ repertoire,
145
+ station: { present: stationPresent },
146
+ profile,
147
+ };
148
+ }
149
+
150
+ export function checkHarness(root, requireHarness = null) {
151
+ const probe = probeHarness(root);
152
+ if (requireHarness && !HARNESS_PROFILES.has(requireHarness)) {
153
+ return {
154
+ ...probe,
155
+ ok: false,
156
+ require: requireHarness,
157
+ detail: `unknown --require-harness=${requireHarness} (bare|suited|partial)`,
158
+ };
159
+ }
160
+ if (requireHarness && probe.profile !== requireHarness) {
161
+ return {
162
+ ...probe,
163
+ ok: false,
164
+ require: requireHarness,
165
+ detail: `harness profile ${probe.profile} != required ${requireHarness}`,
166
+ };
167
+ }
168
+ return {
169
+ ...probe,
170
+ ok: true,
171
+ require: requireHarness,
172
+ skipped: !requireHarness,
173
+ detail: requireHarness ? null : "observational — pass --require-harness to gate",
174
+ };
175
+ }
176
+
177
+ function gitRemote(root) {
178
+ const remote = spawnSync("git", ["config", "--get", "remote.origin.url"], {
179
+ cwd: root,
180
+ encoding: "utf8",
181
+ });
182
+ if (remote.error || remote.status !== 0) return null;
183
+ return (remote.stdout || "").trim() || null;
184
+ }
185
+
186
+ export function buildForgeGo(root, harness, opts = {}) {
187
+ return {
188
+ kind: "forge-go",
189
+ mill: "inspect",
190
+ profile: harness.profile,
191
+ hooksBound: Boolean(harness.hooks?.bound),
192
+ repertoireFastened: Boolean(harness.repertoire?.fastened),
193
+ stationPresent: Boolean(harness.station?.present),
194
+ require: harness.require || null,
195
+ ok: harness.ok !== false,
196
+ goal: opts.goal || process.env.FOUNDRY_GO_GOAL || null,
197
+ repo: gitRemote(root),
198
+ generatedAt: new Date().toISOString(),
199
+ };
200
+ }
201
+
66
202
  function checkDiff(root) {
67
203
  const status = spawnSync("git", ["status", "-sb"], { cwd: root, encoding: "utf8" });
68
204
  if (status.error || status.status === 128) {
@@ -412,6 +548,9 @@ export async function inspectSuit(root, opts = {}) {
412
548
  const skipLive = Boolean(opts.skipLive);
413
549
  const env = opts.env || process.env;
414
550
  const machineHome = opts.machineHome || mint.machineHome();
551
+ const requireHarness = opts.requireHarness || null;
552
+ const go = Boolean(opts.go);
553
+ const goal = opts.goal || null;
415
554
 
416
555
  const checks = [];
417
556
  checks.push(checkDiff(root));
@@ -445,22 +584,38 @@ export async function inspectSuit(root, opts = {}) {
445
584
  }
446
585
 
447
586
  checks.push(checkIsolatedHome(root, env, machineHome));
587
+ checks.push(checkHarness(root, requireHarness));
448
588
 
449
589
  const failed = checks.filter((c) => c.ok === false);
450
590
  const receipt = checks.find((c) => c.id === "receipt") || {};
451
- return {
591
+ const harness = checks.find((c) => c.id === "harness") || {};
592
+ const report = {
452
593
  ok: failed.length === 0,
453
594
  failed: failed.map((c) => c.id),
454
595
  checks,
455
596
  dna: receipt.dna || null,
456
597
  pack: "0xray-suit",
457
598
  };
599
+ if (go) {
600
+ report.go = buildForgeGo(root, harness, { goal });
601
+ }
602
+ return report;
458
603
  }
459
604
 
460
605
  async function main() {
461
- const skipLive = process.argv.includes("--skip-live");
606
+ const args = parseInspectArgs(process.argv.slice(2));
462
607
  const root = resolveMillRoot();
463
- const report = await inspectSuit(root, { skipLive });
608
+ const report = await inspectSuit(root, {
609
+ skipLive: args.skipLive,
610
+ requireHarness: args.requireHarness,
611
+ go: args.go,
612
+ goal: args.goal,
613
+ });
614
+ if (args.goOut) {
615
+ const dest = path.isAbsolute(args.goOut) ? args.goOut : path.join(root, args.goOut);
616
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
617
+ fs.writeFileSync(dest, `${JSON.stringify(report.go || report, null, 2)}\n`);
618
+ }
464
619
  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
465
620
  process.exit(report.ok ? 0 : 1);
466
621
  }