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,111 @@
|
||||
# Phase 2: Complexity and duplication paydown
|
||||
|
||||
Goal: pay down the complexity and duplication debt that `fallow` flags but no issue tracks.
|
||||
|
||||
Phase 1 is scoped to enabling [#266] (the preview formatter).
|
||||
Phase 2 is a distinct theme: it eliminates the five `fallow` refactoring targets and drives down the test-tree duplication.
|
||||
Four of the five targets sit on the security-critical `tool_call` decision path, where high untested complexity is a correctness risk, not only a maintainability one.
|
||||
|
||||
The two phases are otherwise independent and can run in either order, with one exception: do [#285] before Phase 1 step 2, since both modify the `describeToolGate` call site inside `handleToolCall`, and decomposing that function first lets the formatter thread through a clean pipeline.
|
||||
|
||||
## Current health metrics
|
||||
|
||||
| Metric | Value |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| Health score | 74 B |
|
||||
| LOC | 31,416 |
|
||||
| Dead files / exports | 0% |
|
||||
| Avg cyclomatic | 1.4 |
|
||||
| Maintainability | 91.2 (good) |
|
||||
| Duplication | 9.2% (after [#286]) |
|
||||
| Refactoring targets | 3 (2 medium, 1 high) - after [#290]; `config-loader.ts` no longer a target |
|
||||
| Worst CRAP risk | `permission-gate-handler.ts` 79.4 (handleInput) - after [#290] |
|
||||
|
||||
## Findings
|
||||
|
||||
All findings are `fallow`-confirmed and untracked before this phase.
|
||||
|
||||
| # | Finding | Category | Files | Impact | Risk | Priority |
|
||||
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | --------------------------------------- | ------ | ---- | -------- |
|
||||
| 1 | `handleToolCall` runs six gates with a repeated bypass/runner/short-circuit shape - cognitive 52, CRAP 172, the package's worst | B: god function | `handlers/permission-gate-handler.ts` | 5 | 2 | 20 |
|
||||
| 2 | ✅ `resolvePermissions` interleaves scope merge with parallel origin-map bookkeeping - cognitive 33, CRAP 97 - resolved by [#286] | B: god function | `permission-manager.ts` | 4 | 2 | 16 |
|
||||
| 3 | ✅ `runGateCheck` carried the full check→log→emit→approve cycle as six inline phases - cognitive 32 - resolved by [#287] | B: god function | `handlers/gates/runner.ts` | 4 | 2 | 16 |
|
||||
| 4 | ✅ Two token classifiers share a 31-line rejection prelude (production clone); `collectPathCandidateTokens` (37) and `collectPatternCommandTokens` (33) are complexity hotspots - resolved by [#289] | A: duplication / B: god function | `handlers/gates/bash-path-extractor.ts` | 4 | 3 | 12 |
|
||||
| 5 | ✅ `stripJsonComments` is a five-variable character scanner - cognitive 31 - resolved by [#290] | B: god function | `config-loader.ts` | 2 | 2 | 8 |
|
||||
| 6 | ✅ 9.1% duplication concentrated in the test tree - the single largest health deduction (-4.1) - resolved by [#288] | D: test duplication | `test/` (clone families) | 3 | 1 | 15 |
|
||||
|
||||
## Steps
|
||||
|
||||
1. ✅ **Decompose `handleToolCall`** ([#285]) - **completed**
|
||||
- Extracted `validateRequestedTool` (pure, exported) for the tool-name validation prelude.
|
||||
- Extracted `runGate` closure (inside `handleToolCall`) for the unified bypass/runner/short-circuit shape.
|
||||
- Collapsed the body to validate → build context → ordered producer-array pipeline.
|
||||
- Outcome: `handleToolCall` no longer appears as a refactoring target; CRAP risk for the file dropped from 172 → 79.4 (now `handleInput`); refactoring targets 5 → 4.
|
||||
|
||||
2. ✅ **Decompose `resolvePermissions`** ([#286]) - **completed**
|
||||
- Extracted `mergeScopesWithOrigins(scopes)` (into new `src/scope-merge.ts`) returning `{ mergedPermission, origins }`, isolating origin-map bookkeeping from the resolve pipeline.
|
||||
- The remaining body reads as load scopes → merge with origins → extract universal fallback → build config rules → compose.
|
||||
- Outcome: `resolvePermissions` no longer appears as a refactoring target; `permission-manager.ts` dropped from the CRAP-risk list.
|
||||
|
||||
3. ✅ **Thin `runGateCheck`** ([#287]) - **completed**
|
||||
- Introduced `SessionApproval` value object (`src/session-approval.ts`) owning the `{pattern}|{patterns}` union; exposed `representativePattern` and `toGateApproval()`.
|
||||
- `SessionRules.record(approval)` absorbs the per-pattern loop; `GateRunnerDeps` seam renamed to `recordSessionApproval(approval)` - runner tells the store, never interrogates the union.
|
||||
- Extracted `buildDecisionEvent` into `helpers.ts` to deduplicate the `origin/agentName/matchedPattern ?? null` normalization across both emit sites.
|
||||
- Outcome: `runner.ts` no longer appears as a refactoring target; refactoring targets 4 → 3.
|
||||
|
||||
4. ✅ **Decompose `bash-path-extractor.ts`** ([#289]) - **completed**
|
||||
- Extracted pure token classifiers into new `src/handlers/gates/bash-token-classification.ts`; private `rejectNonPathToken` predicate eliminates the 31-line rejection-prelude clone.
|
||||
- Extracted `classifyPatternCommandFlag` (returns a `PatternCommandFlagDirective` discriminated union) to replace the inline flag state machine in `collectPatternCommandTokens`.
|
||||
- Extracted `collectCommandTokens`, `collectGenericCommandTokens`, `collectRedirectTokens` from `collectPathCandidateTokens`; converted both walkers from output-argument accumulator to return-based `string[]`.
|
||||
- Category: A + B (production clone + god functions)
|
||||
- Outcome: clone removed; `collectPathCandidateTokens` and `collectPatternCommandTokens` decomposed into focused helpers; `bash-token-classification.ts` has dedicated unit tests (43 tests) covering every rejection and acceptance branch.
|
||||
|
||||
5. ✅ **Reduce `stripJsonComments` complexity** ([#290]) - **completed**
|
||||
- Replaced the five-flag single-loop scanner with a stateless dispatcher delegating to three private consume helpers: `consumeLineComment`, `consumeBlockComment`, and `consumeString`, each returning a `ScanSegment` value (`{ output, nextIndex }`).
|
||||
- Added 14 direct unit tests for `stripJsonComments` (the function was exported but had no dedicated coverage) to pin the contract before the refactor.
|
||||
- Category: B (god function)
|
||||
- Outcome: `stripJsonComments` no longer appears as a refactoring target; `config-loader.ts` dropped from the CRAP-risk list; refactoring targets 4 → 3.
|
||||
- Commits: `test: add direct stripJsonComments unit tests`, `refactor: model stripJsonComments as consume helpers`
|
||||
|
||||
6. ✅ **Extract shared test fixtures** ([#288]) - **completed**
|
||||
- Created `test/helpers/handler-fixtures.ts` (`makeCtx`, `makeEvents`, `makeSession`, `makeToolRegistry`, `makeToolCallEvent`, `makeCheckResult`, `makeHandler`, `getDecisionEvents`), `test/helpers/gate-fixtures.ts` (`makeDescriptor`, `makeRunnerDeps`, `makeTcc`, `makeGateCheckResult`), and `test/helpers/manager-harness.ts` (`createManager`).
|
||||
- Migrated handler-event clone family (`tool-call-events.test.ts`, `tool-call.test.ts`, `input-events.test.ts`, `input.test.ts`, `permission-session.test.ts`), external-directory family, gate family (`runner.test.ts`, `bash-path.test.ts`, `path.test.ts`), manager harness (`permission-system.test.ts`), and lifecycle setup (`before-agent-start.test.ts`, `lifecycle.test.ts`).
|
||||
- Category: D (test duplication)
|
||||
- Outcome: duplication 9.1% → 7.1%; clone groups 122 → 113; health deduction -4.1 → -2.1.
|
||||
|
||||
## Step dependency diagram
|
||||
|
||||
Steps 1-5 are independent.
|
||||
Step 6 is best sequenced after the production refactors whose tested call sites it touches (dashed edges) - those refactors are behavior-preserving, so the soft ordering only avoids re-migrating fixtures, it does not block.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S1["Step 1: Decompose handleToolCall (#285)"]
|
||||
S2["Step 2: Decompose resolvePermissions (#286)"]
|
||||
S3["Step 3: Decompose runGateCheck (#287)"]
|
||||
S4["Step 4: Decompose bash-path-extractor (#289)"]
|
||||
S5["Step 5: Reduce stripJsonComments (#290)"]
|
||||
S6["Step 6: Extract shared test fixtures (#288)"]
|
||||
|
||||
S1 -.-> S6
|
||||
S2 -.-> S6
|
||||
S3 -.-> S6
|
||||
S4 -.-> S6
|
||||
```
|
||||
|
||||
## Tracks
|
||||
|
||||
| Track | Steps | Description |
|
||||
| --------------------------- | ------- | ------------------------------------------------------------------------ |
|
||||
| A: Decision-path complexity | 1, 2, 3 | Decompose the three `tool_call` hotspots (independent, parallel) |
|
||||
| B: bash-path-extractor | 4 | Remove the production clone and reduce the two collect-function hotspots |
|
||||
| C: config-loader | 5 | Reduce `stripJsonComments` complexity (lowest priority) |
|
||||
| D: Duplication | 6 | Extract shared test fixtures; best sequenced after Tracks A and B |
|
||||
|
||||
[#266]: https://github.com/gotgenes/pi-packages/issues/266
|
||||
[#285]: https://github.com/gotgenes/pi-packages/issues/285
|
||||
[#286]: https://github.com/gotgenes/pi-packages/issues/286
|
||||
[#287]: https://github.com/gotgenes/pi-packages/issues/287
|
||||
[#288]: https://github.com/gotgenes/pi-packages/issues/288
|
||||
[#289]: https://github.com/gotgenes/pi-packages/issues/289
|
||||
[#290]: https://github.com/gotgenes/pi-packages/issues/290
|
||||
Reference in New Issue
Block a user