Agent Skillsluxury-yacht/app › make-impossible-states-impossible

make-impossible-states-impossible

GitHub

通过重构类型系统消除无效状态,使用联合类型、必填字段或验证构造器替代运行时检查,确保编译器拦截非法值,提升代码安全性与可维护性。

.agents/skills/make-impossible-states-impossible/SKILL.md luxury-yacht/app

Trigger Scenarios

存在布尔标志混乱或矛盾的可空字段 需要消除类型系统允许但领域禁止的无效状态

Install

npx skills add luxury-yacht/app --skill make-impossible-states-impossible -g -y
More Options

Non-standard path

npx skills add https://github.com/luxury-yacht/app/tree/main/.agents/skills/make-impossible-states-impossible -g -y

Use without installing

npx skills use luxury-yacht/app@make-impossible-states-impossible

指定 Agent (Claude Code)

npx skills add luxury-yacht/app --skill make-impossible-states-impossible -a claude-code -g -y

安装 repo 全部 skill

npx skills add luxury-yacht/app --all -g -y

预览 repo 内 skill

npx skills add luxury-yacht/app --list

SKILL.md

Frontmatter
{
    "name": "make-impossible-states-impossible",
    "description": "Eliminate representable-but-invalid Luxury Yacht states with discriminated unions, required identity, typed status, validated constructors, or one boundary chokepoint; use for \"make impossible states impossible\", boolean flag soup, contradictory nullable fields, incomplete object refs, or stringly state"
}

Make Impossible States Impossible

An impossible state is a value the type system permits but the domain forbids: { isLoading: true, error: "x", data: [...] } all at once, an object ref with kind but no clusterId, a status: string that can hold "loaidng". The discipline is to change the representation so the compiler (TS) or a constructor (Go) rejects the invalid value, deleting the runtime check that used to catch it.

This is a structural refactor skill. It changes types, rarely behavior. Treat every conversion as a behavior-preserving change proven by the compiler plus the existing test suite — and add a test wherever a runtime guard is being removed.

The one decision

