mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
---
|
||||
issue: 110
|
||||
issue_title: "refactor: split external-directory.ts into focused modules"
|
||||
---
|
||||
|
||||
# Split external-directory.ts into focused modules
|
||||
|
||||
## Problem Statement
|
||||
|
||||
`src/external-directory.ts` is ~760 lines bundling four unrelated concerns: global node_modules discovery, path classification utilities, prompt/reason message formatting, and a tree-sitter bash parser with AST walker.
|
||||
These concerns have no coupling to each other and should be independently testable, loadable, and changeable.
|
||||
|
||||
Issue #109 already extracted `normalizePathForComparison` and `isPathWithinDirectory` into `src/path-utils.ts`, but the remaining path-classification helpers, discovery logic, message formatters, and the entire tree-sitter parser still live in the monolithic file.
|
||||
|
||||
## Goals
|
||||
|
||||
- Split `src/external-directory.ts` into three new modules (path-classification helpers extend the existing `src/path-utils.ts` from #109).
|
||||
- Preserve all existing behavior — pure extraction refactoring, no logic changes.
|
||||
- Keep `src/external-directory.ts` as a barrel re-export so downstream imports can migrate incrementally.
|
||||
- Enable independent testing of each concern without pulling in heavy dependencies (tree-sitter, child_process).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Changing any permission logic or policy semantics.
|
||||
- Re-deduplicating `normalizePathForComparison` / `isPathWithinDirectory` — already completed in #109.
|
||||
|
||||
## Background
|
||||
|
||||
The file touches the `external_directory` permission surface.
|
||||
Current consumers of `src/external-directory.ts`:
|
||||
|
||||
| Consumer | Imports used |
|
||||
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `src/runtime.ts` | `discoverGlobalNodeModulesRoot` |
|
||||
| `src/handlers/gates/external-directory.ts` | `formatExternalDirectoryAskPrompt`, `formatExternalDirectoryDenyReason`, `formatExternalDirectoryUserDeniedReason`, `getPathBearingToolPath`, `isPathOutsideWorkingDirectory`, `isPiInfrastructureRead` (note: `normalizePathForComparison` already imports from `path-utils`) |
|
||||
| `src/handlers/gates/bash-external-directory.ts` | `extractExternalPathsFromBashCommand`, `formatBashExternalDirectoryAskPrompt`, `formatBashExternalDirectoryDenyReason`, `formatExternalDirectoryHardStopHint` |
|
||||
| `src/handlers/gates/tool.ts` | `PATH_BEARING_TOOLS` |
|
||||
| `src/handlers/gates/skill-read.ts` | `normalizePathForComparison` (already imports from `path-utils`) |
|
||||
|
||||
Test files:
|
||||
|
||||
- `tests/external-directory.test.ts` — tests path utils, discovery, messages, and constants.
|
||||
- `tests/bash-external-directory.test.ts` — tests tree-sitter path extraction.
|
||||
- `tests/handlers/gates/external-directory.test.ts` — gate-level integration.
|
||||
- `tests/handlers/gates/bash-external-directory.test.ts` — gate-level integration.
|
||||
|
||||
## Design Overview
|
||||
|
||||
Split into four new modules, each owning one concern:
|
||||
|
||||
### `src/node-modules-discovery.ts`
|
||||
|
||||
- `walkUpToNodeModules()` (internal)
|
||||
- `discoverGlobalNodeModulesViaSubprocess()` (internal)
|
||||
- `discoverGlobalNodeModulesRoot()` (exported)
|
||||
|
||||
Dependencies: `node:child_process`, `node:fs`, `node:path`, `node:url`.
|
||||
|
||||
### `src/path-utils.ts` (extend existing)
|
||||
|
||||
Issue #109 already created this file with `normalizePathForComparison` and `isPathWithinDirectory`.
|
||||
Add the remaining path-classification helpers:
|
||||
|
||||
- `isPathOutsideWorkingDirectory()` (moved from `external-directory.ts`)
|
||||
- `getPathBearingToolPath()` (moved from `external-directory.ts`)
|
||||
- `isPiInfrastructureRead()` (moved from `external-directory.ts`)
|
||||
- `isSafeSystemPath()` (moved from `external-directory.ts`)
|
||||
- `SAFE_SYSTEM_PATHS` (moved from `external-directory.ts`)
|
||||
- `PATH_BEARING_TOOLS` (moved from `external-directory.ts`)
|
||||
- `READ_ONLY_PATH_BEARING_TOOLS` (moved from `external-directory.ts`)
|
||||
|
||||
Dependencies: adds `node:path` (for `join`), `./common` (for `getNonEmptyString`, `toRecord`) to existing `node:os`, `node:path`.
|
||||
|
||||
### `src/external-directory-messages.ts`
|
||||
|
||||
- `formatExternalDirectoryHardStopHint()` (exported)
|
||||
- `formatExternalDirectoryAskPrompt()` (exported)
|
||||
- `formatExternalDirectoryDenyReason()` (exported)
|
||||
- `formatExternalDirectoryUserDeniedReason()` (exported)
|
||||
- `formatBashExternalDirectoryAskPrompt()` (exported)
|
||||
- `formatBashExternalDirectoryDenyReason()` (exported)
|
||||
|
||||
Dependencies: none (pure string builders).
|
||||
|
||||
### `src/bash-path-extractor.ts`
|
||||
|
||||
- All tree-sitter types (`TSNode`, `TSParser`)
|
||||
- Parser lifecycle (`initParser`, `getParser`, `resetParserForTesting`)
|
||||
- AST walker (`resolveNodeText`, `collectPathCandidateTokens`, `collectPatternCommandTokens`)
|
||||
- Pattern-first command config (`PATTERN_FIRST_COMMANDS`, `SKIP_SUBTREE_TYPES`, etc.)
|
||||
- Token classification (`classifyTokenAsPathCandidate`, `URL_PATTERN`, `REGEX_METACHAR_PATTERN`)
|
||||
- `extractExternalPathsFromBashCommand()` (exported)
|
||||
|
||||
Dependencies: `web-tree-sitter`, `tree-sitter-bash`, `node:module`, `node:path`.
|
||||
Imports `normalizePathForComparison`, `isPathOutsideWorkingDirectory` from `./path-utils`.
|
||||
|
||||
### `src/external-directory.ts` (barrel)
|
||||
|
||||
Becomes a thin barrel that re-exports everything from the three new modules plus `./path-utils`.
|
||||
All existing consumer imports continue to work unchanged.
|
||||
Downstream consumers can optionally migrate to direct imports in follow-up work.
|
||||
|
||||
## Module-Level Changes
|
||||
|
||||
### New files
|
||||
|
||||
| File | Contents |
|
||||
| ------------------------------------ | --------------------------------------------------------------------- |
|
||||
| `src/node-modules-discovery.ts` | Global node_modules resolution |
|
||||
| `src/external-directory-messages.ts` | 6 `format*` pure string builders |
|
||||
| `src/bash-path-extractor.ts` | Tree-sitter parser, AST walker, `extractExternalPathsFromBashCommand` |
|
||||
|
||||
### Changed files
|
||||
|
||||
| File | Change |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `src/path-utils.ts` | Add remaining path-classification helpers (`isPathOutsideWorkingDirectory`, `getPathBearingToolPath`, `isPiInfrastructureRead`, `isSafeSystemPath`, `SAFE_SYSTEM_PATHS`, `PATH_BEARING_TOOLS`, `READ_ONLY_PATH_BEARING_TOOLS`) |
|
||||
| `src/external-directory.ts` | Replace implementation with barrel re-exports from three new modules plus `path-utils` |
|
||||
|
||||
### New test files
|
||||
|
||||
| File | Contents |
|
||||
| ------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `tests/node-modules-discovery.test.ts` | Extracted from `tests/external-directory.test.ts` — discovery tests |
|
||||
| `tests/external-directory-messages.test.ts` | Extracted from `tests/external-directory.test.ts` — message format tests |
|
||||
|
||||
### Changed test files
|
||||
|
||||
| File | Change |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `tests/path-utils.test.ts` | Add tests for newly moved helpers (`isPathOutsideWorkingDirectory`, `getPathBearingToolPath`, `isPiInfrastructureRead`, `isSafeSystemPath`, constants) |
|
||||
| `tests/external-directory.test.ts` | Reduced to a thin smoke test verifying the barrel re-exports work, or deleted entirely if all tests migrate |
|
||||
| `tests/bash-external-directory.test.ts` | Update import from `../src/external-directory` to `../src/bash-path-extractor` (or keep barrel import) |
|
||||
|
||||
### No changes needed
|
||||
|
||||
| File | Reason |
|
||||
| ------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||
| `tests/handlers/gates/external-directory.test.ts` | Imports from gate module, not from `external-directory.ts` directly |
|
||||
| `tests/handlers/gates/bash-external-directory.test.ts` | Same — imports from gate module |
|
||||
| `src/handlers/gates/*` | All import from `../../external-directory` barrel, which continues to work |
|
||||
| `src/runtime.ts` | Imports `discoverGlobalNodeModulesRoot` from barrel |
|
||||
| `schemas/permissions.schema.json` | No schema changes |
|
||||
| `config/config.example.json` | No config changes |
|
||||
|
||||
### Architecture docs
|
||||
|
||||
| File | Action |
|
||||
| ------------------------------------------ | ------------------------------------------------------------------------------ |
|
||||
| `docs/architecture/target-architecture.md` | Check if it references `external-directory.ts` and update to reflect the split |
|
||||
|
||||
## Test Impact Analysis
|
||||
|
||||
1. **New unit tests enabled by extraction:**
|
||||
- `tests/node-modules-discovery.test.ts` can mock `child_process` and `fs` without affecting path tests.
|
||||
- `tests/path-utils.test.ts` gains path-classification tests that run without tree-sitter or subprocess mocks (file already exists from #109).
|
||||
- `tests/external-directory-messages.test.ts` needs zero mocks — pure string assertions.
|
||||
|
||||
2. **Existing tests that become redundant:**
|
||||
- `tests/external-directory.test.ts` currently tests all four concerns in one file with shared mocks.
|
||||
After extraction, its individual test blocks migrate to the new/extended test files.
|
||||
The barrel file itself needs only a re-export smoke test (or can be deleted).
|
||||
|
||||
3. **Existing tests that must stay as-is:**
|
||||
- `tests/bash-external-directory.test.ts` — exercises `extractExternalPathsFromBashCommand` end-to-end with real tree-sitter.
|
||||
- `tests/handlers/gates/*.test.ts` — gate-level integration, unaffected by internal splits.
|
||||
|
||||
## TDD Order
|
||||
|
||||
### Step 1 — Extract `node-modules-discovery.ts` with tests
|
||||
|
||||
1. Create `src/node-modules-discovery.ts` with the discovery functions.
|
||||
2. Create `tests/node-modules-discovery.test.ts` by extracting `discoverGlobalNodeModulesRoot` tests from `tests/external-directory.test.ts`.
|
||||
3. Update `src/external-directory.ts` to import-and-re-export from `./node-modules-discovery`.
|
||||
4. Remove the original implementation from `external-directory.ts`.
|
||||
5. Verify all tests pass.
|
||||
|
||||
Commit: `refactor: extract node-modules-discovery module (#110)`
|
||||
|
||||
### Step 2 — Move remaining path helpers into `path-utils.ts`
|
||||
|
||||
1. Move `isPathOutsideWorkingDirectory`, `getPathBearingToolPath`, `isPiInfrastructureRead`, `isSafeSystemPath`, `SAFE_SYSTEM_PATHS`, `PATH_BEARING_TOOLS`, `READ_ONLY_PATH_BEARING_TOOLS` from `external-directory.ts` into `src/path-utils.ts`.
|
||||
2. Move corresponding tests from `tests/external-directory.test.ts` into `tests/path-utils.test.ts`.
|
||||
3. Update `src/external-directory.ts` to re-export the moved symbols from `./path-utils`.
|
||||
4. Verify all tests pass.
|
||||
|
||||
Commit: `refactor: move path-classification helpers into path-utils (#110)`
|
||||
|
||||
### Step 3 — Extract `external-directory-messages.ts` with tests
|
||||
|
||||
1. Create `src/external-directory-messages.ts` with the 6 `format*` functions.
|
||||
2. Create `tests/external-directory-messages.test.ts` by extracting message tests from `tests/external-directory.test.ts`.
|
||||
3. Update `src/external-directory.ts` to import-and-re-export from `./external-directory-messages`.
|
||||
4. Remove the original implementation from `external-directory.ts`.
|
||||
5. Verify all tests pass.
|
||||
|
||||
Commit: `refactor: extract external-directory-messages module (#110)`
|
||||
|
||||
### Step 4 — Extract `bash-path-extractor.ts`
|
||||
|
||||
1. Create `src/bash-path-extractor.ts` with tree-sitter parser, AST walker, and `extractExternalPathsFromBashCommand`.
|
||||
2. Update `tests/bash-external-directory.test.ts` imports if needed (or keep barrel).
|
||||
3. Update `src/external-directory.ts` to import-and-re-export from `./bash-path-extractor`.
|
||||
4. Remove the original implementation from `external-directory.ts`.
|
||||
5. Verify all tests pass.
|
||||
|
||||
Commit: `refactor: extract bash-path-extractor module (#110)`
|
||||
|
||||
### Step 5 — Clean up barrel and original test file
|
||||
|
||||
1. Verify `src/external-directory.ts` is now a pure barrel of re-exports.
|
||||
2. Reduce `tests/external-directory.test.ts` to a smoke test (or delete if fully migrated).
|
||||
3. Run full test suite.
|
||||
|
||||
Commit: `refactor: finalize external-directory barrel re-exports (#110)`
|
||||
|
||||
### Step 6 — Update architecture docs
|
||||
|
||||
1. Update `docs/architecture/target-architecture.md` if it references the monolithic `external-directory.ts`.
|
||||
|
||||
Commit: `docs: update architecture for external-directory split (#110)`
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Could this silently weaken a permission? | No — pure extraction refactoring; no logic changes; barrel preserves all exports. |
|
||||
| Circular dependency between `bash-path-extractor` and `path-utils` | `bash-path-extractor` imports from `path-utils`; no reverse dependency. Barrel re-exports both without creating a cycle. |
|
||||
| Tree-sitter WASM loading breaks after move | `bash-path-extractor.ts` preserves the same `createRequire(import.meta.url)` pattern — `import.meta.url` resolves to the new file's location, but WASM resolution uses `require.resolve` which walks `node_modules`, so it works from any file in `src/`. |
|
||||
| Test mocks leak across modules | Each new test file has its own `vi.mock()` scope. Discovery tests mock `child_process`/`fs`; path tests mock `os`; message tests need no mocks. |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should downstream consumers (`src/handlers/gates/*`, `src/runtime.ts`) be updated to import directly from the new modules in this PR, or deferred to a follow-up?
|
||||
Recommendation: defer — the barrel ensures backward compatibility and keeps the PR focused on extraction.
|
||||
Reference in New Issue
Block a user