Initial Commit
This commit is contained in:
533
GENERIC-OPENSPEC-AGENTS.md
Normal file
533
GENERIC-OPENSPEC-AGENTS.md
Normal file
@@ -0,0 +1,533 @@
|
||||
# OpenSpec — Generic Agent Compliance Checklist
|
||||
|
||||
A reusable spec-driven change-management workflow for any project. Read this file before implementing **any** OpenSpec change. Every section is a non-negotiable gate. Paired with **GENERIC-CLAUDE.md** for general engineering standards.
|
||||
|
||||
---
|
||||
|
||||
## 0. Research before implementing
|
||||
|
||||
Before writing a new package or migrating a library — mandatory, not optional:
|
||||
|
||||
1. **Search the registry** (npm / PyPI / crates.io / etc.) for existing implementations. Prefer battle-tested over hand-rolled. Check the root manifest's `catalog` / `dependencies` block for deps already pinned.
|
||||
2. **Check vendor docs** (via Context7 or similar) to confirm API behavior and version-specific details before wrapping an external library.
|
||||
3. **Search your own repo** for reference implementations and patterns. Try local copies of upstream sources first.
|
||||
4. If an existing implementation covers 80%+ of the requirement, **port or wrap it** — do not start from scratch.
|
||||
5. If a starting-from-scratch is unavoidable but a prior implementation exists, **read its nested `.md` files first** to understand its purpose.
|
||||
6. **Check the dep graph** — if a new type or abstraction is shared across ≥2 packages, it belongs in a zero-dep shared types package. Add it there first, then reference it. Prevents circular dependencies and keeps the public contract in one place.
|
||||
|
||||
---
|
||||
|
||||
## 0a. Migration parity — no cut-down replacements
|
||||
|
||||
When migrating a library from an upstream source, **a greenfield rewrite is a last resort, not a default.** The upstream library exists because it solved real problems. Dropping features because they are "complex" or "not needed yet" is how you end up with broken consumers later.
|
||||
|
||||
### Mandatory parity checklist
|
||||
|
||||
Every migration proposal MUST include a `## Parity Checklist` section. Before any code, read the upstream source, any upstream specs, and any upstream audit docs. Then produce a table:
|
||||
|
||||
```markdown
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| Feature A | ✅ | ✅ | |
|
||||
| Feature B | ✅ | 🚫 | Scoped out: reason … |
|
||||
| Feature C | ✅ | ⬆️ | Enhanced: added … |
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- **No implicit drops.** If upstream has it and the migration does not, the box is 🚫 and MUST have a justification.
|
||||
- **"Not needed yet" is banned as a justification.** Scope out only with a concrete architectural reason (e.g. "replaced by interceptor API which covers the same use case via composition").
|
||||
- **If the justification is longer than one sentence, the feature is not scoped out — it is required.**
|
||||
- **The checklist is a gate.** A proposal with unchecked 🚫 items without justification is rejected.
|
||||
|
||||
### Design quality bar
|
||||
|
||||
Migrations must be **modular and extensible**:
|
||||
|
||||
- Use TypeScript generics for type parameters — not `any` or `unknown` escape hatches.
|
||||
- Prefer composition over inheritance. Public surfaces are interfaces callers can implement.
|
||||
- Avoid closed unions where module augmentation or registry patterns are possible.
|
||||
- Preserve upstream subpath exports — every public module must be tree-shakeable.
|
||||
- Merge upstream and downstream improvements. The migrated package gains every enhancement from both sides.
|
||||
|
||||
### Anti-patterns that block approval
|
||||
|
||||
| Anti-pattern | Why it fails |
|
||||
|---|---|
|
||||
| "Fresh build" with a different API | Consumers expecting upstream contracts break silently. |
|
||||
| `export *` in any non-root barrel | Banned — the barrel IS the contract; convert to explicit named exports inside the migration commit, not a follow-up. |
|
||||
| Direct runner imports in non-exempt test files | Tests must route through the runner-neutral wrapper; rewrite during the migration commit. |
|
||||
| Dropping schema-driven features for "simplicity" | Callers abandon the package and hand-roll the missing logic. |
|
||||
| Replacing upstream errors with plain `Error` | Loses programmatic handling, telemetry, and i18n keys. |
|
||||
| Ignoring upstream enterprise audit | The audit flagged real gaps; ignoring them recreates them. |
|
||||
|
||||
### Reference docs you must read
|
||||
|
||||
Before migrating any package:
|
||||
|
||||
1. The canonical upstream capability spec.
|
||||
2. Any upstream change proposals (active and archived) that modified the package.
|
||||
3. Any upstream enterprise audit with coverage / error / quality gaps.
|
||||
4. Any design docs referenced by those proposals.
|
||||
|
||||
If any are missing, note it in the parity checklist and explain the risk.
|
||||
|
||||
---
|
||||
|
||||
## 0b. Ecosystem awareness — read your status dashboard
|
||||
|
||||
Before writing a new package or migrating a library, **read the root status file** (e.g. `STATUS.md`) to learn which packages already exist, which are completed, and which are pending.
|
||||
|
||||
Rules:
|
||||
|
||||
- **If your package depends on something in `Pending`**, that dependency **must be migrated first** (or a compatible substitute must already exist). Do not hand-roll a replacement without explicit justification in the parity checklist.
|
||||
- **If the package itself is in `Pending`**, it **must have an OpenSpec change written first** (`proposal.md` + `tasks.md`) before any code.
|
||||
- **If the package does not exist anywhere**, it is an original. It **still** must have an OpenSpec change written first.
|
||||
- Never start coding without either (a) completed migration of all pending dependencies, or (b) an approved proposal that explicitly scopes out missing dependencies with architectural justification.
|
||||
|
||||
Checking the status file prevents duplicate work, broken downstream consumers, and incompatible re-implementations.
|
||||
|
||||
---
|
||||
|
||||
## 0c. Authoring the change — folder structure and spec-delta format
|
||||
|
||||
**Read this before you create a single file in `openspec/changes/`.** Every rule here is a gate.
|
||||
|
||||
### The change folder has exactly this shape
|
||||
|
||||
```text
|
||||
openspec/changes/<change-id>/
|
||||
proposal.md # what & why, impact, affected dependents
|
||||
tasks.md # ordered implementation checklist
|
||||
design.md # OPTIONAL — non-normative design: rationale, algorithms,
|
||||
# code sketches, file layout, diagrams, trade-offs
|
||||
specs/<capability>/spec.md # the SPEC DELTA — normative requirements only
|
||||
```
|
||||
|
||||
- `<capability>` is the **package short name** without any scope prefix. One capability folder per package. Mirrors the eventual canonical path `openspec/specs/<capability>/spec.md`.
|
||||
- `design.md` is optional but **strongly preferred** for any non-trivial package. It is the home for everything that is *not* a testable requirement: architecture, algorithm notes, file layout, integration narrative, benchmark plans, decisions & trade-offs. If you're writing prose that isn't a `### Requirement:`, it belongs in `design.md`, not the spec delta.
|
||||
|
||||
### `openspec/specs/` is OFF LIMITS until archival
|
||||
|
||||
`openspec/specs/<capability>/spec.md` is the **canonical, shipped** spec. It is written to **only** by the §14 archival fold step, **after** all gates pass. While a change is `PROPOSED` or in progress:
|
||||
|
||||
- **NEVER create or edit anything under `openspec/specs/`.** Not a stub, not a placeholder, not a "draft for reference". If `openspec/specs/<your-capability>/` exists before your change archives, delete it and move the content into your change's `specs/<capability>/spec.md` delta.
|
||||
- The file inside your **change** folder is a **delta**, not the canonical spec. It is folded into `openspec/specs/` on archive and the change folder is moved to `openspec/archive/`.
|
||||
|
||||
### The spec delta is a normative requirements document — not an architecture dump
|
||||
|
||||
The delta MUST use the OpenSpec requirement/scenario format. Skeleton:
|
||||
|
||||
```markdown
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: <capability>
|
||||
|
||||
**Package**: `<pkg>`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: <Imperative name>
|
||||
|
||||
The package SHALL … / MUST … — normative, testable prose. One requirement = one capability.
|
||||
|
||||
#### Scenario: <observable behaviour>
|
||||
|
||||
- **WHEN** <precondition / action>
|
||||
- **THEN** <observable, checkable outcome>
|
||||
|
||||
**Verified by:** `src/<feature>/<feature>.test.ts::"<planned test name>"` AND `<another planned test>`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: <next one>
|
||||
…
|
||||
```
|
||||
|
||||
Rules for the delta:
|
||||
|
||||
- Top-level headings are exactly `## ADDED Requirements`, `## MODIFIED Requirements`, and/or `## REMOVED Requirements` — nothing else. A fresh package uses only `## ADDED Requirements`.
|
||||
- Every `### Requirement:` has **normative prose** (SHALL / MUST / SHALL NOT), **one or more** `#### Scenario:` blocks as `- **WHEN**` / `- **THEN**` bullets, and **exactly one** `**Verified by:**` line. Separate requirements with `---`.
|
||||
- `**Verified by:**` is authored **at draft time**. The change is not implemented yet, so it names **planned** test files (paths from `tasks.md` / `design.md` file layout) in `path::"test name"` form. That is expected and correct — it is the contract the implementation must satisfy.
|
||||
- Cover the **whole contract**: public API surface, every error code (each must be reachable from a real call site), boundary validation, telemetry instrumentation, `AbortSignal` propagation, subpath exports, tier purity (universal → no browser/Node APIs; browser → no Node; server → no browser). Match the package's depth — a large package is 15–25 requirements, a small leaf is 8–12.
|
||||
|
||||
### `proposal.md` and `tasks.md` obligations
|
||||
|
||||
- `proposal.md` MUST link its design and delta in the header block (`- **Design**: ./design.md`, `- **Spec delta**: ./specs/<capability>/spec.md`) — **never** a `Reference spec:` pointer into `openspec/specs/`. It MUST carry the `## Impact` → `### Affected dependents` section per §14a.
|
||||
- `tasks.md` MUST end with a "Fold `specs/<capability>/spec.md` into `openspec/specs/<capability>/spec.md`" step immediately before the archive step (see §14).
|
||||
|
||||
### Multi-package / cross-cutting feature sets
|
||||
|
||||
When one feature spans many packages (a family, a wave of transports, etc.):
|
||||
|
||||
- **One change per package.** Each package gets its own `openspec/changes/<id>/` with its own `specs/<capability>/spec.md` delta. Do **not** write a single umbrella spec covering the whole family, and do **not** put a shared `specs/<family>/` folder anywhere.
|
||||
- **Shared architecture lives once, in the foundation package's `design.md`.** Sibling `design.md` files **reference** it by relative path rather than duplicating.
|
||||
- Each change still stands alone: its `proposal.md`, `design.md`, `tasks.md`, and delta are self-sufficient.
|
||||
|
||||
### Pre-flight checklist — before you write any change file
|
||||
|
||||
1. The package is in the project status dashboard, or is an original — confirmed per §0b.
|
||||
2. You have read the upstream references in §0a (for migrations) or the design inputs (for originals).
|
||||
3. You will create `proposal.md` + `tasks.md` + `specs/<capability>/spec.md` (+ `design.md`), and **nothing** under `openspec/specs/`.
|
||||
4. The delta will be in `## ADDED/MODIFIED/REMOVED Requirements` form with `### Requirement:` / `#### Scenario:` / `**Verified by:**` — not an API dump.
|
||||
5. `proposal.md` will carry `### Affected dependents` (§14a); `tasks.md` will carry the fold step (§14).
|
||||
6. **You have created a numbered checklist of every `### Requirement` and every `#### Scenario` in the spec delta.** This lives in your working notes. You may not begin implementation until every requirement is listed as a discrete, checkable item. As you implement, check each off. An unchecked item at the end means the package is not done. **Never silently skip a requirement because "tests already pass without it."** That is the single most common cause of spec drift.
|
||||
|
||||
---
|
||||
|
||||
## 1. Dependency conventions
|
||||
|
||||
- All external deps use catalog refs — **never bare version strings** like `"^1.2.3"`.
|
||||
- All internal deps use `workspace:^`.
|
||||
- If a catalog key does not exist, add it to the root manifest first.
|
||||
- Coverage provider must be installed as a `devDependency` via catalog ref. Without `@vitest/coverage-istanbul` (or equivalent), Istanbul-style coverage silently falls back to v8.
|
||||
- Testing-infra packages have **zero** internal workspace deps.
|
||||
|
||||
---
|
||||
|
||||
## 2. No banned patterns in `src/`
|
||||
|
||||
Before closing any task, scan for zero matches:
|
||||
|
||||
```bash
|
||||
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only\|\bany\b\|console\.log" \
|
||||
packages/<pkg>/src/ --include="*.ts"
|
||||
```
|
||||
|
||||
- `@deprecated` → prose: "Backwards-compatibility alias for X."
|
||||
- `any` → `unknown` + narrowing, or a proper generic.
|
||||
- `console.log` → real logger / injectable `LoggerLike`.
|
||||
|
||||
## 2a. Lint suppressions
|
||||
|
||||
Stale `// biome-ignore` / `// eslint-disable*` comments break the lint gate hard.
|
||||
|
||||
Rules:
|
||||
|
||||
1. **Never write a suppression comment for transient lint noise.** If the linter flags something, it's a real signal — fix the code.
|
||||
2. **If a deliberate violation is required**, use a coding pattern the linter cannot statically reject (see §8.4 of GENERIC-CLAUDE.md).
|
||||
3. **If a suppression genuinely is the only path**, pin both the current rule category and a one-sentence reason. Verify the category resolves before committing.
|
||||
4. **Scan before closing any task:** `grep -rn "biome-ignore\|eslint-disable" packages/<pkg>/src/ --include="*.ts"`. Every hit MUST be justified in code review.
|
||||
|
||||
## 2b. NEVER ship stubs, placeholders, or "WIP" code
|
||||
|
||||
See **GENERIC-CLAUDE.md §8.1** for the full list. Banned: stub functions, placeholder types, TODO/FIXME, "Phase 2 unwritten" splits, marketing language masking incomplete work, tautological tests, stub docs, empty test files, silently dropped spec requirements.
|
||||
|
||||
If you discover an existing stub during audit (40-line README that fails `docs:check`, a function with `return undefined as never`, a barrel re-exporting nothing), the right answer is to **fix it inside the same change** as your other work. Leaving it is a vote for the placeholder.
|
||||
|
||||
## 2c. NEVER delete spec elements to satisfy a gate — wire them in
|
||||
|
||||
When a gate fails because a registered element (error code, schema, event topic, requirement, capability constant) is **declared but unused**, the failure is telling you the implementation hasn't caught up to the proposal. The fix is to **wire the element into the call site the proposal designed it for**, NOT to delete the element from the registry.
|
||||
|
||||
Deletion is the path of least resistance and silently strips features the proposal committed to. The proposal is the contract; the registry is the proposal expressed in code. Removing an entry to make a tool happy is the same defect as marking a failing test `.skip` to make CI green.
|
||||
|
||||
Concretely banned:
|
||||
|
||||
- **Removing an error code** from the codes registry because conformance check flags it as unused. Find the call site that should be raising / reporting it and wire it in.
|
||||
- **Deleting a schema field** because no caller populates it yet.
|
||||
- **Removing an event topic** because no emitter exists. Add the emitter; the topic is an API surface the proposal owns.
|
||||
- **Quietly removing a `## ADDED Requirements` line** because the implementation doesn't cover it. Either implement it or open a follow-up change that explicitly defers it with reasoning — never silently retract.
|
||||
- **Lowering coverage / docs-check thresholds** to make a gate pass.
|
||||
|
||||
When in doubt, ask: *"Was this element part of the proposal I'm implementing?"* If yes, **wire it in**. If genuinely no (it crept in by mistake), removing it requires a one-line note in `proposal.md` under `### Deferred` or `### Removed` so the deletion is auditable.
|
||||
|
||||
---
|
||||
|
||||
## 3. File-size budget, documentation, README
|
||||
|
||||
See **GENERIC-CLAUDE.md §§9, 11**. Summary:
|
||||
|
||||
- Source files 300 lines hard / 250 soft; test files 500 max; functions 50 soft.
|
||||
- Every `.ts` impl file needs a sibling `.md` with Purpose / Features / Basic / Advanced / API / Implementation Notes (≥200 words excl. code).
|
||||
- Doc-drift gate: changing a `.ts` requires touching the sibling `.md` in the same change set when the public surface or documented behavior changes.
|
||||
- Root `README.md` per package: name, description, install, quick-start, Modules link list.
|
||||
|
||||
---
|
||||
|
||||
## 4. Subpath exports per feature module
|
||||
|
||||
Every feature directory must have its own entry in `package.json` `exports`. A single `"."` barrel is insufficient. The subpath count should match the feature module count.
|
||||
|
||||
Verify entry points import nothing with side effects (telemetry registration, global state mutations). A `sideEffects: false` declaration with a leaking side effect is a silent bug.
|
||||
|
||||
---
|
||||
|
||||
## 5. Barrel exports — explicit named only
|
||||
|
||||
See **GENERIC-CLAUDE.md §6.4**. Feature barrels MUST use explicit named exports; `export *` is allowed only in generated forwarder files and top-level namespace re-exports.
|
||||
|
||||
---
|
||||
|
||||
## 6. Coverage thresholds
|
||||
|
||||
**100 / 100 / 100 / 100** for every package, no exceptions, no "foundation only" carve-out:
|
||||
|
||||
```ts
|
||||
thresholds: { statements: 100, branches: 100, functions: 100, lines: 100 }
|
||||
```
|
||||
|
||||
The pre-publish gate MUST hard-fail below 100%. Mutation, contract, and property/fuzz testing layer on top — they do not substitute, and 100% line coverage does not substitute for them.
|
||||
|
||||
### Never delete code to make coverage pass
|
||||
|
||||
See **GENERIC-CLAUDE.md §8.3**. When a path is uncovered, write a test that exercises real behavior — do NOT delete defensive guards, rollback helpers, or catch arms.
|
||||
|
||||
---
|
||||
|
||||
## 7. Test runner imports — mandatory
|
||||
|
||||
All test files MUST import runner primitives (`describe`, `it`, `expect`, `vi`, `beforeAll`/`beforeEach`/`afterAll`/`afterEach`, etc.) from a **single project-wide neutral wrapper** that re-exports the active runner via conditional exports:
|
||||
|
||||
```ts
|
||||
// CORRECT — runner-neutral
|
||||
import { describe, expect, it, vi } from "<project>/testing/runner";
|
||||
|
||||
// BANNED — pins the test file to one runner
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it } from "vitest";
|
||||
```
|
||||
|
||||
The wrapper is a conditional-export barrel: `bun` condition → re-exports `bun:test`; `default` / `vitest` condition → re-exports `vitest`. Tests then execute identically under either runner.
|
||||
|
||||
**Exempt allowlist**: only the wrapper-authoring packages (the testing-infra packages themselves). Adding a package to the allowlist requires its own OpenSpec change with justification.
|
||||
|
||||
**Migration prerequisite**: when migrating any library from upstream, every `from "vitest"` / `from "bun:test"` import MUST be rewritten to the wrapper **inside the migration commit, not as a follow-up**. Same rule applies when porting test files between packages or when an LLM agent generates a fresh test file.
|
||||
|
||||
**Verification grep** (zero output for any non-exempt package):
|
||||
|
||||
```bash
|
||||
grep -rnE "from ['\"](vitest|bun:test)['\"]" \
|
||||
packages/<pkg>/src/ --include="*.ts" --include="*.tsx"
|
||||
```
|
||||
|
||||
A repo-wide CI lint gate must run this across every non-exempt package on every commit. Code review must independently flag direct runner imports — relying solely on CI is how 19 packages drifted before the gate existed.
|
||||
|
||||
### Test structure
|
||||
|
||||
Arrange-Act-Assert. Name tests after **behavior**, not implementation. See GENERIC-CLAUDE.md §10.2.
|
||||
|
||||
---
|
||||
|
||||
## 8. Task-runner filtering reference
|
||||
|
||||
```bash
|
||||
<task-runner> test --filter=<pkg> # one package
|
||||
<task-runner> test --filter=<pkg>... # pkg + its deps
|
||||
<task-runner> test --filter=...<pkg> # pkg + its dependents
|
||||
<task-runner> test --filter=...[main] # changed since main
|
||||
<task-runner> test --filter=...[HEAD^1] # changed in last commit
|
||||
<task-runner> test --filter={a,b,c} # multiple (OR)
|
||||
<task-runner> test --filter=!<pkg> # exclude
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Benchmarks
|
||||
|
||||
Every package should include benchmarks in `src/__benchmarks__/`. Use Markdown profiling output (CPU + heap) for LLM-assisted analysis. Publish baseline numbers in README.
|
||||
|
||||
---
|
||||
|
||||
## 10. Non-redundant path naming
|
||||
|
||||
A file or folder name must **not** contain the singular or plural form of any ancestor folder. See GENERIC-CLAUDE.md §6.2 for the rule and table.
|
||||
|
||||
Check:
|
||||
|
||||
```bash
|
||||
find packages/<pkg>/src -type d | awk -F/ '
|
||||
NF>=2 {
|
||||
parent=$(NF-1); child=$NF
|
||||
singular=parent; sub(/ies$/, "y", singular); sub(/s$/, "", singular)
|
||||
if (index(child, singular) > 0) print $0
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Type design — no inline shapes
|
||||
|
||||
See **GENERIC-CLAUDE.md §12**. Named interfaces/types only — no nested object literals in interfaces; no inline shapes in function signatures. Cross-package types live in the shared zero-dep types package.
|
||||
|
||||
---
|
||||
|
||||
## 12. Clean workspace
|
||||
|
||||
Before declaring any task done:
|
||||
|
||||
- Remove scratch files, debug outputs, temporary artifacts.
|
||||
- Verify `git status` shows only intentional changes.
|
||||
|
||||
## 12a. Spec-completeness verification — before any gate
|
||||
|
||||
**Never trust a compaction summary, archive status, or status-dashboard badge as proof that work is complete.** The only source of truth is the change's own `tasks.md` and `design.md`.
|
||||
|
||||
Before running ANY command in §13, manually:
|
||||
|
||||
1. **Open the change's `tasks.md`.** For migrations, also open the upstream `tasks.md`.
|
||||
2. **Walk every task item.** For each item that claims a file/module/type/feature exists, verify it is present. A task that says "Implement `geo/distance` — `greatCircleDistance` and `vincentyDistance`" means BOTH functions must exist, have tests, have docs, and be exported from the barrel.
|
||||
3. **For migrations, compare to upstream.** Diff the upstream `src/` against your equivalent. If upstream has `providers/admiralty/admiralty-provider.ts` with real fetch logic and yours has a 3-line stub, the migration is incomplete.
|
||||
4. **Empty directories are a red flag, not cleanup.** If a directory exists in `src/` and is empty, it was scaffolded for a feature that was never implemented. **Implement and populate it** — do NOT delete the directory to make the tree look tidy.
|
||||
5. **Cross-check against the parity checklist.** Every 🚫 row MUST have a written justification; every ⬆️ row must be real. If the table disagrees with the file tree, the table is wrong — fix the implementation, not the table.
|
||||
|
||||
If any task item is unchecked or unverified, the change is **not done**. Do not proceed to §13, do not archive, do not update the status dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 13. Pre-ship verification sequence
|
||||
|
||||
Run in order — all must be green. **Step 0 is a hard gate; do not skip it.**
|
||||
|
||||
```bash
|
||||
# 0. SPEC RECONCILIATION — NON-NEGOTIABLE. Run BEFORE any other gate.
|
||||
# Walk every `### Requirement` and `#### Scenario` in the spec delta.
|
||||
# For each requirement, grep the implementation to confirm the named
|
||||
# class, function, constant, span, counter, histogram, event, or error
|
||||
# code actually exists in src/. For each `**Verified by:**`, confirm
|
||||
# the test file exists and the test name matches *verbatim*.
|
||||
# If any requirement is unimplemented or any Verified-by is broken,
|
||||
# STOP. Do not run step 1.
|
||||
|
||||
# 1. Lint + typecheck + tests + format
|
||||
<task-runner> lint typecheck test --filter=<pkg>
|
||||
<formatter> check # e.g. biome check --write --unsafe src/
|
||||
|
||||
# 2. Coverage at 100%
|
||||
<task-runner> test:coverage --filter=<pkg>
|
||||
|
||||
# 3. Build
|
||||
<task-runner> build --filter=<pkg>
|
||||
|
||||
# 4. Banned-pattern scan (zero output expected)
|
||||
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" \
|
||||
packages/<pkg>/src/ --include="*.ts"
|
||||
|
||||
# 4a. Barrel-shape scan (zero output for any feature barrel)
|
||||
grep -rn "^\s*export\s\+\(type\s\+\)\?\*\s\+from" \
|
||||
packages/<pkg>/src/ --include="index.ts" \
|
||||
| grep -v "/src/index\.ts:" \
|
||||
| grep -v "\.select/" \
|
||||
| grep -v "export \* as"
|
||||
|
||||
# 4b. Test-runner import scan (zero output for non-exempt packages)
|
||||
grep -rnE "from ['\"](vitest|bun:test)['\"]" \
|
||||
packages/<pkg>/src/ --include="*.ts" --include="*.tsx"
|
||||
|
||||
# 5. Missing .md scan
|
||||
for f in $(find packages/<pkg>/src -name "*.ts" \
|
||||
! -name "*.test.ts" ! -name "*.spec.ts" ! -name "*.types.ts" \
|
||||
! -name "index.ts"); do
|
||||
md="${f%.ts}.md"; [ -f "$md" ] || echo "MISSING: $md"
|
||||
done
|
||||
|
||||
# 6. README check
|
||||
test -f packages/<pkg>/README.md && echo "README OK" || echo "MISSING README"
|
||||
|
||||
# 7. Bare-version check (zero output expected)
|
||||
node -e "
|
||||
const p = JSON.parse(require('fs').readFileSync('packages/<pkg>/package.json','utf8'));
|
||||
const all = {...p.dependencies,...p.devDependencies,...p.peerDependencies};
|
||||
for (const [k,v] of Object.entries(all ?? {})) {
|
||||
if (typeof v === 'string' && /^[\^~]?\d/.test(v)) console.log('BARE VERSION:', k, v);
|
||||
}
|
||||
"
|
||||
|
||||
# 8. Subpath export count
|
||||
node -e "
|
||||
const p = JSON.parse(require('fs').readFileSync('packages/<pkg>/package.json','utf8'));
|
||||
const sub = Object.keys(p.exports ?? {}).filter(k => k.startsWith('./') && k !== '.');
|
||||
console.log('Subpath exports:', sub.length, sub);
|
||||
"
|
||||
|
||||
# 9. Security scan — hardcoded secrets (zero output expected)
|
||||
grep -rn \
|
||||
"password\s*=\s*['\"][^'\"]\|secret\s*=\s*['\"][^'\"]\|apiKey\s*=\s*['\"][^'\"]\|token\s*=\s*['\"][^'\"]" \
|
||||
packages/<pkg>/src/ --include="*.ts"
|
||||
|
||||
# 10. Dependent sweep — mandatory for foundation packages
|
||||
<task-runner> test --filter=...<pkg>
|
||||
```
|
||||
|
||||
For **foundation packages** (widely-consumed: error / types / events / logger / validation in this project's case), step 10 is mandatory — catches API-shape regressions BEFORE shipping. Green local tests do not prove you didn't break consumers.
|
||||
|
||||
For non-foundation packages, step 10 is optional — but if you altered the public surface, run it anyway. The cost is one command; the cost of skipping is finding out from a consumer's CI next week.
|
||||
|
||||
---
|
||||
|
||||
## 13a. Behavior-change propagation
|
||||
|
||||
See **GENERIC-CLAUDE.md §19**. When a change introduces consumer-visible behavior changes (new error code, renamed metric, method that now throws, default flipped, return shape change), you MUST:
|
||||
|
||||
1. **Self-doc**: update sibling `.md` + `README.md` Features section.
|
||||
2. **Dependent surface scan**: `grep -rln "<old-name>" packages/ | grep -v "/dist/\|/<own-pkg>/"`. Every hit needs updating in the same change set.
|
||||
3. **Migration note**: add `## Migration` to `proposal.md` listing (a) old behavior, (b) new behavior, (c) consumer search command, (d) recommended replacement.
|
||||
|
||||
Skipping any of these means a consumer somewhere is acting on an obsolete contract.
|
||||
|
||||
---
|
||||
|
||||
## 13b. Root-doc refresh + markdown-lint
|
||||
|
||||
Once §13 is green, but **before** archival (§14), refresh the repo-root manifest docs and run the markdown-lint pass.
|
||||
|
||||
Mandatory updates:
|
||||
|
||||
- **Auto-generated manifests** (`MANIFEST.md`, `MISSING.yml`, dep-graph indexes): run the generators.
|
||||
- **Hand-edited status dashboard** (e.g. `STATUS.md`): move the package row from `Pending` → `Completed`, keeping icon and link conventions.
|
||||
- **Hand-edited summary doc** (e.g. `SUMMARY.md`): update or add the per-package paragraph to reflect the shipped surface.
|
||||
- **Migration narrative** (e.g. `MIGRATION.md`): update only if the change affects it.
|
||||
|
||||
Then auto-fix markdown:
|
||||
|
||||
```bash
|
||||
bunx markdownlint-cli2 --fix
|
||||
```
|
||||
|
||||
Re-stage anything the lint pass modified. Verify `git status` shows only intentional changes before archiving. If unfixable violations remain, resolve them by hand — never ship with lint failures, never silence with inline disables.
|
||||
|
||||
Verification before archival:
|
||||
|
||||
```bash
|
||||
git status # only intentional changes
|
||||
git diff MANIFEST.md MISSING.yml STATUS.md SUMMARY.md MIGRATION.md
|
||||
bunx markdownlint-cli2 # second pass, no --fix — must exit 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. OpenSpec archival
|
||||
|
||||
After all gates pass:
|
||||
|
||||
1. **Fold** the spec delta into `openspec/specs/<capability>/spec.md`.
|
||||
2. **Move** `openspec/changes/<change-id>/` → `openspec/archive/<yyyy-mm-dd>-<change-id>/`.
|
||||
|
||||
---
|
||||
|
||||
## 14a. Proposal must list affected dependents
|
||||
|
||||
Every `proposal.md` MUST contain an `## Impact` section that explicitly enumerates downstream packages and what was done in each:
|
||||
|
||||
```markdown
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `<task-runner> ls --filter=...<pkg>` to enumerate. For each dependent, check one box:
|
||||
|
||||
- `<dep-1>` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: …)
|
||||
- `<dep-2>` — [x] tests run / [x] docs reviewed / [ ] no action needed
|
||||
- `<dep-3>` — …
|
||||
|
||||
If `tests run` is unchecked for any dependent, the change MUST NOT be folded into the canonical spec — finish that work first or split the proposal.
|
||||
```
|
||||
|
||||
The "no action needed" path requires a one-line reason (e.g. "consumer doesn't touch the changed surface"). It is NOT a free pass; it documents the conscious decision for the next agent reading the archive.
|
||||
|
||||
For **foundation packages**, the Affected-dependents list is non-optional. For non-foundation packages with zero dependents, write: "No dependents (verified by `<task-runner> ls --filter=...<pkg>`)."
|
||||
|
||||
Combined with §13 step 10 (dependent sweep) and §13a (behavior-change propagation), this closes the loop: the proposal names the blast radius, the verification proves the blast radius compiles and tests, and the migration note tells future readers what changed and how to adapt.
|
||||
Reference in New Issue
Block a user