@tsfpp/agents 1.3.5 → 1.4.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/CHANGELOG.md CHANGED
@@ -10,6 +10,21 @@ Versioning follows [Semantic Versioning](https://semver.org/).
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [1.4.0] - 2026-05-18
14
+
15
+ ### Added
16
+
17
+ - Added `copilot/prompts/trunk-changelog.prompt.md` to support trunk-based changelog authoring.
18
+ - Added `copilot/skills/annotation-standard/SKILL.md` to standardize annotation workflow and conventions.
19
+
20
+ ### Changed
21
+
22
+ - Updated `README.md` with rationale updates and new LLM assets/flowchart documentation.
23
+ - Updated `init.mjs` to include deployment wiring for the new trunk changelog prompt and related README/documentation flow.
24
+ - Updated `copilot/agents/tsfpp-annotate.agent.md` to align with the annotation standard.
25
+ - Updated `copilot/agents/tsfpp-audit.agent.md` to include annotation-standard-aware checks.
26
+ - Updated `copilot/agents/tsfpp-guarded-coding.agent.md` to route annotation work through the standard.
27
+
13
28
  ## [1.3.5] - 2026-05-18
14
29
 
15
30
  ### Changed
package/README.md CHANGED
@@ -1,12 +1,103 @@
1
1
  # @tsfpp/agents
2
2
 
3
- GitHub Copilot agents, scoped instruction files, and Claude Code configuration for [TSF++](https://github.com/tsfpp/standard) projects.
3
+ GitHub Copilot agents, scoped instruction files, skills, and Claude Code configuration for [TSF++](https://github.com/tsfpp/standard) projects.
4
4
 
5
- Installs a set of pre-built AI tooling into your project's `.github/` directory. Once installed, the agents are available in VS Code Copilot chat and the instruction files are injected automatically as context whenever you open a matching file.
5
+ Installs a pre-built AI tooling layer into your project's `.github/` directory. Once installed, agents are available in VS Code Copilot chat, instruction files are injected automatically whenever a matching file is open, and skills are loaded on demand based on semantic relevance.
6
+
7
+ ---
8
+
9
+ ## Why this works
10
+
11
+ TSF++ enforces a small, strict set of constraints: sum types with exhaustive dispatch, total functions via `Option` and `Result`, immutable records, no hidden effects, pipelines over imperative loops. These constraints exist because algebraic code is easier for a human to reason about locally — you can understand a function from its type alone, without tracing mutations or hidden control flow.
12
+
13
+ The same properties that help humans happen to help language models significantly more. A codebase that never throws, never mutates, and always makes failure explicit in the type signature gives a model far less surface to hallucinate over. Exhaustive `switch` with `absurd` means the model cannot forget a variant. `Result<T, E>` means error paths are always visible in the return type. `pipe` makes data flow linear and readable in one pass.
14
+
15
+ In practice: the agents generate fewer violations, the audit agent catches the ones that slip through, and the refactor agent can fix them mechanically because the fix is always the same shape. The standard makes the code predictable for humans — and predictable code is cheap for a model to generate correctly.
16
+
17
+ ---
18
+
19
+ ## Quickstart — greenfield project
20
+
21
+ The fastest way to create a fully configured TSF++ project is the bootstrap script. It handles everything in one command:
22
+
23
+ ```sh
24
+ bash <(curl -fsSL https://raw.githubusercontent.com/tsfpp/agents/main/bootstrap/tsfpp-bootstrap.sh) my-project
25
+ cd my-project
26
+ ```
27
+
28
+ Or, if you already have the script locally:
29
+
30
+ ```sh
31
+ bash tsfpp-bootstrap.sh my-project
32
+ cd my-project
33
+ ```
34
+
35
+ Omit the project name to bootstrap in the current directory.
36
+
37
+ ### What the bootstrap script does
38
+
39
+ | Step | What happens |
40
+ |------|-------------|
41
+ | 1 | `git init -b main` (skipped if a repo already exists) |
42
+ | 2 | `pnpm init` |
43
+ | 3 | Installs the full TSF++ ecosystem: `typescript`, `@tsfpp/standard`, `@tsfpp/tsconfig`, `@tsfpp/eslint-config`, `@tsfpp/prelude`, `@tsfpp/boundary`, `@tsfpp/agents` |
44
+ | 4 | Writes `tsconfig.json` extending `@tsfpp/tsconfig/app` |
45
+ | 5 | Writes `eslint.config.js` with the base TSF++ ESLint config |
46
+ | 6 | Patches `package.json` with `type: module` and `typecheck`, `lint`, `check` scripts |
47
+ | 7 | Creates `src/index.ts` with a getting-started comment |
48
+ | 8 | Runs `node node_modules/@tsfpp/agents/init.mjs` — installs all agents, instructions, skills, and prompts |
49
+ | 9 | Writes `.gitignore` (skipped if one already exists) |
50
+ | 10 | Runs `pnpm exec husky install` to activate pre-commit hooks |
51
+
52
+ ### After bootstrapping
53
+
54
+ The bootstrap script does not install `@tsfpp/workflow` (commit linting, release automation) or configure a git remote — those steps are intentionally separate and optional.
55
+
56
+ **Add workflow tooling** (recommended for real projects):
57
+
58
+ ```sh
59
+ pnpm add -D @tsfpp/workflow @commitlint/cli @commitlint/config-conventional husky
60
+ node node_modules/@tsfpp/workflow/init.mjs
61
+ ```
62
+
63
+ This installs:
64
+ - `commitlint` with the Conventional Commits config
65
+ - Husky hooks: `commit-msg` (lint) and `pre-commit` (typecheck + lint)
66
+ - Release Please GitHub Actions workflow for automated semver releases and changelog generation
67
+
68
+ **Push to GitHub** — use the `/trunk-init-repo` prompt in Copilot chat:
69
+
70
+ ```
71
+ /trunk-init-repo
72
+ ```
73
+
74
+ This handles the initial conventional commit, adds the remote, pushes to `main`, and ensures Husky hooks are active after the git init.
75
+
76
+ ### Complete sequence
77
+
78
+ ```sh
79
+ # 1. Bootstrap
80
+ bash tsfpp-bootstrap.sh my-project
81
+ cd my-project
82
+
83
+ # 2. Open in VS Code
84
+ code .
85
+
86
+ # 3. Add workflow tooling (optional but recommended)
87
+ pnpm add -D @tsfpp/workflow @commitlint/cli @commitlint/config-conventional husky
88
+ node node_modules/@tsfpp/workflow/init.mjs
89
+
90
+ # 4. Push to GitHub — in Copilot chat:
91
+ # /trunk-init-repo
92
+ ```
93
+
94
+ From there, the full agent workflow is available immediately.
95
+
96
+ ---
6
97
 
7
98
  ## Prerequisites
8
99
 
9
- All `@tsfpp/*` packages must be installed in the consumer project. The agents reference their documentation from `node_modules/@tsfpp/*/` at runtime — this is what gives them stable, version-locked access to the standard and API surface without bundling duplicate content.
100
+ All `@tsfpp/*` packages must be installed in the consumer project. The agents reference their documentation from `node_modules/@tsfpp/*/` at runtime — this gives them stable, version-locked access to the standard and API surface without bundling duplicate content.
10
101
 
11
102
  ```sh
12
103
  pnpm add -D \
@@ -17,37 +108,64 @@ pnpm add -D \
17
108
  @tsfpp/tsconfig
18
109
  ```
19
110
 
111
+ ---
112
+
20
113
  ## Installation
21
114
 
22
115
  ```sh
23
116
  pnpm add -D @tsfpp/agents
24
- pnpm dlx @tsfpp/agents
117
+ node node_modules/@tsfpp/agents/init.mjs
25
118
  ```
26
119
 
27
120
  The `init` script copies all files into your project and prompts before overwriting anything that already exists. Commit the generated files — they are workspace configuration, not build artefacts.
28
121
 
29
- To re-run without reinstalling:
122
+ ### Non-interactive mode
123
+
124
+ ```sh
125
+ node node_modules/@tsfpp/agents/init.mjs --yes
126
+ ```
127
+
128
+ Copies all package-managed files without prompting. Skips `eslint.config.js` and `tsconfig.json` — those are workspace-owned and never touched automatically. Use this in `postinstall` scripts:
129
+
130
+ ```json
131
+ {
132
+ "scripts": {
133
+ "postinstall": "node node_modules/@tsfpp/agents/init.mjs --yes"
134
+ }
135
+ }
136
+ ```
137
+
138
+ This ensures agents and instructions stay up to date with the installed package version on every `pnpm install`.
139
+
140
+ ### Re-run without reinstalling
30
141
 
31
142
  ```sh
32
143
  node node_modules/@tsfpp/agents/init.mjs
33
144
  ```
34
145
 
146
+ ---
147
+
35
148
  ## What gets installed
36
149
 
37
150
  ```
38
151
  .github/
39
- copilot-instructions.md ← always-on workspace context
152
+ copilot-instructions.md ← always-on workspace context
40
153
  instructions/
41
- tsfpp-base.instructions.md ← applyTo: **/*.ts
42
- tsfpp-prelude.instructions.md ← applyTo: **/*.ts
43
- tsfpp-react.instructions.md ← applyTo: **/*.tsx
44
- tsfpp-api.instructions.md ← applyTo: routes, handlers, api dirs
154
+ tsfpp-base.instructions.md ← applyTo: **/*.ts
155
+ tsfpp-prelude.instructions.md ← applyTo: **/*.ts
156
+ tsfpp-api.instructions.md ← applyTo: routes, handlers, api dirs
157
+ tsfpp-react.instructions.md ← applyTo: **/*.tsx
158
+ tsfpp-testing.instructions.md ← applyTo: **/*.{test,spec}.{ts,tsx}
159
+ trunk.instructions.md ← applyTo: git workflow contexts
45
160
  agents/
161
+ tsfpp-tdd.agent.md
162
+ tsfpp-backfill-tests.agent.md
46
163
  tsfpp-guarded-coding.agent.md
47
164
  tsfpp-audit.agent.md
48
165
  tsfpp-refactor-engineer.agent.md
49
166
  tsfpp-annotate.agent.md
50
167
  prompts/
168
+ trunk-init-repo.prompt.md
51
169
  tsfpp-new-module.prompt.md
52
170
  tsfpp-boundary-review.prompt.md
53
171
  skills/
@@ -55,53 +173,116 @@ node node_modules/@tsfpp/agents/init.mjs
55
173
  prelude-api/SKILL.md
56
174
  boundary-api/SKILL.md
57
175
  react-coding-standard/SKILL.md
176
+ test-standard/SKILL.md
177
+ annotation-standard/SKILL.md
58
178
  .claude/
59
- CLAUDE.md ← Claude Code project context
179
+ CLAUDE.md ← Claude Code project context
60
180
  ```
61
181
 
182
+ ---
183
+
62
184
  ## Agents
63
185
 
64
186
  | Agent | Purpose |
65
187
  |-------|---------|
66
- | `tsfpp-guarded-coding` | Writes TSF++-compliant TypeScript. Specify a layer at session start: `core` · `api` · `dal` · `react` · `cli`. Runs `tsc --noEmit` automatically after every file edit via a `PostToolUse` hook. |
67
- | `tsfpp-audit` | Audits a target path, package, or layer for TSF++ violations. Creates a structured markdown report in `docs/audits/` with per-slice checkboxes and a deviation register. |
68
- | `tsfpp-refactor-engineer` | Reads an audit report and fixes violations slice by slice. Updates the report as it goes. Includes per-rule fix strategies so it never resolves a violation by weakening a type. |
69
- | `tsfpp-annotate` | Adds missing JSDoc blocks, `DEVIATION(N.M)` comments, paired `eslint-disable` annotations, and structured code markers (`TODO`, `FIXME`, `HACK`, `NOTE`, `OPTIMIZE`, `BUG`, `XXX`). Never touches runtime code. |
188
+ | `tsfpp-tdd` | The mandatory first step for new functionality. Writes a failing test suite that specifies the behaviour before any implementation exists. Verifies every test is red for assertion reasons, then hands off to `tsfpp-guarded-coding`. |
189
+ | `tsfpp-backfill-tests` | Writes tests for existing code that has no coverage. Reads the implementation, derives the implicit contract, writes passing tests, and reports uncovered edge cases and implementation gaps. |
190
+ | `tsfpp-guarded-coding` | Writes TSF++-compliant TypeScript. Infers the active layer from context (`core` · `api` · `dal` · `react` · `cli`); applies layer-specific constraints automatically. Refuses to start without failing tests in place. Runs `tsc --noEmit` after every file edit via a `PostToolUse` hook. |
191
+ | `tsfpp-audit` | Audits a target path, package, or layer for TSF++ violations. Supports focus areas: `types` · `boundary` · `complexity` · `loc` · `annotations` · `security` · `react` · `data` · `prelude` · `test` · `all`. Creates a structured markdown report in `docs/audits/` with per-slice checkboxes and a deviation register. |
192
+ | `tsfpp-refactor-engineer` | Reads an audit report and fixes violations slice by slice. Updates the report as it goes. Never resolves a violation by weakening a type. |
193
+ | `tsfpp-annotate` | Adds missing JSDoc blocks, module headers, inline comments (invariants, rejected alternatives, external contracts), `DEVIATION(N.M)` comments, paired `eslint-disable` annotations, and structured code markers (`TODO`, `FIXME`, `HACK`, `NOTE`, `OPTIMIZE`, `BUG`, `XXX`). Never touches runtime code. Uses the `/annotation-standard` skill. |
194
+
195
+ ---
70
196
 
71
197
  ## Workflow
72
198
 
73
- The agents are designed to hand off to each other. After each agent completes, VS Code presents a handoff button to transition to the next step:
199
+ The agents hand off to each other. Each handoff is opt-in the developer reviews and approves each transition via the handoff buttons in Copilot chat.
200
+
201
+ ### New functionality (TDD first)
74
202
 
75
203
  ```
76
- tsfpp-guarded-coding
77
- → tsfpp-audit (verify what was written)
78
- → tsfpp-refactor-engineer (fix violations)
79
- → tsfpp-annotate (document the result)
80
- → tsfpp-audit (verify annotation coverage)
204
+ tsfpp-tdd write failing tests
205
+ → tsfpp-guarded-coding implement against the tests
206
+ → tsfpp-audit verify compliance
207
+ → tsfpp-refactor-engineer fix any violations
208
+ → tsfpp-annotate document the result
81
209
  ```
82
210
 
83
- Handoffs are opt-in the developer reviews and approves each transition.
211
+ ### Existing code without tests
212
+
213
+ ```
214
+ tsfpp-backfill-tests write passing tests from the existing contract
215
+ → tsfpp-audit verify test compliance + coverage
216
+ → tsfpp-refactor-engineer fix violations in tests or implementation
217
+ ```
218
+
219
+ ### Compliance audit only
220
+
221
+ ```
222
+ tsfpp-audit audit target with chosen focus
223
+ → tsfpp-refactor-engineer fix violations
224
+ → tsfpp-annotate document deviations and markers
225
+ → tsfpp-audit re-audit to verify fixes
226
+ ```
227
+
228
+ ---
84
229
 
85
230
  ## Instruction files
86
231
 
87
- Instruction files are injected automatically by VS Code Copilot whenever a matching file is open. They require no explicit invocation.
232
+ Instruction files are injected automatically by VS Code Copilot whenever a matching file is open. No explicit invocation required.
88
233
 
89
234
  | File | Active for | Content |
90
235
  |------|-----------|---------|
91
236
  | `tsfpp-base` | `**/*.ts` | Forbidden constructs, required patterns, size limits, ADT idioms, marker format |
92
- | `tsfpp-prelude` | `**/*.ts` | Full `@tsfpp/prelude` API surface: `Option`, `Result`, combinators, `pipe`, `absurd`, `Brand` |
93
- | `tsfpp-react` | `**/*.tsx` | Component shape, discriminated union state, TanStack Query, `useEffect` policy, memoisation |
94
- | `tsfpp-api` | Routes, handlers, API dirs | Handler shape, `@tsfpp/boundary` imports, Zod validation, status codes, security baseline |
237
+ | `tsfpp-prelude` | `**/*.ts` | Full `@tsfpp/prelude` API: `Option`, `Result`, `ReadonlyMap`, `ReadonlySet`, `List`, combinators, record decoding |
238
+ | `tsfpp-api` | Routes, handlers, api dirs | Handler shape, `@tsfpp/boundary` imports, Zod validation, middleware composition, status codes, security baseline |
239
+ | `tsfpp-react` | `**/*.tsx` | Component shape, discriminated union state, state elimination ladder, `useEffect` policy, TanStack Query, RHF+Zod, cva/cn styling |
240
+ | `tsfpp-testing` | `**/*.{test,spec}.{ts,tsx}` | AAA structure, factory usage, fast-check property tests, MSW, RTL query hierarchy, forbidden patterns |
241
+ | `trunk` | Git workflow contexts | Conventional Commits, branch naming, PR discipline, trunk-based development rules |
242
+
243
+ The workspace-level `copilot-instructions.md` is always active regardless of which file is open. It establishes the TSF++ axioms, the prelude-first principle, and the absolute non-negotiables.
244
+
245
+ ---
246
+
247
+ ## Skills
248
+
249
+ Skills are loaded automatically by Copilot based on semantic match with what is being discussed. Unlike instruction files, skills are not file-scoped — they are injected into the context when the model determines they are relevant.
95
250
 
96
- The workspace-level `copilot-instructions.md` is always active regardless of which file is open. It establishes the language rule (US technical English in all files), points to the agents, and lists the absolute non-negotiables.
251
+ | Skill | Loaded when |
252
+ |-------|------------|
253
+ | `coding-standard` | Writing or reviewing TypeScript; checking rules; auditing compliance |
254
+ | `prelude-api` | Importing from or discussing `@tsfpp/prelude`; choosing between combinators |
255
+ | `boundary-api` | Writing handlers; discussing HTTP error mapping, pagination, or middleware |
256
+ | `react-coding-standard` | Writing React components, hooks, forms, or stores |
257
+ | `test-standard` | Writing or reviewing test files; discussing coverage or test structure |
258
+ | `annotation-standard` | Writing or reviewing any comment, JSDoc block, module header, code marker, or DEVIATION; discussing what to annotate and why |
259
+
260
+ Skills are distilled from the full standard documents — concise enough to fit in the model's context without crowding out code.
261
+
262
+ ---
263
+
264
+ ## Prompts
265
+
266
+ Reusable slash commands available in Copilot chat.
267
+
268
+ | Prompt | Purpose |
269
+ |--------|---------|
270
+ | `/trunk-init-repo` | Initialises a git repository with `main` branch, `.gitignore`, an initial conventional commit, optional remote, and Husky hook activation. Run once after bootstrapping. |
271
+ | `/trunk-changelog` | Inspects the current working tree diff, derives the correct conventional commit message, and appends a matching entry to the `## [Unreleased]` section of `CHANGELOG.md`. |
272
+ | `/tsfpp-new-module` | Scaffolds a new TSF++ module with the correct file structure, barrel export, and JSDoc stubs. |
273
+ | `/tsfpp-boundary-review` | Reviews an HTTP handler against the full `@tsfpp/boundary` contract. |
274
+
275
+ ---
97
276
 
98
277
  ## How agents reference the standard
99
278
 
100
- Each agent reads the TSF++ standards directly from `node_modules/@tsfpp/standard/` and the prelude API from `node_modules/@tsfpp/prelude/`. This means:
279
+ Each agent reads TSF++ standards directly from `node_modules/@tsfpp/standard/spec/` and the prelude API from `node_modules/@tsfpp/prelude/` at runtime. This means:
280
+
281
+ - The agent always enforces the version of the standard your project has installed.
282
+ - Upgrading `@tsfpp/standard` automatically updates what the agents enforce — no changes to agent files required.
283
+ - The agent files contain workflow logic, checklist structure, and layer-specific patterns — not rule content. Rule content lives in the standard packages.
101
284
 
102
- - The agent always uses the version of the standard your project has installed.
103
- - Upgrading `@tsfpp/standard` automatically updates what the agents enforce — no changes to the agent files required.
104
- - The agent files themselves contain workflow logic, not rule content.
285
+ ---
105
286
 
106
287
  ## Further reading
107
288
 
@@ -111,6 +292,8 @@ Each agent reads the TSF++ standards directly from `node_modules/@tsfpp/standard
111
292
  - [`@tsfpp/eslint-config`](https://github.com/tsfpp/eslint-config) — ESLint flat config
112
293
  - [`@tsfpp/tsconfig`](https://github.com/tsfpp/tsconfig) — TypeScript compiler presets
113
294
 
295
+ ---
296
+
114
297
  ## Release process
115
298
 
116
299
  Releases are automated with Release Please.
@@ -119,6 +302,8 @@ Releases are automated with Release Please.
119
302
  2. Release Please opens or updates a release PR on each merge to `main`.
120
303
  3. Merging the release PR publishes to npm, creates a GitHub release, and updates `CHANGELOG.md`.
121
304
 
305
+ ---
306
+
122
307
  ## License
123
308
 
124
309
  MIT
@@ -1,27 +1,36 @@
1
1
  ---
2
- description: Adds missing JSDoc, DEVIATION comments, eslint-disable annotations, and code markers to target files. Never changes runtime behaviour.
2
+ description: >
3
+ Adds missing JSDoc blocks, module headers, inline comments, DEVIATION markers,
4
+ eslint-disable annotations, and code markers to target files. Never changes
5
+ runtime behaviour.
3
6
  name: tsfpp-annotate
4
- argument-hint: "Path(s) to annotate, e.g. src/domain/track.ts or src/domain/"
7
+ argument-hint: "Path(s) to annotate, e.g. src/domain/user.ts or src/domain/"
5
8
  tools:
6
9
  - edit/editFiles
7
10
  - read
8
- - search/codebase
9
- - search/fileSearch
10
- - search/textSearch
11
+ - search
11
12
  - todo
12
13
  - vscode/askQuestions
13
14
  handoffs:
14
15
  - label: Re-audit annotations
15
16
  agent: tsfpp-audit
16
- prompt: "Re-audit the annotated files with focus: annotations. Verify JSDoc coverage and marker format."
17
+ prompt: "Re-audit the annotated files for TSF++ compliance. Focus: annotations. Proceed immediately."
17
18
  send: false
18
19
  ---
19
20
 
20
21
  # TSF++ Annotate
21
22
 
22
- You are a code annotation specialist. Your job is to make code self-documenting and auditable by adding missing JSDoc blocks, DEVIATION markers, eslint-disable comments, and structured code notices — without changing any runtime behaviour.
23
+ You are a code annotation specialist. Your job is to make code self-documenting
24
+ and auditable by adding missing JSDoc blocks, module headers, inline comments,
25
+ DEVIATION markers, eslint-disable pairings, and structured code markers —
26
+ without changing any runtime behaviour.
23
27
 
24
- The canonical standard is at `node_modules/@tsfpp/standard/spec/CODING_STANDARD.md` (Rules 7–8).
28
+ ## Before starting
29
+
30
+ Load and apply the `/annotation-standard` skill. Every annotation you write
31
+ must conform to all rules in that skill.
32
+
33
+ Full standard: `node_modules/@tsfpp/standard/spec/ANNOTATION_CODING_STANDARD.md`
25
34
 
26
35
  > Touch only comments and documentation. **Never alter types, logic, or imports.**
27
36
 
@@ -29,7 +38,7 @@ The canonical standard is at `node_modules/@tsfpp/standard/spec/CODING_STANDARD.
29
38
 
30
39
  ## Session start
31
40
 
32
- If the user has not specified a target, ask:
41
+ If the user has not specified a target, ask once:
33
42
 
34
43
  > Which file(s) or directory should I annotate?
35
44
 
@@ -37,167 +46,138 @@ If the user has not specified a target, ask:
37
46
 
38
47
  ## What to annotate
39
48
 
40
- ### 1. JSDoc on exported symbols (Rule 7.x — MUST)
49
+ ### 1. Module header (§1)
41
50
 
42
- Every exported `function`, `const`, `type`, and `interface` requires a JSDoc block.
51
+ Required on every `.ts` file that exports public API. If absent, add it before the first import.
43
52
 
44
- **Function / const (callable):**
45
53
  ```ts
46
54
  /**
47
- * <One-sentence purpose in imperative mood.>
48
- *
49
- * <Optional: preconditions or invariants the caller must satisfy.>
50
- *
51
- * @param name - <description>
52
- * @returns <description of return value and its semantics>
55
+ * @module <module-name>
53
56
  *
54
- * @law identity - mapO(identity)(x) x
55
- * @law associativity - ...
57
+ * <One-paragraph description: what this module provides, not how it works.>
58
+ * <Key design constraints a consumer needs to know.>
56
59
  *
57
- * @example
58
- * const result = mkUserId('abc-123')
59
- * // => some({ _tag: 'UserId', value: 'abc-123' })
60
+ * @packageDocumentation
60
61
  */
61
62
  ```
62
63
 
63
- **Type alias:**
64
- ```ts
65
- /**
66
- * <What this type represents in the domain.>
67
- *
68
- * Discriminated by `kind`. Variants: `'pending'` | `'resolved'` | `'rejected'`.
69
- */
70
- ```
64
+ ### 2. JSDoc on exported symbols (§2)
65
+
66
+ Every exported `function`, `const` (callable or significant), `type`, and `interface`.
71
67
 
72
- **Module-level block** (top of every `.ts` file that exports public API):
73
68
  ```ts
74
69
  /**
75
- * @module <module-name>
70
+ * <One-sentence purpose in imperative mood.>
76
71
  *
77
- * <One-paragraph description of what this module provides.>
72
+ * <Why: invariants, constraints, domain rules, rejected alternatives,
73
+ * accepted limitations — anything the reader cannot derive from the code.>
78
74
  *
79
- * @packageDocumentation
75
+ * @param name - <domain constraint, not the type>
76
+ * @returns <meaning of the return value, not its type>
77
+ *
78
+ * @law identity — mapO(x => x)(opt) ≡ opt
79
+ *
80
+ * @example
81
+ * mkUserId('usr-00123') // => some(UserId('usr-00123'))
82
+ * mkUserId('') // => none
80
83
  */
81
84
  ```
82
85
 
83
- **Rules:**
84
- - `@param` and `@returns` required on every exported function.
85
- - `@law` required on every combinator that satisfies a functor, monad, or other algebraic law.
86
- - `@example` required on smart constructors and non-obvious combinators.
87
- - Do not add `@throws` in core core does not throw. Use `@throws` only in adapter functions that bridge a throwing third-party API.
88
-
89
- ---
90
-
91
- ### 2. DEVIATION comments
92
-
93
- When a forbidden construct is present and intentional, place this on the line immediately before it:
86
+ Rules:
87
+ - `@param` and `@returns` required on every exported function
88
+ - `@law` required on every combinator with algebraic properties
89
+ - `@example` required on smart constructors and non-obvious combinators
90
+ - `@deprecated` requires a replacement and a version number
91
+ - `@throws` forbidden on functions that return `Result<T, E>`
94
92
 
95
- ```ts
96
- // DEVIATION(N.M): <one-line justification>
97
- ```
98
-
99
- **Common patterns:**
100
- ```ts
101
- // DEVIATION(1.4): Framework plugin API requires an interface — type alias not accepted
102
- interface PluginContract { ... }
93
+ ### 3. Inline comments (§3)
103
94
 
104
- // DEVIATION(1.5): Third-party lib returns any narrowed to unknown immediately below
105
- const raw: any = externalLib.getData()
106
- ```
95
+ Add inline comments only when the code contains something a reader cannot
96
+ confidently derive from the code and types alone:
107
97
 
108
- Only annotate constructs that already exist and already violate a rule. Do not add DEVIATION comments to clean code.
98
+ - **Why this approach** over the alternative the reader will naturally consider
99
+ - **Rejected alternatives** — what was considered and why it was ruled out
100
+ - **Non-obvious invariants** — preconditions the type cannot express
101
+ - **External contracts** — field names / values dictated by a third party
102
+ - **Accepted imprecision** — known limitations that are intentional
103
+ - **Performance trade-offs** — why a non-obvious implementation was chosen
109
104
 
110
- ---
105
+ Do not add inline comments that paraphrase the code. Do not add section dividers.
111
106
 
112
- ### 3. eslint-disable comments
107
+ ### 4. Code markers (§4)
113
108
 
114
- Every lint suppression must be paired with a DEVIATION comment:
109
+ Fix malformed markers (missing author, date, or ticket). Do not add new markers
110
+ to code that has no existing issues.
115
111
 
112
+ Required format:
116
113
  ```ts
117
- // DEVIATION(1.5): Legacy adapter — any narrowed to unknown on next line
118
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
- const payload: any = deserialise(raw)
114
+ // MARKER(author, YYYY-MM-DD[, TICKET]): description
120
115
  ```
121
116
 
122
- - Never add a bare `// eslint-disable` without a DEVIATION comment.
123
- - Prefer `eslint-disable-next-line` over block-level `/* eslint-disable */`.
124
- - If a suppression already exists without a DEVIATION comment, add the DEVIATION comment above it.
117
+ Author is the GitHub handle or initials of the person adding the marker —
118
+ never the AI. If unknown, use `unknown` and flag it in the summary.
125
119
 
126
- ---
120
+ ### 5. DEVIATION comments (§5)
127
121
 
128
- ### 4. Code markers
122
+ When a forbidden construct is present and intentional:
129
123
 
130
- Format:
131
124
  ```ts
132
- // <MARKER>(<author>, <YYYY-MM-DD>[, <ticket>]): <description>
125
+ // DEVIATION(N.M): <reason the violation could not be avoided — not a description of the violation>
133
126
  ```
134
127
 
135
- | Marker | When to use |
136
- |--------|-------------|
137
- | `TODO` | Work that must be done before the next release |
138
- | `FIXME` | Known bug or broken behaviour |
139
- | `HACK` | Temporary workaround — must be revisited |
140
- | `NOTE` | Important context a reader needs to understand the code |
141
- | `OPTIMIZE` | Works correctly but has a known performance concern |
142
- | `BUG` | Confirmed bug, not yet fixed |
143
- | `XXX` | Extra caution warranted — something fragile or surprising |
144
-
145
- **Examples:**
146
- ```ts
147
- // TODO(alice, 2026-05-14, PROJ-421): Replace with Result-based validation once boundary refactor lands
148
- // FIXME(bob, 2026-05-14): Returns none for empty string — should return err('empty')
149
- // HACK(carol, 2026-05-14): Forced cast — third-party type definition is wrong, fixed in v3.x
150
- // NOTE(dave, 2026-05-14): Intentional shallow copy — deep clone would be O(n²) here
151
- // XXX(eve, 2026-05-14): Called before store is hydrated — ordering is load-bearing
152
- ```
153
-
154
- **Rules:**
155
- - Author is the GitHub handle or initials of the person adding the marker — not the AI.
156
- - If the user does not supply an author, use `unknown` and flag it.
157
- - If the user does not supply a date, use today's date.
158
- - Do not add markers to code that has no existing issues. Only annotate genuinely notable constructs.
128
+ Pair every bare `eslint-disable` with a DEVIATION comment above it.
129
+ Only annotate constructs that already exist and already violate a rule.
159
130
 
160
131
  ---
161
132
 
162
133
  ## Execution workflow
163
134
 
164
135
  **Step 1 — Inventory**
165
- For each file in scope, count and list:
136
+
137
+ For each file in scope, list:
166
138
  - Exported symbols missing JSDoc
167
- - Violations present without a `// DEVIATION(N.M)` comment
168
- - `eslint-disable` lines without a paired DEVIATION comment
169
- - Existing markers with missing author, date, or ticket
139
+ - Files missing a module header
140
+ - Constructs with no `DEVIATION` comment where one is needed
141
+ - Bare `eslint-disable` lines without a paired DEVIATION comment
142
+ - Malformed markers (missing author, date, or ticket)
143
+ - Opportunities for inline comments (invariants, external contracts, rejected alternatives visible in the code)
170
144
 
171
- Report the full inventory before making any changes.
145
+ Report the full inventory then proceed immediately — do not ask for confirmation.
172
146
 
173
- **Step 2 Confirm scope**
174
- Present the inventory. Ask: "Shall I proceed with all files, or a subset?"
175
- Do not start editing until the user confirms.
147
+ > **Do not pause between files.** Work through all files without interruption.
148
+ > Only present handoff options after the summary is complete.
176
149
 
177
- **Step 3 — Annotate file by file**
178
- For each confirmed file:
179
- 1. Add missing module-level JSDoc block if absent.
150
+ **Step 2 — Annotate file by file**
151
+
152
+ For each file:
153
+ 1. Add or fix the module-level JSDoc block.
180
154
  2. Add missing JSDoc blocks to each exported symbol.
181
- 3. Add DEVIATION comments above known violations.
182
- 4. Pair bare eslint-disable lines with DEVIATION comments.
183
- 5. Fix malformed markers (fill missing author/date fields with `unknown` / today).
184
- 6. Report what was added per file.
155
+ 3. Add inline comments where the code contains non-obvious reasoning.
156
+ 4. Add DEVIATION comments above known violations.
157
+ 5. Pair bare `eslint-disable` lines with DEVIATION comments.
158
+ 6. Fix malformed markers.
159
+ 7. Report what was added per file.
160
+
161
+ **Step 3 — Summarise**
185
162
 
186
- **Step 4 — Summarise**
187
163
  Report totals:
164
+ - Module headers added
188
165
  - JSDoc blocks added
189
- - Module-level blocks added
166
+ - Inline comments added
190
167
  - DEVIATION comments added
191
- - eslint-disable comments paired
168
+ - `eslint-disable` lines paired
192
169
  - Markers fixed
193
- - Placeholders left for the user to fill in (`unknown` authors, `DEVIATION(?)`)
170
+ - Placeholders requiring author input (`unknown`, `DEVIATION(?)`)
194
171
 
195
172
  ---
196
173
 
197
174
  ## Hard rules
198
175
 
199
- - Never change types, logic, or imports — documentation only.
200
- - Never invent content for `@param` or `@returns` — derive strictly from the signature and implementation.
201
- - If a description cannot be determined, write `// TODO(unknown, <date>): Add JSDoc` as a placeholder and flag it in the summary.
202
- - If a DEVIATION is needed but the rule number is unclear, write `// DEVIATION(?): <description>` and flag it.
203
- - Never add `@throws` to a function that uses `Result` — the error is in the return type, not thrown.
176
+ - Never change types, logic, or imports — documentation only
177
+ - Never invent content for `@param` or `@returns` — derive strictly from the signature and implementation
178
+ - If a description cannot be determined, write `// TODO(unknown, <date>): Add JSDoc` and flag it
179
+ - If a DEVIATION is needed but the rule number is unclear, write `// DEVIATION(?): <description>` and flag it
180
+ - Never add `@throws` to a function that returns `Result<T, E>`
181
+ - Never add commented-out code
182
+ - Never add section dividers or decorative separators
183
+ - Never attribute comments to an AI
@@ -267,25 +267,46 @@ Checklist:
267
267
  - [ ] Test files excluded from LOC limits but flagged if > 600 lines
268
268
 
269
269
  ### `annotations`
270
- Checklist:
271
-
272
- **JSDoc (§7)**
273
- - [ ] Every exported symbol has a JSDoc comment
274
- - [ ] `@param` present for every parameter on exported functions
275
- - [ ] `@returns` present on every exported function with a non-void return
276
- - [ ] `@law` present on every combinator with algebraic laws
277
- - [ ] No JSDoc on non-exported symbols (unnecessary noise)
278
-
279
- **Code markers**
280
- - [ ] `TODO` / `FIXME` / `HACK` / `NOTE` / `OPTIMIZE` / `BUG` / `XXX` all have format: `(author, YYYY-MM-DD[, TICKET]): description`
270
+ Full reference: `node_modules/@tsfpp/standard/spec/ANNOTATION_CODING_STANDARD.md`
271
+
272
+ **Module headers (SS1)**
273
+ - [ ] Every file with public exports has a module-level JSDoc block
274
+ - [ ] Module header describes the contract, not the implementation
275
+ - [ ] `@packageDocumentation` present
276
+
277
+ **JSDoc on exported symbols (SS2)**
278
+ - [ ] Every exported function, const, type has a JSDoc block
279
+ - [ ] First sentence states purpose in imperative mood
280
+ - [ ] JSDoc body explains the **why** -- not a paraphrase of the code
281
+ - [ ] `@param` describes domain constraint, not the type
282
+ - [ ] `@returns` describes meaning of the value, not its type
283
+ - [ ] `@law` present on every combinator with algebraic properties
284
+ - [ ] `@example` present on smart constructors and non-obvious combinators
285
+ - [ ] `@deprecated` includes replacement and version number where present
286
+ - [ ] No `@throws` on functions that return `Result<T, E>`
287
+
288
+ **Inline comments (SS3)**
289
+ - [ ] No comments that paraphrase the code
290
+ - [ ] No commented-out code
291
+ - [ ] No section dividers or decorative separators
292
+ - [ ] Rejected alternatives documented where a reader would naturally question the choice
293
+ - [ ] Non-obvious invariants and external contracts documented
294
+ - [ ] Known limitations explicitly marked as intentional
295
+ - [ ] Performance trade-offs with scale thresholds documented where present
296
+
297
+ **Code markers (SS4)**
298
+ - [ ] All markers follow `// MARKER(author, YYYY-MM-DD[, TICKET]): description` exactly
281
299
  - [ ] No marker missing author or date
282
- - [ ] No stale TODO older than one release cycle without a ticket reference
283
-
284
- **Deviations**
285
- - [ ] Every rule violation has `// DEVIATION(N.M): <one-line justification>` immediately before the offending line
286
- - [ ] DEVIATION format is exact: `DEVIATION(N.M)` — not `deviation`, not `Deviation`, not `DEVIATION N.M`
300
+ - [ ] All `HACK` markers have a ticket and a revisit condition
301
+ - [ ] No `BUG` marker without a conversion plan
302
+ - [ ] No stale marker surviving more than one release cycle without a ticket
303
+
304
+ **Deviations (SS5)**
305
+ - [ ] Every rule violation has `// DEVIATION(N.M): <justification>` immediately before the offending line
306
+ - [ ] Justification explains why no alternative was feasible -- not what the violation is
307
+ - [ ] Format is exact: `DEVIATION(N.M)` -- not `deviation`, not `Deviation`, not `DEVIATION N.M`
308
+ - [ ] Every bare `eslint-disable` is paired with a DEVIATION comment above it
287
309
  - [ ] Project-wide deviations are documented in `DEVIATIONS.md`
288
-
289
310
  ### `security`
290
311
  Full reference: `node_modules/@tsfpp/standard/spec/SECURITY_CODING_STANDARD.md`
291
312
 
@@ -113,6 +113,28 @@ Implement user requests with minimal safe diffs while preserving TSF++ guarantee
113
113
 
114
114
  ---
115
115
 
116
+
117
+ ## Annotate as you go
118
+
119
+ Load and apply the `/annotation-standard` skill before writing any code.
120
+
121
+ Every exported symbol you write must have a JSDoc block. Do not defer annotation
122
+ to the annotate agent -- annotate while the reasoning is fresh.
123
+
124
+ JSDoc body rules:
125
+ - First sentence: what the function computes (imperative mood)
126
+ - Subsequent paragraphs: **why** -- invariants, constraints, rejected alternatives,
127
+ domain rules, accepted limitations. Anything the reader cannot derive from the code.
128
+ - `@param` -- domain constraint, not the type
129
+ - `@returns` -- meaning of the return value, not its type
130
+ - `@law` -- required on every combinator with algebraic properties
131
+ - `@example` -- required on smart constructors and non-obvious combinators
132
+ - Never `@throws` on a function that returns `Result<T, E>`
133
+
134
+ When adding inline comments, apply the same test: does this tell the reader
135
+ something they cannot derive from the code and types alone? If not, omit it.
136
+
137
+ ---
116
138
  ## Prelude-first
117
139
 
118
140
  Before writing any implementation, check `@tsfpp/prelude` for available symbols.
@@ -0,0 +1,160 @@
1
+ # Write changelog entry
2
+
3
+ Inspect the current working tree, derive the correct conventional commit message,
4
+ and append a matching entry to the `## [Unreleased]` section of `CHANGELOG.md`.
5
+
6
+ ---
7
+
8
+ ## Step 1 — Inspect changes
9
+
10
+ Run the following to see what has changed:
11
+
12
+ ```bash
13
+ git diff --stat HEAD
14
+ git status --short
15
+ ```
16
+
17
+ For each changed or added file, read enough of its diff to understand **what
18
+ changed and why** — not just which lines moved.
19
+
20
+ ```bash
21
+ git diff HEAD -- <file>
22
+ ```
23
+
24
+ If files are staged but not committed, use:
25
+
26
+ ```bash
27
+ git diff --cached --stat
28
+ git diff --cached -- <file>
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Step 2 — Classify changes
34
+
35
+ Map each change to a Conventional Commits type:
36
+
37
+ | Type | Use when |
38
+ |---|---|
39
+ | `feat` | New behaviour or capability visible to a consumer |
40
+ | `fix` | Corrects incorrect behaviour |
41
+ | `perf` | Improves performance without changing behaviour |
42
+ | `refactor` | Internal restructuring; no behaviour change, no bug fix |
43
+ | `test` | Adds or fixes tests; no production code change |
44
+ | `docs` | Documentation only |
45
+ | `chore` | Tooling, config, dependencies, release machinery |
46
+ | `build` | Build system or external dependency changes |
47
+ | `ci` | CI configuration changes |
48
+
49
+ **Breaking change:** any change that removes or renames a public export, changes
50
+ a function signature, or alters a type in a way that requires consumer updates.
51
+ Mark with `!` after the type (e.g. `feat!`) and add a `BREAKING CHANGE:` footer.
52
+
53
+ **Scope:** the package or module affected — e.g. `prelude`, `boundary`, `agents`,
54
+ `react`, `dal`. Omit if the change is cross-cutting.
55
+
56
+ ---
57
+
58
+ ## Step 3 — Write the commit message
59
+
60
+ Produce a conventional commit message following this format exactly:
61
+
62
+ ```
63
+ <type>(<scope>): <imperative summary in sentence case, ≤ 72 chars>
64
+
65
+ <optional body — what changed and why, not how, wrapped at 72 chars>
66
+
67
+ <optional footers>
68
+ BREAKING CHANGE: <description if applicable>
69
+ Closes #<issue> (if applicable)
70
+ ```
71
+
72
+ Rules:
73
+ - Summary is imperative mood: "add", "fix", "remove" — not "added", "fixes"
74
+ - Summary does not end with a period
75
+ - Body explains the **why**, not the **what** (the diff is the what)
76
+ - One commit per logical change; if the diff contains multiple unrelated changes,
77
+ produce one message per change and say so
78
+
79
+ ---
80
+
81
+ ## Step 4 — Update CHANGELOG.md
82
+
83
+ Find or create the `## [Unreleased]` section at the top of `CHANGELOG.md`.
84
+ If `CHANGELOG.md` does not exist, create it with this header:
85
+
86
+ ```markdown
87
+ # Changelog
88
+
89
+ All notable changes to this project will be documented in this file.
90
+ This file is maintained automatically by [release-please](https://github.com/googleapis/release-please)
91
+ and supplemented during development via the `/trunk-changelog` prompt.
92
+
93
+ <!-- do not remove this comment — release-please uses it as an anchor -->
94
+ <!-- RELEASE-PLEASE-INSERTION-POINT -->
95
+
96
+ ## [Unreleased]
97
+ ```
98
+
99
+ Append the new entry under `## [Unreleased]`, grouped by type in this order:
100
+
101
+ ```markdown
102
+ ## [Unreleased]
103
+
104
+ ### Breaking changes
105
+ - `feat!(boundary)!: remove legacy `fold` export` — consumers must migrate to `map`/`flatMap`
106
+
107
+ ### Features
108
+ - `feat(prelude): add ReadonlyMap combinators` — `intoMap`, `assoc`, `dissoc`, `lookup`, `entriesOfMap`
109
+
110
+ ### Bug fixes
111
+ - `fix(agents): init.mjs fails with ReferenceError when run with --yes`
112
+
113
+ ### Performance
114
+ - ...
115
+
116
+ ### Refactoring
117
+ - ...
118
+
119
+ ### Tests
120
+ - ...
121
+
122
+ ### Documentation
123
+ - ...
124
+
125
+ ### Chores
126
+ - ...
127
+ ```
128
+
129
+ Only include sections that have entries. Do not add empty sections.
130
+
131
+ Each entry is a single line:
132
+ - Backtick-quoted commit summary
133
+ - Em dash
134
+ - One-sentence plain-English explanation of the user-visible impact
135
+
136
+ ---
137
+
138
+ ## Step 5 — Output
139
+
140
+ Print the proposed commit message in a code block so it can be copied directly
141
+ into the terminal or used with `git commit`:
142
+
143
+ ```
144
+ git commit -m "<type>(<scope>): <summary>" \
145
+ -m "<body paragraph if needed>"
146
+ ```
147
+
148
+ If there are multiple logical changes, list each message separately and recommend
149
+ committing them individually with `git add -p` to stage per-change.
150
+
151
+ ---
152
+
153
+ ## Rules
154
+
155
+ - Never invent changes that are not visible in the diff
156
+ - Never write a changelog entry for a change that has no user-visible impact
157
+ (internal renaming, comment edits) — use `chore` or `refactor` in the commit
158
+ message but omit from `## [Unreleased]`
159
+ - Do not modify any section of `CHANGELOG.md` other than `## [Unreleased]`
160
+ - release-please owns every versioned section (`## [1.2.3]`) — never touch those
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: annotation-standard
3
+ description: >
4
+ Normative TSF++ annotation rules for all comments, JSDoc blocks, module
5
+ headers, code markers, and deviation records. Load when writing, reviewing,
6
+ or adding annotations to any TypeScript file: the "why not what" principle,
7
+ JSDoc body content (invariants, rejected alternatives, external contracts,
8
+ accepted imprecision, performance trade-offs), marker taxonomy with format,
9
+ DEVIATION pairing, and what must never be annotated.
10
+ ---
11
+
12
+ # TSF++ annotation standard
13
+
14
+ Full standard: `node_modules/@tsfpp/standard/spec/ANNOTATION_CODING_STANDARD.md`
15
+
16
+ ---
17
+
18
+ ## The single deciding question
19
+
20
+ > Does this tell the reader something they cannot confidently derive from the code and its types?
21
+
22
+ If no — do not add the comment. If yes — write it.
23
+
24
+ ---
25
+
26
+ ## What only a comment can tell you
27
+
28
+ | Category | Example |
29
+ |---|---|
30
+ | **Why this approach** over the natural alternative | Why linear scan instead of `Map` lookup |
31
+ | **Rejected alternatives** | What was considered and why it was ruled out |
32
+ | **Non-obvious invariants** | Preconditions the type cannot express |
33
+ | **Domain knowledge** | Business rules that live in the problem space |
34
+ | **External contracts** | Field names / values dictated by a third party |
35
+ | **Accepted imprecision** | Known limitations that are intentional |
36
+ | **Performance trade-offs** | Why a non-obvious implementation was chosen |
37
+ | **Temporal context** | Why a workaround exists, when to revisit it |
38
+
39
+ ---
40
+
41
+ ## JSDoc body: the why, not the what
42
+
43
+ The first sentence is the purpose. Everything after is the reasoning.
44
+
45
+ ```ts
46
+ /**
47
+ * Constructs a validated `UserId` from a raw string.
48
+ *
49
+ * Returns `None` if the input is empty. The empty case is excluded rather
50
+ * than mapped to an error because an empty ID indicates a caller bug, not
51
+ * a domain error — the type system prevents this at compile time in
52
+ * internal code; this guard exists for boundary inputs only.
53
+ *
54
+ * @param raw - The raw string to validate. Must be non-empty.
55
+ * @returns `Some(UserId)` if valid; `None` if empty.
56
+ *
57
+ * @example
58
+ * mkUserId('usr-00123') // => some(UserId('usr-00123'))
59
+ * mkUserId('') // => none
60
+ */
61
+ ```
62
+
63
+ `@param` describes the domain constraint, not the type. `@returns` describes the meaning, not the type. Both are already in the signature.
64
+
65
+ ---
66
+
67
+ ## Module header
68
+
69
+ Required on every file with public exports:
70
+
71
+ ```ts
72
+ /**
73
+ * @module user-account
74
+ *
75
+ * Domain model for user accounts. Provides the `UserAccount` sum type,
76
+ * its smart constructors, and the combinators for working with account
77
+ * state and identity.
78
+ *
79
+ * All functions are pure and total. Error cases are modelled via
80
+ * `Option<A>` (absent value) or `Result<T, E>` (fallible computation).
81
+ *
82
+ * @packageDocumentation
83
+ */
84
+ ```
85
+
86
+ First sentence: what the module provides. Second paragraph: key design constraints a consumer needs to know. Never describe the implementation.
87
+
88
+ ---
89
+
90
+ ## Inline comment patterns
91
+
92
+ ### Rejected alternative
93
+
94
+ ```ts
95
+ // NOTE(rob, 2026-05-18): Linear scan rather than `ReadonlyMap` lookup.
96
+ // The active session list is always ≤10 items per user; the allocation
97
+ // overhead of a map outweighs the O(1) lookup benefit at this scale.
98
+ const found = sessions.find(s => s.id === id)
99
+ ```
100
+
101
+ ### Non-obvious invariant
102
+
103
+ ```ts
104
+ // Invariant: `handlers` must be registered before this is called.
105
+ // The runtime guarantees registration order; do not call from a module
106
+ // initialiser that may run before the framework bootstraps.
107
+ ```
108
+
109
+ ### External contract
110
+
111
+ ```ts
112
+ // IMPORTANT: The field name `client_id` is specified by OAuth2 RFC 6749
113
+ // and must not be renamed despite the camelCase convention.
114
+ // DEVIATION(1.8): Field name required by external protocol.
115
+ ```
116
+
117
+ ### Accepted imprecision
118
+
119
+ ```ts
120
+ // NOTE(rob, 2026-05-18): Timestamp comparison has ≤1 s imprecision due
121
+ // to clock drift between service instances. Acceptable for audit logs;
122
+ // not acceptable for financial ordering.
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Code markers
128
+
129
+ Required format — no exceptions:
130
+
131
+ ```ts
132
+ // MARKER(author, YYYY-MM-DD[, TICKET]): description
133
+ ```
134
+
135
+ | Marker | Use when | Blocks merge? |
136
+ |---|---|---|
137
+ | `TODO` | Work required before next release | Soft — needs ticket |
138
+ | `FIXME` | Known bug the author is aware of | Yes |
139
+ | `HACK` | Temporary workaround with a deferred correct solution | Yes — needs ticket + revisit condition |
140
+ | `NOTE` | Context a reader needs to understand the code | No |
141
+ | `OPTIMIZE` | Correct but with a known performance concern at scale | No — needs scale threshold |
142
+ | `BUG` | Confirmed bug not yet in a ticket | Yes — convert to FIXME + ticket |
143
+ | `XXX` | Fragile or load-bearing — must not be casually changed | No — use sparingly |
144
+
145
+ ```ts
146
+ // TODO(rjansen, 2026-05-18, ARCH-44): Replace with Result-based validation
147
+ // once the boundary refactor lands in v2.0.
148
+ // HACK(rjansen, 2026-05-18, INFRA-12): Forced cast — third-party type
149
+ // definition is wrong. Fixed upstream in v4.x — remove after upgrade.
150
+ // NOTE(rjansen, 2026-05-18): Rate-limit window resets at midnight UTC,
151
+ // not relative to first request. Contractual requirement — do not change.
152
+ // XXX(rjansen, 2026-05-18): Initialisation order is load-bearing. The
153
+ // store must be hydrated before any handler is registered.
154
+ ```
155
+
156
+ Author = GitHub handle or initials. Never an AI. If unknown, use `unknown`.
157
+
158
+ ---
159
+
160
+ ## DEVIATION format
161
+
162
+ ```ts
163
+ // DEVIATION(N.M): <reason the violation could not be avoided>
164
+ ```
165
+
166
+ The justification explains why no alternative was feasible — not what the violation is.
167
+
168
+ Every `eslint-disable` must be paired:
169
+
170
+ ```ts
171
+ // DEVIATION(1.5): Legacy adapter — raw type narrowed to unknown immediately below.
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ const payload: any = deserialise(raw)
174
+ ```
175
+
176
+ The `as` in a smart constructor body:
177
+
178
+ ```ts
179
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- DEVIATION(1.6): smart-constructor body
180
+ return some(raw as UserId)
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Never annotate
186
+
187
+ | Forbidden | Example |
188
+ |---|---|
189
+ | Paraphrasing the code | `// Check if user is admin` above `if (user.role === 'admin')` |
190
+ | Restating the type | `// Returns a string` on `: string` |
191
+ | Commented-out code | `// const old = legacyParse(raw)` |
192
+ | Section dividers | `// ─────────────` with nothing meaningful |
193
+ | Stale comments | Any comment that no longer matches the code |
194
+ | AI attribution | `// Generated by Claude` |
195
+ | `@throws` on Result functions | Error is in the return type, not thrown |
196
+ | Apologetic comments | `// This is a bit hacky but...` — use `HACK` properly |
package/init.mjs CHANGED
@@ -55,6 +55,7 @@ const FILES = [
55
55
 
56
56
  // Prompts
57
57
  ['copilot/prompts/trunk-init-repo.prompt.md', '.github/prompts/trunk-init-repo.prompt.md'],
58
+ ['copilot/prompts/trunk-changelog.prompt.md', '.github/prompts/trunk-changelog.prompt.md'],
58
59
  ['copilot/prompts/tsfpp-new-module.prompt.md', '.github/prompts/tsfpp-new-module.prompt.md'],
59
60
  ['copilot/prompts/tsfpp-boundary-review.prompt.md', '.github/prompts/tsfpp-boundary-review.prompt.md'],
60
61
 
@@ -64,6 +65,7 @@ const FILES = [
64
65
  ['copilot/skills/boundary-api/SKILL.md', '.github/skills/boundary-api/SKILL.md'],
65
66
  ['copilot/skills/react-coding-standard/SKILL.md', '.github/skills/react-coding-standard/SKILL.md'],
66
67
  ['copilot/skills/test-standard/SKILL.md', '.github/skills/test-standard/SKILL.md'],
68
+ ['copilot/skills/annotation-standard/SKILL.md', '.github/skills/annotation-standard/SKILL.md'],
67
69
 
68
70
  // Claude Code
69
71
  ['claude/CLAUDE.md', '.claude/CLAUDE.md'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsfpp/agents",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "Workspace AI tooling for TSF++ projects: scoped instructions, coding agents, and reusable prompts",
5
5
  "keywords": [
6
6
  "tsfpp",