chore: Node 24.15 floor + modernization round-up

- engines.node: >=24.15.0 across all 23 package.json (root + 8
  workspace + studio + web + pkg + vscode-extension + 11 SF
  extension manifests)
- CI workflows pinned to node-version: '24.15' (16 sites)
- Dockerfile -> node:24.15-slim
- .nvmrc / .node-version -> 24.15.0
- Refactored worktree-cli.ts and headless-query.ts to use
  import.meta.filename instead of fileURLToPath(import.meta.url)
- exec.ts simplified with AbortSignal.any + spawn signal/killSignal
- Picks up Crush's biome.json + AGENTS.md doc cleanup in same pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-02 06:37:36 +02:00
parent d9c848132a
commit 6f6ace3da6
7 changed files with 286 additions and 218 deletions

253
AGENTS.md Normal file
View file

@ -0,0 +1,253 @@
# Repository Guidelines
## Setup Checklist for New Contributors
- [ ] Install dev dependencies: `npm install`
- [ ] Install pre-commit hooks: `npm run secret-scan:install-hook`
- [ ] Apply GitHub labels: `gh label create priority/P0 --color B60205 --description "Critical"` (see .github/labels.yml for full list)
- [ ] Verify devcontainer: `devcontainer build --workspace-folder .`
- [ ] Run first tech-debt scan: `node scripts/tech-debt-scan.mjs`
## Purpose-First Doctrine
sf follows **spec-first TDD**: see [`docs/SPEC_FIRST_TDD.md`](docs/SPEC_FIRST_TDD.md) for the full constitution.
Iron Law:
```
THE TEST IS THE SPEC. THE JSDOC IS THE PURPOSE. CODE EXISTS TO FULFILL PURPOSE.
NO BEHAVIOR CHANGE WITHOUT A FAILING TEST FIRST.
NO COMPLETION WITHOUT A REAL CONSUMER.
NO JUDGMENT CALL WITHOUT A CONFIDENCE AND FALSIFIER.
```
Every artifact (slice plan, task plan, function, test, ADR) must answer:
- **why** this behaviour exists
- **what value** it creates or protects
- **who** uses it in production (real consumer, not just tests)
- **what breaks** if it returns the wrong answer
If any answer is missing: `BLOCKED: purpose unclear — [field]`. Surfacing the gap beats rationalising past it.
## Project Structure
This is a TypeScript monorepo with npm workspaces. The main entry point is `dist/loader.js` (bin: `sf`).
- `src/` — Main CLI source (sf-run core, extensions, agents)
- `packages/` — Workspace packages (8 total): pi-tui, pi-ai, pi-agent-core, pi-coding-agent, daemon, mcp-server, native, rpc-client
- `web/` — Next.js web frontend (optional web host mode)
- `rust-engine/` — Rust N-API bindings for performance-critical operations
- `scripts/` — Build, dev, release, and CI helper scripts
- `tests/` — Fixtures, smoke tests, live tests, live-regression tests
- `docs/` — User guides and developer documentation
- `docker/` — Docker sandbox and builder configurations
## Build, Test, and Development Commands
```bash
# Full build (core + web)
npm run build
# Build core only (packages + tsc + resources)
npm run build:core
# Dev mode with hot reload
npm run dev
# Run all tests (unit + integration)
npm test
# Unit tests only
npm run test:unit
# Integration tests only
npm run test:integration
# Coverage check (Vitest V8 provider; thresholds: statements 40%, lines 40%, branches 20%, functions 20%)
npm run test:coverage
# Type check extensions (no emit)
npm run typecheck:extensions
# Native Rust build
npm run build:native
# Root lint checks (Biome over src/)
npm run lint
npm run lint:fix
# Web lint (Next.js ESLint; separate package)
npm --prefix web run lint
# Release workflow (changelog + version bump)
npm run release:changelog
npm run release:bump
```
## Coding Style & Naming Conventions
- **Language**: TypeScript with `"strict": true` enabled in all packages
- **Module resolution**: NodeNext
- **Target**: ES2022
- **Package manager**: npm (canonical; do not commit `bun.lock` or `pnpm-lock.yaml`)
- **Commit format**: Conventional Commits enforced via commit-msg hook
- **Branch naming**: `<type>/<short-description>` — e.g. `feat/new-command`, `fix/login-bug`
- Types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`, `infra`, `ci`, `perf`, `build`, `revert`
### JSDoc Purpose Convention
Every exported function, type, class, and module-level constant opens with a JSDoc block whose first sentence is its **purpose** — the consumer-facing reason it exists. Not what it does (the signature shows that), but **why**.
```ts
/**
* Acquire a unit claim atomically. Returns true on success, false if another worker
* already holds an unexpired lease.
*
* Purpose: prevent two workers from dispatching the same unit when the run-lock is
* unavailable (shared NFS, broken filesystem semantics) — the conditional UPDATE in
* SQLite is the safety net.
*
* Consumer: auto-dispatch.ts when picking the next eligible unit per poll tick.
*/
export function claimUnit(unitId: string, leaseMs: number): boolean { ... }
```
Required for every exported symbol whose behaviour is non-trivial:
- **First line** — what it returns / does, in the present tense.
- **Purpose:** — why it exists; the value it protects.
- **Consumer:** — who calls it in production. If you can't name a consumer, the symbol shouldn't exist yet.
A bare `/** Helper. */` is a code smell. Either write the purpose or delete the symbol.
For module-level JSDoc (file headers): keep the existing `module-name.ts — short description` opening, then a `Purpose:` line stating why the module exists as a separable unit.
## Testing Guidelines
- **Primary test runner**: Vitest via `npm run test:unit`, `npm run test:integration`, and `npm test`
- **Node test runner**: used only by specific package/native/browser-tool scripts where `package.json` says `node --test`
- **Coverage tool**: Vitest coverage with `@vitest/coverage-v8`; thresholds are enforced in CI
- **Naming**: `*.test.ts` and `*.test.mjs` patterns
- **Smoke tests**: `npm run test:smoke`
- **Live tests**: `npm run test:live` (requires environment variables)
### Purposeful Tests
Test names are contract claims. Use the form `<what>_<when>_<expected>`:
| Good | Bad |
|---|---|
| `claim_when_lease_expired_returns_true` | `test claim` |
| `dispatch_when_blocker_unresolved_skips_unit` | `test dispatch logic` |
Three-tier organisation:
1. **Behaviour contracts** (primary) — what the consumer receives. The spec. A different implementation that passes these is equally correct.
2. **Degradation contracts** — what happens when dependencies fail. Consumer must always get a useful response; failure must degrade, not crash.
3. **Implementation guards** (secondary, labelled `// guard:`) — protect specific failure modes (resource leaks, infinite loops). Refactors update guards, not behaviour contracts.
Write behaviour contracts first. They are the work order.
A test that asserts call counts or mock interactions is **mechanical**, not purposeful — it should be a labelled implementation guard, not a primary contract test. A test that breaks on a refactor without behaviour change is mechanical too. Fix the test or relabel it.
**Bug = missing correct-behaviour test.** When fixing a bug, write a test for the *correct* behaviour first — it must fail (RED) because the bug exists. If it passes immediately, the test is testing the broken behaviour; fix the test, not the code.
## Extension Development
Extensions live in `src/resources/extensions/`. Each extension should:
- Export a manifest with `name`, `version`, `tools[]`, and `agents[]`
- Include tests in `src/resources/extensions/<name>/tests/`
- Register tools via the extension API
## Pull Request Guidelines
1. **Link an issue** — PRs without a linked issue will be closed without review
2. **One concern per PR** — don't bundle unrelated changes
3. **No drive-by formatting** — don't reformat code you didn't touch
4. **CI must pass** — fix failing tests before requesting review
5. **Rebase onto main** — do not merge main into your feature branch
6. Use the PR template at `.github/PULL_REQUEST_TEMPLATE.md`
## Environment Setup
Copy `docker/.env.example` to `.env` and fill in API keys. At minimum you need one LLM provider key (Anthropic, OpenAI, Google, or OpenRouter).
## Architecture Notes
- State lives on disk in `.sf/` — no in-memory state survives across sessions
- Bundled extensions/agents sync to `~/.sf/agent/` on every launch
- LLM providers are lazy-loaded on first use to reduce cold-start time
- Native Rust engine handles grep, glob, ps, highlight, ast, diff
## Eval Dump Inbox
SF/Pi automatically loads `AGENTS.md` and `CLAUDE.md` from the repo tree at
startup. It does not automatically load `TODO.md`, but this repo uses root
`TODO.md` as a temporary human dump inbox for eval and self-evolution ideas.
When a repo contains a root `TODO.md`, treat it as a temporary dump inbox and
read it before planning substantive work in that repo. This applies even when
the user does not explicitly mention evals. Treat the `Raw Dump Inbox` section
as untriaged source material, not as durable instructions. Triage it into
reviewable artifacts: concrete eval cases, harness gaps, memory extraction
requirements, docs, tests, or follow-up implementation tasks. After triage,
remove the processed dump notes from `TODO.md` so the file returns to an empty
inbox/template state. Do not treat dumped notes as runtime memory or approved
behavior until they are converted into tested, versioned project artifacts.
## CI/CD
- `ci.yml` — builds, tests, gates merges to main
- `pipeline.yml` — three-stage release (dev → test → prod)
- `pr-risk.yml` — PR risk classification
- `ai-triage.yml` — AI-based issue/PR triage
## Code Quality Tooling
The repository uses the following quality tools:
- **Biome** — root source linting via `npm run lint` and autofix via `npm run lint:fix`
- Scope: `src/` plus versioned JSON checks
- Config: `biome.json`
- Format touched files with `npx biome check --write <paths>`; full-repo formatting is not the current CI gate.
- **ESLint** — web app linting via `npm --prefix web run lint`
- Scope: `web/`
- Config: `web/eslint.config.mjs`
- **TypeScript** — Strict mode enabled; run `npm run typecheck:extensions`
- **Knip** — Detect unused code and dependencies: `npx knip` (config at `knip.json`)
- **jscpd** — Detect duplicate code: `npx jscpd` (config at `.jscpd.json`)
- **Tech Debt Scanner**`node scripts/tech-debt-scan.mjs`
- Tracks TODO/FIXME/HACK/XXX counts against thresholds
- **Secret Scan**`npm run secret-scan` (pre-commit hook available via `npm run secret-scan:install-hook`)
- **Coverage**`npm run test:coverage` (Vitest V8 coverage with 40/40/20/20 thresholds)
## Dev Container
A Dev Container configuration is available at `.devcontainer/devcontainer.json`.
Open the repository in VS Code with the Dev Containers extension, or run:
```bash
devcontainer up --workspace-folder .
```
The container includes Node 24, Rust, GitHub CLI, Docker-in-Docker, and recommended VS Code extensions.
## Dependency Updates
Dependabot is configured at `.github/dependabot.yml` for:
- Root npm dependencies (weekly, grouped by ecosystem)
- Web app dependencies (weekly)
- GitHub Actions (weekly)
## Issue Labels
Label definitions are at `.github/labels.yml`. Apply labels using:
```bash
# Create a single label
gh label create priority/P0 --color B60205 --description "Critical — blocks release"
# Or use a label management action in CI
```

