pluribus-context 0.3.39 → 0.3.41
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.
- package/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/bin/pluribus.js +2 -0
- package/docs/.nojekyll +0 -0
- package/docs/.well-known/agent-skills/context-receipts/SKILL.md +206 -0
- package/docs/.well-known/agent-skills/index.json +19 -0
- package/docs/.well-known/agent-skills/skill-policy-receipts/SKILL.md +77 -0
- package/docs/context-budget-receipts.md +43 -0
- package/docs/index.html +38 -0
- package/docs/receipt-playground.html +250 -0
- package/examples/context-attention-receipts/README.md +41 -0
- package/examples/context-attention-receipts/attention-receipt-fail.json +49 -0
- package/examples/context-attention-receipts/attention-receipt-pass.json +53 -0
- package/examples/context-attention-receipts/check-attention-receipt.mjs +97 -0
- package/examples/tool-surface-diff-receipts/tool-surface-diff-receipt.json +61 -0
- package/package.json +10 -2
- package/skills/context-receipts/README.md +13 -2
- package/skills/context-receipts/SKILL.md +65 -0
- package/src/commands/demo.js +120 -1
- package/src/utils/version.js +1 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Pluribus Receipt Playground</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark light; --bg:#0b1020; --card:#121a33; --text:#e8ecf8; --muted:#aab4cf; --accent:#8ee6c4; --bad:#ff8f9c; --warn:#ffd166; --ok:#8ee6c4; --border:#283454; }
|
|
9
|
+
* { box-sizing:border-box; }
|
|
10
|
+
body { margin:0; font:15px/1.5 system-ui,-apple-system,Segoe UI,sans-serif; background:linear-gradient(180deg,#0b1020,#10182e); color:var(--text); }
|
|
11
|
+
main { max-width:1120px; margin:0 auto; padding:34px 18px 48px; }
|
|
12
|
+
h1 { font-size:clamp(2rem,4vw,3.4rem); line-height:1.05; margin:.2rem 0 1rem; letter-spacing:-.04em; }
|
|
13
|
+
h2 { margin-top:0; }
|
|
14
|
+
p, li { color:var(--muted); }
|
|
15
|
+
a { color:var(--accent); }
|
|
16
|
+
.grid { display:grid; grid-template-columns:1fr; gap:16px; }
|
|
17
|
+
@media (min-width:900px) { .grid { grid-template-columns:1.1fr .9fr; } }
|
|
18
|
+
.card { background:rgba(18,26,51,.92); border:1px solid var(--border); border-radius:18px; padding:18px; box-shadow:0 20px 80px rgba(0,0,0,.24); }
|
|
19
|
+
textarea { width:100%; min-height:560px; resize:vertical; border:1px solid var(--border); border-radius:14px; padding:14px; background:#060a15; color:#e8ecf8; font:13px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
20
|
+
button, select { border:1px solid var(--border); border-radius:999px; background:#182344; color:var(--text); padding:10px 14px; cursor:pointer; }
|
|
21
|
+
button:hover, select:hover { border-color:var(--accent); }
|
|
22
|
+
.controls { display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin:0 0 12px; }
|
|
23
|
+
.result { white-space:pre-wrap; overflow:auto; border-radius:14px; padding:14px; background:#060a15; border:1px solid var(--border); min-height:210px; font:13px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
24
|
+
.ok { color:var(--ok); }
|
|
25
|
+
.bad { color:var(--bad); }
|
|
26
|
+
.warn { color:var(--warn); }
|
|
27
|
+
.pill { display:inline-block; border:1px solid var(--border); border-radius:999px; padding:.2rem .55rem; color:var(--muted); margin:.1rem .2rem .1rem 0; }
|
|
28
|
+
code { background:#060a15; padding:.12rem .32rem; border-radius:6px; color:#d7e2ff; }
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<main>
|
|
33
|
+
<p><a href="index.html">← Pluribus</a></p>
|
|
34
|
+
<h1>Receipt playground</h1>
|
|
35
|
+
<p>Paste a Pluribus receipt and validate the boundary evidence locally in your browser. No network calls, no raw prompts/results required.</p>
|
|
36
|
+
<div class="grid">
|
|
37
|
+
<section class="card">
|
|
38
|
+
<div class="controls">
|
|
39
|
+
<select id="sample">
|
|
40
|
+
<option value="toolSurface">Sample: MCP tool-surface diff</option>
|
|
41
|
+
<option value="attentionPass">Sample: context attention pass</option>
|
|
42
|
+
<option value="attentionFail">Sample: context attention fail</option>
|
|
43
|
+
<option value="ruleAttention">Sample: CLAUDE.md rule attention drift</option>
|
|
44
|
+
<option value="skillInstall">Sample: Agent Skill install provenance</option>
|
|
45
|
+
</select>
|
|
46
|
+
<button id="load">Load sample</button>
|
|
47
|
+
<button id="validate">Validate</button>
|
|
48
|
+
</div>
|
|
49
|
+
<textarea id="receipt" spellcheck="false"></textarea>
|
|
50
|
+
</section>
|
|
51
|
+
<aside class="card">
|
|
52
|
+
<h2>Validation result</h2>
|
|
53
|
+
<div id="result" class="result">Click Validate.</div>
|
|
54
|
+
<h2>What this checks</h2>
|
|
55
|
+
<p><span class="pill">tool-surface diff</span> proves runtime discovery changes without logging raw schemas/prompts/results.</p>
|
|
56
|
+
<p><span class="pill">context attention</span> proves required retrieved context was delivered, acknowledged, and cited before edits.</p>
|
|
57
|
+
<p><span class="pill">rule attention</span> shows whether project rules were re-read, cited in the pre-edit plan, and checked before changes.</p>
|
|
58
|
+
<p><span class="pill">skill install provenance</span> shows which <code>SKILL.md</code> source path won, which mirrors were ignored, and whether install metadata stayed content-safe.</p>
|
|
59
|
+
<h2>CLI equivalent</h2>
|
|
60
|
+
<p><code>npx --yes pluribus-context@latest demo tool-surface-diff --json</code></p>
|
|
61
|
+
</aside>
|
|
62
|
+
</div>
|
|
63
|
+
</main>
|
|
64
|
+
<script>
|
|
65
|
+
const samples = {
|
|
66
|
+
toolSurface: {
|
|
67
|
+
schema: 'pluribus.mcp_tool_surface_diff_receipt.v1',
|
|
68
|
+
run_id: 'tool-surface-diff-demo',
|
|
69
|
+
generated_at: '2026-06-09T13:00:00Z',
|
|
70
|
+
platform: { name: 'enterprise-mcp-dynamic-discovery', audit_sink: 'admin-center-or-siem' },
|
|
71
|
+
catalog: { server_id: 'mcp://sales-ops-gateway', previous_hash: 'sha256:previous-catalog-redacted', current_hash: 'sha256:current-catalog-redacted' },
|
|
72
|
+
runtime_discovery: { enabled: true, trigger: 'runtime_tool_catalog_diff' },
|
|
73
|
+
privacy_boundary: { raw_schemas: 'omitted_hash_only', raw_prompts: 'omitted', raw_results: 'omitted' },
|
|
74
|
+
tools: [
|
|
75
|
+
{ tool_id: 'tool:crm.search_accounts', name_hash: 'sha256:0cc2...', schema_hash: 'sha256:6f4f...', status: 'activated', validation_outcome: 'accepted', diff_summary: { added_fields: 1, removed_fields: 0, changed_fields: 0 } },
|
|
76
|
+
{ tool_id: 'tool:crm.export_contacts', name_hash: 'sha256:f38f...', schema_hash: 'sha256:95dd...', status: 'blocked', validation_outcome: 'blocked_by_rai', diff_summary: { added_fields: 4, removed_fields: 0, changed_fields: 2 } },
|
|
77
|
+
{ tool_id: 'tool:billing.refund_invoice', name_hash: 'sha256:abfd...', schema_hash: 'sha256:3b4f...', status: 'withheld', validation_outcome: 'entitlement_filtered', diff_summary: { added_fields: 0, removed_fields: 0, changed_fields: 0 } }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
attentionPass: {
|
|
81
|
+
schema: 'pluribus.context_attention_receipt.v1', receipt_id: 'attn_demo_pass', task_id: 'claude-code-refactor-184', agent_surface: 'claude_code', retrieval_system: 'graph_memory_mcp',
|
|
82
|
+
required_context: [{ id: 'ctx:architecture:tenant-routing' }, { id: 'ctx:decision:readonly-audit-first' }],
|
|
83
|
+
delivery: { surface: 'mcp_tool_response', delivered_context_ids: ['ctx:architecture:tenant-routing', 'ctx:decision:readonly-audit-first'], raw_context_omitted: true, evidence_path: '.pluribus/receipts/context-attention.ndjson' },
|
|
84
|
+
attention: { acknowledged_before_plan: ['ctx:architecture:tenant-routing', 'ctx:decision:readonly-audit-first'], cited_in_plan: ['ctx:architecture:tenant-routing', 'ctx:decision:readonly-audit-first'], missing_context_stop: false },
|
|
85
|
+
privacy: { raw_prompts_omitted: true, raw_documents_omitted: true, source_code_omitted: true, tool_outputs_omitted: true, tokens_omitted: true, customer_data_omitted: true },
|
|
86
|
+
result: { status: 'safe_to_continue', next_safe_action: 'continue with dry-run refactor plan' }
|
|
87
|
+
},
|
|
88
|
+
attentionFail: {
|
|
89
|
+
schema: 'pluribus.context_attention_receipt.v1', receipt_id: 'attn_demo_fail', task_id: 'claude-code-refactor-185', agent_surface: 'claude_code', retrieval_system: 'graph_memory_mcp',
|
|
90
|
+
required_context: [{ id: 'ctx:architecture:tenant-routing' }, { id: 'ctx:decision:readonly-audit-first' }],
|
|
91
|
+
delivery: { surface: 'mcp_tool_response', delivered_context_ids: ['ctx:architecture:tenant-routing', 'ctx:decision:readonly-audit-first'], raw_context_omitted: true, evidence_path: '.pluribus/receipts/context-attention.ndjson' },
|
|
92
|
+
attention: { acknowledged_before_plan: ['ctx:architecture:tenant-routing'], cited_in_plan: [], missing_context_stop: false },
|
|
93
|
+
privacy: { raw_prompts_omitted: true, raw_documents_omitted: true, source_code_omitted: true, tool_outputs_omitted: true, tokens_omitted: true, customer_data_omitted: true },
|
|
94
|
+
result: { status: 'unsafe_to_continue', next_safe_action: 'stop and re-deliver required context before editing' }
|
|
95
|
+
},
|
|
96
|
+
ruleAttention: {
|
|
97
|
+
schema: 'pluribus.claude_code_rule_attention_receipt.v1',
|
|
98
|
+
receipt_id: 'rule_attn_demo_62087',
|
|
99
|
+
task_id: 'claude-code-long-session-62087',
|
|
100
|
+
agent_surface: 'claude_code',
|
|
101
|
+
context_boundary: { session_phase: 'post-compaction-long-session', before_first_edit: true },
|
|
102
|
+
rules: [
|
|
103
|
+
{ rule_id: 'CLAUDE.md:test-after-plan', source: 'CLAUDE.md#workflow', last_loaded_at: '2026-06-09T22:41:00Z', last_referenced_at: null, cited_in_pre_edit_plan: false, edit_check: 'missing', violation_category: 'loaded_but_not_referenced' },
|
|
104
|
+
{ rule_id: 'CLAUDE.md:no-skip-steps', source: 'CLAUDE.md#workflow', last_loaded_at: '2026-06-09T22:41:00Z', last_referenced_at: '2026-06-09T22:58:00Z', cited_in_pre_edit_plan: true, edit_check: 'pass', violation_category: null }
|
|
105
|
+
],
|
|
106
|
+
privacy: { raw_prompts_omitted: true, raw_claude_md_omitted: true, source_code_omitted: true, tool_outputs_omitted: true, tokens_omitted: true, customer_data_omitted: true },
|
|
107
|
+
result: { status: 'unsafe_to_edit', next_safe_action: 'stop, re-read missing rule ids, and restate the pre-edit plan with citations' }
|
|
108
|
+
},
|
|
109
|
+
skillInstall: {
|
|
110
|
+
schema: 'pluribus.agent_skill_install_provenance_receipt.v1',
|
|
111
|
+
receipt_id: 'skill_install_openskills_demo',
|
|
112
|
+
installer: { name: 'agent-skill-package-manager', mode: 'universal', target_agent_dir: '.agent/skills' },
|
|
113
|
+
source: { kind: 'github_repo', repo: 'caioribeiroclw-pixel/pluribus', requested_ref: 'v0.3.40', resolved_sha: 'sha256:resolved-commit-redacted' },
|
|
114
|
+
discovery: {
|
|
115
|
+
requested_skill_names: ['context-receipts', 'skill-policy-receipts'],
|
|
116
|
+
discovered_source_paths: [
|
|
117
|
+
'skills/context-receipts/SKILL.md',
|
|
118
|
+
'examples/agent-skills/context-receipts/SKILL.md',
|
|
119
|
+
'docs/.well-known/agent-skills/context-receipts/SKILL.md',
|
|
120
|
+
'skills/skill-policy-receipts/SKILL.md',
|
|
121
|
+
'examples/agent-skills/skill-policy-receipts/SKILL.md',
|
|
122
|
+
'docs/.well-known/agent-skills/skill-policy-receipts/SKILL.md'
|
|
123
|
+
],
|
|
124
|
+
duplicate_resolution: 'prefer_top_level_skills_dir'
|
|
125
|
+
},
|
|
126
|
+
selected_skills: [
|
|
127
|
+
{ name: 'context-receipts', selected_source_path: 'skills/context-receipts/SKILL.md', target_path: '.agent/skills/context-receipts/SKILL.md', operation: 'install', lockfile_entry_written: true },
|
|
128
|
+
{ name: 'skill-policy-receipts', selected_source_path: 'skills/skill-policy-receipts/SKILL.md', target_path: '.agent/skills/skill-policy-receipts/SKILL.md', operation: 'install', lockfile_entry_written: true }
|
|
129
|
+
],
|
|
130
|
+
ignored_duplicates: [
|
|
131
|
+
{ name: 'context-receipts', ignored_source_path: 'examples/agent-skills/context-receipts/SKILL.md', reason: 'duplicate_mirror_not_canonical' },
|
|
132
|
+
{ name: 'context-receipts', ignored_source_path: 'docs/.well-known/agent-skills/context-receipts/SKILL.md', reason: 'duplicate_mirror_not_canonical' },
|
|
133
|
+
{ name: 'skill-policy-receipts', ignored_source_path: 'examples/agent-skills/skill-policy-receipts/SKILL.md', reason: 'duplicate_mirror_not_canonical' },
|
|
134
|
+
{ name: 'skill-policy-receipts', ignored_source_path: 'docs/.well-known/agent-skills/skill-policy-receipts/SKILL.md', reason: 'duplicate_mirror_not_canonical' }
|
|
135
|
+
],
|
|
136
|
+
safety: { content_sanitizers: ['frontmatter-parse', 'markdown-only'], raw_secret_or_env_values_copied: false, scripts_or_hooks_installed: false, network_capability_added: false, manual_review_required: true },
|
|
137
|
+
privacy: { raw_skill_body_omitted: true, raw_env_values_omitted: true, auth_headers_omitted: true, customer_data_omitted: true },
|
|
138
|
+
result: { status: 'safe_to_install_after_review', installed_count: 2, ignored_duplicate_count: 4 }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const receiptEl = document.querySelector('#receipt')
|
|
143
|
+
const resultEl = document.querySelector('#result')
|
|
144
|
+
document.querySelector('#load').addEventListener('click', loadSample)
|
|
145
|
+
document.querySelector('#validate').addEventListener('click', validate)
|
|
146
|
+
document.querySelector('#sample').addEventListener('change', loadSample)
|
|
147
|
+
loadSample()
|
|
148
|
+
|
|
149
|
+
function loadSample() {
|
|
150
|
+
const key = document.querySelector('#sample').value
|
|
151
|
+
receiptEl.value = JSON.stringify(samples[key], null, 2)
|
|
152
|
+
validate()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validate() {
|
|
156
|
+
let receipt
|
|
157
|
+
try { receipt = JSON.parse(receiptEl.value) }
|
|
158
|
+
catch (error) { return render({ ok:false, errors:[`invalid JSON: ${error.message}`], warnings:[] }) }
|
|
159
|
+
if (receipt.schema === 'pluribus.mcp_tool_surface_diff_receipt.v1') return render(validateToolSurface(receipt))
|
|
160
|
+
if (receipt.schema === 'pluribus.context_attention_receipt.v1') return render(validateAttention(receipt))
|
|
161
|
+
if (receipt.schema === 'pluribus.claude_code_rule_attention_receipt.v1') return render(validateRuleAttention(receipt))
|
|
162
|
+
if (receipt.schema === 'pluribus.agent_skill_install_provenance_receipt.v1') return render(validateSkillInstall(receipt))
|
|
163
|
+
render({ ok:false, errors:[`unsupported schema: ${receipt.schema || '(missing)'}`], warnings:[] })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function validateToolSurface(r) {
|
|
167
|
+
const errors = [], warnings = []
|
|
168
|
+
const privacy = r.privacy_boundary || {}
|
|
169
|
+
if (!r.catalog?.current_hash) errors.push('catalog.current_hash is required')
|
|
170
|
+
if (!Array.isArray(r.tools) || r.tools.length === 0) errors.push('tools must include at least one discovered/activated/withheld/blocked tool')
|
|
171
|
+
for (const field of ['raw_schemas','raw_prompts','raw_results']) if (!String(privacy[field] || '').startsWith('omitted')) errors.push(`privacy_boundary.${field} must be omitted`)
|
|
172
|
+
const counts = countBy(r.tools || [], 'status')
|
|
173
|
+
if (!counts.activated && !counts.withheld && !counts.blocked) warnings.push('receipt has no activated/withheld/blocked status counts')
|
|
174
|
+
return { ok: errors.length === 0, schema:r.schema, summary:counts, errors, warnings }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function validateAttention(r) {
|
|
178
|
+
const errors = [], warnings = []
|
|
179
|
+
const required = ids((r.required_context || []).map(x => x.id))
|
|
180
|
+
const delivered = ids(r.delivery?.delivered_context_ids)
|
|
181
|
+
const ack = ids(r.attention?.acknowledged_before_plan)
|
|
182
|
+
const cited = ids(r.attention?.cited_in_plan)
|
|
183
|
+
if (!required.length) errors.push('required_context must name at least one id')
|
|
184
|
+
for (const id of required) {
|
|
185
|
+
if (!delivered.includes(id)) errors.push(`required context not delivered: ${id}`)
|
|
186
|
+
if (!ack.includes(id)) errors.push(`required context not acknowledged before plan: ${id}`)
|
|
187
|
+
if (!cited.includes(id)) errors.push(`required context not cited in plan: ${id}`)
|
|
188
|
+
}
|
|
189
|
+
if (r.delivery?.raw_context_omitted !== true) errors.push('delivery.raw_context_omitted must be true')
|
|
190
|
+
for (const field of ['raw_prompts_omitted','raw_documents_omitted','source_code_omitted','tool_outputs_omitted','tokens_omitted','customer_data_omitted']) if (r.privacy?.[field] !== true) errors.push(`privacy.${field} must be true`)
|
|
191
|
+
if (errors.length && r.attention?.missing_context_stop !== true) errors.push('missing_context_stop must be true when required attention evidence is incomplete')
|
|
192
|
+
if (!r.delivery?.evidence_path) warnings.push('delivery.evidence_path is missing')
|
|
193
|
+
return { ok: errors.length === 0, schema:r.schema, required_count:required.length, delivered_count:delivered.length, acknowledged_count:ack.length, cited_count:cited.length, errors, warnings }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function validateRuleAttention(r) {
|
|
197
|
+
const errors = [], warnings = []
|
|
198
|
+
const rules = Array.isArray(r.rules) ? r.rules : []
|
|
199
|
+
if (!rules.length) errors.push('rules must include at least one project rule')
|
|
200
|
+
for (const rule of rules) {
|
|
201
|
+
if (!rule.rule_id) errors.push('each rule must include rule_id')
|
|
202
|
+
if (!rule.source) errors.push(`${rule.rule_id || 'rule'} must include source`)
|
|
203
|
+
if (!rule.last_loaded_at) errors.push(`${rule.rule_id || 'rule'} must include last_loaded_at`)
|
|
204
|
+
if (rule.cited_in_pre_edit_plan !== true) warnings.push(`${rule.rule_id || 'rule'} was not cited in the pre-edit plan`)
|
|
205
|
+
if (!['pass','fail','missing'].includes(rule.edit_check)) errors.push(`${rule.rule_id || 'rule'} edit_check must be pass, fail, or missing`)
|
|
206
|
+
}
|
|
207
|
+
for (const field of ['raw_prompts_omitted','raw_claude_md_omitted','source_code_omitted','tool_outputs_omitted','tokens_omitted','customer_data_omitted']) if (r.privacy?.[field] !== true) errors.push(`privacy.${field} must be true`)
|
|
208
|
+
const missing = rules.filter(rule => rule.cited_in_pre_edit_plan !== true || rule.edit_check !== 'pass')
|
|
209
|
+
if (missing.length && r.result?.status === 'safe_to_edit') errors.push('result.status cannot be safe_to_edit while a required rule is uncited or failing')
|
|
210
|
+
return { ok: errors.length === 0 && missing.length === 0, schema:r.schema, rules_count:rules.length, uncited_or_failing_rules:missing.map(rule => rule.rule_id), errors, warnings }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function validateSkillInstall(r) {
|
|
214
|
+
const errors = [], warnings = []
|
|
215
|
+
const selected = Array.isArray(r.selected_skills) ? r.selected_skills : []
|
|
216
|
+
const ignored = Array.isArray(r.ignored_duplicates) ? r.ignored_duplicates : []
|
|
217
|
+
const discovered = Array.isArray(r.discovery?.discovered_source_paths) ? r.discovery.discovered_source_paths : []
|
|
218
|
+
if (!r.source?.repo) errors.push('source.repo is required')
|
|
219
|
+
if (!r.source?.resolved_sha) errors.push('source.resolved_sha is required')
|
|
220
|
+
if (!r.installer?.target_agent_dir) errors.push('installer.target_agent_dir is required')
|
|
221
|
+
if (!selected.length) errors.push('selected_skills must include at least one installed skill')
|
|
222
|
+
for (const skill of selected) {
|
|
223
|
+
if (!skill.name) errors.push('each selected skill must include name')
|
|
224
|
+
if (!skill.selected_source_path) errors.push(`${skill.name || 'skill'} must include selected_source_path`)
|
|
225
|
+
if (!skill.target_path) errors.push(`${skill.name || 'skill'} must include target_path`)
|
|
226
|
+
if (skill.lockfile_entry_written !== true) warnings.push(`${skill.name || 'skill'} did not write lockfile metadata`)
|
|
227
|
+
}
|
|
228
|
+
const selectedNames = selected.map(skill => skill.name).filter(Boolean)
|
|
229
|
+
for (const name of selectedNames) {
|
|
230
|
+
const discoveredForName = discovered.filter(path => path.includes(`/${name}/`) || path.includes(`${name}/SKILL.md`)).length
|
|
231
|
+
const ignoredForName = ignored.filter(item => item.name === name).length
|
|
232
|
+
if (discoveredForName > 1 && ignoredForName === 0) errors.push(`${name} has multiple discovered paths but no ignored_duplicates evidence`)
|
|
233
|
+
}
|
|
234
|
+
if (r.safety?.raw_secret_or_env_values_copied !== false) errors.push('safety.raw_secret_or_env_values_copied must be false')
|
|
235
|
+
if (r.safety?.scripts_or_hooks_installed !== false) errors.push('safety.scripts_or_hooks_installed must be false for a low-authority skill receipt')
|
|
236
|
+
if (r.safety?.network_capability_added !== false) errors.push('safety.network_capability_added must be false for a skill-only install')
|
|
237
|
+
for (const field of ['raw_skill_body_omitted','raw_env_values_omitted','auth_headers_omitted','customer_data_omitted']) if (r.privacy?.[field] !== true) errors.push(`privacy.${field} must be true`)
|
|
238
|
+
if (selected.length !== new Set(selectedNames).size) errors.push('selected_skills contains duplicate names after resolution')
|
|
239
|
+
return { ok: errors.length === 0, schema:r.schema, selected_count:selected.length, ignored_duplicate_count:ignored.length, discovered_source_count:discovered.length, selected_source_paths:selected.map(skill => skill.selected_source_path), errors, warnings }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function countBy(items, key) { return items.reduce((acc, item) => (acc[item[key] || 'unknown'] = (acc[item[key] || 'unknown'] || 0) + 1, acc), {}) }
|
|
243
|
+
function ids(v) { return Array.isArray(v) ? v.filter(x => typeof x === 'string' && x) : [] }
|
|
244
|
+
function render(obj) {
|
|
245
|
+
resultEl.className = 'result ' + (obj.ok ? 'ok' : 'bad')
|
|
246
|
+
resultEl.textContent = JSON.stringify(obj, null, 2)
|
|
247
|
+
}
|
|
248
|
+
</script>
|
|
249
|
+
</body>
|
|
250
|
+
</html>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Context attention receipts
|
|
2
|
+
|
|
3
|
+
This example is for the `r/ClaudeCode` / GraphRAG failure mode: a graph, memory, RAG, or MCP retrieval system finds the right context, but the coding agent behaves as if it never saw it.
|
|
4
|
+
|
|
5
|
+
Pluribus should not replace graph memory, RAG, or MCP search. The useful boundary is smaller: emit a privacy-safe receipt proving whether selected context actually crossed the agent boundary and was treated as required before planning or editing.
|
|
6
|
+
|
|
7
|
+
## What the receipt proves
|
|
8
|
+
|
|
9
|
+
`pluribus.context_attention_receipt.v1` records low-cardinality evidence only:
|
|
10
|
+
|
|
11
|
+
- which retrieval/memory/tool context IDs were required for the task;
|
|
12
|
+
- which surface delivered them (`mcp_tool_response`, `claude_hook`, `CLAUDE.md`, `AGENTS.md`, etc.);
|
|
13
|
+
- whether the agent acknowledged those IDs before plan/edit;
|
|
14
|
+
- whether the final plan cites the IDs it depended on;
|
|
15
|
+
- whether missing context forced a stop instead of a best-effort edit;
|
|
16
|
+
- whether raw documents, prompts, source code, transcripts, tokens, or customer data were omitted.
|
|
17
|
+
|
|
18
|
+
It intentionally does **not** store the retrieved chunks themselves. Use stable IDs, hashes, coarse labels, and evidence paths instead.
|
|
19
|
+
|
|
20
|
+
## Smoke test
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node examples/context-attention-receipts/check-attention-receipt.mjs \
|
|
24
|
+
examples/context-attention-receipts/attention-receipt-pass.json
|
|
25
|
+
|
|
26
|
+
node examples/context-attention-receipts/check-attention-receipt.mjs \
|
|
27
|
+
examples/context-attention-receipts/attention-receipt-fail.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The first command should pass. The second should fail because the graph/memory result was retrieved but not acknowledged or cited before editing.
|
|
31
|
+
|
|
32
|
+
## Why this exists
|
|
33
|
+
|
|
34
|
+
A high-quality retrieval layer is still weak if the next agent turn can ignore it. The receipt makes that failure visible:
|
|
35
|
+
|
|
36
|
+
- retrieval succeeded;
|
|
37
|
+
- required context was or was not delivered to the agent surface;
|
|
38
|
+
- the agent did or did not acknowledge it before changing files;
|
|
39
|
+
- the wrapper/hook did or did not stop the run when required context was missing.
|
|
40
|
+
|
|
41
|
+
That is the narrow Pluribus angle: not more memory, not a new graph database, and not another router — evidence that the context boundary was actually crossed.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "pluribus.context_attention_receipt.v1",
|
|
3
|
+
"receipt_id": "attn_2026_06_09_demo_fail",
|
|
4
|
+
"task_id": "claude-code-refactor-185",
|
|
5
|
+
"agent_surface": "claude_code",
|
|
6
|
+
"retrieval_system": "graph_memory_mcp",
|
|
7
|
+
"required_context": [
|
|
8
|
+
{
|
|
9
|
+
"id": "ctx:architecture:tenant-routing",
|
|
10
|
+
"source_type": "knowledge_graph",
|
|
11
|
+
"source_hash": "sha256:7bf3e0c1-redacted",
|
|
12
|
+
"why_required": "routes tenant-scoped writes through the policy gateway"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "ctx:decision:readonly-audit-first",
|
|
16
|
+
"source_type": "decision_log",
|
|
17
|
+
"source_hash": "sha256:11ab91d4-redacted",
|
|
18
|
+
"why_required": "requires dry-run/audit before writes"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"delivery": {
|
|
22
|
+
"surface": "mcp_tool_response",
|
|
23
|
+
"delivered_context_ids": [
|
|
24
|
+
"ctx:architecture:tenant-routing",
|
|
25
|
+
"ctx:decision:readonly-audit-first"
|
|
26
|
+
],
|
|
27
|
+
"raw_context_omitted": true,
|
|
28
|
+
"evidence_path": ".pluribus/receipts/context-attention.ndjson"
|
|
29
|
+
},
|
|
30
|
+
"attention": {
|
|
31
|
+
"acknowledged_before_plan": [
|
|
32
|
+
"ctx:architecture:tenant-routing"
|
|
33
|
+
],
|
|
34
|
+
"cited_in_plan": [],
|
|
35
|
+
"missing_context_stop": false
|
|
36
|
+
},
|
|
37
|
+
"privacy": {
|
|
38
|
+
"raw_prompts_omitted": true,
|
|
39
|
+
"raw_documents_omitted": true,
|
|
40
|
+
"source_code_omitted": true,
|
|
41
|
+
"tool_outputs_omitted": true,
|
|
42
|
+
"tokens_omitted": true,
|
|
43
|
+
"customer_data_omitted": true
|
|
44
|
+
},
|
|
45
|
+
"result": {
|
|
46
|
+
"status": "unsafe_to_continue",
|
|
47
|
+
"next_safe_action": "stop and ask the retrieval wrapper to re-deliver required context before editing"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "pluribus.context_attention_receipt.v1",
|
|
3
|
+
"receipt_id": "attn_2026_06_09_demo_pass",
|
|
4
|
+
"task_id": "claude-code-refactor-184",
|
|
5
|
+
"agent_surface": "claude_code",
|
|
6
|
+
"retrieval_system": "graph_memory_mcp",
|
|
7
|
+
"required_context": [
|
|
8
|
+
{
|
|
9
|
+
"id": "ctx:architecture:tenant-routing",
|
|
10
|
+
"source_type": "knowledge_graph",
|
|
11
|
+
"source_hash": "sha256:7bf3e0c1-redacted",
|
|
12
|
+
"why_required": "routes tenant-scoped writes through the policy gateway"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "ctx:decision:readonly-audit-first",
|
|
16
|
+
"source_type": "decision_log",
|
|
17
|
+
"source_hash": "sha256:11ab91d4-redacted",
|
|
18
|
+
"why_required": "requires dry-run/audit before writes"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"delivery": {
|
|
22
|
+
"surface": "mcp_tool_response",
|
|
23
|
+
"delivered_context_ids": [
|
|
24
|
+
"ctx:architecture:tenant-routing",
|
|
25
|
+
"ctx:decision:readonly-audit-first"
|
|
26
|
+
],
|
|
27
|
+
"raw_context_omitted": true,
|
|
28
|
+
"evidence_path": ".pluribus/receipts/context-attention.ndjson"
|
|
29
|
+
},
|
|
30
|
+
"attention": {
|
|
31
|
+
"acknowledged_before_plan": [
|
|
32
|
+
"ctx:architecture:tenant-routing",
|
|
33
|
+
"ctx:decision:readonly-audit-first"
|
|
34
|
+
],
|
|
35
|
+
"cited_in_plan": [
|
|
36
|
+
"ctx:architecture:tenant-routing",
|
|
37
|
+
"ctx:decision:readonly-audit-first"
|
|
38
|
+
],
|
|
39
|
+
"missing_context_stop": false
|
|
40
|
+
},
|
|
41
|
+
"privacy": {
|
|
42
|
+
"raw_prompts_omitted": true,
|
|
43
|
+
"raw_documents_omitted": true,
|
|
44
|
+
"source_code_omitted": true,
|
|
45
|
+
"tool_outputs_omitted": true,
|
|
46
|
+
"tokens_omitted": true,
|
|
47
|
+
"customer_data_omitted": true
|
|
48
|
+
},
|
|
49
|
+
"result": {
|
|
50
|
+
"status": "safe_to_continue",
|
|
51
|
+
"next_safe_action": "continue with dry-run refactor plan"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
const [receiptPathArg] = process.argv.slice(2)
|
|
6
|
+
|
|
7
|
+
if (!receiptPathArg) {
|
|
8
|
+
fail(['usage: node check-attention-receipt.mjs <receipt.json>'], 2)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const receiptPath = resolve(process.cwd(), receiptPathArg)
|
|
12
|
+
let receipt
|
|
13
|
+
try {
|
|
14
|
+
receipt = JSON.parse(readFileSync(receiptPath, 'utf8'))
|
|
15
|
+
} catch (error) {
|
|
16
|
+
fail([`could not read receipt JSON: ${error.message}`], 2)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const errors = []
|
|
20
|
+
const warnings = []
|
|
21
|
+
|
|
22
|
+
if (receipt.schema !== 'pluribus.context_attention_receipt.v1') {
|
|
23
|
+
errors.push('schema must be pluribus.context_attention_receipt.v1')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const requiredIds = ids(receipt.required_context?.map((item) => item.id))
|
|
27
|
+
const deliveredIds = ids(receipt.delivery?.delivered_context_ids)
|
|
28
|
+
const acknowledgedIds = ids(receipt.attention?.acknowledged_before_plan)
|
|
29
|
+
const citedIds = ids(receipt.attention?.cited_in_plan)
|
|
30
|
+
|
|
31
|
+
if (requiredIds.length === 0) errors.push('required_context must name at least one context id')
|
|
32
|
+
|
|
33
|
+
for (const id of requiredIds) {
|
|
34
|
+
if (!deliveredIds.includes(id)) errors.push(`required context not delivered: ${id}`)
|
|
35
|
+
if (!acknowledgedIds.includes(id)) errors.push(`required context not acknowledged before plan: ${id}`)
|
|
36
|
+
if (!citedIds.includes(id)) errors.push(`required context not cited in plan: ${id}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (receipt.delivery?.raw_context_omitted !== true) {
|
|
40
|
+
errors.push('delivery.raw_context_omitted must be true')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const privacy = receipt.privacy || {}
|
|
44
|
+
for (const field of [
|
|
45
|
+
'raw_prompts_omitted',
|
|
46
|
+
'raw_documents_omitted',
|
|
47
|
+
'source_code_omitted',
|
|
48
|
+
'tool_outputs_omitted',
|
|
49
|
+
'tokens_omitted',
|
|
50
|
+
'customer_data_omitted'
|
|
51
|
+
]) {
|
|
52
|
+
if (privacy[field] !== true) errors.push(`privacy.${field} must be true`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (errors.length > 0 && receipt.attention?.missing_context_stop !== true) {
|
|
56
|
+
errors.push('missing_context_stop must be true when required context attention evidence is incomplete')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (receipt.result?.status === 'safe_to_continue' && errors.length > 0) {
|
|
60
|
+
errors.push('result.status cannot be safe_to_continue when required evidence is incomplete')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!receipt.delivery?.evidence_path) {
|
|
64
|
+
warnings.push('delivery.evidence_path is missing; reviewers need a pointer to the audit trail')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const output = {
|
|
68
|
+
ok: errors.length === 0,
|
|
69
|
+
receipt_id: receipt.receipt_id || null,
|
|
70
|
+
task_id: receipt.task_id || null,
|
|
71
|
+
agent_surface: receipt.agent_surface || null,
|
|
72
|
+
required_count: requiredIds.length,
|
|
73
|
+
delivered_count: deliveredIds.length,
|
|
74
|
+
acknowledged_count: acknowledgedIds.length,
|
|
75
|
+
cited_count: citedIds.length,
|
|
76
|
+
status: receipt.result?.status || null,
|
|
77
|
+
next_safe_action: receipt.result?.next_safe_action || null,
|
|
78
|
+
errors,
|
|
79
|
+
warnings
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (output.ok) {
|
|
83
|
+
console.log(JSON.stringify(output, null, 2))
|
|
84
|
+
process.exit(0)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.error(JSON.stringify(output, null, 2))
|
|
88
|
+
process.exit(1)
|
|
89
|
+
|
|
90
|
+
function ids(value) {
|
|
91
|
+
return Array.isArray(value) ? value.filter((id) => typeof id === 'string' && id.length > 0) : []
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function fail(errors, code) {
|
|
95
|
+
console.error(JSON.stringify({ ok: false, errors }, null, 2))
|
|
96
|
+
process.exit(code)
|
|
97
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "pluribus.mcp_tool_surface_diff_receipt.v1",
|
|
3
|
+
"run_id": "tool-surface-diff-demo",
|
|
4
|
+
"generated_at": "2026-06-09T13:00:00Z",
|
|
5
|
+
"platform": {
|
|
6
|
+
"name": "enterprise-mcp-dynamic-discovery",
|
|
7
|
+
"audit_sink": "admin-center-or-siem"
|
|
8
|
+
},
|
|
9
|
+
"catalog": {
|
|
10
|
+
"server_id": "mcp://sales-ops-gateway",
|
|
11
|
+
"previous_hash": "sha256:previous-catalog-redacted",
|
|
12
|
+
"current_hash": "sha256:current-catalog-redacted"
|
|
13
|
+
},
|
|
14
|
+
"runtime_discovery": {
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"trigger": "runtime_tool_catalog_diff"
|
|
17
|
+
},
|
|
18
|
+
"privacy_boundary": {
|
|
19
|
+
"raw_schemas": "omitted_hash_only",
|
|
20
|
+
"raw_prompts": "omitted",
|
|
21
|
+
"raw_results": "omitted"
|
|
22
|
+
},
|
|
23
|
+
"tools": [
|
|
24
|
+
{
|
|
25
|
+
"tool_id": "tool:crm.search_accounts",
|
|
26
|
+
"name_hash": "sha256:0cc2efb4a26f4c5eb4f7d8c99e78d37adbdba07d50ee7873452c0216d02b1f48",
|
|
27
|
+
"schema_hash": "sha256:6f4fbe0a8be41b6e29c0c1c113aac38dfefdd12b89c6e9d4a996df2537acdb71",
|
|
28
|
+
"status": "activated",
|
|
29
|
+
"validation_outcome": "accepted",
|
|
30
|
+
"diff_summary": {
|
|
31
|
+
"added_fields": 1,
|
|
32
|
+
"removed_fields": 0,
|
|
33
|
+
"changed_fields": 0
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"tool_id": "tool:crm.export_contacts",
|
|
38
|
+
"name_hash": "sha256:f38f53f9ba3c348e67332a24a7d15f5e7ab1c9253cf01f3451e15d9e15435e13",
|
|
39
|
+
"schema_hash": "sha256:95ddc9b86a0c2d8bc6f3ca0bcce93dca2469ced51b0d38c8f8c5aa88554e0032",
|
|
40
|
+
"status": "blocked",
|
|
41
|
+
"validation_outcome": "blocked_by_rai",
|
|
42
|
+
"diff_summary": {
|
|
43
|
+
"added_fields": 4,
|
|
44
|
+
"removed_fields": 0,
|
|
45
|
+
"changed_fields": 2
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"tool_id": "tool:billing.refund_invoice",
|
|
50
|
+
"name_hash": "sha256:abfdaf6f0a3f33342aca6d8b0d63c303e99978481d26827843a901cb6237617d",
|
|
51
|
+
"schema_hash": "sha256:3b4fdc3ec15d2fa1cc589c1c7de280a19772d5745b4ef54d27806714be12bb8c",
|
|
52
|
+
"status": "withheld",
|
|
53
|
+
"validation_outcome": "entitlement_filtered",
|
|
54
|
+
"diff_summary": {
|
|
55
|
+
"added_fields": 0,
|
|
56
|
+
"removed_fields": 0,
|
|
57
|
+
"changed_fields": 0
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pluribus-context",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.41",
|
|
4
4
|
"description": "AI context and rules sync CLI for Claude.md, Claude Code, Cursor, and Copilot instructions, with privacy-safe context receipts that prove what memory, tools, skills, compactions, and security findings crossed agent boundaries without logging raw content.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/caioribeiroclw-pixel/pluribus#readme",
|
|
@@ -68,6 +68,11 @@
|
|
|
68
68
|
"ai-agent-observability",
|
|
69
69
|
"opentelemetry",
|
|
70
70
|
"mcp",
|
|
71
|
+
"mcp-audit",
|
|
72
|
+
"mcp-gateway",
|
|
73
|
+
"mcp-security",
|
|
74
|
+
"tool-discovery",
|
|
75
|
+
"audit-trail",
|
|
71
76
|
"drift-detection",
|
|
72
77
|
"openclaw",
|
|
73
78
|
"rules",
|
|
@@ -84,7 +89,10 @@
|
|
|
84
89
|
"agent-context-audit",
|
|
85
90
|
"agent-memory",
|
|
86
91
|
"bob",
|
|
87
|
-
"bob-rules"
|
|
92
|
+
"bob-rules",
|
|
93
|
+
"agent-skill",
|
|
94
|
+
"skillpm",
|
|
95
|
+
"agent-skills-registry"
|
|
88
96
|
],
|
|
89
97
|
"author": "Caio Ribeiro",
|
|
90
98
|
"license": "MIT",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Context receipts Agent Skill recipe
|
|
2
2
|
|
|
3
|
-
This is a small, copyable Agent Skill recipe for context-engineering users who are adopting Tool Search, lazy MCP loading, skills, memory, compaction, or subagents and need to verify what actually crossed the context boundary.
|
|
3
|
+
This is a small, copyable Agent Skill recipe for context-engineering users who are adopting Tool Search, lazy MCP loading, dynamic tool discovery, skills, memory, compaction, GraphRAG/code search, transcript review, or subagents and need to verify what actually crossed the context boundary.
|
|
4
4
|
|
|
5
5
|
It is intentionally markdown-only so it can be copied into a local skills directory such as:
|
|
6
6
|
|
|
@@ -8,6 +8,11 @@ It is intentionally markdown-only so it can be copied into a local skills direct
|
|
|
8
8
|
- `.opencode/skills/context-receipts/SKILL.md`
|
|
9
9
|
- `.agents/skills/context-receipts/SKILL.md`
|
|
10
10
|
|
|
11
|
+
The two newest smoke paths are:
|
|
12
|
+
|
|
13
|
+
- **Runtime tool-surface diff:** prove which MCP tools were discovered, activated, withheld, or blocked without copying raw schemas/prompts/results.
|
|
14
|
+
- **Context attention:** prove that retrieved/baseline context was delivered, acknowledged before planning, and cited before edits/tool calls.
|
|
15
|
+
|
|
11
16
|
## Quick smoke
|
|
12
17
|
|
|
13
18
|
Ask an agent or harness using the skill to emit a receipt for one workflow and verify these constraints:
|
|
@@ -19,9 +24,15 @@ grep -E 'raw_(schema|query|args|result|output|transcript|text)_copied":false|raw
|
|
|
19
24
|
|
|
20
25
|
Then manually check that the receipt contains counts, hashes, ids, buckets, and `audit_gap`, but does **not** contain private prompts, raw schemas, tool args/results, skill bodies, memory bodies, customer names, secrets, or transcript text.
|
|
21
26
|
|
|
22
|
-
For executable fixture examples, see
|
|
27
|
+
For executable fixture examples, see:
|
|
28
|
+
|
|
29
|
+
- [`../../examples/tool-surface-diff-receipts/`](../../examples/tool-surface-diff-receipts/) for runtime MCP tool-surface diff receipts.
|
|
30
|
+
- [`../../examples/context-attention-receipts/`](../../examples/context-attention-receipts/) for retrieved-context attention receipts.
|
|
31
|
+
- [`../../examples/context-input-evidence/`](../../examples/context-input-evidence/) for ToolSearch propagation, pruning, and compaction transaction smokes.
|
|
23
32
|
|
|
24
33
|
```bash
|
|
34
|
+
node ../../examples/context-attention-receipts/check-attention-receipt.mjs \
|
|
35
|
+
../../examples/context-attention-receipts/attention-receipt-pass.json
|
|
25
36
|
node ../../examples/context-input-evidence/convert-subagent-toolsearch-propagation-log.mjs
|
|
26
37
|
node ../../examples/context-input-evidence/convert-pruning-log.mjs
|
|
27
38
|
node ../../examples/context-input-evidence/convert-compaction-transaction-log.mjs
|