Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased

- feat(status, existing-app-change): **existing-app-change mode reports the delivery round, not just the stage; agent-slot and done- noise is dropped in that mode only; a template for targeted manual Studio Pro steps, counted by `status.sh`** ([#301](https://github.com/MendixMau/mxcli-project-toolkit/issues/301), [#298](https://github.com/MendixMau/mxcli-project-toolkit/issues/298)). A field run delivered 11 backlog stories in 4 rounds on a 127-module app while `status.sh` still read "Stage 1 — Analysis", WATCH said "agent slots unfilled: 35 in 6 file(s)" and "done- stalled", and NEXT pointed at those. A `## Rounds` table in `PROJECT.md` (`| Round | Title | Stories | State |`, last row = round in flight) now gives a `ROUND Round N: <title> — <state> · stories: … · D of N round(s) delivered` line in existing-app-change mode, and that mode drops the "agent slots unfilled" WATCH item (done- signals stay: Stages 5–6 run the build loop unchanged there). The same run needed six microflow edits mxcli could not round-trip (branches that rejoin early): new `templates/manual-steps.md` (document path, why mxcli cannot, depends-on, exact clicks with an anchor, a Check a person runs, one `- [ ] Done` line naming its evidence), and the rule that a step done by patch still goes through `exec.sh --patch`. `status.sh` prints `manual steps pending N of M` when `docs/manual-steps.md` exists. Also, from the review of #303: NEXT no longer sends a project to `final-look` (the Stage 6 whole-app LOOK) while gate-check's derived stage readout is below Stage 6 — it was sent to Stage 1–5 projects and hid gate-check's next-stage hint. `final-look` only, and only on a derived readout, the same rule as obligation-check's NOT YET DUE (#321): module passes owed under a lagging or hand-written stage line stay in NEXT; the obligations-pending line still lists `final-look`. Apart from that NEXT fix, pipeline modes print byte-identical to before (fixture `tests/wave2/test-status-existing-app.sh` on real scaffolds) — jopterhorst
- learn(testing-shape, test-result-audit): **three test-strength rules, each with a denominator: refusals probed on the server, every undeclared 4xx a failure, one clean-database final run** ([#172](https://github.com/MendixMau/mxcli-project-toolkit/issues/172)). A field comparison of two builds against one frozen spec found the toolkit build checked permission refusals only in the client (a hidden menu item), logged unexpected 400/422 responses as information, and reached 20/20 by stitching three runs. `testing-shape.md` §7 gains three report lines (`N of N restricted pairs probed server-side`, `undeclared 4xx/5xx: 0 of M requests — each fails its step`, `one clean-DB run <id>: N of N scenarios incl. timers, not stitched`); `test-result-audit.md` gains check 2e, which carries the reasons and the field evidence and asks for those numbers. Both baseline skills stay inside the 80,000-word baseline budget (79,997): the reasons live in the on-demand audit skill, and two redundant sentences in `testing-shape.md` §4's verdict paragraphs were folded (the empty-set corollary is stated in the Trace row, `harness-architecture.md` and `report-schema.md`). The backing inbox note is trimmed to its unpromoted "Keep" items. — MendixMau
- learn(learned-detection-gaps, module-review): **two UI defects every model gate passes: a lazy-loaded combo box filter hides its preset, and a theme that pins row heights by position lets the next row cover an inserted widget** ([#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299)). Both passed check, mxbuild, lint and the scripted checks on a 127-module production app and were found only in the running app. Two register rows with the first oracle and the pre-build check (pluggable properties that change *when* data loads; grep the theme for fixed `height` on positional row classes before an insert), and two LOOK lines in `module-review.md` §4c with denominators (`N of N filters show their preset on first load`, `N of N widgets inserted into an existing container fully visible`). — jopterhorst
- fix(app-restart, test-stack-up): **a port held by another process before `run --local` is named and reported as "instrument blocked" (exit 4), never killed** ([#262](https://github.com/MendixMau/mxcli-project-toolkit/issues/262)). Studio Pro had started its own runtime on the app/admin ports a restart needed; `mxcli run --local` then failed with "Authentication failed" (the admin API answering was Studio Pro's), the next journey ran against Studio Pro's runtime, and four more runs were NOT RUN. `app-restart.sh` now checks the app port and the admin port (record's `adminPort`, else app + 10) before stopping anything — a holder outside the recorded process tree exits 4 with its name, pid and parent — and again after the stop, where anything still holding a port is by definition not ours. `test-stack-up.sh` runs the same check wherever it would tell you to start `run --local` (`--no-docker`, no Docker daemon, `--check` with a leftover record), on the record's ports, `RUN_LOCAL_APP_PORT`/`RUN_LOCAL_ADMIN_PORT`, or mxcli's defaults 8080/8090. Holder lookup: lsof, then ss, then netstat + tasklist on Windows; unnamed holders still block when nothing of ours is running. Fixtures: `test-app-restart.sh` +2 cases (14 assertions, all fail on the old script), `test-stack-up-local.sh` T7 (4 of 6 fail on the old script). — AlvinnYeo
Expand Down
75 changes: 72 additions & 3 deletions bin/status.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,46 @@ fi
PH_TOTAL="$(mxtk_placeholder_total "$PROJECT_DIR"/.claude/agents/*.md "$PROJECT_DIR/CLAUDE.local.md")"
PH_FILES="$(mxtk_placeholders "$PROJECT_DIR"/.claude/agents/*.md "$PROJECT_DIR/CLAUDE.local.md" | grep -c .)"

# --- existing-app-change: the delivery round, not the stage (#301) ----------------------------
# A change stream on a live app is delivered as rounds of small stories, and the stage line stood
# still at "Stage 1" through four delivered rounds (field, 2026-10-13). The round is the unit of
# status in that mode: PROJECT.md "## Rounds" (skills/existing-app-change.md), one row per round,
# columns found by header name. The LAST row is the round in flight. Nothing here is read, and
# nothing printed, in the other entry modes.
ROUND_LINE=""; EAC=0
if [ "$ENTRY" = "existing-app-change" ]; then
EAC=1
ROUND_LINE="$(awk -F'|' '
function trim(s) { gsub(/^[ \t*`]+|[ \t*`]+$/, "", s); return s }
function cell(k) { return (k in col) ? trim($(col[k])) : "" }
/^## +Rounds *$/ { sec = 1; next }
sec && /^## / { sec = 0 }
!sec || !/^\|/ { next }
!hdr { for (i = 2; i < NF; i++) col[tolower(trim($i))] = i; hdr = 1; next }
/^\| *:?-/ { next }
{ n++; r = cell("round"); t = cell("title"); s = cell("state"); st = cell("stories")
if (tolower(s) ~ /^delivered/) d++ }
END {
if (!n) { print "none recorded — add a row to PROJECT.md \"## Rounds\" when a round starts (skills/existing-app-change.md)"; exit }
printf "Round %s: %s — %s%s · %d of %d round(s) delivered\n", r, (t != "" ? t : "(no title)"), (s != "" ? s : "(no state)"), (st != "" ? " · stories: " st : ""), d, n
}' "$REG")"
fi

# --- targeted manual Studio Pro steps (#298) -------------------------------------------------
# docs/manual-steps.md, from templates/manual-steps.md: one "- [ ] Done" line per step, ticked
# when the step is done (by hand or by patch). Pending = unticked. Fenced blocks are skipped so
# the template's worked example never counts. No file, no output — the screen is unchanged.
MANUAL=""
MS_FILE="$PROJECT_DIR/docs/manual-steps.md"
if [ -f "$MS_FILE" ]; then
MANUAL="$(awk '
/^[ \t]*```/ { fence = !fence; next }
fence { next }
/^[ \t]*[-*] \[ \] *(\*\*)?Done/ { p++; t++ }
/^[ \t]*[-*] \[[xX]\] *(\*\*)?Done/ { t++ }
END { printf "manual steps pending %d of %d", p, t }' "$MS_FILE")"
fi

# --- doctor receipt -------------------------------------------------------------------------
DR="$PROJECT_DIR/.claude/.doctor-receipt"
if [ -f "$DR" ]; then
Expand Down Expand Up @@ -142,6 +182,25 @@ NEED_ATTN="$(printf '%s\n' "$GC" | awk '/^Needs attention/{f=1;next} /^Next up:|
APPROVED_OVER="$(printf '%s\n' "$GC" | grep -m1 '^Approved over a failing gate:' | sed -E 's/^Approved over a failing gate: *//; s/ — .*$//')"
N_ATTN="$(printf '%s\n' "$GC" | grep -oE '[0-9]+ need attention' | grep -oE '^[0-9]+' || echo 0)"
OB_PENDING="$(printf '%s\n' "$GC" | grep -E '^Obligation ' | grep -E ' (PENDING|FAULT) ' | awk '{print $2": "$3}' | tr '\n' ',' | sed 's/,$//; s/,/, /g')"
# NEXT never sends a project to final-look (the Stage 6 whole-app LOOK) before Stage 6: a Stage 1
# project was told "discharge: final-look" and never saw gate-check's "Next up:" hint behind it
# (review of #303). final-look ONLY, and only when the stage line is gate-check's own derived
# readout — the same rule obligation-check.sh applies once it prints NOT YET DUE (#321), at which
# point final-look never reaches OB_PENDING and this is a no-op. Every other obligation keeps its
# place in NEXT whatever the stage line says: a hand-written or lagging line must not hide the
# module passes a project with an opened module owes (review of #326). The FIRST "Stage N" on the
# line is the stage; the screen's "obligations pending" line still lists final-look.
OB_NEXT="$OB_PENDING"
case "$STAGE_LINE" in *"(derived by gate-check"*)
CUR_STAGE="$(printf '%s\n' "$STAGE_LINE" | grep -oE 'Stage [0-9P]' | head -1 | sed 's/^Stage //')"
FL_FROM="$(awk -F'\t' '$1=="final-look"{print $8; exit}' "$TOOLKIT_ROOT/bin/lib/obligations.tsv" 2>/dev/null)"
case "$CUR_STAGE:$FL_FROM" in
P:[0-9]*) _behind=1 ;;
[0-9]:[0-9]*) [ "$CUR_STAGE" -lt "$FL_FROM" ] && _behind=1 || _behind=0 ;;
*) _behind=0 ;;
esac
[ "$_behind" = 1 ] && OB_NEXT="$(printf '%s\n' "$OB_PENDING" | tr ',' '\n' | sed -E 's/^ +//' | grep -v '^final-look:' | paste -s -d, - | sed 's/,/, /g')" ;;
esac
MOD_OPENED="$(printf '%s\n' "$GC" | grep -m1 -E '^Obligation look ' | grep -q 'no module has been opened' && echo 0 || echo '≥1')"
GC_NEXT="$(printf '%s\n' "$GC" | grep -m1 '^Next up:' | sed 's/^Next up: *//')"
TK_UPD="$(printf '%s\n' "$GC" | grep -m1 '^Toolkit updates:' | sed 's/^Toolkit updates: *//; s/\..*$//')"
Expand All @@ -151,6 +210,13 @@ if [ -x "$PROJECT_DIR/bin/coherence-cadence.sh" ]; then
COH="coherence $COH_OUT"
fi

# existing-app-change: a change stream delivered in rounds does not fill the pipeline agent stubs,
# and "agent slots unfilled: 35 in 6 file(s)" sat in WATCH through four delivered rounds (#301).
# In that mode only, that one WATCH item is dropped; the round line above is the progress readout.
# done- stays: Stage 5-6 run the build loop unchanged in this mode (existing-app-change.md), and
# the stub NEXT rule stays because it still fires only at Stage 5-7. Every other mode prints
# exactly as before (tests/wave2/test-status-existing-app.sh, byte-diff).

# --- NEXT: ordered lookup, earliest wins. Owner skill in the right-hand comment. -------------
next_action() {
if [ -n "$APPROVED_OVER" ]; then echo "$APPROVED_OVER is approved in PROJECT.md but its gate FAILS — fix the gate, or re-open the approval with the user (bin/gate-check.sh)"; return; fi # conversion-runbook.md §2
Expand All @@ -166,7 +232,7 @@ next_action() {
if [ -n "$LAST_FAIL" ]; then echo "last gate FAILED for: $LAST_FAIL— fix and re-exec (docs/BUILD-LOG.md, .mpr-snapshots/last-mxbuild-errors.json)"; return; fi # iterative-build-loop.md Gate: BUILD
if [ -n "$DONE_STALL" ]; then echo "$DONE_STALL — done- needs the coverage checklist; run bin/coverage-preflight.sh to see what blocks it (iterative-build-loop.md)"; return; fi
if [ -n "$NOT_DONE_PASS" ]; then echo "gate-passed but not done-: $NOT_DONE_PASS— walk the happy path + coverage checklist, then git mv to done- (iterative-build-loop.md step 13-15)"; return; fi
if [ -n "$OB_PENDING" ]; then echo "discharge: $OB_PENDING (bin/lib/obligations.tsv names the artifact each owes)"; return; fi
if [ -n "$OB_NEXT" ]; then echo "discharge: $OB_NEXT (bin/lib/obligations.tsv names the artifact each owes)"; return; fi
[ -n "$GC_NEXT" ] && { echo "$GC_NEXT"; return; }
echo "nothing overdue — take the next script in architecture/build-plan.md"
}
Expand Down Expand Up @@ -195,21 +261,24 @@ TK="toolkit $TK_COMMIT_NOW"; [ -n "$TK_COMMIT_REG" ] && [ "$TK_COMMIT_REG" != "$
[ -n "$TK_UPD" ] && TK="$TK · updates: $TK_UPD"
WATCH=""
for _w in "${APPROVED_OVER:+APPROVED OVER A FAILING GATE: $APPROVED_OVER}" "$DONE_STALL" "${BP_STALE%% (*}" \
"$( [ "${PH_TOTAL:-0}" -gt 0 ] && echo "agent slots unfilled: $PH_TOTAL in $PH_FILES file(s)" )" "$PACK_GAP"; do
"$( [ "${PH_TOTAL:-0}" -gt 0 ] && [ "$EAC" = 0 ] && echo "agent slots unfilled: $PH_TOTAL in $PH_FILES file(s)" )" "$PACK_GAP"; do
[ -n "$_w" ] && WATCH="${WATCH:+$WATCH · }$_w"
done
if [ "$BRIEF" = 1 ]; then
echo "WHERE $NAME · $STAGE_LINE${ENTRY:+ · $ENTRY}${ADOPTED:+ · joined at $ADOPTED}"
echo "STATE scripts $N_SCRIPTS written / $N_PASS gate-pass / $N_DONE done- · modules opened $MOD_OPENED${SKELETON:+ · skeleton $SKELETON} · UNSYNCED $UNSYNCED · open questions $OPEN_Q"
[ -n "$ROUND_LINE" ] && echo "ROUND $ROUND_LINE"
echo "STATE scripts $N_SCRIPTS written / $N_PASS gate-pass / $N_DONE done- · modules opened $MOD_OPENED${SKELETON:+ · skeleton $SKELETON} · UNSYNCED $UNSYNCED · open questions $OPEN_Q${MANUAL:+ · $MANUAL}"
[ -n "$WATCH" ] && echo "WATCH $WATCH"
echo "NEXT $NEXT"
"$TOOLKIT_ROOT/bin/token-burn.sh" "$PROJECT_DIR" --brief 2>/dev/null
exit 0
fi
printf '\n%s — %s%s%s\n' "$NAME" "$STAGE_LINE" "${ENTRY:+ · $ENTRY}" "${ADOPTED:+ · joined at stage $ADOPTED}"
printf '%s\n\n' "$TK"
[ -n "$ROUND_LINE" ] && printf 'ROUND %s\n' "$ROUND_LINE"
printf 'DONE scripts: %s written, %s gate-pass, %s done- · modules opened: %s%s\n' "$N_SCRIPTS" "$N_PASS" "$N_DONE" "$MOD_OPENED" "${SKELETON:+ · skeleton proven $SKELETON}"
printf 'OVERDUE %s · %s · %s · UNSYNCED markers: %s · open questions: %s\n' "$DOCTOR" "$LINT" "${COH:-coherence: cadence script not installed}" "$UNSYNCED" "$OPEN_Q"
[ -n "$MANUAL" ] && printf ' %s (docs/manual-steps.md)\n' "$MANUAL"
[ -n "$OB_PENDING" ] && printf ' obligations pending: %s\n' "$OB_PENDING"
[ -n "$LAST_FAIL" ] && printf ' last gate FAILED: %s\n' "$LAST_FAIL"
[ -n "$WATCH" ] && printf 'WATCH %s\n' "$WATCH"
Expand Down
60 changes: 60 additions & 0 deletions skills/existing-app-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,64 @@ end — a regression found three modules later costs the same to fix and much mo

---

## Delivering in rounds — the round is the unit of status

A backlog of small stories (1–5 points each) is delivered in rounds of a few stories, and the stage
machine does not see rounds: on a field run (2026-10-13, 11 stories in 4 rounds on a 127-module
app) `bin/status.sh` still said **"Stage 1 — Analysis, in progress"** after the fourth delivered
round, with "agent slots unfilled: 35" and "done- stalled" in its WATCH line. Stage 0a (the map) is
still the one-time prerequisite; stages 1–4 run per round over that round's stories.

Record each round as one row in `PROJECT.md`, under a section you add when the first round starts:

```markdown
## Rounds

| Round | Title | Stories | State |
|---|---|---|---|
| 1 | Export fixes | S-101, S-102, S-104 | delivered |
| 2 | Approval rework | S-110, S-111 | in test |
```

- **Append, never reorder.** The last row is the round in flight; `status.sh` prints it as
`ROUND Round 2: Approval rework — in test · stories: S-110, S-111 · 1 of 2 round(s) delivered`.
- **State** is one word or phrase: `planned` · `building` · `in test` · `delivered`. Only a state
starting with `delivered` counts as delivered. Update the cell when the round moves, not later.
- **Stories** names the ids from wherever the stories live (a ticket system, a `stories.json`, change
log rows in `PROJECT.md`). The per-story record — scope, touched units, decisions, test verdict,
manual steps — stays there; this table does not copy it.

In this entry mode `status.sh` drops the "agent slots unfilled" item from WATCH: a change stream
delivered in rounds does not fill the pipeline agent stubs, so that line measured nothing, and the
round line is the progress readout instead. The `done-` signals stay — Stages 5–6 run the build loop
unchanged here, `done-` rename included. Other modes print as before.

---

## Edits mxcli cannot make — a targeted manual-step list

Some edits cannot go through `describe → exec` safely. Field case (2026-10-13): six microflow edits
whose decision branches rejoin early, where re-executing the described microflow would change the
flow (upstream mxcli #923) — three were done by hand in Studio Pro, three by a BSON patch. That is
neither a whole-model handoff (`handoff-to-studio-pro.md`) nor an MDL script, and it needs its own
deliverable.

Copy `<toolkit>/templates/manual-steps.md` to `docs/manual-steps.md` and write one section per edit:
the document by full path, why mxcli cannot do it, what it depends on, the exact clicks with an
anchor (the existing activity or widget it goes next to, by caption or name), a **Check** a person can
run in the app, and one `- [ ] Done` line. List the steps in dependency order: attributes an MDL
script adds come before the hand edit that uses them.

- **A step done by patch still goes through `./bin/exec.sh --patch <script>`.** The snapshot, the
mxbuild gate and the BUILD-LOG row are its evidence; a patch run any other way leaves none, and the
Done line cannot cite one.
- **Tick the Done line only after its Check passed**, and name the evidence on it: the commit for a
hand edit, the BUILD-LOG row for a patch.
- `status.sh` counts the file: `manual steps pending N of M` (unticked Done lines of all Done lines).
A round with pending manual steps is not `delivered`.

---

## Coverage in this mode

The ledger in this mode is `mxcli brain plan`, not a hand-kept `coverage-ledger.md`. Each BRD leaf
Expand Down Expand Up @@ -237,5 +295,7 @@ it is the only entry mode where that is true.
- Knowledge base — Path D, scoped, with counts recorded: modules, and per in-scope module the entity, page and microflow totals `SHOW …` returned, so a reader can see the change against the app.
- BRDs for the change, each with as-is and to-be — and one line saying how many capabilities were *not* BRD'd and why.
- A build plan over the change.
- When the work arrives as a backlog: a `## Rounds` table in `PROJECT.md`, one row per round, its state current.
- When an edit cannot go through mxcli: `docs/manual-steps.md` from `templates/manual-steps.md`, every step with its Check and a ticked Done line naming its evidence.
- **A regression baseline that was green before the change and is green after it** — stated as N journeys over M modules, re-run at every module gate. Without this the rest is unverified.
- Anything reusable learned about the app → `skills/learned-*.md`; process learnings → `process/process-learnings.md`.
Loading
Loading