@adbayb/stack 3.4.0 → 3.4.1-next.1792414744076.55a57c2

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/README.md CHANGED
@@ -10,12 +10,12 @@
10
10
  `Stack` allows creating an NPM project in a **quick**, **easy** and **opinionated** way with:
11
11
 
12
12
  - A unified monorepo-driven and git-driven approach for all kinds of projects (single-purpose library, multiple libraries, application(s), ...)
13
- - Support for several application templates (for now, only a default template is available, but others can be added later)
13
+ - Support for several application templates (for now, only a default template is available, but others can be added later).
14
14
  - Standardized file structure including optional folder creation such as `examples`, `packages`, ...
15
- - Enable all project lifecycle steps with commands from the setup, check, fix, build, and test to the versioning and release
16
- - Built-in configuration preset setup (including Node/NPM runtime, Git ignore/hooks, [TypeScript](https://github.com/adbayb/stack/blob/main/stack/configs/typescript/), [Oxlint](https://github.com/adbayb/stack/blob/main/stack/configs/oxlint/), [Oxfmt](https://github.com/adbayb/stack/blob/main/stack/configs/oxfmt/), EditorConfig, Renovate, Changesets, and VSCode)
15
+ - Enable all project lifecycle steps with commands from the setup, check, fix, build, and test to the versioning and release.
16
+ - Built-in configuration preset setup (including Node/NPM runtime, Git ignore/hooks, [TypeScript](https://github.com/adbayb/stack/blob/main/stack/configs/typescript/), [Oxlint](https://github.com/adbayb/stack/blob/main/stack/configs/oxlint/), [Oxfmt](https://github.com/adbayb/stack/blob/main/stack/configs/oxfmt/), EditorConfig, Renovate, Changesets, and VSCode).
17
17
  - A welcoming environment for users and contributors with the continuous integration setup, and the creation of `README.md`, `CONTRIBUTING.md`, and GitHub template files (including issue and pull request ones), ...
18
- - An optimized development environment with command caching (no build needed if nothing changes)
18
+ - An optimized development environment with command caching (no build needed if nothing changes).
19
19
 
20
20
  <br>
21
21
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createLogger, exec, termost } from "termost";
2
2
  import { createRequire } from "node:module";
3
- import { basename, join, resolve } from "node:path";
3
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { existsSync, readdirSync } from "node:fs";
5
5
  import { chmod, cp, mkdir, readFile, readdir, rename, rm, symlink, writeFile } from "node:fs/promises";
6
6
 
@@ -461,7 +461,12 @@ const label$3 = (message) => {
461
461
  };
462
462
  const cleanFiles = async (files) => {
463
463
  await Promise.all(files.map(async (file) => {
464
- await rm(file, {
464
+ const absolutePath = resolveFromWorkingDirectory(file);
465
+ const pathFromWorkingDirectory = relative(process.cwd(), absolutePath);
466
+ assert(pathFromWorkingDirectory !== "" && pathFromWorkingDirectory !== ".." && !pathFromWorkingDirectory.startsWith(`..${sep}`) && !isAbsolute(pathFromWorkingDirectory), () => {
467
+ return createError("stack clean", `Refusing to remove a path outside the working directory: ${file}`);
468
+ });
469
+ await rm(absolutePath, {
465
470
  force: true,
466
471
  recursive: true
467
472
  });
@@ -472,16 +477,31 @@ const isDirectoryExistAndNotEmpty = (path) => {
472
477
  };
473
478
  const LINEBREAK_REGEXP = /\n|\r\n/u;
474
479
  const retrieveIgnoredFiles = async () => {
475
- return (await exec("git clean -fdXn")).split(LINEBREAK_REGEXP).filter((cleanOutput) => {
476
- return PRESERVE_FILES.every((excludedFile) => {
477
- return !cleanOutput.includes(excludedFile);
478
- });
479
- }).map((cleanOutput) => {
480
+ return (await exec("git clean -fdXn")).split(LINEBREAK_REGEXP).map((cleanOutput) => {
480
481
  return cleanOutput.startsWith(REMOVABLE_FILE_PREFIX) ? cleanOutput.slice(REMOVABLE_FILE_PREFIX.length) : "";
481
- }).filter(Boolean);
482
+ }).filter(Boolean).filter((file) => {
483
+ return !isPreservedPath(file);
484
+ });
485
+ };
486
+ /**
487
+ * Check whether `stack clean` must preserve a path.
488
+ *
489
+ * Preserve installed dependencies and local environment files, which may contain secrets and must
490
+ * never be removed by a generated-file cleanup.
491
+ *
492
+ * @param path - The repository-relative path to inspect.
493
+ * @returns `true` when the path or one of its segments is preserved.
494
+ */
495
+ const isPreservedPath = (path) => {
496
+ return path.split(PATH_SEGMENT_SEPARATOR).some((segment) => {
497
+ return PRESERVED_SEGMENTS.some((pattern) => {
498
+ return pattern.test(segment);
499
+ });
500
+ });
482
501
  };
483
502
  const REMOVABLE_FILE_PREFIX = "Would remove ";
484
- const PRESERVE_FILES = ["node_modules"];
503
+ const PATH_SEGMENT_SEPARATOR = /[\\/]/u;
504
+ const PRESERVED_SEGMENTS = [/^node_modules$/u, /^\.env(?:\.|$)/u];
485
505
 
486
506
  //#endregion
487
507
  //#region src/commands/create.ts
@@ -786,8 +806,7 @@ const createInstallCommand = (program) => {
786
806
  }).task({
787
807
  label: label("Install `git.pre-commit` hook"),
788
808
  async handler() {
789
- const changedFiles = `$(node -e 'console.log(require("child_process").execSync("git status --porcelain", {encoding: "utf8"}).split(/${String.raw`\n|\r\n`}/).filter(Boolean).map(item => item.split(" ").at(-1)).join(" "))')`;
790
- await installGitHook("pre-commit", `files=${changedFiles}; if [ -n "$files" ]; then ${getStackCommand("fix $files")}; fi && git add -A`);
809
+ await installGitHook("pre-commit", PRE_COMMIT_HOOK);
791
810
  }
792
811
  }).task({
793
812
  label: label("Install `git.commit-msg` hook"),
@@ -799,6 +818,7 @@ const createInstallCommand = (program) => {
799
818
  const label = (message) => {
800
819
  return `${message} 📲`;
801
820
  };
821
+ const PRE_COMMIT_HOOK = String.raw`node -e 'const { execFileSync } = require("node:child_process"); const files = execFileSync("git", ["diff", "--cached", "--name-only", "-z", "--diff-filter=ACMR"], { encoding: "utf8" }).split("\0").filter(Boolean); if (files.length > 0) { execFileSync("pnpm", ["stack", "fix", ...files], { stdio: "inherit" }); }' && git add -A`;
802
822
  const installGitHook = async (hook, content) => {
803
823
  assert(existsSync(resolveFromWorkingDirectory(".git")), () => {
804
824
  return createError("stack install", "No `.git` directory found. Run `git init` first.");
@@ -908,7 +928,7 @@ const createWatchCommand = (program) => {
908
928
 
909
929
  //#endregion
910
930
  //#region package.json
911
- var version = "3.4.0";
931
+ var version = "3.4.1-next.1792414744076.55a57c2";
912
932
 
913
933
  //#endregion
914
934
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adbayb/stack",
3
- "version": "3.4.0",
3
+ "version": "3.4.1-next.1792414744076.55a57c2",
4
4
  "description": "My opinionated JavaScript-based toolchain",
5
5
  "keywords": [
6
6
  "development",
@@ -53,26 +53,28 @@
53
53
  "dependencies": {
54
54
  "@changesets/changelog-github": "^1.0.1",
55
55
  "@changesets/cli": "^3.0.3",
56
- "@commitlint/cli": "^21.2.3",
57
- "@commitlint/config-conventional": "^21.2.3",
56
+ "@commitlint/cli": "^21.2.4",
57
+ "@commitlint/config-conventional": "^21.2.4",
58
58
  "@e18e/eslint-plugin": "^0.8.1",
59
59
  "@stylistic/eslint-plugin": "^5.10.0",
60
60
  "eslint-plugin-perfectionist": "^5.12.1",
61
- "oxfmt": "^0.72.0",
62
- "oxlint": "^1.87.0",
61
+ "oxfmt": "^0.73.0",
62
+ "oxlint": "^1.88.0",
63
63
  "oxlint-tsgolint": "^7.0.2004",
64
64
  "termost": "^2.0.0",
65
- "turbo": "^2.11.7",
65
+ "turbo": "^2.11.9",
66
66
  "typescript": "^7.0.2"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node": "24.19.2",
70
- "quickbundle": "3.2.2"
70
+ "quickbundle": "3.2.2",
71
+ "vitest": "5.0.5"
71
72
  },
72
73
  "scripts": {
73
74
  "build": "quickbundle build",
74
75
  "prestart": "pnpm build",
75
76
  "start": "./bin/index.js",
77
+ "test": "vitest run",
76
78
  "watch": "quickbundle watch"
77
79
  }
78
80
  }
@@ -3,7 +3,7 @@
3
3
  This repo is `{{projectName}}`: {{projectDescription}}.
4
4
  Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` binary.
5
5
 
6
- > Extend `Project overview`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
6
+ > Extend `Project overview`, `Commands`, `Testing`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
7
7
 
8
8
  ## Project overview
9
9
 
@@ -14,7 +14,7 @@ Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` bi
14
14
  - Cross-package imports only via workspace package names: no relative imports across `applications`/`libraries` boundaries.
15
15
  - `examples/*/`: private playground apps (excluded from build/check orchestration).
16
16
  - `tools/*/`: private local tooling packages.
17
- - Root configs (`oxlint.config.ts`, `oxfmt.config.ts`, `tsconfig.json`, `turbo.json`) just re-export `@adbayb/stack` shared presets: extend, don't replace.
17
+ - Root configs: `oxlint.config.ts` and `oxfmt.config.ts` re-export `@adbayb/stack` presets; `tsconfig.json` extends the shared TypeScript preset; `turbo.json` is the local pipeline config. Extend the shared presets, don't replace them.
18
18
  - pnpm workspace members: `applications/*`, `libraries/*`, `examples/*`, `tools/*` (`pnpm-workspace.yaml`, `saveExact: true`).
19
19
  - If a package needs tailored guidance, add a nested `AGENTS.md` inside it: agents read the nearest file, so it takes precedence over this root one.
20
20
 
@@ -22,25 +22,25 @@ Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` bi
22
22
 
23
23
  Follow this order:
24
24
 
25
- - Requires Node `{{nodeVersion}}` and pnpm `{{pnpmVersion}}` (`devEngines` in root `package.json`).
26
- - After `stack create`: `pnpm install --frozen-lockfile`, then `stack install`.
27
- - `stack install` writes `.git/hooks/pre-commit` (`stack fix <changed-files> && git add -A`) and `commit-msg` (`stack check --filter commit`). Re-run it if hooks are missing.
25
+ - Check `devEngines` in root `package.json` (`runtime.node` and `packageManager.pnpm`) and confirm the local Node and pnpm versions match it before running anything.
26
+ - `stack create` already runs `pnpm install` and `stack install`. After cloning an existing project instead, run `pnpm install --frozen-lockfile` (its lifecycle `install` script runs `stack install`). Re-run `stack install` if hooks are missing.
27
+ - `stack install` writes `.git/hooks/pre-commit` (`stack fix <staged-files> && git add -A`) and `commit-msg` (`stack check --filter commit`). Re-run it if hooks are missing.
28
28
 
29
29
  ## Commands
30
30
 
31
31
  Root scripts (`package.json`) are thin wrappers around `stack`, which wraps turbo / oxlint / oxfmt / commitlint / changesets / quickbundle. Always use these, not raw tools:
32
32
 
33
- - `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`)
34
- - `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`)
35
- - `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`)
36
- - `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first)
37
- - `pnpm clean` (`stack clean`: deletes `git clean -fdXn` output, preserves `node_modules`, plus `node_modules/.cache`)
38
- - Untracked-but-ignored files are removed too; don't hand-delete `dist/`, use `stack clean`.
39
- - Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish`)
33
+ - `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`).
34
+ - `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`).
35
+ - `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`).
36
+ - `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first).
37
+ - `pnpm clean` (`stack clean`: deletes git-ignored files such as build output and caches, including `node_modules/.cache`; preserves `node_modules` and `.env*`).
38
+ - Other untracked-but-ignored files are removed too; don't hand-delete `dist/`, use `stack clean`.
39
+ - Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish`).
40
40
 
41
- Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]`. CI order (`.github/workflows/workflow.yml`) is `install --frozen-lockfile` → `build` → `check` → `test`.
41
+ Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]`; for focused tests, `pnpm --filter @adbayb/stack test` re-runs only that workspace's suite while iterating. CI order (`.github/workflows/workflow.yml`) is `install --frozen-lockfile` → `build` → `check` → `test`.
42
42
 
43
- ### Testing instructions
43
+ ### Testing
44
44
 
45
45
  - Run `pnpm check` and `pnpm test` before finishing; fix failures until green.
46
46
  - Add or update tests for the code you change, even if nobody asked, provided each test earns its place (covers behavior, not coverage).
@@ -48,57 +48,15 @@ Focused verification: `stack check --filter <code|formatting|dependency|commit>
48
48
  ## Code style guidelines
49
49
 
50
50
  Formatting is enforced by the shared `@adbayb/stack` presets (re-exported by root `oxlint.config.ts`/`oxfmt.config.ts`): comply via `stack fix`, never hand-format.
51
- For new code, use the `software-design` skill when available (install via `npx skills add adbayb/stack --skill software-design -g` if missing); otherwise follow these defaults. On conflict the skill wins; this list stays compressed by design:
52
-
53
- - KISS (Keep It Simple, Stupid) over cleverness; DRY (Don't Repeat Yourself) at knowledge level (rule of three before abstracting duplicated logic).
54
- - Minimal API surface (YAGNI: You Aren't Gonna Need It): expose only what's needed now; small explicit functions, narrow interfaces.
55
- - Keep symbols local; promote to the lowest shared scope all consumers already depend on only for a second existing consumer (planned consumers and same-package tests excluded; published-API consumers included).
56
- - Exception: a contract both sides of a boundary must agree on may be shared right away. If tooling prevents sharing it, duplicate it and guard it with a test.
57
- - Before exporting across a boundary, check who imports it. Prefer sharing contracts over implementations. Never export a counterpart (e.g. a reader for a writer) ahead of its first consumer.
58
- - POLA (Principle of Least Astonishment): names and behavior as expected; fail fast at boundaries, no surprise side effects.
59
- - High cohesion, low coupling:
60
- - Keep code that changes together, or serves one responsibility, in one place.
61
- - Keep module/slice/package dependencies narrow, intentional, toward more stable lower-level code. No cycles.
62
- - Maintainable, evolvable design: apply the following only when it solves a present need (YAGNI/KISS/rule of three still win; no speculative layers or patterns in small modules).
63
- - SOLID, especially Dependency Inversion (small interfaces, injected implementations), Single Responsibility (one purpose per unit), Open/Closed (extend via composition, not inheritance or branch edits), Interface Segregation (narrow, consumer-shaped interfaces). Liskov when subtyping.
64
- - Design patterns when the problem matches: Adapter (wrap external APIs behind our contract), Facade (simple public API over complex internals), Strategy (swappable behavior per environment/config), Factory (creation varying by config or dependencies), Decorator (add cross-cutting behavior without touching the wrapped unit). Don't name a pattern in code unless it helps readers.
65
- - Hexagonal/ports-and-adapters at I/O boundaries (CLI args, env, files, network, storage, third-party SDKs): pure testable domain logic without I/O; I/O behind ports implemented by edge adapters; dependencies point inward.
66
- - Testing: verify behavior, keep the suite lean.
67
- - Test functional behavior through the public interface, not implementation details (internal calls, private structure, call order unless it is the contract). A refactor that keeps behavior must not break tests.
68
- - Every test must earn its place: never write tests to raise line count or coverage.
69
- - Consolidate overlapping cases (e.g. `test.each`); remove redundant, obsolete or implementation-coupled tests in files you edit, listing removals and reasons in the change summary.
70
- - One behavior per test, named by scenario and expectation; keep tests FIRST (fast, independent, repeatable, self-validating); cover boundaries and failure paths, not just the happy path.
71
- - Code organization: public-facing interface first (exports before implementation in source files, describes/tests before helpers in test files; no use-before-define of `const` helpers at module load). Order top-down (callers above callees); public/exported APIs carry a one-line TSDoc (`/** */`) stating the contract, with `@param`/`@returns` only when the description adds info beyond the types.
72
- - Consistent naming: pick one verb per contract, use everywhere (POLA), never add synonym for something already named.
73
- - Queries (never throw; `undefined`/empty when missing): `get` (lookup by identity), `find` (search by criteria/filter), `exists`/`count`. A throwing lookup needs its own intention verb (e.g. `getOrThrow`). Collections use a plural noun, never an `All` infix (`getOrders`, `findOverdueOrders`, not `getAllOrders`).
74
- - Commands: `create`/`update`/`remove` for lifecycle; intention verbs for domain behavior (`refundOrder`, not `setStatus`).
75
- - Banned → use: `fetch`/`retrieve`/`load`/`read` → `get`, `query`(verb) → `find`/`exists`, `delete`/`clear` → `remove`, `add`/`insert`/`save` → `create`/`update`, `set` → intention verb (DTOs/builders and React state setters exempt), `process`/`handle`/`manage`/`do` → specific intention verb, `data`/`info`/`util` → domain noun. Third-party and platform APIs (e.g. `fetch`) keep their own names. Apply to new and edited code; don't rename existing exported symbols just for this.
76
- - Booleans: `is*`/`has*`/`can*`. Handlers: `on<Event>`.
77
- - Generic utilities: `input`/`...inputs` for domain-less parameters, `output` for domain-less results (instead of `data`/`arg`/`x`); domain meaning always wins (`userId`, never `input`).
78
- - Boundary contracts: bespoke input/output shapes take `<FunctionName>Input` / `<FunctionName>Output`; reuse domain types otherwise, never alias per function.
79
- - Functions verb-first (`findOverdueOrders`), classes/types nouns (`OrderRefunder`), no stutter (`orders.get(id)` not `orderRepo.getOrder`).
80
- - Editing a file: match verbs already used in that slice, don't add a second synonym.
81
- - Type inference: skip explicit return/output types when TypeScript can infer them; annotate only when a stricter type than inferred is wanted (e.g. an enum instead of `string`) or when an exported API contract should be explicit.
82
- - Explicit logic: name the middle steps (explanatory variables, encapsulated boolean conditions); prefer positive conditionals. Every name must earn its place: single-use consts without meaning, await, narrowing, or literal get inlined. Make dependencies physical (import, parameter, injection); never require a call order or rely on ambient execution order. Every default, limit, timeout, and retry count is a named constant with a stated reason at module top.
83
- - Strict types: never silence the type checker (`as any`, `@ts-ignore`/`@ts-expect-error`, non-null `!`); absence is `undefined`, never `null`. Size names to their scope; no type prefixes, encodings, or `Manager`/`Helper`/`Util` holders.
84
- - Error handling: prefer Result objects from `@open-vanilla/result` over throwing for normal control flow; map foreign exceptions to failed Results at the boundary. Expected absence stays `undefined`/empty (queries never throw). New and edited code only; don't migrate existing throw-based code.
85
- - Avoid comments: prefer self-explanatory code; comment only the why (complex logic, non-obvious workflows, deliberately preserved ambiguous patterns). No narration of readable code, no commented-out code.
86
- - Neutral artifacts: generated text (source, docs, comments, commit messages, changelogs) must not contain symbols that identify model contribution.
87
- - Never emit em-dashes (`—`, U+2014) anywhere; restructure the sentence or use a hyphen (`-`), colon, comma, or parentheses instead.
88
- - Never emit a decorative en-dash (`–`, U+2013) or double hyphen; use a hyphen (ranges fine).
89
- - Never emit invisible unicode (U+00A0, U+200B-U+200D, stray BOM); use a regular space or delete.
90
- - Curly quotes and the ellipsis glyph (`…`, U+2026) are fine in prose but forbidden in code, comments, and copy-pasteable samples (they break pasted code).
91
- - Same for other decorative non-ASCII punctuation.
92
- - Apply to text you write or edit; don't rewrite existing files just for this.
93
- - Avoid assumptions: when the approach is ambiguous and a wrong guess is costly to undo, ask before proceeding. Otherwise pick the sensible default and state it.
94
- - Boy Scout Rule: leave code better than found. In edited files, make small, safe, behavior-preserving improvements (no exported renames) and mention them in the change summary. Larger or cross-file cleanup goes in a separate change.
51
+ Design and style rules have one source: the `software-design` skill (SKILL.md plus references). Install it when missing (`pnpm dlx skills add adbayb/stack --skill software-design -g`); on conflict, the skill wins its defaults and this section carries only repo deltas (none today: add a repo-specific design rule here when one emerges). If the skill cannot be installed, follow existing repository conventions and ask before structural changes.
95
52
 
96
53
  ## Security considerations
97
54
 
98
55
  - Never commit secrets (tokens, credentials): use environment variables and keep `.env*` git-ignored. Never log or print secrets; keep them out of error messages.
99
56
  - Validate untrusted input (CLI args, env, files, network) once at the boundary; fail fast with actionable errors.
100
57
  - Never interpolate untrusted input into shell commands: use parameterized exec helpers, never raw shell strings.
101
- - Keep file writes scoped to the narrowest path set; destructive operations (`clean`) fail closed on ambiguous scope and never delete outside it.
58
+ - Keep file writes scoped to the narrowest path set; destructive operations (`clean`) remove only git-ignored paths and generated cache directories, preserve `node_modules` and `.env*`, and refuse paths outside the working directory.
59
+ - Never run `pnpm release:publish` (or any changeset publish) manually: publishing ships versions; keep it to the documented release flow on a clean, CI-green tree.
102
60
  - Pin devDependencies exact (`saveExact: true`, or `workspace:*` for local packages); prefix `dependencies`/`peerDependencies` with a caret (or `workspace:^` for local dependencies: peers stay explicit); `stack check` verifies them: review automated dependency updates (Renovate) before merging.
103
61
 
104
62
  ## PR instructions
@@ -2,5 +2,5 @@
2
2
 
3
3
  A collection of examples to:
4
4
 
5
- - Showcase the artifacts' usage (mainly for libraries)
6
- - Source some scenarios for testing purposes (test cases)
5
+ - Showcase the artifacts' usage (mainly for libraries).
6
+ - Source some scenarios for testing purposes (test cases).
@@ -3,7 +3,7 @@
3
3
  This repo is `{{projectName}}`: {{projectDescription}}.
4
4
  Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` binary.
5
5
 
6
- > Extend `Project overview`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
6
+ > Extend `Project overview`, `Commands`, `Testing`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
7
7
 
8
8
  ## Project overview
9
9
 
@@ -12,32 +12,32 @@ Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` bi
12
12
  - `{{projectName}}/`: the publishable package (`src/index.ts` entry, `src/*.test.ts` colocated tests via `vitest`, `package.json` `exports` point to `dist/**`, built with `quickbundle`).
13
13
  - `examples/*/`: private playground apps consuming `{{projectName}}` via `workspace:` (excluded from build/check orchestration).
14
14
  - `tools/*/`: private local tooling packages.
15
- - Root configs (`oxlint.config.ts`, `oxfmt.config.ts`, `tsconfig.json`, `turbo.json`) just re-export `@adbayb/stack` shared presets: extend, don't replace.
15
+ - Root configs: `oxlint.config.ts` and `oxfmt.config.ts` re-export `@adbayb/stack` presets; `tsconfig.json` extends the shared TypeScript preset; `turbo.json` is the local pipeline config. Extend the shared presets, don't replace them.
16
16
  - pnpm workspace members: `{{projectName}}`, `examples/*`, `tools/*` (`pnpm-workspace.yaml`, `saveExact: true`).
17
17
 
18
18
  ## Bootstrap
19
19
 
20
20
  Follow this order:
21
21
 
22
- - Requires Node `{{nodeVersion}}` and pnpm `{{pnpmVersion}}` (`devEngines` in root `package.json`).
23
- - After `stack create`: `pnpm install --frozen-lockfile`, then `stack install`.
24
- - `stack install` writes `.git/hooks/pre-commit` (`stack fix <changed-files> && git add -A`) and `commit-msg` (`stack check --filter commit`). Re-run it if hooks are missing.
22
+ - Check `devEngines` in root `package.json` (`runtime.node` and `packageManager.pnpm`) and confirm the local Node and pnpm versions match it before running anything.
23
+ - `stack create` already runs `pnpm install` and `stack install`. After cloning an existing project instead, run `pnpm install --frozen-lockfile` (its lifecycle `install` script runs `stack install`). Re-run `stack install` if hooks are missing.
24
+ - `stack install` writes `.git/hooks/pre-commit` (`stack fix <staged-files> && git add -A`) and `commit-msg` (`stack check --filter commit`). Re-run it if hooks are missing.
25
25
 
26
26
  ## Commands
27
27
 
28
28
  Root scripts (`package.json`) are thin wrappers around `stack`, which wraps turbo / oxlint / oxfmt / commitlint / changesets / quickbundle. Always use these, not raw tools:
29
29
 
30
- - `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`)
31
- - `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`)
32
- - `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`)
33
- - `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first)
34
- - `pnpm clean` (`stack clean`: deletes `git clean -fdXn` output, preserves `node_modules`, plus `node_modules/.cache`)
35
- - Untracked-but-ignored files are removed too; don't hand-delete `dist/`, use `stack clean`.
36
- - Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish`)
30
+ - `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`).
31
+ - `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`).
32
+ - `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`).
33
+ - `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first).
34
+ - `pnpm clean` (`stack clean`: deletes git-ignored files such as build output and caches, including `node_modules/.cache`; preserves `node_modules` and `.env*`).
35
+ - Other untracked-but-ignored files are removed too; don't hand-delete `dist/`, use `stack clean`.
36
+ - Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish`).
37
37
 
38
- Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]`. CI order (`.github/workflows/workflow.yml`) is `install --frozen-lockfile` → `build` → `check` → `test`.
38
+ Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]`; for focused tests, `pnpm --filter @adbayb/stack test` re-runs only that workspace's suite while iterating. CI order (`.github/workflows/workflow.yml`) is `install --frozen-lockfile` → `build` → `check` → `test`.
39
39
 
40
- ### Testing instructions
40
+ ### Testing
41
41
 
42
42
  - Run `pnpm check` and `pnpm test` before finishing; fix failures until green.
43
43
  - Add or update tests for the code you change, even if nobody asked, provided each test earns its place (covers behavior, not coverage).
@@ -45,57 +45,15 @@ Focused verification: `stack check --filter <code|formatting|dependency|commit>
45
45
  ## Code style guidelines
46
46
 
47
47
  Formatting is enforced by the shared `@adbayb/stack` presets (re-exported by root `oxlint.config.ts`/`oxfmt.config.ts`): comply via `stack fix`, never hand-format.
48
- For new code, use the `software-design` skill when available (install via `npx skills add adbayb/stack --skill software-design -g` if missing); otherwise follow these defaults. On conflict the skill wins; this list stays compressed by design:
49
-
50
- - KISS (Keep It Simple, Stupid) over cleverness; DRY (Don't Repeat Yourself) at knowledge level (rule of three before abstracting duplicated logic).
51
- - Minimal API surface (YAGNI: You Aren't Gonna Need It): expose only what's needed now; small explicit functions, narrow interfaces.
52
- - Keep symbols local; promote to the lowest shared scope all consumers already depend on only for a second existing consumer (planned consumers and same-package tests excluded; published-API consumers included).
53
- - Exception: a contract both sides of a boundary must agree on may be shared right away. If tooling prevents sharing it, duplicate it and guard it with a test.
54
- - Before exporting across a boundary, check who imports it. Prefer sharing contracts over implementations. Never export a counterpart (e.g. a reader for a writer) ahead of its first consumer.
55
- - POLA (Principle of Least Astonishment): names and behavior as expected; fail fast at boundaries, no surprise side effects.
56
- - High cohesion, low coupling:
57
- - Keep code that changes together, or serves one responsibility, in one place.
58
- - Keep module/slice/package dependencies narrow, intentional, toward more stable lower-level code. No cycles.
59
- - Maintainable, evolvable design: apply the following only when it solves a present need (YAGNI/KISS/rule of three still win; no speculative layers or patterns in small modules).
60
- - SOLID, especially Dependency Inversion (small interfaces, injected implementations), Single Responsibility (one purpose per unit), Open/Closed (extend via composition, not inheritance or branch edits), Interface Segregation (narrow, consumer-shaped interfaces). Liskov when subtyping.
61
- - Design patterns when the problem matches: Adapter (wrap external APIs behind our contract), Facade (simple public API over complex internals), Strategy (swappable behavior per environment/config), Factory (creation varying by config or dependencies), Decorator (add cross-cutting behavior without touching the wrapped unit). Don't name a pattern in code unless it helps readers.
62
- - Hexagonal/ports-and-adapters at I/O boundaries (CLI args, env, files, network, storage, third-party SDKs): pure testable domain logic without I/O; I/O behind ports implemented by edge adapters; dependencies point inward.
63
- - Testing: verify behavior, keep the suite lean.
64
- - Test functional behavior through the public interface, not implementation details (internal calls, private structure, call order unless it is the contract). A refactor that keeps behavior must not break tests.
65
- - Every test must earn its place: never write tests to raise line count or coverage.
66
- - Consolidate overlapping cases (e.g. `test.each`); remove redundant, obsolete or implementation-coupled tests in files you edit, listing removals and reasons in the change summary.
67
- - One behavior per test, named by scenario and expectation; keep tests FIRST (fast, independent, repeatable, self-validating); cover boundaries and failure paths, not just the happy path.
68
- - Code organization: public-facing interface first (exports before implementation in source files, describes/tests before helpers in test files; no use-before-define of `const` helpers at module load). Order top-down (callers above callees); public/exported APIs carry a one-line TSDoc (`/** */`) stating the contract, with `@param`/`@returns` only when the description adds info beyond the types.
69
- - Consistent naming: pick one verb per contract, use everywhere (POLA), never add synonym for something already named.
70
- - Queries (never throw; `undefined`/empty when missing): `get` (lookup by identity), `find` (search by criteria/filter), `exists`/`count`. A throwing lookup needs its own intention verb (e.g. `getOrThrow`). Collections use a plural noun, never an `All` infix (`getOrders`, `findOverdueOrders`, not `getAllOrders`).
71
- - Commands: `create`/`update`/`remove` for lifecycle; intention verbs for domain behavior (`refundOrder`, not `setStatus`).
72
- - Banned → use: `fetch`/`retrieve`/`load`/`read` → `get`, `query`(verb) → `find`/`exists`, `delete`/`clear` → `remove`, `add`/`insert`/`save` → `create`/`update`, `set` → intention verb (DTOs/builders and React state setters exempt), `process`/`handle`/`manage`/`do` → specific intention verb, `data`/`info`/`util` → domain noun. Third-party and platform APIs (e.g. `fetch`) keep their own names. Apply to new and edited code; don't rename existing exported symbols just for this.
73
- - Booleans: `is*`/`has*`/`can*`. Handlers: `on<Event>`.
74
- - Generic utilities: `input`/`...inputs` for domain-less parameters, `output` for domain-less results (instead of `data`/`arg`/`x`); domain meaning always wins (`userId`, never `input`).
75
- - Boundary contracts: bespoke input/output shapes take `<FunctionName>Input` / `<FunctionName>Output`; reuse domain types otherwise, never alias per function.
76
- - Functions verb-first (`findOverdueOrders`), classes/types nouns (`OrderRefunder`), no stutter (`orders.get(id)` not `orderRepo.getOrder`).
77
- - Editing a file: match verbs already used in that slice, don't add a second synonym.
78
- - Type inference: skip explicit return/output types when TypeScript can infer them; annotate only when a stricter type than inferred is wanted (e.g. an enum instead of `string`) or when an exported API contract should be explicit.
79
- - Explicit logic: name the middle steps (explanatory variables, encapsulated boolean conditions); prefer positive conditionals. Every name must earn its place: single-use consts without meaning, await, narrowing, or literal get inlined. Make dependencies physical (import, parameter, injection); never require a call order or rely on ambient execution order. Every default, limit, timeout, and retry count is a named constant with a stated reason at module top.
80
- - Strict types: never silence the type checker (`as any`, `@ts-ignore`/`@ts-expect-error`, non-null `!`); absence is `undefined`, never `null`. Size names to their scope; no type prefixes, encodings, or `Manager`/`Helper`/`Util` holders.
81
- - Error handling: prefer Result objects from `@open-vanilla/result` over throwing for normal control flow; map foreign exceptions to failed Results at the boundary. Expected absence stays `undefined`/empty (queries never throw). New and edited code only; don't migrate existing throw-based code.
82
- - Avoid comments: prefer self-explanatory code; comment only the why (complex logic, non-obvious workflows, deliberately preserved ambiguous patterns). No narration of readable code, no commented-out code.
83
- - Neutral artifacts: generated text (source, docs, comments, commit messages, changelogs) must not contain symbols that identify model contribution.
84
- - Never emit em-dashes (`—`, U+2014) anywhere; restructure the sentence or use a hyphen (`-`), colon, comma, or parentheses instead.
85
- - Never emit a decorative en-dash (`–`, U+2013) or double hyphen; use a hyphen (ranges fine).
86
- - Never emit invisible unicode (U+00A0, U+200B-U+200D, stray BOM); use a regular space or delete.
87
- - Curly quotes and the ellipsis glyph (`…`, U+2026) are fine in prose but forbidden in code, comments, and copy-pasteable samples (they break pasted code).
88
- - Same for other decorative non-ASCII punctuation.
89
- - Apply to text you write or edit; don't rewrite existing files just for this.
90
- - Avoid assumptions: when the approach is ambiguous and a wrong guess is costly to undo, ask before proceeding. Otherwise pick the sensible default and state it.
91
- - Boy Scout Rule: leave code better than found. In edited files, make small, safe, behavior-preserving improvements (no exported renames) and mention them in the change summary. Larger or cross-file cleanup goes in a separate change.
48
+ Design and style rules have one source: the `software-design` skill (SKILL.md plus references). Install it when missing (`pnpm dlx skills add adbayb/stack --skill software-design -g`); on conflict, the skill wins its defaults and this section carries only repo deltas (none today: add a repo-specific design rule here when one emerges). If the skill cannot be installed, follow existing repository conventions and ask before structural changes.
92
49
 
93
50
  ## Security considerations
94
51
 
95
52
  - Never commit secrets (tokens, credentials): use environment variables and keep `.env*` git-ignored. Never log or print secrets; keep them out of error messages.
96
53
  - Validate untrusted input (CLI args, env, files, network) once at the boundary; fail fast with actionable errors.
97
54
  - Never interpolate untrusted input into shell commands: use parameterized exec helpers, never raw shell strings.
98
- - Keep file writes scoped to the narrowest path set; destructive operations (`clean`) fail closed on ambiguous scope and never delete outside it.
55
+ - Keep file writes scoped to the narrowest path set; destructive operations (`clean`) remove only git-ignored paths and generated cache directories, preserve `node_modules` and `.env*`, and refuse paths outside the working directory.
56
+ - Never run `pnpm release:publish` (or any changeset publish) manually: publishing ships versions; keep it to the documented release flow on a clean, CI-green tree.
99
57
  - Pin devDependencies exact (`saveExact: true`, or `workspace:*` for local packages); prefix `dependencies`/`peerDependencies` with a caret (or `workspace:^` for local dependencies: peers stay explicit); `stack check` verifies them: review automated dependency updates (Renovate) before merging.
100
58
 
101
59
  ## PR instructions
@@ -2,5 +2,5 @@
2
2
 
3
3
  A collection of examples to:
4
4
 
5
- - Showcase the artifacts' usage (mainly for libraries)
6
- - Source some scenarios for testing purposes (test cases)
5
+ - Showcase the artifacts' usage (mainly for libraries).
6
+ - Source some scenarios for testing purposes (test cases).