@leing2021/super-pi 0.22.1 → 0.23.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.
Files changed (110) hide show
  1. package/README.md +253 -211
  2. package/extensions/ce-core/index.ts +190 -7
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -20
  6. package/rules/common/naming.md +154 -0
  7. package/skills/02-plan/SKILL.md +3 -2
  8. package/skills/03-work/SKILL.md +9 -6
  9. package/skills/04-review/SKILL.md +3 -2
  10. package/skills/06-next/SKILL.md +38 -10
  11. package/skills/06-next/references/recommendation-logic.md +2 -2
  12. package/skills/07-worktree/SKILL.md +9 -8
  13. package/skills/{09-help → 08-help}/SKILL.md +3 -3
  14. package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
  15. package/skills/references/language-detection.md +44 -0
  16. package/LICENSE +0 -21
  17. package/THIRD-PARTY-NOTICES.md +0 -35
  18. package/extensions/subagent/__tests__/async-job-tracker.test.ts +0 -21
  19. package/extensions/subagent/__tests__/execution-activity.test.ts +0 -15
  20. package/extensions/subagent/__tests__/parallel-render-stress.test.ts +0 -82
  21. package/extensions/subagent/__tests__/render-dedup.test.ts +0 -98
  22. package/extensions/subagent/__tests__/render-widget.test.ts +0 -28
  23. package/extensions/subagent/__tests__/throttle.test.ts +0 -90
  24. package/extensions/subagent/agent-management.ts +0 -596
  25. package/extensions/subagent/agent-manager-chain-detail.ts +0 -163
  26. package/extensions/subagent/agent-manager-detail.ts +0 -232
  27. package/extensions/subagent/agent-manager-edit.ts +0 -391
  28. package/extensions/subagent/agent-manager-list.ts +0 -279
  29. package/extensions/subagent/agent-manager-parallel.ts +0 -305
  30. package/extensions/subagent/agent-manager.ts +0 -706
  31. package/extensions/subagent/agent-scope.ts +0 -9
  32. package/extensions/subagent/agent-selection.ts +0 -26
  33. package/extensions/subagent/agent-serializer.ts +0 -124
  34. package/extensions/subagent/agent-templates.ts +0 -63
  35. package/extensions/subagent/agents/context-builder.md +0 -37
  36. package/extensions/subagent/agents/delegate.md +0 -9
  37. package/extensions/subagent/agents/oracle.md +0 -73
  38. package/extensions/subagent/agents/planner.md +0 -52
  39. package/extensions/subagent/agents/researcher.md +0 -50
  40. package/extensions/subagent/agents/reviewer.md +0 -38
  41. package/extensions/subagent/agents/scout.md +0 -48
  42. package/extensions/subagent/agents/worker.md +0 -52
  43. package/extensions/subagent/agents.ts +0 -762
  44. package/extensions/subagent/artifacts.ts +0 -101
  45. package/extensions/subagent/async-execution.ts +0 -521
  46. package/extensions/subagent/async-job-tracker.ts +0 -240
  47. package/extensions/subagent/async-status.ts +0 -242
  48. package/extensions/subagent/chain-clarify.ts +0 -1365
  49. package/extensions/subagent/chain-execution.ts +0 -854
  50. package/extensions/subagent/chain-serializer.ts +0 -127
  51. package/extensions/subagent/completion-dedupe.ts +0 -66
  52. package/extensions/subagent/doctor.ts +0 -201
  53. package/extensions/subagent/execution.ts +0 -748
  54. package/extensions/subagent/file-coalescer.ts +0 -43
  55. package/extensions/subagent/fork-context.ts +0 -64
  56. package/extensions/subagent/formatters.ts +0 -123
  57. package/extensions/subagent/frontmatter.ts +0 -32
  58. package/extensions/subagent/index.ts +0 -586
  59. package/extensions/subagent/intercom-bridge.ts +0 -241
  60. package/extensions/subagent/jsonl-writer.ts +0 -84
  61. package/extensions/subagent/model-fallback.ts +0 -109
  62. package/extensions/subagent/notify.ts +0 -111
  63. package/extensions/subagent/parallel-utils.ts +0 -109
  64. package/extensions/subagent/pi-args.ts +0 -139
  65. package/extensions/subagent/pi-spawn.ts +0 -101
  66. package/extensions/subagent/post-exit-stdio-guard.ts +0 -88
  67. package/extensions/subagent/prompt-template-bridge.ts +0 -400
  68. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  69. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  70. package/extensions/subagent/prompts/parallel-research.md +0 -50
  71. package/extensions/subagent/prompts/parallel-review.md +0 -40
  72. package/extensions/subagent/render-helpers.ts +0 -83
  73. package/extensions/subagent/render.ts +0 -908
  74. package/extensions/subagent/result-intercom.ts +0 -238
  75. package/extensions/subagent/result-watcher.ts +0 -172
  76. package/extensions/subagent/run-history.ts +0 -58
  77. package/extensions/subagent/run-status.ts +0 -137
  78. package/extensions/subagent/schemas.ts +0 -165
  79. package/extensions/subagent/session-tokens.ts +0 -51
  80. package/extensions/subagent/settings.ts +0 -368
  81. package/extensions/subagent/single-output.ts +0 -98
  82. package/extensions/subagent/skills.ts +0 -627
  83. package/extensions/subagent/slash-bridge.ts +0 -177
  84. package/extensions/subagent/slash-commands.ts +0 -304
  85. package/extensions/subagent/slash-live-state.ts +0 -295
  86. package/extensions/subagent/subagent-control.ts +0 -151
  87. package/extensions/subagent/subagent-executor.ts +0 -1915
  88. package/extensions/subagent/subagent-prompt-runtime.ts +0 -76
  89. package/extensions/subagent/subagent-runner.ts +0 -1471
  90. package/extensions/subagent/subagents-status.ts +0 -473
  91. package/extensions/subagent/text-editor.ts +0 -273
  92. package/extensions/subagent/throttle.ts +0 -77
  93. package/extensions/subagent/top-level-async.ts +0 -16
  94. package/extensions/subagent/types.ts +0 -624
  95. package/extensions/subagent/utils.ts +0 -457
  96. package/extensions/subagent/worktree.ts +0 -580
  97. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  98. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  99. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  100. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  101. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  102. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  103. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  104. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  105. package/extensions/super-pi-extension/index.ts +0 -129
  106. package/extensions/super-pi-extension/model-sync.ts +0 -204
  107. package/skills/08-status/SKILL.md +0 -41
  108. package/skills/08-status/references/artifact-locations.md +0 -10
  109. package/skills/10-rules/SKILL.md +0 -76
  110. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: 06-next
