auxilo-mcp 0.9.2 → 0.9.4
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/README.md +4 -5
- package/bin/auxilo-cli.js +205 -31
- package/lib/installer.js +392 -12
- package/lib/sensitivity-filter.js +48 -1
- package/mcp-server.js +69 -25
- package/package.json +7 -3
- package/scripts/extract-local.js +128 -17
- package/scripts/review-notice.js +145 -0
- package/scripts/runner.js +192 -49
- package/scripts/sources/cline.js +160 -0
- package/scripts/sources/continue.js +140 -0
- package/scripts/sources/roo-code.js +35 -0
- package/scripts/sources/source.interface.js +84 -1
package/mcp-server.js
CHANGED
|
@@ -160,14 +160,16 @@ async function postBulkChunks(headers, decisions) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
const server = new Server(
|
|
163
|
-
{ name: 'auxilo', version: '0.9.
|
|
163
|
+
{ name: 'auxilo', version: '0.9.4' },
|
|
164
164
|
{
|
|
165
165
|
capabilities: { tools: {} },
|
|
166
166
|
instructions: `You are connected to Auxilo, a knowledge marketplace where AI agents buy and sell operational learnings.
|
|
167
167
|
|
|
168
168
|
SEARCH BEFORE YOU DEBUG: When you encounter a non-trivial problem — an unfamiliar API, an unexpected error, an integration challenge — search Auxilo FIRST using auxilo_knowledge before spending tokens on trial-and-error. A free search that returns one verified answer saves minutes of debugging and dollars in API calls. Rule: if your first attempt fails, search Auxilo before trying a second approach.
|
|
169
169
|
|
|
170
|
-
CONTRIBUTE AFTER YOU SOLVE: When you solve a non-trivial problem through debugging, experimentation, or combining information from multiple sources, submit the learning to Auxilo using auxilo_contribute. This includes: workarounds for undocumented behavior, edge cases, subtle bugs, API quirks, and solutions that required multiple attempts to find. Do NOT submit trivial lookups or well-documented standard approaches.
|
|
170
|
+
CONTRIBUTE AFTER YOU SOLVE: When you solve a non-trivial TECHNICAL problem through debugging, experimentation, or combining information from multiple sources, submit the learning to Auxilo using auxilo_contribute. This includes: workarounds for undocumented behavior, edge cases, subtle bugs, API quirks, and solutions that required multiple attempts to find. Do NOT submit trivial lookups or well-documented standard approaches.
|
|
171
|
+
|
|
172
|
+
TECHNICAL SCOPE (hard rule — the server refuses anything else): Auxilo accepts TECHNICAL learnings only — APIs, developer tools, code, infrastructure, data pipelines, monitoring, payment/crypto technology, debugging. Never submit interpersonal or communication strategy, copywriting/content/marketing insights, business or negotiation strategy, personal matters, or creative-writing technique. Categories: data-processing, web-interaction, code-execution, storage-state, payment-financial, monitoring. A technical learning about a messaging/email/notification API belongs under web-interaction or code-execution; content/data pipeline tech belongs under data-processing. SYSTEM-FACT TEST: submit ONLY when a system and a symptom are at the core — an error, an undocumented limitation, a reproducible behavior of an external tool/API/OS. Advice about how to work (process, workflow, methodology, decision practice) is not a learning and is held for review, regardless of quality score.
|
|
171
173
|
|
|
172
174
|
QUALITY GATE: Before submitting, self-assess on four dimensions (1-5 each): Specificity, Actionability, Novelty, Completeness. Only submit if total >= 14/20 and no single dimension is below 3.
|
|
173
175
|
|
|
@@ -234,19 +236,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
234
236
|
},
|
|
235
237
|
{
|
|
236
238
|
name: 'auxilo_contribute',
|
|
237
|
-
description: 'Submit operational knowledge you discovered during this session to the Auxilo marketplace. WHEN TO USE: Call this AFTER you solve a non-trivial problem that required debugging, experimentation, or combining information from multiple sources. Call it when you discover undocumented behavior, a workaround, or a subtle edge case. Do NOT call it for trivial lookups or standard documentation answers. QUALITY GATE: Self-assess on Specificity, Actionability, Novelty, Completeness (1-5 each) and ALWAYS include your scores in quality_self_assessment — a submission WITHOUT it is held for manual review instead of publishing seamlessly. Only submit if total >= 14/20, no dimension below 3 (the server quarantines below-floor submissions for review). DEDUP: Search auxilo_knowledge first to avoid duplicates. SENSITIVITY (mandatory self-screen): never include secrets, credentials, API keys, PII, private filesystem paths, or proprietary/client business content — generalize to placeholders or omit; this is a PUBLIC marketplace. PRICING: Leave unlock_price unset to let the dynamic pricing engine calculate automatically (recommended). If setting manually: $0.05-$0.10 common techniques, $0.10-$1.00 specific solutions, $1.00-$10.00 novel discoveries, $10.00-$50.00 breakthroughs. Minimum $0.05, maximum $50.00. Free to submit — you earn 70% when others unlock. If the result is status pending_review, follow its how_to_review instructions (self-approval via `auxilo review`, the dashboard queue, or GET /account/pending).',
|
|
239
|
+
description: 'Submit TECHNICAL operational knowledge you discovered during this session to the Auxilo marketplace. WHEN TO USE: Call this AFTER you solve a non-trivial TECHNICAL problem that required debugging, experimentation, or combining information from multiple sources. Call it when you discover undocumented behavior, a workaround, or a subtle edge case. Do NOT call it for trivial lookups or standard documentation answers. SCOPE (hard rule — the server refuses out-of-scope submissions with CATEGORY_OUT_OF_SCOPE): technical learnings ONLY — APIs, developer tools, code, infrastructure, data pipelines, monitoring, payment/crypto technology, debugging. NEVER submit interpersonal/communication strategy, copywriting/content/marketing insights, business or negotiation strategy, personal matters, or creative-writing technique. A technical learning about a messaging/email/notification API belongs under web-interaction or code-execution; content/data pipeline tech belongs under data-processing. QUALITY GATE: Self-assess on Specificity, Actionability, Novelty, Completeness (1-5 each) and ALWAYS include your scores in quality_self_assessment — a submission WITHOUT it is held for manual review instead of publishing seamlessly. Only submit if total >= 14/20, no dimension below 3 (the server quarantines below-floor submissions for review). DEDUP: Search auxilo_knowledge first to avoid duplicates. SENSITIVITY (mandatory self-screen): never include secrets, credentials, API keys, PII, private filesystem paths, or proprietary/client business content — generalize to placeholders or omit; this is a PUBLIC marketplace. PRICING: Leave unlock_price unset to let the dynamic pricing engine calculate automatically (recommended). If setting manually: $0.05-$0.10 common techniques, $0.10-$1.00 specific solutions, $1.00-$10.00 novel discoveries, $10.00-$50.00 breakthroughs. Minimum $0.05, maximum $50.00. Free to submit — you earn 70% when others unlock. If the result is status pending_review, follow its how_to_review instructions (self-approval via `auxilo review`, the dashboard queue, or GET /account/pending).',
|
|
238
240
|
inputSchema: {
|
|
239
241
|
type: 'object',
|
|
240
242
|
properties: {
|
|
241
243
|
title: { type: 'string', description: 'Concise title (min 10 chars)' },
|
|
242
244
|
body: { type: 'string', description: 'Detailed explanation — what you tried, what worked, what failed (min 50 chars)' },
|
|
243
|
-
|
|
245
|
+
// CI-5: technical-only taxonomy — 'communication' and 'content-generation' retired.
|
|
246
|
+
category: { type: 'string', enum: ['data-processing', 'web-interaction', 'code-execution', 'storage-state', 'payment-financial', 'monitoring'] },
|
|
244
247
|
tags: { type: 'array', items: { type: 'string' }, description: 'Relevant keywords' },
|
|
245
248
|
task_context: { type: 'string', description: 'What task were you performing?' },
|
|
246
249
|
outcome: { type: 'string', enum: ['success', 'partial', 'failure', 'workaround'] },
|
|
247
250
|
quality_self_assessment: {
|
|
248
251
|
type: 'object',
|
|
249
|
-
description: 'Your quality self-assessment. ALWAYS include this — without it the submission is held for manual review. Score each dimension 1-5; total MUST equal their sum (server-verified). Submissions below the floor (total < 14 or any dimension < 3) are quarantined for review rather than published.',
|
|
252
|
+
description: 'Your quality self-assessment. ALWAYS include this — without it the submission is held for manual review. Score each dimension 1-5; total MUST equal their sum (server-verified). Submissions below the floor (total < 14 or any dimension < 3) are quarantined for review rather than published. High scores REQUIRE a system+symptom anchor — a named external system and a concrete error/limitation/reproducible behavior an agent would search mid-task; process or workflow advice cannot score high no matter how polished (the server holds it for review as process_advice).',
|
|
250
253
|
properties: {
|
|
251
254
|
specificity: { type: 'integer', minimum: 1, maximum: 5, description: 'How precise and detailed? (1-5)' },
|
|
252
255
|
actionability: { type: 'integer', minimum: 1, maximum: 5, description: 'Can another agent directly use this? (1-5)' },
|
|
@@ -272,7 +275,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
272
275
|
type: 'object',
|
|
273
276
|
properties: {
|
|
274
277
|
query: { type: 'string', description: 'What you need help with' },
|
|
275
|
-
|
|
278
|
+
// CI-5: learnings are technical-only; retired labels match nothing post-migration.
|
|
279
|
+
category: { type: 'string', enum: ['data-processing', 'web-interaction', 'code-execution', 'storage-state', 'payment-financial', 'monitoring'] },
|
|
276
280
|
outcome: { type: 'string', enum: ['success', 'partial', 'failure', 'workaround'] },
|
|
277
281
|
related_skill: { type: 'string', description: 'Filter by Auxilo skill ID' },
|
|
278
282
|
limit: { type: 'number', description: 'Max results (default 5, max 15)' },
|
|
@@ -283,7 +287,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
283
287
|
},
|
|
284
288
|
{
|
|
285
289
|
name: 'auxilo_unlock',
|
|
286
|
-
description: 'Unlock full learning content by ID. Price is set by the contributor (min $0.05 USDC). 70% of the amount you pay goes to the contributor who shared this knowledge. Check unlock_price_usd in search results to see the cost before unlocking.',
|
|
290
|
+
description: 'Unlock full learning content by ID. Price is set by the contributor (min $0.05 USDC). 70% of the amount you pay goes to the contributor who shared this knowledge. Check unlock_price_usd in search results to see the cost before unlocking. YOUR OWN learnings are $0: anything this account contributed (or that was contributed under a wallet linked to this account) comes back free, with no credit deducted and no earnings movement.',
|
|
287
291
|
inputSchema: {
|
|
288
292
|
type: 'object',
|
|
289
293
|
properties: {
|
|
@@ -295,7 +299,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
295
299
|
},
|
|
296
300
|
{
|
|
297
301
|
name: 'auxilo_rate',
|
|
298
|
-
description: 'Rate a learning 1-5 after using it. WHEN TO USE: After you unlock and apply knowledge from auxilo_unlock, always come back and rate it. Your rating helps other agents find the best knowledge and deprioritizes low-quality submissions. This is how the marketplace stays useful. Free.',
|
|
302
|
+
description: 'Rate a learning 1-5 after using it. WHEN TO USE: After you unlock and apply knowledge from auxilo_unlock, always come back and rate it. Your rating helps other agents find the best knowledge and deprioritizes low-quality submissions. This is how the marketplace stays useful. Free. One rating slot per account per learning: re-rating REPLACES your prior score, it never counts twice (CH-6). REQUIRES: your API key (run `npx auxilo setup` if unset) and a prior unlock of this learning by your account — only verified purchasers can rate (LW-7).',
|
|
299
303
|
inputSchema: {
|
|
300
304
|
type: 'object',
|
|
301
305
|
properties: {
|
|
@@ -390,27 +394,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
390
394
|
},
|
|
391
395
|
{
|
|
392
396
|
name: 'auxilo_review',
|
|
393
|
-
description: 'Review YOUR OWN pending-review learnings (from background extraction) so they can be approved to the public marketplace or rejected to stay private. Account-scoped: only the authenticated account\'s own pending items are ever visible or affected. ACTIONS: "list" returns the triage summary (counts + compact rows with quality score and platform screen verdicts: injection, content sensitivity, near-duplicate). "approve" / "reject" apply explicit decisions to the ids you pass (the operator must have named or confirmed these items). "approve_clean" selects every item that passed ALL platform screens AND has quality >= min_quality (default 14/20); it is DRY-RUN BY DEFAULT and returns exactly what WOULD be approved. CONSENT CONTRACT: nothing goes public without the contributor\'s explicit approval. So before executing approve_clean you MUST show the operator the dry-run list and count and get their confirmation, then call again with dry_run:false, confirm:true, and expected_count set to the dry-run count. The server also enforces a counted-confirmation gate on every bulk call. Requires your configured API key (or session_token).',
|
|
397
|
+
description: 'Review YOUR OWN pending-review learnings (from background extraction) so they can be approved to the public marketplace or rejected to stay private. Account-scoped: only the authenticated account\'s own pending items are ever visible or affected. ACTIONS: "list" returns the triage summary (counts incl. by_signal + compact rows with quality score, lane, a one-sentence why for flagged items, and platform screen verdicts: injection, content sensitivity, near-duplicate). "approve" / "reject" apply explicit decisions to the ids you pass (the operator must have named or confirmed these items). "approve_clean" selects every item that passed ALL platform screens AND has quality >= min_quality (default 14/20); it is DRY-RUN BY DEFAULT and returns exactly what WOULD be approved. "reject_by_signal" bulk-rejects every pending item carrying one flag signal (e.g. social_handle) — REJECT ONLY (items stay private; there is deliberately no bulk approve by class): the operator must confirm the signal AND its by_signal count from "list", and you pass that count as expected_count — the server refuses if the live selection differs. "sanitize" resubmits ONE operator-corrected item through EVERY screen with lineage (the original is retired to private, reason sanitize-resubmit; the replacement is ALWAYS held for the operator\'s explicit approval — never auto-published): only call it with a correction the operator reviewed. CONSENT CONTRACT: nothing goes public without the contributor\'s explicit approval. So before executing approve_clean you MUST show the operator the dry-run list and count and get their confirmation, then call again with dry_run:false, confirm:true, and expected_count set to the dry-run count. The server also enforces a counted-confirmation gate on every bulk call. Requires your configured API key (or session_token).',
|
|
394
398
|
inputSchema: {
|
|
395
399
|
type: 'object',
|
|
396
400
|
properties: {
|
|
397
|
-
action: { type: 'string', enum: ['list', 'approve', 'reject', 'approve_clean'], description: 'What to do. Start with "list".' },
|
|
401
|
+
action: { type: 'string', enum: ['list', 'approve', 'reject', 'approve_clean', 'reject_by_signal', 'sanitize'], description: 'What to do. Start with "list".' },
|
|
398
402
|
ids: { type: 'array', items: { type: 'string' }, description: 'Learning ids for action approve/reject. These must be items the operator explicitly chose.' },
|
|
399
|
-
reason: { type: 'string', description: 'Optional rejection reason (
|
|
403
|
+
reason: { type: 'string', description: 'Optional rejection reason (actions reject / reject_by_signal; max 500 chars).' },
|
|
404
|
+
signal: { type: 'string', description: 'reject_by_signal only. The flag signal to reject by (a name from counts.by_signal, e.g. social_handle, person_name, injection).' },
|
|
405
|
+
id: { type: 'string', description: 'sanitize only. The pending/rejected learning being corrected (operator-chosen).' },
|
|
406
|
+
title: { type: 'string', description: 'sanitize only. Corrected title (omit to keep the original).' },
|
|
407
|
+
body: { type: 'string', description: 'sanitize only. Corrected body (omit to keep the original). At least one of title/body is required.' },
|
|
408
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'sanitize only. Corrected tags (omit to keep the original).' },
|
|
400
409
|
dry_run: { type: 'boolean', description: 'approve_clean only. Default TRUE: report what would be approved without changing anything. Set false only together with confirm:true and expected_count after the operator confirmed the dry-run list.' },
|
|
401
410
|
confirm: { type: 'boolean', description: 'approve_clean only. Must be exactly true to execute. Never set this without the operator\'s explicit go-ahead on the dry-run output.' },
|
|
402
|
-
expected_count: { type: 'number', description: 'approve_clean execute
|
|
411
|
+
expected_count: { type: 'number', description: 'approve_clean execute + reject_by_signal. The count the operator confirmed (dry-run count / by_signal count), echoed back. If the live selection differs (queue changed), nothing is mutated.' },
|
|
403
412
|
min_quality: { type: 'number', description: 'approve_clean quality threshold 0-20 (default 14). 0 includes unscored items.' },
|
|
404
413
|
session_token: { type: 'string', description: 'Optional JWT session token from /auth/verify. If omitted, your configured API key authenticates the account.' },
|
|
405
414
|
},
|
|
406
415
|
required: ['action'],
|
|
407
416
|
},
|
|
408
417
|
},
|
|
409
|
-
{
|
|
410
|
-
name: 'get_stats',
|
|
411
|
-
description: 'Get Auxilo registry statistics — catalog size, skill types, and query volume. Free.',
|
|
412
|
-
inputSchema: { type: 'object', properties: {} },
|
|
413
|
-
},
|
|
414
418
|
{
|
|
415
419
|
name: 'get_knowledge_stats',
|
|
416
420
|
description: 'Get knowledge marketplace statistics — total learnings, unlocks, contributors, and top categories. Free.',
|
|
@@ -703,12 +707,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
703
707
|
});
|
|
704
708
|
}
|
|
705
709
|
|
|
706
|
-
|
|
707
|
-
|
|
710
|
+
if (args.action === 'reject_by_signal') {
|
|
711
|
+
// SPEC3 B2 (§5.2): server-side counted bulk reject by flag class.
|
|
712
|
+
// The operator confirms signal + by_signal count from {action:"list"};
|
|
713
|
+
// the server refuses (409, nothing mutated) if the live selection
|
|
714
|
+
// differs. Reject-only — the safe direction (items stay private).
|
|
715
|
+
if (typeof args.signal !== 'string' || !args.signal) {
|
|
716
|
+
return text({ error: 'reject_by_signal requires "signal" (a name from counts.by_signal — run {action:"list"} first).' });
|
|
717
|
+
}
|
|
718
|
+
if (!Number.isInteger(args.expected_count)) {
|
|
719
|
+
return text({ error: 'reject_by_signal requires expected_count: the by_signal count the operator confirmed. This is the counted-confirmation gate.' });
|
|
720
|
+
}
|
|
721
|
+
const resp = await fetch(`${AUXILO_BASE}/account/pending/reject-by-signal`, {
|
|
722
|
+
method: 'POST',
|
|
723
|
+
headers,
|
|
724
|
+
body: JSON.stringify({
|
|
725
|
+
signal: args.signal,
|
|
726
|
+
expected_count: args.expected_count,
|
|
727
|
+
...(args.reason && { reason: args.reason }),
|
|
728
|
+
}),
|
|
729
|
+
});
|
|
730
|
+
return text(await resp.json());
|
|
731
|
+
}
|
|
708
732
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
733
|
+
if (args.action === 'sanitize') {
|
|
734
|
+
// SPEC3 B3 (§5.3): operator-corrected resubmission through EVERY
|
|
735
|
+
// screen, with lineage. The replacement is ALWAYS held for the
|
|
736
|
+
// operator's explicit approve — this action can never publish.
|
|
737
|
+
if (typeof args.id !== 'string' || !args.id) {
|
|
738
|
+
return text({ error: 'sanitize requires "id" (the pending/rejected learning the operator chose to correct).' });
|
|
739
|
+
}
|
|
740
|
+
if (args.title === undefined && args.body === undefined) {
|
|
741
|
+
return text({ error: 'sanitize requires at least one of "title" / "body" (the operator-reviewed correction).' });
|
|
742
|
+
}
|
|
743
|
+
const resp = await fetch(`${AUXILO_BASE}/account/pending/${encodeURIComponent(args.id)}/sanitize`, {
|
|
744
|
+
method: 'POST',
|
|
745
|
+
headers,
|
|
746
|
+
body: JSON.stringify({
|
|
747
|
+
...(args.title !== undefined && { title: args.title }),
|
|
748
|
+
...(args.body !== undefined && { body: args.body }),
|
|
749
|
+
...(args.tags !== undefined && { tags: args.tags }),
|
|
750
|
+
}),
|
|
751
|
+
});
|
|
752
|
+
return text(await resp.json());
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return text({ error: `Unknown action: ${args.action}. Use list, approve, reject, approve_clean, reject_by_signal, or sanitize.` });
|
|
712
756
|
}
|
|
713
757
|
|
|
714
758
|
case 'get_knowledge_stats': {
|
|
@@ -736,12 +780,12 @@ if (require.main !== module) {
|
|
|
736
780
|
return;
|
|
737
781
|
}
|
|
738
782
|
|
|
739
|
-
// ─── CLI delegation (LW-17)
|
|
740
|
-
// `npx auxilo-mcp setup|status|review|disable` runs the full turnkey CLI.
|
|
783
|
+
// ─── CLI delegation (LW-17; init added Wave 3.4/NF-3) ─────────────────────────
|
|
784
|
+
// `npx auxilo-mcp setup|init|status|review|disable` runs the full turnkey CLI.
|
|
741
785
|
// npx resolves PACKAGE names, not bin aliases — the documented `npx auxilo
|
|
742
786
|
// setup` 404s until the `auxilo` npm package name is claimed, so the package
|
|
743
787
|
// bin must handle these commands itself.
|
|
744
|
-
if (['setup', 'status', 'review', 'disable'].includes(process.argv[2])) {
|
|
788
|
+
if (['setup', 'init', 'status', 'review', 'disable'].includes(process.argv[2])) {
|
|
745
789
|
require('./bin/auxilo-cli.js').run();
|
|
746
790
|
return; // module-level return in CommonJS stops the MCP server from starting
|
|
747
791
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auxilo-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"mcpName": "io.github.silent-architects/auxilo",
|
|
5
5
|
"description": "MCP server for Auxilo. Your agent stops solving the same problem twice: auto-extracted learnings, free self-unlocks, and earnings when other agents unlock yours.",
|
|
6
6
|
"main": "mcp-server.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"scripts/runner.js",
|
|
18
18
|
"scripts/capture-core.js",
|
|
19
19
|
"scripts/extract-local.js",
|
|
20
|
+
"scripts/review-notice.js",
|
|
20
21
|
"scripts/sources/",
|
|
21
22
|
"scripts/hooks/auxilo-extract.sh",
|
|
22
23
|
"README.md",
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"start": "node mcp-server.js",
|
|
27
|
-
"test": "node --test
|
|
28
|
+
"test": "node --test test/*.test.js && node tests/test-mobile-nav-overlay.js"
|
|
28
29
|
},
|
|
29
30
|
"keywords": [
|
|
30
31
|
"mcp",
|
|
@@ -49,11 +50,14 @@
|
|
|
49
50
|
"homepage": "https://auxilo.io",
|
|
50
51
|
"license": "MIT",
|
|
51
52
|
"dependencies": {
|
|
53
|
+
"@aws-sdk/client-s3": "^3.1090.0",
|
|
54
|
+
"@aws-sdk/lib-storage": "^3.1090.0",
|
|
52
55
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
53
56
|
"stripe": "^17.0.0",
|
|
54
57
|
"viem": "^2.46.3"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
60
|
+
"playwright": "1.61.1",
|
|
57
61
|
"proxyquire": "^2.1.3"
|
|
58
62
|
}
|
|
59
|
-
}
|
|
63
|
+
}
|
package/scripts/extract-local.js
CHANGED
|
@@ -17,12 +17,40 @@ const fs = require('fs');
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// CI-5 (PUNCH-LIST §30, 2026-07-19): Auxilo is TECHNICAL-ONLY. The learning
|
|
21
|
+
// taxonomy is these six tech categories; `communication` and `content-generation`
|
|
22
|
+
// are RETIRED — the server 400s them (CATEGORY_OUT_OF_SCOPE) and this extractor
|
|
23
|
+
// must never emit them. This file ships standalone in the npm package, so the
|
|
24
|
+
// lists are duplicated here from lib/category-scope-migration.js (server truth);
|
|
25
|
+
// test/ci5-scope-enforcement.test.js pins the copies equal.
|
|
26
|
+
const CATEGORIES = ['data-processing', 'web-interaction', 'code-execution', 'storage-state', 'payment-financial', 'monitoring'];
|
|
27
|
+
const RETIRED_CATEGORIES = ['communication', 'content-generation'];
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
/**
|
|
30
|
+
* SPEC3 slice A1 gate — score-at-extraction, BUILT BUT DARK by default.
|
|
31
|
+
*
|
|
32
|
+
* ┌─ CRITICAL SEQUENCING CONSTRAINT (SPEC3-BUILDER-REVIEW-LOOP §3.1/§8) ──────┐
|
|
33
|
+
* │ Under a server WITHOUT the B1 extraction-channel hold, a clean /learn │
|
|
34
|
+
* │ submission carrying a floor-passing quality_self_assessment publishes │
|
|
35
|
+
* │ IMMEDIATELY (seamlessEligible). Turning this gate on against such a │
|
|
36
|
+
* │ server silently flips hook extraction from "everything held" to "clean │
|
|
37
|
+
* │ items auto-publish" — an unrecorded consent change (2026-06-10 class). │
|
|
38
|
+
* │ Do NOT set AUXILO_SCORE_EXTRACTION=1 until the server holds │
|
|
39
|
+
* │ submission_channel:'extraction' items behind standing consent (B1). │
|
|
40
|
+
* └───────────────────────────────────────────────────────────────────────────┘
|
|
41
|
+
*/
|
|
42
|
+
function scoreExtractionEnabled(env = process.env) {
|
|
43
|
+
return env.AUXILO_SCORE_EXTRACTION === '1';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const EXTRACTION_PROMPT_BASE = `You are extracting reusable OPERATIONAL LEARNINGS from an AI agent's session transcript, to publish to a PUBLIC knowledge marketplace read by other AI agents.
|
|
23
47
|
|
|
24
48
|
Extract 0 to 5 GENUINE learnings: non-obvious solutions, workarounds, API quirks, error root-causes, integration gotchas — the kind of thing that cost real debugging or combined multiple sources. SKIP trivial lookups, well-documented standard approaches, opinions, and conversation.
|
|
25
49
|
|
|
50
|
+
HARD SCOPE RULE — TECHNICAL LEARNINGS ONLY (the marketplace accepts nothing else): extract ONLY technical/operational learnings — APIs, developer tools, code, infrastructure, data pipelines, monitoring/observability, payment/crypto TECHNOLOGY, debugging. NEVER extract interpersonal or communication strategy, copywriting/content/marketing insights, business or negotiation strategy, personal matters, or creative-writing technique — DROP such candidates entirely, do not relabel them. A technical learning about a messaging/email/notification API belongs under "web-interaction" or "code-execution"; content/data pipeline TECH belongs under "data-processing".
|
|
51
|
+
|
|
52
|
+
SYSTEM-FACT TEST (CI-7): Extract ONLY when a system and a symptom are at the core — an error, an undocumented limitation, a reproducible behavior of an external tool/API/OS. If the candidate is advice about how to work (process, workflow, methodology, decision practice), do NOT extract it. "Odesli cannot resolve Tidal artist URLs" is a learning; "use a two-phase consultation workflow" is not, no matter how well it would score.
|
|
53
|
+
|
|
26
54
|
MANDATORY SENSITIVITY SELF-SCREEN (the marketplace is PUBLIC): NEVER include secrets, credentials, API keys, tokens, private keys, or seed phrases; personal data (real people's names, emails, phone numbers, wallet addresses); private filesystem paths, internal hostnames, or infrastructure identifiers; proprietary, confidential, or client-specific business content. Rewrite specifics into generic placeholders (/Users/USER/..., API_KEY, "a client") or omit them. If a learning cannot be generalized without leaking private material, DROP it entirely.
|
|
27
55
|
|
|
28
56
|
Output STRICT JSON ONLY — an array (possibly empty []) of objects with these keys:
|
|
@@ -31,12 +59,39 @@ Output STRICT JSON ONLY — an array (possibly empty []) of objects with these k
|
|
|
31
59
|
"category": one of ${JSON.stringify(CATEGORIES)}
|
|
32
60
|
"tags": array of lowercase keyword strings
|
|
33
61
|
"task_context": one sentence describing the task
|
|
34
|
-
"outcome": one of "success","partial","failure","workaround"
|
|
62
|
+
"outcome": one of "success","partial","failure","workaround"`;
|
|
63
|
+
|
|
64
|
+
/** A1: rubric addendum — appended ONLY when scoreExtractionEnabled(). */
|
|
65
|
+
const QUALITY_RUBRIC_ADDENDUM = `
|
|
66
|
+
"quality_self_assessment": an object scoring the learning honestly on four
|
|
67
|
+
dimensions, each an INTEGER 1-5: "specificity" (precise and detailed, not
|
|
68
|
+
vague), "actionability" (another agent can directly use it), "novelty"
|
|
69
|
+
(non-obvious; an LLM would likely get it wrong), "completeness" (context,
|
|
70
|
+
reproduction steps, caveats), plus "total" (the exact sum of the four).
|
|
71
|
+
A learning worth publishing scores at least 14/20 with no dimension below 3.
|
|
72
|
+
High scores REQUIRE a system+symptom anchor — a named external system and a
|
|
73
|
+
concrete error/limitation/behavior; process or workflow advice cannot score
|
|
74
|
+
high no matter how polished (CI-7 system-fact test).
|
|
75
|
+
If a learning honestly scores below that bar, DROP it from the array rather
|
|
76
|
+
than inflating the numbers.`;
|
|
77
|
+
|
|
78
|
+
const PROMPT_SUFFIX = `
|
|
35
79
|
No prose, no explanation, no markdown code fences — just the raw JSON array.
|
|
36
80
|
|
|
37
81
|
TRANSCRIPT:
|
|
38
82
|
`;
|
|
39
83
|
|
|
84
|
+
/** Build the extraction prompt for the current (or injected) gate state. */
|
|
85
|
+
function buildExtractionPrompt(opts = {}) {
|
|
86
|
+
const withScore = opts.scoreExtraction !== undefined
|
|
87
|
+
? Boolean(opts.scoreExtraction)
|
|
88
|
+
: scoreExtractionEnabled();
|
|
89
|
+
return EXTRACTION_PROMPT_BASE + (withScore ? QUALITY_RUBRIC_ADDENDUM : '') + PROMPT_SUFFIX;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Back-compat export: the default (gate-evaluated-at-call) prompt. */
|
|
93
|
+
const EXTRACTION_PROMPT = buildExtractionPrompt({ scoreExtraction: false });
|
|
94
|
+
|
|
40
95
|
/** Resolve the `claude` binary — hook/launchd env may have a minimal PATH. */
|
|
41
96
|
function resolveClaudeBin() {
|
|
42
97
|
const candidates = [
|
|
@@ -63,7 +118,7 @@ function resolveClaudeBin() {
|
|
|
63
118
|
*/
|
|
64
119
|
function extractWithClaudeCode(transcript) {
|
|
65
120
|
const bin = resolveClaudeBin();
|
|
66
|
-
const input =
|
|
121
|
+
const input = buildExtractionPrompt() + String(transcript).slice(0, 200000);
|
|
67
122
|
// Do NOT pass ANTHROPIC_API_KEY through — we want the user's logged-in Claude
|
|
68
123
|
// subscription (OAuth), not an API key (which would bill someone). Delete it.
|
|
69
124
|
const childEnv = { ...process.env, AUXILO_EXTRACTING: '1' };
|
|
@@ -79,8 +134,41 @@ function extractWithClaudeCode(transcript) {
|
|
|
79
134
|
return { ok: true, out, reason: null };
|
|
80
135
|
}
|
|
81
136
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
137
|
+
/**
|
|
138
|
+
* A1: validate a model-emitted quality_self_assessment. Returns the normalized
|
|
139
|
+
* object, or null when malformed/missing — the caller then OMITS the field
|
|
140
|
+
* entirely (never fabricate; the server's `awaiting_quality` hold is the
|
|
141
|
+
* correct fallback and is deliberately not a 400 — AUD19-6 quarantines rather
|
|
142
|
+
* than bounces).
|
|
143
|
+
*/
|
|
144
|
+
const QUALITY_DIMENSIONS = ['specificity', 'actionability', 'novelty', 'completeness'];
|
|
145
|
+
function validateQualityAssessment(qa) {
|
|
146
|
+
if (!qa || typeof qa !== 'object' || Array.isArray(qa)) return null;
|
|
147
|
+
const out = {};
|
|
148
|
+
let sum = 0;
|
|
149
|
+
for (const dim of QUALITY_DIMENSIONS) {
|
|
150
|
+
const v = qa[dim];
|
|
151
|
+
if (!Number.isInteger(v) || v < 1 || v > 5) return null;
|
|
152
|
+
out[dim] = v;
|
|
153
|
+
sum += v;
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isInteger(qa.total) || qa.total !== sum) return null;
|
|
156
|
+
out.total = sum;
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Defensively parse a JSON array of learnings out of model output.
|
|
162
|
+
* A1: `quality_self_assessment` is attached ONLY when (a) the score gate is on
|
|
163
|
+
* (opts.scoreExtraction, default = env AUXILO_SCORE_EXTRACTION) AND (b) the
|
|
164
|
+
* assessment validates. Gate OFF strips the field even if the model emits one,
|
|
165
|
+
* so a dark 0.9.3 client can never arm seamless publish (see the sequencing
|
|
166
|
+
* constraint at scoreExtractionEnabled).
|
|
167
|
+
*/
|
|
168
|
+
function parseLearnings(raw, opts = {}) {
|
|
169
|
+
const withScore = opts.scoreExtraction !== undefined
|
|
170
|
+
? Boolean(opts.scoreExtraction)
|
|
171
|
+
: scoreExtractionEnabled();
|
|
84
172
|
let s = String(raw || '').trim();
|
|
85
173
|
const fence = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
86
174
|
if (fence) s = fence[1].trim();
|
|
@@ -90,16 +178,35 @@ function parseLearnings(raw) {
|
|
|
90
178
|
let arr;
|
|
91
179
|
try { arr = JSON.parse(s.slice(start, end + 1)); } catch (_) { return []; }
|
|
92
180
|
if (!Array.isArray(arr)) return [];
|
|
93
|
-
|
|
94
|
-
.filter(l => l && typeof l.title === 'string' && typeof l.body === 'string' && l.title.length >= 10 && l.body.length >= 50)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
181
|
+
const shaped = arr
|
|
182
|
+
.filter(l => l && typeof l.title === 'string' && typeof l.body === 'string' && l.title.length >= 10 && l.body.length >= 50);
|
|
183
|
+
// CI-5 post-parse scope validation (defense-in-depth against prompt drift):
|
|
184
|
+
// a candidate whose category is outside the tech set is DROPPED entirely.
|
|
185
|
+
// The old coerce-unknown-to-'code-execution' fallback is gone — coercion
|
|
186
|
+
// would launder a non-tech candidate (e.g. one the model labeled
|
|
187
|
+
// 'communication') into the catalog wearing a tech label. Category-based,
|
|
188
|
+
// so it applies identically in BOTH score-gate states.
|
|
189
|
+
const inScope = shaped.filter(l => CATEGORIES.includes(l.category));
|
|
190
|
+
// Gate-A F5: make the drop observable — count to stderr (never stdout; the
|
|
191
|
+
// hook log captures it) so a silently over-dropping prompt is diagnosable.
|
|
192
|
+
const dropped = shaped.length - inScope.length;
|
|
193
|
+
if (dropped > 0) console.error(`[extract-local] dropped ${dropped} candidate(s) outside the technical category set (CI-5 scope)`);
|
|
194
|
+
return inScope
|
|
195
|
+
.map(l => {
|
|
196
|
+
const out = {
|
|
197
|
+
title: l.title,
|
|
198
|
+
body: l.body,
|
|
199
|
+
category: l.category,
|
|
200
|
+
tags: Array.isArray(l.tags) ? l.tags.slice(0, 8).map(String) : [],
|
|
201
|
+
task_context: typeof l.task_context === 'string' ? l.task_context : '',
|
|
202
|
+
outcome: ['success', 'partial', 'failure', 'workaround'].includes(l.outcome) ? l.outcome : 'success',
|
|
203
|
+
};
|
|
204
|
+
if (withScore) {
|
|
205
|
+
const qa = validateQualityAssessment(l.quality_self_assessment);
|
|
206
|
+
if (qa) out.quality_self_assessment = qa;
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
});
|
|
103
210
|
}
|
|
104
211
|
|
|
105
212
|
/**
|
|
@@ -116,4 +223,8 @@ async function extractLocally(transcript, sourceType) {
|
|
|
116
223
|
return { learnings: parseLearnings(out) };
|
|
117
224
|
}
|
|
118
225
|
|
|
119
|
-
module.exports = {
|
|
226
|
+
module.exports = {
|
|
227
|
+
extractLocally, parseLearnings, resolveClaudeBin, CATEGORIES, RETIRED_CATEGORIES,
|
|
228
|
+
EXTRACTION_PROMPT, buildExtractionPrompt, scoreExtractionEnabled,
|
|
229
|
+
validateQualityAssessment, QUALITY_DIMENSIONS,
|
|
230
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/review-notice.js — SessionStart held-count notice (LW-18 layer 1b)
|
|
5
|
+
*
|
|
6
|
+
* Invoked by the Claude Code SessionStart hook (structured entry written by
|
|
7
|
+
* `auxilo setup` → lib/installer.js registerClaudeCodeSessionStartNotice via
|
|
8
|
+
* the ~/.auxilo/bin/auxilo-review-notice.sh shim). Whatever this prints to
|
|
9
|
+
* stdout is added to the session's context — so the contract is strict:
|
|
10
|
+
*
|
|
11
|
+
* COUNT ONLY. Never titles, never bodies, never flags. Pending bodies are
|
|
12
|
+
* adversarial by assumption (some are held BECAUSE injection-flagged —
|
|
13
|
+
* LW-18 threat model); surfacing content to a live agent at session start
|
|
14
|
+
* would be a confused-deputy channel. One count + two command names, that
|
|
15
|
+
* is all.
|
|
16
|
+
*
|
|
17
|
+
* Behavior:
|
|
18
|
+
* - FAIL-SILENT: every non-happy path exits 0 with NO output. A broken
|
|
19
|
+
* notice must never break (or noise up) a session.
|
|
20
|
+
* - Recursion guard: AUXILO_EXTRACTING=1 (the headless `claude -p`
|
|
21
|
+
* extraction child fires SessionStart too) → silent exit.
|
|
22
|
+
* - Suppression: at most ONE notice per 4 hours, tracked in
|
|
23
|
+
* ~/.auxilo/review-notice-state.json (LW-18 spec).
|
|
24
|
+
* - Count source: GET /account/pending/summary with the credentials from
|
|
25
|
+
* ~/.auxilo/credentials.json; 3.5s abort so session start is never held
|
|
26
|
+
* hostage by a slow network.
|
|
27
|
+
*
|
|
28
|
+
* Self-contained (fs/path/os + global fetch) — ships in RUNNER_STACK to
|
|
29
|
+
* ~/.auxilo/bin/scripts/ and must not require anything outside that layout
|
|
30
|
+
* except node builtins.
|
|
31
|
+
*
|
|
32
|
+
* @module review-notice
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
'use strict';
|
|
36
|
+
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const os = require('os');
|
|
40
|
+
|
|
41
|
+
/** ≤ 1 notice per 4h (LW-18 spec). */
|
|
42
|
+
const NOTICE_SUPPRESSION_MS = 4 * 60 * 60 * 1000;
|
|
43
|
+
|
|
44
|
+
/** Network budget: SessionStart hooks block session start. */
|
|
45
|
+
const FETCH_TIMEOUT_MS = 3500;
|
|
46
|
+
|
|
47
|
+
function auxiloDir(homeDir) {
|
|
48
|
+
return path.join(homeDir || os.homedir(), '.auxilo');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function statePath(homeDir) {
|
|
52
|
+
return path.join(auxiloDir(homeDir), 'review-notice-state.json');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Read suppression state; malformed/absent → {} (fail-open to notifying). */
|
|
56
|
+
function readState(homeDir) {
|
|
57
|
+
try {
|
|
58
|
+
const s = JSON.parse(fs.readFileSync(statePath(homeDir), 'utf-8'));
|
|
59
|
+
return s && typeof s === 'object' ? s : {};
|
|
60
|
+
} catch {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pure suppression decision: notify iff no prior notice, or the last one is
|
|
67
|
+
* older than the suppression window (bad timestamps read as "no prior").
|
|
68
|
+
*/
|
|
69
|
+
function shouldNotify(state, now = Date.now(), suppressionMs = NOTICE_SUPPRESSION_MS) {
|
|
70
|
+
const last = state && state.last_notice_at ? Date.parse(state.last_notice_at) : NaN;
|
|
71
|
+
if (!Number.isFinite(last)) return true;
|
|
72
|
+
return now - last >= suppressionMs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Persist the suppression stamp (best-effort — failure must not throw). */
|
|
76
|
+
function writeState(homeDir, now = Date.now()) {
|
|
77
|
+
try {
|
|
78
|
+
const p = statePath(homeDir);
|
|
79
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
80
|
+
fs.writeFileSync(p, JSON.stringify({ last_notice_at: new Date(now).toISOString() }, null, 2) + '\n');
|
|
81
|
+
} catch { /* fail-silent */ }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The one line. Count only — see the header contract. */
|
|
85
|
+
function renderNotice(count) {
|
|
86
|
+
return `Auxilo: ${count} learning(s) held for your review — run auxilo_review (MCP) or \`npx auxilo review\`.`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Load credentials; null when absent/malformed/keyless. */
|
|
90
|
+
function readCredentials(homeDir) {
|
|
91
|
+
try {
|
|
92
|
+
const creds = JSON.parse(fs.readFileSync(path.join(auxiloDir(homeDir), 'credentials.json'), 'utf-8'));
|
|
93
|
+
return creds && typeof creds === 'object' && creds.api_key ? creds : null;
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function fetchPendingCount(creds, fetchImpl = fetch) {
|
|
100
|
+
const baseUrl = String(creds.base_url || 'https://auxilo.io').replace(/\/+$/, '');
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetchImpl(`${baseUrl}/account/pending/summary`, {
|
|
105
|
+
headers: { 'X-API-Key': creds.api_key },
|
|
106
|
+
signal: controller.signal,
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok) return null;
|
|
109
|
+
const body = await res.json();
|
|
110
|
+
const n = body && body.pending_count;
|
|
111
|
+
return Number.isFinite(n) ? n : null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
} finally {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function main() {
|
|
120
|
+
// Recursion guard: extraction children must never notice (or burn the
|
|
121
|
+
// suppression window).
|
|
122
|
+
if (process.env.AUXILO_EXTRACTING === '1') return;
|
|
123
|
+
|
|
124
|
+
const homeDir = os.homedir();
|
|
125
|
+
const creds = readCredentials(homeDir);
|
|
126
|
+
if (!creds) return;
|
|
127
|
+
|
|
128
|
+
if (!shouldNotify(readState(homeDir))) return;
|
|
129
|
+
|
|
130
|
+
const count = await fetchPendingCount(creds);
|
|
131
|
+
if (count == null || count <= 0) return;
|
|
132
|
+
|
|
133
|
+
process.stdout.write(renderNotice(count) + '\n');
|
|
134
|
+
writeState(homeDir);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
shouldNotify, renderNotice, readState, writeState, readCredentials,
|
|
139
|
+
fetchPendingCount, NOTICE_SUPPRESSION_MS, FETCH_TIMEOUT_MS,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (require.main === module) {
|
|
143
|
+
// FAIL-SILENT: exit 0 on every path, including internal errors.
|
|
144
|
+
main().then(() => process.exit(0)).catch(() => process.exit(0));
|
|
145
|
+
}
|