auxilo-mcp 0.9.6 → 0.9.7
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 -6
- package/bin/auxilo-cli.js +172 -41
- package/lib/hook-status.js +24 -0
- package/lib/installer.js +11 -3
- package/lib/review.js +70 -16
- package/mcp-server.js +1 -1
- package/package.json +2 -1
- package/scripts/runner.js +4 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Enable extraction and a session-end hook runs when your agent finishes a session
|
|
|
33
33
|
1. The hook hands the runner the path to the session transcript.
|
|
34
34
|
2. The runner reads the transcript on your machine and scrubs it with a fail-closed secret filter: 24 patterns covering API keys, tokens, private keys, JWTs, connection strings, cookies, email addresses, phone numbers, and internal IPs. If a rescan still finds a match, the run stops and nothing is sent.
|
|
35
35
|
3. Your own model client (your claude CLI, on your subscription) drafts learnings from the scrubbed text and screens them again.
|
|
36
|
-
4.
|
|
36
|
+
4. Drafts held for review appear in three server-defined lanes: **Ready to publish**, **Needs a score**, and **Needs your eyes**. The last lane includes the server's reason so you can inspect the exception directly.
|
|
37
37
|
|
|
38
38
|
### What never leaves your machine
|
|
39
39
|
|
|
@@ -41,15 +41,17 @@ Your raw transcripts. They are read and scrubbed on your machine, and they are n
|
|
|
41
41
|
|
|
42
42
|
### How publishing works
|
|
43
43
|
|
|
44
|
-
Extraction defaults to seamless: a draft that passes every screen (secrets, sensitivity, injection, near-duplicate, quality) publishes right away, and you can retract it for 7 days. A draft
|
|
44
|
+
Extraction defaults to seamless: a draft that passes every screen (secrets, sensitivity, injection, near-duplicate, quality) publishes right away, and you can retract it for 7 days. A draft held for review waits in a pending queue only you can see, grouped by the server's Ready to publish / Needs a score / Needs your eyes verdict.
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npx auxilo review
|
|
48
|
-
npx auxilo
|
|
49
|
-
npx auxilo
|
|
47
|
+
npx auxilo review --list # show all three lanes and each exception reason
|
|
48
|
+
npx auxilo review --approve-ready # select exactly the server's ready_to_publish lane
|
|
49
|
+
npx auxilo review # approve, reject, view, or skip one draft at a time
|
|
50
|
+
npx auxilo status # clients, hooks, queue depth, consent state
|
|
51
|
+
npx auxilo disable # kill switch: extraction stops immediately
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
Approve a queued draft and it goes live in the marketplace. Reject it and it stays private. Prefer approve-first for everything? Switch your account to manual mode in account settings and every draft waits for you.
|
|
54
|
+
Every bulk approval prints the exact selection and requires you to type its count. `--min-quality 16` narrows the ready lane; values below 14 explicitly reach into Needs a score and print a warning before the same counted confirmation. Approve a queued draft and it goes live in the marketplace. Reject it and it stays private. Prefer approve-first for everything? Switch your account to manual mode in account settings and every draft waits for you.
|
|
53
55
|
|
|
54
56
|
Extraction off? Your agent can still contribute in-session: tell it to submit a learning with the `auxilo_contribute` tool.
|
|
55
57
|
|
package/bin/auxilo-cli.js
CHANGED
|
@@ -32,19 +32,45 @@ const HOME = os.homedir();
|
|
|
32
32
|
// question N+1 arrives with question N's buffer and the next prompt hangs
|
|
33
33
|
// on EOF (the 0.8.1 consent step silently never completed).
|
|
34
34
|
let sharedRl = null;
|
|
35
|
+
let bufferedLines = [];
|
|
36
|
+
let lineWaiters = [];
|
|
37
|
+
let readlineEnded = false;
|
|
35
38
|
function getRl() {
|
|
36
39
|
if (!sharedRl) {
|
|
37
|
-
|
|
40
|
+
readlineEnded = false;
|
|
41
|
+
sharedRl = readline.createInterface({
|
|
42
|
+
input: process.stdin,
|
|
43
|
+
output: process.stdout,
|
|
44
|
+
terminal: !!process.stdin.isTTY,
|
|
45
|
+
});
|
|
46
|
+
sharedRl.on('line', (line) => {
|
|
47
|
+
const answer = line.trim();
|
|
48
|
+
const waiter = lineWaiters.shift();
|
|
49
|
+
if (waiter) waiter(answer);
|
|
50
|
+
else bufferedLines.push(answer);
|
|
51
|
+
});
|
|
52
|
+
sharedRl.on('close', () => {
|
|
53
|
+
readlineEnded = true;
|
|
54
|
+
while (lineWaiters.length) lineWaiters.shift()('');
|
|
55
|
+
});
|
|
38
56
|
}
|
|
39
57
|
return sharedRl;
|
|
40
58
|
}
|
|
41
59
|
function closeRl() {
|
|
42
|
-
if (sharedRl)
|
|
60
|
+
if (sharedRl) sharedRl.close();
|
|
61
|
+
sharedRl = null;
|
|
62
|
+
bufferedLines = [];
|
|
63
|
+
lineWaiters = [];
|
|
64
|
+
readlineEnded = false;
|
|
43
65
|
}
|
|
44
66
|
|
|
45
67
|
function ask(question) {
|
|
68
|
+
getRl();
|
|
69
|
+
process.stdout.write(question);
|
|
70
|
+
if (bufferedLines.length > 0) return Promise.resolve(bufferedLines.shift());
|
|
71
|
+
if (readlineEnded) return Promise.resolve('');
|
|
46
72
|
return new Promise((resolve) => {
|
|
47
|
-
|
|
73
|
+
lineWaiters.push(resolve);
|
|
48
74
|
});
|
|
49
75
|
}
|
|
50
76
|
|
|
@@ -509,7 +535,7 @@ async function cmdDisable(flags) {
|
|
|
509
535
|
// Operates ONLY on the caller's own pending learnings (account-scoped API key).
|
|
510
536
|
//
|
|
511
537
|
// Review-seamless (2026-07-18): the default view is now a triage summary table
|
|
512
|
-
// (quality
|
|
538
|
+
// (quality-desc rows grouped into the server's three lanes), with bulk modes that batch through
|
|
513
539
|
// POST /account/pending/bulk. HARD RULE for every bulk APPROVE path: print the
|
|
514
540
|
// exact list and count first, then require the operator to TYPE THE COUNT.
|
|
515
541
|
// There is no --yes bypass on any approve path; publishing always costs one
|
|
@@ -522,10 +548,18 @@ function fit(s, width) {
|
|
|
522
548
|
return str.padEnd(width);
|
|
523
549
|
}
|
|
524
550
|
|
|
525
|
-
/** Short flag codes for a summary row
|
|
551
|
+
/** Short lane/flag codes for a summary row, e.g. 'ready' or 'inj+sens'. */
|
|
526
552
|
function shortFlags(row) {
|
|
527
|
-
|
|
528
|
-
|
|
553
|
+
const resolved = review.reviewLane(row);
|
|
554
|
+
if (resolved.lane === 'ready_to_publish') return 'ready';
|
|
555
|
+
if (resolved.lane === 'needs_score') return 'score';
|
|
556
|
+
const map = {
|
|
557
|
+
injection: 'inj',
|
|
558
|
+
content_sensitivity: 'sens',
|
|
559
|
+
near_duplicate: 'dup',
|
|
560
|
+
process_advice: 'advice',
|
|
561
|
+
account_vocab: 'vocab',
|
|
562
|
+
};
|
|
529
563
|
return (row.flags || []).map((f) => map[f] || f).join('+') || 'flagged';
|
|
530
564
|
}
|
|
531
565
|
|
|
@@ -535,13 +569,41 @@ function triageLine(row, n, total) {
|
|
|
535
569
|
return ` ${String(n).padStart(String(total).length)}. q=${q} ${fit(shortFlags(row), 9)} ${fit(row.category || '', 20)} ${fit(row.title || '(no title)', 52)}`;
|
|
536
570
|
}
|
|
537
571
|
|
|
538
|
-
|
|
572
|
+
function groupSummaryRows(summary) {
|
|
573
|
+
const groups = {
|
|
574
|
+
ready_to_publish: [],
|
|
575
|
+
needs_score: [],
|
|
576
|
+
needs_your_eyes: [],
|
|
577
|
+
};
|
|
578
|
+
let versionSkew = false;
|
|
579
|
+
for (const row of summary.items || []) {
|
|
580
|
+
const resolved = review.reviewLane(row);
|
|
581
|
+
versionSkew = versionSkew || resolved.version_skew;
|
|
582
|
+
groups[resolved.lane].push(row);
|
|
583
|
+
}
|
|
584
|
+
const serverCounts = summary.counts && summary.counts.by_lane;
|
|
585
|
+
const counts = !versionSkew && serverCounts
|
|
586
|
+
? {
|
|
587
|
+
ready_to_publish: serverCounts.ready_to_publish || 0,
|
|
588
|
+
needs_score: serverCounts.needs_score || 0,
|
|
589
|
+
needs_your_eyes: serverCounts.needs_your_eyes || 0,
|
|
590
|
+
}
|
|
591
|
+
: {
|
|
592
|
+
ready_to_publish: groups.ready_to_publish.length,
|
|
593
|
+
needs_score: groups.needs_score.length,
|
|
594
|
+
needs_your_eyes: groups.needs_your_eyes.length,
|
|
595
|
+
};
|
|
596
|
+
return { groups, counts, versionSkew };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Print the triage summary in the server's three-lane order. */
|
|
539
600
|
function printSummaryTable(summary) {
|
|
540
|
-
const
|
|
541
|
-
const clean = items.filter((r) => r.screens_passed);
|
|
542
|
-
const flagged = items.filter((r) => !r.screens_passed);
|
|
601
|
+
const { groups, counts, versionSkew } = groupSummaryRows(summary);
|
|
543
602
|
|
|
544
|
-
console.log(`\n${summary.pending_count} learning(s) pending your review: ${
|
|
603
|
+
console.log(`\n${summary.pending_count} learning(s) pending your review: ${counts.ready_to_publish} ready to publish, ${counts.needs_score} need a score, ${counts.needs_your_eyes} need your eyes.`);
|
|
604
|
+
if (versionSkew) {
|
|
605
|
+
console.log('VERSION SKEW: this server did not return lane on every row; using the legacy screens-and-quality fallback.');
|
|
606
|
+
}
|
|
545
607
|
const bands = summary.counts && summary.counts.by_quality_band;
|
|
546
608
|
if (bands) {
|
|
547
609
|
console.log(`Quality bands: 18-20: ${bands['18-20'] || 0} · 14-17: ${bands['14-17'] || 0} · 10-13: ${bands['10-13'] || 0} · below 10: ${bands.below_10 || 0} · unscored: ${bands.unscored || 0}`);
|
|
@@ -550,13 +612,20 @@ function printSummaryTable(summary) {
|
|
|
550
612
|
console.log(`Near-duplicate clusters among your pending items: ${summary.near_dup_clusters.length} (review these together)`);
|
|
551
613
|
}
|
|
552
614
|
|
|
553
|
-
if (
|
|
554
|
-
console.log(`\
|
|
555
|
-
|
|
615
|
+
if (groups.ready_to_publish.length > 0) {
|
|
616
|
+
console.log(`\nREADY TO PUBLISH (${counts.ready_to_publish}) - sorted by quality:`);
|
|
617
|
+
groups.ready_to_publish.forEach((r, i) => console.log(triageLine(r, i + 1, groups.ready_to_publish.length)));
|
|
556
618
|
}
|
|
557
|
-
if (
|
|
558
|
-
console.log(`\
|
|
559
|
-
|
|
619
|
+
if (groups.needs_score.length > 0) {
|
|
620
|
+
console.log(`\nNEEDS A SCORE (${counts.needs_score}) - score or review individually:`);
|
|
621
|
+
groups.needs_score.forEach((r, i) => console.log(triageLine(r, i + 1, groups.needs_score.length)));
|
|
622
|
+
}
|
|
623
|
+
if (groups.needs_your_eyes.length > 0) {
|
|
624
|
+
console.log(`\nNEEDS YOUR EYES (${counts.needs_your_eyes}) - review individually:`);
|
|
625
|
+
groups.needs_your_eyes.forEach((r, i) => {
|
|
626
|
+
console.log(triageLine(r, i + 1, groups.needs_your_eyes.length));
|
|
627
|
+
if (r.why) console.log(` why: ${r.why}`);
|
|
628
|
+
});
|
|
560
629
|
}
|
|
561
630
|
console.log('');
|
|
562
631
|
}
|
|
@@ -640,32 +709,43 @@ async function cmdReview(flags) {
|
|
|
640
709
|
// ── --list: summary only, no mutations ────────────────────────────────────
|
|
641
710
|
if (flags.list) return;
|
|
642
711
|
|
|
643
|
-
// ── --approve-
|
|
644
|
-
//
|
|
645
|
-
|
|
712
|
+
// ── --approve-ready: consume the server's ready_to_publish verdict.
|
|
713
|
+
// --approve-clean remains a hidden compatibility alias. A stricter
|
|
714
|
+
// threshold narrows the ready lane; a lower one explicitly reaches into
|
|
715
|
+
// needs_score. Typed-count confirmation, no bypass. ─────────────────────
|
|
716
|
+
if (flags['approve-ready'] || flags['approve-clean']) {
|
|
717
|
+
if (flags['approve-clean']) {
|
|
718
|
+
console.log('Note: --approve-clean was renamed to --approve-ready; the old flag remains a compatibility alias.');
|
|
719
|
+
}
|
|
646
720
|
const minQuality = parseMinQuality(flags);
|
|
647
|
-
const sel = review.selectForBulkApprove(rows, { mode: '
|
|
648
|
-
console.log(`approve-
|
|
649
|
-
if (
|
|
721
|
+
const sel = review.selectForBulkApprove(rows, { mode: 'ready', minQuality });
|
|
722
|
+
console.log(`approve-ready selection: ${sel.selected.length} of ${rows.length} pending (threshold: quality >= ${minQuality}).`);
|
|
723
|
+
if (minQuality < review.DEFAULT_QUALITY_THRESHOLD) {
|
|
724
|
+
const approvableCount = Number.isFinite(summary.approvable_count)
|
|
725
|
+
? summary.approvable_count
|
|
726
|
+
: review.selectForBulkApprove(rows, { mode: 'ready' }).selected.length;
|
|
727
|
+
console.log(`WARNING: selection goes beyond the server's approvable verdict (approvable_count=${approvableCount}); including ${sel.included_beyond_verdict.length} items from needs_score.`);
|
|
728
|
+
}
|
|
729
|
+
if (sel.excluded_flagged.length) console.log(` excluded ${sel.excluded_flagged.length} needs_your_eyes item(s) (review those individually).`);
|
|
650
730
|
if (sel.excluded_low_quality.length) console.log(` excluded ${sel.excluded_low_quality.length} below the quality threshold.`);
|
|
651
731
|
if (sel.excluded_unscored.length) console.log(` excluded ${sel.excluded_unscored.length} with no quality score (pass --min-quality 0 to include).`);
|
|
652
732
|
if (sel.selected.length === 0) { console.log('Nothing qualifies. No changes made.'); return; }
|
|
653
733
|
|
|
654
734
|
if (!await confirmByTypedCount(sel.selected, 'APPROVE and PUBLISH')) return;
|
|
655
735
|
const totals = await runBulk({ apiKey, baseUrl, rows: sel.selected, decision: 'approve' });
|
|
656
|
-
console.log(`\nDone. Approved ${totals.approved} (already approved earlier: ${totals.idempotent}, failed: ${totals.failed}).
|
|
736
|
+
console.log(`\nDone. Approved ${totals.approved} (already approved earlier: ${totals.idempotent}, failed: ${totals.failed}). Items outside the selection stay pending.`);
|
|
657
737
|
return;
|
|
658
738
|
}
|
|
659
739
|
|
|
660
|
-
// ── --all: bulk-approve everything EXCEPT
|
|
740
|
+
// ── --all: bulk-approve everything EXCEPT needs_your_eyes items unless
|
|
661
741
|
// --include-flagged. Typed-count confirmation, no bypass. ───────────────
|
|
662
742
|
if (flags.all) {
|
|
663
743
|
const includeFlagged = flags['include-flagged'] === true;
|
|
664
744
|
const sel = review.selectForBulkApprove(rows, { mode: 'all', includeFlagged });
|
|
665
|
-
if (includeFlagged && sel.selected.some((r) =>
|
|
666
|
-
console.log('WARNING: --include-flagged is set. This selection INCLUDES items
|
|
745
|
+
if (includeFlagged && sel.selected.some((r) => review.reviewLane(r).lane === 'needs_your_eyes')) {
|
|
746
|
+
console.log('WARNING: --include-flagged is set. This selection INCLUDES needs_your_eyes items (possible injection, sensitive content, process advice, account vocabulary, or near-duplicates). Approving publishes them publicly.');
|
|
667
747
|
} else if (sel.excluded_flagged.length) {
|
|
668
|
-
console.log(`Excluding ${sel.excluded_flagged.length}
|
|
748
|
+
console.log(`Excluding ${sel.excluded_flagged.length} needs_your_eyes item(s) (pass --include-flagged to include them; safer to review those individually).`);
|
|
669
749
|
}
|
|
670
750
|
if (sel.selected.length === 0) { console.log('Nothing qualifies. No changes made.'); return; }
|
|
671
751
|
|
|
@@ -707,13 +787,14 @@ async function cmdReview(flags) {
|
|
|
707
787
|
console.log('Rapid review. Per item: [y] approve (goes live) · [n] reject (stays private) · [v] view full body · [s] skip · [q] quit\n');
|
|
708
788
|
|
|
709
789
|
let approved = 0, rejected = 0, skipped = 0;
|
|
710
|
-
const ordered = rows; //
|
|
790
|
+
const ordered = rows; // quality-desc server order; the summary above groups lanes
|
|
711
791
|
for (let i = 0; i < ordered.length; i++) {
|
|
712
792
|
const row = ordered[i];
|
|
713
793
|
console.log(triageLine(row, i + 1, ordered.length));
|
|
714
794
|
let choice = '';
|
|
715
795
|
for (;;) {
|
|
716
796
|
choice = (await ask(' [y/n/v/s/q]? ')).toLowerCase().slice(0, 1);
|
|
797
|
+
if (!choice && readlineEnded) choice = 'q';
|
|
717
798
|
if (choice === 'v') {
|
|
718
799
|
const full = bodies.get(row.id);
|
|
719
800
|
console.log(' ------------------------------------------------------------');
|
|
@@ -747,7 +828,46 @@ async function cmdReview(flags) {
|
|
|
747
828
|
|
|
748
829
|
// ─── Entry point ────────────────────────────────────────────────────────────
|
|
749
830
|
|
|
750
|
-
function usage() {
|
|
831
|
+
function usage(command) {
|
|
832
|
+
const blocks = {
|
|
833
|
+
setup: `Usage: auxilo setup [--re-auth] [--base-url <url>]
|
|
834
|
+
|
|
835
|
+
Interactively detect clients, register Auxilo, sign in, install the optional
|
|
836
|
+
extraction runner and SessionEnd hook, and record the extraction choice.`,
|
|
837
|
+
init: `Usage: auxilo init [--scope <read|earnings-read|contribute>] [--label <name>]
|
|
838
|
+
[--env-file <path>] [--save] [--json] [--no-browser]
|
|
839
|
+
[--base-url <url>]
|
|
840
|
+
|
|
841
|
+
Mint a scoped API key for CI or a second machine without running full setup.`,
|
|
842
|
+
status: `Usage: auxilo status
|
|
843
|
+
|
|
844
|
+
Show detected clients, auth, extraction mode, kill switch, SessionEnd hook,
|
|
845
|
+
last sweep, and pending queue depth.`,
|
|
846
|
+
review: `Usage: auxilo review [flags]
|
|
847
|
+
|
|
848
|
+
Render YOUR pending-review learnings in three server-defined lanes:
|
|
849
|
+
Ready to publish, Needs a score, and Needs your eyes.
|
|
850
|
+
|
|
851
|
+
Flags:
|
|
852
|
+
--list show the three-lane summary only; make no changes
|
|
853
|
+
--approve-ready select the server's ready_to_publish lane (default quality
|
|
854
|
+
floor 14; a higher --min-quality narrows it, while a lower
|
|
855
|
+
value explicitly reaches into needs_score)
|
|
856
|
+
--min-quality N quality threshold for --approve-ready (0-20)
|
|
857
|
+
--all approve everything except needs_your_eyes items
|
|
858
|
+
--include-flagged include needs_your_eyes with --all
|
|
859
|
+
--all-reject reject the whole batch [--yes for scripted incident use]
|
|
860
|
+
--base-url <url>
|
|
861
|
+
|
|
862
|
+
Every approval path prints the exact list and requires typing its count.`,
|
|
863
|
+
disable: `Usage: auxilo disable [--base-url <url>]
|
|
864
|
+
|
|
865
|
+
Disable background extraction locally and optionally revoke server consent.`,
|
|
866
|
+
};
|
|
867
|
+
if (command && blocks[command]) {
|
|
868
|
+
console.log(`\n${blocks[command]}\n`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
751
871
|
console.log(`
|
|
752
872
|
Usage: auxilo <command>
|
|
753
873
|
|
|
@@ -771,18 +891,17 @@ Commands:
|
|
|
771
891
|
status Show install/auth/extraction status.
|
|
772
892
|
review Review YOUR pending-review learnings (from background extraction)
|
|
773
893
|
and approve/reject before anything goes public. Default: triage
|
|
774
|
-
summary
|
|
775
|
-
y/n/s review.
|
|
894
|
+
summary first in the server's Ready to publish / Needs a score /
|
|
895
|
+
Needs your eyes lanes, then rapid y/n/s review.
|
|
776
896
|
Flags:
|
|
777
897
|
--list summary table only, no changes
|
|
778
|
-
--approve-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
items (add --include-flagged to include them).
|
|
898
|
+
--approve-ready select the server's ready_to_publish lane
|
|
899
|
+
(default floor 14; tune with --min-quality N).
|
|
900
|
+
Prints the exact list, then requires its count.
|
|
901
|
+
--all bulk-approve everything EXCEPT needs_your_eyes
|
|
902
|
+
(add --include-flagged to include that lane).
|
|
784
903
|
Same typed-count confirmation.
|
|
785
|
-
--min-quality N quality threshold for --approve-
|
|
904
|
+
--min-quality N quality threshold for --approve-ready (0-20)
|
|
786
905
|
--all-reject reject the whole batch [--yes for scripted
|
|
787
906
|
incident response; rejects stay private]
|
|
788
907
|
--base-url <url>
|
|
@@ -797,6 +916,10 @@ Docs: https://auxilo.io · API: ${installer.DEFAULT_BASE_URL}
|
|
|
797
916
|
|
|
798
917
|
async function main() {
|
|
799
918
|
const cmd = process.argv[2];
|
|
919
|
+
const subcommandHelp = ['help', '--help', '-h'].includes(process.argv[3]);
|
|
920
|
+
if (['setup', 'init', 'status', 'review', 'disable'].includes(cmd) && subcommandHelp) {
|
|
921
|
+
return usage(cmd);
|
|
922
|
+
}
|
|
800
923
|
const flags = parseFlags(process.argv);
|
|
801
924
|
switch (cmd) {
|
|
802
925
|
case 'setup': return cmdSetup(flags);
|
|
@@ -829,4 +952,12 @@ if (require.main === module) {
|
|
|
829
952
|
run();
|
|
830
953
|
}
|
|
831
954
|
|
|
832
|
-
module.exports = {
|
|
955
|
+
module.exports = {
|
|
956
|
+
parseFlags,
|
|
957
|
+
resolveBaseUrl,
|
|
958
|
+
shortFlags,
|
|
959
|
+
groupSummaryRows,
|
|
960
|
+
printSummaryTable,
|
|
961
|
+
usage,
|
|
962
|
+
run,
|
|
963
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* True when a Claude Code SessionEnd collection contains an Auxilo extraction
|
|
5
|
+
* hook. Accepts both the legacy bare-command string and the current matcher
|
|
6
|
+
* group shape ({ hooks: [{ type: 'command', command }] }).
|
|
7
|
+
*
|
|
8
|
+
* @param {unknown} sessionEnd
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
function hasAuxiloSessionEndHook(sessionEnd) {
|
|
12
|
+
if (!Array.isArray(sessionEnd)) return false;
|
|
13
|
+
|
|
14
|
+
return sessionEnd.some((entry) => {
|
|
15
|
+
if (typeof entry === 'string') return entry.includes('auxilo-extract');
|
|
16
|
+
if (!entry || typeof entry !== 'object' || !Array.isArray(entry.hooks)) return false;
|
|
17
|
+
return entry.hooks.some((hook) =>
|
|
18
|
+
hook && typeof hook === 'object' &&
|
|
19
|
+
typeof hook.command === 'string' &&
|
|
20
|
+
hook.command.includes('auxilo-extract'));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { hasAuxiloSessionEndHook };
|
package/lib/installer.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
const fs = require('fs');
|
|
26
26
|
const path = require('path');
|
|
27
|
+
const { hasAuxiloSessionEndHook } = require('./hook-status.js');
|
|
27
28
|
|
|
28
29
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
29
30
|
|
|
@@ -75,6 +76,7 @@ const RUNNER_STACK = Object.freeze([
|
|
|
75
76
|
['lib/sensitivity-filter.js', 'lib/sensitivity-filter.js', 0o644],
|
|
76
77
|
['lib/extraction-index.js', 'lib/extraction-index.js', 0o644],
|
|
77
78
|
['lib/similarity.js', 'lib/similarity.js', 0o644],
|
|
79
|
+
['lib/hook-status.js', 'lib/hook-status.js', 0o644],
|
|
78
80
|
['config/near-duplicate.json', 'config/near-duplicate.json', 0o644],
|
|
79
81
|
]);
|
|
80
82
|
|
|
@@ -1559,16 +1561,21 @@ async function getStatus(homeDir, opts = {}) {
|
|
|
1559
1561
|
// 4. Sentinel + runner + hook install state
|
|
1560
1562
|
const hookPath = hookScriptPathFor(homeDir);
|
|
1561
1563
|
const runnerInstalled = fs.existsSync(path.join(binRootFor(homeDir), 'scripts', 'runner.js'));
|
|
1564
|
+
let hookInstalled = false;
|
|
1562
1565
|
let hookRegistered = false;
|
|
1563
1566
|
try {
|
|
1564
1567
|
const settings = JSON.parse(
|
|
1565
1568
|
fs.readFileSync(path.join(homeDir, '.claude', 'settings.json'), 'utf-8')
|
|
1566
1569
|
);
|
|
1570
|
+
const sessionEnd = settings.hooks && settings.hooks.SessionEnd;
|
|
1571
|
+
// SPEC3-A3: installation detection accepts both the legacy string and the
|
|
1572
|
+
// current object shape through the same helper runner --status uses.
|
|
1573
|
+
hookInstalled = hasAuxiloSessionEndHook(sessionEnd);
|
|
1567
1574
|
// LW-17: only STRUCTURED entries count — Claude Code silently ignores
|
|
1568
1575
|
// bare-string entries (the 0.8.1 dead-hook bug), so reporting one as
|
|
1569
1576
|
// "registered" would mask exactly the failure this status exists to catch.
|
|
1570
|
-
hookRegistered = Array.isArray(
|
|
1571
|
-
|
|
1577
|
+
hookRegistered = Array.isArray(sessionEnd) &&
|
|
1578
|
+
sessionEnd.some((h) =>
|
|
1572
1579
|
h && typeof h === 'object' && Array.isArray(h.hooks) && h.hooks.some(isAuxiloCommandHook));
|
|
1573
1580
|
} catch { /* no settings */ }
|
|
1574
1581
|
|
|
@@ -1592,7 +1599,8 @@ async function getStatus(homeDir, opts = {}) {
|
|
|
1592
1599
|
accountMode,
|
|
1593
1600
|
sentinel: sentinelPresent(homeDir),
|
|
1594
1601
|
runnerInstalled,
|
|
1595
|
-
hookInstalled
|
|
1602
|
+
hookInstalled,
|
|
1603
|
+
hookScriptInstalled: fs.existsSync(hookPath),
|
|
1596
1604
|
hookRegistered,
|
|
1597
1605
|
// LW-18 layer 1b: SessionStart held-count notice
|
|
1598
1606
|
noticeRegistered: sessionStartNoticeRegistered(homeDir),
|
package/lib/review.js
CHANGED
|
@@ -121,7 +121,7 @@ function formatFlags(l) {
|
|
|
121
121
|
|
|
122
122
|
// ── Review-seamless additions (2026-07-18) ──────────────────────────────────
|
|
123
123
|
//
|
|
124
|
-
// Summary + bulk network calls, plus the pure approve-
|
|
124
|
+
// Summary + bulk network calls, plus the pure approve-ready selection logic
|
|
125
125
|
// shared by the CLI, the MCP auxilo_review tool, and the triage report script.
|
|
126
126
|
// One selection implementation everywhere, so what a dry run PRINTS is exactly
|
|
127
127
|
// what a confirmed run DOES.
|
|
@@ -129,8 +129,12 @@ function formatFlags(l) {
|
|
|
129
129
|
/** Server-enforced max decisions per bulk call; clients chunk at this size. */
|
|
130
130
|
const BULK_CHUNK = 100;
|
|
131
131
|
|
|
132
|
-
/** Default
|
|
132
|
+
/** Default ready-to-publish quality threshold (repo quality gate: total >= 14/20). */
|
|
133
133
|
const DEFAULT_QUALITY_THRESHOLD = 14;
|
|
134
|
+
const LANE_READY = 'ready_to_publish';
|
|
135
|
+
const LANE_NEEDS_SCORE = 'needs_score';
|
|
136
|
+
const LANE_NEEDS_EYES = 'needs_your_eyes';
|
|
137
|
+
const REVIEW_LANES = new Set([LANE_READY, LANE_NEEDS_SCORE, LANE_NEEDS_EYES]);
|
|
134
138
|
|
|
135
139
|
/**
|
|
136
140
|
* GET /account/pending/summary: compact triage rows + counts (no bodies).
|
|
@@ -233,46 +237,95 @@ function qualityClears(row, minQuality) {
|
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
240
|
+
* Resolve the server lane on a summary row. Old servers did not return lane;
|
|
241
|
+
* those rows fall back to the legacy screens+quality split and are marked as
|
|
242
|
+
* version-skewed so the caller can tell the operator.
|
|
238
243
|
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
|
|
244
|
+
* @param {object} row
|
|
245
|
+
* @returns {{lane:string, version_skew:boolean}}
|
|
246
|
+
*/
|
|
247
|
+
function reviewLane(row) {
|
|
248
|
+
if (row && REVIEW_LANES.has(row.lane)) {
|
|
249
|
+
return { lane: row.lane, version_skew: false };
|
|
250
|
+
}
|
|
251
|
+
if (!row || !row.screens_passed) {
|
|
252
|
+
return { lane: LANE_NEEDS_EYES, version_skew: true };
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
lane: qualityClears(row, DEFAULT_QUALITY_THRESHOLD) ? LANE_READY : LANE_NEEDS_SCORE,
|
|
256
|
+
version_skew: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* THE approve-ready selection. The server's lane is authoritative:
|
|
262
|
+
*
|
|
263
|
+
* - threshold 14 (default): exactly rows in ready_to_publish
|
|
264
|
+
* - threshold >14: narrows within ready_to_publish
|
|
265
|
+
* - threshold <14: includes qualifying needs_score rows too
|
|
266
|
+
* - needs_your_eyes is excluded unless mode=all + includeFlagged
|
|
242
267
|
*
|
|
243
268
|
* Returns the selection plus what was excluded and why, so every confirmation
|
|
244
269
|
* surface can show the operator the exact consequences before anything runs.
|
|
245
270
|
*
|
|
246
271
|
* @param {Array<object>} rows
|
|
247
|
-
* @param {object} [opts] { mode: 'clean'|'all', minQuality, includeFlagged }
|
|
248
|
-
* @returns {{selected:Array<object>, excluded_flagged:Array<object>, excluded_low_quality:Array<object>, excluded_unscored:Array<object>, min_quality:number|null}}
|
|
272
|
+
* @param {object} [opts] { mode: 'ready'|'clean'|'all', minQuality, includeFlagged }
|
|
273
|
+
* @returns {{selected:Array<object>, included_beyond_verdict:Array<object>, excluded_flagged:Array<object>, excluded_low_quality:Array<object>, excluded_unscored:Array<object>, min_quality:number|null, version_skew:boolean}}
|
|
249
274
|
*/
|
|
250
275
|
function selectForBulkApprove(rows, opts = {}) {
|
|
251
|
-
const mode = opts.mode === 'all' ? 'all' : '
|
|
252
|
-
const minQuality = mode === '
|
|
276
|
+
const mode = opts.mode === 'all' ? 'all' : 'ready';
|
|
277
|
+
const minQuality = mode === 'ready'
|
|
253
278
|
? (Number.isFinite(opts.minQuality) ? opts.minQuality : DEFAULT_QUALITY_THRESHOLD)
|
|
254
279
|
: null;
|
|
255
280
|
const includeFlagged = mode === 'all' && opts.includeFlagged === true;
|
|
256
281
|
|
|
257
282
|
const selected = [];
|
|
283
|
+
const included_beyond_verdict = [];
|
|
258
284
|
const excluded_flagged = [];
|
|
259
285
|
const excluded_low_quality = [];
|
|
260
286
|
const excluded_unscored = [];
|
|
287
|
+
let version_skew = false;
|
|
261
288
|
|
|
262
289
|
for (const row of rows || []) {
|
|
263
290
|
if (!row || !row.id) continue;
|
|
264
|
-
|
|
291
|
+
const resolved = reviewLane(row);
|
|
292
|
+
version_skew = version_skew || resolved.version_skew;
|
|
293
|
+
|
|
294
|
+
if (resolved.lane === LANE_NEEDS_EYES && !includeFlagged) {
|
|
265
295
|
excluded_flagged.push(row);
|
|
266
296
|
continue;
|
|
267
297
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
298
|
+
|
|
299
|
+
if (mode === 'ready') {
|
|
300
|
+
if (resolved.lane === LANE_READY) {
|
|
301
|
+
// At the server floor, consume the verdict verbatim. A stricter
|
|
302
|
+
// operator threshold may narrow it, but never broaden it.
|
|
303
|
+
if (minQuality > DEFAULT_QUALITY_THRESHOLD && !qualityClears(row, minQuality)) {
|
|
304
|
+
(row.quality == null ? excluded_unscored : excluded_low_quality).push(row);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
} else if (resolved.lane === LANE_NEEDS_SCORE) {
|
|
308
|
+
if (minQuality < DEFAULT_QUALITY_THRESHOLD && qualityClears(row, minQuality)) {
|
|
309
|
+
included_beyond_verdict.push(row);
|
|
310
|
+
} else {
|
|
311
|
+
(row.quality == null ? excluded_unscored : excluded_low_quality).push(row);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
271
315
|
}
|
|
316
|
+
|
|
272
317
|
selected.push(row);
|
|
273
318
|
}
|
|
274
319
|
|
|
275
|
-
return {
|
|
320
|
+
return {
|
|
321
|
+
selected,
|
|
322
|
+
included_beyond_verdict,
|
|
323
|
+
excluded_flagged,
|
|
324
|
+
excluded_low_quality,
|
|
325
|
+
excluded_unscored,
|
|
326
|
+
min_quality: minQuality,
|
|
327
|
+
version_skew,
|
|
328
|
+
};
|
|
276
329
|
}
|
|
277
330
|
|
|
278
331
|
module.exports = {
|
|
@@ -287,5 +340,6 @@ module.exports = {
|
|
|
287
340
|
chunkDecisions,
|
|
288
341
|
submitBulkChunked,
|
|
289
342
|
qualityClears,
|
|
343
|
+
reviewLane,
|
|
290
344
|
selectForBulkApprove,
|
|
291
345
|
};
|
package/mcp-server.js
CHANGED
|
@@ -160,7 +160,7 @@ 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.7' },
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auxilo-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
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",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"bin/",
|
|
14
14
|
"lib/installer.js",
|
|
15
15
|
"lib/review.js",
|
|
16
|
+
"lib/hook-status.js",
|
|
16
17
|
"lib/sensitivity-filter.js",
|
|
17
18
|
"lib/extraction-index.js",
|
|
18
19
|
"lib/similarity.js",
|
package/scripts/runner.js
CHANGED
|
@@ -39,6 +39,7 @@ const crypto = require('crypto');
|
|
|
39
39
|
const { spawn } = require('child_process');
|
|
40
40
|
const { scanText, SENSITIVITY_FILTER_VERSION } = require('../lib/sensitivity-filter.js');
|
|
41
41
|
const { appendSubmittedLearning } = require('../lib/extraction-index.js');
|
|
42
|
+
const { hasAuxiloSessionEndHook } = require('../lib/hook-status.js');
|
|
42
43
|
const { TranscriptSource } = require('./sources/source.interface.js');
|
|
43
44
|
const { GenericJsonlSource } = require('./sources/generic-jsonl.js');
|
|
44
45
|
|
|
@@ -516,6 +517,7 @@ function sweeperManifest(repoRoot = path.resolve(__dirname, '..')) {
|
|
|
516
517
|
['lib/sensitivity-filter.js', 'lib/sensitivity-filter.js', 0o644],
|
|
517
518
|
['lib/extraction-index.js', 'lib/extraction-index.js', 0o644],
|
|
518
519
|
['lib/similarity.js', 'lib/similarity.js', 0o644],
|
|
520
|
+
['lib/hook-status.js', 'lib/hook-status.js', 0o644],
|
|
519
521
|
['config/near-duplicate.json', 'config/near-duplicate.json', 0o644],
|
|
520
522
|
// Client-side extraction (2026-07-02) — required by the sweep path since /extract went 410.
|
|
521
523
|
// Missing from this manifest until 2026-07-19: installed sweepers crashed with
|
|
@@ -711,10 +713,10 @@ async function printStatus() {
|
|
|
711
713
|
let hookInstalled = false;
|
|
712
714
|
try {
|
|
713
715
|
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));
|
|
714
|
-
hookInstalled =
|
|
715
|
-
settings.hooks.SessionEnd.some(h => h.includes('auxilo-extract'));
|
|
716
|
+
hookInstalled = hasAuxiloSessionEndHook(settings.hooks?.SessionEnd);
|
|
716
717
|
} catch { /* no settings file */ }
|
|
717
718
|
console.log(`Hook installed: ${hookInstalled ? 'yes' : 'no'}`);
|
|
719
|
+
console.log(`Settings inspected: ${claudeSettingsPath}`);
|
|
718
720
|
|
|
719
721
|
// 5. Last sweep ran at
|
|
720
722
|
console.log(`Last sweep: ${ledger.lastSweep || 'never'}`);
|