3
- description: Inspect workflow state and recommend the single best next Compound Engineering skill.
3
+ description: "Inspect workflow state and recommend the single best next Compound Engineering skill. Use --verbose for a full status report."
4
4
  ---
5
5
 
6
6
  # Next
7
7
 
8
- Use this skill when the user wants to know what to run next in the Compound Engineering workflow.
8
+ Use this skill when the user wants to know what to run next in the Compound Engineering workflow, or wants a full status report of the current project state.
9
9
 
10
10
  ## Core rules
11
11
 
@@ -15,18 +15,43 @@ Use this skill when the user wants to know what to run next in the Compound Engi
15
15
  - Do not execute the recommended skill — only suggest it.
16
16
  - If the workflow state suggests multiple valid paths, pick the one closest to completing a full loop.
17
17
 
18
- ## Workflow
18
+ ## Two modes
19
+
20
+ ### Default mode: "what's next?"
21
+
22
+ When the user asks "what should I do next?", "continue", or runs `/skill:06-next`:
23
+
24
+ 1. Call `workflow_state` with the repo root.
25
+ 2. Apply the recommendation logic from `references/recommendation-logic.md`.
26
+ 3. Return:
27
+ - **Recommended skill name**
28
+ - **Why** this is the next step
29
+ - **Brief** summary of current workflow state (1-2 lines)
30
+
31
+ ### Verbose mode: "full status"
32
+
33
+ When the user asks "show status", "what's the current state", or explicitly requests a detailed report:
19
34
 
