Skip to content

Refine SKILL.md: principles, structure, examples#130

Open
DmitrySharabin wants to merge 2 commits into
mainfrom
refine-skill-md
Open

Refine SKILL.md: principles, structure, examples#130
DmitrySharabin wants to merge 2 commits into
mainfrom
refine-skill-md

Conversation

@DmitrySharabin
Copy link
Copy Markdown
Member

@DmitrySharabin DmitrySharabin commented May 15, 2026

Summary

Iterates on SKILL.md from a real session using it to refactor a downstream project's tests. Three themes: frontload principles, cut duplications, capture gotchas.

Key changes

  • Restructure: Best Practices + Common Mistakes moved up to right after Core Pattern, so principles frame the mechanics that follow.
  • Two foundational principles atop Best Practices, as meta-rules that override specific guidance below on conflict:
    • What abstraction makes the tests easier to understand for humans?
    • Write the tests first; scaffold around them.
  • New Common Mistakes rows: reading values via this.arg.X inside run (use the parameter list); // comments for test intent (use description, kept brief).
  • Tightened rows: expect: true (smell, with yes/no-question-name exception); custom run per test (identical bodies vs unique imperative content).
  • New data example: setup hook stashing a per-test fixture for run, with the hook-vs-run parameter-access asymmetry called out.
  • Docs reference: top-of-file pointer to https://htest.dev + specific link to data docs.
  • Removed duplications: args vs arg warning, "don't test the language", over-prescriptive run (value) row.

Preview: https://deploy-preview-130--h-test.netlify.app/skill.html

- Frontload foundational principles (move Best Practices + Common Mistakes
  to right after Core Pattern so they frame the mechanics that follow)
- Add two foundational principles atop Best Practices: "what abstraction
  makes tests easier to understand for humans" and "write the tests
  first; scaffold around them"
- Add Common Mistakes rows for reading values via this.arg (use the
  parameter list) and inline comments for test intent (use description)
- Add a new data example showing a setup hook stashing a per-test
  fixture for run, with the hook-vs-run parameter-access asymmetry
  called out
- Clarify hook signatures in the Lifecycle property-table row (called
  with no parameters)
- Link to https://htest.dev as source of truth + a specific link to the
  data docs section
- Reduce duplications: args-vs-arg warning lived in both property table
  and Common Mistakes; "don't test the language" lived in both Best
  Practices and Common Mistakes; the over-prescriptive run (value)
  Common Mistakes row contradicted the looser this.arg row, dropped
@netlify
Copy link
Copy Markdown

netlify Bot commented May 15, 2026

Deploy Preview for h-test ready!

Name Link
🔨 Latest commit 0aae0de
🔍 Latest deploy log https://app.netlify.com/projects/h-test/deploys/6a078f021bd5a00008fdc623
😎 Deploy Preview https://deploy-preview-130--h-test.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread SKILL.md Outdated

- **What abstraction makes the tests easier to understand for humans?** If a rule below would impose an abstraction that makes a test harder to read, the rule is wrong for that test. Declarativeness exists to remove repetitive noise — not to force inherently imperative code into a data shape.

- **Write the tests first; scaffold around them.** Express each test as data (`name`, `arg`, `expect`) in whatever shape gives you maximum signal-to-noise on the intent. Write `run()`, `beforeEach()`, and other scaffolding *at the end*, derived from what the tests need. Starting with `run()` first shapes the tests around what `run()` can express, instead of letting the tests express the intent.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add something around "The goal is maximizing signal-to-noise ratio, not characters. DRY is not the endgame, it just often correlates."

But also run() can sometimes be legit part of the test, like in the ones we were discussing.

Maybe instead of discouraging run() altogether, tell the agent to write the test both with and without a run() and evaluate what expresses intent better?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Take a look, please.

Comment thread SKILL.md Outdated
Comment thread SKILL.md Outdated
| Reading values via `this.arg.X` / `this.args[0]` inside `run` | The parameter list IS where `arg`/`args` arrive. Name and unpack them however fits the case: `run ({ x })`, `run ([first, ...rest])`, `run (args) { let first = args[0]; }`, etc. Reserve `this` for `this.data` (lifecycle state) and instance props. |
| Inline `//` comment explaining what the test verifies | Use the `description` field — that's its home as structured metadata next to the test. **Keep it brief** (a sentence, maybe two): add what the `name` can't carry — an issue reference, a subtle invariant, a non-obvious edge case. Don't write a paragraph and don't restate the name in prose. Reserve comments for short notes about mechanics. |
| Setup in `beforeAll` but used in `arg` expressions | Move to module top level — `arg`/`expect` evaluate at import time, before hooks |
| `new Instance()` in both `arg` and `expect` | Share one instance variable |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example? (for me, not the docs)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The row's pointing at hTest's default check using === at leaf level — two structurally-equal-but-distinct instances fail. Short example:

// ❌ Two `new Map()` calls — different instances, fails
{ arg: new Map([["k", "v"]]), expect: { result: new Map([["k", "v"]]) } }

// ✅ Share one instance
let map = new Map([["k", "v"]]);
{ arg: map, expect: { result: map } }

Same pattern is spelled out under Reference Equality for Instances later in the same file. I also tightened the row itself with a cross-link + brief "why" since your "Example?" suggested it was too terse for cold readers — happy to revert if you'd rather leave the row as-is.

Comment thread SKILL.md
| -------------------------- | ---------------------------------------------------------------------------------------------- |
| `beforeEach` / `afterEach` | Run before/after each test. Inherited. `afterEach` runs even if the test throws. Sync or async |
| `beforeAll` / `afterAll` | Run before/after all tests in the group where defined. **Not inherited** |
| `beforeEach` / `afterEach` | Run before/after each test. Receive no parameters — access state via `this.data`, `this.arg`, etc. Inherited. `afterEach` runs even if the test throws. Sync or async |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should be passing parameters to them!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They probably need arg/args in most cases. And yes, I think it's useful. Everything else can be accessed via this.

Copy link
Copy Markdown
Member

@LeaVerou LeaVerou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments, but otherwise this looks great!

- Bullet 2: add DRY-isn't-the-endgame framing; move 'write both ways
  and pick' tactic into the principle; drop the discouragement that
  framed run() as a smell
- First Common Mistakes row: broaden to 'minor differences'; tell the
  reader how to extract (push variations into arg/args)
- new Instance() row: explain ===-at-leaves cause; cross-link to
  Reference Equality for Instances
@DmitrySharabin DmitrySharabin requested a review from LeaVerou May 15, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants