@nklisch/pi-enhanced 0.1.2 → 0.1.4
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/CHANGELOG.md +7 -0
- package/README.md +3 -2
- package/node_modules/@nklisch/pi-clearance/README.md +10 -5
- package/node_modules/@nklisch/pi-clearance/docs/ARCHITECTURE.md +12 -7
- package/node_modules/@nklisch/pi-clearance/docs/CONFIGURATION.md +25 -35
- package/node_modules/@nklisch/pi-clearance/docs/DEVELOPER_GUIDE.md +30 -30
- package/node_modules/@nklisch/pi-clearance/docs/PACK_AUTHORING.md +5 -4
- package/node_modules/@nklisch/pi-clearance/docs/PRINCIPLES.md +14 -10
- package/node_modules/@nklisch/pi-clearance/docs/REFERENCE_PATTERNS.md +1 -1
- package/node_modules/@nklisch/pi-clearance/docs/REVIEWER_PROMPTS.md +12 -8
- package/node_modules/@nklisch/pi-clearance/docs/RULE_PACKS.md +1 -1
- package/node_modules/@nklisch/pi-clearance/docs/SPEC.md +7 -6
- package/node_modules/@nklisch/pi-clearance/docs/TUNE.md +2 -2
- package/node_modules/@nklisch/pi-clearance/docs/USER_GUIDE.md +7 -3
- package/node_modules/@nklisch/pi-clearance/docs/VISION.md +3 -3
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.darwin-arm64.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.darwin-x64.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-arm64-gnu.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-arm64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-clearance/package.json +9 -5
- package/node_modules/@nklisch/pi-clearance/src/config/config-command-plans.ts +79 -3
- package/node_modules/@nklisch/pi-clearance/src/config/config-command-writer.ts +7 -1
- package/node_modules/@nklisch/pi-clearance/src/config/defaults.ts +42 -0
- package/node_modules/@nklisch/pi-clearance/src/config/gated-tools.ts +7 -0
- package/node_modules/@nklisch/pi-clearance/src/config/loader.ts +7 -1
- package/node_modules/@nklisch/pi-clearance/src/config/pack-enablement-writer.ts +1 -0
- package/node_modules/@nklisch/pi-clearance/src/config/paths.ts +14 -3
- package/node_modules/@nklisch/pi-clearance/src/config/persistence.ts +135 -0
- package/node_modules/@nklisch/pi-clearance/src/config/schema.ts +74 -26
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCase.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseCategory.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseExpectation.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseResult.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseResultOutcome.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseSource.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialValidationReport.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialValidationStatus.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashBlock.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashCommandShape.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashConditionalArm.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashControlConstruct.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashFlag.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashForLoopKeywordSpans.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashIteratorEntry.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashIteratorEntryKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashListOperator.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashLoopVariableReference.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFact.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFactContext.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFactProvenance.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFactProvenanceEntry.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFacts.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashPipeline.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashStage.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BashStageProgram.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/BlockOperator.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CapturedOutcome.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CommandFamilyKey.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CommandFamilySummary.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CompileError.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CompositionOperator.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundBodyReason.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundFeatureReason.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundForm.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundIteratorReason.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusEntry.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusExpectedLabel.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusFidelity.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusQueryModel.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusQuerySummary.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusQuerySummarySnapshot.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusRecord.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusSource.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/CountByLabel.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/Decision.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/DecisionEffect.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/DecisionProvenance.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/DecisionSource.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/DiagnosticSeverity.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/EffectivePolicy.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/EmbeddedShellProjection.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/EnvironmentAssignment.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/IteratorSourceKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/LoopQuoteKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/LoopVariableUnknownReason.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/MatcherExpr.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/MutationShapeKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/MutationTrustBoundaryClassification.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/MutationTrustBoundaryKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PackCompileResult.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PackWarningLevel.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ParsedEvidence.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ParsedShapeSummary.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathAccess.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathFactProjectScope.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathFactsRequirement.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathFactsResolvedConfig.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathNormalization.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathScope.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathScopeMatcherExpr.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathScopeMatcherMode.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathUnknownReason.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PathUsageKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolOperation.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolPathInput.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolShape.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolSpec.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PiFileMutationToolName.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PiToolMutationFacts.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPack.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackDocLink.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackExample.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackMetadata.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackWarning.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyRule.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ProposalNotRunReason.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ProposalNotRunSeverity.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/QuoteKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/Redirect.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/RedirectStream.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/RedirectTargetKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayCorpus.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDelta.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaBlockedSummary.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaChangedRecord.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaFamily.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaImprovement.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaRegression.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaStatus.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaTransitionCount.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayPathFacts.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayReviewerMode.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayReviewerOutcome.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayStatus.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayedDecision.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ShapeDiagnostic.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/SourceFidelity.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/SourceSpan.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/Substitution.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/SubstitutionKind.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolCallIdentity.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathAccess.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathFact.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathFactContext.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathFacts.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathUsage.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/ToolShape.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/UnknownPathBehavior.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/UnknownToolShape.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/contracts/index.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/index.ts +33 -2
- package/node_modules/@nklisch/pi-clearance/src/native/loader.ts +29 -42
- package/node_modules/@nklisch/pi-clearance/src/replay/reviewer-config-proposals.ts +7 -4
- package/node_modules/@nklisch/pi-clearance/src/runtime/allow-request-message.ts +21 -0
- package/node_modules/@nklisch/pi-clearance/src/runtime/auto-reviewer-read-models.ts +165 -8
- package/node_modules/@nklisch/pi-clearance/src/runtime/command-registry.ts +1 -1
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/allow.ts +26 -2
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/packs.ts +6 -6
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/scope.ts +5 -5
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/actions.ts +11 -0
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/dispatcher.ts +274 -1
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/model-options.ts +52 -0
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/native-ui.ts +54 -67
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/panels/reviewer.ts +19 -3
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/panels/scope.ts +2 -11
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/panels.ts +10 -1
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/read-model.ts +47 -13
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings.ts +32 -37
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/status.ts +3 -0
- package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/types.ts +14 -5
- package/node_modules/@nklisch/pi-clearance/src/runtime/handler.ts +36 -0
- package/node_modules/@nklisch/pi-clearance/src/runtime/mode-prompt.ts +0 -1
- package/node_modules/@nklisch/pi-clearance/src/runtime/model-adapter.ts +5 -1
- package/node_modules/@nklisch/pi-clearance/src/runtime/reviewer-context.ts +73 -10
- package/node_modules/@nklisch/pi-clearance/src/runtime/reviewer-prompts.ts +186 -175
- package/node_modules/@nklisch/pi-clearance/src/runtime/reviewer.ts +26 -6
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/state/local-lock-filesystem.d.ts +23 -1
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/state/local-lock-filesystem.js +46 -50
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/state/local-lock-filesystem.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-package.js +2 -2
- package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-runtime.js +2 -2
- package/node_modules/@nklisch/pi-plugins/dist/runtime/subagents/pi-subagents-lifecycle.js +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/subagents/pi-subagents-package.js +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +41 -31
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +2 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +17 -11
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +14 -7
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +11 -19
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +58 -53
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/agent-type-resolution.ts +43 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/agent-types.ts +11 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +13 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +0 -52
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/thinking-levels.ts +12 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/handlers/interrupt.ts +5 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/handlers/lifecycle.ts +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +7 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +7 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/parent-snapshot.ts +11 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +26 -56
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +48 -9
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +32 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +105 -17
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/composite-subagent-observer.ts +4 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +50 -46
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +15 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/runtime.ts +2 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +35 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +1 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/model-label.ts +19 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/prompts.ts +24 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/session-config.ts +11 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +70 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +27 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +6 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +12 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +17 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +13 -20
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +6 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +16 -16
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +12 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +24 -11
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +10 -7
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/glyphs.ts +7 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +34 -25
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +30 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +58 -13
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +32 -19
- package/node_modules/@nklisch/pi-plugins/package.json +3 -3
- package/package.json +1 -1
- package/test/verify-bundle.mjs +28 -1
|
@@ -4,7 +4,7 @@ Pi Clearance makes long-running Pi sessions feel fast without turning tool calls
|
|
|
4
4
|
|
|
5
5
|
## Product shape
|
|
6
6
|
|
|
7
|
-
1. **Structural analysis** —
|
|
7
|
+
1. **Structural analysis** — Bash and explicitly opted-in typed Pi tools become inspectable shapes; parser uncertainty reviews or denies. Non-Bash tools not opted in remain host-approved bypasses.
|
|
8
8
|
2. **Broad built-in baseline** — common development, project-scoped mutation, network-read, typed research, non-secret home/support reads, and safe-home workflows are active by default while the sealed floor remains non-overridable.
|
|
9
9
|
3. **One behavioral dial** — `mode: off | ask | auto` globally controls only dispatch of deterministic `review` results.
|
|
10
10
|
4. **Model reviewer** — Auto mode resolves one uncertain call with configurable prompt/context/model details; model decisions never become policy.
|
|
@@ -16,8 +16,8 @@ Pi Clearance makes long-running Pi sessions feel fast without turning tool calls
|
|
|
16
16
|
- **Ask:** known-safe calls run; uncertainty asks the user and blocks unattended calls.
|
|
17
17
|
- **Auto:** known-safe calls run; uncertainty goes model-first, then human/block fallback.
|
|
18
18
|
|
|
19
|
-
The sealed floor always wins. Invalid config fails closed to floor-only policy.
|
|
19
|
+
The sealed floor always wins. Invalid config fails closed to floor-only policy. Non-Bash tools bypass Clearance by default and can be opted in by exact name through global `gatedTools`; Bash remains fully gated. `unknownToolPosture` applies only to opted-in unknown tools.
|
|
20
20
|
|
|
21
21
|
## Trust and growth
|
|
22
22
|
|
|
23
|
-
User-owned global/project config may widen policy. Repository policy and installed packs cannot silently widen it. Tune replay, adversarial checks, explicit confirmation, and post-write validation make policy growth auditable.
|
|
23
|
+
User-owned global/project config may widen policy. Repository policy and installed packs cannot silently widen it. Persisted settings written through Clearance contain only non-default choices, while runtime normalization supplies the rest. Package installation neither changes config nor enables packs. Tune replay, adversarial checks, explicit confirmation, and post-write validation make policy growth auditable. The default non-Bash bypass is an intentional published behavioral break requiring next-minor release communication.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nklisch/pi-clearance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Configurable auto-reviewer Pi extension for parsed, structural command policy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,13 +15,17 @@
|
|
|
15
15
|
"tree-sitter"
|
|
16
16
|
],
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=22"
|
|
18
|
+
"node": ">=22.18"
|
|
19
19
|
},
|
|
20
20
|
"napi": {
|
|
21
21
|
"binaryName": "clearance-core",
|
|
22
22
|
"targets": [
|
|
23
23
|
"x86_64-unknown-linux-gnu",
|
|
24
|
-
"aarch64-
|
|
24
|
+
"aarch64-unknown-linux-gnu",
|
|
25
|
+
"x86_64-apple-darwin",
|
|
26
|
+
"aarch64-apple-darwin",
|
|
27
|
+
"x86_64-pc-windows-msvc",
|
|
28
|
+
"aarch64-pc-windows-msvc"
|
|
25
29
|
]
|
|
26
30
|
},
|
|
27
31
|
"exports": {
|
|
@@ -59,8 +63,7 @@
|
|
|
59
63
|
"cargo:fmt": "cargo fmt --all -- --check"
|
|
60
64
|
},
|
|
61
65
|
"dependencies": {
|
|
62
|
-
"@sinclair/typebox": "^0.34.49"
|
|
63
|
-
"jiti": "^2.7.0"
|
|
66
|
+
"@sinclair/typebox": "^0.34.49"
|
|
64
67
|
},
|
|
65
68
|
"peerDependencies": {
|
|
66
69
|
"@earendil-works/pi-ai": "*",
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
"@earendil-works/pi-tui": "0.82.0",
|
|
74
77
|
"@napi-rs/cli": "^3.7.4",
|
|
75
78
|
"@types/node": "^24.0.0",
|
|
79
|
+
"jiti": "^2.7.0",
|
|
76
80
|
"tsx": "^4.23.1",
|
|
77
81
|
"typescript": "^5.9.3",
|
|
78
82
|
"vitest": "^4.0.0",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
+
import { DEFAULT_PROJECT_SCOPE_BEHAVIOR } from "./defaults.ts";
|
|
4
|
+
import { isExactNonBashToolName } from "./gated-tools.ts";
|
|
3
5
|
import type { JsonPatchOperation } from "../replay/proposal-schema.ts";
|
|
4
6
|
import { isHighCostReviewerModel } from "../runtime/reviewer-model.ts";
|
|
5
7
|
import type {
|
|
@@ -91,10 +93,12 @@ export function inferScopePreset(
|
|
|
91
93
|
const fields = SCOPE_PRESET_FIELDS[preset];
|
|
92
94
|
if (
|
|
93
95
|
scope.safeHomeUseDefaults === fields.safeHomeUseDefaults &&
|
|
94
|
-
(scope.agentSupportUseDefaults
|
|
96
|
+
(scope.agentSupportUseDefaults !== false) ===
|
|
95
97
|
fields.agentSupportUseDefaults &&
|
|
96
|
-
(scope.homePathBehavior ??
|
|
97
|
-
|
|
98
|
+
(scope.homePathBehavior ?? DEFAULT_PROJECT_SCOPE_BEHAVIOR.homePathBehavior) ===
|
|
99
|
+
fields.homePathBehavior &&
|
|
100
|
+
(scope.sensitivePathBehavior ??
|
|
101
|
+
DEFAULT_PROJECT_SCOPE_BEHAVIOR.sensitivePathBehavior) ===
|
|
98
102
|
fields.sensitivePathBehavior &&
|
|
99
103
|
scope.unknownPathBehavior === fields.unknownPathBehavior
|
|
100
104
|
) {
|
|
@@ -137,6 +141,10 @@ export type ReviewNoteDisplayCommandChange =
|
|
|
137
141
|
| { readonly kind: "show-model-label"; readonly enabled: boolean }
|
|
138
142
|
| { readonly kind: "accent"; readonly enabled: boolean };
|
|
139
143
|
|
|
144
|
+
export type GatedToolCommandChange =
|
|
145
|
+
| { readonly kind: "add"; readonly toolName: string }
|
|
146
|
+
| { readonly kind: "remove"; readonly toolName: string };
|
|
147
|
+
|
|
140
148
|
export type ReviewerCommandChange =
|
|
141
149
|
| { readonly kind: "prompt-posture"; readonly promptPosture: string }
|
|
142
150
|
| { readonly kind: "context-mode"; readonly contextMode: ReviewerContextMode }
|
|
@@ -181,6 +189,74 @@ export type PlanProjectScopeCommandChangeResult =
|
|
|
181
189
|
}
|
|
182
190
|
| { readonly ok: false; readonly reason: string };
|
|
183
191
|
|
|
192
|
+
export function planGatedToolCommandChange(input: {
|
|
193
|
+
readonly change: GatedToolCommandChange;
|
|
194
|
+
readonly resolvedConfig: ResolvedConfig;
|
|
195
|
+
}):
|
|
196
|
+
| { readonly ok: true; readonly plan: ConfigCommandPlan }
|
|
197
|
+
| { readonly ok: false; readonly reason: string } {
|
|
198
|
+
const toolName = input.change.toolName;
|
|
199
|
+
if (!isExactNonBashToolName(toolName)) {
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
reason:
|
|
203
|
+
"gatedTools accepts exact non-Bash tool names only; wildcards, whitespace, and bash are not allowed",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const snapshots = input.resolvedConfig.sourceSnapshots;
|
|
208
|
+
if (snapshots === undefined) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
reason:
|
|
212
|
+
"resolved config did not include source snapshots; reload config before planning a gated-tool write",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const before = cloneJsonish({
|
|
217
|
+
...snapshots.global,
|
|
218
|
+
gatedTools: [...(snapshots.global.gatedTools ?? [])],
|
|
219
|
+
});
|
|
220
|
+
const after = cloneJsonish(before) as Mutable<GlobalConfig>;
|
|
221
|
+
const current = [...before.gatedTools];
|
|
222
|
+
const next =
|
|
223
|
+
input.change.kind === "add"
|
|
224
|
+
? current.includes(toolName)
|
|
225
|
+
? current
|
|
226
|
+
: [...current, toolName]
|
|
227
|
+
: current.filter((candidate) => candidate !== toolName);
|
|
228
|
+
after.gatedTools = next;
|
|
229
|
+
const patch = fieldPatch("/gatedTools", current, next);
|
|
230
|
+
const title =
|
|
231
|
+
input.change.kind === "add"
|
|
232
|
+
? `Gate non-Bash tool ${toolName}`
|
|
233
|
+
: `Stop gating non-Bash tool ${toolName}`;
|
|
234
|
+
const planWithoutId = {
|
|
235
|
+
target: { kind: "global", path: snapshots.paths.globalConfigFile },
|
|
236
|
+
title,
|
|
237
|
+
summary:
|
|
238
|
+
patch.length === 0
|
|
239
|
+
? `${title}; no config write is needed.`
|
|
240
|
+
: `${title}; only this exact tool name will use Clearance analysis and policy.`,
|
|
241
|
+
patch,
|
|
242
|
+
before,
|
|
243
|
+
after,
|
|
244
|
+
requiredAcknowledgementCodes: [],
|
|
245
|
+
warnings: [],
|
|
246
|
+
} satisfies Omit<ConfigCommandPlan, "id">;
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
ok: true,
|
|
250
|
+
plan: {
|
|
251
|
+
id: deterministicPlanId({
|
|
252
|
+
gatedToolChange: input.change,
|
|
253
|
+
...planWithoutId,
|
|
254
|
+
}),
|
|
255
|
+
...planWithoutId,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
184
260
|
export function planModeCommandChange(input: {
|
|
185
261
|
readonly mode: ClearanceMode;
|
|
186
262
|
readonly resolvedConfig: ResolvedConfig;
|
|
@@ -12,6 +12,10 @@ import type { TSchema } from "@sinclair/typebox";
|
|
|
12
12
|
|
|
13
13
|
import type { JsonPatchOperation } from "../replay/proposal-schema.ts";
|
|
14
14
|
import type { ResolvedConfig } from "./loader.ts";
|
|
15
|
+
import {
|
|
16
|
+
type ConfigPersistenceKind,
|
|
17
|
+
serializeSparseConfigText,
|
|
18
|
+
} from "./persistence.ts";
|
|
15
19
|
import {
|
|
16
20
|
type GlobalConfig,
|
|
17
21
|
GlobalConfigSchema,
|
|
@@ -101,6 +105,7 @@ export type ConfigCommandApplyResult =
|
|
|
101
105
|
interface AtomicConfigWriteInput {
|
|
102
106
|
readonly planId: string;
|
|
103
107
|
readonly targetPath: string;
|
|
108
|
+
readonly configKind: ConfigPersistenceKind;
|
|
104
109
|
readonly value: GlobalConfig | ProjectOverlayConfig;
|
|
105
110
|
readonly hadExistingFile: boolean;
|
|
106
111
|
readonly reloadConfig: () => Promise<ResolvedConfig>;
|
|
@@ -184,6 +189,7 @@ export async function applyConfigCommandPlan(
|
|
|
184
189
|
return await writeConfigTargetAndValidate({
|
|
185
190
|
planId: plan.id,
|
|
186
191
|
targetPath: plan.target.path,
|
|
192
|
+
configKind: plan.target.kind,
|
|
187
193
|
value: patched.value,
|
|
188
194
|
hadExistingFile: readResult.exists,
|
|
189
195
|
reloadConfig: dependencies.reloadConfig,
|
|
@@ -207,7 +213,7 @@ export async function writeConfigTargetAndValidate(
|
|
|
207
213
|
await mkdir(path.dirname(input.targetPath), { recursive: true });
|
|
208
214
|
await writeFile(
|
|
209
215
|
tempPath,
|
|
210
|
-
|
|
216
|
+
serializeSparseConfigText(input.configKind, input.value),
|
|
211
217
|
"utf8",
|
|
212
218
|
);
|
|
213
219
|
tempPresent = true;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-visible defaults shared by schema normalization and status/read models.
|
|
3
|
+
* Keeping these values in one module prevents a new schema default from being
|
|
4
|
+
* mistaken for a customization by diagnostics.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_REVIEWER_PROMPT_POSTURE = "reviewer.default" as const;
|
|
7
|
+
export const DEFAULT_REVIEWER_CONTEXT_MODE = "recentContext" as const;
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_REVIEWER_TOKEN_BUDGET = {
|
|
10
|
+
window: "24h",
|
|
11
|
+
limit: null,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_REVIEWER_RECENT_CONTEXT = {
|
|
15
|
+
decisionLimit: 25,
|
|
16
|
+
decisionWindow: "2h",
|
|
17
|
+
conversationTurns: 3,
|
|
18
|
+
userTurns: 5,
|
|
19
|
+
conversationCharLimit: 6000,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_REVIEWER_ESCALATION = {
|
|
23
|
+
enabled: true,
|
|
24
|
+
denialLimit: 3,
|
|
25
|
+
window: "10m",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_UNKNOWN_TOOL_POSTURE = "allow" as const;
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_PROJECT_SCOPE_BEHAVIOR = {
|
|
31
|
+
safeHomeUseDefaults: true,
|
|
32
|
+
agentSupportUseDefaults: true,
|
|
33
|
+
unknownPathBehavior: "review",
|
|
34
|
+
sensitivePathBehavior: "review",
|
|
35
|
+
homePathBehavior: "allow",
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_REVIEW_NOTE_DISPLAY = {
|
|
39
|
+
mode: "reason+accent",
|
|
40
|
+
showModelLabel: false,
|
|
41
|
+
accent: true,
|
|
42
|
+
} as const;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Exact-name syntax for the global non-Bash Clearance opt-in list. */
|
|
2
|
+
export const EXACT_NON_BASH_TOOL_NAME_PATTERN =
|
|
3
|
+
"^(?!bash$)[^\\s*?\\[\\]{}]+$" as const;
|
|
4
|
+
|
|
5
|
+
export function isExactNonBashToolName(value: string): boolean {
|
|
6
|
+
return value.length > 0 && new RegExp(EXACT_NON_BASH_TOOL_NAME_PATTERN).test(value);
|
|
7
|
+
}
|
|
@@ -3,6 +3,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
5
5
|
import type { DecisionEffect } from "../policy/core.ts";
|
|
6
|
+
import { DEFAULT_UNKNOWN_TOOL_POSTURE } from "./defaults.ts";
|
|
6
7
|
import {
|
|
7
8
|
isHighCostReviewerModel,
|
|
8
9
|
parseModelSpec,
|
|
@@ -69,6 +70,7 @@ export interface ResolvedReviewerConfig {
|
|
|
69
70
|
readonly decisionLimit: number;
|
|
70
71
|
readonly decisionWindow: string;
|
|
71
72
|
readonly conversationTurns: number;
|
|
73
|
+
readonly userTurns?: number;
|
|
72
74
|
readonly conversationCharLimit: number;
|
|
73
75
|
};
|
|
74
76
|
readonly escalation: {
|
|
@@ -133,6 +135,8 @@ export interface ResolvedConfig {
|
|
|
133
135
|
*/
|
|
134
136
|
readonly sourceSnapshots?: ResolvedConfigSourceSnapshots;
|
|
135
137
|
readonly mode: ClearanceMode;
|
|
138
|
+
/** Exact non-Bash tool names explicitly opted into Clearance gating. */
|
|
139
|
+
readonly gatedTools?: readonly string[];
|
|
136
140
|
readonly unknownToolPosture: DecisionEffect;
|
|
137
141
|
readonly projectScope: ResolvedProjectScope;
|
|
138
142
|
readonly packEnablement: ResolvedPackEnablement;
|
|
@@ -250,7 +254,9 @@ export async function loadConfig(
|
|
|
250
254
|
repository: repositoryPolicy,
|
|
251
255
|
},
|
|
252
256
|
mode,
|
|
253
|
-
|
|
257
|
+
gatedTools: [...globalConfig.gatedTools],
|
|
258
|
+
unknownToolPosture:
|
|
259
|
+
globalConfig.unknownToolPosture ?? DEFAULT_UNKNOWN_TOOL_POSTURE,
|
|
254
260
|
projectScope: projectScopeResolution.scope,
|
|
255
261
|
packEnablement,
|
|
256
262
|
globalPacks: withPackSource(globalConfig.packs, "user-global"),
|
|
@@ -113,6 +113,7 @@ export async function applyPackEnablementPlan(
|
|
|
113
113
|
return await writeConfigTargetAndValidate({
|
|
114
114
|
planId: plan.id,
|
|
115
115
|
targetPath: plan.targetPath,
|
|
116
|
+
configKind: plan.request.scope,
|
|
116
117
|
value: patched.value,
|
|
117
118
|
hadExistingFile: readResult.exists,
|
|
118
119
|
reloadConfig: dependencies.reloadConfig,
|
|
@@ -12,12 +12,18 @@ export interface ConfigPaths {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function resolveUserConfigRoot(): string {
|
|
15
|
-
|
|
15
|
+
// Read the environment explicitly so isolated callers honor the process
|
|
16
|
+
// environment even when os.homedir() is cached.
|
|
17
|
+
const home = firstNonEmpty(
|
|
18
|
+
process.env.HOME,
|
|
19
|
+
process.env.USERPROFILE,
|
|
20
|
+
) ?? homedir();
|
|
16
21
|
|
|
17
22
|
switch (process.platform) {
|
|
18
23
|
case "win32":
|
|
19
24
|
return path.join(
|
|
20
|
-
process.env.LOCALAPPDATA ??
|
|
25
|
+
firstNonEmpty(process.env.LOCALAPPDATA) ??
|
|
26
|
+
path.join(home, "AppData", "Local"),
|
|
21
27
|
"pi",
|
|
22
28
|
"pi-clearance",
|
|
23
29
|
);
|
|
@@ -31,13 +37,18 @@ export function resolveUserConfigRoot(): string {
|
|
|
31
37
|
);
|
|
32
38
|
default:
|
|
33
39
|
return path.join(
|
|
34
|
-
process.env.XDG_CONFIG_HOME ??
|
|
40
|
+
firstNonEmpty(process.env.XDG_CONFIG_HOME) ??
|
|
41
|
+
path.join(home, ".config"),
|
|
35
42
|
"pi",
|
|
36
43
|
"pi-clearance",
|
|
37
44
|
);
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
function firstNonEmpty(...values: readonly (string | undefined)[]): string | undefined {
|
|
49
|
+
return values.find((value) => value !== undefined && value.length > 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
export function projectKeyFor(cwd: string): string {
|
|
42
53
|
const absolute = path.resolve(cwd);
|
|
43
54
|
const hash = createHash("sha256").update(absolute).digest("hex").slice(0, 8);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIG_SCHEMA_VERSION,
|
|
3
|
+
type GlobalConfig,
|
|
4
|
+
GlobalConfigSchema,
|
|
5
|
+
normalizeConfig,
|
|
6
|
+
type ProjectOverlayConfig,
|
|
7
|
+
ProjectOverlaySchema,
|
|
8
|
+
} from "./schema.ts";
|
|
9
|
+
|
|
10
|
+
export type ConfigPersistenceKind = "global" | "project";
|
|
11
|
+
export type NormalizedConfig = GlobalConfig | ProjectOverlayConfig;
|
|
12
|
+
|
|
13
|
+
/** The only JSON shape emitted for user-owned Clearance config files. */
|
|
14
|
+
export type SparseConfigDocument = Readonly<Record<string, unknown>> & {
|
|
15
|
+
readonly version: typeof CONFIG_SCHEMA_VERSION;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const OMIT = Symbol("omit-default");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Serialize a schema-normalized runtime config without materializing defaults.
|
|
22
|
+
*
|
|
23
|
+
* Defaults are obtained by normalizing the schema's version-only seed rather
|
|
24
|
+
* than copied here. Objects are compacted recursively; arrays are semantic
|
|
25
|
+
* values and are retained as a whole whenever they differ from their default.
|
|
26
|
+
*/
|
|
27
|
+
export function serializeSparseConfig(
|
|
28
|
+
kind: ConfigPersistenceKind,
|
|
29
|
+
config: NormalizedConfig,
|
|
30
|
+
): SparseConfigDocument {
|
|
31
|
+
const defaults = normalizedDefaults(kind);
|
|
32
|
+
const compacted = compactValue(config, defaults);
|
|
33
|
+
const document: Record<string, unknown> = {
|
|
34
|
+
version: config.version,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (isRecord(compacted)) {
|
|
38
|
+
for (const [key, value] of Object.entries(compacted)) {
|
|
39
|
+
if (key !== "version") {
|
|
40
|
+
document[key] = value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return canonicalize(document) as SparseConfigDocument;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Return the canonical on-disk representation for a normalized config. */
|
|
49
|
+
export function serializeSparseConfigText(
|
|
50
|
+
kind: ConfigPersistenceKind,
|
|
51
|
+
config: NormalizedConfig,
|
|
52
|
+
): string {
|
|
53
|
+
return `${JSON.stringify(serializeSparseConfig(kind, config), null, 2)}\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizedDefaults(kind: ConfigPersistenceKind): NormalizedConfig {
|
|
57
|
+
const schema = kind === "global" ? GlobalConfigSchema : ProjectOverlaySchema;
|
|
58
|
+
const result = normalizeConfig(schema, { version: CONFIG_SCHEMA_VERSION });
|
|
59
|
+
if (!result.ok) {
|
|
60
|
+
throw new Error(`failed to normalize ${kind} config defaults`);
|
|
61
|
+
}
|
|
62
|
+
return result.value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function compactValue(value: unknown, defaults: unknown): unknown {
|
|
66
|
+
if (jsonEqual(value, defaults)) {
|
|
67
|
+
return OMIT;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value.map((entry) => cloneJsonish(entry));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isRecord(value)) {
|
|
75
|
+
const defaultRecord = isRecord(defaults) ? defaults : undefined;
|
|
76
|
+
const compacted: Record<string, unknown> = {};
|
|
77
|
+
for (const key of orderedKeys(Object.keys(value))) {
|
|
78
|
+
const child = compactValue(value[key], defaultRecord?.[key]);
|
|
79
|
+
if (child !== OMIT) {
|
|
80
|
+
compacted[key] = child;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return Object.keys(compacted).length === 0 ? OMIT : compacted;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function canonicalize(value: unknown): unknown {
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
return value.map(canonicalize);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (isRecord(value)) {
|
|
95
|
+
const result: Record<string, unknown> = {};
|
|
96
|
+
for (const key of orderedKeys(Object.keys(value))) {
|
|
97
|
+
result[key] = canonicalize(value[key]);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function orderedKeys(keys: readonly string[]): readonly string[] {
|
|
106
|
+
return [...keys].sort((left, right) => {
|
|
107
|
+
if (left === "version") return -1;
|
|
108
|
+
if (right === "version") return 1;
|
|
109
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function jsonEqual(left: unknown, right: unknown): boolean {
|
|
114
|
+
return (
|
|
115
|
+
JSON.stringify(canonicalize(left)) === JSON.stringify(canonicalize(right))
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function cloneJsonish<T>(value: T): T {
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value.map((entry) => cloneJsonish(entry)) as T;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isRecord(value)) {
|
|
125
|
+
return Object.fromEntries(
|
|
126
|
+
Object.entries(value).map(([key, entry]) => [key, cloneJsonish(entry)]),
|
|
127
|
+
) as T;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
134
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
135
|
+
}
|