20
35
  1. Call `workflow_state` with the repo root.
21
- 2. Read the structured state: brainstorm count, plan count, solution count, run count, and latest artifact per category.
22
- 3. Apply the recommendation logic from `references/recommendation-logic.md`.
23
- 4. Return the recommended skill and why.
36
+ 2. Call `session_history` with `latest` operation to check recent executions.
37
+ 3. Return:
38
+ - Latest relevant brainstorm (path + brief summary)
39
+ - Latest relevant plan (path + brief summary)
40
+ - Latest relevant solution (path + brief summary)
41
+ - Latest runtime artifacts under `.context/compound-engineering/`
42
+ - Recommended next step with reason
43
+ - Overall workflow phase summary
24
44
 
25
- ## Output format
45
+ ## Artifact locations
26
46
 
27
- - Recommended skill name
28
- - Why this is the next step
29
- - Brief summary of current workflow state
47
+ | Artifact | Path |
48
+ |---|---|
49
+ | Brainstorm | `docs/brainstorms/` |
50
+ | Plan | `docs/plans/` |
51
+ | Solution | `docs/solutions/` |
52
+ | Runtime | `.context/compound-engineering/` |
53
+
54
+ If `workflow_state` is not available, fall back to `bash` with `ls` and `find` to check which directories and recent files exist, then use `read` on the most relevant recent artifact.
30
55
 
31
56
  ## Available skills
32
57
 
@@ -35,3 +60,6 @@ Use this skill when the user wants to know what to run next in the Compound Engi
35
60
  - `03-work` — execute the plan
36
61
  - `04-review` — review changes with structured findings
37
62
  - `05-learn` — capture learnings as solution artifacts
63
+ - `07-worktree` — isolated git worktree development
64
+
65
+ Before finishing this skill, apply the completion checklist in [shared pipeline instructions](../references/pipeline-config.md).
@@ -42,5 +42,5 @@ If brainstorm, plan, and solution all exist:
42
42
  ## Fallback
43
43
 
44
44
  If no rule matches cleanly:
45
- - Recommend `08-status`
46
- - Reason: State is ambiguous. Get a detailed report before deciding.
45
+ - Summarize the ambiguous state
46
+ - Ask the user what they want to focus on
@@ -3,25 +3,26 @@ name: 07-worktree
3
3
  description: Create and manage git worktrees for isolated feature development.
4
4
  ---
5
5
 
6
- # 03-worktree
6
+ # Worktree
7
7
 
8
- Use this skill when you need to create an isolated git worktree for feature work, merge changes back, or clean up after completion.
8
+ Use this skill when you need optional isolated git worktree development for large, risky, or parallel feature work, then merge changes back or clean up after completion.
9
9
 
10
10
  ## Core rules
11
11
 
12
12
  - Use the `worktree_manager` tool for all git worktree operations.
13
- - Default behavior is to **automatically create** a worktree with a descriptive branch name derived from the plan or task.
13
+ - Create a worktree only when the user explicitly asks for isolation, or when the task is large/risky enough and the user confirms.
14
+ - Use a descriptive branch name derived from the plan or task.
14
15
  - After creation, report the worktree path so that `03-work` can execute inside it.
15
- - On completion, merge back and clean up.
16
+ - On completion, offer to merge back and clean up; ask for confirmation before changing the main worktree or deleting branches.
16
17
  - Never force operations — if a worktree already exists, report its status instead of creating a duplicate.
17
18
 
18
19
  ## Workflow
19
20
 
20
- 1. **Create**: Use `worktree_manager` with operation `create` to spin up a new worktree with a feature branch.
21
- 2. **Detect**: Use `worktree_manager` with operation `detect` to check if already inside a worktree.
21
+ 1. **Detect**: Use `worktree_manager` with operation `detect` to check if already inside a worktree.
22
+ 2. **Create**: When confirmed, use `worktree_manager` with operation `create` to spin up a new worktree with a feature branch.
22
23
  3. **Execute**: Run `03-work` inside the worktree directory.
