Skip to content
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m

## Unreleased

- fix(release-review): **the v1.0 release review: the multilingual skill loses its em and en dashes (wording only, content unchanged; its routing row keeps the trigger separator the agent stubs cut on), `doctor.sh` warns when a hook copy in `~/.claude/hooks` calls a toolkit script that no longer exists (it names the script and says to re-run `bin/install-claude-hooks.sh --full`; a warning, never a failure), and the `.gitignore` line for `.claude/progress-report-sessions.txt` now also reaches a project checked out as a git worktree (`.git` is a file there) in `doctor.sh` and `sync-project.sh`; `check-ignore --no-index` stops the line being appended again when the file is already tracked, and a `git rm --cached` hint is printed.** Tests: new `test-doctor-stale-hooks.sh`; worktree and tracked-file cases added to `test-progress-report-gitignore.sh`. CHANGELOG tidy (a stray blank line, one credit). — MendixMau
- fix(progress-report): **round 6 of the review: the session cache is machine-local and tied to its project, the first cached run is not slower than a cold one, and the build ledger decides the Stage 4 to 5 switch.** (1) `.claude/progress-report-sessions.txt` holds absolute session-log paths, so `init-project.sh`, `doctor.sh` and `sync-project.sh` now put `/.claude/progress-report-sessions.txt` in the project's `.gitignore`, the way they do for the doctor receipt and `toolkit.env`. (2) The cache header records the project's real path and the attribution rules (25% of tool calls, 20 calls); a copied or moved project, or a changed threshold, reads it as cold, so a copy never lists the original's sessions. (3) Files skipped by the 300-file cap are recorded as `no`, so a cached run never reads more files than a cold one. (4) With rows in `metrics-ledger.tsv` (else BUILD-LOG) the switch comes from the ledger; an earlier exec.sh call or build skill in a session log is ignored, the timed sign-off rule stays, and the footer says how the switch is chosen. Tests: F15 to F17 in `test-progress-report-field-fixes.sh`, new `test-progress-report-gitignore.sh` — MendixMau
- fix(progress-report): **polish on a fresh and an updated demo project: a "done <date>" plan row counts as built in the card, the plan header and the phase badge alike; a placeholder gate failure on a stage after the current one reads PENDING, not FAIL; the no-session-log notice is one calm line with the regenerate hint inside "How this was measured"; the test-moments table says unit tests run for the flows chosen at CAC-5, not "when switched on".** — field review of the #434 report
- fix(progress-report): **round 6 of the review: the session cache is machine-local and tied to its project, the first cached run is not slower than a cold one, and the build ledger decides the Stage 4 to 5 switch.** (1) `.claude/progress-report-sessions.txt` holds absolute session-log paths, so `init-project.sh`, `doctor.sh` and `sync-project.sh` now put `/.claude/progress-report-sessions.txt` in the project's `.gitignore`, the way they do for the doctor receipt and `toolkit.env`. (2) The cache header records the project's real path and the attribution rules (25% of tool calls, 20 calls); a copied or moved project, or a changed threshold, reads it as cold, so a copy never lists the original's sessions. (3) Files skipped by the 300-file cap are recorded as `no`, so a cached run never reads more files than a cold one. (4) With rows in `metrics-ledger.tsv` (else BUILD-LOG) the switch comes from the ledger; an earlier exec.sh call or build skill in a session log is ignored, the timed sign-off rule stays, and the footer says how the switch is chosen. Tests: F15 to F17 in `test-progress-report-field-fixes.sh`, new `test-progress-report-gitignore.sh` — review of PR #434
- fix(progress-report): **round 5 of the field test: "building now" marks the phase of the last successful build step (its mdlsource folder, else the script name matched to the plan, else the highest active phase; blocked steps and earlier amber rows do not move it); discovery of sessions from other folders counts only tool calls that operate on the project (at least 25% of a session's calls and at least 20, gaps over 30 minutes cut, sessions that run progress-report.py excluded, text mentions never count) and lists the sessions used in a collapsed element; a Stage 4 sign-off row with a date but no time puts the Stage 5 switch at the first build activity on or after that date (marked inferred) instead of midnight; the exec log is now the primary source for build activity: `.claude/loop/metrics-ledger.tsv` (written by exec.sh, UTC shown as local time) gives the Stage 5 build facts with or without a session log (runs, applied, failed, blocked, restored, exec time, wall span, runs per phase, latest model score, share of exec time lost to failed or restored runs), the last build step, the building-now phase and the Stage 4 to 5 switch, with BUILD-LOG and then session activity as fallbacks; docs/progress/TIME-LEDGER.md is an optional hand-written extra and its reader maps columns by each table's header.** Tests F10 (existing-project fixture) and F11 (older toolkit, then sync-project.sh) added. — MendixMau
- fix(progress-report): **field-test round on two real projects: one source of truth for stage state, a plan read from build-plan.md when the JSON is stale, wider log discovery with a ledger fallback, and a Build progress section.** (1) PROJECT.md (the stage line and the Decisions sign-off rows) beats a gate-check page that is older than it: the stage line's `· manual: 5` tail no longer swallows the last passed stage, the counter always equals the list it sits beside ("6 of 8" for P,0,1,2,3,4, plus "N waived"), and a passed stage the page does not list shows an amber "Gate-check page is N days old, re-run gate-check". (2) The report never lists itself as a stage artifact, nor any file outside the project folder. (3) `build-plan.json` older than `build-plan.md` or than the newest `docs/BUILD-LOG.md` row is no longer trusted: row state is read from the md State column by prefix (`not built` first; `built (...)` and `done ...` are built; `partial` and `in progress` are partial; `not needed` rows are not counted) and the page says "plan JSON was stale, counted from build-plan.md". (4) With no log in the project's own folder, discovery also scans other `~/.claude/projects/*` folders (30 days, newest 300 files): a log counts when its cwd is the project or when the project path appears in it at least 5 times, in which case only the stretch from the first to the last mention (padded 30 min) is counted, and the page says so; with no log at all, `docs/progress/TIME-LEDGER.md`, `docs/BUILD-LOG.md` and `.claude/loop/metrics-ledger.tsv` feed a labelled ledger card, and the banner says how to pass `--session-log`. (5) The Stage 4 to 5 switch for time and tokens is the earliest evidenced of the Stage 4 sign-off row in PROJECT.md, the first `exec.sh` call and the build skill, so a stale gate page no longer puts all build work in Stage 4. (6) The build detail section is now "Build progress", linked from the Stage 5 card: stacked built / partial / not built bars per phase, the headline "N of M rows built, K partial", the phase being built now marked, and the last build step with how long ago it ran. Test: `tests/wave2/test-progress-report-field-fixes.sh` (39 checks). — MendixMau
- new(progress-report): **`architecture/progress-report.html` replaces `architecture/build-plan.html`, and adds time, token and tips views from the session log.** `build-plan-status.sh --html` now writes one self-contained page: every phase and the built versus proven views the old page had, stages P to 6 with artifact links, a progress bar and stage nav, and, when a Claude Code log for the project folder exists, time per stage, sub-agent use, context size, an estimated token cost (rates in an optional file, placeholder defaults, labelled as estimates), a Run health strip and a speed panel. Tips and tricks sit in a narrow side column (after the title, headline and stage cards on a phone), in one collapsed box per theme (Context, Tokens and cost, Speed, Health, Sub-agents), empty themes hidden. Only patterns with a concrete action in the project are shown, ranked by time or tokens lost, each with what was seen, why it matters and one fix. Severity is calm: red only for a key script (doctor, gate-check, exec, tests) failing every run with at least 3 runs, or a hang over 30 minutes; context is never red (a Context tip is amber when the window was reached or a compaction happened). Required gates and builds on the inherent list never produce a hang or slow tip and never change Run health; tips that lose under a minute are dropped; tips name real scripts or commands only. Costs that come from how the toolkit and mxcli work (build time, required gates and reviews, waiting on builds or the user, the mandated review model) are flagged inherent in the script and never shown. The context window is 1M only when the log records it (a `[1m]` model id or a context-1m beta); otherwise 200k, and a context above 200k with auto compactions caps the window at the compaction point and is labelled inferred. Never a percentage over 100. Every threshold sits in one block at the top of `project-bin/progress-report.py`, and the footer records the project folder and session log paths used. Only numbers, stage names, script names, file paths and sub-agent short descriptions are read from the log, never message text. Without a log the project sections render and the page says time and token data are unavailable. The page refreshes after every `exec.sh` slice and reloads itself every 30 s only while the build is active. PROJECT.md `Progress report: off | ask | auto` (default `ask`) controls it, and `--open` opens it. An existing `build-plan.html` is never deleted, and gate-check and status.sh still accept it. `--json` is unchanged. Skill words fell by about 100 (Step 10 and step 16 shortened). Tests: `tests/wave2/test-progress-report.sh`, `test-build-plan-json.sh`, `test-surface-column.sh`. — MendixMau
- fix(process-map): migrate no longer trusts a regex for the model key: it checks the top-level `anchors` changed and otherwise re-writes the model, and says so instead of claiming "only that key was changed"; a string `targets` in an old file counts as a one-item list instead of crashing; unfilled entries already in the current anchors file are listed, not dropped silently (follow-up to #438 review) — MendixMau
- new(brd-report): the Stage 2 report opens with the pass-1 process map (drawn by `process-map.js build --embed`, not a copy) and lists requirements and open questions under it, each tied to its step; a click on a step filters the list, what no step covers stays under "whole app". `open-questions.html` and `bin/questions-report.sh` are retired: the gate and the interview hook write `analysis/brd-report.html`, and an old project's `docs/open-questions.html` is never an error, only pointed away from. Stage 3: the prototype links to the full map and the map links back — a business owner could not tell where in the work a question fits (field run, 2026-10-16) — MendixMau

- learn(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, architect, mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which this project's non-admin role could not, with no grant route found (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible; a probe on the `System.User` module role now comes first); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from `<html lang>`; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. — CarlesZ
- process(inbox): **five field notes from an existing-app legacy-widget rebuild** — exec.sh keeps an MDL write when mxbuild crashes on it and the next exec runs on "Baseline NOT measured"; a signal stop of `run --test-endpoint` leaves AfterStartupMicroflow on `MxTest.RegisterEndpoint` (second field case for #244); Unit-row delete refused as ORPHAN, rename-then-drop works; a durable-rebuild kit with a "restate from DESCRIBE" STOP row (DESCRIBE omits module-role and layout-row visibility: 97 role locks in 16 documents, none shown); learned entries for File Uploader image mode (CE0463), native-mapped `radiobuttons`/`image`, tab-page `CaptionParams`/`Visible:` silently dropped, BUG-39 on widget XPath, and two-hop data-source paths. Three further notes (restore-mpr usage, build-plan-status flag order, another project's mxcli test blocking exec.sh) were dropped because master already fixes them. Mendix 11.14.0 Beta, mxcli v0.25.0, toolkit c1efcb3 — AlvinnYeo
- fix(build-plan-status): --refresh keeps an existing build-plan.json current, so a day of building no longer reads as 0 built — MendixMau
Expand Down
46 changes: 45 additions & 1 deletion bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,45 @@ fi

fi

# --- installed Claude hooks: copies that call a toolkit script which is gone ------------------
#
# install-claude-hooks.sh writes COPIES into ~/.claude/hooks with the toolkit path filled in.
# When a toolkit update removes a script (bin/questions-report.sh was retired in v1.0), an old
# copy keeps calling it: the hook still runs but silently loses its output. A warning only,
# never a failure: the hooks are user-global and optional.
if [ -d "$HOME/.claude/hooks" ]; then
head_ "Installed Claude hooks (~/.claude/hooks)"
STALE_HOOKS=0
HOOKS_SEEN=0
for hf in "$HOME"/.claude/hooks/*.sh; do
[ -f "$hf" ] || continue
HOOKS_SEEN=$((HOOKS_SEEN + 1))
# The toolkit root the installer wrote, e.g. TOOLKIT="/path/to/toolkit".
HOOK_ROOT="$(sed -n 's/^[A-Z_]*TOOLKIT[A-Z_]*="\(\/[^"]*\)".*/\1/p' "$hf" | head -1)"
HOOK_REFS=""
if [ -n "$HOOK_ROOT" ]; then
HOOK_REFS="$(grep -oE '[$][{]?[A-Z_]*TOOLKIT[A-Z_]*[}]?/[A-Za-z0-9_./-]+[.](sh|py|js|tsv)' "$hf" 2>/dev/null \
| sed -e 's/^[$][{]*[A-Z_]*[}]*//' -e "s|^|$HOOK_ROOT|" | sort -u)"
fi
HOOK_GONE=""
while IFS= read -r ref; do
[ -n "$ref" ] || continue
[ -e "$ref" ] || HOOK_GONE="$HOOK_GONE ${ref##*/}"
done <<EOF_REFS
$HOOK_REFS
EOF_REFS
if [ -n "$HOOK_GONE" ]; then
STALE_HOOKS=$((STALE_HOOKS + 1))
warn "installed hook $(basename "$hf") calls a toolkit script that no longer exists:$HOOK_GONE"
fi
done
if [ "$STALE_HOOKS" -gt 0 ]; then
note "The installed copy is older than this toolkit. Re-run: $TOOLKIT_ROOT/bin/install-claude-hooks.sh --full"
elif [ "$HOOKS_SEEN" -gt 0 ]; then
ok "installed hooks only call toolkit scripts that exist"
fi
fi

# --- command line tools ---------------------------------------------------------------------

head_ "Command line tools"
Expand Down Expand Up @@ -1467,11 +1506,16 @@ if [ -n "$PROJECT_DIR" ] && [ -d "$PROJECT_DIR" ]; then
note "(added /.claude/toolkit.env to the project's .gitignore — it holds this machine's paths)"
fi
# The progress report's session cache holds absolute session-log paths (user name, session ids).
if [ -d "$PROJECT_DIR/.git" ] && ! git -C "$PROJECT_DIR" check-ignore -q .claude/progress-report-sessions.txt 2>/dev/null; then
# -e, not -d: in a git worktree .git is a file. --no-index: a tracked file is never "ignored" otherwise,
# and the line would be appended again on every run.
if [ -e "$PROJECT_DIR/.git" ] && ! git -C "$PROJECT_DIR" check-ignore -q --no-index .claude/progress-report-sessions.txt 2>/dev/null; then
printf '\n# Machine-local progress report session cache (absolute session-log paths)\n/.claude/progress-report-sessions.txt\n' \
>> "$PROJECT_DIR/.gitignore" 2>/dev/null || true
note "(added /.claude/progress-report-sessions.txt to the project's .gitignore — the cache is machine-local)"
fi
if [ -e "$PROJECT_DIR/.git" ] && git -C "$PROJECT_DIR" ls-files --error-unmatch .claude/progress-report-sessions.txt >/dev/null 2>&1; then
note "(.claude/progress-report-sessions.txt is already tracked by git; untrack it with: git rm --cached .claude/progress-report-sessions.txt)"
fi
fi

if [ "$FAIL" -gt 0 ]; then
Expand Down
9 changes: 7 additions & 2 deletions bin/sync-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1613,8 +1613,9 @@ fi
# ── Progress report session cache: machine-local, keep it out of git ─────────────────────
# It holds absolute session-log paths (user name, session ids). Same shape as doctor.sh's
# receipt and toolkit.env lines; a project synced from an older toolkit has no such line.
if [ "$WIRED" -eq 1 ] && [ -d "$PROJECT_DIR/.git" ] \
&& ! git -C "$PROJECT_DIR" check-ignore -q .claude/progress-report-sessions.txt 2>/dev/null; then
# -e, not -d: in a git worktree .git is a file. --no-index: a tracked file is never "ignored" otherwise.
if [ "$WIRED" -eq 1 ] && [ -e "$PROJECT_DIR/.git" ] \
&& ! git -C "$PROJECT_DIR" check-ignore -q --no-index .claude/progress-report-sessions.txt 2>/dev/null; then
if [ "$DRY_RUN" -eq 1 ]; then
echo "Would add: /.claude/progress-report-sessions.txt to .gitignore (machine-local cache)"
else
Expand All @@ -1624,6 +1625,10 @@ if [ "$WIRED" -eq 1 ] && [ -d "$PROJECT_DIR/.git" ] \
fi
CHANGES=$((CHANGES + 1))
fi
if [ "$WIRED" -eq 1 ] && [ -e "$PROJECT_DIR/.git" ] \
&& git -C "$PROJECT_DIR" ls-files --error-unmatch .claude/progress-report-sessions.txt >/dev/null 2>&1; then
echo "Note: .claude/progress-report-sessions.txt is already tracked by git; untrack it with: git rm --cached .claude/progress-report-sessions.txt"
fi

# ── Starlark lint rules ──────────────────────────────────────────────────────────────────
# Gated on WIRED for the same reason as the crash net: a mistyped path must not scatter
Expand Down
Loading
Loading