MIP-0070 tasks¶
Ordered delivery of MIP-0070 as stacked PRs
(.claude/skills/mip-tasks/SKILL.md, scripts/stack.sh). Branch mip-0070/<k>-<slug>, each based
on the previous; merge bottom-up, scripts/stack.sh restack after each squash-merge. Each row's
delivers cell starts with the repo its PR lands in. Every issue is filed in marola-dev/marola
until that repo exists (§5.7).
| # | slug | delivers | tests (must exist before the PR) | depends on |
|---|---|---|---|---|
| 1 | site-data-only | marola — §5.4 app → site: SiteBuilder.build stops copying site/static, and --site writes data/ only from the --areas file it is given. Dockerfile (both runtime stages) stops copying site/static and site/areas.json. board.schema.json moves under cli/src/main/resources/ (the producer owns it) and ships in the image. site.yml assembles dist/ from site/static plus the boards. Test fixtures copy areas.json and board.json under cli/src/test/resources/site/. This task file and the MIP's Tasks: pointer |
SiteBuilderSpec: "--site writes data only" (no index.html in the output) and "reads the --areas path, not the cwd". BoardSpec validates against the schema from the classpath. §7 step 1: site.yml run on the branch deploys a working map with no site/static in the image (docker run … ls /app/site output in the PR) |
– |
| 2 | knowledge-dir-only | marola — §5.4 corpus → app: nothing walks up to knowledge/ any more. RagOfflineSpec and just ask read MAROLA_KNOWLEDGE_DIR (default knowledge), and ci.yml sets it explicitly. The corpus.version file and scripts/corpus-fetch.sh (default local: copy knowledge/ into .tmp/knowledge) come in so the extraction in task 12 only flips the pin |
RagOfflineSpec green with MAROLA_KNOWLEDGE_DIR=.tmp/knowledge after just corpus-fetch; red when the variable points at an empty dir. corpus-fetch.sh --self-test (local copy, idempotent re-run) |
– |
| 3 | ml-reads-resources | marola — §5.4 app → ml: finetune/build_dataset.py and scripts/benchmark_gate.py take --resources DIR instead of ../core/src/main/resources, ../knowledge and site/fixtures. ci.yml publishes the resources tarball (core/src/main/resources/*.json, benchmark_questions.json, the board fixture) as an artifact per main push. dspy/compile_recommendation_prompt.py writes to --out, not into core/ |
build_dataset.py --self-test builds from a temp resources dir with no core/ next to it. benchmark_gate.py --self-test unchanged. ruff check. The tarball's file list is printed in the PR |
– |
| 4 | umbrella-resolve | marola (devkit code, before extraction) — §5.6: scripts/lib/mip_ref.sh resolves a MIP from ../ inside an umbrella, otherwise via gh api repos/$MAROLA_UMBRELLA/contents/docs/MIPs. uprd.sh, lib/pr_labels.sh and issues.sh use it. Task PRs keep a same-repo Closes #N, and a parent or MIP reference is fully qualified. MAROLA_UMBRELLA defaults to marola-dev/marola |
§7: mip-resolve --self-test (local tree, ../, gh api stub). uprd --self-test gains a same-repo Closes #N case and a qualified parent case. §7 "before step 2": a sub-issue from a scratch second repo added by hand under a marola issue, with a screenshot in the PR |
– |
| 5 | invariants-block | marola — §5.6: the org invariants as one versioned block (agents/invariants.md), pasted into AGENTS.md between markers. scripts/agents-check.sh compares the markers' content against the block, and just quality runs it |
agents-check --self-test: an edited block fails, an unchanged one passes, a missing marker fails with its name. just quality green |
– |
| 6 | issues-per-repo | marola — §5.7: tasks-to-issues reads the repo from a row's delivers prefix and files a MIP parent in the umbrella plus one sub-issue per row in its target repo (falling back to the umbrella while the target doesn't exist). Every issue goes onto Project 1. issue-queue/issue-claim read org:marola-dev label:agent-ready. The Deliverable Project field. The phase list moves from ARCHITECTURE.md §11 to docs/PHASES.md, which issues.sh parses |
tasks-to-issues --self-test files a two-repo table as parent + sub-issues, falls back for a missing repo, and stays idempotent. issues.sh --self-test parses PHASES.md. just docs green after the move |
4 |
| 7 | devkit-repo | marola-devkit (new) — git filter-repo of the Appendix's devkit paths, with history. The flake exposes stack, uprd, uprds, pr, issues, cost-split, cost-fill, mkdocs, agents-check on PATH plus a base devShell and just module. .claude-plugin/marketplace.json with one marola-devkit plugin (generic skills, agents, ${CLAUDE_PLUGIN_ROOT} hooks, skills calling tools by PATH name). labels.yml, ISSUE_TEMPLATE/, the PR template. Tagged v0.1.0 |
nix flake check. claude plugin validate .. git log --follow scripts/stack.sh shows pre-split history. Every script's --self-test passes from a fresh clone with no umbrella |
4, 5, 6 |
| 8 | devkit-workflows | marola-devkit — reusable workflows: scala-ci, python-ci, static-ci, notify-umbrella, labels-sync, agents-check, pr-body, ci-short-circuit. Tagged v0.2.0 |
actionlint. Each one called once from a scratch repo, run links in the PR |
7 |
| 9 | devkit-consume | marola — §7 step 2: this repo takes the devkit as a flake input (v0.2.0), enables marola-devkit@marola-devkit in .claude/settings.json, and ci.yml calls the reusable workflows. The moved scripts, skills, agents, hooks and templates are deleted here. stop-gate.sh/pre-commit call just quality |
§7 step 2: just pr on a throwaway branch fills trailers and the MIP link through the flake's tools; /marola-devkit:mip loads from the plugin; ci.yml green through the reusable workflows. just build && just test && just quality green |
8 |
| 10 | docs-aggregator | marola — §5.5: mkdocs/repos.yml (submodule → mount point), scripts/prepare-docs.sh copying each submodule's README.md + docs/, and docs.yml building on repository_dispatch: submodule-docs-updated, a push to docs/, a daily cron and manual runs (--remote except on PRs). API docs come from a fetched release asset, not sbt doc |
prepare-docs.sh --self-test against a fake two-submodule tree (mounts, the README.md → index.md rewrite, strict links). just docs green with zero submodules (umbrella docs only) |
9 |
| 11 | site-repo | marola-site (new) — filter-repo of the Appendix's site paths. site.yml runs the pinned ghcr.io/marola-dev/marola:<tag> with --site --areas areas.json and takes Pages + CNAME marola.dev. site-data holds coverage/, smoke/, stats/. 404.html sends /docs/* to docs.marola.dev. notify-umbrella. Open site-area issues transferred. Paused #541 and #543 recreated here (Decision 1). Human: the fine-grained PAT, the Pages settings |
§7 step 3: CI log with no sbt; boards from the pinned image; the map live on marola.dev (site_live_check.py green). Fresh-clone gates in nix develop |
1, 9 |
| 12 | docs-host-swap | marola — same day as task 11 (§5.8 step 3): this repo's Pages moves to docs.marola.dev (human: DNS CNAME). marola-site becomes the first submodule. site/, site.yml, site-health.yml and the site scripts are deleted. App CI pushes coverage//smoke/ to marola-site's site-data with the PAT. api-docs.yml publishes Scaladoc as a release asset |
The 404 body for https://marola.dev/docs/1-Using-marola/RUN-LOCALLY/ sends to docs.marola.dev (a JS location.replace, so no Location header: marola-site's scripts/redirect_check.js or a browser load). A push to marola-site/docs/ redeploys the docs with no site deploy (run links). just build && just test && just quality green |
10, 11 |
| 13 | corpus-repo | marola-corpus (new) — filter-repo of knowledge/ and the corpus-doc/eli5 skills; a release workflow publishing the tarball on each tag; notify-umbrella; added as a submodule. In marola: corpus.version pins the first tag, corpus-fetch.sh downloads it, and the image build uses the fetched dir. knowledge/ is deleted |
§7: RagOfflineSpec green from the fetched tarball. Bumping corpus.version changes the image's /app/knowledge (diff in the PR). Fresh-clone gates |
2, 12 |
| 14 | ml-repo | marola-ml (new) — filter-repo of the Appendix's ml paths (dspy/, finetune/, Dockerfile.local, docker-local.yml, marola-sea-publish.yml, benchmark_gate.py, docs/benchmarks/). The gate runs the pinned app image with --benchmark and reads the resources tarball. The compiled prompt reaches the app as a bot PR. pdoc as a release asset. HF_TOKEN moved. Submodule; paths deleted from marola |
build_dataset.py --self-test and benchmark_gate.py --self-test from a fresh clone. One docker-local.yml dispatch against the pinned image (run link). Fresh-clone gates |
3, 13 |
| 15 | app-repo | marola-app (new) — filter-repo of core/ local/ cli/ project/ build.sbt, the oods/ test fixtures on main, Dockerfile, the Scala workflows, .claude/rules/scala.md, jar-verifier, docs/1-*, docs/2-*. Scaladoc and the resources tarball as release assets. STEWARD_GH_TOKEN moved. Open app-area issues transferred. Paused #529, the OODS code stack and the app half of #544 recreated here (Decision 1). Submodule; the umbrella keeps no code |
§7 "each extraction": git log --follow cli/src/main/scala/marola/Main.scala; just build && just test && just quality from a fresh clone; site.yml in marola-site green against an image built here |
12, 13, 14 |
| 16 | oods-repo | marola-oods (new) — the data repo, starting empty: README.md, docs/, notify-umbrella, and an oods-check workflow that runs the pinned app image and passes on an empty tree. The ingest code arrives through the recreated MIP-0056 stack in marola-app, and the backfill (#378) is recreated here after it (Decision 1). Submodule |
Fresh-clone gates in nix develop. oods-check green on the empty tree. A push to its docs/ redeploys docs.marola.dev (run link) |
15 |
| 17 | umbrella-finish | marola — §5.6: the workspace AGENTS.md/CLAUDE.md/README.md (inventory, scope rules, invariants in full, submodule mechanics), the pointer-sync workflow (daily cron + repository_dispatch, one rolling PR), and repomix* re-pointed at the submodules. MIP-0070 flipped to Implemented |
§7 "Done": a fresh git clone --recurse-submodules reaches every repo's AGENTS.md; the umbrella tree has no code directories (git ls-files in the PR); a sync PR opens after a submodule push (link) |
15, 16 |
Decisions¶
- The repo is frozen as of 2026-09-30 (agreed with the team). Only this stack's PRs merge until task 17. Every other open PR is paused and is recreated in its destination repo by the task that creates that repo. It isn't rebased here:
| Paused PR | Destination | Recreated in |
|---|---|---|
| #541, #543 (MIP-0054 site i18n) | marola-site |
task 11 |
| #544 (MIP-0054 board schema 2) | marola-app, then marola-site (producer first) |
task 15, then a site follow-up |
| #529 (scoring fix) | marola-app |
task 15 |
| #372–#377, #379, #380 (MIP-0056 OODS code) | marola-app |
task 15, as a restacked MIP-0056 stack |
| #378 (OODS backfill data) | marola-oods |
after the OODS code lands in marola-app |
| #530 (MIP-0015 docs) | marola-app docs + umbrella docs |
split after task 15 |
| #542, #548 (MIP-0071, MIP-0072 drafts) | umbrella, unchanged paths | resume in place when the freeze lifts |
Task 1 edits SiteBuilder and moves board.schema.json, both of which #544 also touches. That
PR is rebuilt on top of task 1 when it's recreated, never merged across it.
2. Devkit behaviour changes land in the monorepo first (tasks 4–6), reviewed with today's
tooling. The extraction (task 7) is then a move plus packaging, which a reviewer can check by
git log --follow rather than by rereading every script.
3. Tasks 11 and 12 merge the same day. The map changes Pages repo in 11, and the docs take this
repo's freed Pages in 12. Between the two merges marola.dev/docs/ 404s, so they go out back to
back (§5.8 step 3).
4. Tasks 1–5 have no dependencies on one another and can be worked in parallel. The stack
order is for review, and the depends on column is the real graph.
5. Extraction rows run long. A filter-repo move is thousands of lines by count but review-light:
the PR is reviewed on what is added (flake, workflows, pins), and the move itself is checked by
git log --follow. The ≤ ~400-line rule applies to that added part.
6. Human-only steps sit in tasks 11 and 12 (the PAT, Pages settings, DNS). They are named in
the row, so the issue isn't agent-ready until the person doing them is assigned.