15 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 579 | pi-permission-system: fold access-intent stragglers into src/access-intent/ |
Fold the access-intent stragglers into src/access-intent/
Release Recommendation
Release: ship independently
Phase 11 Step 1 (roadmap) carries Release: independent — it is not a member of the shell-tool-aliases batch (Steps 2–3).
It is a refactor: module move with no behavior change, so it is a hidden changelog type: it lands on main and auto-batches into the next release rather than cutting one on its own.
Problem Statement
The access-intent domain — turning (toolName, input) into "what is being accessed" — is named in the architecture doc's first-principles section and already has a directory (src/access-intent/).
But four of its modules never moved out of the flat src/ root:
src/input-normalizer.tssrc/mcp-targets.tssrc/tool-input-path.tssrc/path-surfaces.ts
That hides the seam the shell-tool-aliasing steps (Phase 11 Step 3, #574) extend, and leaves the flat root at 60 top-level modules.
This is a tidy-first move: Phase 11 Step 3 rewrites input-normalizer.ts and tool-input-path.ts for aliased command/workdir extraction, so relocating them now lets that work land in its final location instead of moving twice.
Goals
- Relocate the four modules under
src/access-intent/, rewriting only their import sites. - Move the four modules' test files into
test/access-intent/to preserve the test-tree mirror (operator-confirmed during planning). - Preserve behavior exactly — the existing (relocated) tests are the regression guard;
tsc+ ESLint catch every missed import. - Update the architecture doc and the package skill to reference the new locations, and mark Phase 11 Step 1 complete.
This change is not breaking: no public export, config default, output shape, or observable behavior changes.
The package's only public exports surface is service.ts; all four modules are internal.
Non-Goals
- Moving
bash-advisory-check.tsintoaccess-intent/. It composes the service with a gate orchestrator, and a domain module must not import fromhandlers/— it deliberately stays in the flat root. - Adding a barrel (
access-intent/index.ts). The repo treats barrel re-export sprawl as a smell; direct imports stay. - Any behavior change, new test, signature change, or renamed export.
- The aliased command/workdir extraction that rewrites
input-normalizer.ts/tool-input-path.ts— that is Phase 11 Step 3 (#574), landing after these modules reach their final location.
Background
- The four modules were created in Phase 7 Step 4 (#505) when the
path-utils.tsgrab-bag was dissolved into cohesive modules; they landed in the flat root rather than underaccess-intent/. - The
#src/*import alias maps to./src/*and covers subdirectories, so a#src/access-intent/<mod>specifier resolves after the move with notsconfig/package.jsonedit. - Existing
access-intent/modules establish the intra-domain import convention: same-directory siblings use a./<sibling>relative import (e.g.access-path.tsimports./path-normalization); cross-directory imports use the#src/<path>alias (e.g.tool-kind.tsimports#src/path-surfaces). This plan follows that convention for the moved files and keeps each non-moving importer's existing style (a./importer gainsaccess-intent/in the path; a#src/importer gainsaccess-intent/). - Two ESLint guards touch this area and are unaffected by the move:
no-restricted-syntaxforbidding interiorprocess.platformreads applies to thepackages/pi-permission-system/src/**/*.tsglob (index.tsexempt); the four modules stay under that glob and none readsprocess.platform.no-restricted-importsforbiddingaccess-pathimports is scoped topermission-manager.tsonly; the move does not add such an import, andpermission-manager's imports ofnormalizeInput/PATH_SURFACESstayAccessPath-free (the ADR-0002 string boundary,docs/decisions/0002-path-values-string-boundary.md).
tool-kind.tsmust stayAccessPath-free (it imports onlyPATH_BEARING_TOOLSfrompath-surfaces) sopermission-manager.tsmay consumeclassifyToolKindwithout breaching the boundary; the move keeps that import (as a sibling./path-surfaces) and adds nothing.
Design Overview
A pure relocation.
Each moved module's cross-directory imports switch to the #src/ alias, its same-directory-sibling imports stay ./, and every importer's specifier gains the access-intent/ segment.
Import rewrites in the moved modules
src/access-intent/input-normalizer.ts (from src/input-normalizer.ts):
import type { AccessIntent } from "./access-intent"; // was ./access-intent/access-intent
import { classifyToolKind } from "./tool-kind"; // was ./access-intent/tool-kind
import { stripBashCommentLines } from "#src/bash-arity"; // was ./bash-arity
import { createMcpPermissionTargets } from "./mcp-targets"; // sibling, unchanged ./
import type { PathNormalizer } from "#src/path-normalizer"; // was ./path-normalizer
import { PATH_SURFACES } from "./path-surfaces"; // sibling, unchanged ./
import { getNonEmptyString, toRecord } from "#src/value-guards"; // was ./value-guards
src/access-intent/mcp-targets.ts (from src/mcp-targets.ts):
import { getNonEmptyString, toRecord } from "#src/value-guards"; // was ./value-guards
src/access-intent/tool-input-path.ts (from src/tool-input-path.ts):
import { classifyToolKind } from "./tool-kind"; // was ./access-intent/tool-kind
import type { ToolAccessExtractorLookup } from "#src/tool-access-extractor-registry"; // was ./tool-access-extractor-registry
import { getNonEmptyString, toRecord } from "#src/value-guards"; // was ./value-guards
src/access-intent/path-surfaces.ts (from src/path-surfaces.ts): no imports; moved verbatim.
Import rewrites in the non-moving importers
#src/ alias importers (gain access-intent/):
src/path/pi-infrastructure-read.ts—#src/path-surfaces→#src/access-intent/path-surfacessrc/handlers/gates/path.ts—#src/tool-input-path→#src/access-intent/tool-input-pathsrc/handlers/gates/tool.ts—#src/path-surfaces→#src/access-intent/path-surfaces;#src/tool-input-path→#src/access-intent/tool-input-pathsrc/handlers/gates/external-directory.ts—#src/tool-input-path→#src/access-intent/tool-input-pathsrc/handlers/gates/tool-call-gate-pipeline.ts—#src/tool-input-path→#src/access-intent/tool-input-path
./ relative importers in the flat root (gain access-intent/):
src/permissions-service.ts—./input-normalizer→./access-intent/input-normalizersrc/rule.ts—./path-surfaces→./access-intent/path-surfacessrc/permission-manager.ts—./input-normalizer→./access-intent/input-normalizer;./path-surfaces→./access-intent/path-surfacessrc/pattern-suggest.ts—./path-surfaces→./access-intent/path-surfacessrc/index.ts—./input-normalizer→./access-intent/input-normalizer
Sibling importer already in access-intent/ (cross-dir alias becomes a ./ sibling):
src/access-intent/tool-kind.ts—#src/path-surfaces→./path-surfaces
Test file moves and import rewrites
Moved into test/access-intent/, #src/ specifiers gain access-intent/:
test/input-normalizer.test.ts→test/access-intent/input-normalizer.test.ts—#src/input-normalizer→#src/access-intent/input-normalizer;#src/mcp-targets→#src/access-intent/mcp-targetstest/mcp-targets.test.ts→test/access-intent/mcp-targets.test.ts—#src/mcp-targets→#src/access-intent/mcp-targetstest/tool-input-path.test.ts→test/access-intent/tool-input-path.test.ts—#src/tool-input-path→#src/access-intent/tool-input-pathtest/path-surfaces.test.ts→test/access-intent/path-surfaces.test.ts—#src/path-surfaces→#src/access-intent/path-surfaces
Already in test/access-intent/ (import rewrite only):
test/access-intent/tool-kind.test.ts—#src/path-surfaces→#src/access-intent/path-surfaces
Edge cases
- The move breaks every importer at the type level simultaneously (the specifier no longer resolves), so all import rewrites for a given module must land in the same commit as its
git mv—tscrejects any intermediate half-moved state. git mvpreserves history/blame across the rename; the diff is pure path movement plus one-line import edits.
Module-Level Changes
Source moves (git mv, then rewrite imports as above):
src/input-normalizer.ts→src/access-intent/input-normalizer.tssrc/mcp-targets.ts→src/access-intent/mcp-targets.tssrc/tool-input-path.ts→src/access-intent/tool-input-path.tssrc/path-surfaces.ts→src/access-intent/path-surfaces.ts
Source importers edited (import specifier only): src/permissions-service.ts, src/rule.ts, src/permission-manager.ts, src/pattern-suggest.ts, src/index.ts, src/access-intent/tool-kind.ts, src/path/pi-infrastructure-read.ts, src/handlers/gates/path.ts, src/handlers/gates/tool.ts, src/handlers/gates/external-directory.ts, src/handlers/gates/tool-call-gate-pipeline.ts.
Test moves + import edits: test/input-normalizer.test.ts, test/mcp-targets.test.ts, test/tool-input-path.test.ts, test/path-surfaces.test.ts (moved into test/access-intent/), and test/access-intent/tool-kind.test.ts (edited in place).
Documentation edits:
docs/architecture/architecture.md:- Prose (currently lines 291–292):
src/mcp-targets.ts→src/access-intent/mcp-targets.ts;src/input-normalizer.ts→src/access-intent/input-normalizer.ts. - Module-layout tree: remove the four flat-root entries (
mcp-targets.ts,input-normalizer.ts,tool-input-path.ts,path-surfaces.ts) and add them under theaccess-intent/subtree, descriptions unchanged. - Mark Phase 11 Step 1 complete:
✅on the#### Step 1heading and on the MermaidS1node in the step dependency diagram. - No health-metric-row edit: the "Flat
src/root modules" row tracks the phase baseline (60) → phase target (≤ 56); the target is met by this step but the row stays as the phase-level tracker, and the recompute command (ls .../src | grep -c '\.ts$') reports 56 after the move.
- Prose (currently lines 291–292):
.pi/skills/package-pi-permission-system/SKILL.md(line 136):src/tool-input-path.ts→src/access-intent/tool-input-path.ts.
No edits to historical records (docs/plans/*, docs/retro/*, docs/architecture/history/*): those are point-in-time and name the modules at their then-current paths.
No eslint.config.js, tsconfig.json, or package.json edit (the #src/* alias and both ESLint guards are unaffected — see Background).
Test Impact Analysis
This is a pure relocation, not an extraction:
- New tests enabled: none — no new seam or collaborator is introduced.
- Tests made redundant: none — every existing test moves verbatim (subject and test relocate together) and stays load-bearing.
- Tests that must stay as-is: all four relocated suites (
input-normalizer,mcp-targets,tool-input-path,path-surfaces) plusaccess-intent/tool-kind.test.ts— they are the behavior-preservation guard; green must stay green across the move.
Invariants at risk
The move touches modules a prior phase step refactored, but relocates them without changing behavior; the existing suites pin each invariant:
- ADR-0002 string boundary (
permission-manager.tsstaysAccessPath-free) — pinned by theno-restricted-importsESLint guard (file-scoped, unaffected) andpermission-manager-unified.test.ts. The move keepspermission-manager's imports (normalizeInput,PATH_SURFACES)AccessPath-free. tool-kind.tsisAccessPath-free (Phase 10 Step 1, #568) — pinned bytest/access-intent/tool-kind.test.ts; the move keeps its sole import (PATH_BEARING_TOOLS) intact as a./path-surfacessibling.- Interior
process.platformban (#510) — pinned by theno-restricted-syntaxESLint guard on thesrc/**glob; the four modules remain under the glob.
TDD Order
This is a green-preserving refactor: move with no red cycle — the relocated tests are the regression guard, and tsc + ESLint prove every import was rewritten.
The next stage is /build-plan (a code-touching but test-cycle-free change), which dispatches the tidy-first-assessor at the start and the pre-completion-reviewer at the end.
Establish a green baseline (pnpm --filter @gotgenes/pi-permission-system run check && pnpm -r run test && pnpm run lint && pnpm fallow dead-code) before starting.
-
Move the four modules and their tests; rewrite all imports.
git mveach of the foursrc/modules intosrc/access-intent/and each of the four test files intotest/access-intent/. Rewrite the moved modules' own imports, all eleven source importers, and all five test imports exactly as listed in Design Overview. This is one atomic commit — the specifiers stop resolving the instant the files move, sotscrejects any partial split. Verify:pnpm --filter @gotgenes/pi-permission-system run check(tsc),pnpm -r run test(full suite green — no test content changed),pnpm run lint,pnpm fallow dead-code, andls packages/pi-permission-system/src | grep -c '\.ts$'reports 56. Commit:refactor(pi-permission-system): fold access-intent stragglers into src/access-intent/ (#579). -
Update the architecture doc and package skill; mark Phase 11 Step 1 complete. Edit
docs/architecture/architecture.md(prose lines, module-layout tree relocation,✅on the Step 1 heading and MermaidS1node) and.pi/skills/package-pi-permission-system/SKILL.md(line 136 path). Verify:pnpm exec rumdl checkon the edited docs; confirm no other current-doc reference to the flat-root paths remains (grep -rnoverdocs/architecture/architecture.mdand the skill, excludinghistory/). Commit:docs(pi-permission-system): relocate access-intent stragglers in docs; mark Phase 11 Step 1 (#579).
Risks and Mitigations
- A missed importer.
tscfails on any unresolved specifier and ESLint flags stale relative imports, so a miss cannot compile. The Design Overview enumerates every importer from an exhaustivegrepoversrc/andtest/(bare module names, catching both#src/and./styles per the #559 lesson). - A dynamic or string reference the grep missed.
None exist: the four modules have no
package.jsonexportsentry (onlyservice.tsis public) and no dynamicimport(); the grep covered all.tsundersrc//test/. - Stale doc reference to a flat-root path.
Step 2 greps
architecture.mdand the skill after editing; historicaldocs/plans/docs/retro/docs/architecture/historyare intentionally left as point-in-time records. - Intra-domain import-cycle introduction.
None — the move changes only specifier paths, not the dependency graph;
input-normalizerstill importsmcp-targets/path-surfaces/tool-kind(now siblings), with no new edge.
Open Questions
None.