auxilo-mcp 0.9.20 → 0.9.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/auxilo-cli.js CHANGED
@@ -299,6 +299,8 @@ async function cmdSetup(flags) {
299
299
  console.log('');
300
300
  try {
301
301
  const { binRoot } = installer.installRunner(HOME);
302
+ const claudeBin = installer.recordClaudeBin(HOME, process.env.PATH);
303
+ if (claudeBin !== null) console.log(` ✓ Claude Code CLI recorded: ${claudeBin}`);
302
304
  console.log(` ✓ Extraction runner installed to ${binRoot}`);
303
305
  } catch (err) {
304
306
  console.error(` ✗ Runner install failed: ${err.message}`);
package/lib/installer.js CHANGED
@@ -84,6 +84,18 @@ function sourceAdapterRows(packageRoot = PACKAGE_ROOT) {
84
84
  }
85
85
  }
86
86
 
87
+ /** Versioned extraction prompt bundle rows, derived so future bundles ship automatically. */
88
+ function promptBundleRows(packageRoot = PACKAGE_ROOT) {
89
+ try {
90
+ return fs.readdirSync(path.join(packageRoot, 'scripts', 'prompts'))
91
+ .filter((f) => f.endsWith('.js'))
92
+ .sort()
93
+ .map((f) => [`scripts/prompts/${f}`, `scripts/prompts/${f}`, 0o644]);
94
+ } catch {
95
+ return []; // missing dir surfaces as installRunner missing-file errors
96
+ }
97
+ }
98
+
87
99
  // EXTRACT-PER-CLIENT W1 PART A: extract-local.js now requires
88
100
  // './providers/index.js', which requires './claude-code.js' (and will require
89
101
  // './codex-cli.js' / './byo-key.js' once PART B/C land) — every file under
@@ -137,6 +149,7 @@ const RUNNER_STACK = Object.freeze([
137
149
  // at first extraction; omitting it from the installed stack is a MODULE_NOT_FOUND
138
150
  // for every npm-installed user (test/runner-packaging-closure.test.js guards this).
139
151
  ['scripts/extract-local.js', 'scripts/extract-local.js', 0o644],
152
+ ...promptBundleRows(),
140
153
  // LW-18 layer 1b: SessionStart held-count notice (shim target).
141
154
  ['scripts/review-notice.js', 'scripts/review-notice.js', 0o755],
142
155
  ...sourceAdapterRows(),
@@ -1048,6 +1061,29 @@ function writeRunnerConfig(homeDir, patch) {
1048
1061
  return merged;
1049
1062
  }
1050
1063
 
1064
+ /** Find the first regular executable in PATH, preserving its symlink path. */
1065
+ function findExecutableOnPath(name, envPath, fsImpl = fs) {
1066
+ if (typeof envPath !== 'string' || !envPath) return null;
1067
+ for (const dir of envPath.split(path.delimiter)) {
1068
+ if (!dir) continue;
1069
+ const candidate = path.resolve(dir, name);
1070
+ try {
1071
+ if (!fsImpl.statSync(candidate).isFile()) continue;
1072
+ fsImpl.accessSync(candidate, fs.constants.X_OK);
1073
+ return candidate;
1074
+ } catch (_) { /* missing, inaccessible or non-executable — try the next entry */ }
1075
+ }
1076
+ return null;
1077
+ }
1078
+
1079
+ /** Setup only: record the user's CLI and confirm the best-effort write persisted. */
1080
+ function recordClaudeBin(homeDir, envPath, fsImpl = fs) {
1081
+ const bin = findExecutableOnPath('claude', envPath, fsImpl);
1082
+ if (bin === null) return null;
1083
+ writeRunnerConfig(homeDir, { claude_bin: bin });
1084
+ return readRunnerConfig(homeDir).claude_bin === bin ? bin : null;
1085
+ }
1086
+
1051
1087
  // ─── Device-code auth (spec §LW-12 step 2; server.js /auth/device) ──────────
1052
1088
 
1053
1089
  /**
@@ -2375,6 +2411,7 @@ module.exports = {
2375
2411
  DEFAULT_BASE_URL,
2376
2412
  PACKAGE_ROOT,
2377
2413
  RUNNER_STACK,
2414
+ promptBundleRows,
2378
2415
  clientRegistry,
2379
2416
  detectClients,
2380
2417
  clientHasCaptureHookMode,
@@ -2397,6 +2434,8 @@ module.exports = {
2397
2434
  runnerConfigPath,
2398
2435
  readRunnerConfig,
2399
2436
  writeRunnerConfig,
2437
+ findExecutableOnPath,
2438
+ recordClaudeBin,
2400
2439
  writeEnvFile,
2401
2440
  deviceLogin,
2402
2441
  binRootFor,
package/mcp-server.js CHANGED
@@ -198,7 +198,7 @@ async function postBulkChunks(headers, decisions) {
198
198
  }
199
199
 
200
200
  const server = new Server(
201
- { name: 'auxilo', version: '0.9.20' },
201
+ { name: 'auxilo', version: '0.9.22' },
202
202
  {
203
203
  capabilities: { tools: {} },
204
204
  instructions: `You are connected to Auxilo, a knowledge marketplace where AI agents buy and sell operational learnings.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auxilo-mcp",
3
- "version": "0.9.20",
3
+ "version": "0.9.22",
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",
@@ -27,6 +27,7 @@
27
27
  "scripts/capture-core.js",
28
28
  "scripts/extract-local.js",
29
29
  "scripts/review-notice.js",
30
+ "scripts/prompts/",
30
31
  "scripts/sources/",
31
32
  "scripts/providers/",
32
33
  "scripts/hooks/auxilo-extract.sh",
@@ -25,16 +25,24 @@ const {
25
25
  } = require('../lib/extraction-index.js');
26
26
  const providers = require('./providers/index.js');
27
27
  const claudeCodeProvider = require('./providers/claude-code.js');
28
+ const {
29
+ CATEGORIES,
30
+ PRIVATE_CATEGORIES,
31
+ RETIRED_CATEGORIES,
32
+ EXTRACTION_PROMPT_BASE,
33
+ PUBLIC_SCOPE_BLOCK,
34
+ PRIVATE_SCOPE_BLOCK,
35
+ QUALITY_RUBRIC_ADDENDUM,
36
+ PROMPT_SUFFIX,
37
+ ANCHORED_JUDGE_PROMPT_BASE,
38
+ } = require('./prompts');
28
39
 
29
40
  // CI-5 (PUNCH-LIST §30, 2026-07-19): Auxilo is TECHNICAL-ONLY. The learning
30
41
  // taxonomy is these six tech categories; `communication` and `content-generation`
31
42
  // are RETIRED — the server 400s them (CATEGORY_OUT_OF_SCOPE) and this extractor
32
- // must never emit them. This file ships standalone in the npm package, so the
33
- // lists are duplicated here from lib/category-scope-migration.js (server truth);
43
+ // must never emit them. The governed prompt bundle carries the client-side copy
44
+ // from lib/category-scope-migration.js (server truth);
34
45
  // test/ci5-scope-enforcement.test.js pins the copies equal.
35
- const CATEGORIES = ['data-processing', 'web-interaction', 'code-execution', 'storage-state', 'payment-financial', 'monitoring'];
36
- const PRIVATE_CATEGORIES = [...CATEGORIES, 'non-technical'];
37
- const RETIRED_CATEGORIES = ['communication', 'content-generation'];
38
46
 
39
47
  /**
40
48
  * SPEC3 slice A1 gate — score-at-extraction, ON BY DEFAULT since 0.9.12
@@ -69,37 +77,6 @@ function scoreExtractionEnabled(env = process.env) {
69
77
  return !(v === '0' || v === 'false');
70
78
  }
71
79
 
72
- 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.
73
-
74
- 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.
75
-
76
- 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".
77
-
78
- 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.
79
-
80
- 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.
81
-
82
- Output STRICT JSON ONLY — an object with:
83
- "learnings": an array (possibly empty []) of objects with these keys:
84
- "title": concise, >= 10 chars
85
- "body": >= 50 chars — what was tried, what worked, what failed
86
- "category": one of ${JSON.stringify(CATEGORIES)}
87
- "tags": array of lowercase keyword strings
88
- "task_context": one sentence describing the task
89
- "outcome": one of "success","partial","failure","workaround"
90
- "dedup_drops": an array (possibly empty []) used ONLY for candidates dropped
91
- because they match PREVIOUSLY CAPTURED LESSONS. Each entry must be:
92
- {"candidate": <the complete learning object above>,
93
- "matched_index_id": "<exact id from the memory list>",
94
- "matched_title": "<exact matched title>"}
95
- Scope/quality/sensitivity skips are not dedup_drops.`;
96
-
97
- const PUBLIC_SCOPE_BLOCK = `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".
98
-
99
- 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.`;
100
-
101
- const PRIVATE_SCOPE_BLOCK = `PRIVATE CAPTURE SCOPE — OWNER-ONLY: extract reusable technical OR non-technical operational learnings. Non-technical process, workflow, communication, content, business, or creative learnings may use category "non-technical"; do not drop a genuine reusable candidate solely because it is non-technical. This private lane is never published unless the owner later sanitizes and promotes an item through public review. The mandatory sensitivity screen still applies without exception.`;
102
-
103
80
  function promptBaseForVisibility(captureVisibility) {
104
81
  if (captureVisibility !== 'private') return EXTRACTION_PROMPT_BASE;
105
82
  return EXTRACTION_PROMPT_BASE
@@ -111,27 +88,6 @@ function promptBaseForVisibility(captureVisibility) {
111
88
  .replace(JSON.stringify(CATEGORIES), JSON.stringify(PRIVATE_CATEGORIES));
112
89
  }
113
90
 
114
- /** A1: rubric addendum — appended ONLY when scoreExtractionEnabled(). */
115
- const QUALITY_RUBRIC_ADDENDUM = `
116
- "quality_self_assessment": an object scoring the learning honestly on four
117
- dimensions, each an INTEGER 1-5: "specificity" (precise and detailed, not
118
- vague), "actionability" (another agent can directly use it), "novelty"
119
- (non-obvious; an LLM would likely get it wrong), "completeness" (context,
120
- reproduction steps, caveats), plus "total" (the exact sum of the four).
121
- A learning worth publishing scores at least 14/20 with no dimension below 3.
122
- High scores REQUIRE a system+symptom anchor — a named external system and a
123
- concrete error/limitation/behavior; process or workflow advice cannot score
124
- high no matter how polished (CI-7 system-fact test).
125
- If a learning honestly scores below that bar, DROP it from learnings rather
126
- than inflating the numbers.`;
127
-
128
- const PROMPT_SUFFIX = `
129
- No prose, no explanation, no markdown code fences — just the raw JSON object
130
- {"learnings":[...],"dedup_drops":[...]}.
131
-
132
- TRANSCRIPT:
133
- `;
134
-
135
91
  /** Build the extraction prompt for the current (or injected) gate state. */
136
92
  function buildExtractionPrompt(opts = {}) {
137
93
  const withScore = opts.scoreExtraction !== undefined
@@ -361,18 +317,7 @@ function buildAnchoredJudgePrompt(candidates, indexRows, opts = {}) {
361
317
  title: row.title,
362
318
  })),
363
319
  }));
