diff --git a/.agents/memory/bot-agents-migration-decisions.md b/.agents/memory/bot-agents-migration-decisions.md index 463da32..3fdc32c 100644 --- a/.agents/memory/bot-agents-migration-decisions.md +++ b/.agents/memory/bot-agents-migration-decisions.md @@ -21,13 +21,11 @@ Node + Vite+ migration (PR #20, `node-migration`, 2026-10-15/16): - **Published lint plugins** (2026-10-18, after they reached npm): `eslint-plugin-import-structure` (`modules` config) and `eslint-plugin-explicit-types` (`concrete` and `absence` configs) are - devDependencies, enabled in `vite.config.ts`. explicit-types' `naming` - config is left off: its `upper-snake-string-unions` and - `no-upper-snake-object-keys` rules fire on wire tokens this repo does not - name (profile types such as `"claude"`, failure reasons posted to Gitea, - environment variable names), which cannot change without changing - behavior. Open question for the user. `eslint-plugin-lit-robust` does not apply - (no Lit). + devDependencies, enabled in `vite.config.ts` through their `all` configs. + `upper-snake-string-unions` was off while the user decided; since + 2026-10-19 it is on and profile types, failure reasons, and anchor sides + are `UPPER_SNAKE` ([[enum-values-upper-snake]]). `eslint-plugin-lit-robust` + does not apply (no Lit). - **Stricter input checks were accepted** in the migration: Gitea `/user` and comment responses, the runtime manifest, and the outcome's profile type are validated; some type errors became `TypeError` with the same messages. diff --git a/.agents/memory/enum-values-upper-snake.md b/.agents/memory/enum-values-upper-snake.md new file mode 100644 index 0000000..954c61e --- /dev/null +++ b/.agents/memory/enum-values-upper-snake.md @@ -0,0 +1,48 @@ +--- +description: Enum-like string values are UPPER_SNAKE everywhere this repo owns the format, JSON included; external standards are converted at the boundary with explicit switch functions +--- + +Decided 2026-10-19 (all repos). An enum-like string value, a member of a +string-literal union, is `UPPER_SNAKE` wherever this repo owns the format: +TypeScript unions, `profiles.json`, the runtime outcome file, and the +`anchors.json` the agent writes per `prompt.md`. explicit-types +`upper-snake-string-unions` enforces it and stays on. + +A value an outside party defines (a CLI flag, an environment variable name, +an HTTP API field, a human-readable message) is converted at the boundary by +an explicit `switch` function, never by renaming our own values to match it: + +```ts +export interface ProfileIdentity { + type: "CLAUDE" | "CODEX" | "OPENCODE"; + model: string; +} + +// The agent name the skills installer knows each profile type by. +function skillsAgent(type: RuntimeProfile["type"]): string { + switch (type) { + case "CLAUDE": + return "claude-code"; + case "CODEX": + return "codex"; + case "OPENCODE": + return "opencode"; + } +} +``` + +In this repo: + +- Profile `type`: `CLAUDE`, `CODEX`, `OPENCODE` (also in `profiles.json`). + `skillsAgent()` (run phase) and `tokenVariable()` (profiles) map them to the + installer's agent name and the credential environment variable; + `defaultModel()` (credentials) picks a model. +- `FailureReason`: `SIGN_IN_FAILED`, `USAGE_EXHAUSTED`, `FAILED`; `toMessage()` + gives the words that logs and posted comments show. +- Anchor `side`: `NEW` or `OLD` (`prompt.md` tells the agent so); the Gitea + review call turns it into `new_position` / `old_position`. + +A lookup object keyed by enum values would need `UPPER_SNAKE` keys, which +explicit-types `no-upper-snake-object-keys` bans, so use a `switch`. TypeScript +checks such a switch for exhaustiveness through the declared return type; no +`default:` branch ([[lint-config]]). diff --git a/.agents/memory/lint-config.md b/.agents/memory/lint-config.md index c546420..751ed86 100644 --- a/.agents/memory/lint-config.md +++ b/.agents/memory/lint-config.md @@ -47,5 +47,37 @@ rules: { overrides: projectOverrides, ``` +## Rule decisions (2026-10-19, all repos) + +- `typescript/promise-function-async` is on with `require-await` and + `typescript/return-await`. A function that returns a promise is `async` + and awaits it: `async f() { const x = await g(); return x; }`. Test + doubles do the same (`const resolved = await Promise.resolve(v); return + resolved;`), and `expect(promise).rejects` takes the promise itself. + `src/processes/child.ts` is exempt in `projectOverrides`: `exitCode` must + return the very promise it marks handled, or a failed spawn would surface + as an unhandled rejection before callers await it. +- `init-declarations` is on: an uninitialized `let` becomes + `let x: T | null = null;` (`unknown` already includes `null`); a `try` that + assigns and a `catch` that exits still narrow it. +- `no-use-before-define` is on with only `{ functions: false }`. +- `no-await-in-loop` is off (sequential awaits are intentional) and + `unicorn/no-array-reduce` is on: sequential steps are plain `for` loops, + never `reduce` chains or recursion. +- `consistent-return`, `typescript/switch-exhaustiveness-check`, and + `default-case` are off: TypeScript's checks cover them, and no `default:` + branch is added to an exhaustive switch. +- `unknown` is allowed; `any` is banned and the `typescript/no-unsafe-*` + rules are on. Untrusted data is `unknown` plus a type guard. +- `unicorn/no-array-for-each` (use `for...of`), + `unicorn/prefer-structured-clone`, and `typescript/prefer-nullish-coalescing` + (defaults) stay on. +- explicit-types `upper-snake-string-unions` is on + ([[enum-values-upper-snake]]); `unicorn/custom-error-definition` is on, so a + custom error has a constructor that sets `this.name`. +- `src/post/` and `src/profiles/` (code and tests) are exempt from + `project/prefer-ascii` in `projectOverrides`: posted Gitea text shows emoji + verdict marks and a middle dot. + A new category hit is fixed in the code; an off or exception is added only for a real collision or churn, with its reason. Rules: [[project-lint-plugin]]. diff --git a/.agents/memory/project-lint-plugin.md b/.agents/memory/project-lint-plugin.md index 27df2fd..008f139 100644 --- a/.agents/memory/project-lint-plugin.md +++ b/.agents/memory/project-lint-plugin.md @@ -11,12 +11,19 @@ Oxlint plugin with `meta.name` `"project"`, loaded through `jsPlugins` in plugin configs: Deno parity rules, extras, per-rule options, and opt-outs, built-in Oxlint ids included (`curly`, `id-match`, `import/no-cycle`, ...). - **Custom rules**, enabled as `project/`. Every repo has - `no-disable-comments` and `no-asset-imports`; bot-agents has no others. + `no-disable-comments` and `no-asset-imports`. Since 2026-10-19 every repo + also has `prefer-ascii`, `no-nan` (a "no number" value is `null`, never + `NaN`), and the hugoalh `recommended` ports from + eslint-plugin-import-structure's own lint: `no-duplicate-typeofs`, + `no-empty-yield`, `no-unsafe-number`, `no-useless-continue`, + `no-duplicate-interfaces`, `no-duplicate-types`, `no-useless-type`, and + `no-modifier-private`. `no-duplicate-awaits` and `no-duplicate-voids` are + covered by `unicorn/no-unnecessary-await` and + `typescript/no-meaningless-void-operator`, and `no-enum` by explicit-types. -`scripts/` stays flat; don't add `scripts/rules/`. If the plugin ever needs -more files, they sit flat beside it and share the prefix -(`project-lint-modules.ts`, ...); `scripts/project-lint.ts` keeps the plugin -object and the rule list. +`scripts/` stays flat; don't add `scripts/rules/`. All rules live in the one +`scripts/project-lint.ts` (no file splitting), next to the plugin object and +the rule list. ```ts import type { DummyRuleMap } from "vite-plus/lint";