0xray 4.0.20 → 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 (74) 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 +23 -1
  4. package/.xray/features.json +1 -1
  5. package/AGENTS-consumer.md +15 -15
  6. package/AGENTS.md +12 -12
  7. package/README.md +40 -40
  8. package/SKILLS.md +4 -4
  9. package/dist/AGENTS.md +12 -12
  10. package/dist/CHANGELOG.md +91 -8
  11. package/dist/README.md +40 -40
  12. package/dist/SKILLS.md +4 -4
  13. package/dist/cli/commands/validate.d.ts +1 -1
  14. package/dist/cli/commands/validate.js +7 -1
  15. package/dist/governance/governance-core.d.ts +2 -1
  16. package/dist/governance/governance-core.js +20 -11
  17. package/dist/governance/governance-service.js +9 -7
  18. package/dist/inference/inference-cycle.js +22 -5
  19. package/dist/integrations/cursor/README.md +7 -6
  20. package/dist/integrations/cursor/hooks/after-file-edit.sh +3 -0
  21. package/dist/integrations/cursor/hooks/before-read-file.sh +3 -0
  22. package/dist/integrations/cursor/hooks/before-shell-execution.sh +3 -0
  23. package/dist/integrations/cursor/hooks/cursor-hook-utils.js +112 -12
  24. package/dist/integrations/cursor/hooks/cursor-usage-receipt.js +3 -0
  25. package/dist/integrations/cursor/hooks/hooks.json +13 -3
  26. package/dist/integrations/cursor/hooks/pre-compact.js +9 -0
  27. package/dist/integrations/cursor/hooks/pre-compact.sh +3 -0
  28. package/dist/integrations/cursor/hooks/pre-tool-use.js +17 -12
  29. package/dist/integrations/cursor/hooks/pre-tool-use.sh +3 -0
  30. package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +63 -0
  31. package/dist/integrations/grok/hooks/grok-hook-utils.js +3 -2
  32. package/dist/integrations/hooks/station-hook-runtime.cjs +310 -98
  33. package/dist/integrations/hooks/station-hook-runtime.mjs +10 -0
  34. package/dist/integrations/hooks/station-memory-ingest.mjs +4 -3
  35. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  36. package/dist/memory-routing/provider-loader.d.ts +3 -1
  37. package/dist/memory-routing/provider-loader.js +3 -1
  38. package/dist/nucleus/suit-temperament.d.ts +0 -5
  39. package/dist/nucleus/suit-temperament.js +13 -1
  40. package/dist/nucleus/thin-dispatch.js +11 -2
  41. package/dist/processors/implementations/agents-md-validation-processor.js +1 -1
  42. package/dist/skills/hermes-agent/SKILL.md +1 -1
  43. package/dist/skills/orchestrator/SKILL.md +23 -1
  44. package/llms.txt +1 -1
  45. package/package.json +3 -3
  46. package/scripts/foundry/CHANGELOG.md +2 -0
  47. package/scripts/foundry/README.md +4 -4
  48. package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
  49. package/scripts/foundry/cli.mjs +1 -1
  50. package/scripts/foundry/inspect.mjs +160 -5
  51. package/scripts/foundry/plant/skills/inspect/SKILL.md +2 -0
  52. package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
  53. package/scripts/foundry/validate-release-docs.mjs +48 -0
  54. package/scripts/foundry/version-manager.mjs +127 -7
  55. package/scripts/mjs/test-consumer-readiness.mjs +10 -3
  56. package/scripts/node/install-bridges.cjs +256 -10
  57. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  58. package/src/skills/hermes-agent/SKILL.md +1 -1
  59. package/src/skills/orchestrator/SKILL.md +23 -1
  60. package/vendor/@0xray/repertoire/data/stack-overlay.json +154 -1
  61. package/vendor/@0xray/repertoire/dist/RepertoireService.js +7 -6
  62. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js +10 -11
  63. package/vendor/@0xray/repertoire/dist/paths.js +66 -4
  64. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +54 -14
  65. package/xray/agents_template.md +10 -5
  66. package/xray/features.json +1 -1
  67. package/dist/mcps/shared/prompt-security-validator.d.ts +0 -44
  68. package/dist/mcps/shared/prompt-security-validator.js +0 -148
  69. package/dist/mcps/shared/security-scanner.d.ts +0 -119
  70. package/dist/mcps/shared/security-scanner.js +0 -429
  71. package/dist/scripts/activate-kernel-pipeline.d.ts +0 -7
  72. package/dist/scripts/activate-kernel-pipeline.js +0 -101
  73. package/src/mcps/shared/prompt-security-validator.ts +0 -199
  74. package/src/mcps/shared/security-scanner.ts +0 -599
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "description": "Stack-language overlay for 0xRay 4.0 memory routing. Additive to the 8-name factory seed. Not a plant diary and not the 0.1.8 brain.",
3
- "last_updated": "2026-09-20T00:00:00.000Z",
3
+ "last_updated": "2026-09-23T00:00:00.000Z",
4
4
  "source": "stack-overlay-4.0",
