@erclx/canon 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/claude/.claude-plugin/plugin.json +19 -0
- package/claude/skills/bash-cli-script/REQUIREMENT.md +42 -0
- package/claude/skills/bash-cli-script/SKILL.md +48 -0
- package/claude/skills/bash-cli-script/references/template.md +43 -0
- package/claude/skills/bash-script/REQUIREMENT.md +36 -0
- package/claude/skills/bash-script/SKILL.md +100 -0
- package/claude/skills/bash-script/references/patterns.md +349 -0
- package/claude/skills/canon-cli/REQUIREMENT.md +41 -0
- package/claude/skills/canon-cli/SKILL.md +103 -0
- package/claude/skills/canon-feedback-file/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-file/SKILL.md +80 -0
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +63 -0
- package/claude/skills/canon-operator/REQUIREMENT.md +61 -0
- package/claude/skills/canon-operator/SKILL.md +108 -0
- package/claude/skills/canon-rollout/REQUIREMENT.md +59 -0
- package/claude/skills/canon-rollout/SKILL.md +147 -0
- package/claude/skills/canon-screencast/REQUIREMENT.md +39 -0
- package/claude/skills/canon-screencast/SKILL.md +167 -0
- package/claude/skills/canon-slides-draft/REQUIREMENT.md +39 -0
- package/claude/skills/canon-slides-draft/SKILL.md +62 -0
- package/claude/skills/ci-workflow/REQUIREMENT.md +40 -0
- package/claude/skills/ci-workflow/SKILL.md +65 -0
- package/claude/skills/ci-workflow/references/workflows.md +98 -0
- package/claude/skills/claude-address-review/REQUIREMENT.md +57 -0
- package/claude/skills/claude-address-review/SKILL.md +212 -0
- package/claude/skills/claude-address-review/references/rebase-conflicts.md +39 -0
- package/claude/skills/claude-autoship/REQUIREMENT.md +50 -0
- package/claude/skills/claude-autoship/SKILL.md +207 -0
- package/claude/skills/claude-design-extract/REQUIREMENT.md +42 -0
- package/claude/skills/claude-design-extract/SKILL.md +102 -0
- package/claude/skills/claude-diagram/REQUIREMENT.md +45 -0
- package/claude/skills/claude-diagram/SKILL.md +177 -0
- package/claude/skills/claude-docs/REQUIREMENT.md +60 -0
- package/claude/skills/claude-docs/SKILL.md +287 -0
- package/claude/skills/claude-docs/references/anchor-sweep.md +58 -0
- package/claude/skills/claude-docs/references/wireframe-sweep.md +45 -0
- package/claude/skills/claude-feature/REQUIREMENT.md +36 -0
- package/claude/skills/claude-feature/SKILL.md +115 -0
- package/claude/skills/claude-groundwork/REQUIREMENT.md +48 -0
- package/claude/skills/claude-groundwork/SKILL.md +142 -0
- package/claude/skills/claude-intake/REQUIREMENT.md +49 -0
- package/claude/skills/claude-intake/SKILL.md +114 -0
- package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
- package/claude/skills/claude-intake-answer/SKILL.md +90 -0
- package/claude/skills/claude-markdown-propose/REQUIREMENT.md +48 -0
- package/claude/skills/claude-markdown-propose/SKILL.md +118 -0
- package/claude/skills/claude-markdown-propose/references/format.md +107 -0
- package/claude/skills/claude-memory-capture/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-capture/SKILL.md +101 -0
- package/claude/skills/claude-memory-review/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-review/SKILL.md +210 -0
- package/claude/skills/claude-memory-review/references/receipt-format.md +48 -0
- package/claude/skills/claude-orchestrate/REQUIREMENT.md +121 -0
- package/claude/skills/claude-orchestrate/SKILL.md +241 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +132 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +32 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +72 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +87 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +30 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +21 -0
- package/claude/skills/claude-orchestrate/scripts/poll.sh +373 -0
- package/claude/skills/claude-orchestrate/scripts/watch.sh +181 -0
- package/claude/skills/claude-pr-review/REQUIREMENT.md +47 -0
- package/claude/skills/claude-pr-review/SKILL.md +295 -0
- package/claude/skills/claude-review/REQUIREMENT.md +39 -0
- package/claude/skills/claude-review/SKILL.md +142 -0
- package/claude/skills/claude-seed-sync/REQUIREMENT.md +45 -0
- package/claude/skills/claude-seed-sync/SKILL.md +156 -0
- package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
- package/claude/skills/claude-standards-audit/SKILL.md +99 -0
- package/claude/skills/claude-tasks/REQUIREMENT.md +43 -0
- package/claude/skills/claude-tasks/SKILL.md +161 -0
- package/claude/skills/claude-teach/REQUIREMENT.md +56 -0
- package/claude/skills/claude-teach/SKILL.md +196 -0
- package/claude/skills/claude-teach/references/lesson-craft.md +59 -0
- package/claude/skills/claude-teach/references/pedagogy.md +67 -0
- package/claude/skills/claude-teach/references/promotion.md +54 -0
- package/claude/skills/claude-ui-test/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ui-test/SKILL.md +77 -0
- package/claude/skills/claude-ux-audit/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ux-audit/SKILL.md +79 -0
- package/claude/skills/claude-ux-measure/REQUIREMENT.md +48 -0
- package/claude/skills/claude-ux-measure/SKILL.md +122 -0
- package/claude/skills/claude-worker/REQUIREMENT.md +54 -0
- package/claude/skills/claude-worker/SKILL.md +96 -0
- package/claude/skills/claude-worktree/REQUIREMENT.md +58 -0
- package/claude/skills/claude-worktree/SKILL.md +136 -0
- package/claude/skills/create-rule/REQUIREMENT.md +45 -0
- package/claude/skills/create-rule/SKILL.md +68 -0
- package/claude/skills/create-skill/REQUIREMENT.md +38 -0
- package/claude/skills/create-skill/SKILL.md +32 -0
- package/claude/skills/create-snippet/REQUIREMENT.md +39 -0
- package/claude/skills/create-snippet/SKILL.md +30 -0
- package/claude/skills/create-standard/REQUIREMENT.md +35 -0
- package/claude/skills/create-standard/SKILL.md +29 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +45 -0
- package/claude/skills/decision-escalate/SKILL.md +79 -0
- package/claude/skills/docs-sync/REQUIREMENT.md +41 -0
- package/claude/skills/docs-sync/SKILL.md +95 -0
- package/claude/skills/git-branch/REQUIREMENT.md +38 -0
- package/claude/skills/git-branch/SKILL.md +60 -0
- package/claude/skills/git-commit/REQUIREMENT.md +36 -0
- package/claude/skills/git-commit/SKILL.md +49 -0
- package/claude/skills/git-followup/REQUIREMENT.md +43 -0
- package/claude/skills/git-followup/SKILL.md +48 -0
- package/claude/skills/git-issue/REQUIREMENT.md +38 -0
- package/claude/skills/git-issue/SKILL.md +65 -0
- package/claude/skills/git-pr/REQUIREMENT.md +50 -0
- package/claude/skills/git-pr/SKILL.md +164 -0
- package/claude/skills/git-pr/references/labels.md +95 -0
- package/claude/skills/git-ship/REQUIREMENT.md +42 -0
- package/claude/skills/git-ship/SKILL.md +55 -0
- package/claude/skills/git-split/REQUIREMENT.md +39 -0
- package/claude/skills/git-split/SKILL.md +162 -0
- package/claude/skills/git-stage/REQUIREMENT.md +39 -0
- package/claude/skills/git-stage/SKILL.md +73 -0
- package/claude/skills/git-worktree/REQUIREMENT.md +38 -0
- package/claude/skills/git-worktree/SKILL.md +130 -0
- package/claude/skills/migration-claude-md/REQUIREMENT.md +40 -0
- package/claude/skills/migration-claude-md/SKILL.md +76 -0
- package/claude/skills/migration-context/REQUIREMENT.md +36 -0
- package/claude/skills/migration-context/SKILL.md +95 -0
- package/claude/skills/migration-standards-drop/REQUIREMENT.md +55 -0
- package/claude/skills/migration-standards-drop/SKILL.md +113 -0
- package/claude/skills/migration-superseded/REQUIREMENT.md +44 -0
- package/claude/skills/migration-superseded/SKILL.md +115 -0
- package/claude/skills/project-commands/REQUIREMENT.md +42 -0
- package/claude/skills/project-commands/SKILL.md +85 -0
- package/claude/skills/restate-plainly/REQUIREMENT.md +41 -0
- package/claude/skills/restate-plainly/SKILL.md +39 -0
- package/claude/skills/session-map/REQUIREMENT.md +57 -0
- package/claude/skills/session-map/SKILL.md +70 -0
- package/claude/skills/session-resume/REQUIREMENT.md +49 -0
- package/claude/skills/session-resume/SKILL.md +51 -0
- package/claude/skills/setup-gov/REQUIREMENT.md +37 -0
- package/claude/skills/setup-gov/SKILL.md +77 -0
- package/claude/skills/setup-indexes/REQUIREMENT.md +45 -0
- package/claude/skills/setup-indexes/SKILL.md +153 -0
- package/claude/skills/setup-init/REQUIREMENT.md +45 -0
- package/claude/skills/setup-init/SKILL.md +127 -0
- package/claude/skills/setup-plugins/REQUIREMENT.md +42 -0
- package/claude/skills/setup-plugins/SKILL.md +81 -0
- package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
- package/claude/skills/setup-verify/REQUIREMENT.md +39 -0
- package/claude/skills/setup-verify/SKILL.md +51 -0
- package/claude/skills/systematic-debugging/REQUIREMENT.md +41 -0
- package/claude/skills/systematic-debugging/SKILL.md +70 -0
- package/claude/skills/write-human/REQUIREMENT.md +46 -0
- package/claude/skills/write-human/SKILL.md +68 -0
- package/claude/skills/write-human/references/density.md +38 -0
- package/claude/skills/write-human/references/machine-tells.md +107 -0
- package/claude/skills/write-human/references/source-material.md +37 -0
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +38 -0
- package/claude/skills/youtube-transcripts/SKILL.md +34 -0
- package/docs/agents/audits.md +98 -0
- package/docs/agents/capture.md +37 -0
- package/docs/agents/census.md +23 -0
- package/docs/agents/commands.md +146 -0
- package/docs/agents/comments.md +34 -0
- package/docs/agents/context-audit-checks.md +120 -0
- package/docs/agents/context-audit.md +83 -0
- package/docs/agents/counts.md +76 -0
- package/docs/agents/demo.md +86 -0
- package/docs/agents/docs.md +17 -0
- package/docs/agents/gate.md +84 -0
- package/docs/agents/index.md +47 -0
- package/docs/agents/indexes.md +35 -0
- package/docs/agents/install-and-sync.md +385 -0
- package/docs/agents/intake.md +81 -0
- package/docs/agents/key-changes.md +103 -0
- package/docs/agents/label-coverage.md +73 -0
- package/docs/agents/markdown-audit.md +197 -0
- package/docs/agents/output-shape.md +70 -0
- package/docs/agents/overview.md +26 -0
- package/docs/agents/records.md +170 -0
- package/docs/agents/restated.md +81 -0
- package/docs/agents/review-classification.md +77 -0
- package/docs/agents/routing.md +61 -0
- package/docs/agents/rule-citations.md +98 -0
- package/docs/agents/sandbox.md +71 -0
- package/docs/agents/scripting.md +149 -0
- package/docs/agents/sessions.md +120 -0
- package/docs/agents/skills-audit.md +94 -0
- package/docs/agents/skills-reach.md +64 -0
- package/docs/agents/standards-audit.md +38 -0
- package/docs/agents/state-scoped-risk.md +105 -0
- package/docs/agents/superseded.md +85 -0
- package/docs/agents/targets.md +83 -0
- package/docs/agents/tasks.md +200 -0
- package/docs/agents/teach.md +158 -0
- package/docs/agents/test-order.md +56 -0
- package/docs/agents/worktrees.md +62 -0
- package/docs/ai-workflow.md +317 -0
- package/docs/index.md +23 -0
- package/docs/operating-model.md +223 -0
- package/docs/target-projects.md +258 -0
- package/docs/visual-design-workflow.md +151 -0
- package/docs/zshrc-aliases.md +65 -0
- package/governance/rules/ci/700-ci-workflow.md +44 -0
- package/governance/rules/claude/500-prose.md +15 -0
- package/governance/rules/claude/501-markdown.md +14 -0
- package/governance/rules/claude/510-context.md +28 -0
- package/governance/rules/claude/511-indexes.md +14 -0
- package/governance/rules/claude/520-wireframes.md +12 -0
- package/governance/rules/claude/530-requirements.md +11 -0
- package/governance/rules/claude/540-architecture.md +11 -0
- package/governance/rules/claude/550-design.md +11 -0
- package/governance/rules/claude/555-tasks.md +12 -0
- package/governance/rules/claude/556-groundwork.md +11 -0
- package/governance/rules/claude/557-intake.md +11 -0
- package/governance/rules/claude/558-plan.md +22 -0
- package/governance/rules/claude/559-memory.md +11 -0
- package/governance/rules/claude/560-diagrams.md +18 -0
- package/governance/rules/claude/561-teach.md +13 -0
- package/governance/rules/claude/562-session.md +15 -0
- package/governance/rules/claude/570-skill.md +24 -0
- package/governance/rules/claude/575-hooks.md +17 -0
- package/governance/rules/claude/576-settings.md +14 -0
- package/governance/rules/claude/580-readme.md +11 -0
- package/governance/rules/claude/590-rule-authoring.md +12 -0
- package/governance/rules/claude/591-standard-authoring.md +12 -0
- package/governance/rules/claude/592-claude-md.md +19 -0
- package/governance/rules/core/000-constitution.md +30 -0
- package/governance/rules/core/005-behavior.md +27 -0
- package/governance/rules/core/010-testing.md +35 -0
- package/governance/rules/core/015-output.md +20 -0
- package/governance/rules/core/020-concurrency.md +22 -0
- package/governance/rules/core/025-indexes.md +9 -0
- package/governance/rules/core/030-error-handling.md +31 -0
- package/governance/rules/core/035-tasks.md +13 -0
- package/governance/rules/core/040-performance.md +20 -0
- package/governance/rules/core/045-memory.md +12 -0
- package/governance/rules/core/050-logging.md +20 -0
- package/governance/rules/core/055-scratch.md +9 -0
- package/governance/rules/core/060-naming.md +19 -0
- package/governance/rules/core/065-spelling.md +19 -0
- package/governance/rules/core/070-planning.md +18 -0
- package/governance/rules/core/075-dependencies.md +25 -0
- package/governance/rules/core/080-config-comments.md +22 -0
- package/governance/rules/core/085-worktrees.md +17 -0
- package/governance/rules/core/087-git.md +11 -0
- package/governance/rules/core/090-code-comments.md +39 -0
- package/governance/rules/framework/200-react.md +51 -0
- package/governance/rules/framework/210-astro.md +41 -0
- package/governance/rules/framework/220-fastapi.md +43 -0
- package/governance/rules/framework/230-nextjs.md +48 -0
- package/governance/rules/framework/250-tailwind.md +32 -0
- package/governance/rules/framework/260-shadcn.md +34 -0
- package/governance/rules/lang/100-typescript.md +40 -0
- package/governance/rules/lang/110-python.md +42 -0
- package/governance/rules/lang/120-bash.md +19 -0
- package/governance/rules/lib/300-testing-ts.md +39 -0
- package/governance/rules/lib/305-e2e-reliability.md +34 -0
- package/governance/rules/lib/306-test-scope.md +25 -0
- package/governance/rules/lib/310-zod.md +25 -0
- package/governance/rules/lib/320-tanstack-query.md +32 -0
- package/governance/rules/lib/330-testing-py.md +44 -0
- package/governance/rules/lib/340-pydantic.md +38 -0
- package/governance/rules/lib/350-security-web.md +32 -0
- package/governance/rules/lib/360-security-server.md +39 -0
- package/governance/rules/lib/370-database.md +35 -0
- package/governance/rules/snippets/505-at-references.md +9 -0
- package/governance/rules/ui/400-ui.md +36 -0
- package/governance/rules/ui/410-a11y.md +48 -0
- package/governance/rules/ui/420-forms.md +36 -0
- package/governance/rules/ui/430-ux-completeness.md +65 -0
- package/governance/rules/ui/440-surface-capture.md +34 -0
- package/governance/rules/ui/450-link-behavior.md +19 -0
- package/governance/stacks/astro.toml +2 -0
- package/governance/stacks/base.toml +8 -0
- package/governance/stacks/node-server.toml +2 -0
- package/governance/stacks/node.toml +2 -0
- package/governance/stacks/python-fastapi.toml +2 -0
- package/governance/stacks/python.toml +2 -0
- package/governance/stacks/react.toml +2 -0
- package/package.json +69 -0
- package/scripts/config.sh +11 -0
- package/scripts/core/bootstrap.sh +81 -0
- package/scripts/core/check-color-source.sh +41 -0
- package/scripts/core/check-ignore-parity.sh +162 -0
- package/scripts/core/check-plugin-boundary.sh +45 -0
- package/scripts/core/check-seed-independence.sh +59 -0
- package/scripts/core/check-skill-paths.sh +24 -0
- package/scripts/core/clean.sh +36 -0
- package/scripts/core/install-check.sh +101 -0
- package/scripts/core/list-seed-roots.sh +18 -0
- package/scripts/core/regen-claude-copies.sh +10 -0
- package/scripts/core/regen-hero.sh +217 -0
- package/scripts/core/regen-indexes.sh +10 -0
- package/scripts/core/regen-tooling-paths.sh +61 -0
- package/scripts/core/repair-bare-flag.sh +19 -0
- package/scripts/core/snapshot.sh +134 -0
- package/scripts/core/update.sh +35 -0
- package/scripts/docs/list.sh +165 -0
- package/scripts/lib/frontmatter.sh +30 -0
- package/scripts/lib/gov.sh +14 -0
- package/scripts/lib/sandbox-fixtures.sh +191 -0
- package/scripts/lib/sandbox-git.sh +125 -0
- package/scripts/lib/sandbox-path.sh +206 -0
- package/scripts/lib/tooling.sh +35 -0
- package/scripts/lib/ui.sh +266 -0
- package/scripts/lib/worktree.sh +20 -0
- package/scripts/manage-sandbox.sh +466 -0
- package/scripts/snippets/create.sh +156 -0
- package/scripts/standards/list.sh +115 -0
- package/scripts/tooling/create.sh +109 -0
- package/scripts/tooling/verify.sh +179 -0
- package/snippets/align.md +12 -0
- package/snippets/claude/decision-memo.md +39 -0
- package/snippets/claude/feature-recap.md +19 -0
- package/snippets/claude/figma-steps.md +24 -0
- package/snippets/compact-summary.md +5 -0
- package/snippets/decision-help.md +6 -0
- package/snippets/meta-prompt.md +14 -0
- package/snippets/research-prompt.md +7 -0
- package/snippets/session-notes.md +11 -0
- package/snippets/snippets.toml +5 -0
- package/snippets/step-by-step.md +10 -0
- package/snippets/web-research.md +21 -0
- package/src/audits/baseline.ts +201 -0
- package/src/audits/catalog.ts +876 -0
- package/src/audits/run.ts +204 -0
- package/src/autoship/classify.ts +75 -0
- package/src/autoship/paths.ts +51 -0
- package/src/binary.ts +16 -0
- package/src/browser/engine.ts +40 -0
- package/src/census/count.ts +113 -0
- package/src/claude/cases/all.ts +24 -0
- package/src/claude/cases/authoring.ts +53 -0
- package/src/claude/cases/claude-workflow.ts +158 -0
- package/src/claude/cases/git.ts +44 -0
- package/src/claude/cases/misc.ts +27 -0
- package/src/claude/cases/setup.ts +94 -0
- package/src/claude/gitignore.ts +51 -0
- package/src/claude/routing.ts +283 -0
- package/src/claude/seeds-list.ts +47 -0
- package/src/claude/seeds.ts +150 -0
- package/src/claude/settings.ts +151 -0
- package/src/claude/skills-audit.ts +228 -0
- package/src/claude/skills-drift.ts +156 -0
- package/src/claude/skills-list.ts +99 -0
- package/src/claude/skills-rank.ts +320 -0
- package/src/claude/skills-reach.ts +227 -0
- package/src/cli-run.ts +43 -0
- package/src/cli.ts +200 -0
- package/src/commands/audits.ts +350 -0
- package/src/commands/autoship.ts +129 -0
- package/src/commands/capture.ts +133 -0
- package/src/commands/census.ts +105 -0
- package/src/commands/claude.ts +1286 -0
- package/src/commands/comments.ts +240 -0
- package/src/commands/context.ts +857 -0
- package/src/commands/demo.ts +389 -0
- package/src/commands/deps.ts +173 -0
- package/src/commands/design.ts +36 -0
- package/src/commands/docs.ts +60 -0
- package/src/commands/feedback-format.ts +23 -0
- package/src/commands/feedback.ts +112 -0
- package/src/commands/gate.ts +189 -0
- package/src/commands/gov.ts +1265 -0
- package/src/commands/indexes.ts +184 -0
- package/src/commands/init.ts +113 -0
- package/src/commands/intake.ts +406 -0
- package/src/commands/inventory.ts +256 -0
- package/src/commands/labels.ts +361 -0
- package/src/commands/markdown.ts +544 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/pass-through.ts +39 -0
- package/src/commands/pr.ts +411 -0
- package/src/commands/records.ts +728 -0
- package/src/commands/sandbox.ts +468 -0
- package/src/commands/secrets.ts +132 -0
- package/src/commands/serve.ts +159 -0
- package/src/commands/sessions.ts +408 -0
- package/src/commands/slides.ts +126 -0
- package/src/commands/snippets.ts +84 -0
- package/src/commands/standards.ts +247 -0
- package/src/commands/sync.ts +428 -0
- package/src/commands/targets.ts +319 -0
- package/src/commands/tasks.ts +743 -0
- package/src/commands/teach.ts +786 -0
- package/src/commands/tooling.ts +573 -0
- package/src/commands/transcripts.ts +44 -0
- package/src/commands/upgrade.ts +231 -0
- package/src/commands/wiki.ts +100 -0
- package/src/commands/worktrees.ts +191 -0
- package/src/comments/scan.ts +338 -0
- package/src/comments/trend.ts +207 -0
- package/src/comments/vocabulary.ts +85 -0
- package/src/context/architecture.ts +364 -0
- package/src/context/audit.ts +790 -0
- package/src/context/citations.ts +196 -0
- package/src/context/folders.ts +186 -0
- package/src/context/gate.ts +57 -0
- package/src/context/index-drift.ts +64 -0
- package/src/context/narration.ts +99 -0
- package/src/copy.ts +30 -0
- package/src/counts/catalogs.ts +96 -0
- package/src/counts/numbers.ts +79 -0
- package/src/counts/scan.ts +314 -0
- package/src/demo/beats.ts +135 -0
- package/src/demo/compile.ts +326 -0
- package/src/demo/container.ts +63 -0
- package/src/demo/cursors.ts +55 -0
- package/src/demo/drive.ts +357 -0
- package/src/demo/pointer.ts +178 -0
- package/src/demo/theme.ts +112 -0
- package/src/deps/audit.ts +153 -0
- package/src/design/parse.ts +116 -0
- package/src/design/render.ts +249 -0
- package/src/docs/read.ts +77 -0
- package/src/exec.ts +16 -0
- package/src/exempt-marker.ts +43 -0
- package/src/frontmatter.ts +13 -0
- package/src/gate/measures.ts +682 -0
- package/src/gate/sequencer.ts +386 -0
- package/src/gate/stages.ts +412 -0
- package/src/git-env.ts +36 -0
- package/src/git-files.ts +105 -0
- package/src/git-ignore.ts +46 -0
- package/src/github-format.ts +13 -0
- package/src/github.ts +24 -0
- package/src/gov/adapter.ts +103 -0
- package/src/gov/citations.ts +514 -0
- package/src/gov/consumed.ts +129 -0
- package/src/gov/install.ts +132 -0
- package/src/gov/list.ts +106 -0
- package/src/gov/payload.ts +39 -0
- package/src/gov/restated.ts +814 -0
- package/src/gov/stacks.ts +205 -0
- package/src/gov/superseded.ts +415 -0
- package/src/gov/test-order.ts +407 -0
- package/src/indexes/frontmatter.ts +46 -0
- package/src/indexes/regen.ts +84 -0
- package/src/indexes/render.ts +201 -0
- package/src/indexes/walk.ts +83 -0
- package/src/init/flags.ts +60 -0
- package/src/init/plan.ts +114 -0
- package/src/init/run.ts +46 -0
- package/src/init/steps.ts +77 -0
- package/src/intake/folder.ts +320 -0
- package/src/intake/items.ts +174 -0
- package/src/inventory/config.ts +117 -0
- package/src/inventory/group.ts +76 -0
- package/src/inventory/subjects.ts +114 -0
- package/src/inventory/walk.ts +129 -0
- package/src/labels/audit.ts +82 -0
- package/src/labels/coverage.ts +79 -0
- package/src/labels/map.ts +101 -0
- package/src/labels/phase.ts +95 -0
- package/src/markdown/bans.ts +94 -0
- package/src/markdown/files.ts +102 -0
- package/src/markdown/gate.ts +28 -0
- package/src/markdown/scan.ts +295 -0
- package/src/markdown/structure.ts +730 -0
- package/src/migrate/apply.ts +115 -0
- package/src/migrate/plan.ts +103 -0
- package/src/migrate/rename.ts +183 -0
- package/src/pr/bijection.ts +145 -0
- package/src/pr/paths.ts +335 -0
- package/src/process/harness.ts +167 -0
- package/src/project-root.ts +19 -0
- package/src/records/backup.ts +455 -0
- package/src/records/migrate.ts +78 -0
- package/src/records/size.ts +260 -0
- package/src/records/validate.ts +1162 -0
- package/src/sandbox/census.ts +228 -0
- package/src/sandbox/coverage.ts +115 -0
- package/src/sandbox/expect.ts +629 -0
- package/src/sandbox/tree.ts +47 -0
- package/src/secrets/marker.ts +30 -0
- package/src/secrets/patterns.ts +142 -0
- package/src/secrets/scan.ts +123 -0
- package/src/secrets/shipped.ts +111 -0
- package/src/seed-marker.ts +74 -0
- package/src/serve/static.ts +322 -0
- package/src/sessions/claim.ts +85 -0
- package/src/sessions/live.ts +79 -0
- package/src/sessions/registry.ts +137 -0
- package/src/sessions/resolve.ts +333 -0
- package/src/slides/layouts.ts +391 -0
- package/src/slides/open.ts +18 -0
- package/src/slides/parse.ts +84 -0
- package/src/slides/render.ts +88 -0
- package/src/slides/styles.ts +44 -0
- package/src/snippets/categories.ts +66 -0
- package/src/snippets/list.ts +32 -0
- package/src/snippets/presets.ts +50 -0
- package/src/standards/audit.ts +132 -0
- package/src/standards/read.ts +100 -0
- package/src/sync/check.ts +692 -0
- package/src/sync/engine.ts +576 -0
- package/src/sync/git.ts +225 -0
- package/src/sync/history.ts +123 -0
- package/src/sync/layout.ts +140 -0
- package/src/sync/reverse.ts +268 -0
- package/src/sync/seeds-report.ts +126 -0
- package/src/sync/stamp.ts +358 -0
- package/src/sync/target.ts +70 -0
- package/src/sync/workflow.ts +200 -0
- package/src/target.ts +43 -0
- package/src/targets/pulls.ts +250 -0
- package/src/targets/registry.ts +203 -0
- package/src/targets/resolve.ts +145 -0
- package/src/targets/sweep.ts +246 -0
- package/src/tasks/archive.ts +506 -0
- package/src/tasks/record.ts +311 -0
- package/src/tasks/trunk.ts +89 -0
- package/src/tasks/validate.ts +1001 -0
- package/src/teach/lesson.ts +180 -0
- package/src/teach/workspace.ts +842 -0
- package/src/tooling/gitignore.ts +122 -0
- package/src/tooling/inject.ts +193 -0
- package/src/tooling/list.ts +39 -0
- package/src/tooling/manifest.ts +176 -0
- package/src/tooling/package.ts +166 -0
- package/src/tooling/read.ts +65 -0
- package/src/tooling/scan.ts +147 -0
- package/src/tooling/stamp.ts +44 -0
- package/src/transcripts/fetch.ts +156 -0
- package/src/transcripts/metadata.ts +54 -0
- package/src/transcripts/vtt.ts +114 -0
- package/src/ui.ts +266 -0
- package/src/version/compare.ts +53 -0
- package/src/version/installed.ts +40 -0
- package/src/version/manager.ts +67 -0
- package/src/version/skew.ts +192 -0
- package/src/wiki/init.ts +85 -0
- package/src/worktree.ts +143 -0
- package/src/worktrees/reclaim.ts +306 -0
- package/standards/architecture.md +72 -0
- package/standards/branch.md +59 -0
- package/standards/commit.md +72 -0
- package/standards/context.md +151 -0
- package/standards/design.md +93 -0
- package/standards/diagrams.md +152 -0
- package/standards/glossary.md +75 -0
- package/standards/groundwork.md +211 -0
- package/standards/index.md +36 -0
- package/standards/intake.md +192 -0
- package/standards/issue.md +94 -0
- package/standards/markdown.md +137 -0
- package/standards/memory.md +144 -0
- package/standards/plan.md +172 -0
- package/standards/pr.md +139 -0
- package/standards/publish.md +51 -0
- package/standards/readme.md +208 -0
- package/standards/requirements.md +70 -0
- package/standards/rule.md +118 -0
- package/standards/session.md +109 -0
- package/standards/skill.md +300 -0
- package/standards/slug.md +39 -0
- package/standards/snippets.md +76 -0
- package/standards/standard.md +170 -0
- package/standards/tasks.md +254 -0
- package/standards/teach.md +153 -0
- package/standards/versioning.md +71 -0
- package/standards/wireframes.md +113 -0
- package/tooling/astro/configs/astro.config.mjs +31 -0
- package/tooling/astro/configs/eslint.config.js +79 -0
- package/tooling/astro/configs/playwright.config.ts +26 -0
- package/tooling/astro/configs/tsconfig.json +12 -0
- package/tooling/astro/configs/vitest.config.ts +22 -0
- package/tooling/astro/manifest.toml +34 -0
- package/tooling/astro/reference.md +60 -0
- package/tooling/base/configs/.editorconfig +5 -0
- package/tooling/base/configs/.github/pull_request_template.md +18 -0
- package/tooling/base/configs/.github/workflows/verify.yml +35 -0
- package/tooling/base/configs/.husky/commit-msg +1 -0
- package/tooling/base/configs/.husky/post-merge +61 -0
- package/tooling/base/configs/.husky/post-rewrite +21 -0
- package/tooling/base/configs/.husky/pre-commit +1 -0
- package/tooling/base/configs/.husky/pre-push +1 -0
- package/tooling/base/configs/.prettierrc +12 -0
- package/tooling/base/configs/.shellcheckrc +1 -0
- package/tooling/base/configs/.vscode/extensions.json +9 -0
- package/tooling/base/configs/.vscode/settings.json +3 -0
- package/tooling/base/configs/commitlint.config.js +11 -0
- package/tooling/base/configs/scripts/verify.sh +64 -0
- package/tooling/base/manifest.toml +30 -0
- package/tooling/base/reference.md +98 -0
- package/tooling/base/seeds/.claude/context/ci.md +33 -0
- package/tooling/base/seeds/.claude/context/development.md +37 -0
- package/tooling/base/seeds/.claude/context/index.md +11 -0
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/base/seeds/.lintstagedrc +8 -0
- package/tooling/base/seeds/.prettierignore +0 -0
- package/tooling/base/seeds/cspell.json +20 -0
- package/tooling/claude/manifest.toml +14 -0
- package/tooling/claude/reference.md +79 -0
- package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
- package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
- package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
- package/tooling/claude/seeds/.claude/diagrams/index.md +8 -0
- package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +50 -0
- package/tooling/claude/seeds/.claude/hooks/memory-index.sh +68 -0
- package/tooling/claude/seeds/.claude/hooks/path-form.sh +57 -0
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +52 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +86 -0
- package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +71 -0
- package/tooling/claude/seeds/.claude/memory/index.md +8 -0
- package/tooling/claude/seeds/.claude/settings.json +47 -0
- package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
- package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
- package/tooling/claude/seeds/CLAUDE.md +29 -0
- package/tooling/claude/user/settings.template.json +10 -0
- package/tooling/claude/user/statusline-command.sh +53 -0
- package/tooling/python/configs/.coveragerc +14 -0
- package/tooling/python/configs/.python-version +1 -0
- package/tooling/python/configs/mypy.ini +6 -0
- package/tooling/python/configs/pytest.ini +4 -0
- package/tooling/python/configs/ruff.toml +15 -0
- package/tooling/python/configs/scripts/verify.sh +77 -0
- package/tooling/python/manifest.toml +16 -0
- package/tooling/python/reference.md +66 -0
- package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/python/seeds/tests/test_smoke.py +2 -0
- package/tooling/vite-react/configs/playwright.config.ts +26 -0
- package/tooling/vite-react/configs/tsconfig.json +35 -0
- package/tooling/vite-react/configs/vite.config.ts +24 -0
- package/tooling/vite-react/configs/vitest.config.ts +26 -0
- package/tooling/vite-react/manifest.toml +23 -0
- package/tooling/vite-react/reference.md +55 -0
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
- package/tooling/web/configs/.github/workflows/verify.yml +134 -0
- package/tooling/web/configs/.vscode/extensions.json +13 -0
- package/tooling/web/configs/.vscode/settings.json +10 -0
- package/tooling/web/configs/e2e/home.spec.ts +6 -0
- package/tooling/web/configs/e2e/screenshot.ts +53 -0
- package/tooling/web/configs/eslint.config.js +82 -0
- package/tooling/web/configs/scripts/screenshot.sh +28 -0
- package/tooling/web/configs/scripts/verify.sh +80 -0
- package/tooling/web/configs/scripts/worktree-port.sh +74 -0
- package/tooling/web/configs/src/test/setup.ts +8 -0
- package/tooling/web/manifest.toml +58 -0
- package/tooling/web/reference.md +115 -0
- package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type { Beat, Draft } from '@/demo/beats'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Turns the human-facing draft into the machine-facing plan. The two are
|
|
5
|
+
* separate artifacts on purpose: a beat carries no target, no wait condition,
|
|
6
|
+
* and no timing, and putting those four fields on every beat would destroy the
|
|
7
|
+
* property the draft was designed around. See
|
|
8
|
+
* `.claude/groundwork/38-demo-recorder/06-decision.md`.
|
|
9
|
+
*
|
|
10
|
+
* A compiled plan is committed rather than scratch, because the timing below is
|
|
11
|
+
* a starting point the operator tunes and the draft cannot reproduce a tuned
|
|
12
|
+
* value.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Hand-tuned in spike 2 against one fixture. Nothing establishes them in general. */
|
|
16
|
+
const POINTER_TRAVEL_MS = 400
|
|
17
|
+
const TYPE_DELAY_MS = 110
|
|
18
|
+
const HOLD_MS = 600
|
|
19
|
+
const FINAL_HOLD_MS = 1200
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A step is a round trip to the browser, so a fixed count reproduces the
|
|
23
|
+
* defect under a new name if it does not respond to the round-trip cost
|
|
24
|
+
* measured at drive time. The bounds guard the extremes a bad measurement
|
|
25
|
+
* could produce: too few steps teleports rather than glides, and a
|
|
26
|
+
* near-zero measured cost cannot be trusted enough to let the count run
|
|
27
|
+
* away.
|
|
28
|
+
*
|
|
29
|
+
* Measured on 2026-08-26 against a served fixture, post-navigation: 10
|
|
30
|
+
* rounds of `page.mouse.move` averaged 16.6 milliseconds a step, well short
|
|
31
|
+
* of either bound at the 400 millisecond default (about 24 steps). A
|
|
32
|
+
* machine landing under `POINTER_TRAVEL_MS / MAX_POINTER_STEPS`, 3.33
|
|
33
|
+
* milliseconds a step here, hits the cap and stops responding to a faster
|
|
34
|
+
* one still. Nothing measured here establishes where a real machine sits
|
|
35
|
+
* relative to that boundary in general.
|
|
36
|
+
*/
|
|
37
|
+
export const MIN_POINTER_STEPS = 6
|
|
38
|
+
export const MAX_POINTER_STEPS = 120
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Pure on purpose: the round-trip cost comes from a real browser and can
|
|
42
|
+
* only be measured at drive time, so this takes it as an argument rather
|
|
43
|
+
* than measuring it itself, which is what keeps it testable against a
|
|
44
|
+
* stubbed cost.
|
|
45
|
+
*/
|
|
46
|
+
export function deriveSteps(travelMs: number, roundTripMs: number): number {
|
|
47
|
+
if (roundTripMs <= 0) return MAX_POINTER_STEPS
|
|
48
|
+
const steps = Math.round(travelMs / roundTripMs)
|
|
49
|
+
return Math.min(MAX_POINTER_STEPS, Math.max(MIN_POINTER_STEPS, steps))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const VIEWPORT = { width: 1280, height: 720 } as const
|
|
53
|
+
const ANNOTATIONS = {
|
|
54
|
+
durationMs: 900,
|
|
55
|
+
position: 'bottom-right',
|
|
56
|
+
fontSize: 22,
|
|
57
|
+
} as const
|
|
58
|
+
|
|
59
|
+
export type StepKind =
|
|
60
|
+
| 'navigate'
|
|
61
|
+
| 'click'
|
|
62
|
+
| 'fill'
|
|
63
|
+
| 'hover'
|
|
64
|
+
| 'scroll'
|
|
65
|
+
| 'wait'
|
|
66
|
+
| 'hold'
|
|
67
|
+
|
|
68
|
+
/** Verbs that point at an element, so a plan without a target cannot run. */
|
|
69
|
+
const TARGETED: ReadonlySet<StepKind> = new Set<StepKind>([
|
|
70
|
+
'click',
|
|
71
|
+
'fill',
|
|
72
|
+
'hover',
|
|
73
|
+
'scroll',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
const VERBS: Record<string, StepKind> = {
|
|
77
|
+
navigate: 'navigate',
|
|
78
|
+
open: 'navigate',
|
|
79
|
+
visit: 'navigate',
|
|
80
|
+
load: 'navigate',
|
|
81
|
+
click: 'click',
|
|
82
|
+
press: 'click',
|
|
83
|
+
tap: 'click',
|
|
84
|
+
submit: 'click',
|
|
85
|
+
toggle: 'click',
|
|
86
|
+
type: 'fill',
|
|
87
|
+
fill: 'fill',
|
|
88
|
+
enter: 'fill',
|
|
89
|
+
input: 'fill',
|
|
90
|
+
hover: 'hover',
|
|
91
|
+
scroll: 'scroll',
|
|
92
|
+
wait: 'wait',
|
|
93
|
+
pause: 'wait',
|
|
94
|
+
settle: 'wait',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface DemoStep {
|
|
98
|
+
readonly beat: number
|
|
99
|
+
readonly name: string
|
|
100
|
+
readonly kind: StepKind
|
|
101
|
+
readonly target: string
|
|
102
|
+
readonly text: string
|
|
103
|
+
readonly waitFor: string
|
|
104
|
+
readonly holdMs: number
|
|
105
|
+
readonly caption: string
|
|
106
|
+
readonly still: boolean
|
|
107
|
+
readonly note?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface DemoPlan {
|
|
111
|
+
readonly slug: string
|
|
112
|
+
readonly title: string
|
|
113
|
+
readonly url: string
|
|
114
|
+
readonly viewport: { readonly width: number; readonly height: number }
|
|
115
|
+
readonly output: { readonly video: string; readonly still: string }
|
|
116
|
+
readonly pointer: { readonly travelMs: number; readonly typeDelayMs: number }
|
|
117
|
+
readonly annotations: typeof ANNOTATIONS
|
|
118
|
+
readonly steps: readonly DemoStep[]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CompileOptions {
|
|
122
|
+
readonly slug: string
|
|
123
|
+
readonly outDir: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function compilePlan(draft: Draft, options: CompileOptions): DemoPlan {
|
|
127
|
+
const stillAt = heroIndex(draft.beats)
|
|
128
|
+
return {
|
|
129
|
+
slug: options.slug,
|
|
130
|
+
title: draft.title,
|
|
131
|
+
url: '',
|
|
132
|
+
viewport: VIEWPORT,
|
|
133
|
+
output: {
|
|
134
|
+
video: `${options.outDir}/${options.slug}.webm`,
|
|
135
|
+
still: `${options.outDir}/${options.slug}.png`,
|
|
136
|
+
},
|
|
137
|
+
pointer: { travelMs: POINTER_TRAVEL_MS, typeDelayMs: TYPE_DELAY_MS },
|
|
138
|
+
annotations: ANNOTATIONS,
|
|
139
|
+
steps: draft.beats.map((beat, position) =>
|
|
140
|
+
compileStep(beat, {
|
|
141
|
+
still: position === stillAt,
|
|
142
|
+
last: position === draft.beats.length - 1,
|
|
143
|
+
}),
|
|
144
|
+
),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function compileStep(
|
|
149
|
+
beat: Beat,
|
|
150
|
+
place: { still: boolean; last: boolean },
|
|
151
|
+
): DemoStep {
|
|
152
|
+
const verb = firstVerb(beat.action)
|
|
153
|
+
const kind = verb ? VERBS[verb] : undefined
|
|
154
|
+
const step: DemoStep = {
|
|
155
|
+
beat: beat.index,
|
|
156
|
+
name: beat.name,
|
|
157
|
+
kind: kind ?? 'hold',
|
|
158
|
+
target: '',
|
|
159
|
+
text: '',
|
|
160
|
+
waitFor: '',
|
|
161
|
+
holdMs: place.last ? FINAL_HOLD_MS : HOLD_MS,
|
|
162
|
+
caption: beat.caption,
|
|
163
|
+
still: place.still,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (kind) return step
|
|
167
|
+
return {
|
|
168
|
+
...step,
|
|
169
|
+
note: `no step maps to the verb "${beat.action.trim()}", so this beat only waits`,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The draft specifies one verb per beat, and a hand-edited beat reads as a
|
|
175
|
+
* phrase often enough that matching only the whole field would refuse work a
|
|
176
|
+
* reader can see is a click. The first recognized word wins.
|
|
177
|
+
*/
|
|
178
|
+
function firstVerb(action: string): string | undefined {
|
|
179
|
+
return action
|
|
180
|
+
.toLowerCase()
|
|
181
|
+
.split(/[^a-z]+/)
|
|
182
|
+
.find((word) => word in VERBS)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The still is a landing frame, so it wants the beat the draft calls the hero
|
|
187
|
+
* rather than an arbitrary one. Falling back to the last beat rather than the
|
|
188
|
+
* first is deliberate: a demo's final state is the payoff, and a cold open is
|
|
189
|
+
* usually an empty screen.
|
|
190
|
+
*/
|
|
191
|
+
function heroIndex(beats: readonly Beat[]): number {
|
|
192
|
+
const named = beats.findIndex((beat) => /hero/i.test(beat.name))
|
|
193
|
+
return named === -1 ? beats.length - 1 : named
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type PlanParse =
|
|
197
|
+
| { status: 'parsed'; plan: DemoPlan }
|
|
198
|
+
| { status: 'failed'; reason: string }
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Validates a plan read off disk. The file is hand-edited between being
|
|
202
|
+
* compiled and being run, which is the whole reason it is committed rather than
|
|
203
|
+
* regenerated, so every field is checked here rather than trusted.
|
|
204
|
+
*
|
|
205
|
+
* An absent field takes the seeded default and a present one is kept, so a
|
|
206
|
+
* plan that dropped a timing block still runs while a tuned one is never
|
|
207
|
+
* overwritten.
|
|
208
|
+
*/
|
|
209
|
+
export function parsePlan(text: string): PlanParse {
|
|
210
|
+
let raw: unknown
|
|
211
|
+
try {
|
|
212
|
+
raw = JSON.parse(text)
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
status: 'failed',
|
|
216
|
+
reason: error instanceof Error ? error.message : 'unreadable json',
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!isRecord(raw)) return { status: 'failed', reason: 'not a json object' }
|
|
221
|
+
if (!Array.isArray(raw.steps))
|
|
222
|
+
return { status: 'failed', reason: 'steps is not an array' }
|
|
223
|
+
if (!raw.steps.length) return { status: 'failed', reason: 'steps is empty' }
|
|
224
|
+
|
|
225
|
+
const steps: DemoStep[] = []
|
|
226
|
+
for (const [index, entry] of raw.steps.entries()) {
|
|
227
|
+
const step = parseStep(entry, index)
|
|
228
|
+
if ('reason' in step) return { status: 'failed', reason: step.reason }
|
|
229
|
+
steps.push(step.step)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const slug = asText(raw.slug)
|
|
233
|
+
const output = isRecord(raw.output) ? raw.output : {}
|
|
234
|
+
const pointer = isRecord(raw.pointer) ? raw.pointer : {}
|
|
235
|
+
const viewport = isRecord(raw.viewport) ? raw.viewport : {}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
status: 'parsed',
|
|
239
|
+
plan: {
|
|
240
|
+
slug,
|
|
241
|
+
title: asText(raw.title),
|
|
242
|
+
url: asText(raw.url),
|
|
243
|
+
viewport: {
|
|
244
|
+
width: asNumber(viewport.width, VIEWPORT.width),
|
|
245
|
+
height: asNumber(viewport.height, VIEWPORT.height),
|
|
246
|
+
},
|
|
247
|
+
output: {
|
|
248
|
+
video: asText(output.video) || `demos/${slug || 'demo'}.webm`,
|
|
249
|
+
still: asText(output.still) || `demos/${slug || 'demo'}.png`,
|
|
250
|
+
},
|
|
251
|
+
pointer: {
|
|
252
|
+
travelMs: asNumber(pointer.travelMs, POINTER_TRAVEL_MS),
|
|
253
|
+
typeDelayMs: asNumber(pointer.typeDelayMs, TYPE_DELAY_MS),
|
|
254
|
+
},
|
|
255
|
+
annotations: ANNOTATIONS,
|
|
256
|
+
steps,
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseStep(
|
|
262
|
+
entry: unknown,
|
|
263
|
+
index: number,
|
|
264
|
+
): { step: DemoStep } | { reason: string } {
|
|
265
|
+
if (!isRecord(entry)) return { reason: `steps[${index}] is not an object` }
|
|
266
|
+
|
|
267
|
+
const kind = asText(entry.kind)
|
|
268
|
+
if (!isStepKind(kind)) {
|
|
269
|
+
return {
|
|
270
|
+
reason: `steps[${index}].kind is "${kind}", which is not a step this can drive`,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const step: DemoStep = {
|
|
275
|
+
beat: asNumber(entry.beat, index + 1),
|
|
276
|
+
name: asText(entry.name),
|
|
277
|
+
kind,
|
|
278
|
+
target: asText(entry.target),
|
|
279
|
+
text: asText(entry.text),
|
|
280
|
+
waitFor: asText(entry.waitFor),
|
|
281
|
+
holdMs: asNumber(entry.holdMs, HOLD_MS),
|
|
282
|
+
caption: asText(entry.caption),
|
|
283
|
+
still: entry.still === true,
|
|
284
|
+
}
|
|
285
|
+
const note = asText(entry.note)
|
|
286
|
+
return { step: note ? { ...step, note } : step }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isStepKind(value: string): value is StepKind {
|
|
290
|
+
return (
|
|
291
|
+
value === 'navigate' ||
|
|
292
|
+
value === 'click' ||
|
|
293
|
+
value === 'fill' ||
|
|
294
|
+
value === 'hover' ||
|
|
295
|
+
value === 'scroll' ||
|
|
296
|
+
value === 'wait' ||
|
|
297
|
+
value === 'hold'
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
302
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function asText(value: unknown): string {
|
|
306
|
+
return typeof value === 'string' ? value : ''
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function asNumber(value: unknown, fallback: number): number {
|
|
310
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Names every field a person still has to fill before the plan can drive
|
|
315
|
+
* anything. Compile reports these and run refuses on them, so an unfilled plan
|
|
316
|
+
* fails at the point it is written rather than part-way through a recording.
|
|
317
|
+
*/
|
|
318
|
+
export function unresolved(plan: DemoPlan): string[] {
|
|
319
|
+
const missing: string[] = plan.url.trim() ? [] : ['url']
|
|
320
|
+
plan.steps.forEach((step, index) => {
|
|
321
|
+
if (TARGETED.has(step.kind) && !step.target.trim()) {
|
|
322
|
+
missing.push(`steps[${index}].target`)
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
return missing
|
|
326
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { join, parse } from 'node:path'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A post-step on the file `@/demo/drive` already wrote. It touches neither
|
|
6
|
+
* the compiler nor the driving code, which is why deferring it out of the
|
|
7
|
+
* feature that shipped first cost no rework.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const CONVERTER_BIN = 'ffmpeg'
|
|
11
|
+
export const INSTALL_CONVERTER = 'ffmpeg (see https://ffmpeg.org/download.html)'
|
|
12
|
+
|
|
13
|
+
export type ContainerResult =
|
|
14
|
+
| { status: 'converted'; mp4Path: string }
|
|
15
|
+
| { status: 'skipped'; reason: 'converter-missing' }
|
|
16
|
+
| { status: 'failed'; reason: string }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Writes mp4 beside the webm rather than instead of it, since both stated use
|
|
20
|
+
* cases are a `<video>` tag on a page the operator controls, where webm
|
|
21
|
+
* already plays, and the social platform case is what mp4 is for.
|
|
22
|
+
*
|
|
23
|
+
* A missing binary is reported as skipped rather than failed. The recording
|
|
24
|
+
* already succeeded, and failing the run over an optional step would discard
|
|
25
|
+
* a good artifact.
|
|
26
|
+
*/
|
|
27
|
+
export async function convertToMp4(
|
|
28
|
+
webmPath: string,
|
|
29
|
+
bin: string = CONVERTER_BIN,
|
|
30
|
+
): Promise<ContainerResult> {
|
|
31
|
+
const { dir, name } = parse(webmPath)
|
|
32
|
+
const mp4Path = join(dir, `${name}.mp4`)
|
|
33
|
+
|
|
34
|
+
const result = await execa(
|
|
35
|
+
bin,
|
|
36
|
+
[
|
|
37
|
+
'-y',
|
|
38
|
+
'-i',
|
|
39
|
+
webmPath,
|
|
40
|
+
'-c:v',
|
|
41
|
+
'libx264',
|
|
42
|
+
'-pix_fmt',
|
|
43
|
+
'yuv420p',
|
|
44
|
+
'-c:a',
|
|
45
|
+
'aac',
|
|
46
|
+
'-movflags',
|
|
47
|
+
'+faststart',
|
|
48
|
+
mp4Path,
|
|
49
|
+
],
|
|
50
|
+
{ reject: false },
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if (result.failed && result.code === 'ENOENT') {
|
|
54
|
+
return { status: 'skipped', reason: 'converter-missing' }
|
|
55
|
+
}
|
|
56
|
+
if (result.exitCode !== 0) {
|
|
57
|
+
return {
|
|
58
|
+
status: 'failed',
|
|
59
|
+
reason: result.stderr?.trim() || `ffmpeg exited ${result.exitCode}`,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { status: 'converted', mp4Path }
|
|
63
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CursorSet } from '@/demo/pointer'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The bundled pointer artwork, drawn as vector markup rather than read from a
|
|
5
|
+
* theme on disk. Spike 3 proved the browser decodes a Windows cursor resource
|
|
6
|
+
* directly, so `--cursor` points at a theme folder and gets that path instead.
|
|
7
|
+
* This set is what makes the command work in a target that has no theme to
|
|
8
|
+
* point at, which is every target on first run.
|
|
9
|
+
*
|
|
10
|
+
* Each drawing sits in a 48 by 48 box so one hotspot scale factor covers the
|
|
11
|
+
* set, and each carries a drop shadow so it stays visible over a light surface
|
|
12
|
+
* and a dark one.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SHADOW =
|
|
16
|
+
'<filter id="s" x="-50%" y="-50%" width="200%" height="200%">' +
|
|
17
|
+
'<feDropShadow dx="0" dy="1" stdDeviation="1.2" flood-opacity="0.45"/></filter>'
|
|
18
|
+
|
|
19
|
+
function svg(body: string): string {
|
|
20
|
+
return `data:image/svg+xml;utf8,${encodeURIComponent(
|
|
21
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">${SHADOW}<g filter="url(#s)">${body}</g></svg>`,
|
|
22
|
+
)}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const STROKE = 'fill="#ffffff" stroke="#1b1b1b" stroke-width="2.2"'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Hotspots are stated against the 48 by 48 box each drawing uses, so the
|
|
29
|
+
* pointer scales them the same way it scales a hotspot read off a real cursor
|
|
30
|
+
* resource. Skipping the scale offsets the artwork by roughly a third of a
|
|
31
|
+
* cursor, which is the defect spike 3 recorded.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_CURSORS: CursorSet = {
|
|
34
|
+
default: {
|
|
35
|
+
image: svg(
|
|
36
|
+
`<path ${STROKE} d="M5 3 L5 35 L13.5 27 L19 39.5 L25 37 L19.5 25 L31 24.5 Z"/>`,
|
|
37
|
+
),
|
|
38
|
+
hotspot: { width: 48, height: 48, hotspotX: 5, hotspotY: 3 },
|
|
39
|
+
},
|
|
40
|
+
pointer: {
|
|
41
|
+
image: svg(
|
|
42
|
+
`<path ${STROKE} d="M18 4 a3.2 3.2 0 0 1 6.4 0 v14 a3 3 0 0 1 5.6 0 v2 a3 3 0 0 1 5.6 0 v2 a3 3 0 0 1 5.4 0 v9 a12 12 0 0 1 -12 12 h-6 a12 12 0 0 1 -12 -12 v-9 a3.2 3.2 0 0 1 6.4 0 z"/>`,
|
|
43
|
+
),
|
|
44
|
+
hotspot: { width: 48, height: 48, hotspotX: 21, hotspotY: 4 },
|
|
45
|
+
},
|
|
46
|
+
// Drawn as one filled outline rather than three stroked segments. A stroked
|
|
47
|
+
// version needs its own fill, and a second fill attribute on a path carrying
|
|
48
|
+
// STROKE makes the markup invalid, which renders as a broken image.
|
|
49
|
+
text: {
|
|
50
|
+
image: svg(
|
|
51
|
+
`<path ${STROKE} d="M18 5 h12 v3.5 h-4 v31 h4 V43 h-12 v-3.5 h4 v-31 h-4 z"/>`,
|
|
52
|
+
),
|
|
53
|
+
hotspot: { width: 48, height: 48, hotspotX: 24, hotspotY: 24 },
|
|
54
|
+
},
|
|
55
|
+
}
|