kairos-chain 3.25.2 → 3.26.0
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/CHANGELOG.md +52 -0
- data/bin/kairos-chain +53 -8
- data/lib/kairos_mcp/plugin_projector.rb +40 -4
- data/lib/kairos_mcp/protocol.rb +19 -3
- data/lib/kairos_mcp/version.rb +1 -1
- data/lib/kairos_mcp.rb +124 -4
- data/templates/skillsets/kairos_hook_projector/plugin/SKILL.md +20 -0
- data/templates/skillsets/kairos_hook_projector/plugin/hooks.json +3 -0
- data/templates/skillsets/kairos_hook_projector/skillset.json +21 -0
- data/templates/skillsets/kairos_hook_projector/test/test_skillset_json.rb +45 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efb171bcb7680f795a7132e8d2d68ffb5ca4574d298ca1ea47ac7d44b42398e8
|
|
4
|
+
data.tar.gz: da787664c3683e38f960da502a395bdde91285550a9af8003b59c2cc5cd5cfef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ed8251d8184beed84b8b3d424a32d5e81c667d30f304e90767aa7d80f279291ef2ce18ed129bd1ddc5c527d2ae03b153854c400f1d929f7acc17c50b43e930c
|
|
7
|
+
data.tar.gz: f386544999d14dff7298ecbfcb2c5939adc593767d2dc10843bc09e2e143bc003f2616dc8f01f463bf22f8b75b3571007c7a4e389477e4401dd486377cab0f18
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.26.0] - 2026-05-12
|
|
8
|
+
|
|
9
|
+
### Added — consumer_project_root separation (design v0.2)
|
|
10
|
+
|
|
11
|
+
Decouples the consumer project root (where `CLAUDE.md` and `.claude/` are written)
|
|
12
|
+
from the data directory (where `.kairos/` lives). Fixes silent projection failure
|
|
13
|
+
when `--data-dir` points outside the consumer workspace
|
|
14
|
+
(see `log/handoff_kairoschain_plugin_projection_bug.md`).
|
|
15
|
+
|
|
16
|
+
- `KairosMcp.consumer_project_root` accessor + `consumer_project_root_source`
|
|
17
|
+
provenance (`:explicit_cli`, `:explicit_env`, `:transport_default`, `:absent`).
|
|
18
|
+
- Resolution order: `--project-root` CLI flag → `KAIROS_PROJECT_ROOT` env →
|
|
19
|
+
per-transport default with plausibility check.
|
|
20
|
+
- Plausibility markers: `CLAUDE.md`, `.git/`, `.claude/`, or prior
|
|
21
|
+
`.kairos/projection_manifest.json`.
|
|
22
|
+
- `PluginProjector` constructor accepts `data_dir:` kwarg; refuses construction
|
|
23
|
+
(`CoincidenceRefused`) when project root and data dir resolve to the same
|
|
24
|
+
real path.
|
|
25
|
+
- `kairos-chain mode project|status` now prints resolved project root + source +
|
|
26
|
+
data dir.
|
|
27
|
+
- New `--project-root <path>` global flag and `mode` subcommand option.
|
|
28
|
+
- Manifest files (`projection_manifest.json`,
|
|
29
|
+
`instruction_mode_manifest.json`) now live under `data_dir/`, decoupled from
|
|
30
|
+
`project_root/.kairos/`.
|
|
31
|
+
|
|
32
|
+
### Design references
|
|
33
|
+
|
|
34
|
+
- Design draft v0.2: `log/20260512_consumer_project_root_separation_design_v0.2.md`
|
|
35
|
+
- Multi-LLM review round 1 verdict: REVISE (1/5 APPROVE).
|
|
36
|
+
Reject log: `log/20260512_consumer_project_root_separation_design_v0.2_reject_log.md`.
|
|
37
|
+
|
|
38
|
+
### Out of scope (deferred to future releases)
|
|
39
|
+
|
|
40
|
+
- Multi-consumer routing (Inv 9 invariant declared, implementation deferred —
|
|
41
|
+
single-consumer only in 3.26.0).
|
|
42
|
+
- Remote HTTP MCP projection delivery: HTTP MCP transport now loud-refuses
|
|
43
|
+
when no explicit `--project-root` is configured (no more silent failure),
|
|
44
|
+
but cross-machine artifact delivery is unimplemented. Local HTTP with
|
|
45
|
+
explicit configuration works.
|
|
46
|
+
- Explicit authorization UX (e.g. `kairos-chain mode authorize`): v0.2 treats
|
|
47
|
+
explicit designation as implicit authorization.
|
|
48
|
+
|
|
49
|
+
### Tests
|
|
50
|
+
|
|
51
|
+
- New `test_consumer_project_root.rb` (18 runs, 43 assertions) covering:
|
|
52
|
+
default rule per transport, plausibility check, symlink real-path resolution,
|
|
53
|
+
`PluginProjector` coincidence refusal, and SUSHI-bug regression scenarios.
|
|
54
|
+
- Updated `test_plugin_projector_instruction_mode.rb` for new manifest location.
|
|
55
|
+
- All existing test suites pass (186 total across `test_plugin_projector.rb`,
|
|
56
|
+
`test_plugin_projector_instruction_mode.rb`, `test_capability.rb`,
|
|
57
|
+
`test_skillset_manager.rb`, `test_consumer_project_root.rb`).
|
|
58
|
+
|
|
7
59
|
## [3.25.2] - 2026-05-07
|
|
8
60
|
|
|
9
61
|
### Changed (L1 knowledge: reviewer evaluation feedback loop)
|
data/bin/kairos-chain
CHANGED
|
@@ -370,9 +370,18 @@ when 'mode'
|
|
|
370
370
|
region from CLAUDE.md. Manifest is cleared.
|
|
371
371
|
|
|
372
372
|
Options:
|
|
373
|
-
--data-dir DIR
|
|
373
|
+
--data-dir DIR Override the .kairos/ data directory location.
|
|
374
|
+
--project-root DIR Consumer project root (where CLAUDE.md and .claude/
|
|
375
|
+
are written). Resolution order: --project-root,
|
|
376
|
+
KAIROS_PROJECT_ROOT env, then cwd if it contains a
|
|
377
|
+
recognizable project marker (CLAUDE.md, .git/,
|
|
378
|
+
.claude/, or prior projection manifest).
|
|
374
379
|
|
|
375
380
|
Notes:
|
|
381
|
+
- data_dir and consumer project root are decoupled (design v0.2,
|
|
382
|
+
log/20260512_consumer_project_root_separation_design_v0.2.md).
|
|
383
|
+
When --data-dir points outside the consumer workspace, you MUST
|
|
384
|
+
set --project-root (or KAIROS_PROJECT_ROOT) explicitly.
|
|
376
385
|
- The active mode is read from `instructions_mode` in
|
|
377
386
|
.kairos/skills/config.yml. Use `instructions_update` MCP tool
|
|
378
387
|
to change it; then re-run `mode project`.
|
|
@@ -394,12 +403,35 @@ when 'mode'
|
|
|
394
403
|
ARGV.delete_at(idx)
|
|
395
404
|
end
|
|
396
405
|
|
|
406
|
+
# v0.2: Honor --project-root if present (consumer project root, distinct from data_dir)
|
|
407
|
+
if (idx = ARGV.index('--project-root'))
|
|
408
|
+
KairosMcp.consumer_project_root = File.expand_path(ARGV[idx + 1])
|
|
409
|
+
ARGV.delete_at(idx + 1)
|
|
410
|
+
ARGV.delete_at(idx)
|
|
411
|
+
end
|
|
412
|
+
|
|
397
413
|
require 'kairos_mcp/skills_config'
|
|
398
414
|
require 'kairos_mcp/plugin_projector'
|
|
399
415
|
|
|
400
|
-
|
|
416
|
+
# v0.2: resolve consumer_project_root via the documented order (CLI → env → cwd default).
|
|
417
|
+
# CLI-direct transport is implicit here (we are running from the user's shell).
|
|
418
|
+
project_root = KairosMcp.resolve_consumer_project_root(transport: :cli_direct)
|
|
419
|
+
if project_root.nil?
|
|
420
|
+
warn "ERROR: no plausible consumer project root resolved."
|
|
421
|
+
warn " Set explicitly with --project-root <path> or KAIROS_PROJECT_ROOT env var,"
|
|
422
|
+
warn " or run from a directory containing a recognizable project marker"
|
|
423
|
+
warn " (CLAUDE.md, .git/, .claude/, or a prior .kairos/projection_manifest.json)."
|
|
424
|
+
exit 1
|
|
425
|
+
end
|
|
426
|
+
|
|
401
427
|
projector_mode = KairosMcp.projection_mode
|
|
402
|
-
|
|
428
|
+
begin
|
|
429
|
+
projector = KairosMcp::PluginProjector.new(project_root, mode: projector_mode, data_dir: KairosMcp.data_dir)
|
|
430
|
+
rescue KairosMcp::PluginProjector::CoincidenceRefused => e
|
|
431
|
+
warn "ERROR: #{e.message}"
|
|
432
|
+
warn " Use --project-root <path> to designate a directory distinct from the data directory."
|
|
433
|
+
exit 1
|
|
434
|
+
end
|
|
403
435
|
|
|
404
436
|
case mode_action
|
|
405
437
|
when 'project'
|
|
@@ -433,17 +465,21 @@ when 'mode'
|
|
|
433
465
|
end
|
|
434
466
|
|
|
435
467
|
puts "Instruction mode projected:"
|
|
436
|
-
puts " mode
|
|
437
|
-
puts " source
|
|
438
|
-
puts "
|
|
439
|
-
puts "
|
|
440
|
-
puts "
|
|
468
|
+
puts " mode : #{instructions_mode}#{version ? " v#{version}" : ''}"
|
|
469
|
+
puts " body source : #{body_path}"
|
|
470
|
+
puts " project root : #{project_root} (source: #{KairosMcp.consumer_project_root_source})"
|
|
471
|
+
puts " data dir : #{KairosMcp.data_dir}"
|
|
472
|
+
puts " artifact : #{result[:artifact_path]}"
|
|
473
|
+
puts " size : #{result[:size_bytes]} bytes"
|
|
474
|
+
puts " CLAUDE.md region : #{result[:region_written] ? 'updated' : 'not updated (host file outside project root or unsafe)'}"
|
|
441
475
|
puts ""
|
|
442
476
|
puts "Restart Claude Code to apply (CLAUDE.md @-imports resolve at session start)."
|
|
443
477
|
exit 0
|
|
444
478
|
|
|
445
479
|
when 'status'
|
|
446
480
|
s = projector.instruction_mode_status
|
|
481
|
+
puts "Project root : #{project_root} (source: #{KairosMcp.consumer_project_root_source})"
|
|
482
|
+
puts "Data dir : #{KairosMcp.data_dir}"
|
|
447
483
|
if s[:active]
|
|
448
484
|
puts "Instruction mode projection: ACTIVE"
|
|
449
485
|
puts " mode : #{s[:mode_name]}#{s[:mode_version] ? " v#{s[:mode_version]}" : ''}"
|
|
@@ -481,6 +517,10 @@ OptionParser.new do |opts|
|
|
|
481
517
|
options[:data_dir] = dir
|
|
482
518
|
end
|
|
483
519
|
|
|
520
|
+
opts.on('--project-root DIR', 'Consumer project root for projection artifacts (default: KAIROS_PROJECT_ROOT env, then cwd with plausibility check)') do |dir|
|
|
521
|
+
options[:project_root] = dir
|
|
522
|
+
end
|
|
523
|
+
|
|
484
524
|
opts.on('--http', 'Start in Streamable HTTP mode (default: stdio)') do
|
|
485
525
|
options[:http] = true
|
|
486
526
|
end
|
|
@@ -556,6 +596,11 @@ if options[:data_dir]
|
|
|
556
596
|
KairosMcp.data_dir = File.expand_path(options[:data_dir])
|
|
557
597
|
end
|
|
558
598
|
|
|
599
|
+
# v0.2: Set consumer_project_root if specified via CLI
|
|
600
|
+
if options[:project_root]
|
|
601
|
+
KairosMcp.consumer_project_root = File.expand_path(options[:project_root])
|
|
602
|
+
end
|
|
603
|
+
|
|
559
604
|
# Handle --version
|
|
560
605
|
if options[:version]
|
|
561
606
|
puts "KairosChain MCP Server v#{KairosMcp::VERSION}"
|
|
@@ -27,14 +27,33 @@ module KairosMcp
|
|
|
27
27
|
INSTRUCTION_MODE_SIZE_WARN = 150 * 1024
|
|
28
28
|
INSTRUCTION_MODE_SIZE_REFUSE = 256 * 1024
|
|
29
29
|
|
|
30
|
-
attr_reader :mode, :project_root, :output_root
|
|
30
|
+
attr_reader :mode, :project_root, :output_root, :data_dir
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
# Construct a PluginProjector.
|
|
33
|
+
#
|
|
34
|
+
# @param project_root [String] consumer project root (where .claude/ and CLAUDE.md live)
|
|
35
|
+
# @param mode [Symbol] :auto, :project, or :plugin
|
|
36
|
+
# @param data_dir [String, nil] KairosChain data directory. When provided, the
|
|
37
|
+
# projector enforces design v0.2 Inv 3: refuses construction when
|
|
38
|
+
# real_path(project_root) == real_path(data_dir). When nil, the legacy assumption
|
|
39
|
+
# data_dir = project_root/.kairos is used for manifest location (backward-compat).
|
|
40
|
+
#
|
|
41
|
+
# @raise [CoincidenceRefused] when project_root and data_dir resolve to the same real path
|
|
42
|
+
def initialize(project_root, mode: :auto, data_dir: nil)
|
|
33
43
|
@project_root = project_root
|
|
44
|
+
@data_dir = data_dir || File.join(project_root, '.kairos')
|
|
45
|
+
enforce_no_coincidence!
|
|
34
46
|
@mode = resolve_mode(mode)
|
|
35
47
|
@output_root = @mode == :plugin ? project_root : File.join(project_root, '.claude')
|
|
36
|
-
@manifest_path = File.join(
|
|
37
|
-
@instruction_mode_manifest_path = File.join(
|
|
48
|
+
@manifest_path = File.join(@data_dir, 'projection_manifest.json')
|
|
49
|
+
@instruction_mode_manifest_path = File.join(@data_dir, 'instruction_mode_manifest.json')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Raised when project_root and data_dir resolve to the same real path (design Inv 3).
|
|
53
|
+
class CoincidenceRefused < StandardError
|
|
54
|
+
def initialize(path)
|
|
55
|
+
super("consumer project root and data directory coincide at real path #{path.inspect} (design Inv 3): explicit configuration required")
|
|
56
|
+
end
|
|
38
57
|
end
|
|
39
58
|
|
|
40
59
|
# Main entry: project all SkillSet plugin artifacts + L1 knowledge meta skill
|
|
@@ -185,6 +204,23 @@ module KairosMcp
|
|
|
185
204
|
:project
|
|
186
205
|
end
|
|
187
206
|
|
|
207
|
+
# Inv 3 enforcement: refuse if project_root and data_dir resolve to the same
|
|
208
|
+
# real path. Comparison happens post-realpath (design Inv 8). Non-existent
|
|
209
|
+
# paths fall back to expand_path; coincidence at expand_path level still counts.
|
|
210
|
+
def enforce_no_coincidence!
|
|
211
|
+
pr_real = canonicalize(@project_root)
|
|
212
|
+
dd_real = canonicalize(@data_dir)
|
|
213
|
+
return unless pr_real && dd_real && pr_real == dd_real
|
|
214
|
+
raise CoincidenceRefused, pr_real
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def canonicalize(path)
|
|
218
|
+
return nil if path.nil?
|
|
219
|
+
File.realpath(File.expand_path(path))
|
|
220
|
+
rescue Errno::ENOENT
|
|
221
|
+
File.expand_path(path)
|
|
222
|
+
end
|
|
223
|
+
|
|
188
224
|
# =========================================================================
|
|
189
225
|
# Skill projection
|
|
190
226
|
# =========================================================================
|
data/lib/kairos_mcp/protocol.rb
CHANGED
|
@@ -137,12 +137,27 @@ module KairosMcp
|
|
|
137
137
|
|
|
138
138
|
# Project plugin artifacts (only if .claude/ exists — avoids creating
|
|
139
139
|
# Claude Code artifacts for non-Claude clients like Cursor or Codex)
|
|
140
|
-
|
|
140
|
+
#
|
|
141
|
+
# Design v0.2: resolve consumer_project_root explicitly. Skip projection if
|
|
142
|
+
# no plausible root is available (Inv 5: graceful skip). Refuse on coincidence
|
|
143
|
+
# with data_dir (Inv 3, raised by PluginProjector constructor).
|
|
144
|
+
project_root = KairosMcp.consumer_project_root
|
|
145
|
+
if project_root.nil?
|
|
146
|
+
warn "[PluginProjector] no plausible consumer project root resolved " \
|
|
147
|
+
"(source: #{KairosMcp.consumer_project_root_source}); projection skipped"
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
141
151
|
mode = KairosMcp.projection_mode
|
|
142
152
|
output_root = mode == :plugin ? project_root : File.join(project_root, '.claude')
|
|
143
153
|
return unless File.directory?(output_root)
|
|
144
154
|
|
|
145
|
-
|
|
155
|
+
begin
|
|
156
|
+
projector = PluginProjector.new(project_root, mode: mode, data_dir: KairosMcp.data_dir)
|
|
157
|
+
rescue PluginProjector::CoincidenceRefused => e
|
|
158
|
+
warn "[PluginProjector] #{e.message}; projection skipped"
|
|
159
|
+
return
|
|
160
|
+
end
|
|
146
161
|
enabled = manager.enabled_skillsets
|
|
147
162
|
knowledge_entries = collect_knowledge_entries
|
|
148
163
|
|
|
@@ -229,8 +244,9 @@ module KairosMcp
|
|
|
229
244
|
end
|
|
230
245
|
|
|
231
246
|
# True if the active instruction mode has been projected for this project.
|
|
247
|
+
# v0.2: manifest now lives at data_dir level (decoupled from project_root).
|
|
232
248
|
def instruction_mode_projected?(mode)
|
|
233
|
-
manifest_path = File.join(KairosMcp.
|
|
249
|
+
manifest_path = File.join(KairosMcp.data_dir, 'instruction_mode_manifest.json')
|
|
234
250
|
return false unless File.exist?(manifest_path)
|
|
235
251
|
data = JSON.parse(File.read(manifest_path))
|
|
236
252
|
data['mode_name'] == mode && data['region_present']
|
data/lib/kairos_mcp/version.rb
CHANGED
data/lib/kairos_mcp.rb
CHANGED
|
@@ -276,12 +276,132 @@ module KairosMcp
|
|
|
276
276
|
end
|
|
277
277
|
|
|
278
278
|
# =========================================================================
|
|
279
|
-
# Plugin Projection support
|
|
279
|
+
# Plugin Projection support — consumer project root
|
|
280
280
|
# =========================================================================
|
|
281
|
+
#
|
|
282
|
+
# Design v0.2 (log/20260512_consumer_project_root_separation_design_v0.2.md):
|
|
283
|
+
# consumer_project_root is decoupled from data_dir. Resolution order:
|
|
284
|
+
# 1. Explicit setter (CLI flag --project-root)
|
|
285
|
+
# 2. Environment variable KAIROS_PROJECT_ROOT
|
|
286
|
+
# 3. Per-transport default with plausibility check:
|
|
287
|
+
# stdio_mcp / cli_direct: Dir.pwd if plausible
|
|
288
|
+
# http_mcp: no default (returns nil; caller must refuse projection)
|
|
289
|
+
#
|
|
290
|
+
# See design §2 (invariants) and §6 (failure taxonomy).
|
|
291
|
+
|
|
292
|
+
PLAUSIBILITY_MARKERS = ['CLAUDE.md', '.git', '.claude',
|
|
293
|
+
File.join('.kairos', 'projection_manifest.json')].freeze
|
|
294
|
+
|
|
295
|
+
# Consumer project root: where projection artifacts (CLAUDE.md, .claude/) are written.
|
|
296
|
+
# Distinct from data_dir (where KairosChain state lives).
|
|
297
|
+
#
|
|
298
|
+
# @return [String, nil] absolute real path, or nil if no plausible root is available
|
|
299
|
+
def consumer_project_root
|
|
300
|
+
resolve_consumer_project_root unless defined?(@consumer_project_root_resolved) && @consumer_project_root_resolved
|
|
301
|
+
@consumer_project_root
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Source of the currently resolved consumer_project_root.
|
|
305
|
+
# @return [Symbol] :explicit_cli, :explicit_env, :transport_default, :absent
|
|
306
|
+
def consumer_project_root_source
|
|
307
|
+
consumer_project_root # trigger resolution
|
|
308
|
+
@consumer_project_root_source || :absent
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Explicit setter (used by CLI --project-root flag).
|
|
312
|
+
# Setting to nil clears any cached resolution.
|
|
313
|
+
def consumer_project_root=(path)
|
|
314
|
+
if path.nil?
|
|
315
|
+
@consumer_project_root = nil
|
|
316
|
+
@consumer_project_root_source = :absent
|
|
317
|
+
else
|
|
318
|
+
@consumer_project_root = real_path(File.expand_path(path))
|
|
319
|
+
@consumer_project_root_source = :explicit_cli
|
|
320
|
+
end
|
|
321
|
+
@consumer_project_root_resolved = true
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Reset resolution cache (for testing or re-initialization).
|
|
325
|
+
def reset_consumer_project_root!
|
|
326
|
+
@consumer_project_root = nil
|
|
327
|
+
@consumer_project_root_source = nil
|
|
328
|
+
@consumer_project_root_resolved = false
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Resolve consumer_project_root following the documented order.
|
|
332
|
+
# @param transport [Symbol] :stdio_mcp, :http_mcp, :cli_direct (default: auto-detect)
|
|
333
|
+
# @return [String, nil] resolved absolute real path, or nil
|
|
334
|
+
def resolve_consumer_project_root(transport: nil)
|
|
335
|
+
# Skip env/default lookup if explicit setter was used
|
|
336
|
+
if @consumer_project_root_source == :explicit_cli && @consumer_project_root
|
|
337
|
+
@consumer_project_root_resolved = true
|
|
338
|
+
return @consumer_project_root
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
transport ||= detect_transport
|
|
342
|
+
|
|
343
|
+
# 1. Environment variable
|
|
344
|
+
if (env_val = ENV['KAIROS_PROJECT_ROOT']) && !env_val.empty?
|
|
345
|
+
@consumer_project_root = real_path(File.expand_path(env_val))
|
|
346
|
+
@consumer_project_root_source = :explicit_env
|
|
347
|
+
@consumer_project_root_resolved = true
|
|
348
|
+
return @consumer_project_root
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# 2. Transport default
|
|
352
|
+
if transport == :http_mcp
|
|
353
|
+
# HTTP MCP: no default permitted (design §4)
|
|
354
|
+
@consumer_project_root = nil
|
|
355
|
+
@consumer_project_root_source = :absent
|
|
356
|
+
@consumer_project_root_resolved = true
|
|
357
|
+
return nil
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# stdio_mcp / cli_direct: cwd default with plausibility
|
|
361
|
+
candidate = real_path(Dir.pwd)
|
|
362
|
+
if plausibility_check(candidate)
|
|
363
|
+
@consumer_project_root = candidate
|
|
364
|
+
@consumer_project_root_source = :transport_default
|
|
365
|
+
else
|
|
366
|
+
@consumer_project_root = nil
|
|
367
|
+
@consumer_project_root_source = :absent
|
|
368
|
+
end
|
|
369
|
+
@consumer_project_root_resolved = true
|
|
370
|
+
@consumer_project_root
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Plausibility predicate (design Inv 6 / §11).
|
|
374
|
+
# Candidate passes if any recognizable project marker exists at the path.
|
|
375
|
+
def plausibility_check(path)
|
|
376
|
+
return false if path.nil? || path.empty?
|
|
377
|
+
return false unless Dir.exist?(path)
|
|
378
|
+
PLAUSIBILITY_MARKERS.any? do |marker|
|
|
379
|
+
candidate = File.join(path, marker)
|
|
380
|
+
File.exist?(candidate) || Dir.exist?(candidate)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Resolve a path to its real path (symlinks resolved). Falls back to expand_path
|
|
385
|
+
# for non-existent paths.
|
|
386
|
+
def real_path(path)
|
|
387
|
+
File.realpath(File.expand_path(path))
|
|
388
|
+
rescue Errno::ENOENT
|
|
389
|
+
File.expand_path(path)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Detect current transport mode based on runtime state.
|
|
393
|
+
# @return [Symbol] :http_mcp or :stdio_mcp
|
|
394
|
+
def detect_transport
|
|
395
|
+
return :http_mcp if @http_server
|
|
396
|
+
:stdio_mcp
|
|
397
|
+
end
|
|
281
398
|
|
|
282
|
-
#
|
|
399
|
+
# DEPRECATED in v0.2: parent-of-data-dir derivation.
|
|
400
|
+
# Returns consumer_project_root when available, falling back to File.dirname(data_dir)
|
|
401
|
+
# only for backward compatibility with internal callers that have not yet been
|
|
402
|
+
# migrated. New code should use consumer_project_root and handle nil explicitly.
|
|
283
403
|
def project_root
|
|
284
|
-
File.dirname(data_dir)
|
|
404
|
+
consumer_project_root || File.dirname(data_dir)
|
|
285
405
|
end
|
|
286
406
|
|
|
287
407
|
# Determine projection mode for PluginProjector
|
|
@@ -289,7 +409,7 @@ module KairosMcp
|
|
|
289
409
|
# :plugin — writes to plugin root skills/, agents/, hooks/hooks.json
|
|
290
410
|
def projection_mode
|
|
291
411
|
return :plugin if ENV['KAIROS_PROJECTION_MODE'] == 'plugin'
|
|
292
|
-
root =
|
|
412
|
+
root = consumer_project_root || File.dirname(data_dir)
|
|
293
413
|
plugin_json = File.join(root, '.claude-plugin', 'plugin.json')
|
|
294
414
|
claude_dir = File.join(root, '.claude')
|
|
295
415
|
return :plugin if File.exist?(plugin_json) && !File.exist?(claude_dir)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kairos_hook_projector
|
|
3
|
+
description: >
|
|
4
|
+
Compile mode_hooks definitions into the plugin_projector pipeline.
|
|
5
|
+
Use to inspect mode_hooks status (read-only in stage 0).
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Kairos Hook Projector
|
|
9
|
+
|
|
10
|
+
Rides the existing `plugin_projector` pipeline as a compiler layer:
|
|
11
|
+
mode_hooks YAML → `plugin/hooks.json` → `.claude/settings.json` (via `plugin_projector`).
|
|
12
|
+
|
|
13
|
+
## Stage
|
|
14
|
+
|
|
15
|
+
v0.1 stage 0: skeleton + schema + `hooks_status` (read-only). Zero side effect.
|
|
16
|
+
Later stages add compile / project / unproject / composition.
|
|
17
|
+
|
|
18
|
+
## Design
|
|
19
|
+
|
|
20
|
+
See `docs/drafts/kairos_hook_projector_design_v0.2_draft.md` (frozen).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kairos_hook_projector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Compiles mode_hooks definitions into the plugin_projector hook pipeline. Enables deterministic invocation paths for KairosChain MCP tools via Claude Code hooks. v0.1 stage 0: skeleton + schema + read-only status tool, zero side effect on .claude/settings.json. See docs/drafts/kairos_hook_projector_design_v0.2_draft.md.",
|
|
5
|
+
"author": "Masaomi Hatakeyama",
|
|
6
|
+
"layer": "L1",
|
|
7
|
+
"depends_on": [
|
|
8
|
+
{
|
|
9
|
+
"name": "plugin_projector"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"provides": [
|
|
13
|
+
"hook_compilation",
|
|
14
|
+
"mode_hooks_schema",
|
|
15
|
+
"hooks_status_readonly"
|
|
16
|
+
],
|
|
17
|
+
"tool_classes": [],
|
|
18
|
+
"config_files": [],
|
|
19
|
+
"knowledge_dirs": [],
|
|
20
|
+
"min_core_version": "3.25.0"
|
|
21
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
class TestKairosHookProjectorSkillsetJson < Minitest::Test
|
|
7
|
+
SKILLSET_ROOT = File.expand_path('..', __dir__)
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@path = File.join(SKILLSET_ROOT, 'skillset.json')
|
|
11
|
+
@json = JSON.parse(File.read(@path))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_skillset_json_parses
|
|
15
|
+
assert_kind_of Hash, @json
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_name_is_kairos_hook_projector
|
|
19
|
+
assert_equal 'kairos_hook_projector', @json['name']
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_layer_is_l1
|
|
23
|
+
assert_equal 'L1', @json['layer']
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_depends_on_plugin_projector
|
|
27
|
+
deps = @json['depends_on']
|
|
28
|
+
assert_kind_of Array, deps
|
|
29
|
+
names = deps.map { |d| d['name'] }
|
|
30
|
+
assert_includes names, 'plugin_projector'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_plugin_skill_md_exists
|
|
34
|
+
skill_md = File.join(SKILLSET_ROOT, 'plugin', 'SKILL.md')
|
|
35
|
+
assert File.exist?(skill_md), "plugin/SKILL.md must exist"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_plugin_hooks_json_is_empty_object
|
|
39
|
+
hooks_json = File.join(SKILLSET_ROOT, 'plugin', 'hooks.json')
|
|
40
|
+
assert File.exist?(hooks_json), "plugin/hooks.json must exist"
|
|
41
|
+
parsed = JSON.parse(File.read(hooks_json))
|
|
42
|
+
assert_equal({ 'hooks' => {} }, parsed,
|
|
43
|
+
'stage 0: hooks.json must be {"hooks":{}} — no projections yet')
|
|
44
|
+
end
|
|
45
|
+
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.26.0
|
|
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-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -411,6 +411,10 @@ 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/plugin/SKILL.md
|
|
415
|
+
- templates/skillsets/kairos_hook_projector/plugin/hooks.json
|
|
416
|
+
- templates/skillsets/kairos_hook_projector/skillset.json
|
|
417
|
+
- templates/skillsets/kairos_hook_projector/test/test_skillset_json.rb
|
|
414
418
|
- templates/skillsets/knowledge_creator/config/knowledge_creator.yml
|
|
415
419
|
- templates/skillsets/knowledge_creator/knowledge/creation_guide/creation_guide.md
|
|
416
420
|
- templates/skillsets/knowledge_creator/knowledge/quality_criteria/quality_criteria.md
|