5
5
  "schema_version": "1.1",
6
6
  "implementation_guidance": {
@@ -745,6 +745,159 @@
745
745
  "last_seen": "2026-09-21T00:00:00.000Z",
746
746
  "governance_forced_count": 0
747
747
  }
748
+ },
749
+ {
750
+ "name": "feat-rebase",
751
+ "definition": "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.",
752
+ "tags": [
753
+ "cadence",
754
+ "rebase",
755
+ "lead"
756
+ ],
757
+ "priority": "high",
758
+ "status": "proposed",
759
+ "first_seen": "2026-09-23",
760
+ "evaluation_criteria": "Reply answers the original message from this seat instead of continuing a side thread.",
761
+ "validation_experiment": "A seat keeps answering notifications after the human asked for the original job \u2014 treat as fail.",
762
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
763
+ "implementation_notes": "LEAD-CADENCE.md Feat rebase. Not a new skill.",
764
+ "example_inference_snippet": "Go up a level and answer the original message again from this seat.",
765
+ "observation_stats": {
766
+ "observation_count": 1,
767
+ "avg_confidence": 0.55,
768
+ "max_confidence": 0.55,
769
+ "last_seen": "2026-09-23T00:00:00.000Z",
770
+ "governance_forced_count": 0
771
+ }
772
+ },
773
+ {
774
+ "name": "operating-planes",
775
+ "definition": "Operating planes are 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 copies stack laws onto that list, keeps observation stats, and refreshes a changed definition. Heat writes the matched names onto Station, the short-term card that survives compaction. As the model improves, reactive decisions move out of code into OP-PROC. That move 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.",
776
+ "tags": [
777
+ "planes",
778
+ "op-proc",
779
+ "test-ship",
780
+ "version-manager"
781
+ ],
782
+ "priority": "high",
783
+ "status": "proposed",
784
+ "first_seen": "2026-09-23",
785
+ "evaluation_criteria": "Reply names test/ship as the hero or the catastrophe, and treats a live cut as the stamper stripping shipped guides and shipped OP-PROC.",
786
+ "validation_experiment": "A seat ships without the test, or wakes after compact without the test/ship plane — treat as fail.",
787
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
788
+ "implementation_notes": "LEAD-CADENCE.md Planes. Not a new skill. Patch stamps stay in package.json, CHANGELOG, and stamped JSON. The stamper strips shipped guides and shipped OP-PROC. It does not edit Station, NOTES, dest, or node_modules. It does not publish.",
789
+ "example_inference_snippet": "Test then ship is the hero. Ship without that proof is the catastrophe.",
790
+ "observation_stats": {
791
+ "observation_count": 1,
792
+ "avg_confidence": 0.55,
793
+ "max_confidence": 0.55,
794
+ "last_seen": "2026-09-23T00:00:00.000Z",
795
+ "governance_forced_count": 0
796
+ }
797
+ },
798
+ {
799
+ "name": "wake-cascade",
800
+ "definition": "Chat is not the brain. Compaction drops the seat. Short-term memory is Station. Long-term memory is the project repertoire law list. NOTES is the deep cut: the pickup line cadences onto Station, and the body holds the cascade the card cannot. Capture a cascade by writing a named signal on the stack overlay and hydrating it onto dest. Observation counts are not capture. A reflection is not this capture. This wake, sorted: planes are code, OP-PROC, model, suit, mill, host, and test/ship; test then ship is the hero and ship without that proof is the catastrophe; each wake refreshes a changed stack definition and keeps observation stats, then heat writes matched names onto Station; the lead keeps control and a subagent gets the duty, the latest SHA, the Station card, the laws, and the suit built from the tree under review; when the work becomes mantra or the planes no longer share one present, answer the original message from this seat; 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; engine cadence is change, ship local, test, review the diff, monitor dest against Station, ship the PR; missing moral fusion is not an isotopic reject, vote confidence is a clamped weighted mean, thinDispatch strategy follows the adjusted level, and an empty inference cycle saves state.",
801
+ "tags": [
802
+ "cascade",
803
+ "memory",
804
+ "repertoire"
805
+ ],
806
+ "priority": "high",
807
+ "status": "proposed",
808
+ "first_seen": "2026-09-23",
809
+ "evaluation_criteria": "After compact the seat can state the cascade from the repertoire name and from NOTES, without the chat.",
810
+ "validation_experiment": "A seat wakes blank and rebuilds the planes from the thread summary — treat as fail.",
811
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
812
+ "implementation_notes": "NOTES body is the same cascade for the second Read. Not a reflection. Not a new skill.",
813
+ "example_inference_snippet": "Chat is not the brain. The cascade is the repertoire name wake-cascade.",
814
+ "observation_stats": {
815
+ "observation_count": 1,
816
+ "avg_confidence": 0.55,
817
+ "max_confidence": 0.55,
818
+ "last_seen": "2026-09-23T00:00:00.000Z",
819
+ "governance_forced_count": 0
820
+ }
821
+ },
822
+ {
823
+ "name": "heat-is-not-conviction",
824
+ "definition": "Heat touches last_seen on names the kernel diary already matches. 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. It does not append a confidence sample, so a second heat does not raise observation_count and does not move avg_confidence. More hits do not raise conviction. Promotion still flips a proposed law to validated at two observations and confidence 0.55. Conviction moves on feedback, not on heat. Kernel diary text is logs/framework/activity.log, routing outcomes, pattern metrics, and .xray/inference/latest-workflow.json plus workflow-status.json. Heat matches that text onto existing names and does not mint a law. A missing session file or a stale inference-state timestamp is telemetry, not a law. Repertoire mergeStackOverlay refreshes a changed stack definition and keeps observation stats. It does not pass fleshGeneric. Subject overlay still fleshes only a generic field-observed stub.",
825
+ "tags": ["memory", "heat", "kernel"],
826
+ "priority": "high",
827
+ "status": "proposed",
828
+ "first_seen": "2026-09-23",
829
+ "evaluation_criteria": "A rising observation count at 0.55 is not treated as learning.",
830
+ "validation_experiment": "A seat reads observation mass as conviction — treat as fail.",
831
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
832
+ "implementation_notes": "heatKernelDiary calls touchLastSeen. recordPrimitiveObservations still appends samples for session and field ingest. promoteQualifiedSignals uses DEFAULT_PROMOTION_MIN_CONFIDENCE 0.55 and min observations 2. mergeStackOverlay in repertoire src/paths.ts refreshes stack law fields and keeps observation_stats. It does not pass fleshGeneric.",
833
+ "example_inference_snippet": "Heat touches last_seen and does not append a confidence sample. A changed stack definition refreshes and the observation count stays.",
834
+ "observation_stats": {
835
+ "observation_count": 1,
836
+ "avg_confidence": 0.55,
837
+ "max_confidence": 0.55,
838
+ "last_seen": "2026-09-23T00:00:00.000Z",
839
+ "governance_forced_count": 0
840
+ }
841
+ },
842
+ {
843
+ "name": "inference-cycle-invariant",
844
+ "definition": "An inference cycle that generates no proposals still writes inference-cycle-state.json. Without that write the one-day trigger never starts and the next call repeats. InferenceCycle.getInstance applies later options, including force and skipApply. Governance failure rejects the proposals. Proposal text is a keyword map over session problem strings. generateProposals also emits recurring-pattern titles.",
845
+ "tags": ["inference", "invariant"],
846
+ "priority": "high",
847
+ "status": "proposed",
848
+ "first_seen": "2026-09-23",
849
+ "evaluation_criteria": "Empty cycles persist state, and later getInstance options apply.",
850
+ "validation_experiment": "An empty cycle returns without a state file — treat as fail.",
851
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
852
+ "implementation_notes": "src/inference/inference-cycle.ts and inference-proposal-generator.ts. generateProposals maps session problem strings through a keyword map and also emits recurring-pattern titles. Not a new skill.",
853
+ "example_inference_snippet": "An empty inference cycle still saves state. Proposal text is a keyword map over session problem strings. generateProposals also emits recurring-pattern titles.",
854
+ "observation_stats": {
855
+ "observation_count": 1,
856
+ "avg_confidence": 0.55,
857
+ "max_confidence": 0.55,
858
+ "last_seen": "2026-09-23T00:00:00.000Z",
859
+ "governance_forced_count": 0
860
+ }
861
+ },
862
+ {
863
+ "name": "governance-vote-invariant",
864
+ "definition": "PHI 1.666 and TAU 0.865 are unused constants. The matrix cutoffs are the literals 0.92, 0.95, 0.82, 0.88, 0.75, and 0.80. Reason text does not claim TAU. mergeVotes averageConfidence is the weighted mean of confidence, sum of weight times confidence over sum of weight, clamped to 0..1. That number is resonance. isotopicRatio is compared only when it is a finite number. moralFusion is not defaulted to 0.5. A missing fusion number does not force reject. Critical moral tension does reject. Without moral fields the three-skill vote stands.",
865
+ "tags": ["governance", "invariant"],
866
+ "priority": "high",
867
+ "status": "proposed",
868
+ "first_seen": "2026-09-23",
869
+ "evaluation_criteria": "A vote with no moral fusion is not an isotopic reject, and average confidence stays in 0..1.",
870
+ "validation_experiment": "Reason text names TAU, or average confidence exceeds 1 — treat as fail.",
871
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
872
+ "implementation_notes": "src/governance/governance-core.ts and governance-service.ts. Not a new skill.",
873
+ "example_inference_snippet": "No moral fusion is not an isotopic reject. Average confidence is a clamped weighted mean.",
874
+ "observation_stats": {
875
+ "observation_count": 1,
876
+ "avg_confidence": 0.55,
877
+ "max_confidence": 0.55,
878
+ "last_seen": "2026-09-23T00:00:00.000Z",
879
+ "governance_forced_count": 0
880
+ }
881
+ },
882
+ {
883
+ "name": "thin-dispatch-invariant",
884
+ "definition": "scoreComplexity applies the thresholds it is given. After Repertoire adjusts the score, level, recommendedStrategy, and estimatedAgents follow the adjusted level. A high-confidence trap with an adjusted score of at least 26 forces architect. A provenance-failure tag with an adjusted score under 51 forces bug-triage-specialist. On a frontier host the spawn gate warns and allows a spawn with no plan. Guided and strict hosts deny. The lead-dev plan builder is on.",
885
+ "tags": ["engine", "invariant", "thin-dispatch"],
886
+ "priority": "high",
887
+ "status": "proposed",
888
+ "first_seen": "2026-09-23",
889
+ "evaluation_criteria": "Strategy follows the adjusted level, and the trap routes are architect at 26 and bug-triage under 51.",
890
+ "validation_experiment": "Strategy stays on the pre-adjust level after Repertoire moves the score — treat as fail.",
891
+ "master_index_integration": "Project-local curated_signals after hydrateWritableSignals merges data/stack-overlay.json. Never write the factory tarball.",
892
+ "implementation_notes": "src/nucleus/thin-dispatch.ts and RepertoireOrchestratorBridge.resolveThinDispatchAgent. Not a new skill.",
893
+ "example_inference_snippet": "After Repertoire adjusts the score, strategy follows the adjusted level. A trap at 26 forces architect.",
894
+ "observation_stats": {
895
+ "observation_count": 1,
896
+ "avg_confidence": 0.55,
897
+ "max_confidence": 0.55,
898
+ "last_seen": "2026-09-23T00:00:00.000Z",
899
+ "governance_forced_count": 0
900
+ }
748
901
  }
