@tsfpp/agents 1.3.1 → 1.3.3

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,32 @@ Versioning follows [Semantic Versioning](https://semver.org/).
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [1.3.3] - 2026-05-17
14
+
15
+ ### Added
16
+
17
+ - Added `copilot/agents/tsfpp-backfill-tests.agent.md` for creating tests in codebases that currently have no tests.
18
+ - Added `copilot/prompts/trunk-init-repo.prompt.md` for guided trunk-based repository initialization.
19
+
20
+ ### Fixed
21
+
22
+ - Minor fixes on idempotency of `init.mjs` files.
23
+ - Minor convenience fix for `refactor-engineer`.
24
+
25
+ ### Changed
26
+
27
+ - Updated `copilot/agents/tsfpp-audit.agent.md` to enforce inverse `undefined` checks as well, preventing shortcut patterns we do not want agents to use.
28
+ - Updated `copilot/copilot-instructions.md` for the same anti-shortcut rationale.
29
+ - Updated `copilot/instructions/tsfpp-base.instructions.md` for the same anti-shortcut rationale.
30
+ - Updated `init.mjs` accordingly to include deployment/support for `copilot/agents/tsfpp-backfill-tests.agent.md`.
31
+ - Updated `bin/bootstrap.sh` to improve bootstrap workflow behavior and script robustness.
32
+
33
+ ## [1.3.2] - 2026-05-17
34
+
35
+ ### Fixed
36
+
37
+ - Fixed `init.mjs` duplicate symbol bug by removing a duplicate `writeEslintConfig` declaration.
38
+
13
39
  ## [1.3.1] - 2026-05-17
14
40
 
15
41
  ### Added
package/bin/bootstrap.sh CHANGED
@@ -24,13 +24,23 @@ if [[ -n "$PROJECT_NAME" ]]; then
24
24
  ok "Created directory: $PROJECT_NAME"
25
25
  fi
26
26
 
27
- # ── 1. pnpm init ──────────────────────────────────────────────────────────────
27
+ # ── 1. git init ───────────────────────────────────────────────────────────────
28
+
29
+ if git rev-parse --git-dir > /dev/null 2>&1; then
30
+ ok "Git repository already exists — skipping git init"
31
+ else
32
+ dim "Initialising git repository…"
33
+ git init -b main > /dev/null
34
+ ok "git init -b main"
35
+ fi
36
+
37
+ # ── 2. pnpm init ──────────────────────────────────────────────────────────────
28
38
 
29
39
  dim "Initialising package.json…"
30
40
  pnpm init --yes > /dev/null
31
41
  ok "pnpm init"
32
42
 
33
- # ── 2. Install TSF++ ecosystem ────────────────────────────────────────────────
43
+ # ── 3. Install TSF++ ecosystem ────────────────────────────────────────────────
34
44
 
35
45
  dim "Installing TSF++ packages (this may take a moment)…"
36
46
  pnpm add -D \
@@ -43,7 +53,7 @@ pnpm add -D \
43
53
  @tsfpp/agents
44
54
  ok "Installed TSF++ ecosystem"
45
55
 
46
- # ── 3. tsconfig.json ──────────────────────────────────────────────────────────
56
+ # ── 4. tsconfig.json ──────────────────────────────────────────────────────────
47
57
 
48
58
  dim "Writing tsconfig.json…"
49
59
  cat > tsconfig.json << 'EOF'
@@ -57,7 +67,7 @@ cat > tsconfig.json << 'EOF'
57
67
  EOF
58
68
  ok "tsconfig.json"
59
69
 
60
- # ── 4. eslint.config.js ───────────────────────────────────────────────────────
70
+ # ── 5. eslint.config.js ───────────────────────────────────────────────────────
61
71
 
62
72
  dim "Writing eslint.config.js…"
63
73
  cat > eslint.config.js << 'EOF'
@@ -66,7 +76,7 @@ export default tsfpp
66
76
  EOF
67
77
  ok "eslint.config.js"
68
78
 
69
- # ── 5. package.json — type + scripts ─────────────────────────────────────────
79
+ # ── 6. package.json — type + scripts ─────────────────────────────────────────
70
80
 
71
81
  dim "Patching package.json…"
72
82
  npm pkg set type="module" --silent
@@ -75,7 +85,7 @@ npm pkg set scripts.lint="eslint src" --silent
75
85
  npm pkg set scripts.check="pnpm typecheck && pnpm lint" --silent
76
86
  ok "package.json scripts"
77
87
 
78
- # ── 6. src/index.ts ───────────────────────────────────────────────────────────
88
+ # ── 7. src/index.ts ───────────────────────────────────────────────────────────
79
89
 
80
90
  dim "Creating src/index.ts…"
81
91
  mkdir -p src
@@ -87,17 +97,69 @@ cat > src/index.ts << 'EOF'
87
97
  EOF
88
98
  ok "src/index.ts"
89
99
 
90
- # ── 7. Copilot agents (.github) ───────────────────────────────────────────────
100
+ # ── 8. Copilot agents (.github) ───────────────────────────────────────────────
91
101
 
92
102
  dim "Installing Copilot agents…"
93
103
  node node_modules/@tsfpp/agents/init.mjs
94
104
  ok "Copilot agents installed"
95
105
 
96
- # ── 8. Done ───────────────────────────────────────────────────────────────────
106
+ # ── 9. .gitignore ────────────────────────────────────────────────────────────
107
+
108
+ if [[ ! -f .gitignore ]]; then
109
+ dim "Writing .gitignore…"
110
+ cat > .gitignore << 'GITIGNORE'
111
+ # Dependencies
112
+ node_modules/
113
+
114
+ # Build output
115
+ dist/
116
+ build/
117
+ out/
118
+ coverage/
119
+ .turbo/
120
+
121
+ # TypeScript
122
+ *.tsbuildinfo
123
+
124
+ # Environment
125
+ .env
126
+ .env.*
127
+ !.env.example
128
+
129
+ # OS
130
+ .DS_Store
131
+ Thumbs.db
132
+
133
+ # Editor
134
+ .vscode/*
135
+ !.vscode/extensions.json
136
+ !.vscode/settings.json
137
+ .idea/
138
+
139
+ # pnpm
140
+ .pnpm-store/
141
+
142
+ # Logs
143
+ *.log
144
+ npm-debug.log*
145
+ pnpm-debug.log*
146
+ GITIGNORE
147
+ ok ".gitignore"
148
+ else
149
+ ok ".gitignore already exists — skipping"
150
+ fi
151
+
152
+ # ── 10. Husky ─────────────────────────────────────────────────────────────────
153
+
154
+ dim "Activating Husky hooks…"
155
+ pnpm exec husky install > /dev/null 2>&1 && ok "Husky hooks activated" || ok "Husky not configured — skipping (run 'pnpm exec husky install' after adding husky to package.json)"
156
+
157
+ # ── 11. Done ──────────────────────────────────────────────────────────────────
97
158
 
98
159
  echo ""
99
160
  echo -e "${GREEN}TSF++ sandbox ready.${RESET}"
100
161
  echo ""
101
- echo " pnpm check — typecheck + lint"
102
- echo " code . — open in VS Code"
162
+ echo " pnpm check — typecheck + lint"
163
+ echo " code . — open in VS Code"
164
+ echo " /trunk-init-repo — initialise git remote and push to GitHub"
103
165
  echo ""
@@ -175,7 +175,7 @@ Cross-cutting — applies to all layers. Check for hand-rolled patterns that `@t
175
175
 
176
176
  | Anti-pattern | Violation | Should be |
177
177
  |---|---|---|
178
- | `if (x === undefined)` / `if (x === null)` | MUST | `fromNullable(x)` → `Option<T>` |
178
+ | `if (x === undefined)` / `if (x !== undefined)` / `if (x === null)` / `if (x !== null)` / `if (!x)` | MUST | `fromNullable(x)` → `Option<T>`; use `isSome` / `isNone` to branch |
179
179
  | `x ?? fallback` | MUST | `pipe(x, fromNullable, getOrElse(() => fallback))` |
180
180
  | `try/catch` outside adapter boundary | MUST | `tryCatch` / `tryCatchAsync` |
181
181
  | `.map()` on a fallible function | MUST | `traverseArray` |
@@ -191,7 +191,7 @@ Cross-cutting — applies to all layers. Check for hand-rolled patterns that `@t
191
191
 
192
192
  Checklist:
193
193
 
194
- - [ ] No `if (x === undefined/null)` — use `fromNullable`
194
+ - [ ] No nullability checks in any form — `if (x === undefined)`, `if (x !== undefined)`, `if (x === null)`, `if (x !== null)`, `if (!x)`, `x ?? y` — use `fromNullable` / `getOrElse` / `isSome`
195
195
  - [ ] No `x ?? fallback` — use `getOrElse`
196
196
  - [ ] No `try/catch` outside adapter boundaries — use `tryCatch`/`tryCatchAsync`
197
197
  - [ ] No `.map()` on fallible function — use `traverseArray`
@@ -0,0 +1,256 @@
1
+ ---
2
+ description: >
3
+ Writes tests for existing code that has no test coverage. Reads the
4
+ implementation, derives the implicit contract, writes passing tests that
5
+ specify that contract, and reports uncovered edge cases and error paths.
6
+ Use this for retroactive coverage — not for new functionality (use
7
+ tsfpp-tdd for that).
8
+ name: tsfpp-backfill-tests
9
+ argument-hint: "target=<path|module|layer>"
10
+ tools:
11
+ - edit/createFile
12
+ - edit/editFiles
13
+ - execute/runInTerminal
14
+ - execute/getTerminalOutput
15
+ - execute/testFailure
16
+ - read
17
+ - search
18
+ - todo
19
+ - vscode/askQuestions
20
+ handoffs:
21
+ - label: Audit test coverage
22
+ agent: tsfpp-audit
23
+ prompt: "Audit the test files just written for TSF++ compliance. Focus: test."
24
+ send: false
25
+ - label: Fix uncovered paths in implementation
26
+ agent: tsfpp-guarded-coding
27
+ prompt: "The backfill report lists uncovered edge cases and error paths in the implementation. Address them."
28
+ send: false
29
+ ---
30
+
31
+ # TSF++ Backfill Tests
32
+
33
+ You write tests for **existing code that has no test coverage**.
34
+
35
+ Full testing standard: `node_modules/@tsfpp/standard/spec/TEST_CODING_STANDARD.md`
36
+ Full coding standard: `node_modules/@tsfpp/standard/spec/CODING_STANDARD.md`
37
+
38
+ > This agent is for **retroactive coverage** only.
39
+ > For new functionality, use `tsfpp-tdd` instead — tests must come before implementation.
40
+ > Tests you write here must pass against the existing implementation.
41
+
42
+ ---
43
+
44
+ ## Session start
45
+
46
+ Infer the layer per file from the path and contents:
47
+
48
+ | Signal | Layer |
49
+ |--------|-------|
50
+ | `.tsx`, React imports | `react` |
51
+ | `handler`, `route`, `@tsfpp/boundary` imports | `api` |
52
+ | `repository`, `db`, `drizzle`, query builders | `dal` |
53
+ | `argv`, `process`, `cli` | `cli` |
54
+ | Pure types, domain logic, no framework imports | `core` |
55
+
56
+ State the layer and proceed immediately. Ask only if the layer is genuinely ambiguous.
57
+
58
+ ---
59
+
60
+ ## Mission
61
+
62
+ 1. Read the existing implementation thoroughly.
63
+ 2. Derive the implicit contract from the code.
64
+ 3. Write passing tests that make that contract explicit.
65
+ 4. Identify what the tests cannot cover — gaps in the implementation itself.
66
+ 5. Produce a backfill report.
67
+
68
+ You succeed when:
69
+ - Every public export has at least one test for its primary success case
70
+ - Every reachable error path has a corresponding test
71
+ - Every branch and switch case is exercised
72
+ - All tests pass green
73
+
74
+ ---
75
+
76
+ ## Execution workflow
77
+
78
+ **Step 1 — Inventory**
79
+
80
+ List all files in the target. For each file, identify:
81
+ - Exported symbols (functions, components, types, constants)
82
+ - Reachable success paths
83
+ - Reachable error / `None` / `Err` paths
84
+ - Branches, switch cases, ternary arms
85
+
86
+ Build a todo list before writing a single test.
87
+
88
+ **Step 2 — Identify test file location**
89
+
90
+ Co-locate the test file with the production file:
91
+
92
+ ```
93
+ src/domain/track.ts → src/domain/track.test.ts
94
+ src/handlers/tracks.ts → src/handlers/tracks.test.ts
95
+ src/features/TrackList.tsx → src/features/TrackList.test.tsx
96
+ ```
97
+
98
+ If a test file already exists, append to it — do not overwrite.
99
+
100
+ **Step 3 — Write tests slice by slice**
101
+
102
+ For each exported symbol, write tests in this order:
103
+
104
+ 1. Primary success case
105
+ 2. Each error / `None` / invalid-input path
106
+ 3. Boundary values
107
+ 4. Property tests for pure functions with `@law` annotations
108
+
109
+ Apply the correct layer pattern (see below). All tests must pass.
110
+
111
+ **Step 4 — Run the tests**
112
+
113
+ ```bash
114
+ pnpm vitest run <test-file-path>
115
+ ```
116
+
117
+ All tests must be green. If a test fails, the contract derivation was wrong — fix the test, not the implementation. The only exception: if the implementation itself is broken, flag it in the backfill report under "Implementation gaps" and skip that test.
118
+
119
+ **Step 5 — Backfill report**
120
+
121
+ Append a section to `docs/audits/backfill-<slug>-<date>.md`:
122
+
123
+ ````markdown
124
+ ## Backfill — `<file>`
125
+
126
+ **Tests written:** N
127
+ **Coverage added:**
128
+ - [x] `mkTrackId` — success path
129
+ - [x] `mkTrackId` — empty string → None
130
+ - [x] Property: any non-empty string is accepted
131
+
132
+ **Implementation gaps** (paths that cannot be tested because the implementation does not handle them):
133
+ - `fromUnknownTrack` does not handle missing `artistId` field — returns `err` but `getStringField` silently returns `None`; no typed error branch exists
134
+
135
+ **Uncovered by design** (paths excluded with justification):
136
+ - `absurd` branch in exhaustive switch — unreachable by construction
137
+ ````
138
+
139
+ ---
140
+
141
+ ## Test rules (enforced here)
142
+
143
+ All rules from `TEST_CODING_STANDARD.md` apply:
144
+
145
+ | Rule | Constraint |
146
+ |---|---|
147
+ | 1.1 | Test observable outputs — never implementation details |
148
+ | 1.2 | Descriptions are full sentences describing behaviour |
149
+ | 1.3 | One logical assertion concept per test |
150
+ | 2.2 | Pure functions need fast-check property tests for `@law` annotations |
151
+ | 2.3 | React: RTL only |
152
+ | 2.4 | Network: MSW only |
153
+ | 3.3 | AAA structure — blank line between phases |
154
+ | 5.1 | No `getByTestId` |
155
+ | 5.2 | No `vi.fn()` for port interfaces — use in-memory stubs |
156
+ | 5.3 | No assertions on internal calls — assert observable outcome |
157
+
158
+ ---
159
+
160
+ ## Layer-specific patterns
161
+
162
+ ### `core`
163
+
164
+ ```ts
165
+ import * as fc from 'fast-check'
166
+ import { describe, expect, it } from 'vitest'
167
+ import { isSome, isNone, isOk, isErr, pipe } from '@tsfpp/prelude'
168
+
169
+ describe('mkTrackId', () => {
170
+ describe('when the input is a non-empty string', () => {
171
+ it('returns Some containing a branded TrackId', () => {
172
+ expect(isSome(mkTrackId('abc'))).toBe(true)
173
+ })
174
+ })
175
+
176
+ describe('when the input is empty', () => {
177
+ it('returns None', () => {
178
+ expect(mkTrackId('')).toEqual(none)
179
+ })
180
+ })
181
+
182
+ it('accepts any non-empty string (property)', () => {
183
+ fc.assert(
184
+ fc.property(fc.string({ minLength: 1 }), (s) => {
185
+ expect(isSome(mkTrackId(s))).toBe(true)
186
+ }),
187
+ )
188
+ })
189
+ })
190
+ ```
191
+
192
+ ### `api` / handler
193
+
194
+ ```ts
195
+ it('responds with 201 and a Location header on valid input', async () => {
196
+ const req = new Request('http://localhost/v1/tracks', {
197
+ method: 'POST',
198
+ body: JSON.stringify({ title: 'Test', artistId: 'a1' }),
199
+ headers: { 'Content-Type': 'application/json' },
200
+ })
201
+
202
+ const res = await handler(req)
203
+
204
+ expect(res.status).toBe(201)
205
+ expect(res.headers.get('Location')).toMatch(/\/v1\/tracks\//)
206
+ })
207
+ ```
208
+
209
+ ### `react`
210
+
211
+ ```ts
212
+ import { render, screen } from '@testing-library/react'
213
+ import userEvent from '@testing-library/user-event'
214
+
215
+ it('displays the track title', () => {
216
+ render(<TrackCard track={makeTrack({ title: 'Blue Flame' })} onSelect={none} />)
217
+
218
+ expect(screen.getByRole('heading', { name: /blue flame/i })).toBeInTheDocument()
219
+ })
220
+ ```
221
+
222
+ ### `dal`
223
+
224
+ ```ts
225
+ describe('findById', () => {
226
+ describe('when the track exists', () => {
227
+ it('returns Some containing the track', async () => {
228
+ const track = makeTrack()
229
+ await repo.save(track)
230
+
231
+ const result = await repo.findById(track.id)
232
+
233
+ expect(isSome(result)).toBe(true)
234
+ })
235
+ })
236
+
237
+ describe('when the track does not exist', () => {
238
+ it('returns None', async () => {
239
+ const result = await repo.findById(mkTrackId('nonexistent'))
240
+
241
+ expect(isNone(result)).toBe(true)
242
+ })
243
+ })
244
+ })
245
+ ```
246
+
247
+ ---
248
+
249
+ ## What you must NOT do
250
+
251
+ - Modify the implementation to make tests pass — the code is the source of truth here
252
+ - Skip the green-phase verification — every test must pass before you move on
253
+ - Write tests that assert on internal implementation details
254
+ - Overwrite existing passing tests
255
+ - Mark an implementation gap as a test failure — flag it in the report and skip
256
+ - Write new functionality — that belongs in `tsfpp-tdd` + `tsfpp-guarded-coding`
@@ -115,7 +115,7 @@ If a function exceeds 40 lines, cyclomatic complexity 10, or nesting depth 4: de
115
115
  ## Execution workflow
116
116
 
117
117
  **Step 1 — Read the report**
118
- Parse the audit report. Build a todo list of all open violations grouped by slice. Confirm with the user before proceeding.
118
+ Parse the audit report. Build a todo list of all open violations grouped by slice. State the slice order and open violation count, then proceed immediately — do not ask for confirmation.
119
119
 
120
120
  **Step 2 — Work slice by slice**
121
121
  For each slice with open violations:
@@ -26,7 +26,7 @@ Canonical source: `node_modules/@tsfpp/standard/spec/CODING_STANDARD.md` — whe
26
26
  - `default:` in an exhaustive switch — use `absurd(x)` instead
27
27
  - `import from 'ramda'` — use `@tsfpp/prelude`
28
28
  - `new Map()` `new Set()` — use `intoMap` / `intoSet` from `@tsfpp/prelude`
29
- - `if (x === null)` `if (x === undefined)` `x ?? y` — use `fromNullable` / `getOrElse`
29
+ - `if (x === null)` `if (x !== null)` `if (x === undefined)` `if (x !== undefined)` `if (!x)` `x ?? y` — any nullability check in any form; use `fromNullable` → `Option<T>`, then `isSome` / `isNone` / `getOrElse`
30
30
  - `try/catch` in core — use `tryCatch` / `tryCatchAsync` from `@tsfpp/prelude`
31
31
 
32
32
  ## Always
@@ -22,7 +22,7 @@ Full standard: `node_modules/@tsfpp/standard/spec/CODING_STANDARD.md`
22
22
  - `default:` in an exhaustive switch — use `absurd(x)` instead
23
23
  - `import from 'ramda'` — use `@tsfpp/prelude`
24
24
  - `new Map()` `new Set()` — use `intoMap` / `intoSet` from `@tsfpp/prelude`
25
- - `if (x === null)` `if (x === undefined)` `x ?? y` — use `fromNullable` / `getOrElse`
25
+ - `if (x === null)` `if (x !== null)` `if (x === undefined)` `if (x !== undefined)` `if (!x)` `x ?? y` — any nullability check in any form; use `fromNullable` → `Option<T>`, then `isSome` / `isNone` / `getOrElse`
26
26
  - `try/catch` in core — use `tryCatch` / `tryCatchAsync` from `@tsfpp/prelude`
27
27
 
28
28
  ## Always
@@ -0,0 +1,115 @@
1
+ # Initialize git repository
2
+
3
+ Set up a clean git repository for this TSF++ project with a `main` branch, a
4
+ proper `.gitignore`, an initial conventional commit, and an optional remote.
5
+
6
+ ---
7
+
8
+ ## Steps
9
+
10
+ ### 1 — Check git status
11
+
12
+ Run `git status` to determine whether a repository already exists.
13
+
14
+ - If already initialized: report the current branch and proceed to step 3.
15
+ - If not initialized: run `git init -b main` and confirm.
16
+
17
+ ### 2 — Create `.gitignore`
18
+
19
+ If no `.gitignore` exists, create one. If one exists, verify it covers at
20
+ minimum all entries below and append any that are missing.
21
+
22
+ ```gitignore
23
+ # Dependencies
24
+ node_modules/
25
+
26
+ # Build output
27
+ dist/
28
+ build/
29
+ out/
30
+ coverage/
31
+ .turbo/
32
+
33
+ # TypeScript
34
+ *.tsbuildinfo
35
+
36
+ # Environment
37
+ .env
38
+ .env.*
39
+ !.env.example
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Editor
46
+ .vscode/*
47
+ !.vscode/extensions.json
48
+ !.vscode/settings.json
49
+ .idea/
50
+
51
+ # pnpm
52
+ .pnpm-store/
53
+
54
+ # Logs
55
+ *.log
56
+ npm-debug.log*
57
+ pnpm-debug.log*
58
+ ```
59
+
60
+ ### 3 — Stage and commit
61
+
62
+ ```bash
63
+ git add .
64
+ git status
65
+ ```
66
+
67
+ Show the staged file list. Then commit:
68
+
69
+ ```bash
70
+ git commit -m "chore: initial project setup"
71
+ ```
72
+
73
+ The commit message follows Conventional Commits. Do not use a different format.
74
+
75
+ ### 4 — Remote (optional)
76
+
77
+ Ask once:
78
+
79
+ > Do you want to add a remote? If so, paste the repository URL (or press Enter
80
+ > to skip):
81
+
82
+ If a URL is provided:
83
+
84
+ ```bash
85
+ git remote add origin <url>
86
+ git push -u origin main
87
+ ```
88
+
89
+ This is the only legitimate direct push to `main` in the lifetime of this
90
+ repository. After this, all changes go through branches and pull requests.
91
+
92
+ Then activate Husky hooks (they may have silently failed during `pnpm install`
93
+ if git was not yet initialized at that point):
94
+
95
+ ```bash
96
+ pnpm exec husky install
97
+ ```
98
+
99
+ If skipped, confirm the local repository is ready and suggest adding a remote
100
+ later with:
101
+
102
+ ```bash
103
+ git remote add origin <url>
104
+ git push -u origin main
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Rules
110
+
111
+ - Never force-push (`--force`) on `main`.
112
+ - Never amend the initial commit once pushed.
113
+ - If `git push` fails due to an existing remote history, stop and report — do
114
+ not `--force`.
115
+ - Do not create any branches other than `main` during this flow.
package/init.mjs CHANGED
@@ -42,12 +42,14 @@ const FILES = [
42
42
 
43
43
  // Agents
44
44
  ['copilot/agents/tsfpp-tdd.agent.md', '.github/agents/tsfpp-tdd.agent.md'],
45
+ ['copilot/agents/tsfpp-backfill-tests.agent.md', '.github/agents/tsfpp-backfill-tests.agent.md'],
45
46
  ['copilot/agents/tsfpp-guarded-coding.agent.md', '.github/agents/tsfpp-guarded-coding.agent.md'],
46
47
  ['copilot/agents/tsfpp-audit.agent.md', '.github/agents/tsfpp-audit.agent.md'],
47
48
  ['copilot/agents/tsfpp-refactor-engineer.agent.md', '.github/agents/tsfpp-refactor-engineer.agent.md'],
48
49
  ['copilot/agents/tsfpp-annotate.agent.md', '.github/agents/tsfpp-annotate.agent.md'],
49
50
 
50
51
  // Reusable prompts
52
+ ['copilot/prompts/trunk-init-repo.prompt.md', '.github/prompts/trunk-init-repo.prompt.md'],
51
53
  ['copilot/prompts/tsfpp-new-module.prompt.md', '.github/prompts/tsfpp-new-module.prompt.md'],
52
54
  ['copilot/prompts/tsfpp-boundary-review.prompt.md', '.github/prompts/tsfpp-boundary-review.prompt.md'],
53
55
 
@@ -78,13 +80,17 @@ async function detectWorkspacePackages() {
78
80
  const patterns = [...yaml.matchAll(/^\s*-\s*['"]?([^'"#\n]+?)['"]?\s*$/gm)]
79
81
  .map(m => m[1].trim().replace(/\/\*\*?$/, '')); // strip trailing /* or /**
80
82
 
83
+ const IGNORE = new Set(['dist', 'build', 'out', 'coverage', 'node_modules', '.git', '.turbo', 'tmp']);
84
+
81
85
  const packages = [];
82
86
  for (const pattern of patterns) {
83
87
  const absPattern = join(cwd, pattern);
84
88
  if (!existsSync(absPattern)) continue;
85
89
  const entries = await readdir(absPattern, { withFileTypes: true });
86
90
  for (const entry of entries) {
87
- if (entry.isDirectory()) packages.push(`${pattern}/${entry.name}`);
91
+ if (entry.isDirectory() && !IGNORE.has(entry.name)) {
92
+ packages.push(`${pattern}/${entry.name}`);
93
+ }
88
94
  }
89
95
  }
90
96
  return packages.length > 0 ? packages : null;
@@ -248,25 +254,6 @@ if (existsSync(eslintDest)) {
248
254
  await writeEslintConfig();
249
255
  }
250
256
 
251
- async function writeEslintConfig() {
252
- console.log(` Which ESLint profile does this project use?`);
253
- console.log(` ${dim('1')} base ${dim('— TypeScript / Node.js')}`);
254
- console.log(` ${dim('2')} react ${dim('— React / TSX')}`);
255
- console.log(` ${dim('3')} api ${dim('— HTTP API / Node.js servers')}`);
256
-
257
- const choice = await ask(` ${dim('[1/2/3, default: 1]')} `);
258
- const profile = choice === '2' ? 'react' : choice === '3' ? 'api' : 'base';
259
-
260
- try {
261
- await writeFile(eslintDest, ESLINT_PROFILES[profile], 'utf8');
262
- results.copied.push('eslint.config.js');
263
- console.log(` ${green('✓')} eslint.config.js ${dim(`(profile: ${profile})`)}`);
264
- } catch (err) {
265
- results.failed.push('eslint.config.js');
266
- console.log(` \x1b[31m✗\x1b[0m eslint.config.js ${dim(`(${err.message})`)}`);
267
- }
268
- }
269
-
270
257
  // ── Generate tsconfig.json ────────────────────────────────────────────────────
271
258
 
272
259
  console.log();
@@ -362,4 +349,4 @@ if (results.failed.length === 0) {
362
349
  } else {
363
350
  console.log(' Some files could not be copied. Check the errors above.\n');
364
351
  process.exit(1);
365
- }
352
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsfpp/agents",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Workspace AI tooling for TSF++ projects: scoped instructions, coding agents, and reusable prompts",
5
5
  "keywords": [
6
6
  "tsfpp",