@holmes-lab/holmes-kit 0.1.7 → 0.1.8
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 +8 -0
- package/README.md +11 -7
- package/bin/holmes-mcp.js +0 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/agents.d.ts +50 -0
- package/dist/holmes/cli/ci-gate.d.ts +18 -0
- package/dist/holmes/cli/doctor.d.ts +44 -0
- package/dist/holmes/cli/gitignore-merge.d.ts +18 -0
- package/dist/holmes/cli/governed-precondition.d.ts +27 -0
- package/dist/holmes/cli/index.d.ts +14 -0
- package/dist/holmes/cli/index.js +23 -1
- package/dist/holmes/cli/init.d.ts +69 -0
- package/dist/holmes/cli/interactive-prompt.d.ts +9 -0
- package/dist/holmes/cli/playbook-skills.d.ts +129 -0
- package/dist/holmes/cli/roles-readme.d.ts +12 -0
- package/dist/holmes/cli/serve.d.ts +14 -0
- package/dist/holmes/cli/serve.js +70 -0
- package/dist/holmes/cli/settings-merge.d.ts +66 -0
- package/dist/holmes/config/config.d.ts +13 -0
- package/dist/holmes/context/bundler.d.ts +40 -0
- package/dist/holmes/context/render.d.ts +9 -0
- package/dist/holmes/context/tiers.d.ts +54 -0
- package/dist/holmes/context/tokens.d.ts +2 -0
- package/dist/holmes/cpg/ast-mutation.d.ts +31 -0
- package/dist/holmes/cpg/ast-mutation.js +126 -0
- package/dist/holmes/cpg/cpg-scanner.d.ts +69 -0
- package/dist/holmes/cpg/dynamic-cpg.d.ts +18 -0
- package/dist/holmes/cpg/dynamic-cpg.js +82 -0
- package/dist/holmes/cpg/hash-cache.d.ts +21 -0
- package/dist/holmes/cpg/language-parser-walk.d.ts +31 -0
- package/dist/holmes/cpg/language-parser-worker.d.ts +1 -0
- package/dist/holmes/cpg/language-parser.d.ts +116 -0
- package/dist/holmes/cpg/program-slicing.d.ts +21 -0
- package/dist/holmes/cpg/program-slicing.js +123 -0
- package/dist/holmes/cpg/scan-cache.d.ts +21 -0
- package/dist/holmes/cpg/source-path.d.ts +2 -0
- package/dist/holmes/cpg/test-files.d.ts +36 -0
- package/dist/holmes/governance/constitution-debt.d.ts +3 -0
- package/dist/holmes/governance/constitution-report.d.ts +23 -0
- package/dist/holmes/governance/constitution.d.ts +61 -0
- package/dist/holmes/governance/identity.d.ts +28 -0
- package/dist/holmes/governance/ledger-lock.d.ts +55 -0
- package/dist/holmes/governance/ledger-store.conformance.d.ts +11 -0
- package/dist/holmes/governance/ledger-store.d.ts +114 -0
- package/dist/holmes/governance/progress-ledger.d.ts +25 -0
- package/dist/holmes/governance/provenance-chain.d.ts +154 -0
- package/dist/holmes/governance/provenance-ledger.d.ts +102 -0
- package/dist/holmes/governance/provenance-schema.d.ts +21 -0
- package/dist/holmes/governance/replica-id.d.ts +20 -0
- package/dist/holmes/governance/role-policy.d.ts +52 -0
- package/dist/holmes/governance/trust-score.d.ts +32 -0
- package/dist/holmes/guardrail/anchors.d.ts +8 -0
- package/dist/holmes/guardrail/blind-spots.d.ts +32 -0
- package/dist/holmes/guardrail/decision-ledger.d.ts +29 -0
- package/dist/holmes/guardrail/executable-artifact.d.ts +1 -0
- package/dist/holmes/guardrail/governance-history.d.ts +15 -0
- package/dist/holmes/guardrail/phase.d.ts +27 -0
- package/dist/holmes/guardrail/risk-classifier.d.ts +52 -0
- package/dist/holmes/guardrail/risk-gate.d.ts +77 -0
- package/dist/holmes/guardrail/risk-types.d.ts +48 -0
- package/dist/holmes/guardrail/tspec-state.d.ts +71 -0
- package/dist/holmes/guardrail/write-target.d.ts +77 -0
- package/dist/holmes/hooks/adapters/antigravity.d.ts +42 -0
- package/dist/holmes/hooks/pre-tool-use.d.ts +144 -0
- package/dist/holmes/hooks/stop.d.ts +91 -0
- package/dist/holmes/mcp/basis.d.ts +100 -0
- package/dist/holmes/mcp/handlers.d.ts +482 -0
- package/dist/holmes/mcp/server.d.ts +2 -0
- package/dist/holmes/mcp/stdio-client.d.ts +51 -0
- package/dist/holmes/mcp/supervisor.d.ts +48 -0
- package/dist/holmes/mcp/tool-schemas.d.ts +12 -0
- package/dist/holmes/mcp/validate-args.d.ts +27 -0
- package/dist/holmes/messages/registry.d.ts +23 -0
- package/dist/holmes/project/baseline.d.ts +66 -0
- package/dist/holmes/project/change-source.d.ts +89 -0
- package/dist/holmes/project/ignore.d.ts +33 -0
- package/dist/holmes/project/root.d.ts +34 -0
- package/dist/holmes/reverse/anchor.d.ts +47 -0
- package/dist/holmes/reverse/cluster.d.ts +45 -0
- package/dist/holmes/reverse/draft.d.ts +33 -0
- package/dist/holmes/reverse/dynamic-wiring.d.ts +18 -0
- package/dist/holmes/reverse/scan.d.ts +103 -0
- package/dist/holmes/reverse/surface.d.ts +91 -0
- package/dist/holmes/reverse/test-map.d.ts +91 -0
- package/dist/holmes/review/coverage.d.ts +21 -0
- package/dist/holmes/review/findings.d.ts +49 -0
- package/dist/holmes/review/package.d.ts +26 -0
- package/dist/holmes/review/review-targets.d.ts +28 -0
- package/dist/holmes/review/scope.d.ts +13 -0
- package/dist/holmes/review/test-evidence.d.ts +31 -0
- package/dist/holmes/review/test-runner.d.ts +141 -0
- package/dist/holmes/rtm/dataflow-taint.d.ts +81 -0
- package/dist/holmes/rtm/gap-analyzer.d.ts +28 -0
- package/dist/holmes/rtm/git-changes.d.ts +33 -0
- package/dist/holmes/rtm/heatmap.d.ts +21 -0
- package/dist/holmes/rtm/incremental.d.ts +38 -0
- package/dist/holmes/rtm/localize.d.ts +36 -0
- package/dist/holmes/rtm/rtm-builder.d.ts +101 -0
- package/dist/holmes/rtm/rtm-check.d.ts +13 -0
- package/dist/holmes/rtm/rtm-graph.d.ts +88 -0
- package/dist/holmes/rtm/taint.d.ts +35 -0
- package/dist/holmes/rtm/test-scope.d.ts +85 -0
- package/dist/holmes/server/dashboard.d.ts +30 -0
- package/dist/holmes/server/dashboard.js +1758 -0
- package/dist/holmes/spec/approval-blockers.d.ts +62 -0
- package/dist/holmes/spec/breaking-change.d.ts +42 -0
- package/dist/holmes/spec/legacy-format.d.ts +93 -0
- package/dist/holmes/spec/spec-digest.d.ts +29 -0
- package/dist/holmes/spec/spec-parser.d.ts +12 -0
- package/dist/holmes/spec/spec-store.conformance.d.ts +10 -0
- package/dist/holmes/spec/spec-store.d.ts +130 -0
- package/dist/holmes/spec/spec-types.d.ts +150 -0
- package/dist/holmes/spec/validator.d.ts +65 -0
- package/package.json +2 -1
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { SpecStore } from '../spec/spec-store';
|
|
2
|
+
import { Action } from '../guardrail/phase';
|
|
3
|
+
import { Basis } from './basis';
|
|
4
|
+
import { Finding } from '../review/findings';
|
|
5
|
+
import { Approval, Enforcement } from '../guardrail/risk-gate';
|
|
6
|
+
import { RiskAction } from '../guardrail/risk-types';
|
|
7
|
+
/**
|
|
8
|
+
* @implements A-SPEC-189 §7 (round 10)
|
|
9
|
+
* A DELIBERATE refusal, distinguished by construction rather than by class.
|
|
10
|
+
*
|
|
11
|
+
* Round 9 converted thrown errors into `{ok:false, reason}` at the wire and told them apart with
|
|
12
|
+
* `e.constructor === Error`. Measured: Node's own fs/exec failures (ENOENT, EISDIR, EACCES) are
|
|
13
|
+
* exactly `Error`, so a CRASH became an orderly refusal — and for `review_status` that refusal had
|
|
14
|
+
* no `blocked` field at all, i.e. a gate reader's `if (out.blocked)` read undefined and passed.
|
|
15
|
+
* The check that used to stop the caller loudly (-32603) became a silent gate pass. A refusal is
|
|
16
|
+
* something a handler chooses to say; nothing infers it from a class.
|
|
17
|
+
*/
|
|
18
|
+
export declare class HandlerRefusal extends Error {
|
|
19
|
+
readonly holmesRefusal = true;
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/** Marker test that survives realm boundaries (jest's instanceof does not). */
|
|
23
|
+
export declare function isHandlerRefusal(e: unknown): e is Error;
|
|
24
|
+
import { ChangeSourceInfo } from '../project/change-source';
|
|
25
|
+
import { DraftResult } from '../reverse/draft';
|
|
26
|
+
import { AnchorMapping } from '../reverse/anchor';
|
|
27
|
+
/**
|
|
28
|
+
* @implements A-SPEC-156
|
|
29
|
+
* Basis is attached HERE, in one place, rather than inside each handler. Measured reasoning: 25
|
|
30
|
+
* handlers edited by hand is 25 chances to forget, and the one that forgets is invisible — which is
|
|
31
|
+
* precisely how the surface came to answer confidently with nothing backing it. The coverage test
|
|
32
|
+
* walks the tool list for the same reason.
|
|
33
|
+
*/
|
|
34
|
+
export declare function makeHandlers(store: SpecStore): RawHandlers & {
|
|
35
|
+
basis_detail(a: {
|
|
36
|
+
root?: string;
|
|
37
|
+
}): Promise<Basis & {
|
|
38
|
+
basis: string;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
type RawHandlers = ReturnType<typeof makeRawHandlers>;
|
|
42
|
+
declare function makeRawHandlers(store: SpecStore): {
|
|
43
|
+
spec_create(a: any): Promise<{
|
|
44
|
+
ok: boolean;
|
|
45
|
+
reason: string;
|
|
46
|
+
created?: undefined;
|
|
47
|
+
} | {
|
|
48
|
+
created: any;
|
|
49
|
+
ok?: undefined;
|
|
50
|
+
reason?: undefined;
|
|
51
|
+
}>;
|
|
52
|
+
spec_validate(a: any): Promise<{
|
|
53
|
+
ok: boolean;
|
|
54
|
+
findings: {
|
|
55
|
+
level: string;
|
|
56
|
+
code: string;
|
|
57
|
+
message: any;
|
|
58
|
+
}[];
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* @implements A-SPEC-132
|
|
62
|
+
* Approval as an ACT: validate → seal → flip → ledger, in one call. This is the designed
|
|
63
|
+
* reversal of "no approval tool exists" (which promote-slice documented while it was true) —
|
|
64
|
+
* the act now includes digest computation a hand edit cannot perform honestly. Fail-closed on
|
|
65
|
+
* the SERVER-environment approval: nothing in the request payload can substitute, because the
|
|
66
|
+
* agent authors the payload and the operator authors the environment.
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* @implements A-SPEC-184
|
|
70
|
+
* Raise ONE named document from an older spec format to the current one.
|
|
71
|
+
*
|
|
72
|
+
* The compatibility policy this implements: older documents are read and left alone by default,
|
|
73
|
+
* and rise only when a human points at the one they intend to use. There is deliberately no bulk
|
|
74
|
+
* path — 155 documents here and 90 in the measured adoption target, and which of them are still
|
|
75
|
+
* live specifications is a judgement only a person holds.
|
|
76
|
+
*
|
|
77
|
+
* It declares a KIND. It does not confer approval: 37 of this repository's legacy documents read
|
|
78
|
+
* `status: Approved`, and carrying that across would mint approvals that never passed the sealing
|
|
79
|
+
* act. The old value is preserved as evidence and the document restarts at `draft`.
|
|
80
|
+
*
|
|
81
|
+
* Takes no `root`: the store is bound at server construction, exactly as `spec_create` is.
|
|
82
|
+
*/
|
|
83
|
+
spec_upgrade(a: {
|
|
84
|
+
id: string;
|
|
85
|
+
}): Promise<{
|
|
86
|
+
ok: boolean;
|
|
87
|
+
reason: string;
|
|
88
|
+
upgraded?: undefined;
|
|
89
|
+
type?: undefined;
|
|
90
|
+
legacyStatus?: undefined;
|
|
91
|
+
remaining?: undefined;
|
|
92
|
+
} | {
|
|
93
|
+
ok: boolean;
|
|
94
|
+
upgraded: boolean;
|
|
95
|
+
reason: string;
|
|
96
|
+
type?: undefined;
|
|
97
|
+
legacyStatus?: undefined;
|
|
98
|
+
remaining?: undefined;
|
|
99
|
+
} | {
|
|
100
|
+
ok: boolean;
|
|
101
|
+
upgraded: boolean;
|
|
102
|
+
type: import("../spec/spec-types").SpecType;
|
|
103
|
+
legacyStatus: string;
|
|
104
|
+
remaining: import("../spec/validator").Finding[];
|
|
105
|
+
reason?: undefined;
|
|
106
|
+
}>;
|
|
107
|
+
spec_approve(a: {
|
|
108
|
+
root?: string;
|
|
109
|
+
id: string;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
ok: boolean;
|
|
112
|
+
reason: string;
|
|
113
|
+
findings?: undefined;
|
|
114
|
+
approved?: undefined;
|
|
115
|
+
digest?: undefined;
|
|
116
|
+
} | {
|
|
117
|
+
ok: boolean;
|
|
118
|
+
reason: string;
|
|
119
|
+
findings: import("../spec/validator").Finding[];
|
|
120
|
+
approved?: undefined;
|
|
121
|
+
digest?: undefined;
|
|
122
|
+
} | {
|
|
123
|
+
approved: string;
|
|
124
|
+
digest: string;
|
|
125
|
+
ok?: undefined;
|
|
126
|
+
reason?: undefined;
|
|
127
|
+
findings?: undefined;
|
|
128
|
+
}>;
|
|
129
|
+
spec_list(a: any): Promise<{
|
|
130
|
+
specs: {
|
|
131
|
+
legacy?: boolean | undefined;
|
|
132
|
+
parent?: string | undefined;
|
|
133
|
+
id: string;
|
|
134
|
+
type: import("../spec/spec-types").SpecType;
|
|
135
|
+
status: "draft" | "review" | "approved" | "outdated";
|
|
136
|
+
}[];
|
|
137
|
+
}>;
|
|
138
|
+
spec_next(): Promise<{
|
|
139
|
+
next: import("../spec/spec-types").SpecType;
|
|
140
|
+
} | {
|
|
141
|
+
next: null;
|
|
142
|
+
}>;
|
|
143
|
+
rtm_check(a?: {
|
|
144
|
+
root?: string;
|
|
145
|
+
}): Promise<{
|
|
146
|
+
issues: import("../rtm/rtm-check").RtmIssue[];
|
|
147
|
+
gaps: import("../rtm/gap-analyzer").ConvergenceGaps;
|
|
148
|
+
breakingChangeDistribution: Record<string, number>;
|
|
149
|
+
} | {
|
|
150
|
+
issues: import("../rtm/rtm-check").RtmIssue[];
|
|
151
|
+
breakingChangeDistribution: Record<string, number>;
|
|
152
|
+
gaps?: undefined;
|
|
153
|
+
}>;
|
|
154
|
+
/**
|
|
155
|
+
* Pin a REQ's citations: compute the content digest of every cited source that resolves inside
|
|
156
|
+
* the repo and record it as `rev`, so an author never hashes a file by hand and the digest is
|
|
157
|
+
* always derived from what is actually on disk.
|
|
158
|
+
*
|
|
159
|
+
* Explicit-invocation only, dry-run by DEFAULT, and never overwrites an existing digest — a
|
|
160
|
+
* re-pin would replace evidence of upstream drift with a fresh-looking value, converting the
|
|
161
|
+
* detector into a concealer.
|
|
162
|
+
*/
|
|
163
|
+
citation_pin(a: {
|
|
164
|
+
root: string;
|
|
165
|
+
id: string;
|
|
166
|
+
dryRun?: boolean;
|
|
167
|
+
}): Promise<{
|
|
168
|
+
ok: boolean;
|
|
169
|
+
reason: string;
|
|
170
|
+
id?: undefined;
|
|
171
|
+
dryRun?: undefined;
|
|
172
|
+
pinned?: undefined;
|
|
173
|
+
findings?: undefined;
|
|
174
|
+
citations?: undefined;
|
|
175
|
+
} | {
|
|
176
|
+
ok: boolean;
|
|
177
|
+
id: string;
|
|
178
|
+
dryRun: boolean;
|
|
179
|
+
pinned: string[];
|
|
180
|
+
findings: import("../spec/validator").Finding[];
|
|
181
|
+
citations: unknown[];
|
|
182
|
+
reason?: undefined;
|
|
183
|
+
}>;
|
|
184
|
+
phase_status(a: {
|
|
185
|
+
root?: string;
|
|
186
|
+
}): Promise<{
|
|
187
|
+
ok: boolean;
|
|
188
|
+
reason: string;
|
|
189
|
+
specs?: undefined;
|
|
190
|
+
note?: undefined;
|
|
191
|
+
} | {
|
|
192
|
+
specs: {
|
|
193
|
+
id: string;
|
|
194
|
+
type: import("../spec/spec-types").SpecType;
|
|
195
|
+
status: "draft" | "review" | "approved" | "outdated";
|
|
196
|
+
}[];
|
|
197
|
+
note: string;
|
|
198
|
+
ok?: undefined;
|
|
199
|
+
reason?: undefined;
|
|
200
|
+
}>;
|
|
201
|
+
phase_check(a: {
|
|
202
|
+
action?: Action;
|
|
203
|
+
target: string;
|
|
204
|
+
targetAspecId?: string;
|
|
205
|
+
}): Promise<import("../guardrail/phase").CheckResult | {
|
|
206
|
+
decision: string;
|
|
207
|
+
note: string;
|
|
208
|
+
}>;
|
|
209
|
+
cpg_scan(a: {
|
|
210
|
+
root: string;
|
|
211
|
+
}): Promise<{
|
|
212
|
+
files: number;
|
|
213
|
+
symbols: number;
|
|
214
|
+
skipped: {
|
|
215
|
+
file: string;
|
|
216
|
+
reason: string;
|
|
217
|
+
}[];
|
|
218
|
+
skippedCount: number;
|
|
219
|
+
}>;
|
|
220
|
+
/**
|
|
221
|
+
* @implements A-SPEC-138
|
|
222
|
+
* Call-graph taint REACHABILITY screen: source-named functions that reach sink-named functions
|
|
223
|
+
* through call edges. A SCREENING signal that routes a security review, NOT a data-flow proof —
|
|
224
|
+
* the result always carries the honesty envelope (kind + limits) and phrases pairs as "reaches",
|
|
225
|
+
* never "vulnerable".
|
|
226
|
+
*/
|
|
227
|
+
taint_scan(a: {
|
|
228
|
+
root: string;
|
|
229
|
+
}): Promise<{
|
|
230
|
+
kind: string;
|
|
231
|
+
limits: ("name-based matching" | "no def-use" | "no sanitizers" | "call-edge-only")[];
|
|
232
|
+
maxPaths: number | undefined;
|
|
233
|
+
truncated: number;
|
|
234
|
+
pairs: import("../rtm/taint").TaintPair[];
|
|
235
|
+
}>;
|
|
236
|
+
test_run(a: {
|
|
237
|
+
root: string;
|
|
238
|
+
base?: string;
|
|
239
|
+
head?: string;
|
|
240
|
+
since?: string;
|
|
241
|
+
mark?: string;
|
|
242
|
+
}): Promise<{
|
|
243
|
+
baselineRecorded?: string | undefined;
|
|
244
|
+
scopeFallback?: "full" | undefined;
|
|
245
|
+
tier: import("../rtm/test-scope").RegressionTier;
|
|
246
|
+
mode: "full" | "scoped" | "noop";
|
|
247
|
+
passed: boolean;
|
|
248
|
+
skipped: boolean;
|
|
249
|
+
ranFiles: string[];
|
|
250
|
+
executedByAspec: Record<string, number>;
|
|
251
|
+
tail: string;
|
|
252
|
+
unresolvedFiles: string[];
|
|
253
|
+
changeSource: ChangeSourceInfo;
|
|
254
|
+
}>;
|
|
255
|
+
issue_localize(a: {
|
|
256
|
+
root: string;
|
|
257
|
+
issue: string;
|
|
258
|
+
topN?: number;
|
|
259
|
+
}): Promise<import("../rtm/localize").LocalizationReport>;
|
|
260
|
+
rtm_impact(a: {
|
|
261
|
+
root: string;
|
|
262
|
+
changed: string[];
|
|
263
|
+
}): Promise<{
|
|
264
|
+
breadthWarning?: string | undefined;
|
|
265
|
+
impacted: string[];
|
|
266
|
+
reachedByDepth: number[];
|
|
267
|
+
bounded: {
|
|
268
|
+
id: string;
|
|
269
|
+
reason: "hub" | "depth";
|
|
270
|
+
inDegree?: number;
|
|
271
|
+
}[] | undefined;
|
|
272
|
+
}>;
|
|
273
|
+
rtm_reindex(a: {
|
|
274
|
+
root: string;
|
|
275
|
+
base?: string;
|
|
276
|
+
head?: string;
|
|
277
|
+
since?: string;
|
|
278
|
+
}): Promise<{
|
|
279
|
+
changed: number;
|
|
280
|
+
nodes: number;
|
|
281
|
+
edges: number;
|
|
282
|
+
changeSource: ChangeSourceInfo;
|
|
283
|
+
}>;
|
|
284
|
+
context_bundle(a: {
|
|
285
|
+
root: string;
|
|
286
|
+
seedId: string;
|
|
287
|
+
budget: number;
|
|
288
|
+
}): Promise<import("../context/bundler").ContextBundle>;
|
|
289
|
+
review_scope(a: {
|
|
290
|
+
root: string;
|
|
291
|
+
base?: string;
|
|
292
|
+
head?: string;
|
|
293
|
+
since?: string;
|
|
294
|
+
}): Promise<{
|
|
295
|
+
scopeFallback?: "full" | undefined;
|
|
296
|
+
unscannedChangedFiles: string[];
|
|
297
|
+
testScope: import("../rtm/test-scope").TestScope;
|
|
298
|
+
changeSource: ChangeSourceInfo;
|
|
299
|
+
changedSymbols: string[];
|
|
300
|
+
impactedSpecs: string[];
|
|
301
|
+
acceptanceCriteria: Array<{
|
|
302
|
+
specId: string;
|
|
303
|
+
criteria: string;
|
|
304
|
+
}>;
|
|
305
|
+
unrequestedSymbols: string[];
|
|
306
|
+
coverageGaps: string[];
|
|
307
|
+
}>;
|
|
308
|
+
review_prepare(a: {
|
|
309
|
+
root: string;
|
|
310
|
+
base?: string;
|
|
311
|
+
head?: string;
|
|
312
|
+
since?: string;
|
|
313
|
+
budget?: number;
|
|
314
|
+
}): Promise<{
|
|
315
|
+
scopeFallback?: "full" | undefined;
|
|
316
|
+
package: import("../review/package").ReviewPackage;
|
|
317
|
+
unscannedChangedFiles: string[];
|
|
318
|
+
changeSource: ChangeSourceInfo;
|
|
319
|
+
}>;
|
|
320
|
+
review_record(a: {
|
|
321
|
+
root: string;
|
|
322
|
+
findings: Finding[];
|
|
323
|
+
}): Promise<{
|
|
324
|
+
recorded: number;
|
|
325
|
+
}>;
|
|
326
|
+
review_status(a: {
|
|
327
|
+
root: string;
|
|
328
|
+
}): Promise<{
|
|
329
|
+
blocked: boolean;
|
|
330
|
+
open: {
|
|
331
|
+
critical: number;
|
|
332
|
+
important: number;
|
|
333
|
+
minor: number;
|
|
334
|
+
};
|
|
335
|
+
reviewNeeded: import("../review/review-targets").ReviewNeeded[];
|
|
336
|
+
}>;
|
|
337
|
+
risk_check(a: {
|
|
338
|
+
root?: string;
|
|
339
|
+
action: RiskAction;
|
|
340
|
+
approval?: Approval;
|
|
341
|
+
changedSymbols?: string[];
|
|
342
|
+
enforcement?: Enforcement;
|
|
343
|
+
ts: string;
|
|
344
|
+
actor?: string;
|
|
345
|
+
rationale?: string;
|
|
346
|
+
}): Promise<{
|
|
347
|
+
assessment: import("../guardrail/risk-types").RiskAssessment;
|
|
348
|
+
gate: import("../guardrail/risk-gate").GateResult;
|
|
349
|
+
}>;
|
|
350
|
+
/**
|
|
351
|
+
* Read-only inventory of a brownfield target: candidate clusters, coverage, and what the scan
|
|
352
|
+
* could not resolve. Deliberately does NOT call assertRepoTopLevel — a target that is not a git
|
|
353
|
+
* repository is a supported case here, reported as `isGit: false`.
|
|
354
|
+
*/
|
|
355
|
+
reverse_scan(a: {
|
|
356
|
+
root: string;
|
|
357
|
+
maxFlagged?: number;
|
|
358
|
+
}): Promise<{
|
|
359
|
+
root: string;
|
|
360
|
+
isGit: boolean;
|
|
361
|
+
scanned: number;
|
|
362
|
+
droppedUntracked: number;
|
|
363
|
+
candidates: number;
|
|
364
|
+
anchored: number;
|
|
365
|
+
unanchored: number;
|
|
366
|
+
clusters: import("../reverse/cluster").Cluster[];
|
|
367
|
+
testClusters: import("../reverse/cluster").Cluster[];
|
|
368
|
+
coverage: {
|
|
369
|
+
structurallyResolved: number;
|
|
370
|
+
inTestClusters: number;
|
|
371
|
+
cohesionUnmeasured: number;
|
|
372
|
+
testsMatched: number;
|
|
373
|
+
testsUnmatched: number;
|
|
374
|
+
withDynamicIndicators: number;
|
|
375
|
+
indicators: Partial<Record<import("../reverse/dynamic-wiring").Indicator, number>>;
|
|
376
|
+
uncalibratedIndicatorLanguages: string[];
|
|
377
|
+
ignoreUnsupportedPatterns: string[];
|
|
378
|
+
};
|
|
379
|
+
needsHumanConfirmation: import("../reverse/scan").FlaggedFile[];
|
|
380
|
+
tests: import("../reverse/test-map").TestMapping;
|
|
381
|
+
}>;
|
|
382
|
+
/**
|
|
383
|
+
* Draft H-SPEC/A-SPEC/T-SPEC documents for the recovered clusters under an EXISTING parent REQ.
|
|
384
|
+
*
|
|
385
|
+
* The REQ is the human's to write and this tool refuses without one — a requirement states
|
|
386
|
+
* business intent, which is not in the code. Everything emitted is `status: draft`, and writing
|
|
387
|
+
* requires an explicit `dryRun: false`.
|
|
388
|
+
*
|
|
389
|
+
* WHERE THE DOCUMENTS LAND: in the SERVER's configured spec store (`HOLMES_SPECS`, default
|
|
390
|
+
* `.ax/specs` relative to the server's working directory) — not inside `root`. In the intended
|
|
391
|
+
* adoption the two are the same directory, because the server runs inside the target it governs;
|
|
392
|
+
* pointing `root` at a different repository drafts that repository's slices into THIS store,
|
|
393
|
+
* which is a governance decision the caller has to make deliberately.
|
|
394
|
+
*/
|
|
395
|
+
reverse_draft(a: {
|
|
396
|
+
root: string;
|
|
397
|
+
parentReqId?: string;
|
|
398
|
+
cluster?: string;
|
|
399
|
+
dryRun?: boolean;
|
|
400
|
+
}): Promise<{
|
|
401
|
+
ok: boolean;
|
|
402
|
+
reason: string;
|
|
403
|
+
availableClusters?: undefined;
|
|
404
|
+
dryRun?: undefined;
|
|
405
|
+
parentReqId?: undefined;
|
|
406
|
+
clusters?: undefined;
|
|
407
|
+
drafted?: undefined;
|
|
408
|
+
skipped?: undefined;
|
|
409
|
+
refused?: undefined;
|
|
410
|
+
} | {
|
|
411
|
+
ok: boolean;
|
|
412
|
+
reason: string;
|
|
413
|
+
availableClusters: string[];
|
|
414
|
+
dryRun?: undefined;
|
|
415
|
+
parentReqId?: undefined;
|
|
416
|
+
clusters?: undefined;
|
|
417
|
+
drafted?: undefined;
|
|
418
|
+
skipped?: undefined;
|
|
419
|
+
refused?: undefined;
|
|
420
|
+
} | {
|
|
421
|
+
ok: boolean;
|
|
422
|
+
dryRun: boolean;
|
|
423
|
+
parentReqId: string;
|
|
424
|
+
clusters: number;
|
|
425
|
+
drafted: DraftResult[];
|
|
426
|
+
skipped: {
|
|
427
|
+
clusterKey: string;
|
|
428
|
+
reason: string;
|
|
429
|
+
}[];
|
|
430
|
+
refused: {
|
|
431
|
+
clusterKey: string;
|
|
432
|
+
reason: string;
|
|
433
|
+
findings?: unknown[];
|
|
434
|
+
}[];
|
|
435
|
+
reason?: undefined;
|
|
436
|
+
availableClusters?: undefined;
|
|
437
|
+
}>;
|
|
438
|
+
/**
|
|
439
|
+
* Insert `@implements` anchors into source files. Dry-run by DEFAULT, and anchoring to a
|
|
440
|
+
* non-approved A-SPEC is refused — a reverse-engineered draft describes code; description does
|
|
441
|
+
* not confer approval.
|
|
442
|
+
*/
|
|
443
|
+
reverse_anchor(a: {
|
|
444
|
+
root: string;
|
|
445
|
+
mapping: AnchorMapping[];
|
|
446
|
+
dryRun?: boolean;
|
|
447
|
+
}): Promise<{
|
|
448
|
+
blockers: Record<string, string>;
|
|
449
|
+
applied: number;
|
|
450
|
+
dryRun: boolean;
|
|
451
|
+
would: number;
|
|
452
|
+
failed: import("../reverse/anchor").RefusedAnchor[];
|
|
453
|
+
edits: import("../reverse/anchor").AnchorEdit[];
|
|
454
|
+
refused: import("../reverse/anchor").RefusedAnchor[];
|
|
455
|
+
}>;
|
|
456
|
+
spec_slice_init(a: {
|
|
457
|
+
root?: string;
|
|
458
|
+
sliceName: string;
|
|
459
|
+
title: string;
|
|
460
|
+
objective: string;
|
|
461
|
+
filesToTouch: string[];
|
|
462
|
+
}): Promise<{
|
|
463
|
+
ok: boolean;
|
|
464
|
+
specsCreated: string[];
|
|
465
|
+
}>;
|
|
466
|
+
spec_slice_approve(a: {
|
|
467
|
+
root?: string;
|
|
468
|
+
sliceName: string;
|
|
469
|
+
}): Promise<{
|
|
470
|
+
ok: boolean;
|
|
471
|
+
approvedSpecs: string[];
|
|
472
|
+
}>;
|
|
473
|
+
spec_remediate(a: {
|
|
474
|
+
root?: string;
|
|
475
|
+
targetFile?: string;
|
|
476
|
+
aspecId?: string;
|
|
477
|
+
}): Promise<{
|
|
478
|
+
ok: boolean;
|
|
479
|
+
actionsTaken: string[];
|
|
480
|
+
}>;
|
|
481
|
+
};
|
|
482
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys excluded from surface comparison.
|
|
3
|
+
*
|
|
4
|
+
* `basis` legitimately differs: two processes observe the world at different moments, so the spec
|
|
5
|
+
* fingerprint can move between the two calls. Excluding it is correct — but the exclusion is named
|
|
6
|
+
* and asserted, because an agreement claim whose exceptions are invisible is stronger-sounding than
|
|
7
|
+
* it is.
|
|
8
|
+
*/
|
|
9
|
+
export declare const OMITTED_FROM_COMPARISON: readonly ["basis"];
|
|
10
|
+
/** Strips the excluded keys so two answers can be compared for the part that must match. */
|
|
11
|
+
export declare function comparable(v: unknown): unknown;
|
|
12
|
+
/**
|
|
13
|
+
* Extracts a tool's payload from an MCP result.
|
|
14
|
+
*
|
|
15
|
+
* Pure and exported because the failure it guards — a surface that answers with no text at all —
|
|
16
|
+
* cannot be produced by the real server (an unknown tool comes back as a perfectly well-formed
|
|
17
|
+
* `{"error":"unknown tool"}`), so the branch is unreachable from an end-to-end test. Left inline it
|
|
18
|
+
* would be an untested guard, and an untested guard is one a later change deletes for free: with it
|
|
19
|
+
* gone, a silent surface yields `undefined`, `undefined` compares equal to `undefined`, and the
|
|
20
|
+
* conformance suite reports agreement between two answers that contain nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseToolResult(name: string, result: unknown, error?: unknown): unknown;
|
|
23
|
+
/**
|
|
24
|
+
* A minimal MCP client that SPAWNS the shipped entry point.
|
|
25
|
+
*
|
|
26
|
+
* Importing the handler module — what every other test does — cannot see the boundary where the
|
|
27
|
+
* 2026-08-08 defect lived: a server process running a three-day-old build answered `impactedSpecs:
|
|
28
|
+
* []` for a commit touching 39 specs, while the same handler in a fresh process answered correctly,
|
|
29
|
+
* and 1,370 tests stayed green throughout. Anything that does not cross a process boundary is blind
|
|
30
|
+
* to that entire class.
|
|
31
|
+
*
|
|
32
|
+
* One server is started per suite and reused, so conformance costs one spawn rather than one per
|
|
33
|
+
* tool.
|
|
34
|
+
*/
|
|
35
|
+
export declare class StdioClient {
|
|
36
|
+
private readonly child;
|
|
37
|
+
private buf;
|
|
38
|
+
private nextId;
|
|
39
|
+
private readonly pending;
|
|
40
|
+
private constructor();
|
|
41
|
+
/**
|
|
42
|
+
* @param specsDir Overrides `HOLMES_SPECS` for the child. Needed because the server binds its
|
|
43
|
+
* spec store at start-up from that variable and ignores the `root` argument a tool receives
|
|
44
|
+
* (REQ-169) — without this, a write tool driven for conformance lands in the server's own
|
|
45
|
+
* project. Measured: it wrote REQ-901.md into this repository.
|
|
46
|
+
*/
|
|
47
|
+
static start(repoRoot: string, specsDir?: string): Promise<StdioClient>;
|
|
48
|
+
private send;
|
|
49
|
+
call(name: string, args: unknown): Promise<unknown>;
|
|
50
|
+
stop(): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Opt-in switch. Absent means the entry point behaves exactly as it did before this existed. */
|
|
2
|
+
export declare const AUTORELOAD_ENV = "HOLMES_MCP_AUTORELOAD";
|
|
3
|
+
export declare function autoreloadEnabled(env: NodeJS.ProcessEnv): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether the child may be replaced right now.
|
|
6
|
+
*
|
|
7
|
+
* Pure, so the three ways this can be wrong are testable without spawning anything.
|
|
8
|
+
*
|
|
9
|
+
* An unknown disk build never swaps: not knowing is not divergence, the same posture A-SPEC-160 took
|
|
10
|
+
* for the digest marker. Reading absence as change would restart continuously on any install whose
|
|
11
|
+
* build is unstamped.
|
|
12
|
+
*
|
|
13
|
+
* An in-flight request never swaps. Each child starts its JSON-RPC id counter at 1, so a mid-request
|
|
14
|
+
* swap lets the new child's reply match an id the old one issued — an answer delivered to the wrong
|
|
15
|
+
* question, which is worse than the staleness it was fixing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function shouldSwap(loaded: string, disk: string | undefined, inflight: number): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Runs the real server as a child and relays stdio, replacing the child when the build changes.
|
|
20
|
+
*
|
|
21
|
+
* A child process rather than `require.cache` surgery: this package depends on nine native addons
|
|
22
|
+
* (better-sqlite3 plus eight tree-sitter grammars), and re-requiring a `.node` either crashes the
|
|
23
|
+
* process or leaks the handles it still owns — better-sqlite3 holds open database handles. A fresh
|
|
24
|
+
* child loads them cleanly, and the client never sees the connection drop.
|
|
25
|
+
*
|
|
26
|
+
* This is a CONVENIENCE, not a correctness mechanism. If a swap fails, REQ-156's basis and ART-6
|
|
27
|
+
* still expose the divergence; making the supervisor responsible for correctness would turn it into
|
|
28
|
+
* a new single point of failure for the property it was meant to help with.
|
|
29
|
+
*/
|
|
30
|
+
export declare class Supervisor {
|
|
31
|
+
private readonly repoRoot;
|
|
32
|
+
private readonly onSwap?;
|
|
33
|
+
private child?;
|
|
34
|
+
private inflight;
|
|
35
|
+
private loaded;
|
|
36
|
+
/**
|
|
37
|
+
* The client's opening exchange, replayed into every replacement child. Without it the first call
|
|
38
|
+
* after a swap hits an uninitialised server and fails — which would break the one promise the
|
|
39
|
+
* supervisor makes, that the connection does not drop.
|
|
40
|
+
*/
|
|
41
|
+
private handshake;
|
|
42
|
+
constructor(repoRoot: string, onSwap?: ((from: string, to: string, replayed: number) => void) | undefined);
|
|
43
|
+
start(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): void;
|
|
44
|
+
private forward;
|
|
45
|
+
private swap;
|
|
46
|
+
private spawnChild;
|
|
47
|
+
stop(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface JsonSchema {
|
|
2
|
+
type: 'object';
|
|
3
|
+
properties: Record<string, unknown>;
|
|
4
|
+
required?: string[];
|
|
5
|
+
additionalProperties?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ToolSchema {
|
|
8
|
+
description: string;
|
|
9
|
+
inputSchema: JsonSchema;
|
|
10
|
+
}
|
|
11
|
+
export declare const HOOK_ENFORCED_TOOLS: Set<string>;
|
|
12
|
+
export declare const TOOL_SCHEMAS: Record<string, ToolSchema>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { JsonSchema } from './tool-schemas';
|
|
2
|
+
/**
|
|
3
|
+
* Enforcement for the schemas this server already ADVERTISES.
|
|
4
|
+
*
|
|
5
|
+
* The server declared every tool's inputSchema, pinned it 1:1 against the handlers, served it to
|
|
6
|
+
* clients — and then dispatched `req.params.arguments ?? {}` without ever reading it. Probed: 15 of
|
|
7
|
+
* 26 handlers threw raw internal errors at `{}`, and a one-key typo in reverse_anchor produced
|
|
8
|
+
* either a wrong diagnosis ("target A-SPEC undefined is not approved" — blaming approval for an
|
|
9
|
+
* input problem) or a success-shaped no-op. This module makes the server the first consumer of its
|
|
10
|
+
* own schemas.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately a SUBSET validator: it judges exactly what the repo's `JsonSchema` shape declares —
|
|
13
|
+
* type / properties / required / items / enum / additionalProperties — and nothing more. A general
|
|
14
|
+
* JSON Schema engine would silently decide semantics these schemas never state. Pure: no
|
|
15
|
+
* filesystem, no store; every rule is exercisable from a literal.
|
|
16
|
+
*/
|
|
17
|
+
export type ArgCheck = {
|
|
18
|
+
ok: true;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
reason: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function nearKeys(unknown: string, declared: string[]): string[];
|
|
24
|
+
export declare function nearestKey(unknown: string, declared: string[]): string | null;
|
|
25
|
+
/** 시험용 관측점: 요청이 끝난 뒤 메모가 비어 있음을 고정한다(§7f). */
|
|
26
|
+
export declare const NEAR_CACHE_SIZE: () => number;
|
|
27
|
+
export declare function validateArgs(toolName: string, schema: JsonSchema, args: unknown): ArgCheck;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holmes-Kit Centralized Message Registry
|
|
3
|
+
*
|
|
4
|
+
* Single Source of Truth for all user/agent-facing refusal, validation, and diagnostic messages.
|
|
5
|
+
* Unified 3-Part English Format: [Subsystem] Title — Technical Cause — Next Action
|
|
6
|
+
* Internal Holmes-Kit Spec IDs are strictly masked unless HOLMES_DEBUG=1 is set.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MESSAGES: {
|
|
9
|
+
SECURITY_ENV_ACCESS: (internalTrace?: string) => string;
|
|
10
|
+
INTEGRITY_CONFIG_EDIT: (file?: string, internalTrace?: string) => string;
|
|
11
|
+
GOVERNANCE_LEDGER_DESTRUCTION: (path?: string, internalTrace?: string) => string;
|
|
12
|
+
SEAL_SELF_APPROVAL_RESTRICTED: (specId: string, internalTrace?: string) => string;
|
|
13
|
+
SEAL_APPROVED_SPEC_MUTATION: (specId: string, specTitle?: string, internalTrace?: string) => string;
|
|
14
|
+
GOVERNANCE_UNAPPROVED_TSPEC: (tspecId: string, aspecId: string, aspecTitle?: string, internalTrace?: string) => string;
|
|
15
|
+
GOVERNANCE_MISSING_TSPEC: (aspecId: string, aspecTitle?: string, internalTrace?: string) => string;
|
|
16
|
+
VALIDATION_MISSING_REQUIRED: (paramPath: string, description?: string) => string;
|
|
17
|
+
VALIDATION_TYPO_SUGGESTION: (unknownKey: string, suggestedKey: string) => string;
|
|
18
|
+
VALIDATION_ENUM_INVALID: (paramName: string, value: string, allowedValues: string[]) => string;
|
|
19
|
+
VALIDATION_FOREIGN_ROOT: (root: string, boundRoot: string) => string;
|
|
20
|
+
LEGACY_REVERSE_DRAFT_PARENT: () => string;
|
|
21
|
+
UNAPPROVED_ASPEC: (aspecId?: string, internalTrace?: string) => string;
|
|
22
|
+
MISSING_ANCHOR: (targetFile?: string, internalTrace?: string) => string;
|
|
23
|
+
};
|