@agentsfleet/orly 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +42 -0
- package/.githooks/pre-push +67 -0
- package/AGENTS.md +348 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SOUL.md +90 -0
- package/audits/agents-md.md +497 -0
- package/audits/agents-md.sh +353 -0
- package/audits/cross-tier-rates.sh +105 -0
- package/audits/data.sh +185 -0
- package/audits/deinit-pairs.sh +224 -0
- package/audits/design-tokens.sh +143 -0
- package/audits/doc-read.sh +183 -0
- package/audits/error-codes.sh +221 -0
- package/audits/fixtures/clean.diff +15 -0
- package/audits/fixtures/dirty.diff +16 -0
- package/audits/lifecycle-anchors.sh +22 -0
- package/audits/logging.sh +217 -0
- package/audits/msid-ui.sh +144 -0
- package/audits/parity-dispatch.sh +51 -0
- package/audits/rule-ledger-lib.sh +264 -0
- package/audits/rule-ledger.sh +225 -0
- package/audits/rule-paths.sh +108 -0
- package/audits/spec-template.sh +258 -0
- package/audits/sql-mod.sh +102 -0
- package/audits/ufs.sh +303 -0
- package/bin/orly +26 -0
- package/core/operating-model.md +256 -0
- package/dispatch/edit_rules.md +69 -0
- package/dispatch/lib.sh +213 -0
- package/dispatch/lifecycle.md +187 -0
- package/dispatch/name_architecture.md +93 -0
- package/dispatch/verify.md +111 -0
- package/dispatch/write_any.md +740 -0
- package/dispatch/write_any.sh +49 -0
- package/dispatch/write_auth.md +36 -0
- package/dispatch/write_changelog.md +64 -0
- package/dispatch/write_documentation.md +45 -0
- package/dispatch/write_go.md +24 -0
- package/dispatch/write_http.md +45 -0
- package/dispatch/write_mdx.md +7 -0
- package/dispatch/write_python.md +8 -0
- package/dispatch/write_rust.md +38 -0
- package/dispatch/write_shell.md +8 -0
- package/dispatch/write_spec.md +105 -0
- package/dispatch/write_sql.md +112 -0
- package/dispatch/write_sql.sh +36 -0
- package/dispatch/write_ts_adhere_bun.md +493 -0
- package/dispatch/write_ts_adhere_bun.sh +37 -0
- package/dispatch/write_zig.md +852 -0
- package/dispatch/write_zig.sh +40 -0
- package/docs/CHANGELOG_VOICE.md +18 -0
- package/docs/DISPATCH_ARCHITECTURE.md +664 -0
- package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
- package/docs/DOCUMENTATION_RULES.md +209 -0
- package/docs/EXECUTE_DOC_READS.md +29 -0
- package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
- package/docs/LIFECYCLE_PATTERNS.md +316 -0
- package/docs/LOGGING_STANDARD.md +397 -0
- package/docs/ORLY_ARCHITECTURE.md +129 -0
- package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
- package/docs/RULE_ENFORCEMENT.md +41 -0
- package/docs/SCHEMA_CONVENTIONS.md +93 -0
- package/docs/TEMPLATE.md +411 -0
- package/docs/VERIFY_TIERS.md +57 -0
- package/docs/greptile-learnings/README.md +51 -0
- package/docs/greptile-learnings/RULES.md +1045 -0
- package/fixtures/registry-invalid.json +1 -0
- package/fixtures/registry-valid.json +18 -0
- package/fixtures/unclosed-pack-block.md +8 -0
- package/package.json +63 -0
- package/packs/language/go/rules.md +24 -0
- package/packs/language/mdx/rules.md +7 -0
- package/packs/language/python/rules.md +8 -0
- package/packs/language/rust/rules.md +38 -0
- package/packs/language/shell/rules.md +8 -0
- package/registry.json +405 -0
- package/schemas/evidence.schema.json +43 -0
- package/schemas/profile.schema.json +12 -0
- package/schemas/registry.schema.json +12 -0
- package/skills/orly-babysit-prs/SKILL.md +326 -0
- package/skills/orly-spec-new/SKILL.md +157 -0
- package/skills/orly-write-integration-test/SKILL.md +470 -0
- package/skills/orly-write-unit-test/SKILL.md +520 -0
- package/src/cli.test.ts +70 -0
- package/src/cli.ts +287 -0
- package/src/config.test.ts +226 -0
- package/src/config.ts +288 -0
- package/src/criteria.test.ts +137 -0
- package/src/criteria.ts +166 -0
- package/src/criteria_spec.ts +127 -0
- package/src/criteria_support.ts +53 -0
- package/src/gates.test.ts +183 -0
- package/src/gates.ts +169 -0
- package/src/gates_spec.test.ts +98 -0
- package/src/gates_test_support.ts +194 -0
- package/src/git_env.ts +20 -0
- package/src/install.test.ts +514 -0
- package/src/install.ts +447 -0
- package/src/install_nested.test.ts +32 -0
- package/src/model.test.ts +31 -0
- package/src/model.ts +245 -0
- package/src/references.test.ts +47 -0
- package/src/references.ts +147 -0
- package/src/render.test.ts +60 -0
- package/src/render.ts +94 -0
- package/src/surfaces.test.ts +43 -0
- package/src/surfaces.ts +89 -0
- package/src/validation.test.ts +11 -0
- package/src/validation.ts +87 -0
- package/src/verify.test.ts +13 -0
- package/src/verify.ts +59 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
# write_ts_adhere_bun.md — TypeScript / Bun latent façade
|
|
2
|
+
|
|
3
|
+
This is the prose the AGENT reads before writing any `*.ts` / `*.tsx` / `*.js` / `*.jsx` file. It pairs with the deterministic façade `dispatch/write_ts_adhere_bun.sh` — the machine half that runs the mechanically-checkable subset and emits one verdict block. This document consolidates the former Bun/TypeScript rules merged with the TypeScript-relevant dissolved gate-card deltas (UI substitution, design-token): every original rule line is preserved verbatim (one forward-looking repoint to `write_zig.md`), each `## ` section now carries exactly one enforcement tag, and the dissolved gate-card prose is appended under "Merged from dissolved gate cards." Mechanical thresholds live once in the `.sh`; this file references rule codes, never restates the numbers.
|
|
4
|
+
|
|
5
|
+
**Signal legend** (printed by `write_ts_adhere_bun.sh`):
|
|
6
|
+
|
|
7
|
+
- 🟢 pass — deterministic check passed.
|
|
8
|
+
- 🔴 fail — deterministic check failed (or helper absent); STOP, fix, rerun.
|
|
9
|
+
- 🤔 DECIDE — judgment-only; no script can decide, the agent reads the section and makes the call (blocks the TURN, not the script).
|
|
10
|
+
- 🟣 delegated — the checker runs only in the product repo, not in dotfiles.
|
|
11
|
+
|
|
12
|
+
**Tag legend** — each section heading below carries one of:
|
|
13
|
+
|
|
14
|
+
- `> [DETERMINISTIC → <CODE>]` — a machine can pass/fail it; the `.sh` row for `<CODE>` (e.g. `UFS`, `FLL`, `UIS`, `DTK`, `TSC`) enforces it. `TODO-CHECK` marks a mechanizable rule with no helper wired yet (build-the-check). `NEW:<name>` marks a proposed-but-not-yet-existing code.
|
|
15
|
+
- `> [JUDGMENT → <CODE>]` — no script can decide; the agent decides at write time against the prose.
|
|
16
|
+
- `> [container]` — a non-enforcement wrapper heading (e.g. "Merged from dissolved gate cards"); its tagged subsections carry the real codes, and the coherence audit (§6.3) skips it.
|
|
17
|
+
|
|
18
|
+
The tag grammar and semantic-anchor model are defined by the tagged sections and
|
|
19
|
+
deterministic façade in this snapshot.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Bun / TypeScript discipline
|
|
24
|
+
|
|
25
|
+
Modelled on `write_zig.md`. Pre-design rules, decisive defaults, and the
|
|
26
|
+
anti-patterns each rule exists to prevent. Every rule has a one-line
|
|
27
|
+
"why" so you can judge edge cases.
|
|
28
|
+
|
|
29
|
+
## Scope
|
|
30
|
+
|
|
31
|
+
> [JUDGMENT → TSJ]
|
|
32
|
+
|
|
33
|
+
Triggers on every `Edit`/`Write` to:
|
|
34
|
+
|
|
35
|
+
- `*.ts`, `*.tsx`, `*.js`, `*.jsx` outside `vendor/`, `node_modules/`, `.next/`, `dist/`, `build/`.
|
|
36
|
+
- Any Bun-runtime entrypoint (`bun.lock`, `bunfig.toml`, files invoked via `bun run`).
|
|
37
|
+
|
|
38
|
+
Out of scope: framework-generated files (Next.js generated types, Astro `.astro`).
|
|
39
|
+
|
|
40
|
+
The **UI Component Substitution Gate** (the UI Component Substitution section below) and **GREPTILE GATE** (which carries RULE UFS, RULE TGU, RULE PRI for TypeScript) sit on top of this file — they fire in addition to these rules, not instead of them.
|
|
41
|
+
|
|
42
|
+
## §1 · TS FILE SHAPE DECISION (mandatory at PLAN)
|
|
43
|
+
|
|
44
|
+
> [JUDGMENT → FSD]
|
|
45
|
+
|
|
46
|
+
Before writing the first line of any new `*.ts`/`*.tsx` file under `src/`, `app/`, or `lib/`, print:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
TS FILE SHAPE DECISION: <intended-path>
|
|
50
|
+
Trigger: <new file | threshold-cross: <which>>
|
|
51
|
+
Purpose: <one sentence — what this file's job is>
|
|
52
|
+
Primary export: <ClassName | factory function | const | type-only module>
|
|
53
|
+
Behaviour bound to state: <yes/no>
|
|
54
|
+
Verdict: <class | factory | functions-module | type-only>
|
|
55
|
+
Why not the other: <one sentence — required when verdict is conventional>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Decision matrix:**
|
|
59
|
+
|
|
60
|
+
| Behaviour | State | Verdict |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Operations bound to a single piece of state | yes (mutable, lifecycle) | **class** with `#private` fields |
|
|
63
|
+
| Operations on a passive value (validators, parsers, formatters) | no | **functions-module** (named exports) |
|
|
64
|
+
| One-shot construction returning a stateful holder | yes (frozen after init) | **factory function** returning `Object.freeze({...})` |
|
|
65
|
+
| Only types/interfaces/enums | n/a | **type-only module** |
|
|
66
|
+
|
|
67
|
+
**Threshold-cross binding** — same as Zig: an existing functions-module that's about to gain its first stateful operation rearchitects to `class` in the same diff. Don't grow a `let cachedX = …` at module scope to dodge the conversion.
|
|
68
|
+
|
|
69
|
+
**Override:** `TS FILE SHAPE DECISION: SKIPPED per user override (reason: ...)` — user-only, in this turn. Auto-mode does NOT cover it (mirrors Zig FILE SHAPE rule).
|
|
70
|
+
|
|
71
|
+
## §2 · `const` discipline
|
|
72
|
+
|
|
73
|
+
> [DETERMINISTIC → UFS]
|
|
74
|
+
|
|
75
|
+
_`UFS` is a universal rule **enforced once by `write_any`** (which fires for every source file); this façade carries its prose for the author but does not re-run `audit-ufs` — no redundant full-tree scan (`DISPATCH_ARCHITECTURE.md` §16, Decision 6)._
|
|
76
|
+
|
|
77
|
+
_**ui/ carve-out = manual duty.** `audits/ufs.sh` skips `ui/packages/*/{src,app,tests,components,lib,hooks}/` for the string-dup check (Tailwind class-strings are noise); cross-runtime `ERR_*` parity still runs. So in ui/ TS/TSX the **author** runs the UFS pass by hand: extract any literal repeated ≥2× in-file to a named const; for discriminated-union tags use an `as const` object so narrowing survives (`const STATUS = { approved: "approved" } as const`, reference `STATUS.approved` at every site). Do not trust the audit to catch ui/ literals._
|
|
78
|
+
|
|
79
|
+
- **`const` by default. `let` only when the variable is reassigned in the same scope.** Never `let` "because I might change it later". A future-tense `let` is dead-on-arrival.
|
|
80
|
+
- **No `var`. Ever.** Hoisting is a bug nursery; the lint rule should be a hard error in `tsconfig`/`eslint.config.ts`.
|
|
81
|
+
- **Module-level constants are `SCREAMING_SNAKE_CASE`** when they're tunables (timeouts, limits, magic numbers). `camelCase` when they're configuration values composed at runtime (`const config = {...}`).
|
|
82
|
+
- **RULE UFS — Named constants for repeated and semantic literals (applies to all source).**
|
|
83
|
+
- **String literals used in ≥2 sites become a named const.** "It's just a label" is not an exception. Wire-format enum values (`"platform"`, `"self_managed"`, `"receive"`, `"stage"`) MUST be defined once as a SCREAMING_SNAKE const-as-namespace (`PROVIDER_MODE.platform`, `CHARGE_TYPE.receive`) and referenced everywhere else — including test fixtures, mock returns, and assertion arguments. Inline string repeats are a rule violation even if TypeScript narrows them.
|
|
84
|
+
- **Numeric literals carrying semantic meaning become a named const** even at the first use site. Examples that always need a name: conversion factors (`NANOS_PER_USD`, `MS_PER_SECOND`, `BYTES_PER_MIB`), thresholds (`LOW_BALANCE_THRESHOLD_NANOS`, `MAX_RETRY_COUNT`), magic offsets, sub-cent rates. Bare digits like `1_000_000_000` in a `nanos / 1_000_000_000` expression are a smell — the unit suffix names the operation, but the constant names *what the divisor is*. Carve-out: **pin tests** where the literal IS the contract (`expect(formatDollars(4_710_000_000)).toBe("$4.71")`) — those keep the literal, with a `// pin test: literal is the contract` comment.
|
|
85
|
+
- **Cross-runtime constants must be named identically** across Zig, TS, and JS (same SCREAMING_SNAKE identifier; case-only diff allowed for language idioms). When a constant exists in one runtime, the matching one in any sibling runtime carries the same name. `NANOS_PER_USD` is the canonical example: `pub const NANOS_PER_USD` in Zig, `export const NANOS_PER_USD` in TS, `export const NANOS_PER_USD` in JS — never `NANOS_PER_DOLLAR` in one and `NANOS_PER_USD` in another.
|
|
86
|
+
- **Self-audit at CONFORM.** Before declaring done, grep the diff for repeat string literals and bare-numeric divisors/multipliers — every hit either becomes a named const or earns the `// pin test` carve-out comment. Spec dimensions that introduce a new wire-format value, conversion factor, or threshold must define the constant in PLAN, not EXECUTE.
|
|
87
|
+
|
|
88
|
+
## §3 · Import discipline
|
|
89
|
+
|
|
90
|
+
> [DETERMINISTIC → TSC]
|
|
91
|
+
|
|
92
|
+
- **`import type { X } from "..."`** for type-only imports. Bun's TypeScript transpile drops type-only imports without analysis — using value imports for types adds bundle weight and complicates tree-shaking.
|
|
93
|
+
- **No default exports** for application code. Default exports break renaming, hide rebinding, and confuse `import` autocomplete. Exception: framework conventions that require it (Next.js page/layout, Astro components).
|
|
94
|
+
- **Side-effect imports last** (`import "./bootstrap"`). Distinguish `import "./reset.css"` from value imports by physical position — eyeballs need that signal.
|
|
95
|
+
- **Path aliases over `../../../`.** Configure in `tsconfig.json`/`bunfig.toml`. If you find yourself counting dots, the file is in the wrong directory.
|
|
96
|
+
|
|
97
|
+
## §4 · Bun-native primitives over Node compat
|
|
98
|
+
|
|
99
|
+
> [JUDGMENT → TSJ]
|
|
100
|
+
|
|
101
|
+
| Task | Use | Avoid (unless explicit reason) |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| HTTP server | `Bun.serve({ fetch, port })` | `express`, `fastify`, `hono` (unless a specific middleware is needed) |
|
|
104
|
+
| Filesystem read | `Bun.file(path).stream()` for unknown-size input; `.text()` / `.json()` / `.bytes()` only after size is bounded per §11 | `fs.promises.readFile` |
|
|
105
|
+
| Filesystem write | `await Bun.write(path, data)` | `fs.promises.writeFile` |
|
|
106
|
+
| SQLite | `import { Database } from "bun:sqlite"` | `better-sqlite3`, `sqlite3` |
|
|
107
|
+
| Test runner | `import { test, expect } from "bun:test"` | `vitest`, `jest`, `mocha` |
|
|
108
|
+
| Process spawn | `Bun.spawn(["cmd", "arg"])` | `child_process.spawn` |
|
|
109
|
+
| Hashing | `Bun.password.hash` / `Bun.CryptoHasher` | `crypto.createHash` for new code |
|
|
110
|
+
| Globbing | `new Bun.Glob(pattern).scan(...)` | `glob` package |
|
|
111
|
+
| Env | `Bun.env` (immutable view of `process.env`) | reading `process.env` directly when Bun-native works |
|
|
112
|
+
|
|
113
|
+
The Bun runtime is the constraint. If a dependency forces Node-compat, document the reason in a comment (`// using fs.promises because pdf-lib expects Node Buffer`).
|
|
114
|
+
|
|
115
|
+
## §5 · Type discipline at parse boundaries
|
|
116
|
+
|
|
117
|
+
> [JUDGMENT → TGU]
|
|
118
|
+
|
|
119
|
+
- **Never `any`.** Use `unknown` and narrow.
|
|
120
|
+
- **Narrow at parse boundaries** — when JSON crosses a network/storage boundary, validate with a real schema (Zod / Valibot / TypeBox). Trust nothing from outside the process.
|
|
121
|
+
- **Branded types for IDs** (`type WorkspaceId = string & { readonly _brand: "WorkspaceId" }`). Prevents passing a `UserId` where a `WorkspaceId` is expected — a compile-time bug class that's expensive at runtime.
|
|
122
|
+
- **No `as` casts** except: (a) const assertions (`as const`), (b) narrowing after a runtime check the compiler can't see (`x as NonEmpty<T>` after asserting `x.length > 0`).
|
|
123
|
+
- **Tagged unions over optional-field structs** (RULE TGU, applies to TypeScript too):
|
|
124
|
+
```ts
|
|
125
|
+
// ❌ optional-field
|
|
126
|
+
type Result = { ok: boolean; value?: T; error?: string };
|
|
127
|
+
// ✅ tagged union
|
|
128
|
+
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## §6 · File ordering
|
|
132
|
+
|
|
133
|
+
> [DETERMINISTIC → TSC]
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// 1. Type-only imports
|
|
137
|
+
import type { Workspace, User } from "./types";
|
|
138
|
+
|
|
139
|
+
// 2. Value imports
|
|
140
|
+
import { z } from "zod";
|
|
141
|
+
import { Database } from "bun:sqlite";
|
|
142
|
+
|
|
143
|
+
// 3. Module-local types
|
|
144
|
+
type Internal = { … };
|
|
145
|
+
|
|
146
|
+
// 4. Module-level consts
|
|
147
|
+
const TIMEOUT_MS = 5_000;
|
|
148
|
+
const STATEMENTS = { listAll: "SELECT * FROM ws" } as const;
|
|
149
|
+
|
|
150
|
+
// 5. Main export (class | factory | functions-module)
|
|
151
|
+
export class WorkspaceStore { … }
|
|
152
|
+
|
|
153
|
+
// 6. Helpers (private to module)
|
|
154
|
+
function parseRow(row: unknown): Workspace { … }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Side-effect imports land **after** value imports if order matters; otherwise group with value imports.
|
|
158
|
+
|
|
159
|
+
## §7 · Naming
|
|
160
|
+
|
|
161
|
+
> [DETERMINISTIC → TSC]
|
|
162
|
+
|
|
163
|
+
| Construct | Convention | Example |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| Variable / function | camelCase | `parseWorkspace`, `cachedTokens` |
|
|
166
|
+
| Class / type / interface | PascalCase | `WorkspaceStore`, `RouteHandler` |
|
|
167
|
+
| Enum & enum members | PascalCase / PascalCase | `Status.Pending`, `Status.Active` |
|
|
168
|
+
| Module-level tunable constants | SCREAMING_SNAKE | `MAX_RETRIES`, `TIMEOUT_MS` |
|
|
169
|
+
| Filename | kebab-case matching default-ish export | `workspace-store.ts` |
|
|
170
|
+
| Test file | `<unit>.test.ts` (Bun convention) | `workspace-store.test.ts` |
|
|
171
|
+
| Private class field | `#name` (real private, not `_name`) | `#db`, `#cache` |
|
|
172
|
+
|
|
173
|
+
## §8 · `bun:test` discipline
|
|
174
|
+
|
|
175
|
+
> [DETERMINISTIC → TODO-CHECK]
|
|
176
|
+
|
|
177
|
+
- **One test file per source file**, sibling-named (`workspace-store.test.ts` next to `workspace-store.ts`). Mirrors RULE TST-NAM.
|
|
178
|
+
- **Use `bun:test` exports** — `test`, `expect`, `describe`, `beforeEach`, `afterEach`, `mock`. Never import from `vitest`/`jest` even if the API looks identical.
|
|
179
|
+
- **No milestone IDs in test names** (RULE TST-NAM). Tests describe behaviour, not provenance.
|
|
180
|
+
- **Integration tests opt out via filename**: `workspace-store.integration.test.ts` — Bun pattern for filtered runs (`bun test --test-name-pattern '\.integration\.'`).
|
|
181
|
+
- **Snapshot tests are forbidden by default.** They drift, the diff is opaque, and "looks right" isn't a behaviour assertion. Only land a snapshot when you genuinely have no other way to assert (rare).
|
|
182
|
+
|
|
183
|
+
## §9 · Error handling
|
|
184
|
+
|
|
185
|
+
> [JUDGMENT → TSJ]
|
|
186
|
+
|
|
187
|
+
Pick one style per module — never mix:
|
|
188
|
+
|
|
189
|
+
- **Throw** (`throw new TypeError(...)`) when the caller cannot reasonably handle the error and the program should die or unwind.
|
|
190
|
+
- **Result-style** (`type Result<T,E> = { ok: true; value: T } | { ok: false; error: E }`) when the caller routinely handles the failure and a thrown error would force every caller to wrap in `try/catch`.
|
|
191
|
+
|
|
192
|
+
In a Result-style module, **never throw**. In a throw-style module, **never return `Result<…>`**. Mixing forces every caller to know which mode each function is in — unmaintainable.
|
|
193
|
+
|
|
194
|
+
`Error` subclasses must be named after *what is wrong*, not *when it happened* (mirrors RULE NLG): `WorkspaceNotFound`, not `LegacyWorkspaceLookupError`.
|
|
195
|
+
|
|
196
|
+
## §10 · Anti-patterns (named, banned)
|
|
197
|
+
|
|
198
|
+
> [DETERMINISTIC → TSC]
|
|
199
|
+
|
|
200
|
+
| Pattern | Why banned |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `let` for "I might change it later" | Future-tense reassignment is a bug pretending to be flexibility. |
|
|
203
|
+
| `any` to silence the type-checker | The check exists to find this. |
|
|
204
|
+
| `// @ts-ignore` / `// @ts-expect-error` without an issue link | Comments rot; a permanent `@ts-ignore` is a permanent lie. |
|
|
205
|
+
| `!` non-null assertion (or `as any` / `as unknown as T`) to silence strictness | Strict mode exists to catch null/undefined — that is the migration's whole intent. "Could be undefined" is a found bug: handle with `??` / `if` / early return / array-method narrowing, never assert it away. `noUncheckedIndexedAccess` returning `T \| undefined` for `arr[i]` is the feature, not the obstacle. |
|
|
206
|
+
| Default exports for application code | Renames cascade. Imports lie about what they import. |
|
|
207
|
+
| Re-exporting everything from `index.ts` | Hides dependency graph, defeats tree-shaking. Re-export only what's part of the public surface. |
|
|
208
|
+
| `Object.assign` for shallow merging | Use spread (`{ ...a, ...b }`). `Object.assign` mutates the first arg — bug class waiting to happen. |
|
|
209
|
+
| Top-level `console.log` left in source | Use a real logger, or remove before commit. Audit catches this in pre-commit if a logger gate exists. |
|
|
210
|
+
| `process.env.X` reads scattered through code | Read once at module init, freeze, export typed. Scattered reads make config opaque. |
|
|
211
|
+
|
|
212
|
+
## §11 · Runtime resource discipline
|
|
213
|
+
|
|
214
|
+
> [DETERMINISTIC → TODO-CHECK]
|
|
215
|
+
|
|
216
|
+
TypeScript makes unbounded work look cheap. Bun will still allocate, queue, block, and leak process handles if the code shape is sloppy.
|
|
217
|
+
|
|
218
|
+
### Promise concurrency
|
|
219
|
+
|
|
220
|
+
- **No unbounded `Promise.all(items.map(...))`** on data from users, files, network, databases, or command output. Use a concurrency limiter, chunked loop, or streaming transform.
|
|
221
|
+
- **Every fan-out declares its bound** near the call site: `MAX_CONCURRENT_FETCHES`, `MAX_PARALLEL_FILES`, etc. Numeric literals follow RULE UFS in §2.
|
|
222
|
+
- **Retry loops are bounded**: max attempts, delay/backoff, jitter when multiple clients may retry, and an abort path.
|
|
223
|
+
- **Long-running async work accepts an `AbortSignal`** or has an equivalent owner-controlled cancellation path.
|
|
224
|
+
- **`Promise.race` timeouts cancel the loser.** A timer-only race that leaves work running is a leak.
|
|
225
|
+
|
|
226
|
+
### Memory shape
|
|
227
|
+
|
|
228
|
+
- **Do not materialize unknown-size input.** Avoid `await Bun.file(path).text()`, `response.text()`, `response.json()`, or `Array.from(...)` when size is not bounded. Stream, chunk, or reject at a named limit.
|
|
229
|
+
- **Do not accumulate transform results by default.** Prefer `for await` streaming or chunked processing for large file, HTTP, or command-output flows.
|
|
230
|
+
- **Large JSON parse/stringify is both memory and Central Processing Unit (CPU) work.** Put it behind a size limit or move it out of request-critical code.
|
|
231
|
+
- **Clear resource handles in `finally`.** Timers, intervals, subprocesses, file handles, and temporary listeners must be cleaned even on thrown errors.
|
|
232
|
+
|
|
233
|
+
### Timeout and cancellation
|
|
234
|
+
|
|
235
|
+
Every network request, subprocess wrapper, file watcher, long poll, or task queue wait added by a diff needs a timeout or cancellation owner.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MILLISECONDS);
|
|
240
|
+
try {
|
|
241
|
+
return await fetch(url, { signal: controller.signal });
|
|
242
|
+
} finally {
|
|
243
|
+
clearTimeout(timeout);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Use a local helper only when it preserves the same visible ownership: who starts the timer, who aborts, and who clears it.
|
|
248
|
+
Returning a `Response` from a helper transfers body consumption outside this timeout; only do that when the caller owns a second timeout.
|
|
249
|
+
|
|
250
|
+
### Bun subprocess cleanup
|
|
251
|
+
|
|
252
|
+
- `Bun.spawn` callers must consume, redirect, or intentionally ignore stdout/stderr. Do not leave pipes unread.
|
|
253
|
+
- Every spawned process is awaited, killed on timeout, or tied to an owner cleanup path.
|
|
254
|
+
- Wrapper tests cover the timeout/kill path, not just the success exit code.
|
|
255
|
+
|
|
256
|
+
### CPU-bound work
|
|
257
|
+
|
|
258
|
+
- CPU-heavy synchronous work does not live directly in request handlers without a named bound. Examples: large JSON serialization, hashing loops, compression, directory walks, and bulk formatting.
|
|
259
|
+
- Worker usage declares input size, concurrency, termination, and error propagation. A worker that can fail silently is a hidden hang.
|
|
260
|
+
- Prefer one parse/config load at module startup over repeated parse inside handlers or command loops.
|
|
261
|
+
|
|
262
|
+
## §12 · Override syntax
|
|
263
|
+
|
|
264
|
+
> [JUDGMENT → TSJ]
|
|
265
|
+
|
|
266
|
+
Per-rule override:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
BUN RULE <§N>: SKIPPED per user override (reason: ...)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
immediately preceding the edit. **User-invokable only.** Generic "scope creep" / "save for later" not valid — name a concrete external constraint (third-party-API shape, framework requirement, dependency mismatch).
|
|
273
|
+
|
|
274
|
+
## §13 · Family
|
|
275
|
+
|
|
276
|
+
> [DETERMINISTIC → FLL]
|
|
277
|
+
|
|
278
|
+
- Universal rules (RULE UFS, RULE TGU, RULE PRI, RULE FLL, RULE ORP, RULE TST-NAM) live in `docs/greptile-learnings/RULES.md` and apply to TypeScript via the **GREPTILE GATE**.
|
|
279
|
+
- Frontend visual primitives live in the UI Component Substitution section of this façade (UI GATE).
|
|
280
|
+
- Length caps live in `dispatch/write_any.md` (LENGTH GATE — file ≤ 350, fn ≤ 50, method ≤ 70 — same as every other source language).
|
|
281
|
+
- This file is the **TypeScript / Bun-specific** layer that those universal rules cannot express. Scan its section headers at the first TS/JS touch (`grep -nE "^##+ "` — two-plus hashes, so nested cards surface) and read the sections the diff shape touches; on sub-task shape change, re-scan and read only newly-touched sections — never the full file.
|
|
282
|
+
|
|
283
|
+
## Merged from dissolved gate cards
|
|
284
|
+
|
|
285
|
+
> [container]
|
|
286
|
+
|
|
287
|
+
The TypeScript-relevant gate cards (`ui-substitution`, `design-token`) dissolve into this façade. Their prose is preserved verbatim below (headings demoted one level; words unchanged), and — matching `write_zig.md` — each subsection carries its own enforcement tag (`UIS` for the UI gate, `DTK` for the design-token gate). The mechanical checks delegate to `make lint`: the audit scripts (`msid-ui.sh`, `design-tokens.sh`) scan `ui/packages/**`, which exists in the product repo, not in dotfiles, so the dispatch emits `🟣 DELEGATED` for them here.
|
|
288
|
+
|
|
289
|
+
### UI Component Substitution (UI GATE)
|
|
290
|
+
|
|
291
|
+
> [DETERMINISTIC → UIS]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
**Family:** Frontend design-system discipline. **Source:** `AGENTS.md` (project-side guard). Authoritative primitive set: `ui/packages/design-system/src/index.ts`.
|
|
295
|
+
|
|
296
|
+
**Triggers** — every `Edit`/`Write` to a `*.tsx` / `*.jsx` under `ui/packages/app/` **or** `ui/packages/website/`. Both packages share the same design-system source of truth (`@agentsfleet/design-system`), so the same primitive-first standard applies. Tests (`*.test.tsx`) and Playwright specs (`tests/e2e/**`) are exempt — they assert on rendered DOM and frequently use raw selectors.
|
|
297
|
+
|
|
298
|
+
**Override:** `UI GATE: SKIPPED per user override (reason: ...)` immediately preceding the edit.
|
|
299
|
+
|
|
300
|
+
#### What this gate enforces
|
|
301
|
+
|
|
302
|
+
> [DETERMINISTIC → UIS]
|
|
303
|
+
|
|
304
|
+
The design-system package (`ui/packages/design-system/src/index.ts` exports — `Section`, `Card`, `Badge`, `Button`, `Input`, `Dialog`, `Pagination`, `EmptyState`, `Tooltip`, `List`/`ListItem`, `WakePulse`, `Terminal`, `InstallBlock`, etc.) is the source of truth for visual primitives. Raw HTML in either dashboard or marketing files drifts from those tokens silently.
|
|
305
|
+
|
|
306
|
+
**Marketing-display typography — primitives now exist.** `<DisplayXL>` (marketing hero `<h1>`, `text-fluid-hero`) and `<DisplayLG>` (section heads `<h2>`, `text-fluid-display-lg`) ship from `@agentsfleet/design-system` and compose `font-mono` + the fluid text/leading/tracking tokens internally. Use them on marketing-display surfaces. Raw `<h1>+utilities` is no longer the carve-out — UI GATE blocks it. The dashboard's `<PageTitle>` (`text-heading ≈ 18px`) remains the correct primitive for app surfaces.
|
|
307
|
+
|
|
308
|
+
This gate enforces "use the primitive when one exists" without enumerating the primitives in this rule (so the rule scales as the design-system grows).
|
|
309
|
+
|
|
310
|
+
#### Pre-edit check
|
|
311
|
+
|
|
312
|
+
> [DETERMINISTIC → UIS]
|
|
313
|
+
|
|
314
|
+
1. Read (or recall) the design-system index. Treat its exports as the substitute set.
|
|
315
|
+
2. For each raw HTML element your edit adds (`<section>`, `<button>`, `<input>`, `<article>`, `<dialog>`, `<dl>`, `<table>`, `<nav>`, `<header>`, `<form>`, etc.), check the index for a matching primitive. If one exists, use it.
|
|
316
|
+
3. Use `asChild` when you need the underlying HTML tag for semantics:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
<Section asChild>
|
|
320
|
+
<section aria-label="...">…</section>
|
|
321
|
+
</Section>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### Required output (default — one line)
|
|
325
|
+
|
|
326
|
+
> [DETERMINISTIC → UIS]
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
UI GATE: <file> | primitives:<list> | raw-kept:<list with one-word reason each | none>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Multi-line block fires only when raw-kept is non-empty:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
UI GATE: <file>
|
|
336
|
+
Primitives used: <list>
|
|
337
|
+
Raw HTML kept:
|
|
338
|
+
- <element>: <reason> (e.g. "ul: no DS primitive")
|
|
339
|
+
- ...
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### Self-audit (end-of-turn)
|
|
343
|
+
|
|
344
|
+
> [DETERMINISTIC → UIS]
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
git diff -U0 HEAD -- 'ui/packages/app/**/*.tsx' 'ui/packages/website/src/**/*.tsx' \
|
|
348
|
+
| grep -v -E '\.test\.tsx|tests/e2e/' \
|
|
349
|
+
| grep -E '^\+.*<(section|button|input|dialog|article|nav|header|form)\b' \
|
|
350
|
+
| head
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Non-empty is a violation unless every match has a printed "Raw HTML kept" justification in the corresponding gate block.
|
|
354
|
+
|
|
355
|
+
### Design Tokens (DESIGN TOKEN GATE)
|
|
356
|
+
|
|
357
|
+
> [DETERMINISTIC → DTK]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
**Family:** Frontend design-system discipline (paired with the UI Component Substitution Gate). **Source:** `AGENTS.md` (project-side guard). Authoritative token surface: `ui/packages/design-system/src/theme.css` (Layer-2 `@theme inline`).
|
|
361
|
+
|
|
362
|
+
**Triggers** — every `Edit`/`Write` to `*.tsx` / `*.jsx` under `ui/packages/app/` **or** `ui/packages/website/`. Tests (`*.test.tsx`) and Playwright specs (`tests/e2e/**`) are exempt — they assert on rendered DOM and frequently use raw selectors.
|
|
363
|
+
|
|
364
|
+
**Override:** `// DESIGN TOKEN: SKIPPED per user override (reason: ...)` immediately preceding the affected line. Reasons must cite a concrete constraint — "bespoke per-surface grid template", "tight UI chrome width with no equivalent token", "external library prop expects px-string" — not "looks the same" / "shorter to write". Auto-mode does NOT cover the override.
|
|
365
|
+
|
|
366
|
+
#### What this gate enforces
|
|
367
|
+
|
|
368
|
+
> [DETERMINISTIC → DTK]
|
|
369
|
+
|
|
370
|
+
The design-system package exposes named Tailwind utilities for every typographic and layout primitive (`text-display-xl`, `text-eyebrow`, `leading-prose`, `max-w-narrow`, `tracking-display-md`, `duration-snap`, etc.). Raw arbitrary values bypass the published scale. Over time that drift dilutes the system; the moment one surface ships `text-[14px] leading-[1.6]`, others copy the pattern.
|
|
371
|
+
|
|
372
|
+
This gate blocks arbitrary classes **when an equivalent token utility exists**. It does NOT block:
|
|
373
|
+
|
|
374
|
+
- Tailwind state selectors (`data-[active=true]:bg-accent`)
|
|
375
|
+
- Pseudo-element `content-[...]`
|
|
376
|
+
- Bespoke `grid-cols-[...]` / `grid-rows-[...]` (per-surface templates)
|
|
377
|
+
- `calc(...)` expressions that consume a token (`h-[calc(100vh-var(--header-height))]`)
|
|
378
|
+
- Token-using shadows (`shadow-[0_0_0_3px_var(--pulse-glow)]`)
|
|
379
|
+
|
|
380
|
+
#### Pre-edit check
|
|
381
|
+
|
|
382
|
+
> [DETERMINISTIC → DTK]
|
|
383
|
+
|
|
384
|
+
1. Read (or recall) `ui/packages/design-system/src/theme.css` — the Layer-2 `@theme inline { ... }` block is the authoritative utility surface.
|
|
385
|
+
|
|
386
|
+
2. The available token utilities are exactly the bridged names:
|
|
387
|
+
|
|
388
|
+
| Family | Utilities |
|
|
389
|
+
|---|---|
|
|
390
|
+
| Text size | `text-{label,eyebrow,body-sm,body,body-lg,heading,display-md,display-lg,display-xl}` |
|
|
391
|
+
| Fluid text | `text-fluid-{display-md,display-lg,hero}` |
|
|
392
|
+
| Tracking | `tracking-{display-xl,display-lg,display-md,eyebrow,label}` |
|
|
393
|
+
| Line-height | `leading-{display-xl,display-lg,display-md,heading,eyebrow,body-lg,body,body-sm,label,mono,prose}` |
|
|
394
|
+
| Max width | `max-w-{trim,narrow,measure,form,wide,content,tagline,prose}` |
|
|
395
|
+
| Min width | `min-w-{trim,narrow,measure,form,wide,content,tagline,prose}` |
|
|
396
|
+
| Spacing | `{p,m,gap,space-{x,y}}-{xs,sm,md,lg,xl,2xl,3xl,4xl,5xl,6xl}` |
|
|
397
|
+
| Motion | `duration-snap`, `ease-snap` |
|
|
398
|
+
| Radius | `rounded-{sm,md,lg}` |
|
|
399
|
+
| Color | semantic tokens only — `bg-{background,card,popover,primary,secondary,muted,accent,destructive,…}`, `text-{foreground,muted-foreground,text,text-muted,text-subtle,…}`, `border-{border,border-strong,input,ring}`, status tokens `text-{success,warn,error,info,evidence}` / `text-on-pulse` |
|
|
400
|
+
|
|
401
|
+
3. For every arbitrary `*-[...]` class your edit adds, ask: **does a token utility exist?**
|
|
402
|
+
- If yes, use it.
|
|
403
|
+
- If no, the arbitrary is allowed without an override comment.
|
|
404
|
+
- If close but not exact, prefer the token. The system absorbs ±1–2 px differences; over hundreds of callsites the consistency dominates the per-callsite shift.
|
|
405
|
+
|
|
406
|
+
#### Required output (default — one line)
|
|
407
|
+
|
|
408
|
+
> [DETERMINISTIC → DTK]
|
|
409
|
+
|
|
410
|
+
```
|
|
411
|
+
DESIGN TOKEN GATE: <file> | arbitraries-kept: <list with one-word reason each | none>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Multi-line block fires only when `arbitraries-kept` is non-empty:
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
DESIGN TOKEN GATE: <file>
|
|
418
|
+
Arbitraries kept:
|
|
419
|
+
- <class>: <reason> (e.g. "grid-cols-[2fr_1fr_1fr_1fr]: bespoke footer grid")
|
|
420
|
+
- <class>: <reason>
|
|
421
|
+
...
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### Scope
|
|
425
|
+
|
|
426
|
+
> [DETERMINISTIC → DTK]
|
|
427
|
+
|
|
428
|
+
`design-tokens.sh` walks the **full ui/packages working tree** via `git ls-files`. The index includes staged-but-not-yet-committed content, so a fix staged in pre-commit satisfies the check on the same hook run. The previous `--diff` (`BASE...HEAD`) default was retired with M70 — at pre-commit time `HEAD` is the prior commit, leaving the gate blind to the index.
|
|
429
|
+
|
|
430
|
+
`--all` is accepted as a back-compat alias for the default. `--staged` is preserved as an opt-in narrowing mode for iterative dev loops. `--diff` is rejected with exit 2 + a pointer to this section.
|
|
431
|
+
|
|
432
|
+
#### Self-audit (end-of-turn)
|
|
433
|
+
|
|
434
|
+
> [DETERMINISTIC → DTK]
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
audits/design-tokens.sh # full-codebase scan (default)
|
|
438
|
+
audits/design-tokens.sh --staged # opt-in narrowing for iterative dev
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
The script lives in the project repo at `audits/design-tokens.sh` (symlinked from `audits/`). It prints `file:line` + suggested token utility for every blocking violation and exits 1 on any.
|
|
442
|
+
|
|
443
|
+
A clean run is required before CONFORM can advance.
|
|
444
|
+
|
|
445
|
+
The project repo is also expected to wire the audit into `make/quality.mk` `_website_lint` and `_app_lint` targets so `make lint` catches regressions:
|
|
446
|
+
|
|
447
|
+
```make
|
|
448
|
+
_website_lint:
|
|
449
|
+
@echo "→ [website] Running Oxlint + TypeScript check..."
|
|
450
|
+
@cd ui/packages/website && bun run lint
|
|
451
|
+
@cd ui/packages/website && bun run typecheck
|
|
452
|
+
@echo "→ [website] Running design-token audit..."
|
|
453
|
+
@audits/design-tokens.sh
|
|
454
|
+
@echo "✓ [website] Lint passed"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### Family rules
|
|
458
|
+
|
|
459
|
+
> [DETERMINISTIC → DTK]
|
|
460
|
+
|
|
461
|
+
This gate composes with the UI Component Substitution Gate. Order of operations on a fresh `.tsx` edit:
|
|
462
|
+
|
|
463
|
+
1. **UI GATE** — pick the primitive (`<Card>`, `<Section>`, `<Button>`, `<DisplayXL>`, …)
|
|
464
|
+
2. **DESIGN TOKEN GATE** — pick the token utility (`text-body`, `leading-body`, `max-w-narrow`)
|
|
465
|
+
3. **UFS GATE** — pick a named const for any repeated string literal
|
|
466
|
+
4. **LENGTH GATE** — keep the file ≤350 lines
|
|
467
|
+
|
|
468
|
+
A `.tsx` that passes all four reads as system-conformant without spelunking into the design-system source.
|
|
469
|
+
|
|
470
|
+
#### Why a gate, not a lint rule
|
|
471
|
+
|
|
472
|
+
> [DETERMINISTIC → DTK]
|
|
473
|
+
|
|
474
|
+
Three reasons:
|
|
475
|
+
|
|
476
|
+
1. **Cross-package coupling.** The token set lives in `ui/packages/design-system`; the consumers live in `ui/packages/{app,website}`. A single oxlint config can't see across the boundary cleanly. The audit script reads the diff and the theme.css utilities — it's the right shape.
|
|
477
|
+
|
|
478
|
+
2. **Inline override.** Some `-[...]` classes are intentional and irreplaceable (bespoke grids, `calc(...)`, status-dot sizes). The comment-based override fits the existing gate idiom and gives reviewers a paper trail of *why* each arbitrary stayed.
|
|
479
|
+
|
|
480
|
+
3. **CONFORM integration.** Gates are the canonical end-of-turn audit point. Lint rules surface in editor noise but miss the agentic lifecycle. The gate's pre-edit one-liner + end-of-turn self-audit puts the discipline in the model's hot path.
|
|
481
|
+
|
|
482
|
+
#### When tokens are missing
|
|
483
|
+
|
|
484
|
+
> [DETERMINISTIC → DTK]
|
|
485
|
+
|
|
486
|
+
If the arbitrary you want is a legitimate design value that simply has no token yet, **extend the design system in the same commit**:
|
|
487
|
+
|
|
488
|
+
1. Add `--<family>-<name>` to `ui/packages/design-system/src/tokens.css`
|
|
489
|
+
2. Forward to Tailwind via `@theme inline` in `ui/packages/design-system/src/theme.css`
|
|
490
|
+
3. Migrate your callsite to the new utility
|
|
491
|
+
4. Add the utility to the table above in this gate body (same dotfiles commit — Invariance Suite Gate applies)
|
|
492
|
+
|
|
493
|
+
This is the path to a system that grows without dilution.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dispatch/write_ts_adhere_bun.sh — the TypeScript / Bun dispatch, DETERMINISTIC façade.
|
|
3
|
+
#
|
|
4
|
+
# Pairs with dispatch/write_ts_adhere_bun.md (the LATENT façade — prose the agent reads).
|
|
5
|
+
# This .sh runs the mechanically-checkable subset of the TS/Bun discipline and
|
|
6
|
+
# emits ONE verdict block. The latent trigger ("about to write a *.ts/*.tsx/*.js/*.jsx
|
|
7
|
+
# file") dispatches here; everything below is deterministic and re-runnable — same
|
|
8
|
+
# input, same verdict, no calendar/branch dependence.
|
|
9
|
+
#
|
|
10
|
+
# dispatch/write_ts_adhere_bun.sh <file.ts> [...] # explicit targets (EXECUTE)
|
|
11
|
+
# dispatch/write_ts_adhere_bun.sh --staged # staged TS/JS (CONFORM, pre-commit)
|
|
12
|
+
#
|
|
13
|
+
# Layering: AGENTS.md → write_ts_adhere_bun.md (latent) → write_ts_adhere_bun.sh (this) → audits/*.sh
|
|
14
|
+
# Signals: 🟢 pass · 🔴 fail (blocks) · 🤔 DECIDE (judgment; blocks the TURN, not the script) · 🟣 delegated
|
|
15
|
+
# Exit: 0 = mechanical gates pass · 1 = ≥1 failed · 2 = usage error.
|
|
16
|
+
|
|
17
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
18
|
+
|
|
19
|
+
dispatch_init "TS" '*.ts' '*.tsx' '*.js' '*.jsx'
|
|
20
|
+
dispatch_resolve_files "$@"
|
|
21
|
+
dispatch_header
|
|
22
|
+
|
|
23
|
+
# ── deterministic gates ────────────────────────────────────────────
|
|
24
|
+
dispatch_length_gate 350 # FLL — write_ts_adhere_bun.md §13 (file ≤ 350)
|
|
25
|
+
# UFS — universal rule enforced once by write_any (see write_any.sh); not re-run here
|
|
26
|
+
|
|
27
|
+
# ── delegations (checker lives in the product repo, not dotfiles) ───
|
|
28
|
+
dispatch_delegate "TSC" "make lint (oxlint + tsc — const/import/ordering/naming/anti-patterns)"
|
|
29
|
+
dispatch_delegate "UIS" "make lint (msid-ui.sh — design-system primitive over raw HTML)"
|
|
30
|
+
dispatch_delegate "DTK" "make lint (design-tokens.sh — token utility over arbitrary value)"
|
|
31
|
+
|
|
32
|
+
# ── judgment gates (no script can decide; agent states verdict in chat) ──
|
|
33
|
+
dispatch_judgment "FSD" "new *.ts/*.tsx under src|app|lib? class vs factory vs functions-module vs type-only — decide at PLAN, one-sentence why-not"
|
|
34
|
+
dispatch_judgment "TGU" "result with distinct failure modes? discriminated union ({ok:true…}|{ok:false…}), not optional-field struct"
|
|
35
|
+
dispatch_judgment "TSJ" "TS/Bun convention choices (Bun-native over Node-compat, file ordering, error style: throw XOR Result per module)? justify any Node-compat fallback against a concrete dependency constraint"
|
|
36
|
+
|
|
37
|
+
dispatch_verdict
|