diff --git a/CHANGELOG.md b/CHANGELOG.md index 297a46b..6caa4e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## Unreleased + +- Integrated native verification corrective loop: + - Created `manual-skills/create-verification-skill/references/verification-loop.md` with explicit When, Corrective action, and Evidence ledger protocols (requiring mechanical `FACT:` rows and banning test weakening). + - Updated `manual-skills/create-verification-skill/SKILL.md` to require executing the corrective loop before claiming PASS and copying it into generated skill failure handling. + - Added Corrective loop section to `rules/01-verification.md` referencing the verification loop protocol and anti-test-weakening constraints. + - Added exploratory visual layout shift (CLS) observation recipe in `skills/playwright-qa/references/cls.md` and linked reference in `skills/playwright-qa/SKILL.md` while strictly preserving the 200-newline budget. + - Documented `/chrome-devtools-axi` diagnostic entry point for observed layout shifts via `127.0.0.1:9223`. +- Added 18-second brag launch video card pattern to HyperFrames: + - Created `skills/hyperframes/references/brag.md` specifying an 18-second, 60fps, 1080p deterministic video recipe across 4 narrative beats (Hook & Problem, Solution Reveal, Capability Highlight, Call to Action) with local-only assets and target output `brag-output/brag.mp4`. + - Updated `skills/hyperframes/SKILL.md` and `skills/hyperframes/NOTICE.md` to reference the brag card recipe. + - Updated `rules/00-routing.md` under `video_html` and `README.md` to route short launch video cards to `hyperframes` via `references/brag.md`. +- Implemented optional Crawl4AI web extraction MCP: + - Registered `crawl4ai` as an optional `FOREIGN_ON_DEMAND` remote MCP (`http://127.0.0.1:11235/mcp`; `--cloud` using `https://api.crawl4ai.com/mcp` with `{env:CRAWL4AI_KEY}`) via `opencode-he crawl4ai enable [--cloud]` and `opencode-he crawl4ai disable`. + - Added strict doctor validation in `lib/doctor.py`: enforces `127.0.0.1:11235` local binding, rejects `0.0.0.0` or invalid URLs, prevents raw secret keys on cloud endpoints, and reports `OPTIONAL_ABSENT` when unconfigured. + - Added CLI options in `lib/cli.py` and enablement handlers in `lib/install.py`. + - Added doctor unit tests in `tests/test_doctor.py` covering missing, zero-bind, invalid URL, cloud missing token, cloud raw secret, valid local, valid cloud, and enable/disable flows. + - Documented configuration and boundaries in `docs/mcp.md`, `docs/CATALOG-FREEZE.md`, `docs/source-wave.md`, `docs/troubleshooting.md`, and `README.md` (documenting Scrapling as an unmanaged pointer and retaining Agent-Reach as rejected). + - Maintained frozen catalog of 62 skills (47 model-invoked, 15 manual slash commands) with zero new skill names. Product version remains 0.1.4. + ## 0.1.4 — 2026-09-23 - Upgraded `codebase-memory-mcp` pin to v0.11.0 with SHA-256 verified portable tarball download, and added automatic `--format json` argument propagation in `lib/cbm.py` for reliable JSON extraction across project listing and status commands. diff --git a/README.md b/README.md index 184c6e1..25c4e30 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Default: repository evidence first. Then at most one specialist. | Scroll-led storytelling | `scroll-craft` | | Scroll-driven 3D / camera world | `scroll-world` | | Procedural Three.js object from image | `img2threejs` | -| Deterministic HTML composition video | `hyperframes` | +| Deterministic HTML composition video | `hyperframes` (18s brag card via `references/brag.md`) | | Demo video aplikasi & narasi ID | `id-demo-video` (`/demo-video`) | | Browser | `playwright-qa` (isolated verification edge; not builder self-attest) → `browser-act` → `chrome-devtools-axi` → `click-path-audit` | | Documents (PDF/DOCX/answer/extract/review) | `smartdoc` | @@ -178,6 +178,7 @@ Optional: - `reticle` — `opencode-he reticle enable` registers Reticle as a local perception server (`npx -y @reticlehq/server mcp`). `FOREIGN_ON_DEMAND`. Server package is FSL-1.1-ALv2 (competing-use clause); SDK packages (Apache-2.0) are not vendored. Never an auto-implementer; default verification remains `playwright-qa` / `chrome-devtools-axi`. `opencode-he reticle disable` removes only that server key. Absent is not a `doctor` failure; a malformed entry fails closed. - `ui-skills` — `opencode-he ui-skills enable` registers UI Skills (`https://www.ui-skills.com/mcp`) as an optional remote MCP server. `FOREIGN_ON_DEMAND` for design-skill lookup only. Product UI remains Design Bank + Impeccable + Design V2 atoms + shadcn; `BANK_MISS` never generates from a random ui-skills document. `opencode-he ui-skills disable` removes only that server key. Absent is not a `doctor` failure; a malformed entry fails closed. - `markitdown` — `opencode-he markitdown enable` registers MarkItDown as a local stdio ingest converter (`uvx --from markitdown-mcp==0.1.8 markitdown-mcp`). `FOREIGN_ON_DEMAND`. Local trusted agents only; never `--http` / `0.0.0.0` / docker bind-all. Output is Markdown data; SmartDoc keeps contract/QA/render. `opencode-he markitdown disable` removes only that server key. Absent is not a `doctor` failure; a malformed entry fails closed. +- `crawl4ai` — `opencode-he crawl4ai enable` registers Crawl4AI as an optional web content extraction remote MCP (`http://127.0.0.1:11235/mcp`; `--cloud` registers `https://api.crawl4ai.com/mcp` with `{env:CRAWL4AI_KEY}`). `FOREIGN_ON_DEMAND`. Docker users bind `127.0.0.1:11235`, never `0.0.0.0`. Web content extraction only; not an exploratory QA tool (`playwright-qa` remains default). `opencode-he crawl4ai disable` removes only that server key. Absent is not a `doctor` failure; a malformed entry (or `0.0.0.0`) fails closed. - `jev-mcp` — TypeSafe Jev / `jkudish/jev-mcp` is intentionally SKIPPED as a required runtime MCP; core verification and done-gates operate offline without external server dependencies. - `exa` — `FOREIGN_ON_DEMAND`; installer never adds, removes, or overwrites it diff --git a/docs/CATALOG-FREEZE.md b/docs/CATALOG-FREEZE.md index 951666d..a0820dc 100644 --- a/docs/CATALOG-FREEZE.md +++ b/docs/CATALOG-FREEZE.md @@ -7,7 +7,7 @@ This contract defines the immutable boundary and governance for the OpenCodeHigh - **Retired in this wave and not to be revived**: `ask-matt`, `grilling`, `wait-what`, `matt-implement`. - **Kept on purpose**: `wizard` (target-app bash wizard), `codebase-design` (new module), `/improve-codebase-architecture` (scan + HTML report). - **Name collision remains**: `install-anti-slop` = Oxlint; UI/copy filter lives in `impeccable` taste-gate + `humanizer`; `/unslop` = `humanizer`. -- **FOREIGN_ON_DEMAND stays out of the overlay**: `ECC`, `noodle`, `serena`, `stitch`, `reticle`, `ui-skills` MCP, `markitdown` MCP, `exa`, `Caliper`, `SkillEvaluator`. `doctor` must not fail when they are absent. +- **FOREIGN_ON_DEMAND stays out of the overlay**: `ECC`, `noodle`, `serena`, `stitch`, `reticle`, `ui-skills` MCP, `markitdown` MCP, `crawl4ai` MCP, `exa`, `Caliper`, `SkillEvaluator`. `doctor` must not fail when they are absent. - **No new allowlist name without retiring one existing name in the same change.** - **No padding back to 64.** - **No `/how`, `/poteto-mode`, `/antislop`, `taste-skill`, `axi-core`, `human-atlas`, `awesome-design-md` vendor.** diff --git a/docs/mcp.md b/docs/mcp.md index a8fb0b2..1eaf1d5 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -17,6 +17,7 @@ Optional: - `reticle` — `opencode-he reticle enable` (local stdio via `npx -y @reticlehq/server mcp`; perception only, never auto-implementer) - `ui-skills` — `opencode-he ui-skills enable` (remote HTTP `https://www.ui-skills.com/mcp`; design-skill lookup only) - `markitdown` — `opencode-he markitdown enable` (local stdio via `uvx --from markitdown-mcp==0.1.8 markitdown-mcp`; Markdown ingest only) +- `crawl4ai` — `opencode-he crawl4ai enable` (remote HTTP `http://127.0.0.1:11235/mcp`; cloud via `--cloud` with `{env:CRAWL4AI_KEY}`) - `exa` — foreign; never add/remove/overwrite Merge is parse-aware. Comment-free JSON is rewritten with `json.dumps`. JSONC with comments is patched surgically (owned MCP keys only). If surgical merge cannot be verified, install fails closed instead of destroying comments. @@ -33,8 +34,11 @@ Doctor reports `CONFIGURED` for owned MCP entries present in config. That is not `opencode-he markitdown enable` configures MarkItDown as an optional local stdio ingest MCP (`uvx --from markitdown-mcp==0.1.8 markitdown-mcp`). It is `FOREIGN_ON_DEMAND`. Official server is for local trusted agents only; never `--http`, never bind `0.0.0.0`, never docker bind-all. The converter is not vendored into `lib/`. Missing `uvx` is documented in the skill (CLI/`pipx`/`enable`); enable still writes the stdio command like reticle. `opencode-he markitdown disable` surgically removes only the markitdown server key. Absent is not a doctor failure; a malformed entry (including `--http` / `0.0.0.0`) fails closed. +`opencode-he crawl4ai enable` configures Crawl4AI as an optional web content extraction remote MCP (`http://127.0.0.1:11235/mcp`). It is `FOREIGN_ON_DEMAND` for content extraction, not exploratory browser QA (which remains `playwright-qa`). For Docker users, bind strictly to `127.0.0.1:11235` (e.g. `docker run -p 127.0.0.1:11235:11235 ...`); never bind `0.0.0.0`. OpenCodeHighEnd does not launch or manage the container. With `--cloud`, it configures `https://api.crawl4ai.com/mcp` using `{env:CRAWL4AI_KEY}` without writing secrets to disk. `opencode-he crawl4ai disable` surgically removes only the crawl4ai server key. Absent is not a doctor failure; binding to `0.0.0.0` or invalid URLs fails closed. + ## Evaluated, Skipped & Rejected +- **Scrapling** — Evaluated against Crawl4AI and documented as an unmanaged alternative pointer; not registered as an MCP server, CLI command, or skill to avoid redundant surface. - **TypeSafe Jev (`jev-mcp`)** — TypeSafe Jev / `jkudish/jev-mcp` was evaluated and is intentionally **SKIPPED** as a required runtime MCP. It is not vendored and not bundled in core MCPs. If ever manually configured by a user, it remains optional `FOREIGN_ON_DEMAND` only with `{env:TYPESAFE_API_KEY}`. Core verification, done-gates, and evidence ledgers operate fully offline without external Jev services. - **Agent-Reach (`Panniantong/Agent-Reach`)** — Agent-Reach was evaluated and is strictly **REJECTED** as a core MCP or skill. It is not an alternative to Playwright QA eyes, carries ToS/cookie/account risks, and relies on Exa which is already designated `FOREIGN_ON_DEMAND`. Never register Agent-Reach as a core or required tool. - **TypeSafe MCP (`itsmostafa/typesafe-mcp`)** — Evaluated and **REJECTED** as an extra core MCP. Core MCPs remain strictly `codebase-memory-mcp`, `context7`, and `shadcn`. diff --git a/docs/source-wave.md b/docs/source-wave.md index 6370ee0..708a0f4 100644 --- a/docs/source-wave.md +++ b/docs/source-wave.md @@ -42,3 +42,5 @@ Recorded per Phase 0 contract. | [cathrynlavery/diagram-design](https://github.com/cathrynlavery/diagram-design) | `dc1ace47b99a` (v2.6.33) | Editorial HTML/SVG diagram design. MIT. | **PIN_ONLY** | `skills/diagram-design` | Pinned commit `dc1ace47b99a` in `vendor/sources.json`. Skill body unchanged. | | [blader/humanizer](https://github.com/blader/humanizer) | `3.0.0` | AI prose humanizing and slop removal. MIT. | **PIN_ONLY** | `skills/humanizer` | Pinned v3.0.0 in `vendor/sources.json`. Skill body unchanged. | | [semgrep / gitleaks / osv-scanner](https://github.com) | `semgrep` 1.177.0, `gitleaks` 8.30.1, `osv-scanner` 2.6.0 | Host security scanners. | **PIN_ONLY** | `skills/full-audit-keamanan` | Host scanner version pins recorded in `vendor/sources.json`. | +| [unclecode/crawl4ai](https://github.com/unclecode/crawl4ai) | upstream ref | LLM-friendly web crawler & scraper MCP. Apache-2.0. | **FOREIGN_ON_DEMAND** | `docs/mcp.md` | Optional remote MCP (`http://127.0.0.1:11235/mcp`, cloud via `--cloud`). Not vendored. Content extraction only, not exploratory QA eyes (`playwright-qa`). Bind strictly 127.0.0.1, never 0.0.0.0. | +| [D4Vinci/Scrapling](https://github.com/D4Vinci/Scrapling) | upstream ref | Undetectable web scraping library. | **POINTER_ONLY** | `docs/mcp.md` | Evaluated against Crawl4AI. Kept as documentation pointer only; not registered as MCP, CLI command, or skill. | diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 57572e5..5047219 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -26,6 +26,8 @@ Doctor `OPTIONAL_ABSENT` is not a core failure. `DEGRADED` is non-fatal unless `doctor --strict`. `EMPTY Design V2` means no user bank yet — not a failure. `DEGRADED_FTS` means JSONL search works without SQLite FTS. +`FAIL mcp:crawl4ai` — Crawl4AI must bind to `http://127.0.0.1:11235/mcp` (or cloud `https://api.crawl4ai.com/mcp` with `{env:CRAWL4AI_KEY}`). Binding to `0.0.0.0`, using raw secret keys, or using non-standard URLs triggers a FAIL. Reconfigure with `opencode-he crawl4ai enable` (or `--cloud`). + `doctor --deep` exit 1 with `NOT_CHECKED` — `opencode mcp list` failed or was empty; core MCP is not proven live. Restart OpenCode after install. diff --git a/lib/cli.py b/lib/cli.py index d9d1fb1..6632647 100755 --- a/lib/cli.py +++ b/lib/cli.py @@ -24,6 +24,8 @@ cmd_install, cmd_restore, cmd_restore_list, + cmd_crawl4ai_disable, + cmd_crawl4ai_enable, cmd_markitdown_disable, cmd_markitdown_enable, cmd_reticle_disable, @@ -119,6 +121,10 @@ def build_parser() -> argparse.ArgumentParser: uis = sub.add_parser("ui-skills", help="optional UI Skills remote MCP") uis.add_argument("action", choices=["enable", "disable"]) + c4 = sub.add_parser("crawl4ai", help="optional Crawl4AI web extraction MCP") + c4.add_argument("action", choices=["enable", "disable"]) + c4.add_argument("--cloud", action="store_true", help="use cloud endpoint with CRAWL4AI_KEY instead of local container") + sd = sub.add_parser("smartdoc", help="document profiles, extract, status") add_smartdoc_cli(sd) sb = sub.add_parser("smartbook", help="reusable SmartBook lifecycle") @@ -190,6 +196,10 @@ def main(argv: list[str] | None = None) -> int: if args.action == "enable": return cmd_ui_skills_enable() return cmd_ui_skills_disable() + if cmd == "crawl4ai": + if args.action == "enable": + return cmd_crawl4ai_enable(cloud=args.cloud) + return cmd_crawl4ai_disable() if cmd == "smartdoc": return dispatch_smartdoc(args) if cmd == "smartbook": diff --git a/lib/doctor.py b/lib/doctor.py index 21d1dde..65ddeae 100644 --- a/lib/doctor.py +++ b/lib/doctor.py @@ -118,14 +118,14 @@ def mcp_status_map() -> dict[str, str]: try: data = jsonc.load_path(cfg) except (OSError, json.JSONDecodeError, ValueError): - return {k: "FAIL" for k in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "reticle", "ui-skills", "markitdown", "exa")} + return {k: "FAIL" for k in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "reticle", "ui-skills", "markitdown", "crawl4ai", "exa")} mcp = data.get("mcp") or {} if not isinstance(mcp, dict): - return {k: "FAIL" for k in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "reticle", "ui-skills", "markitdown", "exa")} + return {k: "FAIL" for k in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "reticle", "ui-skills", "markitdown", "crawl4ai", "exa")} servers = jsonc.mcp_servers_from_config(data) owned = {"codebase-memory-mcp", "context7", "shadcn"} - optional = {"serena", "stitch", "reticle", "ui-skills", "markitdown", "exa"} - for name in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "reticle", "ui-skills", "markitdown", "exa"): + optional = {"serena", "stitch", "reticle", "ui-skills", "markitdown", "crawl4ai", "exa"} + for name in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "reticle", "ui-skills", "markitdown", "crawl4ai", "exa"): spec = servers.get(name) if spec is None: out[name] = "OPTIONAL_ABSENT" if name in optional else "FAIL" @@ -190,6 +190,35 @@ def mcp_status_map() -> dict[str, str]: continue out[name] = "CONFIGURED" continue + if name == "crawl4ai": + typ = spec.get("type") + url = spec.get("url") + cmd = spec.get("command") + joined = " ".join(str(part) for part in cmd) if isinstance(cmd, list) else str(cmd or "") + url_str = str(url or "") + if "0.0.0.0" in url_str or "0.0.0.0" in joined: + out[name] = "FAIL" + continue + if typ != "remote": + out[name] = "FAIL" + continue + if url not in ("http://127.0.0.1:11235/mcp", "https://api.crawl4ai.com/mcp"): + out[name] = "FAIL" + continue + headers = spec.get("headers") + if url == "https://api.crawl4ai.com/mcp": + if not isinstance(headers, dict) or not headers: + out[name] = "FAIL" + continue + if not all("{env:CRAWL4AI_KEY}" in str(v) for v in headers.values()): + out[name] = "FAIL" + continue + else: + if headers is not None and not isinstance(headers, dict): + out[name] = "FAIL" + continue + out[name] = "CONFIGURED" + continue if name not in owned: out[name] = "FOREIGN" continue diff --git a/lib/install.py b/lib/install.py index 6d506ed..75159df 100644 --- a/lib/install.py +++ b/lib/install.py @@ -972,7 +972,7 @@ def take(src: Path, dest: Path) -> None: "modelInvokedSkills": meta["model"], "manualSkills": meta["manual"], "ownedMcp": list(OWNED_MCP), - "optionalMcp": ["serena", "stitch", "reticle", "ui-skills", "markitdown", "exa"], + "optionalMcp": ["serena", "stitch", "reticle", "ui-skills", "markitdown", "crawl4ai", "exa"], "designBank": { "root": bank_root, "source": bank_source, @@ -1509,3 +1509,33 @@ def cmd_ui_skills_enable() -> int: def cmd_ui_skills_disable() -> int: return _optional_mcp_disable("ui-skills") + +def cmd_crawl4ai_enable(cloud: bool = False) -> int: + if cloud and not os.environ.get("CRAWL4AI_KEY", "").strip(): + die("CRAWL4AI_KEY environment variable is empty (set CRAWL4AI_KEY to use --cloud)") + if cloud: + spec: dict[str, object] = { + "type": "remote", + "url": "https://api.crawl4ai.com/mcp", + "headers": { + "Authorization": "Bearer {env:CRAWL4AI_KEY}", + }, + "disabled": False, + } + else: + spec = { + "type": "remote", + "url": "http://127.0.0.1:11235/mcp", + "disabled": False, + } + return _optional_mcp_enable( + "crawl4ai", + spec, + already_present_msg="crawl4ai MCP already present; not overwriting (run `crawl4ai disable` first to change endpoint)", + ) + + +def cmd_crawl4ai_disable() -> int: + return _optional_mcp_disable("crawl4ai") + + diff --git a/manual-skills/create-verification-skill/SKILL.md b/manual-skills/create-verification-skill/SKILL.md index fdc0079..2c3c979 100644 --- a/manual-skills/create-verification-skill/SKILL.md +++ b/manual-skills/create-verification-skill/SKILL.md @@ -37,7 +37,7 @@ Create `.opencode/skills/verify-/features/README.md` plus one file per user ## 4. Prove the generated skill before handing it over -Run its own instructions end to end once: launch, doctor, drive ONE mapped feature (one is enough; the map exists so later runs can cover the rest), capture evidence, clean up. After cleanup, confirm the evidence still exists at the named location — a cleanup that eats the proof fails this step. Fix what fails, and run the generated cleanup after every failed iteration too, so broken attempts don't strand processes and ports. A generated skill that was never executed is a **DRAFT**, not a PASS. Do not claim PASS until that live run succeeded. +Run its own instructions end to end once: launch, doctor, drive ONE mapped feature (one is enough; the map exists so later runs can cover the rest), capture evidence, clean up. After cleanup, confirm the evidence still exists at the named location — a cleanup that eats the proof fails this step. Fix what fails, and run the generated cleanup after every failed iteration too, so broken attempts don't strand processes and ports. Enforce the corrective loop in [`references/verification-loop.md`](references/verification-loop.md): if an assertion fails or evidence is missing, fix the product or the harness; never weaken tests, skip assertions, or drop checks to force a PASS. Copy this loop into the generated skill's failure section. A generated skill that was never executed is a **DRAFT**, not a PASS. Do not claim PASS until that live run succeeded. ## 5. Offer the maintenance loop diff --git a/manual-skills/create-verification-skill/references/verification-loop.md b/manual-skills/create-verification-skill/references/verification-loop.md new file mode 100644 index 0000000..a9decf6 --- /dev/null +++ b/manual-skills/create-verification-skill/references/verification-loop.md @@ -0,0 +1,20 @@ +# Verification loop + +## When + +Activate this loop when a required verification check fails, proof evidence is missing or lost after teardown, or when there is temptation to weaken a test or harness assertion to turn a check green. + +## Corrective action + +1. Treat failures as signals: diagnose whether the defect lies in product behavior or harness configuration. +2. Fix the product code or update the test harness so that it accurately exercises the user path. +3. Re-run verification from the beginning (launch, doctor, drive, observe, cleanup). +4. Never weaken tests: do not delete tests, mark failing tests with skip/xfail, loosen threshold assertions, comment out checks, or bypass the user path with internal setters just to force a PASS. + +## Evidence + +Record mechanical proof in the evidence ledger: +- Every completed check requires a single verifiable line starting with `FACT:` containing the exact command run, exit code, and artifact path (e.g. `FACT: exit 0`, `FACT: artifacts/verify.png`). +- A screenshot or transcript is a `FACT` only if the artifact file physically exists at the path named by the verification skill after cleanup finishes. +- `JUDGMENT:` entries are advisory only and cannot substitute for or override missing or failing `FACT:` rows. +- A missing verification command is recorded as `NOT_CONFIGURED`, never as `PASS`. diff --git a/rules/00-routing.md b/rules/00-routing.md index 083cde2..0faa639 100644 --- a/rules/00-routing.md +++ b/rules/00-routing.md @@ -139,7 +139,7 @@ The specialist architecture forms a deterministic graph connected by file artifa - Scroll-led storytelling (scroll is the timeline, scrollytelling, signature interaction): `/scroll-craft`. Ordinary scrollable UI stays `/impeccable`. `/scroll-craft` plus Continuous World: Scroll Craft writes the brief, then `/scroll-world`. - Continuous camera fly-through, diorama, or 3D-world landing: `/scroll-world` even if the request says scroll. - Procedural Three.js object from image: `/img2threejs` (factory Group, editable primitives, visible vs inferred spec). Not `scroll-world` (camera fly-through), not `hyperframes` (HTML-to-MP4), not `visual-studio` (photoreal stills/media), and not `impeccable` (product UI). -- Deterministic HTML composition rendered to video: `/hyperframes` (headless Chrome + FFmpeg). Not `visual-studio`, not `emil-design-eng`. Ordinary scrollable UI stays `/impeccable`. +- Deterministic HTML composition rendered to video: `/hyperframes` (headless Chrome + FFmpeg; 18s brag/launch cards via `references/brag.md`). Not `visual-studio`, not `emil-design-eng`. Ordinary scrollable UI stays `/impeccable`. - Demo video aplikasi, walkthrough layar, narasi Indonesia, demo lomba: skill `id-demo-video` (bukan `hyperframes` untuk durasi panjang utuh, bukan `playwright-qa`, bukan `visual-studio`). Kartu judul HTML→MP4 tetap `hyperframes`. - Photoreal stills / ads / identity with no UI surface: `/visual-studio`. - Motion after Impeccable: `/emil-design-eng`. diff --git a/rules/01-verification.md b/rules/01-verification.md index a7c8049..500f277 100644 --- a/rules/01-verification.md +++ b/rules/01-verification.md @@ -62,3 +62,10 @@ Jev/Canny-shaped policy without calling Jev: Do not claim done while a required configured gate is unrun. Missing project command = `NOT_CONFIGURED`, not PASS. Distinguish mechanical facts (typecheck, tests, build actually ran) from reasoning quality (in-session review or `/matt-code-review`). Do not install Claude Code Stop hooks, learning-library mtime gates, or disk-space blockers. Those remain `FOREIGN_ON_DEMAND`. + +## Corrective loop + +When a required check in the active verification profile fails, proof evidence is missing, or assertions fail, follow the corrective loop (see `manual-skills/create-verification-skill/references/verification-loop.md`): +1. Fix the product code or harness recipe, then rerun the verification pass from scratch. +2. Anti-test-weakening: never delete assertions, add skip/xfail tags, widen numeric thresholds, or substitute internal setters to manufacture an artificial PASS. +3. Record mechanical `FACT:` rows for reruns. A missing verification command remains `NOT_CONFIGURED`, not PASS. diff --git a/skills/chrome-devtools-axi/SKILL.md b/skills/chrome-devtools-axi/SKILL.md index 452825e..7d024e1 100644 --- a/skills/chrome-devtools-axi/SKILL.md +++ b/skills/chrome-devtools-axi/SKILL.md @@ -30,7 +30,9 @@ If chrome-devtools-axi output shows a follow-up command starting with `chrome-de ## When to use -Use chrome-devtools-axi after an observed browser issue needs diagnostics: click, form, console, or network. Start `opencode-chromium-cdp` first and attach with `CHROME_DEVTOOLS_AXI_BROWSER_URL`. +Use chrome-devtools-axi after an observed browser issue needs diagnostics: click, form, console, network, or observed layout shift (CLS). Start `opencode-chromium-cdp` first (`127.0.0.1:9223`) and attach with `CHROME_DEVTOOLS_AXI_BROWSER_URL`. + +For layout shift diagnostics after a visual shift is observed, profile with `lighthouse` or `perf-start` / `perf-stop` on `127.0.0.1:9223`. Never fall back to Google Chrome. Do not treat this as a fifth browser door; it remains door 3 diagnostics. Skip it when a plain `fetch`/`curl` or `web_fetch` suffices. Do not use it for exploratory multi-role QA (`/browser-act`) and do not fall back to Google Chrome. diff --git a/skills/hyperframes/NOTICE.md b/skills/hyperframes/NOTICE.md index 23336ff..6557600 100644 --- a/skills/hyperframes/NOTICE.md +++ b/skills/hyperframes/NOTICE.md @@ -13,4 +13,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -Integrated into OpenCodeHighEnd as a single deterministic HTML-to-video specialist with local CLI/Chrome/FFmpeg execution boundaries. +Integrated into OpenCodeHighEnd as a single deterministic HTML-to-video specialist with local CLI/Chrome/FFmpeg execution boundaries. Includes first-party deterministic 18-second brag card launch video recipes and offline rendering workflows. diff --git a/skills/hyperframes/SKILL.md b/skills/hyperframes/SKILL.md index 4664b1a..7f64cc6 100644 --- a/skills/hyperframes/SKILL.md +++ b/skills/hyperframes/SKILL.md @@ -39,6 +39,7 @@ Load the specific reference required for the task: - [references/composition.md](references/composition.md) — HTML composition layout, aspect ratios, seekable timeline contracts. - [references/render.md](references/render.md) — Headless Chrome capture, frame stepping, FFmpeg encoding parameters. - [references/workflows.md](references/workflows.md) — Workflow archetypes (product launch, animated explainer, motion graphics, data video). +- [references/brag.md](references/brag.md) — 18-second product launch / brag card recipe, local assets, exact output path. ## Hard Rules diff --git a/skills/hyperframes/references/brag.md b/skills/hyperframes/references/brag.md new file mode 100644 index 0000000..0a6f03a --- /dev/null +++ b/skills/hyperframes/references/brag.md @@ -0,0 +1,69 @@ +# 18-Second Product Launch / Brag Card Recipe + +Deterministic, high-impact 18-second video card for new product releases, feature highlights, and milestone announcements. + +## Boundary +- Intent: `video_html` (short deterministic launch card, <90s). +- Full application walkthroughs and Indonesian spoken narration remain strictly `id-demo-video`. +- Photoreal 3D/cinematic promo ads remain `visual-studio`. +- Always load `composition.md` and `render.md` alongside this recipe. +- If Chromium or FFmpeg is absent: report `NOT_CONFIGURED` or `DEGRADED`, generate self-contained HTML/CSS/JS artifacts, and provide the exact render command. Never call remote HeyGen APIs or request hosted keys. + +## Composition Specification +- **Duration:** Exactly 18.0 seconds (`totalSeconds = 18`, `fps = 60`, `totalFrames = 1080`). +- **Canvas / Viewport:** 1920x1080 (16:9 landscape) or 1080x1920 (9:16 vertical/shorts). +- **Target Output Path:** `brag-output/brag.mp4`. +- **Assets:** Fully local only (inline SVGs, CSS typography, base64 or relative images). No external CDN links. + +## Beat Sheet (18 Seconds) + +| Beat | Window | Scene | Visual Action | +|---|---|---|---| +| **1. Hook & Problem** | `0.0s - 3.5s` | Hook Card | Dark minimal backdrop, glitching problem statement or bold metric text fading in with spring scale. | +| **2. Solution Reveal** | `3.5s - 8.0s` | Hero Reveal | Product brand logo + high-fidelity UI mockup card translates into center with smooth deceleration curve (`cubic-bezier(0.16, 1, 0.3, 1)`). | +| **3. Capability Highlight** | `8.0s - 13.5s` | Feature Carousel | 3 kinetic badge callouts or terminal command executions popping in sequence with numerical counters. | +| **4. Call to Action** | `13.5s - 18.0s` | Outro Lockup | Clean repository / install command (`opencode-he ...` or `git clone ...`), release tag badge, and link fade-out. | + +## Seekable Timeline Contract + +```javascript +// Master timeline contract driven strictly by frame or elapsed seconds +function renderFrame(t) { + // t is in seconds (0.000 to 18.000) + const progress = Math.min(Math.max(t / 18.0, 0), 1); + + if (t < 3.5) { + // Scene 1: Hook + const s1Progress = t / 3.5; + renderHookScene(s1Progress); + } else if (t < 8.0) { + // Scene 2: Hero Reveal + const s2Progress = (t - 3.5) / 4.5; + renderHeroScene(s2Progress); + } else if (t < 13.5) { + // Scene 3: Highlight + const s3Progress = (t - 8.0) / 5.5; + renderHighlightScene(s3Progress); + } else { + // Scene 4: CTA + const s4Progress = (t - 13.5) / 4.5; + renderOutroScene(s4Progress); + } +} +``` + +## Local Execution & Output + +```bash +# 1. Output directory preparation +mkdir -p brag-output + +# 2. Local deterministic render via hyperframes CLI or local script +npx -y hyperframes render \ + --input index.html \ + --output brag-output/brag.mp4 \ + --width 1920 \ + --height 1080 \ + --fps 60 \ + --duration 18 +``` diff --git a/skills/playwright-qa/SKILL.md b/skills/playwright-qa/SKILL.md index 7d6657d..ca53a95 100644 --- a/skills/playwright-qa/SKILL.md +++ b/skills/playwright-qa/SKILL.md @@ -53,3 +53,4 @@ playwright-cli -s=task-ui close - Operational QA flow & assertions: [references/workflow.md](references/workflow.md) - Session isolation & cleanup: [references/sessions.md](references/sessions.md) - Setup, runtime discovery, & dependency boundaries: [references/setup.md](references/setup.md) +- Layout shift (CLS) observation recipe: [references/cls.md](references/cls.md) diff --git a/skills/playwright-qa/references/cls.md b/skills/playwright-qa/references/cls.md new file mode 100644 index 0000000..bde0ded --- /dev/null +++ b/skills/playwright-qa/references/cls.md @@ -0,0 +1,33 @@ +# Visual Layout Shift (CLS) Recipe + +Exploratory layout shift observation recipe for `playwright-qa`. + +## Boundary +- Visual screenshots provide qualitative visual proof of shifts between actions, NOT quantitative Core Web Vitals scores. +- Measured numerical CLS (< 0.1) thresholds belong to `/full-performance-audit`. +- If an observed layout shift requires deep performance profiling, attach `/chrome-devtools-axi` via `opencode-chromium-cdp` (`127.0.0.1:9223`). + +## Recipe: Before-and-After Shift Observation + +When testing interactive surfaces susceptible to layout instability (lazy images, dynamic fonts, asynchronous banners, expanding drawers): + +1. **Baseline Snapshot & Screenshot:** + ```bash + playwright-cli -s=task-ui snapshot + playwright-cli -s=task-ui screenshot --filename=artifacts/cls-before.png + ``` + +2. **Trigger Asynchronous Interaction / Mutation:** + ```bash + playwright-cli -s=task-ui click e4 + ``` + +3. **Settled Screenshot:** + ```bash + playwright-cli -s=task-ui screenshot --filename=artifacts/cls-after.png + ``` + +4. **Verify Evidence:** + - Confirm visual elements did not unexpectedly reflow or displace existing content. + - Record `FACT: artifacts/cls-before.png` and `FACT: artifacts/cls-after.png`. + - Never claim quantitative CLS compliance from screenshots alone. diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 9e4fe6e..0221819 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -15,7 +15,11 @@ from lib import jsonc # noqa: E402 from lib.doctor import cmd_doctor, owned_agents_block, parse_mcp_list # noqa: E402 -from lib.install import cmd_install # noqa: E402 +from lib.install import ( # noqa: E402 + cmd_crawl4ai_disable, + cmd_crawl4ai_enable, + cmd_install, +) from lib.integrity import cmd_verify # noqa: E402 from tests.support import IsolatedHome # noqa: E402 @@ -447,6 +451,156 @@ def test_doctor_markitdown_valid_configured_passes(self): self.assertEqual(rc, 0, buf.getvalue()) self.assertIn("CONFIGURED mcp:markitdown", buf.getvalue()) + def test_doctor_crawl4ai_missing_does_not_fail(self): + self._install() + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 0, buf.getvalue()) + self.assertIn("OPTIONAL_ABSENT mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_zero_bind_fails(self): + self._install() + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + data["mcp"]["crawl4ai"] = { + "type": "remote", + "url": "http://0.0.0.0:11235/mcp", + "enabled": True, + } + cfg.write_text(jsonc.dumps(data), encoding="utf-8") + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 1, buf.getvalue()) + self.assertIn("FAIL mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_invalid_url_fails(self): + self._install() + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + data["mcp"]["crawl4ai"] = { + "type": "remote", + "url": "http://127.0.0.1:9999/mcp", + "enabled": True, + } + cfg.write_text(jsonc.dumps(data), encoding="utf-8") + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 1, buf.getvalue()) + self.assertIn("FAIL mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_cloud_raw_secret_fails(self): + self._install() + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + data["mcp"]["crawl4ai"] = { + "type": "remote", + "url": "https://api.crawl4ai.com/mcp", + "headers": {"Authorization": "Bearer raw_secret_key_123"}, + "enabled": True, + } + cfg.write_text(jsonc.dumps(data), encoding="utf-8") + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 1, buf.getvalue()) + self.assertIn("FAIL mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_cloud_missing_token_fails(self): + self._install() + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + data["mcp"]["crawl4ai"] = { + "type": "remote", + "url": "https://api.crawl4ai.com/mcp", + "enabled": True, + } + cfg.write_text(jsonc.dumps(data), encoding="utf-8") + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 1, buf.getvalue()) + self.assertIn("FAIL mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_valid_local_passes(self): + self._install() + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + data["mcp"]["crawl4ai"] = { + "type": "remote", + "url": "http://127.0.0.1:11235/mcp", + "enabled": True, + } + cfg.write_text(jsonc.dumps(data), encoding="utf-8") + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 0, buf.getvalue()) + self.assertIn("CONFIGURED mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_valid_cloud_passes(self): + self._install() + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + data["mcp"]["crawl4ai"] = { + "type": "remote", + "url": "https://api.crawl4ai.com/mcp", + "headers": {"Authorization": "Bearer {env:CRAWL4AI_KEY}"}, + "enabled": True, + } + cfg.write_text(jsonc.dumps(data), encoding="utf-8") + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 0, buf.getvalue()) + self.assertIn("CONFIGURED mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_enable_local_and_disable(self): + self._install() + rc = cmd_crawl4ai_enable() + self.assertEqual(rc, 0) + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + servers = jsonc.mcp_servers_from_config(data) + self.assertIn("crawl4ai", servers) + self.assertEqual(servers["crawl4ai"]["url"], "http://127.0.0.1:11235/mcp") + self.assertNotIn("0.0.0.0", str(servers["crawl4ai"])) + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 0, buf.getvalue()) + self.assertIn("CONFIGURED mcp:crawl4ai", buf.getvalue()) + rc = cmd_crawl4ai_disable() + self.assertEqual(rc, 0) + data = jsonc.loads(cfg.read_text(encoding="utf-8")) + servers = jsonc.mcp_servers_from_config(data) + self.assertNotIn("crawl4ai", servers) + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 0, buf.getvalue()) + self.assertIn("OPTIONAL_ABSENT mcp:crawl4ai", buf.getvalue()) + + def test_doctor_crawl4ai_enable_cloud(self): + self._install() + os.environ["CRAWL4AI_KEY"] = "super-secret-key-999" + try: + rc = cmd_crawl4ai_enable(cloud=True) + self.assertEqual(rc, 0) + cfg = self.tmp / ".config" / "opencode" / "opencode.jsonc" + raw_text = cfg.read_text(encoding="utf-8") + self.assertNotIn("super-secret-key-999", raw_text) + self.assertIn("{env:CRAWL4AI_KEY}", raw_text) + buf = io.StringIO() + with redirect_stdout(buf): + rc = cmd_doctor() + self.assertEqual(rc, 0, buf.getvalue()) + self.assertIn("CONFIGURED mcp:crawl4ai", buf.getvalue()) + finally: + os.environ.pop("CRAWL4AI_KEY", None) + def test_doctor_plugins_clean_when_absent(self): self._install() buf = io.StringIO() diff --git a/vendor/mcp-policy.json b/vendor/mcp-policy.json index b038348..7b46bff 100644 --- a/vendor/mcp-policy.json +++ b/vendor/mcp-policy.json @@ -55,6 +55,11 @@ "transport": "stdio", "command": "uvx", "args": ["--from", "markitdown-mcp==0.1.8", "markitdown-mcp"] + }, + "crawl4ai": { + "enabled": false, + "transport": "http", + "url": "http://127.0.0.1:11235/mcp" } } } diff --git a/vendor/mcp-wanted.json b/vendor/mcp-wanted.json index a619992..cb1f538 100644 --- a/vendor/mcp-wanted.json +++ b/vendor/mcp-wanted.json @@ -72,6 +72,14 @@ "command": "uvx", "args": ["--from", "markitdown-mcp==0.1.8", "markitdown-mcp"], "status": "FOREIGN_ON_DEMAND" + }, + "crawl4ai": { + "wanted": false, + "ownedIfAdded": false, + "scope": "user", + "transport": "http", + "url": "http://127.0.0.1:11235/mcp", + "status": "FOREIGN_ON_DEMAND" } } }