@leing2021/super-pi 0.16.0 → 0.18.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 +143 -12
- package/extensions/ce-core/index.ts +55 -24
- package/extensions/ce-core/tools/async-mutex.ts +28 -0
- package/extensions/ce-core/tools/parallel-subagent.ts +49 -3
- package/extensions/ce-core/tools/subagent-depth-guard.ts +86 -0
- package/extensions/ce-core/tools/subagent.ts +58 -3
- package/package.json +2 -1
- package/rules/README.md +111 -0
- package/rules/common/agents.md +50 -0
- package/rules/common/code-review.md +124 -0
- package/rules/common/coding-style.md +90 -0
- package/rules/common/development-workflow.md +44 -0
- package/rules/common/git-workflow.md +24 -0
- package/rules/common/hooks.md +30 -0
- package/rules/common/patterns.md +31 -0
- package/rules/common/performance.md +55 -0
- package/rules/common/security.md +29 -0
- package/rules/common/testing.md +57 -0
- package/rules/cpp/coding-style.md +44 -0
- package/rules/cpp/hooks.md +39 -0
- package/rules/cpp/patterns.md +51 -0
- package/rules/cpp/security.md +51 -0
- package/rules/cpp/testing.md +44 -0
- package/rules/csharp/coding-style.md +72 -0
- package/rules/csharp/hooks.md +25 -0
- package/rules/csharp/patterns.md +50 -0
- package/rules/csharp/security.md +58 -0
- package/rules/csharp/testing.md +46 -0
- package/rules/dart/coding-style.md +159 -0
- package/rules/dart/hooks.md +66 -0
- package/rules/dart/patterns.md +261 -0
- package/rules/dart/security.md +135 -0
- package/rules/dart/testing.md +215 -0
- package/rules/golang/coding-style.md +32 -0
- package/rules/golang/hooks.md +17 -0
- package/rules/golang/patterns.md +45 -0
- package/rules/golang/security.md +34 -0
- package/rules/golang/testing.md +31 -0
- package/rules/java/coding-style.md +114 -0
- package/rules/java/hooks.md +18 -0
- package/rules/java/patterns.md +146 -0
- package/rules/java/security.md +100 -0
- package/rules/java/testing.md +131 -0
- package/rules/kotlin/coding-style.md +86 -0
- package/rules/kotlin/hooks.md +17 -0
- package/rules/kotlin/patterns.md +146 -0
- package/rules/kotlin/security.md +82 -0
- package/rules/kotlin/testing.md +128 -0
- package/rules/perl/coding-style.md +46 -0
- package/rules/perl/hooks.md +22 -0
- package/rules/perl/patterns.md +76 -0
- package/rules/perl/security.md +69 -0
- package/rules/perl/testing.md +54 -0
- package/rules/php/coding-style.md +40 -0
- package/rules/php/hooks.md +24 -0
- package/rules/php/patterns.md +33 -0
- package/rules/php/security.md +37 -0
- package/rules/php/testing.md +39 -0
- package/rules/python/coding-style.md +42 -0
- package/rules/python/hooks.md +19 -0
- package/rules/python/patterns.md +39 -0
- package/rules/python/security.md +30 -0
- package/rules/python/testing.md +38 -0
- package/rules/rust/coding-style.md +151 -0
- package/rules/rust/hooks.md +16 -0
- package/rules/rust/patterns.md +168 -0
- package/rules/rust/security.md +141 -0
- package/rules/rust/testing.md +154 -0
- package/rules/swift/coding-style.md +47 -0
- package/rules/swift/hooks.md +20 -0
- package/rules/swift/patterns.md +66 -0
- package/rules/swift/security.md +33 -0
- package/rules/swift/testing.md +45 -0
- package/rules/typescript/coding-style.md +199 -0
- package/rules/typescript/hooks.md +22 -0
- package/rules/typescript/patterns.md +52 -0
- package/rules/typescript/security.md +28 -0
- package/rules/typescript/testing.md +18 -0
- package/rules/web/coding-style.md +96 -0
- package/rules/web/design-quality.md +63 -0
- package/rules/web/hooks.md +120 -0
- package/rules/web/patterns.md +79 -0
- package/rules/web/performance.md +64 -0
- package/rules/web/security.md +57 -0
- package/rules/web/testing.md +55 -0
- package/skills/02-plan/SKILL.md +1 -0
- package/skills/03-work/SKILL.md +1 -0
- package/skills/04-review/SKILL.md +1 -0
- package/skills/10-rules/SKILL.md +68 -0
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Next time `02-plan` or `04-review` runs, a grep-first search strategy automatica
|
|
|
85
85
|
|
|
86
86
|
## Technical Architecture
|
|
87
87
|
|
|
88
|
-
###
|
|
88
|
+
### 10 Skills (workflow nodes)
|
|
89
89
|
|
|
90
90
|
| Skill | One-liner | Core Tool |
|
|
91
91
|
|-------|-----------|-----------|
|
|
@@ -98,15 +98,16 @@ Next time `02-plan` or `04-review` runs, a grep-first search strategy automatica
|
|
|
98
98
|
| `07-worktree` | Git worktree isolated development | `worktree_manager` |
|
|
99
99
|
| `08-status` | Scan artifacts, report progress | `workflow_state`, `session_history` |
|
|
100
100
|
| `09-help` | Usage guide | — |
|
|
101
|
+
| `10-rules` | Progressive rule loading for coding standards | — |
|
|
101
102
|
|
|
102
|
-
###
|
|
103
|
+
### 15 Tools (underlying capabilities)
|
|
103
104
|
|
|
104
105
|
| Tool | What it does |
|
|
105
106
|
|------|-------------|
|
|
106
107
|
| `task_splitter` | Union-Find algorithm analyzes file dependencies, auto-groups parallel-safe units |
|
|
107
108
|
| `session_checkpoint` | JSON-persisted checkpoints with save/load/fail/retry operations |
|
|
108
109
|
| `plan_diff` | Incremental plans: compare detects diffs, patch applies changes |
|
|
109
|
-
| `parallel_subagent` | `Promise.allSettled`-style parallel subagent execution |
|
|
110
|
+
| `parallel_subagent` | `Promise.allSettled`-style parallel subagent execution with context slimming |
|
|
110
111
|
| `review_router` | Auto-assign reviewer personas from diff metadata |
|
|
111
112
|
| `pattern_extractor` | Extract and categorize patterns from artifacts |
|
|
112
113
|
| `brainstorm_dialog` | Multi-round dialog state machine (start → refine × N → summarize) |
|
|
@@ -115,13 +116,39 @@ Next time `02-plan` or `04-review` runs, a grep-first search strategy automatica
|
|
|
115
116
|
| `worktree_manager` | Full git worktree lifecycle management |
|
|
116
117
|
| `artifact_helper` | Artifact path resolution and directory creation |
|
|
117
118
|
| `ask_user_question` | Structured user prompts (choices / free input) |
|
|
118
|
-
| `subagent` | Serial subagent chain |
|
|
119
|
+
| `subagent` | Serial subagent chain with depth guard and context control |
|
|
120
|
+
| `subagent-depth-guard` | Env-based recursion depth tracking (prevents runaway nesting) |
|
|
121
|
+
| `async-mutex` | Serializes `process.env` mutation for concurrency-safe child process spawning |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Token Cost
|
|
126
|
+
|
|
127
|
+
New conversation overhead: **~2,500 tokens** (1.26% of Claude Sonnet 4's 200K context).
|
|
128
|
+
|
|
129
|
+
| Component | Tokens | When loaded |
|
|
130
|
+
|-----------|--------|-------------|
|
|
131
|
+
| 10 skill registrations | ~615 | Every conversation (fixed) |
|
|
132
|
+
| 13 tool registrations | ~1,914 | Every conversation (fixed) |
|
|
133
|
+
| Hooks & filters | 0 | Runtime interception, zero prompt cost |
|
|
134
|
+
| Single skill trigger | ~1,000–4,000 | On-demand via `read` |
|
|
135
|
+
| Rules minimal (2 files) | ~900 | Before plan/work |
|
|
136
|
+
| Rules + language (7 files) | ~2,600 | Before work with specific language |
|
|
137
|
+
|
|
138
|
+
| vs bare Pi | vs global rules injection | vs super-pi |
|
|
139
|
+
|-----------|----------------------|------------|
|
|
140
|
+
| No rules | All rules loaded upfront | Progressive on-demand |
|
|
141
|
+
| No output filtering | No output filtering | Auto-compress (bash ~65–98%, read ~30–60%) |
|
|
142
|
+
| No TDD gate | No TDD gate | Hard gate prevents rework |
|
|
143
|
+
| 0 tokens | ~5,000–36,000 tokens | **~2,500 tokens** |
|
|
144
|
+
|
|
145
|
+
Single `npm install` output filtered once pays for the entire overhead. Full evaluation → [`docs/token-cost-evaluation.md`](docs/token-cost-evaluation.md)
|
|
119
146
|
|
|
120
147
|
---
|
|
121
148
|
|
|
122
149
|
## Code Scale
|
|
123
150
|
|
|
124
|
-
~
|
|
151
|
+
~2500 lines of TypeScript implementing 15 tools, 22 Markdown reference files + 78 rule files driving 10 skills, 162 tests covering all tool logic.
|
|
125
152
|
|
|
126
153
|
Not a heavy framework. Each tool has a single responsibility, each skill works independently, and together they form a complete workflow.
|
|
127
154
|
|
|
@@ -202,22 +229,103 @@ your-project/
|
|
|
202
229
|
|
|
203
230
|
**Recommendation: commit everything to git** — these files are the project's traceable memory.
|
|
204
231
|
|
|
232
|
+
### `10-rules`: Progressive Rule Loading
|
|
233
|
+
|
|
234
|
+
Built-in coding rules live under `rules/` in the package. The `10-rules` skill loads them **progressively** — never all at once, only what the current task needs.
|
|
235
|
+
|
|
236
|
+
**How it works:**
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
system prompt (30 tokens: skill name + description)
|
|
240
|
+
→ 10-rules SKILL.md (~200 tokens: loading decision tree)
|
|
241
|
+
→ specific rule files via read tool (on-demand, 900–2600 tokens)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Three CE skills auto-trigger rule loading at their entry points:
|
|
245
|
+
|
|
246
|
+
| Skill | Rules pre-loaded |
|
|
247
|
+
|-------|-----------------|
|
|
248
|
+
| `02-plan` | `common/development-workflow.md` + `common/testing.md` |
|
|
249
|
+
| `03-work` | Language-specific rules matching the active codebase |
|
|
250
|
+
| `04-review` | `common/code-review.md` + language rules for changed files |
|
|
251
|
+
|
|
252
|
+
**Rule precedence** (when layers overlap on the same topic):
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
language-specific > web > common
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
No rules are loaded when you brainstorm, check status, or do non-code tasks. Zero waste.
|
|
259
|
+
|
|
260
|
+
#### Included rule layers
|
|
261
|
+
|
|
262
|
+
| Layer | Files | When loaded |
|
|
263
|
+
|-------|-------|------------|
|
|
264
|
+
| `common/` | 10 files | Always (baseline for all tasks) |
|
|
265
|
+
| `typescript/`, `python/`, `cpp/`, `csharp/`, `dart/`, `golang/`, `java/`, `kotlin/`, `perl/`, `php/`, `rust/`, `swift/` | 5 files each | When the task touches that language |
|
|
266
|
+
| `web/` | 7 files (includes `design-quality.md`, `performance.md`) | When frontend/browser is relevant |
|
|
267
|
+
|
|
268
|
+
#### Customizing rules for your project
|
|
269
|
+
|
|
270
|
+
Rules are plain Markdown files in the `rules/` directory. Edit them freely:
|
|
271
|
+
|
|
272
|
+
**Add a language** — create a new directory with the 5 standard topics:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
mkdir rules/elixir
|
|
276
|
+
touch rules/elixir/{coding-style,testing,patterns,security,hooks}.md
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Each file should start with:
|
|
280
|
+
|
|
281
|
+
```markdown
|
|
282
|
+
> This file extends [common/xxx.md](../common/xxx.md) with Elixir-specific content.
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Remove unused languages** — just delete the directory:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
rm -rf rules/perl rules/cpp # don't need these? remove them
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Tweak a rule** — edit the `.md` file directly:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Override testing conventions for your team
|
|
295
|
+
vim rules/common/testing.md
|
|
296
|
+
|
|
297
|
+
# Override for a specific language
|
|
298
|
+
vim rules/typescript/testing.md
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Add a new topic** — create a new `.md` in the appropriate layer:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# Common topic
|
|
305
|
+
vim rules/common/api-design.md
|
|
306
|
+
|
|
307
|
+
# Language-specific override
|
|
308
|
+
vim rules/python/api-design.md
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The `10-rules` skill will pick up any `.md` file in `rules/` — no configuration needed. If a language directory exists, it's available for loading. If it's gone, it's simply never loaded.
|
|
312
|
+
|
|
205
313
|
---
|
|
206
314
|
|
|
207
|
-
## Design Philosophy
|
|
315
|
+
## Design Philosophy & Acknowledgements
|
|
208
316
|
|
|
209
317
|
**80% planning and review, 20% execution.**
|
|
210
318
|
|
|
211
319
|
The goal isn't making AI write code faster — it's making AI think before writing, review after writing, and compound learnings after reviewing. Speed comes from fewer rewrites, not from skipping steps.
|
|
212
320
|
|
|
213
|
-
|
|
321
|
+
The following projects directly inspired this work:
|
|
214
322
|
|
|
215
|
-
- **
|
|
216
|
-
- **
|
|
217
|
-
- **
|
|
218
|
-
- **
|
|
323
|
+
- **[everything-claude-code](https://github.com/affaan-m/everything-claude-code)** (162K★) → Parallel subagent orchestration, checkpoint resume, continuous learning
|
|
324
|
+
- **[superpowers](https://github.com/obra/superpowers)** (161K★) → Strict TDD gates, design checklists, review discipline
|
|
325
|
+
- **[gstack](https://github.com/garrytan/gstack)** (78K★) → YC-style forcing questions, CEO Review frameworks, browser QA
|
|
326
|
+
- **[compound-engineering-plugin](https://github.com/EveryInc/compound-engineering-plugin)** (14K★) → Five-step loop, knowledge compounding backbone
|
|
219
327
|
|
|
220
|
-
Not a fork. Not a wrapper.
|
|
328
|
+
Not a fork. Not a wrapper. Methodologies extracted and rebuilt with Pi's native tool + skill system.
|
|
221
329
|
|
|
222
330
|
---
|
|
223
331
|
|
|
@@ -236,6 +344,29 @@ Not a fork. Not a wrapper. The methodologies are extracted and rebuilt with Pi's
|
|
|
236
344
|
|
|
237
345
|
## Changelog
|
|
238
346
|
|
|
347
|
+
### 0.18.0 — Progressive rules
|
|
348
|
+
- Built-in `rules/` directory with 13 language layers + common + web (78 Markdown files)
|
|
349
|
+
- New `10-rules` skill: progressive on-demand loading, zero waste
|
|
350
|
+
- `02-plan`, `03-work`, `04-review` auto-trigger rule loading at entry points
|
|
351
|
+
- Users can add/remove languages and edit rules freely — plain Markdown, no config
|
|
352
|
+
- 10 skills, 15 tools, 162 tests passing
|
|
353
|
+
|
|
354
|
+
### 0.17.0 — Subagent safety
|
|
355
|
+
- Recursion depth guard (`PI_SUBAGENT_DEPTH` / `PI_SUBAGENT_MAX_DEPTH`) prevents runaway nesting
|
|
356
|
+
- Async mutex for `process.env` concurrency safety during parallel subagent execution
|
|
357
|
+
- Context slimming: `inheritSkills` parameter, parallel workers default to slim context (`--no-skills`)
|
|
358
|
+
- Shared `createSubagentRunner` factory (deduped runner closures)
|
|
359
|
+
- 162 tests passing
|
|
360
|
+
|
|
361
|
+
### 0.16.0 — Context optimization
|
|
362
|
+
- Read output filter: structural compression for large code files, lock files, markdown
|
|
363
|
+
- Compaction optimizer: focused summary instructions for session compaction
|
|
364
|
+
- Bash output filter improvements
|
|
365
|
+
|
|
366
|
+
### 0.15.0 — Output filtering
|
|
367
|
+
- Bash output filter: smart truncation by command type (install, test, build)
|
|
368
|
+
- Read output filter: preserves structure while cutting verbosity
|
|
369
|
+
|
|
239
370
|
### 0.14.0 — Structured solution retrieval
|
|
240
371
|
- YAML frontmatter tagging + grep-first two-level search
|
|
241
372
|
- 95 tests passing
|
|
@@ -16,6 +16,51 @@ import { createPatternExtractorTool } from "./tools/pattern-extractor"
|
|
|
16
16
|
import { filterBashOutput } from "./tools/bash-output-filter"
|
|
17
17
|
import { filterReadOutput } from "./tools/read-output-filter"
|
|
18
18
|
import { COMPACTION_FOCUS_INSTRUCTIONS } from "./tools/compaction-optimizer"
|
|
19
|
+
import { AsyncMutex } from "./tools/async-mutex"
|
|
20
|
+
import type { SubagentExecOptions, SubagentRunner } from "./tools/subagent"
|
|
21
|
+
|
|
22
|
+
const _subagentEnvMutex = new AsyncMutex()
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a subagent runner that handles env injection with mutex protection
|
|
26
|
+
* for concurrency safety when multiple parallel subagents share the process.
|
|
27
|
+
*/
|
|
28
|
+
function createSubagentRunner(
|
|
29
|
+
pi: ExtensionAPI,
|
|
30
|
+
signal?: AbortSignal,
|
|
31
|
+
): SubagentRunner {
|
|
32
|
+
return async (prompt: string, options?: SubagentExecOptions) => {
|
|
33
|
+
const args = ["--no-session", ...(options?.extraFlags ?? []), "-p", prompt]
|
|
34
|
+
const release = await _subagentEnvMutex.acquire()
|
|
35
|
+
const savedEnv: Record<string, string | undefined> = {}
|
|
36
|
+
const extraEnv = options?.extraEnv ?? {}
|
|
37
|
+
for (const [key, value] of Object.entries(extraEnv)) {
|
|
38
|
+
savedEnv[key] = process.env[key]
|
|
39
|
+
process.env[key] = value
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const execResult = await pi.exec("pi", args, {
|
|
43
|
+
signal,
|
|
44
|
+
timeout: 10 * 60 * 1000,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
if (execResult.code !== 0) {
|
|
48
|
+
throw new Error(execResult.stderr || execResult.stdout || `Subagent failed for prompt: ${prompt}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (execResult.stdout || "").trim()
|
|
52
|
+
} finally {
|
|
53
|
+
for (const [key, oldValue] of Object.entries(savedEnv)) {
|
|
54
|
+
if (oldValue === undefined) {
|
|
55
|
+
delete process.env[key]
|
|
56
|
+
} else {
|
|
57
|
+
process.env[key] = oldValue
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
release()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
19
64
|
|
|
20
65
|
const artifactHelperParams = Type.Object({
|
|
21
66
|
repoRoot: Type.String({ description: "Repository root where workflow artifacts should be created" }),
|
|
@@ -48,6 +93,7 @@ const subagentParams = Type.Object({
|
|
|
48
93
|
agent: Type.Optional(Type.String({ description: "Single subagent skill name" })),
|
|
49
94
|
task: Type.Optional(Type.String({ description: "Single subagent task" })),
|
|
50
95
|
chain: Type.Optional(Type.Array(subagentTaskSchema, { description: "Serial subagent chain with optional {previous} placeholder" })),
|
|
96
|
+
inheritSkills: Type.Optional(Type.Boolean({ description: "Whether the subagent should inherit skills. Default: true" })),
|
|
51
97
|
})
|
|
52
98
|
|
|
53
99
|
const workflowStateParams = Type.Object({
|
|
@@ -79,6 +125,7 @@ const parallelSubagentTaskSchema = Type.Object({
|
|
|
79
125
|
|
|
80
126
|
const parallelSubagentParams = Type.Object({
|
|
81
127
|
tasks: Type.Array(parallelSubagentTaskSchema, { description: "Array of independent tasks to run concurrently" }),
|
|
128
|
+
inheritSkills: Type.Optional(Type.Boolean({ description: "Whether subagents should inherit skills. Default: false" })),
|
|
82
129
|
})
|
|
83
130
|
|
|
84
131
|
const sessionCheckpointParams = Type.Object({
|
|
@@ -263,19 +310,9 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
|
|
|
263
310
|
agent: params.agent,
|
|
264
311
|
task: params.task,
|
|
265
312
|
chain: params.chain,
|
|
313
|
+
inheritSkills: params.inheritSkills,
|
|
266
314
|
},
|
|
267
|
-
|
|
268
|
-
const execResult = await pi.exec("pi", ["--no-session", "-p", prompt], {
|
|
269
|
-
signal,
|
|
270
|
-
timeout: 10 * 60 * 1000,
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
if (execResult.code !== 0) {
|
|
274
|
-
throw new Error(execResult.stderr || execResult.stdout || `Subagent failed for prompt: ${prompt}`)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return (execResult.stdout || "").trim()
|
|
278
|
-
},
|
|
315
|
+
createSubagentRunner(pi, signal),
|
|
279
316
|
)
|
|
280
317
|
|
|
281
318
|
return {
|
|
@@ -363,19 +400,11 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
|
|
|
363
400
|
parameters: parallelSubagentParams,
|
|
364
401
|
async execute(_toolCallId, params, signal) {
|
|
365
402
|
const result = await parallelSubagent.execute(
|
|
366
|
-
{
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
signal,
|
|
370
|
-
timeout: 10 * 60 * 1000,
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
if (execResult.code !== 0) {
|
|
374
|
-
throw new Error(execResult.stderr || execResult.stdout || `Subagent failed for prompt: ${prompt}`)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return (execResult.stdout || "").trim()
|
|
403
|
+
{
|
|
404
|
+
tasks: params.tasks,
|
|
405
|
+
inheritSkills: params.inheritSkills,
|
|
378
406
|
},
|
|
407
|
+
createSubagentRunner(pi, signal),
|
|
379
408
|
)
|
|
380
409
|
|
|
381
410
|
return {
|
|
@@ -595,6 +624,8 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
|
|
|
595
624
|
export { createArtifactHelperTool } from "./tools/artifact-helper"
|
|
596
625
|
export { createAskUserQuestionTool } from "./tools/ask-user-question"
|
|
597
626
|
export { createSubagentTool } from "./tools/subagent"
|
|
627
|
+
export { checkSubagentDepth, getChildDepthEnv, DEFAULT_MAX_SUBAGENT_DEPTH } from "./tools/subagent-depth-guard"
|
|
628
|
+
export { AsyncMutex } from "./tools/async-mutex"
|
|
598
629
|
export { createWorkflowStateTool } from "./tools/workflow-state"
|
|
599
630
|
export { createWorktreeManagerTool } from "./tools/worktree-manager"
|
|
600
631
|
export { createReviewRouterTool } from "./tools/review-router"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async mutex for serializing critical sections that mutate shared state
|
|
3
|
+
* (e.g. process.env) across concurrent async operations.
|
|
4
|
+
*/
|
|
5
|
+
export class AsyncMutex {
|
|
6
|
+
private queue: (() => void)[] = []
|
|
7
|
+
private locked = false
|
|
8
|
+
|
|
9
|
+
async acquire(): Promise<() => void> {
|
|
10
|
+
return new Promise<() => void>((resolve) => {
|
|
11
|
+
const tryAcquire = () => {
|
|
12
|
+
if (!this.locked) {
|
|
13
|
+
this.locked = true
|
|
14
|
+
resolve(() => this.release())
|
|
15
|
+
} else {
|
|
16
|
+
this.queue.push(tryAcquire)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
tryAcquire()
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private release(): void {
|
|
24
|
+
this.locked = false
|
|
25
|
+
const next = this.queue.shift()
|
|
26
|
+
if (next) next()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel subagent tool — runs multiple skill-based subagent tasks concurrently.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Recursion depth guard (PI_SUBAGENT_DEPTH / PI_SUBAGENT_MAX_DEPTH)
|
|
6
|
+
* - Optional context slimming via --no-skills flag
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
checkSubagentDepth,
|
|
11
|
+
getChildDepthEnv,
|
|
12
|
+
} from "./subagent-depth-guard"
|
|
13
|
+
import type { SubagentExecOptions, SubagentRunner } from "./subagent"
|
|
14
|
+
|
|
1
15
|
export interface ParallelSubagentTask {
|
|
2
16
|
agent: string
|
|
3
17
|
task: string
|
|
@@ -5,6 +19,8 @@ export interface ParallelSubagentTask {
|
|
|
5
19
|
|
|
6
20
|
export interface ParallelSubagentInput {
|
|
7
21
|
tasks: ParallelSubagentTask[]
|
|
22
|
+
/** Whether subagents should inherit skills. Default: false for parallel */
|
|
23
|
+
inheritSkills?: boolean
|
|
8
24
|
}
|
|
9
25
|
|
|
10
26
|
export interface ParallelResultItem {
|
|
@@ -18,8 +34,6 @@ export interface ParallelSubagentResult {
|
|
|
18
34
|
outputs: ParallelResultItem[]
|
|
19
35
|
}
|
|
20
36
|
|
|
21
|
-
export type SubagentRunner = (prompt: string) => Promise<string>
|
|
22
|
-
|
|
23
37
|
function buildPrompt(agent: string, task: string): string {
|
|
24
38
|
return `/skill:${agent} ${task}`
|
|
25
39
|
}
|
|
@@ -28,12 +42,22 @@ export function createParallelSubagentTool() {
|
|
|
28
42
|
return {
|
|
29
43
|
name: "parallel_subagent",
|
|
30
44
|
async execute(input: ParallelSubagentInput, runner: SubagentRunner): Promise<ParallelSubagentResult> {
|
|
45
|
+
// Recursion depth guard
|
|
46
|
+
const depthCheck = checkSubagentDepth()
|
|
47
|
+
if (!depthCheck.allowed) {
|
|
48
|
+
throw new Error(depthCheck.reason!)
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
if (!input.tasks || input.tasks.length === 0) {
|
|
32
52
|
throw new Error("parallel_subagent requires at least one task")
|
|
33
53
|
}
|
|
34
54
|
|
|
55
|
+
// Default: parallel workers get a slim context (no inherited skills)
|
|
56
|
+
const inheritSkills = input.inheritSkills ?? false
|
|
57
|
+
const execOptions = buildParallelExecOptions(inheritSkills)
|
|
58
|
+
|
|
35
59
|
const promises = input.tasks.map(({ agent, task }) =>
|
|
36
|
-
runner(buildPrompt(agent, task)),
|
|
60
|
+
runner(buildPrompt(agent, task), execOptions),
|
|
37
61
|
)
|
|
38
62
|
|
|
39
63
|
const settled = await Promise.allSettled(promises)
|
|
@@ -52,3 +76,25 @@ export function createParallelSubagentTool() {
|
|
|
52
76
|
},
|
|
53
77
|
}
|
|
54
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build exec options for parallel subagent workers.
|
|
82
|
+
* Defaults to slim context (no inherited skills).
|
|
83
|
+
*/
|
|
84
|
+
function buildParallelExecOptions(inheritSkills: boolean): SubagentExecOptions {
|
|
85
|
+
const flags: string[] = []
|
|
86
|
+
const env: Record<string, string> = {}
|
|
87
|
+
|
|
88
|
+
// Context slimming
|
|
89
|
+
if (!inheritSkills) {
|
|
90
|
+
flags.push("--no-skills")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Recursion depth
|
|
94
|
+
Object.assign(env, getChildDepthEnv())
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
extraFlags: flags.length > 0 ? flags : undefined,
|
|
98
|
+
extraEnv: env,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent recursion depth guard.
|
|
3
|
+
*
|
|
4
|
+
* Prevents uncontrolled recursive subagent spawning by tracking depth
|
|
5
|
+
* via environment variables. Inspired by pi-subagents' depth control
|
|
6
|
+
* but implemented as a minimal, standalone module.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* PI_SUBAGENT_DEPTH — current nesting depth (default: 0)
|
|
10
|
+
* PI_SUBAGENT_MAX_DEPTH — maximum allowed depth (default: 2)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Default maximum subagent nesting depth */
|
|
14
|
+
export const DEFAULT_MAX_SUBAGENT_DEPTH = 2
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read the current subagent depth from environment.
|
|
18
|
+
* Returns 0 if unset or invalid.
|
|
19
|
+
*/
|
|
20
|
+
export function getCurrentDepth(): number {
|
|
21
|
+
const raw = process.env.PI_SUBAGENT_DEPTH
|
|
22
|
+
if (!raw) return 0
|
|
23
|
+
const depth = parseInt(raw, 10)
|
|
24
|
+
return Number.isNaN(depth) ? 0 : depth
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Read the maximum allowed depth from environment.
|
|
29
|
+
* Falls back to DEFAULT_MAX_SUBAGENT_DEPTH if unset or invalid.
|
|
30
|
+
*/
|
|
31
|
+
export function getMaxDepth(): number {
|
|
32
|
+
const raw = process.env.PI_SUBAGENT_MAX_DEPTH
|
|
33
|
+
if (!raw) return DEFAULT_MAX_SUBAGENT_DEPTH
|
|
34
|
+
const max = parseInt(raw, 10)
|
|
35
|
+
return Number.isNaN(max) || max < 0 ? DEFAULT_MAX_SUBAGENT_DEPTH : max
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check whether subagent execution is allowed at the current depth.
|
|
40
|
+
* Returns an object with:
|
|
41
|
+
* allowed — whether execution can proceed
|
|
42
|
+
* depth — the current depth
|
|
43
|
+
* max — the maximum depth
|
|
44
|
+
* reason — human-readable explanation if blocked
|
|
45
|
+
*/
|
|
46
|
+
export function checkSubagentDepth(): {
|
|
47
|
+
allowed: boolean
|
|
48
|
+
depth: number
|
|
49
|
+
max: number
|
|
50
|
+
reason?: string
|
|
51
|
+
} {
|
|
52
|
+
const depth = getCurrentDepth()
|
|
53
|
+
const max = getMaxDepth()
|
|
54
|
+
|
|
55
|
+
if (depth >= max) {
|
|
56
|
+
return {
|
|
57
|
+
allowed: false,
|
|
58
|
+
depth,
|
|
59
|
+
max,
|
|
60
|
+
reason: `Subagent recursion depth limit reached (depth=${depth}, max=${max}). ` +
|
|
61
|
+
`Set PI_SUBAGENT_MAX_DEPTH to increase the limit.`,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { allowed: true, depth, max }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build environment variables to pass to a child subagent process.
|
|
70
|
+
* Increments the depth counter so the child can guard its own spawns.
|
|
71
|
+
*
|
|
72
|
+
* @param overrides — optional overrides for max depth per-agent
|
|
73
|
+
*/
|
|
74
|
+
export function getChildDepthEnv(overrides?: {
|
|
75
|
+
maxDepth?: number
|
|
76
|
+
}): Record<string, string> {
|
|
77
|
+
const currentDepth = getCurrentDepth()
|
|
78
|
+
const childDepth = currentDepth + 1
|
|
79
|
+
let maxDepth = overrides?.maxDepth ?? getMaxDepth()
|
|
80
|
+
if (maxDepth < 0) maxDepth = DEFAULT_MAX_SUBAGENT_DEPTH
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
PI_SUBAGENT_DEPTH: String(childDepth),
|
|
84
|
+
PI_SUBAGENT_MAX_DEPTH: String(maxDepth),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent tool — runs a single skill-based subagent or a serial chain.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Recursion depth guard (PI_SUBAGENT_DEPTH / PI_SUBAGENT_MAX_DEPTH)
|
|
6
|
+
* - Optional context slimming via --no-skills flag
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
checkSubagentDepth,
|
|
11
|
+
getChildDepthEnv,
|
|
12
|
+
} from "./subagent-depth-guard"
|
|
13
|
+
|
|
1
14
|
export interface SubagentTask {
|
|
2
15
|
agent: string
|
|
3
16
|
task: string
|
|
@@ -7,6 +20,8 @@ export interface SubagentInput {
|
|
|
7
20
|
agent?: string
|
|
8
21
|
task?: string
|
|
9
22
|
chain?: SubagentTask[]
|
|
23
|
+
/** Whether the subagent should inherit skills. Default: true */
|
|
24
|
+
inheritSkills?: boolean
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
export interface SubagentResult {
|
|
@@ -14,12 +29,28 @@ export interface SubagentResult {
|
|
|
14
29
|
outputs: string[]
|
|
15
30
|
}
|
|
16
31
|
|
|
17
|
-
export
|
|
32
|
+
export interface SubagentExecOptions {
|
|
33
|
+
/** Extra CLI flags to pass to the child pi process */
|
|
34
|
+
extraFlags?: string[]
|
|
35
|
+
/** Environment variables to inject into the child process */
|
|
36
|
+
extraEnv?: Record<string, string>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SubagentRunner = (
|
|
40
|
+
prompt: string,
|
|
41
|
+
options?: SubagentExecOptions,
|
|
42
|
+
) => Promise<string>
|
|
18
43
|
|
|
19
44
|
export function createSubagentTool() {
|
|
20
45
|
return {
|
|
21
46
|
name: "subagent",
|
|
22
47
|
async execute(input: SubagentInput, runner: SubagentRunner): Promise<SubagentResult> {
|
|
48
|
+
// Recursion depth guard
|
|
49
|
+
const depthCheck = checkSubagentDepth()
|
|
50
|
+
if (!depthCheck.allowed) {
|
|
51
|
+
throw new Error(depthCheck.reason!)
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
const hasSingle = Boolean(input.agent && input.task)
|
|
24
55
|
const hasChain = Boolean(input.chain && input.chain.length > 0)
|
|
25
56
|
|
|
@@ -27,9 +58,11 @@ export function createSubagentTool() {
|
|
|
27
58
|
throw new Error("Provide exactly one mode: single or chain")
|
|
28
59
|
}
|
|
29
60
|
|
|
61
|
+
const execOptions = buildExecOptions(input.inheritSkills)
|
|
62
|
+
|
|
30
63
|
if (hasSingle) {
|
|
31
64
|
const prompt = buildPrompt(input.agent!, input.task!)
|
|
32
|
-
const output = await runner(prompt)
|
|
65
|
+
const output = await runner(prompt, execOptions)
|
|
33
66
|
return {
|
|
34
67
|
mode: "single",
|
|
35
68
|
outputs: [output],
|
|
@@ -41,7 +74,7 @@ export function createSubagentTool() {
|
|
|
41
74
|
|
|
42
75
|
for (const task of input.chain ?? []) {
|
|
43
76
|
const prompt = buildPrompt(task.agent, task.task.replace(/\{previous\}/g, previous))
|
|
44
|
-
const output = await runner(prompt)
|
|
77
|
+
const output = await runner(prompt, execOptions)
|
|
45
78
|
outputs.push(output)
|
|
46
79
|
previous = output
|
|
47
80
|
}
|
|
@@ -54,6 +87,28 @@ export function createSubagentTool() {
|
|
|
54
87
|
}
|
|
55
88
|
}
|
|
56
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Build exec options based on subagent configuration.
|
|
92
|
+
* Controls context inheritance and recursion depth.
|
|
93
|
+
*/
|
|
94
|
+
function buildExecOptions(inheritSkills?: boolean): SubagentExecOptions {
|
|
95
|
+
const flags: string[] = []
|
|
96
|
+
const env: Record<string, string> = {}
|
|
97
|
+
|
|
98
|
+
// Context slimming: skip skills inheritance when explicitly disabled
|
|
99
|
+
if (inheritSkills === false) {
|
|
100
|
+
flags.push("--no-skills")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Recursion depth: pass incremented depth to child process
|
|
104
|
+
Object.assign(env, getChildDepthEnv())
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
extraFlags: flags.length > 0 ? flags : undefined,
|
|
108
|
+
extraEnv: env,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
57
112
|
function buildPrompt(agent: string, task: string): string {
|
|
58
113
|
return `/skill:${agent} ${task}`
|
|
59
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leing2021/super-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Pi-native Compound Engineering package for iterative development workflows",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"skills",
|
|
26
26
|
"extensions",
|
|
27
|
+
"rules",
|
|
27
28
|
"README.md",
|
|
28
29
|
"package.json"
|
|
29
30
|
],
|