gentle-pi 3.7.0 → 4.0.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 +37 -6
- package/assets/agents/gentle-ai-explore.md +4 -4
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -9
- package/assets/orchestrator-delegation.md +40 -41
- package/assets/orchestrator-memory.md +1 -22
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator.md +12 -24
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/gentle-shell.mjs +254 -29
- package/docs/delegated-verification.md +26 -1
- package/docs/gentle-agents-activity.md +24 -0
- package/docs/gentle-shell.md +92 -28
- package/docs/native-authority-architecture.md +2 -2
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +112 -206
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +26 -0
- package/extensions/child-safety.ts +23 -0
- package/extensions/gentle-agents.ts +429 -262
- package/extensions/gentle-ai.ts +781 -683
- package/extensions/gentle-shell.ts +1375 -88
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +18 -7
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1419 -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/quiet-tools.ts +179 -87
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +16 -12
- package/extensions/startup-banner.ts +60 -31
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-protocol.ts +60 -0
- package/lib/agents-runner.ts +96 -98
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/bounded-writer-admission.ts +147 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/codemode-renderer.ts +185 -0
- package/lib/command-palette-catalog.ts +3 -9
- package/lib/command-palette.ts +25 -14
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-launcher.ts +24 -13
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +227 -0
- package/lib/native-review-cli.ts +49 -108
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +67 -17
- package/lib/review-candidate-view.ts +112 -25
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +156 -11
- package/lib/review-sidebar-state.ts +223 -0
- package/lib/selection-engine.ts +515 -0
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-worktree-registry.ts +14 -2
- package/lib/shell-bar.ts +179 -24
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +2 -1
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +78 -14
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +23 -13
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +66 -10
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +379 -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 +108 -0
- package/lib/visual-customize-view.ts +330 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/yolo-session-policy.ts +240 -0
- package/package.json +20 -8
- package/runtime/gentle-shell-launcher.mjs +23 -12
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/native-review-cli.mjs +49 -108
- package/runtime/review-risk-assessment.mjs +154 -9
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/gentle-ai-installer.mjs +14 -13
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/run-test-suite.mjs +76 -0
- package/scripts/test-packed-runner.mjs +31 -14
- package/scripts/verify-package-files.mjs +11 -22
- 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 +9 -15
- 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-profiles.test.ts +18 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +40 -0
- package/tests/agents-runner.test.ts +378 -89
- 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/autonomous-guard.test.ts +69 -1
- package/tests/bounded-writer-admission.test.ts +95 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/card-style-policy.test.ts +55 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-safety.test.ts +82 -0
- package/tests/codemode-rendering.test.ts +491 -0
- package/tests/command-palette.test.ts +39 -3
- package/tests/delegated-key-learnings-contract.test.ts +0 -76
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +170 -2
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -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 +54 -0
- package/tests/gentle-agents.test.ts +1047 -252
- package/tests/gentle-ai-binary.test.ts +3 -3
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +68 -54
- package/tests/gentle-ai-renderer.test.ts +487 -8
- package/tests/gentle-ai.test.ts +288 -65
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-shell-bin.test.ts +651 -114
- package/tests/gentle-shell-launcher.test.ts +99 -52
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell.test.ts +3839 -187
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +80 -8
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -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 +29 -19
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/nan-provider.test.ts +471 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +6 -120
- package/tests/native-review-parity-runtime.test.ts +100 -3
- package/tests/odd-integration.test.ts +67 -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 +11 -6
- package/tests/odd-routing-contract.test.ts +86 -35
- package/tests/orchestrator-budget.test.ts +14 -39
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +252 -115
- package/tests/packed-runner-owned-path.test.ts +46 -0
- package/tests/persona-single-channel.test.ts +6 -6
- package/tests/provider-defect-handoff.test.ts +3 -11
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +409 -184
- package/tests/rdd-aware-verification-contract.test.ts +76 -1
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +176 -12
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +298 -0
- package/tests/review-contract-prompt.test.ts +108 -43
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-routing.test.ts +611 -5
- package/tests/review-controller-workspace-root.test.ts +163 -4
- package/tests/review-host-relay-routing.test.ts +338 -2
- package/tests/review-integration-v2-forward.test.ts +200 -0
- package/tests/review-ledger-contract.test.ts +10 -34
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +498 -6
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/run-test-suite.test.ts +124 -0
- package/tests/runtime-harness.mjs +145 -786
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/selection-engine.test.ts +421 -0
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-worktree-registry.test.ts +77 -0
- package/tests/shell-bar.test.ts +382 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +52 -0
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-sidebar-layout.test.ts +325 -21
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +87 -1
- package/tests/shell-usage-view.test.ts +27 -0
- package/tests/shell-usage.test.ts +73 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/startup-banner.test.ts +130 -2
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/telemetry-trigger.test.ts +81 -20
- package/tests/theme-customization.test.ts +72 -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 +110 -0
- package/tests/visual-customize-view.test.ts +418 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/yolo-customize.test.ts +256 -0
- package/tests/yolo-mode-runtime.test.ts +161 -0
- package/tests/yolo-mode.test.ts +261 -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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
// Unit 4 — L6 wheel slice (spec C5, design §D6).
|
|
7
|
+
//
|
|
8
|
+
// Source-parse structural pins on extensions/history/index.ts (no pi-tui
|
|
9
|
+
// runtime graph — the same discipline as the other source-parse suites).
|
|
10
|
+
// The overlay renders only through pi-tui, so the unit-level contract is the
|
|
11
|
+
// SHAPE of the handleMouse override:
|
|
12
|
+
//
|
|
13
|
+
// - wheel-only: every non-wheel event type returns undefined (press/click/
|
|
14
|
+
// drag stay host-owned) and the dispatch table gains no extra entry (wheel
|
|
15
|
+
// is not a keybinding — dispatch.test.ts remains the authoritative
|
|
16
|
+
// untouched pin);
|
|
17
|
+
// - ONE consumed wheel return: `handled: true` plus the synthetic target
|
|
18
|
+
// enrichment, reached by every wheel path including the no-op regions —
|
|
19
|
+
// this closes the pre-existing fullscreen SGR-fallthrough hazard by
|
|
20
|
+
// construction;
|
|
21
|
+
// - fixed 30-row geometry routing: list region y 5–14, preview region y 17–26,
|
|
22
|
+
// all other rows consumed no-ops;
|
|
23
|
+
// - list wheel: sign × |wheelDelta| steps through moveDown (the arrow grow
|
|
24
|
+
// path applies per step) / moveUp, magnitude clamped to the filtered list,
|
|
25
|
+
// zero/absent delta a no-op move — the override itself never re-implements
|
|
26
|
+
// growth;
|
|
27
|
+
// - preview wheel: 1 line per notch toward the delta direction via the
|
|
28
|
+
// existing clampPreviewOffset semantics + rebuildPreview.
|
|
29
|
+
|
|
30
|
+
const selectorSource = fs.readFileSync(
|
|
31
|
+
fileURLToPath(new URL("../extensions/history/index.ts", import.meta.url)),
|
|
32
|
+
"utf8",
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// T13 — AC-L6-1: wheel-only override + no extra dispatch entry.
|
|
36
|
+
|
|
37
|
+
test("handleMouse override is wheel-only and the dispatch table keeps 12 entries (AC-L6-1)", () => {
|
|
38
|
+
const decl = selectorSource.indexOf("override handleMouse(");
|
|
39
|
+
assert.ok(decl >= 0, "PromptHistorySelector should override handleMouse");
|
|
40
|
+
const end = selectorSource.indexOf("\n }", decl);
|
|
41
|
+
assert.ok(end > decl, "handleMouse's body should close");
|
|
42
|
+
const body = selectorSource.slice(decl, end);
|
|
43
|
+
|
|
44
|
+
assert.ok(
|
|
45
|
+
body.includes('if (event.type !== "wheel") return undefined;'),
|
|
46
|
+
"non-wheel event types must return undefined (press/click/drag stay host-owned)",
|
|
47
|
+
);
|
|
48
|
+
assert.ok(
|
|
49
|
+
body.includes('ReturnType<Container["handleMouse"]>'),
|
|
50
|
+
'the return type must name the base contract via ReturnType<Container["handleMouse"]>',
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const tableAt = selectorSource.indexOf(
|
|
54
|
+
"private readonly dispatch: readonly DispatchEntry[] = [",
|
|
55
|
+
);
|
|
56
|
+
assert.ok(tableAt >= 0, "the dispatch table should exist");
|
|
57
|
+
const tableEnd = selectorSource.indexOf("\n ];", tableAt);
|
|
58
|
+
assert.ok(tableEnd > tableAt, "the dispatch table should close");
|
|
59
|
+
const table = selectorSource.slice(tableAt, tableEnd);
|
|
60
|
+
const entries = table.split("match:").length - 1;
|
|
61
|
+
assert.equal(
|
|
62
|
+
entries,
|
|
63
|
+
12,
|
|
64
|
+
"wheel is not a keybinding: exactly 12 dispatch entries, no 13th",
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// T13 — AC-L6-2: ONE consumed wheel return with the target enrichment,
|
|
69
|
+
// reached by every wheel path including the no-op regions.
|
|
70
|
+
|
|
71
|
+
test("every wheel path reaches the single handled:true return with target enrichment (AC-L6-2)", () => {
|
|
72
|
+
const decl = selectorSource.indexOf("override handleMouse(");
|
|
73
|
+
assert.ok(decl >= 0, "handleMouse should exist");
|
|
74
|
+
const end = selectorSource.indexOf("\n }", decl);
|
|
75
|
+
const body = selectorSource.slice(decl, end);
|
|
76
|
+
|
|
77
|
+
const returns = body.split("return").length - 1;
|
|
78
|
+
assert.equal(
|
|
79
|
+
returns,
|
|
80
|
+
2,
|
|
81
|
+
"exactly two returns: the guard's undefined and the ONE consumed wheel return",
|
|
82
|
+
);
|
|
83
|
+
assert.equal(
|
|
84
|
+
body.split("handled: true").length - 1,
|
|
85
|
+
1,
|
|
86
|
+
"exactly one handled:true — the single wheel return",
|
|
87
|
+
);
|
|
88
|
+
assert.equal(
|
|
89
|
+
body.split("return {").length - 1,
|
|
90
|
+
1,
|
|
91
|
+
"exactly one object return, so list, preview, and no-op regions all reach it",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Synthetic target mirroring dispatchMouseEvent's enrichment math
|
|
95
|
+
// (pi-tui tui.js dispatchMouseEvent: originX = screenX - x, originY =
|
|
96
|
+
// screenY - y, bounds from the event) — the result carries `target`, so
|
|
97
|
+
// dispatch passes it through verbatim.
|
|
98
|
+
assert.ok(body.includes("component: this,"), "target.component: this");
|
|
99
|
+
assert.ok(
|
|
100
|
+
body.includes("originX: event.screenX - event.x,"),
|
|
101
|
+
"target.originX mirrors the dispatch enrichment math",
|
|
102
|
+
);
|
|
103
|
+
assert.ok(
|
|
104
|
+
body.includes("originY: event.screenY - event.y,"),
|
|
105
|
+
"target.originY mirrors the dispatch enrichment math",
|
|
106
|
+
);
|
|
107
|
+
assert.ok(body.includes("width: event.width,"), "target bounds width");
|
|
108
|
+
assert.ok(body.includes("height: event.height,"), "target bounds height");
|
|
109
|
+
|
|
110
|
+
// No manual render: pi-tui re-renders handled wheels by default.
|
|
111
|
+
assert.ok(
|
|
112
|
+
!body.includes("requestRender"),
|
|
113
|
+
"handleMouse must not call requestRender (wheel results render by default)",
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// T13 — AC-L6-3: region routing truth table — the fixed 30-row geometry's
|
|
118
|
+
// list band 5–14 and preview band 17–26 appear as the y comparisons, all
|
|
119
|
+
// other rows fall through to the consumed no-op return.
|
|
120
|
+
|
|
121
|
+
test("region constants 5-14 / 17-26 route the y comparisons (AC-L6-3)", () => {
|
|
122
|
+
assert.ok(
|
|
123
|
+
selectorSource.includes("const LIST_WHEEL_Y_FIRST = 5;"),
|
|
124
|
+
"LIST_WHEEL_Y_FIRST = 5 (list container rows)",
|
|
125
|
+
);
|
|
126
|
+
assert.ok(
|
|
127
|
+
selectorSource.includes("const LIST_WHEEL_Y_LAST = 14;"),
|
|
128
|
+
"LIST_WHEEL_Y_LAST = 14",
|
|
129
|
+
);
|
|
130
|
+
assert.ok(
|
|
131
|
+
selectorSource.includes("const PREVIEW_WHEEL_Y_FIRST = 17;"),
|
|
132
|
+
"PREVIEW_WHEEL_Y_FIRST = 17 (preview container rows)",
|
|
133
|
+
);
|
|
134
|
+
assert.ok(
|
|
135
|
+
selectorSource.includes("const PREVIEW_WHEEL_Y_LAST = 26;"),
|
|
136
|
+
"PREVIEW_WHEEL_Y_LAST = 26",
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const decl = selectorSource.indexOf("override handleMouse(");
|
|
140
|
+
assert.ok(decl >= 0, "handleMouse should exist");
|
|
141
|
+
const end = selectorSource.indexOf("\n }", decl);
|
|
142
|
+
const body = selectorSource.slice(decl, end);
|
|
143
|
+
|
|
144
|
+
assert.ok(
|
|
145
|
+
body.includes("event.y >= this.listWheelFirstRow") &&
|
|
146
|
+
body.includes("event.y <= LIST_WHEEL_Y_LAST"),
|
|
147
|
+
"the list branch must compare y against the list band",
|
|
148
|
+
);
|
|
149
|
+
assert.ok(
|
|
150
|
+
body.includes("event.y >= PREVIEW_WHEEL_Y_FIRST") &&
|
|
151
|
+
body.includes("event.y <= PREVIEW_WHEEL_Y_LAST"),
|
|
152
|
+
"the preview branch must compare y against the preview band",
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// T13 — AC-L6-4: list wheel semantics — sign picks the direction, magnitude
|
|
157
|
+
// is clamped to the filtered list, zero/absent delta is a no-op, and the
|
|
158
|
+
// routing goes THROUGH moveDown's own grow path (never a re-implementation).
|
|
159
|
+
|
|
160
|
+
test("list wheel routes sign-clamped steps through moveDown/moveUp (AC-L6-4)", () => {
|
|
161
|
+
const decl = selectorSource.indexOf("override handleMouse(");
|
|
162
|
+
assert.ok(decl >= 0, "handleMouse should exist");
|
|
163
|
+
const end = selectorSource.indexOf("\n }", decl);
|
|
164
|
+
const body = selectorSource.slice(decl, end);
|
|
165
|
+
|
|
166
|
+
// Absent wheelDelta normalizes to 0 → zero steps → no-op move.
|
|
167
|
+
assert.ok(
|
|
168
|
+
body.includes("const delta = event.wheelDelta ?? 0;"),
|
|
169
|
+
"delta must default an absent wheelDelta to 0",
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const listStart = body.indexOf("if (event.y >= this.listWheelFirstRow");
|
|
173
|
+
const listEnd = body.indexOf("} else if (", listStart);
|
|
174
|
+
assert.ok(
|
|
175
|
+
listStart >= 0 && listEnd > listStart,
|
|
176
|
+
"the list branch should exist",
|
|
177
|
+
);
|
|
178
|
+
const listBranch = body.slice(listStart, listEnd);
|
|
179
|
+
|
|
180
|
+
assert.ok(
|
|
181
|
+
listBranch.includes(
|
|
182
|
+
"const steps = Math.min(Math.abs(delta), this.filteredRecords.length);",
|
|
183
|
+
),
|
|
184
|
+
"magnitude must clamp to the filtered list length",
|
|
185
|
+
);
|
|
186
|
+
assert.ok(
|
|
187
|
+
listBranch.includes("for (let i = 0; i < steps; i++) {"),
|
|
188
|
+
"steps must move one row at a time (0 for a zero delta — the no-op)",
|
|
189
|
+
);
|
|
190
|
+
assert.ok(
|
|
191
|
+
listBranch.includes("if (delta > 0) this.moveDown();") &&
|
|
192
|
+
listBranch.includes("else this.moveUp();"),
|
|
193
|
+
"sign semantics: positive delta moves down through moveDown, else up",
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Prefetch interplay intact: growth belongs to moveDown itself — the
|
|
197
|
+
// override must not re-implement the trigger.
|
|
198
|
+
assert.ok(
|
|
199
|
+
!body.includes("shouldGrowWindow") && !body.includes("nextLoadedCount"),
|
|
200
|
+
"the override must not re-implement growth (wheel-down grows via moveDown)",
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// T13 — AC-L6-5: preview wheel semantics — one line per notch toward the
|
|
205
|
+
// delta direction through the existing clamp, zero-delta no-op.
|
|
206
|
+
|
|
207
|
+
test("preview wheel scrolls one clamped line per notch (AC-L6-5)", () => {
|
|
208
|
+
const decl = selectorSource.indexOf("override handleMouse(");
|
|
209
|
+
assert.ok(decl >= 0, "handleMouse should exist");
|
|
210
|
+
const end = selectorSource.indexOf("\n }", decl);
|
|
211
|
+
const body = selectorSource.slice(decl, end);
|
|
212
|
+
|
|
213
|
+
const previewStart = body.indexOf("} else if (");
|
|
214
|
+
const previewEnd = body.indexOf("return {", previewStart);
|
|
215
|
+
assert.ok(
|
|
216
|
+
previewStart >= 0 && previewEnd > previewStart,
|
|
217
|
+
"the preview branch should exist",
|
|
218
|
+
);
|
|
219
|
+
const previewBranch = body.slice(previewStart, previewEnd);
|
|
220
|
+
|
|
221
|
+
assert.ok(
|
|
222
|
+
previewBranch.includes("if (delta !== 0) {"),
|
|
223
|
+
"a zero delta must be a no-op in the preview band too",
|
|
224
|
+
);
|
|
225
|
+
assert.ok(
|
|
226
|
+
previewBranch.includes("this.previewScrollOffset = clampPreviewOffset("),
|
|
227
|
+
"the preview must scroll through the existing clamp semantics",
|
|
228
|
+
);
|
|
229
|
+
assert.ok(
|
|
230
|
+
previewBranch.includes("this.previewScrollOffset + (delta > 0 ? 1 : -1)"),
|
|
231
|
+
"exactly one line per notch toward the delta direction",
|
|
232
|
+
);
|
|
233
|
+
assert.ok(
|
|
234
|
+
previewBranch.includes("this.wrappedPreviewLines.length") &&
|
|
235
|
+
previewBranch.includes("PREVIEW_ROWS"),
|
|
236
|
+
"the clamp must run against the wrapped length and the viewport rows",
|
|
237
|
+
);
|
|
238
|
+
assert.ok(
|
|
239
|
+
previewBranch.includes("this.rebuildPreview()"),
|
|
240
|
+
"the preview must re-render after the offset change",
|
|
241
|
+
);
|
|
242
|
+
});
|
|
@@ -88,15 +88,20 @@ function capturingComplete(assistant: AssistantMessage) {
|
|
|
88
88
|
*/
|
|
89
89
|
function signalAwaitingComplete(): typeof completeSimple {
|
|
90
90
|
return (async (_model: Model<Api>, _context: Context, options?: SimpleStreamOptions) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
const keepalive = setTimeout(() => {}, 60_000);
|
|
92
|
+
try {
|
|
93
|
+
return await new Promise<AssistantMessage>((_resolve, reject) => {
|
|
94
|
+
const signal = options?.signal;
|
|
95
|
+
if (signal === undefined) return;
|
|
96
|
+
if (signal.aborted) {
|
|
97
|
+
reject(new Error("aborted"));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
signal.addEventListener("abort", () => reject(new Error("aborted")), { once: true });
|
|
101
|
+
});
|
|
102
|
+
} finally {
|
|
103
|
+
clearTimeout(keepalive);
|
|
104
|
+
}
|
|
100
105
|
}) as typeof completeSimple;
|
|
101
106
|
}
|
|
102
107
|
|
|
@@ -107,16 +112,21 @@ function signalAwaitingComplete(): typeof completeSimple {
|
|
|
107
112
|
*/
|
|
108
113
|
function signalResolvingAbortedComplete(partialText = "partial revi"): typeof completeSimple {
|
|
109
114
|
return (async (_model, _context, options) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
const keepalive = setTimeout(() => {}, 60_000);
|
|
116
|
+
try {
|
|
117
|
+
return await new Promise<AssistantMessage>((resolve) => {
|
|
118
|
+
const signal = options?.signal;
|
|
119
|
+
const settle = () => resolve(assistantText(partialText, { stopReason: "aborted" }));
|
|
120
|
+
if (signal === undefined) return;
|
|
121
|
+
if (signal.aborted) {
|
|
122
|
+
settle();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
signal.addEventListener("abort", settle, { once: true });
|
|
126
|
+
});
|
|
127
|
+
} finally {
|
|
128
|
+
clearTimeout(keepalive);
|
|
129
|
+
}
|
|
120
130
|
}) as typeof completeSimple;
|
|
121
131
|
}
|
|
122
132
|
|
|
@@ -69,6 +69,35 @@ test("publishes reviewed Issue Form bodies through a private body file", () => {
|
|
|
69
69
|
assert.match(skill, /target-host read-back/i);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
function assertRemoteDiscoveryGate(candidate: string) {
|
|
73
|
+
const gate = candidate.indexOf("## Safe Discovery");
|
|
74
|
+
const probe = candidate.indexOf("\ngh auth status\n", gate);
|
|
75
|
+
assert.ok(gate >= 0 && probe > gate);
|
|
76
|
+
const beforeProbe = candidate.slice(gate, probe);
|
|
77
|
+
assert.match(beforeProbe, /explicit (?:human|user) authorization.*destination.*operation.*credential\/session/is);
|
|
78
|
+
assert.match(beforeProbe, /(?:do not|never).*probe.*(?:credential|session).*ambiguity/is);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
test("requires remote authorization before authentication or target discovery", () => {
|
|
82
|
+
assertRemoteDiscoveryGate(skill);
|
|
83
|
+
assert.throws(() => assertRemoteDiscoveryGate(skill.replace(/explicit (?:human|user) authorization[^\n]*/i, "Remote checks are read-only.")));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function assertProtectedFormLabels(candidate: string) {
|
|
87
|
+
const creation = candidate.slice(candidate.indexOf("## Safe Discovery"), candidate.indexOf("## Duplicate And Form Decision"));
|
|
88
|
+
assert.match(creation, /(?:YAML|form).*labels/is);
|
|
89
|
+
assert.match(creation, /protected.*(?:label|labels)/is);
|
|
90
|
+
assert.match(creation, /(?:exact|label-specific).*direct (?:human|user) instruction/is);
|
|
91
|
+
assert.match(creation, /`MAINTAIN` or `ADMIN`/);
|
|
92
|
+
assert.match(creation, /(?:skip|omit).*protected.*(?:optional|permitted)/is);
|
|
93
|
+
assert.match(creation, /(?:required|mandatory).*fail closed/is);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
test("gates protected form labels during issue creation, including required labels", () => {
|
|
97
|
+
assertProtectedFormLabels(skill);
|
|
98
|
+
assert.throws(() => assertProtectedFormLabels(skill.replace(/`MAINTAIN` or `ADMIN`/, "`WRITE`")));
|
|
99
|
+
});
|
|
100
|
+
|
|
72
101
|
function assertBrowserContract(candidate: string) {
|
|
73
102
|
assert.match(candidate, /gh issue create --repo "\$TARGET" --title "\$TITLE" --body-file "\$BODY_FILE"/);
|
|
74
103
|
assert.match(
|
|
@@ -101,6 +130,38 @@ test("keeps automated and conditional browser Issue Form paths distinct", () =>
|
|
|
101
130
|
}
|
|
102
131
|
});
|
|
103
132
|
|
|
133
|
+
const delegated = readFileSync(
|
|
134
|
+
join(repoRoot, "skills", "issue-creation", "references", "delegated-workflow-actions.md"),
|
|
135
|
+
"utf8",
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
function assertDelegatedAuthority(candidate: string) {
|
|
139
|
+
assert.match(candidate, /current direct human instruction.*exact `HOST`, `REPO`, issue-or-PR number, and action/i);
|
|
140
|
+
assert.match(candidate, /target-host `viewerPermission`.*`MAINTAIN` or `ADMIN`/i);
|
|
141
|
+
assert.match(candidate, /no separate target-host proof of the instruction-giver's identity/i);
|
|
142
|
+
assert.match(candidate, /`size:exception`.*current human-approved rationale/i);
|
|
143
|
+
assert.match(candidate, /one mutation attempt.*never retry timeouts or uncertain responses/i);
|
|
144
|
+
assert.match(candidate, /target-host post-readback.*`no_write`.*`unknown`/i);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
test("delegates only separately authorized, bounded post-publication actions", () => {
|
|
148
|
+
assert.match(skill, /\[delegated workflow actions\]\(references\/delegated-workflow-actions\.md\)/);
|
|
149
|
+
assert.match(skill, /Publication is not authorization for a later action/i);
|
|
150
|
+
assertDelegatedAuthority(delegated);
|
|
151
|
+
assert.throws(() => assertDelegatedAuthority(delegated.replace(/`MAINTAIN` or `ADMIN`/, "`WRITE`")));
|
|
152
|
+
assert.throws(() => assertDelegatedAuthority(delegated.replace(/current human-approved rationale/, "inferred from diff size")));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("keeps checkbox agency distinct from later label authority", () => {
|
|
156
|
+
assertCheckboxAttestationContract(skill);
|
|
157
|
+
assert.match(delegated, /operational checkboxes.*retained agent evidence/i);
|
|
158
|
+
assert.match(delegated, /personal, legal and user-consent declarations.*explicit first-person user affirmation/i);
|
|
159
|
+
assert.match(delegated, /never a delegated label action or the publication request/i);
|
|
160
|
+
assert.match(delegated, /do not create, rename, or substitute a label/i);
|
|
161
|
+
assert.match(delegated, /atomic update.*conflicting status labels/i);
|
|
162
|
+
assert.doesNotMatch(delegated, /(?:Strict TDD|sdd-init|TUI)/i);
|
|
163
|
+
});
|
|
164
|
+
|
|
104
165
|
test("discovers support routing and keeps all issue files private until cleanup", () => {
|
|
105
166
|
assert.match(skill, /README\.md/);
|
|
106
167
|
assert.match(skill, /\.github\/ISSUE_TEMPLATE\/config\.yml/);
|
|
@@ -32,6 +32,22 @@ test("model routing authority normalizes and preserves sync/async source status"
|
|
|
32
32
|
authority.normalizeRoutingEntry({ model: " anthropic/opus ", thinking: "high" }),
|
|
33
33
|
{ model: "anthropic/opus", thinking: "high" },
|
|
34
34
|
);
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
authority.normalizeRoutingEntry({ model: "anthropic/opus", effort: "high" }),
|
|
37
|
+
{ model: "anthropic/opus", thinking: "high" },
|
|
38
|
+
);
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
authority.normalizeRoutingEntry({ effort: "medium" }),
|
|
41
|
+
{ model: undefined, thinking: "medium" },
|
|
42
|
+
);
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
authority.normalizeRoutingEntry({ model: "anthropic/opus", thinking: "high", effort: "low" }),
|
|
45
|
+
{ model: "anthropic/opus", thinking: "high" },
|
|
46
|
+
);
|
|
47
|
+
assert.deepEqual(
|
|
48
|
+
authority.normalizeRoutingEntry({ model: "anthropic/opus", effort: "invalid" }),
|
|
49
|
+
{ model: "anthropic/opus", thinking: undefined },
|
|
50
|
+
);
|
|
35
51
|
assert.deepEqual(authority.normalizeRoutingEntry(null), undefined);
|
|
36
52
|
assert.deepEqual(
|
|
37
53
|
authority.normalizeModelConfig({
|