auxilo-mcp 0.9.11 → 0.9.12

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
@@ -493,6 +493,10 @@ async function cmdStatus() {
493
493
  console.log(`Account mode: ${s.accountMode}`);
494
494
  console.log(`Kill-switch sentinel: ${s.sentinel ? 'present (extraction enabled)' : 'absent (extraction disabled)'}`);
495
495
  console.log(`Runner installed: ${s.runnerInstalled ? 'yes (~/.auxilo/bin)' : 'no'}`);
496
+ if (s.runnerInstalled) {
497
+ const line = runnerSkewLine(installer.runnerVersionSkew(HOME));
498
+ if (line) console.log(line);
499
+ }
496
500
  console.log(`SessionEnd hook: ${s.hookInstalled ? 'installed' : 'not installed'}${s.hookRegistered ? ', registered in Claude Code settings' : ''}`);
497
501
  for (const c of s.clients.filter((c) => c.captureHook)) {
498
502
  console.log(`Capture hooks: ${c.name} (${c.captureEvent}, ${c.captureRegistered ? 'registered' : 'not registered'})`);
@@ -501,6 +505,17 @@ async function cmdStatus() {
501
505
  console.log(`Pending upload queue: ${s.pendingCount} file(s)\n`);
502
506
  }
503
507
 
508
+ /**
509
+ * CLEAN-LANE-FLIP Phase B: ONE line when ~/.auxilo/bin/VERSION is missing or
510
+ * differs from this CLI's package version; null when the stack is current.
511
+ * `setup` is idempotent and re-copies the stack, so that is the remedy.
512
+ */
513
+ function runnerSkewLine(skew) {
514
+ if (!skew || !skew.skew) return null;
515
+ const installed = skew.installed ? `v${skew.installed}` : 'unstamped (pre-0.9.12)';
516
+ return ` ⚠ Installed runner is ${installed} (package v${skew.package}) — run: npx auxilo setup`;
517
+ }
518
+
504
519
  // ─── auxilo disable ─────────────────────────────────────────────────────────
505
520
 
506
521
  async function cmdDisable(flags) {
@@ -879,6 +894,20 @@ async function cmdReview(flags) {
879
894
  // from GET /account/clean-lane (consent_version_current).
880
895
  const CLEAN_LANE_AFFIRMATION = 'I understand and choose auto-publish for qualifying extracted learnings.';
881
896
  const CLEAN_LANE_UNAVAILABLE = 'Auto-publish for clean learnings is not yet available on this account.';
897
+ // CLEAN-LANE-FLIP Phase B (legal; DRAFT pending Tyler): the full text of ToS
898
+ // §5.9.3(g) (plus its ratchet paragraph) prints ABOVE the affirmation prompt —
899
+ // counsel condition: the enrollment surface must show what "qualifying",
900
+ // revocation and the 7-day retraction mean, on both the dashboard and CLI
901
+ // paths. Same package-boundary reason as the affirmation: these literals are
902
+ // pinned byte-equal to docs/TERMS-OF-SERVICE.md and public/dashboard.html by
903
+ // test/clean-lane-phase-b-legal.test.js. Edit the Terms first, then mirror.
904
+ const CLEAN_LANE_TERMS_G = '(g) Standing publication consent (optional). Standing publication consent is off by default. A Builder may turn it on by an affirmative act — a dashboard setting, or a terminal command that requires typing the affirmation sentence shown on that screen. Auxilo records that act, the affirmation, and the consent-text version in a durable, hash-chained consent log, retained for the life of the account plus three (3) years under subsection (b). While it is on, a Learning submitted through Autonomous Extraction is published without separate per-item approval only if it passes every Platform screen and the quality threshold the Builder chose at activation. An account\'s first public Learning is never published this way; it is held for operator review under Section 4.1. Auxilo records each such publication in the Builder\'s dashboard and returns a notice in the response to the submission that produced it; each is retractable for seven (7) days under Section 5.9.4. If more than five percent (5%) of a Builder\'s Learnings published this way in any thirty (30) day period are retracted, Auxilo freezes the feature for that account until the Builder turns it on again. A Builder may turn it off at any time, effective immediately for later submissions; doing so does not affect Learnings already published. Subsection (c) applies in full to every Learning so published.';
905
+ const CLEAN_LANE_TERMS_G2 = 'The quality threshold in effect for a Builder is the one that Builder selected, and Auxilo will not broaden the conditions under which a Learning qualifies for publication under this subsection without recording a new consent; Auxilo may make those conditions stricter at any time.';
906
+ // CLEAN-LANE-FLIP Phase B (notice hardening): the no-email enrollment line —
907
+ // GOV-2 counsel draft §6 read #2 "move 3" — printed verbatim before the
908
+ // affirmation prompt on every enrollment surface. Byte-equal to the dashboard's
909
+ // #clean-lane-no-email-line (test/clean-lane-phase-b-notice.test.js).
910
+ const CLEAN_LANE_NO_EMAIL_LINE = 'You will not receive an email for these. Publications appear in your dashboard and in the response to the session that submitted them. The 7-day retraction window runs from publication.';
882
911
  const CLEAN_LANE_MIN_QUALITY_MIN = 14;
883
912
  const CLEAN_LANE_MIN_QUALITY_MAX = 20;
884
913
  const CLEAN_LANE_MIN_QUALITY_DEFAULT = 16;
@@ -897,6 +926,18 @@ and anything after an auto-freeze. Every auto-published learning can be
897
926
  retracted for 7 days (\`npx auxilo review\` or your dashboard).
898
927
  `;
899
928
 
929
+ /** Word-wrap a single paragraph at `width` columns (whitespace only; no word is altered). */
930
+ function wrapForTerminal(paragraph, width = 78) {
931
+ const lines = [];
932
+ let line = '';
933
+ for (const word of paragraph.split(' ')) {
934
+ if (line && (line.length + 1 + word.length) > width) { lines.push(line); line = word; }
935
+ else line = line ? `${line} ${word}` : word;
936
+ }
937
+ if (line) lines.push(line);
938
+ return lines.map((l) => ` ${l}`).join('\n');
939
+ }
940
+
900
941
  async function cleanLaneRequest({ apiKey, baseUrl, method, route, body }) {
901
942
  const url = `${String(baseUrl).replace(/\/+$/, '')}${route}`;
902
943
  const headers = { 'X-API-Key': apiKey };
@@ -927,6 +968,12 @@ function printCleanLaneStatus(data) {
927
968
  console.log(` a grant exists under consent version ${data.consent_version_recorded} but the current version is ${data.consent_version_current}; re-grant to re-activate.`);
928
969
  }
929
970
  console.log(` current consent version: ${data.consent_version_current}`);
971
+ // CLEAN-LANE-FLIP Phase B (notice hardening): the unread count, printed only
972
+ // when > 0. Nothing here acknowledges it — only the dashboard button does.
973
+ const unread = data.unacknowledged_publications;
974
+ if (Number.isInteger(unread) && unread > 0) {
975
+ console.log(` auto-published since you last checked: ${unread} (review and acknowledge them in your dashboard)`);
976
+ }
930
977
  }
931
978
 
932
979
  async function cmdCleanLane(flags) {
@@ -1011,6 +1058,14 @@ async function cmdCleanLane(flags) {
1011
1058
  console.log(`Enter a whole number from ${CLEAN_LANE_MIN_QUALITY_MIN} to ${CLEAN_LANE_MIN_QUALITY_MAX}.`);
1012
1059
  }
1013
1060
 
1061
+ // The consent text itself, verbatim (word-wrapped for the terminal only), before the sentence.
1062
+ console.log('\nTerms of Service, Section 5.9.3(g): the consent you are giving\n');
1063
+ console.log(wrapForTerminal(CLEAN_LANE_TERMS_G));
1064
+ console.log('');
1065
+ console.log(wrapForTerminal(CLEAN_LANE_TERMS_G2));
1066
+ console.log(`\nFull Terms: ${baseUrl}/terms`);
1067
+ // The no-email line, verbatim, directly before the affirmation prompt.
1068
+ console.log(`\n${wrapForTerminal(CLEAN_LANE_NO_EMAIL_LINE)}`);
1014
1069
  console.log('\nTo turn on auto-publish, type this sentence exactly as written, then press Enter:');
1015
1070
  console.log(`\n ${CLEAN_LANE_AFFIRMATION}\n`);
1016
1071
  const typed = await ask('> ');
@@ -1204,6 +1259,7 @@ if (require.main === module) {
1204
1259
 
1205
1260
  module.exports = {
1206
1261
  parseFlags,
1262
+ runnerSkewLine,
1207
1263
  resolveBaseUrl,
1208
1264
  shortFlags,
1209
1265
  groupSummaryRows,
@@ -1212,4 +1268,8 @@ module.exports = {
1212
1268
  run,
1213
1269
  CLEAN_LANE_AFFIRMATION,
1214
1270
  CLEAN_LANE_UNAVAILABLE,
1271
+ CLEAN_LANE_TERMS_G,
1272
+ CLEAN_LANE_TERMS_G2,
1273
+ CLEAN_LANE_NO_EMAIL_LINE,
1274
+ wrapForTerminal,
1215
1275
  };
package/lib/installer.js CHANGED
@@ -792,10 +792,18 @@ exit 0
792
792
  * relative layout runner.js requires. Missing package files throw — the lib
793
793
  * never calls process.exit.
794
794
  *
795
+ * CLEAN-LANE-FLIP Phase B: the install also stamps <bin>/VERSION with the
796
+ * package version read from <packageRoot>/package.json at install time, so
797
+ * `auxilo status` can tell a stale copied stack from the CLI that is running
798
+ * (npx resolves a fresh auxilo-mcp; ~/.auxilo/bin keeps whatever setup last
799
+ * copied). VERSION is a stamp, not a stack row — it is deliberately NOT in
800
+ * RUNNER_STACK (test/runner-packaging-closure.test.js walks the require
801
+ * closure against the stack, and a version file is not code).
802
+ *
795
803
  * @param {string} homeDir
796
804
  * @param {object} [opts]
797
805
  * @param {string} [opts.packageRoot=PACKAGE_ROOT]
798
- * @returns {{ binRoot: string, hookPath: string, installed: string[] }}
806
+ * @returns {{ binRoot: string, hookPath: string, versionPath: string, version: string, installed: string[] }}
799
807
  */
800
808
  function installRunner(homeDir, opts = {}) {
801
809
  if (!homeDir) throw new Error('installRunner: homeDir is required');
@@ -820,7 +828,55 @@ function installRunner(homeDir, opts = {}) {
820
828
  fs.chmodSync(hookPath, 0o755);
821
829
  installed.push(hookPath);
822
830
 
823
- return { binRoot, hookPath, installed };
831
+ const version = packageVersion(packageRoot);
832
+ const versionPath = runnerVersionPath(homeDir);
833
+ fs.writeFileSync(versionPath, `${version}\n`);
834
+ fs.chmodSync(versionPath, 0o644);
835
+ installed.push(versionPath);
836
+
837
+ return { binRoot, hookPath, versionPath, version, installed };
838
+ }
839
+
840
+ // ─── Runner-stack freshness (CLEAN-LANE-FLIP Phase B) ───────────────────────
841
+
842
+ /** <home>/.auxilo/bin/VERSION — the package version installRunner last copied. */
843
+ function runnerVersionPath(homeDir) {
844
+ return path.join(binRootFor(homeDir), 'VERSION');
845
+ }
846
+
847
+ /** Package version as shipped in <packageRoot>/package.json (throws if unreadable). */
848
+ function packageVersion(packageRoot = PACKAGE_ROOT) {
849
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
850
+ if (!pkg || typeof pkg.version !== 'string' || !pkg.version) {
851
+ throw new Error(`packageVersion: no version in ${path.join(packageRoot, 'package.json')}`);
852
+ }
853
+ return pkg.version;
854
+ }
855
+
856
+ /** Installed runner stamp, or null when the stamp is missing/unreadable (pre-0.9.12 installs). */
857
+ function installedRunnerVersion(homeDir) {
858
+ try {
859
+ const v = fs.readFileSync(runnerVersionPath(homeDir), 'utf-8').trim();
860
+ return v || null;
861
+ } catch {
862
+ return null;
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Compare the copied runner stack against the running package.
868
+ *
869
+ * @param {string} homeDir
870
+ * @param {object} [opts]
871
+ * @param {string} [opts.packageRoot=PACKAGE_ROOT]
872
+ * @returns {{ installed: string|null, package: string, skew: boolean }}
873
+ * skew is true when the stamp is missing OR differs from the package version.
874
+ */
875
+ function runnerVersionSkew(homeDir, opts = {}) {
876
+ if (!homeDir) throw new Error('runnerVersionSkew: homeDir is required');
877
+ const installed = installedRunnerVersion(homeDir);
878
+ const pkg = packageVersion(opts.packageRoot || PACKAGE_ROOT);
879
+ return { installed, package: pkg, skew: installed !== pkg };
824
880
  }
825
881
 
826
882
  // ─── Claude Code hook registration (spec §LW-12 step 4) ─────────────────────
@@ -1704,6 +1760,10 @@ module.exports = {
1704
1760
  hookScriptPathFor,
1705
1761
  renderHookScript,
1706
1762
  installRunner,
1763
+ runnerVersionPath,
1764
+ packageVersion,
1765
+ installedRunnerVersion,
1766
+ runnerVersionSkew,
1707
1767
  registerClaudeCodeHook,
1708
1768
  removeClaudeCodeHook,
1709
1769
  captureShimPath,
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.11' },
201
+ { name: 'auxilo', version: '0.9.12' },
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.11",
3
+ "version": "0.9.12",
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",
@@ -36,20 +36,36 @@ const PRIVATE_CATEGORIES = [...CATEGORIES, 'non-technical'];
36
36
  const RETIRED_CATEGORIES = ['communication', 'content-generation'];
37
37
 
38
38
  /**
39
- * SPEC3 slice A1 gate — score-at-extraction, BUILT BUT DARK by default.
39
+ * SPEC3 slice A1 gate — score-at-extraction, ON BY DEFAULT since 0.9.12
40
+ * (CLEAN-LANE-FLIP Phase B). Explicit opt-out only: AUXILO_SCORE_EXTRACTION=0
41
+ * (or 'false') disables scoring; any other value, including unset, scores.
40
42
  *
41
- * ┌─ CRITICAL SEQUENCING CONSTRAINT (SPEC3-BUILDER-REVIEW-LOOP §3.1/§8) ──────┐
42
- * │ Under a server WITHOUT the B1 extraction-channel hold, a clean /learn
43
- * │ submission carrying a floor-passing quality_self_assessment publishes
44
- * │ IMMEDIATELY (seamlessEligible). Turning this gate on against such a
45
- * │ server silently flips hook extraction from "everything held" to "clean
46
- * │ items auto-publish" an unrecorded consent change (2026-06-10 class).
47
- * │ Do NOT set AUXILO_SCORE_EXTRACTION=1 until the server holds
48
- * │ submission_channel:'extraction' items behind standing consent (B1).
49
- * └───────────────────────────────────────────────────────────────────────────┘
43
+ * ┌─ SEQUENCING CONSTRAINT — SATISFIED (SPEC3-BUILDER-REVIEW-LOOP §3.1/§8) ──┐
44
+ * │ This gate shipped dark (opt-in via AUXILO_SCORE_EXTRACTION=1) because
45
+ * │ under a server WITHOUT the B1 extraction-channel hold, a clean /learn
46
+ * │ submission carrying a floor-passing quality_self_assessment published
47
+ * │ IMMEDIATELY (seamlessEligible) arming scoring would have silently
48
+ * │ flipped hook extraction from "everything held" to "clean items
49
+ * │ auto-publish", an unrecorded consent change (2026-06-10 class).
50
+ * │
51
+ * │ B1 SHIPPED (lib/clean-lane.js; server.js /learn): every submission │
52
+ * │ carrying submission_channel:'extraction' (runner.js stamps it │
53
+ * │ unconditionally) is HELD with reason 'standing_consent_off' unless the │
54
+ * │ account holds an active standing-consent grant — regardless of the │
55
+ * │ score. The server brake, not the absence of a score, is what keeps │
56
+ * │ unconsented items out of the catalog, so default-on scoring is safe: │
57
+ * │ the score can only move a held item into the ready_to_publish lane (one │
58
+ * │ counted approve away) or, under an active grant, into the clean lane the │
59
+ * │ contributor explicitly opted into. Without a score every extraction │
60
+ * │ submission held awaiting_quality forever, so a stock install could never │
61
+ * │ enter the clean lane at all — that was the Phase B defect this closes. │
62
+ * └──────────────────────────────────────────────────────────────────────────┘
50
63
  */
51
64
  function scoreExtractionEnabled(env = process.env) {
52
- return env.AUXILO_SCORE_EXTRACTION === '1';
65
+ const raw = env.AUXILO_SCORE_EXTRACTION;
66
+ if (raw === undefined || raw === null) return true;
67
+ const v = String(raw).trim().toLowerCase();
68
+ return !(v === '0' || v === 'false');
53
69
  }
54
70
 
55
71
  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.