Generated banner for business-context-os
Generated logo for business-context-os

business-context-os

LLM wiki kit

First committed by walm00

Published in walm00/business-context-os. Publisher page

22 GitHub stars

What it is

CLEAR Context OS is the maintenance layer your second brain never had: ownership boundaries enforced mechanically, staleness surfaced on a schedule, drift propagated across linked docs, a dispatcher that learns from your clicks. One source of truth per topic. Searchable. Task-aware.
— the description by walm00

AutoGineer overview, from the brain’s own files

CLEAR Context OS is a framework designed to solve the problem of silent context degradation in AI agents. By implementing a structured methodology called CLEAR (Contextual Ownership, Linking, Elimination, Alignment, Refinement), it provides a maintenance layer that manages knowledge through automated scheduling, ownership enforcement, and drift detection. It organizes information into specific zones—such as canonical data, wiki-based derivatives, and verbatim evidence—ensuring that agents always operate on the most accurate, source-of-truth-ranked information. The system includes a dispatcher for scheduled maintenance, a local dashboard for monitoring, and integration with Claude Code to facilitate onboarding and task-aware context retrieval.

How it works

Memory model: LLM wiki

AutoGineer overview, from the brain’s own files

The system organizes information into a folder hierarchy where the path itself acts as a trust signal for the LLM. It uses a dispatcher to run scheduled maintenance jobs that check for staleness, propagate updates across linked documents, and audit content health. New information is ingested through a routing process that places content into specific zones: canonical data for facts, wiki pages for operational knowledge, and collections for legal evidence. The system learns from user interactions to suggest fixes and uses frontmatter metadata to allow agents to filter and retrieve information efficiently without full-text scanning.

Folders
FolderRoleFiles
.claudeagent-config193
.githubagent-config6
docsdocs68

The agent reads

The agent writes

Requirements

Works with: Claude Code, OpenCode

Set it up

  1. This brain runs code automatically: 5 hooks (claude-code), 1 script. Review .claude/settings.json, install.sh before opening the folder in Claude Code. No step below turns a hook on for you.

    • .claude/settings.json: bash "$CLAUDE_PROJECT_DIR/.claude/hooks/auto_save_session.sh"
    • .claude/settings.json: bash "$CLAUDE_PROJECT_DIR/.claude/hooks/precompact_save.sh"
    • .claude/settings.json: "$CLAUDE_PROJECT_DIR/.claude/bin/python3" "$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit_frontmatter_check.py"
    • .claude/settings.json: "$CLAUDE_PROJECT_DIR/.claude/bin/python3" "$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit_frontmatter_check.py"
    • .claude/settings.json: "$CLAUDE_PROJECT_DIR/.claude/bin/python3" "$CLAUDE_PROJECT_DIR/.claude/hooks/post_commit_context_check.py"
    • install.sh: sh install.sh
  2. Clone the repository at the commit AutoGineer scanned:

    git clone https://github.com/walm00/business-context-os.git && cd business-context-os && git checkout 463568372f31f24fa6c01121c38f989845ca7c84
  3. Claude Code reads CLAUDE.md from this folder automatically.

  4. cd business-context-os && claude

  5. AutoGineer scanned these files at commit 4635683.

  6. First committed by walm00. Published in walm00/business-context-os. Licence: MIT License.

Usage

AutoGineer overview, from the brain’s own files

  • Automating the maintenance of project documentation and SOPs.
  • Providing AI agents with a reliable, freshness-aware knowledge base.
  • Managing ownership boundaries across large, multi-topic documentation sets.
  • Syncing and auditing context across linked documents to prevent drift.
  • Generating daily digests of system health and pending maintenance tasks.
“Install BCOS from https://github.com/walm00/business-context-os and walk me through onboarding.”
Claude clones the repository, sets up the environment, and guides the user through an interactive onboarding process to configure the context architecture.
“/context bundle market-report:write”
The agent retrieves a curated, freshness-aware package of context specifically relevant to writing a market report.
“Run the audit twice a week.”
The dispatcher updates the maintenance schedule to perform an audit of the knowledge base on a bi-weekly cadence.
“/wiki run https://stripe.com/docs/api”
The system fetches the URL, summarizes the content, and creates a cited wiki entry within the knowledge base.

Documentation by walm00

<p align="center"> <h1 align="center">CLEAR Context OS</h1> <p align="center"> <strong>Knowledge that maintains itself —</strong><br> so your AI doesn't act on what was true last month. </p> <p align="center"> <a href="#install">Install</a> &nbsp;·&nbsp; <a href="#what-it-does">What it does</a> &nbsp;·&nbsp; <a href="#the-wiki-zone">Wiki zone</a> &nbsp;·&nbsp; <a href="#what-ships">What ships</a> &nbsp;·&nbsp; <a href="docs/_bcos-framework/guides/getting-started.md">Getting Started</a> </p> <p align="center"> <img src="https://img.shields.io/badge/claude_code-ready-blueviolet" alt="Claude Code"> <img src="https://img.shields.io/badge/methodology-CLEAR-green" alt="CLEAR Methodology"> <img src="https://img.shields.io/badge/install-one_command-success" alt="One-command install"> <img src="https://img.shields.io/badge/skills-17-blue" alt="17 skills"> <img src="https://img.shields.io/badge/maintenance_jobs-12-orange" alt="12 maintenance jobs"> <img src="https://img.shields.io/badge/license-MIT-lightgrey" alt="MIT"> </p> </p>


Your AI had perfect context — two weeks ago. Then your strategy shifted, three meetings redefined priorities, and the docs you wrote on Monday are wrong by Friday. CLEAR Context OS is the maintenance layer your second brain never had: ownership boundaries enforced mechanically, staleness surfaced on a schedule, drift propagated across linked docs, a dispatcher that learns from your clicks. One source of truth per topic. Searchable. Task-aware. Never silently stale.


Install

Easiest — let Claude do it. Open Claude Code in any directory and paste:

"Install BCOS from https://github.com/walm00/business-context-os and walk me through onboarding."

Claude clones the repo, picks the right install path for your platform (Windows/macOS/Linux), runs setup, and steps straight into onboarding. No prereq checks to debug — if something's missing, Claude resolves it interactively.

Or manually, two commands:

curl -fsSL https://raw.githubusercontent.com/walm00/business-context-os/main/install.sh -o /tmp/bcos-install.sh
bash /tmp/bcos-install.sh

(Requires bash, curl, and tar on PATH — fine on macOS/Linux and on Windows under WSL or Git Bash. Two steps instead of a single bash <(curl …) pipe so Claude Code's permission gate auto-approves both commands — you don't need to drop out to a terminal.)

Drop anything you already have — SOPs, brand docs, meeting notes, exports — into docs/_inbox/ (skip if you're starting fresh). Then open Claude Code in that repo and say:

"Launch BCOS onboarding."

That's the only prompt you need. Onboarding inspects your inbox, scans the rest of the repo, asks one Q&A round, and proposes the architecture for review. Works the same whether the repo is fresh or already full of context.


From zero to running

  INSTALL  →  DISCOVER  →  ALIGN  →  SCHEDULE  →  RUNNING
#StepWhat happens
1InstallOne bash line drops the framework into any repo
2DiscoverClaude scans your repo + any MCP you've connected (Drive, Notion, Slack, Gmail, your CRM — whatever's wired in) for existing context, plus anything you've dropped into docs/_inbox/
3AlignOne Q&A round — project type, key sources, ownership boundaries. Architecture drafted for your review
4SchedulePick maintenance cadence in plain English: "audit twice a week", "dispatcher at 08:30", "deep daydream off"
5RunningDaily digest. Local dashboard. /context bundle <task> ready. Context compounds from here — the system maintains itself.

Simple, guided, automatic. Claude walks you through each step — you review, you decide. Nothing deleted along the way — originals preserved or archived, never silently overwritten.


What it does

Stops context rot in three ways:

What it does
OrganizeEvery doc declares what it owns. CLEAR boundaries prevent the same fact living in 5 places.
MaintainScheduled jobs check freshness, surface stale docs, propagate updates across linked content.
Use/context search and /context bundle <task> give Claude curated, freshness-aware, source-of-truth-ranked context for the job at hand.

