Index
# Release checklists What to look at before a version is committed or shipped, one box per item -- or one per item and per member when a team tests it together -- saved as it is checked. A checklist may follow a `` board: its items then name the tickets they verify, a ticket all of whose items are checked is labelled verified on the board, and can be moved to done from the checklist, which comments in its discussion what was checked. | | | |---|---| | `MMO\admin\helpers\Checklist` | a checklist: its items, its checks, its progress, its tickets' progress | | `MMO\admin\helpers\ChecklistBuilder` | builds the items from a recipe: `git status` of repositories, the board's tickets, items written by hand | | `MMO\admin\helpers\KanbanBoard` | a board's file: tickets, discussions, labels, moves, and its lock | | `MMO\admin\helpers\BoardFileRepository` | a board's file as mmo.client's kanban entities (mmo.git's `BoardSync` with GitHub) | | `MMO\admin\controllers\ChecklistController` | the checklists' JSON API | | `MMO\admin\controllers\BoardController` | the boards' JSON API: a board, its discussions, its tickets' AI prompts | | `{MMO\admin}/templates/checklists.phtml` | the page of the checklists: one (`?name=`), the default one, or every one (`?list=1`), a selector between them | | `{MMO\admin}/templates/board.phtml` | the page of a board (`?board=`, `?ticket=12` opens a ticket) | | `{MMO\admin}/pages/checklists`, `pages/boards` | the two templates as pages, in English | The `mmo-release-bugs` agent skill (`.claude/skills/`, also a prompt of the MCP server) is the whole method around it: a bug report becomes tickets with a fix prompt each, agents fix them and report in the tickets, the checklist proves them. ## Adding them to an application ```jsonc // configs/config.json "MMO\\core\\Application": { "routes": { "MMO\\admin\\controllers\\ChecklistController": {}, "MMO\\admin\\controllers\\BoardController": {}, "MMO\\client\\controllers\\PageController": {} // after them: its `**` route catches everything } }, "MMO\\admin\\controllers\\ChecklistController": { "folder": "{root}/checklists", // one JSON file per checklist "boards": "{root}/boards", // the boards a checklist may follow: {boards}/{board}.json; null for none "scope": "admin", // mmo.oauth: who may read and check; null on a local tool "anonymous": false, // true: the routes answer whoever calls, logged in or not "checklists": { // the checklists the application declares (see below) "general": {"title": "Release checklist", "recipe": {"itemsFile": "{root}/src/checklists/general.json"}} }, "default": "general", // the one the page opens when it is given none "verifiedLabel": {"id": "verified", "label": "verified", "color": "green", "icon": "check"}, "doneComment": "**Release checklist**: verified, moved to **done**." }, "MMO\\admin\\controllers\\BoardController": { "folder": "{root}/boards", // one JSON file per board; null: no boards (every route 404) "scope": "admin", "anonymous": false }, "MMO\\client\\Theme": {"pages": ["{root}/src/pages", "{MMO\\admin}/pages"]} ``` Rather than mmo.admin's pages (in English, a menu group of their own), a page of the application imports the templates, in its words (every key optional; `texts` lists each template's words at its top): ```php import(\MMO\filename('{MMO\admin}/templates/checklists.phtml'), ['locale' => 'fr-FR', 'texts' => ['title' => 'Checklists', ...]], false) ?> import(\MMO\filename('{MMO\admin}/templates/board.phtml'), ['board' => 'acme', 'checklists' => \MMO\url('admin/checklists')], false) ?> ``` The web server writes the files: the folders must be writable by it. Without `scope`, whoever reaches the routes writes the checklists and the boards: keep that to localhost, as `anonymous` (the agents' `curl` on a development machine, `configs/dev.json`). my.oviepro has both: its general checklist in production (`{db}/checklists`, `sudo`), the board and the review checklists in dev only. ### Declared checklists A checklist of `checklists` is made the first time it is asked for, and kept up to its declaration (`title`, `description`, `members`, `types`, `groups`, `board`, `boardUrl`, `recipe`). Its items are built from its recipe when it is made, and again whenever what they come from changes -- the recipe, or its `itemsFile` (a new version of the application asking other checks) -- for a recipe of `items` and `itemsFile` alone; a recipe reading repositories or a board is built by `build.json` only. What was checked stays checked while its item is there. Such a checklist is the general one of an application: what is tested before each version, round after round (`reset.json`). ## The file `{folder}/{name}.json`: ```jsonc { "version": 4, "updatedAt": "2026-10-05T14:17:00+02:00", "title": "my.oviepro 4.1: review before commit", "description": "...", "board": "my.oviepro-deploy", // optional: the board of the tickets "boardUrl": "boards/oviepro-deploy", // optional: its page, where `?ticket=12` opens a ticket "members": [], // optional: one box per member "recipe": {...}, // optional: how `build.json` makes the items "types": {"pdf": {"label": "PDF", "icon": "file-pdf"}}, // optional: what each `kind` is called, and its icon "groups": {"General": {"icon": "globe"}}, // optional: a group's icon "items": [{"id": "3d332c914651", "group": "mmo.core", "section": "src/entities", "kind": "code", "title": "src/entities/Entity.php (modified)", "detail": "Review the diff: `git -C ... diff -- ...`", "url": null, "note": "", "tickets": ["80"]}], "checks": {"3d332c914651": {"*": {"at": "2026-10-05T13:34:55+02:00", "by": "marc"}}}, "release": {"name": "4.1", "startedAt": "...", "startedBy": "marc"}, // the round being checked "history": [{"name": "4.0", "startedAt": "...", "startedBy": "marc", "closedAt": "...", "closedBy": "marc", "done": 700, "total": 760}] } ``` - An item's `id` is `sha1(group|section|title)`, 12 characters: rebuilt items keep their checks, a reworded item starts again unchecked. - `group` is what the page folds (a repository, the pages, "before committing"), `section` a heading in it (a folder, a page), `kind` what it is (`code`, `page`, `global`, a test's type), `url` where to look, `detail` how (`` `code` `` shows as code, nothing else is markup). - `checks[item][member]`, `*` without members. An item is done once every box is checked. - `note` is a word on the item's section (a page's), shown by its heading; a section whose items share an `url` links it. - The first check starts the round (`release`); `reset.json` closes it -- every box unchecked, a line of `history` saying how far it got -- and names the next. ## The recipe ```jsonc { "repositories": [ {"label": "acme", "path": "~/dev/src/acme", "together": ["modules/old/"], "exclude": ["libs/"], "pages": true} ], "treated": "/\\*\\*Claude — (résultat|result)\\*\\*/u", "site": "http://localhost/acme/", "pages": "acme — pages", "checks": {"12": {"page": "gestion/accidents", "check": "sorted by date, newest first", "url": "gestion/accidents"}}, "items": [{"group": "before committing", "section": "checks", "title": "`php tools/mmo.php verify` passes"}], "itemsFile": "src/pages/admin/release-checklist.items.json", "texts": {"diff": "Relire le diff : `git -C {root} diff -- {path}`"} } ``` 1. **items**, **itemsFile**: written by hand, first. The file holds flat items, or a page by page test: `{members, types, sections: [{label, icon, pages: [{label, path, note, checks: [{label, type}]}]}]}`, whose `members`, `types` and sections' icons the checklist takes too (the section is the group, the page the section, the check's type the kind). 2. **repositories**: one `code` item per file of `git status --porcelain -uall`, under the repository's `label`; its section is its folder (two levels, three under `src/`), a page's folder for a file of `src/pages` unless `"pages": false`. `together` makes one item of a folder's files, `exclude` leaves paths out. `~/` is the home of whoever owns the application's folder (not the web server's). The item names the treated tickets whose discussion cites the file -- by its path, or its last three or two parts, `a/{b,c}.php` written out; a file no ticket cites says it is a transverse change. 3. **pages**: for each treated ticket -- one whose discussion matches `treated`, or that `checks` names -- its functional checks: `checks[id]` (one, or a list), else the last ```` ```check ```` block of its discussion, else of its description, one check per line: ````markdown ```check gestion/accidents | sorted by date, newest first home (mobile 390 px) | home | the drawer closes on Escape ``` ```` `page | what to check`, or `page | url | what to check`; a relative url is relative to `site`. A treated ticket with no check gets one item asking to check its result on screen. 4. **texts**: the words of the generated items, in the application's language: `M`, `D`, `??`, `A`, `R`, `T` (the states), `files`, `diff`, `new`, `deleted`, `orphan`, `pages`, `root`, `page`, `unchecked`, `uncheckedDetail`. `POST api/checklists/{name}/build.json` answers the checklist and `build: {orphans, unmatched}`: the changed files no ticket cites, the treated tickets no file names. ## The API | | | |---|---| | `GET api/checklists.json` | `default`, and every checklist: name, title, description, board, version, release, progress, declared | | `GET api/checklists/{name}.json` | one, with `progress` and `tickets` (each ticket its items name: title, status, total, checked, verified) | | `PUT api/checklists/{name}.json` | `{version, title, description, board, boardUrl, members, recipe, items}`: create (version 0) or change; 409 when saved meanwhile | | `POST api/checklists/{name}/build.json` | rebuild the items from the recipe | | `PUT api/checklists/{name}/items/{item}.json` | `{checked, member}` | | `POST api/checklists/{name}/reset.json` | `{name}`: a new round, every box unchecked, a line of `history` | | `POST api/checklists/{name}/tickets/{ticket}/done.json` | move a verified ticket to the board's first done status, with a comment | | Boards | | |---|---| | `GET api/boards.json` | every board: name, title, version, updatedAt, tickets | | `GET api/boards/{board}.json` | `{board, title, version, updatedAt, data}`, each ticket counting its `comments`; an unknown board is empty, version 0 | | `PUT api/boards/{board}.json` | `{version, data}`: the whole board; 409 with the board as it is when it was saved meanwhile | | `GET api/boards/{board}/prompts.json` | `?agent=claude&status=todo&done=1`: the tickets' AI prompts, and `markdown`, the brief | | `GET`, `POST api/boards/{board}/{ticket}/comments.json` | a ticket's discussion; `{body, parent}` comments it | | `PUT`, `DELETE api/boards/{board}/{ticket}/comments/{comment}.json` | `{body}` changes a comment | ## From PHP ```php use MMO\admin\helpers\{Checklist, ChecklistBuilder, KanbanBoard}; $board = KanbanBoard::Edit(\MMO\filename('{root}/boards/acme.json'), fn(KanbanBoard $b) => $b); Checklist::Edit(\MMO\filename('{root}/checklists/acme-2.0.json'), function (Checklist $list) use ($board) { $builder = new ChecklistBuilder($list->data['recipe'] ?? [], $board); $list->setItems($builder->build()); print_r($builder->orphans()); }, true); ```