@tsfpp/agents 1.2.1 → 1.2.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,31 @@ Versioning follows [Semantic Versioning](https://semver.org/).
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [1.2.3] - 2026-05-16
14
+
15
+ ### Added
16
+
17
+ - Added `react` and `data` as supported focus values for `copilot/agents/tsfpp-audit.agent.md`.
18
+ - Added Data profile overlay support via `node_modules/@tsfpp/standard/spec/DATA_CODING_STANDARD.md`.
19
+
20
+ ### Changed
21
+
22
+ - Updated TSF++ standard reference paths in `copilot/agents/tsfpp-audit.agent.md` to `node_modules/@tsfpp/standard/spec/*.md`.
23
+ - Updated audit startup prompt and argument hints to include React and Data focused targets.
24
+
25
+ ### Removed
26
+
27
+ - Removed checklist rule `8.x` (prelude ADT/helper reuse enforcement) from the base audit template.
28
+ - Simplified checklist rule `9.x` from broad dependency hygiene checks to a direct `ramda` import guard.
29
+
30
+ ## [1.2.2] - 2026-05-16
31
+
32
+ ### Fixed
33
+
34
+ - Updated `init.mjs` questionnaire flow to avoid overwriting existing `tsconfig` and ESLint config files.
35
+ - Added `N`/skip escape hatch for existing-file prompts.
36
+ - Added `SIGINT` handling by wrapping execution in `main` so Ctrl+C exits cleanly without hanging awaits.
37
+
13
38
  ## [1.2.1] - 2026-05-16
14
39
 
15
40
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: TSF++ standards compliance auditor. Produces a structured markdown report in docs/audits/ with per-slice checkboxes.
3
3
  name: tsfpp-audit
4
- argument-hint: "target=<path|package|layer> focus=<all|types|boundary|complexity|loc|annotations|security>"
4
+ argument-hint: "target=<path|package|layer> focus=<all|types|boundary|complexity|loc|annotations|security|react|data>"
5
5
  tools:
6
6
  - edit/createFile
7
7
  - edit/editFiles
@@ -29,6 +29,7 @@ The canonical standard is at `node_modules/@tsfpp/standard/spec/CODING_STANDARD.
29
29
  Profile overlays:
30
30
  - API: `node_modules/@tsfpp/standard/spec/API_CODING_STANDARD.md`
31
31
  - React: `node_modules/@tsfpp/standard/spec/REACT_CODING_STANDARD.md`
32
+ - Data: `node_modules/@tsfpp/standard/spec/DATA_CODING_STANDARD.md`
32
33
  - Security: `node_modules/@tsfpp/standard/spec/SECURITY_CODING_STANDARD.md`
33
34
 
34
35
  If any referenced file is missing, stop immediately and report the path. Do not proceed.
@@ -42,7 +43,7 @@ If any referenced file is missing, stop immediately and report the path. Do not
42
43
  If the user has not provided both `target` and `focus`, ask exactly this:
43
44
 
44
45
  > **Target** — path, package name, or layer to audit (e.g. `src/domain`, `@tsfpp/prelude`, `api layer`)?
45
- > **Focus** — `all` · `types` · `boundary` · `complexity` · `loc` · `annotations` · `security` · or comma-separated combination?
46
+ > **Focus** — `all` · `types` · `boundary` · `complexity` · `loc` · `annotations` · `security` · `react` · `data` · or comma-separated combination?
46
47
 
47
48
  Do not proceed until both are confirmed.
48
49
 
@@ -132,8 +133,7 @@ Append each completed slice to the report:
132
133
  - [x] 5.1 — Pipelines via `pipe` from prelude
133
134
  - [x] 6.x — No `throw` in core
134
135
  - [x] 7.x — JSDoc on all exports
135
- - [x] 8.x — Prefer prelude ADTs/constructors/helpers (no downstream reimplementation in domain code)
136
- - [x] 9.x — Dependency hygiene (no deprecated dependencies, no banned imports per policy, no layer-violating imports)
136
+ - [x] 9.x — No direct `ramda` import
137
137
 
138
138
  #### Deviation register
139
139
 
@@ -147,10 +147,10 @@ Append each completed slice to the report:
147
147
  ## Focus-specific rule sets
148
148
 
149
149
  ### `types`
150
- 1.4 (no bare interface) · 1.5 (no `any`) · 1.6 (no `!` or `as`) · 3.x (readonly) · branded types on domain primitives · smart constructor completeness · exhaustive sum-type dispatch · prelude ADT/constructor/helper reuse (no downstream reimplementation)
150
+ 1.4 (no bare interface) · 1.5 (no `any`) · 1.6 (no `!` or `as`) · 3.x (readonly) · branded types on domain primitives · smart constructor completeness · exhaustive sum-type dispatch
151
151
 
152
152
  ### `boundary`
153
- API_CODING_STANDARD.md Rules 1–5 · Zod schema completeness · Result/Option at I/O · `extractContext` usage · `apiErrorToResponse` coverage · no raw `throw` across boundaries · `@tsfpp/boundary` response builders used · avoid boundary-local ADT/helper reinvention when prelude equivalents exist
153
+ API_CODING_STANDARD.md Rules 1–5 · Zod schema completeness · Result/Option at I/O · `extractContext` usage · `apiErrorToResponse` coverage · no raw `throw` across boundaries · `@tsfpp/boundary` response builders used
154
154
 
155
155
  ### `complexity`
156
156
  Function body ≤ 40 lines · cyclomatic complexity ≤ 10 · nesting ≤ 4 · arity ≤ 3 positional params · pipeline depth ≤ 8 stages
@@ -164,6 +164,12 @@ JSDoc on every export · `@param` + `@returns` present · `@law` on combinators
164
164
  ### `security`
165
165
  SECURITY_CODING_STANDARD.md: input validation at boundaries · no secrets in code · no sensitive data in errors · auth/authz at correct layer · dependency hygiene
166
166
 
167
+ ### `react`
168
+ REACT_CODING_STANDARD.md: component shape and explicit return types · readonly props contracts · state model quality · effect discipline (`useEffect` only for external sync) · server state via TanStack Query · form and routing standards
169
+
170
+ ### `data`
171
+ DATA_CODING_STANDARD.md: schema-first design · migration safety and reversibility · repository/query boundaries · transaction discipline · index/key correctness · deterministic data transformation and serialization at boundaries
172
+
167
173
  ### `all`
168
174
  All focus areas above in sequence.
169
175
 
package/init.mjs CHANGED
@@ -99,7 +99,9 @@ async function askProfile(label) {
99
99
  console.log(` ${dim('1')} base ${dim('— TypeScript / Node.js')}`);
100
100
  console.log(` ${dim('2')} react ${dim('— React / TSX')}`);
101
101
  console.log(` ${dim('3')} api ${dim('— HTTP API / Node.js servers')}`);
102
- const choice = await ask(` ${dim('[1/2/3, default: 1]')} `);
102
+ console.log(` ${dim('n')} skip ${dim('— keep existing / do not generate')}`);
103
+ const choice = await ask(` ${dim('[1/2/3/n, default: 1]')} `);
104
+ if (choice === 'n') return null;
103
105
  return choice === '2' ? 'react' : choice === '3' ? 'api' : 'base';
104
106
  }
105
107
 
@@ -146,7 +148,7 @@ function generateSingleConfig(profile) {
146
148
  return `${imp}\nexport default [...${spread}]\n`;
147
149
  }
148
150
 
149
- async function writeEslintConfig() {
151
+ async function writeEslintConfig(results) {
150
152
  const packages = await detectWorkspacePackages();
151
153
 
152
154
  let content;
@@ -158,12 +160,17 @@ async function writeEslintConfig() {
158
160
  for (const pkg of packages) {
159
161
  packageProfiles[pkg] = await askProfile(pkg);
160
162
  }
161
- content = generateMonorepoConfig(packageProfiles);
163
+ const activeProfiles = Object.fromEntries(
164
+ Object.entries(packageProfiles).filter(([, p]) => p !== null)
165
+ );
166
+ if (Object.keys(activeProfiles).length === 0) return;
167
+ content = generateMonorepoConfig(activeProfiles);
162
168
  description = 'monorepo';
163
169
  } else {
164
170
  const profile = await askProfile('this project');
165
- content = generateSingleConfig(profile);
166
- description = `profile: ${profile}`;
171
+ if (profile === null) return;
172
+ content = generateSingleConfig(profile);
173
+ description = `profile: ${profile}`;
167
174
  }
168
175
 
169
176
  try {
@@ -199,11 +206,17 @@ async function confirm(question) {
199
206
 
200
207
  // ─── Main ─────────────────────────────────────────────────────────────────────
201
208
 
202
- console.log();
203
- console.log(bold(' @tsfpp/agents — init'));
204
- console.log(dim(' Sets up Copilot agents, instructions, prompts, skills, and ESLint config.\n'));
209
+ process.on('SIGINT', () => {
210
+ console.log('\n\n Aborted.\n');
211
+ process.exit(0);
212
+ });
205
213
 
206
- const results = { copied: [], skipped: [], failed: [] };
214
+ async function main() {
215
+ console.log();
216
+ console.log(bold(' @tsfpp/agents — init'));
217
+ console.log(dim(' Sets up Copilot agents, instructions, prompts, skills, and ESLint config.\n'));
218
+
219
+ const results = { copied: [], skipped: [], failed: [] };
207
220
 
208
221
  // ── Copy files ────────────────────────────────────────────────────────────────
209
222
 
@@ -240,17 +253,21 @@ console.log();
240
253
  const eslintDest = join(cwd, 'eslint.config.js');
241
254
 
242
255
  if (existsSync(eslintDest)) {
243
- const overwrite = await confirm(
244
- ` ${yellow('!')} eslint.config.js already exists. Overwrite? ${dim('[y/N]')} `
245
- );
246
- if (!overwrite) {
256
+ if (yes) {
247
257
  results.skipped.push('eslint.config.js');
248
- console.log(` ${dim('–')} ${dim('eslint.config.js')} ${dim('(skipped)')}`);
258
+ console.log(` ${dim('–')} ${dim('eslint.config.js')} ${dim('(skipped — project-managed)')}`);
249
259
  } else {
250
- await writeEslintConfig();
260
+ const overwrite = await confirm(
261
+ ` ${yellow('!')} eslint.config.js already exists. Overwrite? ${dim('[y/N]')} `
262
+ );
263
+ if (overwrite) await writeEslintConfig(results);
264
+ else {
265
+ results.skipped.push('eslint.config.js');
266
+ console.log(` ${dim('–')} ${dim('eslint.config.js')} ${dim('(skipped)')}`);
267
+ }
251
268
  }
252
269
  } else {
253
- await writeEslintConfig();
270
+ await writeEslintConfig(results);
254
271
  }
255
272
 
256
273
 
@@ -259,9 +276,9 @@ if (existsSync(eslintDest)) {
259
276
 
260
277
  console.log();
261
278
 
262
- await writeTsConfigs(await detectWorkspacePackages());
279
+ await writeTsConfigs(await detectWorkspacePackages(), results);
263
280
 
264
- async function writeTsConfigs(packages) {
281
+ async function writeTsConfigs(packages, results) {
265
282
  const PRESETS = {
266
283
  app: { extends: '@tsfpp/tsconfig/app', label: 'app — application / tool (noEmit)' },
267
284
  lib: { extends: '@tsfpp/tsconfig/lib', label: 'lib — publishable package (declaration, composite)' },
@@ -271,7 +288,9 @@ async function writeTsConfigs(packages) {
271
288
  console.log(`\n tsconfig preset for ${bold(label)}:`);
272
289
  console.log(` ${dim('1')} app ${dim('— application / tool (noEmit: true)')}`);
273
290
  console.log(` ${dim('2')} lib ${dim('— publishable package (declaration, composite)')}`);
274
- const choice = await ask(` ${dim('[1/2, default: 1]')} `);
291
+ console.log(` ${dim('n')} skip ${dim('— keep existing / do not generate')}`);
292
+ const choice = await ask(` ${dim('[1/2/n, default: 1]')} `);
293
+ if (choice === 'n') return null;
275
294
  return choice === '2' ? 'lib' : 'app';
276
295
  }
277
296
 
@@ -292,6 +311,11 @@ async function writeTsConfigs(packages) {
292
311
 
293
312
  async function writeIfConfirmed(destPath, content, label) {
294
313
  if (existsSync(destPath)) {
314
+ if (yes) {
315
+ results.skipped.push(label);
316
+ console.log(` ${dim('–')} ${dim(label)} ${dim('(skipped — project-managed)')}`);
317
+ return;
318
+ }
295
319
  const overwrite = await confirm(
296
320
  ` ${yellow('!')} ${label} already exists. Overwrite? ${dim('[y/N]')} `
297
321
  );
@@ -321,6 +345,7 @@ async function writeTsConfigs(packages) {
321
345
  }
322
346
 
323
347
  for (const [pkg, preset] of Object.entries(packagePresets)) {
348
+ if (preset === null) continue;
324
349
  const destPath = join(cwd, pkg, 'tsconfig.json');
325
350
  const content = generateTsConfig(preset);
326
351
  await writeIfConfirmed(destPath, content, `${pkg}/tsconfig.json`);
@@ -332,22 +357,26 @@ async function writeTsConfigs(packages) {
332
357
  await writeIfConfirmed(rootDest, rootContent, 'tsconfig.json (root references)');
333
358
  } else {
334
359
  // Single package
335
- const preset = await askPreset('this project');
360
+ const preset = await askPreset('this project');
361
+ if (preset === null) return;
336
362
  const dest = join(cwd, 'tsconfig.json');
337
363
  const content = generateTsConfig(preset);
338
364
  await writeIfConfirmed(dest, content, 'tsconfig.json');
339
365
  }
340
366
  }
341
367
 
342
- console.log();
343
- console.log(dim(' ─────────────────────────────────────────'));
344
- console.log(` ${green(results.copied.length + ' copied')} ${yellow(results.skipped.length + ' skipped')} ${results.failed.length > 0 ? `\x1b[31m${results.failed.length} failed\x1b[0m` : dim('0 failed')}`);
345
- console.log();
368
+ console.log();
369
+ console.log(dim(' ─────────────────────────────────────────'));
370
+ console.log(` ${green(results.copied.length + ' copied')} ${yellow(results.skipped.length + ' skipped')} ${results.failed.length > 0 ? `\x1b[31m${results.failed.length} failed\x1b[0m` : dim('0 failed')}`);
371
+ console.log();
346
372
 
347
- if (results.failed.length === 0) {
348
- console.log(' ' + bold('Done.') + ' Reload VS Code to activate Copilot instructions.');
349
- console.log(dim(' Commit the generated files — they are workspace configuration.\n'));
350
- } else {
351
- console.log(' Some files could not be copied. Check the errors above.\n');
352
- process.exit(1);
373
+ if (results.failed.length === 0) {
374
+ console.log(' ' + bold('Done.') + ' Reload VS Code to activate Copilot instructions.');
375
+ console.log(dim(' Commit the generated files — they are workspace configuration.\n'));
376
+ } else {
377
+ console.log(' Some files could not be copied. Check the errors above.\n');
378
+ process.exit(1);
379
+ }
353
380
  }
381
+
382
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsfpp/agents",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Workspace AI tooling for TSF++ projects: scoped instructions, coding agents, and reusable prompts",
5
5
  "keywords": [
6
6
  "tsfpp",