The folder tells the LLM what to trust. Claude reads the path before the content: docs/*.md = canonical, _planned/ = not yet real, _archive/ = history, _inbox/ = unprocessed, _collections/ = verbatim evidence, _wiki/ = explanatory. The hierarchy is the trust signal — fewer tokens spent re-deciding, no chance of treating an archived draft as today's truth.

docs/
├── *.md                  ← canonical data points (one source of truth per topic)
├── document-index.md     ← auto-generated TOC
├── current-state.md      ← what's true right now + what changed recently
├── table-of-context.md   ← high-level cluster map for orientation
├── _inbox/               ← drop zone — Claude triages on demand
├── _planned/             ← polished ideas, not yet real
├── _archive/             ← superseded — kept, not deleted
├── _collections/         ← verbatim evidence (invoices, contracts, transcripts)
│   └── <type>/_manifest.md   (each collection has a manifest)
├── _wiki/                ← derivative knowledge (how-tos, source summaries)
│   ├── pages/            ← user-authored explainers
│   ├── source-summary/   ← external captures with citation banners
│   ├── raw/              ← original artifacts (PDFs, HTML)
│   └── queue.md          ← URLs awaiting fetch
└── _bcos-framework/      ← framework docs — never user content
    ├── architecture/     ← 12 zone + system architecture docs
    ├── methodology/      ← 5 CLEAR + ownership + standards docs
    ├── patterns/         ← 8 project-type starting templates
    └── templates/        ← 15 frontmatter / schema / starter files

The wiki zone

Inspired by Karpathy's LLM Wiki approach — give the LLM a wiki of your knowledge, the way you'd brief a smart new hire.

Save anything to the wiki, from anywhere:

/wiki run https://stripe.com/docs/api          ← fetch a URL, summarize, cite
/wiki create from /Users/me/Downloads/spec.pdf ← extract a PDF, link to the binary
/wiki promote docs/_inbox/meeting-notes.md     ← turn an inbox capture into a wiki page

The wiki holds how-tos, runbooks, glossaries, source summaries, post-mortems, decision logs — derivative knowledge that explains and supports your canonical data points. Every page cites what it builds on. Stale pages surface automatically when their sources advance.

Wiki content is searchable alongside the rest of your context:

/context search "stripe billing"       ← cross-zone search, ranked by source-of-truth
/context bundle market-report:write    ← curated bundle for a specific task

The dashboard

A local cockpit at http://127.0.0.1:8091 that shows your system at a glance — no third-party services, no telemetry, no logins.

Image: BCOS dashboard cockpit — knowledge system status, things waiting on you, maintenance routine

python .claude/scripts/bcos-dashboard/run.py
SurfaceWhat you see
CockpitOne-line status (healthy / N waiting / heads-up). Per-job dot strip for every maintenance job (core BCOS + wiki zone).
Update"Run full sync" button → update.py + CLAUDE.md drift review + auto-commit + push, with a live log drawer.
Schedules (/settings/schedules)Per-job preset buttons (daily, mon_wed_fri, weekly_mon, off) + the global auto-commit toggle + auto-fix whitelist. Saves directly to schedule-config.json.
Run history (/settings/runs)Last 20 dispatcher runs with filter chips (job / verdict / trigger).
File health (/settings/files)Frontmatter / cross-reference / staleness findings with one-click fixes.

The dashboard is read-mostly: data flows from the dispatcher's diary, the schedule config, and the canonical context index. Writes (mark-done, schedule preset, auto-commit toggle, file fix) go through dedicated POST endpoints.


Requirements

  • Claude Code (desktop app, CLI, or web)

  • Python 3.8+ on PATH

  • bash (default macOS bash 3.2 is fine)


Updating

python .claude/scripts/update.py

Refreshes framework files only — skills, hooks, scripts, templates. Your docs/ and .private/ are never touched.


Maintenance

One scheduled task per repo. The dispatcher reads schedule-config.json, runs the jobs due today, writes one consolidated digest at docs/_inbox/daily-digest.md, and surfaces findings as one-click cards in the dashboard.

Image: BCOS maintenance schedule dispatcher — daily, weekly, monthly jobs flowing into the daily digest and dashboard cards

You can tune any of it in plain English:

"Run the audit twice a week." · "Move dispatcher to 08:30." · "Turn off deep daydream."

The system learns from your clicks. After three consistent uses of the same fix, the dashboard pre-selects it as a "✨ suggested" action — you accept or reject. Reversals trigger an automatic safety brake.


CLEAR — the methodology

Five principles, one rule each:

  • C — Contextual Ownership · one document owns each topic. No duplicates.

  • L — Linking · reference sources; don't copy them.

  • E — Elimination · consolidate; don't keep two versions.

  • A — Alignment · context that doesn't support decisions doesn't belong.

  • R — Refinement · structured maintenance, not ad-hoc fixes.

The whole framework is the mechanical implementation of these five rules.


What ships

Everything below installs as one repo drop-in. No third-party services, no logins, no telemetry — all state lives in your .claude/ and docs/ folders.

LayerCountExamples
Skills (chat-invokable via slash command)17clear-planner · bcos-wiki · context-routing · context-audit · context-ingest · daydream · learning · schedule-tune · lessons-consolidate · ecosystem-manager
Sub-agents (delegated bounded tasks)3wiki-fetch · explore · plus one optional
Scripts (mechanical Python)46lifecycle_sweep · run_wiki_* (5) · cmd_wiki_* (7) · auto_fix_audit · bcos_inventory · digest_sidecar · record_resolution
Hooks (auto-validation on edit)4post-edit frontmatter check · post-commit context check · pre-compact save · auto-save session
Maintenance jobs (scheduled, one dispatcher)12index-health · audit-inbox · daydream-lessons · daydream-deep · architecture-review · auto-fix-audit · lifecycle-sweep · wiki-stale-propagation · wiki-source-refresh · wiki-graveyard · wiki-coverage-audit · wiki-canonical-drift
Auto-fix IDs (silent dispatcher fixes)12missing-last-updated · eof-newline · frontmatter-field-order · wiki-mark-queue-ingested · wiki-archive-expired-post-mortem
Architecture docs12wiki-zone.md · lifecycle-routing.md · typed-events.md · context-zones.md · content-routing.md · wiki-headless-scripts.md
Methodology docs5CLEAR principles · context architecture · decision framework · document standards · ownership specification
Project patterns (starting templates by project type)8client-project · gtm · marketing · operational · product-development · product-service · internal-tool-app · internal-tool-automation
Templates (frontmatter, schema, starters)15data-point · cluster · current-state · table-of-context · wiki-schema · wiki-config · session-diary-starter

Every script and skill is documented at the source. Every job has a reference doc under .claude/skills/schedule-dispatcher/references/job-*.md. Every typed-event finding maps to a renderer in the dashboard cockpit. Adding a new check means dropping a cmd_*.py or run_*.py, registering it in one map, and the dashboard picks it up.


Contributing

Contributions welcome — open an issue or submit a PR targeting the dev branch. CI runs frontmatter, reference, and ecosystem-integration checks on every PR.

The fastest contribution path is lessons learned. As you use the system it captures insights; if you find one universal, contribute it back.


Origin

Built by Guntis Coders after two years of operating context systems in production. The CLEAR methodology emerged from a practical problem: AI context degrades silently, and no amount of one-time documentation prevents it. The solution required a system — ownership boundaries, automated maintenance, self-learning, and structured reflection.

Background: What is the Table of Contexts and why does it matter.


License

MIT

.claude/scripts/bcos-dashboard/single_repo.py

Shown as plain text.

"""
single_repo.py — Collectors for the BCOS single-repo control panel.

Feeds three panels:
  - Jobs panel        — per-job cards with verdict, schedule, next/last run,
                        actions_needed, auto_fixed, notes, 5-run history
  - Actions inbox     — deduped user-facing decisions from today's digest
                        + the latest diary entries
  - Run history       — unified timeline of the last N diary entries

Data sources (all standard BCOS locations — resolved relative to REPO_ROOT):
  - .claude/quality/schedule-config.json   → enabled jobs + their schedule
  - docs/_inbox/daily-digest.md            → today's per-job verdict + actions
  - .claude/hook_state/schedule-diary.jsonl → immutable run history
  - ~/.local-dashboard/schedules.json      → nextRunAt / lastRunAt snapshot

REPO_ROOT detection:
  1. BCOS_REPO_ROOT env var (explicit override), or
  2. _HERE.parents[2] (default: dashboard lives at .claude/scripts/bcos-dashboard/,
     so three parents up is the repo root)

A shared _snapshot() caches the heavy reads per refresh so the three
collectors read each file once per TTL.
"""

from __future__ import annotations

import json
import os
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path

_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
    sys.path.insert(0, str(_HERE))

from diary_grouper import DiaryEntry, group_by_job, recent_unified  # noqa: E402
from digest_parser import ParsedDigest, parse_digest  # noqa: E402

# 1.1.0: load the typed-event sidecar (daily-digest.json) alongside the prose
# digest. The cockpit action items decorate themselves with the sidecar's
# `category`, `consecutive_runs`, `first_seen`, and `severity_override`
# fields when a matching Finding can be located. Pure additive — if the
# sidecar is absent or 1.0.0, action items keep their existing shape.
_scripts_dir = _HERE.parent
if str(_scripts_dir) not in sys.path:
    sys.path.insert(0, str(_scripts_dir))
try:
    from digest_sidecar import Finding, ParsedSidecar, parse_sidecar  # noqa: E402
except ImportError:  # graceful degrade — older installs without the module
    Finding = None  # type: ignore[misc,assignment]
    ParsedSidecar = None  # type: ignore[misc,assignment]
    parse_sidecar = None  # type: ignore[misc,assignment]
from labels import (  # noqa: E402
    JOB_LABELS,
    decorate_action,
    decorate_diary_entry,
    decorate_file_finding,
    decorate_job,
    humanize_time,
    job_display,
    schedule_display,
    verdict_display,
    verdict_dot,
)


def _repo_root() -> Path:
    override = os.environ.get("BCOS_REPO_ROOT", "").strip()
    if override:
        p = Path(override).expanduser().resolve()
        if p.is_dir():
            return p
    return _HERE.parents[2]


REPO_ROOT = _repo_root()

SCHEDULE_CONFIG_PATH = REPO_ROOT / ".claude" / "quality" / "schedule-config.json"
DIGEST_PATH = REPO_ROOT / "docs" / "_inbox" / "daily-digest.md"
SIDECAR_PATH = REPO_ROOT / "docs" / "_inbox" / "daily-digest.json"
DIARY_PATH = REPO_ROOT / ".claude" / "hook_state" / "schedule-diary.jsonl"
SCHEDULES_PATH = Path.home() / ".local-dashboard" / "schedules.json"

_VERDICT_SEV = {"green": "ok", "amber": "warn", "red": "critical", "error": "critical"}
_VERDICT_EMOJI = {"green": "🟢", "amber": "🟡", "red": "🔴", "error": "⚠️"}

# The canonical BCOS job roster. Consumer configs may enable a subset.
# Unknown jobs are still surfaced with placeholder status so the dashboard
# shows what's POSSIBLE, not just what's configured.
#
# Two groups: core maintenance (5 jobs) + wiki zone maintenance (4 jobs).
# Consumers without a `_wiki/` zone simply leave the wiki jobs disabled in
# `schedule-config.json`; they surface as "not configured" without errors.
_KNOWN_JOBS = [
    # Core BCOS maintenance
    "index-health",
    "audit-inbox",
    "daydream-lessons",
    "daydream-deep",
    "architecture-review",
    # Self-learning safety brake (Friday)
    "auto-fix-audit",
    # Lifecycle classifier (Friday)
    "lifecycle-sweep",
    # Wiki zone maintenance
    "wiki-stale-propagation",
    "wiki-source-refresh",
    "wiki-graveyard",
    "wiki-coverage-audit",
    "wiki-canonical-drift",
]


# ---------------------------------------------------------------------------
# Cockpit (hero)
#
# The cockpit is the "one glance" surface. It wraps a plain-English
# summary sentence + a per-job maintenance strip. All the data is
# already computed by the other collectors — this is pure composition
# with a headline-picking helper.
# ---------------------------------------------------------------------------


def _headline_for_state(action_count: int, overall_sev: str, freshness_sev: str,
                       freshness_value: str | None, first_run: bool = False,
                       any_enabled: bool = False,
                       display_next_run: str | None = None) -> tuple[str, str]:
    """Return (sentence, tone) for the hero block.

    tone is one of ok / warn / critical / info / first_run — drives the
    accent colour and lets the client pick the empty/first-run skin.

    First-run wording branches on whether ANY scheduled job is enabled.
    Claiming "your first check runs tomorrow at 9 AM" when nothing is
    actually scheduled is a lie that erodes trust on day one.
    """
    if first_run:
        if not any_enabled:
            return (
                "Welcome to BCOS. No maintenance jobs are scheduled yet — "
                "open Settings to enable the routine you want.",
                "first_run",
            )
        if display_next_run and display_next_run != "—":
            return (
                f"Welcome to BCOS. Your first maintenance check runs "
                f"{display_next_run}. Come back to see what it finds.",
                "first_run",
            )
        return (
            "Welcome to BCOS. Your maintenance routine is configured and "
            "will run on its next scheduled trigger.",
            "first_run",
        )

    # Freshness critical beats everything — if the snapshot is broken we say so.
    if freshness_sev == "critical":
        return ("Heads up — background data is missing or outdated. "
                "Refresh when you can.", "warn")

    if action_count > 0 and overall_sev == "critical":
        word = "thing needs" if action_count == 1 else "things need"
        return (f"Your attention is needed on {action_count} {word} review.", "critical")

    if action_count > 0:
        word = "thing is" if action_count == 1 else "things are"
        return (f"Your system is healthy. {action_count} {word} waiting on "
                "a decision from you.", "warn")

    if overall_sev == "warn":
        return ("Most things are fine. A few findings worth a look "
                "when you have time.", "warn")

    if freshness_sev == "warn":
        return (f"Everything is in order. Note: background data is "
                f"{freshness_value or 'a bit old'}.", "info")

    return ("Everything is in order. Nothing needs your attention "
            "right now.", "ok")


def collect_cockpit() -> dict:
    # Reuse the other collectors — they're cached by the outer TTL
    # machinery so this isn't a re-read storm.
    from freshness import collect_snapshot_freshness

    fresh = collect_snapshot_freshness()
    jobs = collect_jobs_panel()
    actions = collect_actions_inbox()

    # First-run detector — diary file has zero entries (or is missing).
    # A fresh BCOS install ships with schedule-config.json so we cannot
    # use that as the signal — only the diary tells us whether the
    # dispatcher has actually run yet. See plan open question #3.
    first_run = False
    try:
        if not DIARY_PATH.is_file() or DIARY_PATH.stat().st_size == 0:
            first_run = True
    except Exception:
        first_run = False

    # Pull per-job data for the maintenance strip (human label + verdict glyph + severity).
    # Track:
    #   - any_enabled: at least one scheduled job is on (drives first-run copy)
    #   - earliest_next_run: the soonest configured next-run timestamp across
    #     enabled jobs (used in the first-run welcome line when applicable)
    dots = []
    worst_sev = "ok"
    any_enabled = False
    earliest_next_run_iso: str | None = None
    earliest_next_run_display: str | None = None
    # Jobs that ACTUALLY run end-to-end from the dashboard via a standalone
    # Python script (subprocess). All others — judgement jobs (audit-inbox,
    # daydream-*, architecture-review) and wiki jobs without scripts yet —
    # fall through to a "Run via chat" hint. Honest UX: only call something
    # Run-now if the click really does the thing.
    #
    # Headless wiki jobs ship per docs/_planned/wiki-headless-scripts/:
    #   - wiki-stale-propagation: run_wiki_stale_propagation.py (shipped)
    #   - wiki-graveyard, wiki-coverage-audit, wiki-source-refresh: TODO
    _HEADLESS_RUNNABLE = {
        "index-health",
        "auto-fix-audit",
        "wiki-stale-propagation",
    }

    for j in jobs.get("jobs") or []:
        sev = _VERDICT_SEV.get(j.get("verdict") or "", "muted")
        is_enabled = bool(j.get("enabled"))
        if is_enabled:
            any_enabled = True
            nri = j.get("next_run_iso")
            if nri and (earliest_next_run_iso is None or nri < earliest_next_run_iso):
                earliest_next_run_iso = nri
                earliest_next_run_display = j.get("display_next_run")

        # Last 5 verdicts as compact dots (most-recent first → reverse for
        # left-to-right reading). Diary entries are already newest-first.
        history = (j.get("history") or [])[:5]
        recent_verdicts = [
            {"ts": h.get("ts"), "verdict": h.get("verdict"),
             "findings_count": h.get("findings_count", 0)}
            for h in reversed(history)
        ]

        # Last-run findings count (for the card status line).
        last_findings = None
        if history:
            last_findings = (history[0] or {}).get("findings_count")

        job_id = j.get("job") or ""
        dots.append({
            "job": job_id,
            "label": j.get("display_name") or job_id,
            "dot": j.get("display_dot") or ("○" if not j.get("verdict") else "●"),
            "severity": sev,
            "verdict": j.get("verdict"),
            "display_verdict": j.get("display_verdict"),
            "schedule": j.get("display_schedule_short"),
            "last_run": j.get("display_last_run"),
            "next_run": j.get("display_next_run"),
            "enabled": is_enabled,
            # placeholder copy the client can show next to a dim dot:
            #   - "Not configured" when enabled=false
            #   - "Scheduled — not run yet" when enabled=true but no verdict
            #   - "" when there's a real verdict
            "placeholder": (
                "Not configured" if not is_enabled
                else ("Scheduled — not run yet" if not j.get("verdict") else "")
            ),
            # New fields for the per-job card layout (replaces dot strip):
            "recent_verdicts": recent_verdicts,           # last 5, oldest→newest
            "last_findings_count": last_findings,         # int or None
            "headless_runnable": job_id in _HEADLESS_RUNNABLE,
            "hint": j.get("display_hint") or "",
            "actions_needed_count": len(j.get("actions_needed") or []),
        })
        if _rank(sev) > _rank(worst_sev):
            worst_sev = sev

    freshness_sev = fresh.get("_severity", "ok")
    freshness_value = None
    stats = fresh.get("stats") or []
    if stats:
        freshness_value = stats[0].get("value")

    action_count = int(actions.get("count") or 0)
    headline, tone = _headline_for_state(
        action_count=action_count,
        overall_sev=worst_sev,
        freshness_sev=freshness_sev,
        freshness_value=freshness_value,
        first_run=first_run,
        any_enabled=any_enabled,
        display_next_run=earliest_next_run_display,
    )

    # Empty-state detection — everything healthy, nothing waiting. The
    # client uses this to collapse the cockpit to a one-line "all clear".
    is_empty = (
        not first_run
        and action_count == 0
        and worst_sev == "ok"
        and freshness_sev == "ok"
    )

    # Panel severity tracks the tone so the aggregate health badge stays honest.
    _TONE_TO_SEV = {"ok": "ok", "warn": "warn", "critical": "critical",
                    "info": "info", "first_run": "info"}

    return {
        "kind": "cockpit",
        "title": "Your knowledge system",
        "headline": headline,
        "tone": tone,
        "first_run": first_run,
        "is_empty": is_empty,
        "action_count": action_count,
        "actions": {
            "items": actions.get("items") or [],
            "hidden_resolved": actions.get("hidden_resolved") or 0,
        },
        "dots": dots,
        "freshness": {
            "value": freshness_value,
            "severity": freshness_sev,
            "show_line": freshness_sev in {"warn", "critical"},
        },
        "bcos_framework": _collect_bcos_framework_block(),
        "atlas": _collect_atlas_teaser(),
        "_severity": _TONE_TO_SEV.get(tone, "ok"),
    }


def _collect_bcos_framework_block() -> dict:
    """Cockpit block for the §0 BCOS framework strip (status + 3 buttons).

    Resolves to a small dict the JS cockpit renderer consumes. Errors are
    swallowed so a transient GitHub-API hiccup never breaks the cockpit.
    """
    try:
        from bcos_sync import collect_freshness
        return collect_freshness()
    except Exception as exc:  # noqa: BLE001
        return {"error": f"{type(exc).__name__}: {exc}"}


def _collect_atlas_teaser() -> dict:
    """Small cockpit block linking to the Context Atlas view."""
    try:
        from atlas_collectors import collect_atlas_teaser
        return collect_atlas_teaser()
    except Exception as exc:  # noqa: BLE001
        return {"ok": False, "error": f"{type(exc).__name__}: {exc}", "href": "/atlas"}


# ---------------------------------------------------------------------------
# §04 File health (v1: frontmatter + stale + textual)
# ---------------------------------------------------------------------------

def collect_file_health() -> dict:
    # Lazy import — file_health imports REPO_ROOT back from this module.
    from file_health import collect as _collect

    report = _collect()
    total = report.get("total", 0)
    fixable = report.get("fixable", 0)

    # Severity escalates with unfixable finding count
    cats = report.get("categories", {}) or {}
    # Attach display_* fields to every finding.
    for items in cats.values():
        for f in items:
            decorate_file_finding(f)
    unfixable = sum(1 for lst in cats.values() for f in lst if not f.get("fix_id"))
    if total == 0:
        sev = "ok"
    elif unfixable == 0:
        sev = "info"   # all findings are auto-fixable, not really bad
    elif unfixable <= 3:
        sev = "warn"
    else:
        sev = "critical"

    return {
        "kind": "file_health",
        "categories": cats,
        "total": total,
        "fixable": fixable,
        "stale_threshold_days": report.get("stale_threshold_days", 30),
        "_severity": sev,
    }

_SEV_ORDER = {"muted": 0, "info": 1, "ok": 2, "warn": 3, "critical": 4}


def _rank(sev: str) -> int:
    return _SEV_ORDER.get(sev, 0)


def _load_schedule_config() -> dict:
    if not SCHEDULE_CONFIG_PATH.is_file():
        return {}
    try:
        return json.loads(SCHEDULE_CONFIG_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {}


def _load_schedules_map() -> dict[str, dict]:
    """Return { taskId: task } from ~/.local-dashboard/schedules.json."""
    if not SCHEDULES_PATH.is_file():
        return {}
    try:
        data = json.loads(SCHEDULES_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {}
    tasks = data.get("tasks") or []
    return {t.get("taskId"): t for t in tasks if isinstance(t, dict) and t.get("taskId")}


def _humanize_relative(iso: str | None, now: datetime | None = None) -> str:
    if not iso:
        return "—"
    now = now or datetime.now(timezone.utc)
    try:
        dt = datetime.fromisoformat(iso.replace("Z", "+00:00"))
    except ValueError:
        return "—"
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    delta = (now - dt.astimezone(timezone.utc)).total_seconds()
    future = delta < 0
    mag = abs(delta)
    if mag < 60:
        s = f"{int(mag)}s"
    elif mag < 3600:
        s = f"{int(mag // 60)}m"
    elif mag < 86400:
        s = f"{mag / 3600:.1f}h"
    elif mag < 86400 * 14:
        s = f"{mag / 86400:.1f}d"
    else:
        return dt.date().isoformat()
    return f"in {s}" if future else f"{s} ago"


def _node_task_id() -> str | None:
    """Infer the scheduled-task id for this node.

    BCOS convention: the dispatcher scheduled task is `bcos-<slug>` where
    slug matches the repo directory name (lowercased). If BCOS_TASK_ID is
    set, that takes precedence for non-standard installs.
    """
    override = os.environ.get("BCOS_TASK_ID", "").strip()
    if override:
        return override
    return f"bcos-{REPO_ROOT.name.lower()}"


@dataclass
class _Snapshot:
    cfg: dict
    digest: ParsedDigest | None
    diary_by_job: dict[str, list[DiaryEntry]]
    schedules: dict[str, dict]
    sidecar: "ParsedSidecar | None" = None  # 1.1.0 — typed-event sidecar


def _snapshot() -> _Snapshot:
    side = None
    if parse_sidecar is not None:
        try:
            side = parse_sidecar(SIDECAR_PATH)
        except Exception:  # noqa: BLE001
            # Malformed sidecar should not crash the dashboard — log silently and
            # continue with prose-only data. The dispatcher will emit a
            # schema-validation-failed finding on its next tick.
            side = None
    return _Snapshot(
        cfg=_load_schedule_config(),
        digest=parse_digest(DIGEST_PATH),
        diary_by_job=group_by_job(DIARY_PATH, n=5),
        schedules=_load_schedules_map(),
        sidecar=side,
    )


# ---------------------------------------------------------------------------
# §B Jobs panel
# ---------------------------------------------------------------------------

def collect_jobs_panel() -> dict:
    """One card's worth of data per job.

    Returns union of (configured jobs ∪ known BCOS jobs) so missing config
    surfaces as "not configured" instead of being invisible.
    """
    snap = _snapshot()
    cfg_jobs: dict[str, dict] = (snap.cfg.get("jobs") or {}) if isinstance(snap.cfg, dict) else {}

    job_ids: list[str] = []
    seen: set[str] = set()
    for jid in cfg_jobs:
        if jid not in seen:
            job_ids.append(jid); seen.add(jid)
    for jid in _KNOWN_JOBS:
        if jid not in seen:
            job_ids.append(jid); seen.add(jid)

    task_id = _node_task_id()
    sched_task = snap.schedules.get(task_id) if task_id else None
    next_run_iso = sched_task.get("nextRunAt") if sched_task else None
    last_run_iso = sched_task.get("lastRunAt") if sched_task else None

    worst_sev = "ok"
    cards = []
    for jid in job_ids:
      try:
        cfg_entry = cfg_jobs.get(jid) or {}
        enabled = bool(cfg_entry.get("enabled")) if cfg_entry else False
        schedule = cfg_entry.get("schedule") if cfg_entry else None

        history = snap.diary_by_job.get(jid) or []
        last = history[0] if history else None

        dg_job = snap.digest.jobs.get(jid) if snap.digest else None
        verdict = None
        actions_needed: list[str] = []
        notes = ""
        if last is not None:
            verdict = last.verdict
            actions_needed = list(last.actions_needed)
            notes = last.notes
        if dg_job is not None:
            verdict = dg_job.verdict
            notes = dg_job.body or notes

        if cfg_entry:
            status = "configured" if enabled else "disabled"
        else:
            status = "unknown"
        status_detail = "never run" if not history else None

        sev = _VERDICT_SEV.get(verdict or "", "muted") if history else "muted"
        if _rank(sev) > _rank(worst_sev):
            worst_sev = sev

        card = {
            "job": jid,
            "status": status,
            "status_detail": status_detail,
            "enabled": enabled,
            "schedule": schedule or ("off" if cfg_entry and not enabled else "—"),
            "verdict": verdict,
            "verdict_emoji": _VERDICT_EMOJI.get(verdict or "", "·"),
            "next_run_iso": next_run_iso if enabled else None,
            "last_run_iso": last.ts if last else None,
            "next_run_rel": _humanize_relative(next_run_iso) if enabled else "—",
            # Only show last-run when THIS job has a diary entry. The
            # scheduled-task-level lastRunAt refers to the whole dispatcher
            # run, not this specific job — using it as a fallback would
            # falsely show "N hours ago" for jobs that never actually ran.
            "last_run_rel": _humanize_relative(last.ts) if last else "—",
            "actions_needed": actions_needed,
            "auto_fixed": list(last.auto_fixed) if last else [],
            "notes": notes,
            "history": [
                {"ts": e.ts, "verdict": e.verdict, "findings_count": e.findings_count}
                for e in history
            ],
        }
        decorate_job(card)
        cards.append(card)
      except Exception as exc:  # noqa: BLE001 — per-job isolation
        # Plan P3_006: one job's collect failure must not blank the whole
        # cockpit. Emit a stub card carrying the error so the dot renders
        # as a warning glyph with hover context.
        cards.append({
            "job": jid,
            "status": "unknown",
            "status_detail": "collect failed",
            "enabled": False,
            "schedule": None,
            "verdict": "error",
            "verdict_emoji": "⚠️",
            "next_run_iso": None,
            "last_run_iso": None,
            "next_run_rel": "—",
            "last_run_rel": "—",
            "actions_needed": [],
            "auto_fixed": [],
            "notes": f"collect failed: {type(exc).__name__}: {exc}",
            "history": [],
            "display_name": jid,
            "display_hint": f"collect failed: {type(exc).__name__}",
            "display_dot": "⚠",
            "display_verdict": "Failed to collect",
            "display_status": "Collect error",
            "display_schedule_short": "—",
            "display_schedule_long": "—",
            "display_next_run": "—",
            "display_last_run": "—",
            "_error": True,
        })

    return {
        "kind": "jobs_panel",
        "jobs": cards,
        "task_id": task_id,
        "repo_root": str(REPO_ROOT),
        "_severity": worst_sev,
    }


# ---------------------------------------------------------------------------
# §C Per-job detail (drawer)
#
# Rich payload for a single job — everything the side drawer needs to
# render without a second round-trip. Composes off the same _snapshot()
# the other collectors use, so it costs nothing extra during a refresh
# tick.
# ---------------------------------------------------------------------------


def collect_job_detail(job_id: str) -> dict:
    """Per-job payload for the drawer.

    Returns a dict with:
      - identity: job, display_name, description (JOB_LABELS hint)
      - schedule: raw + display_short + display_long + enabled flag
      - timing: next_run_iso/rel + last_run_iso/rel
      - verdict: raw + display + dot
      - today: digest body for this job (markdown), if present
      - actions_needed / auto_fixed (from latest diary entry)
      - runs: last 5 history entries with display_when + display_verdict
      - technical: schedule_config_path, task_id, repo_root, fingerprint inputs
    """
    jid = (job_id or "").strip()
    if not jid:
        return {"error": "job_id required", "_severity": "warn"}

    snap = _snapshot()
    cfg_jobs: dict[str, dict] = (snap.cfg.get("jobs") or {}) if isinstance(snap.cfg, dict) else {}
    cfg_entry = cfg_jobs.get(jid) or {}

    if not cfg_entry and jid not in JOB_LABELS and jid not in _KNOWN_JOBS:
        return {"error": f"unknown job: {jid}", "_severity": "warn"}

    enabled = bool(cfg_entry.get("enabled")) if cfg_entry else False
    schedule = cfg_entry.get("schedule")
    if cfg_entry:
        status = "configured" if enabled else "disabled"
    else:
        status = "unknown"

    label, hint = job_display(jid)

    task_id = _node_task_id()
    sched_task = snap.schedules.get(task_id) if task_id else None
    next_run_iso = sched_task.get("nextRunAt") if (sched_task and enabled) else None
    last_task_run_iso = sched_task.get("lastRunAt") if sched_task else None

    history = snap.diary_by_job.get(jid) or []
    last = history[0] if history else None

    dg_job = snap.digest.jobs.get(jid) if snap.digest else None
    verdict = None
    actions_needed: list[str] = []
    auto_fixed: list[str] = []
    notes = ""
    if last is not None:
        verdict = last.verdict
        actions_needed = list(last.actions_needed)
        auto_fixed = list(last.auto_fixed)
        notes = last.notes
    if dg_job is not None:
        verdict = dg_job.verdict

    today_body = (dg_job.body or "").strip() if dg_job else ""

    runs = []
    for e in history[:5]:
        sev = _VERDICT_SEV.get(e.verdict, "muted")
        runs.append({
            "ts": e.ts,
            "verdict": e.verdict,
            "display_verdict": verdict_display(e.verdict),
            "display_dot": verdict_dot(e.verdict),
            "display_when": humanize_time(e.ts),
            "severity": sev,
            "findings_count": e.findings_count,
            "trigger": e.trigger or "—",
            "notes_preview": (e.notes[:180] + "…") if len(e.notes) > 180 else e.notes,
        })

    sev = _VERDICT_SEV.get(verdict or "", "muted") if history else "muted"

    # Frequency hint: when the last 5 runs are all green AND the current
    # schedule is denser than the next preset down, suggest slowing it down.
    # The dispatcher hint is mechanical-first — the user makes the final call
    # via the in-drawer one-click button.
    frequency_hint: dict | None = None
    if (
        enabled
        and len(runs) >= 5
        and all((r.get("verdict") == "green") for r in runs[:5])
    ):
        # Map current schedule -> next-slower preset id (used by /api/schedule/preset).
        # Skip when already at "weekly_mon" or below; further slowing is the human's call.
        sched_norm = (schedule or "").strip().lower()
        slower = None
        if sched_norm in ("daily", "everyday", "every day", "mon-fri", "mon-fri,daily"):
            slower = ("mon_wed_fri", "Mon / Wed / Fri")
        elif sched_norm in ("mon,wed,fri", "mon, wed, fri", "mon_wed_fri"):
            slower = ("weekly_mon", "weekly (Monday)")
        if slower:
            preset_id, preset_label = slower
            frequency_hint = {
                "reason": "5_green",
                "current_schedule": schedule,
                "suggested_preset": preset_id,
                "suggested_label": preset_label,
                "message": (
                    f"Last 5 runs were all green. Want to slow this to {preset_label}?"
                ),
            }

    return {
        "kind": "job_detail",
        "job": jid,
        "display_name": label,
        "description": hint,
        "status": status,
        "enabled": enabled,
        "schedule": schedule or ("off" if cfg_entry and not enabled else None),
        "display_schedule_short": schedule_display(schedule, short=True),
        "display_schedule_long": schedule_display(schedule, short=False),
        "frequency_hint": frequency_hint,
        "verdict": verdict,
        "display_verdict": verdict_display(verdict),
        "display_dot": verdict_dot(verdict),
        "next_run_iso": next_run_iso,
        "last_run_iso": last.ts if last else None,
        "display_next_run": humanize_time(next_run_iso) if next_run_iso else "—",
        "display_last_run": humanize_time(last.ts) if last else "—",
        "today_body": today_body,
        "actions_needed": actions_needed,
        "auto_fixed": auto_fixed,
        "notes": notes,
        "runs": runs,
        "technical": {
            "task_id": task_id,
            "repo_root": str(REPO_ROOT),
            "schedule_config_path": str(SCHEDULE_CONFIG_PATH),
            "diary_path": str(DIARY_PATH),
            "digest_path": str(DIGEST_PATH),
            "schedules_snapshot_path": str(SCHEDULES_PATH),
            "task_last_run_iso": last_task_run_iso,
        },
        "_severity": sev,
    }


# ---------------------------------------------------------------------------
# §D Run history
# ---------------------------------------------------------------------------

def collect_run_history(n: int = 20) -> dict:
    entries = recent_unified(DIARY_PATH, n=n)
    worst_sev = "ok"
    items = []
    for e in entries:
        sev = _VERDICT_SEV.get(e.verdict, "muted")
        if _rank(sev) > _rank(worst_sev):
            worst_sev = sev
        row = {
            "timestamp": e.ts,
            "job": e.job,
            "verdict": e.verdict,
            "verdict_emoji": _VERDICT_EMOJI.get(e.verdict, "·"),
            "findings_count": e.findings_count,
            "trigger": e.trigger or "—",
            "notes_preview": (e.notes[:180] + "…") if len(e.notes) > 180 else e.notes,
            "actions_preview": e.actions_needed[:3],
            "severity": sev,
        }
        decorate_diary_entry(row)
        items.append(row)
    return {
        "kind": "run_history",
        "items": items,
        "_severity": worst_sev if items else "info",
    }


# ---------------------------------------------------------------------------
# §E Actions inbox
# ---------------------------------------------------------------------------

def collect_actions_inbox() -> dict:
    # Lazy import to avoid a module-level cycle (actions_resolved imports
    # REPO_ROOT from this module).
    from actions_resolved import fingerprint, load_resolved_set

    snap = _snapshot()
    resolved = load_resolved_set()

    items: list[dict] = []
    hidden = 0
    seen_titles: set[str] = set()

    import re as _re
    _PATH_RE = _re.compile(r"(docs/[A-Za-z0-9_./-]+\.[A-Za-z0-9]+)")

    def _emit(title: str, source: str, source_job: str | None, number: int | None, body: str) -> None:
        nonlocal hidden
        fp = fingerprint(title)
        if fp in resolved:
            hidden += 1
            return
        # Detect referenced repo-relative file path and flag whether it
        # actually exists. The seed/fixture digest references demo paths
        # (Q1-notes.md, competitor-x.md, …) that won't resolve on a fresh
        # repo — surfacing the "example" tag prevents users from clicking
        # Open file and getting a silent "path not found" error.
        ref_path: str | None = None
        path_exists = True
        m = _PATH_RE.search(title)
        if m:
            ref_path = m.group(1)
            try:
                path_exists = (REPO_ROOT / ref_path).exists()
            except Exception:  # noqa: BLE001
                path_exists = False
        item = {
            "source": source,
            "source_job": source_job,
            "number": number,
            "title": title,
            "body": body,
            "fingerprint": fp,
            "ref_path": ref_path,
            "path_exists": path_exists,
        }
        decorate_action(item)
        items.append(item)

    # 1.1.0: look up the typed sidecar Finding by `number` to attach
    # category / consecutive_runs / first_seen / severity_override to the
    # matching action item. Absent sidecar or 1.0.0 sidecar: skipped silently
    # (items keep their existing shape).
    sidecar_findings_by_number: dict[int, "Finding"] = {}
    if snap.sidecar is not None:
        for f in snap.sidecar.findings:
            sidecar_findings_by_number[int(f.number)] = f

    def _attach_sidecar_fields(item: dict, number: int | None) -> None:
        if number is None or not sidecar_findings_by_number:
            return
        f = sidecar_findings_by_number.get(int(number))
        if f is None:
            return
        category = getattr(f, "category", "repo-context") or "repo-context"
        consecutive_runs = int(getattr(f, "consecutive_runs", 1) or 1)
        severity_override = getattr(f, "severity_override", None)
        stuck = severity_override == "stuck" or consecutive_runs >= 3
        item["category"] = category
        item["consecutive_runs"] = consecutive_runs
        item["first_seen"] = getattr(f, "first_seen", None)
        item["stuck"] = stuck
        item["finding_type"] = f.finding_type
        if category == "bcos-framework":
            item["footer_note"] = (
                "Reported to BCOS — will be fixed in next release."
            )
            # Framework findings get the acknowledge action, not the
            # standard Fix/Mark-done flow. Front-end branches on this.
            item["primary_action"] = "acknowledge"

    if snap.digest is not None:
        for act in snap.digest.actions:
            key = act.title.strip().lower()
            if key in seen_titles:
                continue
            seen_titles.add(key)
            before_count = len(items)
            _emit(
                title=act.title.strip(),
                source="digest",
                source_job=_infer_source_job(act.title, act.body, snap.digest),
                number=act.number,
                body=act.body,
            )
            if len(items) > before_count:
                _attach_sidecar_fields(items[-1], act.number)

    # 1.1.0: sidecar fallback. When the prose digest's Action-needed table
    # format isn't recognised by digest_parser (it expects ### N. headings;
    # the 1.1.0 template uses markdown tables), the sidecar IS the source.
    # Emit any sidecar finding whose number wasn't already covered above so
    # the user sees their cockpit cards even if the prose parser misses them.
    if snap.sidecar is not None:
        for f in snap.sidecar.findings:
            # Synthesize a short title + body from the typed fields. This is
            # the same shape `_emit` produces from the prose parser so the
            # rest of the pipeline (atlas-open buttons, mark-done flow,
            # filtering) works identically.
            attrs = f.finding_attrs or {}
            primary_attr = (
                attrs.get("file")
                or attrs.get("wiki_file")
                or attrs.get("data_point_file")
                or attrs.get("job")
                or attrs.get("expected_path")
                or attrs.get("rule_id")
                or attrs.get("offending_finding_type")
                or "(no primary attr)"
            )
            title = f"{f.finding_type}: {primary_attr}"
            key = title.strip().lower()
            if key in seen_titles:
                continue
            seen_titles.add(key)
            before_count = len(items)
            _emit(
                title=title,
                source="digest",
                source_job=f.emitted_by,
                number=int(f.number),
                body="",
            )
            if len(items) > before_count:
                _attach_sidecar_fields(items[-1], int(f.number))

    for job, entries in snap.diary_by_job.items():
        if not entries:
            continue
        latest = entries[0]
        for raw in latest.actions_needed:
            key = raw.strip().lower()
            if key in seen_titles:
                continue
            if any(key.split(":")[0].strip() in t for t in seen_titles):
                continue
            seen_titles.add(key)
            _emit(
                title=raw.strip(),
                source="diary",
                source_job=job,
                number=None,
                body="",
            )

    sev = "ok" if not items else ("warn" if len(items) <= 2 else "critical")
    return {
        "kind": "actions_inbox",
        "items": items,
        "count": len(items),
        "hidden_resolved": hidden,
        "_severity": sev,
    }


def _infer_source_job(title: str, body: str, pd: ParsedDigest) -> str | None:
    if not pd.jobs:
        return None
    probe = (title.split("—")[0] + " " + body).lower()
    tokens = {w for w in probe.replace("`", " ").split() if len(w) > 3}
    best = None
    best_score = 0
    for job_id, section in pd.jobs.items():
        body_l = section.body.lower()
        score = sum(1 for t in tokens if t in body_l)
        if score > best_score:
            best = job_id
            best_score = score
    return best if best_score >= 2 else None
278 files · 9 not served here

Advantages

Limitations

  • Requires adherence to a specific folder structure and frontmatter schema to function correctly. evidence: README.md
  • Maintenance jobs and dispatcher functionality rely on Python and bash being available on the host system. evidence: README.md

Security

Risk level: Medium risk

This brain runs

Agent instructions that run or fetch something (170)

  • .claude/agents/wiki-fetch/AGENT.md: A 100k-token URL fetched into the main thread by `/wiki run` saturates the context window before a single page lands.
  • .claude/agents/wiki-fetch/AGENT.md: | Calling skill | Subcommand | Trigger | |---|---|---| | `bcos-wiki` | `run` Pass 1 | Each URL in the queue → one `wiki-fetch` invocation | | `bcos-wiki` | `refresh` full-tier | Source-summary past `stale_threshold_days` and HEAD signals changed | | `bcos-wiki` | `promote` (Path B) | S …[+132 chars] · uses the network
  • .claude/agents/wiki-fetch/AGENT.md: The cumulative budget guard ([`_wiki_budget.decide_dispatch_strategy`](../../scripts/_wiki_budget.py)) decides whether N sub-agents run in parallel or serial — `parallel` when projected cumulative tokens stay ≤ 80% of the main context limit;
  • .claude/agents/wiki-fetch/AGENT.md: queue an explicit retry tag for next run | | `error.kind: fetch-error` | 4xx/5xx from upstream | Surface to user;
  • .claude/agents/wiki-fetch/AGENT.md: `python .claude/scripts/test_wiki_fetch.py` — asserts this AGENT.md declares the documented input kinds and output fields, that the 4000-token cap is stated, that calling-skill docs reference both `wiki-fetch` and the Task tool, and that the result-schema validator + cumulative budget g …[+47 chars]
  • .claude/skills/bcos-wiki/SKILL.md: **`/wiki`** with subcommands: `init`, `init --defaults`, `run`, `queue`, `promote`, `create`, `review`, `archive`, `refresh`, `lint`, `remove`, `schema`, `search`
  • .claude/skills/bcos-wiki/SKILL.md: "Run the wiki refresh"
  • .claude/skills/bcos-wiki/SKILL.md: | | `run [<url>]` | implemented | P3 | Path A: batch (no arg) processes all `## Pending` queue items;
  • .claude/skills/bcos-wiki/SKILL.md: single (with URL) auto-queues + ingests | | `queue <url> [tags]` | implemented | P3 | Append URL to `## Pending` of `queue.md` without fetching | | `ingest` | shared protocol | P3 | Internal — called by `run`/`promote`/`create`;
  • .claude/skills/bcos-wiki/SKILL.md: optionally re-pull `builds-on` data points to verify | | `archive <slug>` | implemented | P3 | Soft-delete to `_wiki/.archive/` with dangling-reference scan | | `refresh <slug>` | implemented | P3 | Path A: re-fetch a `source-summary` page (refresh-must-rediscover applies, D-05 two-tie …[+470 chars]
  • .claude/skills/bcos-wiki/SKILL.md: `run` → `run.md`
  • .claude/skills/bcos-wiki/SKILL.md: Run `/wiki init` first."* and exit.
  • .claude/skills/bcos-wiki/SKILL.md: | Choice | Behavior | |---|---| | Init with defaults + proceed | Run `.claude/scripts/cmd_wiki_init.py` (or equivalent `ensure_wiki_zone.py` call).
  • .claude/skills/bcos-wiki/SKILL.md: Then resume the original subcommand (`create from`, `promote`, `run`, etc.) as if the Guard had passed.
  • .claude/skills/bcos-wiki/SKILL.md: | | Full interview + proceed | Read `init.md` and run the 6-question interactive scaffold.
  • .claude/skills/bcos-wiki/SKILL.md: The dispatched subcommand files (`create.md`, `promote.md`, `run.md`, `refresh.md`, `lint.md`, `archive.md`, `review.md`, `remove.md`, `schema.md`, `search.md`, `bundle.md`, `queue.md`) all reference *"the `.config.yml` Guard defined in `SKILL.md`"* — they inherit this AskUserQuestion p …[+46 chars]
  • .claude/skills/bcos-wiki/SKILL.md: The human reviews diffs and runs `git commit` / `git push` when ready. · uses the network
  • .claude/skills/bcos-wiki/SKILL.md: This applies to `run`, `refresh`, and `create` (when source content is large).
  • .claude/skills/bcos-wiki/SKILL.md: Users who want a binary in collections-as-evidence must run `/collections add` explicitly.
  • .claude/skills/bcos-wiki/SKILL.md: ├── run.md ← /wiki run [<url>]
  • .claude/skills/bcos-wiki/SKILL.md: ├── ingest.md ← shared ingest pipeline (called by run/promote/create/refresh)
  • .claude/skills/clear-planner/SKILL.md: Step 2: Run Discovery
  • .claude/skills/clear-planner/SKILL.md: { "step": 2, "name": "Run Discovery", "status": "in_progress" }
  • .claude/skills/clear-planner/SKILL.md: **Run all three discovery scripts:**
  • .claude/skills/clear-planner/SKILL.md: bash .claude/agents/agent-discovery/find_agents.sh
  • .claude/skills/clear-planner/SKILL.md: bash .claude/skills/skill-discovery/find_skills.sh
  • .claude/skills/clear-planner/SKILL.md: python .claude/scripts/find_lessons.py --tags "#{scenarioType}"
  • .claude/skills/clear-planner/SKILL.md: Run discovery scripts (Step 2 results)
  • .claude/skills/clear-planner/SKILL.md: | | ------------------- | ------------------------------------ | --------------------------------------------------------- | ------------------- | | Integration Audit | Detect stale references in ecosystem | `python .claude/scripts/analyze_integration.py --staged` + AI review | **YES …[+455 chars]
  • .claude/skills/clear-planner/SKILL.md: | Scenario | Key Requirement | | ----------------- | ------------------------------------------------------------------ | | **AGENTING** | Check for ecosystem overlaps + run integration audit before commit | | **DOCUMENTAT …[+76 chars]
  • .claude/skills/context-audit/SKILL.md: Run all five audit categories against the scoped files.
  • .claude/skills/context-audit/SKILL.md: python .claude/scripts/build_document_index.py
  • .claude/skills/context-ingest/SKILL.md: | Input shape | Dispatch to | Why | |-------------|-------------|-----| | HTTP/HTTPS URL (article, GitHub, YouTube) | `/wiki run <url>` (the `bcos-wiki` skill, Path A) | Wiki owns external-source ingest, banner citations, schema validation, refresh tier | | Path inside `docs/_inbox/` a …[+508 chars]
  • .claude/skills/context-ingest/SKILL.md: If the input is mixed (e.g. Slack export *and* a URL), do mine first, then handle the URL via `/wiki run` separately, then return to ingest for whatever's left.
  • .claude/skills/context-ingest/SKILL.md: Run `python .claude/scripts/build_document_index.py` after material changes
  • .claude/skills/context-ingest/SKILL.md: **→ Rebuild Document Index** — Always: `python .claude/scripts/build_document_index.py`
  • .claude/skills/context-ingest/SKILL.md: **→ context-audit** — If a new data point was created: "Want me to run a quick CLEAR audit on the new doc?"
  • .claude/skills/context-mine/SKILL.md: Recommend: "Run context-ingest to classify and route these items"
  • .claude/skills/context-onboarding/SKILL.md: | What you see | What you do | |---|---| | User shares a URL, pitch deck, or describes their business | Gather from those sources, ask 2-3 follow-ups if needed | | Repo already has docs/, README, markdown files with business content | Scan the repo, map what exists | | User drops files …[+394 chars] · uses the network
  • .claude/skills/context-onboarding/SKILL.md: Before anything else, run **once** per onboarding session to make sure the BCOS-managed instructions are present in `CLAUDE.md`.
  • .claude/skills/context-onboarding/SKILL.md: This catches the `git clone`-without-`install.sh` case: if `CLAUDE.md` exists but lacks the `<!-- BCOS:CORE:START -->` / `<!-- BCOS:CORE:END -->` markers, the framework section is missing or unstructured. · uses the network
  • .claude/skills/context-onboarding/SKILL.md: If `.claude/bcos-claude-reference.md` doesn't exist yet (fresh clone), point `--source` at the upstream template available in this repo's tree (the same file install.sh would have copied).
  • .claude/skills/context-onboarding/SKILL.md: **First-run routing principle:** onboarding is allowed to identify all BCOS zones, but it should only create active data points after the user approves the architecture.
  • .claude/skills/context-onboarding/SKILL.md: | Material | First-run handling | |---|---| | Canonical facts, current decisions, project/task/business reality | Draft active data points in `docs/*.md` | | Raw notes, unsorted snippets, uncertain material | Keep in `docs/_inbox/` until triaged | | Future ideas, plans, or maybes | Par …[+578 chars]
  • .claude/skills/context-onboarding/SKILL.md: Options: **Activate** (set all to active, run index + wake-up) / **Adjust** (tell me what to fix)
  • .claude/skills/context-onboarding/SKILL.md: Run `python .claude/scripts/build_document_index.py` to generate the Document Index with DOMAIN one-liners
  • .claude/skills/context-onboarding/SKILL.md: Run `python .claude/scripts/generate_wakeup_context.py` to generate the Wake-Up Context for instant session orientation
  • .claude/skills/context-onboarding/SKILL.md: The checklist ships with install at `docs/.onboarding-checklist.md`.
  • .claude/skills/context-onboarding/SKILL.md: Ensure the `.claude/hook_state/` directory exists (it usually does from install.sh — check and create if missing).
  • .claude/skills/context-onboarding/SKILL.md: the dispatcher will create it on first run.
  • .claude/skills/context-onboarding/SKILL.md: **Run the catalog drift guard:**
  • .claude/skills/context-onboarding/SKILL.md: python .claude/scripts/validate_permissions_catalog.py
  • .claude/skills/context-onboarding/SKILL.md: python .claude/scripts/update.py
  • .claude/skills/context-onboarding/SKILL.md: Re-run the validator to confirm clean.
  • .claude/skills/context-onboarding/SKILL.md: If the answer is "Yes, multi-repo" (or the user confirms after explanation), run:
  • .claude/skills/context-onboarding/SKILL.md: python .claude/scripts/install_global_permissions.py
  • .claude/skills/context-onboarding/SKILL.md: Question: "When should the morning dispatcher run?"
  • .claude/skills/context-onboarding/SKILL.md: Run the schedule-dispatcher skill to execute today's scheduled CLEAR maintenance. It will:
  • .claude/skills/context-onboarding/SKILL.md: 3. Execute each job in sequence, appending one diary entry per job
  • .claude/skills/context-onboarding/SKILL.md: The portfolio aggregator picks it up on the next dispatcher run.
  • .claude/skills/context-onboarding/SKILL.md: The dispatcher will run in the wrong directory and writes meant for this repo's `docs/_inbox/` will be blocked.
  • .claude/skills/context-onboarding/SKILL.md: If "Recreate", call `mcp__scheduled-tasks__delete_event` (or the equivalent delete tool) on the bad task, then re-run Step 6d.
  • .claude/skills/context-onboarding/SKILL.md: That's intentional — fresh setups should over-monitor for the first 1-2 weeks until the auto-tuner suggests reductions after green-run streaks.
  • .claude/skills/context-onboarding/SKILL.md: Let the user invoke the first run on their terms.
  • .claude/skills/context-onboarding/SKILL.md: A fresh install has nothing to migrate and should never see the word.
  • .claude/skills/core-discipline/SKILL.md: **Significant change** (restructure a cluster, create new skills) → use clear-planner, run the full workflow
  • .claude/skills/core-discipline/SKILL.md: | Task Type | Action | | ------------------------- | ---------------------- | | Questions, commands | Respond directly | | Single-file fixes | Just do it | | Small changes (1-3 files) | Think briefly, execute |
  • .claude/skills/core-discipline/SKILL.md: A scheduled dispatcher run finishes clean green with zero findings (one-line summary keeps the dashboard "Ready" status meaningful)
  • .claude/skills/core-discipline/SKILL.md: **Special case — wrapping scripts with built-in prompts** (like `update.py`'s `[y/N]` prompt): invoke the script with `--dry-run` first, parse the output, surface the summary via `AskUserQuestion`, then re-invoke with `--yes` based on the user's choice.
  • .claude/skills/daydream/SKILL.md: If the file doesn't exist, this is the first run — use 2 weeks ago as the baseline.
  • .claude/skills/daydream/SKILL.md: # Read last run timestamp (or default to 2 weeks ago)
  • .claude/skills/daydream/SKILL.md: cat .claude/quality/last-daydream.txt 2>/dev/null || echo "no previous run"
  • .claude/skills/daydream/SKILL.md: git log --since="{last_daydream_date}" --name-only --pretty=format:"%h %s (%ar)" -- docs/ ':!docs/_bcos-framework/methodology/' ':!docs/_bcos-framework/guides/' ':!docs/_bcos-framework/templates/'
  • .claude/skills/daydream/SKILL.md: Report "No document changes since last run" and skip to Phase 3 (Imagine) for forward-looking reflection only.
  • .claude/skills/daydream/SKILL.md: git diff {last_daydream_commit}..HEAD -- docs/path/to/changed-file.md
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/build_document_index.py --dry-run
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/generate_wakeup_context.py
  • .claude/skills/daydream/SKILL.md: **Step 5b: Run maintenance scripts**
  • .claude/skills/daydream/SKILL.md: Daydream is the natural time to run periodic maintenance:
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/prune_sessions.py
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/prune_diary.py
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/generate_wakeup_context.py
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/analyze_crossrefs.py
  • .claude/skills/daydream/SKILL.md: **Step 6: Record this run**
  • .claude/skills/daydream/SKILL.md: python .claude/scripts/record_daydream.py
  • .claude/skills/daydream/SKILL.md: **Document Index** — Run `python .claude/scripts/build_document_index.py` if files changed
  • .claude/skills/doc-lint/SKILL.md: If `.md`: Run markdown quality checks and cross-reference validation
  • .claude/skills/doc-lint/SKILL.md: If `.json`: Run JSON structure validation
  • .claude/skills/doc-lint/SKILL.md: Px_001: Run markdown quality check -> doc-lint (markdown category)
  • .claude/skills/doc-lint/SKILL.md: Px_002: Run example code validation -> doc-lint (cross_reference category)
  • .claude/skills/doc-lint/SKILL.md: Px_001: Run JSON structure validation -> doc-lint (json_structure category)
  • .claude/skills/doc-lint/SKILL.md: Px_002: Run markdown quality check -> doc-lint (markdown category)
  • .claude/skills/ecosystem-manager/SKILL.md: Run discovery to see what exists:
  • .claude/skills/ecosystem-manager/SKILL.md: bash .claude/agents/agent-discovery/find_agents.sh
  • .claude/skills/ecosystem-manager/SKILL.md: bash .claude/skills/skill-discovery/find_skills.sh
  • .claude/skills/ecosystem-manager/SKILL.md: Run it before committing.
  • .claude/skills/ecosystem-manager/SKILL.md: python .claude/scripts/analyze_integration.py --uncommitted
  • .claude/skills/ecosystem-manager/SKILL.md: Coverage gaps in install.sh, settings.json, state.json, .gitignore
  • .claude/skills/ecosystem-manager/SKILL.md: Add fix tasks to the plan and execute them before committing.
  • .claude/skills/ecosystem-manager/SKILL.md: [ ] **install.sh installs new files**
  • .claude/skills/ecosystem-manager/SKILL.md: Run after major development sessions or as part of monthly architecture review.
  • .claude/skills/ecosystem-manager/SKILL.md: Run `python .claude/scripts/wiki_schema.py list` to inspect page-types, lint-checks, and auto-fixes from `_wiki/.schema.yml` or the framework fallback.
  • .claude/skills/ecosystem-manager/SKILL.md: Run all 4 CI checks locally:
  • .claude/skills/ecosystem-manager/SKILL.md: python .github/scripts/validate_frontmatter.py # YAML metadata on all managed markdown
  • .claude/skills/ecosystem-manager/SKILL.md: python .github/scripts/validate_references.py # All registry paths resolve + state.json accuracy
  • .claude/skills/ecosystem-manager/SKILL.md: python .claude/scripts/analyze_integration.py --ci # Ecosystem wiring (install.sh, settings.json coverage)
  • .claude/skills/learning/SKILL.md: **Mechanism:** `python .claude/scripts/promote_resolutions.py` (the module's `__main__` calls `regenerate()`).
  • .claude/skills/learning/SKILL.md: Run the matching subcommand directly.
  • .claude/skills/lessons-consolidate/SKILL.md: python .claude/scripts/consolidate_lessons.py
  • .claude/skills/schedule-dispatcher/SKILL.md: Run `python .claude/scripts/update.py` first."
  • .claude/skills/schedule-dispatcher/SKILL.md: Report to the user: "schedule-config.json is malformed — fix before the dispatcher can run.
  • .claude/skills/schedule-dispatcher/SKILL.md: For each entry in `jobs`, decide if it should run today.
  • .claude/skills/schedule-dispatcher/SKILL.md: | `schedule` value | Runs when | |--------------------|----------------------------------------------------| | `"daily"` | Every day | | `"mon"`..`"sun"` | Only on that weekday …[+549 chars]
  • .claude/skills/schedule-dispatcher/SKILL.md: **For on-demand dispatch** (user said "run the audit-inbox job"): skip this step, run only the named job.
  • .claude/skills/schedule-dispatcher/SKILL.md: Build an ordered list of jobs to run.
  • .claude/skills/schedule-dispatcher/SKILL.md: python .claude/scripts/context_index.py --write
  • .claude/skills/schedule-dispatcher/SKILL.md: The cache TTL is 10 min (longer than a full dispatcher cycle), so all jobs in this run hit the same in-memory snapshot.
  • .claude/skills/schedule-dispatcher/SKILL.md: Last run timestamp per job?
  • .claude/skills/schedule-dispatcher/SKILL.md: Use the helper script — it creates `.claude/hook_state/` on first run and matches the allowlisted command prefix so it never prompts:
  • .claude/skills/schedule-dispatcher/SKILL.md: python .claude/scripts/append_diary.py '{"ts":"2026-04-15T09:04:12","job":"index-health","verdict":"green","findings_count":0,"auto_fixed":[],"actions_needed":[],"duration_s":4}'
  • .claude/skills/schedule-dispatcher/SKILL.md: **Fallback:** when `.bcos-umbrella.json` is absent (standalone BCOS install with no umbrella), preflight reduces to the `../` + absolute-path scans — no sibling-name list to check against.
  • .claude/skills/schedule-dispatcher/SKILL.md: After Step 4 completes (last job returned), verify that **every job listed in Step 2's run-list produced exactly one diary entry in this dispatcher tick**.
  • .claude/skills/schedule-dispatcher/SKILL.md: Read back the diary entries appended during this tick (use the dispatcher's in-memory record, or the tail of `.claude/hook_state/schedule-diary.jsonl` filtered to the current run's `ts` prefix if you're running on-demand).
  • .claude/skills/schedule-dispatcher/SKILL.md: **Added in schema 1.1.0.** Before applying auto-fixes (Step 5) or rendering the digest (Step 7), enrich each finding emitted in Step 4 with three computed fields and produce the run's headline string.
  • .claude/skills/schedule-dispatcher/SKILL.md: **Match against prior tick.** Scan diary entries from the **immediately-prior dispatcher tick** (the most-recent run before today, identified by the latest `ts` < today's run window).
  • .claude/skills/schedule-dispatcher/SKILL.md: | Run shape | Headline template | |---|---| | Green clean (0 findings, 0 framework) | "{Nth consecutive green run.}{recovery callout if any job verdict flipped error→green this tick.}" | | Green with frequency suggestions only | "All {N} jobs green.
  • .claude/skills/schedule-dispatcher/SKILL.md: And the run has a `headline` string ready for Step 7.
  • .claude/skills/schedule-dispatcher/SKILL.md: For each job run today, check recent diary:
  • .claude/skills/schedule-dispatcher/SKILL.md: **Last run was `error`** → suggest investigating, not frequency change
  • .claude/skills/schedule-dispatcher/SKILL.md: Both are overwritten each run.
  • .claude/skills/schedule-dispatcher/SKILL.md: Conditional sections render ONLY when their counts are non-zero — a green clean run is ~5 lines, an amber run with 5 findings is ~40 lines.
  • .claude/skills/schedule-dispatcher/SKILL.md: | 1 | `{emitted_by}` | `{primary_attr}` | {one-line meaning} | {`🔁 Nth run` if consecutive_runs ≥ 3, else blank} |
  • .claude/skills/schedule-dispatcher/SKILL.md: Run at {ISO timestamp} · `.claude/hook_state/schedule-diary.jsonl`
  • .claude/skills/schedule-dispatcher/SKILL.md: verdict emoji + finding count + inline status markers in Note column | | `💡 Suggestions` | `≥1` `frequency-suggestion` finding | Bullet list | | Footer | Always | Run timestamp, diary path, auto-commit status |
  • .claude/skills/schedule-dispatcher/SKILL.md: | Scenario | Verdict bar example | Top line | Action hint | |---|---|---|---| | Green clean | `**🟢 BCOS green** · 1 ran · 0 findings · 0 fixed · 4s` | `Headline string from Step 4c, e.g. "10th consecutive green run."` | `→ Nothing to act on.
  • .claude/skills/schedule-dispatcher/SKILL.md: Run `git rev-parse --abbrev-ref HEAD` to get the current branch.
  • .claude/skills/schedule-dispatcher/SKILL.md: This is the per-tick safety property: a stray hand-edit to a "generated" file on a day its owning job did not run still blocks auto-commit.
  • .claude/skills/schedule-dispatcher/SKILL.md: Globs are resolved against `git status --porcelain` paths (same surface the clean-tree rule already inspects).
  • .claude/skills/schedule-dispatcher/SKILL.md: **Emit the `job-missing-outputs-declaration` framework-finding only when** the job's run would have changed at least one path *outside* `GLOBAL_ALLOWED` (i.e. a path that *could* have been auto-committable had it been declared).
  • .claude/skills/schedule-dispatcher/SKILL.md: Run `git status --porcelain`.
  • .claude/skills/schedule-dispatcher/SKILL.md: `git commit -m "bcos: daily maintenance {YYYY-MM-DD}"` — no push, ever.
  • .claude/skills/schedule-dispatcher/SKILL.md: A BCOS install with no umbrella still writes the log — it just sits locally until somebody (or the dashboard) reads it.
  • .claude/skills/schedule-dispatcher/SKILL.md: The last thing the dispatcher does depends on whether the run produced anything that needs user judgement.
  • .claude/skills/schedule-dispatcher/SKILL.md: Otherwise — clean green run, zero findings, zero suggestions — output a short summary and stop.
  • .claude/skills/schedule-dispatcher/SKILL.md: **Why this rule:** if every run always asked a question, every scheduled run would show as "Awaiting input" in the dashboard even on clean days.
  • .claude/skills/schedule-dispatcher/SKILL.md: Or even shorter when the user is watching an on-demand run:
  • .claude/skills/schedule-dispatcher/SKILL.md: ("Do you want architecture review to run weekly?" is too abstract — `schedule-tune` handles that)
  • .claude/skills/schedule-dispatcher/SKILL.md: **Retry the failed job(s)** → re-run only the errored jobs (on-demand mode), then ask again with the new result
  • .claude/skills/schedule-dispatcher/SKILL.md: If the user says "run the {job-name} job now" or "run today's maintenance now":
  • .claude/skills/schedule-dispatcher/SKILL.md: Skip the schedule predicate — run only the named job(s)
  • .claude/skills/schedule-dispatcher/SKILL.md: ## On-demand run — {HH:MM} ({job-name})
  • .claude/skills/schedule-dispatcher/SKILL.md: If `daily-digest.md` does not yet exist (no scheduled run happened today), create it with a minimal header before appending the on-demand section.
  • .claude/skills/schedule-dispatcher/SKILL.md: DO append a diary entry per job with `"trigger":"on-demand"` so the run is tracked in history
  • .claude/skills/schedule-dispatcher/SKILL.md: Run another job
  • .claude/skills/schedule-dispatcher/SKILL.md: The morning scheduled run always OVERWRITES `daily-digest.md` fresh — so yesterday's on-demand sections are not preserved in the digest (diary retains them).
  • .claude/skills/schedule-dispatcher/SKILL.md: Every dispatcher run produces either a digest OR a user-facing error message.
  • .claude/skills/schedule-dispatcher/SKILL.md: `schedule-tune` — user-facing skill for changing config ("run audit twice a week")
  • .claude/skills/schedule-dispatcher/SKILL.md: If a run was wrong, add a corrective entry, don't mutate history.
  • .claude/skills/schedule-tune/SKILL.md: If you haven't set up BCOS scheduling yet, run `context-onboarding` Step 6.
  • .claude/skills/schedule-tune/SKILL.md: **Utterances:** *"run audit twice a week"*, *"change daydream to every 3 days"*, *"switch architecture review to quarterly"*, *"run index-health on weekdays only"*, *"audit on Tuesdays and Fridays"*
  • .claude/skills/schedule-tune/SKILL.md: **Utterances:** *"run the dispatcher at 08:30"*, *"move morning maintenance to 07:00"*
  • .claude/skills/schedule-tune/SKILL.md: Update the actual scheduled task's cron via `mcp__scheduled-tasks__update_scheduled_task` — that's what actually moves the run
  • .claude/skills/schedule-tune/SKILL.md: **Utterances:** *"what's my maintenance schedule?"*, *"when does audit run?"*, *"is architecture-review enabled?"*
  • .claude/skills/schedule-tune/SKILL.md: If the user proposes a cron that would run more than once per day, refuse: *"Dispatcher is designed to run once daily.
  • .claude/skills/schedule-tune/SKILL.md: | User says | Config edit | |----------------------------------------|----------------------------------------------------------------| | "run audit on Tuesday instead of Friday" | `jobs.audit-inbox.schedu …[+1012 chars]
  • .claude/skills/todo-utilities/SKILL.md: **Sequential Agent Invocation** — Tasks run one after another
  • CLAUDE.md: **Small changes (1-3 files)** — think briefly, execute.
  • CLAUDE.md: Auto-generated by `python .claude/scripts/generate_wakeup_context.py`.
  • CLAUDE.md: When unsure, run `context-ingest` and let it propose the route.
  • CLAUDE.md: **Post-work handoffs:** After integrating new content, offer to (a) rebuild the document index, (b) update related data points, (c) run an audit if architecture shifted.

Hooks (5)

  • .claude/settings.json (Stop): bash "$CLAUDE_PROJECT_DIR/.claude/hooks/auto_save_session.sh"
  • .claude/settings.json (PreCompact): bash "$CLAUDE_PROJECT_DIR/.claude/hooks/precompact_save.sh"
  • .claude/settings.json (PostToolUse): "$CLAUDE_PROJECT_DIR/.claude/bin/python3" "$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit_frontmatter_check.py"
  • .claude/settings.json (PostToolUse): "$CLAUDE_PROJECT_DIR/.claude/bin/python3" "$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit_frontmatter_check.py"
  • .claude/settings.json (PostToolUse): "$CLAUDE_PROJECT_DIR/.claude/bin/python3" "$CLAUDE_PROJECT_DIR/.claude/hooks/post_commit_context_check.py"

Install scripts (1)

  • install.sh: sh install.sh
  • Medium risk: runs-instruction-instruction-network (.claude/agents/wiki-fetch/AGENT.md:93)
  • Medium risk: runs-instruction-instruction-network (.claude/skills/bcos-wiki/SKILL.md:150)
  • Medium risk: runs-instruction-instruction-network (.claude/skills/context-onboarding/SKILL.md:33)
  • Medium risk: runs-instruction-instruction-network (.claude/skills/context-onboarding/SKILL.md:50)
  • Low risk: scripts-present (.claude/agents/agent-discovery/find_agents.sh)
  • Low risk: scripts-present (.claude/bin/python3)
  • Low risk: scripts-present (.claude/hooks/auto_save_session.sh)
  • Low risk: scripts-present (.claude/hooks/post_commit_context_check.py)
  • Low risk: scripts-present (.claude/hooks/post_edit_frontmatter_check.py)
  • Low risk: scripts-present (.claude/hooks/precompact_save.sh)
  • Low risk: scripts-present (.claude/scripts/analyze_crossrefs.py)
  • Low risk: scripts-present (.claude/scripts/analyze_integration.py)
  • Low risk: scripts-present (.claude/scripts/append_diary.py)
  • Low risk: scripts-present (.claude/scripts/audit_node_configs.py)
  • Low risk: scripts-present (.claude/scripts/auto_fix_audit.py)
  • Low risk: scripts-present (.claude/scripts/bcos_inventory.py)
  • Low risk: scripts-present (.claude/scripts/build_document_index.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_archive.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_init.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_lint.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_queue_add.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_remove.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_review.py)
  • Low risk: scripts-present (.claude/scripts/cmd_wiki_search.py)
  • Low risk: scripts-present (.claude/scripts/consolidate_lessons.py)
  • Low risk: scripts-present (.claude/scripts/context_bundle.py)
  • Low risk: scripts-present (.claude/scripts/context_index.py)
  • Low risk: scripts-present (.claude/scripts/context_search.py)
  • Low risk: scripts-present (.claude/scripts/digest_sidecar.py)
  • Low risk: scripts-present (.claude/scripts/find_lessons.py)
  • Low risk: scripts-present (.claude/scripts/generate_wakeup_context.py)
  • Low risk: scripts-present (.claude/scripts/install_global_permissions.py)
  • Low risk: scripts-present (.claude/scripts/lifecycle_sweep.py)
  • Low risk: scripts-present (.claude/scripts/load_task_profiles.py)
  • Low risk: scripts-present (.claude/scripts/load_zone_registry.py)
  • Low risk: scripts-present (.claude/scripts/promote_resolutions.py)
  • Low risk: scripts-present (.claude/scripts/prune_diary.py)
  • Low risk: scripts-present (.claude/scripts/prune_sessions.py)
  • Low risk: scripts-present (.claude/scripts/record_daydream.py)
  • Low risk: scripts-present (.claude/scripts/record_resolution.py)
  • Low risk: scripts-present (.claude/scripts/refresh_ecosystem_state.py)
  • Low risk: scripts-present (.claude/scripts/refresh_wiki_index.py)
  • Low risk: scripts-present (.claude/scripts/run_wiki_canonical_drift.py)
  • Low risk: scripts-present (.claude/scripts/run_wiki_coverage_audit.py)
  • Low risk: scripts-present (.claude/scripts/run_wiki_graveyard.py)
  • Low risk: scripts-present (.claude/scripts/run_wiki_source_refresh.py)
  • Low risk: scripts-present (.claude/scripts/run_wiki_stale_propagation.py)
  • Low risk: scripts-present (.claude/scripts/scan_docs_structure.py)
  • Low risk: scripts-present (.claude/scripts/set_profile.sh)
  • Low risk: scripts-present (.claude/scripts/update.py)

Attribution and licence

First committed by
GitHub avatar of walm00walm00
Publisher
Guntis Coders (Community)
Source
walm00/business-context-os · commit 4635683
Licence
MIT License (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Contributors
walm00

AutoGineer is not affiliated with or endorsed by walm00.

Install from source

9 of its files are not served here, so there is no ZIP. walm00/business-context-os at 4635683