auxilo-mcp 0.9.11 → 0.9.13

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.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * scripts/providers/provider.interface.js — Model Provider Interface (EXTRACT-PER-CLIENT W1 PART A)
3
+ *
4
+ * Doc-only contract, no runtime logic — mirrors scripts/sources/source.interface.js's
5
+ * role for transcript sources, but for the model that DOES the extraction/dedup-judge
6
+ * work. A provider module (scripts/providers/claude-code.js, codex-cli.js, byo-key.js)
7
+ * is any object exposing the shape documented below; there is no base class to extend
8
+ * because every provider's actual invocation mechanics (CLI spawn vs HTTP call) differ
9
+ * too much to share an implementation, only the contract.
10
+ *
11
+ * @module providers/provider.interface
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ /**
17
+ * @typedef {'extract'|'judge'} RunModelMode
18
+ * 'extract' (default) — draft learnings from a transcript.
19
+ * 'judge' — binary anchored-dedup decision against previously captured lessons.
20
+ * A CLI provider may pick a different argv/parsing variant per mode (Claude Code
21
+ * does — extraction and judge invocations differ today). A provider that has no
22
+ * mode-specific behavior (e.g. codex-cli, PART B) may ignore `mode` and always use
23
+ * the same invocation shape.
24
+ */
25
+
26
+ /**
27
+ * @typedef {object} RunModelUsage
28
+ * @property {number} input_tokens
29
+ * @property {number} output_tokens
30
+ */
31
+
32
+ /**
33
+ * @typedef {object} RunModelOptions
34
+ * @property {string} prompt - The instruction text. Combined with `input` as the
35
+ * model's full instruction: `prompt + (input || '')` — stdin for CLI providers,
36
+ * message body for HTTP providers. This is the existing convention
37
+ * `extractWithClaudeCode` already used; providers do not change it.
38
+ * @property {string} [input] - The transcript (extract mode) or empty (judge mode,
39
+ * where the full payload already lives in `prompt`).
40
+ * @property {number} [timeoutMs] - Invocation timeout in milliseconds.
41
+ * @property {Function} [log] - Logger sink, `(line: string) => void`.
42
+ * @property {Function} [spawnSyncImpl] - Injectable `child_process.spawnSync`
43
+ * replacement (CLI providers only) — the test-injection seam.
44
+ * @property {Function} [fetchImpl] - Injectable `fetch` replacement (HTTP providers
45
+ * only) — the test-injection seam.
46
+ * @property {RunModelMode} [mode] - 'extract' (default) or 'judge'.
47
+ * @property {object} [schema] - Optional JSON-Schema hint a provider MAY use to
48
+ * constrain its output. Claude Code ignores this — `extractJsonValue`'s
49
+ * fence-strip + brace-scan parser is already model-agnostic and needs no schema
50
+ * hint to do its job.
51
+ */
52
+
53
+ /**
54
+ * @typedef {object} RunModelResult
55
+ * @property {boolean} ok
56
+ * @property {string} text - Raw model output (empty string on failure).
57
+ * @property {RunModelUsage|null} usage - Normalized token usage, or null when the
58
+ * provider cannot report it (e.g. codex-cli's `-o` file carries no token counts).
59
+ * Callers that need an estimate when usage is null do their own text-length
60
+ * fallback (see scripts/extract-local.js's judge-usage bookkeeping) — providers
61
+ * are not required to estimate on the caller's behalf.
62
+ * @property {string} [reasonCode] - Machine-matchable failure/skip classifier
63
+ * (e.g. 'cli-unauthenticated', 'cli-billing-helper-configured', 'model-error',
64
+ * 'unknown'). Present on both success and failure paths where applicable.
65
+ * @property {string|null} [reason] - Human-readable reason, present when !ok.
66
+ * @property {string} [authStatus] - 'logged-in' | 'logged-out' | 'unknown', when
67
+ * the provider has a meaningful concept of local auth state.
68
+ * @property {object} [identity] - {provider, model, version, vendor} — which
69
+ * provider/model actually ran this call, for the extraction_model stamp
70
+ * (scripts/extract-local.js's resolveExtractionModelIdentity reads THIS
71
+ * field, not any other name). byo-key.js always sets it (full identity,
72
+ * read from its stored config). codex-cli.js sets it (provider/version;
73
+ * model/vendor null — codex exposes no per-call model id without --json).
74
+ * claude-code.js does not set one yet; resolveExtractionModelIdentity
75
+ * falls back to the resolved provider id alone when absent (EXTRACT-PER-
76
+ * CLIENT W1 FIX GATE-A item (a) — codex-cli.js used to export this same
77
+ * data under the field name `extraction_model`, which nothing here ever
78
+ * read, so its real version silently never reached the stamp; that field
79
+ * name survives one more release as a deprecated alias of `identity`).
80
+ */
81
+
82
+ /**
83
+ * @callback RunModel
84
+ * @param {RunModelOptions} opts
85
+ * @returns {Promise<RunModelResult>}
86
+ */
87
+
88
+ /**
89
+ * Contract a provider module implements:
90
+ * - runModel(opts) → Promise<RunModelResult> (see typedefs above)
91
+ * - detect(opts) → boolean|Promise<boolean> (installed/usable on this host?)
92
+ * - checkAuthStatus(opts) → string ('logged-in'|'logged-out'|'unknown')
93
+ *
94
+ * No base class — see module comment. Nothing here is imported for its runtime
95
+ * value; this file exists so the contract has one canonical, grep-able location.
96
+ */
97
+
98
+ module.exports = {};
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$comment": "EXTRACT-PER-CLIENT W1 PART B — passed to `codex exec --output-schema` for mode:'extract'. Mirrors scripts/extract-local.js's parseExtractionOutput (line 252) and normalizeLearningArray (line 208, title>=10 chars / body>=50 chars gate at the filter on line ~211-212) exactly as they exist at this wave's base. A bare array of the same learning shape is ALSO accepted by the parser (parseExtractionOutput's Array.isArray(value) branch) — hence the oneOf. This file is a HINT to codex, not the parser: extract-local.js's extractJsonValue + parseExtractionOutput remain the actual source of truth and are model-agnostic already. Keep this file in sync if that shape ever changes; a schema/parser drift fails soft (codex ignores an unmet hint, parseExtractionOutput still runs) rather than throwing.",
4
+ "oneOf": [
5
+ { "type": "array", "items": { "$ref": "#/definitions/learning" } },
6
+ {
7
+ "type": "object",
8
+ "properties": {
9
+ "learnings": { "type": "array", "items": { "$ref": "#/definitions/learning" } },
10
+ "dedup_drops": { "type": "array", "items": {} }
11
+ },
12
+ "required": ["learnings"]
13
+ }
14
+ ],
15
+ "definitions": {
16
+ "learning": {
17
+ "type": "object",
18
+ "properties": {
19
+ "title": { "type": "string", "minLength": 10 },
20
+ "body": { "type": "string", "minLength": 50 },
21
+ "category": {
22
+ "type": "string",
23
+ "enum": ["data-processing", "web-interaction", "code-execution", "storage-state", "payment-financial", "monitoring", "non-technical"]
24
+ },
25
+ "tags": { "type": "array", "items": { "type": "string" }, "maxItems": 8 },
26
+ "task_context": { "type": "string" },
27
+ "outcome": { "type": "string", "enum": ["success", "partial", "failure", "workaround"] },
28
+ "quality_self_assessment": {
29
+ "type": "object",
30
+ "properties": {
31
+ "specificity": { "type": "integer", "minimum": 1, "maximum": 5 },
32
+ "actionability": { "type": "integer", "minimum": 1, "maximum": 5 },
33
+ "novelty": { "type": "integer", "minimum": 1, "maximum": 5 },
34
+ "completeness": { "type": "integer", "minimum": 1, "maximum": 5 },
35
+ "total": { "type": "integer" }
36
+ }
37
+ }
38
+ },
39
+ "required": ["title", "body", "category"]
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$comment": "EXTRACT-PER-CLIENT W1 PART B — passed to `codex exec --output-schema` for mode:'judge'. Mirrors scripts/extract-local.js's parseJudgeDecisions (line 447): one decision object per candidate, keyed by candidate_index, matched_index_id only meaningful when duplicate:true. The 'required id must be inside the candidate's own top-K rankings list' rule and the 'exactly one decision per candidate, no duplicate indices' rule are enforced by parseJudgeDecisions itself, not expressible in JSON Schema — a schema-conformant response can still fail the parser. This file is a HINT to codex; parseJudgeDecisions remains the source of truth.",
4
+ "type": "object",
5
+ "properties": {
6
+ "decisions": {
7
+ "type": "array",
8
+ "items": {
9
+ "type": "object",
10
+ "properties": {
11
+ "candidate_index": { "type": "integer", "minimum": 0 },
12
+ "duplicate": { "type": "boolean" },
13
+ "matched_index_id": { "type": "string" }
14
+ },
15
+ "required": ["candidate_index", "duplicate"]
16
+ }
17
+ }
18
+ },
19
+ "required": ["decisions"]
20
+ }
package/scripts/runner.js CHANGED
@@ -243,6 +243,12 @@ function zeroExtractionSkipState() {
243
243
  first_skip_at: null,
244
244
  last_skip_at: null,
245
245
  last_alert_at: null,
246
+ // EXTRACT-PER-CLIENT W1 PART C: the reasonCode of the most recent
247
+ // extraction outcome (skip or real), so `auxilo status` can print WHY
248
+ // (cli-billing-helper-configured / cli-unauthenticated /
249
+ // no-model-provider-available) without re-running extraction. null on a
250
+ // real full success (nothing to report) and on a fresh/zeroed state.
251
+ last_reason_code: null,
246
252
  };
247
253
  }
248
254
 
@@ -263,12 +269,17 @@ function normalizeExtractionSkipState(value) {
263
269
  for (const key of ['first_skip_at', 'last_skip_at', 'last_alert_at']) {
264
270
  if (!isCanonicalIsoTimestamp(value[key])) return null;
265
271
  }
272
+ // PART C: optional, back-compat with every state file written before this
273
+ // field existed — absent (old shape) normalizes to null, not a rejection.
274
+ const lastReasonCode = value.last_reason_code === undefined ? null : value.last_reason_code;
275
+ if (lastReasonCode !== null && typeof lastReasonCode !== 'string') return null;
266
276
  return {
267
277
  consecutive_skips: value.consecutive_skips,
268
278
  consecutive_unknown: consecutiveUnknown,
269
279
  first_skip_at: value.first_skip_at,
270
280
  last_skip_at: value.last_skip_at,
271
281
  last_alert_at: value.last_alert_at,
282
+ last_reason_code: lastReasonCode,
272
283
  };
273
284
  }
274
285
 
@@ -328,6 +339,19 @@ function resolveSkipAlertThreshold(value, env = process.env) {
328
339
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_SKIP_ALERT_THRESHOLD;
329
340
  }
330
341
 
342
+ /**
343
+ * PART C: the reasonCode of the last row in this batch that carries one
344
+ * (rows are processed in order; postExtractDetailed sets reasonCode:null on
345
+ * a real success, per the shape at its two return sites above). Used only to
346
+ * populate last_reason_code for `auxilo status` — never affects streak logic.
347
+ */
348
+ function latestReasonCode(rows) {
349
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
350
+ if (rows[i] && typeof rows[i].reasonCode === 'string') return rows[i].reasonCode;
351
+ }
352
+ return null;
353
+ }
354
+
331
355
  async function finalizeExtractionRun(outcomes, opts = {}) {
332
356
  const rows = Array.isArray(outcomes) ? outcomes.filter(Boolean) : [];
333
357
  const state = opts.state
@@ -345,6 +369,7 @@ async function finalizeExtractionRun(outcomes, opts = {}) {
345
369
  // The streak resets, but alert delivery remains globally deduplicated
346
370
  // for 20h even if a real extraction lands between two skip streaks.
347
371
  last_alert_at: state.last_alert_at,
372
+ last_reason_code: latestReasonCode(rows),
348
373
  };
349
374
  persistExtractionSkipState(reset, opts);
350
375
  return reset;
@@ -360,6 +385,7 @@ async function finalizeExtractionRun(outcomes, opts = {}) {
360
385
  ...state,
361
386
  // R1: UNKNOWN never increments, resets, or alerts on the auth streak.
362
387
  consecutive_unknown: state.consecutive_unknown + 1,
388
+ last_reason_code: latestReasonCode(rows),
363
389
  };
364
390
  persistExtractionSkipState(unknown, opts);
365
391
  return unknown;
@@ -374,6 +400,7 @@ async function finalizeExtractionRun(outcomes, opts = {}) {
374
400
  : timestamp,
375
401
  last_skip_at: timestamp,
376
402
  last_alert_at: state.last_alert_at,
403
+ last_reason_code: latestReasonCode(rows),
377
404
  };
378
405
  const threshold = resolveSkipAlertThreshold(opts.threshold, opts.env || process.env);
379
406
  const lastAlertMs = next.last_alert_at ? Date.parse(next.last_alert_at) : NaN;
@@ -557,6 +584,10 @@ async function submitLearnings(learnings, sourceType, opts = {}) {
557
584
  submission_channel: 'extraction',
558
585
  ...(captureVisibility === 'private' && { visibility: 'private' }),
559
586
  ...(l.quality_self_assessment && { quality_self_assessment: l.quality_self_assessment }),
587
+ // EXTRACT-PER-CLIENT W1 PART C: which provider/model extracted this
588
+ // learning (extract-local.js attaches it per-learning; absent for
589
+ // pre-PART-C learnings and for the sourceless direct-submit paths).
590
+ ...(l.extraction_model && { extraction_model: l.extraction_model }),
560
591
  }),
561
592
  });
562
593
  if (!res.ok) { rejected += 1; continue; }
@@ -1511,6 +1542,7 @@ module.exports = {
1511
1542
  parseArgs, writeQueueFile, deleteQueueFile, listPendingFiles,
1512
1543
  loadLedger, saveLedger, ledgerHighWater, ledgerHas, ledgerMark,
1513
1544
  loadExtractionSkipState, saveExtractionSkipState, finalizeExtractionRun,
1545
+ normalizeExtractionSkipState, zeroExtractionSkipState,
1514
1546
  isSkippedExtraction, recordRealExtractionCompletion, persistRealExtractionCompletion,
1515
1547
  renderExtractionStatus, renderExtractionResult,
1516
1548
  installHooks, installSweeper, installDigest, printStatus, scrubAndVerify, enumerateActiveSources,