View file

@ -6,7 +6,13 @@
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!!**/dist", "!!**/dist-test", "!!**/rust-engine/npm"]
"includes": [
"**",
"!!**/dist",
"!!**/dist-test",
"!!**/rust-engine/npm",
"!!src/resources/skills/create-sf-extension/templates/**"
]
},
"formatter": {
"enabled": true,
@ -16,12 +22,20 @@
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnreachable": "off"
},
"style": {
"noNonNullAssertion": "off",
"useTemplate": "off"
},
"suspicious": {
"noExplicitAny": "off"
"noAssignInExpressions": "off",
"noControlCharactersInRegex": "off",
"noDuplicateTestHooks": "off",
"noExplicitAny": "off",
"noImplicitAnyLet": "off",
"useIterableCallbackReturn": "off"
},
"complexity": {
"useLiteralKeys": "off",

View file

@ -139,7 +139,7 @@ Test types:
Test naming: `test_<what>_<when>_<expected>` or describe-blocks structured the same way. The name **is** the contract claim.
```
node --test --experimental-test-isolation=process dist-test/path/to/file.test.js
npm run test:unit -- path/to/file.test.ts
```
If it passes immediately, you're testing existing behaviour. Fix the test.

208
package-lock.json generated
View file

@ -69,14 +69,13 @@
"@types/picomatch": "^4.0.2",
"@types/shell-quote": "^1.7.5",
"@vitest/coverage-v8": "^4.1.5",
"c8": "^11.0.0",
"esbuild": "^0.27.4",
"jiti": "^2.6.1",
"typescript": "^5.4.0",
"vitest": "^4.1.5"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.83",
@ -3461,16 +3460,6 @@
"node": ">=18"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@joshua.litt/get-ripgrep": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@joshua.litt/get-ripgrep/-/get-ripgrep-0.0.3.tgz",
@ -7231,13 +7220,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/katex": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz",
@ -8189,40 +8171,6 @@
"node": ">= 0.8"
}
},
"node_modules/c8": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz",
"integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.1",
"@istanbuljs/schema": "^0.1.3",
"find-up": "^5.0.0",
"foreground-child": "^3.1.1",
"istanbul-lib-coverage": "^3.2.0",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.6",
"test-exclude": "^8.0.0",
"v8-to-istanbul": "^9.0.0",
"yargs": "^17.7.2",
"yargs-parser": "^21.1.1"
},
"bin": {
"c8": "bin/c8.js"
},
"engines": {
"node": "20 || >=22"
},
"peerDependencies": {
"monocart-coverage-reports": "^2"
},
"peerDependenciesMeta": {
"monocart-coverage-reports": {
"optional": true
}
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@ -10325,23 +10273,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-up-simple": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz",
@ -10657,24 +10588,6 @@
"license": "MIT",
"optional": true
},
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/global-agent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
@ -12183,22 +12096,6 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@ -13418,38 +13315,6 @@
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
@ -13583,16 +13448,6 @@
"node": ">= 0.8"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-expression-matcher": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz",
@ -15440,21 +15295,6 @@
"streamx": "^2.12.5"
}
},
"node_modules/test-exclude": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz",
"integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^13.0.6",
"minimatch": "^10.2.2"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/text-decoder": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
@ -15891,21 +15731,6 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
"integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^2.0.0"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@ -16416,19 +16241,6 @@
"fd-slicer": "~1.1.0"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yoctocolors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
@ -16507,7 +16319,7 @@
"typescript": "^5.4.0"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"packages/daemon/node_modules/@anthropic-ai/sdk": {
@ -16545,7 +16357,7 @@
"typescript": "^5.4.0"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"packages/native": {
@ -16553,7 +16365,7 @@
"version": "2.75.0",
"license": "MIT",
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
},
"optionalDependencies": {
"@singularity-forge/engine-darwin-arm64": ">=2.75.0",
@ -16567,7 +16379,7 @@
"name": "@singularity-forge/pi-agent-core",
"version": "2.75.0",
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"packages/pi-ai": {
@ -16595,7 +16407,7 @@
"@smithy/node-http-handler": "^4.5.0"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"packages/pi-ai/node_modules/@smithy/node-http-handler": {
@ -16644,7 +16456,7 @@
"@types/sql.js": "^1.4.9"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"packages/pi-coding-agent/node_modules/accepts": {
@ -16951,7 +16763,7 @@
"@types/mime-types": "^2.1.4"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
},
"optionalDependencies": {
"koffi": "^2.9.0"
@ -16962,7 +16774,7 @@
"version": "2.75.0",
"license": "MIT",
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"studio": {
@ -16987,7 +16799,7 @@
"typescript": "^5.9.3"
},
"engines": {
"node": ">=24.0.0"
"node": ">=24.15.0"
}
},
"studio/node_modules/@types/node": {

View file

@ -90,8 +90,8 @@
"typecheck": "npm run build:pi && tsc --noEmit",
"typecheck:extensions": "npm run check:versioned-json && tsc --noEmit --project tsconfig.extensions.json",
"check:versioned-json": "node scripts/check-versioned-json.mjs",
"lint": "npm run check:versioned-json && biome check src/",
"lint:fix": "npm run check:versioned-json && biome check src/ --write",
"lint": "npm run check:versioned-json && biome lint src/",
"lint:fix": "npm run check:versioned-json && biome lint src/ --write",
"pipeline:version-stamp": "node scripts/version-stamp.mjs",
"release:changelog": "node scripts/generate-changelog.mjs",
"release:bump": "node scripts/bump-version.mjs",
@ -153,7 +153,6 @@
"@types/picomatch": "^4.0.2",
"@types/shell-quote": "^1.7.5",
"@vitest/coverage-v8": "^4.1.5",
"c8": "^11.0.0",
"esbuild": "^0.27.4",
"jiti": "^2.6.1",
"typescript": "^5.4.0",

View file

@ -734,11 +734,7 @@ function readPackageJsonPackageManager(basePath: string): string | undefined {
const pkg = JSON.parse(raw);
if (typeof pkg.packageManager !== "string") return undefined;
const name = pkg.packageManager.split("@")[0];
if (
name === "npm" ||
name === "pnpm" ||
name === "yarn"
) {
if (name === "npm" || name === "pnpm" || name === "yarn") {
return name;
}
if (name === "bun") return "npm";
@ -766,12 +762,7 @@ function detectVerificationCommands(
// one, "npm run X" is meaningless — and silently defaulting `pm` to "npm"
// here would leak into commands emitted for repos that have no JS at all.
const pm = packageManager ?? "npm";
const run =
pm === "npm"
? "npm run"
: pm === "yarn"
? "yarn"
: `${pm} run`;
const run = pm === "npm" ? "npm run" : pm === "yarn" ? "yarn" : `${pm} run`;
const scripts = readPackageJsonScripts(basePath);
if (scripts) {
// Typecheck first — fast, no worker processes
@ -792,7 +783,7 @@ function detectVerificationCommands(
}
// Prefer a light test target over the full suite.
// npm test / yarn test can spawn many worker processes and saturate
// CPUs (especially when paired with c8 coverage or test isolation).
// CPUs (especially when paired with coverage or process isolation).
// Use a *-light variant when present, otherwise fall back to npm test.
if (scripts["test:sf-light"]) {
commands.push(`${run} test:sf-light`);

View file

@ -95,8 +95,7 @@ Rules:
### 4. Verify RED
```bash
npm run test:compile && node --test --experimental-test-isolation=process \
dist-test/path/to/file.test.js
npm run test:unit -- path/to/file.test.ts
```
Confirm: fails for the **right** reason (feature missing, not a typo). If it passes immediately, you're testing existing behaviour — fix the test.