diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index 430ccb2..92bb311 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -25,12 +25,10 @@ steps: commands: - apk add --no-cache bash - bash packages/mosaic/framework/tools/quality/scripts/verify-sanitized.sh - # L0 resident-token budget: keep the Constitution + dispatcher small. - - | - for f in CONSTITUTION.md AGENTS.md; do - n=$(wc -l < "packages/mosaic/framework/defaults/$f") - if [ "$n" -gt 120 ]; then echo "L0 budget exceeded: defaults/$f is $n lines (max 120)"; exit 1; fi - done + # Resident line-count ceiling over framework-owned resident files + # (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9. + - bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test + - bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh typecheck: image: *node_image diff --git a/docs/TASKS.md b/docs/TASKS.md index 238b838..b8c4fb0 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -48,6 +48,9 @@ Active workstream is **W1 — Federation v1**. Workers should: ## P5 — Overlay composer + cross-harness (#604) — feat/p5-overlay-composer -- Status: in progress. R7 (compose-contract) + R8 (cross-harness) + R9 (composer test). +- Status: MERGED to main (#605). R7 (compose-contract) + R8 (cross-harness) + R9 (composer test). - `composeContract({harness, mosaicHome})` pure fn + `.local` overlay deltas-by-value; `mosaic compose-contract ` command; AGENTS bare-launch nudge; composer spec (per-tier anchor + Tier-3 byte-equality). Detail: scratchpads/p5-overlay-composer.md. -- Defer to P6: CONTRIBUTING.md + compliance matrix; line-count CI ceiling; aiguide; alpha tag. + +## P6 — Docs, compliance matrix, alpha tag (#606) — feat/p6-docs-compliance-alpha + +- Status: in-repo deliverables done (CONTRIBUTING.md + harness×gate compliance matrix + check-resident-budget.sh + CI wiring + ALPHA-DOD.md). Remaining: alpha tag v0.0.39-alpha (Lead, post-merge). aiguide reconcile merged (#8). Detail: scratchpads/p6-docs-compliance-alpha.md. diff --git a/docs/design/framework-constitution/ALPHA-DOD.md b/docs/design/framework-constitution/ALPHA-DOD.md new file mode 100644 index 0000000..d3fa979 --- /dev/null +++ b/docs/design/framework-constitution/ALPHA-DOD.md @@ -0,0 +1,75 @@ +# Constitution Alpha — Definition-of-Done checklist + release notes + +Drafted for the `v0.0.39-alpha` tag (Lead cuts after P5 #605 → P6 #607 → aiguide #8 merge). +Maps every DoD §8 acceptance criterion to its merged evidence. Legend: +**✅ merged on main** · **⏳ review-ready PR (pending merge)** · **🔲 Lead action**. + +## DoD §8 green-checklist + +| # | Acceptance criterion (DESIGN §8) | Status | Evidence / PR | +| --- | ------------------------------------------------------------------------------------------------------ | ------ | ----------------- | +| 1 | MIT `LICENSE` (root + framework) + `"license":"MIT"` in package.json | ✅ | P0 #570 | +| 2 | Three credential-path sites + hook URL fast-failed (no private paths in `*.sh`/hooks) | ✅ | P0 #570 | +| 3 | `verify-sanitized.sh` (two-class, `*.sh`+`*.md`, self-tested) wired **blocking** in CI | ✅ | P1 #572 | +| 4 | Operator data purged from the full set (guides / tools / init-generator) | ✅ | P2 #572 | +| 5 | `rails/`→`tools/` in **both** template families | ✅ | P2 #572 | +| 6 | `jarvis-loop.json` deleted; `defaults/SOUL.md` → **neutral sanitized persona** (Q10 decision) | ✅ | P2 #572 | +| 7 | `CONSTITUTION.md` extracted (gates one place, capability-verb, §1.4 split, no false "already loaded") | ✅ | P3 #575 / #577 | +| 8 | `AGENTS.md`/`STANDARDS.md` out of `PRESERVE_PATHS` + seed-semantics → overwrite in **both** installers | ✅ | P4 #590 | +| 9 | Snapshot + v2→v3 migration moving user edits to `.local`/`.bak`; `FRAMEWORK_VERSION=3` | ✅ | P4 #590 / #593 | +| 10 | `mosaic-init --non-interactive` fail-closed persona | ✅ | P4 #590 | +| 11 | **5-fixture migration matrix** green against **both** installers asserting **injected bytes** | ✅ | P4 #590 / #593 | +| 12 | `compose-contract` built + composer unit test (per-tier anchor + Tier-3 byte-equality) | ⏳ | P5 #605 | +| 13 | Resident line-count ceiling enforced (framework-owned resident files) | ⏳ | P6 #607 | +| 14 | `CONTRIBUTING.md` + harness×gate compliance matrix | ⏳ | P6 #607 | +| 15 | `aiguide` reconciled with the Constitution | ⏳ | aiguide #8 | +| 16 | Each phase PR CI-green; alpha tag pushed + Gitea release published | 🔲 | Lead (post-merge) | + +**Note on #6:** the DoD's literal "delete `defaults/SOUL.md`" was superseded by the resolved +**Q10** decision — ship a _neutral, operator-agnostic_ example persona instead of deleting it. Main +carries the sanitized 2.6 KB neutral SOUL.md ("Mosaic agent", no operator identity); the sanitization +gate confirms it is PII-clean. Criterion met in spirit (no operator persona leaks) via the better option. + +**Gate to flip 12–14 → ✅:** merge P5 #605 → P6 #607 (rebase auto-drops the dup format fix +`adc7df2`/`9f6da92`) → aiguide #8, with `ci.yml` terminal-green on the merged head. + +--- + +## Release notes — `v0.0.39-alpha` (Mosaic Framework Constitution, alpha) + +### Mosaic Framework Constitution — Alpha + +This release makes the Mosaic framework a **safe-to-open-source, fork-and-customize agent +operating layer**. It separates the non-negotiable law from operator identity, makes +customization survive upgrades, and wires the guarantees into CI. + +**Highlights** + +- **Constitution (L0).** The hard gates now live in one place — `CONSTITUTION.md` — authored in + capability verbs, with a thin `AGENTS.md` dispatcher that references the law instead of restating + it. Governance model in `constitution/LAYER-MODEL.md`. +- **Public & sanitized.** MIT-licensed; all operator identity, private paths, and credential sites + removed from shipped files. A self-tested `verify-sanitized.sh` gate (two rule classes) runs + **blocking** in CI so re-contamination can't merge. +- **Upgrade-safe customization.** Framework-owned files overwrite cleanly on upgrade while + `SOUL.md`/`USER.md`/`*.local.md`/`credentials` are preserved. The v2→v3 migration snapshots first + and moves any user-edited `AGENTS.md`/`STANDARDS.md` to `.pre-constitution.bak`/`.local.md` — + never silently lost. Verified by a 5-fixture matrix across **both** installers. +- **Operator overlays.** `mosaic compose-contract ` merges your `*.local.md` deltas into + the contract per harness, so customization reaches the model as one pre-merged blob. +- **Cross-harness.** Single L0 source referenced (never restated) by Claude / Codex / OpenCode / Pi; + tiered injection with a byte-equal Tier-3 fallback read. +- **Guardrails in CI.** Resident line-count ceiling over framework-owned resident files; composer + unit test; sanitization gate — all blocking. +- **Docs.** `CONTRIBUTING.md` with the layer model, dual-installer parity rule, and a harness×gate + **compliance matrix** (the Codex/OpenCode/Pi hook-parity gap is tracked for v2). + +**Known limitations (accepted, documented in `CONTRIBUTING.md` §9)** + +- Bare launches that bypass `mosaic` get base contracts only (no `*.local` overlays) and are not + drift-checked by `mosaic doctor` — mitigated by the unconditional Tier-3 self-load + a nudge. +- Codex/OpenCode/Pi mechanical hook parity, `policy/*.md` composition, and live-launch cross-harness + verification are **v2**. + +**Phase lineage:** P0 #570 · P1+P2 #572 · P3 #575/#577 · P4 #590/#593 · P5 #605 · P6 #607 · +aiguide #8 (umbrella #542). diff --git a/docs/scratchpads/p6-docs-compliance-alpha.md b/docs/scratchpads/p6-docs-compliance-alpha.md new file mode 100644 index 0000000..fa8aad2 --- /dev/null +++ b/docs/scratchpads/p6-docs-compliance-alpha.md @@ -0,0 +1,29 @@ +# P6 — Docs, compliance matrix, alpha tag (constitution capstone) + +- **Issue:** #606 · **Branch:** `feat/p6-docs-compliance-alpha` · **Lineage:** #542 +- **Requirements:** R9 (resident line-count ceiling) + R10 (CONTRIBUTING + compliance matrix + aiguide) + alpha tag + +## Delivered (in-repo) + +- `framework/CONTRIBUTING.md` — layer model, operator-hygiene/PII prohibition, dedup rule, resident + budget, **dual-installer parity rule**, adding-a-harness, re-contamination rule, **harness×gate + compliance matrix** (hook-parity gap marked ⚠️ tracked-v2), known-limitations (§9 residuals), PR checklist. +- `framework/tools/quality/scripts/check-resident-budget.sh` — line-count ceiling over framework-owned + resident files (CONSTITUTION + AGENTS + each runtime/\*/RUNTIME.md); `--self-test`; replaces the crude + inline ci.yml loop. Wired blocking in `.woodpecker/ci.yml`. +- Composer unit test (R9) already runs via `pnpm test`; `verify-sanitized.sh` (P1) already wired. + +## Verification + +- Sanitization gate green (CONTRIBUTING is operator-neutral). Resident-budget self-test + real run green. +- prettier clean. Current resident counts: CONSTITUTION 96, AGENTS 83, RUNTIME max 75 — all < ceiling. + +## Remaining + +- [ ] `aiguide` reconcile (separate repo `~/src/aiguide` / mosaicstack/aiguide) — consistency pass vs Constitution. +- [ ] Alpha tag `mosaic-vX.Y.Z-alpha` — propose version; Lead cuts after full DoD §8 green + all phases merged. + +## Notes + +- Alpha DoD (DESIGN §8): all phases P0–P6 merged + CI green. P5 (#605) pending merge after 0.0.38 publish. +- Hook parity (codex/opencode/pi) = tracked v2 gap, documented in the matrix, not closed here. diff --git a/packages/mosaic/framework/CONTRIBUTING.md b/packages/mosaic/framework/CONTRIBUTING.md new file mode 100644 index 0000000..505ad4a --- /dev/null +++ b/packages/mosaic/framework/CONTRIBUTING.md @@ -0,0 +1,185 @@ +# Contributing to the Mosaic Framework + +The Mosaic framework is the open-source agent-operating layer that deploys to +`~/.config/mosaic/`. It is designed to be **forked and customized** — but the +shared core must stay operator-neutral, deduplicated, and upgrade-safe. This +guide is the contract for changing framework-owned files. + +> Governance model and layer rationale: `constitution/LAYER-MODEL.md` (source-only). +> Requirements & phase history: `docs/design/framework-constitution/`. + +--- + +## 1. The layer model (where does my change go?) + +| Layer | What | Owner | On upgrade | File(s) | +| ------ | ------------------------------------------------------------- | ---------------- | --------------------------------------- | -------------------------------------------- | +| **L0** | Constitution — the non-negotiable law (hard gates) | Framework | **Overwritten** | `CONSTITUTION.md` | +| **L1** | Standards & guides — how to do the work well | Framework | Overwritten; user delta → `*.local.md` | `STANDARDS.md`, `guides/*` | +| **L2** | Persona (SOUL) — agent name, tone, role | User (init) | **Never overwritten** | `SOUL.md` (+ optional `SOUL.local.md`) | +| **L3** | Operator (USER) — human identity, prefs, policy | User (init) | **Never overwritten** | `USER.md` (+ optional `USER.local.md`) | +| **L4** | Project / runtime mechanism — per-repo deltas; harness wiring | Repo / framework | Project user-owned; runtime overwritten | `/AGENTS.md`, `runtime//RUNTIME.md` | + +**The one sentence a user can rely on:** edit `SOUL.md` / `USER.md` and the +`.local.md` overlays — they survive every upgrade. To change framework behavior, +add a `.local.md` overlay; never edit a framework-owned file in place. + +--- + +## 2. Operator hygiene (PII / secrets prohibition) — **blocking** + +Framework-owned files ship publicly. They **must not** contain: + +- Operator or personal identity (names, handles, pronouns, accessibility notes). +- Private `$HOME` paths, private hostnames, or domains. +- Secrets, tokens, or credentials (use `~/.config/mosaic/credentials.json`; the + hook URL soft-degrades via `${OPENBRAIN_URL}`). + +This is enforced by `tools/quality/scripts/verify-sanitized.sh`, wired **blocking** +in CI (`.woodpecker/ci.yml`). It runs two rule classes: structural (private-`$HOME` +defaults, dead paths, unrendered tokens) and a labeled current-contaminant denylist. +Run it locally before pushing: + +```bash +bash packages/mosaic/framework/tools/quality/scripts/verify-sanitized.sh +``` + +Operator-specific behavior belongs in **your** `SOUL.md`/`USER.md`/`*.local.md`, +never in the shared core. (The "framework-PR firewall" in `CONSTITUTION.md` §4 +states this as law for agents opening framework PRs.) + +--- + +## 3. Dedup rule — one source, everyone references it + +Hard gates live in **`CONSTITUTION.md` (L0) only**. `AGENTS.md`, `STANDARDS.md`, +and every `runtime//RUNTIME.md` **reference** the law — they never restate it. +Restating a gate is a defect: it creates two sources that drift. If you find a +gate duplicated outside L0, delete the copy and point to L0. + +`AGENTS.md` is a thin dispatcher (load order + guide router + the tier-aware +self-load). Keep it that way; new procedure goes in `guides/*` (on-demand), not +in the resident core. + +--- + +## 4. Resident line-count ceiling — **blocking** + +The framework-owned files injected by value (`CONSTITUTION.md`, `AGENTS.md`, each +`runtime//RUNTIME.md`) are budgeted by **line count** — never by word count +(a word cap forces paraphrasing the law, the exact drift vector we removed). + +```bash +bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh +``` + +Wired blocking in CI. Gate **wording** stays intact; if a file legitimately needs +more lines, raise its ceiling in the script deliberately (in the same PR, with +rationale). The per-harness _total_ resident prompt (which also sums the user's +`SOUL.md`/`USER.md`) is a `mosaic doctor` runtime advisory — CI cannot see user +files, so it is out of CI scope by design (DESIGN §7). + +--- + +## 5. Dual-installer parity rule + +Two installers seed and migrate `~/.config/mosaic/`: + +- **`framework/install.sh`** (bash) — the canonical installer. +- **`packages/mosaic/src/config/file-adapter.ts`** (TS) — the wizard path. + +**Any change to seed lists, overwrite/preserve semantics, or migration MUST land +in BOTH**, validated by the **shared fixture suite**: + +- `framework/tools/quality/scripts/test-install-migration.sh` (bash matrix) +- `packages/mosaic/src/config/file-adapter.test.ts` (vitest) + +Both assert the same behavior: framework-owned files overwrite (backup-once to +`*.pre-constitution.bak`); user-seeded files seed-if-absent; `SOUL.md`/`USER.md`/ +`*.local.md`/`credentials` are preserved. A change in one installer without the +other (and its fixtures) is incomplete. + +--- + +## 6. Adding a harness adapter + +A harness (runtime) is wired by: + +1. `runtime//RUNTIME.md` — **mechanism only** (subagent syntax, hook/MCP wiring, + injection method). No restated gates (see §3). +2. Launcher emission in `src/commands/launch.ts` — how the composed contract reaches + the harness (system-prompt append vs. instructions file). Add the harness to the + `RuntimeName` union and the runtime-path map. +3. `mosaic compose-contract ` works automatically once the runtime path + exists (it composes base + `*.local.md` overlays for that harness). + +Then add a row to the compliance matrix (§8) and mark which gates are mechanical +vs. resident-only for the new harness. + +--- + +## 7. Re-contamination rule + +A green sanitization gate is not permanent. Before every PR: + +- Do not reintroduce operator identity, private paths, or secrets (§2). +- Do not copy a gate out of L0 (§3). +- Do not add an unrendered template token or a dead path to a shipped file. + +If `verify-sanitized.sh` goes red, that diff **is** your worklist — fix it, don't +suppress it. + +--- + +## 8. Harness × gate compliance matrix + +How each gate is enforced per harness. **Mechanical** = a hook/CI check the agent +cannot bypass. **Resident** = injected contract prose (strong, but not a hard stop). +**CI** = repo-side, harness-independent. + +| Gate / mechanism | Claude | Codex | OpenCode | Pi | +| --------------------------------------------- | ----------- | ---------------- | ---------------- | ---------------- | +| Contract injection (resident-by-value) | append SP | instructions | `AGENTS.md` | append SP | +| Operator overlays (`*.local`, composed) | ✅ | ✅ | ✅ | ✅ | +| Bare-launch self-load (Tier-3, read L0) | ✅ | ✅ | ✅ | ✅ | +| Sanitization (no PII) — `verify-sanitized` | CI ✅ | CI ✅ | CI ✅ | CI ✅ | +| Resident budget ceiling | CI ✅ | CI ✅ | CI ✅ | CI ✅ | +| Migration parity (5-fixture, both installers) | CI ✅ | CI ✅ | CI ✅ | CI ✅ | +| `no-memory-write` (PreToolUse hook) | **mech ✅** | resident-only ⚠️ | resident-only ⚠️ | resident-only ⚠️ | +| QA / typecheck (PostToolUse hooks) | **mech ✅** | resident-only ⚠️ | resident-only ⚠️ | resident-only ⚠️ | +| Native heartbeat (fleet `ps` model/status) | sidecar | sidecar | sidecar | **native ✅** | + +⚠️ **Hook-parity gap (tracked, v2):** the mechanical PreToolUse/PostToolUse hooks +exist for Claude Code only. On Codex/OpenCode/Pi those gates are currently enforced +by the resident contract + CI, not by a per-tool hook. Closing hook parity is a +**v2** item, not part of this alpha. + +--- + +## 9. Known limitations (accepted residual risks) + +These are accepted with rationale (DESIGN §9); they are documented, not bugs: + +- **Bare-launch overlays are base-only.** A harness started without `mosaic` never + ran the composer, so `*.local.md` overlays are not applied. Mitigated by the + unconditional Tier-3 self-load + the `mosaic doctor` nudge in `AGENTS.md`; not + eliminated. Relaunch via `mosaic ` to pick up overlays. +- **Bare-launch drift is undetected by `mosaic doctor`** (the launcher never ran). +- **Codex/OpenCode/Pi hook parity** is a tracked v2 gap (§8). +- **Live-launch cross-harness verification** is v2; the alpha verifies the composer + by unit test (per-tier anchor + Tier-3 byte-equality), not a live launch. + +**Deferred to v2 (explicit):** `constitution/` deploy directory; capability JSON +adapters; 3-way merge; `policy/*.md` composition; per-layer version stamps as a +migration driver. + +--- + +## 10. PR checklist + +- [ ] No operator identity / private paths / secrets (`verify-sanitized.sh` green). +- [ ] No gate restated outside `CONSTITUTION.md` (§3). +- [ ] Resident budget green (`check-resident-budget.sh`). +- [ ] Seed/migration changes landed in **both** installers + shared fixtures (§5). +- [ ] New harness → compliance-matrix row updated (§8). +- [ ] `prettier --check` + `pnpm lint` + `pnpm typecheck` + `pnpm test` green. diff --git a/packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh b/packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh new file mode 100755 index 0000000..baa5ef1 --- /dev/null +++ b/packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# check-resident-budget.sh — resident line-count ceiling (R9 / DESIGN §7). +# +# Budgets the *container* (line count) of the framework-owned files that are +# injected into every agent's context by value — the Constitution (L0), the +# AGENTS dispatcher, and each runtime RUNTIME.md slice. Gate *wording* is never +# capped (a word cap forces paraphrasing law — the exact drift vector P3 killed); +# only the file's line count is bounded, so prose creep is caught in review. +# +# This is the CI-enforceable half of the budget. The per-harness *total* resident +# prompt (which also includes user-generated SOUL.md/USER.md and the per-tier +# slice) is summed by `mosaic doctor` as a runtime advisory — CI cannot see user +# files, so it is deliberately out of scope here (DESIGN §7). +# +# Usage: check-resident-budget.sh [--self-test] +# Exit: 0 = all within budget · 1 = a file exceeds its ceiling · 2 = self-test failed +set -uo pipefail + +FW="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" # packages/mosaic/framework + +# Per-file ceilings (lines). Headroom above current counts; tighten as files settle. +# Format: ":" +CEILINGS=( + "defaults/CONSTITUTION.md:120" + "defaults/AGENTS.md:120" + "runtime/claude/RUNTIME.md:90" + "runtime/codex/RUNTIME.md:90" + "runtime/opencode/RUNTIME.md:90" + "runtime/pi/RUNTIME.md:90" +) + +# check_file → echoes ""; returns 0 if n<=max, 1 otherwise. +check_file() { + local path="$1" max="$2" n + n=$(wc -l <"$path" 2>/dev/null || echo 0) + n=$((n + 0)) + echo "$n" + [ "$n" -le "$max" ] +} + +run_budget() { + local fail=0 rel max abs n + printf '%-32s %8s %8s %s\n' "FILE" "LINES" "CEILING" "STATUS" + for entry in "${CEILINGS[@]}"; do + rel="${entry%%:*}" + max="${entry##*:}" + abs="$FW/$rel" + if [ ! -f "$abs" ]; then + printf '%-32s %8s %8s %s\n' "$rel" "-" "$max" "MISSING" + fail=1 + continue + fi + n=$(check_file "$abs" "$max") + if [ "$n" -le "$max" ]; then + printf '%-32s %8s %8s %s\n' "$rel" "$n" "$max" "ok" + else + printf '%-32s %8s %8s %s\n' "$rel" "$n" "$max" "OVER BUDGET" + fail=1 + fi + done + return "$fail" +} + +self_test() { + local tmp rc + tmp=$(mktemp) + # 3 lines, ceiling 5 → within budget (rc 0) + printf 'a\nb\nc\n' >"$tmp" + check_file "$tmp" 5 >/dev/null + rc=$? + if [ "$rc" -ne 0 ]; then echo "self-test FAIL: under-budget file flagged"; rm -f "$tmp"; return 2; fi + # 6 lines, ceiling 5 → over budget (rc 1) + printf 'a\nb\nc\nd\ne\nf\n' >"$tmp" + check_file "$tmp" 5 >/dev/null + rc=$? + if [ "$rc" -ne 1 ]; then echo "self-test FAIL: over-budget file not flagged"; rm -f "$tmp"; return 2; fi + rm -f "$tmp" + echo "self-test OK" + return 0 +} + +if [ "${1:-}" = "--self-test" ]; then + self_test + exit $? +fi + +if run_budget; then + echo "Resident budget: all framework-owned resident files within ceiling." + exit 0 +else + echo "Resident budget EXCEEDED — trim prose or raise the ceiling deliberately (see DESIGN §7)." >&2 + exit 1 +fi