kairos-chain 3.94.0 → 3.95.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 +71 -0
- data/bin/kairos-chain +2 -0
- data/lib/kairos_mcp/skillset.rb +21 -0
- data/lib/kairos_mcp/skillset_manager.rb +58 -13
- data/lib/kairos_mcp/tools/system_upgrade.rb +2 -0
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/knowledge/design_to_implementation_workflow/design_to_implementation_workflow.md +4 -1
- data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +40 -5
- data/templates/skillsets/agent/bin/agent_harvest.rb +28 -0
- data/templates/skillsets/agent/config/policies/masa_2026_10_20.rb +17 -0
- data/templates/skillsets/agent/hooks/agent_hook.rb +139 -0
- data/templates/skillsets/agent/lib/agent/confinement.rb +107 -239
- data/templates/skillsets/agent/lib/agent/dsl.rb +219 -0
- data/templates/skillsets/agent/lib/agent/guard.rb +799 -0
- data/templates/skillsets/agent/lib/agent/harvest.rb +145 -0
- data/templates/skillsets/agent/lib/agent/hooks.rb +785 -0
- data/templates/skillsets/agent/lib/agent/journal.rb +118 -0
- data/templates/skillsets/agent/lib/agent/policies.rb +153 -0
- data/templates/skillsets/agent/lib/agent/precedents.rb +151 -0
- data/templates/skillsets/agent/lib/agent/review.rb +507 -0
- data/templates/skillsets/agent/lib/agent/run.rb +497 -0
- data/templates/skillsets/agent/lib/agent/service.rb +1661 -0
- data/templates/skillsets/agent/lib/agent/stand_in.rb +274 -0
- data/templates/skillsets/agent/lib/agent/templates.rb +131 -0
- data/templates/skillsets/agent/lib/agent/transcript.rb +134 -0
- data/templates/skillsets/agent/lib/agent.rb +34 -22
- data/templates/skillsets/agent/plugin/SKILL.md +138 -171
- data/templates/skillsets/agent/plugin/hooks.json +94 -0
- data/templates/skillsets/agent/report_kit/KIT_SHA256.txt +5 -0
- data/templates/skillsets/agent/report_kit/check.py +23 -0
- data/templates/skillsets/agent/report_kit/kit.py +364 -0
- data/templates/skillsets/agent/report_kit/make_report_deck.py +159 -0
- data/templates/skillsets/agent/report_kit/render.py +19 -0
- data/templates/skillsets/agent/report_kit/viz.py +203 -0
- data/templates/skillsets/agent/skillset.json +99 -14
- data/templates/skillsets/agent/test/test_agent1_dsl.rb +89 -0
- data/templates/skillsets/agent/test/test_agent1_guard.rb +127 -0
- data/templates/skillsets/agent/test/test_agent1_hooks_service.rb +401 -0
- data/templates/skillsets/agent/test/test_agent1_journal_run.rb +93 -0
- data/templates/skillsets/agent/test/test_agent1_r1_check.rb +571 -0
- data/templates/skillsets/agent/test/test_agent1_r1_guard.rb +475 -0
- data/templates/skillsets/agent/test/test_agent1_r1_hookscript.rb +410 -0
- data/templates/skillsets/agent/test/test_agent1_r1_lifecycle.rb +556 -0
- data/templates/skillsets/agent/test/test_agent1_r1_report.rb +588 -0
- data/templates/skillsets/agent/test/test_agent1_r1_review.rb +689 -0
- data/templates/skillsets/agent/test/test_agent1_r1_words.rb +614 -0
- data/templates/skillsets/agent/test/test_agent1_r1b_fixes.rb +258 -0
- data/templates/skillsets/agent/test/test_agent1_r2_guard.rb +565 -0
- data/templates/skillsets/agent/test/test_agent1_r2_words.rb +349 -0
- data/templates/skillsets/agent/test/test_agent1_r2v_checkreview.rb +725 -0
- data/templates/skillsets/agent/test/test_agent1_r2v_decisions.rb +756 -0
- data/templates/skillsets/agent/test/test_agent1_r2v_progress.rb +539 -0
- data/templates/skillsets/agent/test/test_agent1_r2v_sessions.rb +770 -0
- data/templates/skillsets/agent/test/test_agent1_r3_guard.rb +354 -0
- data/templates/skillsets/agent/test/test_agent1_r3_words.rb +367 -0
- data/templates/skillsets/agent/test/test_agent1_r3v_checkreview.rb +854 -0
- data/templates/skillsets/agent/test/test_agent1_r3v_guardwords.rb +730 -0
- data/templates/skillsets/agent/test/test_agent1_r3v_lifecycle.rb +1096 -0
- data/templates/skillsets/agent/test/test_agent1_selfreview_fixes.rb +75 -0
- data/templates/skillsets/agent/test/test_agent1_stand_in_precedents.rb +113 -0
- data/templates/skillsets/agent/test/test_agent1_v06.rb +1129 -0
- data/templates/skillsets/agent/tools/agent_advance.rb +51 -0
- data/templates/skillsets/agent/tools/agent_finish.rb +50 -0
- data/templates/skillsets/agent/tools/agent_items.rb +56 -0
- data/templates/skillsets/agent/tools/agent_start.rb +17 -224
- data/templates/skillsets/agent/tools/agent_status.rb +9 -89
- data/templates/skillsets/agent/tools/agent_stop.rb +8 -122
- data/templates/skillsets/introspection/lib/introspection/safety_inspector.rb +28 -14
- data/templates/skillsets/llm_client/lib/llm_client/call_router.rb +7 -1
- data/templates/skillsets/llm_client/lib/llm_client/claude_code_adapter.rb +21 -2
- data/templates/skillsets/llm_client/skillset.json +1 -1
- data/templates/skillsets/llm_client/test/test_no_fallback_and_isolation.rb +113 -0
- data/templates/skillsets/llm_client/tools/llm_call.rb +20 -2
- metadata +54 -62
- data/templates/skillsets/agent/bin/agent_distill.rb +0 -124
- data/templates/skillsets/agent/bin/agent_rule.rb +0 -215
- data/templates/skillsets/agent/bin/agent_shadow_judge.rb +0 -70
- data/templates/skillsets/agent/bin/agent_step_worker.rb +0 -321
- data/templates/skillsets/agent/config/agent.yml +0 -171
- data/templates/skillsets/agent/lib/agent/act_classification.rb +0 -336
- data/templates/skillsets/agent/lib/agent/act_classification.yml +0 -54
- data/templates/skillsets/agent/lib/agent/admission.rb +0 -384
- data/templates/skillsets/agent/lib/agent/adoption.rb +0 -340
- data/templates/skillsets/agent/lib/agent/advance_gate.rb +0 -357
- data/templates/skillsets/agent/lib/agent/answer_ruling.rb +0 -491
- data/templates/skillsets/agent/lib/agent/cognitive_loop.rb +0 -452
- data/templates/skillsets/agent/lib/agent/delegation.rb +0 -372
- data/templates/skillsets/agent/lib/agent/delegation_table.yml +0 -44
- data/templates/skillsets/agent/lib/agent/distill_run.rb +0 -197
- data/templates/skillsets/agent/lib/agent/distiller.rb +0 -313
- data/templates/skillsets/agent/lib/agent/distiller_prompt.rb +0 -130
- data/templates/skillsets/agent/lib/agent/mandate_adapter.rb +0 -69
- data/templates/skillsets/agent/lib/agent/mediator.rb +0 -211
- data/templates/skillsets/agent/lib/agent/message_format.rb +0 -33
- data/templates/skillsets/agent/lib/agent/native_body/main.rb +0 -166
- data/templates/skillsets/agent/lib/agent/native_body/model_client.rb +0 -135
- data/templates/skillsets/agent/lib/agent/native_body/spend_meter.rb +0 -131
- data/templates/skillsets/agent/lib/agent/native_body/tool_layer.rb +0 -195
- data/templates/skillsets/agent/lib/agent/point_facts.rb +0 -231
- data/templates/skillsets/agent/lib/agent/review_hint.rb +0 -84
- data/templates/skillsets/agent/lib/agent/session.rb +0 -282
- data/templates/skillsets/agent/lib/agent/shadow_judge.rb +0 -564
- data/templates/skillsets/agent/lib/agent/staging.rb +0 -267
- data/templates/skillsets/agent/lib/agent/step_delegation.rb +0 -450
- data/templates/skillsets/agent/lib/agent/trigger_validator.rb +0 -69
- data/templates/skillsets/agent/lib/agent/verdict.rb +0 -177
- data/templates/skillsets/agent/plugin/agents/monitor.md +0 -24
- data/templates/skillsets/agent/test/test_agent_act_hardening.rb +0 -488
- data/templates/skillsets/agent/test/test_agent_admission.rb +0 -112
- data/templates/skillsets/agent/test/test_agent_allowlist.rb +0 -724
- data/templates/skillsets/agent/test/test_agent_answer_rulings.rb +0 -949
- data/templates/skillsets/agent/test/test_agent_capability_discovery.rb +0 -307
- data/templates/skillsets/agent/test/test_agent_complexity_review.rb +0 -852
- data/templates/skillsets/agent/test/test_agent_confinement.rb +0 -184
- data/templates/skillsets/agent/test/test_agent_distillation.rb +0 -539
- data/templates/skillsets/agent/test/test_agent_guard_integration.rb +0 -218
- data/templates/skillsets/agent/test/test_agent_guard_verdict_record.rb +0 -446
- data/templates/skillsets/agent/test/test_agent_halt_semantics.rb +0 -370
- data/templates/skillsets/agent/test/test_agent_human_mark_gate.rb +0 -486
- data/templates/skillsets/agent/test/test_agent_interruption_resilience.rb +0 -729
- data/templates/skillsets/agent/test/test_agent_m1.rb +0 -329
- data/templates/skillsets/agent/test/test_agent_m2.rb +0 -625
- data/templates/skillsets/agent/test/test_agent_m3.rb +0 -719
- data/templates/skillsets/agent/test/test_agent_m4.rb +0 -545
- data/templates/skillsets/agent/test/test_agent_native_body.rb +0 -788
- data/templates/skillsets/agent/test/test_agent_shadow_judge.rb +0 -1233
- data/templates/skillsets/agent/test/test_agent_verdict.rb +0 -119
- data/templates/skillsets/agent/test/test_agent_worker_exit_and_review_parse.rb +0 -622
- data/templates/skillsets/agent/test/test_agent_worker_process.rb +0 -386
- data/templates/skillsets/agent/test/test_decide_prompt_contract.rb +0 -158
- data/templates/skillsets/agent/test/test_review_hint.rb +0 -168
- data/templates/skillsets/agent/tools/agent_execute.rb +0 -627
- data/templates/skillsets/agent/tools/agent_step.rb +0 -3909
- data/templates/skillsets/agent/tools/agent_wait.rb +0 -195
- data/templates/skillsets/agent/tools/operator_report.rb +0 -130
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3bce3f299f0bb91ecbb6199e464a2ea24602ede6e9d4e972c1a7b5905ec447c5
|
|
4
|
+
data.tar.gz: 4986b9e4f8c87ff233b67586791d8b3068aed4decaecb1319f5c0553b4a5445c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4af517774c9e5e90299d4877190d83659932acf7851bf04275d5c3066298411ca628084a490cfef6348b327d550c05d5cc4f96cab086e5c297e2a169e1f72b3
|
|
7
|
+
data.tar.gz: ba67dc90461e4350615a4ba9e232702efc27b241fa853a2934a80714bb5753c2aa1ebba06921ebe58e4db7145d8f3fb5d747be4ab37f8ad5da25150ab8c7ce8d
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,77 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [3.95.0] - 2026-10-21
|
|
10
|
+
|
|
11
|
+
### Changed — the `agent` SkillSet is rebuilt (1.0): long runs in the host session, a stand-in instead of terminal approvals
|
|
12
|
+
|
|
13
|
+
The 0.x agent could not carry the operator's ordinary request (design → design review → implement →
|
|
14
|
+
implementation review): its act route wrote only `docs/drafts/draft_*`, could not reach multi-LLM review or
|
|
15
|
+
tests, and counted only terminal-attested answers (0 counted pairs). Design:
|
|
16
|
+
`log/agent_skillset_rebuild_design_v0.5.1_20261020.md` (five review rounds, frozen as the author's proposal)
|
|
17
|
+
and its delta `log/agent_skillset_rebuild_design_v0.6.3_20261020.md`.
|
|
18
|
+
|
|
19
|
+
- The host session (Claude Code) is the only body. The SkillSet supplies a plan from a Ruby-DSL template, a
|
|
20
|
+
stand-in (isolated `claude -p --safe-mode --tools ""`, Opus 5.5) that answers decision points from the
|
|
21
|
+
operator's own words and the instruction mode, a guard (host hooks: `PreToolUse` on every tool, fail-closed)
|
|
22
|
+
that lets through only what the run created or its worktree and branch hold, a hash-chained journal, and a
|
|
23
|
+
report check. Six tools: `agent_start`, `agent_status`, `agent_advance`, `agent_items`, `agent_finish`,
|
|
24
|
+
`agent_stop`.
|
|
25
|
+
- Runs need an adopted policy (the operator's mandate, `config/policies/*.rb`), adopted by the operator's own
|
|
26
|
+
typed words naming its digest; the adoption is recorded on the chain.
|
|
27
|
+
- Templates: `dev_cycle`, `impl_cycle`, `design_only`, `doc_cycle` (a document kept in git, revised in place,
|
|
28
|
+
optional build check), `survey` (versioned survey files from local sources), `general`.
|
|
29
|
+
- A review closes only when no (a)/(b) P0 is open. Each review may take the policy's `review_rounds`
|
|
30
|
+
(10), counted for that review alone; at the cap it waits for the operator, who grants more rounds or
|
|
31
|
+
freezes it. Revisions follow L1 multi_llm_review_workflow § Revision
|
|
32
|
+
Discipline: each round says which new P0s the actor's last addition drew, and the next step is to undo it.
|
|
33
|
+
Design reviews stay at the design level; implementation-only findings are deferred to the implementation
|
|
34
|
+
review and are operator items at once.
|
|
35
|
+
- Every run ends with a layered-deck report (`report_kit/`, the kit of L1 `layered_visual_pitch_deck`).
|
|
36
|
+
- `bin/agent_harvest.rb` harvests the operator's own answers from host transcripts (hook output, peer
|
|
37
|
+
messages and pasted host output excluded).
|
|
38
|
+
- Removed: the in-server OODA loop, act-route table and terminal rulings, shadow judge, distillation,
|
|
39
|
+
native body, `agent_execute`, `agent_step`, `agent_wait`, `operator_report`, the `agent-monitor` subagent.
|
|
40
|
+
- `agent` is no longer installed automatically in new instances (it needs a policy and projects hooks).
|
|
41
|
+
An installed 0.x agent becomes 1.0 on upgrade. 1.0 needs `multi_llm_review` and `llm_client` >= 0.3.0 in
|
|
42
|
+
the same instance; upgrade does not install a missing one (`kairos-chain skillset install multi_llm_review`).
|
|
43
|
+
|
|
44
|
+
### Added — `removed_files` in `skillset.json`; `system_upgrade` archives them
|
|
45
|
+
|
|
46
|
+
`upgrade_apply` copied changed files and never deleted one, so a SkillSet that dropped files left them to be
|
|
47
|
+
loaded. A template may now list `removed_files`; upgrade moves each from the installed copy to
|
|
48
|
+
`storage/skillset_removed/<name>/<time>/`, and a forced reinstall no longer restores a retired config file.
|
|
49
|
+
|
|
50
|
+
### Added — `llm_call` options `no_fallback` and `isolated`
|
|
51
|
+
|
|
52
|
+
`no_fallback` makes an authentication failure on the configured provider a failure instead of a silent switch
|
|
53
|
+
to `claude_code`. `isolated` (claude_code) runs `claude -p --safe-mode --tools ""` in an empty directory, so
|
|
54
|
+
no CLAUDE.md, skills, plugins, hooks, memory or MCP servers reach the call. An isolated call's payload carries
|
|
55
|
+
`isolation` (`safe_mode`, `tools`, `cwd`, as `claude -p` was spawned); llm_client is 0.3.0. The agent's
|
|
56
|
+
stand-in refuses any reply without that evidence (and refuses to call an llm_client too old to isolate), and the
|
|
57
|
+
agent SkillSet declares `llm_client >= 0.3.0`; upgrade both together.
|
|
58
|
+
|
|
59
|
+
### Added — `user_level_conflicts` in `skillset.json`; install and upgrade report them
|
|
60
|
+
|
|
61
|
+
A SkillSet may list files an earlier version left at user level that it cannot remove itself (e.g. a personal
|
|
62
|
+
skill that shadows the project's). `install`, `upgrade_apply`, `system_upgrade` and `kairos-chain skillset
|
|
63
|
+
install/upgrade` print the ones present as warnings, with a command that moves them to a timestamped backup.
|
|
64
|
+
agent 1.0 lists the 0.x personal skill `~/.claude/skills/agent/SKILL.md` and `~/.claude/agents/agent-monitor.md`.
|
|
65
|
+
|
|
66
|
+
### Changed — the shipped agent policy needs a new adoption
|
|
67
|
+
|
|
68
|
+
`masa_2026_10_20.rb` sets `review_rounds` to 10 rounds for each review (counted apart, no longer shared by a
|
|
69
|
+
run's reviews) and drops the lookup consent comment (runs never look things up on the web). Its digest
|
|
70
|
+
changed: adopt it with `adopt policy masa_2026_10_20 8846b67e`.
|
|
71
|
+
|
|
72
|
+
### Changed — L1 `multi_llm_review_workflow` 3.16.0 and `design_to_implementation_workflow` 1.3
|
|
73
|
+
|
|
74
|
+
A new entry-level clause, § Provisional delegation under a mandate: under a pinned, journaled operator policy a
|
|
75
|
+
named delegate — never the context whose work is judged — answers reserved judgments provisionally, and nothing
|
|
76
|
+
resolves, closes, freezes or demotes beyond the run until the operator confirms it in their own words. Pointers
|
|
77
|
+
from INV-U2, INV-U4, INV-U5, Step 0.25 and § Convergence Rules, and from the merge criteria of
|
|
78
|
+
`design_to_implementation_workflow`. An instance's own L1 copy is not rewritten by upgrade.
|
|
79
|
+
|
|
9
80
|
## [3.94.0] - 2026-10-20
|
|
10
81
|
|
|
11
82
|
### Added — L0 changes proposed for later approval, L0 restores on the record, L1 body history
|
data/bin/kairos-chain
CHANGED
|
@@ -102,6 +102,7 @@ when 'skillset'
|
|
|
102
102
|
result = manager.install(source, layer_override: layer_override, force: force)
|
|
103
103
|
puts "Installed SkillSet '#{result[:name]}' v#{result[:version]} [#{result[:layer]}]"
|
|
104
104
|
puts "Path: #{result[:path]}"
|
|
105
|
+
manager.user_level_conflicts(KairosMcp::Skillset.new(result[:path])).each { |w| puts " [WARNING] #{w}" }
|
|
105
106
|
|
|
106
107
|
when 'enable'
|
|
107
108
|
name = ARGV.shift
|
|
@@ -162,6 +163,7 @@ when 'skillset'
|
|
|
162
163
|
puts "## SkillSets"
|
|
163
164
|
results.each do |r|
|
|
164
165
|
puts " [UPGRADED] #{r[:name].ljust(22)} v#{r[:from]} -> v#{r[:to]} (#{r[:files_updated]} files)"
|
|
166
|
+
Array(r[:warnings]).each { |w| puts " [WARNING] #{w}" }
|
|
165
167
|
end
|
|
166
168
|
checkable.reject { |ss| upgraded_names.include?(ss.name) }.each do |ss|
|
|
167
169
|
puts " [UNCHANGED] #{ss.name.ljust(22)} v#{ss.version}"
|
data/lib/kairos_mcp/skillset.rb
CHANGED
|
@@ -87,6 +87,27 @@ module KairosMcp
|
|
|
87
87
|
@metadata['config_files'] || []
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# Files a release takes out of an installed copy (relative paths). An
|
|
91
|
+
# upgrade copies changed files and never deletes; a SkillSet that drops
|
|
92
|
+
# files names them here so system_upgrade archives them instead of
|
|
93
|
+
# leaving them to be loaded (Skillset#load requires every tools/*.rb).
|
|
94
|
+
def removed_files
|
|
95
|
+
Array(@metadata['removed_files']).select do |rel|
|
|
96
|
+
rel.is_a?(String) && !rel.empty? && !rel.start_with?('/') && !rel.split('/').include?('..')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Files outside the instance that an earlier version left at user level
|
|
101
|
+
# and that this version cannot remove itself (e.g. a personal skill that
|
|
102
|
+
# shadows the project's): each { "path": "~/...", "pattern": "regex",
|
|
103
|
+
# "note": "..." }. Upgrade reports the ones present; the operator moves
|
|
104
|
+
# them.
|
|
105
|
+
def user_level_conflicts
|
|
106
|
+
Array(@metadata['user_level_conflicts']).select do |c|
|
|
107
|
+
c.is_a?(Hash) && c['path'].is_a?(String) && c['path'].start_with?('~/') && !c['path'].split('/').include?('..')
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
90
111
|
# Optional fail-closed activation hook: a fully-qualified class name
|
|
91
112
|
# whose `activate_on_load!` runs at skillset-load time, independent of
|
|
92
113
|
# whether any of the SkillSet's tools are instantiated. Used by
|
|
@@ -89,12 +89,14 @@ module KairosMcp
|
|
|
89
89
|
dest = File.join(@skillsets_dir, temp_skillset.name)
|
|
90
90
|
if File.directory?(dest)
|
|
91
91
|
if force
|
|
92
|
-
# Preserve user config files before replacing
|
|
92
|
+
# Preserve user config files (subdirectories included) before replacing
|
|
93
93
|
config_dir = File.join(dest, 'config')
|
|
94
94
|
saved_configs = {}
|
|
95
95
|
if File.directory?(config_dir)
|
|
96
|
-
Dir.glob(File.join(config_dir, '*')).each do |f|
|
|
97
|
-
|
|
96
|
+
Dir.glob(File.join(config_dir, '**', '*')).each do |f|
|
|
97
|
+
next unless File.file?(f)
|
|
98
|
+
|
|
99
|
+
saved_configs[f.sub("#{config_dir}/", '')] = File.read(f)
|
|
98
100
|
end
|
|
99
101
|
end
|
|
100
102
|
|
|
@@ -103,12 +105,13 @@ module KairosMcp
|
|
|
103
105
|
# Copy new version
|
|
104
106
|
FileUtils.cp_r(source_path, dest)
|
|
105
107
|
|
|
106
|
-
# Restore user configs
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
# Restore user configs, except those the new version retired
|
|
109
|
+
retired = temp_skillset.removed_files.select { |r| r.start_with?('config/') }.map { |r| r.sub('config/', '') }
|
|
110
|
+
saved_configs.reject! { |rel, _| retired.include?(rel) }
|
|
111
|
+
saved_configs.each do |rel, content|
|
|
112
|
+
path = File.join(dest, 'config', rel)
|
|
113
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
114
|
+
File.write(path, content)
|
|
112
115
|
end
|
|
113
116
|
else
|
|
114
117
|
raise ArgumentError, "SkillSet '#{temp_skillset.name}' already installed at #{dest}. Use --force to reinstall."
|
|
@@ -154,14 +157,16 @@ module KairosMcp
|
|
|
154
157
|
template_ver = Gem::Version.new(template_ss.version)
|
|
155
158
|
|
|
156
159
|
changed_files = diff_files(template_path, installed.path)
|
|
160
|
+
removals = template_ss.removed_files.select { |rel| File.exist?(File.join(installed.path, rel)) }
|
|
157
161
|
|
|
158
|
-
if template_ver > installed_ver || changed_files.any?
|
|
162
|
+
if template_ver > installed_ver || changed_files.any? || removals.any?
|
|
159
163
|
results << {
|
|
160
164
|
name: installed.name,
|
|
161
165
|
installed_version: installed.version,
|
|
162
166
|
available_version: template_ss.version,
|
|
163
167
|
version_bump: template_ver > installed_ver,
|
|
164
168
|
changed_files: changed_files,
|
|
169
|
+
removed_files: removals,
|
|
165
170
|
new_skillset: false
|
|
166
171
|
}
|
|
167
172
|
end
|
|
@@ -194,7 +199,7 @@ module KairosMcp
|
|
|
194
199
|
# Others (multiuser, service_grant, hestia, mmp, skillset_exchange, mcp_client)
|
|
195
200
|
# require PostgreSQL or networking and must be installed explicitly.
|
|
196
201
|
CORE_SKILLSETS = %w[
|
|
197
|
-
plugin_projector
|
|
202
|
+
plugin_projector llm_client autoexec autonomos
|
|
198
203
|
skillset_creator knowledge_creator introspection dream
|
|
199
204
|
document_authoring synoptis
|
|
200
205
|
].freeze
|
|
@@ -223,7 +228,8 @@ module KairosMcp
|
|
|
223
228
|
# Install new SkillSet from template
|
|
224
229
|
result = install(template_path)
|
|
225
230
|
results << { name: info[:name], from: nil, to: info[:available_version],
|
|
226
|
-
action: 'installed', files_updated: 0
|
|
231
|
+
action: 'installed', files_updated: 0,
|
|
232
|
+
warnings: user_level_conflicts(Skillset.new(File.join(@skillsets_dir, info[:name]))) }.reject { |_, v| v == [] }
|
|
227
233
|
else
|
|
228
234
|
# Upgrade existing: copy changed files
|
|
229
235
|
dest = File.join(@skillsets_dir, info[:name])
|
|
@@ -233,11 +239,13 @@ module KairosMcp
|
|
|
233
239
|
FileUtils.mkdir_p(File.dirname(dst))
|
|
234
240
|
FileUtils.cp(src, dst) if File.exist?(src)
|
|
235
241
|
end
|
|
242
|
+
archived = archive_removed_files(info[:name], dest, Array(info[:removed_files]))
|
|
236
243
|
|
|
237
244
|
installed = Skillset.new(dest)
|
|
238
245
|
record_skillset_event(installed, 'upgrade')
|
|
239
246
|
results << { name: info[:name], from: info[:installed_version], to: info[:available_version],
|
|
240
|
-
action: 'upgraded', files_updated: info[:changed_files].size
|
|
247
|
+
action: 'upgraded', files_updated: info[:changed_files].size, files_archived: archived.size,
|
|
248
|
+
warnings: user_level_conflicts(installed) }.reject { |_, v| v == [] }
|
|
241
249
|
end
|
|
242
250
|
end
|
|
243
251
|
|
|
@@ -445,8 +453,45 @@ module KairosMcp
|
|
|
445
453
|
all_skillsets.find { |ss| ss.name == name.to_s }
|
|
446
454
|
end
|
|
447
455
|
|
|
456
|
+
# The user-level leftovers a SkillSet declares that are present now, as
|
|
457
|
+
# warnings with the command that moves them aside (a timestamped backup,
|
|
458
|
+
# so a second move never overwrites the first).
|
|
459
|
+
def user_level_conflicts(skillset)
|
|
460
|
+
skillset.user_level_conflicts.filter_map do |c|
|
|
461
|
+
path = File.expand_path(c['path'])
|
|
462
|
+
next unless File.exist?(path)
|
|
463
|
+
next if c['pattern'] && !File.read(path, encoding: 'UTF-8').match?(Regexp.new(c['pattern']))
|
|
464
|
+
|
|
465
|
+
backup = File.expand_path("~/.claude/#{skillset.name}_previous_version_backup/#{Time.now.strftime('%Y%m%d_%H%M%S')}")
|
|
466
|
+
"#{path}: #{c['note'] || 'left by an earlier version'} (move it aside: mkdir -p #{backup} && mv #{path} #{backup}/)"
|
|
467
|
+
end
|
|
468
|
+
rescue StandardError
|
|
469
|
+
[]
|
|
470
|
+
end
|
|
471
|
+
|
|
448
472
|
private
|
|
449
473
|
|
|
474
|
+
# Move files a release dropped out of the installed copy, into
|
|
475
|
+
# storage/skillset_removed/<name>/<time>/ (kept, not deleted, so the
|
|
476
|
+
# operator can read back what the upgrade took out). Paths are checked to
|
|
477
|
+
# stay inside the installed SkillSet.
|
|
478
|
+
def archive_removed_files(name, dest, rels)
|
|
479
|
+
return [] if rels.empty?
|
|
480
|
+
|
|
481
|
+
root = File.realpath(dest)
|
|
482
|
+
stamp = Time.now.utc.strftime('%Y%m%dT%H%M%SZ')
|
|
483
|
+
archive = File.join(KairosMcp.storage_dir, 'skillset_removed', name.to_s, stamp)
|
|
484
|
+
rels.filter_map do |rel|
|
|
485
|
+
src = File.join(root, rel)
|
|
486
|
+
next unless File.exist?(src) && File.expand_path(src).start_with?("#{root}/")
|
|
487
|
+
|
|
488
|
+
to = File.join(archive, rel)
|
|
489
|
+
FileUtils.mkdir_p(File.dirname(to))
|
|
490
|
+
FileUtils.mv(src, to)
|
|
491
|
+
rel
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
450
495
|
# Compare files between template and installed SkillSet
|
|
451
496
|
def diff_files(template_path, installed_path)
|
|
452
497
|
changed = []
|
|
@@ -361,6 +361,7 @@ module KairosMcp
|
|
|
361
361
|
actions[:skillsets_upgraded] = (actions[:skillsets_upgraded] || []) << r[:name]
|
|
362
362
|
output += " [UPGRADED] #{r[:name]} v#{r[:from]} → v#{r[:to]} (#{r[:files_updated]} files)\n"
|
|
363
363
|
end
|
|
364
|
+
Array(r[:warnings]).each { |w| output += " [WARNING] #{w}\n" }
|
|
364
365
|
end
|
|
365
366
|
end
|
|
366
367
|
rescue => e
|
|
@@ -487,6 +488,7 @@ module KairosMcp
|
|
|
487
488
|
else
|
|
488
489
|
output += " [UPGRADED] #{r[:name]} v#{r[:from]} → v#{r[:to]} (#{r[:files_updated]} files)\n"
|
|
489
490
|
end
|
|
491
|
+
Array(r[:warnings]).each { |w| output += " [WARNING] #{w}\n" }
|
|
490
492
|
end
|
|
491
493
|
output += "\nNo L0 template changes needed.\n"
|
|
492
494
|
output += "Restart the MCP server to load new SkillSet tools.\n"
|
data/lib/kairos_mcp/version.rb
CHANGED
data/templates/knowledge/design_to_implementation_workflow/design_to_implementation_workflow.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design_to_implementation_workflow
|
|
3
3
|
description: "Full-lifecycle workflow for complex features: design review, self-review, implementation review, and final merge gate. Derived from Service Grant + Attestation Nudge experiments."
|
|
4
|
-
version: "1.
|
|
4
|
+
version: "1.3"
|
|
5
5
|
tags:
|
|
6
6
|
- workflow
|
|
7
7
|
- implementation
|
|
@@ -157,6 +157,9 @@ Claude Persona Assembly (4 personas):
|
|
|
157
157
|
- NOT for intermediate rounds (diminishing returns)
|
|
158
158
|
|
|
159
159
|
**Merge criteria**: new (a)+(b) P0 = 0, with the freeze declared by the operator.
|
|
160
|
+
Inside an agent run a review closes provisionally (`multi_llm_review_workflow`
|
|
161
|
+
§ Provisional delegation under a mandate) and only the operator freezes one; the
|
|
162
|
+
operator's word at merge settles the merge and confirms only the closures it names.
|
|
160
163
|
A Codex APPROVE is the strongest *reference* signal (see
|
|
161
164
|
`multi_llm_reviewer_evaluation`), and it is not a gate: Codex went 24 of 24
|
|
162
165
|
reviews without reaching APPROVE on one design loop, so a merge waiting on it
|
|
@@ -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.16.0"
|
|
5
5
|
tags:
|
|
6
6
|
- workflow
|
|
7
7
|
- review
|
|
@@ -29,6 +29,21 @@ This skill covers:
|
|
|
29
29
|
For **WHO** (which LLM is good at what), see: `multi_llm_reviewer_evaluation`
|
|
30
30
|
For **development lifecycle** (design → implement → verify), see: `design_to_implementation_workflow`
|
|
31
31
|
|
|
32
|
+
## Provisional delegation under a mandate
|
|
33
|
+
|
|
34
|
+
This clause reaches every judgment that a norm pointing here reserves to a human, or gives to the context
|
|
35
|
+
whose work it judges. Under a human-authored mandate — which may be a standing operator-owned policy,
|
|
36
|
+
pinned by digest and journaled per run — a named delegate, never the context whose work the judgment
|
|
37
|
+
concerns, may answer named classes of such judgments provisionally. Reversible work proceeds within the
|
|
38
|
+
run on such an answer; the answer resolves, closes, freezes and demotes nothing beyond the run until the
|
|
39
|
+
human confirms it in the human's own words. A review closed this way is provisionally closed and is
|
|
40
|
+
reported as such; it is closed in the sense of § Convergence only on that confirmation.
|
|
41
|
+
|
|
42
|
+
*Why:* runs of the agent SkillSet (1.0) are started by the operator and not answered during them; the
|
|
43
|
+
operator's standing ruling of 2026-10-20 delegates reversible judgment to a stand-in that reads the
|
|
44
|
+
operator's own words. Provisionality keeps what each pointing norm protects — the human's last word —
|
|
45
|
+
without stopping the run at every judgment.
|
|
46
|
+
|
|
32
47
|
## Step -1 — Pre-declared review spec (loop hygiene)
|
|
33
48
|
|
|
34
49
|
> Validation scope: these rules were derived from one non-converging loop
|
|
@@ -271,6 +286,8 @@ still in its transient state blocks dispatch until re-triaged.
|
|
|
271
286
|
orchestrator posing a question and answering it itself does not constitute
|
|
272
287
|
resolution. (Same conceptual line as ACT-1 in L2 attestation — analogical,
|
|
273
288
|
not a shared implementation: judgment-shaped decisions belong to the human.)
|
|
289
|
+
Inside a run under a mandate, a delegate's answer in the interview is a provisional
|
|
290
|
+
declaration, not a resolution (§ Provisional delegation under a mandate).
|
|
274
291
|
|
|
275
292
|
**INV-U3 (constitutive recording).** The products of the pass — enumerated
|
|
276
293
|
questions, human answers, declared Open Unknowns — are recorded to L2. A
|
|
@@ -300,7 +317,9 @@ advisory by default, subject to two bounds:
|
|
|
300
317
|
|
|
301
318
|
Findings about the *integrity* of the design — internal contradiction,
|
|
302
319
|
unrealizable invariant, an Open Unknown whose declared deferral is itself
|
|
303
|
-
unsafe — remain (a)/(b) and block as before.
|
|
320
|
+
unsafe — remain (a)/(b) and block as before. A provisional declaration made
|
|
321
|
+
under a mandate carries no demotion power beyond its run until the human
|
|
322
|
+
confirms it (§ Provisional delegation under a mandate).
|
|
304
323
|
|
|
305
324
|
**INV-U5 (classification authority).** The judgment-shaped /
|
|
306
325
|
non-judgment-shaped classification of an unknown is a human prerogative.
|
|
@@ -309,7 +328,9 @@ declining, or judging a question draft-answerable). Unattended, every
|
|
|
309
328
|
surfaced unknown is judgment-shaped by default; only a human-authored
|
|
310
329
|
mandate may pre-classify named categories as non-judgment-shaped for a given
|
|
311
330
|
run. An orchestrator's self-classification has no force. (Fail-closed:
|
|
312
|
-
unclassified ⇒ judgment-shaped ⇒ stop.)
|
|
331
|
+
unclassified ⇒ judgment-shaped ⇒ stop.) A standing operator-owned policy,
|
|
332
|
+
pinned and journaled per run, is a human-authored mandate (§ Provisional
|
|
333
|
+
delegation under a mandate).
|
|
313
334
|
|
|
314
335
|
### Unattended execution (autonomous loops)
|
|
315
336
|
|
|
@@ -334,7 +355,9 @@ same pending question produce no new side effects and no forward motion —
|
|
|
334
355
|
re-stopping is idempotent; forward motion requires a human answer. The
|
|
335
356
|
mandate's expressive power, the ratification protocol, and the
|
|
336
357
|
pending-question queue mechanism are owned by the Autonomous Growth Loop
|
|
337
|
-
guard track (see the frozen design's §11 backlog).
|
|
358
|
+
guard track (see the frozen design's §11 backlog). Mandates for unattended
|
|
359
|
+
growth loops remain with the Autonomous Growth Loop guard track; operator-started
|
|
360
|
+
agent runs use the policy form of § Provisional delegation under a mandate.
|
|
338
361
|
|
|
339
362
|
### Acceptance observation (selective survival)
|
|
340
363
|
|
|
@@ -744,7 +767,9 @@ subtractive — see § Revision Discipline), is a freeze candidate for the
|
|
|
744
767
|
operator regardless of the numerator. Neither of the two 2026-08 threads
|
|
745
768
|
ever reached its APPROVE ratio; both closed by (a)+(b) exhaustion + operator
|
|
746
769
|
freeze declaration — the intended close described above, now with a
|
|
747
|
-
measurable trigger.
|
|
770
|
+
measurable trigger. Inside a run under a mandate, the labelling and the closure
|
|
771
|
+
declaration are the delegate's, provisionally, and never the orchestrating
|
|
772
|
+
context's when that context did the work (§ Provisional delegation under a mandate).
|
|
748
773
|
|
|
749
774
|
**Escalating raises the bar.** The rule is a ratio over the observers that
|
|
750
775
|
counted, so adding reserve observers with `escalate: true` raises the number of
|
|
@@ -1993,6 +2018,16 @@ Compression ratio: parallel agent raw → Assembly ≈ 2:1
|
|
|
1993
2018
|
Upgrading does not rewrite an instance's own `config/multi_llm_review.yml`,
|
|
1994
2019
|
so an existing instance keeps Opus 4.6 until that entry is edited by hand.
|
|
1995
2020
|
|
|
2021
|
+
- Provisional delegation under a mandate (v3.16.0, 2026-10-21, operator
|
|
2022
|
+
decision: 「基本1-9はそのように進めて下さい」, adopting the agent SkillSet
|
|
2023
|
+
1.0 L1 clause drafts). A new entry-level clause after § Overview lets a
|
|
2024
|
+
named delegate — never the context whose work is judged — answer reserved
|
|
2025
|
+
judgments provisionally under a pinned, journaled operator policy; nothing
|
|
2026
|
+
resolves, closes, freezes or demotes beyond the run until the human confirms
|
|
2027
|
+
in their own words. Pointers were appended to INV-U2, INV-U4, INV-U5, the
|
|
2028
|
+
Step 0.25 guard-track sentence and § Convergence Rules. Source: design
|
|
2029
|
+
`log/agent_skillset_rebuild_design_v0.5.1_20261020.md` §2 and its delta v0.6.6.
|
|
2030
|
+
|
|
1996
2031
|
**Key insight**: Design reviews and implementation reviews find
|
|
1997
2032
|
**categorically different bugs**. Both phases are necessary. The corollary that
|
|
1998
2033
|
cost eight rounds to learn: **which of the two you are in is readable from the
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Harvest the operator's own words from host transcripts into the agent's
|
|
4
|
+
# precedent store (design v0.5.1 INV-5). Run it apart from runs, regularly:
|
|
5
|
+
# the host deletes transcripts after 30 days. The operator runs it; the
|
|
6
|
+
# store governs runs, so no run writes it.
|
|
7
|
+
#
|
|
8
|
+
# ruby .kairos/skillsets/agent/bin/agent_harvest.rb [--data-dir DIR] [--since-days N]
|
|
9
|
+
# [--import-snapshot SNAPSHOT_DIR] [--no-transcripts]
|
|
10
|
+
|
|
11
|
+
require_relative '../lib/agent/harvest'
|
|
12
|
+
require_relative '../lib/agent/run'
|
|
13
|
+
|
|
14
|
+
def arg(name)
|
|
15
|
+
i = ARGV.index(name)
|
|
16
|
+
i && ARGV[i + 1]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
data_dir = arg('--data-dir') || ENV['KAIROS_DATA_DIR'] || File.join(ENV['CLAUDE_PROJECT_DIR'] || Dir.pwd, '.kairos')
|
|
20
|
+
since = (d = arg('--since-days')) ? Time.now - Integer(d) * 86_400 : nil
|
|
21
|
+
paths = KairosMcp::SkillSets::Agent::Paths.new(data_dir)
|
|
22
|
+
harvest = KairosMcp::SkillSets::Agent::Harvest.new(paths)
|
|
23
|
+
if (snap = arg('--import-snapshot'))
|
|
24
|
+
puts "imported #{harvest.import_snapshot(File.expand_path(snap))} snapshot records"
|
|
25
|
+
end
|
|
26
|
+
puts "harvested #{harvest.run(since: since)} new records from transcripts" unless ARGV.include?('--no-transcripts')
|
|
27
|
+
total = KairosMcp::SkillSets::Agent::Precedents.new(paths.precedents_dir).all.size
|
|
28
|
+
puts "the store at #{paths.precedents_dir} holds #{total}"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# The operator's policy — the mandate for agent runs (design v0.5.1 §2).
|
|
2
|
+
# A transcription of the operator's ruling of 2026-10-20 (L2
|
|
3
|
+
# ruling_agent_skillset_rebuild_premise_20261020): reduce questions to the
|
|
4
|
+
# minimum by judging autonomously from past logs and the instruction mode;
|
|
5
|
+
# follow the operator's past decisions rather than default recommendations;
|
|
6
|
+
# occasional mistakes are acceptable; claude -p from a host session; never
|
|
7
|
+
# unattended. It binds only after the operator adopts this file's digest in
|
|
8
|
+
# their own typed words; any edit changes the digest and needs a new adoption.
|
|
9
|
+
|
|
10
|
+
policy "masa_2026_10_20" do
|
|
11
|
+
stand_in may: [:precedent, :instruction_mode, :novel_reversible]
|
|
12
|
+
consent review: :configured_providers, of: :project_artifacts
|
|
13
|
+
free_of_personal_data projects: []
|
|
14
|
+
# Review rounds for each review, counted apart: the only cap on a review,
|
|
15
|
+
# which closes only when no P0 is open; at the cap it waits for the operator.
|
|
16
|
+
spend default: { review_rounds: 10, stand_in_calls: 300 }
|
|
17
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Host hook entry for the agent SkillSet (design v0.5.1 §4). One script for
|
|
4
|
+
# every event; the event name comes from the hook input. In a session that is
|
|
5
|
+
# not bound to a run it only marks the guard as seen (PreToolUse,
|
|
6
|
+
# SessionStart), tells KairosChain's own agent tools which session calls them,
|
|
7
|
+
# and keeps the operator's words that may later adopt, confirm or correct
|
|
8
|
+
# something; otherwise it returns without output, so ordinary sessions pay one
|
|
9
|
+
# Ruby start and a file stat.
|
|
10
|
+
|
|
11
|
+
require 'json'
|
|
12
|
+
require 'fileutils'
|
|
13
|
+
|
|
14
|
+
# The host may start hooks without a UTF-8 locale; journals and transcripts
|
|
15
|
+
# hold the operator's words in UTF-8.
|
|
16
|
+
Encoding.default_external = Encoding::UTF_8
|
|
17
|
+
|
|
18
|
+
input = begin
|
|
19
|
+
JSON.parse($stdin.read)
|
|
20
|
+
rescue JSON::ParserError => e
|
|
21
|
+
# Unreadable input cannot be attributed to a session; on a failing exit
|
|
22
|
+
# the plugin's PreToolUse command line blocks the call only if the session
|
|
23
|
+
# id it finds in the input is bound here.
|
|
24
|
+
warn "agent hook: unreadable hook input (#{e.message[0, 100]})"
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
event = input['hook_event_name'].to_s
|
|
29
|
+
sid = input['session_id'].to_s
|
|
30
|
+
data_dir = ENV['KAIROS_DATA_DIR'] || File.join(ENV['CLAUDE_PROJECT_DIR'] || Dir.pwd, '.kairos')
|
|
31
|
+
root = File.join(data_dir, 'storage', 'agent')
|
|
32
|
+
safe = sid.gsub(/[^A-Za-z0-9_.-]/, '_')
|
|
33
|
+
|
|
34
|
+
bound = !sid.empty? && File.exist?(File.join(root, 'bindings', safe))
|
|
35
|
+
begin
|
|
36
|
+
if %w[PreToolUse SessionStart].include?(event) && !sid.empty?
|
|
37
|
+
FileUtils.mkdir_p(File.join(root, 'guard_seen'))
|
|
38
|
+
FileUtils.touch(File.join(root, 'guard_seen', safe))
|
|
39
|
+
end
|
|
40
|
+
rescue SystemCallError
|
|
41
|
+
# The prelude never blocks an ordinary session; a bound one fails closed below.
|
|
42
|
+
exit 0 unless bound
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# KairosChain's own agent tools (a server whose name says kairos, never
|
|
46
|
+
# another server's agent_*) learn the calling session and its project in
|
|
47
|
+
# every session (INV-6), without loading the library (Hooks.agent_tool?).
|
|
48
|
+
AGENT_TOOL = /\Amcp__(.+)__agent_(start|status|advance|items|finish|stop)\z/
|
|
49
|
+
agent_tool = (m = AGENT_TOOL.match(input['tool_name'].to_s)) && m[1].match?(/kairos/i) ? m[2] : nil
|
|
50
|
+
# A bound session goes through the library, which also releases an ended
|
|
51
|
+
# run's guard once the operator has spoken.
|
|
52
|
+
if event == 'PreToolUse' && agent_tool && !bound
|
|
53
|
+
extra = { '_session_id' => sid, '_project_dir' => ENV['CLAUDE_PROJECT_DIR'] || input['cwd'] }.compact
|
|
54
|
+
puts JSON.generate('hookSpecificOutput' => { 'hookEventName' => 'PreToolUse', 'permissionDecision' => 'allow',
|
|
55
|
+
'updatedInput' => (input['tool_input'] || {}).merge(extra) })
|
|
56
|
+
exit 0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Words that may adopt a policy, or name a decision or item to confirm,
|
|
60
|
+
# correct or resolve (Hooks::REFERENCE).
|
|
61
|
+
REFERENCE = /(?<![A-Za-z0-9])[DI]\d+(?![0-9A-Za-z])|(?i:adopt\s+policy)|採用|訂正|承認|確定(?!申告)|差し戻/
|
|
62
|
+
needs_full = bound ||
|
|
63
|
+
(event == 'PostToolUse' && agent_tool == 'start') ||
|
|
64
|
+
(event == 'SessionStart' && Dir.exist?(File.join(root, 'runs'))) ||
|
|
65
|
+
(event == 'UserPromptSubmit' && input['prompt'].to_s.match?(REFERENCE))
|
|
66
|
+
exit 0 unless needs_full
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
$LOAD_PATH.unshift(ENV['KAIROS_MCP_LIB']) if ENV['KAIROS_MCP_LIB']
|
|
70
|
+
require_relative '../lib/agent/hooks'
|
|
71
|
+
|
|
72
|
+
project_root = ENV['CLAUDE_PROJECT_DIR'] || File.dirname(data_dir)
|
|
73
|
+
paths = KairosMcp::SkillSets::Agent::Paths.new(data_dir)
|
|
74
|
+
|
|
75
|
+
# The operator's own deny and ask rules (user and project). A guard allow
|
|
76
|
+
# does not suppress the host's prompt for an ask rule, and a run's operator
|
|
77
|
+
# is not answering, so both are refused and queued (design §8). Bash rules
|
|
78
|
+
# are matched against every command the original line runs, before it is
|
|
79
|
+
# wrapped for confinement.
|
|
80
|
+
deny = []
|
|
81
|
+
rules = []
|
|
82
|
+
[File.expand_path('~/.claude/settings.json'),
|
|
83
|
+
File.join(project_root, '.claude', 'settings.json'),
|
|
84
|
+
File.join(project_root, '.claude', 'settings.local.json')].each do |path|
|
|
85
|
+
next unless File.exist?(path)
|
|
86
|
+
|
|
87
|
+
perms = JSON.parse(File.read(path))['permissions'] || {}
|
|
88
|
+
%w[deny ask].each do |kind|
|
|
89
|
+
Array(perms[kind]).each do |rule|
|
|
90
|
+
rule = rule.to_s.strip
|
|
91
|
+
if (m = rule.match(/\ABash\((.+)\)\z/))
|
|
92
|
+
pat = m[1]
|
|
93
|
+
prefix = pat.end_with?(':*')
|
|
94
|
+
# The host's wildcard form: * spans any text, a multi-line command's
|
|
95
|
+
# newlines too, and a final ' *' also takes the bare command
|
|
96
|
+
# (Bash(git commit *) names `git commit`).
|
|
97
|
+
body = Regexp.escape(prefix ? pat[0..-3] : pat).gsub('\\*', '.*').sub(/\\ \.\*\z/) { '(?:\s.*)?' }
|
|
98
|
+
# A prefix ends at a word: Bash(mv:*) names mv, not mvn.
|
|
99
|
+
deny << if !prefix then /\A\s*#{body}\s*\z/m
|
|
100
|
+
elsif pat[0..-3].match?(/\w\z/) then /\A\s*#{body}(?=\s|\z)/m
|
|
101
|
+
else /\A\s*#{body}/m
|
|
102
|
+
end
|
|
103
|
+
elsif rule == 'Bash'
|
|
104
|
+
deny << /./
|
|
105
|
+
elsif (m = rule.match(/\A([A-Za-z0-9_*-]+)(?:\((.*)\))?\z/))
|
|
106
|
+
rules << { 'kind' => kind, 'tool' => m[1], 'spec' => m[2] }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
rescue JSON::ParserError
|
|
111
|
+
next
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
stand_in_factory = lambda do |_run, policy|
|
|
115
|
+
llm_call = File.join(data_dir, 'skillsets', 'llm_client', 'tools', 'llm_call.rb')
|
|
116
|
+
require 'kairos_mcp/tools/base_tool'
|
|
117
|
+
require llm_call
|
|
118
|
+
KairosMcp::SkillSets::Agent::StandIn.new(
|
|
119
|
+
policy: policy,
|
|
120
|
+
instruction_mode: KairosMcp::SkillSets::Agent::InstructionMode.load(data_dir),
|
|
121
|
+
llm: KairosMcp::SkillSets::Agent::StandIn.default_llm(KairosMcp::SkillSets::LlmClient::Tools::LlmCall)
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
hooks = KairosMcp::SkillSets::Agent::Hooks.new(paths: paths, project_root: project_root,
|
|
126
|
+
stand_in_factory: stand_in_factory, settings_deny: deny,
|
|
127
|
+
settings_rules: rules)
|
|
128
|
+
out = hooks.handle(event, input)
|
|
129
|
+
puts JSON.generate(out) if out
|
|
130
|
+
exit 0
|
|
131
|
+
rescue StandardError, ScriptError => e
|
|
132
|
+
warn "agent hook (#{event}): #{e.class}: #{e.message[0, 300]}"
|
|
133
|
+
if event == 'PreToolUse' && bound
|
|
134
|
+
puts JSON.generate('hookSpecificOutput' => { 'hookEventName' => 'PreToolUse', 'permissionDecision' => 'deny',
|
|
135
|
+
'permissionDecisionReason' => "agent guard failed: #{e.class}" })
|
|
136
|
+
exit 0
|
|
137
|
+
end
|
|
138
|
+
exit 1
|
|
139
|
+
end
|