gentle-pi 3.7.0 → 4.1.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 +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- 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 +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- 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 +30 -38
- 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/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- 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/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -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 +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- 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/history-capture-policy.ts +95 -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 +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- 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-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- 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/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- 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-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- 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 +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- 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 +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- 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 +8 -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 +83 -35
- 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/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- 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 +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- 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 +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -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 +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- 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 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- 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-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- 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 +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- 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 +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -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-authority.test.ts +16 -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 +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- 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 +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- 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 +8 -7
- 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/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- 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 +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- 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 +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -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 +14 -37
- 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-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- 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-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- 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-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- 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 +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -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 +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -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 +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -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-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /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
package/lib/shell-card.ts
CHANGED
|
@@ -23,12 +23,45 @@ export interface Card {
|
|
|
23
23
|
|
|
24
24
|
export interface CardTheme {
|
|
25
25
|
fg(color: string, text: string): string;
|
|
26
|
+
/** Pi themes have it; the float style needs it to paint its panel. */
|
|
27
|
+
bg?(color: string, text: string): string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Conversation cards come in two styles, chosen in /gentle:customize. Pi loads
|
|
31
|
+
// every extension with its own jiti loader (moduleCache:false), so each one
|
|
32
|
+
// gets a copy of this module; the global symbol keeps one style per process.
|
|
33
|
+
export const CARD_STYLE = {
|
|
34
|
+
NEON: "neon",
|
|
35
|
+
FLOAT: "float",
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export type CardStyle = (typeof CARD_STYLE)[keyof typeof CARD_STYLE];
|
|
39
|
+
|
|
40
|
+
const CARD_STYLE_SLOT = Symbol.for("gentle-pi.card-style");
|
|
41
|
+
const styleState = globalThis as typeof globalThis & { [CARD_STYLE_SLOT]?: unknown };
|
|
42
|
+
|
|
43
|
+
export function cardStyle(): CardStyle {
|
|
44
|
+
return styleState[CARD_STYLE_SLOT] === CARD_STYLE.NEON ? CARD_STYLE.NEON : CARD_STYLE.FLOAT;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setCardStyle(style: CardStyle): void {
|
|
48
|
+
styleState[CARD_STYLE_SLOT] = style;
|
|
26
49
|
}
|
|
27
50
|
|
|
28
51
|
export interface CardRenderOptions {
|
|
29
52
|
expanded: boolean;
|
|
53
|
+
/** Optional physical-row budget for useful tool previews; notices default to one. */
|
|
54
|
+
previewRows?: number;
|
|
30
55
|
/** Right-aligned hint in the top rule, e.g. the expand key. May carry ANSI. */
|
|
31
56
|
hint?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Fixed chrome panels (Agents, Todos, Status rail) opt in here. The float
|
|
59
|
+
* style then paints them like float cards, centered between two padding
|
|
60
|
+
* rows with a blank row between header and body: `panelExtraRows` taller
|
|
61
|
+
* than neon when a body exists, with the header on `panelHeaderRow`.
|
|
62
|
+
* Neon keeps the outlined frame. Conversation cards never set it.
|
|
63
|
+
*/
|
|
64
|
+
panel?: boolean;
|
|
32
65
|
}
|
|
33
66
|
|
|
34
67
|
export const CARD_GLYPH = "✿";
|
|
@@ -54,8 +87,39 @@ const BODY_ROLE = "text";
|
|
|
54
87
|
const SEPARATOR = "·";
|
|
55
88
|
const FRAME_COLUMNS = 4;
|
|
56
89
|
|
|
90
|
+
// The characters that draw a card around its content. The float style keeps
|
|
91
|
+
// every row the same width: the left edge becomes an accent bar and the rules
|
|
92
|
+
// and the right rail become spaces. Rows are built from these by position, so
|
|
93
|
+
// content that happens to contain box-drawing characters is never rewritten.
|
|
94
|
+
interface CardChrome {
|
|
95
|
+
topLeft: string;
|
|
96
|
+
topLead: string;
|
|
97
|
+
topRight: string;
|
|
98
|
+
rule: string;
|
|
99
|
+
side: string;
|
|
100
|
+
rail: string;
|
|
101
|
+
bottomLeft: string;
|
|
102
|
+
bottomRight: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const OUTLINE_CHROME: CardChrome = { topLeft: "╭", topLead: "─ ", topRight: "╮", rule: "─", side: "│", rail: "│", bottomLeft: "╰", bottomRight: "╯" };
|
|
106
|
+
// `▎ ` is one cell narrower than `╭─ `: the spare cell moves to the right end
|
|
107
|
+
// so the heading glyph starts in the same column as the body text.
|
|
108
|
+
const FLOAT_CHROME: CardChrome = { topLeft: "▎", topLead: " ", topRight: " ", rule: " ", side: "▎", rail: " ", bottomLeft: "▎", bottomRight: " " };
|
|
109
|
+
let chrome = OUTLINE_CHROME;
|
|
110
|
+
|
|
111
|
+
function withChrome<T>(next: CardChrome, run: () => T): T {
|
|
112
|
+
const previous = chrome;
|
|
113
|
+
chrome = next;
|
|
114
|
+
try {
|
|
115
|
+
return run();
|
|
116
|
+
} finally {
|
|
117
|
+
chrome = previous;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
57
121
|
function rule(length: number): string {
|
|
58
|
-
return
|
|
122
|
+
return chrome.rule.repeat(Math.max(0, length));
|
|
59
123
|
}
|
|
60
124
|
|
|
61
125
|
function titleText(card: Card, theme: CardTheme): { styled: string; width: number } {
|
|
@@ -92,13 +156,13 @@ export function cardTop(card: Card, theme: CardTheme, width: number, hint?: stri
|
|
|
92
156
|
const styledTitleWidth = title.width <= titleWidth ? title.width : visibleWidth(styledTitle);
|
|
93
157
|
const fill = rule(targetWidth - styledTitleWidth - 5 - hintWidth);
|
|
94
158
|
const tail = shownHint ? ` ${theme.fg(HINT_ROLE, shownHint)} ` : "";
|
|
95
|
-
return theme.fg(FRAME_ROLE[card.tone],
|
|
159
|
+
return theme.fg(FRAME_ROLE[card.tone], chrome.topLeft) + frame(theme, card.tone, chrome.topLead) + styledTitle + frame(theme, card.tone, ` ${fill}`) + tail + frame(theme, card.tone, chrome.topRight);
|
|
96
160
|
}
|
|
97
161
|
|
|
98
162
|
export function cardLine(text: string, tone: CardTone, theme: CardTheme, width: number): string {
|
|
99
163
|
const targetWidth = Math.max(0, Math.floor(width));
|
|
100
164
|
if (targetWidth === 0) return "";
|
|
101
|
-
const left = theme.fg(FRAME_ROLE[tone],
|
|
165
|
+
const left = theme.fg(FRAME_ROLE[tone], chrome.side);
|
|
102
166
|
if (targetWidth === 1) return left;
|
|
103
167
|
if (targetWidth === 2) return left + frame(theme, tone, "│");
|
|
104
168
|
if (targetWidth === 3) return `${left} ${frame(theme, tone, "│")}`;
|
|
@@ -106,34 +170,239 @@ export function cardLine(text: string, tone: CardTone, theme: CardTheme, width:
|
|
|
106
170
|
const innerWidth = targetWidth - FRAME_COLUMNS;
|
|
107
171
|
const clipped = innerWidth === 0 ? "" : truncateToWidth(text, innerWidth, "…");
|
|
108
172
|
const padding = " ".repeat(Math.max(0, innerWidth - visibleWidth(clipped)));
|
|
109
|
-
return `${left} ${clipped}${padding} ${frame(theme, tone,
|
|
173
|
+
return `${left} ${clipped}${padding} ${frame(theme, tone, chrome.rail)}`;
|
|
110
174
|
}
|
|
111
175
|
|
|
112
|
-
export function cardBottom(tone: CardTone, theme: CardTheme, width: number): string {
|
|
176
|
+
export function cardBottom(tone: CardTone, theme: CardTheme, width: number, content?: string): string {
|
|
113
177
|
const targetWidth = Math.max(0, Math.floor(width));
|
|
114
178
|
if (targetWidth === 0) return "";
|
|
115
|
-
const left = theme.fg(FRAME_ROLE[tone],
|
|
179
|
+
const left = theme.fg(FRAME_ROLE[tone], chrome.bottomLeft);
|
|
116
180
|
if (targetWidth === 1) return left;
|
|
117
|
-
return left + frame(theme, tone, `${rule(targetWidth - 2)}
|
|
181
|
+
if (content === undefined || visibleWidth(content) === 0) return left + frame(theme, tone, `${rule(targetWidth - 2)}${chrome.bottomRight}`);
|
|
182
|
+
const contentWidth = visibleWidth(content) + 2;
|
|
183
|
+
// Responsive: the duration rides the closing rule only when it fits with a minimum fill.
|
|
184
|
+
if (targetWidth - 2 - contentWidth < 3) return left + frame(theme, tone, `${rule(targetWidth - 2)}${chrome.bottomRight}`);
|
|
185
|
+
return left + frame(theme, tone, `${rule(targetWidth - 2 - contentWidth)} `) + theme.fg(HINT_ROLE, content) + frame(theme, tone, ` ${chrome.bottomRight}`);
|
|
118
186
|
}
|
|
119
187
|
|
|
120
188
|
export function cardInnerWidth(width: number): number {
|
|
121
189
|
return Math.max(1, width - FRAME_COLUMNS);
|
|
122
190
|
}
|
|
123
191
|
|
|
192
|
+
/** Shared body for whole cards and separate call/result components. Budgets apply after wrapping. */
|
|
193
|
+
export function cardBodyRows(
|
|
194
|
+
rows: readonly string[], tone: CardTone, theme: CardTheme, width: number,
|
|
195
|
+
options: CardRenderOptions,
|
|
196
|
+
): string[] {
|
|
197
|
+
if (width <= 0) return [];
|
|
198
|
+
const wrapped = rows.flatMap((row) => row.split("\n").flatMap((line) => line === "" ? [""] : wrapTextWithAnsi(line, cardInnerWidth(width))));
|
|
199
|
+
const limit = Math.max(0, Math.floor(options.previewRows ?? 3));
|
|
200
|
+
const shown = options.expanded ? wrapped : wrapped.slice(0, limit);
|
|
201
|
+
return shown.map((line) => cardLine(line, tone, theme, width));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Pi draws a tool row as the call component followed, once a result exists,
|
|
205
|
+
// by the result component, which closes the frame. The call cannot see the
|
|
206
|
+
// result, so result renderers mark the row state both share, and a call still
|
|
207
|
+
// waiting for its first result closes its own frame.
|
|
208
|
+
const RESULT_MARK = Symbol.for("gentle-pi.card-result");
|
|
209
|
+
const RUNNING_TEXT = "running…";
|
|
210
|
+
const RUNNING_ROLE = "muted";
|
|
211
|
+
|
|
212
|
+
/** The fields of pi's tool render context that tell whether a result exists. */
|
|
213
|
+
export interface CardRowContext {
|
|
214
|
+
isPartial?: boolean;
|
|
215
|
+
state?: unknown;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Result renderers mark the shared row state: from now on a result component closes the card. */
|
|
219
|
+
export function markCardResult(state: unknown): void {
|
|
220
|
+
if (state !== null && typeof state === "object") (state as Record<symbol, unknown>)[RESULT_MARK] = true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Whether a call card must close its own frame. Read it at render time: pi builds the result component after the call. */
|
|
224
|
+
export function cardAwaitingResult(context: CardRowContext): boolean {
|
|
225
|
+
if (context.isPartial === false) return false;
|
|
226
|
+
const state = context.state;
|
|
227
|
+
return !(state !== null && typeof state === "object" && (state as Record<symbol, unknown>)[RESULT_MARK] === true);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** The muted body row of a call card that is still waiting for its result. */
|
|
231
|
+
export function cardRunningLine(tone: CardTone, theme: CardTheme, width: number): string {
|
|
232
|
+
return cardLine(theme.fg(RUNNING_ROLE, RUNNING_TEXT), tone, theme, width);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Quiet calls may continue long or multiline headings inside the same frame. */
|
|
236
|
+
export function cardTopRows(card: Card, theme: CardTheme, width: number, hint?: string): string[] {
|
|
237
|
+
const target = Math.max(0, Math.floor(width));
|
|
238
|
+
if (target === 0) return [];
|
|
239
|
+
if (target < 8) return [cardTop(card, theme, target, hint)];
|
|
240
|
+
const [first = "", ...rest] = card.title.split("\n");
|
|
241
|
+
// Reserve the hint before wrapping, plus one rule column and enough heading
|
|
242
|
+
// space to retain identity. This policy belongs only to continuing tool tops.
|
|
243
|
+
const available = target - 6 - visibleWidth(`${card.glyph ?? CARD_GLYPH} `) - (card.subtitle ? visibleWidth(card.subtitle) + 3 : 0);
|
|
244
|
+
const hintWidth = hint ? visibleWidth(hint) + 2 : 0;
|
|
245
|
+
const shownHint = hint && available - hintWidth >= 12 ? hint : undefined;
|
|
246
|
+
const room = Math.max(1, available - (shownHint ? hintWidth : 0));
|
|
247
|
+
const [head = "", ...overflow] = wrapTextWithAnsi(first, room);
|
|
248
|
+
return [
|
|
249
|
+
cardTop({ ...card, title: head }, theme, target, shownHint),
|
|
250
|
+
...cardBodyRows([...overflow, ...rest], card.tone, theme, target, { expanded: true }),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** The rows one card component draws, by position. */
|
|
255
|
+
export interface CardParts {
|
|
256
|
+
/** The top rule plus any heading continuation rows. */
|
|
257
|
+
head?: readonly string[];
|
|
258
|
+
body?: readonly string[];
|
|
259
|
+
/** The closing rule, when this component closes the card. */
|
|
260
|
+
bottom?: string;
|
|
261
|
+
/** A component above this one already drew the heading. */
|
|
262
|
+
afterHeading?: boolean;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const FLOAT_MARGIN = 1;
|
|
266
|
+
const FLOAT_MIN_WIDTH = 10;
|
|
267
|
+
const FLOAT_BG_ROLE: Record<CardTone, string> = {
|
|
268
|
+
[CARD_TONE.INFO]: "toolSuccessBg",
|
|
269
|
+
[CARD_TONE.SUCCESS]: "toolSuccessBg",
|
|
270
|
+
[CARD_TONE.WARNING]: "toolPendingBg",
|
|
271
|
+
[CARD_TONE.ERROR]: "toolErrorBg",
|
|
272
|
+
};
|
|
273
|
+
const BG_RESET = "\x1b[49m";
|
|
274
|
+
// Full resets (pi-tui truncation inserts one) and background resets.
|
|
275
|
+
const BG_CLEARING = /\x1b\[(?:0|49)?m/g;
|
|
276
|
+
|
|
277
|
+
function panelOpener(theme: CardTheme, tone: CardTone): string {
|
|
278
|
+
if (typeof theme.bg !== "function") return "";
|
|
279
|
+
try {
|
|
280
|
+
const painted = theme.bg(FLOAT_BG_ROLE[tone], "");
|
|
281
|
+
return painted.endsWith(BG_RESET) ? painted.slice(0, -BG_RESET.length) : "";
|
|
282
|
+
} catch {
|
|
283
|
+
// A theme without the tool background keeps the outlined card.
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Draws one conversation card component in the active style. The outlined
|
|
290
|
+
* style returns the parts unchanged; the float style renders them one margin
|
|
291
|
+
* narrower with the float chrome and paints a tone background behind every
|
|
292
|
+
* row. A blank row sits above the heading and between it and the body.
|
|
293
|
+
*/
|
|
294
|
+
export function floatRows(tone: CardTone, theme: CardTheme, width: number, render: (width: number) => CardParts): string[] {
|
|
295
|
+
const target = Math.max(0, Math.floor(width));
|
|
296
|
+
const open = floatOpener(theme, tone, target);
|
|
297
|
+
if (!open) {
|
|
298
|
+
const { head = [], body = [], bottom } = render(width);
|
|
299
|
+
return [...head, ...body, ...(bottom === undefined ? [] : [bottom])];
|
|
300
|
+
}
|
|
301
|
+
const inner = target - FLOAT_MARGIN * 2;
|
|
302
|
+
const rows = withChrome(FLOAT_CHROME, () => {
|
|
303
|
+
const { head = [], body = [], bottom, afterHeading = false } = render(inner);
|
|
304
|
+
const blank = cardBottom(tone, theme, inner);
|
|
305
|
+
return [
|
|
306
|
+
...(head.length > 0 ? [blank, ...head] : []),
|
|
307
|
+
...(body.length > 0 && (head.length > 0 || afterHeading) ? [blank] : []),
|
|
308
|
+
...body,
|
|
309
|
+
...(bottom === undefined ? [] : [bottom]),
|
|
310
|
+
];
|
|
311
|
+
});
|
|
312
|
+
return paintFloat(rows, open);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** The tone background opener when the float style applies at this width, or "" for the outlined card. */
|
|
316
|
+
function floatOpener(theme: CardTheme, tone: CardTone, width: number): string {
|
|
317
|
+
return cardStyle() === CARD_STYLE.FLOAT && Math.floor(width) >= FLOAT_MIN_WIDTH ? panelOpener(theme, tone) : "";
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Paints every row behind its tone background, re-armed after any reset the
|
|
321
|
+
// content carries, inside a transparent one-column margin on both sides.
|
|
322
|
+
// Padding rows keep their tone-coloured accent just like content rows.
|
|
323
|
+
function paintFloat(rows: readonly string[], open: string): string[] {
|
|
324
|
+
const margin = " ".repeat(FLOAT_MARGIN);
|
|
325
|
+
return rows.map((row) => `${margin}${open}${row.replace(BG_CLEARING, (reset) => reset + open)}${BG_RESET}${margin}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Content columns of a panel body (Agents, Todos, Status rail) in the active
|
|
330
|
+
* style. Callers that pre-wrap or pre-fit rows use it so a float panel, two
|
|
331
|
+
* columns narrower than the outlined frame, never re-wraps or clips them.
|
|
332
|
+
*/
|
|
333
|
+
export function panelInnerWidth(theme: CardTheme, width: number, tone: CardTone = CARD_TONE.INFO): number {
|
|
334
|
+
return floatOpener(theme, tone, width) ? cardInnerWidth(Math.floor(width) - FLOAT_MARGIN * 2) : cardInnerWidth(width);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The row a panel draws its header on in the active style: 0 for the
|
|
339
|
+
* outlined frame, 1 below the float panel's top padding row. Callers
|
|
340
|
+
* hit-test their header control with it.
|
|
341
|
+
*/
|
|
342
|
+
export function panelHeaderRow(theme: CardTheme, width: number, tone: CardTone = CARD_TONE.INFO): number {
|
|
343
|
+
return floatOpener(theme, tone, width) ? 1 : 0;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Rows a float panel with a body adds over the outlined frame: the top
|
|
348
|
+
* padding row and the separator below the header, or 0 for the outlined
|
|
349
|
+
* frame. Callers with a row budget spend them from the body.
|
|
350
|
+
*/
|
|
351
|
+
export function panelExtraRows(theme: CardTheme, width: number, tone: CardTone = CARD_TONE.INFO): number {
|
|
352
|
+
return floatOpener(theme, tone, width) ? 2 : 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
124
355
|
export function renderCard(card: Card, theme: CardTheme, width: number, options: CardRenderOptions): string[] {
|
|
125
|
-
|
|
356
|
+
// Only opt-in tool previews collapse to nothing at nonpositive widths; the
|
|
357
|
+
// legacy path keeps its empty-row shape for widgets that call it unguarded.
|
|
358
|
+
if (options.previewRows !== undefined) return width <= 0 ? [] : floatRows(card.tone, theme, width, (inner) => ({
|
|
359
|
+
head: [cardTop(card, theme, inner, options.hint)],
|
|
360
|
+
body: cardBodyRows(card.body.map((line) => theme.fg(BODY_ROLE, line)), card.tone, theme, inner, options),
|
|
361
|
+
bottom: cardBottom(card.tone, theme, inner),
|
|
362
|
+
}));
|
|
363
|
+
// Without the panel opt-in, legacy cards always keep the outlined frame.
|
|
364
|
+
if (chrome !== OUTLINE_CHROME) return withChrome(OUTLINE_CHROME, () => renderCard(card, theme, width, options));
|
|
365
|
+
const open = options.panel ? floatOpener(theme, card.tone, width) : "";
|
|
366
|
+
if (open) return paintFloat(withChrome(FLOAT_CHROME, () => floatPanel(card, theme, Math.floor(width) - FLOAT_MARGIN * 2, options)), open);
|
|
126
367
|
const top = cardTop(card, theme, width, options.hint);
|
|
127
368
|
const bottom = cardBottom(card.tone, theme, width);
|
|
369
|
+
return [top, ...cardText(card, theme, cardInnerWidth(width), options.expanded).map((line) => cardLine(line, card.tone, theme, width)), bottom];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Body text of a legacy card or panel, wrapped to its content columns, before the side rails. */
|
|
373
|
+
function cardText(card: Card, theme: CardTheme, innerWidth: number, expanded: boolean): string[] {
|
|
128
374
|
const lines = bodyLines(card, innerWidth);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
375
|
+
if (lines.length === 0) return [];
|
|
376
|
+
if (!expanded) {
|
|
377
|
+
const first = lines.find((line) => line !== "") ?? "";
|
|
378
|
+
const clipped = lines.length > 1 ? truncateToWidth(first, Math.max(1, innerWidth - 1), "") + "…" : first;
|
|
379
|
+
return [theme.fg(BODY_ROLE, clipped)];
|
|
380
|
+
}
|
|
381
|
+
return lines.map((line) => (line === "" ? "" : theme.fg(BODY_ROLE, line)));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Float panels: the float card chrome. Padding rows that keep the accent bar
|
|
385
|
+
// sit above the heading, between heading and body (only when a body exists)
|
|
386
|
+
// and in the bottom rule's place, centering the content: panelExtraRows taller
|
|
387
|
+
// than neon, with the heading on row 1 (see panelHeaderRow).
|
|
388
|
+
function floatPanel(card: Card, theme: CardTheme, width: number, options: CardRenderOptions): string[] {
|
|
389
|
+
const blank = cardBottom(card.tone, theme, width);
|
|
390
|
+
const body = cardText(card, theme, cardInnerWidth(width), options.expanded).map((line) => cardLine(line, card.tone, theme, width));
|
|
391
|
+
return [blank, panelHeader(card, theme, width, options.hint), ...(body.length > 0 ? [blank, ...body] : []), blank];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function fitRow(text: string, width: number): string {
|
|
395
|
+
const clipped = visibleWidth(text) <= width ? text : truncateToWidth(text, width, "…");
|
|
396
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// `▎ <glyph> <title> <subtitle>` left and the hint right, ending in the same
|
|
400
|
+
// two columns the body rows keep for their right edge.
|
|
401
|
+
function panelHeader(card: Card, theme: CardTheme, width: number, hint?: string): string {
|
|
402
|
+
const lead = theme.fg(FRAME_ROLE[card.tone], chrome.topLeft) + chrome.topLead;
|
|
403
|
+
const subtitle = card.subtitle ? ` ${theme.fg(SUBTITLE_ROLE, card.subtitle)}` : "";
|
|
404
|
+
const left = `${lead}${theme.fg(TITLE_ROLE[card.tone], `${card.glyph ?? CARD_GLYPH} ${card.title}`)}${subtitle}`;
|
|
405
|
+
const right = hint ? `${theme.fg(SUBTITLE_ROLE, hint)}${chrome.topRight}` : "";
|
|
406
|
+
const gap = width - visibleWidth(left) - visibleWidth(right);
|
|
407
|
+
return right && gap >= 1 ? `${left}${" ".repeat(gap)}${right}` : fitRow(left, width);
|
|
139
408
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Key, matchesKey, truncateToWidth, visibleWidth, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
2
2
|
import { sanitizeTerminalText } from "./terminal-theme.ts";
|
|
3
|
-
import { basename } from "node:path";
|
|
4
|
-
import { CHANGE_STATUS, changesSummary, type ChangedFile, type ChangesModel, type WorktreeChanges } from "./shell-changes.ts";
|
|
3
|
+
import { basename, posix } from "node:path";
|
|
4
|
+
import { CHANGE_STATUS, DIFF_TOO_LARGE, changesSummary, type ChangedFile, type ChangesModel, type WorktreeChanges } from "./shell-changes.ts";
|
|
5
5
|
import { paintHoverable } from "./shell-hover.ts";
|
|
6
6
|
|
|
7
7
|
// Gentle Shell changes overlay: a framed two-pane view with the working
|
|
@@ -37,7 +37,7 @@ const ROLE = {
|
|
|
37
37
|
} as const;
|
|
38
38
|
|
|
39
39
|
const HEADER_PREFIXES = ["diff --git", "index ", "--- ", "+++ ", "new file mode", "deleted file mode", "similarity index", "rename from", "rename to", "Binary files"];
|
|
40
|
-
const LIST_MAX_WIDTH =
|
|
40
|
+
const LIST_MAX_WIDTH = 48;
|
|
41
41
|
const LIST_RATIO = 0.35;
|
|
42
42
|
const CHROME_ROWS = 3;
|
|
43
43
|
const MIN_BODY_ROWS = 1;
|
|
@@ -56,7 +56,8 @@ function rule(length: number): string {
|
|
|
56
56
|
|
|
57
57
|
export function colorDiff(text: string, theme: ChangesViewTheme): string[] {
|
|
58
58
|
const lines: string[] = [];
|
|
59
|
-
for (const
|
|
59
|
+
for (const rawLine of text.split("\n")) {
|
|
60
|
+
const line = sanitizeTerminalText(rawLine.replace(/\r/g, "")).replace(/\t/g, " ");
|
|
60
61
|
if (line === "" || HEADER_PREFIXES.some((prefix) => line.startsWith(prefix))) continue;
|
|
61
62
|
if (line.startsWith("@@")) lines.push(theme.fg(ROLE.HUNK, line));
|
|
62
63
|
else if (line.startsWith("+")) lines.push(theme.fg("toolDiffAdded", line));
|
|
@@ -79,8 +80,32 @@ function fileCounts(file: ChangedFile, theme: ChangesViewTheme): string {
|
|
|
79
80
|
return `${theme.fg(ROLE.ADDED, `+${file.added}`)} ${theme.fg(ROLE.REMOVED, `-${file.deleted}`)}`;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
// The name leads and the directory trails, so a narrow list clips the
|
|
84
|
+
// directory instead of the part that identifies the file.
|
|
82
85
|
function fileLabel(file: ChangedFile, theme: ChangesViewTheme, hovered: boolean, idleRole: string): string {
|
|
83
|
-
|
|
86
|
+
const directory = posix.dirname(file.path.replace(/\\/g, "/"));
|
|
87
|
+
const label = `${paintHoverable(theme, `${FILE_STATUS[file.status]} ${displayText(posix.basename(file.path.replace(/\\/g, "/")))}`, hovered, idleRole)} ${fileCounts(file, theme)}`;
|
|
88
|
+
return directory === "." ? label : `${label} ${theme.fg(ROLE.EMPTY, displayText(directory))}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The diff pane's first row: the selected file's full path, clipped from the
|
|
92
|
+
// left so the file name always stays visible.
|
|
93
|
+
const PATH_HEADER_ROWS = 1;
|
|
94
|
+
|
|
95
|
+
function pathHeader(file: ChangedFile, theme: ChangesViewTheme, width: number): string {
|
|
96
|
+
const status = `${FILE_STATUS[file.status]} `;
|
|
97
|
+
let path = displayText(file.path);
|
|
98
|
+
const room = Math.max(1, width - visibleWidth(status));
|
|
99
|
+
if (visibleWidth(path) > room) {
|
|
100
|
+
const characters = [...path];
|
|
101
|
+
while (characters.length > 0 && visibleWidth(`…${characters.join("")}`) > room) characters.shift();
|
|
102
|
+
path = `…${characters.join("")}`;
|
|
103
|
+
}
|
|
104
|
+
return theme.fg(ROLE.SELECTED, status + path);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function diffRows(bodyRows: number): number {
|
|
108
|
+
return Math.max(MIN_BODY_ROWS, bodyRows - PATH_HEADER_ROWS);
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
function fit(text: string, width: number): string {
|
|
@@ -89,10 +114,11 @@ function fit(text: string, width: number): string {
|
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
// Both standalone files and the worktree accordion use the original frame.
|
|
92
|
-
function renderPanes(width: number, rows: number, theme: ChangesViewTheme, title: string, leftLine: (row: number) => string,
|
|
117
|
+
function renderPanes(width: number, rows: number, theme: ChangesViewTheme, title: string, leftLine: (row: number) => string, diffLines: string[], keys: string, headerFile?: ChangedFile): string[] {
|
|
93
118
|
const inner = Math.max(8, width - 2);
|
|
94
119
|
const listWidth = Math.min(LIST_MAX_WIDTH, Math.floor(inner * LIST_RATIO));
|
|
95
120
|
const diffWidth = inner - listWidth - 4;
|
|
121
|
+
const rightLines = headerFile ? [pathHeader(headerFile, theme, diffWidth), ...diffLines] : diffLines;
|
|
96
122
|
const titleText = truncateToWidth(title, inner - 4, "…");
|
|
97
123
|
const top = theme.fg(ROLE.FRAME, "╭─ ") + theme.fg(ROLE.TITLE, titleText) + theme.fg(ROLE.FRAME, ` ${rule(inner - visibleWidth(titleText) - 3)}╮`);
|
|
98
124
|
const body: string[] = [];
|
|
@@ -291,7 +317,7 @@ export class WorktreeChangesView {
|
|
|
291
317
|
};
|
|
292
318
|
const keys = theme.fg(ROLE.KEY_TEXT, "j/k select enter toggle/open ← parent/fold ctrl+j/k scroll r refresh esc close");
|
|
293
319
|
this.pointerLayout = pointerLayout(width, height);
|
|
294
|
-
return renderPanes(width, height, theme, `✎ Changes · ${this.trees.length} worktrees`, leftLine, preview, keys);
|
|
320
|
+
return renderPanes(width, height, theme, `✎ Changes · ${this.trees.length} worktrees`, leftLine, preview, keys, selected?.file);
|
|
295
321
|
}
|
|
296
322
|
|
|
297
323
|
invalidate(): void {
|
|
@@ -405,8 +431,8 @@ export class ChangesView {
|
|
|
405
431
|
// the scroll keys are checked before the open key; a real Enter is CR.
|
|
406
432
|
if (data === "j" || matchesKey(data, Key.down)) this.select(this.selected + 1);
|
|
407
433
|
else if (data === "k" || matchesKey(data, Key.up)) this.select(this.selected - 1);
|
|
408
|
-
else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("j"))) this.scrollBy(this.bodyRows());
|
|
409
|
-
else if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.ctrl("k"))) this.scrollBy(-this.bodyRows());
|
|
434
|
+
else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("j"))) this.scrollBy(diffRows(this.bodyRows()));
|
|
435
|
+
else if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.ctrl("k"))) this.scrollBy(-diffRows(this.bodyRows()));
|
|
410
436
|
else if (data === "o" || matchesKey(data, Key.enter)) {
|
|
411
437
|
const file = this.model.files[this.selected];
|
|
412
438
|
if (file) this.deps.onOpen(file);
|
|
@@ -463,7 +489,7 @@ export class ChangesView {
|
|
|
463
489
|
this.fileScroll = this.clampFileScroll(this.fileScroll, rows);
|
|
464
490
|
const keys = KEYS.map(([key, label]) => `${theme.fg(ROLE.KEY, key)} ${theme.fg(ROLE.KEY_TEXT, key === "esc" ? (this.deps.backLabel ?? label) : label)}`).join(" ");
|
|
465
491
|
this.pointerLayout = pointerLayout(width, rows);
|
|
466
|
-
return renderPanes(width, rows, theme, `✎ Changes · ${changesSummary(this.model)}`, (row) => this.fileLine(row), this.visibleDiff(rows), keys);
|
|
492
|
+
return renderPanes(width, rows, theme, `✎ Changes · ${changesSummary(this.model)}`, (row) => this.fileLine(row), this.visibleDiff(rows), keys, this.model.files[this.selected]);
|
|
467
493
|
}
|
|
468
494
|
|
|
469
495
|
invalidate(): void {
|
|
@@ -499,8 +525,9 @@ export class ChangesView {
|
|
|
499
525
|
const lines = this.diffs.get(file.path);
|
|
500
526
|
if (!lines) return [];
|
|
501
527
|
if (lines.length === 0) return [this.deps.theme.fg(ROLE.EMPTY, EMPTY_DIFF)];
|
|
502
|
-
|
|
503
|
-
|
|
528
|
+
const visible = diffRows(rows);
|
|
529
|
+
this.diffScroll = Math.min(this.diffScroll, Math.max(0, lines.length - visible));
|
|
530
|
+
return lines.slice(this.diffScroll, this.diffScroll + visible);
|
|
504
531
|
}
|
|
505
532
|
|
|
506
533
|
private select(index: number): void {
|
|
@@ -533,7 +560,7 @@ export class ChangesView {
|
|
|
533
560
|
const file = this.model.files[this.selected];
|
|
534
561
|
const lines = file ? this.diffs.get(file.path) : undefined;
|
|
535
562
|
if (!lines) return false;
|
|
536
|
-
const next = Math.max(0, Math.min(Math.max(0, lines.length - rows), this.diffScroll + Math.trunc(delta)));
|
|
563
|
+
const next = Math.max(0, Math.min(Math.max(0, lines.length - diffRows(rows)), this.diffScroll + Math.trunc(delta)));
|
|
537
564
|
if (next === this.diffScroll) return false;
|
|
538
565
|
this.diffScroll = next;
|
|
539
566
|
return true;
|
|
@@ -556,7 +583,8 @@ export class ChangesView {
|
|
|
556
583
|
void this.deps.loadDiff(file).then(
|
|
557
584
|
(text) => {
|
|
558
585
|
if (this.disposed) return;
|
|
559
|
-
|
|
586
|
+
// An oversized diff arrives as one plain line pointing at the editor.
|
|
587
|
+
this.diffs.set(file.path, text === DIFF_TOO_LARGE ? [this.deps.theme.fg(ROLE.EMPTY, text)] : colorDiff(text, this.deps.theme));
|
|
560
588
|
this.deps.requestRender();
|
|
561
589
|
},
|
|
562
590
|
() => {
|