auxilo-mcp 0.9.17 → 0.9.18

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
@@ -586,7 +586,7 @@ async function cmdStatus() {
586
586
  );
587
587
  if (autoupdateLine) console.log(autoupdateLine);
588
588
  }
589
- console.log(extractionProviderLine(await providers.resolveProvider({})));
589
+ console.log(extractionProviderLine(lastRecordedProviderResolution()));
590
590
  // Lazy require: scripts/runner.js is a heavier module (sources, sensitivity
591
591
  // filter, ops-alert) than this one status line needs at require-time for
592
592
  // every CLI invocation.
@@ -625,18 +625,58 @@ function runnerSkewLine(skew) {
625
625
  */
626
626
  const CLI_CLEAN_LANE_CALIBRATED_PROVIDERS = ['claude-code'];
627
627
 
628
+ /**
629
+ * EXTRACTION-MODEL-PROVENANCE (PUNCH-LIST P1): `auxilo status` used to feed
630
+ * extractionProviderLine() a LIVE `providers.resolveProvider({})` call — a
631
+ * fresh detect() answering "what would run right now" (and, on a full scan,
632
+ * capable of writing ~/.auxilo/providers.json's `selected` field as a side
633
+ * effect of a status check), not "what actually ran". This function replaces
634
+ * that with two read-only, no-detect sources of TRUTH, in priority order:
635
+ * (1) AUXILO_EXTRACTION_PROVIDER, if set, is a certain fact about the
636
+ * current session's config — reading it is not a guess — validated
637
+ * against providers.PROVIDER_ORDER exactly as resolveProvider() itself
638
+ * validates an override, without calling it.
639
+ * (2) Otherwise, providers.json's `selected` field — the LAST provider a
640
+ * genuine resolveProvider() full-scan actually chose and persisted
641
+ * (scripts/providers/index.js's persistSelected(), only ever called
642
+ * after a real detect() succeeded) — read here with a plain
643
+ * fs.readFileSync, no detect() invoked, no possibility of writing.
644
+ * Neither source can misrepresent "would run" as "ran": (1) is what WILL
645
+ * run (an explicit operator override, not a probe), and (2) is what was
646
+ * last recorded to have been selected, honestly labeled as such below.
647
+ * `{ok:false}` (shown as "no recorded provider selection yet") when neither
648
+ * source has an answer — e.g. a fresh install that has never extracted.
649
+ */
650
+ function lastRecordedProviderResolution() {
651
+ const override = process.env.AUXILO_EXTRACTION_PROVIDER;
652
+ if (override) {
653
+ if (providers.PROVIDER_ORDER.includes(override)) return { ok: true, id: override };
654
+ return {
655
+ ok: false,
656
+ reason: `AUXILO_EXTRACTION_PROVIDER="${override}" is not a known provider (expected one of: ${providers.PROVIDER_ORDER.join(', ')})`,
657
+ };
658
+ }
659
+ try {
660
+ const raw = fs.readFileSync(providers.PROVIDERS_STATE_PATH, 'utf8');
661
+ const parsed = JSON.parse(raw);
662
+ const id = parsed && typeof parsed === 'object' && typeof parsed.selected === 'string' ? parsed.selected : null;
663
+ if (id) return { ok: true, id };
664
+ } catch { /* no recorded selection yet, or the file is unreadable/corrupt */ }
665
+ return { ok: false, reason: 'no recorded provider selection yet' };
666
+ }
667
+
628
668
  /**
629
669
  * EXTRACT-PER-CLIENT W1 PART A/C — one unconditional line naming which
630
- * extraction model provider resolves, why (env override vs auto-detected),
631
- * and (PART C) whether that provider's submissions can reach the clean-lane
632
- * auto-publish path at all (server-side gate: lib/clean-lane.js's
633
- * CLEAN_LANE_CALIBRATED_PROVIDERS, mirrored above).
670
+ * extraction model provider resolves, why (env override vs last recorded
671
+ * selection), and (PART C) whether that provider's submissions can reach
672
+ * the clean-lane auto-publish path at all (server-side gate:
673
+ * lib/clean-lane.js's CLEAN_LANE_CALIBRATED_PROVIDERS, mirrored above).
634
674
  */
635
675
  function extractionProviderLine(resolution) {
636
676
  if (resolution && resolution.ok) {
637
677
  const via = process.env.AUXILO_EXTRACTION_PROVIDER
638
678
  ? 'env override AUXILO_EXTRACTION_PROVIDER'
639
- : 'auto-detected';
679
+ : 'last recorded selection';
640
680
  const calibration = CLI_CLEAN_LANE_CALIBRATED_PROVIDERS.includes(resolution.id)
641
681
  ? 'clean-lane calibrated'
642
682
  : 'review-lane only';
@@ -1639,6 +1679,7 @@ module.exports = {
1639
1679
  parseFlags,
1640
1680
  runnerSkewLine,
1641
1681
  extractionProviderLine,
1682
+ lastRecordedProviderResolution,
1642
1683
  resolveBaseUrl,
1643
1684
  shortFlags,
1644
1685
  groupSummaryRows,
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.17' },
201
+ { name: 'auxilo', version: '0.9.18' },
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.17",
3
+ "version": "0.9.18",
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",
@@ -400,37 +400,37 @@ function judgeUsage(usage, prompt, completion) {
400
400
 
401
401
  /**
402
402
  * PART C — resolve the extraction_model identity for a runModel result.
403
- * Prefers the additive `identity` field a provider's runModel result may
404
- * carry. Current state (post Gate-A item a): byo-key.js always sets one
405
- * ({provider:'byo-key', model, version:null, vendor}); codex-cli.js sets one
406
- * on success ({provider:'codex-cli', model:null, version:<codex --version>,
407
- * vendor:null} its result also carries the same object under the
408
- * deprecated `extraction_model` alias, kept for one release only for
409
- * test/codex-cli-provider.test.js). claude-code.js is the one provider that
410
- * still sets no `identity` on its result that's the case this function's
411
- * fallback exists for: it re-resolves via providers.resolveProvider() and
412
- * stamps {provider: resolved.id, model: null, version: null, vendor: null},
413
- * so every provider gets SOME stamp, never silently none. That re-resolution
414
- * walks scripts/providers/index.js's PROVIDER_ORDER (claude-code
415
- * codex-cli byo-key); resolveProvider/runModel there fall through from one
416
- * provider to the next only on a NON_RETRYABLE_FOR_THIS_PROVIDER reasonCode
417
- * (unauthenticated, not installed, a billing helper configured, an
418
- * unconfigured BYO key, or an unsafe providers.json mode) — a provider that
419
- * merely failed once (a timeout, a model error) is not retried under a
420
- * different one. Best-effort throughout: a resolution failure here must
421
- * never block extraction itself.
403
+ *
404
+ * EXTRACTION-MODEL-PROVENANCE (PUNCH-LIST P1): this function used to fall
405
+ * back to a FRESH, INDEPENDENT providers.resolveProvider() call whenever the
406
+ * result carried no `identity` — a re-detect decoupled from which provider
407
+ * actually produced `runModelResult`, which is what "what would run now"
408
+ * answers, not "what ran". That re-resolve could also silently rewrite
409
+ * `~/.auxilo/providers.json` (resolveProvider's full-scan path calls
410
+ * persistSelected) from what should have been a read-only identity lookup.
411
+ * Both are gone. `identity` is now ALWAYS attached by
412
+ * scripts/providers/index.js's runModel() itself centrally, because that
413
+ * registry is the only thing that knows which module it actually invoked
414
+ * for this call (see its withIdentity()/deriveIdentity() claude-code's own
415
+ * cliVersion is used there when present, richer than the null/null/null
416
+ * triple this function used to guess). This function's job shrinks to: use
417
+ * the identity the result actually carries, or admit the honest
418
+ * `provider:'unknown'` when none exists (the `no-usable-provider` /
419
+ * bad-override-name aggregate failures nothing actually ran to
420
+ * completion, so there is nothing to attribute). Never re-derives, never
421
+ * writes, never blocks extraction on failure.
422
422
  */
423
- async function resolveExtractionModelIdentity(runModelResult, opts) {
424
- if (runModelResult && runModelResult.identity && typeof runModelResult.identity === 'object') {
423
+ function resolveExtractionModelIdentity(runModelResult) {
424
+ if (
425
+ runModelResult
426
+ && runModelResult.identity
427
+ && typeof runModelResult.identity === 'object'
428
+ && typeof runModelResult.identity.provider === 'string'
429
+ && runModelResult.identity.provider
430
+ ) {
425
431
  return runModelResult.identity;
426
432
  }
427
- try {
428
- const resolved = await providers.resolveProvider(opts);
429
- if (resolved && resolved.ok && resolved.id) {
430
- return { provider: resolved.id, model: null, version: null, vendor: null };
431
- }
432
- } catch { /* identity is best-effort; never block extraction on it */ }
433
- return null;
433
+ return { provider: 'unknown', model: null, version: null, vendor: null };
434
434
  }
435
435
 
436
436
  /**
@@ -456,7 +456,7 @@ async function defaultInvokeModel(transcript, invokeOpts, opts) {
456
456
  reason: result.reason,
457
457
  reasonCode: result.reasonCode,
458
458
  authStatus: result.authStatus,
459
- extractionModel: await resolveExtractionModelIdentity(result, opts),
459
+ extractionModel: resolveExtractionModelIdentity(result),
460
460
  ...(result.authDiscrepancy !== undefined && { authDiscrepancy: result.authDiscrepancy }),
461
461
  // EXTRACTION-RUN-LOG (0.9.15): additive passthrough for the one-line-per-run
462
462
  // provider summary logged at the end of extractLocally() below. Only
@@ -704,12 +704,53 @@ function formatArgvForLog(argv) {
704
704
  * (spawned) this run, not whether it succeeded — a spawn that ran and then
705
705
  * hit a model error still counts as "ran" (it happened; the failure is in
706
706
  * `reason`, not in whether isolation applied). `hooks` is `claude-code`-
707
- * specific: 'isolated' whenever a claude-code spawn this run carried
708
- * --setting-sources (the only state a spawn can be in per the fail-closed
709
- * gate in scripts/providers/claude-code.js it never spawns without the
710
- * flag), 'unsupported' when the CLI was found not to support the flag at
711
- * all, 'n/a' for a non-claude-code provider (codex-cli/byo-key isolate by a
712
- * different mechanism entirely, out of this row's scope).
707
+ * specific and EVIDENCE-DERIVED (EXTRACT-LOG-HOOKS-EVIDENCE, PUNCH-LIST P2):
708
+ * it reads the argv this run actually captured rather than inferring
709
+ * isolation from the provider name plus the absence of a reason code —
710
+ * 'isolated' ONLY when that argv literally contains --setting-sources (the
711
+ * flag scripts/providers/claude-code.js's fail-closed gate never spawns
712
+ * without), 'unsupported' when the CLI was found not to support the flag at
713
+ * all (existing reason-code path, unchanged), 'unknown' when no argv was
714
+ * captured this run (finder skipped pre-spawn, or the run that actually
715
+ * produced this result fell through to a different/no provider — see the
716
+ * EXTRACT-LOG-HOOKS-EVIDENCE root-cause note below), and 'n/a' for a
717
+ * non-claude-code provider (codex-cli/byo-key isolate by a different
718
+ * mechanism entirely, out of this row's scope). Never 'isolated' without an
719
+ * argv carrying the flag in hand — a safety claim needs evidence, not an
720
+ * absence of contrary evidence.
721
+ *
722
+ * Root cause of the missing-argv runs (EXTRACT-LOG-HOOKS-EVIDENCE
723
+ * investigation): claude-code.js's own runExtractMode() never omits argv on
724
+ * a claude-code result that actually reached this line — every return after
725
+ * `const argv = EXTRACT_MODE_ARGV` carries it, and the two pre-spawn
726
+ * short-circuits that don't (cached --setting-sources-unsupported,
727
+ * cli-unauthenticated) both carry reasonCodes already in
728
+ * PRE_SPAWN_SKIP_REASON_CODES, so they render finder=skipped, not ran. The
729
+ * observed defect lines (finder=ran, flags=n/a) come from a DIFFERENT case:
730
+ * scripts/providers/index.js's runModel() falls through from claude-code to
731
+ * the next configured provider (e.g. codex-cli) whenever claude-code's own
732
+ * attempt fails with a NON_RETRYABLE_FOR_THIS_PROVIDER reasonCode, and
733
+ * returns that OTHER provider's result directly when it stops there. That
734
+ * provider's result carries no `argv` field at all (argv is a
735
+ * claude-code-only concept), so there is no shipped argv being hidden here —
736
+ * 'unknown' remains the correct, honest `hooks` value regardless of which
737
+ * provider is named.
738
+ *
739
+ * EXTRACTION-MODEL-PROVENANCE (PUNCH-LIST P1) closed the mismatch this
740
+ * docblock used to describe as a known, deferred gap: a fallthrough
741
+ * provider's result used to reach this function carrying no `identity` on
742
+ * failure (codex-cli/byo-key only self-stamped on success), so
743
+ * resolveExtractionModelIdentity()'s old fallback re-resolved the label via
744
+ * a FRESH, INDEPENDENT providers.resolveProvider() call — decoupled from
745
+ * which provider's runModel() result was actually being logged, and prone to
746
+ * landing back on 'claude-code' (its detect() only checks the
747
+ * billing-helper gate + auth status, not whether the earlier attempt
748
+ * actually spawned). That fallback is gone. `identity` is now attached
749
+ * centrally by providers/index.js's runModel() to EVERY result it returns —
750
+ * success or failure, fallthrough or not — because that registry alone
751
+ * knows which module it actually invoked for a given attempt. The line this
752
+ * function renders now names the provider that actually ran (or 'unknown'
753
+ * only when nothing did), not a guess.
713
754
  */
714
755
  function logProviderRunSummary(opts, runId, modelResult, judged) {
715
756
  try {
@@ -725,8 +766,11 @@ function logProviderRunSummary(opts, runId, modelResult, judged) {
725
766
  const cliVersion = modelResult.cliVersion || (judged && judged.judgeCliVersion) || null;
726
767
  const finderUnsupported = modelResult.reasonCode === 'cli-settings-isolation-unsupported';
727
768
  const judgeUnsupported = Boolean(judged && judged.judgeReasonCode === 'cli-settings-isolation-unsupported');
769
+ const hasSettingSourcesArgv = Array.isArray(argv) && argv.includes('--setting-sources');
728
770
  const hooks = provider === 'claude-code'
729
- ? ((finderUnsupported || judgeUnsupported) ? 'unsupported' : 'isolated')
771
+ ? ((finderUnsupported || judgeUnsupported)
772
+ ? 'unsupported'
773
+ : (hasSettingSourcesArgv ? 'isolated' : 'unknown'))
730
774
  : 'n/a';
731
775
  log(
732
776
  `[providers] run=${runId || 'unknown'} provider=${provider} cli=${cliVersion || '-'} ` +
@@ -875,4 +919,7 @@ module.exports = {
875
919
  resolveClaudeBin: claudeCodeProvider.resolveClaudeBin,
876
920
  // EXTRACTION-RUN-LOG (0.9.15) — exported for direct unit coverage.
877
921
  formatArgvForLog, logProviderRunSummary, PRE_SPAWN_SKIP_REASON_CODES,
922
+ // EXTRACTION-MODEL-PROVENANCE (PUNCH-LIST P1) — exported for direct unit
923
+ // coverage of the "never guess, fail closed to unknown" contract.
924
+ resolveExtractionModelIdentity,
878
925
  };
@@ -245,6 +245,92 @@ const NON_RETRYABLE_FOR_THIS_PROVIDER = new Set([
245
245
  'cli-settings-isolation-unsupported',
246
246
  ]);
247
247
 
248
+ /**
249
+ * EXTRACTION-MODEL-PROVENANCE (PUNCH-LIST P1, follow-up to the
250
+ * EXTRACT-LOG-HOOKS-EVIDENCE row): `identity` on a runModel() result is
251
+ * provenance — a record of which provider actually produced this text — not
252
+ * a guess. It must never be re-derived after the fact from a fresh detect(),
253
+ * because a fresh detect() answers "what would run now", not "what ran".
254
+ * This registry is the only thing that KNOWS which module's runModel() it
255
+ * just invoked for a given attempt, so identity is enforced HERE, centrally,
256
+ * rather than trusted to per-provider convention (the gap this row closes:
257
+ * before this fix, a provider `ok:true` return that forgot to attach
258
+ * `identity` would silently fall through to extract-local.js's
259
+ * resolveExtractionModelIdentity() re-detecting via resolveProvider() —
260
+ * decoupled from which module actually ran).
261
+ */
262
+ function hasUsableIdentity(identity) {
263
+ return Boolean(
264
+ identity
265
+ && typeof identity === 'object'
266
+ && typeof identity.provider === 'string'
267
+ && identity.provider
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Derive an identity for the module actually invoked as `id`, used only when
273
+ * that module's own result didn't already carry one. The derivation differs
274
+ * by outcome, because a SUCCESS identity can reach a published learning and
275
+ * the clean-lane calibration gate, while a FAILURE identity is purely
276
+ * diagnostic (extract-local.js returns before stamping anything onto a
277
+ * candidate on `ok:false` — see its `:824-834`):
278
+ *
279
+ * - claude-code: always gets a real, provider-specific identity — it
280
+ * already has `cliVersion` in hand on every returned result (success or
281
+ * failure), richer than the null/null/null triple this used to guess, and
282
+ * claude-code is the one provider documented to never self-stamp
283
+ * `identity` at all, so this is filling a KNOWN, structural gap, not
284
+ * papering over a violated contract.
285
+ * - Every other provider on a FAILURE: `{provider:id, model:null,
286
+ * version:null, vendor:null}` — naming the module we actually invoked is
287
+ * a plain structural fact (we chose to call it), not a guess, and it is
288
+ * what lets the per-run `[providers]` log name the provider that actually
289
+ * ran even when that provider's own failure return carries no identity
290
+ * (codex-cli and byo-key only self-stamp on their SINGLE success return —
291
+ * this is the exact fall-through failure case the investigation traced:
292
+ * claude-code skipped, codex-cli ran and failed, no identity of its own,
293
+ * the label used to be re-guessed as claude-code by the old fallback).
294
+ * - Every other provider on a SUCCESS: `{provider:'unknown', ...}` —
295
+ * byo-key.js and codex-cli.js both self-stamp `identity` on their only
296
+ * success return BY CONTRACT; a success with none means that contract was
297
+ * violated, so this registry has no provider-reported basis for the
298
+ * claim. Confidently naming the module here would look like provenance
299
+ * without being backed by anything the provider itself reported — since
300
+ * this stamp CAN reach a published learning, the fail-closed, honest
301
+ * answer is 'unknown', never a guess dressed up as a fact.
302
+ */
303
+ function deriveIdentity(id, result) {
304
+ if (id === 'claude-code') {
305
+ return {
306
+ provider: 'claude-code',
307
+ model: null,
308
+ version: (result && result.cliVersion) || null,
309
+ vendor: 'anthropic',
310
+ };
311
+ }
312
+ if (!(result && result.ok)) {
313
+ return { provider: id, model: null, version: null, vendor: null };
314
+ }
315
+ return { provider: 'unknown', model: null, version: null, vendor: null };
316
+ }
317
+
318
+ /**
319
+ * Attach a derived identity to `result` IFF it doesn't already carry a
320
+ * usable one — never overwrites a provider-reported identity (e.g.
321
+ * byo-key's real model name). Applied to every result this registry
322
+ * returns, success or failure, so the per-run `[providers]` log
323
+ * (scripts/extract-local.js's logProviderRunSummary) names the provider
324
+ * that actually ran even on a failure — that is the fall-through failure
325
+ * case this row's investigation traced (claude-code skipped, codex-cli ran
326
+ * and failed with no identity of its own, the stamp used to be re-guessed
327
+ * as claude-code by the caller).
328
+ */
329
+ function withIdentity(id, result) {
330
+ if (hasUsableIdentity(result && result.identity)) return result;
331
+ return { ...result, identity: deriveIdentity(id, result) };
332
+ }
333
+
248
334
  /**
249
335
  * runModel(opts) — resolve a starting provider via resolveProvider(), then
250
336
  * walk PROVIDER_ORDER from there, calling each candidate's OWN runModel()
@@ -262,7 +348,9 @@ const NON_RETRYABLE_FOR_THIS_PROVIDER = new Set([
262
348
  * as-is. When every provider tried is exhausted, returns reasonCode
263
349
  * 'no-usable-provider' with a bounded summary of every provider's reason in
264
350
  * `reason` (no secrets — each provider's own reason string is already
265
- * secret-free by contract). Never throws.
351
+ * secret-free by contract) and NO identity — nothing actually ran to
352
+ * completion, so the caller (extract-local.js) stamps `provider:'unknown'`
353
+ * rather than have this registry guess one. Never throws.
266
354
  */
267
355
  async function runModel(opts = {}) {
268
356
  const mode = opts.mode === 'judge' ? 'judge' : 'extract';
@@ -281,7 +369,8 @@ async function runModel(opts = {}) {
281
369
  authStatus: 'unknown',
282
370
  };
283
371
  }
284
- return resolved.module.runModel({ ...opts, mode });
372
+ const result = await resolved.module.runModel({ ...opts, mode });
373
+ return withIdentity(resolved.id, result);
285
374
  }
286
375
 
287
376
  const log = typeof opts.log === 'function' ? opts.log : console.error;
@@ -293,7 +382,8 @@ async function runModel(opts = {}) {
293
382
  for (const id of order) {
294
383
  const mod = PROVIDERS[id];
295
384
  // eslint-disable-next-line no-await-in-loop
296
- const result = await mod.runModel({ ...opts, mode });
385
+ const rawResult = await mod.runModel({ ...opts, mode });
386
+ const result = withIdentity(id, rawResult);
297
387
  if (result.ok) return result;
298
388
  attempts.push({ id, reasonCode: result.reasonCode, reason: result.reason });
299
389
  if (!NON_RETRYABLE_FOR_THIS_PROVIDER.has(result.reasonCode)) {