diff --git a/harness/pstack/claude-code/skills/typescript-best-practices/SKILL.md b/harness/pstack/claude-code/skills/typescript-best-practices/SKILL.md index 2c0279d9a..e1e5aa7c9 100644 --- a/harness/pstack/claude-code/skills/typescript-best-practices/SKILL.md +++ b/harness/pstack/claude-code/skills/typescript-best-practices/SKILL.md @@ -12,7 +12,7 @@ Apply the **type-system-discipline** principle skill first. | Rule | Summary | |------|---------| | Discriminated unions | Model variants with a `kind` literal discriminant so impossible states can't be represented. No optional-field bags. | -| Branded types | Brand primitives with `& { readonly __brand: "X" }` so they can't be mixed up. Validate once at the boundary. | +| Branded types | Brand primitives with `& { readonly __brand: "X" }` so they can't be mixed up. Validate once at the boundary. A brand comes from a verifying type guard (`input is AgentId`), never from `as`. | | Constructive modeling | Build the shape so the illegal value can't be constructed. `[T, ...T[]]` for non-empty, `[T, T][]` for even length, `start` plus `duration` for a range. Not a runtime guard, not a wish for refinement types. | | Simplest total type | Keep `T[]` while every operation on it stays total. Strengthen to `NonEmpty` only where the loose type forces `!`, a cast, or a "should never happen" throw. | | `unknown` over `any` | External data is `unknown`. | diff --git a/harness/pstack/claude-code/skills/typescript-best-practices/references/patterns.md b/harness/pstack/claude-code/skills/typescript-best-practices/references/patterns.md index c21d876b7..f8dd99b63 100644 --- a/harness/pstack/claude-code/skills/typescript-best-practices/references/patterns.md +++ b/harness/pstack/claude-code/skills/typescript-best-practices/references/patterns.md @@ -9,9 +9,13 @@ Brand primitives so they can't be mixed up. Validate once at the boundary. Downs ```ts type AgentId = string & { readonly __brand: "AgentId" }; +function isAgentId(input: string): input is AgentId { + return isUUID(input); +} + function parseAgentId(input: string): AgentId { - if (!isUUID(input)) throw new Error(`Invalid agent id: ${input}`); - return input as AgentId; + if (!isAgentId(input)) throw new Error(`Invalid agent id: ${input}`); + return input; } function focusAgent(id: AgentId): void { diff --git a/pstack/skills/typescript-best-practices/SKILL.md b/pstack/skills/typescript-best-practices/SKILL.md index 2c0279d9a..e1e5aa7c9 100644 --- a/pstack/skills/typescript-best-practices/SKILL.md +++ b/pstack/skills/typescript-best-practices/SKILL.md @@ -12,7 +12,7 @@ Apply the **type-system-discipline** principle skill first. | Rule | Summary | |------|---------| | Discriminated unions | Model variants with a `kind` literal discriminant so impossible states can't be represented. No optional-field bags. | -| Branded types | Brand primitives with `& { readonly __brand: "X" }` so they can't be mixed up. Validate once at the boundary. | +| Branded types | Brand primitives with `& { readonly __brand: "X" }` so they can't be mixed up. Validate once at the boundary. A brand comes from a verifying type guard (`input is AgentId`), never from `as`. | | Constructive modeling | Build the shape so the illegal value can't be constructed. `[T, ...T[]]` for non-empty, `[T, T][]` for even length, `start` plus `duration` for a range. Not a runtime guard, not a wish for refinement types. | | Simplest total type | Keep `T[]` while every operation on it stays total. Strengthen to `NonEmpty` only where the loose type forces `!`, a cast, or a "should never happen" throw. | | `unknown` over `any` | External data is `unknown`. | diff --git a/pstack/skills/typescript-best-practices/references/patterns.md b/pstack/skills/typescript-best-practices/references/patterns.md index c21d876b7..f8dd99b63 100644 --- a/pstack/skills/typescript-best-practices/references/patterns.md +++ b/pstack/skills/typescript-best-practices/references/patterns.md @@ -9,9 +9,13 @@ Brand primitives so they can't be mixed up. Validate once at the boundary. Downs ```ts type AgentId = string & { readonly __brand: "AgentId" }; +function isAgentId(input: string): input is AgentId { + return isUUID(input); +} + function parseAgentId(input: string): AgentId { - if (!isUUID(input)) throw new Error(`Invalid agent id: ${input}`); - return input as AgentId; + if (!isAgentId(input)) throw new Error(`Invalid agent id: ${input}`); + return input; } function focusAgent(id: AgentId): void {