Remediate cc-architect audit findings across os-* plugins (#75-#79)

- os-context: audit-sessions rubric updated to ADR-0042 git-issues-only
  boundary (#75)
- os-backlog: drop dead Planka Config#board/#configured?, retarget tests;
  record ADR-0025 exemption for bin/wakeup-poll in plugin CLAUDE.md (#76)
- os-vault: align hooks/config.py default to graphify-out; refresh stale
  eval/README.md WS2 status (#77)
- gitignore .pytest_cache/ (#78)
- cc-architect: plugin_config resolves actual marketplace from
  installed_plugins.json instead of assuming cc-os (#79)

Opus-reviewed PASS; os-backlog 85 runs / cc-architect script suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTE1yYvTEg1cJZBhKNRZvL
This commit is contained in:
jared 2026-07-16 16:36:23 -04:00
parent f2fe45bab8
commit cc2f2914c5
8 changed files with 65 additions and 49 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ graphify-out/
# Python bytecode # Python bytecode
__pycache__/ __pycache__/
.pytest_cache/
# autoresearch skill run logs (eval iterations) # autoresearch skill run logs (eval iterations)
/autoresearch/ /autoresearch/

View File

@ -20,7 +20,23 @@ module PluginConfig
private private
def plugin_key def plugin_key
"#{plugin}@#{marketplace}" "#{plugin}@#{resolved_marketplace}"
end
# The marketplace id this plugin is actually installed under, per
# installed_plugins.json, falling back to the --marketplace param when
# no installed entry exists yet (e.g. before first install). Needed
# because cc-os's os-* plugins install under a `local-plugins`
# marketplace (symlink into ~/.claude/plugins) rather than whatever
# marketplace id the caller assumes (e.g. the repo's own `cc-os`),
# which otherwise produces false "not installed" results.
def resolved_marketplace
data = read_json(installed_plugins_path)
if data && data['plugins']
match = data['plugins'].keys.find { |k| k.start_with?("#{plugin}@") }
return match.split('@', 2).last if match
end
marketplace
end end
def read_json(path) def read_json(path)

View File

@ -12,3 +12,20 @@ see NOTE below.
NOTE (2026-07-16): a major rework retiring Planka in favor of git-issues-only NOTE (2026-07-16): a major rework retiring Planka in favor of git-issues-only
(ADR-0042, OpenSpec change retire-planka-git-issues-only) is in flight on main; (ADR-0042, OpenSpec change retire-planka-git-issues-only) is in flight on main;
this index intentionally avoids enumerating internals that rework replaces. this index intentionally avoids enumerating internals that rework replaces.
## ADR-0025 exemption
`bin/wakeup-poll` is a second `bin/` entry point alongside the `bin/os-backlog`
dispatcher — a deviation from [ADR-0025](../../docs/adr/0025-standard-ruby-structure-for-cc-os-plugins-lib-single-dispatcher-bin-fail-soft-installed-gem-only-dependencies.md)'s
single-dispatcher rule. This is **grandfathered**, following the same
exemption mechanism os-adr recorded for its own ADR-0025 deviation (see
`plugins/os-adr/CLAUDE.md`): the split is backed by
[ADR-0034](../../docs/adr/0034-cross-project-filing-file-dont-fix-with-a-derived-global-project-index.md)
(global project index),
[ADR-0035](../../docs/adr/0035-issue-triggered-project-ai-wakeup-via-polling-not-webhooks.md)
(issue-triggered wakeup via polling), and
[ADR-0036](../../docs/adr/0036-tmux-session-convention-for-ai-run-sessions-cc-project-purpose.md)
(tmux session convention) — "Pilot only" per the script's header comment,
not an oversight. Don't
fold `wakeup-poll` into the dispatcher as a drive-by; revisit if/when those
ADRs graduate past pilot.

View File

@ -25,11 +25,6 @@ module Backlog
end end
end end
# @return [String, nil] the explicit board name override, if configured
def board
@data["board"]
end
# @return [String, nil] the tracker key (e.g. "forgejo:owner/repo"), if configured # @return [String, nil] the tracker key (e.g. "forgejo:owner/repo"), if configured
def tracker def tracker
@data["tracker"] @data["tracker"]
@ -42,7 +37,6 @@ module Backlog
@data["board_id"] @data["board_id"]
end end
def configured? = !board.nil?
def tracker_configured? = !tracker.nil? def tracker_configured? = !tracker.nil?
# @return [Hash] a copy of the parsed config data # @return [Hash] a copy of the parsed config data

View File

@ -1,37 +1,21 @@
require_relative "test_helper" require_relative "test_helper"
class ConfigTest < Minitest::Test class ConfigTest < Minitest::Test
def test_nil_contents_is_unconfigured def test_nil_contents_has_no_tracker
config = Backlog::Config.new(nil) config = Backlog::Config.new(nil)
refute config.configured? refute config.tracker_configured?
assert_nil config.board assert_nil config.tracker
end end
def test_blank_contents_is_unconfigured def test_reads_legacy_yaml_style_key
config = Backlog::Config.new(" \n") config = Backlog::Config.new("---\ntracker: forgejo:jared/cc-os\n")
refute config.configured? assert config.tracker_configured?
end assert_equal "forgejo:jared/cc-os", config.tracker
def test_reads_board_key
config = Backlog::Config.new("board=llf-schema\n")
assert config.configured?
assert_equal "llf-schema", config.board
end
def test_reads_legacy_yaml_style_board_key
config = Backlog::Config.new("---\nboard: llf-schema\n")
assert config.configured?
assert_equal "llf-schema", config.board
end end
def test_lines_without_separator_are_ignored def test_lines_without_separator_are_ignored
config = Backlog::Config.new("# comment\njunk line\n") config = Backlog::Config.new("# comment\njunk line\n")
refute config.configured? assert_empty config.to_h
end
def test_contents_without_board_key_is_unconfigured
config = Backlog::Config.new("other=value\n")
refute config.configured?
end end
def test_reads_tracker_key def test_reads_tracker_key
@ -41,7 +25,7 @@ class ConfigTest < Minitest::Test
end end
def test_tracker_unconfigured_when_absent def test_tracker_unconfigured_when_absent
config = Backlog::Config.new("board=llf-schema\n") config = Backlog::Config.new("other=value\n")
refute config.tracker_configured? refute config.tracker_configured?
end end
@ -52,11 +36,11 @@ class ConfigTest < Minitest::Test
def test_merge_preserves_other_keys def test_merge_preserves_other_keys
existing = "board=llf-schema\nother=kept\n" existing = "board=llf-schema\nother=kept\n"
updated_contents = Backlog::Config.merge(existing, "tracker", "planka:llf-schema") updated_contents = Backlog::Config.merge(existing, "tracker", "forgejo:jared/cc-os")
updated = Backlog::Config.new(updated_contents) updated = Backlog::Config.new(updated_contents)
assert_equal "llf-schema", updated.board assert_equal "llf-schema", updated.to_h["board"]
assert_equal "planka:llf-schema", updated.tracker assert_equal "forgejo:jared/cc-os", updated.tracker
assert_equal "kept", updated.to_h["other"] assert_equal "kept", updated.to_h["other"]
end end
@ -82,7 +66,7 @@ class ConfigTest < Minitest::Test
def test_merge_normalizes_existing_spaced_lines_on_rewrite def test_merge_normalizes_existing_spaced_lines_on_rewrite
existing = "version = 1\nhub = my-hub\n" existing = "version = 1\nhub = my-hub\n"
updated_contents = Backlog::Config.merge(existing, "tracker", "planka:board") updated_contents = Backlog::Config.merge(existing, "tracker", "forgejo:jared/demo")
updated_contents.each_line do |line| updated_contents.each_line do |line|
next if line.strip.empty? next if line.strip.empty?
@ -91,26 +75,26 @@ class ConfigTest < Minitest::Test
updated = Backlog::Config.new(updated_contents) updated = Backlog::Config.new(updated_contents)
assert_equal "1", updated.to_h["version"] assert_equal "1", updated.to_h["version"]
assert_equal "my-hub", updated.to_h["hub"] assert_equal "my-hub", updated.to_h["hub"]
assert_equal "planka:board", updated.tracker assert_equal "forgejo:jared/demo", updated.tracker
end end
def test_board_id_reader def test_board_id_reader
config = Backlog::Config.new("tracker=planka:board\nboard_id=42\n") config = Backlog::Config.new("tracker=forgejo:jared/demo\nboard_id=42\n")
assert_equal "42", config.board_id assert_equal "42", config.board_id
assert_nil Backlog::Config.new(nil).board_id assert_nil Backlog::Config.new(nil).board_id
end end
def test_set_preserves_comments_and_blank_lines def test_set_preserves_comments_and_blank_lines
existing = "# managed by cc-os\ntracker=planka:board\n\n# stamped by os-status\nversion=3\n" existing = "# managed by cc-os\ntracker=forgejo:jared/demo\n\n# stamped by os-status\nversion=3\n"
updated = Backlog::Config.set(existing, "board_id", "42") updated = Backlog::Config.set(existing, "board_id", "42")
assert_equal "# managed by cc-os\ntracker=planka:board\n\n# stamped by os-status\nversion=3\nboard_id=42\n", assert_equal "# managed by cc-os\ntracker=forgejo:jared/demo\n\n# stamped by os-status\nversion=3\nboard_id=42\n",
updated updated
end end
def test_set_replaces_existing_key_in_place def test_set_replaces_existing_key_in_place
existing = "# top comment\nboard_id=old\ntracker=planka:board\n" existing = "# top comment\nboard_id=old\ntracker=forgejo:jared/demo\n"
updated = Backlog::Config.set(existing, "board_id", "new") updated = Backlog::Config.set(existing, "board_id", "new")
assert_equal "# top comment\nboard_id=new\ntracker=planka:board\n", updated assert_equal "# top comment\nboard_id=new\ntracker=forgejo:jared/demo\n", updated
end end
def test_set_normalizes_spaced_key_value_lines_only def test_set_normalizes_spaced_key_value_lines_only

View File

@ -32,11 +32,13 @@ severity, and category.
especially after a design/decision settles. especially after a design/decision settles.
8. **tracker-routing** — mid-session follow-up work (a concrete task, bug, or deferred 8. **tracker-routing** — mid-session follow-up work (a concrete task, bug, or deferred
item not done this session) left as a TODO comment or chat mention instead of captured item not done this session) left as a TODO comment or chat mention instead of captured
to the tracked board; durable spec text routed to a Planka card description (or task as an issue on the repo's configured tracker (`.cc-os/config` tracker key,
state routed to git issues) against the Planka-state/git-issues-spec boundary `os-backlog:capture`); an AI-authored issue carrying state labels or the human-curated
(ADR-0033, `.cc-os/config` tracker key); or a Planka card visibly accreting spec-like `next` label it shouldn't (ADR-0042 — git issues are the single tracker, `next` is
decisions in comments with no promotion flag raised to the user — silent promotion human-only, the AI never adds or removes it short of an explicit direct request); or an
counts too. Convention compliance only, not whether the routing choice was wise. issue visibly accreting scope/decisions in comments with no promotion flag raised to the
user — silent promotion counts too. Convention compliance only, not whether the routing
choice was wise.
## Per-session report format (return exactly this) ## Per-session report format (return exactly this)

View File

@ -1,6 +1,8 @@
# os-vault write-behavior eval — unprompted vault-write discrimination (held-out) # os-vault write-behavior eval — unprompted vault-write discrimination (held-out)
_Last updated: 2026-07-06 — harness built (WS2); baseline grid NOT yet run._ _Last updated: 2026-07-07 — harness built (WS2), baseline grid run, and the
wording loop complete and shipped; run-set and reserve are now contaminated for
future wording tuning (see `eval/results/`)._
Measures whether the model **unprompted** (a) writes evergreen cross-project knowledge to Measures whether the model **unprompted** (a) writes evergreen cross-project knowledge to
the SecondBrain vault when it surfaces during normal work, and (b) writes it with the SecondBrain vault when it surfaces during normal work, and (b) writes it with

View File

@ -21,7 +21,7 @@ except Exception: # pragma: no cover - yaml is provided via graphify
CONFIG_PATH = os.path.expanduser("~/.claude/plugins/os-vault/config.yaml") CONFIG_PATH = os.path.expanduser("~/.claude/plugins/os-vault/config.yaml")
DEFAULT_VAULT_PATH = "~/Documents/SecondBrain" DEFAULT_VAULT_PATH = "~/Documents/SecondBrain"
DEFAULT_GRAPHIFY_OUTPUT_DIR = "~/Documents/SecondBrain/.graphify" DEFAULT_GRAPHIFY_OUTPUT_DIR = "~/Documents/SecondBrain/graphify-out"
DEFAULT_MODEL = "qwen25-coder-7b-16k" DEFAULT_MODEL = "qwen25-coder-7b-16k"
DEFAULT_STALE_DAYS = 7 DEFAULT_STALE_DAYS = 7
DEFAULT_MEMSEARCH_DIR = "~/.memsearch" DEFAULT_MEMSEARCH_DIR = "~/.memsearch"