@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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
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
7
|
<!-- @implements A-SPEC-209 -->
|
|
8
|
+
## [0.1.8] - 2026-08-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Hybrid Dynamic CPG Engine (REQ-216)**: Dynamic test coverage trace fusion with static AST graphs to eliminate reflection/dynamic invocation false negatives.
|
|
12
|
+
- **Datalog-based Program Slicing & Noise Pruning (REQ-217)**: Transitive closure reachability slicing (forward/backward/both) to prune irrelevant AST nodes and isolate affected blast radius.
|
|
13
|
+
- **AST Mutation & Fuzzing Harness (REQ-214)**: Multi-operator mutation generator (relational, arithmetic, logical, unary) with mutation score calculation.
|
|
14
|
+
- **TypeScript Declaration Bundling**: Full `.d.ts` declaration generation across all modules and `types` entrypoint in `package.json`.
|
|
15
|
+
|
|
8
16
|
## [0.1.7] - 2026-08-19
|
|
9
17
|
|
|
10
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ Holmes-Kit prioritizes **CLI-based AI Coding Agents** where OS-level process hoo
|
|
|
55
55
|
```bash
|
|
56
56
|
npm install -g @holmes-lab/holmes-kit
|
|
57
57
|
```
|
|
58
|
-
*(
|
|
58
|
+
*(Prerequisites: Node.js `>= 20.0.0`. Prebuilt binaries are provided automatically for macOS, Linux, and Windows. For minimal Alpine Docker containers, install build tools: `apk add --no-cache python3 make g++`)*
|
|
59
59
|
|
|
60
60
|
### 2. Initialize in Your Project
|
|
61
61
|
```bash
|
|
@@ -104,12 +104,16 @@ flowchart LR
|
|
|
104
104
|
| `holmes-kit init --agent all` | Non-interactive instant setup for all supported agents |
|
|
105
105
|
| `holmes-kit init --dry-run` | Preview files and configuration changes without writing |
|
|
106
106
|
| `holmes-kit doctor` | Comprehensive health check of specs, hooks, MCP, and anchors |
|
|
107
|
+
| `holmes-kit doctor --fix` | Automatically self-heal and repair broken hooks or missing skills |
|
|
108
|
+
| `holmes-kit ci` | Run non-interactive headless governance gate for GitHub Actions / GitLab CI |
|
|
109
|
+
| `holmes-kit ci --json` | Run CI gate and emit machine-readable JSON results |
|
|
107
110
|
|
|
108
111
|
---
|
|
109
112
|
|
|
110
|
-
##
|
|
113
|
+
## 🧩 Optional: Manual MCP Integration (Cursor, Windsurf, Claude Desktop)
|
|
111
114
|
|
|
112
|
-
If you
|
|
115
|
+
> **Note**: If you ran `holmes-kit init`, this configuration is **100% automated for you**.
|
|
116
|
+
> Use the manual configuration below only if you wish to integrate Holmes-Kit into standalone third-party MCP clients like Cursor, Windsurf, Claude Desktop, or VS Code:
|
|
113
117
|
|
|
114
118
|
```json
|
|
115
119
|
{
|
|
@@ -154,11 +158,11 @@ Holmes-Kit embeds native AST & Code Property Graph (D-CPG) analyzers to track ca
|
|
|
154
158
|
|
|
155
159
|
---
|
|
156
160
|
|
|
157
|
-
##
|
|
161
|
+
## 🔒 Source Code Availability & Distribution Policy
|
|
158
162
|
|
|
159
|
-
- **
|
|
160
|
-
- **
|
|
161
|
-
- **
|
|
163
|
+
- **Distribution Mode**: Holmes-Kit is currently distributed and executable as an official public npm package ([`@holmes-lab/holmes-kit`](https://www.npmjs.com/package/@holmes-lab/holmes-kit)).
|
|
164
|
+
- **Source Code Status**: The underlying source code repository is currently **private / closed-source**.
|
|
165
|
+
- **Open Source Consideration**: Decisions regarding whether, when, and how to transition to a full open-source codebase will be reviewed and determined in future milestones based on enterprise feedback, security audits, and community governance requirements.
|
|
162
166
|
|
|
163
167
|
---
|
|
164
168
|
|
package/bin/holmes-mcp.js
CHANGED
|
File without changes
|
package/dist/.build-id
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
9a778c2-mt0p7fwt
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 하네스별 배선물.
|
|
3
|
+
*
|
|
4
|
+
* Holmes-Kit 의 명제("승인된 스펙 없이는 코드가 들어가지 않는다")를 **집행**하는 것은 MCP
|
|
5
|
+
* 도구가 아니라 훅이다. 그래서 하네스마다 물어야 할 첫 질문은 "이 도구에 훅이 있는가"이고,
|
|
6
|
+
* 없다면 **집행한다고 말하지 않는 것**이 이 파일의 일이다. 도구는 주되 규율은 못 세우면서
|
|
7
|
+
* 세운다고 적으면, 그 문장을 믿은 사용자가 지켜지지 않는 게이트 위에서 일한다.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AGENTS: readonly ["claude", "antigravity", "codex"];
|
|
10
|
+
export type Agent = (typeof AGENTS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* 그 하네스에서 게이트가 **집행되는가**.
|
|
13
|
+
*
|
|
14
|
+
* - `claude` — PreToolUse/Stop 훅. 이 저장소가 처음부터 배선해 온 하네스.
|
|
15
|
+
* - `antigravity` — PreToolUse/Stop 훅이 있고 `deny`/`continue` 로 막을 수 있다(설치본의
|
|
16
|
+
* `agy-customizations` 규약 문서와 실제 대화 기록에서 실측).
|
|
17
|
+
* - `codex` — 이 기계에 설치되어 있지 않아 훅 규약을 **재지 못했다**. 재지 못한 것을 있다고
|
|
18
|
+
* 적지 않는다. 도구(MCP)와 지침만 준다.
|
|
19
|
+
*/
|
|
20
|
+
export declare const HARNESS_ENFORCES: Record<Agent, boolean>;
|
|
21
|
+
export interface AgentWiringOptions {
|
|
22
|
+
target: string;
|
|
23
|
+
packageRoot: string;
|
|
24
|
+
specsDir: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AgentFile {
|
|
27
|
+
path: string;
|
|
28
|
+
content: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 이 하네스에 써야 할 파일들. **쓰지는 않는다** — 무엇을 쓸지만 말한다.
|
|
32
|
+
*
|
|
33
|
+
* 계산과 쓰기를 나누는 이유는 dry-run 이 실제 실행과 같은 집합을 예고해야 하기 때문이다
|
|
34
|
+
* (A-SPEC-190 §9). 같은 함수가 두 경로에 답하면 둘이 어긋날 수 없다.
|
|
35
|
+
*/
|
|
36
|
+
export declare function agentFiles(agent: Agent, opts: AgentWiringOptions): AgentFile[];
|
|
37
|
+
export interface AgentLink {
|
|
38
|
+
path: string;
|
|
39
|
+
target: string;
|
|
40
|
+
why: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 하네스가 스킬을 **볼 수 있게** 하는 링크.
|
|
44
|
+
*
|
|
45
|
+
* 스킬 본문은 한 곳(`.claude/skills`)에만 둔다 — 같은 내용을 두 벌 두면 하나가 갱신될 때
|
|
46
|
+
* 다른 하나가 낡고, doctor 는 한쪽만 본다(그 드리프트가 REQ-190 의 출발점이었다). 그래서
|
|
47
|
+
* Antigravity 쪽은 링크로 같은 파일을 가리킨다. 이 배치는 타깃 안에 머물므로 설치 담장을
|
|
48
|
+
* 지나간다(A-SPEC-193 §7 이 그 담장을 '타깃 밖만'으로 좁힌 이유이기도 하다).
|
|
49
|
+
*/
|
|
50
|
+
export declare function agentLinks(agent: Agent, opts: AgentWiringOptions): AgentLink[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CiViolation {
|
|
2
|
+
file?: string;
|
|
3
|
+
specId?: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CiGateResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
violations: CiViolation[];
|
|
9
|
+
summary: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Headless CI/CD Governance Gate Runner for GitHub Actions / GitLab CI.
|
|
13
|
+
*
|
|
14
|
+
* @implements A-SPEC-213
|
|
15
|
+
*/
|
|
16
|
+
export declare function runCiGate(targetDir: string, options?: {
|
|
17
|
+
specsDir?: string;
|
|
18
|
+
}): CiGateResult;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `holmes-kit doctor` — install diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Native-module breakage is the likeliest failure mode of an install on another machine, and the
|
|
5
|
+
* hooks fail OPEN, so a broken install is silent: governance simply never fires. This command makes
|
|
6
|
+
* that loud. Every check reports PASS/WARN/FAIL plus a concrete remediation.
|
|
7
|
+
*/
|
|
8
|
+
export type Level = 'PASS' | 'WARN' | 'FAIL';
|
|
9
|
+
export interface Check {
|
|
10
|
+
name: string;
|
|
11
|
+
level: Level;
|
|
12
|
+
detail: string;
|
|
13
|
+
fix?: string;
|
|
14
|
+
remediate?: () => boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface DoctorOptions {
|
|
17
|
+
run?: ProbeRunner;
|
|
18
|
+
autoFix?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Environment variables removed before spawning a probe.
|
|
22
|
+
*
|
|
23
|
+
* @implements A-SPEC-143
|
|
24
|
+
* `HOLMES_APPROVAL` grants permission, so a probe that inherits one measures the GRANT rather than
|
|
25
|
+
* the gate. `HOLMES_SPECS` redirects the spec root, so a probe reading another project's specs
|
|
26
|
+
* answers a different question than the one asked. `HOLMES_LEDGER_KEY` is deliberately NOT here: it
|
|
27
|
+
* changes how the ledger is signed, not whether the gate enforces, and stripping it could mask a
|
|
28
|
+
* real key-related failure.
|
|
29
|
+
*/
|
|
30
|
+
export declare const STRIPPED_FOR_PROBE: readonly ["HOLMES_APPROVAL", "HOLMES_SPECS"];
|
|
31
|
+
/** The parent environment minus the variables that legitimately change a gate decision. Pure. */
|
|
32
|
+
export declare function probeEnv(parent: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
33
|
+
/** Seam for the gate probe's child process. Default is a real `spawnSync`; tests inject a fake. */
|
|
34
|
+
export interface ProbeRunner {
|
|
35
|
+
/** `env` is passed EXPLICITLY so a test can observe what the child would actually receive — a
|
|
36
|
+
* runner that never sees it cannot verify the wiring, only restate the helper. */
|
|
37
|
+
(script: string, payload: unknown, env: NodeJS.ProcessEnv): {
|
|
38
|
+
status: number | null;
|
|
39
|
+
stdout?: string;
|
|
40
|
+
stderr?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export declare function runDoctor(packageRoot: string, target?: string, opts?: DoctorOptions, extraChecks?: Check[]): Promise<Check[]>;
|
|
44
|
+
export declare function formatChecks(checks: Check[]): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PURE .gitignore block merge. holmes-kit writes derived state under `.ax/` in the TARGET project
|
|
3
|
+
* (ledger, cpg_cache, reports) that must never be committed. The block is delimited so it can be
|
|
4
|
+
* added, refreshed, and removed exactly — never touching the user's own lines.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BEGIN = "# >>> holmes-kit >>>";
|
|
7
|
+
export declare const END = "# <<< holmes-kit <<<";
|
|
8
|
+
/** Derived/runtime paths holmes-kit creates in a target project. */
|
|
9
|
+
export declare const IGNORE_LINES: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Add (or refresh) the holmes-kit block. Idempotent: a second call reproduces the same text.
|
|
12
|
+
* Preserves everything outside the block, and preserves whether the file ended with a newline.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mergeGitignore(existing: string, lines?: string[]): string;
|
|
15
|
+
/** Remove the block (and the blank line that preceded it, if we added one). Inverse of merge. */
|
|
16
|
+
export declare function removeGitignoreBlock(existing: string): string;
|
|
17
|
+
/** True iff the holmes-kit block is present. */
|
|
18
|
+
export declare function hasGitignoreBlock(existing: string): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many directories the walk may visit.
|
|
3
|
+
*
|
|
4
|
+
* The cap only bites on projects with NO code — the walk returns at the first gated file — and those
|
|
5
|
+
* are exactly the ones where exhausting it gives the wrong playbook. Measured 2026-08-12 at roughly
|
|
6
|
+
* 0.05ms per directory, so 2000 costs about 100ms inside a one-shot command that also writes files
|
|
7
|
+
* and installs skills.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_DIRS = 2000;
|
|
10
|
+
/**
|
|
11
|
+
* Does this target contain a file the Write/Edit gate would refuse once governed mode is live?
|
|
12
|
+
*
|
|
13
|
+
* The judgement is `classifyAction` — the gate's own function. Re-deriving "is this code" from a
|
|
14
|
+
* second extension list would leave two sources of truth, and the guidance would be wrong the moment
|
|
15
|
+
* they drifted apart (the failure REQ-163 measured for path identity).
|
|
16
|
+
*
|
|
17
|
+
* Content is never read: `classifyAction`'s shebang signal needs the file open, which does not fit
|
|
18
|
+
* the cost ceiling, and missing it errs toward `adopt` — the harmless direction.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasGatedCode(target: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The three lines a forced governed transition owes the user: that the precondition was skipped,
|
|
23
|
+
* what is now denied (and what still works), and the one playbook that fits this target.
|
|
24
|
+
*
|
|
25
|
+
* Naming both playbooks would hand the judgement back to the user, which is the defect itself.
|
|
26
|
+
*/
|
|
27
|
+
export declare function governedWarning(target: string): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `holmes-kit` CLI entry — argv dispatch, hand-rolled (no new dependency).
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* init [--mode guardrail|governed] [--target dir] [--dry-run] [--remove] [--force] ...
|
|
6
|
+
* doctor [--target dir] [--json]
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The installed package root. From `dist/holmes/cli/index.js` that is three levels up. Node resolves
|
|
10
|
+
* a global bin symlink to its real path before setting __dirname, so this is correct even when the
|
|
11
|
+
* command is invoked through the PATH shim.
|
|
12
|
+
*/
|
|
13
|
+
export declare function packageRoot(): string;
|
|
14
|
+
export declare function main(argv: string[]): Promise<number>;
|
package/dist/holmes/cli/index.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.packageRoot = packageRoot;
|
|
|
37
37
|
exports.main = main;
|
|
38
38
|
// @implements A-SPEC-100.2
|
|
39
39
|
// @implements A-SPEC-213
|
|
40
|
+
// @implements A-SPEC-215
|
|
40
41
|
const fs = __importStar(require("node:fs"));
|
|
41
42
|
const path = __importStar(require("node:path"));
|
|
42
43
|
const init_1 = require("./init");
|
|
@@ -71,6 +72,7 @@ const KNOWN_FLAGS = {
|
|
|
71
72
|
doctor: ['help', 'target', 'json'],
|
|
72
73
|
skills: ['help', 'target'],
|
|
73
74
|
ci: ['help', 'target', 'specs-dir', 'json'],
|
|
75
|
+
serve: ['help', 'target', 'port'],
|
|
74
76
|
};
|
|
75
77
|
class UnknownFlagError extends Error {
|
|
76
78
|
}
|
|
@@ -325,6 +327,21 @@ async function main(argv) {
|
|
|
325
327
|
}
|
|
326
328
|
return res.ok ? 0 : 1;
|
|
327
329
|
}
|
|
330
|
+
// @implements A-SPEC-215
|
|
331
|
+
if (cmd === 'serve') {
|
|
332
|
+
if (typeof flags.target === 'string') {
|
|
333
|
+
const t = path.resolve(flags.target);
|
|
334
|
+
if (!fs.existsSync(t) || !fs.statSync(t).isDirectory()) {
|
|
335
|
+
process.stderr.write(`--target ${t} is not an existing directory\n\n${USAGE}`);
|
|
336
|
+
return 2;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const target = typeof flags.target === 'string' ? flags.target : process.cwd();
|
|
340
|
+
const port = typeof flags.port === 'string' ? parseInt(flags.port, 10) : undefined;
|
|
341
|
+
const { runServeCommand } = require('./serve');
|
|
342
|
+
await runServeCommand({ root: target, port });
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
328
345
|
if (cmd === 'init') {
|
|
329
346
|
const mode = (typeof flags.mode === 'string' ? flags.mode : 'guardrail');
|
|
330
347
|
// 형제 열거 오류와 같은 모양(round 7): --settings·--target 은 USAGE 와 함께 exit 2 인데
|
|
@@ -428,5 +445,10 @@ async function main(argv) {
|
|
|
428
445
|
return cmd.startsWith('-') ? 2 : 1;
|
|
429
446
|
}
|
|
430
447
|
if (require.main === module) {
|
|
431
|
-
main(process.argv.slice(2))
|
|
448
|
+
main(process.argv.slice(2))
|
|
449
|
+
.then((code) => process.exit(code))
|
|
450
|
+
.catch((err) => {
|
|
451
|
+
process.stderr.write(`[holmes-kit error] ${err?.message || err}\n`);
|
|
452
|
+
process.exit(1);
|
|
453
|
+
});
|
|
432
454
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { HookPlan } from './settings-merge';
|
|
2
|
+
import { Agent } from './agents';
|
|
3
|
+
import { Approval } from '../guardrail/risk-gate';
|
|
4
|
+
/**
|
|
5
|
+
* `holmes-kit init` — wire holmes-kit into a TARGET project (the I/O half; all merge logic is pure
|
|
6
|
+
* and lives in settings-merge.ts / gitignore-merge.ts).
|
|
7
|
+
*
|
|
8
|
+
* The mode choice is the single most consequential decision this tool makes:
|
|
9
|
+
* guardrail (DEFAULT) — PreToolUse matcher 'Bash' + Stop. On a spec-less project this blocks
|
|
10
|
+
* nothing (the shell-code-write gate checks for approved specs first, and the
|
|
11
|
+
* Stop gate early-returns on an empty governed set).
|
|
12
|
+
* governed — matcher 'Bash|Write|Edit'. phaseCheck has NO empty-spec escape, so on a
|
|
13
|
+
* project without an approved A-SPEC this denies EVERY code write — it would
|
|
14
|
+
* brick the target. Hence the precondition check below.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SERVER_NAME = "holmes-kit";
|
|
17
|
+
export type InitMode = 'guardrail' | 'governed';
|
|
18
|
+
export interface InitOptions {
|
|
19
|
+
target: string;
|
|
20
|
+
packageRoot: string;
|
|
21
|
+
mode: InitMode;
|
|
22
|
+
specsDir: string;
|
|
23
|
+
settingsFile: 'local' | 'project';
|
|
24
|
+
mcp: boolean;
|
|
25
|
+
matcher?: string;
|
|
26
|
+
dryRun: boolean;
|
|
27
|
+
force: boolean;
|
|
28
|
+
remove: boolean;
|
|
29
|
+
approval?: Approval;
|
|
30
|
+
agents?: Agent[];
|
|
31
|
+
allowAdditive?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface FileChange {
|
|
34
|
+
path: string;
|
|
35
|
+
before: string | null;
|
|
36
|
+
after: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface InitResult {
|
|
39
|
+
ok: boolean;
|
|
40
|
+
exitCode: 0 | 1 | 2;
|
|
41
|
+
messages: string[];
|
|
42
|
+
changes: FileChange[];
|
|
43
|
+
/** Paths a dry-run says it WOULD DELETE — never rendered with the write verb (round-8). */
|
|
44
|
+
removals?: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @implements A-SPEC-163
|
|
48
|
+
* Which tools the PreToolUse hook is registered for.
|
|
49
|
+
*
|
|
50
|
+
* `guardrail` used to be `Bash` alone, so an installed project's `Write` never reached the hook at
|
|
51
|
+
* all — measured 2026-08-08 against a real install. Fixing the gate's own tool predicate would have
|
|
52
|
+
* changed nothing there, which is the third time a capability existed in the repo and stopped at the
|
|
53
|
+
* installer (REQ-142, REQ-153).
|
|
54
|
+
*
|
|
55
|
+
* `.*` rather than a list: the list is what let `MultiEdit` and `NotebookEdit` through, and the next
|
|
56
|
+
* tool would repeat it. The gate itself decides what is a write (`writesFiles`); the matcher only
|
|
57
|
+
* decides what it gets to look at, and it should look at everything.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* @implements A-SPEC-178
|
|
61
|
+
* The matcher `init` installs. Exported so `doctor` compares against THIS value rather than a second
|
|
62
|
+
* spelling of it — a copy drifts the moment this changes, and doctor would then warn about correct
|
|
63
|
+
* wiring, which is worse than saying nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare const MATCHERS: Record<InitMode, string>;
|
|
66
|
+
/** Absolute hook/mcp commands, quoted so a package path containing spaces still works. */
|
|
67
|
+
export declare function buildHookPlan(packageRoot: string, matcher: string, specsDir?: string): HookPlan;
|
|
68
|
+
export declare const settingsPathOf: (target: string, which: "local" | "project") => string;
|
|
69
|
+
export declare function runInit(opts: InitOptions): InitResult;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Agent } from './agents';
|
|
2
|
+
/**
|
|
3
|
+
* Parses user input or CLI flag string into normalized Agent array.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseAgentList(input: string): Agent[];
|
|
6
|
+
/**
|
|
7
|
+
* Renders an interactive TTY checkbox selection menu using standard readline & ANSI codes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function promptAgentSelection(availableAgents?: readonly Agent[], currentWired?: Agent[]): Promise<Agent[]>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install the recovery playbooks into a project as agent skills.
|
|
3
|
+
*
|
|
4
|
+
* WHY this exists, measured: the gate denies with five deliberately specific strings, and each
|
|
5
|
+
* playbook is a skill whose `description` quotes the exact deny text that should summon it — the
|
|
6
|
+
* layer that turns a refusal into a next step. It did not ship. `npm pack --dry-run` carried ZERO
|
|
7
|
+
* playbook files, because `files: ["bin/","dist/"]` covered neither `playbooks/` nor anything like
|
|
8
|
+
* it, and `init` never wrote one either. Every installed user got the enforcement and none of the
|
|
9
|
+
* recovery, while all 47 conformance assertions passed in this repository and protected nobody.
|
|
10
|
+
*
|
|
11
|
+
* Playbooks are ADVISORY. No gate decision reads them, and a user who deletes every one of them
|
|
12
|
+
* still gets identical enforcement — which is why a missing playbook is a doctor WARN, never a FAIL.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Marks a file as OURS. Ownership is decided by this marker and never by the path, because the path
|
|
16
|
+
* is exactly what a user's own skill of the same name would occupy, and overwriting that would be
|
|
17
|
+
* the same class of error as clobbering their settings.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PLAYBOOK_SKILL_MARKER = "<!-- installed by holmes-kit; edits will be replaced on upgrade -->";
|
|
20
|
+
/**
|
|
21
|
+
* @implements A-SPEC-190 (round 7)
|
|
22
|
+
* The bytes an install writes. The marker used to go FIRST, which put it ahead of the playbook's
|
|
23
|
+
* YAML frontmatter — and frontmatter is only frontmatter when it starts at byte 0. Measured on this
|
|
24
|
+
* repository's own installed skills: the agent's skill list rendered
|
|
25
|
+
* `holmes-adopt: <!-- installed by holmes-kit; edits will be replaced on upgrade -->` for all three,
|
|
26
|
+
* i.e. the `description` that quotes the deny text — the entire reason this layer exists, since a
|
|
27
|
+
* refusal is supposed to summon the playbook by that sentence — was destroyed at install time,
|
|
28
|
+
* while doctor certified the layout as `current`. The marker now follows the frontmatter block.
|
|
29
|
+
*/
|
|
30
|
+
export declare function installedSkillBytes(body: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* @implements A-SPEC-190 (round 7)
|
|
33
|
+
* Ownership is the marker AT ITS PLACE, not the string anywhere in the file. `includes` made any
|
|
34
|
+
* document that merely QUOTES the marker — documentation about holmes-kit, a review note — read as
|
|
35
|
+
* ours: doctor called it `drifted` and the refresh it advised overwrote the user's file with no
|
|
36
|
+
* backup. A quotation is not a claim of ownership.
|
|
37
|
+
*/
|
|
38
|
+
export declare function carriesMarker(content: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* @implements A-SPEC-190 (round 8)
|
|
41
|
+
* Where a leading YAML frontmatter block ends, or null. Round-8 measured the shapes the first
|
|
42
|
+
* regex missed — a BOM, an EMPTY block (`---\n---\n`), and a closing fence at end-of-file with no
|
|
43
|
+
* trailing newline — each of which sent the marker back to byte 0, recreating the CRITICAL the
|
|
44
|
+
* round-7 fix existed to remove while doctor certified the result as `current`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function frontmatterEnd(content: string): number | null;
|
|
47
|
+
/**
|
|
48
|
+
* @implements A-SPEC-172
|
|
49
|
+
* What the user types to reach a playbook.
|
|
50
|
+
*
|
|
51
|
+
* The install directory is namespaced (`holmes-adopt`), but the internal playbook name is bare
|
|
52
|
+
* (`adopt`), and every message echoed the bare one. Measured 2026-08-12 on an installed tarball: init
|
|
53
|
+
* printed `Installed 3 recovery skill(s): adopt, author-slice, promote-slice` while nothing by those
|
|
54
|
+
* names existed. Anything shown to a user goes through here.
|
|
55
|
+
*/
|
|
56
|
+
export declare function invocableSkillName(playbookName: string): string;
|
|
57
|
+
/** `<target>/.claude/skills/holmes-<name>/SKILL.md` */
|
|
58
|
+
export declare function skillPathFor(target: string, playbookName: string): string;
|
|
59
|
+
export interface PlaybookInstallResult {
|
|
60
|
+
/** Playbook names written (new, or refreshed because the destination was ours). */
|
|
61
|
+
written: string[];
|
|
62
|
+
/** Destinations whose previous content was preserved beside them before being rewritten. */
|
|
63
|
+
backedUp: {
|
|
64
|
+
name: string;
|
|
65
|
+
path: string;
|
|
66
|
+
}[];
|
|
67
|
+
/** Names left alone because a FOREIGN file occupies the destination. */
|
|
68
|
+
skipped: string[];
|
|
69
|
+
/** Names whose destination could not be written; reported rather than thrown. */
|
|
70
|
+
failed: {
|
|
71
|
+
name: string;
|
|
72
|
+
reason: string;
|
|
73
|
+
}[];
|
|
74
|
+
}
|
|
75
|
+
export declare function installPlaybookSkills(packageRoot: string, target: string): PlaybookInstallResult;
|
|
76
|
+
export interface PlaybookRemoveResult {
|
|
77
|
+
/** RAW directory names, case intact — round-7: the report re-prefixed a case variant and named a directory that does not exist. */
|
|
78
|
+
removed: string[];
|
|
79
|
+
failed: {
|
|
80
|
+
name: string;
|
|
81
|
+
reason: string;
|
|
82
|
+
}[];
|
|
83
|
+
}
|
|
84
|
+
/** Removes ONLY files carrying the marker; a foreign file, and the directory holding it, are left. */
|
|
85
|
+
export declare function removePlaybookSkills(target: string): PlaybookRemoveResult;
|
|
86
|
+
/**
|
|
87
|
+
* How a failed entry should be NAMED in a report.
|
|
88
|
+
*
|
|
89
|
+
* @implements A-SPEC-190 §21 (round 13) — root-level refusals carry `.claude/skills` as their name,
|
|
90
|
+
* and the callers ran every name through `invocableSkillName`, printing `holmes-.claude/skills` —
|
|
91
|
+
* a skill that does not exist. Round 7 fixed the same prefixing on the remove side. A name that
|
|
92
|
+
* already looks like a path is a path.
|
|
93
|
+
*/
|
|
94
|
+
export declare function skillLabel(name: string): string;
|
|
95
|
+
export type PlaybookSkillState = 'current' | 'drifted' | 'foreign' | 'missing' | 'orphaned' | 'source-unreadable' | 'aliased' | 'unresolvable';
|
|
96
|
+
/**
|
|
97
|
+
* @implements A-SPEC-190
|
|
98
|
+
* Per-playbook state of the installed copy, judged against the bytes install would write NOW
|
|
99
|
+
* (`marker + '\n' + shipped body`). An installed skill is a COPY: the kit's conventions move on
|
|
100
|
+
* while the copy stays frozen at install time. Measured 2026-08-15 in this very repository: all
|
|
101
|
+
* three installed skills had drifted (diff 50/38/64 lines) and the author-slice copy still taught
|
|
102
|
+
* the abolished `A-SPEC-125.5 → T-SPEC-1255` id convention — while the count-only doctor check
|
|
103
|
+
* reported "3 of 3 … PASS". Equality with the reconstruction is the single source of truth; a
|
|
104
|
+
* version stamp or hash file would be a second truth that itself goes stale.
|
|
105
|
+
*
|
|
106
|
+
* A shipped body that cannot be read (a race — playbookNames just confirmed it) yields no entry
|
|
107
|
+
* for that name: silence beats a wrong repair hint.
|
|
108
|
+
*/
|
|
109
|
+
export declare function playbookSkillStates(packageRoot: string, target: string): {
|
|
110
|
+
name: string;
|
|
111
|
+
state: PlaybookSkillState;
|
|
112
|
+
}[];
|
|
113
|
+
/**
|
|
114
|
+
* @implements A-SPEC-190 (§6)
|
|
115
|
+
* Which extra `.claude/skills` entries are ORPHANED marker-owned installs? Pure — the fs facts
|
|
116
|
+
* (inode identity, marker ownership) come in as functions, so the case-SENSITIVE world is testable
|
|
117
|
+
* on any machine (round-5 HIGH lived exactly in the branch this box's case-folding FS cannot
|
|
118
|
+
* exercise: `Holmes-adopt` sliced to `adopt`, matched shipped, and was swallowed BEFORE the
|
|
119
|
+
* variant/inode logic — doctor printed PASS over stale marker-owned guidance).
|
|
120
|
+
*
|
|
121
|
+
* - exact canonical entries are the shipped loop's business (skip);
|
|
122
|
+
* - a case-variant with the SAME inode is the same physical dir (case-insensitive FS — skip;
|
|
123
|
+
* round-2: double-counting made the orphan remedy delete the current skill);
|
|
124
|
+
* - everything else marker-owned is orphaned; unreadable/markerless stays silent (ownership is
|
|
125
|
+
* the marker's job — user territory).
|
|
126
|
+
*/
|
|
127
|
+
export declare function classifyExtraEntries(entries: string[], shipped: string[], sameInode: (entry: string, canonicalDir: string) => boolean | undefined, owned: (entry: string) => boolean): string[];
|
|
128
|
+
/** How many recovery skills are installed — the fact `doctor` reports. */
|
|
129
|
+
export declare function installedPlaybookCount(target: string): number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Identifies the file as ours, so `--remove` never reclaims something a user wrote. */
|
|
2
|
+
export declare const ROLES_README_MARKER = "<!-- holmes-kit:roles-guide -->";
|
|
3
|
+
export declare const rolesReadmePath: (root: string) => string;
|
|
4
|
+
/** Installs the guidance. A file the user has edited is left alone. */
|
|
5
|
+
export declare function installRolesReadme(root: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Reclaims the guidance on `init --remove`. Returns whether it was ours to remove.
|
|
8
|
+
*
|
|
9
|
+
* Only this file is touched — a project's real `policy.yaml` living beside it must survive, since
|
|
10
|
+
* removing the docs is not a request to disable governance.
|
|
11
|
+
*/
|
|
12
|
+
export declare function removeRolesReadme(root: string): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ServeOptions {
|
|
2
|
+
root: string;
|
|
3
|
+
port?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Execute holmes-kit serve subcommand. Keeps process alive until SIGINT/SIGTERM in CLI execution.
|
|
7
|
+
*
|
|
8
|
+
* @implements A-SPEC-215
|
|
9
|
+
*/
|
|
10
|
+
export declare function runServeCommand(options: ServeOptions): Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
url: string;
|
|
13
|
+
stop: () => Promise<void>;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runServeCommand = runServeCommand;
|
|
37
|
+
// @implements A-SPEC-215
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const dashboard_1 = require("../server/dashboard");
|
|
41
|
+
/**
|
|
42
|
+
* Execute holmes-kit serve subcommand. Keeps process alive until SIGINT/SIGTERM in CLI execution.
|
|
43
|
+
*
|
|
44
|
+
* @implements A-SPEC-215
|
|
45
|
+
*/
|
|
46
|
+
async function runServeCommand(options) {
|
|
47
|
+
const root = path.resolve(options.root);
|
|
48
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
49
|
+
process.stderr.write(`[holmes-kit serve] Target directory does not exist: ${root}\n`);
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
const handle = await (0, dashboard_1.startDashboardServer)({ root, port: options.port });
|
|
53
|
+
process.stdout.write(`🚀 Holmes-Kit Dashboard Server running at ${handle.url}\nPress Ctrl+C to stop.\n`);
|
|
54
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
55
|
+
await new Promise((resolve) => {
|
|
56
|
+
const shutdown = async () => {
|
|
57
|
+
process.stdout.write(`\nStopping Holmes-Kit Dashboard Server...\n`);
|
|
58
|
+
await handle.stop();
|
|
59
|
+
resolve();
|
|
60
|
+
};
|
|
61
|
+
process.once('SIGINT', shutdown);
|
|
62
|
+
process.once('SIGTERM', shutdown);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
url: handle.url,
|
|
68
|
+
stop: handle.stop,
|
|
69
|
+
};
|
|
70
|
+
}
|