364
- const prompt = `You are a binary deduplication judge for operational learnings.
365
- For each candidate, decide whether it is a re-statement of ANY listed previously
366
- captured lesson. The same operational insight in different words is YES.
367
- A genuinely new fact is NO only when it would change what another agent does.
368
-
369
- Return STRICT JSON ONLY:
370
- {"decisions":[{"candidate_index":0,"duplicate":true,"matched_index_id":"..."}]}
371
- Return exactly one decision for every candidate_index. When duplicate=false,
372
- omit matched_index_id. When duplicate=true, matched_index_id MUST be one of that
373
- candidate's listed ids. No prose and no markdown.
374
-
375
- ${JSON.stringify(payload)}`;
320
+ const prompt = `${ANCHORED_JUDGE_PROMPT_BASE}\n\n${JSON.stringify(payload)}`;
376
321
  return { prompt, rankings };
377
322
  }
378
323
 
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const PROMPT_BUNDLE_VERSION = '1';
4
+ const ASSEMBLY_CONTRACT_VERSION = '1';
5
+
6
+ const CATEGORIES = Object.freeze([
7
+ 'data-processing',
8
+ 'web-interaction',
9
+ 'code-execution',
10
+ 'storage-state',
11
+ 'payment-financial',
12
+ 'monitoring',
13
+ ]);
14
+ const PRIVATE_CATEGORIES = Object.freeze([
15
+ 'data-processing',
16
+ 'web-interaction',
17
+ 'code-execution',
18
+ 'storage-state',
19
+ 'payment-financial',
20
+ 'monitoring',
21
+ 'non-technical',
22
+ ]);
23
+ const RETIRED_CATEGORIES = Object.freeze(['communication', 'content-generation']);
24
+
25
+ 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.
26
+
27
+ 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.
28
+
29
+ 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".
30
+
31
+ 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.
32
+
33
+ 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.
34
+
35
+ Output STRICT JSON ONLY — an object with:
36
+ "learnings": an array (possibly empty []) of objects with these keys:
37
+ "title": concise, >= 10 chars
38
+ "body": >= 50 chars — what was tried, what worked, what failed
39
+ "category": one of ["data-processing","web-interaction","code-execution","storage-state","payment-financial","monitoring"]
40
+ "tags": array of lowercase keyword strings
41
+ "task_context": one sentence describing the task
42
+ "outcome": one of "success","partial","failure","workaround"
43
+ "dedup_drops": an array (possibly empty []) used ONLY for candidates dropped
44
+ because they match PREVIOUSLY CAPTURED LESSONS. Each entry must be:
45
+ {"candidate": <the complete learning object above>,
46
+ "matched_index_id": "<exact id from the memory list>",
47
+ "matched_title": "<exact matched title>"}
48
+ Scope/quality/sensitivity skips are not dedup_drops.`;
49
+
50
+ const PUBLIC_SCOPE_BLOCK = `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
+
54
+ const PRIVATE_SCOPE_BLOCK = `PRIVATE CAPTURE SCOPE — OWNER-ONLY: extract reusable technical OR non-technical operational learnings. Non-technical process, workflow, communication, content, business, or creative learnings may use category "non-technical"; do not drop a genuine reusable candidate solely because it is non-technical. This private lane is never published unless the owner later sanitizes and promotes an item through public review. The mandatory sensitivity screen still applies without exception.`;
55
+
56
+ const QUALITY_RUBRIC_ADDENDUM = `
57
+ "quality_self_assessment": an object scoring the learning honestly on four
58
+ dimensions, each an INTEGER 1-5: "specificity" (precise and detailed, not
59
+ vague), "actionability" (another agent can directly use it), "novelty"
60
+ (non-obvious; an LLM would likely get it wrong), "completeness" (context,
61
+ reproduction steps, caveats), plus "total" (the exact sum of the four).
62
+ A learning worth publishing scores at least 14/20 with no dimension below 3.
63
+ High scores REQUIRE a system+symptom anchor — a named external system and a
64
+ concrete error/limitation/behavior; process or workflow advice cannot score
65
+ high no matter how polished (CI-7 system-fact test).
66
+ If a learning honestly scores below that bar, DROP it from learnings rather
67
+ than inflating the numbers.`;
68
+
69
+ const PROMPT_SUFFIX = `
70
+ No prose, no explanation, no markdown code fences — just the raw JSON object
71
+ {"learnings":[...],"dedup_drops":[...]}.
72
+
73
+ TRANSCRIPT:
74
+ `;
75
+
76
+ const ANCHORED_JUDGE_PROMPT_BASE = `You are a binary deduplication judge for operational learnings.
77
+ For each candidate, decide whether it is a re-statement of ANY listed previously
78
+ captured lesson. The same operational insight in different words is YES.
79
+ A genuinely new fact is NO only when it would change what another agent does.
80
+
81
+ Return STRICT JSON ONLY:
82
+ {"decisions":[{"candidate_index":0,"duplicate":true,"matched_index_id":"..."}]}
83
+ Return exactly one decision for every candidate_index. When duplicate=false,
84
+ omit matched_index_id. When duplicate=true, matched_index_id MUST be one of that
85
+ candidate's listed ids. No prose and no markdown.`;
86
+
87
+ module.exports = Object.freeze({
88
+ PROMPT_BUNDLE_VERSION,
89
+ ASSEMBLY_CONTRACT_VERSION,
90
+ CATEGORIES,
91
+ PRIVATE_CATEGORIES,
92
+ RETIRED_CATEGORIES,
93
+ EXTRACTION_PROMPT_BASE,
94
+ PUBLIC_SCOPE_BLOCK,
95
+ PRIVATE_SCOPE_BLOCK,
96
+ QUALITY_RUBRIC_ADDENDUM,
97
+ PROMPT_SUFFIX,
98
+ ANCHORED_JUDGE_PROMPT_BASE,
99
+ });
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const ACTIVE_BUNDLE_VERSION = '1';
8
+ const BUNDLE_PATHS = Object.freeze({
9
+ '1': path.join(__dirname, 'extraction.v1.js'),
10
+ });
11
+
12
+ function bundlePath(version = ACTIVE_BUNDLE_VERSION) {
13
+ const resolved = BUNDLE_PATHS[String(version)];
14
+ if (!resolved) throw new Error(`Unknown extraction prompt bundle version: ${version}`);
15
+ return resolved;
16
+ }
17
+
18
+ function loadPromptBundle(version = ACTIVE_BUNDLE_VERSION) {
19
+ return require(bundlePath(version));
20
+ }
21
+
22
+ function bundleDigest(version = ACTIVE_BUNDLE_VERSION) {
23
+ return crypto.createHash('sha256').update(fs.readFileSync(bundlePath(version))).digest('hex');
24
+ }
25
+
26
+ module.exports = Object.freeze({
27
+ ...loadPromptBundle(),
28
+ bundleDigest,
29
+ loadPromptBundle,
30
+ });
@@ -22,23 +22,56 @@ const fs = require('fs');
22
22
  const path = require('path');
