# WS3 — per-plugin SessionStart status-check convention + glue plugin

_Created: 2026-07-06. Status: **superseded 2026-07-06** by the OpenSpec change
`add-os-status-plugin` (implemented as `plugins/os-status/`; decision record: ADR-022 in
`docs/memory-system/03-architecture-decisions.md`). The perspective reviews kept the Option B
direction but replaced the per-plugin enumeration protocol with in-process checks in a single
plugin; this doc is the historical starting sketch only. See
`2026-07-06-plugin-evals-overview.md`._

## Goal

A convention-over-configuration status system: at SessionStart, every installed cc-os
plugin verifies its expected per-project artifacts exist (adr: ADR system present; vault:
project hub note exists — optionally injecting it as context). All good → silent. Problem →
one short banner prompting corrective action ("<xyz> missing — run /os-vault:… to fix").
Plugins stay independent; new plugins get picked up automatically by following the
convention; only the glue plugin is mandatory for the collection.

This is architecture → **OpenSpec change + ADR** (use `openspec-propose`; decisions land in
`docs/memory-system/03-architecture-decisions.md`). Do not build outside that workflow.

## User's sketch (the starting design)

Per-plugin Python status scripts following a cc-os convention (naming, interface,
location), living in a specific gitignored hidden directory in the project. SessionStart
fires a cc-os master status script that enumerates all status scripts in that directory
(excluding itself) and runs them. Adding a plugin later = drop in a conforming script; no
global rewrites.

## The one design tension the proposal must settle (pressure-test, don't fiat)

**Where does status-check CODE live?**

- **Option A (user sketch): project-local dir** (e.g. `.cc-os/status.d/*.py`, gitignored).
  Pro: per-project opt-in/customization. Con: code copies drift across projects; needs an
  install/update step per project per plugin; the 2026-07-04 stale-cache incident shows
  copy-drift is a real failure class here.
- **Option B: plugin-source enumeration** — each plugin ships `status/check.py` in its own
  source; the master enumerates installed `os-*` plugins at session start. Same modularity
  and zero-global-rewrite extension; no copies, no drift, no install step. Project-local
  `.cc-os/` still exists but holds STATE only (suppress files, per-project config like
  "this project opts out of hub-note checks"), mirroring os-adr's `.os-adr/` and
  os-doc-hygiene's `.dochygiene/` — consolidating those is itself a candidate scope item.

Run `perspectives:simplifier` and `perspectives:implementer` (both sonnet) against both
shapes inside the proposal. Lean: B for code, A-style dir for state.

## Interface contract (draft for the proposal)

- Each check: stdin/argv gets project root + plugin config; stdout JSON
  `{"status": "ok"|"warn", "message": str, "context": str|null}`; hard timeout (~5s);
  non-zero exit or timeout = treated as warn with a generic message, never blocks session.
- **Silent on ok** — os-adr's near-zero-token SessionStart discipline is the template.
  Master aggregates warns into ONE short banner; per-project once-per-day snooze +
  permanent suppress via state dir (pattern already proven in os-adr/os-doc-hygiene).
- `context` field = optional session-context injection (the "pull the hub note into
  context if useful" idea) — but gate it through injection-economics thinking (see the
  design-template skill's filter); default null.

## Glue plugin scope ("cc-os collection manager")

Naming: per `~/Documents/SecondBrain/cc-os-plugin-skill-naming-convention.md` (read before
naming; `os-[domain]`, verb-first skills, NO `name:` frontmatter). Candidates: `os-core`
(lean) or `os-status` (narrower). Settle in the proposal.

Phase 1 (this change): master SessionStart hook + enumeration + interface contract +
convert the two existing consumers:
- os-adr: refactor its existing existence-check hook into a conforming status check (it is
  already exactly this pattern — present→usage note, absent→once-per-day suggestion).
- os-vault: new hub-note check (project hub note exists for the current project; missing →
  prompt to run onboarding/creation; optionally inject on present).
- os-orchestration: env-override check — warn when `CLAUDE_CODE_SUBAGENT_MODEL` (or similar
  model-forcing env) is set in `~/.claude/settings.json` or the environment. Added 2026-07-06:
  the E1 canary proved the orchestrator cannot observe resolved models at spawn time
  (`resolvedModel` is harness-side only), so silent-override protection must be deterministic,
  not model behavior. This check would have caught the WS1 Cluster 1 incident at SessionStart.

Phase 2 (separate change, later): collection-manager duties — verify installed cc-os
plugin caches are fresh vs source (would have auto-caught the 2026-07-04 stale-cache
incident), migrations complete, conventions followed (e.g. no `name:` in SKILL.md
frontmatter). Effectively an automated `bin/refresh-plugins` advisor.

## Deliverables

1. OpenSpec change (design.md resolving the tension above + specs + tasks) — via
   `openspec-propose`.
2. ADR in `docs/memory-system/03-architecture-decisions.md` (status-check convention +
   code-location decision).
3. Implementation via `openspec-apply-change`: master hook + shared contract module
   (Python, matching os-vault's deep-module style: thin entry points, shared config/io
   modules), conforming checks for os-adr + os-vault, model-free tests (Python, mirroring
   `tests/hook_test.py` style) + invariants.md.
4. `bin/refresh-plugins`, `claude plugin details` verification, fresh-session smoke test.

## Models

Design synthesis + ADR: orchestrator. Perspective reviews: sonnet. Implementation: haiku
for thin scripts/tests, sonnet for the shared contract module. No behavioral eval needed —
deterministic code; invariants + model-free tests suffice (same rationale as the os-adr
hook).
