@holmes-lab/holmes-kit 0.2.1 → 0.3.0
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 +21 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/assoc/assoc-arm.d.ts +94 -0
- package/dist/holmes/assoc/assoc-arm.js +187 -0
- package/dist/holmes/assoc/explore.d.ts +21 -0
- package/dist/holmes/assoc/explore.js +160 -0
- package/dist/holmes/assoc/impact-baseline.d.ts +13 -0
- package/dist/holmes/assoc/impact-baseline.js +48 -0
- package/dist/holmes/assoc/ppr.d.ts +64 -0
- package/dist/holmes/assoc/ppr.js +110 -0
- package/dist/holmes/cli/agents.d.ts +24 -11
- package/dist/holmes/cli/agents.js +93 -21
- package/dist/holmes/cli/doctor.d.ts +56 -0
- package/dist/holmes/cli/doctor.js +311 -5
- package/dist/holmes/cli/index.js +13 -1
- package/dist/holmes/cli/semantic-key.d.ts +19 -0
- package/dist/holmes/cli/semantic-key.js +93 -0
- package/dist/holmes/config/config.d.ts +9 -0
- package/dist/holmes/config/config.js +8 -1
- package/dist/holmes/cpg/language-capability.d.ts +65 -0
- package/dist/holmes/cpg/language-capability.js +145 -0
- package/dist/holmes/cpg/language-parser-walk.js +179 -34
- package/dist/holmes/cpg/language-parser.d.ts +1 -1
- package/dist/holmes/governance/ledger-rechain.d.ts +12 -0
- package/dist/holmes/governance/ledger-rechain.js +17 -2
- package/dist/holmes/governance/provenance-ledger.js +21 -0
- package/dist/holmes/guardrail/blind-spots.js +12 -1
- package/dist/holmes/guardrail/impact-gate.d.ts +77 -0
- package/dist/holmes/guardrail/impact-gate.js +263 -0
- package/dist/holmes/guardrail/write-target.d.ts +38 -1
- package/dist/holmes/guardrail/write-target.js +41 -4
- package/dist/holmes/hooks/adapters/antigravity.js +12 -1
- package/dist/holmes/hooks/corrupt-state-run.d.ts +33 -0
- package/dist/holmes/hooks/corrupt-state-run.js +16 -0
- package/dist/holmes/hooks/pre-tool-use.d.ts +26 -0
- package/dist/holmes/hooks/pre-tool-use.js +203 -11
- package/dist/holmes/hooks/stop.d.ts +65 -0
- package/dist/holmes/hooks/stop.js +200 -2
- package/dist/holmes/mcp/handlers.d.ts +88 -0
- package/dist/holmes/mcp/handlers.js +597 -16
- package/dist/holmes/mcp/maintenance-analyze.d.ts +435 -0
- package/dist/holmes/mcp/maintenance-analyze.js +994 -0
- package/dist/holmes/mcp/maintenance-evidence.d.ts +140 -0
- package/dist/holmes/mcp/maintenance-evidence.js +253 -0
- package/dist/holmes/mcp/tool-schemas.js +71 -0
- package/dist/holmes/project/root.js +3 -1
- package/dist/holmes/review/baseline-arm.d.ts +37 -0
- package/dist/holmes/review/baseline-arm.js +51 -0
- package/dist/holmes/review/captured-stdin-guard.d.ts +8 -0
- package/dist/holmes/review/captured-stdin-guard.js +48 -0
- package/dist/holmes/review/coherence-verify.d.ts +31 -0
- package/dist/holmes/review/coherence-verify.js +144 -0
- package/dist/holmes/review/commit-text.d.ts +50 -0
- package/dist/holmes/review/commit-text.js +76 -0
- package/dist/holmes/review/confidence-calibration.d.ts +39 -0
- package/dist/holmes/review/confidence-calibration.js +39 -0
- package/dist/holmes/review/content-baseline.d.ts +38 -0
- package/dist/holmes/review/content-baseline.js +103 -0
- package/dist/holmes/review/content-verify.d.ts +20 -0
- package/dist/holmes/review/content-verify.js +73 -0
- package/dist/holmes/review/dense-retrieval.d.ts +66 -0
- package/dist/holmes/review/dense-retrieval.js +97 -0
- package/dist/holmes/review/edge-quality.d.ts +44 -0
- package/dist/holmes/review/edge-quality.js +117 -0
- package/dist/holmes/review/evaluation-metrics.d.ts +138 -0
- package/dist/holmes/review/evaluation-metrics.js +175 -0
- package/dist/holmes/review/graph-verifier.d.ts +34 -0
- package/dist/holmes/review/graph-verifier.js +62 -0
- package/dist/holmes/review/hop-ablation.d.ts +100 -0
- package/dist/holmes/review/hop-ablation.js +89 -0
- package/dist/holmes/review/manual-baseline.d.ts +209 -0
- package/dist/holmes/review/manual-baseline.js +2846 -0
- package/dist/holmes/review/oracle-gap.d.ts +32 -0
- package/dist/holmes/review/oracle-gap.js +102 -0
- package/dist/holmes/review/point-in-time-replay.d.ts +41 -0
- package/dist/holmes/review/point-in-time-replay.js +161 -0
- package/dist/holmes/review/rank-diagnosis.d.ts +43 -0
- package/dist/holmes/review/rank-diagnosis.js +163 -0
- package/dist/holmes/review/replay-calibration.d.ts +62 -0
- package/dist/holmes/review/replay-calibration.js +83 -0
- package/dist/holmes/review/replay-corpus.d.ts +135 -0
- package/dist/holmes/review/replay-corpus.js +210 -0
- package/dist/holmes/review/run-replay.d.ts +260 -0
- package/dist/holmes/review/run-replay.js +729 -0
- package/dist/holmes/review/semantic-arm.d.ts +271 -0
- package/dist/holmes/review/semantic-arm.js +717 -0
- package/dist/holmes/review/semantic-retrieval.d.ts +55 -0
- package/dist/holmes/review/semantic-retrieval.js +156 -0
- package/dist/holmes/review/spec-layer-stats.d.ts +38 -0
- package/dist/holmes/review/spec-layer-stats.js +52 -0
- package/dist/holmes/review/temporal-prior.d.ts +33 -0
- package/dist/holmes/review/temporal-prior.js +53 -0
- package/dist/holmes/review/test-runner.d.ts +15 -0
- package/dist/holmes/review/test-runner.js +41 -4
- package/dist/holmes/review/test-selection-breadth.d.ts +75 -0
- package/dist/holmes/review/test-selection-breadth.js +57 -0
- package/dist/holmes/review/traceability-benchmark.d.ts +81 -0
- package/dist/holmes/review/traceability-benchmark.js +135 -0
- package/dist/holmes/review/union-verify.d.ts +12 -0
- package/dist/holmes/review/union-verify.js +70 -0
- package/dist/holmes/rtm/graph-store.d.ts +51 -0
- package/dist/holmes/rtm/graph-store.js +122 -0
- package/dist/holmes/rtm/incremental.d.ts +25 -1
- package/dist/holmes/rtm/incremental.js +18 -1
- package/dist/holmes/rtm/localize.d.ts +28 -0
- package/dist/holmes/rtm/localize.js +272 -14
- package/dist/holmes/rtm/rtm-builder.d.ts +118 -3
- package/dist/holmes/rtm/rtm-builder.js +265 -28
- package/dist/holmes/rtm/rtm-graph.d.ts +117 -2
- package/dist/holmes/rtm/rtm-graph.js +194 -31
- package/dist/holmes/rtm/taint-benchmark.d.ts +97 -0
- package/dist/holmes/rtm/taint-benchmark.js +141 -0
- package/dist/holmes/rtm/test-scope.js +24 -1
- package/dist/holmes/semantic/credentials.d.ts +15 -0
- package/dist/holmes/semantic/credentials.js +134 -0
- package/dist/holmes/semantic/embedder.d.ts +44 -0
- package/dist/holmes/semantic/embedder.js +185 -0
- package/dist/holmes/semantic/hit-rerank.d.ts +4 -0
- package/dist/holmes/semantic/hit-rerank.js +38 -0
- package/dist/holmes/semantic/tier.d.ts +37 -0
- package/dist/holmes/semantic/tier.js +54 -0
- package/dist/holmes/semantic/vector-cache.d.ts +11 -0
- package/dist/holmes/semantic/vector-cache.js +91 -0
- package/dist/holmes/spec/acceptance-quality.d.ts +81 -0
- package/dist/holmes/spec/acceptance-quality.js +169 -0
- package/dist/holmes/spec/validator.js +33 -1
- package/dist/holmes/spec/yaml-scalar.d.ts +1 -0
- package/dist/holmes/spec/yaml-scalar.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @implements A-SPEC-467
|
|
3
|
+
/**
|
|
4
|
+
* Personalized PageRank over the RTM/CPG edge set — the associative-recall primitive.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the shipped retrieval primitives sit at two extremes. `issue_localize` stops at
|
|
7
|
+
* one spec hop (its own comment defers anything further), and `rtm_impact` returns an UNGRADED
|
|
8
|
+
* transitive closure — measured on the replay corpus, 84% of a commit's co-changed files are
|
|
9
|
+
* reachable through that closure while the analysis names 11% of them: the bottleneck is selection
|
|
10
|
+
* within reach, not reach. What is missing between the two is graded multi-hop mass —
|
|
11
|
+
* Score(1-hop) > Score(2-hop) > … > 0 — which is exactly what a damped random walk provides.
|
|
12
|
+
*
|
|
13
|
+
* Eleven graph/semantic mechanisms were measured and rejected on this corpus before this one
|
|
14
|
+
* (binary k-hop expansion, caller closure, co-change retrieval, dense embeddings, …). PPR is
|
|
15
|
+
* structurally different from all of them: it produces a RANKING over nodes rather than an
|
|
16
|
+
* admission set, and the geometric damping suppresses the crowd problem that sank the closure.
|
|
17
|
+
* That difference is the reason this module exists; whether it PAYS is decided by the replay arm
|
|
18
|
+
* (rerank and admit modes scored separately, three corpora), never assumed here.
|
|
19
|
+
*
|
|
20
|
+
* Deterministic, pure, no I/O. PPR mass never enters a gate decision — association proposes,
|
|
21
|
+
* the deterministic layers judge.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.buildAdjacency = buildAdjacency;
|
|
25
|
+
exports.personalizedPageRank = personalizedPageRank;
|
|
26
|
+
/** Build the weighted, direction-aware, hub-damped transition structure once per graph. */
|
|
27
|
+
function buildAdjacency(edges, profile) {
|
|
28
|
+
// Degree first (undirected, unweighted): the hub penalty divides by how CONNECTED the target is,
|
|
29
|
+
// not by how heavily this particular rel weighs it.
|
|
30
|
+
const degree = new Map();
|
|
31
|
+
const kept = [];
|
|
32
|
+
for (const e of edges) {
|
|
33
|
+
const w = profile.rels[e.rel];
|
|
34
|
+
if (w === undefined || (w.fwd <= 0 && w.back <= 0))
|
|
35
|
+
continue;
|
|
36
|
+
kept.push({ src: e.src, dst: e.dst, fwd: w.fwd, back: w.back });
|
|
37
|
+
degree.set(e.src, (degree.get(e.src) ?? 0) + 1);
|
|
38
|
+
degree.set(e.dst, (degree.get(e.dst) ?? 0) + 1);
|
|
39
|
+
}
|
|
40
|
+
const damp = (node) => profile.hubExponent > 0 ? Math.pow(degree.get(node) ?? 1, profile.hubExponent) : 1;
|
|
41
|
+
const raw = new Map();
|
|
42
|
+
const nodes = new Set();
|
|
43
|
+
const add = (from, to, w) => {
|
|
44
|
+
if (w <= 0)
|
|
45
|
+
return;
|
|
46
|
+
(raw.get(from) ?? raw.set(from, []).get(from)).push({ to, w: w / damp(to) });
|
|
47
|
+
};
|
|
48
|
+
for (const e of kept) {
|
|
49
|
+
nodes.add(e.src);
|
|
50
|
+
nodes.add(e.dst);
|
|
51
|
+
add(e.src, e.dst, e.fwd);
|
|
52
|
+
add(e.dst, e.src, e.back);
|
|
53
|
+
}
|
|
54
|
+
// Normalize out-mass per node so the walk is a probability distribution.
|
|
55
|
+
for (const [, list] of raw) {
|
|
56
|
+
const total = list.reduce((a, b) => a + b.w, 0);
|
|
57
|
+
for (const o of list)
|
|
58
|
+
o.w /= total;
|
|
59
|
+
}
|
|
60
|
+
return { out: raw, nodes };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Power iteration: v' = d·(Wᵀv + danglingMass·s) + (1−d)·s.
|
|
64
|
+
*
|
|
65
|
+
* Dangling mass refluxes to the SEEDS, not uniformly — a node with no out-edges returns the walk
|
|
66
|
+
* to the question rather than to the whole graph, and this is also what keeps a disconnected
|
|
67
|
+
* component at exactly zero. The vector always sums to 1 (asserted by test): a distribution that
|
|
68
|
+
* silently leaks mass would make cross-config comparisons meaningless.
|
|
69
|
+
*/
|
|
70
|
+
function personalizedPageRank(adj, seeds, opts) {
|
|
71
|
+
const { d } = opts;
|
|
72
|
+
const maxIter = opts.maxIter ?? 10;
|
|
73
|
+
const eps = opts.eps ?? 1e-4;
|
|
74
|
+
const seedTotal = [...seeds.values()].reduce((a, b) => a + b, 0);
|
|
75
|
+
if (seedTotal <= 0)
|
|
76
|
+
return new Map();
|
|
77
|
+
const s = new Map();
|
|
78
|
+
for (const [k, v] of seeds)
|
|
79
|
+
if (v > 0)
|
|
80
|
+
s.set(k, v / seedTotal);
|
|
81
|
+
let v = new Map(s);
|
|
82
|
+
for (let i = 0; i < maxIter; i++) {
|
|
83
|
+
// walk = Wᵀv + dangling·s (dangling mass returns to the seeds), then v' = d·walk + (1−d)·s.
|
|
84
|
+
const walk = new Map();
|
|
85
|
+
let dangling = 0;
|
|
86
|
+
for (const [node, mass] of v) {
|
|
87
|
+
const out = adj.out.get(node);
|
|
88
|
+
if (out === undefined || out.length === 0) {
|
|
89
|
+
dangling += mass;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
for (const o of out)
|
|
93
|
+
walk.set(o.to, (walk.get(o.to) ?? 0) + mass * o.w);
|
|
94
|
+
}
|
|
95
|
+
const next = new Map();
|
|
96
|
+
for (const [k, sv] of s)
|
|
97
|
+
walk.set(k, (walk.get(k) ?? 0) + dangling * sv);
|
|
98
|
+
for (const [k, m] of walk)
|
|
99
|
+
next.set(k, d * m);
|
|
100
|
+
for (const [k, sv] of s)
|
|
101
|
+
next.set(k, (next.get(k) ?? 0) + (1 - d) * sv);
|
|
102
|
+
let l1 = 0;
|
|
103
|
+
for (const k of new Set([...v.keys(), ...next.keys()]))
|
|
104
|
+
l1 += Math.abs((v.get(k) ?? 0) - (next.get(k) ?? 0));
|
|
105
|
+
v = next;
|
|
106
|
+
if (eps > 0 && l1 < eps)
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
return v;
|
|
110
|
+
}
|
|
@@ -13,19 +13,32 @@ export type Agent = (typeof AGENTS)[number];
|
|
|
13
13
|
* 그 하네스에서 게이트가 **집행되는가**.
|
|
14
14
|
*
|
|
15
15
|
* - `claude` — PreToolUse/Stop 훅. 이 저장소가 처음부터 배선해 온 하네스.
|
|
16
|
-
* - `antigravity` —
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
16
|
+
* - `antigravity` — **true**, 실기 프로브로 확인(2026-08-30, `agy -p` 비대화 실행). Codex 에서
|
|
17
|
+
* 무너진 세 단계를 여기서는 전부 통과한다: `.agents/hooks.json` 이 적재되고(훅이 41회 호출됨),
|
|
18
|
+
* 페이로드가 어댑터의 `TOOL_MAP` 과 정확히 맞으며(`write_to_file`/`TargetFile`/`CodeContent`),
|
|
19
|
+
* 미승인 쓰기에 `deny` 를 내자 **파일이 생성되지 않았다.** Stop 훅의 헌법 감사와 루프 가드도
|
|
20
|
+
* 같은 실행에서 발화했다. 이 값의 근거는 이제 규약 문서가 아니라 실측이다.
|
|
21
|
+
* - `codex` — **false**, 실측(codex-cli 0.151.0, 2026-08-30, A-SPEC-442). Codex 는 훅을 집행할
|
|
22
|
+
* 수 있다: 벤더 규약대로 배선한 플러그인을 설치하면 PreToolUse 가 발화하고, exit 2 를 내면
|
|
23
|
+
* `echo BLOCKED > blocked.txt` 가 실제로 실행되지 않는다(프로브 4회 중 3·4번째).
|
|
24
|
+
*
|
|
25
|
+
* 그런데 Codex 는 플러그인을 **설치된 마켓플레이스 스냅샷**에서만 적재한다. 프로젝트 폴더에
|
|
26
|
+
* 매니페스트를 놓아도, 그것을 마켓플레이스로 등록해 두어도, 사용자가 `codex plugin add` 를
|
|
27
|
+
* 실행하기 전에는 훅이 발화하지 않는다(프로브 1·2번째). 설치는 사용자의 행위이고 doctor 는
|
|
28
|
+
* 오프라인이므로, init 이 끝난 시점에 이 값이 참이라고 말할 근거가 없다.
|
|
29
|
+
*
|
|
30
|
+
* A-SPEC-441 은 이 값을 true 로 올렸다. 그 결과 `AGENTS_MD(true)` 가 집행되지 않는 하네스에
|
|
31
|
+
* "Gates are **enforced**" 라고 적었다 — 이 파일의 머리말이 하지 말라고 적어 둔 바로 그것이다.
|
|
27
32
|
*/
|
|
28
33
|
export declare const HARNESS_ENFORCES: Record<Agent, boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* @implements A-SPEC-442
|
|
36
|
+
* codex 플러그인이 저장소 안에서 사는 자리. 저장소 루트에 `plugins/` 를 만들지 않으려고
|
|
37
|
+
* 중첩 경로를 쓴다 — 마켓플레이스의 `source.path` 가 `./` 로 시작하는 상대경로이기만 하면
|
|
38
|
+
* 중첩이 허용된다는 것은 실측으로 확인했다.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CODEX_PLUGIN_DIR: string;
|
|
41
|
+
export declare const CODEX_MARKETPLACE = "holmes-kit-local";
|
|
29
42
|
export interface AgentWiringOptions {
|
|
30
43
|
target: string;
|
|
31
44
|
packageRoot: string;
|
|
@@ -33,9 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.HARNESS_ENFORCES = exports.AGENTS = void 0;
|
|
36
|
+
exports.CODEX_MARKETPLACE = exports.CODEX_PLUGIN_DIR = exports.HARNESS_ENFORCES = exports.AGENTS = void 0;
|
|
37
37
|
exports.agentFiles = agentFiles;
|
|
38
38
|
exports.agentLinks = agentLinks;
|
|
39
|
+
// @implements A-SPEC-442
|
|
39
40
|
// @implements A-SPEC-193
|
|
40
41
|
const path = __importStar(require("node:path"));
|
|
41
42
|
const mcp_launcher_1 = require("./mcp-launcher");
|
|
@@ -52,23 +53,36 @@ exports.AGENTS = ['claude', 'antigravity', 'codex'];
|
|
|
52
53
|
* 그 하네스에서 게이트가 **집행되는가**.
|
|
53
54
|
*
|
|
54
55
|
* - `claude` — PreToolUse/Stop 훅. 이 저장소가 처음부터 배선해 온 하네스.
|
|
55
|
-
* - `antigravity` —
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* `
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
56
|
+
* - `antigravity` — **true**, 실기 프로브로 확인(2026-08-30, `agy -p` 비대화 실행). Codex 에서
|
|
57
|
+
* 무너진 세 단계를 여기서는 전부 통과한다: `.agents/hooks.json` 이 적재되고(훅이 41회 호출됨),
|
|
58
|
+
* 페이로드가 어댑터의 `TOOL_MAP` 과 정확히 맞으며(`write_to_file`/`TargetFile`/`CodeContent`),
|
|
59
|
+
* 미승인 쓰기에 `deny` 를 내자 **파일이 생성되지 않았다.** Stop 훅의 헌법 감사와 루프 가드도
|
|
60
|
+
* 같은 실행에서 발화했다. 이 값의 근거는 이제 규약 문서가 아니라 실측이다.
|
|
61
|
+
* - `codex` — **false**, 실측(codex-cli 0.151.0, 2026-08-30, A-SPEC-442). Codex 는 훅을 집행할
|
|
62
|
+
* 수 있다: 벤더 규약대로 배선한 플러그인을 설치하면 PreToolUse 가 발화하고, exit 2 를 내면
|
|
63
|
+
* `echo BLOCKED > blocked.txt` 가 실제로 실행되지 않는다(프로브 4회 중 3·4번째).
|
|
64
|
+
*
|
|
65
|
+
* 그런데 Codex 는 플러그인을 **설치된 마켓플레이스 스냅샷**에서만 적재한다. 프로젝트 폴더에
|
|
66
|
+
* 매니페스트를 놓아도, 그것을 마켓플레이스로 등록해 두어도, 사용자가 `codex plugin add` 를
|
|
67
|
+
* 실행하기 전에는 훅이 발화하지 않는다(프로브 1·2번째). 설치는 사용자의 행위이고 doctor 는
|
|
68
|
+
* 오프라인이므로, init 이 끝난 시점에 이 값이 참이라고 말할 근거가 없다.
|
|
69
|
+
*
|
|
70
|
+
* A-SPEC-441 은 이 값을 true 로 올렸다. 그 결과 `AGENTS_MD(true)` 가 집행되지 않는 하네스에
|
|
71
|
+
* "Gates are **enforced**" 라고 적었다 — 이 파일의 머리말이 하지 말라고 적어 둔 바로 그것이다.
|
|
66
72
|
*/
|
|
67
73
|
exports.HARNESS_ENFORCES = {
|
|
68
74
|
claude: true,
|
|
69
75
|
antigravity: true,
|
|
70
76
|
codex: false,
|
|
71
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* @implements A-SPEC-442
|
|
80
|
+
* codex 플러그인이 저장소 안에서 사는 자리. 저장소 루트에 `plugins/` 를 만들지 않으려고
|
|
81
|
+
* 중첩 경로를 쓴다 — 마켓플레이스의 `source.path` 가 `./` 로 시작하는 상대경로이기만 하면
|
|
82
|
+
* 중첩이 허용된다는 것은 실측으로 확인했다.
|
|
83
|
+
*/
|
|
84
|
+
exports.CODEX_PLUGIN_DIR = path.join('.holmes', 'codex-plugin', 'holmes-kit');
|
|
85
|
+
exports.CODEX_MARKETPLACE = 'holmes-kit-local';
|
|
72
86
|
const q = (p) => p;
|
|
73
87
|
// @implements A-SPEC-251.1 — same launch contract as the Claude wiring: npx-pin for installed
|
|
74
88
|
// packageRoots, node for source checkouts. Shared via mcpEntryForInstall so all three harnesses
|
|
@@ -99,6 +113,17 @@ const mcpConfig = (packageRoot, specsDir, launcher) => {
|
|
|
99
113
|
// Antigravity gate was silently off. (The MCP config above is different: it passes `args` as an
|
|
100
114
|
// array, where quoting would put literal quote characters into the path.)
|
|
101
115
|
const hookCommand = (packageRoot, script) => `node "${path.join(packageRoot, 'bin', script)}"`;
|
|
116
|
+
/**
|
|
117
|
+
* @implements A-SPEC-442
|
|
118
|
+
* Codex 훅이 부를 것: **Claude 와 같은 정책 커널**.
|
|
119
|
+
*
|
|
120
|
+
* Codex 의 PreToolUse 출력 스키마는 `hookSpecificOutput.permissionDecision`(allow|deny|ask)이며,
|
|
121
|
+
* 이것은 Claude 규약과 같은 모양이다(codex 0.151.0 바이너리의 JSON 스키마에서 확인). 커널은
|
|
122
|
+
* 이미 그 형식을 낸다. 반면 antigravity 어댑터는 `toolCall.name` 을 찾고 `{"decision":"deny"}` 를
|
|
123
|
+
* exit 0 으로 낸다 — 실기에서 Codex 는 그것을 이해하지 못하고 `PreToolUse Failed` 로 처리하며
|
|
124
|
+
* **차단하지 않는다**(실측). 적재되는 훅과 말이 통하는 훅은 다른 문제이고, 둘 다 틀려 있었다.
|
|
125
|
+
*/
|
|
126
|
+
const kernelCommand = (packageRoot, script) => `node "${path.join(packageRoot, 'dist', 'holmes', 'hooks', script)}"`;
|
|
102
127
|
const hooksJson = (packageRoot) => `${JSON.stringify({
|
|
103
128
|
'holmes-kit': {
|
|
104
129
|
PreToolUse: [{
|
|
@@ -137,11 +162,12 @@ This repository operates under **No Spec, No Code** governance. An approved spec
|
|
|
137
162
|
|
|
138
163
|
1. Run \`spec_slice_init\` or \`spec_create\` to author REQ -> H-SPEC -> A-SPEC.
|
|
139
164
|
2. Approve specs via \`spec_slice_approve\` or \`spec_approve\`.
|
|
140
|
-
3. **
|
|
141
|
-
4.
|
|
142
|
-
5.
|
|
165
|
+
3. **Before editing source**, run \`maintenance_analyze({ root, request, persist: true })\` and keep the digest it returns — that persisted analysis is what the pre-edit evidence gate checks.
|
|
166
|
+
4. **Write tests first** and verify failure (RED stage).
|
|
167
|
+
5. Approve the mirroring T-SPEC (\`A-SPEC-188\` -> \`T-SPEC-188\`).
|
|
168
|
+
6. Implement source code (GREEN stage).
|
|
143
169
|
|
|
144
|
-
Use \`spec_next\` to check the next required governance step. Use \`phase_check\` to verify action permissions ahead of execution.
|
|
170
|
+
Use \`spec_next\` to check the next required governance step. Use \`phase_check\` to verify action permissions ahead of execution. Use \`impact_gate_check\` to ask what the pre-edit evidence gate would say about an edit — it is advisory and blocks nothing, so it answers the same way in every harness.
|
|
145
171
|
|
|
146
172
|
## Enforcement in this Harness
|
|
147
173
|
|
|
@@ -169,12 +195,58 @@ function agentFiles(agent, opts) {
|
|
|
169
195
|
{ path: path.join(target, 'AGENTS.md'), content: AGENTS_MD(exports.HARNESS_ENFORCES.antigravity) },
|
|
170
196
|
];
|
|
171
197
|
case 'codex':
|
|
172
|
-
// @implements A-SPEC-
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
198
|
+
// @implements A-SPEC-442 (was A-SPEC-266, A-SPEC-441) — Codex plugin, at the layout Codex reads:
|
|
199
|
+
// 1. the repository becomes a LOCAL MARKETPLACE (`.agents/plugins/marketplace.json`),
|
|
200
|
+
// because Codex loads plugins only from installed marketplace snapshots;
|
|
201
|
+
// 2. the plugin manifest's `hooks` names `./hooks.json` at the PLUGIN ROOT — the vendor's
|
|
202
|
+
// plugin reference (embedded in the codex binary) defines it that way, and the
|
|
203
|
+
// `./hooks/hooks.json` A-SPEC-441 emitted was measured never to load.
|
|
204
|
+
// The MCP wiring stays in `.codex/config.toml` (merged by init.ts).
|
|
205
|
+
// Wiring is not installation: the user still runs `codex plugin marketplace add` and
|
|
206
|
+
// `codex plugin add`, which is why HARNESS_ENFORCES.codex is false.
|
|
177
207
|
return [
|
|
208
|
+
{
|
|
209
|
+
path: path.join(target, '.agents', 'plugins', 'marketplace.json'),
|
|
210
|
+
content: `${JSON.stringify({
|
|
211
|
+
name: exports.CODEX_MARKETPLACE,
|
|
212
|
+
interface: { displayName: 'Holmes-Kit (local)' },
|
|
213
|
+
plugins: [{
|
|
214
|
+
name: 'holmes-kit',
|
|
215
|
+
source: { source: 'local', path: `./${exports.CODEX_PLUGIN_DIR.split(path.sep).join('/')}` },
|
|
216
|
+
policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' },
|
|
217
|
+
category: 'Developer Tools',
|
|
218
|
+
}],
|
|
219
|
+
}, null, 2)}\n`,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
path: path.join(target, exports.CODEX_PLUGIN_DIR, '.codex-plugin', 'plugin.json'),
|
|
223
|
+
content: `${JSON.stringify({
|
|
224
|
+
name: 'holmes-kit',
|
|
225
|
+
version: '0.2.1',
|
|
226
|
+
description: 'Holmes-Kit deterministic governance plugin for Codex',
|
|
227
|
+
hooks: './hooks.json',
|
|
228
|
+
}, null, 2)}\n`,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
path: path.join(target, exports.CODEX_PLUGIN_DIR, 'hooks.json'),
|
|
232
|
+
content: `${JSON.stringify({
|
|
233
|
+
hooks: {
|
|
234
|
+
PreToolUse: [{
|
|
235
|
+
matcher: '.*',
|
|
236
|
+
hooks: [{
|
|
237
|
+
type: 'command',
|
|
238
|
+
command: kernelCommand(packageRoot, 'pre-tool-use.js'),
|
|
239
|
+
timeout: 30,
|
|
240
|
+
}],
|
|
241
|
+
}],
|
|
242
|
+
Stop: [{
|
|
243
|
+
type: 'command',
|
|
244
|
+
command: kernelCommand(packageRoot, 'stop.js'),
|
|
245
|
+
timeout: 30,
|
|
246
|
+
}],
|
|
247
|
+
},
|
|
248
|
+
}, null, 2)}\n`,
|
|
249
|
+
},
|
|
178
250
|
{ path: path.join(target, 'AGENTS.md'), content: AGENTS_MD(exports.HARNESS_ENFORCES.codex) },
|
|
179
251
|
];
|
|
180
252
|
default:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SemanticTier } from '../semantic/tier';
|
|
1
2
|
/**
|
|
2
3
|
* `holmes-kit doctor` — install diagnostics.
|
|
3
4
|
*
|
|
@@ -39,6 +40,20 @@ export declare const STRIPPED_FOR_PROBE: readonly ["HOLMES_APPROVAL", "HOLMES_SP
|
|
|
39
40
|
* only lever left is naming the condition here.
|
|
40
41
|
*/
|
|
41
42
|
export declare function globalInstallDir(prefix: string, platform: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* @implements A-SPEC-466 — W-5. npm's own JS entry, so the prefix probe can run it with THIS node
|
|
45
|
+
* binary instead of spawning the name 'npm' — which on Windows is a `.cmd` shim that execFileSync
|
|
46
|
+
* cannot start (ENOENT; naming the .cmd is EINVAL under Node's batch-file mitigation), leaving the
|
|
47
|
+
* global-prefix check a permanent WARN there.
|
|
48
|
+
*
|
|
49
|
+
* Candidates, in order: the `npm_execpath` this process was launched under (exact when running via
|
|
50
|
+
* npm/npx — guarded to npm-cli.js so an npx/yarn launcher is not mistaken for npm), then the two
|
|
51
|
+
* standard layouts next to the node binary — `<dir>\node_modules\npm` (Windows official,
|
|
52
|
+
* nvm-windows) and `<dir>/../lib/node_modules/npm` (POSIX standard, nvm). Homebrew's Cellar
|
|
53
|
+
* matches neither (measured) — null there, and the caller keeps the plain-'npm' spawn, which is
|
|
54
|
+
* today's behavior and has no .cmd problem on POSIX. env/execPath are injectable for tests only.
|
|
55
|
+
*/
|
|
56
|
+
export declare function npmCliEntry(env?: NodeJS.ProcessEnv, execPath?: string): string | null;
|
|
42
57
|
/** What the global-prefix probe saw. `unreadable` carries WHY npm could not be consulted. */
|
|
43
58
|
export interface PrefixProbe {
|
|
44
59
|
prefix?: unknown;
|
|
@@ -81,3 +96,44 @@ export interface ProbeRunner {
|
|
|
81
96
|
}
|
|
82
97
|
export declare function runDoctor(packageRoot: string, target?: string, opts?: DoctorOptions, extraChecks?: Check[]): Promise<Check[]>;
|
|
83
98
|
export declare function formatChecks(checks: Check[]): string;
|
|
99
|
+
/**
|
|
100
|
+
* Prove that EVERY wired harness can actually start a server, not merely that its file parses.
|
|
101
|
+
*
|
|
102
|
+
* A user reported that `holmes-kit init --agent codex` wrote a file Codex never reads and that doctor
|
|
103
|
+
* passed on it anyway. Measured against the current tree, the first half was already fixed by
|
|
104
|
+
* A-SPEC-266 — init writes `.codex/config.toml` with `[mcp_servers.*]`, migrates the legacy JSON, and
|
|
105
|
+
* doctor flags the old location. But the second half was real and sat one layer further in:
|
|
106
|
+
*
|
|
107
|
+
* PASS mcp wiring spawn — node wiring resolves: …/holmes-mcp.js <- existence only
|
|
108
|
+
* PASS codex wiring — MCP 배선이 이 설치본으로 해석됩니다 <- path resolution only
|
|
109
|
+
*
|
|
110
|
+
* The one check that drives a real `initialize -> tools/list` ran for `.mcp.json` ONLY, and only when
|
|
111
|
+
* the command was not `node` — which is what init writes for all three harnesses. So on the common
|
|
112
|
+
* path nothing ever started a server through the wiring a harness would actually use.
|
|
113
|
+
*
|
|
114
|
+
* The `node` fast-path is gone. Existence survives as a fast FAIL, because spending a 30s deadline on
|
|
115
|
+
* a path that does not exist tells nobody anything new.
|
|
116
|
+
*/
|
|
117
|
+
export declare function wiringHandshakeChecks(target: string): Promise<Check[]>;
|
|
118
|
+
/**
|
|
119
|
+
* @implements A-SPEC-476
|
|
120
|
+
* The semantic tier's doctor verdict, pure so the three states are unit-testable offline.
|
|
121
|
+
* The cloud notice NAMES the egress — what leaves the machine and where — because the key being
|
|
122
|
+
* set is the consent, and consent must be able to see what it consented to. The 'none' state
|
|
123
|
+
* quotes the measured stakes (lexical-zero recovery 0% -> 52% local / 92% cloud, S-491) so the
|
|
124
|
+
* user decides the trade with numbers rather than adjectives.
|
|
125
|
+
*/
|
|
126
|
+
export declare function semanticTierVerdict(t: SemanticTier): {
|
|
127
|
+
level: 'PASS' | 'WARN';
|
|
128
|
+
detail: string;
|
|
129
|
+
fix?: string;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* @implements A-SPEC-477
|
|
133
|
+
* Detects the "임시 조치": a semantic key pattern inside the PROJECT's .env. The value is read
|
|
134
|
+
* only to detect the pattern and is never returned or printed.
|
|
135
|
+
*/
|
|
136
|
+
export declare function detectTreeKeyTemporary(root: string): {
|
|
137
|
+
found: boolean;
|
|
138
|
+
mode?: number;
|
|
139
|
+
};
|