feat: vendor permission system source

This commit is contained in:
云服务部-叶林立
2026-08-19 14:35:19 +08:00
parent 198584daf8
commit 410c50a3e5
809 changed files with 157793 additions and 139 deletions
@@ -0,0 +1,100 @@
---
issue: 32
issue_title: "Drop .js extensions from all internal imports"
---
# Drop `.js` extensions from all internal imports
## Problem Statement
All internal imports in `src/` and `tests/` carry explicit `.js` extensions (e.g. `from "./common.js"`).
This convention is required by `moduleResolution: "Node16"` / `"NodeNext"`, but the project uses `moduleResolution: "Bundler"` with `noEmit: true`, so the extensions serve no purpose.
They add noise to every new file and are a common source of copy-paste mistakes.
## Goals
- Strip `.js` from every relative import path in `src/` and `tests/` (~116 occurrences across ~30 files).
- Add a guard so `.js` extensions do not creep back in.
- Verify `npx vitest run` and `npm run build` both pass after the change.
## Non-Goals
- No behavior change to the permission system.
- No changes to `package.json`, `tsconfig.json`, or the public API.
- No changes to imports of `node:*` builtins or bare package specifiers (only relative `./` and `../` paths).
## Background
- `tsconfig.json` uses `"module": "ESNext"` and `"moduleResolution": "Bundler"` — both resolve extensionless relative imports natively.
- Vitest transforms via esbuild, which also resolves extensionless imports.
- `tsc` is `"noEmit": true` — Node's strict-ESM extension rule never fires.
- **Runtime loading**: Pi loads extensions via `@mariozechner/jiti` (a fork of `unjs/jiti`), which uses esbuild internally and resolves extensionless `.ts` imports the same way Vitest does.
Empirically verified: stripping `.js` from `src/permission-manager.ts` imports passes both `npm run build` and `npx vitest run` with no changes.
- Biome has `useImportExtensions` (enforces extensions), which is the opposite of what we want.
There is no built-in Biome rule to *ban* extensions, so prevention uses a lint script.
No permission surface is involved — this is a purely mechanical code-quality change.
## Design Overview
### Step 1: Mechanical find-and-replace
Use `sed` or a script to strip `.js` from every relative import in `src/**/*.ts` and `tests/**/*.ts`:
```text
from "./foo.js" → from "./foo"
from "../bar.js" → from "../bar"
```
Only touch relative paths (starting with `./` or `../`).
Do not touch `node:*`, bare specifiers, or type-only imports (they follow the same rule, so strip those too).
### Step 2: Prevention guard
Add a `lint:imports` npm script that greps for `.js"` in relative imports and fails if any are found:
```jsonc
"lint:imports": "! grep -rn --include='*.ts' 'from \"\\.\\{1,2\\}/.*\\.js\"' src/ tests/"
```
Wire it into `lint:all` so `npm run check` catches regressions.
### Step 3: Verify
Run `npm run build` (tsc) and `npm run test` (vitest).
Both must pass with zero changes beyond the import paths.
## Module-Level Changes
| File / glob | Change |
| --------------------------- | ----------------------------------------------- |
| `src/**/*.ts` (~20 files) | Strip `.js` from relative import paths |
| `tests/**/*.ts` (~10 files) | Strip `.js` from relative import paths |
| `package.json` | Add `lint:imports` script; append to `lint:all` |
No schema, config, or documentation changes required.
## TDD Order
This change is mechanical with no new logic, so a classic red→green cycle is lightweight:
1. **Green: strip extensions in `src/`** — run `npm run build` to confirm tsc still passes.
Commit: `refactor: drop .js extensions from src/ imports (#32)`
2. **Green: strip extensions in `tests/`** — run `npx vitest run` to confirm tests still pass.
Commit: `refactor: drop .js extensions from tests/ imports (#32)`
3. **Guard: add lint:imports script** — add the grep guard and verify it passes on the cleaned tree and fails if a `.js` extension is reintroduced.
Commit: `chore: add lint:imports guard against .js extensions (#32)`
Alternatively, steps 12 can be a single commit since the change is atomic and trivially reversible.
## Risks and Mitigations
| Risk | Mitigation |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Missed an import that actually needs `.js` (e.g. a non-TS asset) | Only strip from `*.ts` files with relative paths; `npm run build` and `npm run test` catch any resolution failures immediately. |
| Could this silently weaken a permission? | No — this change touches only import specifier strings, not runtime behavior or policy resolution. |
| `lint:imports` grep is too broad / too narrow | The pattern targets `from ".<relative>.js"` only; tested against the cleaned tree and a synthetic reintroduction before committing. |
## Open Questions
None — the issue's proposed change is unambiguous and the project's toolchain already supports extensionless imports.