@holmes-lab/holmes-kit 0.1.10 → 0.1.11
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/CHANGELOG.md +19 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/doctor.d.ts +39 -0
- package/dist/holmes/cli/doctor.js +79 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
<!-- @implements A-SPEC-209 -->
|
|
8
|
+
## [0.1.11] - 2026-08-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`doctor` diagnoses the global npm prefix (REQ-243)**: after a Windows adopter hit `EPERM` on
|
|
12
|
+
`npm install -g` twice — once elevated — `doctor` now reads the npm global prefix, computes where
|
|
13
|
+
a global install would land (win32: `<prefix>\\node_modules`; POSIX: `<prefix>/lib/node_modules`),
|
|
14
|
+
and checks writability on the deepest existing ancestor, which is what decides the real mkdir's
|
|
15
|
+
fate. An unwritable prefix is a **WARN** naming the exact directory the install would die in, with
|
|
16
|
+
a user-space prefix prescription — never elevation, which npm's own error text suggests and which
|
|
17
|
+
would run better-sqlite3's install script with system privileges. An unreadable probe is a WARN
|
|
18
|
+
carrying the reason, never a silent pass. The check cannot FAIL: a red doctor would tell a user
|
|
19
|
+
with a healthy local install that the install is broken.
|
|
20
|
+
|
|
21
|
+
### Unchanged
|
|
22
|
+
- The `-g` EPERM itself is not fixable by any package: it occurs while npm creates the scope
|
|
23
|
+
directory, before a single package file is transferred. The supported regular-account path remains
|
|
24
|
+
the local install (`npm install --save-dev`), which touches no protected directory.
|
|
25
|
+
|
|
7
26
|
<!-- @implements A-SPEC-209 -->
|
|
8
27
|
## [0.1.10] - 2026-08-23
|
|
9
28
|
|
package/dist/.build-id
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c3422e5-mt570gca
|
|
@@ -28,6 +28,45 @@ export interface DoctorOptions {
|
|
|
28
28
|
* real key-related failure.
|
|
29
29
|
*/
|
|
30
30
|
export declare const STRIPPED_FOR_PROBE: readonly ["HOLMES_APPROVAL", "HOLMES_SPECS"];
|
|
31
|
+
/**
|
|
32
|
+
* Where a global install actually lands for a given prefix.
|
|
33
|
+
*
|
|
34
|
+
* @implements A-SPEC-243
|
|
35
|
+
* The shapes differ, not just the separators: win32 puts global modules directly under the prefix,
|
|
36
|
+
* POSIX puts them under `lib/`. The win32 result is the exact directory the reported EPERM died in
|
|
37
|
+
* — npm mkdir-ing `@holmes-lab` inside `C:\Program Files\nodejs\node_modules`, before a single
|
|
38
|
+
* package file was transferred. That is why no package content can fix that failure, and why the
|
|
39
|
+
* only lever left is naming the condition here.
|
|
40
|
+
*/
|
|
41
|
+
export declare function globalInstallDir(prefix: string, platform: string): string;
|
|
42
|
+
/** What the global-prefix probe saw. `unreadable` carries WHY npm could not be consulted. */
|
|
43
|
+
export interface PrefixProbe {
|
|
44
|
+
prefix?: unknown;
|
|
45
|
+
dir?: string;
|
|
46
|
+
writable?: boolean;
|
|
47
|
+
platform?: string;
|
|
48
|
+
unreadable?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Judge the global prefix. Pure — the probing lives with the caller.
|
|
52
|
+
*
|
|
53
|
+
* @implements A-SPEC-243
|
|
54
|
+
* Never FAIL: the `-g` path is optional, and a red doctor tells a user with a healthy local
|
|
55
|
+
* install that the install is broken — a false signal that comes back as distrust of the check.
|
|
56
|
+
*
|
|
57
|
+
* Never elevation: npm's own EPERM text ends with "try running the command again as
|
|
58
|
+
* root/Administrator", and pointing the opposite way is this check's reason to exist — elevation
|
|
59
|
+
* runs better-sqlite3's install script (`prebuild-install || node-gyp rebuild`) with system
|
|
60
|
+
* privileges. The prescription is a user-space prefix, per platform.
|
|
61
|
+
*
|
|
62
|
+
* An unreadable probe is a WARN carrying the reason, not a silent pass: a silent pass reads as
|
|
63
|
+
* "checked, no problem", which translates what was not seen into a verdict.
|
|
64
|
+
*/
|
|
65
|
+
export declare function prefixVerdict(input: PrefixProbe): {
|
|
66
|
+
level: Level;
|
|
67
|
+
detail: string;
|
|
68
|
+
fix?: string;
|
|
69
|
+
};
|
|
31
70
|
/** The parent environment minus the variables that legitimately change a gate decision. Pure. */
|
|
32
71
|
export declare function probeEnv(parent: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
33
72
|
/** Seam for the gate probe's child process. Default is a real `spawnSync`; tests inject a fake. */
|
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.STRIPPED_FOR_PROBE = void 0;
|
|
37
|
+
exports.globalInstallDir = globalInstallDir;
|
|
38
|
+
exports.prefixVerdict = prefixVerdict;
|
|
37
39
|
exports.probeEnv = probeEnv;
|
|
38
40
|
exports.runDoctor = runDoctor;
|
|
39
41
|
exports.formatChecks = formatChecks;
|
|
@@ -63,6 +65,56 @@ const GRAMMARS = [
|
|
|
63
65
|
* real key-related failure.
|
|
64
66
|
*/
|
|
65
67
|
exports.STRIPPED_FOR_PROBE = ['HOLMES_APPROVAL', 'HOLMES_SPECS'];
|
|
68
|
+
/**
|
|
69
|
+
* Where a global install actually lands for a given prefix.
|
|
70
|
+
*
|
|
71
|
+
* @implements A-SPEC-243
|
|
72
|
+
* The shapes differ, not just the separators: win32 puts global modules directly under the prefix,
|
|
73
|
+
* POSIX puts them under `lib/`. The win32 result is the exact directory the reported EPERM died in
|
|
74
|
+
* — npm mkdir-ing `@holmes-lab` inside `C:\Program Files\nodejs\node_modules`, before a single
|
|
75
|
+
* package file was transferred. That is why no package content can fix that failure, and why the
|
|
76
|
+
* only lever left is naming the condition here.
|
|
77
|
+
*/
|
|
78
|
+
function globalInstallDir(prefix, platform) {
|
|
79
|
+
return platform === 'win32'
|
|
80
|
+
? `${prefix.replace(/[\\/]+$/, '')}\\node_modules`
|
|
81
|
+
: `${prefix.replace(/\/+$/, '')}/lib/node_modules`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Judge the global prefix. Pure — the probing lives with the caller.
|
|
85
|
+
*
|
|
86
|
+
* @implements A-SPEC-243
|
|
87
|
+
* Never FAIL: the `-g` path is optional, and a red doctor tells a user with a healthy local
|
|
88
|
+
* install that the install is broken — a false signal that comes back as distrust of the check.
|
|
89
|
+
*
|
|
90
|
+
* Never elevation: npm's own EPERM text ends with "try running the command again as
|
|
91
|
+
* root/Administrator", and pointing the opposite way is this check's reason to exist — elevation
|
|
92
|
+
* runs better-sqlite3's install script (`prebuild-install || node-gyp rebuild`) with system
|
|
93
|
+
* privileges. The prescription is a user-space prefix, per platform.
|
|
94
|
+
*
|
|
95
|
+
* An unreadable probe is a WARN carrying the reason, not a silent pass: a silent pass reads as
|
|
96
|
+
* "checked, no problem", which translates what was not seen into a verdict.
|
|
97
|
+
*/
|
|
98
|
+
function prefixVerdict(input) {
|
|
99
|
+
const reason = input.unreadable ?? (typeof input.prefix !== 'string' || input.prefix === ''
|
|
100
|
+
? `npm 이 돌려준 prefix 를 해석할 수 없습니다 (${String(input.prefix)})`
|
|
101
|
+
: undefined);
|
|
102
|
+
if (reason !== undefined) {
|
|
103
|
+
return { level: 'WARN', detail: `전역 prefix 를 판독하지 못했습니다 — ${reason}. 로컬 설치(--save-dev)는 이 판정과 무관하게 동작합니다.` };
|
|
104
|
+
}
|
|
105
|
+
const prefix = input.prefix;
|
|
106
|
+
if (input.writable) {
|
|
107
|
+
return { level: 'PASS', detail: `전역 prefix(${prefix})가 현재 계정으로 쓰기 가능합니다 — npm install -g 가 권한 문제 없이 동작합니다.` };
|
|
108
|
+
}
|
|
109
|
+
const fix = input.platform === 'win32'
|
|
110
|
+
? 'npm config set prefix "%APPDATA%\\npm" 실행 후 %APPDATA%\\npm 을 PATH 에 추가하고 터미널을 다시 여세요. 로컬 설치(npm install --save-dev)는 이 설정 없이도 동작합니다.'
|
|
111
|
+
: 'npm config set prefix "$HOME/.npm-global" 실행 후 $HOME/.npm-global/bin 을 PATH 에 추가하세요. 로컬 설치(npm install --save-dev)는 이 설정 없이도 동작합니다.';
|
|
112
|
+
return {
|
|
113
|
+
level: 'WARN',
|
|
114
|
+
detail: `전역 prefix(${prefix})가 현재 계정으로 쓰기 불가입니다 — npm install -g 는 ${input.dir ?? ''} 에 디렉터리를 만들다 EPERM 으로 죽습니다. 패키지가 도착하기 전 단계라 어떤 패키지도 이를 고칠 수 없습니다.`,
|
|
115
|
+
fix,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
66
118
|
/** The parent environment minus the variables that legitimately change a gate decision. Pure. */
|
|
67
119
|
function probeEnv(parent) {
|
|
68
120
|
const out = { ...parent };
|
|
@@ -451,6 +503,33 @@ async function runDoctor(packageRoot, target, opts, extraChecks) {
|
|
|
451
503
|
catch { /* an unreadable settings file is `target wiring`'s business, not this check's */ }
|
|
452
504
|
}
|
|
453
505
|
add('gate blind spots', 'PASS', (0, blind_spots_1.blindSpotSummary)());
|
|
506
|
+
// @implements A-SPEC-243 — the writability of the deepest EXISTING ancestor is what decides the
|
|
507
|
+
// real mkdir's fate, since the scope directory being created does not exist yet.
|
|
508
|
+
{
|
|
509
|
+
let verdict;
|
|
510
|
+
try {
|
|
511
|
+
const prefix = (0, node_child_process_1.execFileSync)('npm', ['config', 'get', 'prefix'], { encoding: 'utf8' }).trim();
|
|
512
|
+
const dir = globalInstallDir(prefix, process.platform);
|
|
513
|
+
let probe = dir;
|
|
514
|
+
while (!fs.existsSync(probe)) {
|
|
515
|
+
const parent = path.dirname(probe);
|
|
516
|
+
if (parent === probe)
|
|
517
|
+
break;
|
|
518
|
+
probe = parent;
|
|
519
|
+
}
|
|
520
|
+
let writable = false;
|
|
521
|
+
try {
|
|
522
|
+
fs.accessSync(probe, fs.constants.W_OK);
|
|
523
|
+
writable = true;
|
|
524
|
+
}
|
|
525
|
+
catch { /* not writable */ }
|
|
526
|
+
verdict = prefixVerdict({ prefix, dir, writable, platform: process.platform });
|
|
527
|
+
}
|
|
528
|
+
catch (e) {
|
|
529
|
+
verdict = prefixVerdict({ unreadable: e instanceof Error ? e.message.split('\n')[0] : String(e) });
|
|
530
|
+
}
|
|
531
|
+
add('global prefix', verdict.level, verdict.detail, verdict.fix);
|
|
532
|
+
}
|
|
454
533
|
add('environment', 'PASS', present);
|
|
455
534
|
if (extraChecks) {
|
|
456
535
|
checks.push(...extraChecks);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@implements A-SPEC-209",
|
|
3
3
|
"name": "@holmes-lab/holmes-kit",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
|
|
6
6
|
"main": "dist/holmes/mcp/server.js",
|
|
7
7
|
"types": "dist/holmes/mcp/server.d.ts",
|