@holmes-lab/holmes-kit 0.18.0 → 0.19.2
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 +149 -0
- package/README.md +3 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/agents.d.ts +22 -0
- package/dist/holmes/cli/agents.js +76 -1
- package/dist/holmes/cli/approve.js +6 -1
- package/dist/holmes/cli/doctor.d.ts +36 -1
- package/dist/holmes/cli/doctor.js +182 -35
- package/dist/holmes/cli/index.js +7 -1
- package/dist/holmes/cli/init.js +12 -0
- package/dist/holmes/cli/native-deps.d.ts +65 -0
- package/dist/holmes/cli/native-deps.js +131 -0
- package/dist/holmes/cli/release-docs.d.ts +27 -0
- package/dist/holmes/cli/release-docs.js +68 -0
- package/dist/holmes/cpg/arch-observe.d.ts +15 -0
- package/dist/holmes/cpg/arch-observe.js +19 -0
- package/dist/holmes/cpg/cpg-scanner.d.ts +10 -36
- package/dist/holmes/cpg/cpg-scanner.js +27 -3
- package/dist/holmes/cpg/cycle-detect.d.ts +87 -0
- package/dist/holmes/cpg/cycle-detect.js +251 -0
- package/dist/holmes/cpg/cycle-observation.d.ts +65 -0
- package/dist/holmes/cpg/cycle-observation.js +146 -0
- package/dist/holmes/cpg/scan-cache.d.ts +1 -1
- package/dist/holmes/cpg/scanned-file.d.ts +36 -0
- package/dist/holmes/cpg/scanned-file.js +2 -0
- package/dist/holmes/governance/approval-queue.d.ts +23 -4
- package/dist/holmes/governance/approval-queue.js +44 -6
- package/dist/holmes/governance/constitution.d.ts +20 -0
- package/dist/holmes/governance/constitution.js +17 -0
- package/dist/holmes/governance/ledger-store.d.ts +9 -0
- package/dist/holmes/governance/ledger-store.js +47 -0
- package/dist/holmes/governance/provenance-chain.d.ts +16 -1
- package/dist/holmes/governance/provenance-chain.js +5 -3
- package/dist/holmes/hooks/pre-tool-use.js +3 -1
- package/dist/holmes/hooks/stop.d.ts +29 -0
- package/dist/holmes/hooks/stop.js +119 -3
- package/dist/holmes/mcp/defuse-bound.d.ts +1 -0
- package/dist/holmes/mcp/defuse-bound.js +8 -0
- package/dist/holmes/mcp/handlers.d.ts +7 -0
- package/dist/holmes/mcp/handlers.js +132 -6
- package/dist/holmes/mcp/history-admission.d.ts +15 -0
- package/dist/holmes/mcp/history-admission.js +37 -0
- package/dist/holmes/mcp/maintenance-analyze.d.ts +45 -0
- package/dist/holmes/mcp/maintenance-analyze.js +117 -9
- package/dist/holmes/mcp/maintenance-evidence.d.ts +41 -0
- package/dist/holmes/mcp/maintenance-evidence.js +71 -4
- package/dist/holmes/project/install-scripts-policy.d.ts +76 -0
- package/dist/holmes/project/install-scripts-policy.js +131 -0
- package/dist/holmes/project/npx-bin.d.ts +6 -0
- package/dist/holmes/project/npx-bin.js +10 -0
- package/dist/holmes/review/evaluation-metrics.d.ts +6 -0
- package/dist/holmes/review/evaluation-metrics.js +18 -1
- package/dist/holmes/review/failed-test-names.d.ts +19 -0
- package/dist/holmes/review/failed-test-names.js +43 -0
- package/dist/holmes/review/paired-power.d.ts +14 -0
- package/dist/holmes/review/paired-power.js +57 -0
- package/dist/holmes/review/replay-corpus.d.ts +11 -0
- package/dist/holmes/review/replay-corpus.js +34 -0
- package/dist/holmes/review/run-replay.d.ts +23 -0
- package/dist/holmes/review/run-replay.js +90 -4
- package/dist/holmes/review/symbol-truth.d.ts +14 -0
- package/dist/holmes/review/symbol-truth.js +23 -0
- package/dist/holmes/review/test-runner.d.ts +27 -0
- package/dist/holmes/review/test-runner.js +59 -3
- package/dist/holmes/rtm/defuse-symbols.d.ts +17 -0
- package/dist/holmes/rtm/defuse-symbols.js +91 -0
- package/dist/holmes/rtm/incremental.js +5 -0
- package/dist/holmes/rtm/rtm-builder.d.ts +8 -0
- package/dist/holmes/rtm/rtm-builder.js +32 -5
- package/dist/holmes/rtm/rtm-graph.d.ts +11 -0
- package/dist/holmes/rtm/rtm-graph.js +13 -0
- package/dist/holmes/spec/legacy-fields.d.ts +2 -0
- package/dist/holmes/spec/legacy-fields.js +9 -0
- package/dist/holmes/spec/legacy-format.d.ts +1 -1
- package/dist/holmes/spec/legacy-format.js +4 -1
- package/dist/holmes/spec/spec-parser.js +5 -3
- package/docs/install-guide.md +54 -5
- package/package.json +4 -1
- package/playbooks/author-slice/PLAYBOOK.md +14 -0
- package/playbooks/publish/PLAYBOOK.md +32 -0
- package/playbooks/tdd-slice/PLAYBOOK.md +14 -0
|
@@ -56,7 +56,12 @@ function applyIncremental(graph, changes, ctx) {
|
|
|
56
56
|
}
|
|
57
57
|
if (!ctx.allScanned)
|
|
58
58
|
return { callEdgesResolved: false };
|
|
59
|
+
// @implements A-SPEC-573.5 — `calls_ambiguous` is rebuilt by the same pass, so it must be cleared
|
|
60
|
+
// by the same pass. Missing it left a stale candidate edge behind when an ambiguity RESOLVED
|
|
61
|
+
// (the second definer was deleted), and incremental stopped converging with a full rebuild —
|
|
62
|
+
// caught by A-SPEC-280's convergence property, which is exactly what it is for.
|
|
59
63
|
graph.removeEdgesByRel('calls');
|
|
64
|
+
graph.removeEdgesByRel('calls_ambiguous');
|
|
60
65
|
(0, rtm_builder_1.addCallEdges)(ctx.allScanned(), graph, ctx.buildOptions);
|
|
61
66
|
return { callEdgesResolved: true };
|
|
62
67
|
}
|
|
@@ -16,6 +16,14 @@ import { ScannedFile } from '../cpg/cpg-scanner';
|
|
|
16
16
|
*/
|
|
17
17
|
export interface ResolutionReport {
|
|
18
18
|
resolved: number;
|
|
19
|
+
/**
|
|
20
|
+
* @implements A-SPEC-573.5
|
|
21
|
+
* Candidate edges kept for a call whose name is defined in more than one place. Census on this
|
|
22
|
+
* repository (2026-09-08): 102 qualified-name and 590 last-segment collisions were being dropped
|
|
23
|
+
* against 3,067 resolved edges — 22.6% of the call graph, at a mean fan-out of 2.29. They ride on
|
|
24
|
+
* the `calls_ambiguous` relation, which every certainty-requiring reader filters out by name.
|
|
25
|
+
*/
|
|
26
|
+
ambiguousEdges: number;
|
|
19
27
|
/** Callee defined in several files: precision-over-recall refused to guess. A real miss. */
|
|
20
28
|
ambiguous: number;
|
|
21
29
|
/** Callee defined nowhere in the scan — usually an external package or runtime builtin. */
|
|
@@ -139,7 +139,7 @@ function buildRtm(specs, scanned, graph, opts) {
|
|
|
139
139
|
// on a 56k-insert graph (216 ms -> 173 ms with a transaction; 88 -> 52 ms with cached statements).
|
|
140
140
|
// It also makes a failed build atomic — no half-graph that could be mistaken for a complete one.
|
|
141
141
|
let resolution = {
|
|
142
|
-
resolved: 0, ambiguous: 0, unknownTarget: 0, callerNotNamed: 0, selfReference: 0, moduleScoped: 0,
|
|
142
|
+
resolved: 0, ambiguousEdges: 0, ambiguous: 0, unknownTarget: 0, callerNotNamed: 0, selfReference: 0, moduleScoped: 0,
|
|
143
143
|
};
|
|
144
144
|
graph.transaction(() => {
|
|
145
145
|
// Add spec nodes and dependencies
|
|
@@ -185,7 +185,7 @@ function buildRtm(specs, scanned, graph, opts) {
|
|
|
185
185
|
*/
|
|
186
186
|
function addCallEdges(scanned, graph, opts) {
|
|
187
187
|
const report = {
|
|
188
|
-
resolved: 0, ambiguous: 0, unknownTarget: 0, callerNotNamed: 0, selfReference: 0, moduleScoped: 0,
|
|
188
|
+
resolved: 0, ambiguousEdges: 0, ambiguous: 0, unknownTarget: 0, callerNotNamed: 0, selfReference: 0, moduleScoped: 0,
|
|
189
189
|
};
|
|
190
190
|
// @implements A-SPEC-300
|
|
191
191
|
// Indexes are built PER LANGUAGE FAMILY. Resolution used to look across the whole scan, so a Java
|
|
@@ -247,11 +247,37 @@ function addCallEdges(scanned, graph, opts) {
|
|
|
247
247
|
}
|
|
248
248
|
const family = familyOf(f.sourcePath);
|
|
249
249
|
const table = definedIn.get(family);
|
|
250
|
+
// @implements A-SPEC-573.5 — an ambiguous target is a CANDIDATE SET, not a dead end. Both
|
|
251
|
+
// shapes of ambiguity keep every candidate on `calls_ambiguous`; `calls` still means "one
|
|
252
|
+
// place, certain", so nothing that reads certainty changes. Emitting nothing was buying
|
|
253
|
+
// precision at the cost of 22.6% of the call graph.
|
|
254
|
+
const ambiguousCandidates = (qn, paths) => {
|
|
255
|
+
for (const p of paths) {
|
|
256
|
+
const to = `CODE:${qn}@${p}`;
|
|
257
|
+
if (to === fromId)
|
|
258
|
+
continue; // self-recursion carries no impact, ambiguous or not
|
|
259
|
+
graph.addEdge(fromId, to, 'calls_ambiguous', f.sourcePath, fact(opts, `${f.sourcePath}`, 'name-resolution', null));
|
|
260
|
+
report.ambiguousEdges++;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
250
263
|
let target = null;
|
|
251
264
|
if (table?.has(e.to))
|
|
252
265
|
target = { qn: e.to, paths: table.get(e.to) };
|
|
253
|
-
else
|
|
254
|
-
|
|
266
|
+
else {
|
|
267
|
+
const byLast = byLastSegment.get(family);
|
|
268
|
+
const hit = byLast?.get(e.to);
|
|
269
|
+
if (hit === null) {
|
|
270
|
+
// The last segment is owned by several qualified names — keep every owner as a candidate.
|
|
271
|
+
for (const [qn, paths] of table ?? []) {
|
|
272
|
+
const last = qn.includes('.') ? qn.slice(qn.lastIndexOf('.') + 1) : qn;
|
|
273
|
+
if (last === e.to)
|
|
274
|
+
ambiguousCandidates(qn, paths);
|
|
275
|
+
}
|
|
276
|
+
report.unknownTarget++;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
target = hit ?? null;
|
|
280
|
+
}
|
|
255
281
|
if (!target) {
|
|
256
282
|
report.unknownTarget++;
|
|
257
283
|
continue;
|
|
@@ -259,9 +285,10 @@ function addCallEdges(scanned, graph, opts) {
|
|
|
259
285
|
const sameFile = target.paths.includes(f.sourcePath);
|
|
260
286
|
const path = sameFile ? f.sourcePath : (target.paths.length === 1 ? target.paths[0] : null);
|
|
261
287
|
if (!path) {
|
|
288
|
+
ambiguousCandidates(target.qn, target.paths);
|
|
262
289
|
report.ambiguous++;
|
|
263
290
|
continue;
|
|
264
|
-
}
|
|
291
|
+
}
|
|
265
292
|
const toId = `CODE:${target.qn}@${path}`;
|
|
266
293
|
if (toId === fromId) {
|
|
267
294
|
report.selfReference++;
|
|
@@ -136,6 +136,17 @@ export declare class RtmGraph {
|
|
|
136
136
|
*/
|
|
137
137
|
maxCalleeInDegree?: number;
|
|
138
138
|
}): string[];
|
|
139
|
+
/**
|
|
140
|
+
* Every resolved FILE->FILE `imports` edge, as repo-relative path pairs.
|
|
141
|
+
*
|
|
142
|
+
* Exists because cycle detection needs the whole import graph at once, and the alternative —
|
|
143
|
+
* parsing `dumpCanonical()` — builds a multi-megabyte string on every call to answer a question
|
|
144
|
+
* one query answers.
|
|
145
|
+
*/
|
|
146
|
+
importEdges(): Array<{
|
|
147
|
+
from: string;
|
|
148
|
+
to: string;
|
|
149
|
+
}>;
|
|
139
150
|
/**
|
|
140
151
|
* @implements A-SPEC-289
|
|
141
152
|
* Repo-relative paths of the files that import this one, via resolved FILE->FILE `imports` edges.
|
|
@@ -224,6 +224,19 @@ class RtmGraph {
|
|
|
224
224
|
: this.callees(id).filter((callee) => this.callerCount(callee) <= limit);
|
|
225
225
|
return [...new Set([...this.impactSourcesOf(id), ...downstream])].sort();
|
|
226
226
|
}
|
|
227
|
+
// @implements A-SPEC-574.2
|
|
228
|
+
/**
|
|
229
|
+
* Every resolved FILE->FILE `imports` edge, as repo-relative path pairs.
|
|
230
|
+
*
|
|
231
|
+
* Exists because cycle detection needs the whole import graph at once, and the alternative —
|
|
232
|
+
* parsing `dumpCanonical()` — builds a multi-megabyte string on every call to answer a question
|
|
233
|
+
* one query answers.
|
|
234
|
+
*/
|
|
235
|
+
importEdges() {
|
|
236
|
+
return this.db.prepare("SELECT DISTINCT src, dst FROM edges WHERE rel='imports' ORDER BY src ASC, dst ASC")
|
|
237
|
+
.all()
|
|
238
|
+
.map((r) => ({ from: r.src.replace(/^FILE:/, ''), to: r.dst.replace(/^FILE:/, '') }));
|
|
239
|
+
}
|
|
227
240
|
/**
|
|
228
241
|
* @implements A-SPEC-289
|
|
229
242
|
* Repo-relative paths of the files that import this one, via resolved FILE->FILE `imports` edges.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LEGACY_DEPENDS_FIELD = void 0;
|
|
4
|
+
// @implements A-SPEC-574.3
|
|
5
|
+
// One constant, in a module neither side owns. `spec-parser` needed the field name and `legacy-format`
|
|
6
|
+
// needed the parser's `Spec` type, so each imported the other — a cycle held up by a single string.
|
|
7
|
+
// Shared things belong to a third module; that is the whole rule.
|
|
8
|
+
/** Frontmatter key that carries a pre-migration `depends_on` value. */
|
|
9
|
+
exports.LEGACY_DEPENDS_FIELD = 'legacy_depends_on';
|
|
@@ -48,7 +48,7 @@ export declare const LEGACY_STATUS_FIELD = "legacy_status";
|
|
|
48
48
|
* computation — asserting it anyway is the fabrication this repository keeps removing. Verbatim
|
|
49
49
|
* preservation leaves the judgement to a person who can actually make it.
|
|
50
50
|
*/
|
|
51
|
-
export
|
|
51
|
+
export { LEGACY_DEPENDS_FIELD } from './legacy-fields';
|
|
52
52
|
/**
|
|
53
53
|
* Classify a document by FORMAT, not by validity.
|
|
54
54
|
*
|
|
@@ -21,7 +21,10 @@ exports.LEGACY_STATUS_FIELD = 'legacy_status';
|
|
|
21
21
|
* computation — asserting it anyway is the fabrication this repository keeps removing. Verbatim
|
|
22
22
|
* preservation leaves the judgement to a person who can actually make it.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
// @implements A-SPEC-574.3 — moved to `legacy-fields` and re-exported, so existing importers
|
|
25
|
+
// keep working while the cycle it created is gone.
|
|
26
|
+
var legacy_fields_1 = require("./legacy-fields");
|
|
27
|
+
Object.defineProperty(exports, "LEGACY_DEPENDS_FIELD", { enumerable: true, get: function () { return legacy_fields_1.LEGACY_DEPENDS_FIELD; } });
|
|
25
28
|
/**
|
|
26
29
|
* Classify a document by FORMAT, not by validity.
|
|
27
30
|
*
|
|
@@ -37,7 +37,9 @@ exports.parseDependsOn = parseDependsOn;
|
|
|
37
37
|
exports.parseSpec = parseSpec;
|
|
38
38
|
exports.serializeSpec = serializeSpec;
|
|
39
39
|
const yaml = __importStar(require("js-yaml"));
|
|
40
|
-
|
|
40
|
+
// @implements A-SPEC-574.3 — from the shared module, not from `legacy-format`, which imports
|
|
41
|
+
// this file's `Spec` type. That pair was a cycle over one string constant.
|
|
42
|
+
const legacy_fields_1 = require("./legacy-fields");
|
|
41
43
|
/**
|
|
42
44
|
* Read `depends_on` in every spelling the corpus contains, keeping what cannot be represented.
|
|
43
45
|
*
|
|
@@ -121,8 +123,8 @@ function parseSpec(input) {
|
|
|
121
123
|
const deps = parseDependsOn(fm.depends_on);
|
|
122
124
|
// @implements A-SPEC-221 — never overwrite an existing preservation: a second round trip must not
|
|
123
125
|
// chew up what the first one saved, the same rule `legacy_status` follows.
|
|
124
|
-
if (deps.legacy !== null && fm[
|
|
125
|
-
fm[
|
|
126
|
+
if (deps.legacy !== null && fm[legacy_fields_1.LEGACY_DEPENDS_FIELD] === undefined) {
|
|
127
|
+
fm[legacy_fields_1.LEGACY_DEPENDS_FIELD] = deps.legacy;
|
|
126
128
|
}
|
|
127
129
|
return {
|
|
128
130
|
id: String(fm.id ?? ''),
|
package/docs/install-guide.md
CHANGED
|
@@ -91,19 +91,63 @@ Your npm metadata cache predates the release — measured minutes after publishi
|
|
|
91
91
|
registry already listed the version while a default-cache install still refused it. Add
|
|
92
92
|
`--prefer-online`, or retry in a few minutes.
|
|
93
93
|
|
|
94
|
+
### npm 12: `better-sqlite3` has no binary and nothing failed
|
|
95
|
+
|
|
96
|
+
npm 12 (and npm ≥ 11.19) **blocks dependency install scripts by default** and skips them
|
|
97
|
+
silently: `npm ci` exits 0, `better-sqlite3` never runs `prebuild-install`, and the first
|
|
98
|
+
`require` dies with "Could not locate the bindings file". Measured 2026-09-09 (Windows 11,
|
|
99
|
+
npm 12.0.1, Node 24.19.0). `npx holmes-kit doctor` names this cause as `scripts-blocked` and prints
|
|
100
|
+
the commands below; do not reinstall — a reinstall reproduces the same state.
|
|
101
|
+
|
|
102
|
+
Only **one** package needs its script: `better-sqlite3`. The 8 tree-sitter packages are also
|
|
103
|
+
listed as blocked, but they load from their shipped `prebuilds/` without the script (measured on
|
|
104
|
+
all 8), so they are deliberately NOT approved. This repository's `package.json` therefore carries
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
"allowScripts": { "better-sqlite3@12.11.1": true }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
pinned to the lockfile version, so a dependency bump forces a fresh review (a test fails until the
|
|
111
|
+
pin is updated). Recovery, by how you installed — on Windows use the `.cmd` spellings (see below):
|
|
112
|
+
|
|
113
|
+
| Layout | Commands |
|
|
114
|
+
|---|---|
|
|
115
|
+
| A project that depends on holmes-kit | `npm install-scripts approve better-sqlite3@12.11.1` then `npm rebuild better-sqlite3 --foreground-scripts` (from the project root; this writes the pin into YOUR package.json) |
|
|
116
|
+
| This repository checkout | already approved — `npm rebuild better-sqlite3 --foreground-scripts` if the binary is missing |
|
|
117
|
+
| Global (`npm install -g`) | `npm rebuild -g better-sqlite3 --foreground-scripts --allow-scripts=better-sqlite3` — a per-command flag scoped to one package; no npm config is changed. Target the dependency: `rebuild -g @holmes-lab/holmes-kit` re-links the bin and dies `EEXIST` under npm 12 (measured). If this fails `EPERM` under `C:\Program Files\nodejs`, your prefix is protected — see "Before `npm install -g`" above |
|
|
118
|
+
| Bare `npx` | not repairable in place — do a local install and use the first row |
|
|
119
|
+
|
|
120
|
+
Never use `--dangerously-allow-all-scripts` or a global `allow-scripts` config: the point of the
|
|
121
|
+
policy is that only the reviewed package runs code at install time. Older npm 11 (measured 11.6.2)
|
|
122
|
+
ignores the `allowScripts` field and runs scripts as before; npm 11.19 honours it exactly like 12.
|
|
123
|
+
|
|
124
|
+
### Windows: `npm`/`npx` are blocked by the PowerShell execution policy
|
|
125
|
+
|
|
126
|
+
`npm`, `npx` and `holmes-kit` resolve to `.ps1` shims first, and a `Restricted`/`AllSigned`
|
|
127
|
+
policy refuses them (PSSecurityException). The `.cmd` shims always run: `npm.cmd`, `npx.cmd`.
|
|
128
|
+
doctor emits `.cmd` commands on Windows and appends the policy note when it can observe a
|
|
129
|
+
blocking policy. Do not change the execution policy to fix this.
|
|
130
|
+
|
|
94
131
|
### `better-sqlite3` fails to build
|
|
95
132
|
|
|
96
133
|
The one dependency that may need a toolchain. The 8 tree-sitter grammars ship prebuilt binaries
|
|
97
134
|
(`darwin-arm64`, `darwin-x64`, `linux-x64`, `win32-x64`) and compile nothing; `better-sqlite3`
|
|
98
135
|
downloads a prebuild at install time and **falls back to compiling** when none matches your
|
|
99
|
-
platform and Node ABI.
|
|
136
|
+
platform and Node ABI. doctor reports this as `build-failed` — with the approval in place, the
|
|
137
|
+
script ran and left no binary — and names what it can observe on Windows (Python on PATH, Visual
|
|
138
|
+
Studio C++ Build Tools, a space in the install path). It cannot observe a failed prebuild
|
|
139
|
+
download; rerun with `--foreground-scripts` to see the script's own output. If it compiles, you
|
|
140
|
+
need:
|
|
100
141
|
|
|
101
142
|
| Platform | Toolchain |
|
|
102
143
|
|---|---|
|
|
103
|
-
| Windows | Visual Studio Build Tools (C++ workload) |
|
|
144
|
+
| Windows | Visual Studio Build Tools (C++ workload) + Python 3 on PATH; prefer an install path without spaces |
|
|
104
145
|
| macOS | Xcode Command Line Tools (`xcode-select --install`) |
|
|
105
146
|
| Alpine | `apk add --no-cache python3 make g++` |
|
|
106
147
|
|
|
148
|
+
A binary that exists but fails with `NODE_MODULE_VERSION` was built for another Node — doctor
|
|
149
|
+
calls that `abi-mismatch`; rebuild against the Node you run.
|
|
150
|
+
|
|
107
151
|
### `spawn sh ENOENT` during a git-URL install
|
|
108
152
|
|
|
109
153
|
`npm i -g git+ssh://…` is not a supported path: npm 11 clones the repository into its cache and
|
|
@@ -117,15 +161,20 @@ npx holmes-kit doctor # local install
|
|
|
117
161
|
holmes-kit doctor # global install
|
|
118
162
|
```
|
|
119
163
|
|
|
120
|
-
Expect `
|
|
164
|
+
Expect `0 fail` on a healthy install (a few `warn` lines are normal — measured `12 pass, 4 warn,
|
|
165
|
+
0 fail` on a Windows source checkout). The lines that matter most:
|
|
121
166
|
|
|
122
167
|
- `global prefix` — whether `-g` would work on this machine, and the remedy if not
|
|
123
|
-
- `tree-sitter grammars`
|
|
168
|
+
- `tree-sitter grammars` — every grammar actually PARSES in a fresh process (`8 grammars parse`)
|
|
169
|
+
- `better-sqlite3` — loads and runs a `:memory:` query; on FAIL the detail names the cause
|
|
170
|
+
(`scripts-blocked`, `abi-mismatch`, `build-failed`, or `unknown` with the raw error) and the fix
|
|
171
|
+
is the exact command for your install layout
|
|
124
172
|
|
|
125
173
|
## What we deliberately do NOT do
|
|
126
174
|
|
|
127
175
|
| Idea | Why not |
|
|
128
176
|
|---|---|
|
|
129
|
-
| A `postinstall` script that prints guidance |
|
|
177
|
+
| A `postinstall` script that prints guidance | Under npm 12 it would be blocked like any other install script, and it forfeits this package's property of running no install scripts of its own |
|
|
178
|
+
| Approving the tree-sitter grammars in `allowScripts` | Measured unnecessary — they load from shipped prebuilds — and every extra approval is code that runs at install time |
|
|
130
179
|
| Recommending `npx @holmes-lab/holmes-kit init` with no install | `init` writes wiring with absolute paths; under bare `npx` those point into the npx cache and break when it is pruned |
|
|
131
180
|
| Fixing your npm prefix from inside the package | A package rewriting your npm configuration is exactly the supply-chain behaviour this guide warns about |
|
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.
|
|
4
|
+
"version": "0.19.2",
|
|
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",
|
|
@@ -85,5 +85,8 @@
|
|
|
85
85
|
},
|
|
86
86
|
"publishConfig": {
|
|
87
87
|
"access": "public"
|
|
88
|
+
},
|
|
89
|
+
"allowScripts": {
|
|
90
|
+
"better-sqlite3@12.11.1": true
|
|
88
91
|
}
|
|
89
92
|
}
|
|
@@ -136,3 +136,17 @@ T-SPEC 필수 섹션은 4분면 그대로다: **Normal · Corner · Negative ·
|
|
|
136
136
|
번호가 A-SPEC과 T-SPEC에 한정되는 것, 그리고 스펙 폴더 밑 `.ts`가 author 게이트로 강등되지 않는다는 사실. 번호
|
|
137
137
|
상속 관습은 엔진이 강제하지 않으므로 테스트도 고정하지 않는다 — 관습은 관습이라 말한다. 변이
|
|
138
138
|
검사로 판별력을 증명한 뒤 신뢰한다.
|
|
139
|
+
|
|
140
|
+
### 순환 의존을 만들지 않는다
|
|
141
|
+
|
|
142
|
+
두 모듈이 서로를 import 하면 순환이다. **설계 단계에서 피하는 것이 유일하게 값싼 시점이다** ——
|
|
143
|
+
코드가 쓰인 뒤에는 공유 타입 추출이나 지연 `require()` 워크어라운드로만 풀 수 있고, 후자는
|
|
144
|
+
순환을 숨길 뿐 없애지 않는다.
|
|
145
|
+
|
|
146
|
+
- **공유 타입은 별도 모듈로 뺀다.** A 와 B 가 같은 타입을 필요로 하면 그 타입은 A 도 B 도 아닌
|
|
147
|
+
세 번째 모듈에 있어야 한다.
|
|
148
|
+
- **타입만 필요하면 `import type` 을 쓴다.** TypeScript 가 방출에서 지우므로 런타임 순환이 되지
|
|
149
|
+
않는다. 값 import 로 두면 타입만 쓰면서도 순환을 만든다.
|
|
150
|
+
- **지연 `require()` 로 순환을 우회하지 않는다.** 그것은 수리가 아니라 청구서 이연이다.
|
|
151
|
+
|
|
152
|
+
이 저장소는 **스펙 그래프의 무순환을 ART-2 로 집행**한다. 코드 그래프도 같은 기준을 향한다.
|
|
@@ -96,6 +96,38 @@ npm publish --access public
|
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
|
99
|
+
### 6단계: 외부 문서 표면 (External Docs Reach) — 발행이 닿는 곳까지 정직하게
|
|
100
|
+
|
|
101
|
+
npm 만 갱신하고 끝나면, 사람들이 실제로 읽는 문서는 낡은 채로 남는다. **사고 이력**: 이 저장소에는
|
|
102
|
+
GitHub 리모트가 없어(origin 이 로컬 gitea) 0.16.0·0.17.0·0.18.0 **세 릴리스 동안** GitHub README 가
|
|
103
|
+
한 번도 갱신되지 않았고, `package.json` 의 `homepage` 가 바로 그 문서를 가리킨다.
|
|
104
|
+
|
|
105
|
+
**발행이 성공한 뒤에만** 실행한다 — 실패한 릴리스의 문서를 최신이라고 주장하지 않는다.
|
|
106
|
+
|
|
107
|
+
1. **대상 파생 (하드코딩 금지)**: `package.json` 의 `repository` 에서 `<owner>/<repo>` 를 얻는다
|
|
108
|
+
(`repoTargetFrom`). 이 플레이북은 **소비 프로젝트에도 설치**되므로 특정 저장소를 박아 두면 남의
|
|
109
|
+
릴리스가 그 저장소를 덮어쓴다. 파생 실패 → **SKIP(사유: `repository` 필드 없음/비-GitHub)**.
|
|
110
|
+
2. **덮어쓸 것을 먼저 본다**: `gh api repos/<owner>/<repo>/contents/README.md` 로 원격을 읽어
|
|
111
|
+
로컬과의 차이를 **보고**한다. 원격이 분기했다면 동기화는 남의 편집을 지우는 행위다 — 사람이
|
|
112
|
+
알고 결정해야 한다.
|
|
113
|
+
3. **동기화**: 로컬 `README.md` 를 `gh api --method PUT` 으로 올린다(`sha` 는 2 에서 읽은 값).
|
|
114
|
+
4. **재조회 검증**: 다시 읽어 로컬과 일치하는지 확인한다. **쓴 것과 남은 것은 다를 수 있다** —
|
|
115
|
+
검증 없는 "갱신했다"는 주장이지 사실이 아니다.
|
|
116
|
+
5. **profile README drift 감지 (자동 수정 금지)**: `<owner>/.github` 의 `profile/README.md` 를 읽어
|
|
117
|
+
`profileDriftFindings` 로 주장-현실 불일치를 **보고만** 한다. 포지셔닝 문안은 오너 결정이며
|
|
118
|
+
릴리스 절차가 정할 것이 아니다.
|
|
119
|
+
6. **SKIP 은 값이지 침묵이 아니다**: `gh` 미설치 / 미인증(`gh auth status` 실패) / 권한 없음 /
|
|
120
|
+
`repository` 부재 — 각각 **무엇을 하지 않았는지 명시**하고 다음으로 간다. 소비 프로젝트의 발행을
|
|
121
|
+
우리 편의로 막지 않는다. 다만 **조용한 성공은 금지** — 아무 말 없이 넘어가면 그것은 3단계
|
|
122
|
+
전부를 한 것처럼 읽힌다.
|
|
123
|
+
|
|
124
|
+
> [!CAUTION]
|
|
125
|
+
> 2.5단계가 로컬 문서에 적용하는 규율("drift 도 누락도 거짓 주장이다")은 **외부 표면에도** 적용된다.
|
|
126
|
+
> 다른 점은 하나뿐이다: repo README 는 로컬의 사본이라 **동기화**하고, profile README 는 독립
|
|
127
|
+
> 문서라 **감지·보고**한다. 무엇이 정본인가가 처방을 정한다.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
99
131
|
## 플레이북 트리거 조건
|
|
100
132
|
- "npm publish"
|
|
101
133
|
- "release to npm"
|
|
@@ -85,3 +85,17 @@ holmes는 기계적으로 판별한다: `red-error`로는 red→green 시퀀스
|
|
|
85
85
|
|
|
86
86
|
`src/holmes/playbooks/tdd-slice.test.ts` — 이 스킬이 집행 태그(ART-1/4/8)와 red-assertion/red-error
|
|
87
87
|
구별을 담고, 설치기가 이를 발견함을 고정한다. 상시 스위트 포함.
|
|
88
|
+
|
|
89
|
+
### 순환 의존을 만들지 않는다
|
|
90
|
+
|
|
91
|
+
두 모듈이 서로를 import 하면 순환이다. **설계 단계에서 피하는 것이 유일하게 값싼 시점이다** ——
|
|
92
|
+
코드가 쓰인 뒤에는 공유 타입 추출이나 지연 `require()` 워크어라운드로만 풀 수 있고, 후자는
|
|
93
|
+
순환을 숨길 뿐 없애지 않는다.
|
|
94
|
+
|
|
95
|
+
- **공유 타입은 별도 모듈로 뺀다.** A 와 B 가 같은 타입을 필요로 하면 그 타입은 A 도 B 도 아닌
|
|
96
|
+
세 번째 모듈에 있어야 한다.
|
|
97
|
+
- **타입만 필요하면 `import type` 을 쓴다.** TypeScript 가 방출에서 지우므로 런타임 순환이 되지
|
|
98
|
+
않는다. 값 import 로 두면 타입만 쓰면서도 순환을 만든다.
|
|
99
|
+
- **지연 `require()` 로 순환을 우회하지 않는다.** 그것은 수리가 아니라 청구서 이연이다.
|
|
100
|
+
|
|
101
|
+
이 저장소는 **스펙 그래프의 무순환을 ART-2 로 집행**한다. 코드 그래프도 같은 기준을 향한다.
|