23
- 4. **Merge**: Use `worktree_manager` with operation `merge` to merge the feature branch back to main.
24
- 5. **Cleanup**: Use `worktree_manager` with operation `cleanup` to remove the worktree and delete the branch.
24
+ 4. **Merge**: After user confirmation, use `worktree_manager` with operation `merge` to merge the feature branch back to main.
25
+ 5. **Cleanup**: After user confirmation, use `worktree_manager` with operation `cleanup` to remove the worktree and delete the branch.
25
26
 
26
27
  ## Output
27
28
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: 09-help
2
+ name: 08-help
3
3
  description: Explain when to use each Compound Engineering Phase 1 skill and how they connect.
4
4
  ---
5
5
 
@@ -14,7 +14,7 @@ Use this skill when the user asks how to use the package, which workflow step co
14
14
  - `03-work` — use when there is a plan or a tightly scoped task ready for controlled execution.
15
15
  - `04-review` — use after code changes to produce structured findings. After code review, user can optionally extend to browser-based QA testing (find visual/functional bugs with agent-browser) and regression test generation.
16
16
  - `05-learn` — use after solving a problem so the repo gains a durable learning in `docs/solutions/`.
17
- - `08-status` — use when the user wants to know which artifact already exists and what the next workflow step should be.
17
+ - `06-next` — use when the user wants to know what to run next, or wants a full status report of the current project state. Default mode recommends one next step; verbose mode (`--verbose` or ask "show status") shows full artifact details.
18
18
 
19
19
  ## Recommended flow
20
20
 
@@ -30,7 +30,7 @@ Use this skill when the user asks how to use the package, which workflow step co
30
30
  - Optionally extend to **Browser QA** for visual and functional bug finding.
31
31
  - Or **Browser QA + Regression Tests** to also generate automated test coverage.
32
32
  5. Capture key learnings with `05-learn`.
33
- 6. Use `08-status` at any point to inspect repo-local workflow state.
33
+ 6. Use `06-next` at any point to check what to do next, or ask for a full status report.
34
34
 
35
35
  ## Output
36
36
 
@@ -6,4 +6,5 @@
6
6
  4. `04-review` → inspect the changes (optionally + Browser QA / QA + Regression Tests)
7
7
  5. `05-learn` → save key learnings
8
8
 