23
23
  const os = require('os');
24
24
 
25
- /** Resolve the `claude` binary hook/launchd env may have a minimal PATH. */
25
+ /** Leading major.minor.patch only; unreadable versions retain fallback behavior. */
26
+ function versionParts(version) {
27
+ const match = typeof version === 'string' && /^(\d+)\.(\d+)\.(\d+)/.exec(version);
28
+ return match ? match.slice(1).map(Number) : null;
29
+ }
30
+
31
+ function compareVersions(left, right) {
32
+ for (let i = 0; i < 3; i += 1) {
33
+ if (left[i] !== right[i]) return left[i] > right[i] ? 1 : -1;
34
+ }
35
+ return 0;
36
+ }
37
+
38
+ /** Resolve the newest readable CLI — hook/launchd env may have a minimal PATH. */
26
39
  function resolveClaudeBin(opts = {}) {
27
40
  const homeDir = typeof opts.homeDir === 'string' ? opts.homeDir : os.homedir();
28
41
  const existsSync = typeof opts.existsSync === 'function' ? opts.existsSync : fs.existsSync;
42
+ const readFileSyncImpl = typeof opts.readFileSyncImpl === 'function' ? opts.readFileSyncImpl : fs.readFileSync;
29
43
  const candidates = [
30
44
  path.join(homeDir, '.claude', 'local', 'claude'),
31
45
  '/usr/local/bin/claude',
32
46
  '/opt/homebrew/bin/claude',
33
47
  path.join(homeDir, '.local', 'bin', 'claude'),
48
+ path.join(homeDir, '.npm-global', 'bin', 'claude'),
34
49
  ];
50
+ // Read directly: the sweeper install does not include lib/installer.js.
51
+ try {
52
+ const config = JSON.parse(readFileSyncImpl(path.join(homeDir, '.auxilo', 'runner-config.json'), 'utf8'));
53
+ const recorded = config && config.claude_bin;
54
+ if (typeof recorded === 'string' && path.isAbsolute(recorded) && path.basename(recorded) === 'claude') {
55
+ candidates.unshift(recorded);
56
+ }
57
+ } catch (_) { /* missing/malformed config means no recorded candidate */ }
58
+
59
+ let firstExisting;
60
+ let newest;
61
+ let newestVersion;
35
62
  for (const c of candidates) {
36
63
  try {
37
- if (existsSync(c)) return c;
64
+ if (!existsSync(c)) continue;
65
+ if (!firstExisting) firstExisting = c;
66
+ const version = versionParts(getClaudeCliVersion(c, opts));
67
+ if (version && (!newestVersion || compareVersions(version, newestVersion) > 0)) {
68
+ newest = c;
69
+ newestVersion = version;
70
+ }
38
71
  } catch (_) { /* ignore */ }
39
72
  }
40
73
  // Absolute launchd fallbacks are absent; let PATH resolve the final option.
41
- return 'claude';
74
+ return newest || firstExisting || 'claude';
42
75
  }
