kairos-chain 3.27.0 → 3.28.1
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.
- checksums.yaml +4 -4
- data/lib/kairos_mcp/resource_registry.rb +17 -3
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +39 -5
- data/templates/knowledge/multi_llm_reviewer_evaluation/multi_llm_reviewer_evaluation.md +17 -17
- data/templates/skillsets/agent/plugin/SKILL.md +29 -0
- data/templates/skillsets/agent/skillset.json +1 -1
- data/templates/skillsets/kairos_hook_projector/lib/boot_time_assertion.rb +78 -0
- data/templates/skillsets/kairos_hook_projector/mode_hooks/_schema.json +41 -0
- data/templates/skillsets/kairos_hook_projector/plugin/SKILL.md +20 -2
- data/templates/skillsets/kairos_hook_projector/skillset.json +3 -1
- data/templates/skillsets/kairos_hook_projector/test/test_boot_time_assertion.rb +113 -0
- data/templates/skillsets/kairos_hook_projector/test/test_hooks_status.rb +81 -0
- data/templates/skillsets/kairos_hook_projector/test/test_mode_hooks_schema.rb +68 -0
- data/templates/skillsets/kairos_hook_projector/tools/hooks_status.rb +138 -0
- data/templates/skillsets/multi_llm_review/config/multi_llm_review.yml +3 -3
- data/templates/skillsets/multi_llm_review/tools/multi_llm_review.rb +10 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 603eca51c926a1987575ae63a1266a34840fc59e7a3603685e03c37676ba23ba
|
|
4
|
+
data.tar.gz: c4da91a87396d878a62465219d80fa4e26219766f4ca8389ea132a0c7828d873
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea9230f6dbe43986f6a2f81c0c77a549567110dda38205d769f0802c4ccb8418db4c5dee8e3ac037ae91082eab5cb5b5d890feb823119f0182980abf92e217c9
|
|
7
|
+
data.tar.gz: 906f320cc565a68ac28d644978c35a8234d1aecc0cb50f828260d407d425f90f3f514c968c0f391e46b69f2db2842ecd33ae31ed85760d4d2b4410723f7ca509
|
|
@@ -310,9 +310,11 @@ module KairosMcp
|
|
|
310
310
|
end
|
|
311
311
|
|
|
312
312
|
def read_l2_resource(parsed)
|
|
313
|
-
session_id = parsed[:session]
|
|
314
313
|
name = parsed[:name]
|
|
315
|
-
return nil unless
|
|
314
|
+
return nil unless name
|
|
315
|
+
|
|
316
|
+
session_id = parsed[:session] || find_session_for_context(name)
|
|
317
|
+
return nil unless session_id
|
|
316
318
|
|
|
317
319
|
context_dir = File.join(@context_dir, session_id, name)
|
|
318
320
|
return nil unless File.directory?(context_dir)
|
|
@@ -336,7 +338,11 @@ module KairosMcp
|
|
|
336
338
|
|
|
337
339
|
def parse_context_uri(path)
|
|
338
340
|
parts = path.split('/')
|
|
339
|
-
return nil if parts.
|
|
341
|
+
return nil if parts.empty?
|
|
342
|
+
|
|
343
|
+
if parts.length < 2
|
|
344
|
+
return { scheme: 'context', session: nil, name: parts[0] }
|
|
345
|
+
end
|
|
340
346
|
|
|
341
347
|
session_id = parts[0]
|
|
342
348
|
name = parts[1]
|
|
@@ -488,6 +494,14 @@ module KairosMcp
|
|
|
488
494
|
}
|
|
489
495
|
end
|
|
490
496
|
|
|
497
|
+
def find_session_for_context(name)
|
|
498
|
+
session_dirs.sort.reverse.each do |session_dir|
|
|
499
|
+
candidate = File.join(session_dir, name)
|
|
500
|
+
return File.basename(session_dir) if File.directory?(candidate)
|
|
501
|
+
end
|
|
502
|
+
nil
|
|
503
|
+
end
|
|
504
|
+
|
|
491
505
|
def knowledge_dirs
|
|
492
506
|
Dir[File.join(@knowledge_dir, '*')].select do |f|
|
|
493
507
|
File.directory?(f) && !File.basename(f).match?(KnowledgeProvider::BACKUP_DIR_PATTERN)
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: multi_llm_review_workflow
|
|
3
3
|
description: "Multi-LLM review methodology and execution — workflow pattern, CLI tooling, consensus analysis, Persona Assembly. Applicable to design, implementation, documentation, or any artifact."
|
|
4
|
-
version: "3.
|
|
4
|
+
version: "3.4"
|
|
5
5
|
tags:
|
|
6
6
|
- workflow
|
|
7
7
|
- review
|
|
@@ -168,12 +168,46 @@ Pick the right one for your environment:
|
|
|
168
168
|
|
|
169
169
|
| Question | Answer |
|
|
170
170
|
|----------|--------|
|
|
171
|
-
|
|
|
171
|
+
| **Default**: Is the `multi_llm_review` MCP tool available? | **Path B** (roster from config, orchestrator exclusion automatic) |
|
|
172
|
+
| MCP tool unavailable or user explicitly requests manual execution? | **Path A** (fallback — roster construction is the orchestrator's responsibility) |
|
|
172
173
|
| Do you need this to work in Cursor / autonomous mode / other MCP host? | **Path B** |
|
|
173
174
|
| Do you want the consensus result inside the MCP tool response? | **Path B** |
|
|
174
175
|
| Did you observe `XX shells` in the statusbar last time it worked? | That was Path A |
|
|
175
176
|
| Did the run produce a `collect_token` and a `pending/<token>/` directory? | That was Path B |
|
|
176
177
|
|
|
178
|
+
**Why Path B is the default**: Path A delegates roster construction to the
|
|
179
|
+
orchestrating LLM, which must correctly extract reviewer count, model assignments,
|
|
180
|
+
orchestrator exclusion rules, and convergence thresholds from this skill and
|
|
181
|
+
`multi_llm_reviewer_evaluation`. Empirically, LLMs misread these parameters —
|
|
182
|
+
e.g., confusing "exclude orchestrator from subprocess" with "exclude orchestrator
|
|
183
|
+
model from all reviewers" (the Agent Team Personas are *designed* to use the
|
|
184
|
+
orchestrator's own model for persona diversity). Path B enforces the correct
|
|
185
|
+
configuration from `config/multi_llm_review.yml`, eliminating this error class.
|
|
186
|
+
|
|
187
|
+
### Pre-flight checklist (Path A only)
|
|
188
|
+
|
|
189
|
+
If Path B is unavailable and you must use Path A, extract these values **before
|
|
190
|
+
starting** and verify each against `config/multi_llm_review.yml`:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
- [ ] Your model (orchestrator): ___
|
|
194
|
+
- [ ] Agent Team Personas model: = orchestrator model (NOT a different model)
|
|
195
|
+
- [ ] Subprocess CLI model: opposite Opus (4.6 if you are 4.7, vice versa)
|
|
196
|
+
- [ ] Codex models: gpt-5.4 AND gpt-5.5 (both, not either/or)
|
|
197
|
+
- [ ] Cursor model: default (composer-2.5, no --model flag)
|
|
198
|
+
- [ ] Total reviewer count: 5 (or 4 after orchestrator exclusion from subprocess)
|
|
199
|
+
- [ ] Convergence rule: 3/5 APPROVE (full) or 3/4 APPROVE (after exclusion)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Common mistakes (Path A)
|
|
203
|
+
|
|
204
|
+
| Mistake | Correct behavior | Why it happens |
|
|
205
|
+
|---------|-----------------|----------------|
|
|
206
|
+
| Exclude orchestrator model from Agent Team Personas | Agent Team uses orchestrator model — they provide persona diversity, not epistemic diversity | LLM misreads "do not assign yourself as a reviewer" as applying to Agent Team; it applies only to subprocess CLI |
|
|
207
|
+
| Run only Codex GPT-5.4, skip 5.5 | Run both — they catch different things (5.5 found §5 schema contradiction in Phase 2 Case A that no other reviewer caught) | Cost-saving heuristic; roster has both for a reason |
|
|
208
|
+
| Use a smaller/cheaper model as Agent Team substitute | Use the orchestrator's own model with different personas | Confusing "model diversity" with "persona diversity" — Agent Team is the latter |
|
|
209
|
+
| Run 3 reviewers instead of 5 (or 4 after exclusion) | Use the full roster from config | Ad-hoc "3 is enough" reasoning; config specifies 5 for empirical reasons |
|
|
210
|
+
|
|
177
211
|
## Roles
|
|
178
212
|
|
|
179
213
|
| Role | Who | Responsibility |
|
|
@@ -377,7 +411,7 @@ which claude 2>/dev/null && echo "claude: available" || echo "claude: NOT FOUND"
|
|
|
377
411
|
| Tool | Command | Prompt Input | Output Collection | Model |
|
|
378
412
|
|------|---------|-------------|-------------------|-------|
|
|
379
413
|
| **Codex** | `codex exec` | stdin pipe: `cat prompt.md \| codex exec -` | `-o /path/output.md` | GPT-5.4 (default) |
|
|
380
|
-
| **Cursor Agent** | `agent -p` | File reference (stdin NOT supported) | stdout redirect: `> output.md` | Composer-2 (default) |
|
|
414
|
+
| **Cursor Agent** | `agent -p` | File reference (stdin NOT supported) | stdout redirect: `> output.md` | Composer-2.5 (default) |
|
|
381
415
|
| **Claude Code** | Agent tool (internal) | Direct prompt string | Write to workspace file | Opus 4.6 (session) |
|
|
382
416
|
| **Claude CLI (4.7)** | `claude -p --model claude-opus-4-7 --bare` | stdin pipe: `cat prompt.md \| claude -p --model claude-opus-4-7 --bare` | stdout redirect: `> output.md` | Opus 4.7 |
|
|
383
417
|
|
|
@@ -393,7 +427,7 @@ Based on cross-evaluation experiment (7 models × 4 tasks + Nomic, 518 CLI calls
|
|
|
393
427
|
| **Coding sub-agent** | Opus 4.7 | `--effort medium` | Cost-effective default; use `high` for complex tasks |
|
|
394
428
|
| **Design sub-agent** | Opus 4.7 | `--effort medium` | Cost-effective default; use `high` for complex tasks |
|
|
395
429
|
| **Codex** | GPT-5.4 | (no flag) | Fixed effort |
|
|
396
|
-
| **Cursor Agent** | Composer-2 | (no flag) | Fixed effort |
|
|
430
|
+
| **Cursor Agent** | Composer-2.5 | (no flag) | Fixed effort |
|
|
397
431
|
|
|
398
432
|
Key findings:
|
|
399
433
|
- **Opus 4.6** high effort improves Evaluator/Strategy (+0.43/+0.200 Nomic), not Response
|
|
@@ -675,7 +709,7 @@ Step 1: Generate review prompt
|
|
|
675
709
|
Step 2: Detect environment and models
|
|
676
710
|
- Run: which codex && which agent && which claude
|
|
677
711
|
- Detect default models
|
|
678
|
-
- Report: "Auto mode: Codex (gpt-5.4), Agent (composer-2), Claude (opus-4.6), Claude CLI (opus-4.7)"
|
|
712
|
+
- Report: "Auto mode: Codex (gpt-5.4), Agent (composer-2.5), Claude (opus-4.6), Claude CLI (opus-4.7)"
|
|
679
713
|
|
|
680
714
|
Step 3: Execute N reviews in parallel (default 4 reviewers)
|
|
681
715
|
- Bash(background): cat prompt.md | codex exec -C workspace -o log/review_codex.md -
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: multi_llm_reviewer_evaluation
|
|
3
3
|
description: "Multi-LLM reviewer performance evaluation — strengths, weaknesses, value-system biases, and recommended workflows. Based on 185+ reviews (Phase 1, 2026-02 to 03) + Phase 2 Case A 4-round Codex bias study (2026-05-04)."
|
|
4
|
-
version: "1.
|
|
4
|
+
version: "1.4"
|
|
5
5
|
tags:
|
|
6
6
|
- multi-llm
|
|
7
7
|
- review
|
|
@@ -22,7 +22,7 @@ Based on 185+ review files across KairosChain development (2026-02-24 to 2026-03
|
|
|
22
22
|
| Claude Team Opus 4.6 | 53 | 3/18-3/28 | 0% | Persona assembly review |
|
|
23
23
|
| Codex GPT-5.4 | 49 | 3/19-3/28 | 27% | Auto review |
|
|
24
24
|
| Codex GPT-5.5 | 4 (Phase 2 Case A) | 2026-05-04 | 100% | Auto review |
|
|
25
|
-
| Cursor Composer-2 | 27 | 3/20-3/28 | 0% | Auto review |
|
|
25
|
+
| Cursor Composer-2.5 | 27 | 3/20-3/28 | 0% | Auto review |
|
|
26
26
|
| Cursor GPT-5.4 | 16 | 3/21-3/25 | 12% | Manual review (Codex fallback) |
|
|
27
27
|
| Cursor Premium | 26 | 3/19-3/21 | 12% | Manual review |
|
|
28
28
|
| Claude CLI Opus 4.7 | 2 | 4/19- | 0% | Auto review (CLI) |
|
|
@@ -35,12 +35,12 @@ Based on 185+ review files across KairosChain development (2026-02-24 to 2026-03
|
|
|
35
35
|
| Security design | Claude Opus 4.6 | Cursor Premium | Codex GPT-5.4 |
|
|
36
36
|
| Implementation bugs | Codex GPT-5.4 | Cursor Premium | Claude Opus 4.6 |
|
|
37
37
|
| State transition/config | Codex GPT-5.4 | Cursor GPT-5.4 | — |
|
|
38
|
-
| Overall design coherence | Composer-2 | Claude Team | Gemini 3.1 |
|
|
38
|
+
| Overall design coherence | Composer-2.5 | Claude Team | Gemini 3.1 |
|
|
39
39
|
| Philosophical alignment | Gemini 3.1 | Claude Team | — |
|
|
40
|
-
| Future extensibility | Gemini 3.1 | Composer-2 | — |
|
|
41
|
-
| Deployment/ops | Composer-2 | Cursor GPT-5.4 | — |
|
|
40
|
+
| Future extensibility | Gemini 3.1 | Composer-2.5 | — |
|
|
41
|
+
| Deployment/ops | Composer-2.5 | Cursor GPT-5.4 | — |
|
|
42
42
|
| Test adequacy | Codex GPT-5.4 | Cursor GPT-5.4 | Cursor Premium |
|
|
43
|
-
| Design-implementation seam | Codex GPT-5.4 | Claude Opus 4.6 | Composer-2 |
|
|
43
|
+
| Design-implementation seam | Codex GPT-5.4 | Claude Opus 4.6 | Composer-2.5 |
|
|
44
44
|
| Fail-open/fail-closed detection | Codex GPT-5.4 | Claude Opus 4.6 | — |
|
|
45
45
|
|
|
46
46
|
> Claude CLI Opus 4.7: not yet ranked. Pending evaluation data (added 2026-04-19).
|
|
@@ -81,7 +81,7 @@ Based on 185+ review files across KairosChain development (2026-02-24 to 2026-03
|
|
|
81
81
|
- **Verdict bias**: REJECT-default; convergence behavior similar to GPT-5.4 but slower
|
|
82
82
|
- **Provisional**: Profile based on Phase 2 Case A 4-round data only. Will refine after additional sessions
|
|
83
83
|
|
|
84
|
-
### Cursor Composer-2
|
|
84
|
+
### Cursor Composer-2.5
|
|
85
85
|
|
|
86
86
|
- **Strength**: High-level design coherence, protocol correctness, practical deployability, balanced architecture + pragmatics
|
|
87
87
|
- **Weakness**: Less detail on cryptographic edge cases and thread safety
|
|
@@ -190,10 +190,10 @@ Codex effectively is **not** silencing it but classifying its output.
|
|
|
190
190
|
Across the Attestation Nudge session (4 rounds, 12 reviews), Codex demonstrated a distinctive convergence pattern:
|
|
191
191
|
|
|
192
192
|
```
|
|
193
|
-
Design R1: Codex REJECT | Composer-2 APPROVE+ | Claude APPROVE+
|
|
194
|
-
Design R2: Codex REJECT | Composer-2 APPROVE+ | Claude APPROVE+
|
|
195
|
-
Impl Review: Codex REJECT | Composer-2 APPROVE+ | Claude APPROVE+
|
|
196
|
-
Final Review: Codex APPROVE | Composer-2 APPROVE+ | Claude APPROVE+
|
|
193
|
+
Design R1: Codex REJECT | Composer-2.5 APPROVE+ | Claude APPROVE+
|
|
194
|
+
Design R2: Codex REJECT | Composer-2.5 APPROVE+ | Claude APPROVE+
|
|
195
|
+
Impl Review: Codex REJECT | Composer-2.5 APPROVE+ | Claude APPROVE+
|
|
196
|
+
Final Review: Codex APPROVE | Composer-2.5 APPROVE+ | Claude APPROVE+
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
**Key observations**:
|
|
@@ -250,7 +250,7 @@ toward the rule below.
|
|
|
250
250
|
| Cursor Premium | 55min | 4/5 | 5/5 | 2/5 | Excellent |
|
|
251
251
|
| Codex GPT-5.4 | 60min | 3/5 | 5/5 | 1/5 | Excellent |
|
|
252
252
|
| Gemini 3.1 | 50min | 3/5 | 2/5 | 5/5 | Good |
|
|
253
|
-
| Composer-2 | 40min | 2/5 | 3/5 | 2/5 | Good |
|
|
253
|
+
| Composer-2.5 | 40min | 2/5 | 3/5 | 2/5 | Good |
|
|
254
254
|
| Claude Team | 90min | 3/5 | 3/5 | 4/5 | Fair |
|
|
255
255
|
| Cursor GPT-5.4 | 35min | 2/5 | 3/5 | 1/5 | Fair |
|
|
256
256
|
|
|
@@ -259,11 +259,11 @@ toward the rule below.
|
|
|
259
259
|
> Note: Workflows updated for 4-reviewer default (Opus 4.7 added 2026-04-19). Opus 4.7 profile is provisional pending evaluation data.
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
Design phase: Claude Opus 4.6 + Claude CLI Opus 4.7 + Codex GPT-5.4 + Composer-2
|
|
263
|
-
Implementation: Codex GPT-5.4 + Composer-2 + Claude Opus 4.6 + Claude CLI Opus 4.7
|
|
264
|
-
Final merge gate: Codex GPT-5.4 + Composer-2 + Claude Opus 4.6 Assembly + Claude CLI Opus 4.7
|
|
262
|
+
Design phase: Claude Opus 4.6 + Claude CLI Opus 4.7 + Codex GPT-5.4 + Composer-2.5
|
|
263
|
+
Implementation: Codex GPT-5.4 + Composer-2.5 + Claude Opus 4.6 + Claude CLI Opus 4.7
|
|
264
|
+
Final merge gate: Codex GPT-5.4 + Composer-2.5 + Claude Opus 4.6 Assembly + Claude CLI Opus 4.7
|
|
265
265
|
Philosophy/Grant: Gemini 3.1 + Claude Team
|
|
266
|
-
Deployment: Composer-2 or Cursor GPT-5.4
|
|
266
|
+
Deployment: Composer-2.5 or Cursor GPT-5.4
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
## One-Line Summaries
|
|
@@ -274,7 +274,7 @@ Deployment: Composer-2 or Cursor GPT-5.4
|
|
|
274
274
|
| Codex GPT-5.4 | Strictest judge. Classify findings (a)/(b)/(c) before treating REJECT as blocking; APPROVE is a strong signal **when reachable**, not a mandatory gate (see Phase 2 Case A caveat) |
|
|
275
275
|
| Codex GPT-5.5 | Stricter sibling of 5.4. Same value-system divergence (3 biases); apply the same classification discipline |
|
|
276
276
|
| Cursor Premium | Implementation craftsman. Bug hunter for concurrency and resource management |
|
|
277
|
-
| Composer-2 | Fastest pragmatist. First to determine if something is deployable |
|
|
277
|
+
| Composer-2.5 | Fastest pragmatist. First to determine if something is deployable |
|
|
278
278
|
| Cursor GPT-5.4 | Binary sword. Clear approve-or-reject, strictest on test coverage |
|
|
279
279
|
| Claude Team | Consensus philosopher. Best at integrating multiple viewpoints |
|
|
280
280
|
| Claude CLI Opus 4.7 | Operability guardian. Finds auth, stderr, and execution-layer issues internal reviewers miss |
|
|
@@ -22,6 +22,35 @@ Manage autonomous agent sessions with observe-orient-decide-act-reflect cycles.
|
|
|
22
22
|
3. `agent_status` — check progress at any time
|
|
23
23
|
4. `agent_stop` — terminate when done or if paused
|
|
24
24
|
|
|
25
|
+
## Capabilities
|
|
26
|
+
|
|
27
|
+
### External LLM Invocation (via `llm_call` adapter chain)
|
|
28
|
+
|
|
29
|
+
The agent's OODA phases invoke `llm_call` (from the `llm_client` dependency)
|
|
30
|
+
which spawns external LLMs as subprocesses via adapter classes:
|
|
31
|
+
|
|
32
|
+
| Adapter | Subprocess command | Use case |
|
|
33
|
+
|---------|-------------------|----------|
|
|
34
|
+
| `ClaudeCodeAdapter` | `claude -p --output-format json` | Sub-author (4.6), persona reviewers |
|
|
35
|
+
| `CodexAdapter` | `codex exec --sandbox read-only` | Codex review |
|
|
36
|
+
| `CursorAdapter` | `agent -p` | Cursor review |
|
|
37
|
+
| `AnthropicAdapter` | Direct API (no subprocess) | Anthropic API calls |
|
|
38
|
+
| `OpenaiAdapter` | Direct API | OpenAI API calls |
|
|
39
|
+
|
|
40
|
+
The agent CAN orchestrate multi-LLM review, invoke sub-author processes,
|
|
41
|
+
and leverage cross-provider reviewers — all from within the governed OODA
|
|
42
|
+
loop with blockchain recording of each step.
|
|
43
|
+
|
|
44
|
+
### File Operations (via `external_tools` SkillSet)
|
|
45
|
+
|
|
46
|
+
`SafeFileWrite` and `SafeFileEdit` are available via `invoke_tool`, enabling
|
|
47
|
+
the agent to write design drafts to `docs/drafts/` or other project paths.
|
|
48
|
+
|
|
49
|
+
### MCP Tool Access
|
|
50
|
+
|
|
51
|
+
All KairosChain MCP tools (`context_save`, `multi_llm_review`, `chain_record`,
|
|
52
|
+
`knowledge_get`, etc.) are available via `invoke_tool` in the Act phase.
|
|
53
|
+
|
|
25
54
|
## Sub-Agents
|
|
26
55
|
|
|
27
56
|
### `/kairos-chain:agent-monitor`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"description": "Governed autonomous OODA loop combining llm_client, autonomos, and autoexec into a cognitive agent. Observe-Orient-Decide-Act-Reflect with human checkpoints.",
|
|
4
|
+
"description": "Governed autonomous OODA loop combining llm_client, autonomos, and autoexec into a cognitive agent. Observe-Orient-Decide-Act-Reflect with human checkpoints. External LLM invocation (claude -p, codex, cursor) available via llm_call adapter chain. File operations via SafeFileWrite/SafeFileEdit.",
|
|
5
5
|
"author": "Masaomi Hatakeyama",
|
|
6
6
|
"layer": "L1",
|
|
7
7
|
"depends_on": ["llm_client", "autonomos", "autoexec"],
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
5
|
+
module KairosMcp
|
|
6
|
+
module SkillSets
|
|
7
|
+
module KairosHookProjector
|
|
8
|
+
# Structural guarantee of zero side effect on a configurable set of
|
|
9
|
+
# watched paths, per design v0.2 §7.2 DoD-0-4 and Inv-6.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# assertion = BootTimeAssertion.new(watch_paths: [...])
|
|
13
|
+
# assertion.snapshot_pre!
|
|
14
|
+
# <do read-only work>
|
|
15
|
+
# assertion.verify_post! # raises StructuralAssertionFailure on any drift
|
|
16
|
+
#
|
|
17
|
+
# The assertion captures (sha256, mtime_ns, size) for each watched path,
|
|
18
|
+
# or :absent if the path does not exist. Any pre/post mismatch is treated
|
|
19
|
+
# as a structural violation: absent->present, present->absent,
|
|
20
|
+
# content change, or mtime advance.
|
|
21
|
+
class BootTimeAssertion
|
|
22
|
+
class StructuralAssertionFailure < StandardError; end
|
|
23
|
+
|
|
24
|
+
attr_reader :watch_paths
|
|
25
|
+
|
|
26
|
+
def initialize(watch_paths:)
|
|
27
|
+
@watch_paths = Array(watch_paths).map(&:to_s)
|
|
28
|
+
@pre = nil
|
|
29
|
+
@post = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def snapshot_pre!
|
|
33
|
+
@pre = snapshot
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def verify_post!
|
|
38
|
+
raise 'snapshot_pre! must be called before verify_post!' if @pre.nil?
|
|
39
|
+
|
|
40
|
+
@post = snapshot
|
|
41
|
+
diffs = diff(@pre, @post)
|
|
42
|
+
return self if diffs.empty?
|
|
43
|
+
|
|
44
|
+
raise StructuralAssertionFailure,
|
|
45
|
+
"stage 0 side-effect-zero violation: #{diffs.inspect}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def snapshots
|
|
49
|
+
{ pre: @pre, post: @post }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def snapshot
|
|
55
|
+
@watch_paths.each_with_object({}) do |path, acc|
|
|
56
|
+
acc[path] = if File.exist?(path)
|
|
57
|
+
{ sha256: Digest::SHA256.file(path).hexdigest,
|
|
58
|
+
mtime_ns: File.stat(path).mtime.to_r * 1_000_000_000,
|
|
59
|
+
size: File.size(path) }
|
|
60
|
+
else
|
|
61
|
+
:absent
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def diff(pre, post)
|
|
67
|
+
pre.keys.each_with_object([]) do |path, drifts|
|
|
68
|
+
a = pre[path]
|
|
69
|
+
b = post[path]
|
|
70
|
+
next if a == b
|
|
71
|
+
|
|
72
|
+
drifts << { path: path, pre: a, post: b }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
+
"$id": "https://kairoschain.dev/schemas/kairos_hook_projector/mode_hooks/v0.json",
|
|
4
|
+
"title": "mode_hooks definition (kairos_hook_projector stage 0)",
|
|
5
|
+
"description": "Schema for mode_hooks YAML/JSON definitions. Stage 0 validates the outer envelope (mode_name, version) and reserves composition fields (extends, conflict_policy) per design v0.2 §7.2. The internal shape of `hooks` is permissive at this stage; the stage 1 compiler will become the source of truth for hook entry structure.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["mode_name", "version"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"mode_name": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "The instruction mode this hooks definition applies to (e.g. 'masa', 'tutorial')."
|
|
14
|
+
},
|
|
15
|
+
"version": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"description": "Schema version of this mode_hooks document. Used by the compiler to dispatch to the correct parser."
|
|
19
|
+
},
|
|
20
|
+
"hooks": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "Optional (composite-only variants may omit). Map of event name to hook entries. Inner shape is intentionally unvalidated in stage 0; locked down in stage 1.",
|
|
23
|
+
"additionalProperties": true
|
|
24
|
+
},
|
|
25
|
+
"extends": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"description": "Optional composition field (reserved per Inv-C6). Names of variant SkillSets this definition composes from. Reference resolution and dangling-reference detection are compile-time concerns (Inv-C7), not schema concerns.",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
"uniqueItems": true
|
|
33
|
+
},
|
|
34
|
+
"conflict_policy": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"default": "error",
|
|
38
|
+
"description": "Optional composition field (reserved per Inv-C4). Conflict resolution policy for composing extends inputs. Default 'error' means human judgment is required (Inv-5). Auto-resolution policies are warn-but-accept at schema level in stage 0; activation requires operator consent chain_record (Inv-C10). The specific enum of allowed policy values is intentionally deferred to §11 backlog."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -12,8 +12,26 @@ mode_hooks YAML → `plugin/hooks.json` → `.claude/settings.json` (via `plugin
|
|
|
12
12
|
|
|
13
13
|
## Stage
|
|
14
14
|
|
|
15
|
-
v0.1 stage 0: skeleton + schema + `hooks_status` (read-only). Zero side effect
|
|
16
|
-
|
|
15
|
+
v0.1 stage 0: skeleton + schema + `hooks_status` (read-only). Zero side effect,
|
|
16
|
+
structurally guaranteed via boot-time hash/mtime assertion on projection target
|
|
17
|
+
files (DoD-0-4). Later stages add compile / project / unproject / composition.
|
|
18
|
+
|
|
19
|
+
## Tools
|
|
20
|
+
|
|
21
|
+
### `hooks_status` (read-only)
|
|
22
|
+
|
|
23
|
+
Inspect current state of `kairos_hook_projector`. Reports stage, schema
|
|
24
|
+
location, and mode_hooks document inventory. Each invocation runs a pre/post
|
|
25
|
+
hash+mtime assertion over the watched projection targets
|
|
26
|
+
(`.claude/settings.json` and the SkillSet's own `plugin/hooks.json`); any
|
|
27
|
+
drift fails the call with `StructuralAssertionFailure`. This is the
|
|
28
|
+
structural side-effect-zero guarantee for stage 0, not a convention.
|
|
29
|
+
|
|
30
|
+
## Schema
|
|
31
|
+
|
|
32
|
+
`mode_hooks/_schema.json` defines the envelope for mode_hooks definitions
|
|
33
|
+
(JSON Schema draft-04). Required: `mode_name`, `version`. Optional: `hooks`,
|
|
34
|
+
plus reserved composition fields (`extends`, `conflict_policy`).
|
|
17
35
|
|
|
18
36
|
## Design
|
|
19
37
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require_relative '../lib/boot_time_assertion'
|
|
7
|
+
|
|
8
|
+
# Stage 0 commit 3: BootTimeAssertion tests.
|
|
9
|
+
#
|
|
10
|
+
# Design reference: docs/drafts/kairos_hook_projector_design_v0.2_draft.md
|
|
11
|
+
# - Inv-6: stage 0 side-effect-zero is structurally guaranteed (not by
|
|
12
|
+
# convention) via boot-time verification.
|
|
13
|
+
# - DoD-0-4: read-only status tool with boot-time hash/mtime assertion
|
|
14
|
+
# fails fast on any drift in watched projection target files.
|
|
15
|
+
#
|
|
16
|
+
# Watched target categories the assertion must cover:
|
|
17
|
+
# - existing file unchanged across pre/post -> passes
|
|
18
|
+
# - existing file content changed -> raises (content drift)
|
|
19
|
+
# - absent file stays absent -> passes
|
|
20
|
+
# - absent file appears between pre/post -> raises (absent->present drift)
|
|
21
|
+
class TestBootTimeAssertion < Minitest::Test
|
|
22
|
+
AssertionClass = ::KairosMcp::SkillSets::KairosHookProjector::BootTimeAssertion
|
|
23
|
+
FailureClass = AssertionClass::StructuralAssertionFailure
|
|
24
|
+
|
|
25
|
+
def setup
|
|
26
|
+
@tmpdir = Dir.mktmpdir('kairos_hook_projector_boot_time_assertion_')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def teardown
|
|
30
|
+
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.directory?(@tmpdir)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Test 1: empty watch_paths is a no-op that always passes.
|
|
34
|
+
# Establishes that the assertion does not require any watched file to exist.
|
|
35
|
+
def test_empty_watch_paths_passes
|
|
36
|
+
assertion = AssertionClass.new(watch_paths: [])
|
|
37
|
+
assertion.snapshot_pre!
|
|
38
|
+
assertion.verify_post!
|
|
39
|
+
assert_empty assertion.snapshots[:pre]
|
|
40
|
+
assert_empty assertion.snapshots[:post]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Test 2: existing file unchanged between pre and post -> passes.
|
|
44
|
+
# This is the success path for the read-only tool wrapping its body.
|
|
45
|
+
def test_unchanged_existing_file_passes
|
|
46
|
+
path = File.join(@tmpdir, 'untouched.json')
|
|
47
|
+
File.write(path, '{"hooks":{}}')
|
|
48
|
+
assertion = AssertionClass.new(watch_paths: [path])
|
|
49
|
+
assertion.snapshot_pre!
|
|
50
|
+
# body does nothing
|
|
51
|
+
assertion.verify_post!
|
|
52
|
+
pre = assertion.snapshots[:pre][path]
|
|
53
|
+
post = assertion.snapshots[:post][path]
|
|
54
|
+
assert_equal pre, post,
|
|
55
|
+
'unchanged file must produce identical pre/post snapshots'
|
|
56
|
+
refute_equal :absent, pre
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Test 3: file content modified between pre and post -> raises with diff
|
|
60
|
+
# detail. This is the positive control proving the assertion actually
|
|
61
|
+
# catches stage 0 side-effect-zero violations.
|
|
62
|
+
def test_modified_existing_file_raises
|
|
63
|
+
path = File.join(@tmpdir, 'mutated.json')
|
|
64
|
+
File.write(path, '{"hooks":{}}')
|
|
65
|
+
# Ensure mtime resolution does not mask the change on fast filesystems.
|
|
66
|
+
File.utime(Time.now - 60, Time.now - 60, path)
|
|
67
|
+
|
|
68
|
+
assertion = AssertionClass.new(watch_paths: [path])
|
|
69
|
+
assertion.snapshot_pre!
|
|
70
|
+
File.write(path, '{"hooks":{"PostToolUse":[]}}')
|
|
71
|
+
|
|
72
|
+
error = assert_raises(FailureClass) { assertion.verify_post! }
|
|
73
|
+
assert_includes error.message, 'stage 0 side-effect-zero violation'
|
|
74
|
+
assert_includes error.message, path
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Test 4: absent file appearing between pre and post -> raises. Stage 0
|
|
78
|
+
# demands that .claude/settings.json which does not exist must remain
|
|
79
|
+
# non-existent for the duration of the tool call.
|
|
80
|
+
def test_absent_file_appearing_raises
|
|
81
|
+
path = File.join(@tmpdir, 'not_yet_present.json')
|
|
82
|
+
refute File.exist?(path), 'precondition: file must be absent'
|
|
83
|
+
|
|
84
|
+
assertion = AssertionClass.new(watch_paths: [path])
|
|
85
|
+
assertion.snapshot_pre!
|
|
86
|
+
assert_equal :absent, assertion.snapshots[:pre][path]
|
|
87
|
+
|
|
88
|
+
File.write(path, '{"created":"by violation"}')
|
|
89
|
+
|
|
90
|
+
error = assert_raises(FailureClass) { assertion.verify_post! }
|
|
91
|
+
assert_includes error.message, path
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Test 5 (control): absent file that stays absent -> passes. Confirms the
|
|
95
|
+
# assertion does not spuriously fire for projection targets that legitimately
|
|
96
|
+
# do not exist during stage 0.
|
|
97
|
+
def test_absent_file_staying_absent_passes
|
|
98
|
+
path = File.join(@tmpdir, 'stays_absent.json')
|
|
99
|
+
assertion = AssertionClass.new(watch_paths: [path])
|
|
100
|
+
assertion.snapshot_pre!
|
|
101
|
+
assertion.verify_post!
|
|
102
|
+
assert_equal :absent, assertion.snapshots[:pre][path]
|
|
103
|
+
assert_equal :absent, assertion.snapshots[:post][path]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Test 6: verify_post! without snapshot_pre! raises. Sanity check on the
|
|
107
|
+
# contract that pre/post calls are ordered.
|
|
108
|
+
def test_verify_post_without_pre_raises
|
|
109
|
+
assertion = AssertionClass.new(watch_paths: [])
|
|
110
|
+
error = assert_raises(RuntimeError) { assertion.verify_post! }
|
|
111
|
+
assert_includes error.message, 'snapshot_pre!'
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
# Stage 0 commit 3 smoke test: HooksStatus tool returns a well-formed
|
|
9
|
+
# response and the boot-time assertion reports passed when the tool body
|
|
10
|
+
# does not touch any watched file.
|
|
11
|
+
#
|
|
12
|
+
# This test deliberately stubs the BaseTool dependency rather than loading
|
|
13
|
+
# the full KairosChain MCP server gem, so that the SkillSet's stage 0 surface
|
|
14
|
+
# can be validated in isolation. Integration with the gem-level tool
|
|
15
|
+
# registration is verified at gem build / install time, not here.
|
|
16
|
+
|
|
17
|
+
module KairosMcp
|
|
18
|
+
module Tools
|
|
19
|
+
# Minimal stub of KairosMcp::Tools::BaseTool sufficient for stage 0
|
|
20
|
+
# smoke testing. The real implementation lives in
|
|
21
|
+
# KairosChain_mcp_server/lib/kairos_mcp/tools/base_tool.rb.
|
|
22
|
+
class BaseTool
|
|
23
|
+
def initialize(safety = nil, registry: nil); end
|
|
24
|
+
|
|
25
|
+
def text_content(text)
|
|
26
|
+
[{ type: 'text', text: text }]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end unless defined?(::KairosMcp::Tools::BaseTool)
|
|
31
|
+
|
|
32
|
+
# Stub the KairosMcp module project_root accessor so the tool can resolve
|
|
33
|
+
# .claude/settings.json against our tmpdir.
|
|
34
|
+
module KairosMcp
|
|
35
|
+
class << self
|
|
36
|
+
attr_accessor :project_root unless method_defined?(:project_root)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
require_relative '../tools/hooks_status'
|
|
41
|
+
|
|
42
|
+
class TestHooksStatus < Minitest::Test
|
|
43
|
+
ToolClass = ::KairosMcp::SkillSets::KairosHookProjector::Tools::HooksStatus
|
|
44
|
+
|
|
45
|
+
def setup
|
|
46
|
+
@tmpdir = Dir.mktmpdir('kairos_hook_projector_smoke_')
|
|
47
|
+
::KairosMcp.project_root = @tmpdir
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def teardown
|
|
51
|
+
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.directory?(@tmpdir)
|
|
52
|
+
::KairosMcp.project_root = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_tool_returns_well_formed_response_with_assertion_passed
|
|
56
|
+
response = ToolClass.new.call({})
|
|
57
|
+
assert_kind_of Array, response
|
|
58
|
+
assert_equal 'text', response.first[:type]
|
|
59
|
+
|
|
60
|
+
body = JSON.parse(response.first[:text])
|
|
61
|
+
|
|
62
|
+
# Shape invariants
|
|
63
|
+
assert_equal 'kairos_hook_projector', body['skillset']
|
|
64
|
+
assert_match(/stage 0/, body['stage'])
|
|
65
|
+
assert_equal @tmpdir, body['project_root']
|
|
66
|
+
assert body['schema']['present'],
|
|
67
|
+
'stage 0 schema (_schema.json) must be present after commit 2'
|
|
68
|
+
assert_kind_of Integer, body['mode_hooks']['count']
|
|
69
|
+
assert_kind_of Array, body['mode_hooks']['files']
|
|
70
|
+
|
|
71
|
+
# Boot-time assertion outcome
|
|
72
|
+
assert_equal 'passed', body['boot_time_assertion']['status']
|
|
73
|
+
watched = body['boot_time_assertion']['watched_paths']
|
|
74
|
+
assert_kind_of Array, watched
|
|
75
|
+
refute_empty watched
|
|
76
|
+
assert(watched.any? { |p| p.end_with?('settings.json') },
|
|
77
|
+
'must watch .claude/settings.json projection target')
|
|
78
|
+
assert(watched.any? { |p| p.end_with?('hooks.json') },
|
|
79
|
+
"must watch the skillset's own plugin/hooks.json")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'json-schema'
|
|
6
|
+
|
|
7
|
+
# Stage 0 commit 2: mode_hooks/_schema.json self-validation tests.
|
|
8
|
+
#
|
|
9
|
+
# Design reference: docs/drafts/kairos_hook_projector_design_v0.2_draft.md
|
|
10
|
+
# - DoD-0-2: mode_hooks schema is self-validating.
|
|
11
|
+
# - DoD-0-3: composition fields are validation targets; syntactically valid
|
|
12
|
+
# declarations are accepted; syntactically invalid ones are rejected.
|
|
13
|
+
# - §7.2 schema invariants: mode_name/version required, hooks optional,
|
|
14
|
+
# composition fields optional but accepted.
|
|
15
|
+
class TestModeHooksSchema < Minitest::Test
|
|
16
|
+
SKILLSET_ROOT = File.expand_path('..', __dir__)
|
|
17
|
+
SCHEMA_PATH = File.join(SKILLSET_ROOT, 'mode_hooks', '_schema.json')
|
|
18
|
+
|
|
19
|
+
def setup
|
|
20
|
+
@schema = JSON.parse(File.read(SCHEMA_PATH))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Test 1 (happy path, DoD-0-2): a minimal valid document with only the
|
|
24
|
+
# required fields validates clean.
|
|
25
|
+
def test_minimal_valid_document_accepted
|
|
26
|
+
doc = { 'mode_name' => 'masa', 'version' => '0.1' }
|
|
27
|
+
errors = JSON::Validator.fully_validate(@schema, doc)
|
|
28
|
+
assert_empty errors,
|
|
29
|
+
"Minimal valid document {mode_name, version} must validate clean. Got: #{errors.inspect}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Test 2 (reject path, DoD-0-3 second half): a document missing a required
|
|
33
|
+
# field is rejected. Document also exercises a syntactically invalid
|
|
34
|
+
# composition field (extends with non-string entry) to confirm composition
|
|
35
|
+
# fields are real validation targets, not silently passed through.
|
|
36
|
+
def test_syntactically_invalid_document_rejected
|
|
37
|
+
missing_version = { 'mode_name' => 'masa' }
|
|
38
|
+
errors_a = JSON::Validator.fully_validate(@schema, missing_version)
|
|
39
|
+
refute_empty errors_a, "Document missing 'version' must be rejected"
|
|
40
|
+
assert errors_a.any? { |e| e.include?('version') },
|
|
41
|
+
"Rejection error must mention the missing 'version' field. Got: #{errors_a.inspect}"
|
|
42
|
+
|
|
43
|
+
bad_extends = {
|
|
44
|
+
'mode_name' => 'masa', 'version' => '0.1',
|
|
45
|
+
'extends' => ['conservative', 42] # 42 is not a string
|
|
46
|
+
}
|
|
47
|
+
errors_b = JSON::Validator.fully_validate(@schema, bad_extends)
|
|
48
|
+
refute_empty errors_b,
|
|
49
|
+
"Document with non-string entry in 'extends' must be rejected (composition field is a real validation target, not warn-but-accept)"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Test 3 (composition optional + accepted, DoD-0-3 first half + §7.2): a
|
|
53
|
+
# document carrying syntactically valid composition fields (extends,
|
|
54
|
+
# conflict_policy) is accepted. This is the "reservation" invariant — the
|
|
55
|
+
# fields exist in the schema and validate, but stage 0 does not yet attach
|
|
56
|
+
# any compile-time semantics to them.
|
|
57
|
+
def test_composition_fields_optional_and_accepted_when_valid
|
|
58
|
+
doc = {
|
|
59
|
+
'mode_name' => 'masa',
|
|
60
|
+
'version' => '0.1',
|
|
61
|
+
'extends' => %w[conservative agent_aggressive],
|
|
62
|
+
'conflict_policy' => 'error'
|
|
63
|
+
}
|
|
64
|
+
errors = JSON::Validator.fully_validate(@schema, doc)
|
|
65
|
+
assert_empty errors,
|
|
66
|
+
"Composition fields (extends, conflict_policy) must be accepted when syntactically valid. Got: #{errors.inspect}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative '../lib/boot_time_assertion'
|
|
5
|
+
|
|
6
|
+
module KairosMcp
|
|
7
|
+
module SkillSets
|
|
8
|
+
module KairosHookProjector
|
|
9
|
+
module Tools
|
|
10
|
+
# Read-only inspection of `kairos_hook_projector` state.
|
|
11
|
+
# Wraps its read-only body with a BootTimeAssertion that captures
|
|
12
|
+
# pre/post hash+mtime of projection target files, providing structural
|
|
13
|
+
# (not conventional) guarantee of stage 0 side-effect-zero per design
|
|
14
|
+
# v0.2 §7.2 DoD-0-4 and DoD-0-6.
|
|
15
|
+
#
|
|
16
|
+
# The watched target set in stage 0:
|
|
17
|
+
# - <project_root>/.claude/settings.json (Stage 2+ projection target)
|
|
18
|
+
# - <skillset_root>/plugin/hooks.json (intermediate target)
|
|
19
|
+
#
|
|
20
|
+
# If either file drifts between pre and post snapshots, the tool
|
|
21
|
+
# raises and the caller receives a fail-fast error rather than a
|
|
22
|
+
# successful response. This is the structural guarantee.
|
|
23
|
+
class HooksStatus < ::KairosMcp::Tools::BaseTool
|
|
24
|
+
SKILLSET_ROOT = File.expand_path('..', __dir__)
|
|
25
|
+
SKILLSET_NAME = 'kairos_hook_projector'
|
|
26
|
+
STAGE_MARKER = 'stage 0 (skeleton + schema + status)'
|
|
27
|
+
|
|
28
|
+
def name
|
|
29
|
+
'hooks_status'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def description
|
|
33
|
+
'Read-only inspection of kairos_hook_projector state. ' \
|
|
34
|
+
'Reports stage, schema location, and mode_hooks document inventory. ' \
|
|
35
|
+
'Structurally guarantees zero side effect on projection targets via ' \
|
|
36
|
+
'a pre/post hash+mtime boot-time assertion (DoD-0-4).'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def category
|
|
40
|
+
:meta
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def usecase_tags
|
|
44
|
+
%w[hooks status read-only stage0 self-referential]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def related_tools
|
|
48
|
+
%w[plugin_project skills_audit]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def input_schema
|
|
52
|
+
{
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {},
|
|
55
|
+
additionalProperties: false
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def call(_arguments)
|
|
60
|
+
project_root = resolve_project_root
|
|
61
|
+
watch_paths = compute_watch_paths(project_root)
|
|
62
|
+
|
|
63
|
+
assertion = BootTimeAssertion.new(watch_paths: watch_paths)
|
|
64
|
+
assertion.snapshot_pre!
|
|
65
|
+
|
|
66
|
+
body = compose_status_body(project_root: project_root,
|
|
67
|
+
watch_paths: watch_paths)
|
|
68
|
+
|
|
69
|
+
assertion.verify_post!
|
|
70
|
+
|
|
71
|
+
text_content(JSON.pretty_generate(
|
|
72
|
+
body.merge(boot_time_assertion: {
|
|
73
|
+
status: 'passed',
|
|
74
|
+
watched_paths: watch_paths,
|
|
75
|
+
snapshots: assertion.snapshots
|
|
76
|
+
})
|
|
77
|
+
))
|
|
78
|
+
rescue BootTimeAssertion::StructuralAssertionFailure => e
|
|
79
|
+
text_content(JSON.pretty_generate({
|
|
80
|
+
error: 'StructuralAssertionFailure',
|
|
81
|
+
detail: e.message,
|
|
82
|
+
skillset: SKILLSET_NAME,
|
|
83
|
+
stage: STAGE_MARKER
|
|
84
|
+
}))
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
text_content(JSON.pretty_generate({
|
|
87
|
+
error: e.class.name,
|
|
88
|
+
detail: e.message,
|
|
89
|
+
backtrace: e.backtrace&.first(3)
|
|
90
|
+
}))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def resolve_project_root
|
|
96
|
+
if defined?(::KairosMcp) && ::KairosMcp.respond_to?(:project_root)
|
|
97
|
+
::KairosMcp.project_root
|
|
98
|
+
else
|
|
99
|
+
Dir.pwd
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def compute_watch_paths(project_root)
|
|
104
|
+
[
|
|
105
|
+
File.join(project_root.to_s, '.claude', 'settings.json'),
|
|
106
|
+
File.join(SKILLSET_ROOT, 'plugin', 'hooks.json')
|
|
107
|
+
]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def compose_status_body(project_root:, watch_paths:)
|
|
111
|
+
mode_hooks_dir = File.join(SKILLSET_ROOT, 'mode_hooks')
|
|
112
|
+
schema_path = File.join(mode_hooks_dir, '_schema.json')
|
|
113
|
+
mode_files = Dir.glob(File.join(mode_hooks_dir, '*'))
|
|
114
|
+
.reject { |p| File.basename(p).start_with?('_') }
|
|
115
|
+
.map { |p| File.basename(p) }
|
|
116
|
+
.sort
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
skillset: SKILLSET_NAME,
|
|
120
|
+
stage: STAGE_MARKER,
|
|
121
|
+
project_root: project_root.to_s,
|
|
122
|
+
schema: {
|
|
123
|
+
path: schema_path,
|
|
124
|
+
present: File.exist?(schema_path)
|
|
125
|
+
},
|
|
126
|
+
mode_hooks: {
|
|
127
|
+
count: mode_files.size,
|
|
128
|
+
files: mode_files
|
|
129
|
+
},
|
|
130
|
+
note: 'Read-only. Stage 0 emits no projections; see ' \
|
|
131
|
+
'docs/drafts/kairos_hook_projector_design_v0.2_draft.md'
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
# Convergence rules
|
|
7
7
|
# Roster has 5 reviewers (claude_team_opus4.7, claude_cli_opus4.6,
|
|
8
|
-
# codex_gpt5.4, codex_gpt5.5, cursor_composer2). Rules are ratio-based
|
|
8
|
+
# codex_gpt5.4, codex_gpt5.5, cursor_composer2.5). Rules are ratio-based
|
|
9
9
|
# (parser interprets "N/M" as N/M fraction applied to successful count),
|
|
10
10
|
# so the literal numerator/denominator is informational; what matters is
|
|
11
11
|
# the ratio.
|
|
@@ -108,8 +108,8 @@ reviewers:
|
|
|
108
108
|
role_label: codex_gpt5.5
|
|
109
109
|
|
|
110
110
|
- provider: cursor
|
|
111
|
-
role_label: cursor_composer2
|
|
112
|
-
# No `model` key → cursor adapter omits --model → cursor's default (
|
|
111
|
+
role_label: cursor_composer2.5
|
|
112
|
+
# No `model` key → cursor adapter omits --model → cursor's default (composer-2.5-fast as of 2026-05).
|
|
113
113
|
# This entry preserves the original 5-reviewer roster behavior.
|
|
114
114
|
|
|
115
115
|
# ----------------------------------------------------------------------
|
|
@@ -712,12 +712,13 @@ module KairosMcp
|
|
|
712
712
|
end
|
|
713
713
|
|
|
714
714
|
def symbolize_keys(hash)
|
|
715
|
+
hash = coerce_to_hash(hash)
|
|
715
716
|
hash.transform_keys(&:to_sym)
|
|
716
717
|
end
|
|
717
718
|
|
|
718
719
|
def symbolize_findings(findings)
|
|
719
720
|
return nil unless findings.is_a?(Array)
|
|
720
|
-
findings.map { |f| f.transform_keys(&:to_sym) }
|
|
721
|
+
findings.map { |f| coerce_to_hash(f).transform_keys(&:to_sym) }
|
|
721
722
|
end
|
|
722
723
|
|
|
723
724
|
def hash_to_string_keys(hash)
|
|
@@ -725,6 +726,14 @@ module KairosMcp
|
|
|
725
726
|
hash.transform_keys(&:to_s)
|
|
726
727
|
end
|
|
727
728
|
|
|
729
|
+
def coerce_to_hash(obj)
|
|
730
|
+
return obj if obj.is_a?(Hash)
|
|
731
|
+
return JSON.parse(obj) if obj.is_a?(String)
|
|
732
|
+
obj
|
|
733
|
+
rescue JSON::ParserError
|
|
734
|
+
{ '_raw' => obj.to_s }
|
|
735
|
+
end
|
|
736
|
+
|
|
728
737
|
end
|
|
729
738
|
end
|
|
730
739
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kairos-chain
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.28.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Masaomi Hatakeyama
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -411,10 +411,16 @@ files:
|
|
|
411
411
|
- templates/skillsets/introspection/tools/introspection_check.rb
|
|
412
412
|
- templates/skillsets/introspection/tools/introspection_health.rb
|
|
413
413
|
- templates/skillsets/introspection/tools/introspection_safety.rb
|
|
414
|
+
- templates/skillsets/kairos_hook_projector/lib/boot_time_assertion.rb
|
|
415
|
+
- templates/skillsets/kairos_hook_projector/mode_hooks/_schema.json
|
|
414
416
|
- templates/skillsets/kairos_hook_projector/plugin/SKILL.md
|
|
415
417
|
- templates/skillsets/kairos_hook_projector/plugin/hooks.json
|
|
416
418
|
- templates/skillsets/kairos_hook_projector/skillset.json
|
|
419
|
+
- templates/skillsets/kairos_hook_projector/test/test_boot_time_assertion.rb
|
|
420
|
+
- templates/skillsets/kairos_hook_projector/test/test_hooks_status.rb
|
|
421
|
+
- templates/skillsets/kairos_hook_projector/test/test_mode_hooks_schema.rb
|
|
417
422
|
- templates/skillsets/kairos_hook_projector/test/test_skillset_json.rb
|
|
423
|
+
- templates/skillsets/kairos_hook_projector/tools/hooks_status.rb
|
|
418
424
|
- templates/skillsets/knowledge_creator/config/knowledge_creator.yml
|
|
419
425
|
- templates/skillsets/knowledge_creator/knowledge/creation_guide/creation_guide.md
|
|
420
426
|
- templates/skillsets/knowledge_creator/knowledge/quality_criteria/quality_criteria.md
|