You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit a32fc04
Browse filesBrowse the repository at this point in the historyBrowse files
style: widen the house-style rules to CLAUDE.md and .conventions
First slice of the broadening in #169. The three prose rules were scoped
to READMEs only, because landing them repo-wide meant roughly 2300
findings at level: error. This adds the two smallest surfaces and fixes
what they catch, so the slice is green on its own.
41 findings: 39 em dashes rewritten as a period, comma, colon, or
parentheses per the rule's own message rather than swapped mechanically
for one substitute, and two uses of "simply" where the sentence was
describing a real distinction ("merely lives further down", "just out of
date") rather than hedging.
Fenced code blocks are untouched. These rules carry Vale's default
scope, which does not read them, so the em dashes in the shell comments
at CLAUDE.md:155 and STYLEGUIDE-CODE.md:222 are out of scope and stay.
Remaining surfaces, in the order #169 proposes: TypeScript comments
(Vale's comments-only tier), then the agent-facing recipe text, which is
the largest. openspec/changes/archive/ gets a permanent exclusion rather
than a slice.
Refs #169
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cEN93Acyp4zBwP3oDnyy1
Copy file name to clipboardExpand all lines: .conventions/STYLEGUIDE-CODE.md
+13-13Lines changed: 13 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,7 +124,7 @@ interface GitHubComment {
124
124
125
125
### Export Types Referenced by Public API Signatures
126
126
127
-
**DO NOT** remove `export` from types that are transitively referenced by exported functions, values, or other exported types — even if tools like knip report them as "unused exports." With `declaration: true` in `tsconfig`, TypeScript requires all types in exported signatures to be exported themselves.
127
+
**DO NOT** remove `export` from types that are transitively referenced by exported functions, values, or other exported types, even if tools like knip report them as "unused exports." With `declaration: true` in `tsconfig`, TypeScript requires all types in exported signatures to be exported themselves.
128
128
129
129
Before removing an `export` from a type, check whether any exported function or value references it in its signature (parameters, return types, or fields of other exported types).
- Knip tracks direct import usage, not transitive type reachability through exported signatures
152
152
- Removing these exports causes `declaration: true` to fail with "exported function has or is using private name" errors
153
-
- The fix is tedious — each type must be re-exported individually, often across multiple review cycles
153
+
- The fix is tedious: each type must be re-exported individually, often across multiple review cycles
154
154
155
155
## Cross-Worker Durable Object Access
156
156
@@ -201,7 +201,7 @@ import type { UserDO, GitHubOrganizationDO } from "@taskless/storage";
201
201
202
202
### Verify Build Output In The Build, Not By Parsing It
203
203
204
-
**A failing build is still a valid test — of the build.** When an invariant is about a build artifact, enforce it where the artifact is produced. If a bundle must not contain something, the build should refuse to emit it, rather than emitting it and leaving a test to go looking afterwards. An invariant enforced at production time cannot be violated; one enforced afterwards can only be detected.
204
+
**A failing build is still a valid test of the build.** When an invariant is about a build artifact, enforce it where the artifact is produced. If a bundle must not contain something, the build should refuse to emit it, rather than emitting it and leaving a test to go looking afterwards. An invariant enforced at production time cannot be violated; one enforced afterwards can only be detected.
205
205
206
206
**DO NOT** reconstruct a fact about generated output by parsing that output.
207
207
@@ -240,7 +240,7 @@ for (const specifier of specifiers) {
240
240
}
241
241
```
242
242
243
-
**Tests that _use_ a built artifact are fine.** Importing the built entry and asserting on its behavior, or spawning the built CLI and asserting on its output, are ordinary tests. The rule is not "tests must not touch build output" — it is that tests must not re-derive what the build already knew.
243
+
**Tests that _use_ a built artifact are fine.** Importing the built entry and asserting on its behavior, or spawning the built CLI and asserting on its output, are ordinary tests. The rule is not "tests must not touch build output". It is that tests must not re-derive what the build already knew.
244
244
245
245
```typescript
246
246
// ✅ Fine - uses the artifact, asserts on behavior
**Do not add a dependency in order to test an assertion.** If a test needs a parser to make sense of an artifact, that is the signal the check is in the wrong place — the generator already has the structured data. Reach for a new devDependency only when several tests need it and nothing in the existing toolchain can answer the question.
255
+
**Do not add a dependency in order to test an assertion.** If a test needs a parser to make sense of an artifact, that is the signal the check is in the wrong place: the generator already has the structured data. Reach for a new devDependency only when several tests need it and nothing in the existing toolchain can answer the question.
256
256
257
-
**Worked example.**`packages/cli/test/prompts.test.ts` asserted that the built `dist/prompts.js` chunk graph never reaches the CLI entry or a host capability, by regex-scanning the built JavaScript for `from "…"` to reconstruct the import graph. A built chunk embeds every help recipe as a string literal, and the `engine-selection` recipe contains the phrase `a different axis from "which engine"` — so the scan reported `dist/prompts.js graph imports which engine`. Prose was read as an import.
257
+
**Worked example.**`packages/cli/test/prompts.test.ts` asserted that the built `dist/prompts.js` chunk graph never reaches the CLI entry or a host capability, by regex-scanning the built JavaScript for `from "…"` to reconstruct the import graph. A built chunk embeds every help recipe as a string literal, and the `engine-selection` recipe contains the phrase `a different axis from "which engine"`, so the scan reported `dist/prompts.js graph imports which engine`. Prose was read as an import.
| Filter candidates by specifier shape (`/^(?:node:)?[@\w./-]+$/`) | Passed only because that phrase contains a space. Measured against the real bundle the regex yields `["which engine"]` and the filter drops it — but `differs from "static-tier"` is a bare hyphenated name with no whitespace and would have been reported. The guard held by luck of punctuation. |
264
-
| Add `es-module-lexer` as a devDependency | Parsed the graph correctly, but bought a dependency — and a second major version, since vite already pulls 1.7.0 transitively — to serve a single test. |
265
-
| Anchor the regex to line-start | Matched the lexer exactly on today's bundles, but required `from` on the same line as `import`. A future bundler that wrapped a long import would silently stop detecting real imports — trading a loud false positive for a quiet false negative in the guard whose entire job is catching a leak. |
| Filter candidates by specifier shape (`/^(?:node:)?[@\w./-]+$/`) | Passed only because that phrase contains a space. Measured against the real bundle the regex yields `["which engine"]` and the filter drops it, but `differs from "static-tier"` is a bare hyphenated name with no whitespace and would have been reported. The guard held by luck of punctuation. |
264
+
| Add `es-module-lexer` as a devDependency | Parsed the graph correctly, but bought a dependency, and a second major version since vite already pulls 1.7.0 transitively, to serve a single test.|
265
+
| Anchor the regex to line-start | Matched the lexer exactly on today's bundles, but required `from` on the same line as `import`. A future bundler that wrapped a long import would silently stop detecting real imports, trading a loud false positive for a quiet false negative in the guard whose entire job is catching a leak. |
266
266
267
-
The resolution: rollup's `OutputChunk` already exposes `imports` and `dynamicImports` — the exact resolved graph. The check moved into a vite plugin that fails the build, and the test was deleted.
267
+
The resolution: rollup's `OutputChunk` already exposes `imports` and `dynamicImports`, the exact resolved graph. The check moved into a vite plugin that fails the build, and the test was deleted.
268
268
269
269
The same reasoning forbids adding a YAML parser to assert on generated config, or an HTML parser to assert on rendered output. In each case the generator knows the answer and the test is guessing at it.
270
270
271
271
**Rationale:**
272
272
273
273
- An invariant enforced at production time cannot be violated; one enforced afterwards can only be detected
274
274
- Parsing generated text reconstructs information the generator already had, using a weaker tool
275
-
- A check that needs a parser is a check in the wrong place — move it to where the structured data lives
275
+
- A check that needs a parser is a check in the wrong place; move it to where the structured data lives
276
276
- A build that fails is a faster, earlier signal than a test that fails, and it cannot be skipped
277
277
- Regexes over generated output are brittle in the worst direction: they break on content that merely resembles code, and they quietly stop matching when the generator's formatting changes
0 commit comments