43
76
 
44
77
  // ─── Child settings/hooks isolation (EXTRACTION-CHILD-HOOKS, PUNCH-LIST P1,
@@ -105,27 +138,35 @@ function _resetSettingSourcesCacheForTests() {
105
138
  cachedSettingSourcesUnsupported = undefined;
106
139
  }
107
140
 
108
- // ─── CLI version, for diagnostics only (no subprocess spawn) ───────────────
141
+ // ─── CLI version, for selection, auth gating and provenance (no spawn) ──────
109
142
  //
110
143
  // Resolves the installed package's own package.json version by following the
111
144
  // resolved binary's real path (e.g. `/usr/local/bin/claude` -> `.../
112
145
  // node_modules/@anthropic-ai/claude-code/cli.js`) and reading the sibling
113
- // package.json — filesystem-only, so it never adds a spawn to the extraction
146
+ // package.json, or the named parent package for the native bin/claude.exe
147
+ // layout — filesystem-only, so it never adds a spawn to the extraction
114
148
  // path (verified live: realpath + package.json read, no `claude --version`
115
149
  // call). Best-effort: any failure (bare `claude` unresolved via PATH, an
116
- // install layout that doesn't carry a sibling package.json, a fixture path in
150
+ // install layout that doesn't carry either package.json, a fixture path in
117
151
  // tests) yields null, never throws.
