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,110 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
drainGlobal,
|
|
8
|
+
drainProject,
|
|
9
|
+
globalSeedPath,
|
|
10
|
+
projectHash,
|
|
11
|
+
type DrainResult,
|
|
12
|
+
} from "../extensions/history/store.ts";
|
|
13
|
+
|
|
14
|
+
// Portable project identity: a never-existing literal. projectHash falls
|
|
15
|
+
// back to hashing the raw string when realpath fails, so the identity is
|
|
16
|
+
// deterministic on every machine (no machine-specific absolute paths).
|
|
17
|
+
|
|
18
|
+
const CWD = "/pi-history-test/drain-hidden-project";
|
|
19
|
+
|
|
20
|
+
function write(file: string, texts: string[], ts = 100): void {
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
fs.writeFileSync(
|
|
23
|
+
file,
|
|
24
|
+
`${texts.map((t) => JSON.stringify({ v: 1, text: t, ts })).join("\n")}\n`,
|
|
25
|
+
"utf8",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Unwrap the ok shape. Drains FAIL CLOSED: the blocked variant carries no
|
|
30
|
+
// prompts field at all (asserted in the blocked test below).
|
|
31
|
+
function okPrompts(result: DrainResult): string[] {
|
|
32
|
+
assert.equal(result.status, "ok");
|
|
33
|
+
if (result.status !== "ok") throw new Error("unreachable");
|
|
34
|
+
return result.prompts;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test("drains skip tombstoned prompts in seeds and session files", () => {
|
|
38
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), "hid-"));
|
|
39
|
+
const root = path.join(base, "h");
|
|
40
|
+
const stateDir = path.join(base, "state");
|
|
41
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
42
|
+
fs.writeFileSync(
|
|
43
|
+
path.join(stateDir, "hidden.json"),
|
|
44
|
+
JSON.stringify(["deleted from seed", "deleted from session"]),
|
|
45
|
+
"utf8",
|
|
46
|
+
);
|
|
47
|
+
const dir = path.join(root, "projects", projectHash(CWD));
|
|
48
|
+
write(path.join(dir, "seed.jsonl"), ["keep", "deleted from seed"], 100);
|
|
49
|
+
write(path.join(dir, "s1.jsonl"), ["also keep", "deleted from session"], 200);
|
|
50
|
+
write(globalSeedPath(root), ["deleted from seed", "legacy keep"], 50);
|
|
51
|
+
|
|
52
|
+
assert.deepEqual(okPrompts(drainProject(root, CWD, 1000, stateDir)), [
|
|
53
|
+
"also keep",
|
|
54
|
+
"keep",
|
|
55
|
+
]);
|
|
56
|
+
assert.deepEqual(okPrompts(drainGlobal(root, 1000, stateDir)), [
|
|
57
|
+
"also keep",
|
|
58
|
+
"keep",
|
|
59
|
+
"legacy keep",
|
|
60
|
+
]);
|
|
61
|
+
// Without a stateDir the filter is off (raw drain semantics).
|
|
62
|
+
assert.equal(
|
|
63
|
+
okPrompts(drainProject(root, CWD)).includes("deleted from seed"),
|
|
64
|
+
true,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Fail-closed seam: an untrusted hidden.json BLOCKS both drains with the
|
|
69
|
+
// recovery message and no prompts field; a stateDir whose hidden.json is
|
|
70
|
+
// MISSING stays the safe empty-tombstones case (the full expected prompts).
|
|
71
|
+
test("corrupt hidden.json blocks both drains with no prompts field; a missing file drains normally", () => {
|
|
72
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), "hid-blocked-"));
|
|
73
|
+
const root = path.join(base, "h");
|
|
74
|
+
const stateDir = path.join(base, "state");
|
|
75
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
76
|
+
fs.writeFileSync(
|
|
77
|
+
path.join(stateDir, "hidden.json"),
|
|
78
|
+
"{corrupt bytes",
|
|
79
|
+
"utf8",
|
|
80
|
+
);
|
|
81
|
+
const dir = path.join(root, "projects", projectHash(CWD));
|
|
82
|
+
write(path.join(dir, "seed.jsonl"), ["secret prompt", "keeper"], 100);
|
|
83
|
+
|
|
84
|
+
for (const result of [
|
|
85
|
+
drainProject(root, CWD, 1000, stateDir),
|
|
86
|
+
drainGlobal(root, 1000, stateDir),
|
|
87
|
+
]) {
|
|
88
|
+
assert.equal(result.status, "blocked");
|
|
89
|
+
if (result.status !== "blocked") throw new Error("unreachable");
|
|
90
|
+
assert.ok(result.message.includes("hidden.json"));
|
|
91
|
+
// The blocked shape carries no prompts to render.
|
|
92
|
+
assert.equal("prompts" in result, false);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Missing file: safe empty tombstones — the full drain comes back.
|
|
96
|
+
const missingBase = fs.mkdtempSync(path.join(os.tmpdir(), "hid-missing-"));
|
|
97
|
+
const missingRoot = path.join(missingBase, "h");
|
|
98
|
+
const missingState = path.join(missingBase, "state");
|
|
99
|
+
fs.mkdirSync(missingState, { recursive: true });
|
|
100
|
+
const missingDir = path.join(missingRoot, "projects", projectHash(CWD));
|
|
101
|
+
write(path.join(missingDir, "seed.jsonl"), ["kept", "shown"], 100);
|
|
102
|
+
assert.deepEqual(
|
|
103
|
+
okPrompts(drainProject(missingRoot, CWD, 1000, missingState)),
|
|
104
|
+
["shown", "kept"],
|
|
105
|
+
);
|
|
106
|
+
assert.deepEqual(okPrompts(drainGlobal(missingRoot, 1000, missingState)), [
|
|
107
|
+
"shown",
|
|
108
|
+
"kept",
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
drainGlobal,
|
|
8
|
+
drainProject,
|
|
9
|
+
globalSeedPath,
|
|
10
|
+
projectHash,
|
|
11
|
+
type DrainResult,
|
|
12
|
+
} from "../extensions/history/store.ts";
|
|
13
|
+
|
|
14
|
+
// Portable project identity: a never-existing literal. projectHash falls
|
|
15
|
+
// back to hashing the raw string when realpath fails, so the identity is
|
|
16
|
+
// deterministic on every machine (no machine-specific absolute paths).
|
|
17
|
+
|
|
18
|
+
const CWD = "/pi-history-test/drain-order-project";
|
|
19
|
+
|
|
20
|
+
function writeTs(file: string, texts: string[], ts: number): void {
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
fs.writeFileSync(
|
|
23
|
+
file,
|
|
24
|
+
`${texts.map((t) => JSON.stringify({ v: 1, text: t, ts })).join("\n")}\n`,
|
|
25
|
+
"utf8",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Mechanical unwrap of the ok shape (drains can also return blocked).
|
|
30
|
+
function okPrompts(result: DrainResult): string[] {
|
|
31
|
+
assert.equal(result.status, "ok");
|
|
32
|
+
if (result.status !== "ok") throw new Error("unreachable");
|
|
33
|
+
return result.prompts;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test("atomic rewrite (delete) does not reshuffle the drain order", () => {
|
|
37
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ord-"));
|
|
38
|
+
const dir = path.join(root, "projects", projectHash(CWD));
|
|
39
|
+
writeTs(path.join(dir, "old.jsonl"), ["a-old"], 100);
|
|
40
|
+
writeTs(path.join(dir, "new.jsonl"), ["z-new"], 200);
|
|
41
|
+
assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new", "a-old"]);
|
|
42
|
+
// Slice 5 ports deleteFromProject; its observable effect on the drain is
|
|
43
|
+
// simulated directly here: an atomic rewrite of the affected file that
|
|
44
|
+
// empties it — the mtime jumps to NOW, and the drain order must not move.
|
|
45
|
+
fs.writeFileSync(path.join(dir, "old.jsonl"), "", "utf8");
|
|
46
|
+
fs.utimesSync(path.join(dir, "old.jsonl"), new Date(), new Date());
|
|
47
|
+
assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new"]);
|
|
48
|
+
// Re-add with an OLD ts via direct write: still ordered by ts, not mtime.
|
|
49
|
+
writeTs(path.join(dir, "old2.jsonl"), ["b-old"], 150);
|
|
50
|
+
fs.utimesSync(
|
|
51
|
+
path.join(dir, "old2.jsonl"),
|
|
52
|
+
new Date(Date.now() + 99999),
|
|
53
|
+
new Date(Date.now() + 99999),
|
|
54
|
+
);
|
|
55
|
+
assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new", "b-old"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("global drain puts the legacy seed last regardless of its fresh mtime", () => {
|
|
59
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "seed-"));
|
|
60
|
+
const dir = path.join(root, "projects", projectHash(CWD));
|
|
61
|
+
writeTs(path.join(dir, "s.jsonl"), ["fresh"], 200);
|
|
62
|
+
const seed = globalSeedPath(root);
|
|
63
|
+
writeTs(seed, ["legacy-1", "legacy-2"], 10);
|
|
64
|
+
fs.utimesSync(seed, new Date(Date.now() + 5000), new Date(Date.now() + 5000));
|
|
65
|
+
assert.deepEqual(okPrompts(drainGlobal(root)), [
|
|
66
|
+
"fresh",
|
|
67
|
+
"legacy-2",
|
|
68
|
+
"legacy-1",
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test(
|
|
73
|
+
"an unreadable store file is skipped; the rest drain in the expected order",
|
|
74
|
+
{ skip: process.getuid?.() === 0 },
|
|
75
|
+
() => {
|
|
76
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ord-sealed-"));
|
|
77
|
+
const dir = path.join(root, "projects", projectHash(CWD));
|
|
78
|
+
writeTs(path.join(dir, "old.jsonl"), ["a-old"], 100);
|
|
79
|
+
const sealed = path.join(dir, "sealed.jsonl");
|
|
80
|
+
writeTs(sealed, ["sealed-never"], 150);
|
|
81
|
+
writeTs(path.join(dir, "new.jsonl"), ["z-new"], 200);
|
|
82
|
+
// The sealed file's fresh mtime would sort it FIRST if it were readable —
|
|
83
|
+
// its absence from the drain is caused by the unreadable skip alone.
|
|
84
|
+
fs.utimesSync(
|
|
85
|
+
sealed,
|
|
86
|
+
new Date(Date.now() + 99999),
|
|
87
|
+
new Date(Date.now() + 99999),
|
|
88
|
+
);
|
|
89
|
+
fs.chmodSync(sealed, 0o000);
|
|
90
|
+
try {
|
|
91
|
+
// An unreadable file reads as zero entries and drops out of the drain;
|
|
92
|
+
// the readable files keep their ts order. No throw.
|
|
93
|
+
assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new", "a-old"]);
|
|
94
|
+
} finally {
|
|
95
|
+
fs.chmodSync(sealed, 0o644); // restore before cleanup
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
type PiHistoryGlobals,
|
|
5
|
+
withExpandedHistoryGlobals,
|
|
6
|
+
} from "../extensions/history/selector-helpers.ts";
|
|
7
|
+
|
|
8
|
+
// withExpandedHistoryGlobals contract: optional hooks invoked via `?.` —
|
|
9
|
+
// expand exactly once BEFORE the run starts, trim exactly once in the
|
|
10
|
+
// finally (success and rejection alike), and the run's resolution passes
|
|
11
|
+
// through untouched. Fixtures track call order in one array so the
|
|
12
|
+
// before/after ordering and the call counts are pinned together.
|
|
13
|
+
|
|
14
|
+
function trackingGlobals(): { globals: PiHistoryGlobals; events: string[] } {
|
|
15
|
+
const events: string[] = [];
|
|
16
|
+
return {
|
|
17
|
+
events,
|
|
18
|
+
globals: {
|
|
19
|
+
__piHistoryExpand: () => {
|
|
20
|
+
events.push("expand");
|
|
21
|
+
},
|
|
22
|
+
__piHistoryTrim: () => {
|
|
23
|
+
events.push("trim");
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("expand runs once before the run; trim once after; the result passes through", async () => {
|
|
30
|
+
const { globals, events } = trackingGlobals();
|
|
31
|
+
let ran = 0;
|
|
32
|
+
const value = await withExpandedHistoryGlobals(globals, async () => {
|
|
33
|
+
// By the time the body executes, expand already ran — exactly once.
|
|
34
|
+
ran += 1;
|
|
35
|
+
assert.deepEqual(events, ["expand"]);
|
|
36
|
+
return 42;
|
|
37
|
+
});
|
|
38
|
+
assert.equal(value, 42);
|
|
39
|
+
assert.equal(ran, 1);
|
|
40
|
+
assert.deepEqual(events, ["expand", "trim"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("a rejected run still trims (finally) and the rejection propagates unchanged", async () => {
|
|
44
|
+
const { globals, events } = trackingGlobals();
|
|
45
|
+
const boom = new Error("boom");
|
|
46
|
+
let caught: unknown;
|
|
47
|
+
try {
|
|
48
|
+
await withExpandedHistoryGlobals(globals, async () => {
|
|
49
|
+
throw boom;
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
caught = error;
|
|
53
|
+
}
|
|
54
|
+
assert.equal(caught, boom);
|
|
55
|
+
assert.deepEqual(events, ["expand", "trim"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("absent hooks are tolerated: the run executes with no throw", async () => {
|
|
59
|
+
const empty: PiHistoryGlobals = {};
|
|
60
|
+
const value = await withExpandedHistoryGlobals(empty, async () => "ok");
|
|
61
|
+
assert.equal(value, "ok");
|
|
62
|
+
});
|