Skip to content

Commit 42b5540

Browse files
committed
1.0.0-gamma.6: repairable Full themes and patches (from 82f4edf)
1 parent 19fb131 commit 42b5540

23 files changed

Lines changed: 1954 additions & 57 deletions

‎.claude-plugin/marketplace.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"source": "./",
1515
"displayName": "html2wp — HTML to WordPress",
1616
"description": "BETA. Convert a static HTML site into a standalone WordPress block theme, verified against the original at three viewport widths — and reviewed by you before handover. The conversion runs through a verified Astro 5 build, so the Astro project is a real output too — compared pixel by pixel against the source before anything WordPress-shaped is generated.",
17-
"version": "1.0.0-gamma.5",
17+
"version": "1.0.0-gamma.6",
1818
"author": {
1919
"name": "BELNEM s.r.o.",
2020
"email": "hello@html2wp.dev",

‎.claude-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "html2wp",
33
"displayName": "html2wp — HTML to WordPress",
44
"description": "BETA. Convert a static HTML site — or anything that builds to one (Lovable, Bolt, v0, Next export) — into a standalone WordPress block theme: verified pixel by pixel, pages, blog, forms, menus, SEO and redirects, with no plugin required and verification gates at every stage. Two modes: Full (every gate repaired until green, then your own page-by-page review) and Flash (every stage once, no repair loop, the gates reported). The gates catch most fidelity drift, not all of it.",
5-
"version": "1.0.0-gamma.5",
5+
"version": "1.0.0-gamma.6",
66
"author": {
77
"name": "BELNEM s.r.o.",
88
"email": "hello@html2wp.dev",

‎.codex-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "html2wp",
3-
"version": "1.0.0-gamma.5",
3+
"version": "1.0.0-gamma.6",
44
"description": "Convert static HTML or buildable web projects into verified, editable WordPress block themes.",
55
"author": {
66
"name": "BELNEM s.r.o.",

‎VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.0-gamma.5
1+
1.0.0-gamma.6

‎skills/html2wp/SKILL.md‎

Lines changed: 149 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ generated. Two ways to answer it, and they are not interchangeable:
203203
site) → file it at `/v1/report`, then rebuild; the fix ships as a service
204204
update and everyone gets it;
205205
- **this one site is unusual** → patch the delivered theme locally, and write
206-
the patch into your notes, because the next `rebuild-theme.sh` overwrites it.
206+
the patch with `theme-patches.py`; the next server build replays it before
207+
replacing the working theme, and preserves the old theme on conflict.
207208

208209
## Beta — read this before you promise anything
209210

@@ -348,18 +349,153 @@ block editor is the editor they certify — so install it on the verification
348349
WordPress only after `gutenberg-verify-local.py` has passed, and check that it
349350
activates without an admin error.
350351

352+
## Full HTML: repair first, deliver the product
353+
354+
For **Full + HTML** this section takes precedence over later instructions
355+
that say a red gate stops the run, "until green", or "after the fix ships".
356+
Gutenberg and Astro keep their own contracts. A delivery is a usable theme
357+
ZIP with measured limitations; only all applicable checks passed means verified.
358+
359+
### Local theme patches (HTML)
360+
361+
Prefer a small repair in `{workspace}/theme/<slug>/` when the source proves
362+
what the output should do. Keep the installed converter, gate checkers and
363+
editor plugin unchanged. Before editing, open the normal counted repair
364+
and write its `repair-plan.json` (include the touched theme paths), then:
365+
366+
```bash
367+
python3 "$S/theme-patches.py" "$WS" begin --stage 5 --reason "<observed cause and intended fix>"
368+
# Edit only the affected theme files.
369+
python3 "$S/theme-patches.py" "$WS" record
370+
# Apply/install to the preview and run the affected checks through repair-check.py.
371+
python3 "$S/full-delivery.py" "$WS" package
372+
```
373+
374+
After delivery, normal owner changes can use the same begin/record protocol
375+
without a conversion repair attempt. Keep using `apply-change.py` for the
376+
preview and the owner's Make release flow for delivery; recording a patch
377+
does not publish or certify it. A Full repair turn still needs its counted
378+
attempt. If a patch is wrong, `theme-patches.py "$WS" abort` restores its
379+
before snapshot and retains the rejected edit in a backup. Record refuses
380+
broken PHP or removal of required theme files.
381+
382+
`theme-patches/` holds base hashes, content blobs, per-file changes, readable
383+
diffs, reasons and repair IDs outside the theme ZIP. `convert-remote.sh`
384+
automatically replays the composed changes on a fresh server theme. Separate
385+
text changes merge; overlapping edits, conflicting adds/deletes and binary
386+
changes stop promotion. The working theme and last ZIP remain intact.
387+
388+
For a conflict, run `theme-patches.py "$WS" status`. It names the files and
389+
the candidate directory under `theme-patches/`. Its `versions` lists the
390+
base/local/server blob hashes; inspect `theme-patches/blobs/<hash>` for the
391+
specific conflict instead of reading the whole ledger. That candidate includes clean
392+
merges and retains local versions at conflicted paths. Read the stored server
393+
and base blobs to decide the correct result; edit the candidate explicitly.
394+
Then, in a counted repair, run `theme-patches.py "$WS" resolve --stage 3
395+
--reason "<how the conflict was reconciled>"`. Resolve validates and promotes
396+
the entire candidate; it never means the visual/functional gates passed.
397+
Do not re-upload or recapture merely to retry the same conflict. If working
398+
files changed while resolving, preserve/reconcile those edits first.
399+
400+
All packaging paths refuse pending/conflicting patches and retain the last
401+
ZIP. Recorded patches with red quality gates can still use Full best-effort
402+
packaging. Unrecorded owner changes are captured before a rebuild or package
403+
as a preservation measure; begin/record remains the required repair workflow.
404+
Normal cleanup keeps the theme, ledger, exports and handover/repair state;
405+
Start over archives the ledger and old working theme. An older edited theme
406+
without a known server baseline requires explicit reconciliation before its
407+
first rebuild; never silently adopt it as pristine server output. On a
408+
conflict, the received server reports and menu metadata are retained with the
409+
candidate and completed transactionally by resolve.
410+
This implements output patching, not execution of modified converter copies.
411+
412+
1. Run every applicable check. On failure read the report and diagnose the
413+
actual cause. Use `progress.sh repair <stage> <lever> <signature>`:
414+
**at most 3 attempts per stage, 9 per run**. Named levers first, then
415+
`ai-fix` for diagnosed site repairs (including quality failures in Full).
416+
For each Full `ai-fix`, write `repair-plan.json` with `stage`, a concrete
417+
`hypothesis`, and workspace-relative `files` to change. Never repeat the
418+
same failed hypothesis on unchanged files. Do not edit the installed
419+
plugin or weaken a gate. Fix working input/manifest/runtime output where
420+
the original behavior provides evidence, then verify the affected result.
421+
Run the check through `repair-check.py {workspace} <stage> -- <command…>`;
422+
only its successful fresh receipt permits `repaired <stage> fixed`.
423+
For Woo, call `woo-repair.py` directly: it owns the attempt; do not wrap
424+
it in another `progress.sh repair`.
425+
2. If the remaining cause needs a converter release, report it and continue
426+
with its limitation. If no useful repair remains, do not burn the budget
427+
on identical reruns. A failed quality gate is not a reason to withhold
428+
the theme. Record it using `full-delivery.py {workspace} defer --stage S
429+
--reason "<measured failure and attempted fix>"`, then finish the remaining
430+
substeps of that stage and continue. A fix of one substep does not certify
431+
all the other substeps.
432+
3. For SPA stage -1, distinguish captured content from replay quality. With
433+
usable rendered HTML, pass `--capture <capture-dir>` to `defer` and continue
434+
stage 0 on that capture, keeping the red prerender report. Repair a
435+
captured interaction from the source's actual behavior where possible;
436+
`--gates-only` verifies it. Re-record only after a concrete upstream change
437+
that requires it, not because a long unchanged capture might work again.
438+
Missing routes remain reported. Never use a blank SPA shell as a product.
439+
4. After server assembly, **package at stage 3.5, before long WordPress
440+
checks**: `full-delivery.py {workspace} package`. It uses the existing
441+
theme packer, retains hard PHP/content/ZIP checks and records content or
442+
screenshot limitations. Keep that ZIP while testing and repairing later
443+
stages. A unavailable preview or failed smoke does not remove the ZIP.
444+
5. If assembly cannot produce the dynamic theme after repairs, first retain
445+
working capabilities and fix the failing manifest region. Last resort:
446+
`full-delivery.py {workspace} fallback --reason "<assembly error>"`.
447+
This preserves the requested manifest and all captured pages, and derives
448+
an explicitly static assembly plan. Run stage 1 and its invalidated
449+
dependencies, then the same licensed server transform and package. Only
450+
one fallback per run; it is delivery work, not another repair loop.
451+
Report which articles/products became ordinary pages and which functions
452+
remain unavailable. Never claim that the original had no blog or shop.
453+
6. Report/send gates exactly as measured and call `write-result.py` even
454+
when some remain red. A valid ZIP yields `delivered` with failed or
455+
missing checks and an explicit recovery action. `stopped` is reserved for
456+
no usable input, denied license/security, or no valid artifact after
457+
bounded recovery/fallback. Never fake a ZIP or bypass service controls.
458+
7. User may change model and choose **Repair remaining issues**. In
459+
`H2WP_MODE=repair-delivery`, first run `full-delivery.py {workspace} begin`.
460+
It retains the old ZIP and records this owner turn. Read `recovery.stages`
461+
from the saved result in `repair-session.json`. Each owner request has
462+
3 attempts; automatic continuation shares that budget. Preserve owner
463+
edits; snapshot/merge them before an upstream rebuild. Re-run only the
464+
dependencies invalidated by the repair, complete the checks and package
465+
again, then `write-result.py`. Run repaired candidate checks through
466+
`repair-check.py`: verification alone spends no repair attempt and can run
467+
without an open attempt. To close an editing attempt as fixed, run its
468+
check before closing it. Receipts bind the report to
469+
the checked manifest and theme/dist. A later patch invalidates that proof.
470+
For prerender repairs, use `--gates-only`; the receipt binds the original
471+
built dist and capture, without repeating recording. This applies to
472+
repairs during the initial Full run as well as owner repair turns.
473+
The previous ZIP stays available if no new
474+
valid artifact can be made. Normal post-delivery chat remains Change mode.
475+
476+
After changing a counted repair's inputs, authorize dependency rebuilds with
477+
`full-delivery.py {workspace} invalidate --stage <earliest affected stage>
478+
--reason "<changed input and dependent outputs>"`. This permits earlier
479+
stages in the SAME run, without `mode --new`. After all outstanding issues
480+
of one stage pass current checks, `full-delivery.py {workspace} resolve
481+
--stage S` removes its deferred warning using the successful repair receipt. It does not reset attempts. This exception also
482+
applies to owner recovery from a stopped Full run. In repair-stop mode use
483+
its counted attempts; if the capture/output is usable but still red, `defer`
484+
archives the old stop and continues. Never mark an entire stage done just
485+
because a single failed command was fixed.
486+
351487
## Choose the mode — Flash or Full
352488

353489
**Right after the output.** The same stages run in one of two modes:
354490

355491
| mode | what it is | for |
356492
|---|---|---|
357-
| **Full** | this file end to end: every gate, repaired until it is green, and the owner's page-by-page review | the hand-over run; the default in a CLI |
493+
| **Full** | this file end to end: every gate checked, bounded repairs, then the best usable ZIP with remaining issues reported | the hand-over run; the default in a CLI |
358494
| **Flash** | every stage ONCE, no repair loop. At stage 0 you identify the menus, the blog (posts and listing), the shop and every form, so each is wired and connectable in Visual Edit Lite; the gates run once and are REPORTED, not repaired | a converted, installed, honestly reported theme in about an hour; the desktop app's default |
359495
| **Astro** (`H2WP_MODE=astro`, `H2WP_TARGET=astro`) | the Astro 5 project only: stages -1 to 1 and gates A/A2, each once — no service, no WordPress, no theme | the owner who wants the static site or its source, not WordPress |
360496
| **Change** (`H2WP_MODE=change`) | after delivery: the owner's change made in the LIVE theme and applied to the running preview in seconds — never a new build (section "Changes after delivery") | every request once the project is delivered (`{workspace}/result.json` status `delivered`) |
361497

362-
- **`H2WP_MODE` set** (`flash`, `full`, `astro` or `change`): use it and do not ask. It wins over
498+
- **`H2WP_MODE` set** (`flash`, `full`, `astro`, `change`, `repair-stop` or `repair-delivery`): use it and do not ask. It wins over
363499
the wording of the request — a prompt cannot turn a Flash run into a repair
364500
loop. Any other value: stop and say so.
365501
- **Not set**: Flash only when the owner asked for it ("Flash", "one pass",
@@ -376,7 +512,7 @@ activates without an admin error.
376512
then `assets/scripts/write-result.py {workspace} --status stopped --stopped-stage -4 --stopped-reason "<the same words>"`.
377513
- A **Continue** keeps the mode the run started with (`{workspace}/.h2wp-mode`)
378514
and does NOT call `progress.sh mode` again: it resumes at the stage
379-
`progress.json` names and never redoes a finished stage. `progress.sh mode`
515+
`progress.json` names and redoes only dependencies invalidated by a documented Full repair. `progress.sh mode`
380516
starts a NEW run — the last run's progress is kept beside it and every stage
381517
starts pending (the owner asked for Flash again, or Full after Flash). Over
382518
a run still in progress (a Stop leaves one) it refuses with exit 3 and names
@@ -698,11 +834,11 @@ message (and Continue) on a stopped run comes with `H2WP_MODE=repair-stop`.
698834
```
699835

700836
3. **Fixed → continue from that stage.** A fixed attempt puts the stopped
701-
result aside, so the run is running again: `progress.sh done <stage>`, then
702-
every stage after it exactly as its mode's table says, to the end —
837+
result aside, so the run is running again: finish every remaining substep
838+
of that stage before `progress.sh done <stage>`, then every stage after it exactly as its mode's table says, to the end —
703839
stage 6.5 and `write-result.py` included (Flash: the "Flash mode" table;
704840
the mode is the one the run started with, whatever `H2WP_MODE` says about
705-
the turn). Never the stages before it, never `mode`, never a new run.
841+
the turn). In Full, rebuild invalidated dependencies when needed; never `mode` or a new run.
706842
4. **Not fixed** (the message's 3 attempts are spent): write the stop
707843
again — `write-result.py {workspace} --status stopped --stopped-stage
708844
<stage> --stopped-reason "<why>"` — and tell the owner plainly what could
@@ -2969,16 +3105,15 @@ What to do instead, in order:
29693105
in the README under Status), or an issue on the plugin's own repository. A fix there ships to everybody as
29703106
a release, which is the only way a plugin fix is allowed to reach a site.
29713107

2972-
The same rule holds for the delivered theme: hand-editing it is pointless
2973-
because the next rebuild erases it. The difference is that a theme edit only
2974-
costs you the edit, while a plugin edit costs the product its integrity.
3108+
The delivered HTML theme can be repaired locally using the patch ledger
3109+
above. Server rebuilds replay recorded repairs before replacing it. The
3110+
editor plugin remains a separately released product.
29753111

29763112
A delivered site comes back as REPORTS: "this should be a collection", "the
29773113
canonical points somewhere odd", "photos flash when I switch pages". The
2978-
repair is almost never a re-conversion — but it is also never a hand-edit of
2979-
the delivered theme, because the next rebuild would erase it. Every repair
2980-
lands in the manifest, or in a defect report to the service, and re-runs the
2981-
SHORTEST suffix of the pipeline that can carry it out.
3114+
repair is usually a local theme patch or a manifest repair. Use the shortest
3115+
affected part of the pipeline; report a general generator defect as well.
3116+
Preserve the output patch until the server fixes the underlying cause.
29823117

29833118
**First, reproduce against the thing the owner actually uses.** Two traps,
29843119
both measured on one repair campaign:

‎skills/html2wp/VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.0-gamma.5
1+
1.0.0-gamma.6

‎skills/html2wp/assets/scripts/cleanup.sh‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ keep() { # a name is kept if it matches anything in the keep list
130130
case "$1" in
131131
conversion-manifest.json|astro-report.json|FINDINGS.md|visual-review) return 0 ;;
132132
block-plan|.gutenberg|gutenberg-routes.json) return 0 ;;
133+
theme|theme-patches|theme-patches-*) return 0 ;; # editable output and replay/rollback history
134+
result.json|progress.json|changes.json|.theme-applied.json|out) return 0 ;;
135+
repair-session.json|repair-history|repair-plan.json|repair-check*.json|repair-invalidated.json) return 0 ;;
136+
delivery-issues.json|delivery-artifact.json|requested-manifest.json|fallback-manifest.json) return 0 ;;
133137
astro-project) return 0 ;; # pruned rather than kept whole — see below
134138
esac
135139
return 1

‎skills/html2wp/assets/scripts/convert-remote.sh‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -912,10 +912,25 @@ if [ -n "$BAD" ]; then
912912
"refusing the archive: it contains links or special files, which a theme does not" \
913913
"the service that answered is not one this client will unpack from"
914914
fi
915-
# -R and not `mv`, so an existing theme directory is updated rather than
916-
# replaced wholesale — a re-run must not delete anything the owner added.
917-
cp -R "$STAGE"/. "$WS"/
918915
SLUG="$(json_field "$TMP/result.json" slug)"
916+
# Replay local HTML repairs on the sanitized server output BEFORE touching
917+
# the working theme. A conflict preserves the working tree and last ZIP.
918+
if [ "$(python3 -c 'import json,sys; m=json.load(open(sys.argv[1])); print(m.get("schema", "html2wp/1"))' "$WS/conversion-manifest.json")" != "html2wp/2" ]; then
919+
if ! python3 "$SCRIPT_DIR/theme-patches.py" "$WS" integrate --theme "$STAGE/theme/$SLUG"; then
920+
fail_with LOCAL_PATCH_CONFLICT local-patches \
921+
"server theme retained as a patch candidate; the previous working theme and ZIP are unchanged" \
922+
"inspect theme-patches status; resolve the conflict with a counted repair, then verify and package"
923+
fi
924+
# Theme promotion above is a replacement: removed generated files must not
925+
# survive as stale files. Copy only the remaining server reports here.
926+
for artifact in "$STAGE"/* "$STAGE"/.[!.]*; do
927+
[ -e "$artifact" ] || continue
928+
[ "$(basename "$artifact")" = theme ] && continue
929+
cp -R "$artifact" "$WS"/
930+
done
931+
else
932+
cp -R "$STAGE"/. "$WS"/
933+
fi
919934
echo "theme unpacked: $WS/theme/$SLUG"
920935

921936
# The generator stamps every menu zone it located with data-ve-nav="n" and

0 commit comments

Comments
 (0)