118
152
  function getClaudeCliVersion(bin, opts = {}) {
119
153
  const realpathSyncImpl = typeof opts.realpathSyncImpl === 'function' ? opts.realpathSyncImpl : fs.realpathSync;
120
154
  const readFileSyncImpl = typeof opts.readFileSyncImpl === 'function' ? opts.readFileSyncImpl : fs.readFileSync;
121
155
  try {
122
156
  const real = realpathSyncImpl(bin);
123
- const pkgPath = path.join(path.dirname(real), 'package.json');
124
- const pkg = JSON.parse(readFileSyncImpl(pkgPath, 'utf8'));
125
- return pkg && typeof pkg.version === 'string' ? pkg.version : null;
157
+ const dir = path.dirname(real);
158
+ for (const [pkgDir, requireName] of [[dir, false], [path.dirname(dir), true]]) {
159
+ try {
160
+ const pkg = JSON.parse(readFileSyncImpl(path.join(pkgDir, 'package.json'), 'utf8'));
161
+ if (pkg && typeof pkg.version === 'string' && (!requireName || pkg.name === '@anthropic-ai/claude-code')) {
162
+ return pkg.version;
163
+ }
164
+ } catch (_) { /* unreadable sibling may still have a valid parent */ }
165
+ }
126
166
  } catch {
127
- return null;
167
+ /* unresolved binary */
128
168
  }
169
+ return null;
129
170
  }
