kairos-chain 3.27.0 → 3.28.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0eb600b698a295c1d826f77fd01e7b557608b8771b1d40593406d6027b345d51
4
- data.tar.gz: 6428da295ba73bcb338cfd545510cb14980288d381eac0e64eabaca9941ee354
3
+ metadata.gz: 7f3b4857e18283e4c80b9197c54e83a962ce2ee4b465b85de7969dc78244fb58
4
+ data.tar.gz: 512356715a6db9e5110162b61a81017c99f00179dd8bbe9250fb23ba0f631981
5
5
  SHA512:
6
- metadata.gz: 492d3c12ed01371fa093b1504fc137499a4ad1e9fac6168e3ccff8d9c3bbdc5c8b4bdf127bc8bfa6dae70f87f8bf4e8fa94a7ee8c6ecb9acd805193efede861d
7
- data.tar.gz: 923e4189b8b60e1d313296513187143b63ea96ae9099e944b4ccb15ee72ec1dca82d6117bc235093dcac57a92eba82f4b6c15921992674edf57ffde2c2aa7a48
6
+ metadata.gz: 65b002260019464cc20531ab9c7c7d8d5ebecf293a21414e9af5ead16c00490b29495dc53bb8bbddc3d32d572697e91984644350149f93f587022c94b96bbf4c
7
+ data.tar.gz: e673f6f2233ebc56ea8fb7247ed6e91bd05e61f5efd624ca8d48e814dbda70007f808ebd5db2199920f312ef0b978a576b1e3a5d910a2eb094a45f63c986097f
@@ -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 session_id && name
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.length < 2
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)
@@ -215,6 +215,7 @@ module KairosMcp
215
215
  # Resource tools (unified access to L0/L1/L2 resources)
216
216
  register_if_defined('KairosMcp::Tools::ResourceList')
217
217
  register_if_defined('KairosMcp::Tools::ResourceRead')
218
+ register_if_defined('KairosMcp::Tools::ResourceRender')
218
219
 
219
220
  # L1: knowledge/ (Anthropic skills format with hash-only blockchain record)
