9.9 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 382 | pi-permission-system: external_directory base permission doesn't auto-detect or allow overrides for pi docs directory when installed via npm on Windows |
Retro: #382 — Windows case-insensitive external_directory matching and Pi-install auto-detect
Stage: Planning (2026-06-10T00:00:00Z)
Session summary
Traced the reported bug to a Windows-only path-comparison asymmetry: the path under test is canonicalized and lowercased on win32 (normalizePathForComparison / canonicalNormalizePathForComparison), but infra-dir containment (isPathWithinDirectory, case-sensitive startsWith) and config-pattern matching (compileWildcardPattern, case-sensitive RegExp) keep native case — so both the infrastructure auto-allow and explicit external_directory overrides silently fail.
Produced docs/plans/0382-windows-external-directory-case-insensitive.md with a 6-step TDD order covering path.relative containment, case/separator-folded path-surface matching, and Pi-install auto-detect via getPackageDir().
Observations
- The user steered the design with two questions ("is there a builtin node path library?"
/ "how does pi handle this itself?").
Confirmed
path.win32.relativefolds case natively, and Pi's own idiom (getCwdRelativePath,getPiDocsClassificationinpackages/coding-agent/src/utils/paths.tsandcore/tools/read.ts) isrelative()+../absolute check with no manual lowercasing — adopted as the containment approach. path.matchesGlobwas rejected: its*does not cross separators and it is case-sensitive even onwin32, so it would change the established*→.*semantics without fixing the case bug.- Two
ask_usercalls settled scope: (1) comparison fix plus pi-API auto-detect, (2) adoptpath.relativebroadly; a follow-up pickedgetPackageDir()(whole Pi install dir) over docs-only paths. - Key dependency constraint:
getPackageDir()/getDocsPath()are only re-exported from@earendil-works/pi-coding-agent's entry point as ofv0.79.0(commiteb43bd44); the installed devDependency0.75.4exports onlygetAgentDir+VERSION. The plan therefore bumps the peer floor>=0.75.0→>=0.79.0(the reporter runs0.79.1). - Testability decision: stubbing
process.platformdoes not switch Node's top-levelpathfunctions towin32, so production code selectspath.win32/path.posixfrom an injected, defaultedplatformparameter and tests pass"win32"+C:\…paths. This also satisfies the AGENTS.md "noprocess.platforminside library functions" guidance. evaluateinrule.tsis the single surface-aware matching site; folding is scoped to a new exportedPATH_SURFACESset (PATH_BEARING_TOOLS∪{ external_directory, path }) sobash/skill/mcpstay case-sensitive.- Classified as a non-breaking
fix:— POSIX behavior is unchanged and the peer bump does not alter runtime behavior/config on upgrade. - Deferred (non-goals): removing the now-redundant
win32lowercasing innormalizePathForComparison, and dissolvingsubagent-context.ts's duplicate containment helper.
Stage: Implementation — TDD (2026-06-10T19:17:00Z)
Session summary
Implemented the Windows case-insensitive path-matching fix across 7 commits (6 TDD cycles + 1 docs): path.relative-based containment in isPathWithinDirectory, WildcardMatchOptions (case-insensitive + Windows-separator folding) on the matcher, case-insensitive infra-read auto-allow, path-surface case folding in evaluate via a new PATH_SURFACES set, an optional piPackageDir on computeExtensionPaths, and getPackageDir() wiring at the composition root (with the @earendil-works/pi-coding-agent / pi-tui floor bump >=0.75.0 → >=0.79.0, devDeps 0.79.1).
Test count went from 1902 to 1921 (+19); full suite, check, lint, and fallow dead-code all green.
Observations
- Pre-completion reviewer: PASS.
- Reviewer warnings: one non-blocking WARN —
evaluateFirst/evaluateMostRestrictivedelegate toevaluatewithout exposingplatform, so they are not unit-testable for Windows case-folding on a POSIX CI (runtime behavior is correct becauseprocess.platformiswin32in production; the Windows path is covered compositionally by theevaluate-level tests). Left as-is; a future change could threadplatformthrough them if dedicated Windows coverage is wanted. - Deviation 1: did not thread
platformintoisPathOutsideWorkingDirectory(the plan suggested it). Its internalisPathWithinDirectorycall already captures the runtime platform via the default param, and thewin32path there is not unit-testable on a POSIX CI becausecanonicalizePathsplits on/. Avoided an untested parameter. - Deviation 2: reordered the plan's steps — the
wildcard-matcheroptions had to land beforeisPiInfrastructureReadconsumed them (the plan listed infra-read first and the matcher options later). A real dependency-ordering correction. - Deviation 3: skipped the plan's brittle end-to-end external-directory gate integration test (Red C).
Flipping
process.platformdoes not switch Node'spath.win32dispatch on a POSIX runner, and the gate'scanonicalNormalizePathForComparisononly lowercases on realwin32, so a darwin-runner integration test would be unreliable. Coverage is provided by the composed unit tests at theevaluate/ wildcard /path-utilslevels. - The
pnpm installafter the dep bump printed "Already up to date" but did updatepnpm-lock.yaml(+205 lines adding0.79.1); verified the installedindex.d.tsre-exportsgetPackageDirbefore wiringindex.ts.
Stage: Final Retrospective (2026-06-10T20:30:00Z)
Session summary
Shipped the Windows case-insensitive external_directory fix end-to-end across planning, TDD, and release: 7 implementation commits, +19 tests, pi-permission-system v10.10.0 released and issue #382 closed.
The session was clean throughout — no rabbit-holes, no instruction violations, all gates green on the first push, and a PASS pre-completion review.
The one dominant friction was a planning-stage missing-context gap that the user redirected with two well-aimed questions.
Observations
What went well
- The injected-
platform-parameter testability pattern (selectpath.win32/path.posixfrom a defaultedplatformarg, pass"win32"+C:\…in tests) cleanly solved the "can't stubprocess.platformto switch Node'spathdispatch" problem. Reusable for any cross-platform path logic. - Empirical verification before designing: a throwaway
node -escript confirmedpath.win32.relativefolds case, and reading the local pi checkout (~/development/pi/pi) surfacedgetCwdRelativePath/getPiDocsClassification/getPackageDirbefore committing to an approach. - Tight feedback loop:
vitest run <file>after every Red and Green,pnpm run checkafter each type-affecting step, full suite before commits touching shared helpers (isPathWithinDirectory,evaluate). No end-of-session verification surprises. - Execution-time adaptation: caught a plan dependency-ordering inversion (the
wildcard-matcheroptions had to land beforeisPiInfrastructureReadconsumed them) and reordered without rework.
What caused friction (agent side)
missing-context— during planning I converged on a hand-rolled case-folding fix (lowercase both sides / case-insensitive regex) without first checking whether Node'spathmodule had a containment primitive or how the upstream host@earendil-works/pi-coding-agentsolves the same problem. The user's two questions ("is there a builtin node path library?" / "how does pi handle this itself?") supplied exactly the missing checks, which redirected the design topath.win32.relative(native case-folding) andgetPackageDir()(robust auto-detect). Self-identified: no (user-caught, via redirecting questions). Impact: no rework — caught in planning before the plan was written — but without the redirect the plan would have shipped more complex hand-rolled containment instead of the cleaner builtin-based design.other(plan dependency-ordering miss) — the plan listed the infra-read folding step before thewildcard-matcheroptions step it depended on. Self-identified at execution; reordered with no rework.
What caused friction (user side)
- The high-leverage context (prefer Node builtins; check how pi-coding-agent itself solves filesystem/platform problems) arrived as a mid-planning redirect rather than being available up front. Framed as opportunity: encoding "check pi-coding-agent's implementation first for path/platform bugs" in the package skill lets the agent do this proactively without the redirect. The intervention style — two redirecting questions instead of a correction — was ideal and worth preserving.
Diagnostic details
- Model-performance correlation — one subagent dispatched (
pre-completion-reviewer, judgment-heavy review). It returned a structured PASS with a genuine non-blocking WARN (theevaluateFirst/evaluateMostRestrictivetestability gap) — appropriate quality for the task; no model/task mismatch. - Escalation-delay tracking — no
rabbit-holefriction points; longest run on a single error was the expected Red→Green cycle (1–2 tool calls). - Unused-tool detection — the
missing-contextgap was not a tool-usage failure: once pointed at~/development/pi/pi, exploration viagrep/Bash/node -ewas efficient. The gap was "did not think to check the upstream host implementation," not "checked it inefficiently." - Feedback-loop gap analysis — no gap; verification ran incrementally after each change, not deferred to the end.
Changes made
.pi/skills/package-pi-permission-system/SKILL.md— added a 4th item to the Debugging section: for path/filesystem/platform bugs, check how@earendil-works/pi-coding-agentsolves it first and prefer Nodepathbuiltins over hand-rolled comparison.