ultimate-pi 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
- package/.agents/skills/harness-decisions/SKILL.md +2 -3
- package/.agents/skills/harness-governor/SKILL.md +6 -5
- package/.agents/skills/harness-orchestration/SKILL.md +4 -4
- package/.agents/skills/harness-review/SKILL.md +7 -7
- package/.agents/skills/harness-sentrux-setup/SKILL.md +4 -3
- package/.agents/skills/harness-steer/SKILL.md +1 -1
- package/.agents/skills/sentrux/SKILL.md +9 -9
- package/.pi/PACKAGING.md +4 -4
- package/.pi/SYSTEM.md +54 -120
- package/.pi/agents/harness/incident-recorder.md +0 -1
- package/.pi/agents/harness/planning/decompose.md +1 -3
- package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
- package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
- package/.pi/agents/harness/planning/hypothesis.md +0 -2
- package/.pi/agents/harness/planning/implementation-researcher.md +0 -2
- package/.pi/agents/harness/planning/plan-adversary.md +0 -2
- package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
- package/.pi/agents/harness/planning/planning-context.md +0 -2
- package/.pi/agents/harness/planning/review-integrator.md +0 -2
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
- package/.pi/agents/harness/planning/stack-researcher.md +0 -2
- package/.pi/agents/harness/{adversary.md → reviewing/adversary.md} +0 -2
- package/.pi/agents/harness/{evaluator.md → reviewing/evaluator.md} +0 -2
- package/.pi/agents/harness/{tie-breaker.md → reviewing/tie-breaker.md} +0 -2
- package/.pi/agents/harness/{executor.md → running/executor.md} +0 -2
- package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
- package/.pi/agents/harness/sentrux-steward.md +0 -2
- package/.pi/agents/harness/trace-librarian.md +0 -1
- package/.pi/extensions/00-harness-project-control.ts +133 -0
- package/.pi/extensions/00-posthog-network-bootstrap.ts +1 -1
- package/.pi/extensions/agt-kill-switch.ts +57 -0
- package/.pi/extensions/agt-prompt-guard.ts +32 -0
- package/.pi/extensions/budget-guard.ts +2 -0
- package/.pi/extensions/custom-footer.ts +46 -145
- package/.pi/extensions/custom-header.ts +1 -1
- package/.pi/extensions/custom-system-prompt.ts +1 -1
- package/.pi/extensions/debate-orchestrator.ts +7 -5
- package/.pi/extensions/harness-ask-user.ts +8 -8
- package/.pi/extensions/harness-debate-tools.ts +27 -43
- package/.pi/extensions/harness-lens.ts +94 -0
- package/.pi/extensions/harness-live-widget.ts +33 -2
- package/.pi/extensions/harness-plan-approval.ts +12 -12
- package/.pi/extensions/harness-run-context.ts +1214 -852
- package/.pi/extensions/harness-subagent-governance.ts +8 -0
- package/.pi/extensions/harness-subagent-submit.ts +36 -164
- package/.pi/extensions/harness-subagents.ts +4 -4
- package/.pi/extensions/harness-telemetry.ts +3 -1
- package/.pi/extensions/harness-web-tools.ts +3 -3
- package/.pi/extensions/observation-bus.ts +2 -0
- package/.pi/extensions/policy-gate.ts +27 -5
- package/.pi/extensions/review-integrity.ts +91 -10
- package/.pi/extensions/sentrux-rules-sync.ts +3 -1
- package/.pi/extensions/subagent-governance.ts +92 -0
- package/.pi/extensions/test-diff-integrity.ts +1 -0
- package/.pi/extensions/trace-recorder.ts +3 -1
- package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
- package/.pi/harness/README.md +6 -2
- package/.pi/harness/agents.manifest.json +38 -49
- package/.pi/harness/agents.policy.yaml +275 -0
- package/.pi/harness/corpus/graphify-kb-updater.config.json +55 -0
- package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +2 -1
- package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
- package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
- package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +3 -2
- package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
- package/.pi/harness/docs/adrs/0045-phase-scoped-agent-directories.md +33 -0
- package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
- package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
- package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
- package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
- package/.pi/harness/docs/adrs/README.md +6 -0
- package/.pi/harness/docs/graphify-kb-updater-runbook.md +11 -5
- package/.pi/harness/docs/practice-map.md +2 -2
- package/.pi/harness/evolution/README.md +1 -2
- package/.pi/harness/examples/agents.policy.project.yaml +19 -0
- package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
- package/.pi/harness/policies/bash-denylists.yaml +5 -0
- package/.pi/harness/policies/defaults.yaml +51 -0
- package/.pi/harness/policies/orchestrator.yaml +18 -0
- package/.pi/harness/policies/phases.yaml +10 -0
- package/.pi/harness/policies/roles.yaml +5 -0
- package/.pi/harness/policies/web-guard.yaml +5 -0
- package/.pi/harness/policies/workflow-sequences.yaml +9 -0
- package/.pi/harness/sentrux/architecture.manifest.json +26 -4
- package/.pi/harness/specs/harness-spawn-context.schema.json +1 -1
- package/.pi/harness/specs/observation.schema.json +2 -1
- package/.pi/lib/agents-policy.d.mts +70 -0
- package/.pi/lib/agents-policy.mjs +325 -0
- package/.pi/lib/agents-policy.ts +19 -0
- package/.pi/lib/agt/audit-run-sink.ts +52 -0
- package/.pi/lib/agt/build-evaluation-context.ts +285 -0
- package/.pi/lib/agt/config.ts +28 -0
- package/.pi/lib/agt/delegation.ts +69 -0
- package/.pi/lib/agt/evaluate-policy.ts +56 -0
- package/.pi/lib/agt/identity-registry.ts +41 -0
- package/.pi/lib/agt/index.ts +55 -0
- package/.pi/lib/agt/kill-switch-state.ts +11 -0
- package/.pi/lib/agt/legacy-evaluate.ts +101 -0
- package/.pi/lib/agt/policy-engine.ts +154 -0
- package/.pi/lib/agt/rings.ts +21 -0
- package/.pi/lib/agt/sre-hooks.ts +45 -0
- package/.pi/lib/agt/trust-run-store.ts +26 -0
- package/.pi/lib/agt/workflow-history.ts +29 -0
- package/.pi/lib/agt-governance-active.ts +14 -0
- package/.pi/lib/agt-tool-guard.ts +78 -0
- package/.pi/lib/ask-user/dialog.ts +314 -0
- package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
- package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
- package/.pi/{extensions/lib → lib}/extension-load-guard.ts +21 -0
- package/.pi/lib/harness-agt-tool-guard.ts +5 -0
- package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +6 -16
- package/.pi/lib/harness-debate-core-deps.ts +14 -0
- package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
- package/.pi/lib/harness-lens/.gitattributes +1 -0
- package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
- package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
- package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
- package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
- package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
- package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
- package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
- package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
- package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
- package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
- package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
- package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
- package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
- package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
- package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
- package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
- package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
- package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
- package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
- package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
- package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
- package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
- package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
- package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
- package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
- package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
- package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
- package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
- package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
- package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
- package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
- package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
- package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
- package/.pi/lib/harness-lens/clients/types.ts +59 -0
- package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
- package/.pi/lib/harness-lens/index.ts +532 -0
- package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
- package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
- package/.pi/lib/harness-project-config.ts +91 -0
- package/.pi/lib/harness-run-context-responses.ts +9 -0
- package/.pi/lib/harness-run-context.ts +1 -3
- package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +4 -3
- package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +5 -28
- package/.pi/lib/harness-subagent-auth.ts +51 -0
- package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +13 -10
- package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
- package/.pi/lib/harness-subagent-submit-register.ts +163 -0
- package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -55
- package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +53 -14
- package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
- package/.pi/lib/harness-ui-state.ts +27 -12
- package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
- package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
- package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +3 -52
- package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
- package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-plan.md +4 -6
- package/.pi/prompts/harness-review.md +9 -9
- package/.pi/prompts/harness-run.md +7 -7
- package/.pi/prompts/harness-setup.md +42 -68
- package/.pi/prompts/harness-steer.md +2 -2
- package/.pi/scripts/README.md +3 -5
- package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
- package/.pi/scripts/graphify-kb-updater.mjs +48 -8
- package/.pi/scripts/harness-agents-manifest.mjs +61 -4
- package/.pi/scripts/harness-agt-doctor.ts +36 -0
- package/.pi/scripts/harness-cli-verify.sh +9 -2
- package/.pi/scripts/harness-project-toggle.mjs +129 -0
- package/.pi/scripts/harness-sentrux-cli.mjs +142 -0
- package/.pi/scripts/harness-verify.mjs +113 -39
- package/.pi/scripts/harness-web-policy-guard.mjs +2 -2
- package/.pi/scripts/validate-plan-dag.mjs +65 -74
- package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
- package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
- package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
- package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
- package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
- package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
- package/.pi/skills/architecture/layered/SKILL.md +68 -0
- package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
- package/.pi/skills/architecture/microservices/SKILL.md +64 -0
- package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
- package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
- package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
- package/.pi/skills/architecture/service-based/SKILL.md +64 -0
- package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
- package/.pi/skills/architecture/space-based/SKILL.md +60 -0
- package/.pi/skills/ast-grep/SKILL.md +40 -321
- package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
- package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
- package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
- package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
- package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
- package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
- package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
- package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
- package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
- package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
- package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
- package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
- package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
- package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
- package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
- package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
- package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
- package/.pi/skills/lsp-navigation/SKILL.md +89 -0
- package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
- package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
- package/.pi/skills/quality/security-review/SKILL.md +34 -0
- package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
- package/.pi/skills/quality/testability-design/SKILL.md +33 -0
- package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
- package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
- package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
- package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
- package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
- package/.sentrux/rules.toml +20 -4
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +26 -0
- package/README.md +85 -58
- package/THIRD_PARTY_NOTICES.md +12 -21
- package/package.json +15 -7
- package/vendor/pi-subagents/src/agents.ts +45 -1
- package/vendor/pi-subagents/src/subagents.ts +866 -811
- package/vendor/pi-vcc/src/core/brief.ts +68 -99
- package/vendor/pi-vcc/src/core/settings.ts +2 -2
- package/.agents/skills/caveman/SKILL.md +0 -67
- package/.pi/agents/harness/meta-optimizer.md +0 -36
- package/.pi/agents/harness/planning/scout-graphify.md +0 -39
- package/.pi/agents/harness/planning/scout-semantic.md +0 -41
- package/.pi/agents/harness/planning/scout-structure.md +0 -37
- package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
- package/.pi/extensions/lib/harness-subagent-auth.ts +0 -209
- package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
- package/.pi/extensions/pi-model-router-harness.ts +0 -42
- package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
- package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
- package/.pi/model-router.example.json +0 -36
- package/.pi/prompts/harness-critic.md +0 -10
- package/.pi/prompts/harness-eval.md +0 -10
- package/.pi/prompts/harness-router-tune.md +0 -52
- package/.pi/scripts/harness-generate-model-router.mjs +0 -327
- package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
- package/.pi/scripts/harness-sync-model-router.mjs +0 -97
- package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
- package/vendor/pi-model-router/.prettierignore +0 -4
- package/vendor/pi-model-router/.prettierrc +0 -5
- package/vendor/pi-model-router/AGENTS.md +0 -39
- package/vendor/pi-model-router/LICENSE +0 -21
- package/vendor/pi-model-router/README.md +0 -99
- package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
- package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
- package/vendor/pi-model-router/extensions/commands.ts +0 -720
- package/vendor/pi-model-router/extensions/config.ts +0 -348
- package/vendor/pi-model-router/extensions/constants.ts +0 -1
- package/vendor/pi-model-router/extensions/index.ts +0 -478
- package/vendor/pi-model-router/extensions/provider.ts +0 -580
- package/vendor/pi-model-router/extensions/routing.ts +0 -564
- package/vendor/pi-model-router/extensions/state.ts +0 -52
- package/vendor/pi-model-router/extensions/types.ts +0 -95
- package/vendor/pi-model-router/extensions/ui.ts +0 -144
- package/vendor/pi-model-router/model-router.example.json +0 -48
- package/vendor/pi-model-router/package.json +0 -48
- package/vendor/pi-model-router/tsconfig.json +0 -16
- /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
- /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
- /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Kind Detection for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Centralized file type detection to avoid duplication across clients.
|
|
5
|
+
* Maps file extensions and paths to semantic file kinds.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { basename, extname } from "node:path";
|
|
9
|
+
|
|
10
|
+
// --- Types ---
|
|
11
|
+
|
|
12
|
+
export type FileKind =
|
|
13
|
+
| "clojure" // Clojure
|
|
14
|
+
| "cmake" // CMake
|
|
15
|
+
| "csharp" // C#
|
|
16
|
+
| "css" // CSS
|
|
17
|
+
| "cxx" // C/C++
|
|
18
|
+
| "dart" // Dart
|
|
19
|
+
| "docker" // Dockerfile
|
|
20
|
+
| "elixir" // Elixir
|
|
21
|
+
| "fish" // Fish shell
|
|
22
|
+
| "fsharp" // F#
|
|
23
|
+
| "gleam" // Gleam
|
|
24
|
+
| "go" // Go
|
|
25
|
+
| "haskell" // Haskell
|
|
26
|
+
| "html" // HTML
|
|
27
|
+
| "java" // Java
|
|
28
|
+
| "json" // JSON
|
|
29
|
+
| "jsts" // JavaScript/TypeScript/frameworks
|
|
30
|
+
| "kotlin" // Kotlin
|
|
31
|
+
| "lua" // Lua
|
|
32
|
+
| "markdown" // Markdown
|
|
33
|
+
| "nix" // Nix
|
|
34
|
+
| "ocaml" // OCaml
|
|
35
|
+
| "php" // PHP
|
|
36
|
+
| "powershell" // PowerShell
|
|
37
|
+
| "prisma" // Prisma schema
|
|
38
|
+
| "python" // Python
|
|
39
|
+
| "ruby" // Ruby
|
|
40
|
+
| "rust" // Rust
|
|
41
|
+
| "shell" // Shell
|
|
42
|
+
| "sql" // SQL
|
|
43
|
+
| "swift" // Swift
|
|
44
|
+
| "terraform" // Terraform
|
|
45
|
+
| "toml" // TOML
|
|
46
|
+
| "yaml" // YAML
|
|
47
|
+
| "zig"; // Zig
|
|
48
|
+
|
|
49
|
+
// --- Extension Maps ---
|
|
50
|
+
|
|
51
|
+
export const KIND_EXTENSIONS: Record<FileKind, readonly string[]> = {
|
|
52
|
+
clojure: [".clj", ".cljc", ".cljs", ".edn"],
|
|
53
|
+
cmake: [".cmake"],
|
|
54
|
+
csharp: [".cs"],
|
|
55
|
+
css: [".css", ".less", ".sass", ".scss"],
|
|
56
|
+
// From llvm-project/clang/lib/Driver/Types.cpp clang::driver::types::lookupTypeForExtension:
|
|
57
|
+
cxx: [
|
|
58
|
+
// C
|
|
59
|
+
".c",
|
|
60
|
+
".h",
|
|
61
|
+
// C++
|
|
62
|
+
".c++",
|
|
63
|
+
".cc",
|
|
64
|
+
".cp",
|
|
65
|
+
".cpp",
|
|
66
|
+
".cxx",
|
|
67
|
+
".hh",
|
|
68
|
+
".hpp",
|
|
69
|
+
".hxx",
|
|
70
|
+
// C++ include files
|
|
71
|
+
".inl",
|
|
72
|
+
".ipp",
|
|
73
|
+
".tpp",
|
|
74
|
+
".txx",
|
|
75
|
+
// C++20 module interface files
|
|
76
|
+
".c++m",
|
|
77
|
+
".cppm",
|
|
78
|
+
".cxxm",
|
|
79
|
+
".ixx",
|
|
80
|
+
// CUDA
|
|
81
|
+
".cu",
|
|
82
|
+
// HIP
|
|
83
|
+
".hip",
|
|
84
|
+
// Objective-C
|
|
85
|
+
".m",
|
|
86
|
+
".mm",
|
|
87
|
+
// OpenCL
|
|
88
|
+
".cl",
|
|
89
|
+
".clcpp",
|
|
90
|
+
],
|
|
91
|
+
dart: [".dart"],
|
|
92
|
+
docker: [".dockerfile"],
|
|
93
|
+
elixir: [".ex", ".exs"],
|
|
94
|
+
fish: [".fish"],
|
|
95
|
+
fsharp: [".fs", ".fsi", ".fsx"],
|
|
96
|
+
gleam: [".gleam"],
|
|
97
|
+
go: [".go"],
|
|
98
|
+
haskell: [".hs", ".lhs"],
|
|
99
|
+
html: [".htm", ".html"],
|
|
100
|
+
java: [".java"],
|
|
101
|
+
json: [".json", ".json5", ".jsonc"],
|
|
102
|
+
jsts: [
|
|
103
|
+
".cjs",
|
|
104
|
+
".cts",
|
|
105
|
+
".js",
|
|
106
|
+
".jsx",
|
|
107
|
+
".mjs",
|
|
108
|
+
".mts",
|
|
109
|
+
".svelte",
|
|
110
|
+
".ts",
|
|
111
|
+
".tsx",
|
|
112
|
+
".vue",
|
|
113
|
+
],
|
|
114
|
+
kotlin: [".kt", ".kts"],
|
|
115
|
+
lua: [".lua"],
|
|
116
|
+
markdown: [".md", ".mdx"],
|
|
117
|
+
nix: [".nix"],
|
|
118
|
+
ocaml: [".ml", ".mli"],
|
|
119
|
+
php: [".php"],
|
|
120
|
+
powershell: [".ps1", ".psm1", ".psd1"],
|
|
121
|
+
prisma: [".prisma"],
|
|
122
|
+
python: [".py", ".pyi"],
|
|
123
|
+
ruby: [".gemspec", ".rake", ".rb", ".ru"],
|
|
124
|
+
rust: [".rs"],
|
|
125
|
+
shell: [".bash", ".sh", ".zsh"],
|
|
126
|
+
sql: [".sql"],
|
|
127
|
+
swift: [".swift"],
|
|
128
|
+
terraform: [".tf", ".tfvars"],
|
|
129
|
+
toml: [".toml"],
|
|
130
|
+
yaml: [".yaml", ".yml"],
|
|
131
|
+
zig: [".zig", ".zon"],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Reverse map: extension → file kind (for fast lookup)
|
|
135
|
+
const EXT_TO_KIND = new Map<string, FileKind>();
|
|
136
|
+
for (const [kind, exts] of Object.entries(KIND_EXTENSIONS)) {
|
|
137
|
+
for (const ext of exts) {
|
|
138
|
+
EXT_TO_KIND.set(ext.toLowerCase(), kind as FileKind);
|
|
139
|
+
}
|
|
140
|
+
// Also register without leading dot
|
|
141
|
+
for (const ext of exts) {
|
|
142
|
+
if (ext.startsWith(".")) {
|
|
143
|
+
EXT_TO_KIND.set(ext.slice(1).toLowerCase(), kind as FileKind);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Special filenames that indicate a file kind
|
|
149
|
+
const SPECIAL_FILENAMES: Array<{ pattern: RegExp; kind: FileKind }> = [
|
|
150
|
+
{ pattern: /^CMakeLists\.txt$/i, kind: "cmake" },
|
|
151
|
+
{ pattern: /^Makefile$/i, kind: "shell" },
|
|
152
|
+
{ pattern: /^Dockerfile(\.\w+)?$/i, kind: "docker" },
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// --- Detection Functions ---
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Detect the file kind from a file path.
|
|
159
|
+
* Returns the semantic file kind or undefined if unknown.
|
|
160
|
+
*/
|
|
161
|
+
export function detectFileKind(filePath: string): FileKind | undefined {
|
|
162
|
+
if (!filePath || typeof filePath !== "string") {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check special filenames first
|
|
167
|
+
const base = basename(filePath);
|
|
168
|
+
for (const { pattern, kind } of SPECIAL_FILENAMES) {
|
|
169
|
+
if (pattern.test(base)) {
|
|
170
|
+
return kind;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check by extension
|
|
175
|
+
const ext = extname(filePath).toLowerCase();
|
|
176
|
+
return EXT_TO_KIND.get(ext);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check if a file kind is supported by a specific tool or capability.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* // Check if TypeScript file
|
|
184
|
+
* if (isFileKind(filePath, "jsts")) { ... }
|
|
185
|
+
*
|
|
186
|
+
* // Check for multiple kinds
|
|
187
|
+
* if (isFileKind(filePath, ["jsts", "python"])) { ... }
|
|
188
|
+
*/
|
|
189
|
+
export function isFileKind(
|
|
190
|
+
filePath: string,
|
|
191
|
+
kind: FileKind | FileKind[],
|
|
192
|
+
): boolean {
|
|
193
|
+
const detected = detectFileKind(filePath);
|
|
194
|
+
if (!detected) return false;
|
|
195
|
+
|
|
196
|
+
if (Array.isArray(kind)) {
|
|
197
|
+
return kind.includes(detected);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return detected === kind;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get all file kinds that match a given file extension.
|
|
205
|
+
* Useful for listing which tools might handle a file.
|
|
206
|
+
*/
|
|
207
|
+
export function getFileKindsForExtension(ext: string): FileKind[] {
|
|
208
|
+
const normalizedExt = ext.startsWith(".") ? ext : `.${ext}`;
|
|
209
|
+
const kind = EXT_TO_KIND.get(normalizedExt.toLowerCase());
|
|
210
|
+
return kind ? [kind] : [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if a file kind represents a code file (not config/markdown).
|
|
215
|
+
*/
|
|
216
|
+
export function isCodeKind(kind: FileKind): boolean {
|
|
217
|
+
return [
|
|
218
|
+
"jsts",
|
|
219
|
+
"python",
|
|
220
|
+
"go",
|
|
221
|
+
"rust",
|
|
222
|
+
"cxx",
|
|
223
|
+
"fish",
|
|
224
|
+
"shell",
|
|
225
|
+
"ruby",
|
|
226
|
+
"html",
|
|
227
|
+
"php",
|
|
228
|
+
"powershell",
|
|
229
|
+
"prisma",
|
|
230
|
+
"csharp",
|
|
231
|
+
"fsharp",
|
|
232
|
+
"java",
|
|
233
|
+
"kotlin",
|
|
234
|
+
"swift",
|
|
235
|
+
"dart",
|
|
236
|
+
"lua",
|
|
237
|
+
"zig",
|
|
238
|
+
"haskell",
|
|
239
|
+
"elixir",
|
|
240
|
+
"gleam",
|
|
241
|
+
"ocaml",
|
|
242
|
+
"clojure",
|
|
243
|
+
"terraform",
|
|
244
|
+
"nix",
|
|
245
|
+
].includes(kind);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if a file kind represents a text/config file.
|
|
250
|
+
*/
|
|
251
|
+
export function isConfigKind(kind: FileKind): boolean {
|
|
252
|
+
return [
|
|
253
|
+
"json",
|
|
254
|
+
"yaml",
|
|
255
|
+
"markdown",
|
|
256
|
+
"css",
|
|
257
|
+
"sql",
|
|
258
|
+
"docker",
|
|
259
|
+
"cmake",
|
|
260
|
+
"toml",
|
|
261
|
+
].includes(kind);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get human-readable description of a file kind.
|
|
266
|
+
*/
|
|
267
|
+
export function getFileKindLabel(kind: FileKind): string {
|
|
268
|
+
const labels: Record<FileKind, string> = {
|
|
269
|
+
jsts: "JavaScript/TypeScript",
|
|
270
|
+
python: "Python",
|
|
271
|
+
go: "Go",
|
|
272
|
+
rust: "Rust",
|
|
273
|
+
cxx: "C/C++",
|
|
274
|
+
cmake: "CMake",
|
|
275
|
+
shell: "Shell",
|
|
276
|
+
json: "JSON",
|
|
277
|
+
markdown: "Markdown",
|
|
278
|
+
css: "CSS",
|
|
279
|
+
yaml: "YAML",
|
|
280
|
+
sql: "SQL",
|
|
281
|
+
ruby: "Ruby",
|
|
282
|
+
html: "HTML",
|
|
283
|
+
docker: "Dockerfile",
|
|
284
|
+
php: "PHP",
|
|
285
|
+
powershell: "PowerShell",
|
|
286
|
+
prisma: "Prisma",
|
|
287
|
+
csharp: "C#",
|
|
288
|
+
fish: "Fish shell",
|
|
289
|
+
fsharp: "F#",
|
|
290
|
+
java: "Java",
|
|
291
|
+
kotlin: "Kotlin",
|
|
292
|
+
swift: "Swift",
|
|
293
|
+
dart: "Dart",
|
|
294
|
+
lua: "Lua",
|
|
295
|
+
zig: "Zig",
|
|
296
|
+
haskell: "Haskell",
|
|
297
|
+
elixir: "Elixir",
|
|
298
|
+
gleam: "Gleam",
|
|
299
|
+
ocaml: "OCaml",
|
|
300
|
+
clojure: "Clojure",
|
|
301
|
+
terraform: "Terraform",
|
|
302
|
+
nix: "Nix",
|
|
303
|
+
toml: "TOML",
|
|
304
|
+
};
|
|
305
|
+
return labels[kind] ?? kind;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get file extensions for a file kind.
|
|
310
|
+
*/
|
|
311
|
+
export function getExtensionsForKind(kind: FileKind): string[] {
|
|
312
|
+
return [...(KIND_EXTENSIONS[kind] ?? [])];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Check if a file should be scanned for linting/formatting.
|
|
317
|
+
* Excludes test files, generated files, etc.
|
|
318
|
+
*/
|
|
319
|
+
export function isScannableFile(filePath: string): boolean {
|
|
320
|
+
const kind = detectFileKind(filePath);
|
|
321
|
+
if (!kind) return false;
|
|
322
|
+
|
|
323
|
+
// Exclude test files for most kinds
|
|
324
|
+
const base = basename(filePath);
|
|
325
|
+
if (
|
|
326
|
+
base.includes(".test.") ||
|
|
327
|
+
base.includes(".spec.") ||
|
|
328
|
+
base.startsWith("test-") ||
|
|
329
|
+
base.startsWith("spec-")
|
|
330
|
+
) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Only scan code and config files
|
|
335
|
+
return isCodeKind(kind) || isConfigKind(kind);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get the language identifier for LSP/tools that use language IDs.
|
|
340
|
+
*/
|
|
341
|
+
export function getLanguageId(kind: FileKind): string {
|
|
342
|
+
const languageIds: Record<FileKind, string> = {
|
|
343
|
+
jsts: "typescript",
|
|
344
|
+
python: "python",
|
|
345
|
+
go: "go",
|
|
346
|
+
rust: "rust",
|
|
347
|
+
cxx: "cpp",
|
|
348
|
+
cmake: "cmake",
|
|
349
|
+
shell: "shell",
|
|
350
|
+
json: "json",
|
|
351
|
+
markdown: "markdown",
|
|
352
|
+
css: "css",
|
|
353
|
+
yaml: "yaml",
|
|
354
|
+
sql: "sql",
|
|
355
|
+
ruby: "ruby",
|
|
356
|
+
html: "html",
|
|
357
|
+
docker: "dockerfile",
|
|
358
|
+
php: "php",
|
|
359
|
+
powershell: "powershell",
|
|
360
|
+
prisma: "prisma",
|
|
361
|
+
csharp: "csharp",
|
|
362
|
+
fish: "fish",
|
|
363
|
+
fsharp: "fsharp",
|
|
364
|
+
java: "java",
|
|
365
|
+
kotlin: "kotlin",
|
|
366
|
+
swift: "swift",
|
|
367
|
+
dart: "dart",
|
|
368
|
+
lua: "lua",
|
|
369
|
+
zig: "zig",
|
|
370
|
+
haskell: "haskell",
|
|
371
|
+
elixir: "elixir",
|
|
372
|
+
gleam: "gleam",
|
|
373
|
+
ocaml: "ocaml",
|
|
374
|
+
clojure: "clojure",
|
|
375
|
+
terraform: "terraform",
|
|
376
|
+
nix: "nix",
|
|
377
|
+
toml: "toml",
|
|
378
|
+
};
|
|
379
|
+
return languageIds[kind] ?? "plaintext";
|
|
380
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileTime Tracking for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Prevents race conditions when auto-formatting or external tools modify files.
|
|
5
|
+
* Tracks file modification times and sizes to detect external changes.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by OpenCode's FileTime system - ensures agents re-read files
|
|
8
|
+
* that have been modified externally (including by formatters).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
|
|
14
|
+
// --- Types ---
|
|
15
|
+
|
|
16
|
+
export interface FileStamp {
|
|
17
|
+
readAt: Date;
|
|
18
|
+
mtime: number | undefined;
|
|
19
|
+
ctime: number | undefined;
|
|
20
|
+
size: number | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface FileTimeState {
|
|
24
|
+
reads: Map<string, Map<string, FileStamp>>; // sessionID -> filePath -> stamp
|
|
25
|
+
locks: Map<string, Promise<void>>; // filePath -> lock promise
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --- Singleton State ---
|
|
29
|
+
|
|
30
|
+
const globalState: FileTimeState = {
|
|
31
|
+
reads: new Map(),
|
|
32
|
+
locks: new Map(),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// --- Public API ---
|
|
36
|
+
|
|
37
|
+
export class FileTime {
|
|
38
|
+
private sessionID: string;
|
|
39
|
+
|
|
40
|
+
constructor(sessionID: string) {
|
|
41
|
+
this.sessionID = sessionID;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Record a file read with current stats
|
|
46
|
+
* Call this after ANY file modification (including formatting)
|
|
47
|
+
*/
|
|
48
|
+
read(filePath: string): FileStamp {
|
|
49
|
+
const absolutePath = path.resolve(filePath);
|
|
50
|
+
const stamp = createStamp(absolutePath);
|
|
51
|
+
|
|
52
|
+
let sessionReads = globalState.reads.get(this.sessionID);
|
|
53
|
+
if (!sessionReads) {
|
|
54
|
+
sessionReads = new Map();
|
|
55
|
+
globalState.reads.set(this.sessionID, sessionReads);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sessionReads.set(absolutePath, stamp);
|
|
59
|
+
return stamp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get last recorded stamp for a file
|
|
64
|
+
*/
|
|
65
|
+
get(filePath: string): FileStamp | undefined {
|
|
66
|
+
const absolutePath = path.resolve(filePath);
|
|
67
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
68
|
+
return sessionReads?.get(absolutePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Assert file hasn't changed since last read
|
|
73
|
+
* Throws error if file modified externally - forces agent to re-read
|
|
74
|
+
*/
|
|
75
|
+
assert(filePath: string): void {
|
|
76
|
+
const absolutePath = path.resolve(filePath);
|
|
77
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
78
|
+
const recorded = sessionReads?.get(absolutePath);
|
|
79
|
+
|
|
80
|
+
if (!recorded) {
|
|
81
|
+
throw new FileTimeError(
|
|
82
|
+
`You must read file ${absolutePath} before modifying it. Use the read tool first.`,
|
|
83
|
+
absolutePath,
|
|
84
|
+
"not-read",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const current = createStamp(absolutePath);
|
|
89
|
+
const changed =
|
|
90
|
+
current.mtime !== recorded.mtime ||
|
|
91
|
+
current.ctime !== recorded.ctime ||
|
|
92
|
+
current.size !== recorded.size;
|
|
93
|
+
|
|
94
|
+
if (changed) {
|
|
95
|
+
throw new FileTimeError(
|
|
96
|
+
`File ${absolutePath} has been modified since it was last read.\n` +
|
|
97
|
+
`Last modification: ${new Date(current.mtime ?? Date.now()).toISOString()}\n` +
|
|
98
|
+
`Last read: ${recorded.readAt.toISOString()}\n\n` +
|
|
99
|
+
`Please read the file again before modifying it.`,
|
|
100
|
+
absolutePath,
|
|
101
|
+
"modified",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if file has changed (non-throwing version of assert)
|
|
108
|
+
*/
|
|
109
|
+
hasChanged(filePath: string): boolean {
|
|
110
|
+
const absolutePath = path.resolve(filePath);
|
|
111
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
112
|
+
const recorded = sessionReads?.get(absolutePath);
|
|
113
|
+
|
|
114
|
+
if (!recorded) return true; // Never read = changed
|
|
115
|
+
|
|
116
|
+
const current = createStamp(absolutePath);
|
|
117
|
+
return (
|
|
118
|
+
current.mtime !== recorded.mtime ||
|
|
119
|
+
current.ctime !== recorded.ctime ||
|
|
120
|
+
current.size !== recorded.size
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Acquire exclusive lock on file
|
|
126
|
+
* Prevents concurrent modifications to same file
|
|
127
|
+
*/
|
|
128
|
+
async withLock<T>(filePath: string, fn: () => Promise<T>): Promise<T> {
|
|
129
|
+
const absolutePath = path.resolve(filePath);
|
|
130
|
+
|
|
131
|
+
// Wait for existing lock
|
|
132
|
+
while (globalState.locks.has(absolutePath)) {
|
|
133
|
+
const existing = globalState.locks.get(absolutePath);
|
|
134
|
+
if (existing) await existing;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create new lock
|
|
138
|
+
const lockPromise = fn().finally(() => {
|
|
139
|
+
globalState.locks.delete(absolutePath);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
globalState.locks.set(
|
|
143
|
+
absolutePath,
|
|
144
|
+
lockPromise.then(() => {}),
|
|
145
|
+
);
|
|
146
|
+
return lockPromise;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clear all tracked files for this session
|
|
151
|
+
*/
|
|
152
|
+
clear(): void {
|
|
153
|
+
globalState.reads.delete(this.sessionID);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clear specific file tracking
|
|
158
|
+
*/
|
|
159
|
+
clearFile(filePath: string): void {
|
|
160
|
+
const absolutePath = path.resolve(filePath);
|
|
161
|
+
const sessionReads = globalState.reads.get(this.sessionID);
|
|
162
|
+
sessionReads?.delete(absolutePath);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Error Type ---
|
|
167
|
+
|
|
168
|
+
export class FileTimeError extends Error {
|
|
169
|
+
readonly filePath: string;
|
|
170
|
+
readonly reason: "not-read" | "modified";
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
message: string,
|
|
174
|
+
filePath: string,
|
|
175
|
+
reason: "not-read" | "modified",
|
|
176
|
+
) {
|
|
177
|
+
super(message);
|
|
178
|
+
this.name = "FileTimeError";
|
|
179
|
+
this.filePath = filePath;
|
|
180
|
+
this.reason = reason;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- Utilities ---
|
|
185
|
+
|
|
186
|
+
function createStamp(filePath: string): FileStamp {
|
|
187
|
+
try {
|
|
188
|
+
const stats = fs.statSync(filePath);
|
|
189
|
+
return {
|
|
190
|
+
readAt: new Date(),
|
|
191
|
+
mtime: stats.mtime.getTime(),
|
|
192
|
+
ctime: stats.ctime.getTime(),
|
|
193
|
+
size: stats.size,
|
|
194
|
+
};
|
|
195
|
+
} catch {
|
|
196
|
+
// File doesn't exist - return empty stamp
|
|
197
|
+
return {
|
|
198
|
+
readAt: new Date(),
|
|
199
|
+
mtime: undefined,
|
|
200
|
+
ctime: undefined,
|
|
201
|
+
size: undefined,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Global Helpers ---
|
|
207
|
+
|
|
208
|
+
export function createFileTime(sessionID: string): FileTime {
|
|
209
|
+
return new FileTime(sessionID);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function clearAllSessions(): void {
|
|
213
|
+
globalState.reads.clear();
|
|
214
|
+
globalState.locks.clear();
|
|
215
|
+
}
|