diff --git a/CHANGELOG.md b/CHANGELOG.md index df3d67a9..e00e47d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,21 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- perf(ui-loop): **`test-stack-up.sh` recognises a live `mxcli run --local` as this project's app, and `--restart` no longer builds twice.** The script read only Docker labels, so an app started with `./mxcli run --local --watch` (mxcli's own ~20s page loop) was found by port scan, published as `APP_OWNERSHIP=unverified`, and refused by the e2e harness unless `ALLOW_UNVERIFIED_APP=1`. It now reads mxcli's `.mxcli/run-local.json` handshake (v0.24+; beside the `.mpr` or the `-p` path on a two-tree checkout), checks its pid is alive, and publishes `verified` with a new `APP_SOURCE=run-local` line in `stack.env` (`docker` / `scan` otherwise). A model edited after boot reads as applied under `--watch` and as STALE without it — the script never restarts a loop it did not start. Docker bring-up and restart now wait on `mxcli docker run --wait` instead of a 240s poll loop, and still prove HTTP; `--restart` used to fall through into the bring-up block and run `docker run` a second time. New fixture `tests/wave2/test-stack-up-local.sh` over a redacted capture of the handshake. Field run 2026-10-07, existing app: same app, same port, `unverified` → `verified`; a touched model read `watch`; stopping the loop removed the handshake and the script fell back. — MendixMau +- fix(init): **local by default, no Dev Container.** `init-project.sh` now parks the `.devcontainer/` that `mxcli new`/`init` always writes under `.mxtk-backup/` (keep it with `MXTK_KEEP_DEVCONTAINER=1`), and the README says "say no" to VS Code's reopen prompt. Field incident 2026-10-08: a first-time user followed the docs and landed in the container with "claude: command not found" (the template installs `claude` to `~/.local/bin` and never puts it on PATH) and a fresh login prompt. Local use was always the intent. — MendixMau +- docs(lint): **Mendix quality-source review: 19 new lint-backlog rows (Batch 4) plus seven skill additions.** Recommender, QSM/AQM, ATS, Menditect and docs.mendix.com mapped against shipped rules; indexes, scheduled events, Atlas 4 tokens, accessibility floor, workflow targeting, test isolation and rollback semantics added to their skills; record in `process/quality-sources-review-2026-10-08.md`; no rule probed on a model yet — MendixMau +- docs(lint): **`process/lint-backlog.md` — the lint rollout plan, on disk.** Twelve rule candidates mined from `skills/` with the evidence each rests on, the catalog field each needs, and five rollout rules (two or three rules per PR, ship as warning, one field run before merge, promote after two clean projects, blind rules say so). Plus what is not lintable and lives in a skill (theme, logging, scheduled events) and four brain contradictions to settle first. Until today this plan existed only in chat. — MendixMau +- new(lint): **the lint gate keeps a ledger, so a rule's history is readable.** `project-bin/lint-gate.sh` appends one row per rule per run to `.claude/loop/lint-ledger.tsv` (ts, verdict, rule, count, severity, blind — plus a `_run` row with the total), and new `project-bin/lint-trend.sh` prints per rule: runs fired in, first/last count with dates, best/worst, blind runs, and up/down/flat (`--rule ` for one rule's series, `--runs` for the run rows). Until now the only per-rule history was `lint-baseline.json` in git, which records the count that was accepted, not what each run saw — so "did the rule we shipped catch anything, and did it go down" had no answer. This is the measurement the lint rollout plan's "promote after two clean projects" step depends on. Probed on the gate's own parser with a crafted lint result (3 findings, one blind rule): ledger rows and trend output as expected; `lint-last.json` and the ratchet verdict unchanged. Field run on a real model still owed (laptop). — MendixMau +- learn(test): **`skills/e2e-locators-that-lie.md`** — four Playwright harness defects that report a defect in the app: a widget name is unique per page not per DOM (hidden tab pane first, so `.first()` drives or asserts the wrong node), `click({force:true})` on a Mendix tab is swallowed and never throws (so force-then-plain fallback is dead), a grid re-renders on the response not the click (single-read text assertions race), and a fixture literal the reset script writes measures only the reset. Measured 2026-09-17 on a user-group administration module; routed for test/review at Stages 5–6 (diagnose), pointer from `e2e-harness-base.md`. The shipped `project-tests/e2e/journey-runner.js` does not yet apply rules 1–3; the skill says so. — MendixMau +- learn(skills): **`unhappy-path-testing.md` — requirement-cited unhappy-path e2e.** Every case cites a requirement (BRD rule, entity doc comment, `not null` error, `Visible:` expression, grant) before it runs; results are PASS / GAP / UNSPECIFIED, and UNSPECIFIED is re-scored against seven house rules (whitespace-as-required, silent truncation, no empty-state text, …) as GAP-HOUSE so a silent requirement cannot hide a user-visible defect. Measured on a warehouse-management demo project, 2026-09-24/25: 45 cases over 5 modules, five overnight runs 16 → 25 of 45 PASS (run 2's 17 GAPs were 10 instrument faults from one deep link), fix loop to 27/45 PASS, 5 GAP, 13 UNSPECIFIED, every GAP an open product Decision. Routed `test,review` at stages 5–6 (`verify`, ondemand) and pointed at from `testing-shape.md`'s owner table. — MendixMau +- fix(testing-shape): **`mxcli test --local` shares the deploy directory with `run --local`, so a warm app does not keep serving.** The skill said separate ports and database meant a warm `run --local` loop survives a test run; mxbuild writes `/deployment` for both, so a building test run breaks the live app (200 with empty bodies, NoClassDefFoundError). Now says to restart `run --local` after `test --local` and that `--attach` hot-applies into the live app. Field run 2026-10-07, existing app (#230) — MendixMau +- fix(obligations): **LOOK staleness reads the newest `VALID AT` stamp, anywhere in the report.** Both pickers took the first stamp inside the first 8000 bytes, so a review file with dated per-build addenda went false-STALE; now `tail -1` over the whole file. Field run 2026-09-26, requirements-driven build (#155) — MendixMau +- fix(lint): **CONV020 works out of the box and counts page navigation as feedback.** `PROJECT_MODULES` now defaults to `"*"` (every module the lint run includes, minus `.claude/lint-vendor-modules.txt`) instead of an empty tuple that made a fresh install inert; ShowPage/ClosePage/ShowHomePage actions count as user feedback (presence only; refresh-in-client is not exposed by mxcli, noted in the rule header). `lint-gate.sh --update-baseline` no longer baselines `_rule` (self-reported blindness) findings. Starlark fixture not run: no mxcli binary or rule runner in this repo; installer fixture `test-lint-delivery.sh` green (#229, #245) — MendixMau +- fix(e2e): **journey-runner rung 2 now runs for a negative-only span claim.** `spans: { ordered: [], mustNotFire: [...] }` was skipped silently because the rung required a non-empty `ordered`; it now runs when either list is non-empty (sequence assert only when `ordered` is non-empty). Zero captured spans still reports INVALID (a vacuous negative is not proof). Verified by `node --check` and a gate-condition probe, no live Jaeger run (#150) — MendixMau +- fix(gates): **Stage 6 Surface agrees with the gate, source-sufficiency title resolves `.`, journeys artifact row honours `JOURNEY_DIR`.** `test_surface_hit()` is now the one test behind both check_stage_6 and the Surface column (#233); the report title takes the realpath basename (#234); the manifest accepts a `${JOURNEY_DIR}` token resolved like verify-module.sh (#235). Field run 2026-10-08, existing app — MendixMau - perf(routing): **CLAUDE.local.md routing carries triggers only.** The `baseline` view (the copy loaded into every session of every project) now cuts each row's "when" cell at its first " — " and replaces the three-paragraph preamble with two lines pointing at `ROUTING.md` for the full text; `readme-baseline` (README, runbook) is unchanged. Fresh scaffold: CLAUDE.local.md 17.5 KB → 11.7 KB (−33%). Field run 2026-10-07 (existing app, one main session per run, 2 runs per side, an entity + 2 microflows + 2 pages): 10/10 steps applied each side, new mx errors 5 vs 5 (the same CE2421 page-binding slip in both), cost $1.39 vs $1.38; the slim lanes opened the MDL pre-flight skill as often or more. Also drops two stray merge-artifact lines from this section. — MendixMau +- fix(sync): **`sync-project.sh` no longer exits 1, silently, before its `bin/` and `tests/e2e/` refresh.** The CLAUDE.md duplicate-routing check (section 3) looked up the "mxcli-project-toolkit Integration" heading with an unguarded `grep | head | cut` under `set -euo pipefail`; when CLAUDE.md cites `skills/query-the-model.md` or `skills/learned-mdl-preflight.md` without that heading, which is every CLAUDE.md mxcli init v0.25 writes, grep's exit 1 ended the script with no message. Every step after it never ran: the crash-net refresh, the tests/e2e install and the lint-rule restore. Introduced by 9a0350b (2026-09-18). Now `|| true`, and `ledger_row_prefix` gets the same guard. Field: three consuming projects of one organisation (2026-10-07): the dry run went from exit 1 after "Would rewrite: CLAUDE.md" to exit 0 listing 7 and 8 skipped `bin/` refreshes. New fixture `tests/wave2/test-sync-dup-heading-exit.sh` (4 of 6 fail on the pre-fix script). — MendixMau (field run, existing apps) + perf/faster-ui-loop - perf(ui-loop): **fewer mxbuilds, no fixed sleeps, legible screenshots.** Field run 2026-10-07 (a requirements-driven build): `exec.sh` was 22% of tool time at two mxbuilds per exec, journeys and page waits slept a fixed time, and screenshots were 11.5% of re-read tokens. `exec.sh` now skips the pre-flight baseline mxbuild when the model is byte-identical to its last *clean* pass: the verification stamp gains an `errors:` field (`model-stamp.sh check --clean` accepts only `errors: 0`, so a delta-gate pass over pre-existing errors never counts; `SKIP_BASELINE=0` is unchanged). New `tests/e2e/settle.js` waits for the page's own state (no visible Mendix progress/loading indicator, DOM size stable) instead of a fixed time; `journey-runner.js` uses it after each action (an explicit `settleMs` still wins) and `page-audit.js` per page. Probed in a real browser: on a page that loads in 1.8 s the old 1200 ms sleep and networkidle+600 both captured it half-loaded, settle waited for it; on a fast page settle took 775 ms vs 1200. `SETTLE_MODE=fixed` restores the old waits for an A/B run. `page-audit.js` also saves a viewport shot `page-audit-.top.png` beside the full-page one, and `ui-loop.md` says to look at viewport shots: a tall full-page shot reaches the model at ~1024 px high, text unreadable. `shrink-image-read.sh` now works off macOS (ImageMagick or Pillow, python when jq is absent; it was a silent no-op without `sips`). — MendixMau - perf(ui-loop): **a stale app reloads instead of restarting, journeys stop sleeping, worst pages are looked at first.** Field run 2026-10-07, existing app. `test-stack-up.sh` records which model the running app was built from (`.claude/loop/served-model`); when the model has changed since, it hot-reloads with `mxcli docker reload` (adding `--skip-check` when the exec.sh gate already passed this model) and restarts only if that fails: 93 s instead of 179 s. `ui-loop.md` and the script header carry the measured table and point at `mxcli run --local --watch` as the fast loop (~18 s per page change). `journey-runner.js` waits on page state after a combobox action instead of fixed pauses, and a combobox step now opens the widget it names: it used to open the page's first combobox, so on a form with three every step but the first failed. `page-audit.js` clicks the first copy of a nav item a user could actually click, not just the first match: with the sidebar collapsed, every in-nav page faulted while the same item sat in the top menu bar. It also writes `page-audit-look-order.txt`, all pages worst first (`module-review.md` §4a: order, not selection, every page is still looked at). `design-audit.js` drops its fixed 1.2 s wait. Same 7-step journey on the same app: 56 s with `SETTLE_MODE=fixed`, 24 s with settle, 11/11 pass both ways, 3 runs each. — MendixMau diff --git a/README.md b/README.md index 120e7d90..465a11f5 100644 --- a/README.md +++ b/README.md @@ -111,13 +111,14 @@ network hop with path translation on every file argument. The toolkit does none Git Bash runs where Studio Pro, `mxcli` and your model already live, and ships with Git for Windows. `doctor.sh` detects WSL and warns. -**This is about the shell you type in, not about Docker.** Docker Desktop on Windows runs its -engine on a WSL2 backend, and VS Code will offer to "install Docker on WSL" the first time you -reopen an `mxcli new` project in its Dev Container — say yes. That is the `devcontainer` lane -(`CONVERSION-RUNBOOK.md` → *Where you run this*), headless and fully supported; `doctor.sh` -detects it and records it. The warning above applies only when you run the toolkit's scripts -*yourself* from a WSL prompt against a Studio Pro on the Windows side. Real question, 2026-09-07: -"the toolkit says not WSL, but `mxcli new`'s Dev Container wants Docker on WSL — yes or no?" Yes. +**This is about the shell you type in, not about Docker.** `mxcli new` writes a `.devcontainer/` +and VS Code then offers to "Reopen in Container" — **say no, work locally.** The container is a +second machine (no `claude` login, no dotfiles, a fragile install step), and the toolkit's path +is plain local use: `bin/init-project.sh` parks that folder under `.mxtk-backup/` so the prompt +stops. If you already clicked yes: F1 → "Dev Containers: Reopen Folder Locally", nothing is lost. +Real incident, 2026-10-08: a first-time user landed in the container with "claude: command not +found" and a fresh login prompt. The `devcontainer` lane in `CONVERSION-RUNBOOK.md` stays +supported for those who want it (`MXTK_KEEP_DEVCONTAINER=1`), it is just not the default. **The lanes mix freely on one project.** Do the headless stages in the Dev Container, then open the same `.mpr` in Studio Pro from Git Bash for the steps MDL cannot express and UI polish, and go back. @@ -649,6 +650,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini | Writing DB assertion tests that cross-check UI state against the database | `skills/learned-db-assertions.md` | | Establishing the data and identities a journey run needs — BEFORE it runs. Derive and measure with project-bin/fixture-manifest.sh first; interview only the residue, and never seed from inside the harness | `skills/fixture-seeding.md` | | Proving a module's user journey end-to-end — the deep form of step 3 PROVE; use whenever an instrument reports green and you cannot say what would have made it red | `skills/journey-proof.md` | +| Writing or running e2e cases beyond the happy journeys (blank/whitespace required fields, over-length, duplicates, cancel, double submit, wrong state, wrong role, backend down, stale write, empty search) — every case cites a requirement first, UNSPECIFIED is re-scored against house rules, GAP counts carry the denominator; runs after journey-proof and before monkey-test | `skills/unhappy-path-testing.md` | | Running the fuzz/crash net on a module whose journeys are already green — and reading the result, which is NOT evidence the module works | `skills/monkey-test.md` | | UX audit and screenshot-loop discipline | `skills/learned-skill-ux-audit.md` | | Tracking scope delta between the BRD and the built state | `skills/learned-skill-scope-delta.md` | @@ -674,6 +676,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini | Task | Skill to load | |---|---| | Reading a whole class of tool defects (a retest, a new mxcli release, an audit) — for one CE code or symptom use bin/bug-lookup.sh instead; the ledger is 32k words | `bug-logs/mxcli-bugs.md` | +| An e2e rung reports a UI defect (widget never appeared, click did nothing, screen contradicts the data, fixture count wrong) — check the locator before blaming the app: :visible, click order, poll, derived fixtures | `skills/e2e-locators-that-lie.md` | | Any refused, denied or blocked command — BEFORE rewriting a permission rule and before telling the user a tool is blocked. A rule matches the START of the command line, so an allowlisted tool prefixed with cd matches nothing | `skills/agent-permission-friction.md` | | Studio Pro will not load the project, or the .mpr looks gutted — recover before relaunching SP, never git checkout | `skills/mpr-corruption-and-sp-load-errors.md` | | Preparing an mxcli/Studio Pro bug for submission — scope pinning, read-back-vs-write-path verification, gate-sensitivity negative controls, severity scoping, before it's called filable | `skills/bug-submission-checklist.md` | diff --git a/ROUTING.md b/ROUTING.md index 10bd579b..d2c508e7 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -211,6 +211,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Writing DB assertion tests that cross-check UI state against the database | `skills/learned-db-assertions.md` | test | 6 | ondemand | | Establishing the data and identities a journey run needs — BEFORE it runs. Derive and measure with project-bin/fixture-manifest.sh first; interview only the residue, and never seed from inside the harness | `skills/fixture-seeding.md` | test,review | 5,6 | ondemand | | Proving a module's user journey end-to-end — the deep form of step 3 PROVE; use whenever an instrument reports green and you cannot say what would have made it red | `skills/journey-proof.md` | test,review | 5,6 | ondemand | +| Writing or running e2e cases beyond the happy journeys (blank/whitespace required fields, over-length, duplicates, cancel, double submit, wrong state, wrong role, backend down, stale write, empty search) — every case cites a requirement first, UNSPECIFIED is re-scored against house rules, GAP counts carry the denominator; runs after journey-proof and before monkey-test | `skills/unhappy-path-testing.md` | test,review | 5,6 | ondemand | | Running the fuzz/crash net on a module whose journeys are already green — and reading the result, which is NOT evidence the module works | `skills/monkey-test.md` | test,review | 5,6 | ondemand | | UX audit and screenshot-loop discipline | `skills/learned-skill-ux-audit.md` | review | 6 | ondemand | | Tracking scope delta between the BRD and the built state | `skills/learned-skill-scope-delta.md` | review | 6 | ondemand | @@ -237,6 +238,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Always relevant for | Load this | Agent(s) | Stage(s) | Tier | |---|---|---|---|---| | Reading a whole class of tool defects (a retest, a new mxcli release, an audit) — for one CE code or symptom use bin/bug-lookup.sh instead; the ledger is 32k words | `bug-logs/mxcli-bugs.md` | mdl,gate | 5,6 | ondemand | +| An e2e rung reports a UI defect (widget never appeared, click did nothing, screen contradicts the data, fixture count wrong) — check the locator before blaming the app: :visible, click order, poll, derived fixtures | `skills/e2e-locators-that-lie.md` | test,review | 5,6 | ondemand | | Any time an exit code, a tool's output or a subagent's report is about to become a stated finding — verify before you conclude | `skills/tool-output-is-not-ground-truth.md` | all | - | baseline | | Any refused, denied or blocked command — BEFORE rewriting a permission rule and before telling the user a tool is blocked. A rule matches the START of the command line, so an allowlisted tool prefixed with cd matches nothing | `skills/agent-permission-friction.md` | all | - | ondemand | | Studio Pro will not load the project, or the .mpr looks gutted — recover before relaunching SP, never git checkout | `skills/mpr-corruption-and-sp-load-errors.md` | mdl,gate | - | ondemand | diff --git a/agents/review-agent.md b/agents/review-agent.md index deff9cbd..63a12b64 100644 --- a/agents/review-agent.md +++ b/agents/review-agent.md @@ -69,7 +69,9 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `project-bin/graph-sweep.sh` | Running the wiring rung alone — a module imported but never reached, an element built but wired to nothing, a boundary crossed; mxbuild and e2e are blind to all three | | `skills/fixture-seeding.md` | Establishing the data and identities a journey run needs — BEFORE it runs. Derive and measure with project-bin/fixture-manifest.sh first; interview only the residue, and never seed from inside the harness | | `skills/journey-proof.md` | Proving a module's user journey end-to-end — the deep form of step 3 PROVE; use whenever an instrument reports green and you cannot say what would have made it red | +| `skills/unhappy-path-testing.md` | Writing or running e2e cases beyond the happy journeys (blank/whitespace required fields, over-length, duplicates, cancel, double submit, wrong state, wrong role, backend down, stale write, empty search) — every case cites a requirement first, UNSPECIFIED is re-scored against house rules, GAP counts carry the denominator; runs after journey-proof and before monkey-test | | `skills/monkey-test.md` | Running the fuzz/crash net on a module whose journeys are already green — and reading the result, which is NOT evidence the module works | +| `skills/e2e-locators-that-lie.md` | An e2e rung reports a UI defect (widget never appeared, click did nothing, screen contradicts the data, fixture count wrong) — check the locator before blaming the app: :visible, click order, poll, derived fixtures | | `skills/learned-skill-ux-audit.md` | UX audit and screenshot-loop discipline | | `skills/learned-skill-scope-delta.md` | Tracking scope delta between the BRD and the built state | | `skills/company-brain.md` | Setting up or wiring a COMPANY BRAIN — the private tier between the toolkit and a project for own skills, conventions, lint rules, MDL snippets and approved MPKs; and deciding whether something goes to the toolkit, the company brain or docs/brain/ | diff --git a/agents/test-agent.md b/agents/test-agent.md index ec92c7ba..984b4b7f 100644 --- a/agents/test-agent.md +++ b/agents/test-agent.md @@ -50,7 +50,9 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/learned-db-assertions.md` | Writing DB assertion tests that cross-check UI state against the database | | `skills/fixture-seeding.md` | Establishing the data and identities a journey run needs — BEFORE it runs. Derive and measure with project-bin/fixture-manifest.sh first; interview only the residue, and never seed from inside the harness | | `skills/journey-proof.md` | Proving a module's user journey end-to-end — the deep form of step 3 PROVE; use whenever an instrument reports green and you cannot say what would have made it red | +| `skills/unhappy-path-testing.md` | Writing or running e2e cases beyond the happy journeys (blank/whitespace required fields, over-length, duplicates, cancel, double submit, wrong state, wrong role, backend down, stale write, empty search) — every case cites a requirement first, UNSPECIFIED is re-scored against house rules, GAP counts carry the denominator; runs after journey-proof and before monkey-test | | `skills/monkey-test.md` | Running the fuzz/crash net on a module whose journeys are already green — and reading the result, which is NOT evidence the module works | +| `skills/e2e-locators-that-lie.md` | An e2e rung reports a UI defect (widget never appeared, click did nothing, screen contradicts the data, fixture count wrong) — check the locator before blaming the app: :visible, click order, poll, derived fixtures | | `skills/company-brain.md` | Setting up or wiring a COMPANY BRAIN — the private tier between the toolkit and a project for own skills, conventions, lint rules, MDL snippets and approved MPKs; and deciding whether something goes to the toolkit, the company brain or docs/brain/ | | `skills/close-the-loop.md` | Cutover and retrospective — promoting proven patterns back into the toolkit | | `skills/report-schema.md` | Writing or reading docs/report.json — the append-only contract every instrument writes to and every renderer reads; open BEFORE building a new instrument or a second renderer | diff --git a/bin/gate-check.sh b/bin/gate-check.sh index c1772943..23e6e797 100755 --- a/bin/gate-check.sh +++ b/bin/gate-check.sh @@ -1315,21 +1315,26 @@ EOF fi } -check_stage_6() { - local f test_ok="" review_ok="" +# The one test for "is there a test surface?" — check_stage_6 and the dashboard's Surface +# column both call it, so the gate and the column cannot disagree about one file (#233). +# Accepts test-report.html (root or reports/, project or analysis base) OR the harness pair +# verify-module.sh's render rung produces: docs/verification/report.html rendered FROM +# docs/report.json. Both halves must be present — accepting one without the other would accept +# a surface whose machine half is gone. (Before this, test-report.html was the only accepted +# name and nothing in the toolkit generated it, so Stage 6 was structurally unpassable — +# improvement-plan Finding 14 / P9.) +test_surface_hit() { + local f for f in "$PROJECT_DIR/test-report.html" "$PROJECT_DIR"/reports/test-report.html \ "$ANALYSIS_BASE/test-report.html" "$ANALYSIS_BASE"/reports/test-report.html; do - [ -s "$f" ] && test_ok=1 + [ -s "$f" ] && return 0 done - # The harness's own surface also satisfies the test half: verify-module.sh's final render - # rung produces docs/verification/report.html from docs/report.json (report-render.js / - # report-normalize.js). Both halves must be present — the HTML is rendered FROM the JSON, - # and accepting one without the other would accept a surface whose machine half is gone. - # Before this, test-report.html was the only accepted name and nothing in the toolkit - # generated it, so Stage 6 was structurally unpassable (improvement-plan Finding 14 / P9). - if [ -s "$PROJECT_DIR/docs/verification/report.html" ] && [ -s "$PROJECT_DIR/docs/report.json" ]; then - test_ok=1 - fi + [ -s "$PROJECT_DIR/docs/verification/report.html" ] && [ -s "$PROJECT_DIR/docs/report.json" ] +} + +check_stage_6() { + local test_ok="" review_ok="" + test_surface_hit && test_ok=1 # module-review.md report — any non-empty dated report under a ui-reviews/ dir if [ -n "$(find_artifact -path '*/ui-reviews/ui-review-*.html' -size +0c)" ]; then review_ok=1 @@ -1706,6 +1711,9 @@ stage_surface_status() { fi for pat in $pats; do hit=0 + # test-report.html is answered by the same function check_stage_6 uses (#233), so the + # harness report pair also discharges it. + if [ "$pat" = "test-report.html" ] && test_surface_hit; then continue; fi # SURFACE_BASES, not just the project root. §2's Surface cells are inconsistent about # paths: some carry one ("architecture/blueprint.html", "docs/report.json") and some name # the file bare ("design-system.html", "build-plan.html", "test-report.html"). A bare cell @@ -1763,8 +1771,8 @@ stage_protocol_paths() { 2) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-brd.md skills/checkpoints/checkpoint-architecture.md skills/image-transcription.md skills/small-project-tier.md skills/kb-generation.md skills/brd-generation.md skills/brd-validation.md" ;; 3) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/brd-to-build-plan.md skills/workflow-structure-rules.md skills/learned-mdl-cannot-express.md" ;; 4) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-build.md skills/agent-roles.md skills/small-project-tier.md skills/module-brief.md skills/module-folder-convention.md skills/brd-to-build-plan.md skills/coverage-ledger.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/learned-constants-and-secrets.md" ;; - 5|build-ready) echo "skills/interview-protocol.md skills/grill-mode.md skills/agent-roles.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/microflow-loop-antipatterns.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/monkey-test.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; - 6) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/qa-loop-goal-pattern.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/monkey-test.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 5|build-ready) echo "skills/interview-protocol.md skills/grill-mode.md skills/agent-roles.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/microflow-loop-antipatterns.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 6) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/qa-loop-goal-pattern.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; 7) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/close-the-loop.md skills/share-demo-package.md skills/handoff-to-studio-pro.md skills/platform-link.md skills/teamserver-alignment.md skills/deploy-to-sandbox.md" ;; *) echo "" ;; # diff --git a/bin/init-project.sh b/bin/init-project.sh index 4c6b2269..f0056183 100755 --- a/bin/init-project.sh +++ b/bin/init-project.sh @@ -689,6 +689,21 @@ else echo " $DENYFILE by hand, or this project's name is invisible to the guard." fi +# --- no Dev Container ---------------------------------------------------------------- +# `mxcli new` / `mxcli init` always writes `.devcontainer/`, and VS Code then offers (and later +# remembers) "Reopen in Container". That container is a second machine: no `claude` login, no +# dotfiles, and its install step is fragile. The toolkit's path is plain local use, so the +# folder is moved aside here. Real incident, 2026-10-08: a first-time user landed in the +# container with "claude: command not found" and a fresh login prompt, and asked how to get +# rid of it. Keep it with MXTK_KEEP_DEVCONTAINER=1. Nothing is deleted: the folder is parked +# under .mxtk-backup/ so it can be restored by hand. +if [ -d "$PROJECT_DIR/.devcontainer" ] && [ -z "${MXTK_KEEP_DEVCONTAINER:-}" ]; then + mkdir -p "$PROJECT_DIR/.mxtk-backup" + mv "$PROJECT_DIR/.devcontainer" "$PROJECT_DIR/.mxtk-backup/devcontainer-$(date +%Y%m%d-%H%M%S)" + echo "Moved .devcontainer/ to .mxtk-backup/ — work locally; VS Code will not ask to reopen in a container." + echo " (MXTK_KEEP_DEVCONTAINER=1 keeps it.)" +fi + GUIDE="$SCRIPT_DIR/../toolkit-guide.html" # First-touch sentinel. This is the same file every agent must test before opening the guide # (see CLAUDE.md "First-touch rule"); writing it here is what stops the next session — and the diff --git a/bin/lib/artifact-check.sh b/bin/lib/artifact-check.sh index e2f5ee6a..7da2fa3e 100755 --- a/bin/lib/artifact-check.sh +++ b/bin/lib/artifact-check.sh @@ -189,9 +189,29 @@ _art_newer() { } # resolve_artifact() already serves. + +# Resolve the ${JOURNEY_DIR} manifest token (#235) with verify-module.sh's own precedence +# (env > /.claude/toolkit.env > ~/.mxcli-toolkit.env > /journeys) by sourcing +# project-bin/_common.sh in a subshell with PROJECT_ROOT pinned. Prints a path relative to the +# root when it lies under it, otherwise as given (absolute); falls back to "journeys". +_art_journey_dir() { + local root="$1" common="$_ART_LIB_DIR/../../project-bin/_common.sh" jd="" + if [ -f "$common" ]; then + jd="$( export PROJECT_ROOT="$root"; . "$common" >/dev/null 2>&1; printf '%s' "${JOURNEY_DIR:-}" )" || jd="" + else + jd="${JOURNEY_DIR:-}" + fi + jd="${jd:-journeys}"; jd="${jd%/}" + case "$jd" in "$root"/*) jd="${jd#"$root"/}" ;; esac + printf '%s' "$jd" +} + _art_find() { - local root="$1" paths="$2" pat base f rest + local root="$1" paths="$2" pat base f rest jd ART_HIT=""; ART_EMPTY="" + case "$paths" in + *'${JOURNEY_DIR}'*) jd="$(_art_journey_dir "$root")"; paths="${paths//\$\{JOURNEY_DIR\}/$jd}" ;; + esac rest="$paths" while [ -n "$rest" ]; do pat="${rest%%|*}" @@ -207,8 +227,10 @@ _art_find() { # defect, same day, as the one fixed in obligation-check.sh's `_ob_find`, where it DID # change the verdict. local newest_hit="" - for base in "$root" "$root"/analysis/*; do - [ -d "$base" ] || continue + local bases=("$root" "$root"/analysis/*) + case "$pat" in /*) bases=("") ;; esac # absolute (a JOURNEY_DIR outside the root) + for base in "${bases[@]}"; do + [ -z "$base" ] || [ -d "$base" ] || continue # base quoted, pattern not: the base must survive spaces, the pattern must glob. for f in "$base"/$pat; do [ -e "$f" ] || continue diff --git a/bin/lib/artifact-manifest.tsv b/bin/lib/artifact-manifest.tsv index b830de02..93c912ab 100644 --- a/bin/lib/artifact-manifest.tsv +++ b/bin/lib/artifact-manifest.tsv @@ -36,6 +36,10 @@ # ANALYSIS_BASE serves). Alternatives exist where real projects legitimately # differ (the three ledger shapes of skills/coverage-ledger.md §"Where the # ledger lives"), never as a hedge against not knowing the convention. +# A path may start with the token ${JOURNEY_DIR}: artifact-check.sh resolves it the +# way verify-module.sh does (env > .claude/toolkit.env > ~/.mxcli-toolkit.env > +# journeys/), so a project keeping journeys elsewhere is judged where its journey +# rung actually reads them (#235). # 4 producer the skill step or script that emits it — the thing a PENDING row tells you # to run # 5 consumers the citations that earn the row its place (see above) @@ -92,7 +96,7 @@ build-plan-json 4 architecture/build-plan.json project-bin/build-plan-status.sh module-brief 4 architecture/modules/*/module-brief.md|architecture/modules/*-brief.md skills/module-brief.md — ba-agent writes the first at Stage 4, the rest just-in-time bin/gate-check.sh:1121 (build-ready fails without one), project-bin/build-plan-status.sh:109 migration,requirements,existing-app-change gate coverage-ledger 4 architecture/modules/*/coverage-ledger.md|architecture/modules/*-coverage-ledger.md|architecture/coverage-ledger.md skills/coverage-ledger.md procedure at Stage 4 (BRD leaves × build-plan claims); the three shapes are §"Where the ledger lives", first canonical project-bin/conformance-check.sh:85-93, project-bin/coverage-preflight.sh:134-137, project-bin/verify-module.sh:299-300, project-bin/review-module.sh:289, conversion-runbook.md:487 (Stage-4 produces row) — the eleven-consumers-no-producer artifact of improvement-plan Finding 12 migration,requirements report ui-reviews 5 design/ui-reviews/ui-review-*.html skills/module-review.md pass (review-agent), one report per module close-out bin/gate-check.sh:984 (check_stage_6), bin/lib/obligations.tsv:47 (the look row — per-module coverage and denominators live THERE; this row only asks whether the stage produced any report at all) migration,requirements,greenfield,existing-app-change gate -journeys 5 journeys/*.journey.json journey authoring per skills/testing-shape.md / skills/journey-proof.md (worked example: project-tests/e2e/example.journey.json) project-bin/verify-module.sh:376,404 (the journeys rung), project-tests/e2e/journey-runner.js:5 migration,requirements,greenfield,existing-app-change report +journeys 5 ${JOURNEY_DIR}/*.journey.json journey authoring per skills/testing-shape.md / skills/journey-proof.md (worked example: project-tests/e2e/example.journey.json) project-bin/verify-module.sh:376,404 (the journeys rung), project-tests/e2e/journey-runner.js:5 migration,requirements,greenfield,existing-app-change report page-scope 5 .claude/loop/page-scope.json project-bin/page-scope.sh (the phantom of improvement-plan Finding 15 until 078c7d8 shipped it) project-tests/e2e/design-audit.js:55, project-tests/e2e/page-audit.js:66, project-tests/e2e/report-normalize.js:112 — without it the LOOK instruments degrade to --static-only migration,requirements,greenfield,existing-app-change report lint-last 5 .claude/loop/lint-last.json project-bin/lint-gate.sh (written on every run that reaches a verdict, not only --update-baseline), run as part of skills/module-review.md stage 2 GATE ("bin/exec.sh; gate-agent confirms 0 mxbuild errors, lint clean" — same step named in skills/iterative-build-loop.md's "Gate: BUILD") bin/status.sh:75-81,129 (the OVERDUE line's lint readout — without it status.sh reports "lint gate never recorded here") migration,requirements,greenfield,existing-app-change report improvement-register 5 docs/improvement-register.md skills/improvement-register.md:39 (append-only, one row per finding, written during build/verify passes) project-bin/build-plan-status.sh:69,148 (the proven-vs-built cross-reference), skills/close-the-loop.md migration,requirements,greenfield,existing-app-change report diff --git a/bin/lib/install-manifest.sh b/bin/lib/install-manifest.sh index 4bb29c67..3b831d7d 100644 --- a/bin/lib/install-manifest.sh +++ b/bin/lib/install-manifest.sh @@ -110,7 +110,7 @@ MXTK_AGENTS_STAGE_BUILD="mdl-agent.md gate-agent.md test-agent.md review-agent.m # own sibling, and the PostToolUse(Read) hook install-claude-permissions.sh writes runs # `bash bin/look-ledger.sh seen`. Two unattended builds built every page and opened no # screenshot of one; the Stage 5 gate now joins what this script records. -MXTK_PROJECT_BIN="assemble-prototype.js prototype-route.js check-prototype-links.js wf-add-path-terminators.py wf-set-call-captions.py mxunit_bson.py _common.sh app-facts.sh _claims.sh snapshot-mpr.sh restore-mpr.sh exec.sh look-ledger.sh save-sp.sh restart-sp.sh check-sp-health.sh verify-module.sh test-stack-up.sh fixture-manifest.sh check-root-clean.sh lint-gate.sh close-task.sh conformance-check.sh coverage-preflight.sh graph-sweep.sh review-module.sh coherence-cadence.sh build-plan-status.sh done-drift-check.sh page-scope.sh render-improvement-register.sh check-design-portability.sh check-design-reaches-app.sh check-page-shell.sh page-fidelity.js model-stamp.sh verify-model.sh install-project-hooks.sh session-check.sh constants-audit.sh context-pack.sh" +MXTK_PROJECT_BIN="assemble-prototype.js prototype-route.js check-prototype-links.js wf-add-path-terminators.py wf-set-call-captions.py mxunit_bson.py _common.sh app-facts.sh _claims.sh snapshot-mpr.sh restore-mpr.sh exec.sh look-ledger.sh save-sp.sh restart-sp.sh check-sp-health.sh verify-module.sh test-stack-up.sh fixture-manifest.sh check-root-clean.sh lint-gate.sh lint-trend.sh close-task.sh conformance-check.sh coverage-preflight.sh graph-sweep.sh review-module.sh coherence-cadence.sh build-plan-status.sh done-drift-check.sh page-scope.sh render-improvement-register.sh check-design-portability.sh check-design-reaches-app.sh check-page-shell.sh page-fidelity.js model-stamp.sh verify-model.sh install-project-hooks.sh session-check.sh constants-audit.sh context-pack.sh" # Files in project-bin/ that are deliberately NOT installed into projects. The reverse check # below flags anything named by NEITHER list, so a new file in project-bin/ has to be either diff --git a/bin/lib/obligation-check.sh b/bin/lib/obligation-check.sh index 7fde53e1..af7b25ac 100755 --- a/bin/lib/obligation-check.sh +++ b/bin/lib/obligation-check.sh @@ -246,9 +246,9 @@ _ob_has_denominator() { # "expired". _ob_stale() { local root="$1" hit="$2" hash n - hash="$(head -c 8000 "$hit" 2>/dev/null | tr -d '\r' \ + hash="$(cat "$hit" 2>/dev/null | tr -d '\r' \ | grep -oiE 'VALID[[:space:]]+AT:?[[:space:]]*[0-9a-f]{7,40}' \ - | grep -oiE '[0-9a-f]{7,40}$' | head -1)" + | grep -oiE '[0-9a-f]{7,40}$' | tail -1)" [ -n "$hash" ] || return 1 git -C "$root" cat-file -e "${hash}^{commit}" 2>/dev/null || return 1 n="$(git -C "$root" log --oneline "${hash}..HEAD" -- '*.mpr' mdlsource 2>/dev/null | wc -l | tr -d ' ')" @@ -303,9 +303,9 @@ _ob_look_proof_broken() { # VALID AT commit time, for freshness. Unresolvable → freshness unmeasured, not failed. local vhash vtime="" - vhash="$(head -c 8000 "$hit" 2>/dev/null | tr -d '\r' \ + vhash="$(cat "$hit" 2>/dev/null | tr -d '\r' \ | grep -oiE 'VALID[[:space:]]+AT:?[[:space:]]*[0-9a-f]{7,40}' \ - | grep -oiE '[0-9a-f]{7,40}$' | head -1)" + | grep -oiE '[0-9a-f]{7,40}$' | tail -1)" [ -n "$vhash" ] && vtime="$(git -C "$root" show -s --format=%ct "${vhash}^{commit}" 2>/dev/null || true)" reldir="$(dirname "$hit")" diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index cd670c84..c0fd385e 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -160,7 +160,9 @@ e2e-harness-base skills/e2e-harness-base.md Standing up or extending the Playwri learned-db-assertions skills/learned-db-assertions.md Writing DB assertion tests that cross-check UI state against the database test 6 ondemand verify fixture-seeding skills/fixture-seeding.md Establishing the data and identities a journey run needs — BEFORE it runs. Derive and measure with project-bin/fixture-manifest.sh first; interview only the residue, and never seed from inside the harness test,review 5,6 ondemand verify journey-proof skills/journey-proof.md Proving a module's user journey end-to-end — the deep form of step 3 PROVE; use whenever an instrument reports green and you cannot say what would have made it red test,review 5,6 ondemand verify +unhappy-path-testing skills/unhappy-path-testing.md Writing or running e2e cases beyond the happy journeys (blank/whitespace required fields, over-length, duplicates, cancel, double submit, wrong state, wrong role, backend down, stale write, empty search) — every case cites a requirement first, UNSPECIFIED is re-scored against house rules, GAP counts carry the denominator; runs after journey-proof and before monkey-test test,review 5,6 ondemand verify monkey-test skills/monkey-test.md Running the fuzz/crash net on a module whose journeys are already green — and reading the result, which is NOT evidence the module works test,review 5,6 ondemand verify +e2e-locators-that-lie skills/e2e-locators-that-lie.md An e2e rung reports a UI defect (widget never appeared, click did nothing, screen contradicts the data, fixture count wrong) — check the locator before blaming the app: :visible, click order, poll, derived fixtures test,review 5,6 ondemand diagnose learned-skill-ux-audit skills/learned-skill-ux-audit.md UX audit and screenshot-loop discipline review 6 ondemand verify learned-skill-scope-delta skills/learned-skill-scope-delta.md Tracking scope delta between the BRD and the built state review 6 ondemand verify company-brain skills/company-brain.md Setting up or wiring a COMPANY BRAIN — the private tier between the toolkit and a project for own skills, conventions, lint rules, MDL snippets and approved MPKs; and deciding whether something goes to the toolkit, the company brain or docs/brain/ all - ondemand spine diff --git a/bin/source-sufficiency.sh b/bin/source-sufficiency.sh index cb50d41e..016b2218 100755 --- a/bin/source-sufficiency.sh +++ b/bin/source-sufficiency.sh @@ -622,7 +622,7 @@ if html_out: src_list = ''.join(f'
  • {esc(os.path.basename(s))}
  • ' for s in sources) \ or '
  • no source files named
  • ' - page = f"""Source sufficiency — {esc(os.path.basename(os.environ.get('PROJECT_DIR','')))} + page = f"""Source sufficiency — {esc(os.path.basename(os.path.realpath(os.environ.get('PROJECT_DIR',''))))}