astack-skills 0.6.0 → 0.7.1

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.
Files changed (45) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +36 -0
  4. package/README.md +6 -4
  5. package/package.json +4 -2
  6. package/plugin.json +1 -1
  7. package/skills/astack/SKILL.md +14 -12
  8. package/skills/astack/agents/openai.yaml +1 -1
  9. package/skills/astack/scripts/shoot.mjs +14 -5
  10. package/skills/context/SKILL.md +6 -6
  11. package/skills/context/agents/openai.yaml +1 -1
  12. package/skills/context/references/source-selection.md +1 -1
  13. package/skills/deslop/SKILL.md +1 -1
  14. package/skills/deslop/agents/openai.yaml +1 -1
  15. package/skills/discoverability/SKILL.md +1 -1
  16. package/skills/discoverability/agents/openai.yaml +1 -1
  17. package/skills/dogfood/SKILL.md +1 -1
  18. package/skills/dogfood/agents/openai.yaml +1 -1
  19. package/skills/dogfood/scripts/shoot.mjs +14 -5
  20. package/skills/instrument/SKILL.md +1 -1
  21. package/skills/instrument/agents/openai.yaml +1 -1
  22. package/skills/launch/SKILL.md +6 -4
  23. package/skills/launch/agents/openai.yaml +1 -1
  24. package/skills/launch/references/film.md +77 -0
  25. package/skills/launch/references/kit.md +5 -4
  26. package/skills/launch/scripts/film.mjs +347 -0
  27. package/skills/launch/scripts/render-gate.mjs +388 -0
  28. package/skills/launch/scripts/shoot.mjs +14 -5
  29. package/skills/oneshot/SKILL.md +11 -11
  30. package/skills/oneshot/agents/openai.yaml +2 -2
  31. package/skills/oneshot/scripts/shoot.mjs +14 -5
  32. package/skills/ship/SKILL.md +6 -6
  33. package/skills/ship/agents/openai.yaml +1 -1
  34. package/skills/ship/scripts/shoot.mjs +14 -5
  35. package/skills/web-design/SKILL.md +12 -2
  36. package/skills/web-design/agents/openai.yaml +1 -1
  37. package/skills/web-design/references/anti-patterns.md +11 -0
  38. package/skills/web-design/references/review-rubric.md +66 -0
  39. package/skills/web-design/scripts/render-gate.mjs +388 -0
  40. package/skills/web-design/scripts/shoot.mjs +14 -5
  41. package/skills/web-graphics/SKILL.md +11 -2
  42. package/skills/web-graphics/agents/openai.yaml +1 -1
  43. package/skills/web-graphics/scripts/fi.mjs +15 -8
  44. package/skills/web-graphics/scripts/render-gate.mjs +388 -0
  45. package/skills/web-graphics/scripts/shoot.mjs +14 -5
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astack",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
5
5
  "author": {
6
6
  "name": "Andrew Wilkinson",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astack",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
5
5
  "author": {
6
6
  "name": "Andrew Wilkinson",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1
4
+
5
+ - Keep coupled work in one agent context and load skills as needed. Independent review now depends on risk, unresolved judgment and repository requirements.
6
+ - Shorten skill descriptions while preserving tested routing cues. Context gathering stops when remaining unknowns cannot change the next step.
7
+ - Reuse existing delivery authority and preserve explicit targets. Skills do not create permission to send, publish or bypass a repository gate.
8
+ - Reject incomplete routing evals and report individual improvements and regressions, including when aggregate scores tie.
9
+
10
+ ## 0.7.0
11
+
12
+ Review gates and video from LangChain's open-source design agent, [Content Studio](https://github.com/langchain-ai/design-agent) (Apache-2.0), credited where they are used. Its idea is that the model makes the creative calls and code renders, measures and refuses.
13
+
14
+ - New `render-gate.mjs` in `web-design`, `web-graphics` and `launch`. It renders a page, card or slide at each size as a fresh layout, then fails:
15
+ - a brand face that silently fell back (Chrome reports which font actually painted each run);
16
+ - supplied copy that changed, or a deliberate clamp that cuts required copy;
17
+ - text clipped by its box or cropped by the canvas edge;
18
+ - painted colours and faces outside the DESIGN.md tokens.
19
+
20
+ Exit 2 means it could not check and is never a pass.
21
+ - New `film.mjs` in `launch`. It renders an HTML composition to MP4 frame by frame: every CSS and Web Animations animation is paused and seeked, so a demo clip is code and every take is identical. It fails:
22
+ - a black or flat opening;
23
+ - a film where nothing moves;
24
+ - clock-driven motion, found by re-seeking frames and comparing them;
25
+ - the wrong size or duration;
26
+ - failed fonts or requests.
27
+ - New `web-design` review rubric:
28
+ - three gates before scoring (accuracy, the deterministic gates, the ask);
29
+ - six areas scored 1 to 5 with evidence;
30
+ - a ship / fix / rework verdict;
31
+ - a failure-pattern table.
32
+
33
+ `astack`'s independent verifier and `launch` use it for visual work.
34
+ - `launch` builds share cards with `render-gate.mjs` and demo clips with `film.mjs`. A new film reference covers storyboard, animatic, motion, contact sheets and changelog clips from a pull request.
35
+ - `web-design` anti-patterns gain a section for sets and assets: scaled instead of recomposed, template sameness, silent font fallback, faked weight.
36
+ - New `launch-assets` comparison eval task. Scored blind: astack 19, baseline 17. Both fixed every planted problem; astack's clip re-rendered bit-identically and its cards stayed on tokens.
37
+ - `shoot.mjs`, `render-gate.mjs` and `film.mjs` now stop Chrome's helper processes along with the browser, so the temporary profile is removed cleanly on Linux instead of failing the run.
38
+
3
39
  ## 0.6.0
4
40
 
5
41
  Motion and mobile craft from [Emil Kowalski's skills](https://github.com/emilkowalski/skills) and animated stickers from [fun-isometric](https://skills.klvn.dev/fun-isometric) by Kelvin Adithya, both MIT and credited where they are used.
package/README.md CHANGED
@@ -40,15 +40,15 @@ Two ways in, depending on whether you're watching.
40
40
  astack this: add annual pricing to the billing page. phones matter most
41
41
  ```
42
42
 
43
- `astack` takes it to a PR you'd be happy to merge. It reads the context, plans, builds, uses the thing like a customer, adds tracking, cleans up the slop, gets a second agent to check the work, and stops there for you.
43
+ `astack` takes it to a PR you'd be happy to merge. It reads the context, plans, builds, uses the thing like a customer, adds relevant tracking, cleans up the diff, and verifies the result. A separate reviewer joins when the risk or judgment calls for one.
44
44
 
45
45
  ```text
46
46
  oneshot this: add annual pricing and get it live
47
47
  ```
48
48
 
49
- `oneshot` is for when you're not watching. Same work, then it merges, deploys, proves production is serving it and tells the team. It only runs when you ask for it by name.
49
+ `oneshot` is for when you're not watching. Same work, then it merges, deploys, proves production is serving it and sends any authorized update. It only runs when you ask for it by name.
50
50
 
51
- Both pull in the other skills as they need them.
51
+ Both keep one working context and load other skills only when their procedure helps the next step. Required repository checks and delivery proof still apply.
52
52
 
53
53
  ## Skills
54
54
 
@@ -63,7 +63,7 @@ Both pull in the other skills as they need them.
63
63
  | [instrument](skills/instrument/SKILL.md) | "can we measure this" | Tracking that fires once, on real success, and a query you can actually trust. |
64
64
  | [discoverability](skills/discoverability/SKILL.md) | "why doesn't ChatGPT mention us" | Checks what Google and AI crawlers really see, fixes it, measures before and after. |
65
65
  | [dogfood](skills/dogfood/SKILL.md) | "use it like a customer" | Walks the real journeys signed in, fixes what breaks and goes round again. |
66
- | [ship](skills/ship/SKILL.md) | "ship it" | Clears CI and review comments, gets an independent check, deploys, and proves production is serving it. Also checks other people's "it's shipped". |
66
+ | [ship](skills/ship/SKILL.md) | "ship it" | Clears CI and review comments, verifies the affected behavior, deploys, and proves production is serving it. Also checks other people's "it's shipped". |
67
67
  | [launch](skills/launch/SKILL.md) | "we launch tomorrow" | Share cards, page checks, posts for X and Show HN, a tracker and how you'll read the results. |
68
68
 
69
69
  Each one has a short guide in [docs/skills](docs/skills/).
@@ -78,6 +78,8 @@ Most skill packs tell the model to be thorough. These come with small scripts so
78
78
  - `slop-scan.mjs` finds the usual suspects, from "seamless" to gradient headlines to emoji bullets, and explains why each one reads as generated. <!-- slop-scan: ignore-line -->
79
79
  - `page-audit.mjs` shows what crawlers and link previews see, like a forgotten noindex or a broken share card.
80
80
  - `iso.mjs` turns a small scene description into clean isometric SVG with the geometry right, because nobody should hand-roll isometric maths.
81
+ - `render-gate.mjs` renders a card, slide or page at each size and fails it when the brand font quietly fell back, the copy drifted from what was asked, text got clipped, or a colour isn't one of your tokens.
82
+ - `film.mjs` turns an HTML page with CSS animations into an MP4, frame by frame, so a demo clip is code you can fix and re-render. It refuses films that open on black, never move, or come out different each take.
81
83
 
82
84
  ## Does it actually help?
83
85
 
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "astack-skills",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "private": false,
5
5
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
6
6
  "license": "MIT",
7
7
  "repository": "https://github.com/ADWilkinson/astack",
8
8
  "packageManager": "bun@1.4.2",
9
9
  "scripts": {
10
- "check": "bun scripts/check.ts && bun test",
10
+ "check": "bun scripts/check.ts && bun test tests/package.test.ts tests/install.test.ts && bun scripts/smoke.ts",
11
+ "smoke": "bun scripts/smoke.ts",
12
+ "test": "bun test",
11
13
  "validate": "bun scripts/check.ts",
12
14
  "metadata": "bun scripts/metadata.ts",
13
15
  "pack": "bun scripts/pack.ts",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "astack",
4
- "version": "0.6.0",
4
+ "version": "0.7.1",
5
5
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
6
6
  "author": {
7
7
  "name": "Andrew Wilkinson",
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: astack
3
- description: Take a product task from "here's the idea" to a pull request that is proven, green and ready for review, covering context, plan, build, real use, measurement and cleanup, without being walked through each step. Use when the user says "astack this", "take this end to end", "do this properly", or hands over a feature, fix, page or experiment and expects it finished rather than planned. Stops before merge; for all the way to production use oneshot.
3
+ description: "Own context, planning, implementation, real use, measurement and checks through a review-ready PR. Use for \"astack this\", \"own this\" or a product task to finish end to end. Load further procedures only as needed. Honor an explicit delivery target."
4
4
  ---
5
5
 
6
6
  # astack
7
7
 
8
- You own the outcome up to a PR the user can review and merge with confidence. Nobody should have to come back and ask "did you check mobile", "is the event firing" or "did CI pass".
8
+ By default, own the outcome up to a PR the user can review and merge with confidence. Honor an explicit target and existing authority. Nobody should have to come back and ask "did you check mobile", "is the event firing" or "did CI pass".
9
9
 
10
10
  `<skill-dir>` means this skill's folder. Run its scripts from the project root.
11
11
 
12
- ## Start by writing the stages into your todo list
12
+ ## Own the work in one loop
13
13
 
14
- Copy these eight lines into your todo list before any task-specific items. Close each one with its proof, or leave it in the list as `skip: <one-line reason>`. A stage that silently disappears is a stage you didn't do.
14
+ Keep context, decisions and execution in the main agent. Load another skill or reference only when its procedure changes the next step; the names below are options, not a required chain. Small fixes need only the relevant checks. For sustained work, track the acceptance criteria and their evidence in the existing plan or PR.
15
15
 
16
16
  1. **Context**: outcome, decisions already made, starting state, the one unknown that could change the approach. (`context`)
17
17
  2. **Plan**: every requirement has a change, a proof and a target. Measurement and delivery are in it. ([planning](references/plan.md); small fixes need three lines)
@@ -19,23 +19,25 @@ Copy these eight lines into your todo list before any task-specific items. Close
19
19
  4. **Use it**: the real journey, in a browser or runtime, to the real outcome; fix and replay. (`dogfood`)
20
20
  5. **Measure**: the success action fires once, on real success, and arrives. (`instrument`, when there is a success action)
21
21
  6. **Clean**: `deslop` scan on changed copy, UI and code; every medium or high hit fixed or dismissed with a reason.
22
- 7. **Verify independently**: a fresh agent that did not write the change exercises it and returns PASS or FAIL with evidence. (`ship`; docs-only or trivial changes may skip)
22
+ 7. **Verify**: run the repo's required checks and exercise the affected behavior. Use an independent reviewer when subjective quality, security, money, migrations or unresolved uncertainty warrants a separate perspective. Give it acceptance criteria and source evidence, not your conclusions. For visual work, inspect renders against the product's design and the user's references.
23
23
  8. **PR ready**: `node <skill-dir>/scripts/pr-state.mjs` reports no blockers on the current head; the PR body says what changed, how it was proven, and what was not verified. (`ship`)
24
24
 
25
- For visual changes, capture 390 and 1440 in light and dark with [`scripts/shoot.mjs`](scripts/shoot.mjs) and look at the images. Use [completion checks](references/completion-checks.md) as the final gate.
25
+ For visual changes, capture 390 and 1440 with [`scripts/shoot.mjs`](scripts/shoot.mjs) and inspect the images, including the themes and states the product supports. Read [completion checks](references/completion-checks.md) when reconciling a larger change before delivery.
26
+
27
+ Delegate when parallel work needs independent context, reading would crowd out the main task, or an independent verdict matters. Keep ownership separate and return evidence, limitations and affected files. A specialist name alone is no reason to start another agent.
26
28
 
27
29
  ## What you may do without asking
28
30
 
29
- Invoking astack is the permission for everything reversible that the stages need: create a branch, run the app and dev servers, drive a browser, take screenshots, run any test method, install dev dependencies the repo already declares, push the branch, open or update the PR, reply to review threads with fixes, and re-run CI after a diagnosis.
31
+ Within the user's authorized task and target: create a branch, run the app and dev servers, drive a browser, take screenshots, run tests, install dev dependencies the repo already declares, push the branch, open or update the PR, and re-run CI after a diagnosis. External replies still need authority for that destination. A skill describes procedure; it does not expand the user's grant.
30
32
 
31
33
  ## Ask only when
32
34
 
33
35
  - you cannot find a way to prove a change;
34
36
  - the goal is ambiguous in a way that changes the result;
35
- - the next step is outside the grant: merging, deploying to production, deleting data, spending money, messaging people, changing anything outside the repo;
37
+ - the next step is outside the existing grant, such as merging, production deployment, deleting data, spending money or messaging people;
36
38
  - another PR or decision makes this one obsolete.
37
39
 
38
- Ask with the harness's question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex), not in a normal message, or the thread looks finished. Put the context and your recommendation in the question. If no tool exists or nobody answers, take the safe path: leave the work on its branch or open PR, and start your final message with the question. Decide everything else yourself and list those decisions in the report.
40
+ Reuse authority already given for the same action and target. If a material decision remains, use the client's available question mechanism and explain what it changes. Continue independent work while waiting. Without required authority, leave the result concrete and reviewable at the authorized target. Decide routine implementation details yourself.
39
41
 
40
42
  ## Do not stop early
41
43
 
@@ -43,9 +45,9 @@ Ending your turn while checks or reviews are pending ends the work, because nobo
43
45
 
44
46
  ## Finish
45
47
 
46
- - **Outcome**: one sentence, at the PR-ready target.
47
- - **Proof**: PR link, checks, the independent verdict, screenshots or queries, each with how it was observed.
48
+ - **Outcome**: one sentence at the agreed target, defaulting to PR-ready.
49
+ - **Proof**: PR link, verification method and any independent verdict, screenshots or queries, each with how it was observed.
48
50
  - **Decisions you made** for the user.
49
51
  - **Not verified**: the exact gap and next step, if any.
50
52
 
51
- To take it further (merge, deploy, verify live, tell the team) the user says `oneshot`.
53
+ Further delivery follows the user's explicit target and existing authority; `oneshot` requests the full path.
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: astack
3
3
  short_description: "Own a product task up to a proven, review-ready PR"
@@ -94,6 +94,12 @@ async function launch() {
94
94
  catch (e) { await sleep(1000); try { return await launchOnce(); } catch { throw e; } }
95
95
  }
96
96
 
97
+ // Best effort: a leftover temp profile must never fail the check itself.
98
+ function removeProfile(profile) {
99
+ try { rmSync(profile, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }); }
100
+ catch (e) { process.stderr.write(`warning: could not remove ${profile}: ${e.code ?? e.message}\n`); }
101
+ }
102
+
97
103
  async function launchOnce() {
98
104
  const profile = mkdtempSync(join(tmpdir(), "shoot-profile-"));
99
105
  const proc = spawn(findChrome(), [
@@ -104,11 +110,13 @@ async function launchOnce() {
104
110
  ...(process.env.CI || process.getuid?.() === 0 ? ["--no-sandbox"] : []),
105
111
  ...(process.env.SHOOT_CHROME_FLAGS ? process.env.SHOOT_CHROME_FLAGS.split(/\s+/) : []),
106
112
  "about:blank",
107
- ], { stdio: ["ignore", "ignore", "pipe"] });
113
+ ], { stdio: ["ignore", "ignore", "pipe"], detached: process.platform !== "win32" });
108
114
  let stderr = "";
109
115
  proc.stderr.on("data", d => { stderr = (stderr + d).slice(-2000); });
110
116
  const port = await new Promise((ok, fail) => {
111
- const timer = setTimeout(() => fail(new Error("Chrome did not expose a DevTools port")), 15000);
117
+ const timer = setTimeout(() => { clearInterval(poll);
118
+ try { process.kill(process.platform === "win32" ? proc.pid : -proc.pid, "SIGKILL"); } catch { /* already exited */ }
119
+ fail(new Error("Chrome did not expose a DevTools port")); }, 15000);
112
120
  const poll = setInterval(() => {
113
121
  const file = join(profile, "DevToolsActivePort");
114
122
  if (existsSync(file)) {
@@ -117,14 +125,15 @@ async function launchOnce() {
117
125
  }
118
126
  }, 50);
119
127
  proc.on("exit", code => { clearInterval(poll); clearTimeout(timer);
120
- rmSync(profile, { recursive: true, force: true });
128
+ removeProfile(profile);
121
129
  fail(new Error(`Chrome exited (${code})${stderr.trim() ? `: ${stderr.trim().split("\n").slice(-3).join(" | ")}` : ""}`)); });
122
130
  });
123
131
  const exited = new Promise(r => proc.once("exit", r));
124
132
  const close = async () => {
125
- try { proc.kill("SIGKILL"); } catch { /* already exited */ }
133
+ // Chrome's helpers (crashpad, zygote) outlive the main process and keep writing the profile.
134
+ try { process.kill(process.platform === "win32" ? proc.pid : -proc.pid, "SIGKILL"); } catch { /* already exited */ }
126
135
  await Promise.race([exited, sleep(3000)]);
127
- rmSync(profile, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
136
+ removeProfile(profile);
128
137
  };
129
138
  return { port, close };
130
139
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: context
3
- description: Gather what actually changes a task before starting it, from Slack threads, issues, PRs, docs, git history, the live product and analytics, and reconcile it into a short brief. Use when picking up a ticket, a Slack ask, a half-finished branch, "look into X", or any task where earlier decisions, corrections or numbers live somewhere other than the prompt.
3
+ description: "Reconcile prior decisions and current evidence into a brief that changes the next step. Use when picking up a ticket, thread or unfinished branch whose scope depends on history outside the prompt."
4
4
  ---
5
5
 
6
6
  # Context
@@ -21,7 +21,7 @@ Search for those answers. Stop reading sources that answer nothing on the list.
21
21
 
22
22
  If the task touches existing code, anchor first: the files and line ranges involved, `git blame` on them, the last commits and the PRs those commits came from (`gh pr view <n> --json title,body,comments,reviews`). That anchor tells you which threads and decisions to look for.
23
23
 
24
- Then go through each kind of evidence that is available: code history, the issue tracker, docs, team chat, error tracking, analytics. One that has nothing relevant is still a finding; write "searched, nothing" rather than skipping it silently. One you cannot reach is a gap; say so.
24
+ Follow the sources most likely to settle the named unknowns: code history, the issue tracker, docs, team chat, error tracking or analytics. Stop when the next action is clear. Report an unreachable source when it leaves a material question unresolved; an exhaustive source sweep is not a completion requirement.
25
25
 
26
26
  Use the connected tools; prefer the original over a summary of it. Commands that pay off:
27
27
 
@@ -34,10 +34,10 @@ git log -p --follow -- path/to/file | head -200
34
34
  git blame -L 120,160 path/to/file
35
35
  ```
36
36
 
37
- - **Slack / chat**: read the whole thread including late replies; the correction is usually at the bottom. Search with the channel and date bounds (`in:#channel after:2026-01-01 "keyword"`), then open each hit in context.
38
- - **Docs and specs**: check the date and whether later code or chat superseded them.
39
- - **Live product**: open the real page or call the real endpoint. What it does today beats what a doc says it does.
40
- - **Analytics**: read the definition (event, filters, window) before reading the number. A number without its definition is not context.
37
+ - Slack / chat: read the whole thread including late replies; the correction is usually at the bottom. Search with the channel and date bounds (`in:#channel after:2026-01-01 "keyword"`), then open each hit in context.
38
+ - Docs and specs: check the date and whether later code or chat superseded them.
39
+ - Live product: open the real page or call the real endpoint. What it does today beats what a doc says it does.
40
+ - Analytics: read the definition (event, filters, window) before reading the number. A number without its definition is not context.
41
41
 
42
42
  See [source selection](references/source-selection.md) for which source wins when they disagree, and for multi-source tasks.
43
43
 
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: Context
3
3
  short_description: Gather and reconcile what changes the task
@@ -22,7 +22,7 @@
22
22
 
23
23
  ## Signs the context is wrong
24
24
 
25
- - The brief has no "Open" line. Something is always uncertain; find it.
25
+ - A material unknown is hidden. If no remaining uncertainty changes the next step, say "Open: none material" and proceed.
26
26
  - The brief repeats the prompt. You read nothing that changed the work.
27
27
  - A number appears without project, population, filter and window.
28
28
  - A "decided" item has no link or date.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: deslop
3
- description: Strip the tells of generated work from copy, UI and code, such as inflated vocabulary, "not just X but Y", staged reveals, em-dash rhythm, emoji bullets, gradient-and-glass UI, narrating comments and defensive clutter, while keeping facts, behaviour and voice. Use when the user says deslop, "this sounds like AI", "make it sound human", "tighten this", before publishing copy or merging UI, or when writing anything public. Also builds a voice profile from someone's real writing.
3
+ description: "Write or revise public copy, UI and code without generated tells, preserving facts and voice. Use for \"deslop\", \"sounds like AI\", tightening prose, user emails, removing generic UI or learning a voice from real writing."
4
4
  ---
5
5
 
6
6
  # Deslop
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: Deslop
3
3
  short_description: "Strip AI tells from copy, UI and code"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: discoverability
3
- description: Find out why the right people do not find or choose a product through Google, Bing and AI answer engines (ChatGPT, Claude, Perplexity, AI Overviews), fix the cause, and measure the result. Use for SEO audits, "why aren't we ranking", "does ChatGPT recommend us", GEO/AEO, indexing problems, canonical/robots/sitemap issues, comparison and alternatives pages, or before and after a content push.
3
+ description: "Diagnose and fix why Google, Bing or AI answers miss or misdescribe a product. Use for SEO/GEO/AEO, ranking or indexing problems, canonical/robots/sitemap checks and search-focused content."
4
4
  ---
5
5
 
6
6
  # Discoverability
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: Discoverability
3
3
  short_description: Fix why search and AI answers miss the product
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dogfood
3
- description: Use the product like a real user, signed in, with real data, through to the result they came for, then fix what breaks and repeat until the journeys pass. Use for "test this end to end", "QA the app", "use it like a customer", "find what's broken", pre-launch checks, after a big change, or for AI features whose answers need repeated real trials. Fixes as it goes when fixes are authorised.
3
+ description: "Walk real product journeys, fix authorized problems and replay through the outcome. Use for end-to-end QA, signed-in mobile checks, pre-launch testing and repeated trials of AI features. Review-only requests produce findings."
4
4
  ---
5
5
 
6
6
  # Dogfood
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: Dogfood
3
3
  short_description: "Use the product for real, fix what breaks"
@@ -94,6 +94,12 @@ async function launch() {
94
94
  catch (e) { await sleep(1000); try { return await launchOnce(); } catch { throw e; } }
95
95
  }
96
96
 
97
+ // Best effort: a leftover temp profile must never fail the check itself.
98
+ function removeProfile(profile) {
99
+ try { rmSync(profile, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }); }
100
+ catch (e) { process.stderr.write(`warning: could not remove ${profile}: ${e.code ?? e.message}\n`); }
101
+ }
102
+
97
103
  async function launchOnce() {
98
104
  const profile = mkdtempSync(join(tmpdir(), "shoot-profile-"));
99
105
  const proc = spawn(findChrome(), [
@@ -104,11 +110,13 @@ async function launchOnce() {
104
110
  ...(process.env.CI || process.getuid?.() === 0 ? ["--no-sandbox"] : []),
105
111
  ...(process.env.SHOOT_CHROME_FLAGS ? process.env.SHOOT_CHROME_FLAGS.split(/\s+/) : []),
106
112
  "about:blank",
107
- ], { stdio: ["ignore", "ignore", "pipe"] });
113
+ ], { stdio: ["ignore", "ignore", "pipe"], detached: process.platform !== "win32" });
108
114
  let stderr = "";
109
115
  proc.stderr.on("data", d => { stderr = (stderr + d).slice(-2000); });
110
116
  const port = await new Promise((ok, fail) => {
111
- const timer = setTimeout(() => fail(new Error("Chrome did not expose a DevTools port")), 15000);
117
+ const timer = setTimeout(() => { clearInterval(poll);
118
+ try { process.kill(process.platform === "win32" ? proc.pid : -proc.pid, "SIGKILL"); } catch { /* already exited */ }
119
+ fail(new Error("Chrome did not expose a DevTools port")); }, 15000);
112
120
  const poll = setInterval(() => {
113
121
  const file = join(profile, "DevToolsActivePort");
114
122
  if (existsSync(file)) {
@@ -117,14 +125,15 @@ async function launchOnce() {
117
125
  }
118
126
  }, 50);
119
127
  proc.on("exit", code => { clearInterval(poll); clearTimeout(timer);
120
- rmSync(profile, { recursive: true, force: true });
128
+ removeProfile(profile);
121
129
  fail(new Error(`Chrome exited (${code})${stderr.trim() ? `: ${stderr.trim().split("\n").slice(-3).join(" | ")}` : ""}`)); });
122
130
  });
123
131
  const exited = new Promise(r => proc.once("exit", r));
124
132
  const close = async () => {
125
- try { proc.kill("SIGKILL"); } catch { /* already exited */ }
133
+ // Chrome's helpers (crashpad, zygote) outlive the main process and keep writing the profile.
134
+ try { process.kill(process.platform === "win32" ? proc.pid : -proc.pid, "SIGKILL"); } catch { /* already exited */ }
126
135
  await Promise.race([exited, sleep(3000)]);
127
- rmSync(profile, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
136
+ removeProfile(profile);
128
137
  };
129
138
  return { port, close };
130
139
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: instrument
3
- description: Add product analytics that answer a real question, such as events, funnels, conversion and activation metrics, and prove them end to end, so the event fires once on real success, arrives in the right project with clean properties and identity, and a saved query reads it. Use when adding tracking, "can we measure this", setting up PostHog/Amplitude/Mixpanel/GA events, building a funnel, when a dashboard number looks wrong or changed suddenly, or before shipping a feature whose impact matters.
3
+ description: "Add or diagnose product analytics and prove events arrive once on real success with safe properties. Use for tracking, conversion funnels, PostHog/GA events or dashboard numbers that look wrong or change suddenly."
4
4
  ---
5
5
 
6
6
  # Instrument
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: Instrument
3
3
  short_description: Track the outcome and prove the event arrives
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: launch
3
- description: Get a product or feature ready to be seen in public and put it in front of people, covering share cards and metadata, a launch-ready landing page, demo media, announcement posts, changelog, directory and community listings with a tracker, and launch-week measurement. Use for "launch this", "prepare a launch kit", "write the launch post", "submit to directories", "make an OG image", Product Hunt / Show HN / X launches, or before telling the public about anything new.
3
+ description: "Prepare a product launch, share cards, OG images, demo clips, announcement copy, changelog or community listings, with a distribution tracker and measurement. Use for launch kits, X/Show HN posts and directory submissions."
4
4
  ---
5
5
 
6
6
  # Launch
@@ -31,18 +31,20 @@ Fix before announcing:
31
31
 
32
32
  Put it in one folder (`launch/` or `docs/launch/`). Contents and formats in [kit](references/kit.md):
33
33
 
34
- - **Share card** rendered from HTML/SVG source by script, committed with its source.
35
- - **Demo media**: 3 to 5 screenshots at real sizes, and a 6 to 15 second clip of the core action if motion helps. Real product, real-looking data, no fake metrics.
34
+ - **Share card** rendered from HTML/SVG source with `render-gate.mjs`, committed with its source. The gate fails a card whose font fell back, whose copy drifted from `copy.md`, or whose text is cropped.
35
+ - **Demo media**: 3 to 5 screenshots at real sizes, and a 6 to 15 second clip of the core action if motion helps. Build the clip as an HTML composition and render it with `film.mjs`, so a fix is an edit and a re-render ([film](references/film.md)). Real product, real-looking data, no fake metrics.
36
36
  - **Copy**: one-line description (60 characters or fewer), a short paragraph, a longer description, the X/LinkedIn post, a Show HN or community post if it fits, a changelog entry. Rules in [copy](references/copy.md).
37
37
  - **Tracker**: a CSV of where you will post or submit.
38
38
 
39
+ Before anything goes out, score the card, screenshots and clip with `web-design`'s review rubric if it is installed, from your own renders at feed size. Only a ship verdict goes out.
40
+
39
41
  ## 3. Write copy that survives scrutiny
40
42
 
41
43
  Specific, verifiable, in the product's voice. Lead with what someone can do now. Every number has a source; someone else's numbers are labelled as theirs. No guarantees you cannot keep. Run `deslop`'s scan on every piece. Vary the angle per audience rather than pasting one blurb everywhere.
42
44
 
43
45
  ## 4. Distribute deliberately
44
46
 
45
- Order the tracker by fit, not by count: the communities where the product's users already are come first. Respect each place's rules (self-promotion limits, required formats, no voting rings). Posting, submitting and messaging go out under the user's accounts: draft everything, and send or submit only with their go-ahead for each destination. Update the tracker with status and the live URL as each goes out.
47
+ Order the tracker by fit, not by count: the communities where the product's users already are come first. Respect each place's rules (self-promotion limits, required formats, no voting rings). Draft the material, then send or submit within the user's authority for each destination; reuse authority already given for that action and target. Update the tracker with status and the live URL as each goes out.
46
48
 
47
49
  ## 5. Measure the week
48
50
 
@@ -1,3 +1,3 @@
1
- interface:
1
+ interface:
2
2
  display_name: Launch
3
3
  short_description: "Ready the page, kit, posts and tracker for launch"
@@ -0,0 +1,77 @@
1
+ # Film: demo and changelog clips as code
2
+
3
+ A clip built as an HTML page on a timeline can be reviewed, diffed and re-rendered. A fix is an edit, not a re-shoot. `film.mjs` pauses the page, seeks every animation to each frame time, captures it and encodes with ffmpeg. The approach follows LangChain's [Content Studio](https://github.com/langchain-ai/design-agent) and [HyperFrames](https://hyperframes.heygen.com); `film.mjs` is a dependency-free take on the same idea.
4
+
5
+ ## The order of work
6
+
7
+ 1. **Storyboard.** Write it in `launch/media/STORYBOARD.md`, one line per beat. Give each: its time, what is on screen, the one thing the viewer should read, and the source (a real screenshot, a real number with its query). 6 to 15 seconds is 3 to 5 beats. Cut any beat that has nothing to read.
8
+ 2. **Material.** Capture real screens with `shoot.mjs` at the size they will appear, at `--scale 2`. No mock dashboards, no invented numbers.
9
+ 3. **Animatic.** Build the composition with every beat in place and almost no motion. Render it, and look at a contact sheet (below) before adding motion. Most problems are layout and copy, and they are cheaper to fix here.
10
+ 4. **Motion.** Add only motion that explains: a cursor that travels to the button, a value that changes, a panel that opens. Ease out, 200 to 600ms per move, one thing moving at a time.
11
+ 5. **Render and gate.** `film.mjs` must exit 0. Then watch it once at full size and once at feed size, and score it with `web-design`'s review rubric if it is installed.
12
+
13
+ ## The composition contract
14
+
15
+ - Size the page to the frame: `body { width: 1920px; height: 1080px; margin: 0; overflow: hidden }`.
16
+ - Declare the length: `<html data-duration="8">`.
17
+ - Drive everything from CSS animations or the Web Animations API, started on load and placed with `animation-delay`. `film.mjs` pauses all of them and sets their time per frame, so they render identically every take.
18
+ - `<video>` elements are seeked to the frame time. GSAP's global timeline is seeked if `window.gsap` exists.
19
+ - Anything else (canvas loops, counters on `setInterval`, `requestAnimationFrame`) must expose `window.__film = { duration, seek(seconds) }` and draw the exact state for that time. `film.mjs` re-seeks two frames after rendering and fails the film if they changed: that is how clock-driven motion is caught.
20
+ - Use the product's real fonts and tokens, and run `render-gate.mjs` on the composition at its frame size to catch font fallback and off-token colours before rendering.
21
+
22
+ A minimal composition:
23
+
24
+ ```html
25
+ <!doctype html>
26
+ <html data-duration="8">
27
+ <style>
28
+ @font-face { font-family: "Brand"; src: url(../../public/fonts/brand.woff2) }
29
+ body { width: 1920px; height: 1080px; margin: 0; overflow: hidden; background: #F6F8FA; color: #0E1726; font: 600 72px/1.1 Brand, sans-serif }
30
+ .beat { position: absolute; inset: 0; display: grid; place-items: center; opacity: 0 }
31
+ .b1 { animation: leave 2.8s both }
32
+ .b2 { animation: show 3s 2.8s both }
33
+ .b3 { animation: show 2.2s 5.8s both }
34
+ .shot { width: 1400px; border-radius: 16px; box-shadow: 0 24px 60px rgb(14 23 38 / .18) }
35
+ .b2 .shot { animation: rise .5s 2.8s cubic-bezier(.2, .8, .2, 1) both }
36
+ @keyframes leave { 0%, 92% { opacity: 1 } 100% { opacity: 0 } }
37
+ @keyframes show { 0%, 100% { opacity: 0 } 6%, 94% { opacity: 1 } }
38
+ @keyframes rise { from { transform: translateY(40px) } }
39
+ </style>
40
+ <div class="beat b1">Sell USDC for cash</div>
41
+ <div class="beat b2"><img class="shot" src="shots/1440-light.png" alt=""></div>
42
+ <div class="beat b3">usdctofiat.xyz</div>
43
+ </html>
44
+ ```
45
+
46
+ ```sh
47
+ node <skill-dir>/scripts/film.mjs launch/media/demo.html --size 1920x1080 --out launch/media/demo.mp4 --poster launch/media/demo-poster.png --poster-at 3
48
+ ```
49
+
50
+ The first beat is fully visible at 0s and only fades on its way out, so the film opens on words. `film.mjs` fails a film that opens on a flat or black frame.
51
+
52
+ ## Look at it
53
+
54
+ A contact sheet shows the whole film at once:
55
+
56
+ ```sh
57
+ ffmpeg -loglevel error -i launch/media/demo.mp4 -vf "fps=1,scale=480:-1,tile=4x2" -frames:v 1 launch/media/sheet.png
58
+ ```
59
+
60
+ Read every beat: is the one thing to read legible at 480 wide? Does each beat hold long enough to read it aloud? `film.mjs` notes holds over 2 seconds, which are right for a beat with words and wrong between beats.
61
+
62
+ ## Changelog clip from a pull request
63
+
64
+ For "make a clip of what shipped":
65
+
66
+ 1. Read the PR: title, description, diff and linked issue. Find the one change a user would notice. A refactor has no clip.
67
+ 2. Capture before and after with `shoot.mjs` at the same size and state: before from the base revision or production, after from the preview or the merged deploy.
68
+ 3. Three beats: what changed in the user's words, before and after (side by side or as a wipe), where to try it.
69
+ 4. Every claim in the clip traces to the PR or a real measurement.
70
+
71
+ ## Sound
72
+
73
+ Silent by default; feeds autoplay muted. If speech matters, burn the captions into the composition as text. Use `--audio` only for a track you have rights to, and normalise it first: `ffmpeg -i in.wav -af loudnorm=I=-16:TP=-1.5 out.m4a`.
74
+
75
+ ## Heavier jobs
76
+
77
+ Long films, many scenes or a team review loop may outgrow one HTML file. [HyperFrames](https://hyperframes.heygen.com) (HeyGen, Apache-2.0) is a full framework for the same model, with a CLI, a linter and its own agent skills. It installs third-party code, so ask the user before adding it to a project.
@@ -20,7 +20,7 @@ launch/
20
20
 
21
21
  ## Share card
22
22
 
23
- - Author in HTML or SVG at 1200x630 with the product's fonts and colours; render with `node <skill-dir>/scripts/shoot.mjs launch/card/card.html --widths 1200 --height 630 --themes light --scale 1 --out launch/card`. Rename the output to `og.png`.
23
+ - Author in HTML or SVG at 1200x630 with the product's fonts and colours; render and gate with `node <skill-dir>/scripts/render-gate.mjs launch/card/card.html --size 1200x630 --size 1200x1200 --design DESIGN.md --expect-file launch/card/copy.txt --out launch/card`. Rename the outputs to `og.png` and `og-square.png`. Each size is a fresh layout; recompose the square, never scale it.
24
24
  - One message: the product name, what it does in under eight words, and either the product itself (a real screenshot crop) or one strong graphic. No paragraphs.
25
25
  - Keep important content inside the central ~1000x500; platforms crop edges.
26
26
  - Test it on a dark and a light feed background; a white card with no border disappears on a white feed.
@@ -36,9 +36,10 @@ launch/
36
36
  ## Demo clip
37
37
 
38
38
  - 6 to 15 seconds, one action from start to result. Start on the meaningful frame, not a blank page.
39
- - Record at a fixed viewport (1920x1080 or 1080x1920 for vertical) with reduced motion off and a steady pace; avoid cursor wandering.
40
- - Deterministic: same data and same timing each take, so you can re-record after a fix.
41
- - Check the export: duration, dimensions, no black first frame, file size within each platform's limit.
39
+ - Build it as an HTML composition and render it with `film.mjs` ([film](film.md)): real screenshots from `shoot.mjs` as the material, captions and motion in CSS. 1920x1080, or 1080x1920 for vertical.
40
+ - Deterministic: the same composition renders the same film every time, so a fix is an edit and a re-render. `film.mjs` fails a clip that does not.
41
+ - `film.mjs` checks size, duration, a black or flat opening frame, a film where nothing moves, and failed fonts or requests. Then check the file size against each platform's limit.
42
+ - Record the live product instead only when the interaction itself is the point; keep the viewport fixed, the pace steady and the cursor purposeful.
42
43
 
43
44
  ## Tracker
44
45