220
221
  register_if_defined('KairosMcp::Tools::KnowledgeList')
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'timeout'
5
+ require_relative 'base_tool'
6
+ require_relative '../anthropic_skill_parser'
7
+
8
+ module KairosMcp
9
+ module Tools
10
+ class ResourceRender < BaseTool
11
+ RENDER_TIMEOUT = 30
12
+
13
+ def name
14
+ 'resource_render'
15
+ end
16
+
17
+ def description
18
+ 'Execute a render script from a knowledge entry to generate an HTML asset. ' \
19
+ 'Scripts live in knowledge/{name}/scripts/ and output HTML to assets/. ' \
20
+ 'Data is passed via stdin as JSON. Convention: scripts named render_*.rb.'
21
+ end
22
+
23
+ def category
24
+ :resource
25
+ end
26
+
27
+ def usecase_tags
28
+ %w[render html visualize dashboard asset generate]
29
+ end
30
+
31
+ def examples
32
+ [
33
+ {
34
+ title: 'Generate review dashboard',
35
+ code: 'resource_render(knowledge: "multi_llm_review_workflow", ' \
36
+ 'script: "render_dashboard.rb", data: "{...}", open: true)'
37
+ },
38
+ {
39
+ title: 'Generate with custom output name',
40
+ code: 'resource_render(knowledge: "multi_llm_review_workflow", ' \
41
+ 'script: "render_dashboard.rb", data: "{...}", output: "round2.html")'
42
+ }
43
+ ]
44
+ end
45
+
46
+ def related_tools
47
+ %w[resource_list resource_read knowledge_get]
48
+ end
49
+
50
+ def input_schema
51
+ {
52
+ type: 'object',
53
+ properties: {
54
+ knowledge: {
55
+ type: 'string',
56
+ description: 'L1 knowledge entry name (e.g., "multi_llm_review_workflow")'
57
+ },
58
+ script: {
59
+ type: 'string',
60
+ description: 'Script filename in the knowledge scripts/ directory (e.g., "render_dashboard.rb")'
61
+ },
62
+ data: {
63
+ type: 'string',
64
+ description: 'JSON data to pass to the script via stdin'
65
+ },
66
+ output: {
67
+ type: 'string',
68
+ description: 'Output filename in assets/ (default: derived from script name, e.g., render_dashboard.rb -> dashboard.html)'
69
+ },
70
+ open: {
71
+ type: 'boolean',
72
+ description: 'Open the generated HTML in the default browser (default: false)'
73
+ }
74
+ },
75
+ required: %w[knowledge script data]
76
+ }
77
+ end
78
+
79
+ def call(arguments)
80
+ knowledge_name = arguments['knowledge']
81
+ script_name = arguments['script']
82
+ data = arguments['data']
83
+ output_name = arguments['output']
84
+ open_after = arguments['open'] || false
85
+
86
+ return text_content("Error: knowledge is required") unless knowledge_name && !knowledge_name.empty?
87
+ return text_content("Error: script is required") unless script_name && !script_name.empty?
88
+ return text_content("Error: data is required") unless data && !data.empty?
89
+
90
+ # Validate JSON
91
+ begin
92
+ JSON.parse(data)
93
+ rescue JSON::ParserError => e
94
+ return text_content("Error: invalid JSON data — #{e.message}")
95
+ end
96
+
97
+ # Resolve knowledge directory
98
+ knowledge_dir = File.join(KairosMcp.knowledge_dir(user_context: @safety&.current_user), knowledge_name)
99
+ unless File.directory?(knowledge_dir)
100
+ return text_content("Error: knowledge '#{knowledge_name}' not found")
101
+ end
102
+
103
+ # Security: normalize script name to prevent path traversal
104
+ safe_script = File.basename(script_name)
105
+ script_path = File.join(knowledge_dir, 'scripts', safe_script)
106
+ unless File.exist?(script_path)
107
+ return text_content("Error: script '#{safe_script}' not found in #{knowledge_name}/scripts/")
108
+ end
109
+
110
+ # Derive output filename
111
+ if output_name
112
+ safe_output = File.basename(output_name)
113
+ else
114
+ safe_output = safe_script
115
+ .sub(/\Arender_/, '')
116
+ .sub(/\.rb\z/, '.html')
117
+ end
118
+
119
+ # Ensure assets/ directory exists
120
+ assets_dir = File.join(knowledge_dir, 'assets')
121
+ FileUtils.mkdir_p(assets_dir)
122
+
123
+ # Execute script with data on stdin
124
+ stdout, stderr, status = execute_script(script_path, data, knowledge_dir)
125
+
126
+ unless status.success?
127
+ error_msg = "Error: script exited with code #{status.exitstatus}"
128
+ error_msg += "\n\nstderr:\n```\n#{stderr}\n```" unless stderr.empty?
129
+ return text_content(error_msg)
130
+ end
131
+
132
+ if stdout.nil? || stdout.empty?
133
+ return text_content("Error: script produced no output")
134
+ end
135
+
136
+ # Write output to assets/
137
+ output_path = File.join(assets_dir, safe_output)
138
+ AnthropicSkillParser.atomic_write(output_path, stdout)
139
+
140
+ # Open in browser if requested
141
+ if open_after
142
+ system('open', output_path)
143
+ end
144
+
145
+ uri = "knowledge://#{knowledge_name}/assets/#{safe_output}"
146
+ build_success_response(uri, output_path, stdout.bytesize, open_after)
147
+ end
148
+
149
+ private
150
+
151
+ def execute_script(script_path, data, working_dir)
152
+ Timeout.timeout(RENDER_TIMEOUT) do
153
+ Open3.capture3(
154
+ 'ruby', script_path,
155
+ stdin_data: data,
156
+ chdir: working_dir
157
+ )
158
+ end
159
+ rescue Timeout::Error
160
+ ["", "Script execution timed out after #{RENDER_TIMEOUT}s", OpenStruct.new(success?: false, exitstatus: 124)]
161
+ end
162
+
163
+ def build_success_response(uri, path, size, opened)
164
+ output = "## Resource Rendered\n\n"
165
+ output += "| Property | Value |\n"
166
+ output += "|----------|-------|\n"
167
+ output += "| **URI** | `#{uri}` |\n"
168
+ output += "| **Path** | `#{path}` |\n"
169
+ output += "| **Size** | #{format_size(size)} |\n"
170
+ output += "| **Opened** | #{opened ? 'Yes' : 'No'} |\n\n"
171
+ output += "Use `resource_read(uri: \"#{uri}\")` to read the generated content.\n"
172
+ output += "Use `open #{path}` to view in browser." unless opened
173
+ text_content(output)
174
+ end
175
+
176
+ def format_size(bytes)
177
+ if bytes < 1024
178
+ "#{bytes} B"
179
+ elsif bytes < 1024 * 1024
180
+ "#{(bytes / 1024.0).round(1)} KB"
181
+ else
182
+ "#{(bytes / (1024.0 * 1024)).round(1)} MB"
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "3.27.0"
2
+ VERSION = "3.28.3"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -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.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
- | Are you in an interactive Claude Code session and just need one review? | **Path A** |
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.5 (default) AND gpt-5.4 (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 |
@@ -376,8 +410,8 @@ which claude 2>/dev/null && echo "claude: available" || echo "claude: NOT FOUND"
376
410
 
377
411
  | Tool | Command | Prompt Input | Output Collection | Model |
378
412
  |------|---------|-------------|-------------------|-------|
379
- | **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) |
413
+ | **Codex** | `codex exec` | stdin pipe: `cat prompt.md \| codex exec -` | `-o /path/output.md` | GPT-5.5 (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
 
@@ -392,8 +426,8 @@ Based on cross-evaluation experiment (7 models × 4 tasks + Nomic, 518 CLI calls
392
426
  | **Reviewer: Claude CLI** | Opus 4.7 | `--effort low` | Evaluator quality is effort-independent (low≈high: 8.35 vs 8.16) |
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
- | **Codex** | GPT-5.4 | (no flag) | Fixed effort |
396
- | **Cursor Agent** | Composer-2 | (no flag) | Fixed effort |
429
+ | **Codex** | GPT-5.5 (default) | (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.5), 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 -
@@ -719,7 +753,7 @@ log/{artifact}_review{N}_consensus_{date}.md # Consensus analysis
719
753
  ```
720
754
 
721
755
  LLM identifiers: `claude_opus4.6`, `claude_team_opus4.6`,
722
- `claude_cli_opus4.7`, `codex_gpt5.4`, `cursor_composer2`, `cursor_gpt5.4`,
756
+ `claude_cli_opus4.7`, `codex_gpt5.5`, `codex_gpt5.4`, `cursor_composer2`, `cursor_gpt5.4`,
723
757
  `cursor_premium`
724
758
 
725
759
  ## Internal Agent Team Review
@@ -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.3"
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
- Later stages add compile / project / unproject / composition.
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
 
@@ -14,7 +14,9 @@
14
14
  "mode_hooks_schema",
15
15
  "hooks_status_readonly"
16
16
  ],
17
- "tool_classes": [],
17
+ "tool_classes": [
18
+ "KairosMcp::SkillSets::KairosHookProjector::Tools::HooksStatus"
19
+ ],
18
20
  "config_files": [],
19
21
  "knowledge_dirs": [],
20
22
  "min_core_version": "3.25.0"
@@ -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 (composer2).
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.27.0
4
+ version: 3.28.3
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-19 00:00:00.000000000 Z
11
+ date: 2026-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -194,6 +194,7 @@ files:
194
194
  - lib/kairos_mcp/tools/knowledge_update.rb
195
195
  - lib/kairos_mcp/tools/resource_list.rb
196
196
  - lib/kairos_mcp/tools/resource_read.rb
197
+ - lib/kairos_mcp/tools/resource_render.rb
197
198
  - lib/kairos_mcp/tools/skills_audit.rb
198
199
  - lib/kairos_mcp/tools/skills_dsl_get.rb
199
200
  - lib/kairos_mcp/tools/skills_dsl_list.rb
@@ -411,10 +412,16 @@ files:
411
412
  - templates/skillsets/introspection/tools/introspection_check.rb
412
413
  - templates/skillsets/introspection/tools/introspection_health.rb
413
414
  - templates/skillsets/introspection/tools/introspection_safety.rb
415
+ - templates/skillsets/kairos_hook_projector/lib/boot_time_assertion.rb
416
+ - templates/skillsets/kairos_hook_projector/mode_hooks/_schema.json
414
417
  - templates/skillsets/kairos_hook_projector/plugin/SKILL.md
415
418
  - templates/skillsets/kairos_hook_projector/plugin/hooks.json
416
419
  - templates/skillsets/kairos_hook_projector/skillset.json
420
+ - templates/skillsets/kairos_hook_projector/test/test_boot_time_assertion.rb
421
+ - templates/skillsets/kairos_hook_projector/test/test_hooks_status.rb
422
+ - templates/skillsets/kairos_hook_projector/test/test_mode_hooks_schema.rb
417
423
  - templates/skillsets/kairos_hook_projector/test/test_skillset_json.rb
424
+ - templates/skillsets/kairos_hook_projector/tools/hooks_status.rb
418
425
  - templates/skillsets/knowledge_creator/config/knowledge_creator.yml
419
426
  - templates/skillsets/knowledge_creator/knowledge/creation_guide/creation_guide.md
420
427
  - templates/skillsets/knowledge_creator/knowledge/quality_criteria/quality_criteria.md