kairos-chain 3.30.0 → 3.32.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 +67 -0
- data/bin/kairos-chain +34 -7
- data/bin/kairos-plugin-project +21 -1
- data/lib/kairos_mcp/drift_detection/correspondence_checker.rb +136 -0
- data/lib/kairos_mcp/knowledge_provider.rb +5 -0
- data/lib/kairos_mcp/plugin_projector.rb +235 -24
- data/lib/kairos_mcp/tools/knowledge_get.rb +50 -1
- data/lib/kairos_mcp/tools/knowledge_update.rb +7 -5
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/knowledge/llm_cross_evaluation/scripts/run_cross_eval.rb +8 -0
- data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +77 -52
- data/templates/skillsets/llm_client/lib/llm_client/call_router.rb +8 -2
- data/templates/skillsets/llm_client/lib/llm_client/codex_mcp_adapter.rb +160 -0
- data/templates/skillsets/llm_client/tools/llm_call.rb +13 -2
- data/templates/skillsets/multi_llm_review/config/multi_llm_review.yml +47 -16
- data/templates/skillsets/multi_llm_review/lib/multi_llm_review/dispatcher.rb +4 -0
- data/templates/skillsets/plugin_projector/tools/plugin_project.rb +13 -3
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c01be861d1c6a82ee12606f25c5526268351cd4353aa21cf00a0dcb28fc5ed30
|
|
4
|
+
data.tar.gz: 4f2cfd5edc74107aa944ba16a953d30935dca34141806bf3b3b71efe9ed84b51
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a88518aa910493860251c66f0ecdc60ce2d39131626bf2b1e571a3d274560c5e2a876c3ccae6a165f73c1c706ca94022ee9f28369e88fea76e9010ad8f234e7d
|
|
7
|
+
data.tar.gz: 8fe7372a93fdbebe24b9bb6a57e9d2d464966840cfef2c35674319230a6ff05d66f27e6934484bf78b1c5a3fad55de7088639038126e95d1cece92ad1695b5ed
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,73 @@ 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.32.0] - 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Added — multi-host SkillSet projection (Codex + OpenCode)
|
|
10
|
+
|
|
11
|
+
`PluginProjector` gains a `HostProfile` abstraction so projection targets Codex
|
|
12
|
+
CLI and OpenCode in addition to Claude Code. Claude behavior is unchanged
|
|
13
|
+
(byte-compatible; `projection_manifest.json` keeps its legacy name).
|
|
14
|
+
|
|
15
|
+
- Instruction mode: `AGENTS.md` with the mode body inlined for codex/opencode
|
|
16
|
+
(they do not resolve Claude's `@`-import); `CLAUDE.md` `@`-import for claude.
|
|
17
|
+
- Codex: `.codex/skills`, `.codex/agents`, `.codex/hooks.json` (merge-preserving
|
|
18
|
+
user hooks via `_projected_by` tags; re-projection hook commands carry
|
|
19
|
+
`--host codex`). Codex reads `<repo>/.codex/hooks.json` natively — no TOML.
|
|
20
|
+
- OpenCode: reuses `.claude/skills/` (Claude co-use assumption), converts agents
|
|
21
|
+
to `.opencode/agent/` frontmatter; hooks skipped (OpenCode hooks are JS plugins).
|
|
22
|
+
- CLI `kairos-chain mode` and `kairos-plugin-project` gain
|
|
23
|
+
`--host claude|codex|opencode`; MCP tool `plugin_project` gains a `host` param.
|
|
24
|
+
Per-host manifest slots so hosts coexist.
|
|
25
|
+
- Hardened via multi-LLM implementation review (codex/claude/cursor, all
|
|
26
|
+
APPROVE-WITH-CHANGES, 0 P0): hooks merge-preserve, agent-conversion Hash guard
|
|
27
|
+
and list-form `disallowedTools`, separator-boundary path containment, live-file
|
|
28
|
+
region status, malformed `.codex/hooks.json` normalization. 111 projector tests.
|
|
29
|
+
|
|
30
|
+
### Added — codex mcp-server reviewer backend (opt-in)
|
|
31
|
+
|
|
32
|
+
`CodexMcpAdapter` drives Codex via `codex mcp-server` (stdio JSON-RPC, one-shot,
|
|
33
|
+
read-only sandbox) as an alternative to the `codex exec` CLI adapter. A codex
|
|
34
|
+
reviewer opts in with `backend: mcp` (default stays CLI, backward compatible);
|
|
35
|
+
the backend switch is wired through `llm_call`, `call_router`, and the
|
|
36
|
+
multi_llm_review dispatcher. A/B against the CLI path showed parity (same
|
|
37
|
+
verdict, comparable latency).
|
|
38
|
+
|
|
39
|
+
## [3.31.0] - 2026-06-11
|
|
40
|
+
|
|
41
|
+
### Changed — multi_llm_review roster: Fable 5 + Opus 4.6/4.8 (6 reviewers)
|
|
42
|
+
|
|
43
|
+
Default reviewer roster updated for the Fable 5 / Opus 4.8 model generation:
|
|
44
|
+
|
|
45
|
+
- Orchestrator/team slot: `claude-fable-5` (`claude_team_fable5`) replaces
|
|
46
|
+
Opus 4.7. Opus 4.8 added as a second subprocess CLI reviewer
|
|
47
|
+
(`claude_cli_opus4.8`) alongside Opus 4.6, which is retained for its
|
|
48
|
+
documented complementary bias (ambiguity-preserving, self-reference-friendly).
|
|
49
|
+
4.7 retired: its register is covered by 4.8 and Fable 5.
|
|
50
|
+
- Convergence rules: `4/6 APPROVE` full roster, `3/5 APPROVE` after
|
|
51
|
+
orchestrator exclusion ("exclude" strategy only — "subprocess" keeps the
|
|
52
|
+
full roster; "delegate" re-adds the slot at collect, so 4/6 governs there).
|
|
53
|
+
- `timeout_seconds` raised 300 → 600 (live 6-roster run measured 381s
|
|
54
|
+
wall-clock with `max_concurrent: 2`).
|
|
55
|
+
- Validated by a 2-round self-referential review of the workflow L1 with the
|
|
56
|
+
new roster itself (R1 REVISE → fixes → R2 with 4.6/4.8/codex-5.4 APPROVE,
|
|
57
|
+
including a code-grounded Cursor correction of the exclusion semantics).
|
|
58
|
+
|
|
59
|
+
### Changed — `multi_llm_review_workflow` L1 v3.5
|
|
60
|
+
|
|
61
|
+
- All roster-dependent sections updated (pre-flight checklist, CLI tool
|
|
62
|
+
matrix, convergence rules, orchestrator self-identification, orchestration
|
|
63
|
+
template, LLM identifiers). Claude CLI 4.6/4.8 rows verified live 2026-06-10.
|
|
64
|
+
- Effort escalation paragraph scoped to coding/design sub-agents and the
|
|
65
|
+
revision phase (reviewers stay at default per the 2026-04-29 policy).
|
|
66
|
+
|
|
67
|
+
### Fixed — `knowledge_update` size guidance
|
|
68
|
+
|
|
69
|
+
- Removed the "~2 KB MCP stdio limit" warning from the tool description and
|
|
70
|
+
nil-content diagnostic: two ~40 KB updates succeeded over stdio on
|
|
71
|
+
2026-06-10/11. The old figure generalized a single unreproduced
|
|
72
|
+
nil-content incident; the nil-content detection itself is retained.
|
|
73
|
+
|
|
7
74
|
## [3.30.0] - 2026-06-08
|
|
8
75
|
|
|
9
76
|
### Added — `dream_digest`: derived narrative view over L2/L1 fragments (dream SkillSet v0.3.0)
|
data/bin/kairos-chain
CHANGED
|
@@ -376,6 +376,11 @@ when 'mode'
|
|
|
376
376
|
KAIROS_PROJECT_ROOT env, then cwd if it contains a
|
|
377
377
|
recognizable project marker (CLAUDE.md, .git/,
|
|
378
378
|
.claude/, or prior projection manifest).
|
|
379
|
+
--host HOST Target host: claude (default) | codex | opencode.
|
|
380
|
+
claude writes CLAUDE.md with an @-import pointer;
|
|
381
|
+
codex/opencode write AGENTS.md with the mode body
|
|
382
|
+
inlined (they do not resolve @-import). Each host
|
|
383
|
+
uses a separate manifest slot, so hosts can coexist.
|
|
379
384
|
|
|
380
385
|
Notes:
|
|
381
386
|
- data_dir and consumer project root are decoupled (design v0.2,
|
|
@@ -410,6 +415,19 @@ when 'mode'
|
|
|
410
415
|
ARGV.delete_at(idx)
|
|
411
416
|
end
|
|
412
417
|
|
|
418
|
+
# 2026-07-02: target host for projection (claude | codex | opencode). Default claude.
|
|
419
|
+
# codex/opencode write AGENTS.md with the mode body inlined (they do not resolve @-import).
|
|
420
|
+
mode_host = 'claude'
|
|
421
|
+
if (idx = ARGV.index('--host'))
|
|
422
|
+
mode_host = ARGV[idx + 1]
|
|
423
|
+
if mode_host.nil? || mode_host.start_with?('--')
|
|
424
|
+
warn "ERROR: --host requires a value (claude, codex, or opencode)."
|
|
425
|
+
exit 1
|
|
426
|
+
end
|
|
427
|
+
ARGV.delete_at(idx + 1)
|
|
428
|
+
ARGV.delete_at(idx)
|
|
429
|
+
end
|
|
430
|
+
|
|
413
431
|
require 'kairos_mcp/skills_config'
|
|
414
432
|
require 'kairos_mcp/plugin_projector'
|
|
415
433
|
|
|
@@ -426,11 +444,15 @@ when 'mode'
|
|
|
426
444
|
|
|
427
445
|
projector_mode = KairosMcp.projection_mode
|
|
428
446
|
begin
|
|
429
|
-
projector = KairosMcp::PluginProjector.new(project_root, mode: projector_mode, data_dir: KairosMcp.data_dir)
|
|
447
|
+
projector = KairosMcp::PluginProjector.new(project_root, mode: projector_mode, data_dir: KairosMcp.data_dir, host: mode_host)
|
|
430
448
|
rescue KairosMcp::PluginProjector::CoincidenceRefused => e
|
|
431
449
|
warn "ERROR: #{e.message}"
|
|
432
450
|
warn " Use --project-root <path> to designate a directory distinct from the data directory."
|
|
433
451
|
exit 1
|
|
452
|
+
rescue ArgumentError => e
|
|
453
|
+
warn "ERROR: #{e.message}"
|
|
454
|
+
warn " Valid --host values: claude, codex, opencode."
|
|
455
|
+
exit 1
|
|
434
456
|
end
|
|
435
457
|
|
|
436
458
|
case mode_action
|
|
@@ -464,27 +486,31 @@ when 'mode'
|
|
|
464
486
|
exit 1
|
|
465
487
|
end
|
|
466
488
|
|
|
489
|
+
ctx_file = projector.host.context_file
|
|
467
490
|
puts "Instruction mode projected:"
|
|
468
491
|
puts " mode : #{instructions_mode}#{version ? " v#{version}" : ''}"
|
|
492
|
+
puts " host : #{projector.host.key} (delivery: #{projector.host.instruction_mode_delivery})"
|
|
469
493
|
puts " body source : #{body_path}"
|
|
470
494
|
puts " project root : #{project_root} (source: #{KairosMcp.consumer_project_root_source})"
|
|
471
495
|
puts " data dir : #{KairosMcp.data_dir}"
|
|
472
496
|
puts " artifact : #{result[:artifact_path]}"
|
|
473
497
|
puts " size : #{result[:size_bytes]} bytes"
|
|
474
|
-
puts "
|
|
498
|
+
puts " #{ctx_file} region : #{result[:region_written] ? 'updated' : 'not updated (host file outside project root or unsafe)'}"
|
|
475
499
|
puts ""
|
|
476
|
-
puts "Restart
|
|
500
|
+
puts "Restart the host to apply (#{ctx_file} is read at session start)."
|
|
477
501
|
exit 0
|
|
478
502
|
|
|
479
503
|
when 'status'
|
|
480
504
|
s = projector.instruction_mode_status
|
|
505
|
+
ctx_file = projector.host.context_file
|
|
481
506
|
puts "Project root : #{project_root} (source: #{KairosMcp.consumer_project_root_source})"
|
|
482
507
|
puts "Data dir : #{KairosMcp.data_dir}"
|
|
508
|
+
puts "Host : #{projector.host.key}"
|
|
483
509
|
if s[:active]
|
|
484
510
|
puts "Instruction mode projection: ACTIVE"
|
|
485
511
|
puts " mode : #{s[:mode_name]}#{s[:mode_version] ? " v#{s[:mode_version]}" : ''}"
|
|
486
512
|
puts " artifact : #{s[:artifact_path]} (#{s[:artifact_size]} bytes)"
|
|
487
|
-
puts " region : #{s[:region_present] ?
|
|
513
|
+
puts " region : #{s[:region_present] ? "present in #{ctx_file}" : "absent from #{ctx_file}"}"
|
|
488
514
|
puts " projected : #{s[:projected_at]}"
|
|
489
515
|
else
|
|
490
516
|
puts "Instruction mode projection: not active for this project."
|
|
@@ -493,11 +519,12 @@ when 'mode'
|
|
|
493
519
|
|
|
494
520
|
when 'remove'
|
|
495
521
|
result = projector.remove_projected_instruction_mode!
|
|
496
|
-
|
|
522
|
+
ctx_file = projector.host.context_file
|
|
523
|
+
puts "Instruction mode projection removed (host: #{projector.host.key}):"
|
|
497
524
|
puts " artifact : #{result[:artifact_removed] ? 'deleted' : 'not present'}"
|
|
498
|
-
puts "
|
|
525
|
+
puts " #{ctx_file} : #{result[:region_removed] ? 'region removed' : 'region not present'}"
|
|
499
526
|
puts ""
|
|
500
|
-
puts "Restart
|
|
527
|
+
puts "Restart the host to apply (#{ctx_file} is read at session start)."
|
|
501
528
|
exit 0
|
|
502
529
|
|
|
503
530
|
else
|
data/bin/kairos-plugin-project
CHANGED
|
@@ -17,7 +17,27 @@ require_relative '../lib/kairos_mcp/skillset_manager'
|
|
|
17
17
|
|
|
18
18
|
project_root = KairosMcp.project_root
|
|
19
19
|
mode = KairosMcp.projection_mode
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
# 2026-07-02: target host for projection (claude | codex | opencode). Default claude.
|
|
22
|
+
# Used both for manual invocation and by projected re-projection hooks (--host <host>).
|
|
23
|
+
host = 'claude'
|
|
24
|
+
if (idx = ARGV.index('--host'))
|
|
25
|
+
host = ARGV[idx + 1]
|
|
26
|
+
if host.nil? || host.start_with?('--')
|
|
27
|
+
warn "ERROR: --host requires a value (claude, codex, or opencode)."
|
|
28
|
+
exit 1
|
|
29
|
+
end
|
|
30
|
+
ARGV.delete_at(idx + 1)
|
|
31
|
+
ARGV.delete_at(idx)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
projector = KairosMcp::PluginProjector.new(project_root, mode: mode, host: host)
|
|
36
|
+
rescue ArgumentError => e
|
|
37
|
+
warn "ERROR: #{e.message}"
|
|
38
|
+
warn " Valid --host values: claude, codex, opencode."
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
21
41
|
|
|
22
42
|
if ARGV.include?('--status')
|
|
23
43
|
require 'json'
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../kairos_chain/chain'
|
|
6
|
+
|
|
7
|
+
module KairosMcp
|
|
8
|
+
module DriftDetection
|
|
9
|
+
# CorrespondenceChecker — INV-A detection floor (Cycle 1, toward by-construction).
|
|
10
|
+
#
|
|
11
|
+
# Checks whether a live L0/L1 artifact still corresponds to its *current
|
|
12
|
+
# recorded provenance*: the content digest stored for that artifact at the
|
|
13
|
+
# head of the constitutive record (the hash chain). This is detection only —
|
|
14
|
+
# it surfaces divergence; it does not prevent edits or gate writes. Those are
|
|
15
|
+
# later cycles (single-source enforcement, record-as-gate).
|
|
16
|
+
#
|
|
17
|
+
# Provenance is rooted in the hash chain, not in the SQLite knowledge_meta
|
|
18
|
+
# cache: INV-A names the chain head as the non-editable anchor. The chain is
|
|
19
|
+
# therefore the single source consulted here; the meta table (when present)
|
|
20
|
+
# is a derived view and is intentionally not used for the comparison.
|
|
21
|
+
#
|
|
22
|
+
# The digest is computed over the *raw file content* (frontmatter included),
|
|
23
|
+
# matching exactly how it was recorded on create/update (a verbatim write,
|
|
24
|
+
# no normalization). Comparing the parsed/stripped body would never match.
|
|
25
|
+
class CorrespondenceChecker
|
|
26
|
+
# Result of a single correspondence check.
|
|
27
|
+
#
|
|
28
|
+
# status:
|
|
29
|
+
# :match live artifact corresponds to recorded provenance
|
|
30
|
+
# :mismatch live content diverged from recorded provenance (silent edit)
|
|
31
|
+
# :missing_record live artifact relied upon, but no recorded provenance exists
|
|
32
|
+
# :missing_artifact recorded/expected artifact is absent at the reliance point
|
|
33
|
+
# :error the check itself could not complete (not a correspondence claim)
|
|
34
|
+
Result = Struct.new(
|
|
35
|
+
:status, :name, :active_digest, :recorded_digest, :message,
|
|
36
|
+
keyword_init: true
|
|
37
|
+
) do
|
|
38
|
+
def corresponds?
|
|
39
|
+
status == :match
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A surfaced non-correspondence per INV-A (divergence, not an internal error).
|
|
43
|
+
def divergence?
|
|
44
|
+
%i[mismatch missing_record missing_artifact].include?(status)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
# Check an L1 knowledge artifact against its recorded provenance.
|
|
50
|
+
#
|
|
51
|
+
# @param name [String] knowledge id (knowledge_id on the chain record)
|
|
52
|
+
# @param md_file_path [String, nil] path to the live .md file relied upon
|
|
53
|
+
# @param storage_backend [Storage::Backend, nil] backend for chain access
|
|
54
|
+
# @return [Result]
|
|
55
|
+
def check_l1(name:, md_file_path:, storage_backend: nil)
|
|
56
|
+
unless md_file_path && File.file?(md_file_path)
|
|
57
|
+
# Relied upon but absent — a missing artifact is itself a
|
|
58
|
+
# non-correspondence under INV-A (the expected set is recorded).
|
|
59
|
+
return Result.new(
|
|
60
|
+
status: :missing_artifact, name: name,
|
|
61
|
+
active_digest: nil, recorded_digest: nil,
|
|
62
|
+
message: "L1 '#{name}': artifact missing at the point of reliance"
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
active = Digest::SHA256.hexdigest(File.read(md_file_path))
|
|
67
|
+
recorded = recorded_digest_for(name, storage_backend)
|
|
68
|
+
|
|
69
|
+
if recorded.nil?
|
|
70
|
+
return Result.new(
|
|
71
|
+
status: :missing_record, name: name,
|
|
72
|
+
active_digest: active, recorded_digest: nil,
|
|
73
|
+
message: "L1 '#{name}': live artifact has no recorded provenance on the chain"
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if active == recorded
|
|
78
|
+
Result.new(
|
|
79
|
+
status: :match, name: name,
|
|
80
|
+
active_digest: active, recorded_digest: recorded, message: nil
|
|
81
|
+
)
|
|
82
|
+
else
|
|
83
|
+
Result.new(
|
|
84
|
+
status: :mismatch, name: name,
|
|
85
|
+
active_digest: active, recorded_digest: recorded,
|
|
86
|
+
message: "L1 '#{name}': live content diverged from recorded provenance " \
|
|
87
|
+
"(active #{short(active)} ≠ recorded #{short(recorded)})"
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
Result.new(
|
|
92
|
+
status: :error, name: name,
|
|
93
|
+
active_digest: nil, recorded_digest: nil,
|
|
94
|
+
message: "L1 '#{name}': correspondence check could not complete: #{e.message}"
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# The current recorded content digest for a knowledge_id: the next_hash of
|
|
101
|
+
# the most recent knowledge_update record, scanning the chain from head
|
|
102
|
+
# backward. Returns nil when the most recent relevant record removed the
|
|
103
|
+
# artifact (next_hash nil — delete/archive) or when none exists.
|
|
104
|
+
def recorded_digest_for(name, storage_backend)
|
|
105
|
+
chain = KairosChain::Chain.new(storage_backend: storage_backend)
|
|
106
|
+
chain.chain.reverse_each do |block|
|
|
107
|
+
Array(block.data).each do |entry|
|
|
108
|
+
record = parse_entry(entry)
|
|
109
|
+
next unless record.is_a?(Hash)
|
|
110
|
+
next unless record['type'] == 'knowledge_update'
|
|
111
|
+
next unless record['knowledge_id'] == name
|
|
112
|
+
|
|
113
|
+
# First match from the head is the current provenance (may be nil
|
|
114
|
+
# if the artifact was removed — caller treats nil as no record).
|
|
115
|
+
return record['next_hash']
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parse_entry(entry)
|
|
122
|
+
return entry if entry.is_a?(Hash)
|
|
123
|
+
return JSON.parse(entry) if entry.is_a?(String)
|
|
124
|
+
|
|
125
|
+
nil
|
|
126
|
+
rescue JSON::ParserError
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def short(digest)
|
|
131
|
+
digest ? digest[0, 12] : '-'
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -23,6 +23,11 @@ module KairosMcp
|
|
|
23
23
|
# - Blockchain: Uses the configured storage backend
|
|
24
24
|
#
|
|
25
25
|
class KnowledgeProvider
|
|
26
|
+
# Main knowledge directory (constitutively-recorded L1). Exposed so callers
|
|
27
|
+
# can distinguish main-dir knowledge from read-only external SkillSet
|
|
28
|
+
# knowledge, e.g. to scope INV-A correspondence checks to recorded artifacts.
|
|
29
|
+
attr_reader :knowledge_dir
|
|
30
|
+
|
|
26
31
|
ARCHIVED_DIR = '.archived'
|
|
27
32
|
ARCHIVE_META_FILE = '.archive_meta.yml'
|
|
28
33
|
# Backup directories created by upgrade flow (`.bak.<timestamp>`).
|