@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 +15 -0
- package/README.md +216 -31
- package/copilot/agents/tsfpp-annotate.agent.md +99 -119
- package/copilot/agents/tsfpp-audit.agent.md +38 -17
- package/copilot/agents/tsfpp-guarded-coding.agent.md +22 -0
- package/copilot/prompts/trunk-changelog.prompt.md +160 -0
- package/copilot/skills/annotation-standard/SKILL.md +196 -0
- package/init.mjs +2 -0
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
152
|
+
copilot-instructions.md ← always-on workspace context
|
|
40
153
|
instructions/
|
|
41
|
-
tsfpp-base.instructions.md
|
|
42
|
-
tsfpp-prelude.instructions.md
|
|
43
|
-
tsfpp-
|
|
44
|
-
tsfpp-
|
|
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
|
|
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-
|
|
67
|
-
| `tsfpp-
|
|
68
|
-
| `tsfpp-
|
|
69
|
-
| `tsfpp-
|
|
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
|
|
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-
|
|
77
|
-
→ tsfpp-
|
|
78
|
-
→ tsfpp-
|
|
79
|
-
→ tsfpp-
|
|
80
|
-
→ tsfpp-
|
|
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
|
-
|
|
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.
|
|
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
|
|
93
|
-
| `tsfpp-
|
|
94
|
-
| `tsfpp-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
49
|
+
### 1. Module header (§1)
|
|
41
50
|
|
|
42
|
-
|
|
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
|
-
* <
|
|
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
|
-
*
|
|
55
|
-
*
|
|
57
|
+
* <One-paragraph description: what this module provides, not how it works.>
|
|
58
|
+
* <Key design constraints a consumer needs to know.>
|
|
56
59
|
*
|
|
57
|
-
* @
|
|
58
|
-
* const result = mkUserId('abc-123')
|
|
59
|
-
* // => some({ _tag: 'UserId', value: 'abc-123' })
|
|
60
|
+
* @packageDocumentation
|
|
60
61
|
*/
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
*
|
|
70
|
+
* <One-sentence purpose in imperative mood.>
|
|
76
71
|
*
|
|
77
|
-
* <
|
|
72
|
+
* <Why: invariants, constraints, domain rules, rejected alternatives,
|
|
73
|
+
* accepted limitations — anything the reader cannot derive from the code.>
|
|
78
74
|
*
|
|
79
|
-
* @
|
|
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
|
-
|
|
84
|
-
- `@param` and `@returns` required on every exported function
|
|
85
|
-
- `@law` required on every combinator
|
|
86
|
-
- `@example` required on smart constructors and non-obvious combinators
|
|
87
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
107
|
+
### 4. Code markers (§4)
|
|
113
108
|
|
|
114
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
122
|
+
When a forbidden construct is present and intentional:
|
|
129
123
|
|
|
130
|
-
Format:
|
|
131
124
|
```ts
|
|
132
|
-
//
|
|
125
|
+
// DEVIATION(N.M): <reason the violation could not be avoided — not a description of the violation>
|
|
133
126
|
```
|
|
134
127
|
|
|
135
|
-
|
|
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
|
-
|
|
136
|
+
|
|
137
|
+
For each file in scope, list:
|
|
166
138
|
- Exported symbols missing JSDoc
|
|
167
|
-
-
|
|
168
|
-
- `
|
|
169
|
-
-
|
|
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
|
|
145
|
+
Report the full inventory then proceed immediately — do not ask for confirmation.
|
|
172
146
|
|
|
173
|
-
**
|
|
174
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
182
|
-
4.
|
|
183
|
-
5.
|
|
184
|
-
6.
|
|
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
|
-
-
|
|
166
|
+
- Inline comments added
|
|
190
167
|
- DEVIATION comments added
|
|
191
|
-
- eslint-disable
|
|
168
|
+
- `eslint-disable` lines paired
|
|
192
169
|
- Markers fixed
|
|
193
|
-
- Placeholders
|
|
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`
|
|
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
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
**
|
|
273
|
-
- [ ] Every
|
|
274
|
-
- [ ]
|
|
275
|
-
- [ ] `@
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
- [ ]
|
|
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
|
-
- [ ]
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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'],
|