130
171
 
131
172
  // ─── Env scrub (EXTRACT-TOOLS-LOCK, PUNCH-LIST) ────────────────────────────
@@ -345,6 +386,10 @@ function detectBillingHelperConfigured(opts = {}) {
345
386
  function checkAuthStatus(opts = {}) {
346
387
  const spawnSyncImpl = typeof opts.spawnSyncImpl === 'function' ? opts.spawnSyncImpl : spawnSync;
347
388
  const bin = typeof opts.claudeBin === 'string' ? opts.claudeBin : resolveClaudeBin(opts);
389
+ const version = versionParts(getClaudeCliVersion(bin, opts));
390
+ // Older CLIs treat `auth status` as a model prompt. Unknown versions retain
391
+ // the existing probe; only a known-old build can safely skip it here.
392
+ if (version && compareVersions(version, [2, 1, 41]) < 0) return 'unknown';
348
393
  let res;
349
394
  try {
350
395
  res = spawnSyncImpl(bin, ['auth', 'status'], {
package/scripts/runner.js CHANGED
@@ -850,15 +850,36 @@ const SWEEPER_LABEL = 'io.auxilo.sweeper';
850
850
  */
851
851
  function sweeperManifest(repoRoot = path.resolve(__dirname, '..')) {
852
852
  const sourceRows = [];
853
+ const promptRows = [];
854
+ const providerRows = [];
855
+ const providerSchemaRows = [];
853
856
  try {
854
857
  for (const f of fs.readdirSync(path.join(repoRoot, 'scripts', 'sources')).filter(f => f.endsWith('.js')).sort()) {
855
858
  sourceRows.push([`scripts/sources/${f}`, `scripts/sources/${f}`, 0o644]);
856
859
  }
857
860
  } catch { /* missing dir surfaces as missing-file errors below */ }
861
+ try {
862
+ for (const f of fs.readdirSync(path.join(repoRoot, 'scripts', 'prompts')).filter(f => f.endsWith('.js')).sort()) {
863
+ promptRows.push([`scripts/prompts/${f}`, `scripts/prompts/${f}`, 0o644]);
864
+ }
865
+ } catch { /* missing dir surfaces as missing-file errors below */ }
866
+ try {
867
+ for (const f of fs.readdirSync(path.join(repoRoot, 'scripts', 'providers')).filter(f => f.endsWith('.js')).sort()) {
868
+ providerRows.push([`scripts/providers/${f}`, `scripts/providers/${f}`, 0o644]);
869
+ }
870
+ } catch { /* missing dir surfaces as missing-file errors below */ }
871
+ try {
872
+ for (const f of fs.readdirSync(path.join(repoRoot, 'scripts', 'providers', 'schemas')).filter(f => f.endsWith('.json')).sort()) {
873
+ providerSchemaRows.push([`scripts/providers/schemas/${f}`, `scripts/providers/schemas/${f}`, 0o644]);
874
+ }
875
+ } catch { /* missing dir surfaces as missing-file errors below */ }
858
876
  return [
859
877
  ['scripts/auxilo-sweeper-wrapper.sh', 'auxilo-sweeper-wrapper.sh', 0o755],
860
878
  ['scripts/runner.js', 'scripts/runner.js', 0o755],
861
879
  ...sourceRows,
880
+ ...promptRows,
881
+ ...providerRows,
882
+ ...providerSchemaRows,
862
883
  ['lib/sensitivity-filter.js', 'lib/sensitivity-filter.js', 0o644],
863
884
  ['lib/extraction-index.js', 'lib/extraction-index.js', 0o644],
864
885
  ['lib/similarity.js', 'lib/similarity.js', 0o644],