gentle-pi 4.0.0 → 4.2.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/README.md +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
SESSION_PROFILE_CUSTOM_TYPE,
|
|
5
|
+
createSessionProfileBind,
|
|
6
|
+
createSessionProfileClear,
|
|
7
|
+
readSessionProfileEntry,
|
|
8
|
+
replaySessionProfileBranch,
|
|
9
|
+
} from "../lib/session-profile-persistence.ts";
|
|
10
|
+
|
|
11
|
+
import { normalizeModelConfig } from "../lib/model-routing-authority.ts";
|
|
12
|
+
import {
|
|
13
|
+
normalizeProfilesFile,
|
|
14
|
+
PROFILES_KIND,
|
|
15
|
+
PROFILES_VERSION,
|
|
16
|
+
} from "../lib/agent-profiles.ts";
|
|
17
|
+
|
|
18
|
+
const custom = (data: unknown, customType = SESSION_PROFILE_CUSTOM_TYPE) => ({
|
|
19
|
+
type: "custom",
|
|
20
|
+
customType,
|
|
21
|
+
data,
|
|
22
|
+
});
|
|
23
|
+
const bind = (modelProfiles: unknown = {}) => ({
|
|
24
|
+
kind: "bind",
|
|
25
|
+
origin: "user",
|
|
26
|
+
name: "work",
|
|
27
|
+
modelProfiles,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// These are supplied branch entries, not evidence of persistence on disk.
|
|
31
|
+
test("writer emits only the v1 user binding fields and a minimal clear", () => {
|
|
32
|
+
assert.equal(SESSION_PROFILE_CUSTOM_TYPE, "gentle-pi.session-profile/v1");
|
|
33
|
+
assert.deepEqual(createSessionProfileBind("work", {}), bind());
|
|
34
|
+
assert.deepEqual(createSessionProfileClear(), { kind: "clear" });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("writer accepts real normalized optional fields without persisting undefined", () => {
|
|
38
|
+
const raw = {
|
|
39
|
+
worker: "provider/model",
|
|
40
|
+
reviewer: { effort: "high" },
|
|
41
|
+
orchestrator: { model: "provider/main" },
|
|
42
|
+
inheriting: {},
|
|
43
|
+
};
|
|
44
|
+
const routes = normalizeModelConfig(raw);
|
|
45
|
+
const profiles = normalizeProfilesFile({
|
|
46
|
+
kind: PROFILES_KIND,
|
|
47
|
+
version: PROFILES_VERSION,
|
|
48
|
+
profiles: { work: raw },
|
|
49
|
+
});
|
|
50
|
+
assert.ok(routes);
|
|
51
|
+
assert.ok(profiles);
|
|
52
|
+
assert.equal(Object.hasOwn(routes.reviewer, "model"), true);
|
|
53
|
+
assert.equal(Object.hasOwn(routes.reviewer, "thinking"), true);
|
|
54
|
+
const expected = bind({
|
|
55
|
+
worker: { model: "provider/model" },
|
|
56
|
+
reviewer: { thinking: "high" },
|
|
57
|
+
orchestrator: { model: "provider/main" },
|
|
58
|
+
inheriting: {},
|
|
59
|
+
});
|
|
60
|
+
for (const config of [routes, profiles.file.profiles.work]) {
|
|
61
|
+
const payload = createSessionProfileBind("work", config);
|
|
62
|
+
assert.deepEqual(payload, expected);
|
|
63
|
+
assert.deepEqual(readSessionProfileEntry(custom(payload)), {
|
|
64
|
+
status: "bound",
|
|
65
|
+
binding: expected,
|
|
66
|
+
});
|
|
67
|
+
assert.notEqual(payload.modelProfiles.reviewer, config.reviewer);
|
|
68
|
+
}
|
|
69
|
+
assert.equal(Object.hasOwn(routes.reviewer, "model"), true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("decoder stays strict about present undefined known fields", () => {
|
|
73
|
+
for (const route of [
|
|
74
|
+
{ model: undefined },
|
|
75
|
+
{ thinking: undefined },
|
|
76
|
+
{ effort: undefined },
|
|
77
|
+
]) {
|
|
78
|
+
assert.deepEqual(readSessionProfileEntry(custom(bind({ worker: route }))), {
|
|
79
|
+
status: "invalid",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("bound empty snapshot is distinct from absent and cleared", () => {
|
|
85
|
+
assert.deepEqual(readSessionProfileEntry(custom(bind())), {
|
|
86
|
+
status: "bound",
|
|
87
|
+
binding: bind(),
|
|
88
|
+
});
|
|
89
|
+
assert.deepEqual(replaySessionProfileBranch([]), { status: "absent" });
|
|
90
|
+
assert.deepEqual(readSessionProfileEntry(custom({ kind: "clear" })), {
|
|
91
|
+
status: "cleared",
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("reader accepts all four origins and ignores unknown payload fields", () => {
|
|
96
|
+
for (const origin of ["user", "local", "repo", "global"]) {
|
|
97
|
+
assert.deepEqual(
|
|
98
|
+
readSessionProfileEntry(
|
|
99
|
+
custom({ ...bind(), origin, extra: true, version: 99 }),
|
|
100
|
+
),
|
|
101
|
+
{
|
|
102
|
+
status: "bound",
|
|
103
|
+
binding: { ...bind(), origin },
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
assert.deepEqual(
|
|
108
|
+
readSessionProfileEntry(custom({ kind: "clear", extra: true })),
|
|
109
|
+
{ status: "cleared" },
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("legacy strings and effort normalize while orchestrator remains in the snapshot", () => {
|
|
114
|
+
const result = readSessionProfileEntry(
|
|
115
|
+
custom(
|
|
116
|
+
bind({
|
|
117
|
+
worker: " provider/model ",
|
|
118
|
+
reviewer: { effort: "high", ignored: true },
|
|
119
|
+
orchestrator: { model: "provider/main", thinking: "low", effort: "max" },
|
|
120
|
+
inheriting: {},
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
assert.deepEqual(result, {
|
|
125
|
+
status: "bound",
|
|
126
|
+
binding: bind({
|
|
127
|
+
worker: { model: "provider/model" },
|
|
128
|
+
reviewer: { thinking: "high" },
|
|
129
|
+
orchestrator: { model: "provider/main", thinking: "low" },
|
|
130
|
+
inheriting: {},
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("missing required fields and invalid known meanings fail closed", () => {
|
|
136
|
+
const invalid: unknown[] = [
|
|
137
|
+
null,
|
|
138
|
+
[],
|
|
139
|
+
{},
|
|
140
|
+
{ kind: "other" },
|
|
141
|
+
{ kind: "bind", name: "work", modelProfiles: {} },
|
|
142
|
+
{ ...bind(), origin: "future" },
|
|
143
|
+
{ ...bind(), name: "../work" },
|
|
144
|
+
{ ...bind(), name: "constructor" },
|
|
145
|
+
{ ...bind(), modelProfiles: undefined },
|
|
146
|
+
{ ...bind(), modelProfiles: [] },
|
|
147
|
+
];
|
|
148
|
+
for (const data of invalid) {
|
|
149
|
+
assert.deepEqual(readSessionProfileEntry(custom(data)), {
|
|
150
|
+
status: "invalid",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("one invalid route invalidates the whole snapshot instead of silently dropping it", () => {
|
|
156
|
+
const routes: unknown[] = [
|
|
157
|
+
null,
|
|
158
|
+
[],
|
|
159
|
+
3,
|
|
160
|
+
"bad model",
|
|
161
|
+
{ model: "bad model", thinking: "low" },
|
|
162
|
+
{ model: "provider/good", thinking: "extreme" },
|
|
163
|
+
{ effort: "extreme" },
|
|
164
|
+
{ thinking: "low", effort: "extreme" },
|
|
165
|
+
{ model: null },
|
|
166
|
+
];
|
|
167
|
+
for (const route of routes) {
|
|
168
|
+
assert.deepEqual(readSessionProfileEntry(custom(bind({ worker: route }))), {
|
|
169
|
+
status: "invalid",
|
|
170
|
+
});
|
|
171
|
+
assert.deepEqual(
|
|
172
|
+
readSessionProfileEntry(
|
|
173
|
+
custom(bind({ good: "provider/good", worker: route })),
|
|
174
|
+
),
|
|
175
|
+
{ status: "invalid" },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
assert.deepEqual(readSessionProfileEntry(custom(bind({ "bad role": {} }))), {
|
|
179
|
+
status: "invalid",
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("unknown route fields are ignored, including an unknown-only route", () => {
|
|
184
|
+
assert.deepEqual(
|
|
185
|
+
readSessionProfileEntry(custom(bind({ worker: { future: true } }))),
|
|
186
|
+
{
|
|
187
|
+
status: "bound",
|
|
188
|
+
binding: bind({ worker: {} }),
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("hostile record keys remain own data without changing prototypes", () => {
|
|
194
|
+
const routes = JSON.parse(
|
|
195
|
+
'{"__proto__":{"model":"provider/safe"},"constructor":{},"prototype":{}}',
|
|
196
|
+
);
|
|
197
|
+
const result = readSessionProfileEntry(custom(bind(routes)));
|
|
198
|
+
assert.equal(result.status, "bound");
|
|
199
|
+
if (result.status !== "bound") return;
|
|
200
|
+
assert.equal(
|
|
201
|
+
Object.getPrototypeOf(result.binding.modelProfiles),
|
|
202
|
+
Object.prototype,
|
|
203
|
+
);
|
|
204
|
+
assert.equal(Object.hasOwn(result.binding.modelProfiles, "__proto__"), true);
|
|
205
|
+
assert.deepEqual(result.binding.modelProfiles["__proto__"], {
|
|
206
|
+
model: "provider/safe",
|
|
207
|
+
});
|
|
208
|
+
for (const key of ["constructor", "prototype"]) {
|
|
209
|
+
assert.equal(Object.hasOwn(result.binding.modelProfiles, key), true);
|
|
210
|
+
assert.deepEqual(result.binding.modelProfiles[key], {});
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("required fields cannot be supplied by prototypes", () => {
|
|
215
|
+
assert.deepEqual(readSessionProfileEntry(custom(Object.create(bind()))), {
|
|
216
|
+
status: "invalid",
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("writer rejects invalid input rather than manufacturing an empty snapshot", () => {
|
|
221
|
+
assert.throws(() => createSessionProfileBind("../bad", {}));
|
|
222
|
+
assert.throws(() =>
|
|
223
|
+
createSessionProfileBind("work", { worker: { model: "bad model" } }),
|
|
224
|
+
);
|
|
225
|
+
for (const route of [
|
|
226
|
+
{ model: null },
|
|
227
|
+
{ model: "bad model", thinking: undefined },
|
|
228
|
+
{ model: undefined, thinking: "extreme" },
|
|
229
|
+
{ effort: undefined },
|
|
230
|
+
]) {
|
|
231
|
+
assert.throws(() =>
|
|
232
|
+
createSessionProfileBind("work", {
|
|
233
|
+
good: { model: "provider/good" },
|
|
234
|
+
worker: route,
|
|
235
|
+
} as Parameters<typeof createSessionProfileBind>[1]),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("writer and reader return defensive snapshots", () => {
|
|
241
|
+
const source = { worker: { model: "provider/first" } };
|
|
242
|
+
const payload = createSessionProfileBind("work", source);
|
|
243
|
+
source.worker.model = "provider/second";
|
|
244
|
+
assert.equal(payload.modelProfiles.worker.model, "provider/first");
|
|
245
|
+
const entry = custom(payload);
|
|
246
|
+
const first = readSessionProfileEntry(entry);
|
|
247
|
+
assert.equal(first.status, "bound");
|
|
248
|
+
if (first.status !== "bound") return;
|
|
249
|
+
first.binding.modelProfiles.worker.model = "provider/third";
|
|
250
|
+
const second = readSessionProfileEntry(entry);
|
|
251
|
+
assert.equal(second.status, "bound");
|
|
252
|
+
if (second.status === "bound")
|
|
253
|
+
assert.equal(second.binding.modelProfiles.worker.model, "provider/first");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("noncustom and unrelated custom entries are absent even with matching payloads", () => {
|
|
257
|
+
for (const entry of [
|
|
258
|
+
{ type: "message", customType: SESSION_PROFILE_CUSTOM_TYPE, data: bind() },
|
|
259
|
+
{ type: "message", message: { role: "user", content: "hello" } },
|
|
260
|
+
custom(bind(), "other/v1"),
|
|
261
|
+
])
|
|
262
|
+
assert.deepEqual(readSessionProfileEntry(entry), { status: "absent" });
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("newest family entry is terminal and retains its actual branch index", () => {
|
|
266
|
+
const old = custom(bind({ worker: "provider/old" }));
|
|
267
|
+
const message = {
|
|
268
|
+
type: "message",
|
|
269
|
+
message: { role: "user", content: "hello" },
|
|
270
|
+
};
|
|
271
|
+
for (const [entry, status] of [
|
|
272
|
+
[custom({ kind: "clear" }), "cleared"],
|
|
273
|
+
[custom({ kind: "bind" }), "invalid"],
|
|
274
|
+
[custom(bind(), "gentle-pi.session-profile/v2"), "unsupported"],
|
|
275
|
+
] as const) {
|
|
276
|
+
assert.deepEqual(
|
|
277
|
+
replaySessionProfileBranch([old, message, entry, custom({}, "other/v1")]),
|
|
278
|
+
{
|
|
279
|
+
status,
|
|
280
|
+
entryIndex: 2,
|
|
281
|
+
},
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("family identifier, not payload version, determines support", () => {
|
|
287
|
+
for (const customType of [
|
|
288
|
+
"gentle-pi.session-profile/v0",
|
|
289
|
+
"gentle-pi.session-profile/v99",
|
|
290
|
+
"gentle-pi.session-profile/future",
|
|
291
|
+
]) {
|
|
292
|
+
assert.deepEqual(readSessionProfileEntry(custom(null, customType)), {
|
|
293
|
+
status: "unsupported",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
assert.deepEqual(
|
|
297
|
+
readSessionProfileEntry(custom(bind(), "gentle-pi.session-profile-other/v1")),
|
|
298
|
+
{ status: "absent" },
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("branch order selects latest bind; clear and bad entries can be explicitly superseded", () => {
|
|
303
|
+
const latest = custom(bind({ worker: "provider/new" }));
|
|
304
|
+
for (const earlier of [
|
|
305
|
+
custom({ kind: "clear" }),
|
|
306
|
+
custom(null),
|
|
307
|
+
custom(null, "gentle-pi.session-profile/v2"),
|
|
308
|
+
]) {
|
|
309
|
+
assert.deepEqual(replaySessionProfileBranch([earlier, latest]), {
|
|
310
|
+
status: "bound",
|
|
311
|
+
entryIndex: 1,
|
|
312
|
+
binding: bind({ worker: { model: "provider/new" } }),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("replay sees only caller-supplied active branch, not siblings or later paths", () => {
|
|
318
|
+
const ancestor = custom(bind());
|
|
319
|
+
const sibling = custom({ kind: "clear" });
|
|
320
|
+
const future = custom(null, "gentle-pi.session-profile/v2");
|
|
321
|
+
assert.equal(
|
|
322
|
+
replaySessionProfileBranch([ancestor, sibling]).status,
|
|
323
|
+
"cleared",
|
|
324
|
+
);
|
|
325
|
+
assert.equal(
|
|
326
|
+
replaySessionProfileBranch([ancestor, future]).status,
|
|
327
|
+
"unsupported",
|
|
328
|
+
);
|
|
329
|
+
assert.deepEqual(replaySessionProfileBranch([ancestor, { type: "message" }]), {
|
|
330
|
+
status: "bound",
|
|
331
|
+
entryIndex: 0,
|
|
332
|
+
binding: bind(),
|
|
333
|
+
});
|
|
334
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
const root = join(import.meta.dirname, "..");
|
|
7
|
+
const source = readFileSync(join(root, "extensions/gentle-agents.ts"), "utf8");
|
|
8
|
+
const match = source.match(/name: "orchestrator_session_id",\s*label: "[^"]+",\s*description: ("(?:[^"\\]|\\.)*")/);
|
|
9
|
+
assert.ok(match, "Registered session tool must expose a public description");
|
|
10
|
+
const description: string = JSON.parse(match[1]);
|
|
11
|
+
const detail = readFileSync(join(root, "assets/orchestrator-delegation.md"), "utf8")
|
|
12
|
+
.split("## Session subject and display identity\n")[1].split("### Publish and find classified work")[0];
|
|
13
|
+
const docs = readFileSync(join(root, "docs/gentle-agents-activity.md"), "utf8")
|
|
14
|
+
.split("### Declare a recognizable subject\n")[1].split("### Publish curated state")[0];
|
|
15
|
+
|
|
16
|
+
// The changed behavior is model-facing metadata/guidance, not a new runtime guard.
|
|
17
|
+
test("session declaration guidance exempts small direct tasks in every public surface", () => {
|
|
18
|
+
for (const [name, text] of [["tool description", description], ["coordination policy", detail], ["user docs", docs]]) {
|
|
19
|
+
assert.match(text, /Do not require a subject declaration for small direct tasks/, name);
|
|
20
|
+
assert.match(text, /before delegation or cross-session coordination/i, name);
|
|
21
|
+
assert.doesNotMatch(text, /When starting a task or delegation/, name);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("existing coordination identity and privacy guidance remains intact", () => {
|
|
26
|
+
assert.match(description, /Names never authenticate/);
|
|
27
|
+
assert.match(description, /Existing Pi names and human renames are preserved/);
|
|
28
|
+
assert.match(description, /Never include credentials, internal instructions, or raw prompts/);
|
|
29
|
+
assert.match(detail, /short, non-sensitive `subject`/);
|
|
30
|
+
assert.match(detail, /Batch with setup if possible; no extra model call/);
|
|
31
|
+
assert.match(detail, /Names display only; stable IDs route/);
|
|
32
|
+
assert.match(docs, /preserving existing names\s+and later human renames/);
|
|
33
|
+
assert.match(docs, /Aliases are display hints, never authentication or routing identities/);
|
|
34
|
+
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
+
import childProcess, { execFileSync } from "node:child_process";
|
|
3
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
3
4
|
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import test from "node:test";
|
|
7
8
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { SessionWorktreeRegistry, resolveSessionWorktree, resolveSessionWorktreeWithGit, SESSION_WORKTREE_ENTRY, toolWorktreePath, worktreeGitEnvironment } from "../lib/session-worktree-registry.ts";
|
|
9
|
+
import { SessionWorktreeRegistry, clearSessionWorktreeMemo, memoizeWorktreeResolver, resolveSessionWorktree, resolveSessionWorktreeWithGit, SESSION_WORKTREE_ENTRY, toolWorktreePath, worktreeGitEnvironment } from "../lib/session-worktree-registry.ts";
|
|
10
|
+
import { sessionRepositoryAuthority } from "../lib/bounded-writer-admission.ts";
|
|
9
11
|
|
|
10
12
|
// All Git and session writes belong to unique fixtures, never the live clone.
|
|
11
13
|
function fixture(t: test.TestContext) {
|
|
@@ -48,6 +50,8 @@ test("canonical roots include cwd, dedupe aliases immediately and reject unrelat
|
|
|
48
50
|
assert.deepEqual(registry.roots(), [f.main, f.linked]);
|
|
49
51
|
assert.equal(h.session.getEntries().length, 2);
|
|
50
52
|
assert.throws(() => registry.register(f.other, "explicit"), /same Git clone/);
|
|
53
|
+
// The refusal names the runnable exit for an independent repository (#1915).
|
|
54
|
+
assert.throws(() => registry.register(f.other, "explicit"), /subagent_run with repository_root/);
|
|
51
55
|
assert.throws(() => registry.register(join(f.dir, "missing"), "explicit"), /worktree/);
|
|
52
56
|
assert.equal(resolveSessionWorktree(f.alias, f.main)?.root, f.linked);
|
|
53
57
|
});
|
|
@@ -251,3 +255,54 @@ test("nearest-repository resolution: an inner repo's files never resolve to an o
|
|
|
251
255
|
writeFileSync(outerFile, "outer\n");
|
|
252
256
|
assert.equal(resolveSessionWorktree("README.md", f.outer)?.root, f.outer);
|
|
253
257
|
});
|
|
258
|
+
|
|
259
|
+
// S4 (odd/tasks/startup-home-repo-and-changes.md): every extension resolves the
|
|
260
|
+
// same (cwd, cwd) identity during startup. Count the real Git children through
|
|
261
|
+
// the public resolver, not through an injected seam.
|
|
262
|
+
function countGitSpawns(t: test.TestContext): () => number {
|
|
263
|
+
let spawns = 0;
|
|
264
|
+
const original = childProcess.execFileSync;
|
|
265
|
+
childProcess.execFileSync = ((...args: Parameters<typeof original>) => { if (args[0] === "git") spawns++; return original(...args); }) as typeof original;
|
|
266
|
+
syncBuiltinESMExports();
|
|
267
|
+
t.after(() => { childProcess.execFileSync = original; syncBuiltinESMExports(); });
|
|
268
|
+
return () => spawns;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
test("repeated startup resolutions of one cwd share a single Git resolution", (t) => {
|
|
272
|
+
const f = fixture(t);
|
|
273
|
+
clearSessionWorktreeMemo();
|
|
274
|
+
const spawns = countGitSpawns(t);
|
|
275
|
+
const first = resolveSessionWorktree(f.main, f.main);
|
|
276
|
+
for (let i = 0; i < 5; i++) assert.deepEqual(resolveSessionWorktree(f.main, f.main), first);
|
|
277
|
+
assert.equal(spawns(), 2, "one resolution is two rev-parse children");
|
|
278
|
+
clearSessionWorktreeMemo();
|
|
279
|
+
resolveSessionWorktree(f.main, f.main);
|
|
280
|
+
assert.equal(spawns(), 4, "explicit invalidation forces a fresh resolution");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("memoized identity still observes removal, nested repositories and expiry", (t) => {
|
|
284
|
+
const f = fixture(t);
|
|
285
|
+
let now = 0;
|
|
286
|
+
const resolver = memoizeWorktreeResolver(resolveSessionWorktreeWithGit, { ttlMs: 1000, now: () => now });
|
|
287
|
+
const authority = sessionRepositoryAuthority(f.main, resolver);
|
|
288
|
+
const nested = join(f.main, "nested");
|
|
289
|
+
mkdirSync(nested);
|
|
290
|
+
assert.equal(resolver(nested, f.main)?.root, f.main);
|
|
291
|
+
execFileSync("git", ["init", "--quiet", nested], { env: worktreeGitEnvironment(), stdio: "pipe" });
|
|
292
|
+
assert.equal(resolver(nested, f.main)?.root, realpathSync(nested), "a new nested .git below the cached root is observed");
|
|
293
|
+
assert.equal(authority(), true);
|
|
294
|
+
rmSync(join(f.main, ".git"), { recursive: true, force: true });
|
|
295
|
+
assert.equal(resolver(f.main, f.main), undefined, "a removed clone is never served from the memo");
|
|
296
|
+
assert.equal(authority(), false, "drift checks still see the removal");
|
|
297
|
+
let calls = 0;
|
|
298
|
+
const counted = memoizeWorktreeResolver((path, cwd) => { calls++; return resolveSessionWorktreeWithGit(path, cwd); }, { ttlMs: 1000, now: () => now });
|
|
299
|
+
counted(f.other, f.other);
|
|
300
|
+
counted(f.other, f.other);
|
|
301
|
+
assert.equal(calls, 1);
|
|
302
|
+
now = 1001;
|
|
303
|
+
counted(f.other, f.other);
|
|
304
|
+
assert.equal(calls, 2, "expired entries resolve again");
|
|
305
|
+
counted(join(f.dir, "empty"), f.dir);
|
|
306
|
+
counted(join(f.dir, "empty"), f.dir);
|
|
307
|
+
assert.equal(calls, 4, "a missing repository is never memoized, so bootstrap is observed at once");
|
|
308
|
+
});
|
package/tests/shell-bar.test.ts
CHANGED
|
@@ -107,6 +107,46 @@ test("visual visibility hides only selected optional status segments", () => {
|
|
|
107
107
|
assert.doesNotMatch(renderShellHeaderBar(buildShellHeaderModel(data), plainTheme, 160, undefined, settings).text, /gpt-5\.5|ctx|\$9\.49|usage/);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
test("Status groups running job descriptions beside RDD without a compact count", () => {
|
|
111
|
+
const data = model({ jobs: [
|
|
112
|
+
{ id: "job-2", label: "CI for PR 1860", startedAt: 2 },
|
|
113
|
+
{ id: "job-1", label: "Build runtime", startedAt: 1 },
|
|
114
|
+
], review: { state: "reviewing", scope: "first.ts" } });
|
|
115
|
+
for (const width of [30, 60]) {
|
|
116
|
+
const lines = renderShellSidebarBar(data, plainTheme, width);
|
|
117
|
+
const text = lines.join("\n");
|
|
118
|
+
assert.match(text, /Jobs[\s\S]*CI for PR 1860[\s\S]*Build runtime/);
|
|
119
|
+
assert.match(text, /🌹 RDD/);
|
|
120
|
+
assert.doesNotMatch(text, /2 jobs/);
|
|
121
|
+
assert.ok(lines.every((line) => visibleWidth(line) <= width));
|
|
122
|
+
}
|
|
123
|
+
assert.doesNotMatch(renderShellSidebarBar(model({ jobs: [] }), plainTheme, 60).join("\n"), /Jobs/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("header shows only the running job count when requested and preserves it at mobile widths", () => {
|
|
127
|
+
const data = model({ jobs: [{ id: "job-1", label: "CI for PR 1860", startedAt: 1 }] });
|
|
128
|
+
for (const width of [40, 80, 160]) {
|
|
129
|
+
const text = renderShellHeaderBar(buildShellHeaderModel(data, true), plainTheme, width).text;
|
|
130
|
+
assert.match(text, /⧗ 1 job/);
|
|
131
|
+
assert.doesNotMatch(text, /CI for PR/);
|
|
132
|
+
assert.ok(visibleWidth(text) <= width);
|
|
133
|
+
}
|
|
134
|
+
assert.doesNotMatch(renderShellHeaderBar(buildShellHeaderModel(data), plainTheme, 160).text, /1 job/);
|
|
135
|
+
assert.doesNotMatch(renderShellHeaderBar(buildShellHeaderModel(model({ jobs: [] }), true), plainTheme, 160).text, /jobs?/);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("bottom-only and hidden below-input chrome retain a compact jobs count", (t) => {
|
|
139
|
+
const data = model({ jobs: [{ id: "job-1", label: "CI for PR 1860", startedAt: 1 }] });
|
|
140
|
+
assert.match(renderShellBottomOnlyBar(data, plainTheme, 80).join("\n"), /⧗ 1 job/);
|
|
141
|
+
useCardStyle(t, CARD_STYLE.FLOAT);
|
|
142
|
+
const settings = { ...DEFAULT_VISUAL_SETTINGS, headerPlacement: "below-input" as const, statusPlacement: "hidden" as const };
|
|
143
|
+
const rows = renderShellBelowInputFloat(data, withBackground(plainTheme), 80, undefined, settings)?.rows;
|
|
144
|
+
assert.ok(rows);
|
|
145
|
+
assert.match(rows.join("\n"), /⧗ 1 job/);
|
|
146
|
+
assert.doesNotMatch(rows.join("\n"), /CI for PR/);
|
|
147
|
+
assert.ok(rows.every((line) => visibleWidth(line) <= 80));
|
|
148
|
+
});
|
|
149
|
+
|
|
110
150
|
test("Status title stays plain without an active review", () => {
|
|
111
151
|
const lines = renderShellSidebarBar(model(), plainTheme, 60);
|
|
112
152
|
assert.match(lines[0], /^╭─ ✿ Status ─+╮$/);
|