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.
- package/README.md +8 -8
- package/bin/auxilo-cli.js +416 -14
- package/lib/installer.js +102 -2
- package/mcp-server.js +8 -8
- package/package.json +2 -1
- package/scripts/extract-local.js +134 -212
- package/scripts/providers/byo-key.js +631 -0
- package/scripts/providers/claude-code.js +477 -0
- package/scripts/providers/codex-cli.js +358 -0
- package/scripts/providers/index.js +316 -0
- package/scripts/providers/provider.interface.js +98 -0
- package/scripts/providers/schemas/extraction-envelope.schema.json +42 -0
- package/scripts/providers/schemas/judge-decisions.schema.json +20 -0
- package/scripts/runner.js +32 -0
|
@@ -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,
|