@scovant/core 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/bin/scovant.js +57 -14
  2. package/package.json +1 -1
package/bin/scovant.js CHANGED
@@ -28,6 +28,10 @@ const TESTING = process.env.SCOVANT_NPM_TEST === '1';
28
28
  // missing tool: reporting "install uv" to someone whose uv is installed and
29
29
  // merely slow is false. Track the two apart so the no-runner message can.
30
30
  let timedOut = false;
31
+ // Every python3.NN candidate that answered the probe with the WRONG engine
32
+ // version (only populated once the loop in resolveRunner has exhausted
33
+ // every candidate with no match) — see mismatchExhaustedMessage.
34
+ let versionMismatches = [];
31
35
 
32
36
  function probe(cmd, args, opts = PROBE) {
33
37
  const r = spawnSync(cmd, args, opts);
@@ -64,21 +68,43 @@ function existsOnPath(cmd) {
64
68
  // that for free — they resolve the pinned SPEC. The `python3 -m
65
69
  // scovant_core` path runs whatever that interpreter happens to have, so the
66
70
  // guarantee only holds if we check it: parse the version the probe printed
67
- // and compare. On a mismatch (or an unparseable answer) we still run —
68
- // leaving a user with a working engine beats refusing to start — but we say
69
- // so on stderr, naming BOTH versions, because scores are only comparable
70
- // within one ruleset version and a silent substitution is exactly the
71
- // failure this check exists to prevent.
72
- function warnIfVersionDiffers(cmd, stdout) {
71
+ // and compare. Scores are only comparable within one ruleset version, so a
72
+ // mismatch here is not a warning-and-continue situation it is a launcher
73
+ // that is about to run a different engine than the one it claims to be —
74
+ // and this fails CLOSED (exit 9), naming BOTH versions on stderr, unless
75
+ // the caller explicitly opts into the old warn-and-continue behavior via
76
+ // SCOVANT_ALLOW_VERSION_MISMATCH=1.
77
+ // Returns the version string the probe reported, or null if `stdout`
78
+ // didn't carry a recognizable one.
79
+ function extractVersion(stdout) {
73
80
  const m = /scovant-core\s+(\S+)/.exec(stdout || '');
74
- const found = m ? m[1] : null;
75
- if (found === version) return;
81
+ return m ? m[1] : null;
82
+ }
83
+
84
+ function mismatchWarning(cmd, found) {
76
85
  const what = found ? `version ${found}` : 'an unknown version';
77
- process.stderr.write(
78
- `scovant: warning — running ${what} of the scovant-core engine via "${cmd} -m scovant_core", ` +
86
+ return (
87
+ `scovant: warning — scovant: engine version mismatch "${cmd} -m scovant_core" runs scovant-core ${what}, ` +
79
88
  `but this launcher is @scovant/core ${version}.\n` +
80
- `Results may use a different ruleset and are not comparable across versions. ` +
81
- `Install uv or pipx (or "${cmd} -m pip install \\"${SPEC}\\"") to pin the engine version.\n`);
89
+ `Scores are only comparable within one ruleset version, so the launcher refuses to run a different engine.\n` +
90
+ `Fix: install uv or pipx (they pin "${SPEC}"), or "${cmd} -m pip install \\"${SPEC}\\"".\n`
91
+ );
92
+ }
93
+
94
+ // Every candidate that answered the probe but carried the wrong version —
95
+ // a mismatched EARLIER candidate must not abort the whole loop when a
96
+ // LATER one carries the exact version, so this is only consulted once the
97
+ // loop has exhausted every candidate with no match.
98
+ function mismatchExhaustedMessage(tried) {
99
+ const lines = tried.map(({ cmd, found }) => ` - "${cmd} -m scovant_core --version" reported ${found ? `version ${found}` : 'an unknown version'}`);
100
+ return (
101
+ `scovant: engine version mismatch — every Python interpreter tried on PATH runs a different\n` +
102
+ `scovant-core version than this launcher (@scovant/core ${version}):\n` +
103
+ lines.join('\n') + '\n' +
104
+ `Scores are only comparable within one ruleset version, so the launcher refuses to run a different engine.\n` +
105
+ `Fix: install uv or pipx (they pin "${SPEC}"), or "python3 -m pip install \\"${SPEC}\\"".\n` +
106
+ `Override (not recommended): SCOVANT_ALLOW_VERSION_MISMATCH=1\n`
107
+ );
82
108
  }
83
109
 
84
110
  function resolveRunner() {
@@ -105,18 +131,35 @@ function resolveRunner() {
105
131
  // and still fail to execute as a module; a probe that only checks import
106
132
  // would hand back a runner that crashes on the real invocation instead of
107
133
  // reporting "no runner found".
134
+ // A mismatched EARLIER candidate must not abort the probe: keep trying
135
+ // later candidates, and only report failure once every one of them has
136
+ // been tried and none matched.
137
+ const mismatches = [];
108
138
  for (const py of ['python3.13', 'python3.12', 'python3']) {
109
139
  const r = probe(py, ['-m', 'scovant_core', '--version'], PROBE_CAPTURE);
110
- if (r.ok) {
111
- warnIfVersionDiffers(py, r.stdout);
140
+ if (!r.ok) continue;
141
+ const found = extractVersion(r.stdout);
142
+ if (found === version) {
112
143
  return { cmd: py, pre: ['-m', 'scovant_core'] };
113
144
  }
145
+ if (process.env.SCOVANT_ALLOW_VERSION_MISMATCH === '1') {
146
+ process.stderr.write(mismatchWarning(py, found));
147
+ return { cmd: py, pre: ['-m', 'scovant_core'] };
148
+ }
149
+ mismatches.push({ cmd: py, found });
150
+ }
151
+ if (mismatches.length) {
152
+ versionMismatches = mismatches;
114
153
  }
115
154
  return null;
116
155
  }
117
156
 
118
157
  const runner = resolveRunner();
119
158
  if (!runner) {
159
+ if (versionMismatches.length) {
160
+ process.stderr.write(mismatchExhaustedMessage(versionMismatches));
161
+ process.exit(9);
162
+ }
120
163
  if (timedOut) {
121
164
  process.stderr.write(
122
165
  `scovant: a runner was found but its probe timed out, so nothing could be started.\n\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scovant/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Passive AI-agent-readiness scanner (thin launcher for the Python package scovant-core)",
5
5
  "bin": {
6
6
  "scovant": "bin/scovant.js"