749
902
  ]
750
903
  }
@@ -39,7 +39,6 @@ export class RepertoireService {
39
39
  }
40
40
  if (shouldAutoSyncXray(options.syncXray)) {
41
41
  this.syncXrayMemory();
42
- this.syncWorkspaceRepos();
43
42
  this.heatKernelDiary();
44
43
  }
45
44
  }
@@ -126,6 +125,10 @@ export class RepertoireService {
126
125
  }
127
126
  /**
128
127
  * Heat existing dest names from kernel diary text. No new names.
128
+ * Touches last_seen only when the diary contains that signal's id,
129
+ * or the id with hyphens read as spaces. Does not append a confidence sample.
130
+ * Two definition words are not a hit. Heat must not mint and must not
131
+ * change observation_count or avg_confidence.
129
132
  * Colon pattern ids like `architect:architect_skill` never become dest keys.
130
133
  */
131
134
  heatKernelDiary(collected) {
@@ -134,11 +137,9 @@ export class RepertoireService {
134
137
  return { heated: [], sources: diary.sources };
135
138
  }
136
139
  const hits = this.signalsManager.matchByText(diary.text, 2);
137
- const matches = hits.map((hit) => ({
138
- name: hit.signal.name,
139
- confidence: 0.55,
140
- }));
141
- const heated = matches.length > 0 ? this.signalsManager.recordPrimitiveObservations(matches) : [];
140
+ const heated = hits.length > 0
141
+ ? this.signalsManager.touchLastSeen(hits.map((hit) => hit.signal.name))
142
+ : [];
142
143
  return { heated, sources: diary.sources };
143
144
  }
