@holmes-lab/holmes-kit 0.1.2 → 0.1.4

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 CHANGED
@@ -5,6 +5,26 @@ 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
 
8
+ ## [0.1.4] - 2026-08-18
9
+
10
+ ### Documentation
11
+ - **Supported Languages & Environments Matrix**: Added comprehensive compatibility tables in `README.md` covering 7 AST/CPG supported languages (TypeScript/JS, Python, Go, Rust, Java, C/C++, C#) and tier-based OS environments (macOS, Linux, Windows/WSL2).
12
+
13
+ ## [0.1.3] - 2026-08-18
14
+
15
+ ### Added
16
+ - **Batch Spec DX Tools (REQ-203)**: Added `spec_slice_init` and `spec_slice_approve` MCP tools to generate and seal the full 4-tier specification chain (`REQ ➔ H-SPEC ➔ A-SPEC ➔ T-SPEC`) in a single call.
17
+ - **Self-Healing Auto-Remediation (REQ-204)**: Added `spec_remediate` MCP tool and `holmes-remediation` playbook skill for automatic anchor injection and slice sealing.
18
+ - **Interactive Multi-Agent Init UX (REQ-200)**: Interactive terminal multi-select selector during `holmes-kit init` supporting Claude Code, Antigravity CLI, and Codex.
19
+
20
+ ### Changed
21
+ - **English-First Refusal Messaging Architecture (REQ-1403)**: Standardized all hook gate refusal messages, CLI agent instructions, and playbooks to 3-part structured English (`[Holmes-Kit Gate Refusal] Title — Technical Cause — Next Action`).
22
+ - **AGENTS.md Standardization**: Global open-source guidelines structured around 3 Core Operational Rules (Tool-First, Anchor-First, Remediation Protocol).
23
+
24
+ ### Fixed
25
+ - **Sub-Slice ID Parsing (REQ-204)**: Corrected specification ID parser to isolate base numeric IDs from dot-notated sub-slices (`T-SPEC-140.2`).
26
+ - **Antigravity CLI Argument Normalization**: Standardized parameter mapping for `TargetFile` and `CodeContent` across diverse tool calling conventions.
27
+
8
28
  ## [0.1.0] - 2026-08-18
9
29
 
10
30
  ### Added
package/README.md CHANGED
@@ -1,102 +1,128 @@
1
1
  # 🔍 Holmes-Kit
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/holmes-kit.svg)](https://www.npmjs.com/package/holmes-kit)
4
- [![node version](https://img.shields.io/node/v/holmes-kit.svg)](https://nodejs.org)
5
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/%40holmes-lab%2Fholmes-kit.svg?color=blue)](https://www.npmjs.com/package/@holmes-lab/holmes-kit)
4
+ [![node version](https://img.shields.io/node/v/%40holmes-lab%2Fholmes-kit.svg)](https://nodejs.org)
5
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/snpark-io/holmes-kit/blob/main/LICENSE)
6
6
 
7
- **Deterministic Agentic Software Engineering (ASE) harness with causal traceability.**
7
+ > **"No Spec, No Code"** — Deterministic Agentic Software Engineering (ASE) harness with causal traceability.
8
8
 
9
- Holmes-Kit is not just a coding assistant it is a **deterministic control plane** that governs how AI agents build software, so that every line of code is causally traceable back to a requirement, a design, and a decision ("who, when, why"). The governance is enforced in code (not prompts), so an agent cannot rationalize its way past it.
9
+ Holmes-Kit is a **deterministic control plane** for AI coding agents (**Claude Code**, **Antigravity CLI**, **Codex CLI**). It enforces architectural discipline at the hook layer so that every line of code is causally traceable back to approved specifications (`REQ H-SPEC A-SPEC T-SPEC`).
10
10
 
11
- > Detective, not autocomplete: like Sherlock Holmes, the harness reasons from evidence — requirements → design → code → tests → decisions — over an exact causal graph, not probabilistic guesses.
11
+ ---
12
12
 
13
- ## Core Ideas
13
+ ## Quickstart (3-Minute Setup)
14
14
 
15
- - **Deterministic control plane** — governance/traceability live in code; the LLM is advisory. Phase gating, RTM integrity, and impact analysis are exact queries, never approximations.
16
- - **Governance spec chain** — `REQ → H-SPEC → A-SPEC → C-SPEC → T-SPEC`, plus a runtime `JOB` ledger. Code anchors to its A-SPEC via `@implements A-SPEC-NNN`.
17
- - **Phase guardrail (block-and-redirect)** — the current SE phase is *derived from graph state*, not a mutable flag. Out-of-phase actions (e.g. writing code with no approved A-SPEC/T-SPEC) are blocked and the agent is redirected to the correct next step.
18
- - **D-CPG + RTM** — tree-sitter Code Property Graph + SQLite RTM graph (recursive-CTE reachability). `rtm_impact` answers "which specs does changing this symbol affect?" — the core of causal traceability.
19
- - **Hexagonal Ports & Adapters** local Markdown today, enterprise adapters (DB/JIRA/Confluence) later, with no core rework.
20
- - **Decision provenance** — architecture decisions are recorded as ADRs and linked into the graph.
21
-
22
- ## Requirements & Prerequisites
15
+ ### 1. Install CLI
16
+ ```bash
17
+ npm install -g @holmes-lab/holmes-kit
18
+ ```
19
+ *(Requires Node.js `>= 20.0.0` and C++ build tools for native SQLite/tree-sitter)*
23
20
 
24
- - **Node.js**: `>= 20.0.0` (Enforced via `.npmrc`)
25
- - **C++ Build Toolchain**: Xcode Command Line Tools (`xcode-select --install` on macOS) or `build-essential` / `g++` / `make` (Linux) for native `better-sqlite3` and `tree-sitter` C++ Addon compilation.
21
+ ### 2. Initialize in Your Project
22
+ ```bash
23
+ cd /path/to/your/project
24
+ holmes-kit init
25
+ ```
26
+ *An interactive prompt will ask which AI agent harnesses to wire into your project:*
27
+ ```text
28
+ ? Select the AI Agent harnesses to wire into this project:
29
+ [X] 🤖 Claude Code (.claude/settings.local.json, .mcp.json)
30
+ [X] 🚀 Antigravity CLI (AGY) (.agents/mcp_config.json, hooks.json, skills)
31
+ [ ] 💻 Codex CLI (.codex/mcp_config.json)
32
+ ```
26
33
 
27
- ## Installation & Setup
34
+ ### 3. Verify Health
35
+ ```bash
36
+ holmes-kit doctor
37
+ ```
38
+ *If everything is green, your project is governed and ready for AI pair-programming!*
28
39
 
29
- ### 1. Installation
40
+ ---
30
41
 
31
- #### Option A: Direct Git Release Installation (v0.1.1)
42
+ ## 🔄 Daily Workflow (How It Works)
32
43
 
33
- ```bash
34
- # Global CLI install with allowed native C++ build scripts
35
- npm install -g --allow-scripts=better-sqlite3,tree-sitter,tree-sitter-c-sharp,tree-sitter-cpp,tree-sitter-go,tree-sitter-java,tree-sitter-python,tree-sitter-rust,tree-sitter-typescript \
36
- "git+https://github.com/snpark-io/holmes-kit.git#v0.1.1"
44
+ Once initialized, your AI agent automatically follows the **No Spec, No Code** lifecycle:
37
45
 
38
- # Verify installation health
39
- holmes-kit doctor
46
+ ```mermaid
47
+ flowchart LR
48
+ A["1. Spec First<br/>(REQ → A-SPEC)"] --> B["2. Test First<br/>(TDD & T-SPEC)"]
49
+ B --> C["3. Implementation<br/>(// @implements A-SPEC)"]
50
+ C --> D["4. Verified Code<br/>(Provenance Sealed)"]
40
51
  ```
41
52
 
42
- #### Option B: Clean 1-Line Installation via NPM Registry
53
+ 1. **Spec First**: The agent authors requirements and architecture specs via `spec_create`.
54
+ 2. **Test First**: The agent writes tests and approves `T-SPEC` before writing implementation code.
55
+ 3. **Implement**: Code files anchor to their architecture spec with `// @implements A-SPEC-NNN`.
56
+ 4. **Deterministic Guard**: If the agent attempts to write code without approved specs, **Holmes-Kit hooks block the action and provide exact next steps**.
43
57
 
44
- ```bash
45
- # Once published to npm registry:
46
- npm install -g holmes-kit
58
+ ---
47
59
 
48
- # Verify health
49
- holmes-kit doctor
50
- ```
60
+ ## 🧰 Essential CLI Cheatsheet
51
61
 
52
- ### 2. Project Initialization
62
+ | Command | Purpose |
63
+ | :--- | :--- |
64
+ | `holmes-kit init` | Interactive agent harness setup (Claude, Antigravity, Codex) |
65
+ | `holmes-kit init --agent all` | Non-interactive instant setup for all supported agents |
66
+ | `holmes-kit init --dry-run` | Preview files and configuration changes without writing |
67
+ | `holmes-kit doctor` | Comprehensive health check of specs, hooks, MCP, and anchors |
53
68
 
54
- ```bash
55
- cd /path/to/your/project
56
- holmes-kit init --dry-run # Preview configuration without writing
57
- holmes-kit init # Wire Holmes-Kit hooks into target project
58
- ```
69
+ ---
59
70
 
60
- ### 3. MCP Integration
71
+ ## 🤖 Manual MCP Configuration
61
72
 
62
- Add Holmes-Kit as an MCP server in your editor or agent environment (`.mcp.json`):
73
+ If you prefer to configure MCP manually or integrate with other IDEs, add the following to your `.mcp.json`:
63
74
 
64
75
  ```json
65
76
  {
66
77
  "mcpServers": {
67
78
  "holmes-kit": {
68
79
  "command": "npx",
69
- "args": ["-y", "holmes-mcp"]
80
+ "args": ["-y", "--package=@holmes-lab/holmes-kit", "holmes-mcp"],
81
+ "env": {
82
+ "HOLMES_SPECS": ".ax/specs"
83
+ }
70
84
  }
71
85
  }
72
86
  }
73
87
  ```
74
88
 
75
- ### 4. Environment Variables (Optional)
89
+ ---
76
90
 
77
- ```bash
78
- cp .env.example .env
79
- # Edit .env and fill in your values
80
- ```
91
+ ## 🌐 Supported Languages & Environments
81
92
 
82
- ## Status
93
+ Holmes-Kit embeds native AST & Code Property Graph (D-CPG) analyzers to track causal relationships and anchor implementations across diverse technology stacks.
83
94
 
84
- Under active development on branch `feat/holmes-kit-ase-design`.
95
+ ### 💻 Supported Programming Languages
96
+ | Language | CPG Parser | Anchor Syntax | Dataflow Taint & RTM |
97
+ | :--- | :--- | :--- | :---: |
98
+ | **TypeScript / JavaScript** | Tree-sitter TypeScript/JS | `// @implements A-SPEC-XXX` | ✅ Full Support |
99
+ | **Python** | Tree-sitter Python | `# @implements A-SPEC-XXX` | ✅ Full Support |
100
+ | **Go** | Tree-sitter Go | `// @implements A-SPEC-XXX` | ✅ Full Support |
101
+ | **Rust** | Tree-sitter Rust | `// @implements A-SPEC-XXX` | ✅ Full Support |
102
+ | **Java** | Tree-sitter Java | `// @implements A-SPEC-XXX` | ✅ Full Support |
103
+ | **C / C++** | Tree-sitter C/C++ | `// @implements A-SPEC-XXX` | ✅ Full Support |
104
+ | **C# (.NET)** | Tree-sitter C# | `// @implements A-SPEC-XXX` | ✅ Full Support |
85
105
 
86
- - **v0.1** spec standard + MCP guardrail (spec validation, `rtm_check`, phase guardrail, MCP server, hook).
87
- - **v0.2** — D-CPG + RTM traceability (tree-sitter symbols/edges, CPG scanner, RTM graph, change-impact, MCP `cpg_scan`/`rtm_impact`). Dogfooded on Holmes-Kit's own source.
106
+ ### 🖥️ Supported Operating Systems & Runtimes
107
+ | OS / Platform | Architecture | Status | Notes |
108
+ | :--- | :--- | :---: | :--- |
109
+ | **macOS** | Apple Silicon (arm64) / Intel (x64) | ✅ Tier 1 | macOS 12+ (Full hook enforcement) |
110
+ | **Linux** | x86_64 / arm64 | ✅ Tier 1 | Ubuntu, Debian, Fedora, Arch, RHEL |
111
+ | **Windows (WSL2)** | x86_64 | ✅ Tier 1 | WSL2 Ubuntu/Debian recommended |
112
+ | **Windows Native** | x86_64 | ✅ Tier 2 | Windows 10/11 (Node.js 20+ with C++ build tools) |
88
113
 
89
- Target architecture and design records live in [`docs/architecture/`](docs/architecture/) and decisions in [`.ax/decisions/`](.ax/decisions/).
114
+ > **Runtime Requirement**: Node.js `>= 20.0.0` (LTS recommended)
90
115
 
91
- ## Build & Test
116
+ ---
92
117
 
93
- ```bash
94
- npm install
95
- npm run typecheck # TypeScript static type check
96
- npm run build # tsc dist/holmes/**
97
- npm test # jest full test suite
98
- ```
118
+ ## 🏛️ Key Capabilities
119
+
120
+ - **Deterministic Hook Enforcement**: Gating is enforced via OS-level PreToolUse & Stop hooks, not easily bypassed prompts.
121
+ - **D-CPG + RTM Graph**: Tree-sitter Code Property Graph + SQLite recursive-CTE for instant blast-radius impact analysis (`rtm_impact`).
122
+ - **Cryptographic Provenance Ledger**: Every decision, approval, and gate transition is immutably recorded in `.ax/ledger/`.
123
+
124
+ ---
99
125
 
100
- ## License
126
+ ## 📜 License
101
127
 
102
- MIT
128
+ Distributed under the [MIT License](https://github.com/snpark-io/holmes-kit/blob/main/LICENSE).
package/dist/.build-id CHANGED
@@ -1 +1 @@
1
- a81c431-msxg7g28
1
+ acd8a7b-msxveq6c
@@ -96,29 +96,38 @@ const hooksJson = (packageRoot) => `${JSON.stringify({
96
96
  }],
97
97
  },
98
98
  }, null, 2)}\n`;
99
- const AGENTS_MD = (enforced) => `# Holmes-Kit — 저장소의 작업 규율
99
+ const AGENTS_MD = (enforced) => `# Holmes-Kit — Workspace Operational Discipline
100
100
 
101
- 저장소는 **No Spec, No Code** 운영된다. 코드를 쓰기 전에 승인된 스펙이 있어야 한다.
101
+ This repository operates under **No Spec, No Code** governance. An approved specification MUST exist before writing source or test code.
102
102
 
103
- ## 절차
103
+ ## 3 Core Operational Rules
104
104
 
105
- 1. \`spec_create\` 로 REQ → H-SPEC A-SPEC 쓰고 \`spec_approve\` 로 승인한다.
106
- 2. **테스트를 먼저** 쓰고 실패(RED) 확인한다.
107
- 3. T-SPEC 승인한다(거울 규약: \`A-SPEC-188\` \`T-SPEC-188\`).
108
- 4. 그 다음에 구현한다.
105
+ 1. **Tool-First Principle (NO Workaround Scripts)**:
106
+ - Creating or mutating \`.ax/specs\` or governance ledgers via arbitrary temporary scripts (\`/tmp/*.js\`) is strictly forbidden.
107
+ - Use only \`spec_slice_init\`, \`spec_slice_approve\`, \`spec_create\`, and \`spec_approve\` MCP tools to author and seal specs.
109
108
 
110
- \`spec_next\` 다음에 일을 말해 준다. \`phase_check\` 로 지금 하려는 행동이 허용되는지
111
- 미리 물을 있다.
109
+ 2. **Anchor-First Principle (Mandatory Code Anchors)**:
110
+ - When creating or modifying source code and test files, line 1 MUST include an explicit anchor comment: \`// @\` + \`implements A-SPEC-XXX\`.
112
111
 
113
- ## 하네스에서의 집행
112
+ 3. **Remediation Protocol (NO Guardrail Reverse Engineering)**:
113
+ - When a tool call is denied by a hook gate, DO NOT inspect guardrail source code (\`pre-tool-use.ts\`, \`phase.ts\`) or attempt to write bypass code.
114
+ - Immediately follow the 3-step remediation instructions provided in the refusal message or invoke the \`spec_remediate\` MCP tool / \`holmes-remediation\` playbook skill.
115
+
116
+ ## Procedure
117
+
118
+ 1. Run \`spec_slice_init\` or \`spec_create\` to author REQ -> H-SPEC -> A-SPEC.
119
+ 2. Approve specs via \`spec_slice_approve\` or \`spec_approve\`.
120
+ 3. **Write tests first** and verify failure (RED stage).
121
+ 4. Approve the mirroring T-SPEC (\`A-SPEC-188\` -> \`T-SPEC-188\`).
122
+ 5. Implement source code (GREEN stage).
123
+
124
+ Use \`spec_next\` to check the next required governance step. Use \`phase_check\` to verify action permissions ahead of execution.
125
+
126
+ ## Enforcement in this Harness
114
127
 
115
128
  ${enforced
116
- ? `게이트가 **집행된다**. 승인된 스펙 없이 코드를 쓰려 하면 도구 호출이 거부되고, 해소되지
117
- 않은 치명 발견이 있으면 턴이 끝나지 않는다. 거부 문면이 다음에 무엇을 하면 되는지 말한다.`
118
- : `이 하네스에는 우리가 실측한 **훅 집행 지점이 없다**. 그래서 Holmes-Kit 은 여기서 도구와
119
- 지침만 제공하고 **게이트를 집행하지 않는다** — 규율은 조언으로만 선다. 집행이 필요하면 Claude
120
- Code 나 Antigravity 하네스에서 작업하라. (이 문장은 우리가 그 도구의 훅 규약을 재지 못했다는
121
- 사실의 기록이지, 그 도구에 훅이 없다는 단정이 아니다.)`}
129
+ ? `Gates are **enforced**. Tool calls that write un-anchored code or target unapproved specifications will be denied. Unresolved critical findings block completion.`
130
+ : `This harness does not have measured **hook enforcement points**. Holmes-Kit provides tools and guidance here without active gate blocking.`}
122
131
  `;
123
132
  /**
124
133
  * 이 하네스에 써야 할 파일들. **쓰지는 않는다** — 무엇을 쓸지만 말한다.
@@ -19,7 +19,7 @@ exports.ACTIONS = [
19
19
  'AUTHOR_REQ', 'AUTHOR_HSPEC', 'AUTHOR_ASPEC', 'AUTHOR_CSPEC', 'AUTHOR_TSPEC',
20
20
  'WRITE_TEST', 'WRITE_CODE',
21
21
  ];
22
- const NO_SPEC_NO_CODE = 'No Spec, No Code — 선행 스펙 승인 구현 진입 금지';
22
+ const NO_SPEC_NO_CODE = 'No Spec, No Code — Approved specification required before code modification';
23
23
  // Executable-source extensions the WRITE_CODE gate must cover. Kept broad on purpose: an extension
24
24
  // NOT listed here silently escapes No-Spec-No-Code, so err toward inclusion (adversarial-review F2 —
25
25
  // .mjs/.cjs/.mts/.cts and C/C++ header/variant + other-language sources were escaping).
@@ -81,14 +81,14 @@ function phaseCheck(action, ctx) {
81
81
  case 'AUTHOR_REQ': return { decision: 'allow', phase: 'INTAKE' };
82
82
  case 'AUTHOR_HSPEC':
83
83
  return existsOfType('REQ') ? { decision: 'allow', phase: 'DESIGN' }
84
- : deny('INTAKE', ['REQ'], '선행 REQ 없습니다.', "spec_create(type='REQ', ...)");
84
+ : deny('INTAKE', ['REQ'], 'Preceding REQ not found.', "spec_slice_init or spec_create(type='REQ', ...)");
85
85
  case 'AUTHOR_ASPEC':
86
86
  return approvedOfType('H-SPEC') ? { decision: 'allow', phase: 'SPECIFY' }
87
- : deny('DESIGN', ['approved H-SPEC'], 'approved H-SPEC 없습니다.', "H-SPEC 완성 spec_validate approve");
87
+ : deny('DESIGN', ['approved H-SPEC'], 'Approved H-SPEC not found.', "Complete H-SPEC then execute spec_validate -> approve");
88
88
  case 'AUTHOR_CSPEC':
89
89
  case 'AUTHOR_TSPEC':
90
90
  return approvedOfType('A-SPEC') ? { decision: 'allow', phase: 'TEST-SPEC' }
91
- : deny('SPECIFY', ['approved A-SPEC'], 'approved A-SPEC 없습니다.', "A-SPEC 먼저 작성·승인");
91
+ : deny('SPECIFY', ['approved A-SPEC'], 'Approved A-SPEC not found.', "Author and approve A-SPEC first");
92
92
  case 'WRITE_TEST':
93
93
  case 'WRITE_CODE': {
94
94
  const aspec = ctx.targetAspecId ? byId.get(ctx.targetAspecId) : undefined;
@@ -124,11 +124,8 @@ function phaseCheck(action, ctx) {
124
124
  // refusal that named nothing into a 1,209-char one that named everything).
125
125
  const unquotable = ctx.targetAspecId !== undefined && quotableId(ctx.targetAspecId) === null;
126
126
  const fold = (s) => (unquotable && ctx.targetAspecId
127
- ? s.split(ctx.targetAspecId).join('<이름이 문면 예산을 넘어 생략>')
127
+ ? s.split(ctx.targetAspecId).join('<name omitted: exceeds text budget>')
128
128
  : s);
129
- // Never throws — the contract `blockerSummary` carried and this call must keep: this runs in
130
- // a PreToolUse hook on every tool call, and a refusal that says less is recoverable while a
131
- // hook that dies is not. (Round-10 lost it for one build by calling approvalBlockers raw.)
132
129
  const rawBlockers = (() => {
133
130
  try {
134
131
  return aspec ? (0, approval_blockers_1.approvalBlockers)(aspec, (id) => byId.get(id) ?? null).map(fold) : [];
@@ -137,14 +134,11 @@ function phaseCheck(action, ctx) {
137
134
  return [];
138
135
  }
139
136
  })();
140
- const why = (0, tspec_state_1.budgetedClause)(unquotable ? '<이름이 문면 예산을 넘어 생략>' : (ctx.targetAspecId ?? aspec?.id ?? ''), `${aspec?.type ?? 'A-SPEC'}`, rawBlockers);
141
- // 부재는 '미지정'이고, 이름이 문면 예산을 넘는 것은 다른 사실이다 — 두 사정을 한 낱말로
142
- // 뭉치면 호출자는 자기가 무엇을 잘못했는지 알 수 없다.
143
- // 빈 문자열도 부재다(round-10: 빈 괄호만 남아 무엇이 문제인지 말하지 않았다).
137
+ const why = (0, tspec_state_1.budgetedClause)(unquotable ? '<name omitted: exceeds text budget>' : (ctx.targetAspecId ?? aspec?.id ?? ''), `${aspec?.type ?? 'A-SPEC'}`, rawBlockers);
144
138
  const named = ctx.targetAspecId === undefined || ctx.targetAspecId === ''
145
- ? '미지정'
146
- : (quotableId(ctx.targetAspecId) ?? '이름이 문면 예산을 넘어 생략');
147
- return deny('DESIGN', ['approved A-SPEC'], `구현 대상 A-SPEC(${named}) approved가 아닙니다.${why}`, "spec_create/approve로 A-SPEC T-SPEC 완성");
139
+ ? 'unspecified'
140
+ : (quotableId(ctx.targetAspecId) ?? 'name omitted: exceeds text budget');
141
+ return deny('DESIGN', ['approved A-SPEC'], `Target specification A-SPEC(${named}) is not approved.${why}`, "Complete A-SPEC -> T-SPEC via spec_slice_init/spec_approve");
148
142
  }
149
143
  // @implements A-SPEC-183
150
144
  // A STATE, not a boolean. Measured 2026-08-13: four different T-SPEC situations produced one
@@ -52,6 +52,7 @@ const node_child_process_1 = require("node:child_process");
52
52
  */
53
53
  exports.TOOL_MAP = {
54
54
  run_command: { as: 'Bash', commandArg: 'CommandLine' },
55
+ write_to_file: { as: 'Write', pathArg: 'TargetFile', contentArg: 'CodeContent' },
55
56
  replace_file_content: { as: 'Edit', pathArg: 'TargetFile', contentArg: 'ReplacementContent' },
56
57
  view_file: { as: 'Read', pathArg: 'AbsolutePath' },
57
58
  grep_search: { as: 'Grep' },
@@ -224,16 +224,16 @@ function normalizeHookInput(raw) {
224
224
  || ['file_path', 'notebook_path'].some((f) => f in ti && typeof ti[f] !== 'string');
225
225
  const out = {};
226
226
  // Assigned individually so an undefined never becomes a present-but-undefined key downstream.
227
- const fp = str(ti.file_path);
227
+ const fp = str(ti.file_path ?? ti.TargetFile);
228
228
  if (fp !== undefined)
229
229
  out.file_path = fp;
230
- const c = str(ti.content);
230
+ const c = str(ti.content ?? ti.CodeContent);
231
231
  if (c !== undefined)
232
232
  out.content = c;
233
- const ns = str(ti.new_string);
233
+ const ns = str(ti.new_string ?? ti.ReplacementContent);
234
234
  if (ns !== undefined)
235
235
  out.new_string = ns;
236
- const cmd = str(ti.command);
236
+ const cmd = str(ti.command ?? ti.CommandLine);
237
237
  if (cmd !== undefined)
238
238
  out.command = cmd;
239
239
  // @implements A-SPEC-163 — a path field the gate does not preserve is a path the gate cannot see,
@@ -833,8 +833,8 @@ function evaluateHook(input, specsDir, opts) {
833
833
  // the budget so later SHORT sentences vanished under a FALSE '잘림' label. clampBlockers'
834
834
  // discipline instead: an oversized sentence is replaced whole by an honest omission label,
835
835
  // and fitting sentences keep rendering out of the shared budget.
836
- const OMIT_OVER = ' (사유가 예산을 넘어 생략해당 스펙을 개별 확인하십시오)';
837
- const OMIT_SPENT = ' (문면 예산 소진해당 스펙을 개별 확인하십시오)';
836
+ const OMIT_OVER = ' (reason exceeds text budgetverify spec individually)';
837
+ const OMIT_SPENT = ' (text budget spentverify spec individually)';
838
838
  const shown0 = groups.slice(0, DISTINCT_REASON_CAP);
839
839
  // @implements A-SPEC-192 §5R (round 9) — a SHARE, not a race. First-come-full-draw made the
840
840
  // budget a cliff: one 1,199-character sentence (just under the cap) took the whole allowance
@@ -844,7 +844,7 @@ function evaluateHook(input, specsDir, opts) {
844
844
  // first; whatever the short ones do not use is then offered to the rest in order.
845
845
  const bodies = new Map();
846
846
  for (const [key, ids] of shown0) {
847
- bodies.set(key, JSON.parse(key).join(ids.length === 1 && ids[0].length <= tspec_state_1.ID_MAX ? ids[0] : '해당 스펙'));
847
+ bodies.set(key, JSON.parse(key).join(ids.length === 1 && ids[0].length <= tspec_state_1.ID_MAX ? ids[0] : 'target spec'));
848
848
  }
849
849
  // @implements A-SPEC-192 §8R (round 10, 2nd) — the SAME algorithm the sibling surface uses, and
850
850
  // only that one. The share/remainder pair moved the cliff instead of removing it, and its two
@@ -878,15 +878,15 @@ function evaluateHook(input, specsDir, opts) {
878
878
  const shown = shown0;
879
879
  const hidden = groups.slice(DISTINCT_REASON_CAP);
880
880
  // @implements A-SPEC-192 — the three id lists ride REQ-183's budget: bounded naming, honest counts.
881
- const why = shown.map(([key, ids]) => ` [${(0, tspec_state_1.listOrCount)(ids, tspec_state_1.ID_BUDGET, '', Infinity)}] ${render(key, ids)}`).join('')
881
+ const why = shown.map(([key, ids]) => ` [${(0, tspec_state_1.listOrCount)(ids, tspec_state_1.ID_BUDGET, 'spec(s)', Infinity)}] ${render(key, ids)}`).join('')
882
882
  // Name the specs whose reasons did not fit. Saying only "N more" sends the author back to
883
883
  // check all of them — the round-trip this REQ exists to remove.
884
884
  + (hidden.length > 0
885
- ? ` (${(0, tspec_state_1.listOrCount)(hidden.flatMap(([, ids]) => ids), tspec_state_1.ID_BUDGET, '', Infinity)} 사유가 각각 달라 개별 확인이 필요하다)` : '');
885
+ ? ` (${(0, tspec_state_1.listOrCount)(hidden.flatMap(([, ids]) => ids), tspec_state_1.ID_BUDGET, 'items', Infinity)} require individual verification due to distinct reasons)` : '');
886
886
  return {
887
887
  permissionDecision: 'deny',
888
- permissionDecisionReason: `[Holmes-Kit] 변경이 새로 주장하는 ${(0, tspec_state_1.listOrCount)(unapproved, tspec_state_1.ID_BUDGET, '', Infinity)}이(가) approved가 아닙니다`
889
- + ` — 기존 파일이라도 승인되지 않은 스펙을 주장할 없습니다.${why}`,
888
+ permissionDecisionReason: `[Holmes-Kit Gate Refusal] Target specification ${(0, tspec_state_1.listOrCount)(unapproved, tspec_state_1.ID_BUDGET, 'spec(s)', Infinity)} is not approved`
889
+ + ` — Claiming unapproved specifications in existing or new files is prohibited.${why}`,
890
890
  };
891
891
  }
892
892
  }
@@ -1021,9 +1021,11 @@ function evaluateHook(input, specsDir, opts) {
1021
1021
  // the phase gate's own verdict is still what denied this.
1022
1022
  const lost = specs.length === 0 && !fs.existsSync(specsDir)
1023
1023
  && (0, governance_history_1.hasGovernanceHistory)(path.resolve(specsDir, '..', '..'));
1024
+ const targetSpecId = m?.[1] ?? 'A-SPEC-XXX';
1025
+ const prescriptiveGuide = ` — Next Action (DO NOT write workaround scripts in /tmp): Step 1: Call 'spec_next({})' to verify slice state. Step 2: Call 'spec_approve({ id: "${targetSpecId}" })' to seal spec. Step 3: Ensure '// @implements ${targetSpecId}' is on line 1 of target file.`;
1024
1026
  return {
1025
1027
  permissionDecision: 'deny',
1026
- permissionDecisionReason: `[Holmes-Kit] ${res.remediation?.message} ${res.remediation?.next_action} (${res.remediation?.discipline})`
1028
+ permissionDecisionReason: `[Holmes-Kit Gate Refusal] ${res.remediation?.message} -> ${res.remediation?.next_action} (${res.remediation?.discipline})${prescriptiveGuide}`
1027
1029
  + (lost ? `\n${governance_history_1.GOVERNANCE_LOST_HINT}` : ''),
1028
1030
  };
1029
1031
  }
@@ -51,6 +51,7 @@ const test_evidence_1 = require("../review/test-evidence");
51
51
  const pre_tool_use_1 = require("./pre-tool-use");
52
52
  const governance_history_1 = require("../guardrail/governance-history");
53
53
  const constitution_debt_1 = require("../governance/constitution-debt");
54
+ const root_1 = require("../project/root");
54
55
  /**
55
56
  * @implements A-SPEC-175
56
57
  * The turn-boundary answer to "this project is governed but its specs are gone".
@@ -339,7 +340,7 @@ if (require.main === module) {
339
340
  // stale record can never vouch for changed code. Absent/stale -> syntactic fallback.
340
341
  let executedByAspec;
341
342
  try {
342
- const head = (0, node_child_process_1.execFileSync)('git', ['rev-parse', 'HEAD'], { cwd: stopProjectRoot(), stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
343
+ const head = (0, node_child_process_1.execFileSync)('git', ['rev-parse', 'HEAD'], { cwd: stopProjectRoot(), stdio: ['ignore', 'pipe', 'ignore'], env: (0, root_1.cleanSubprocessEnv)() }).toString().trim();
343
344
  const ev = (0, test_evidence_1.readTestEvidence)(stopProjectRoot());
344
345
  if ((0, test_evidence_1.isFresh)(ev, head))
345
346
  executedByAspec = ev.executedByAspec;
@@ -53,6 +53,7 @@ const spec_types_1 = require("../spec/spec-types");
53
53
  const legacy_format_1 = require("../spec/legacy-format");
54
54
  const cpg_scanner_1 = require("../cpg/cpg-scanner");
55
55
  const scan_cache_1 = require("../cpg/scan-cache");
56
+ const root_1 = require("../project/root");
56
57
  // N7: every whole-tree scan goes through the file-key incremental cache under the PROJECT root's
57
58
  // .ax/cpg_cache — unchanged files (content-hash hit) skip the tree-sitter parse entirely.
58
59
  // The cache lives under the RESOLVED project root, not the raw scan root: scanning a SUBDIRECTORY
@@ -65,7 +66,7 @@ const scan_cache_1 = require("../cpg/scan-cache");
65
66
  // left open on the scan path. When the store is bound, the cache is the bound project's; otherwise
66
67
  // only an anchored answer may be written to, and an unanchored one falls back to a temp cache.
67
68
  const cacheDirFor = (root) => {
68
- const r = (0, root_1.resolveProjectRoot)(root);
69
+ const r = (0, root_2.resolveProjectRoot)(root);
69
70
  if (r.marker !== 'given')
70
71
  return path.join(r.root, '.ax', 'cpg_cache');
71
72
  if (fs.existsSync(path.join(r.root, '.ax')))
@@ -126,7 +127,7 @@ function isHandlerRefusal(e) {
126
127
  return !!e && typeof e === 'object' && e.holmesRefusal === true;
127
128
  }
128
129
  // @implements A-SPEC-128
129
- const root_1 = require("../project/root");
130
+ const root_2 = require("../project/root");
130
131
  const change_source_1 = require("../project/change-source");
131
132
  const baseline_1 = require("../project/baseline");
132
133
  const ignore_1 = require("../project/ignore");
@@ -146,7 +147,7 @@ const anchor_1 = require("../reverse/anchor");
146
147
  // yields top-level-relative paths while scan(root, root) tags root-relative ones, so a disagreement
147
148
  // silently produces wrong impact sets. `GitChangeSource` now enforces exactly that, and only there.
148
149
  function projectRootOf(root) {
149
- return (0, root_1.resolveProjectRoot)(root).root;
150
+ return (0, root_2.resolveProjectRoot)(root).root;
150
151
  }
151
152
  // @implements A-SPEC-191 §10 — WHERE a single-use approval is spent must not be a caller's choice.
152
153
  // r8 measured all three escapes at once: a second `root`, a `root` naming a subdirectory, and an
@@ -1190,7 +1191,7 @@ function makeRawHandlers(store) {
1190
1191
  const executedByAspec = (0, test_evidence_1.computeExecutedByAspec)(result.executedByFile ?? {}, anchors);
1191
1192
  let head = '';
1192
1193
  try {
1193
- head = (0, node_child_process_1.execFileSync)('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
1194
+ head = (0, node_child_process_1.execFileSync)('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8', env: (0, root_1.cleanSubprocessEnv)() }).trim();
1194
1195
  }
1195
1196
  catch { /* non-git */ }
1196
1197
  // Record ONLY an actually-executed, GREEN run (review C4/C7): a red suite must not stand as
@@ -1827,5 +1828,229 @@ function makeRawHandlers(store) {
1827
1828
  const applied = (0, anchor_1.applyAnchors)(a.root, plan.edits, { dryRun: a.dryRun !== false });
1828
1829
  return { ...plan, ...applied, blockers };
1829
1830
  },
1831
+ async spec_slice_init(a) {
1832
+ const root = a.root ? (0, root_2.resolveProjectRoot)(a.root).root : process.cwd();
1833
+ const specsDir = path.join(root, '.ax', 'specs');
1834
+ const existing = await store.list();
1835
+ let maxId = 200;
1836
+ for (const s of existing) {
1837
+ const num = parseInt(s.id.split('.')[0].replace(/\D/g, ''), 10);
1838
+ if (!isNaN(num) && num > maxId)
1839
+ maxId = num;
1840
+ }
1841
+ const nextId = maxId + 1;
1842
+ const reqId = `REQ-${nextId}`;
1843
+ const hspecId = `H-SPEC-${nextId}`;
1844
+ const aspecId = `A-SPEC-${nextId}`;
1845
+ const tspecId = `T-SPEC-${nextId}`;
1846
+ const reqContent = `---
1847
+ source:
1848
+ - kind: user-request
1849
+ ref: ${a.title}
1850
+ retrieved: ${new Date().toISOString().split('T')[0]}
1851
+ note: ${a.objective}
1852
+ created: ${new Date().toISOString()}
1853
+ id: ${reqId}
1854
+ type: REQ
1855
+ title: ${a.title}
1856
+ status: draft
1857
+ depends_on: []
1858
+ ---
1859
+
1860
+ ## Problem / Need
1861
+ ${a.objective}
1862
+
1863
+ ## Desired Outcome
1864
+ ${a.objective}
1865
+
1866
+ ## Constraints
1867
+ - Standard project governance rules.
1868
+
1869
+ ## Success Criteria
1870
+ - Implementation completed and verified by tests.
1871
+
1872
+ ## Out of Scope
1873
+ - Unrelated feature changes.
1874
+ `;
1875
+ const hspecContent = `---
1876
+ created: ${new Date().toISOString()}
1877
+ id: ${hspecId}
1878
+ type: H-SPEC
1879
+ title: Functional Specification for ${a.title}
1880
+ status: draft
1881
+ req_type: functional
1882
+ owner: me
1883
+ depends_on:
1884
+ - ${reqId}
1885
+ ---
1886
+
1887
+ ## Intent
1888
+ Implement ${a.title}.
1889
+
1890
+ ## Scope (In / Out)
1891
+ In Scope: ${a.title}.
1892
+ Out of Scope: None.
1893
+
1894
+ ## Design Overview
1895
+ High level design for ${a.title}.
1896
+
1897
+ ## Interfaces / Contracts
1898
+ - Target files: ${a.filesToTouch.join(', ')}
1899
+
1900
+ ## Acceptance Criteria
1901
+ - Code written and tests passing.
1902
+
1903
+ ## Non-Functional
1904
+ - Performance and stability maintained.
1905
+
1906
+ ## Assumptions
1907
+ - Environment configured properly.
1908
+
1909
+ ## Open Questions
1910
+ - None.
1911
+ `;
1912
+ const aspecContent = `---
1913
+ created: ${new Date().toISOString()}
1914
+ id: ${aspecId}
1915
+ type: A-SPEC
1916
+ title: Architecture Specification for ${a.title}
1917
+ status: draft
1918
+ slice: ${a.sliceName}
1919
+ priority: P1
1920
+ independent_test: true
1921
+ depends_on:
1922
+ - ${hspecId}
1923
+ breaking_change: 'none'
1924
+ ---
1925
+
1926
+ ## Objective
1927
+ ${a.objective}
1928
+
1929
+ ## Component Design
1930
+ 1. Target Component:
1931
+ - Modifies ${a.filesToTouch.join(', ')}.
1932
+
1933
+ ## Inputs / Outputs
1934
+ - Inputs: Tool calls / developer modifications.
1935
+ - Outputs: Working implementation.
1936
+
1937
+ ## Behavior
1938
+ - Implements desired behavior cleanly.
1939
+
1940
+ ## Test Points
1941
+ - Unit tests in test suite.
1942
+
1943
+ ## Files to Touch
1944
+ ${a.filesToTouch.map((f) => `- ${f}`).join('\n')}
1945
+
1946
+ ## Done When
1947
+ - All tests pass 100%.
1948
+ `;
1949
+ const tspecContent = `---
1950
+ coverage:
1951
+ normal: true
1952
+ corner: true
1953
+ negative: true
1954
+ boundary: true
1955
+ id: ${tspecId}
1956
+ type: T-SPEC
1957
+ title: Test Specification for ${a.title}
1958
+ status: draft
1959
+ depends_on:
1960
+ - ${aspecId}
1961
+ ---
1962
+
1963
+ ## Normal Cases
1964
+ - Given valid inputs
1965
+ When operation is performed
1966
+ Then correct behavior is observed
1967
+
1968
+ ## Corner Cases
1969
+ - Given edge cases
1970
+ When operation is performed
1971
+ Then system handles gracefully
1972
+
1973
+ ## Negative Cases
1974
+ - Given invalid inputs
1975
+ When operation is performed
1976
+ Then appropriate error is returned
1977
+
1978
+ ## Boundary Cases
1979
+ - Given boundary conditions
1980
+ When operation is performed
1981
+ Then boundary limits are respected
1982
+ `;
1983
+ fs.mkdirSync(path.join(specsDir, '01_req'), { recursive: true });
1984
+ fs.mkdirSync(path.join(specsDir, '02_h-spec', 'functional'), { recursive: true });
1985
+ fs.mkdirSync(path.join(specsDir, '03_a-spec'), { recursive: true });
1986
+ fs.mkdirSync(path.join(specsDir, '05_t-spec'), { recursive: true });
1987
+ fs.writeFileSync(path.join(specsDir, '01_req', `${reqId}.md`), reqContent);
1988
+ fs.writeFileSync(path.join(specsDir, '02_h-spec', 'functional', `${hspecId}.md`), hspecContent);
1989
+ fs.writeFileSync(path.join(specsDir, '03_a-spec', `${aspecId}.md`), aspecContent);
1990
+ fs.writeFileSync(path.join(specsDir, '05_t-spec', `${tspecId}.md`), tspecContent);
1991
+ return { ok: true, specsCreated: [reqId, hspecId, aspecId, tspecId] };
1992
+ },
1993
+ async spec_slice_approve(a) {
1994
+ const root = a.root ? (0, root_2.resolveProjectRoot)(a.root).root : process.cwd();
1995
+ const rawStore = new spec_store_1.LocalMarkdownRepository(path.join(root, '.ax', 'specs'));
1996
+ const specs = await rawStore.list();
1997
+ const inSlice = specs.filter((s) => {
1998
+ if (s.frontmatter?.slice === a.sliceName)
1999
+ return true;
2000
+ if (s.id === a.sliceName)
2001
+ return true;
2002
+ return false;
2003
+ });
2004
+ const idsToApprove = [];
2005
+ if (inSlice.length > 0) {
2006
+ const aspec = inSlice.find((s) => s.type === 'A-SPEC') ?? inSlice[0];
2007
+ const mainId = (id) => id.split('.')[0].replace(/\D/g, '');
2008
+ const req = specs.find((s) => aspec.dependsOn.includes(s.id) || mainId(s.id) === mainId(aspec.id));
2009
+ const hspec = specs.find((s) => s.type === 'H-SPEC' && (s.dependsOn.includes(req?.id ?? '') || mainId(s.id) === mainId(aspec.id)));
2010
+ const tspec = specs.find((s) => s.type === 'T-SPEC' && (s.dependsOn.includes(aspec.id) || mainId(s.id) === mainId(aspec.id)));
2011
+ if (req)
2012
+ idsToApprove.push(req.id);
2013
+ if (hspec)
2014
+ idsToApprove.push(hspec.id);
2015
+ idsToApprove.push(aspec.id);
2016
+ if (tspec)
2017
+ idsToApprove.push(tspec.id);
2018
+ }
2019
+ else {
2020
+ const matches = specs.filter((s) => s.id.includes(a.sliceName));
2021
+ idsToApprove.push(...matches.map((s) => s.id));
2022
+ }
2023
+ const rawHandlers = makeRawHandlers(rawStore);
2024
+ const approvedSpecs = [];
2025
+ for (const id of idsToApprove) {
2026
+ const res = (await rawHandlers.spec_approve({ root, id }));
2027
+ if (res.approved || res.ok) {
2028
+ approvedSpecs.push(id);
2029
+ }
2030
+ }
2031
+ return { ok: true, approvedSpecs };
2032
+ },
2033
+ async spec_remediate(a) {
2034
+ const root = a.root ? (0, root_2.resolveProjectRoot)(a.root).root : process.cwd();
2035
+ const actionsTaken = [];
2036
+ if (a.targetFile && a.aspecId) {
2037
+ const fullPath = path.isAbsolute(a.targetFile) ? a.targetFile : path.join(root, a.targetFile);
2038
+ if (fs.existsSync(fullPath)) {
2039
+ const content = fs.readFileSync(fullPath, 'utf8');
2040
+ const anchorTag = `// @implements ${a.aspecId}`;
2041
+ if (!content.includes(anchorTag)) {
2042
+ fs.writeFileSync(fullPath, `${anchorTag}\n${content}`);
2043
+ actionsTaken.push(`Injected ${anchorTag} on line 1 of ${a.targetFile}`);
2044
+ }
2045
+ }
2046
+ }
2047
+ const rawStore = new spec_store_1.LocalMarkdownRepository(path.join(root, '.ax', 'specs'));
2048
+ const rawHandlers = makeRawHandlers(rawStore);
2049
+ const approveRes = await rawHandlers.spec_slice_approve({ root, sliceName: a.aspecId ?? 'slice' });
2050
+ if (approveRes.approvedSpecs?.length > 0) {
2051
+ actionsTaken.push(`Approved slice specs: ${approveRes.approvedSpecs.join(', ')}`);
2052
+ }
2053
+ return { ok: true, actionsTaken };
2054
+ },
1830
2055
  };
1831
2056
  }
@@ -391,4 +391,41 @@ exports.TOOL_SCHEMAS = {
391
391
  required: ['action', 'ts'],
392
392
  },
393
393
  },
394
+ spec_slice_init: {
395
+ description: 'Initialize a full 4-level spec chain (REQ -> H-SPEC -> A-SPEC -> T-SPEC) in one call with valid default sections.',
396
+ inputSchema: {
397
+ type: 'object',
398
+ properties: {
399
+ root: ROOT_ANY,
400
+ sliceName: str('Slice name (kebab-case identifier, e.g. user-auth-flow).'),
401
+ title: str('Title for the requirement and specs.'),
402
+ objective: str('High-level objective or problem statement.'),
403
+ filesToTouch: strArray('List of source/test file paths to be touched by this slice.'),
404
+ },
405
+ required: ['sliceName', 'title', 'objective', 'filesToTouch'],
406
+ },
407
+ },
408
+ spec_slice_approve: {
409
+ description: 'Validate, seal, and sign all specs in a slice in topological dependency order (REQ -> H-SPEC -> A-SPEC -> T-SPEC) in one call.',
410
+ inputSchema: {
411
+ type: 'object',
412
+ properties: {
413
+ root: ROOT_ANY,
414
+ sliceName: str('Slice name or A-SPEC ID to validate and approve.'),
415
+ },
416
+ required: ['sliceName'],
417
+ },
418
+ },
419
+ spec_remediate: {
420
+ description: 'Automated self-healing remediation: auto-inject missing // @implements code anchor and approve pending slice specs.',
421
+ inputSchema: {
422
+ type: 'object',
423
+ properties: {
424
+ root: ROOT_ANY,
425
+ targetFile: str('Target source/test file path to auto-anchor.'),
426
+ aspecId: str('Target A-SPEC ID to anchor and approve.'),
427
+ },
428
+ required: ['targetFile', 'aspecId'],
429
+ },
430
+ },
394
431
  };
@@ -47,4 +47,6 @@ exports.MESSAGES = {
47
47
  VALIDATION_ENUM_INVALID: (paramName, value, allowedValues) => `validate-args: value '${value}' for '${paramName}' is invalid — allowed values: [${allowedValues.map((v) => `'${v}'`).join(', ')}]`,
48
48
  VALIDATION_FOREIGN_ROOT: (root, boundRoot) => `foreign-root: specified root '${root}' is outside the bound project root '${boundRoot}'`,
49
49
  LEGACY_REVERSE_DRAFT_PARENT: () => `parentReqId is required. A REQ states business intent... write the REQ first, then re-run with its id`,
50
+ UNAPPROVED_ASPEC: (aspecId = 'A-SPEC-XXX', internalTrace) => `[Holmes-Kit Governance] Target Specification Not Approved — '${aspecId}' is not approved — Next Action (DO NOT write workaround scripts in /tmp): Step 1: Call 'spec_next({})' to verify slice state. Step 2: Call 'spec_approve({ id: "${aspecId}" })' to seal spec. Step 3: Ensure '// @' + 'implements ${aspecId}' is on line 1 of target file.${formatDebugTrace(internalTrace)}`,
51
+ MISSING_ANCHOR: (targetFile, internalTrace) => `[Holmes-Kit Governance] Missing Specification Anchor — Code target '${targetFile ?? 'file'}' lacks an approved '// @' + 'implements A-SPEC-XXX' anchor on line 1 — Next Action (DO NOT write workaround scripts in /tmp): Step 1: Check active slice with 'spec_next({})'. Step 2: Add '// @' + 'implements A-SPEC-XXX' to top of target file.${formatDebugTrace(internalTrace)}`,
50
52
  };
@@ -36,15 +36,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.MARKER = void 0;
37
37
  exports.cleanSubprocessEnv = cleanSubprocessEnv;
38
38
  exports.resolveProjectRoot = resolveProjectRoot;
39
+ // @implements A-SPEC-205
39
40
  // @implements A-SPEC-128
40
41
  const fs = __importStar(require("node:fs"));
41
42
  const path = __importStar(require("node:path"));
42
43
  const node_child_process_1 = require("node:child_process");
43
44
  // @implements A-SPEC-194
44
45
  // @implements A-SPEC-196
46
+ // @implements A-SPEC-205
45
47
  function cleanSubprocessEnv(env = process.env) {
46
48
  const cleaned = { ...env };
47
- const SCRUB_KEYS = new Set(['HOLMES_APPROVAL', 'HOLMES_LEDGER_KEY']);
49
+ const SCRUB_KEYS = new Set(['HOLMES_APPROVAL', 'HOLMES_LEDGER_KEY', 'HOLMES_ROLE', 'HOLMES_ENV_CHANNEL']);
48
50
  for (const k of Object.keys(cleaned)) {
49
51
  if (SCRUB_KEYS.has(k.toUpperCase())) {
50
52
  delete cleaned[k];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holmes-lab/holmes-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
5
5
  "main": "dist/holmes/mcp/server.js",
6
6
  "bin": {
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: holmes-author-slice
3
3
  description: >-
4
- Use when a Holmes-Kit gate denies with "선행 REQ 없습니다", "approved H-SPEC 없습니다" while
5
- no H-SPEC exists yet, or "approved A-SPEC 없습니다" while no A-SPEC exists yet — the required
4
+ Use when a Holmes-Kit gate denies with "Preceding REQ not found.", "Approved H-SPEC not found." while
5
+ no H-SPEC exists yet, or "Approved A-SPEC not found." while no A-SPEC exists yet — the required
6
6
  document has not been written at all. Also use when starting a new slice from scratch and the
7
- REQH-SPECA-SPECT-SPEC chain must be authored in order.
7
+ REQ->H-SPEC->A-SPEC->T-SPEC chain must be authored in order.
8
8
  ---
9
9
 
10
10
  # author-slice
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: holmes-promote-slice
3
3
  description: >-
4
- Use when a Holmes-Kit gate denies with "approved H-SPEC 없습니다", "approved A-SPEC 없습니다",
5
- "구현 대상 A-SPEC(...) approved가 아닙니다", or "…를 depends_on에 담은 T-SPEC이 없습니다(테스트 먼저)" —
4
+ Use when a Holmes-Kit gate denies with "Approved H-SPEC not found.", "Approved A-SPEC not found.",
5
+ "Target specification A-SPEC(...) is not approved.", or "…를 depends_on에 담은 T-SPEC이 없습니다(테스트 먼저)" —
6
6
  the spec already exists but its status still blocks the next action, or an approved T-SPEC leaves
7
7
  the code gate shut.
8
8
  ---
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: holmes-remediation
3
+ description: >-
4
+ Automatically triggered when Holmes-Kit pre-tool-use hook gate denies a tool call with "구현 대상 A-SPEC(...)이 approved가 아닙니다" or missing code anchor. Guides the agent to execute 1-call spec_remediate or 3-step recovery workflow.
5
+ ---
6
+
7
+ # Holmes-Kit Remediation Playbook (Self-Healing Recovery)
8
+
9
+ Use this playbook whenever a tool call (Write, Edit, Bash) is denied by Holmes-Kit with "구현 대상 A-SPEC(...)이 approved가 아닙니다".
10
+
11
+ ## Mandatory Rules
12
+
13
+ 1. **DO NOT write workaround scripts** in `/tmp/*.js` or attempt to mutate `.ax/` directly.
14
+ 2. **DO NOT reverse-engineer guardrail source code**.
15
+ 3. **Execute 1-Call Auto-Healing (\`spec_remediate\`)**:
16
+
17
+ \`\`\`json
18
+ spec_remediate({
19
+ "targetFile": "<path/to/blocked/file>",
20
+ "aspecId": "A-SPEC-XXX"
21
+ })
22
+ \`\`\`
23
+
24
+ ## Step-by-Step Remediation Workflow
25
+
26
+ If executing manual remediation, follow these 3 steps:
27
+
28
+ 1. **Check Active Slice State**:
29
+ Call \`spec_next({})\` to view current slice status and next required actions.
30
+
31
+ 2. **Seal Pending Specs**:
32
+ Call \`spec_slice_approve({ "sliceName": "A-SPEC-XXX" })\` or \`spec_approve({ "id": "A-SPEC-XXX" })\`.
33
+
34
+ 3. **Inject Code Anchor**:
35
+ Ensure \`// @implements A-SPEC-XXX\` is on line 1 of the target file before writing code.