From f0f4f111c9a6c1636fbd236150717b6c34e41d1d Mon Sep 17 00:00:00 2001 From: Andrew Anderson Date: Wed, 23 Sep 2026 10:21:42 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=93=96=20docs:=20add=20Spektacular=20?= =?UTF-8?q?as=20a=20sibling=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spektacular (github.com/hivecommons/spektacular, canonical site spektacular.dev) transferred into hivecommons from jumppad-labs. Wire it into the sibling-docs sync alongside hotshot, pluk, rationguard and promptargs: - sync-sibling-docs.ts: new project entry (README.md required, docs/knowledge-base.md optional; SPEKTACULAR_DOCS_REF override) and a jumppad-labs -> hivecommons rewrite for repo sub-paths only (Go module path and Homebrew tap still live under jumppad-labs) - versions.ts / shared.json: extend ProjectId, add versions + projects entries, related-projects card and edit URL - page-map, [...slug] layout/page, sitemap, sidebar, source actions, edit link, related projects: enumerate spektacular everywhere the other siblings are - netlify.toml: /docs/spektacular root redirects - docs/content/spektacular: committed fallback copies used when the sync is unreachable Co-Authored-By: Claude Fable 5.1 Signed-off-by: Andrew Anderson --- README.md | 3 +- docs/content/spektacular/knowledge-base.md | 427 +++++++++++++++++++++ docs/content/spektacular/readme.md | 399 +++++++++++++++++++ netlify.toml | 10 + public/config/shared.json | 21 +- scripts/sync-sibling-docs.ts | 19 +- src/app/docs/[...slug]/layout.tsx | 1 + src/app/docs/[...slug]/page.tsx | 2 +- src/app/docs/page-map.ts | 22 ++ src/app/sitemap.ts | 2 +- src/components/docs/DocsSidebar.tsx | 1 + src/components/docs/DocsSourceActions.tsx | 1 + src/components/docs/EditPageLink.tsx | 1 + src/components/docs/RelatedProjects.tsx | 1 + src/config/versions.ts | 24 +- 15 files changed, 927 insertions(+), 7 deletions(-) create mode 100644 docs/content/spektacular/knowledge-base.md create mode 100644 docs/content/spektacular/readme.md diff --git a/README.md b/README.md index efeb21b..4e6afce 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Official documentation site for [Hive Commons](https://hivecommons.dev), served - **[pluk](https://github.com/hivecommons/pluk)** — pub-sub event streaming for AI agent tmux sessions - **[rationguard](https://github.com/hivecommons/rationguard)** — detect and rebut rationalization patterns in AI agent output - **[promptargs](https://github.com/hivecommons/promptargs)** — template expansion for AI prompts +- **[Spektacular](https://github.com/hivecommons/spektacular)** — spec-driven development for AI coding agents (canonical site: [spektacular.dev](https://spektacular.dev)) ## How content is sourced @@ -22,7 +23,7 @@ at build time: - `scripts/sync-hive-docs.ts` pulls Hive docs from `hivecommons/hive` (`src/docs/`, branch `v5`; override with `HIVE_DOCS_OWNER` / `HIVE_DOCS_REPO` / `HIVE_DOCS_REF`). -- `scripts/sync-sibling-docs.ts` pulls the hotshot, pluk, rationguard, and promptargs docs from their repos. +- `scripts/sync-sibling-docs.ts` pulls the hotshot, pluk, rationguard, promptargs, and Spektacular docs from their repos. Edit the canonical source in the project repository — not the synced copies under `docs/content/`. If a sync source is unreachable at build time, the committed copies diff --git a/docs/content/spektacular/knowledge-base.md b/docs/content/spektacular/knowledge-base.md new file mode 100644 index 0000000..7bee536 --- /dev/null +++ b/docs/content/spektacular/knowledge-base.md @@ -0,0 +1,427 @@ +> **Synced from spektacular.** This page is pulled from [hivecommons/spektacular@main](https://github.com/hivecommons/spektacular/blob/main/docs/knowledge-base.md) during the docs build. Edit the canonical source in the spektacular repository. + +# The Knowledge Base + +Knowledge is the accumulated know-how a project draws on when planning — the rules +it commits to, the shared vocabulary it speaks, how the system is built, the traps +to avoid, what past work taught, and why decisions were made. It is a +**planning-time input**: the planning agent reads it while producing a plan, and +the relevant parts are written into the plan itself. The implement workflow then +consumes only the plan documents, so it never re-reads the knowledge base — the +plan is the contract. + +This document describes how the knowledge base is organised and how it behaves, so +a maintainer new to the project can understand it without reading the source. + +## The category model + +Every knowledge entry belongs to a **category**, determined by the first segment of +its path within a knowledge source (an entry at `gotchas/timeouts.md` is a +`gotchas` entry). There are six categories, each with a real definition — a +purpose, a boundary (what looks similar but belongs elsewhere), a retrieval tier, +and an expected entry shape: + +| Category | Tier | What it holds | +|----------|------|---------------| +| `conventions` | always-applied | The rules a team always wants honoured — coding standards, naming, formatting, required patterns, house style. A standing rule stated as an instruction to follow. | +| `glossary` | always-applied | The shared vocabulary of the project — the domain and project-specific terms needed to read the rest of the knowledge base and the code. A term and a short gloss. | +| `architecture` | looked-up | How the system is built and how new work must be built into it — components, responsibilities, boundaries, data and control flow. Binding on the work being planned, not a survey of today's code. | +| `gotchas` | looked-up | Sharp edges and non-obvious traps — surprising behaviours and easy mistakes. A warning about a specific trap and how to avoid it. | +| `learnings` | looked-up | Empirical knowledge from doing the work — what was tried, what worked, what didn't. An observation from experience. | +| `decisions` | looked-up | The reasoning behind choices (ADR-style) — options considered, trade-offs weighed, why one path was taken. The why, not the what or how. | + +These definitions are the categories' contract with both contributors and the +assistant. The boundaries are what keep the categories distinct: the *reasoning* +behind a rule is a `decision`, not a `convention`; a *defined term* is a +`glossary` entry, not `architecture`; an *empirical finding* is a `learning`, not +a `gotcha`. + +### A single source of truth + +The category model is declared **once**, in a registry in the code +(`internal/knowledge/category.go`). That one declaration drives everything +downstream: + +- **Project initialisation** scaffolds a directory and a self-documenting README + for each category from the registry, so a freshly initialised project contains + every category with a real definition rather than a placeholder. +- **Retrieval behaviour** (below) reads each category's tier from the registry. +- **Contribution routing** surfaces the definitions to the assistant via the + `spektacular knowledge categories` command, so a new entry is filed in the right + place. + +Adding a category, or moving one between tiers, is a change to this registry alone. + +## The two retrieval tiers + +Categories are split into two tiers by *how they are retrieved*, declared per +category in the registry: + +- **Always-applied** (`conventions`, `glossary`). Loaded **in full on every task** + and deliberately **excluded from search**, so the same content is never surfaced + twice. These are the rules every plan must reflect and the vocabulary needed to + read everything else. Keep them compact — their whole content is paid for on + every task. +- **Looked-up** (`architecture`, `gotchas`, `learnings`, `decisions`). The larger + reference body, retrieved **only when a query matches**. This is reached through + keyword search, targeted at the specific surfaces a feature touches. + +Because tier membership is read from the single registry declaration, **re-tiering +is one self-consistent action**: flipping a category to always-applied +simultaneously makes the full-load reader include it *and* makes search stop +returning it. The two behaviours cannot drift out of step — there is no second +place to forget to update. + +The always-applied set is loaded with `spektacular knowledge always-applied` +(every always-applied entry across all sources, each tagged with its category). +The legacy `spektacular knowledge conventions` command remains for backward +compatibility, returning only the conventions. + +## Search behaviour + +Search is keyword-based and runs in-process. Results come back ranked — one result +per matching document, strongest match first. Each result carries: + +- its **tier** and store **name** (which configured store it came from — together + with the path, exactly what a read needs to fetch it back), +- its **category** label (so a consumer can tell a `gotchas` warning from an + `architecture` fact from a `learnings` finding), +- a **checksum** (a content hash, used for de-duplication — see below), +- its **tags** (what the entry declares itself to be about; an empty list when it + declares nothing), +- a **title**, a **score**, and up to three **excerpts** of the strongest matching + lines. + +**A document does not have to contain every word of the query.** It is returned if +it carries evidence for any of them, and ranked on how good a match it is. Two +consequences follow, and both matter to anything consuming search: a returned hit +is not proof that every query word appeared in it, and an empty result is not +proof that nothing on the subject exists. + +Always-applied categories never appear in search results — they are already loaded +in full — so search surfaces only the looked-up reference knowledge. + +### How a result is ranked + +The query is lower-cased and split on whitespace into terms. There is no stemming, +no synonyms and no plural handling, so `router` and `routers` are different terms. + +For each term, an entry offers at most two kinds of evidence: + +- a **tag match**, full strength when the term equals one of the entry's tags and + reduced when the two are prefix-related (see below); +- **body occurrences**, the number of case-insensitive substring matches in the + entry's text. The frontmatter block is not part of the text, so a tag is never + also counted as a mention of itself. + +```text +damp(n) = 0 if n = 0 + = 1 + log2(n) otherwise + +tagAffinity(t) = 1 if t equals one of the tags + = len(shorter)/len(longer) if t and a tag are prefix-related + and the shorter is >= minPrefixLen + = 0 otherwise + +termScore(t) = tagWeight * tagAffinity(t) + damp(bodyCount(t)) + +coverage = (terms with any evidence) / (total terms) + +score = sum(termScore(t) for t in terms) * coverage ^ coverageExponent +``` + +with `tagWeight = 8`, `coverageExponent = 2` and `minPrefixLen = 4`. Where a term +is prefix-related to more than one tag, the strongest match wins; affinities do +not add up. + +**The named constants in `internal/knowledge/ranking.go` are the single source of +truth for these values.** They are quoted here, on the published knowledge base +page, and in the `architecture/knowledge-search-ranking.md` entry, so changing one +is a four-file change: the code and all three documents, or they disagree. + +A worked example, searching `http routing` against two entries: + +| | Entry A, tagged `http, routing` | Entry B, untagged, mentions "http" 8 times | +|---|---|---| +| `http` | tag match, 0 in body: 8 + 0 = 8 | no tag, 8 in body: 0 + (1 + log2 8) = 4 | +| `routing` | tag match, 2 in body: 8 + 2 = 10 | no evidence: 0 | +| coverage | 2 of 2 = 1.0 | 1 of 2 = 0.5 | +| **score** | (8 + 10) * 1.0² = **18** | 4 * 0.5² = **1** | + +Entry B mentions the word far more often and still ranks well below Entry A, +because Entry A is *about* both terms and Entry B is about neither. + +**Prefix partial credit.** A tag does not have to be typed exactly. When a search +term and a tag are prefix-related, the tag still counts, at the shorter string's +length over the longer's: `http` finds a `https` tag at 80%, `apple` and `apples` +find each other at 83%, `https` finds `https-security` at 36%. Two guards are +deliberate. The match must be anchored at the start, so `test` does not find +`latest`; and the shorter of the two must be at least `minPrefixLen` characters, +so `go` matches only exactly rather than reaching `golang`, `google` and +`gorilla`. + +This is **positional, not morphological** — it is not stemming. `route` does not +find `routing`, because `route` is not the opening of `routing`. Where a form +differs by more than its ending and both are likely search words, carry both tags. +Edit distance was considered and rejected for exactly this surface: on short +technical tokens it scores `test`/`rest` and `cors`/`core` at 0.75 and +`tls`/`tld`, `dns`/`dos` and `api`/`apt` at 0.67, all unrelated subjects and all +plausible tags here, while producing scores identical to prefix matching on every +pair that should match. + +**The cutoff.** Once every store has been searched and the results merged, any hit +scoring below `cutoffFraction` of the best hit is dropped, with +`cutoffFraction = 0.25`. The threshold is relative rather than fixed, so a loosely +related entry surfaces when nothing better exists and falls away once something +genuinely relevant is present, and it keeps its meaning as a knowledge base grows. +In the example above, Entry B is dropped: 1 is below 0.25 × 18. Ties break by +configured store order and then by path, so repeated searches return the same +order. + +### Where each part is computed + +A store finds candidate documents and describes them: per-term body occurrence +counts, the entry's tags, the locator, title, excerpts and checksum. It does +**not** compute a score. Tokenizing the query, scoring every reported hit, +merging, sorting and applying the cutoff all happen once in the knowledge layer, +above the store interface. + +The reason is the merge. Hits from every covered store are ranked against each +other, so every score must be on the same scale; a formula reproduced inside each +provider would have to be reproduced exactly, and any drift would leave the merged +ordering quietly wrong rather than visibly broken. The precedent is already in the +result type — `tier`, `name` and `category` are left empty by the store because it +has no notion of its caller's addressing scheme, and `score` joins them because it +has no notion of the stores it will be ranked against. One consequence is worth +stating for anyone writing a backend: a store must be able to report per-term +occurrence counts to take part in this ranking at all. The public reference for +writing a storage backend covers the contract in full. + +### Tags on an entry + +An entry may open with a YAML frontmatter block declaring what it is about, +independently of the words its prose happens to use: + +```markdown +--- +tags: [go, http, routing] +--- + +# HTTP routing standard + +All Go services route HTTP endpoints through chi. +``` + +The block list form works too: + +```markdown +--- +tags: + - go + - http + - routing +--- +``` + +The rules, each of which is a real behaviour rather than a convention: + +- **Tags are optional.** An entry without a block is perfectly valid and fully + searchable. There is nothing to migrate, no backfill, and no command to run. +- **The block must lead the file**, opened and closed by a line containing only + `---`. +- **`tags` is the only key read.** Any other key is ignored, so the block is safe + to use for other metadata. +- **Tags are lower-cased, trimmed and de-duplicated** when read, so `HTTP` and + `http` can never both exist as separate tags. +- **A malformed or unclosed block is treated as no frontmatter**, not as an error. + An entry that happens to open with a horizontal rule keeps working, and one bad + entry can never fail a search across a whole store. +- **The block is not part of the entry's prose**, so a tag is not also counted as + a body mention of itself. The checksum is the deliberate exception: it covers the + file's exact raw bytes including the block, because it identifies the file rather + than its prose, so two entries differing only in their tags stay distinct during + de-duplication. + +You rarely write this block by hand. The `spek-knowledge` skill proposes tags when +it captures an entry, drawn from the vocabulary already in use, and shows them +alongside the destination for confirmation before anything is written. + +## De-duplication and consolidation + +The same knowledge often lives in more than one place — copied across stores, or +worded differently in two entries. A lookup does not return that pile of +overlapping hits. Instead it returns a single consolidated, source-cited answer, +produced in two strictly separated stages: + +1. **Exact de-duplication (deterministic).** Search results are grouped by their + `checksum`. Results sharing a checksum are **byte-identical** copies of the same + entry; each group collapses to one candidate, unioning the source citations of + every copy. This is pure equality — no thresholds, no judgement. +2. **Consolidation (judgement).** The surviving unique candidates are handed to a + consolidation **sub-agent**, which reads their full bodies and classifies the + *relationship* between them: + - **equivalent** (same point, different words) → merged into one cited point; + - **refinement** (one is a more specific case) → kept alongside what it + refines and noted, never hidden (there is no precedence between stores — + see below); + - **genuine contradiction** (sources actually disagree) → **surfaced** as a + conflict naming both sources, never silently dropped; + - **distinct** → both kept. + + The sub-agent returns one consolidated answer, each point citing the source(s) + it was drawn from. The raw per-source hit list is never the output. (Where an + executing agent cannot spawn a sub-agent, the identical consolidation runs + inline; only the context isolation differs.) + +### Why the mechanical layer is exact, not fuzzy + +De-duplication and ranking answer different questions, and the tools differ +accordingly. De-duplication asks whether two entries are *the same*, which is a +yes-or-no fact and is therefore decided on exact bytes. Ranking asks which entry +is a better *answer* to a query, which is a matter of degree and is therefore +scored, with prefix partial credit and damping. The argument below is about the +first question only; it is not an argument against inexact ranking. + + +The deterministic de-dup step matches on **exact byte-identity** rather than a +normalized or similarity-based fingerprint. This is deliberate, because **lexical +distance does not track semantic distance**: + +- "Always retry" and "never retry" are ~95% lexically identical yet a total + contradiction. +- "Add jitter to retries" and "randomize backoff intervals" share almost no words + yet state the same knowledge. + +A graded similarity score (edit distance, SimHash/MinHash) would mis-rank exactly +these cases and reduces to a brittle magic threshold — and a mechanical similarity +index for equivalence is precisely the kind of embedding/semantic index the +project does not build. The real need is not a difference *scalar* but a +relationship *classification* (a 70%-similar pair could be a refinement or a +contradiction), which only an LLM does reliably. + +So exact-equivalence is the most effective *mechanical* primitive precisely because +it is narrow: it claims the one provable point — zero difference — for free, and +routes everything semantic to the judgement layer, fed the inputs that make that +judgement good (full bodies, the store each came from, and the category label). + +## Two tiers, and how a store is addressed + +Knowledge lives in one of two tiers. Every registered repo contributes exactly one +store, holding what is true of that repo's own code, declared as a single provider +block in its `repo.yaml` and addressed by the name the project registered the repo +under. The project declares any number of shared stores under `knowledge.sources` +in `.spektacular/config.yaml`, each with its own `name`, for knowledge that belongs +to no single repo. Names must be unique within a tier; the same name may appear +once in each, because a store is identified by its tier and name together. + +Reading and writing address exactly one store, so they take a `tier` and a `name` +alongside the path. A request that leaves either out is refused rather than +resolved to whichever store looks closest, and the refusal lists the names +available in that tier so it can be reissued immediately. + +Searching, listing, conventions, and the always-applied load take `--tier +` and a repeatable `--filter `. Omitting the narrowing +covers every store the tier reaches; naming stores covers exactly those. No store +is ever included or excluded implicitly, which is what lets planning work on one +repo skip every other repo's standing rules. + +**There is no precedence between stores.** A repo's own store answers what is true +of that repo's code, and the project's shared stores answer what spans repos; they +answer different questions rather than overriding one another. During consolidation +a refinement is kept alongside what it refines and noted rather than hidden, and a +*genuine* disagreement is surfaced as a conflict naming both stores, not silently +resolved. + +## Contributing knowledge + +Contributions are routed to the right category at the moment they are filed. The +`spek-knowledge` skill loads the category definitions +(`spektacular knowledge categories`), picks the category whose purpose matches the +entry and whose boundary does not exclude it, and steers over-long or +multi-paragraph content out of the always-applied `glossary` toward a more fitting +category — keeping the always-applied retrieval tier compact. The entry is filed at +`/.md`. + +The skill also loads the tag vocabulary already in use +(`spektacular knowledge tags`) and proposes tags for the entry, preferring an +established tag over a near-duplicate so the vocabulary converges instead of +fragmenting as the knowledge base grows. As always, the assistant proposes the +destination — the tier, the store name, and the path — along with the tags and the +body, and waits for explicit confirmation before writing. That is one gate, not +several: tags are confirmed with everything else. + +### Auditing the tags on existing entries + +Entries written before tags existed, or tagged carelessly, can be reviewed the +same way. There is no separate command: invoke the `spek-knowledge` skill and ask +it to audit the knowledge base tags. The skill discriminates the audit intent from +the wording of the request, the same way it tells a lookup from a contribution. + +The flow is read-only until you approve something. It enumerates the entries in +scope with `knowledge list`, loads the vocabulary in use with `knowledge tags`, +reads each entry, and for each one reports two things: + +- **Unsupported tags** — a tag the entry's content does not bear out, reported for + removal. +- **Missing tags** — a subject the entry is clearly about but carries no tag for, + proposed from the existing vocabulary where something fits and as a new tag only + where nothing does. + +Changes are proposed and confirmed **one entry at a time**. Accepting one entry's +changes never applies another's, and declining one does not carry to the next, so +a suggestion you disagree with costs a "no" rather than a cleanup. Narrow the +scope with `--tier` and `--filter` when you only want part of the knowledge base +reviewed. + +Two judgements the audit is deliberately careful about, because they pull in +opposite directions. It will not tell an entry tagged `https` to use `http` +instead: those are different subjects, and prefix matching already relates them at +reduced strength. But it will report `apples` sitting beside `apple` as removable, +because prefix matching already reaches it and a redundant tag is how a vocabulary +silently doubles. + +### Reviewing whether entries are still true + +The audit above asks whether an entry is well *labelled*. Its sibling, the +maintenance review, asks the question that matters more over time: whether the +entry is still *true*. It is the same skill and the same shape — invoke +`spek-knowledge` and ask whether the knowledge base is still accurate — and it is +read-only until you approve something, one entry at a time. + +Each entry in scope is classified as **current**, **stale**, **incorrect** or +**unverifiable**, and every stale or incorrect verdict has to name the specific +file, command or behaviour that changed, so you can check a finding rather than +take it on trust. "Looks old" is not a finding. + +One rule decides whether such a review helps or harms, and it is worth stating +plainly: an entry describing a standard the code has not met yet is **current**, +not stale. In this project an entry states the target and the code is what has yet +to meet it, so a difference between the two is work to do, never evidence against +the entry. Only an entry whose subject no longer exists is stale. Without that +rule a review would propose deleting exactly the entries doing the most work. + +The review also reports any category description that no longer matches the +project's current definition of that category, naming where it is and pointing at +`spektacular init ` as the remedy. It reports drift and never repairs it — +bringing a store back into line stays something you run deliberately. An entry you +agree to remove is removed with `spektacular knowledge delete`, and only after you +agree to that entry specifically. + +## Command reference + +Agents (and you) reach knowledge through the `spektacular knowledge` commands +rather than reading the files directly, so access stays consistent across stores. +Each command has a `--schema` mode that prints its input/output schema. + +| Command | Purpose | +|---------|---------| +| `spektacular knowledge search [--tier T] [--filter N] [--tag G]` | Keyword-search the stores the request covers (always-applied categories excluded); ranked, one tier-, store- and category-tagged result per matching document, each with title, score, excerpts, tags, and a content checksum. A document need not contain every query word. Repeatable `--tag` restricts results to entries carrying every tag listed | +| `spektacular knowledge read --data '{"tier":"repo","name":"docs","path":"architecture/x.md"}'` | Read one entry's full body from one addressed store | +| `spektacular knowledge list [--tier T] [--filter N]` | List every entry across the stores the request covers | +| `spektacular knowledge write --data '{"tier":"repo","name":"docs","path":"gotchas/x.md"}' --file ` | Write an entry into one addressed store (content from `--file`, or stdin) | +| `spektacular knowledge delete --data '{"tier":"repo","name":"docs","path":"gotchas/x.md"}'` | Remove one entry from one addressed store. An address naming an undeclared store is refused with the names available in that tier; an address holding nothing reports success and changes nothing, so a maintenance pass that retries is safe. A category's own generated `README.md` is refused, since it is rendered from the project's definition of that category rather than written by anyone | +| `spektacular knowledge sources` | List the configured stores by tier and name, with their locations | +| `spektacular knowledge categories` | List the category definitions — purpose, boundary, tier, and entry shape | +| `spektacular knowledge tags [--tier T] [--filter N]` | List the tag vocabulary already in use across the stores the request covers, each with the number of entries carrying it, most-used first | +| `spektacular knowledge always-applied [--tier T] [--filter N]` | Read every always-applied entry (conventions and glossary) across the stores the request covers, each tagged with its category | +| `spektacular knowledge conventions [--tier T] [--filter N]` | Read every convention across the stores the request covers (the conventions-only view) | diff --git a/docs/content/spektacular/readme.md b/docs/content/spektacular/readme.md new file mode 100644 index 0000000..4bde1c3 --- /dev/null +++ b/docs/content/spektacular/readme.md @@ -0,0 +1,399 @@ +> **Synced from spektacular.** This page is pulled from [hivecommons/spektacular@main](https://github.com/hivecommons/spektacular/blob/main/README.md) during the docs build. Edit the canonical source in the spektacular repository. + +# Spektacular + +Agent-agnostic CLI tool for spec-driven development, providing skills and integrations for coding agents (Claude, Bob, Codex) to plan and implement work from a written spec. + +> **Status:** early development — see the [releases page](https://github.com/hivecommons/spektacular/releases) for the latest version. + +## What is Spektacular? + +Spektacular is a self-contained Go binary that brings spec-driven development to AI coding agents. You write a markdown specification; Spektacular turns it into a reviewed implementation plan and then drives a coding agent to implement it — keeping your intent reviewable at every stage. + +Its core competencies: + +- **Self-contained binary plus installed agent skills.** A single binary that, on `init`, installs the skills (and commands) your coding agent needs to run the Spektacular workflows. +- **State-machine-driven workflow.** Spec, plan, and implement each run as a stepwise state machine. Spektacular hands the agent one per-step prompt at a time (`new` / `goto` / `steps`), so every stage is resumable — stop, inspect, edit, and resume without losing work. +- **Agent-agnostic, multi-agent support.** Works with claude, bob, and codex; pick the one your team already uses, or register your own. +- **Project knowledge base.** A searchable, layered store of conventions, architecture, gotchas, and learnings that feeds context into planning. +- **Project design documents.** The worked design a feature is built to (an API shape, a user-facing flow, a data format) kept wherever your team already keeps it and referenced by the spec that needs it, so specs stay readable and planning is bound to the design that was agreed. + +## How It Works + +Spektacular follows a three-stage workflow — **spec → plan → implement** — each driven step by step by a state machine: + +1. **Spec.** You write a markdown spec (requirements, constraints, acceptance criteria); `spec new` scaffolds one from a template. +2. **Plan.** `plan new` explores your codebase, asks clarifying questions, and writes a detailed implementation plan — `plan.md`, `research.md`, and `context.md`. +3. **Implement.** `implement new` drives the coding agent through each phase of the plan and validates the result against your acceptance criteria. + +Workflow progress can be inspected without reading Spektacular's state files directly. `spektacular spec status` and `spektacular plan status` keep reporting the single in-progress workflow. Passing an artifact name switches the commands to per-artifact status: + +```bash +spektacular spec status +spektacular plan status +``` + +Both take the bare artifact name with no extension (`000057_git-commit`, not `000057_git-commit.md`), the same name `state.json` records in `data.name`. That bare name is the stable key shared by convention across the spec file, the plan directory and the changelog record; it is the key to join on, and the addressing convention [#46](https://github.com/hivecommons/spektacular/issues/46) proposes for every verb. `plan status ` reports the plan's `plan.md` only, not the plan's other documents. + +The named form returns JSON with the artifact kind, name, document status, workflow step when that artifact is currently in progress, completed steps, `created_at`, `closed_at`, and the `spec` / `plan` frontmatter cross-references (surfaced when present, but rarely populated today). Two timestamps are kept apart: `updated_at` is workflow activity and appears only while that artifact has the in-progress workflow, so its absence means nothing is live; `modified_at` is the store's modification time for the artifact and moves on any write, including a checkout or a reformat. Frontmatter dates are stored as `YYYY-MM-DD` and are emitted as RFC3339 midnight UTC timestamps. `spec file list` and `plan file list` carry `modified_at` per entry, so polling many artifacts is one list call. + +For the full pipeline, see the [how-it-works documentation](https://spektacular.dev/how-it-works/). + +## Install & getting started + +Spektacular is a single self-contained Go binary. + +```bash +# Homebrew +brew install jumppad-labs/homebrew-repo/spektacular + +# Go 1.21+ +go install github.com/jumppad-labs/spektacular@latest +``` + +Or download a pre-built binary from the [releases page](https://github.com/hivecommons/spektacular/releases). See the [install docs](https://spektacular.dev/install/) for apt and other methods. You also need a supported coding agent CLI (claude, bob, or codex) installed and configured. + +Once installed, the minimal path is initialise → spec → plan → implement: + +```bash +# 1. Initialise your project for a coding agent (claude, bob, or codex) +spektacular init claude + +# 2. Scaffold a spec, then fill in your requirements +spektacular spec new --data '{"name":"auth-feature"}' +$EDITOR .spektacular/specs/.md + +# 3. Generate an implementation plan +spektacular plan new --data '{"name":""}' + +# 4. Implement the plan +spektacular implement new --data '{"name":""}' +``` + +Spec names are normalised and prefixed by the CLI, so use the returned `spec_name` and `spec_path` for follow-up commands rather than the name you passed. + +Specs are plain markdown with a small set of structured sections (overview, requirements, constraints, acceptance criteria, and so on), and `spec new` scaffolds the template for you. For the full walkthrough and spec format, see the [getting-started tutorial](https://spektacular.dev/tutorials/getting-started) and the [how-it-works documentation](https://spektacular.dev/how-it-works/). + +## Supported agents + +Spektacular ships with three coding-agent integrations. `spektacular init ` runs the chosen agent's install step, writing its workflow skills (and, where the agent has no skill mechanism, command wrappers) into your project: + +- **claude** — installs the workflow skills under `.claude/skills/` and ensures the project's `CLAUDE.md` imports `@AGENTS.md`, so the Spektacular agent rules take effect. +- **bob** — installs skills under `.bob/skills/` and command wrappers under `.bob/commands/`. +- **codex** — installs skills under `.agents/skills/`. + +Each integration is deliberately small: an agent implements a narrow `Agent` interface — `Name()` (its CLI identifier) and `Install()` (which writes its workflow artefacts) — and registers itself with the agent package from an `init()` function. Adding a new agent means implementing those two methods and registering the type. + +Both the coding agent and the storage layer are pluggable behind defined Go interfaces — the `Agent` interface in `internal/agent` and the `Store` interface in `internal/store` (the read/write/search surface backing the spec, plan, and knowledge stores). Only the `file` store ships today. For the full interface signatures and how to add your own backend or agent, see the [extending documentation](https://spektacular.dev/extending/) and the [plugins overview](https://spektacular.dev/plugins/). + +## Project Structure + +Running `spektacular init ` creates: + +``` +.spektacular/ +├── config.yaml # agent, command, debug, store settings, and the repo registry +├── repo.yaml # the colocated repo's own configuration +├── specs/ # your specification files +├── plans/ # generated plans (plan.md, research.md, context.md) +├── changelog/ # changelog records written by the implement workflow +└── knowledge/ # default project knowledge source + ├── conventions/ # always-applied: standing rules, one per file + ├── glossary/ # always-applied: shared domain/project terms + ├── architecture/ # looked-up: how the system is built + ├── gotchas/ # looked-up: sharp edges and traps + ├── learnings/ # looked-up: empirical findings from past work + └── decisions/ # looked-up: the reasoning behind choices +``` + +Each knowledge category directory is scaffolded with a `README.md` describing what belongs in it. By default Spektacular reads `.spektacular/knowledge/` as this repo's own store, addressed by the name the project registered the repo under; the project can declare additional shared stores — for example a `team` directory or a machine-wide `global` one — under `knowledge.sources` (see [Configuration](#configuration)). See [Knowledge](#knowledge) for how it is organised and consumed. + +## Knowledge + +Knowledge is the accumulated know-how a project draws on when planning — conventions, glossary terms, architecture notes, gotchas, learnings, and decisions. It is strictly a **planning-time input**: the planning agent reads it while producing a plan, and the relevant parts are written into the plan itself. The implement workflow then consumes only the plan documents — the plan is the contract. + +### Six categories, two retrieval tiers + +Every entry belongs to exactly one of six categories, fixed by the first segment of its path. Each category has a **retrieval tier** that decides when its entries are loaded: + +- **Always-applied** — `conventions` (standing rules to follow) and `glossary` (shared domain and project terms). Loaded in full on every planning task, and deliberately excluded from search results so they are never surfaced twice. +- **Looked-up** — `architecture`, `gotchas`, `learnings`, and `decisions`. The larger reference body, fetched only when a search matches, so it can grow without weighing down every task. + +The category model — names, retrieval tiers, and per-category boundaries — is declared once in code, so it stays consistent across directory scaffolding, search labelling, and retrieval. A category's *retrieval tier* says **when** its entries are loaded; the addressing *tier* below says **which** knowledge a store holds. The two are different axes. + +### Tiers, search, and de-duplication + +Knowledge lives in one of two tiers. Every registered repo contributes exactly one store, addressed by the name the project registered it under, holding knowledge about that repo's own code. The project declares any number of shared stores under `knowledge.sources`, each with its own `name`, for knowledge that belongs to no single repo (see [Configuration](#configuration)). Every read, search, and always-applied load states a tier and, optionally, the store names to narrow to; every result reports the tier and store it came from. + +Reading and writing name exactly one store, so they take a `tier` and a `name` alongside the path; a request that leaves either out is refused, and the refusal lists the names available in that tier. Searching, listing, conventions, and the always-applied load take `--tier ` and a repeatable `--filter `; omitting the narrowing covers every store the tier reaches, and no store is ever included or excluded implicitly. + +Lookups are **consolidated and de-duplicated** across stores: each entry carries a SHA-256 checksum over its exact bytes, and byte-identical entries appearing in more than one store collapse to a single result. A search result looks like: + +``` +Hit { + tier // addressing tier of the originating store (project or repo) + name // name of the originating store (e.g. docs) + path // locator relative to the store root (e.g. gotchas/db-timeouts.md) + title // the document's first heading, or the locator when it has none + excerpts // compact matched excerpts + score // sum of query-term occurrences (ranking) + category // category derived from the path (e.g. gotchas, architecture) + checksum // SHA-256 over the entry's raw bytes; the byte-identity de-dup key +} +``` + +For the full model — every category definition, the retrieval tiers, the addressing tiers, and the de-duplication rationale — see the [knowledge-base documentation](https://spektacular.dev/knowledge-base/). + +### CLI + +Agents (and you) reach knowledge through the `spektacular knowledge` commands rather than reading the files directly, so access stays consistent across stores. The main subcommands: + +- `knowledge search ` — keyword-search the stores the request covers (excluding the always-applied categories), returning ranked, tier- and category-tagged hits, each carrying the entry's tags. A document need not contain every query word: it is returned if it carries evidence for any of them. Narrow with `--tier`, `--filter`, and a repeatable `--tag` +- `knowledge tags` — list the tag vocabulary already in use, with an entry count for each, most-used first; takes `--tier` and `--filter` +- `knowledge conventions` / `knowledge always-applied` — read the always-applied entries in full; both take `--tier` and `--filter` +- `knowledge categories` — list the categories and their retrieval tiers +- `knowledge read` / `knowledge write` — read and write one addressed entry, via `--data '{"tier":"…","name":"…","path":"…"}'` +- `knowledge delete` — remove one addressed entry, via `--data '{"tier":"…","name":"…","path":"…"}'`; an address holding nothing succeeds and changes nothing, and a category's own generated description is refused +- `knowledge list` — list entries across the stores the request covers; takes `--tier` and `--filter` +- `knowledge sources` — list the configured stores by tier and name, with their locations + +Every subcommand accepts `--schema` to print its input/output JSON schema and exit. + +### Capturing knowledge + +When research surfaces a durable learning, gotcha, or convention worth keeping, the agent **proposes** the destination — the tier, the store name, and the path — along with the exact content, and waits for your explicit confirmation before writing; it never persists to a knowledge store unprompted. In a Spektacular-initialised repo, the `spek-knowledge` skill is the entry point for reading, contributing to, and updating the knowledge base in any session, and coding agents route what they would otherwise save to their own per-user memory into the project knowledge base instead, so captured knowledge lands in git and travels with the project. + +## Configuration + +Configuration is split across two files, and a colocated single-repo project simply holds both in the same `.spektacular/` directory. A repo's Spektacular files can also live apart from its code, in a folder that points at the code (see [Repo configuration](#repo-configuration-repoyaml) below). + +- **`.spektacular/config.yaml` (project configuration).** The project's identity, the coding agent Spektacular drives, the registry of member repos with the location of each repo's Spektacular files, the central `spec`, `plan`, and `changelog` stores, and the design sources the project declares. Spektacular always runs against a project: running it in a directory with no `config.yaml` produces an explicit error pointing at `init` (there is no parent-directory search). +- **`.spektacular/repo.yaml` (repo configuration).** A repo's own concerns only: what it is, where its code lives, its knowledge sources, and its changelog provider. It carries no pointer to any project, so one repo can belong to several projects at once. + +> **Breaking change**: earlier releases used a single `config.yaml` without a project `name`. Existing setups re-initialize with `spektacular init `: init backfills the name (from the directory basename, or `--name`), seeds the colocated repo's `repo.yaml`, and registers it in the new `repos` list. + +### Project configuration (`config.yaml`) + +```yaml +schema: 3 # settings format version, written by Spektacular; `migrate` raises it +written_by: 0.16.0 # the Spektacular version that last wrote this file (informational) +skills_version: 0.16.0 # the Spektacular version that last installed the agent skills +name: my-project # required, slug-safe; namespaces changelog entries +source: git@example.com:org/my-project.git # optional; the project's git address, recorded in derived changelog entries only +command: spektacular +agent: claude +debug: + enabled: false +spec: + provider: file + id_method: timestamp # how new spec identifiers are generated + config: + directory: specs # relative to the folder holding config.yaml +plan: + provider: file + config: + directory: plans # relative to the folder holding config.yaml +changelog: + provider: file + config: + directory: changelog # central changelog; entries land under // +repos: + - name: my-project # the colocated repo, registered by init: this .spektacular/ folder + location: . + - name: docs # a repo checked out beside this one, with its own .spektacular/ + location: ../../docs/.spektacular + - name: lib # a repo folder in this project; its code is cloned from a git source + location: ../repos/lib +knowledge: + sources: # optional, the project's shared stores only (e.g. a team share); + - name: team # each repo declares its own store in its repo.yaml + provider: file + config: + location: ../team-kb # relative to the folder holding config.yaml, as repos are +design: + sources: # optional, where the project's design documents live; + - name: api # declared by the project only, never by a repo + provider: file + config: + location: ../design/api # relative to the folder holding config.yaml; may sit outside the project +``` + +Each repo entry needs a slug-safe unique `name` and a `location`: the folder holding that repo's `repo.yaml` (`local` is still accepted and means the same thing). A relative location is resolved from the folder holding `config.yaml`, and nothing is appended to it, so the project's own footprint is `.` and a repo folder in the project is `../repos/`. A repo is normally added through a guided flow: you are asked which repo to add, and its name, description, role and tags are each proposed for you from what the repo says about itself, one question at a time, with a plain-language confirmation before anything is written. Spektacular's files go inside the repo being added unless it cannot take them or you say otherwise, in which case they live in a folder under the project and the repo is left with only its code. An add can be started and finished while a spec or plan is already in progress. A caller that already knows every detail can still register a repo in a single command with `repo add`. Where the code lives is declared in the repo's own `repo.yaml` as `source`; the old `address` key is no longer read, and a config that still carries it fails to load with an error saying where the value now goes. `description`, `role`, and `tags` are optional metadata, also in `repo.yaml`, that cross-repo planning uses to attribute requirements to the right repo. Add to the registry with `spektacular repo new`, or `spektacular repo add` when every detail is already known, and inspect it with `spektacular repo list`; removal is a manual config edit. Cloned repos are never fetched or pulled automatically; a stale clone produces a warning only. + +**Relative locations everywhere in `config.yaml` share one base: the folder holding `config.yaml`.** That covers a repos entry's `location`, a `knowledge.sources` entry's `config.location`, a `design.sources` entry's `config.location`, and the `spec`, `plan` and `changelog` store directories, so `..` is the project's own root and `../team-kb` a folder beside it. The store directories default to `specs`, `plans` and `changelog`, which land in `.spektacular/specs`, `.spektacular/plans` and `.spektacular/changelog`; a store directory outside the project is refused with the corrected value to write. An absolute location is used as written. A knowledge source that does not resolve to a directory fails fast, naming the store, the path it resolved to, and the base it resolved from; when the store is found where the pre-1.0 rule would have put it, the error also names the exact corrected value to write. A design source behaves the same way and fails fast with the same three facts, with one deliberate difference from the store directories: a design source's location is allowed to resolve outside the project, and is written back exactly as declared rather than re-expressed, because a design source points at a folder the team already keeps. + +### Repo configuration (`repo.yaml`) + +```yaml +schema: 2 # settings format version, written by Spektacular +written_by: 0.16.0 # the Spektacular version that last wrote this file +description: the documentation repo +role: documentation +tags: [docs] +source: # where the code is; omit when it is this folder + provider: file # file or git + config: + location: .. # a path relative to this file, or a git URL for the git provider +knowledge: # the repo's single store; synthesised if the file is absent + provider: file # addressed by the name the project registered this repo under + config: + location: knowledge +changelog: + provider: file + config: + directory: changelog # where this repo's derived entries land +``` + +A repo's Spektacular files can sit inside its code, in a `.spektacular/` folder holding `repo.yaml` with a file source pointing at `..`, or in a folder of their own, for example one folder per repo under a project, with `source` pointing at a checkout on disk (absolute, relative to the folder holding `repo.yaml`, or using `${VAR}`) or at a git repository that Spektacular clones into `.spektacular/repos//` on first use. In the separate layout the code repository receives only code changes; knowledge and changelog entries land under the folder holding `repo.yaml`. `spektacular repo list` reports the resolved source as each repo's `root`. See [Multi-Repo Projects](https://spektacular.dev/projects/) for the layouts. + +Knowledge aggregates across every registered repo's declared sources (in registry order) followed by the project-owned sources, so a repo's knowledge travels with it into every project that registers it. Changelog entries, central and derived per-repo, are namespaced under a folder named after the project (`//_.md`), so multiple projects writing into one repo can never collide. + +### Upgrading (`migrate`) + +Every settings file records the format version it was written in. When a release changes that format, or when you install a new Spektacular, commands stop with an error naming `migrate` until the project is brought up to date; `migrate`, `init`, `version check` and `help` always run, so the way out is always reachable. + +```bash +spektacular migrate --dry-run # list every change without touching disk +spektacular migrate # apply them +``` + +An upgrade brings `config.yaml` and every registered repo's `repo.yaml` present on disk to the current format, keeps a `.v.old` copy of each file it rewrites, names any repo it skipped because it is not checked out, and reinstalls the agent skills when they are older than the running Spektacular. Re-running `spektacular init ` applies the same upgrades. Settings written by a *newer* Spektacular are refused rather than converted, with a message to update Spektacular; such a file is never rewritten. + +### Excluding paths (`.spektacular_ignore`) + +Any source root (a repo, or the project's own storage locations) may carry a `.spektacular_ignore` file using gitignore pattern syntax. Matching paths are excluded from Spektacular's own listing and search results, keeping build artifacts and dependency directories out of planning research, but a directly named path is never blocked, and agents' native file tools are unaffected. + +For the full reference (every key, the id-method semantics, name-normalisation rules, and `${VAR}` expansion) see the [configuration documentation](https://spektacular.dev/configuration/). For the concept of multi-repo projects, why the configuration is split this way, and how work is attributed across repos, see [Multi-Repo Projects](https://spektacular.dev/projects/). + +## Testing + +Spektacular has two layers of tests: a fast Go unit suite, and an end-to-end [Harbor](https://harborframework.com/) harness that runs the workflows against real AI coding agents inside sandboxed Docker containers. + +### Unit tests + +```bash +go test ./... # or: make test +``` + +### End-to-end (Harbor) + +#### Prerequisites + +- Docker +- [uv](https://docs.astral.sh/uv/) (Python package manager) + +#### Install Harbor + +```bash +uv tool install harbor +``` + +#### Run the oracle (scripted) tests + +The oracle agent runs a scripted solution to validate the test harness itself — +no AI tokens required: + +```bash +harbor run -p tests/harbor/spec-workflow -a oracle -o tests/harbor/jobs +``` + +#### Run with a real agent + +Harbor needs an auth token to run Claude Code inside the container. If you use +Claude Max (OAuth), export the token from your local credentials: + +```bash +export ANTHROPIC_AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$HOME/.claude/.credentials.json'))['claudeAiOauth']['accessToken'])") +``` + +If you use an API key instead, export that: + +```bash +export ANTHROPIC_API_KEY=sk-ant-... +``` + +Then run: + +```bash +harbor run -p tests/harbor/spec-workflow -a claude-code -m claude-sonnet-4-6 -o tests/harbor/jobs +``` + +Makefile wrappers run the suites for you, building the binary and wiring up the agent-specific placeholders: + +```bash +make harbor-test-spec # spec workflow (claude) +make harbor-test-spec-codex # spec workflow (codex) +make harbor-test-plan # plan workflow (claude) +make harbor-test-repo # guided repo add, answering each question (claude) +make harbor-test-repo-delegated # guided repo add, handing the whole set over (claude) +``` + +#### Test results + +Results are written to `tests/harbor/jobs/` (gitignored). Each run produces: + +``` +tests/harbor/jobs// +├── result.json # Overall pass/fail and metrics +└── spec-workflow__/ + ├── agent/ # Agent output log + ├── verifier/ + │ ├── test-stdout.txt # pytest output + │ └── reward.txt # 1 = pass, 0 = fail + └── trial.log # Full trial log +``` + +#### Available test tasks + +| Task | Description | +|---|---| +| `tests/harbor/spec-workflow` | Full spec creation workflow, end to end | +| `tests/harbor/plan-workflow` | Full plan generation workflow, end to end | +| `tests/harbor/repo-workflow` | Guided repo add, checked against the agent's own transcript | + +The repo-workflow suite is the one whose assertions are about the conversation rather than the +files: that only the repo itself is asked for cold, that every later question carries a proposed +value, that the questions arrive one per exchange, and that none of Spektacular's internal +vocabulary reaches the user. Its rules are themselves checked by +`tests/harbor/repo-workflow/tests/test_verifier_selfcheck.py`, which runs locally under plain +pytest with no container and proves each rule fails on a transcript that breaks it: + +```bash +python3 -m pytest tests/harbor/repo-workflow/tests/test_verifier_selfcheck.py +``` + +## Building from Source + +```bash +# build binary +make build + +# run tests +make test + +# cross-compile for all platforms +make cross +``` + +`make build` produces the binary at `./bin/spektacular`. The `Makefile` targets: + +| Target | Description | +|---|---| +| `make build` | Build the `./bin/spektacular` binary | +| `make test` | Run `go test ./...` | +| `make lint` | Run `go vet ./...` | +| `make clean` | Remove build artefacts | +| `make install-local` | Build and copy the binary to `/usr/local/bin` | +| `make cross` | Cross-compile for darwin/linux/windows (amd64 + arm64) | + +## Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b my-feature`) +3. Make your changes +4. Run the tests and vet checks (`make test`, `make lint`) +5. Submit a pull request + +## License + +[Apache 2.0](https://github.com/hivecommons/spektacular/blob/main/LICENSE) diff --git a/netlify.toml b/netlify.toml index a4a3a71..18d8cb6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -80,6 +80,16 @@ to = "/docs/promptargs/overview/introduction" status = 302 +[[redirects]] + from = "/docs/spektacular" + to = "/docs/spektacular/overview/introduction" + status = 302 + +[[redirects]] + from = "/docs/spektacular/" + to = "/docs/spektacular/overview/introduction" + status = 302 + # Short URLs [[redirects]] from = "/code" diff --git a/public/config/shared.json b/public/config/shared.json index 60a005d..9922553 100644 --- a/public/config/shared.json +++ b/public/config/shared.json @@ -34,6 +34,13 @@ "branch": "main", "isDefault": true } + }, + "spektacular": { + "latest": { + "label": "main (Latest)", + "branch": "main", + "isDefault": true + } } }, "projects": { @@ -61,6 +68,11 @@ "name": "promptargs", "basePath": "promptargs", "currentVersion": "main" + }, + "spektacular": { + "name": "Spektacular", + "basePath": "spektacular", + "currentVersion": "main" } }, "relatedProjects": [ @@ -92,6 +104,12 @@ "href": "/docs/promptargs/overview/introduction", "description": "Template expansion for AI prompts", "secondary": true + }, + { + "title": "Spektacular", + "href": "/docs/spektacular/overview/introduction", + "description": "Spec-driven development for AI coding agents (canonical site: spektacular.dev)", + "secondary": true } ], "editBaseUrls": { @@ -99,7 +117,8 @@ "hotshot": "https://github.com/hivecommons/hotshot/edit/main", "pluk": "https://github.com/hivecommons/pluk/edit/main", "rationguard": "https://github.com/hivecommons/rationguard/edit/main", - "promptargs": "https://github.com/hivecommons/promptargs/edit/main" + "promptargs": "https://github.com/hivecommons/promptargs/edit/main", + "spektacular": "https://github.com/hivecommons/spektacular/edit/main" }, "updatedAt": "2026-09-02T15:07:02.112Z" } diff --git a/scripts/sync-sibling-docs.ts b/scripts/sync-sibling-docs.ts index ecc2e9f..14d73b5 100644 --- a/scripts/sync-sibling-docs.ts +++ b/scripts/sync-sibling-docs.ts @@ -1,5 +1,5 @@ /** - * Sync hotshot, pluk, rationguard, and promptargs docs from their GitHub repos into docs/content/. + * Sync hotshot, pluk, rationguard, promptargs, and Spektacular docs from their GitHub repos into docs/content/. * * Same single-sourcing model as scripts/sync-hive-docs.ts: the repos own the * markdown; this script pulls it at build time (prebuild) so the site never @@ -57,6 +57,17 @@ const PROJECTS: ProjectSync[] = [ branch: process.env.PROMPTARGS_DOCS_REF || "main", files: [{ source: "README.md", target: "readme.md", required: true }], }, + { + // Canonical site is https://spektacular.dev; the repo README is the doc source. + project: "spektacular", + owner: process.env.SIBLING_DOCS_OWNER || "hivecommons", + repo: "spektacular", + branch: process.env.SPEKTACULAR_DOCS_REF || "main", + files: [ + { source: "README.md", target: "readme.md", required: true }, + { source: "docs/knowledge-base.md", target: "knowledge-base.md" }, + ], + }, ]; @@ -79,6 +90,12 @@ function scrubLegacyBranding(content: string): string { .replace(/kubestellar\/hive/g, "hivecommons/hive") .replace(/kubestellar\/pluk/g, "hivecommons/pluk") .replace(/kubestellar\/hotshot/g, "hivecommons/hotshot") + // Spektacular transferred from jumppad-labs. Only rewrite repo sub-paths + // (releases, issues, blob): the Go module path is still + // github.com/jumppad-labs/spektacular and the Homebrew tap still lives + // under jumppad-labs, so `go install ...@latest` and `brew install` must + // keep their original targets. + .replace(/github\.com\/jumppad-labs\/spektacular\//g, "github.com/hivecommons/spektacular/") .replace(/kubestellar\.io/g, "hivecommons.dev") .replace(/KubeStellar/g, "Hive Commons") .replace(/Kubestellar/g, "Hive Commons") diff --git a/src/app/docs/[...slug]/layout.tsx b/src/app/docs/[...slug]/layout.tsx index c705129..dc7907e 100644 --- a/src/app/docs/[...slug]/layout.tsx +++ b/src/app/docs/[...slug]/layout.tsx @@ -14,6 +14,7 @@ function getProjectFromSlug(slug: string[]): ProjectId { if (slug[0] === 'pluk') return 'pluk' if (slug[0] === 'rationguard') return 'rationguard' if (slug[0] === 'promptargs') return 'promptargs' + if (slug[0] === 'spektacular') return 'spektacular' } return 'hive' } diff --git a/src/app/docs/[...slug]/page.tsx b/src/app/docs/[...slug]/page.tsx index a44f4a7..d9b1f0b 100644 --- a/src/app/docs/[...slug]/page.tsx +++ b/src/app/docs/[...slug]/page.tsx @@ -167,7 +167,7 @@ async function buildContent(slug: string[], projectId?: ProjectId): Promise 0 && knownProjects.includes(slug[0])) { return { diff --git a/src/app/docs/page-map.ts b/src/app/docs/page-map.ts index 1076927..a969226 100644 --- a/src/app/docs/page-map.ts +++ b/src/app/docs/page-map.ts @@ -18,6 +18,8 @@ export function getContentPath(projectId: ProjectId): string { return path.join(process.cwd(), 'docs', 'content', 'rationguard') case 'promptargs': return path.join(process.cwd(), 'docs', 'content', 'promptargs') + case 'spektacular': + return path.join(process.cwd(), 'docs', 'content', 'spektacular') case 'hive': default: return path.join(process.cwd(), 'docs', 'content', 'hive') @@ -35,6 +37,8 @@ export function getBasePath(projectId: ProjectId): string { return 'docs/rationguard' case 'promptargs': return 'docs/promptargs' + case 'spektacular': + return 'docs/spektacular' case 'hive': default: return 'docs/hive' @@ -147,6 +151,22 @@ const NAV_STRUCTURE_PROMPTARGS: Array<{ title: string; items: NavItem[] }> = [ } ] +// Spektacular Navigation Structure (canonical site: https://spektacular.dev) +const NAV_STRUCTURE_SPEKTACULAR: Array<{ title: string; items: NavItem[] }> = [ + { + title: 'Overview', + items: [ + { 'Introduction': 'readme.md' }, + ] + }, + { + title: 'Guides', + items: [ + { 'Knowledge base': 'knowledge-base.md' }, + ] + } +] + // Get navigation structure for a project function getNavStructure(projectId: ProjectId): Array<{ title: string; items: NavItem[] }> { switch (projectId) { @@ -158,6 +178,8 @@ function getNavStructure(projectId: ProjectId): Array<{ title: string; items: Na return NAV_STRUCTURE_RATIONGUARD case 'promptargs': return NAV_STRUCTURE_PROMPTARGS + case 'spektacular': + return NAV_STRUCTURE_SPEKTACULAR case 'hive': default: return NAV_STRUCTURE_HIVE diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index c7728cc..a74c350 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -125,7 +125,7 @@ export default function sitemap(): MetadataRoute.Sitemap { }) // --- Project-specific docs --- - const projectIds: ProjectId[] = ['hive', 'hotshot', 'pluk', 'rationguard', 'promptargs'] + const projectIds: ProjectId[] = ['hive', 'hotshot', 'pluk', 'rationguard', 'promptargs', 'spektacular'] for (const projectId of projectIds) { const projectContentPath = path.join(contentRoot, PROJECTS[projectId].basePath) diff --git a/src/components/docs/DocsSidebar.tsx b/src/components/docs/DocsSidebar.tsx index 9de72a5..0072e9d 100644 --- a/src/components/docs/DocsSidebar.tsx +++ b/src/components/docs/DocsSidebar.tsx @@ -36,6 +36,7 @@ const PRIMARY_PROJECTS = [ { id: 'pluk', label: 'pluk', href: '/docs/pluk/overview/introduction' }, { id: 'rationguard', label: 'rationguard', href: '/docs/rationguard/overview/introduction' }, { id: 'promptargs', label: 'promptargs', href: '/docs/promptargs/overview/introduction' }, + { id: 'spektacular', label: 'Spektacular', href: '/docs/spektacular/overview/introduction' }, ] as const; // No legacy projects on the Hive Commons docs site. diff --git a/src/components/docs/DocsSourceActions.tsx b/src/components/docs/DocsSourceActions.tsx index f339922..5d8d271 100644 --- a/src/components/docs/DocsSourceActions.tsx +++ b/src/components/docs/DocsSourceActions.tsx @@ -16,6 +16,7 @@ const STATIC_EDIT_BASE_URLS: Record = { pluk: 'https://github.com/hivecommons/pluk/edit/main', rationguard: 'https://github.com/hivecommons/rationguard/edit/main', promptargs: 'https://github.com/hivecommons/promptargs/edit/main', + spektacular: 'https://github.com/hivecommons/spektacular/edit/main', }; // Prevent path traversal diff --git a/src/components/docs/EditPageLink.tsx b/src/components/docs/EditPageLink.tsx index 6049841..ebc72cd 100644 --- a/src/components/docs/EditPageLink.tsx +++ b/src/components/docs/EditPageLink.tsx @@ -11,6 +11,7 @@ const STATIC_EDIT_BASE_URLS: Record = { pluk: 'https://github.com/hivecommons/pluk/edit/main', rationguard: 'https://github.com/hivecommons/rationguard/edit/main', promptargs: 'https://github.com/hivecommons/promptargs/edit/main', + spektacular: 'https://github.com/hivecommons/spektacular/edit/main', }; interface EditPageLinkProps { diff --git a/src/components/docs/RelatedProjects.tsx b/src/components/docs/RelatedProjects.tsx index 5c0d5ce..ac9f44d 100644 --- a/src/components/docs/RelatedProjects.tsx +++ b/src/components/docs/RelatedProjects.tsx @@ -227,6 +227,7 @@ export function RelatedProjects({ variant = 'full', onCollapse, bannerActive = f if (pathname.startsWith('/docs/kubeflex')) return 'KubeFlex'; if (pathname.startsWith('/docs/multi-plugin')) return 'Multi Plugin'; if (pathname.startsWith('/docs/pluk')) return 'pluk'; + if (pathname.startsWith('/docs/spektacular')) return 'Spektacular'; if (pathname.startsWith('/docs/contributing')) return 'Contributing'; if (pathname.startsWith('/docs/community')) return 'Community'; if (pathname.startsWith('/docs/news')) return 'News'; diff --git a/src/config/versions.ts b/src/config/versions.ts index d5f95bf..4bc706f 100644 --- a/src/config/versions.ts +++ b/src/config/versions.ts @@ -1,5 +1,5 @@ // Multi-project versions config for the Hive Commons docs site -// Supports hive, hotshot, pluk, rationguard, and promptargs with independent versioning +// Supports hive, hotshot, pluk, rationguard, promptargs, and spektacular with independent versioning // // Versioning Strategy: // - Each project has its own version scheme @@ -18,7 +18,7 @@ export const NETLIFY_SITE_NAME = "hivecommons-docs" export const PRODUCTION_URL = "https://docs.hivecommons.dev" // Project identifiers -export type ProjectId = "hive" | "hotshot" | "pluk" | "rationguard" | "promptargs" +export type ProjectId = "hive" | "hotshot" | "pluk" | "rationguard" | "promptargs" | "spektacular" // Version info structure export interface VersionInfo { @@ -84,6 +84,15 @@ const PROMPTARGS_VERSIONS: Record = { }, } +// spektacular versions (canonical site: https://spektacular.dev) +const SPEKTACULAR_VERSIONS: Record = { + latest: { + label: "main (Latest)", + branch: "main", + isDefault: true, + }, +} + // All projects configuration export const PROJECTS: Record = { hive: { @@ -126,6 +135,14 @@ export const PROJECTS: Record = { contentPath: "docs/content/promptargs", versions: PROMPTARGS_VERSIONS, }, + spektacular: { + id: "spektacular", + name: "Spektacular", + basePath: "spektacular", + currentVersion: "main", + contentPath: "docs/content/spektacular", + versions: SPEKTACULAR_VERSIONS, + }, } // Get project from URL pathname @@ -142,6 +159,9 @@ export function getProjectFromPath(pathname: string): ProjectConfig { if (pathname.startsWith("/docs/promptargs")) { return PROJECTS.promptargs } + if (pathname.startsWith("/docs/spektacular")) { + return PROJECTS.spektacular + } return PROJECTS.hive } From 13afd63d307203e5a06ebcbf223276109ae754eb Mon Sep 17 00:00:00 2001 From: Andrew Anderson Date: Wed, 23 Sep 2026 11:08:57 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=96=20docs:=20port=20Spektacular?= =?UTF-8?q?=20docs=20from=20both=20repos=20and=20make=20the=20sync=20lint-?= =?UTF-8?q?clean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the sibling-docs sync so the Spektacular section on docs.hivecommons.dev carries the full documentation set: - Overview (README) and Knowledge base from hivecommons/spektacular - "Getting started" and "Dealing with unknown criteria" tutorials from hivecommons/spektacular-website (Astro MDX), converted to Markdown by the new scripts/mdx-to-markdown.ts: imports/exports stripped, and rendered as headed sections, as a timestamped link, unknown components flattened to their text with a note in the sync log, site-absolute links pointed at spektacular.dev, and referenced images copied under docs/content/spektacular/images/ - a second source per project with its own ref (SPEKTACULAR_WEBSITE_DOCS_REF) - an "also published at spektacular.dev" canonical note on every page - sidebar: Overview / Tutorials / Guides Make the synced content satisfy markdownlint (MD031/MD040) at the source instead of hand-editing generated files: scripts/markdown-fences.ts adds a language to bare fences and blank lines around them, applied by both sync scripts. The committed copies are regenerated by running the syncs (this also brings the Hive pages up to the v5 source that #13 retargeted). CI fixes for the three pre-existing red checks: - Vitest: run on Node 22 (jsdom 30 pulls in undici 8, which needs >= 22.19; Node 20 failed with "webidl.util.markAsUncloneable is not a function") - ESLint: drop eslint-disable comments for the removed rule @typescript-eslint/no-throw-literal - markdownlint: fence normalisation above Tests: fence normaliser, MDX converter, and the sibling sync config (every project enumerated, Spektacular pulling from both repos). Signed-off-by: Andrew Anderson --- .github/workflows/vitest.yml | 4 +- README.md | 2 + docs/content/hive/acmm-policy-matrix.md | 88 +- .../adr/0001-record-architecture-decisions.md | 2 +- .../0002-mitm-proxy-network-enforcement.md | 2 +- .../hive/adr/0003-acmm-autonomy-levels.md | 4 +- .../hive/adr/0004-beads-work-ledger.md | 4 +- .../hive/adr/0005-forge-abstraction.md | 4 +- .../hive/adr/0006-planning-intelligence.md | 10 +- docs/content/hive/adr/0007-token-mint.md | 8 +- .../hive/adr/0008-ioscan-untrusted-input.md | 10 +- .../hive/adr/0009-trajectory-review.md | 8 +- .../adr/0010-escalation-circuit-breaker.md | 29 +- .../content/hive/adr/0011-knowledge-system.md | 14 +- docs/content/hive/adr/0012-skill-registry.md | 50 +- docs/content/hive/adr/0013-cel-triggers.md | 6 +- docs/content/hive/adr/0014-hub-spoke.md | 4 +- .../hive/adr/0015-csp-style-src-scope.md | 6 +- .../hive/adr/0016-csp-script-src-scope.md | 8 +- .../hive/adr/0017-podman-quadlet-lifecycle.md | 12 +- docs/content/hive/adr/readme.md | 2 +- docs/content/hive/agent-configuration.md | 316 ++++- docs/content/hive/architecture.md | 67 +- docs/content/hive/backup-dr.md | 159 ++- docs/content/hive/contributor-relay.md | 1091 ++++++++++++++++- docs/content/hive/getting-started.md | 22 +- docs/content/hive/hivectl.md | 590 ++++++++- docs/content/hive/landscape.md | 2 +- docs/content/hive/manual-provisioning.md | 57 +- docs/content/hive/net-admin-requirement.md | 120 +- docs/content/hive/readme.md | 265 ++-- docs/content/hive/release-channels.md | 67 +- docs/content/hive/roadmap.md | 38 +- docs/content/hive/security-model.md | 23 +- docs/content/hive/security-threat-model.md | 14 +- docs/content/hive/troubleshooting.md | 384 +++++- docs/content/hotshot/linux.md | 4 +- docs/content/pluk/readme.md | 2 +- docs/content/promptargs/readme.md | 31 +- docs/content/rationguard/readme.md | 6 +- docs/content/spektacular/getting-started.md | 879 +++++++++++++ .../getting-started/acceptance-bob.png | Bin 0 -> 505266 bytes .../getting-started/acceptance-claude.png | Bin 0 -> 136340 bytes .../getting-started/approval-claude.png | Bin 0 -> 76504 bytes .../getting-started/constraint-bob.png | Bin 0 -> 493334 bytes .../getting-started/constraint-claude.png | Bin 0 -> 134203 bytes .../getting-started/finished-bob.png | Bin 0 -> 479300 bytes .../getting-started/finished-claude.png | Bin 0 -> 130973 bytes .../getting-started/implement-done-claude.png | Bin 0 -> 133347 bytes .../getting-started/implement-init-bob.png | Bin 0 -> 413326 bytes .../getting-started/implement-init-claude.png | Bin 0 -> 143546 bytes .../tutorials/getting-started/init-bob.png | Bin 0 -> 387640 bytes .../tutorials/getting-started/init-claude.png | Bin 0 -> 83563 bytes .../getting-started/new-spec-bob.png | Bin 0 -> 455625 bytes .../getting-started/new-spec-claude.png | Bin 0 -> 85140 bytes .../getting-started/non-goals-bob.png | Bin 0 -> 453435 bytes .../getting-started/non-goals-claude.png | Bin 0 -> 126057 bytes .../getting-started/overview-bob.png | Bin 0 -> 516567 bytes .../getting-started/overview-claude.png | Bin 0 -> 112101 bytes .../tutorials/getting-started/plan-bob.png | Bin 0 -> 398834 bytes .../tutorials/getting-started/plan-claude.png | Bin 0 -> 142644 bytes .../getting-started/plan-confirm-claude.png | Bin 0 -> 145428 bytes .../getting-started/plan-question-bob.png | Bin 0 -> 426667 bytes .../getting-started/plan-question-claude.png | Bin 0 -> 142092 bytes .../getting-started/requirements-bob.png | Bin 0 -> 579502 bytes .../getting-started/requirements-claude.png | Bin 0 -> 119342 bytes .../getting-started/success-metrics-bob.png | Bin 0 -> 625897 bytes .../success-metrics-claude.png | Bin 0 -> 133995 bytes docs/content/spektacular/knowledge-base.md | 2 + docs/content/spektacular/readme.md | 8 +- docs/content/spektacular/unknown-criteria.md | 175 +++ scripts/markdown-fences.test.ts | 48 + scripts/markdown-fences.ts | 79 ++ scripts/mdx-to-markdown.test.ts | 172 +++ scripts/mdx-to-markdown.ts | 306 +++++ scripts/sync-hive-docs.ts | 6 +- scripts/sync-sibling-docs.test.ts | 98 ++ scripts/sync-sibling-docs.ts | 219 +++- src/__tests__/healthz-route.test.ts | 1 - .../search-route.non-error-throw.test.ts | 1 - src/app/docs/page-map.ts | 9 + 81 files changed, 5091 insertions(+), 447 deletions(-) create mode 100644 docs/content/spektacular/getting-started.md create mode 100644 docs/content/spektacular/images/tutorials/getting-started/acceptance-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/acceptance-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/approval-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/constraint-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/constraint-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/finished-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/finished-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/implement-done-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/implement-init-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/implement-init-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/init-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/init-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/new-spec-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/new-spec-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/non-goals-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/non-goals-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/overview-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/overview-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/plan-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/plan-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/plan-confirm-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/plan-question-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/plan-question-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/requirements-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/requirements-claude.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/success-metrics-bob.png create mode 100644 docs/content/spektacular/images/tutorials/getting-started/success-metrics-claude.png create mode 100644 docs/content/spektacular/unknown-criteria.md create mode 100644 scripts/markdown-fences.test.ts create mode 100644 scripts/markdown-fences.ts create mode 100644 scripts/mdx-to-markdown.test.ts create mode 100644 scripts/mdx-to-markdown.ts create mode 100644 scripts/sync-sibling-docs.test.ts diff --git a/.github/workflows/vitest.yml b/.github/workflows/vitest.yml index c151fcb..50956c5 100644 --- a/.github/workflows/vitest.yml +++ b/.github/workflows/vitest.yml @@ -29,7 +29,9 @@ jobs: - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "20" + # jsdom 30 depends on undici 8, which requires Node >= 22.19 + # (Node 20 fails with "webidl.util.markAsUncloneable is not a function"). + node-version: "22" cache: "npm" - name: Install dependencies diff --git a/README.md b/README.md index 4e6afce..8f63b7f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ at build time: - `scripts/sync-hive-docs.ts` pulls Hive docs from `hivecommons/hive` (`src/docs/`, branch `v5`; override with `HIVE_DOCS_OWNER` / `HIVE_DOCS_REPO` / `HIVE_DOCS_REF`). - `scripts/sync-sibling-docs.ts` pulls the hotshot, pluk, rationguard, promptargs, and Spektacular docs from their repos. + Spektacular also pulls its tutorials from `hivecommons/spektacular-website` (MDX, converted to Markdown by + `scripts/mdx-to-markdown.ts`; override the ref with `SPEKTACULAR_WEBSITE_DOCS_REF`). Edit the canonical source in the project repository — not the synced copies under `docs/content/`. If a sync source is unreachable at build time, the committed copies diff --git a/docs/content/hive/acmm-policy-matrix.md b/docs/content/hive/acmm-policy-matrix.md index c67e830..45114f8 100644 --- a/docs/content/hive/acmm-policy-matrix.md +++ b/docs/content/hive/acmm-policy-matrix.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/acmm-policy-matrix.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/acmm-policy-matrix.md) during the docs build. Edit the canonical source in the Hive repository. # ACMM Policy Matrix @@ -15,14 +15,14 @@ Each agent runs in one of four modes, controlling what actions it can take on Gi - **Advisory**: Agent observes and records findings as beads on the dashboard. No GitHub interaction. - **Measured**: Agent can file GitHub issues to make findings visible to the team. No code changes. -- **Holdgated**: Agent can write code and open PRs, but every PR gets a `hold` label. A human must review and remove `hold` before merge. Agent never merges. +- **Holdgated**: Agent can write code and open PRs, but every PR gets a `hold` label. A human must review and remove `hold` before merge. Agent never merges. One exception: a hold the hive applied *for level reasons* is released automatically once the current level no longer calls for it — see the promotion note below. A hold **you** applied is never removed automatically. - **Full**: Agent operates autonomously — opens PRs and merges on green CI. Highest trust level. ## ACMM Levels ### L1 — Inception (Assisted) (2 agents) -A single interactive advisor helps with repo setup and architecture decisions. Guide agent makes advisory beads. Brainstorm agent handles project inception — turning raw ideas into structured KB facts and scaffold. No feedback loops. See the [Inception operator guide](https://github.com/hivecommons/hive/blob/v4/src/docs/inception.md) for the end-to-end workflow and API reference. +A single interactive advisor helps with repo setup and architecture decisions. Guide agent makes advisory beads. Brainstorm agent handles project inception — turning raw ideas into structured KB facts and scaffold. No feedback loops. See the [Inception operator guide](https://github.com/hivecommons/hive/blob/v5/src/docs/inception.md) for the end-to-end workflow and API reference. | Agent | Mode | Template | |-------|------|----------| @@ -68,9 +68,9 @@ Delivery agents open GitHub issues — bugs, docs gaps, CI problems, security vu | **sec-check** | **holdgated** | `sec-check-holdgated.md` | | brainstorm | advisory | `brainstorm-advisory.md` | -### L5 — Semi-Autonomous (Semi-Automated) (11 agents) +### L5 — Semi-Autonomous (Semi-Automated) (12 agents) -Agents open issues AND pull requests. All PRs get a hold label — humans batch-review and approve. Architect produces RFCs, strategist coordinates across agents. The system proposes; it does not merge autonomously. +Agents open issues AND pull requests. All PRs get a hold label — humans batch-review and approve. Architect produces RFCs, strategist coordinates across agents, and reviewer works the hold-gated PR queue every 30 minutes. The system proposes; it does not merge autonomously. | Agent | Mode | Template | |-------|------|----------| @@ -82,13 +82,14 @@ Agents open issues AND pull requests. All PRs get a hold label — humans batch- | sec-check | holdgated | `sec-check-holdgated.md` | | architect | holdgated | `architect-holdgated.md` | | strategist | holdgated | `strategist-holdgated.md` | +| reviewer | converse | `reviewer-queue.md` | | telemetry (paused) | holdgated | `telemetry-holdgated.md` | | operations (paused) | holdgated | `operations-holdgated.md` | | brainstorm | advisory | `brainstorm-advisory.md` | -### L6 — Fully Autonomous (12 agents) +### L6 — Fully Autonomous (13 agents) -Existing autonomous lanes can open issues, create PRs, and auto-merge on green CI. No hold label. Outreach handles community engagement. Telemetry and operations remain paused and use `ISSUES_AND_PRS`, so they never merge their own PRs. +Existing autonomous lanes can open issues, create PRs, and auto-merge on green CI. No hold label. Outreach handles community engagement. Reviewer stays advisory even here — its `requires_human` verdict is what pulls a PR out of the auto-merge lane. Telemetry and operations remain paused and use `ISSUES_AND_PRS`, so they never merge their own PRs. | Agent | Mode | Template | |-------|------|----------| @@ -101,6 +102,7 @@ Existing autonomous lanes can open issues, create PRs, and auto-merge on green C | architect | full | `architect-full.md` | | strategist | full | `strategist-full.md` | | outreach | full | `outreach-full.md` | +| reviewer | converse | `reviewer-queue.md` | | telemetry (paused) | full | `telemetry-full.md` | | operations (paused) | full | `operations-full.md` | | brainstorm | advisory | `brainstorm-advisory.md` | @@ -112,12 +114,43 @@ Existing autonomous lanes can open issues, create PRs, and auto-merge on green C ## Key Rules 1. **All PRs are holdgated below L6.** No agent can auto-merge unless running at L6 (Fully Autonomous). -2. **Advisory agents never get GH auth.** The `${GH_AUTH}` template variable is only injected into measured, holdgated, and full templates. +2. **Advisory agents never get GH auth.** The `${GH_AUTH}` template variable is only injected into measured, holdgated, full, and converse templates. The converse tier is the one place an agent writes to GitHub without sitting on the mode ladder: `reviewer` is `mode: ADVISORY` plus the orthogonal `converse` capability ([#4492](https://github.com/hivecommons/hive/issues/4492)), which grants comments and PR reviews and nothing else — no issue creation, no relabelling, no push, no merge. It needs the auth block because posting a review *is* a GitHub write. 3. **Supervisor uses no-GitHub advisory mode.** At every level, supervisor uses `supervisor-nogithub.md` in the built-in ACMM packs — it monitors agent health, not code. 4. **Mode escalation is per-agent.** At L4, some agents are measured (issues only) while others are holdgated (issues + PRs). The level defines the mix. 5. **Knowledge priming works at all levels.** The `${KNOWLEDGE}` template variable injects relevant facts from git sources and wiki layers regardless of the agent's mode. 6. **Brainstorm is always advisory.** It produces KB facts and beads, never GitHub issues or PRs. Its role evolves from inception (L1) to ongoing ideation (L2+), but its mode stays advisory at all levels. -7. **Telemetry and operations are L5/L6-only opt-in agents.** Below L5 they are absent from the pack roster and dashboard, do not spawn panes, and cannot be kicked. At L5–L6 they use a paused cadence in every governor mode until an operator opts in; they may open issues and PRs but never merge. +7. **Reviewer is L5/L6-only by default and never merges.** It joined the L5 and L6 rosters in [#8023](https://github.com/hivecommons/hive/issues/8023) at a 30-minute cadence in every governor mode. Below L5 no pack lists it, so an operator who wants repo-grounded PR review creates it by hand and a pack apply leaves that agent's mode, model, backend, and pause state alone. Its mode stays `ADVISORY` at both levels, including L6: it reads the queue, comments, and returns a verdict, and it is that verdict — `requires_human` or `reject` — that pulls a PR out of the auto-merge lane. +8. **Telemetry and operations are L5/L6-only opt-in agents.** Below L5 they are absent from the pack roster and dashboard, do not spawn panes, and cannot be kicked. At L5–L6 they use a paused cadence in every governor mode until an operator opts in; they may open issues and PRs but never merge. + +## ioscan hardening defaults per level + +Two `ioscan` hardening modes take their default from the pack governor rather +than being fixed globally. Both are overridable per hive in either direction — +the pack only supplies the default when the hive leaves the key unset. + +| Setting | L1–L4 default | L5–L6 default | What the default does | Override | +|---|---|---|---|---| +| `ioscan.canaries` | **on** | **on** | Plants a per-kick `HIVE-CANARY-*` marker and scans agent egress for it. Default flipped on ([#7083](https://github.com/hivecommons/hive/issues/7083)) now that the egress scan is encoding-aware ([#6701](https://github.com/hivecommons/hive/issues/6701), [#6720](https://github.com/hivecommons/hive/issues/6720)). | `ioscan.canaries: false` | +| `ioscan.fail_mode` | `open` | **`closed`** (set by the L5/L6 packs' `governor.ioscan_fail_mode`) | `open` redacts a Critical injection finding and continues the kick; `closed` blocks the kick and records an `ioscan_fail_closed` audit entry. | `ioscan.fail_mode: open` (or `closed` to opt in below L5) | + +`fail_mode: closed` is the default only at L5–L6 because those are the levels +where agents can merge, so a Critical finding that slips through has the highest +blast radius. **The tradeoff is real and worth stating plainly: under `closed`, +every Critical false-positive becomes a stalled queue item that an operator must +clear by hand.** A hive that cannot absorb that operational load should set +`ioscan.fail_mode: open` explicitly; an L1–L4 hive that wants the stricter +posture sets `ioscan.fail_mode: closed`. An explicit value always wins over the +pack default. The knob lives on the pack governor: + +```yaml +# packs/level-5.yaml (and level-6.yaml) +governor: + ioscan_fail_mode: closed # "" (open) below L5; closed at L5/L6 +``` + +## Which levels may publish audit findings + +The audit campaign's issue publisher ([audit-campaign.md](https://github.com/hivecommons/hive/blob/v5/src/docs/audit-campaign.md#publication)) files validated findings as issues only at **L3 and above**, the first level whose pack grants an agent the measured (issues) mode. At L1 and L2 every agent is advisory, so the publisher refuses with a typed error and an audit entry instead of filing. Security-sensitive findings never become public issues at any level; they go to `publication.private_channel` or are refused. Publication also requires `publication.enabled: true` and the `enforce` convergence mode; `shadow` records `withheld:mode` and writes nothing. ## Where ACMM gap issues are filed @@ -187,6 +220,13 @@ Notes: - **Promotion adds agents and capability; demotion narrows it.** Moving up to L6 makes agents auto-merge on green CI; moving down returns them to holdgated or advisory. The per-level capability grid is the table at the top of this page. + Promotion also **releases the level holds the hive itself applied** to open App + PRs that the new level no longer requires, so you do not have to clean them up + by hand after a level bump. Release is fail-closed: it applies only to + App-authored PRs carrying the hive's own attributable level-hold notice, only + when the most recent `hold` label event was applied by the App, and never while + a self-authorization hold applies. A hold a human applied — or re-applied after + the hive removed one — is never touched. - **Operator-created agents are preserved.** `ApplyPack` reconciles pack agents; agents you created yourself are not removed by a level change (deletion is tombstoned separately — see agent configuration). @@ -200,6 +240,34 @@ A separate, advisory-only computation — the ACMM advisor (`pkg/acmmadvisor`) can tell you whether a hive has earned progression to the next level, based on test coverage, green-CI streak, merge success rate, and backlog/hold signals. It never changes the applied level itself; changing the level is always the -manual process described above. See the [ACMM advisor](https://github.com/hivecommons/hive/blob/v4/src/docs/acmm-advisor.md) page +manual process described above. See the [ACMM advisor](https://github.com/hivecommons/hive/blob/v5/src/docs/acmm-advisor.md) page for the exact thresholds per target level and what the `GET /api/acmm-recommendation` endpoint returns. + +## Automatic autonomy-signal level changes + +The retro lane can also act on recorded autonomy signal findings. This policy is +additive and **off by default**: + +```yaml +autonomy: + auto_promote: false + auto_demote: false + promote_after: 3 + demote_on: rollback # rollback | rework | either + max_level: 6 + cooldown_days: 7 +``` + +When enabled, three consecutive qualifying repo-scoped retro findings promote +the repo by one level, never skipping a level and never above `max_level`. +A rollback finding demotes by one level immediately; demotions are not blocked +by cooldown. A pinned repo policy (`project.repo_policies[].acmm_pinned: true`) +is never moved automatically. + +Every automatic move writes a repo-keyed `project.repo_policies[]` record with +the last change, evidence bead IDs, and pin state, and also records an audit +entry plus a visible decision bead. The hive-wide `acmm_level` remains the +ceiling. Until the per-repo ACMM RFC (#6111) lands, Hive keeps this repo-keyed +seam and teaches the live proxy to apply the repo override on matching +repository requests so enforcement observes the decision without a restart. diff --git a/docs/content/hive/adr/0001-record-architecture-decisions.md b/docs/content/hive/adr/0001-record-architecture-decisions.md index f98fd75..90a3634 100644 --- a/docs/content/hive/adr/0001-record-architecture-decisions.md +++ b/docs/content/hive/adr/0001-record-architecture-decisions.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0001-record-architecture-decisions.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0001-record-architecture-decisions.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0001: Record architecture decisions diff --git a/docs/content/hive/adr/0002-mitm-proxy-network-enforcement.md b/docs/content/hive/adr/0002-mitm-proxy-network-enforcement.md index 9a3a00e..2f64f8f 100644 --- a/docs/content/hive/adr/0002-mitm-proxy-network-enforcement.md +++ b/docs/content/hive/adr/0002-mitm-proxy-network-enforcement.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0002-mitm-proxy-network-enforcement.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0002-mitm-proxy-network-enforcement.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0002: MITM proxy network enforcement diff --git a/docs/content/hive/adr/0003-acmm-autonomy-levels.md b/docs/content/hive/adr/0003-acmm-autonomy-levels.md index c22ac83..b0a2cbe 100644 --- a/docs/content/hive/adr/0003-acmm-autonomy-levels.md +++ b/docs/content/hive/adr/0003-acmm-autonomy-levels.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0003-acmm-autonomy-levels.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0003-acmm-autonomy-levels.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0003: ACMM autonomy levels @@ -10,7 +10,7 @@ Hive needs one operator-facing control for how autonomous agents may be. The reference architecture describes ACMM as the human-selected dial that maps to per-agent modes, and the policy matrix defines which agents may only advise, file issues, open hold-gated PRs, or auto-merge at each level -([architecture §6](/docs/hive/architecture#6-acmm-controlling-agent-autonomy), +([architecture §6](/docs/hive/architecture#6-acmm--controlling-agent-autonomy), [ACMM policy matrix](/docs/hive/acmm-policy-matrix)). The same modes feed the layered guardrails in [architecture §5](/docs/hive/architecture#5-layered-guardrails-defense-in-depth). diff --git a/docs/content/hive/adr/0004-beads-work-ledger.md b/docs/content/hive/adr/0004-beads-work-ledger.md index 4dfc3b0..d459066 100644 --- a/docs/content/hive/adr/0004-beads-work-ledger.md +++ b/docs/content/hive/adr/0004-beads-work-ledger.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0004-beads-work-ledger.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0004-beads-work-ledger.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0004: Beads work ledger @@ -9,7 +9,7 @@ Status: Accepted (retroactive) Hive agents need durable coordination state that is separate from GitHub's issue queue. The reference architecture describes beads as a git-backed JSON ledger per agent, with typed work items, priorities, dependencies, metadata, and -actor-scoped ready queues ([architecture §7](/docs/hive/architecture#7-beads-the-work-ledger)). +actor-scoped ready queues ([architecture §7](/docs/hive/architecture#7-beads--the-work-ledger)). The deterministic pipeline can turn GitHub work into internal artifacts before agents act ([architecture §4](/docs/hive/architecture#4-the-deterministic-pipeline)). diff --git a/docs/content/hive/adr/0005-forge-abstraction.md b/docs/content/hive/adr/0005-forge-abstraction.md index 5229b87..351632a 100644 --- a/docs/content/hive/adr/0005-forge-abstraction.md +++ b/docs/content/hive/adr/0005-forge-abstraction.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0005-forge-abstraction.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0005-forge-abstraction.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0005: Forge-neutral source control interface @@ -11,7 +11,7 @@ needs agents and schedulers to reason about work without baking GitHub names into every interface. The forge package defines this boundary explicitly: it is "not hardcoded to GitHub" and exposes neutral repositories, issues, and change requests while concrete adapters handle GitHub, GitLab, and Gitea/Forgejo -details ([forge package](https://github.com/hivecommons/hive/blob/v4/src/pkg/forge/forge.go)). +details ([forge package](https://github.com/hivecommons/hive/blob/v5/src/pkg/forge/forge.go)). ## Decision diff --git a/docs/content/hive/adr/0006-planning-intelligence.md b/docs/content/hive/adr/0006-planning-intelligence.md index 27a4fee..bd8b795 100644 --- a/docs/content/hive/adr/0006-planning-intelligence.md +++ b/docs/content/hive/adr/0006-planning-intelligence.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0006-planning-intelligence.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0006-planning-intelligence.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0006: Planning intelligence with human review @@ -11,11 +11,11 @@ to turn an epic into ordered, claimable work without letting an agent immediatel execute an unreviewed decomposition. The planning docs describe the flow: architect decomposition, a draft plan hidden from `Ready()`, a plan-review gate, and bounded stall replanning -([planning intelligence](https://github.com/hivecommons/hive/blob/v4/src/docs/planning-intelligence.md)). The package code records +([planning intelligence](https://github.com/hivecommons/hive/blob/v5/src/docs/planning-intelligence.md)). The package code records the same metadata conventions and review transitions -([decompose](https://github.com/hivecommons/hive/blob/v4/src/pkg/planning/decompose.go), -[plan review](https://github.com/hivecommons/hive/blob/v4/src/pkg/planning/plan_review.go), -[stall replan](https://github.com/hivecommons/hive/blob/v4/src/pkg/planning/replan.go)). +([decompose](https://github.com/hivecommons/hive/blob/v5/src/pkg/planning/decompose.go), +[plan review](https://github.com/hivecommons/hive/blob/v5/src/pkg/planning/plan_review.go), +[stall replan](https://github.com/hivecommons/hive/blob/v5/src/pkg/planning/replan.go)). ## Decision diff --git a/docs/content/hive/adr/0007-token-mint.md b/docs/content/hive/adr/0007-token-mint.md index 05476d2..0e5538e 100644 --- a/docs/content/hive/adr/0007-token-mint.md +++ b/docs/content/hive/adr/0007-token-mint.md @@ -1,11 +1,11 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0007-token-mint.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0007-token-mint.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0007: Mint short-lived scoped agent credentials Status: Accepted (retroactive) For the operator-facing config reference (`mint:` block, key lifecycle, and -the trust boundary stated plainly), see [Token mint](https://github.com/hivecommons/hive/blob/v4/src/docs/token-mint.md). +the trust boundary stated plainly), see [Token mint](https://github.com/hivecommons/hive/blob/v5/src/docs/token-mint.md). ## Context @@ -21,12 +21,12 @@ without distributing shared long-lived credentials. ## Decision Introduce the mint as an opt-in short-lived credential issuer -([mint package](https://github.com/hivecommons/hive/blob/v4/src/pkg/mint/mint.go)). It signs scoped JWTs with bounded TTLs, +([mint package](https://github.com/hivecommons/hive/blob/v5/src/pkg/mint/mint.go)). It signs scoped JWTs with bounded TTLs, verification that fails closed, and a JWKS endpoint for downstream Workload Identity Federation providers. Agent integration maps the same trust tiers used by agent modes (`advisor`, `newcomer`, `contributor`, `trusted`) to explicit scope strings such as `issues:read`, `contents:write`, and `pulls:merge` -([agent minting](https://github.com/hivecommons/hive/blob/v4/src/pkg/mint/agent.go)). +([agent minting](https://github.com/hivecommons/hive/blob/v5/src/pkg/mint/agent.go)). The mint supplements, rather than replaces, the existing GitHub App token path. When disabled, agent minting is a no-op. When enabled, empty agent identities, diff --git a/docs/content/hive/adr/0008-ioscan-untrusted-input.md b/docs/content/hive/adr/0008-ioscan-untrusted-input.md index a0dff66..d29b43c 100644 --- a/docs/content/hive/adr/0008-ioscan-untrusted-input.md +++ b/docs/content/hive/adr/0008-ioscan-untrusted-input.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0008-ioscan-untrusted-input.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0008-ioscan-untrusted-input.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0008: Redact untrusted kick input with visible markers @@ -11,7 +11,7 @@ may be controlled by an attacker. That text can carry prompt-injection phrases, hidden Unicode, base64-encoded instructions, destructive commands, or secret shapes. The ioscan package provides a pure scanner for input and output text with structured findings and block decisions -([ioscan scanner](https://github.com/hivecommons/hive/blob/v4/src/pkg/ioscan/ioscan.go)). +([ioscan scanner](https://github.com/hivecommons/hive/blob/v5/src/pkg/ioscan/ioscan.go)). ## Decision @@ -19,14 +19,14 @@ Scan untrusted kick-path text before injection and redact blocked content rather than silently dropping the item or passing the raw payload through. `EnforceInput` returns benign text byte-for-byte, but replaces blocked text with a visible marker of the form `[ioscan: content withheld — ...]` that names the fired rules -without echoing the payload ([enforcement](https://github.com/hivecommons/hive/blob/v4/src/pkg/ioscan/enforce.go)). The +without echoing the payload ([enforcement](https://github.com/hivecommons/hive/blob/v5/src/pkg/ioscan/enforce.go)). The scheduler applies this to issue text and labels and writes an audit entry for blocked findings when an audit sink is attached -([scheduler enforcement](https://github.com/hivecommons/hive/blob/v4/src/pkg/scheduler/ioscan_enforce.go)). +([scheduler enforcement](https://github.com/hivecommons/hive/blob/v5/src/pkg/scheduler/ioscan_enforce.go)). The v4 base also includes the fail-safe default-on mode: absent `ioscan:` configuration scans by default, while an explicit `ioscan.enabled: false` -opts out ([config](https://github.com/hivecommons/hive/blob/v4/src/pkg/config/config.go)). +opts out ([config](https://github.com/hivecommons/hive/blob/v5/src/pkg/config/config.go)). Operators may additionally enable `ioscan.canaries: true`. Hive then prepends a random `HIVE-CANARY-...` marker to each kick with instructions that the agent diff --git a/docs/content/hive/adr/0009-trajectory-review.md b/docs/content/hive/adr/0009-trajectory-review.md index df032ee..bdc2d81 100644 --- a/docs/content/hive/adr/0009-trajectory-review.md +++ b/docs/content/hive/adr/0009-trajectory-review.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0009-trajectory-review.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0009-trajectory-review.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0009: Trajectory review lane @@ -11,7 +11,7 @@ steps while the overall sequence moves toward an unauthorized outcome. The trajectory-review docs describe this as the case where only the direction of the transcript reveals sandbox escape, scanner evasion, credential splitting, or a goal adopted from repository content -([trajectory review](https://github.com/hivecommons/hive/blob/v4/src/docs/trajectory-review.md)). +([trajectory review](https://github.com/hivecommons/hive/blob/v5/src/docs/trajectory-review.md)). ## Decision @@ -19,8 +19,8 @@ Run a periodic trajectory-review lane from the governor tick. The lane snapshots running, non-exempt agents with their assigned intent and recent transcript, sends a bounded transcript tail to a second model through the existing OpenAI-compatible/LiteLLM endpoint, and asks for a compact JSON verdict -([reviewer](https://github.com/hivecommons/hive/blob/v4/src/pkg/trajectory/trajectory.go), -[lane](https://github.com/hivecommons/hive/blob/v4/src/pkg/trajectory/lane.go)). On divergence, the configured response is +([reviewer](https://github.com/hivecommons/hive/blob/v5/src/pkg/trajectory/trajectory.go), +[lane](https://github.com/hivecommons/hive/blob/v5/src/pkg/trajectory/lane.go)). On divergence, the configured response is to pause the agent and alert by default, or to alert only. The reviewer prompt instructs the model to judge direction, not single commands, and to answer non-divergent when unsure. diff --git a/docs/content/hive/adr/0010-escalation-circuit-breaker.md b/docs/content/hive/adr/0010-escalation-circuit-breaker.md index a22e21f..f872c95 100644 --- a/docs/content/hive/adr/0010-escalation-circuit-breaker.md +++ b/docs/content/hive/adr/0010-escalation-circuit-breaker.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0010-escalation-circuit-breaker.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0010-escalation-circuit-breaker.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0010: Escalation circuit breaker for CI fix loops @@ -10,7 +10,7 @@ Hive agents can repair their own failing PRs, but an unbounded retry loop can keep re-dispatching blind fixes without surfacing the root CI error. The escalation package records the incident that forced this boundary: a console test split kept `main` red for days while scanner fix PRs missed the one-line -failure in shard logs ([escalation package](https://github.com/hivecommons/hive/blob/v4/src/pkg/escalation/escalation.go)). +failure in shard logs ([escalation package](https://github.com/hivecommons/hive/blob/v5/src/pkg/escalation/escalation.go)). ## Decision @@ -26,6 +26,31 @@ For unchanged red heads, track staleness separately and cap re-engagements at three per current SHA. A branch that moves resets the re-engagement counter; a permanently red, never-moving branch is not nudged forever. +A PR escalating for the SECOND time — after the reviewer lane ([#5480]) already +repaired or de-escalated it once — gets a structured hand-off note instead of +the generic body. The ledger stamps the head SHA the reviewer left on the branch +and when its verdict was reconciled, and keeps both across the reset that +reconciliation performs, so the comment can say what was already tried, that the +attempt count is measured from the reviewer's pass, and that no further +automated pass is coming. One reviewer pass per PR is the whole ladder: without +the note, nothing distinguished that terminal hand-off from a first escalation +except the label set. + +[#5480]: https://github.com/hivecommons/hive/issues/5480 + +The `needs-human` label on the forge, not the ledger, is the authoritative +record that a PR has been escalated. The ledger is a cache of it: a PR that +wears the label reads as escalated even to an empty ledger (so the evidence +comment is never posted twice, whatever happens to `/data`), and a PR whose +confirmed label a human removes is un-parked with a fresh budget. A pass that +cannot conclude CI state (checks running, or the check-run fetch failed — both +surface as `pending`) leaves the ledger untouched; only a conclusive green +clears history. Entries are pruned 24h after their PR stops being enumerated, +not on the first pass that misses it. + +Dependency bots (`renovate[bot]`, `dependabot[bot]`, `mergeraptor[bot]`) are +not agent authors: their red PRs are not fix loops to break. + ## Consequences The fleet stops spending cycles on fix loops that are not converging and gives a diff --git a/docs/content/hive/adr/0011-knowledge-system.md b/docs/content/hive/adr/0011-knowledge-system.md index b8690df..317f3a8 100644 --- a/docs/content/hive/adr/0011-knowledge-system.md +++ b/docs/content/hive/adr/0011-knowledge-system.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0011-knowledge-system.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0011-knowledge-system.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0011: Knowledge system for durable agent context @@ -10,27 +10,27 @@ Hive agents need project memory that outlives a single chat session, but prompt context is scarce and unsafe to fill with every past note. The knowledge design keeps that memory layered by scope and privacy — personal, project, org, and community — and merges only relevant facts when preparing an agent kick -([knowledge design](https://github.com/hivecommons/hive/blob/v4/src/docs/design/knowledge-system.md)). The implementation also +([knowledge design](https://github.com/hivecommons/hive/blob/v5/src/docs/design/knowledge-system.md)). The implementation also needs to work when no external embedding service is available. ## Decision Represent reusable lessons as typed facts with confidence, sources, tags, relationships, usage counts, and layer metadata -([knowledge types](https://github.com/hivecommons/hive/blob/v4/src/pkg/knowledge/types.go)). Store local vault pages and +([knowledge types](https://github.com/hivecommons/hive/blob/v5/src/pkg/knowledge/types.go)). Store local vault pages and remote layer clients behind one `KnowledgeAPI`, and prime agents by formatting a bounded set of selected facts into the kick prompt rather than letting agents -query the store directly ([knowledge API](https://github.com/hivecommons/hive/blob/v4/src/pkg/knowledge/api.go)). +query the store directly ([knowledge API](https://github.com/hivecommons/hive/blob/v5/src/pkg/knowledge/api.go)). Use a deterministic term-frequency embedder as the default semantic signal: text is tokenized, feature-hashed into a 256-dimensional vector, and L2-normalized, with embeddings cached only for the search/reindex pass -([TF embedder](https://github.com/hivecommons/hive/blob/v4/src/pkg/knowledge/embedding.go)). Persist fact relationships in +([TF embedder](https://github.com/hivecommons/hive/blob/v5/src/pkg/knowledge/embedding.go)). Persist fact relationships in a bbolt-backed graph store with SPO/POS/OSP indexes so facts can be traversed by -subject, predicate, or object ([graph store](https://github.com/hivecommons/hive/blob/v4/src/pkg/knowledge/graphstore.go)). +subject, predicate, or object ([graph store](https://github.com/hivecommons/hive/blob/v5/src/pkg/knowledge/graphstore.go)). The retro lane ingests model-generated lessons only after length, secret, and deduplication gates, then stores them as ordinary project facts derived from the -source bead or PR ([retro lessons](https://github.com/hivecommons/hive/blob/v4/src/pkg/knowledge/retro_lesson.go)). +source bead or PR ([retro lessons](https://github.com/hivecommons/hive/blob/v5/src/pkg/knowledge/retro_lesson.go)). ## Consequences diff --git a/docs/content/hive/adr/0012-skill-registry.md b/docs/content/hive/adr/0012-skill-registry.md index 8c7f225..ffe3f2c 100644 --- a/docs/content/hive/adr/0012-skill-registry.md +++ b/docs/content/hive/adr/0012-skill-registry.md @@ -1,47 +1,49 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0012-skill-registry.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0012-skill-registry.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0012: Skill registry and BYO-agent contract Status: Accepted (back-filled) -> **Operator guide:** [Skill registry](https://github.com/hivecommons/hive/blob/v4/src/docs/skills.md) covers the on-disk format +> **Operator guide:** [Skill registry](https://github.com/hivecommons/hive/blob/v5/src/docs/skills.md) covers the on-disk format > and the current wiring status. This ADR records the decision; it is not an > operator guide. ## Context `AGENTS.md` can carry repo-local skill snippets, but inline snippets do not give -Hive a shareable catalog, version metadata, search, or a stable contract for -third-party agents. Hive also needs custom agents to be declared without binding -them to internal launcher structures. +Hive a shareable catalog or version metadata. ## Decision Introduce `skillreg` as a concurrency-safe registry of named, versioned skill -files plus a minimal bring-your-own-agent SDK contract -([skill registry](https://github.com/hivecommons/hive/blob/v4/src/pkg/skillreg/skillreg.go)). A skill is a Markdown body -with optional YAML front matter for name, version, description, and tags. Missing -skill directories, unreadable files, and malformed front matter are skipped with -logs so one bad catalog entry does not prevent Hive from loading. +files ([skill registry](https://github.com/hivecommons/hive/blob/v5/src/pkg/skillreg/skillreg.go)). A skill is a Markdown +body with optional YAML front matter for name, version, description, and tags. +Missing skill directories, unreadable files, and malformed front matter are +skipped with logs so one bad catalog entry does not prevent Hive from loading. -Resolve requested skills by preferring the curated registry over inline +Requested skills are selected by preferring the curated registry over inline `AGENTS.md` snippets, while falling back to repo-local skills the registry does -not know about. Version resolution supports latest, exact versions, wildcard, -caret-major, and greater-than-or-equal constraints, and `InjectionText` renders a -single Markdown block for the kick path. +not know about. `Get` returns the newest loaded version, `List`/`Search` provide +catalog UX, and `InjectionText` renders a single Markdown block for the kick +path. -Define the BYO-agent contract as `AgentSpec`: name, backend, model, operating -mode, and default skills ([agent spec](https://github.com/hivecommons/hive/blob/v4/src/pkg/skillreg/agentspec.go)). YAML -agent specs are strict: malformed YAML, missing name/backend/model, or unknown -modes fail before an agent can launch silently. +The same package defines `AgentSpec`, the minimal BYO-agent contract: +name, backend, model, operating mode, optional launch command, prompt, tools, +and default skills. Agent configs opt into that contract with `agent_spec`, +which points at a YAML file or spec directory. + +## Status notes + +- Implemented in #6004: `agent_spec` is loaded through `skillreg.LoadAgentSpec` + on the pkg/agent launch path, and `hivectl agent specs list|search` exposes + the registry discovery surface. ## Consequences Rationale not recorded beyond the implementation, linked code, and cited design notes. -Skills become portable and discoverable without removing simple repo-local -snippets. Custom agents get a small, stable interface that catalogs and launchers -can share. The trade-off is that registry skills intentionally override inline -snippets, so catalog governance matters; and the current registry helper is a -contract and rendering layer, not a full package manager or deep launcher -integration. +Skills become portable without removing simple repo-local snippets. The +trade-off is that registry skills intentionally override inline snippets, so +catalog governance matters. BYO-agent specs add launcher integration without +turning the registry into a package manager: Hive resolves a local declaration +and applies only the fields in the stable contract. diff --git a/docs/content/hive/adr/0013-cel-triggers.md b/docs/content/hive/adr/0013-cel-triggers.md index ba88bfa..9f4c082 100644 --- a/docs/content/hive/adr/0013-cel-triggers.md +++ b/docs/content/hive/adr/0013-cel-triggers.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0013-cel-triggers.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0013-cel-triggers.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0013: CEL triggers over normalized forge events @@ -15,7 +15,7 @@ operator writes a bad rule. ## Decision Add CEL-based declarative triggers over a forge-neutral `NormalizedEvent` -([CEL trigger engine](https://github.com/hivecommons/hive/blob/v4/src/pkg/celtrigger/celtrigger.go)). Forge adapters map +([CEL trigger engine](https://github.com/hivecommons/hive/blob/v5/src/pkg/celtrigger/celtrigger.go)). Forge adapters map native events onto stable kinds such as `issue.opened`, `pr.labeled`, `pr.ready_for_review`, and `comment.created`; CEL expressions see only the `event` object with normalized fields such as repo, labels, title, author, body, @@ -26,7 +26,7 @@ parse errors, type errors, empty expressions, or non-boolean results reject the engine at config load. Runtime evaluation errors are treated as no match. Matched rules are returned in descending priority, and `MatchAgents` de-duplicates agent names before the governor unions them with existing built-in triggers -([config wiring](https://github.com/hivecommons/hive/blob/v4/src/pkg/celtrigger/wire.go)). +([config wiring](https://github.com/hivecommons/hive/blob/v5/src/pkg/celtrigger/wire.go)). ## Consequences diff --git a/docs/content/hive/adr/0014-hub-spoke.md b/docs/content/hive/adr/0014-hub-spoke.md index 3daebe5..62f411d 100644 --- a/docs/content/hive/adr/0014-hub-spoke.md +++ b/docs/content/hive/adr/0014-hub-spoke.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0014-hub-spoke.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0014-hub-spoke.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0014: Hub/spoke fleet over heartbeat callbacks @@ -19,7 +19,7 @@ Use spoke-initiated heartbeats as the control channel. A spoke posts identity, repos, ACMM level, agents, governor state, contributors, leaderboard, health, version, image, and related status to `/api/heartbeat`; the hub persists a sanitized registry entry and marks the spoke online -([heartbeat payload](https://github.com/hivecommons/hive/blob/v4/src/pkg/hub/heartbeat.go), [hub registry](https://github.com/hivecommons/hive/blob/v4/src/pkg/hub/server.go)). +([heartbeat payload](https://github.com/hivecommons/hive/blob/v5/src/pkg/hub/heartbeat.go), [hub registry](https://github.com/hivecommons/hive/blob/v5/src/pkg/hub/server.go)). The heartbeat response carries callbacks for upgrade, branch switch, GitHub App config, banners, visibility, authorized users, project config, gateway config, and restart requests, letting the hub act even when it cannot open a connection diff --git a/docs/content/hive/adr/0015-csp-style-src-scope.md b/docs/content/hive/adr/0015-csp-style-src-scope.md index 6654956..dd0a874 100644 --- a/docs/content/hive/adr/0015-csp-style-src-scope.md +++ b/docs/content/hive/adr/0015-csp-style-src-scope.md @@ -1,4 +1,4 @@ -> **Synced from Hive.** This page is pulled from [hivecommons/hive@v4](https://github.com/hivecommons/hive/blob/v4/src/docs/adr/0015-csp-style-src-scope.md) during the docs build. Edit the canonical source in the Hive repository. +> **Synced from Hive.** This page is pulled from [hivecommons/hive@v5](https://github.com/hivecommons/hive/blob/v5/src/docs/adr/0015-csp-style-src-scope.md) during the docs build. Edit the canonical source in the Hive repository. # ADR-0015: Scope `style-src` as two directives and accept inline style attributes @@ -7,7 +7,7 @@ Status: Accepted ## Context The dashboard's Content-Security-Policy carried one blanket `style-src 'self' -'unsafe-inline'` ([securityHeaders](https://github.com/hivecommons/hive/blob/v4/src/pkg/dashboard/server.go)). That single +'unsafe-inline'` ([securityHeaders](https://github.com/hivecommons/hive/blob/v5/src/pkg/dashboard/server.go)). That single token covered two different things with two different futures, and hid the fact that only one of them is closable. @@ -48,7 +48,7 @@ protection. Split the single `style-src` into the two directives CSP Level 3 provides, and state a different verdict for each. -``` +```text style-src 'self' 'unsafe-inline' ← CSP2 fallback, unchanged style-src-elem 'self' 'unsafe-inline' ← the 7