9
- Use `08-status` at any point to see where you are.
9
+ Use `06-next` at any point to see where you are and what to do next.
10
+ Use `07-worktree` for isolated feature development on a separate branch.
@@ -0,0 +1,44 @@
1
+ # Language Detection
2
+
3
+ Detect the project's primary language by checking for these files in the repo root:
4
+
5
+ | File(s) | Language | Rules directory |
6
+ |---------|----------|----------------|
7
+ | `tsconfig.json` | TypeScript | `rules/typescript/` |
8
+ | `package.json` (without `tsconfig.json`) | JavaScript | `rules/typescript/` |
9
+ | `Cargo.toml` | Rust | `rules/rust/` |
10
+ | `go.mod` | Go | `rules/golang/` |
11
+ | `pubspec.yaml` | Dart | `rules/dart/` |
12
+ | `pom.xml` / `build.gradle` | Java | `rules/java/` |
13
+ | `*.sln` / `*.csproj` | C# | `rules/csharp/` |
14
+ | `Package.swift` | Swift | `rules/swift/` |
15
+ | `requirements.txt` / `pyproject.toml` / `setup.py` | Python | `rules/python/` |
16
+ | `composer.json` | PHP | `rules/php/` |
17
+ | `Makefile.PL` / `cpanfile` | Perl | `rules/perl/` |
18
+ | `build.gradle.kts` | Kotlin | `rules/kotlin/` |
19
+
20
+ ## Rules loading strategy
21
+
22
+ Rules are loaded from two locations with priority:
23
+
24
+ 1. **Project-level** `{repo-root}/rules/` — takes priority, survives `pi update`.
25
+ 2. **Package-level** `rules/` in the super-pi package — built-in defaults.
26
+
27
+ Check project-level first. If a file exists there for the topic, use it. Otherwise fall back to package-level.
28
+
29
+ ## Rule precedence
30
+
31
+ ```
32
+ language-specific > web > common
33
+ ```
34
+
35
+ Override mapping by topic:
36
+ - `common/testing.md` ← `web/testing.md` ← `<lang>/testing.md`
37
+ - `common/coding-style.md` ← `web/coding-style.md` ← `<lang>/coding-style.md`
38
+ - `common/patterns.md` ← `web/patterns.md` ← `<lang>/patterns.md`
39
+ - `common/security.md` ← `web/security.md` ← `<lang>/security.md`
40
+ - `common/hooks.md` ← `web/hooks.md` ← `<lang>/hooks.md`
41
+
42
+ ## Do not load all rules at once
43
+
44
+ Only load what the current task needs. See each skill's SKILL.md for phase-specific loading instructions.
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025-2026 leing2021
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,35 +0,0 @@
1
- # Third-Party Notices
2
-
3
- ## pi-subagents
4
-
5
- This software includes source code from the **pi-subagents** project.
6
-
7
- - **Author**: Nico Bailon
8
- - **Repository**: https://github.com/nicobailon/pi-subagents
9
- - **License**: MIT
10
-
11
- ```
12
- MIT License
13
-
14
- Copyright (c) 2025 Nico Bailon
15
-
16
- Permission is hereby granted, free of charge, to any person obtaining a copy
17
- of this software and associated documentation files (the "Software"), to deal
18
- in the Software without restriction, including without limitation the rights
19
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
- copies of the Software, and to permit persons to whom the Software is
21
- furnished to do so, subject to the following conditions:
22
-
23
- The above copyright notice and this permission notice shall be included in all
24
- copies or substantial portions of the Software.
25
-
26
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
- SOFTWARE.
33
- ```
34
-
35
- The pi-subagents source code is located in `extensions/subagent/`.
@@ -1,21 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { resolvePollInterval } from "../async-job-tracker.ts";
3
-
4
- describe("resolvePollInterval", () => {
5
- it("returns 1000ms as default (no running jobs)", () => {
6
- expect(resolvePollInterval(0)).toBe(1000);
7
- expect(resolvePollInterval(1)).toBe(1000);
8
- expect(resolvePollInterval(2)).toBe(1000);
9
- });
10
-
11
- it("returns 1500ms for 3-4 running jobs", () => {
12
- expect(resolvePollInterval(3)).toBe(1500);
13
- expect(resolvePollInterval(4)).toBe(1500);
14
- });
15
-
16
- it("returns 2000ms for 5+ running jobs", () => {
17
- expect(resolvePollInterval(5)).toBe(2000);
18
- expect(resolvePollInterval(8)).toBe(2000);
19
- expect(resolvePollInterval(16)).toBe(2000);
20
- });
21
- });
@@ -1,15 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { resolveActivityTimerInterval } from "../execution.ts";
3
-
4
- describe("resolveActivityTimerInterval", () => {
5
- it("returns 2000ms as default (single subprocess)", () => {
6
- expect(resolveActivityTimerInterval()).toBe(2000);
7
- expect(resolveActivityTimerInterval(1)).toBe(2000);
8
- });
9
-
10
- it("returns 3000ms for parallel scenarios (2+ concurrent)", () => {
11
- expect(resolveActivityTimerInterval(2)).toBe(3000);
12
- expect(resolveActivityTimerInterval(4)).toBe(3000);
13
- expect(resolveActivityTimerInterval(8)).toBe(3000);
14
- });
15
- });
@@ -1,82 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "bun:test";
2
- import { createThrottle, resolveThrottleInterval } from "../throttle.ts";
3
- import { resolveWidgetInterval } from "../render.ts";
4
- import { resolveActivityTimerInterval } from "../execution.ts";
5
- import { resolvePollInterval } from "../async-job-tracker.ts";
6
-
7
- /**
8
- * Integration stress test: simulates 8 parallel subagents firing events
9
- * and validates that total render frequency stays within bounds.
10
- */
11
- describe("parallel render stress", () => {
12
- beforeEach(() => { vi.useFakeTimers(); });
13
- afterEach(() => { vi.useRealTimers(); });
14
-
15
- it("limits onUpdate to ≤5 triggers/s with 8 parallel tasks", () => {
16
- const interval = resolveThrottleInterval(8);
17
- expect(interval).toBe(500);
18
-
19
- const fn = vi.fn();
20
- const throttled = createThrottle(fn, interval);
21
-
22
- // Simulate 100 rapid fireUpdate calls in 1ms intervals (like 8 processes sending events)
23
- for (let i = 0; i < 100; i++) {
24
- throttled({ index: i });
25
- vi.advanceTimersByTime(1);
26
- }
27
-
28
- // First call executes immediately, then at most 1 call per 500ms
29
- // Over 100ms simulated time: 1 (immediate) + ~0 (no 500ms passed yet) = 1 call
30
- expect(fn.mock.calls.length).toBeGreaterThanOrEqual(1);
31
- expect(fn.mock.calls.length).toBeLessThanOrEqual(2);
32
-
33
- throttled.dispose();
34
- });
35
-
36
- it("allows at most 2 calls in 500ms for 8 tasks (500ms throttle)", () => {
37
- const interval = resolveThrottleInterval(8);
38
- const fn = vi.fn();
39
- const throttled = createThrottle(fn, interval);
40
-
41
- // Immediate first call
42
- throttled("a");
43
- expect(fn).toHaveBeenCalledTimes(1);
44
-
45
- // Fire many more
46
- for (let i = 0; i < 50; i++) throttled(`b-${i}`);
47
-
48
- // Advance 500ms — trailing fires
49
- vi.advanceTimersByTime(500);
50
- expect(fn).toHaveBeenCalledTimes(2); // immediate + 1 trailing
51
-
52
- throttled.dispose();
53
- });
54
-
55
- it("widget animation interval adapts to 1000ms at 7+ running jobs", () => {
56
- expect(resolveWidgetInterval(7)).toBe(1000);
57
- expect(resolveWidgetInterval(8)).toBe(1000);
58
- });
59
-
60
- it("activity timer adapts to 3000ms for parallel scenarios", () => {
61
- expect(resolveActivityTimerInterval(8)).toBe(3000);
62
- });
63
-
64
- it("poll interval adapts to 2000ms at 5+ running jobs", () => {
65
- expect(resolvePollInterval(5)).toBe(2000);
66
- expect(resolvePollInterval(8)).toBe(2000);
67
- });
68
-
69
- it("total theoretical render rate with 8 parallel subagents is ≤ 8/s", () => {
70
- const widgetRate = 1000 / resolveWidgetInterval(8); // renders/s
71
- const pollRate = 1000 / resolvePollInterval(8); // polls/s
72
- const throttleRate = 1000 / resolveThrottleInterval(8); // onUpdate/s
73
- const activityRate = 1000 / resolveActivityTimerInterval(8); // activity checks/s
74
-
75
- // Widget animation + poll + onUpdate throttle + activity are the main render sources
76
- // Note: not all fire requestRender every time
77
- const totalMaxRate = widgetRate + pollRate + throttleRate + activityRate;
78
-
79
- // With all optimizations, should be well under 8/s total
80
- expect(totalMaxRate).toBeLessThanOrEqual(8);
81
- });
82
- });
@@ -1,98 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "bun:test";
2
-
3
- describe("render dedup", () => {
4
- beforeEach(async () => {
5
- const { _testResetWidgetState } = await import("../render.ts");
6
- _testResetWidgetState();
7
- });
8
-
9
- it("should not call requestRender when widget data is identical", async () => {
10
- const { renderWidget, stopWidgetAnimation } = await import("../render.ts");
11
-
12
- const setWidget = vi.fn();
13
- const requestRender = vi.fn();
14
- const theme = {
15
- fg: (_color: string, text: string) => text,
16
- bold: (text: string) => text,
17
- };
18
- const ctx = {
19
- hasUI: true,
20
- ui: { setWidget, requestRender, theme },
21
- } as any;
22
-
23
- const jobs = [
24
- { asyncId: "test-1", asyncDir: "/tmp/test", status: "complete" as const, updatedAt: 1000 },
25
- ];
26
-
27
- // First render — should call requestRender (new content)
28
- renderWidget(ctx, jobs);
29
- expect(requestRender).toHaveBeenCalledTimes(1);
30
-
31
- // Second render with EXACTLY same data — should NOT call requestRender
32
- renderWidget(ctx, jobs);
33
- expect(requestRender).toHaveBeenCalledTimes(1); // Still 1
34
-
35
- stopWidgetAnimation();
36
- });
37
-
38
- it("should call requestRender when widget data changes", async () => {
39
- const { renderWidget, stopWidgetAnimation } = await import("../render.ts");
40
-
41
- const setWidget = vi.fn();
42
- const requestRender = vi.fn();
43
- const theme = {
44
- fg: (_color: string, text: string) => text,
45
- bold: (text: string) => text,
46
- };
47
- const ctx = {
48
- hasUI: true,
49
- ui: { setWidget, requestRender, theme },
50
- } as any;
51
-
52
- // First render
53
- const jobs1 = [
54
- { asyncId: "test-1", asyncDir: "/tmp/test", status: "running" as const, updatedAt: 1000 },
55
- ];
56
- renderWidget(ctx, jobs1);
57
- expect(requestRender).toHaveBeenCalledTimes(1);
58
-
59
- // Second render with changed updatedAt — should call requestRender
60
- const jobs2 = [
61
- { asyncId: "test-1", asyncDir: "/tmp/test", status: "running" as const, updatedAt: 2000 },
62
- ];
63
- renderWidget(ctx, jobs2);
64
- expect(requestRender).toHaveBeenCalledTimes(2);
65
-
66
- stopWidgetAnimation();
67
- });
68
-
69
- it("should call requestRender on status transitions", async () => {
70
- const { renderWidget, stopWidgetAnimation } = await import("../render.ts");
71
-
72
- const setWidget = vi.fn();
73
- const requestRender = vi.fn();
74
- const theme = {
75
- fg: (_color: string, text: string) => text,
76
- bold: (text: string) => text,
77
- };
78
- const ctx = {
79
- hasUI: true,
80
- ui: { setWidget, requestRender, theme },
81
- } as any;
82
-
83
- const jobs1 = [
84
- { asyncId: "test-1", asyncDir: "/tmp/test", status: "running" as const, updatedAt: 1000 },
85
- ];
86
- renderWidget(ctx, jobs1);
87
- expect(requestRender).toHaveBeenCalledTimes(1);
88
-
89
- // Status changes to complete — should trigger requestRender
90
- const jobs2 = [
91
- { asyncId: "test-1", asyncDir: "/tmp/test", status: "complete" as const, updatedAt: 2000 },
92
- ];
93
- renderWidget(ctx, jobs2);
94
- expect(requestRender).toHaveBeenCalledTimes(2);
95
-
96
- stopWidgetAnimation();
97
- });
98
- });
@@ -1,28 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "bun:test";
2
- import {
3
- resolveWidgetInterval,
4
- } from "../render.ts";
5
-
6
- describe("resolveWidgetInterval", () => {
7
- it("returns 250ms for 1-2 running jobs", () => {
8
- expect(resolveWidgetInterval(0)).toBe(250);
9
- expect(resolveWidgetInterval(1)).toBe(250);
10
- expect(resolveWidgetInterval(2)).toBe(250);
11
- });
12
-
13
- it("returns 400ms for 3-4 running jobs", () => {
14
- expect(resolveWidgetInterval(3)).toBe(400);
15
- expect(resolveWidgetInterval(4)).toBe(400);
16
- });
17
-
18
- it("returns 600ms for 5-6 running jobs", () => {
19
- expect(resolveWidgetInterval(5)).toBe(600);
20
- expect(resolveWidgetInterval(6)).toBe(600);
21
- });
22
-
23
- it("returns 1000ms for 7+ running jobs", () => {
24
- expect(resolveWidgetInterval(7)).toBe(1000);
25
- expect(resolveWidgetInterval(8)).toBe(1000);
26
- expect(resolveWidgetInterval(16)).toBe(1000);
27
- });
28
- });
@@ -1,90 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "bun:test";
2
- import { createThrottle, resolveThrottleInterval } from "../throttle.ts";
3
-
4
- describe("createThrottle", () => {
5
- beforeEach(() => { vi.useFakeTimers(); });
6
- afterEach(() => { vi.useRealTimers(); });
7
-
8
- it("executes the first call immediately", () => {
9
- const fn = vi.fn();
10
- const throttled = createThrottle(fn, 200);
11
- throttled("a");
12
- expect(fn).toHaveBeenCalledTimes(1);
13
- expect(fn).toHaveBeenCalledWith("a");
14
- });
15
-
16
- it("drops calls within the interval, keeping only the latest args", () => {
17
- const fn = vi.fn();
18
- const throttled = createThrottle(fn, 200);
19
- throttled("a");
20
- throttled("b");
21
- throttled("c");
22
- expect(fn).toHaveBeenCalledTimes(1);
23
- expect(fn).toHaveBeenCalledWith("a");
24
-
25
- vi.advanceTimersByTime(200);
26
- expect(fn).toHaveBeenCalledTimes(2);
27
- expect(fn).toHaveBeenCalledWith("c");
28
- });
29
-
30
- it("does not fire trailing call if no pending args", () => {
31
- const fn = vi.fn();
32
- const throttled = createThrottle(fn, 200);
33
- throttled("a");
34
- expect(fn).toHaveBeenCalledTimes(1);
35
-
36
- vi.advanceTimersByTime(200);
37
- // No new call after "a", so no trailing fire
38
- expect(fn).toHaveBeenCalledTimes(1);
39
- });
40
-
41
- it("flush() immediately executes the latest pending args", () => {
42
- const fn = vi.fn();
43
- const throttled = createThrottle(fn, 200);
44
- throttled("a");
45
- throttled("b");
46
- throttled.flush();
47
- expect(fn).toHaveBeenCalledTimes(2);
48
- expect(fn).toHaveBeenCalledWith("b");
49
- });
50
-
51
- it("flush() does nothing if no pending call", () => {
52
- const fn = vi.fn();
53
- const throttled = createThrottle(fn, 200);
54
- throttled.flush();
55
- expect(fn).toHaveBeenCalledTimes(0);
56
- });
57
-
58
- it("dispose() clears timers and prevents future trailing calls", () => {
59
- const fn = vi.fn();
60
- const throttled = createThrottle(fn, 200);
61
- throttled("a");
62
- throttled("b");
63
- throttled.dispose();
64
- vi.advanceTimersByTime(500);
65
- expect(fn).toHaveBeenCalledTimes(1);
66
- expect(fn).toHaveBeenCalledWith("a");
67
- });
68
- });
69
-
70
- describe("resolveThrottleInterval", () => {
71
- it("returns 0 for 1 task (no throttle)", () => {
72
- expect(resolveThrottleInterval(1)).toBe(0);
73
- });
74
-
75
- it("returns 200ms for 2-4 tasks", () => {
76
- expect(resolveThrottleInterval(2)).toBe(200);
77
- expect(resolveThrottleInterval(3)).toBe(200);
78
- expect(resolveThrottleInterval(4)).toBe(200);
79
- });
80
-
81
- it("returns 500ms for 5-8 tasks", () => {
82
- expect(resolveThrottleInterval(5)).toBe(500);
83
- expect(resolveThrottleInterval(8)).toBe(500);
84
- });
85
-
86
- it("returns 1000ms for 9+ tasks", () => {
87
- expect(resolveThrottleInterval(9)).toBe(1000);
88
- expect(resolveThrottleInterval(16)).toBe(1000);
89
- });
90
- });