144
145
  reloadOpProc() {
@@ -36,7 +36,7 @@ export class XraySessionIngester {
36
36
  const entry = this.sessionToInferenceEntry(session);
37
37
  const targetFile = join(this.targetDir, `${session.timestamp.split('T')[0]}.jsonl`);
38
38
  appendFileSync(targetFile, JSON.stringify(entry) + '\n');
39
- this.recordObservations(session, entry);
39
+ this.recordObservations(session);
40
40
  if (this.stateManager && session.sessionId) {
41
41
  this.stateManager.markProcessed([session.sessionId], 'session');
42
42
  }
@@ -50,13 +50,13 @@ export class XraySessionIngester {
50
50
  const promoted = imported > 0 ? this.signalsManager.promoteQualifiedSignals() : [];
51
51
  return { imported, skipped, promoted };
52
52
  }
53
- recordObservations(session, entry) {
54
- const matches = this.extractMatches(session, entry);
53
+ recordObservations(session) {
54
+ const matches = this.extractMatches(session);
55
55
  if (matches.length === 0)
56
56
  return;
57
57
  this.signalsManager.recordPrimitiveObservations(matches);
58
58
  }
59
- extractMatches(session, entry) {
59
+ extractMatches(session) {
60
60
  const matches = [];
61
61
  const seen = new Set();
62
62
  const add = (name, confidence) => {
@@ -70,20 +70,19 @@ export class XraySessionIngester {
70
70
  for (const pattern of session.patterns ?? []) {
71
71
  const raw = pattern.name || pattern.type || '';
72
72
  const slug = slugFieldPrimitiveName(raw);
73
- const confidence = typeof pattern.confidence === 'number' ? pattern.confidence : DEFAULT_PROMOTION_MIN_CONFIDENCE;
74
- if (slug)
75
- add(slug, Math.max(confidence, DEFAULT_PROMOTION_MIN_CONFIDENCE));
73
+ if (!slug || typeof pattern.confidence !== 'number')
74
+ continue;
75
+ add(slug, pattern.confidence);
76
76
  }
77
77
  for (const name of session.matched_primitives ?? []) {
78
- const confidence = session.match_confidence?.[name] ?? DEFAULT_PROMOTION_MIN_CONFIDENCE;
78
+ const confidence = session.match_confidence?.[name];
79
+ if (typeof confidence !== 'number')
80
+ continue;
79
81
  add(name, confidence);
80
82
  }
81
83
  if (this.repoPrimitive) {
82
84
  add(this.repoPrimitive, DEFAULT_PROMOTION_MIN_CONFIDENCE);
83
85
  }
84
- for (const hit of this.signalsManager.matchByText(entry.inference, 2)) {
85
- add(hit.signal.name, DEFAULT_PROMOTION_MIN_CONFIDENCE);
86
- }
87
86
  return matches;
88
87
  }
89
88
  sessionToInferenceEntry(session) {
@@ -70,6 +70,65 @@ export function resolveWritableConfigPath(configured, cwd, fallback) {
70
70
  return configured;
71
71
  return resolve(cwd, configured);
72
72
  }
73
+ const STACK_LAW_FIELDS = [
74
+ 'definition',
75
+ 'tags',
76
+ 'priority',
77
+ 'evaluation_criteria',
78
+ 'validation_experiment',
79
+ 'example_inference_snippet',
80
+ 'implementation_notes',
81
+ ];
82
+ function assignStackLawField(existing, field, next) {
83
+ switch (field) {
84
+ case 'definition':
85
+ if (typeof next === 'string')
86
+ existing.definition = next;
87
+ break;
88
+ case 'tags':
89
+ if (Array.isArray(next))
90
+ existing.tags = [...next];
91
+ break;
92
+ case 'priority':
93
+ if (typeof next === 'string')
94
+ existing.priority = next;
95
+ break;
96
+ case 'evaluation_criteria':
97
+ if (typeof next === 'string')
98
+ existing.evaluation_criteria = next;
99
+ break;
100
+ case 'validation_experiment':
101
+ if (typeof next === 'string')
102
+ existing.validation_experiment = next;
103
+ break;
104
+ case 'example_inference_snippet':
105
+ if (typeof next === 'string')
106
+ existing.example_inference_snippet = next;
107
+ break;
108
+ case 'implementation_notes':
109
+ if (typeof next === 'string')
110
+ existing.implementation_notes = next;
111
+ break;
112
+ default: {
113
+ const unexpected = field;
114
+ throw new Error(`unknown stack law field ${String(unexpected)}`);
115
+ }
116
+ }
117
+ }
118
+ /** Copy changed stack law text onto an existing project name. Observation stats stay. */
119
+ function refreshStackLaw(existing, signal) {
120
+ let dirty = false;
121
+ for (const field of STACK_LAW_FIELDS) {
122
+ const next = signal[field];
123
+ if (next == null)
124
+ continue;
125
+ if (JSON.stringify(existing[field]) === JSON.stringify(next))
126
+ continue;
127
+ assignStackLawField(existing, field, next);
128
+ dirty = true;
129
+ }
130
+ return dirty;
131
+ }
73
132
  export function isGenericFieldObservedDefinition(definition) {
74
133
  return definition.includes('Field-observed domain primitive');
75
134
  }
@@ -104,6 +163,9 @@ function mergeOverlaySignals(destPath, overlayPath, options = {}) {
104
163
  changed += 1;
105
164
  continue;
106
165
  }
166
+ if (options.refreshLaws && refreshStackLaw(existing, signal)) {
167
+ changed += 1;
168
+ }
107
169
  if (options.fleshGeneric && isGenericFieldObservedDefinition(existing.definition)) {
108
170
  existing.definition = signal.definition;
109
171
  if (signal.tags)
@@ -127,12 +189,12 @@ function mergeOverlaySignals(destPath, overlayPath, options = {}) {
127
189
  return changed;
128
190
  }
129
191
  /**
130
- * Additive merge of `data/stack-overlay.json` into a project copy.
131
- * Existing names keep their stats. Missing overlay names are appended.
132
- * Refuses the factory tarball path.
192
+ * Merge `data/stack-overlay.json` into a project copy.
193
+ * Missing names are appended. A changed stack law field is refreshed.
194
+ * Observation stats on existing names stay. Refuses the factory tarball path.
133
195
  */
134
196
  export function mergeStackOverlay(destPath, overlayPath = DEFAULT_STACK_OVERLAY_PATH) {
135
- return mergeOverlaySignals(destPath, overlayPath);
197
+ return mergeOverlaySignals(destPath, overlayPath, { refreshLaws: true });
136
198
  }
137
199
  /**
138
200
  * Additive merge of `data/subject-overlay.json` — product/repo flesh.
@@ -7,6 +7,21 @@ export const FEEDBACK_SUCCESS_CONFIDENCE_BOOST = 0.002;
7
7
  export const FEEDBACK_FAILURE_CONFIDENCE_PENALTY = 0.005;
8
8
  export const FEEDBACK_MIN_CONFIDENCE = DEFAULT_PROMOTION_MIN_CONFIDENCE;
9
9
  const FIELD_PRIMITIVE_NAME = /^[A-Za-z][A-Za-z0-9_-]{2,119}$/;
10
+ /**
11
+ * The diary names a law only when it contains the signal id, or the id with
12
+ * hyphens read as spaces. A repo tail, leftover tokens, and two definition
13
+ * words are not the name.
14
+ */
15
+ export function signalNameInText(text, name) {
16
+ const normalized = text.toLowerCase();
17
+ const id = name.toLowerCase();
18
+ if (!id)
19
+ return false;
20
+ if (normalized.includes(id))
21
+ return true;
22
+ const spaced = id.replace(/-/g, ' ');
23
+ return spaced !== id && normalized.includes(spaced);
24
+ }
10
25
  const GROOVER_EXPERIMENT_NAMES = new Set([
11
26
  'criteria_selection_gap',
12
27
  'external_norm_smuggling_risk',
@@ -119,25 +134,17 @@ export class CuratedSignalsManager {
119
134
  return this.load().signals.filter((s) => s.priority === priority);
120
135
  }
121
136
  /**
122
- * Score text against all signals using name, tags, definition, criteria, and snippet.
137
+ * Score text against signals. A hit requires the signal id, or the id with
138
+ * hyphens read as spaces. Two definition words are not a match.
123
139
  */
124
140
  matchByText(text, minScore = 2) {
125
141
  const normalized = text.toLowerCase();
126
142
  const matches = [];
127
143
  for (const signal of this.load().signals) {
128
- const matchedOn = [];
129
- let score = 0;
130
- if (normalized.includes(signal.name.replace(/-/g, ' ')) || normalized.includes(signal.name)) {
131
- score += 5;
132
- matchedOn.push('name');
133
- }
134
- else if (signal.name.startsWith('repo-')) {
135
- const tail = signal.name.slice(5);
136
- if (tail.length >= 3 && new RegExp(`\\b${tail}\\b`, 'i').test(normalized)) {
137
- score += 5;
138
- matchedOn.push('name');
139
- }
140
- }
144
+ if (!signalNameInText(text, signal.name))
145
+ continue;
146
+ const matchedOn = ['name'];
147
+ let score = 5;
141
148
  for (const tag of signal.tags) {
142
149
  if (normalized.includes(tag.toLowerCase())) {
143
150
  score += 3;
@@ -191,6 +198,39 @@ export class CuratedSignalsManager {
191
198
  }
192
199
  return matches.sort((a, b) => b.score - a.score);
193
200
  }
201
+ /**
202
+ * Mark names the diary already matched. Does not append a confidence sample.
203
+ * Missing names are left absent. Heat must not mint a law.
204
+ */
205
+ touchLastSeen(names) {
206
+ const data = this.load();
207
+ const now = new Date().toISOString();
208
+ const updated = [];
209
+ const seen = new Set();
210
+ for (const name of names) {
211
+ if (!name || seen.has(name))
212
+ continue;
213
+ seen.add(name);
214
+ const signal = data.signals.find((entry) => entry.name === name);
215
+ if (!signal)
216
+ continue;
217
+ const previous = signal.observation_stats;
218
+ signal.observation_stats = previous
219
+ ? { ...previous, last_seen: now }
220
+ : {
221
+ observation_count: 0,
222
+ avg_confidence: 0,
223
+ max_confidence: 0,
224
+ last_seen: now,
225
+ governance_forced_count: 0,
226
+ };
227
+ updated.push(signal.name);
228
+ }
229
+ if (updated.length > 0) {
230
+ this.save(data);
231
+ }
232
+ return updated;
233
+ }
194
234
  recordPrimitiveObservations(matches, options = {}) {
195
235
  const minConfidence = options.minConfidence ?? DEFAULT_PROMOTION_MIN_CONFIDENCE;
196
236
  const data = this.load();
@@ -1,22 +1,27 @@
1
1
  # 0xRay AI Agents
2
2
 
3
- Quick reference for the 0xRay AI orchestration framework (v16 MCPs-centric three-subsystem).
3
+ Quick reference for the 0xRay AI orchestration framework (**4.0** — 7 consumer MCP servers).
4
4
 
5
5
  ## Available MCP Servers
6
6
 
7
+ All seven use `npx -y 0xray mcp <cmd>`:
8
+
7
9
  | Server | Role |
8
10
  |--------|------|
9
11
  | `xray-governance` | Proposal governance, codex snapshot, quality gates |
10
- | `xray-skills` | Skill invocation, agent-specific knowledge servers |
12
+ | `xray-skills` | Skill invocation, 45 knowledge skills |
13
+ | `xray-orchestrator` | thinDispatch routing, AsideContext, confidence gate |
11
14
  | `xray-enforcer` | Codex compliance enforcement, rule validation |
12
- | `xray-orchestrator` | Multi-agent workflow coordination, task delegation |
15
+ | `xray-researcher` | Codebase exploration, memory-routing enrichment |
16
+ | `xray-code-review` | Proposal quality, code review deliberation |
17
+ | `xray-architect-tools` | System design, architecture decisions |
13
18
 
14
19
  ## CLI Commands
15
20
 
16
21
  | Command | Description |
17
22
  |---------|-------------|
18
23
  | `xray setup` | Full framework setup (hooks, Hermes, symlinks) |
19
- | `xray validate` | Validate codex compliance |
24
+ | `xray validate` | Wear check (pack paths, Cursor hooks, mill) — not leftover init.sh |
20
25
  | `xray codex check` | Check codex rules |
21
26
  | `xray health` | Framework health check |
22
27
  | `xray hooks` | Manage lifecycle hooks |
@@ -25,7 +30,7 @@ Quick reference for the 0xRay AI orchestration framework (v16 MCPs-centric three
25
30
 
26
31
  xray operates under the three-subsystem model: Inference + External Governance (Dynamo Solar SSOT) + Autonomous Engine (thinDispatch 7-flow in MCP orchestrator). All actions are validated against the Universal Development Codex before execution.
27
32
 
28
- **Codex**: The codex lives in `.xray/codex.json` and enforces 60 terms across all agent interactions.
33
+ **Codex**: `.xray/codex.json` — **69 terms** across all agent interactions.
29
34
 
30
35
  ## thinDispatch Routing
31
36
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.20",
3
+ "version": "4.0.22",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",
@@ -1,44 +0,0 @@
1
- /**
2
- * 0xRay Framework - AI Prompt Security Validator
3
- *
4
- * Specialized security validation for AI agent prompts and responses
5
- * Prevents prompt injection, system prompt override, and malicious inputs
6
- */
7
- export interface PromptSecurityConfig {
8
- enabled: boolean;
9
- maxPromptLength: number;
10
- allowedPatterns: RegExp[];
11
- blockedPatterns: RegExp[];
12
- sanitizeLevel: "basic" | "strict" | "paranoid";
13
- }
14
- export interface SecurityValidationResult {
15
- isSafe: boolean;
16
- violations: string[];
17
- sanitizedPrompt?: string | undefined;
18
- riskLevel: "low" | "medium" | "high" | "critical";
19
- }
20
- export declare class PromptSecurityValidator {
21
- private config;
22
- constructor(config?: Partial<PromptSecurityConfig>);
23
- /**
24
- * Validate AI prompt for security issues
25
- */
26
- validatePrompt(prompt: string): SecurityValidationResult;
27
- /**
28
- * Check for system prompt override attempts
29
- */
30
- private containsSystemPromptOverride;
31
- /**
32
- * Check for prompt injection attempts
33
- */
34
- private containsInjectionAttempts;
35
- /**
36
- * Sanitize prompt by removing dangerous patterns
37
- */
38
- private sanitizePrompt;
39
- /**
40
- * Validate agent response for safety
41
- */
42
- validateResponse(response: string): SecurityValidationResult;
43
- }
44
- export declare const promptSecurityValidator: PromptSecurityValidator;