pluribus-context 0.3.41 โ†’ 0.3.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +3 -2
  3. package/bin/pluribus.js +3 -1
  4. package/docs/agent-surface-proof-chain.md +176 -0
  5. package/docs/cursor-claude-context-handoff.md +68 -0
  6. package/docs/index.html +1 -1
  7. package/docs/receipt-playground.html +54 -0
  8. package/docs/session-preflight-receipts.md +77 -0
  9. package/examples/claude-md-read-receipts/README.md +70 -0
  10. package/examples/claude-md-read-receipts/check-read-receipt.mjs +119 -0
  11. package/examples/claude-md-read-receipts/sample-read-receipt.json +45 -0
  12. package/examples/claude-md-read-receipts/stale-read-receipt.json +18 -0
  13. package/examples/context-sufficiency-trace/README.md +22 -0
  14. package/examples/context-sufficiency-trace/check-context-sufficiency.mjs +40 -0
  15. package/examples/context-sufficiency-trace/context-trace-pass.json +28 -0
  16. package/examples/context-sufficiency-trace/context-trace.json +47 -0
  17. package/examples/context-sufficiency-trace/ground-truth.json +13 -0
  18. package/examples/provider-degradation-canaries/README.md +79 -0
  19. package/examples/provider-degradation-canaries/check-degradation-receipt.mjs +64 -0
  20. package/examples/provider-degradation-canaries/healthy-decision.json +27 -0
  21. package/examples/provider-degradation-canaries/unsafe-write-decision.json +26 -0
  22. package/examples/semantic-anchor-receipts/README.md +49 -0
  23. package/examples/semantic-anchor-receipts/check-semantic-anchors.mjs +153 -0
  24. package/examples/semantic-anchor-receipts/cleaned-paste.md +17 -0
  25. package/examples/semantic-anchor-receipts/original-paste.md +19 -0
  26. package/examples/semantic-anchor-receipts/sample-receipt.json +62 -0
  27. package/examples/session-preflight-receipts/README.md +25 -0
  28. package/examples/session-preflight-receipts/session-preflight-receipt.json +39 -0
  29. package/examples/session-preflight-receipts/session-preflight.mdc +18 -0
  30. package/examples/task-scoped-mcp-config/README.md +60 -0
  31. package/examples/task-scoped-mcp-config/mcp-catalog.json +46 -0
  32. package/examples/task-scoped-mcp-config/select-mcp-config.mjs +64 -0
  33. package/examples/task-scoped-mcp-config/tasks/browser-debug.json +7 -0
  34. package/package.json +1 -1
  35. package/src/commands/demo.js +81 -1
  36. package/src/utils/version.js +1 -1
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ function parseArgs(argv) {
6
+ const args = { out: null };
7
+ for (let i = 0; i < argv.length; i += 1) {
8
+ const key = argv[i];
9
+ const value = argv[i + 1];
10
+ if (key === '--original') { args.original = value; i += 1; continue; }
11
+ if (key === '--cleaned') { args.cleaned = value; i += 1; continue; }
12
+ if (key === '--out') { args.out = value; i += 1; continue; }
13
+ if (key === '--help' || key === '-h') { args.help = true; continue; }
14
+ throw new Error(`Unknown argument: ${key}`);
15
+ }
16
+ return args;
17
+ }
18
+
19
+ function usage() {
20
+ return `Usage: node check-semantic-anchors.mjs --original original-paste.md --cleaned cleaned-paste.md [--out receipt.json]\n`;
21
+ }
22
+
23
+ function normalize(value) {
24
+ return value
25
+ .replace(/`+/g, '')
26
+ .replace(/\s+/g, ' ')
27
+ .trim()
28
+ .toLowerCase();
29
+ }
30
+
31
+ function approxTokens(value) {
32
+ const chunks = value.trim().split(/\s+/).filter(Boolean).length;
33
+ return Math.max(1, Math.ceil(chunks * 1.33));
34
+ }
35
+
36
+ function extractAnchors(markdown) {
37
+ const anchors = [];
38
+ const lines = markdown.split(/\r?\n/);
39
+ let inFence = false;
40
+ let fenceLang = '';
41
+ let fenceLines = [];
42
+
43
+ function push(type, text, extra = {}) {
44
+ const canonical = normalize(text);
45
+ if (!canonical) return;
46
+ anchors.push({ type, text: text.trim(), canonical, ...extra });
47
+ }
48
+
49
+ for (const line of lines) {
50
+ const fence = line.match(/^```\s*([\w-]*)/);
51
+ if (fence) {
52
+ if (!inFence) {
53
+ inFence = true;
54
+ fenceLang = fence[1] || 'plain';
55
+ fenceLines = [];
56
+ } else {
57
+ const body = fenceLines.join('\n').trim();
58
+ if (body) push('code_fence', body, { language: fenceLang });
59
+ inFence = false;
60
+ fenceLang = '';
61
+ fenceLines = [];
62
+ }
63
+ continue;
64
+ }
65
+
66
+ if (inFence) {
67
+ fenceLines.push(line);
68
+ const sig = line.match(/\b(export\s+)?(async\s+)?(function|class|interface|type|def)\s+[A-Za-z0-9_]+[^;{]*/);
69
+ if (sig) push('api_signature', sig[0]);
70
+ continue;
71
+ }
72
+
73
+ if (/^#{1,4}\s+\S/.test(line)) push('heading', line.replace(/^#{1,4}\s+/, ''));
74
+ if (/\b(v?\d+\.\d+(?:\.\d+)?)\b/.test(line) && /\b(version|v\d|deprecated|removed|migration|upgrade|breaking|changed)\b/i.test(line)) {
75
+ push('version_or_migration_note', line);
76
+ }
77
+ if (/\b(never|must|do not|required|preserve|security|constraint)\b/i.test(line)) {
78
+ push('must_keep_policy', line.replace(/^[-*]\s+/, ''));
79
+ }
80
+ const sig = line.match(/\b(export\s+)?(async\s+)?(function|class|interface|type|def)\s+[A-Za-z0-9_]+[^;{]*/);
81
+ if (sig) push('api_signature', sig[0]);
82
+ }
83
+
84
+ const seen = new Set();
85
+ return anchors.filter((anchor) => {
86
+ const key = `${anchor.type}:${anchor.canonical}`;
87
+ if (seen.has(key)) return false;
88
+ seen.add(key);
89
+ return true;
90
+ });
91
+ }
92
+
93
+ function main() {
94
+ const args = parseArgs(process.argv.slice(2));
95
+ if (args.help) { process.stdout.write(usage()); return; }
96
+ if (!args.original || !args.cleaned) throw new Error(usage().trim());
97
+
98
+ const originalPath = path.resolve(args.original);
99
+ const cleanedPath = path.resolve(args.cleaned);
100
+ const original = fs.readFileSync(originalPath, 'utf8');
101
+ const cleaned = fs.readFileSync(cleanedPath, 'utf8');
102
+ const cleanedCanonical = normalize(cleaned);
103
+ const anchors = extractAnchors(original);
104
+ const preserved = [];
105
+ const missing = [];
106
+
107
+ for (const anchor of anchors) {
108
+ const keep = cleanedCanonical.includes(anchor.canonical);
109
+ const compactAnchor = { type: anchor.type, text: anchor.text };
110
+ if (anchor.language) compactAnchor.language = anchor.language;
111
+ (keep ? preserved : missing).push(compactAnchor);
112
+ }
113
+
114
+ const before = approxTokens(original);
115
+ const after = approxTokens(cleaned);
116
+ const reduction = Number((((before - after) / before) * 100).toFixed(1));
117
+ const receipt = {
118
+ schema: 'pluribus.semantic_anchor_preservation_receipt.v1',
119
+ source_type: 'paste-cleaning-skill-or-cli-output',
120
+ original_ref: path.basename(originalPath),
121
+ cleaned_ref: path.basename(cleanedPath),
122
+ approximate_tokens_before: before,
123
+ approximate_tokens_after: after,
124
+ approximate_reduction_percent: reduction,
125
+ raw_source_logged: false,
126
+ anchor_detection_policy: [
127
+ 'headings',
128
+ 'code_fences',
129
+ 'api_signatures',
130
+ 'version_or_migration_notes',
131
+ 'must_keep_policy_lines'
132
+ ],
133
+ anchors_total: anchors.length,
134
+ anchors_preserved: preserved.length,
135
+ anchors_missing: missing.length,
136
+ preserved_anchors: preserved,
137
+ missing_anchors: missing,
138
+ semantic_loss_check_passed: missing.length === 0,
139
+ token_savings_claim_allowed: missing.length === 0 && after < before
140
+ };
141
+
142
+ const serialized = `${JSON.stringify(receipt, null, 2)}\n`;
143
+ if (args.out) fs.writeFileSync(path.resolve(args.out), serialized);
144
+ process.stdout.write(serialized);
145
+ if (!receipt.semantic_loss_check_passed) process.exitCode = 1;
146
+ }
147
+
148
+ try {
149
+ main();
150
+ } catch (error) {
151
+ console.error(error.message);
152
+ process.exit(1);
153
+ }
@@ -0,0 +1,17 @@
1
+ # Upload API v2 migration notes
2
+
3
+ The `uploadFile` helper changed in v2.4.0. Keep this version note because older snippets still use v1.
4
+
5
+ ```ts
6
+ export async function uploadFile(input: UploadInput): Promise<UploadResult> {
7
+ return client.files.upload(input)
8
+ }
9
+ ```
10
+
11
+ ## Required behavior
12
+
13
+ - Preserve the retry policy: max 3 attempts with exponential backoff.
14
+ - Do not strip the security constraint: never log raw file contents.
15
+ - Deprecated: `uploadLegacy(path)` is removed after v2.5.0.
16
+
17
+ Most verbose examples were removed, but the API signature, version notes, and security constraint survive.
@@ -0,0 +1,19 @@
1
+ # Upload API v2 migration notes
2
+
3
+ The `uploadFile` helper changed in v2.4.0. Keep this version note because older snippets still use v1.
4
+
5
+ ```ts
6
+ export async function uploadFile(input: UploadInput): Promise<UploadResult> {
7
+ return client.files.upload(input)
8
+ }
9
+ ```
10
+
11
+ ## Required behavior
12
+
13
+ - Preserve the retry policy: max 3 attempts with exponential backoff.
14
+ - Do not strip the security constraint: never log raw file contents.
15
+ - Deprecated: `uploadLegacy(path)` is removed after v2.5.0.
16
+
17
+ Most examples below are verbose and can be compressed before pasting into Claude Code once the important anchors are checked.
18
+
19
+ Long example narrative: in staging we saw several users retry uploads manually after a network timeout, then paste screenshots and unrelated logs into the issue. The cleaned context does not need every anecdote, every repeated stack frame, or every copy of the same explanatory paragraph. It only needs enough surrounding language for the agent to understand the migration target after the anchors above are preserved. Remove repeated examples, duplicated support notes, and verbose operational chatter before the paste enters the session.
@@ -0,0 +1,62 @@
1
+ {
2
+ "schema": "pluribus.semantic_anchor_preservation_receipt.v1",
3
+ "source_type": "paste-cleaning-skill-or-cli-output",
4
+ "original_ref": "original-paste.md",
5
+ "cleaned_ref": "cleaned-paste.md",
6
+ "approximate_tokens_before": 224,
7
+ "approximate_tokens_after": 110,
8
+ "approximate_reduction_percent": 50.9,
9
+ "raw_source_logged": false,
10
+ "anchor_detection_policy": [
11
+ "headings",
12
+ "code_fences",
13
+ "api_signatures",
14
+ "version_or_migration_notes",
15
+ "must_keep_policy_lines"
16
+ ],
17
+ "anchors_total": 9,
18
+ "anchors_preserved": 9,
19
+ "anchors_missing": 0,
20
+ "preserved_anchors": [
21
+ {
22
+ "type": "heading",
23
+ "text": "Upload API v2 migration notes"
24
+ },
25
+ {
26
+ "type": "version_or_migration_note",
27
+ "text": "The `uploadFile` helper changed in v2.4.0. Keep this version note because older snippets still use v1."
28
+ },
29
+ {
30
+ "type": "api_signature",
31
+ "text": "export async function uploadFile(input: UploadInput): Promise<UploadResult>"
32
+ },
33
+ {
34
+ "type": "code_fence",
35
+ "text": "export async function uploadFile(input: UploadInput): Promise<UploadResult> {\n return client.files.upload(input)\n}",
36
+ "language": "ts"
37
+ },
38
+ {
39
+ "type": "heading",
40
+ "text": "Required behavior"
41
+ },
42
+ {
43
+ "type": "must_keep_policy",
44
+ "text": "## Required behavior"
45
+ },
46
+ {
47
+ "type": "must_keep_policy",
48
+ "text": "Preserve the retry policy: max 3 attempts with exponential backoff."
49
+ },
50
+ {
51
+ "type": "must_keep_policy",
52
+ "text": "Do not strip the security constraint: never log raw file contents."
53
+ },
54
+ {
55
+ "type": "version_or_migration_note",
56
+ "text": "- Deprecated: `uploadLegacy(path)` is removed after v2.5.0."
57
+ }
58
+ ],
59
+ "missing_anchors": [],
60
+ "semantic_loss_check_passed": true,
61
+ "token_savings_claim_allowed": true
62
+ }
@@ -0,0 +1,25 @@
1
+ # Session preflight receipt example
2
+
3
+ This example is for Cursor/Claude Code/MCP workflows where a project wants a required first step before agent work, such as `session_guard.session_init` or reading `MEMORY.md`.
4
+
5
+ It turns a behavioral instruction into a reviewable artifact:
6
+
7
+ 1. The rule says the agent must initialize context first.
8
+ 2. The receipt records whether the required context was loaded.
9
+ 3. The decision says whether the run may proceed, must stay read-only, or should stop.
10
+
11
+ Copy the sample rule into `.cursor/rules/session-preflight.mdc` and adapt the JSON fields to your local guard/MCP server.
12
+
13
+ ## Try it
14
+
15
+ ```bash
16
+ node -e "const fs=require('fs'); const r=JSON.parse(fs.readFileSync('examples/session-preflight-receipts/session-preflight-receipt.json','utf8')); if (!r.decision.allowed_to_start_work) process.exit(1); console.log(r.schema, r.decision.mode)"
17
+ ```
18
+
19
+ Expected output:
20
+
21
+ ```text
22
+ pluribus.session_preflight_receipt.v1 read_then_patch
23
+ ```
24
+
25
+ This does not enforce Cursor's tool calls by itself. It gives teams a concrete evidence object to ask for when evaluating required-first-tool or pre-tool-hook workflows.
@@ -0,0 +1,39 @@
1
+ {
2
+ "schema": "pluribus.session_preflight_receipt.v1",
3
+ "session_id": "local-2026-06-17T11:00Z",
4
+ "client": "cursor",
5
+ "required_first_step": {
6
+ "kind": "mcp_tool",
7
+ "name": "session_guard.session_init",
8
+ "enforcement": "behavioral_rule_only"
9
+ },
10
+ "required_context": [
11
+ {
12
+ "id": "project-memory",
13
+ "path": "MEMORY.md",
14
+ "status": "loaded",
15
+ "fingerprint": "sha256:replace-with-non-secret-digest"
16
+ },
17
+ {
18
+ "id": "project-rules",
19
+ "path": ".cursor/rules/session-preflight.mdc",
20
+ "status": "loaded",
21
+ "fingerprint": "sha256:replace-with-non-secret-digest"
22
+ }
23
+ ],
24
+ "tool_surface": {
25
+ "mcp_servers_seen": ["session-guard", "playwright"],
26
+ "side_effecting_tools_blocked_until_preflight": ["Shell", "Write"],
27
+ "read_only_tools_allowed_before_preflight": ["Read"]
28
+ },
29
+ "decision": {
30
+ "allowed_to_start_work": true,
31
+ "mode": "read_then_patch",
32
+ "reason": "required project memory and rules were checked before side-effecting tool use"
33
+ },
34
+ "privacy": {
35
+ "raw_context_logged": false,
36
+ "secrets_logged": false,
37
+ "fingerprints_only": true
38
+ }
39
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: Require a visible session preflight before side-effecting agent work.
3
+ alwaysApply: true
4
+ ---
5
+
6
+ Before using Shell, Write, Apply Patch, or external MCP tools, produce a session preflight receipt.
7
+
8
+ The receipt must state:
9
+
10
+ - required first step: `session_guard.session_init` or the local equivalent
11
+ - required context files checked, such as `MEMORY.md`, `AGENTS.md`, `.cursor/rules/*.mdc`, or `CLAUDE.md`
12
+ - whether only read-only tools are allowed before preflight
13
+ - proceed mode: `read_only`, `read_then_patch`, `stop`, or `ask_human`
14
+ - reason for the mode
15
+
16
+ Do not paste raw memory or secrets into the receipt. Use status and non-secret fingerprints instead.
17
+
18
+ If the preflight is missing or required context is unavailable, stay in `read_only` mode and ask for the missing context before edits.
@@ -0,0 +1,60 @@
1
+ # Task-scoped MCP config receipt
2
+
3
+ A tiny demo for the Claude Code / MCP context-bloat complaint: MCP tools can consume context before they are used. One practical workaround is to keep a catalog of MCP server configs and start the agent with a task-specific `--mcp-config` instead of loading every server every time.
4
+
5
+ This example makes that workaround auditable. It produces:
6
+
7
+ 1. a minimal Claude Code-compatible MCP config for one task; and
8
+ 2. a privacy-safe receipt showing which servers were selected, which were withheld, and why.
9
+
10
+ It deliberately does **not** claim adoption. A selected MCP server is only agent-visible; a later tool-adoption receipt would still be needed to prove the agent called it.
11
+
12
+ ## Run it
13
+
14
+ ```bash
15
+ cd examples/task-scoped-mcp-config
16
+ node select-mcp-config.mjs \
17
+ --task tasks/browser-debug.json \
18
+ --out /tmp/browser-debug.mcp.json \
19
+ --receipt /tmp/browser-debug.receipt.json
20
+ ```
21
+
22
+ Use the generated config with Claude Code or a compatible client:
23
+
24
+ ```bash
25
+ claude --mcp-config /tmp/browser-debug.mcp.json
26
+ ```
27
+
28
+ The demo catalog includes five plausible MCP servers. The `browser-debug` task selects only `playwright` and `context7`, withholding memory, observability, and repo-operation servers for the first pass.
29
+
30
+ ## Receipt shape
31
+
32
+ The receipt is intentionally low-cardinality:
33
+
34
+ ```json
35
+ {
36
+ "schema": "pluribus.task_scoped_mcp_config_receipt.v1",
37
+ "task_id": "browser-debug",
38
+ "selected_server_ids": ["playwright", "context7"],
39
+ "withheld_server_ids": ["sentry", "openmemory", "github"],
40
+ "selected_estimated_schema_tokens": 20000,
41
+ "withheld_estimated_schema_tokens": 24000,
42
+ "raw_tool_schemas_logged": false,
43
+ "adoption_claim_allowed": false
44
+ }
45
+ ```
46
+
47
+ Use it to review the initial context surface:
48
+
49
+ - Did this task need every MCP server, or only a small subset?
50
+ - Which server descriptions were kept out of the first context window?
51
+ - Are we accidentally claiming โ€œthe agent used the toolโ€ when we only proved โ€œthe tool was selected into the configโ€?
52
+
53
+ ## Why this exists
54
+
55
+ The market signal is not โ€œMCP is bad.โ€ It is that large tool catalogs need two separate proofs:
56
+
57
+ - **Surface proof:** which tools/servers were made visible for this task?
58
+ - **Adoption proof:** which visible tools were actually called, cited, or used before claims/edits?
59
+
60
+ This demo covers only the first proof. Pair it with a tool-adoption receipt when you need the second.
@@ -0,0 +1,46 @@
1
+ {
2
+ "schema": "pluribus.mcp_catalog.v1",
3
+ "catalogId": "demo-claude-code-mcp-catalog",
4
+ "servers": [
5
+ {
6
+ "id": "playwright",
7
+ "command": "npx",
8
+ "args": ["@playwright/mcp@latest"],
9
+ "category": "browser_automation",
10
+ "estimatedSchemaTokens": 11800,
11
+ "taskTags": ["browser", "web-debug", "e2e"]
12
+ },
13
+ {
14
+ "id": "context7",
15
+ "command": "npx",
16
+ "args": ["@upstash/context7-mcp@latest"],
17
+ "category": "docs_lookup",
18
+ "estimatedSchemaTokens": 8200,
19
+ "taskTags": ["docs", "api-reference"]
20
+ },
21
+ {
22
+ "id": "sentry",
23
+ "command": "npx",
24
+ "args": ["@sentry/mcp-server@latest"],
25
+ "category": "observability",
26
+ "estimatedSchemaTokens": 9700,
27
+ "taskTags": ["errors", "production-debug"]
28
+ },
29
+ {
30
+ "id": "openmemory",
31
+ "command": "npx",
32
+ "args": ["openmemory-mcp@latest"],
33
+ "category": "memory",
34
+ "estimatedSchemaTokens": 6900,
35
+ "taskTags": ["memory", "recall"]
36
+ },
37
+ {
38
+ "id": "github",
39
+ "command": "npx",
40
+ "args": ["@modelcontextprotocol/server-github@latest"],
41
+ "category": "repo_ops",
42
+ "estimatedSchemaTokens": 7400,
43
+ "taskTags": ["issues", "pull-requests", "repo"]
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ const args = new Map();
8
+ for (let i = 2; i < process.argv.length; i += 1) {
9
+ const key = process.argv[i];
10
+ const value = process.argv[i + 1];
11
+ if (!key.startsWith('--') || value === undefined || value.startsWith('--')) {
12
+ throw new Error(`Expected --key value, got ${key}`);
13
+ }
14
+ args.set(key.slice(2), value);
15
+ i += 1;
16
+ }
17
+
18
+ const taskPath = args.has('task') ? resolve(process.cwd(), args.get('task')) : resolve(here, 'tasks/browser-debug.json');
19
+ const catalogPath = args.has('catalog') ? resolve(process.cwd(), args.get('catalog')) : resolve(here, 'mcp-catalog.json');
20
+ const outPath = args.has('out') ? resolve(process.cwd(), args.get('out')) : null;
21
+ const receiptPath = args.has('receipt') ? resolve(process.cwd(), args.get('receipt')) : null;
22
+
23
+ const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
24
+ const task = JSON.parse(readFileSync(taskPath, 'utf8'));
25
+ const servers = new Map(catalog.servers.map((server) => [server.id, server]));
26
+ const missing = task.includeServerIds.filter((id) => !servers.has(id));
27
+ if (missing.length) {
28
+ throw new Error(`Task references unknown server ids: ${missing.join(', ')}`);
29
+ }
30
+
31
+ const selected = task.includeServerIds.map((id) => servers.get(id));
32
+ const withheld = catalog.servers.filter((server) => !task.includeServerIds.includes(server.id));
33
+ const mcpServers = Object.fromEntries(
34
+ selected.map((server) => [
35
+ server.id,
36
+ {
37
+ command: server.command,
38
+ args: server.args,
39
+ },
40
+ ]),
41
+ );
42
+
43
+ const config = { mcpServers };
44
+ const receipt = {
45
+ schema: 'pluribus.task_scoped_mcp_config_receipt.v1',
46
+ task_id: task.taskId,
47
+ catalog_id: catalog.catalogId,
48
+ selected_server_ids: selected.map((server) => server.id),
49
+ withheld_server_ids: withheld.map((server) => server.id),
50
+ selected_estimated_schema_tokens: selected.reduce((sum, server) => sum + server.estimatedSchemaTokens, 0),
51
+ withheld_estimated_schema_tokens: withheld.reduce((sum, server) => sum + server.estimatedSchemaTokens, 0),
52
+ selection_reason: task.description,
53
+ withheld_reason: task.excludeReason,
54
+ raw_tool_schemas_logged: false,
55
+ raw_prompts_logged: false,
56
+ raw_tool_outputs_logged: false,
57
+ adoption_claim_allowed: false,
58
+ note: 'This proves only the task-scoped MCP config surface. It does not prove that the agent later called or adopted the selected tools.',
59
+ };
60
+
61
+ if (outPath) writeFileSync(outPath, `${JSON.stringify(config, null, 2)}\n`);
62
+ if (receiptPath) writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
63
+
64
+ console.log(JSON.stringify({ ok: true, config, receipt }, null, 2));
@@ -0,0 +1,7 @@
1
+ {
2
+ "schema": "pluribus.mcp_task_profile.v1",
3
+ "taskId": "browser-debug",
4
+ "description": "Debug a failing browser flow and look up one library API while keeping unrelated memory/observability/repo tools out of the initial context.",
5
+ "includeServerIds": ["playwright", "context7"],
6
+ "excludeReason": "Not needed for this task's first pass; load a different --mcp-config if the task changes."
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pluribus-context",
3
- "version": "0.3.41",
3
+ "version": "0.3.42",
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",
@@ -12,7 +12,8 @@ const SKILL_USE_RATE_DEMO = 'skill-use-rate'
12
12
  const MCP_AUDIT_RECEIPT_DEMO = 'mcp-audit-receipt'
13
13
  const MCP_TELEMETRY_IMPORT_DEMO = 'mcp-telemetry-import'
14
14
  const TOOL_SURFACE_DIFF_DEMO = 'tool-surface-diff'
15
- const AVAILABLE_DEMOS = [SKILL_USE_RATE_DEMO, MCP_AUDIT_RECEIPT_DEMO, MCP_TELEMETRY_IMPORT_DEMO, TOOL_SURFACE_DIFF_DEMO]
15
+ const CONTEXT_SUFFICIENCY_TRACE_DEMO = 'context-sufficiency-trace'
16
+ const AVAILABLE_DEMOS = [SKILL_USE_RATE_DEMO, MCP_AUDIT_RECEIPT_DEMO, MCP_TELEMETRY_IMPORT_DEMO, TOOL_SURFACE_DIFF_DEMO, CONTEXT_SUFFICIENCY_TRACE_DEMO]
16
17
  const SKILL_USE_RATE_SCHEMA = 'pluribus.skill_use_rate_receipt.v1'
17
18
  const MCP_AUDIT_RECEIPT_SCHEMA = 'pluribus.mcp_tool_call_audit_receipt.v1'
18
19
  const TOOL_SURFACE_DIFF_SCHEMA = 'pluribus.mcp_tool_surface_diff_receipt.v1'
@@ -33,6 +34,8 @@ export async function runDemo(args, positional = []) {
33
34
  return runMcpTelemetryImportDemo(args)
34
35
  case TOOL_SURFACE_DIFF_DEMO:
35
36
  return runToolSurfaceDiffDemo(args)
37
+ case CONTEXT_SUFFICIENCY_TRACE_DEMO:
38
+ return runContextSufficiencyTraceDemo(args)
36
39
  default:
37
40
  console.error(`โŒ Unknown demo: ${demoName}`)
38
41
  console.error(` Available demos: ${AVAILABLE_DEMOS.join(', ')}`)
@@ -196,6 +199,18 @@ function bundledToolSurfaceDiffReceiptPath() {
196
199
  return fileURLToPath(new URL('../../examples/tool-surface-diff-receipts/tool-surface-diff-receipt.json', import.meta.url))
197
200
  }
198
201
 
202
+ function bundledContextSufficiencyGroundTruthPath() {
203
+ return fileURLToPath(new URL('../../examples/context-sufficiency-trace/ground-truth.json', import.meta.url))
204
+ }
205
+
206
+ function bundledContextSufficiencyTracePath() {
207
+ return fileURLToPath(new URL('../../examples/context-sufficiency-trace/context-trace.json', import.meta.url))
208
+ }
209
+
210
+ function bundledContextSufficiencyPassTracePath() {
211
+ return fileURLToPath(new URL('../../examples/context-sufficiency-trace/context-trace-pass.json', import.meta.url))
212
+ }
213
+
199
214
  function runToolSurfaceDiffDemo(args) {
200
215
  const receiptPath = selectedReceiptPath(args, bundledToolSurfaceDiffReceiptPath())
201
216
  const receipt = readReceipt(receiptPath, 'tool-surface diff')
@@ -230,6 +245,71 @@ function runToolSurfaceDiffDemo(args) {
230
245
  if (result.errors.length > 0) process.exit(1)
231
246
  }
232
247
 
248
+ function runContextSufficiencyTraceDemo(args) {
249
+ const truthPath = typeof args.receipt === 'string' && args.receipt.trim()
250
+ ? path.resolve(process.cwd(), args.receipt)
251
+ : bundledContextSufficiencyGroundTruthPath()
252
+ const tracePath = typeof args.input === 'string' && args.input.trim()
253
+ ? path.resolve(process.cwd(), args.input)
254
+ : (Boolean(args.pass) ? bundledContextSufficiencyPassTracePath() : bundledContextSufficiencyTracePath())
255
+
256
+ const truth = readReceipt(truthPath, 'context sufficiency ground-truth')
257
+ const trace = readReceipt(tracePath, 'context trace')
258
+ const result = validateContextSufficiencyTrace(truth, trace)
259
+
260
+ if (Boolean(args.json)) {
261
+ console.log(JSON.stringify({
262
+ ok: result.verdict === 'pass',
263
+ demo: CONTEXT_SUFFICIENCY_TRACE_DEMO,
264
+ groundTruth: path.relative(process.cwd(), truthPath) || truthPath,
265
+ trace: path.relative(process.cwd(), tracePath) || tracePath,
266
+ summary: result,
267
+ }, null, 2))
268
+ } else {
269
+ console.log('๐Ÿงช Pluribus demo: context sufficiency trace')
270
+ console.log(` Ground truth: ${path.relative(process.cwd(), truthPath) || truthPath}`)
271
+ console.log(` Trace: ${path.relative(process.cwd(), tracePath) || tracePath}`)
272
+ console.log('')
273
+
274
+ const mark = result.verdict === 'pass' ? 'โœ…' : 'โŒ'
275
+ console.log(`${mark} context sufficiency ${result.verdict}: gold_context_recall=${result.gold_context_recall}, missed_required_file_rate=${result.missed_required_file_rate}, late_context_rate=${result.late_context_rate}`)
276
+ if (result.missed_required_files.length > 0) console.log(` โ€ข missed_required_files: ${result.missed_required_files.join(', ')}`)
277
+ if (result.frontier_cut_misses.length > 0) console.log(` โ€ข frontier_cut_misses: ${result.frontier_cut_misses.join(', ')}`)
278
+ console.log('')
279
+ console.log('Why this matters: context compression is only safe if the reduced bundle still contains the files/symbols the task ground truth requires before editing starts.')
280
+ console.log('Try your own trace: pluribus demo context-sufficiency-trace --receipt ground-truth.json --input context-trace.json --json')
281
+ }
282
+
283
+ if (result.verdict !== 'pass') process.exit(1)
284
+ }
285
+
286
+ export function validateContextSufficiencyTrace(truth, trace) {
287
+ const required = new Set(Array.isArray(truth.required_files) ? truth.required_files : [])
288
+ const returned = new Set((Array.isArray(trace.returned_files) ? trace.returned_files : []).map((file) => file.path).filter(Boolean))
289
+ const frontierCut = new Set((Array.isArray(trace.frontier_cut) ? trace.frontier_cut : []).map((file) => file.path).filter(Boolean))
290
+ const late = new Set((Array.isArray(trace.late_files) ? trace.late_files : []).map((file) => file.path).filter(Boolean))
291
+
292
+ const requiredList = [...required]
293
+ const returnedRequired = requiredList.filter((filePath) => returned.has(filePath))
294
+ const missedRequired = requiredList.filter((filePath) => !returned.has(filePath))
295
+ const frontierCutMisses = missedRequired.filter((filePath) => frontierCut.has(filePath))
296
+ const lateMisses = missedRequired.filter((filePath) => late.has(filePath))
297
+
298
+ const ratio = (count, total) => (total === 0 ? 0 : Number((count / total).toFixed(4)))
299
+ return {
300
+ task_id: truth.task_id || 'unknown-task',
301
+ trace_id: trace.trace_id || 'unknown-trace',
302
+ required_files: requiredList.length,
303
+ returned_files: returned.size,
304
+ gold_context_recall: ratio(returnedRequired.length, requiredList.length),
305
+ missed_required_file_rate: ratio(missedRequired.length, requiredList.length),
306
+ late_context_rate: ratio(lateMisses.length, requiredList.length),
307
+ missed_required_files: missedRequired,
308
+ frontier_cut_misses: frontierCutMisses,
309
+ verdict: missedRequired.length === 0 ? 'pass' : 'fail',
310
+ }
311
+ }
312
+
233
313
  export function validateSkillUseRateReceipt(receipt) {
234
314
  const errors = []
235
315
  const warnings = []
@@ -1 +1 @@
1
- export const VERSION = '0.3.41'
1
+ export const VERSION = '0.3.42'