For each impossible state, choose where to make it impossible:

  1. Type level (preferred). Redesign so the bad value cannot be constructed: discriminated union, required field, typed enum, type-state. Then delete the runtime guard that became unreachable (see "Always remove dead code").
  2. Single chokepoint (when the type can't be tightened). External/legacy shapes sometimes force a loose type — e.g. KubernetesObjectReference carries [key: string]: unknown for raw K8s objects and backwards-compat. There, the project keeps one validating chokepoint, not scattered checks. The canonical example is assertObjectRefHasRequiredIdentity in frontend/src/shared/utils/objectIdentity.ts, called once at useObjectPanel.openWithObject — it is an assertion function that narrows the loose ref to ClusterObjectReference in place, so everything past the chokepoint carries the required-identity type. Match that pattern; never sprinkle per-caller if (!ref.clusterId) checks.

Prefer the difficult-but-correct type-level fix over a new local guard (AGENTS.md). Centralize at a chokepoint only when a true boundary makes the type genuinely un-tightenable.

Frontend smell → fix (TypeScript)

Smell Where it looks like Fix
Flag soup — booleans encoding one state port-forward/* uses isError/isStopping/isLoading together One status discriminated union. Good model already in repo: useResourceInventoryTable.ts derives isEmpty from status === 'empty'; permissionTypes.ts uses status: 'loading' | 'ready' | 'error'.
Optional pair that must co-occur { error?; data? } where exactly one is set Variants: { status: 'error'; error } | { status: 'ready'; data }.
Nullable identity KubernetesObjectReference extends NullableResourceRefFields (every GVK field ?| null) Narrow at a parse boundary into a resolved type with required fields — ResolvedObjectReference (GVK+name required) or, past a cluster-identity boundary, ClusterObjectReference (clusterId also required) in shared/utils/objectIdentity.ts. Downstream code takes the resolved type, not the nullable one.
Stringly-typed state status: string, phase: string Literal union; exhaustive switch with a never default.
[key: string]: unknown escape hatch view-state raw-object shapes Acceptable only at the external boundary; convert to a typed value immediately after, and don't let the loose type leak downstream.

Backend smell → fix (Go — no sum types)

Smell Fix
string-typed state with a valid zero value Defined type + unexported field + constructor that validates. See ClusterLifecycleState (backend/cluster_lifecycle.go), JobState (backend/refresh/types.go), HealthState (backend/objectcatalog/types.go). Guard transitions in one method, not at every call site.
Bool flag selecting behavior Type-state: distinct types per state so the wrong operation won't compile.
Exported struct with invalid field combos Unexport fields; expose a constructor that rejects invalid combinations. The zero value should be either valid or unconstructable.
Sum type needed Sealed interface (unexported marker method) + one small impl per case + exhaustive type switch with a default that errors/panics.

Workflow

  1. Scope. Pick one module (frontend) or package (backend), or a single type. The todo is "all areas, ultimately" — do it one bounded area at a time.
  2. Audit with the greps below; list candidate impossible states.
  3. Trace the contract first. Any type that crosses backend/frontend, lifecycle, refresh domains, cluster identity, or object references is governed by AGENTS.md's Cross-Layer Contract Rule. Identify the producer, every consumer, and ordering before editing. Names are not contracts.
  4. Rank & present like the improve-* skills: a numbered list, each with the invalid value it permits today and the proposed representation. Let the user pick one. Don't batch a module-wide rewrite into one step.
  5. Impact gate. Before editing production source, write a fresh entry to .claude/impact-analysis.md (the hook blocks edits otherwise).
  6. TDD (required, AGENTS.md).
    • Red: write a test that pins the behavior — for a chokepoint, that the invalid construction is rejected; for a union conversion, that consumers handle each variant. Confirm it fails for the right reason.
    • Green: change the type; let the compiler list the consumers to update.
    • Refactor: delete the now-unreachable runtime guards and any dead branch (bottom-up, same change).
  7. Validate: follow the root final validation gate.

Audit greps

# Flag soup: 2+ boolean state flags near each other
rg -n -g '*.ts' -g '*.tsx' \
  "is(Loading|Error|Fetching|Empty|Ready|Connected|Pending|Stopping)\b\s*[:?]" \
  frontend/src | rg -v "\.(test|stories)\."

# Stringly-typed states that should be literal unions
rg -n -g '*.ts' -g '*.tsx' "(status|phase|state):\s*string\b" frontend/src

# Existing good unions to emulate
rg -n "status:\s*['\"](loading|error|ready|idle|empty)['\"]" frontend/src

# Backend string-typed states (candidates for validated constructors)
rg -n -g '*.go' "type\s+\w*(State|Status|Phase|Lifecycle)\s+string" backend

What NOT to do

  • Don't add a new scattered runtime if-guard when a type change or the existing chokepoint is the correct fix.
  • Don't widen a type to silence the compiler — that re-introduces the impossible state. Update consumers instead.
  • Don't drop or guess clusterId/GVK to make a ref "fit" a tighter type; a ref without full identity is the impossible state (AGENTS.md).
  • Don't leave the old runtime check behind once a type makes it unreachable.
  • Don't convert a whole module in one commit; one impossible state at a time, each proven by a test.
  • Don't change runtime behavior under the guise of a type refactor without a test that names the behavior change.

Version History

  • ee846a6 Current 2026-08-20 13:02

Same Skill Collection

.agents/skills/add-resource/SKILL.md
.agents/skills/app-review/SKILL.md
.agents/skills/app-shell/SKILL.md
.agents/skills/branch-review/SKILL.md
.agents/skills/browse-tables/SKILL.md
.agents/skills/cluster-auth-lifecycle/SKILL.md
.agents/skills/draft-release-notes/SKILL.md
.agents/skills/improve-backend/SKILL.md
.agents/skills/improve-frontend/SKILL.md
.agents/skills/new-story/SKILL.md
.agents/skills/object-map/SKILL.md
.agents/skills/object-panel/SKILL.md
.agents/skills/operations-workflows/SKILL.md
.agents/skills/permissions-capabilities/SKILL.md
.agents/skills/refresh-subsystem/SKILL.md
.agents/skills/shared-resource-model/SKILL.md

Metadata

Files
0
Version
ee846a6
Hash
6e240fe4
Indexed
2026-08-20 13:02

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-25 08:02
浙ICP备14020137号-1 $방문자$