gentle-pi 0.1.23 → 0.2.1
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 +46 -13
- package/assets/agents/sdd-apply.md +6 -0
- package/assets/agents/sdd-archive.md +6 -0
- package/assets/agents/sdd-design.md +6 -0
- package/assets/agents/sdd-explore.md +6 -0
- package/assets/agents/sdd-init.md +6 -0
- package/assets/agents/sdd-onboard.md +6 -0
- package/assets/agents/sdd-proposal.md +6 -0
- package/assets/agents/sdd-spec.md +6 -0
- package/assets/agents/sdd-tasks.md +6 -0
- package/assets/agents/sdd-verify.md +6 -0
- package/assets/orchestrator.md +74 -11
- package/extensions/gentle-ai.ts +1 -141
- package/extensions/skill-registry.ts +17 -70
- package/extensions/startup-banner.ts +463 -0
- package/package.json +4 -1
- package/skills/gentle-ai/SKILL.md +11 -2
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Follow the project and the community around it:
|
|
|
25
25
|
- YouTube: [Gentleman Programming](https://www.youtube.com/c/GentlemanProgramming)
|
|
26
26
|
- Community Discord: [Gentleman Programming](https://discord.com/invite/gentleman-programming-769863833996754944)
|
|
27
27
|
|
|
28
|
+
Startup intro collaboration: thanks to [@aporcelli](https://github.com/aporcelli) for [`pi-gentle-startup`](https://github.com/aporcelli/pi-gentle-startup), which inspired the clean-screen startup animation, compact runtime panel, and pink visual treatment.
|
|
29
|
+
|
|
28
30
|
## The problem
|
|
29
31
|
|
|
30
32
|
Most coding-agent sessions fail for operational reasons, not model reasons:
|
|
@@ -41,18 +43,19 @@ Most coding-agent sessions fail for operational reasons, not model reasons:
|
|
|
41
43
|
|
|
42
44
|
## What it adds
|
|
43
45
|
|
|
44
|
-
| Capability | What it does
|
|
45
|
-
| ------------------------------ |
|
|
46
|
-
| **el Gentleman persona** | Makes Pi behave like a senior architect and teacher, not a generic chatbot. Spanish responses use Rioplatense voseo by default.
|
|
47
|
-
| **
|
|
48
|
-
| **
|
|
49
|
-
| **
|
|
50
|
-
| **
|
|
51
|
-
| **
|
|
52
|
-
| **
|
|
53
|
-
| **
|
|
54
|
-
| **
|
|
55
|
-
| **
|
|
46
|
+
| Capability | What it does |
|
|
47
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| **el Gentleman persona** | Makes Pi behave like a senior architect and teacher, not a generic chatbot. Spanish responses use Rioplatense voseo by default. |
|
|
49
|
+
| **Rose startup intro** | Adds a pink rose fade-in, compact project/runtime panel, and visible startup collaboration credit for @aporcelli's `pi-gentle-startup` ideas. |
|
|
50
|
+
| **Work routing discipline** | Small tasks stay inline. Context-heavy exploration can be delegated. Large or risky changes go through SDD/OpenSpec. |
|
|
51
|
+
| **SDD/OpenSpec assets** | Installs phase agents and chains for `init`, `explore`, `proposal`, `spec`, `design`, `tasks`, `apply`, `verify`, and `archive`. |
|
|
52
|
+
| **Subagent orchestration** | Keeps one parent session responsible while child agents explore, implement, test, or review with focused context. |
|
|
53
|
+
| **Strict TDD support** | When project config declares a test command, apply/verify phases must record RED → GREEN → TRIANGULATE → REFACTOR evidence. |
|
|
54
|
+
| **Reviewer protection** | Surfaces review workload risk before a task turns into an oversized PR. |
|
|
55
|
+
| **Per-agent model assignment** | Pi-native modal for assigning stronger or cheaper models to specific SDD/custom agents. |
|
|
56
|
+
| **Skill discovery registry** | Maintains `.atl/skill-registry.md` from project and user skills so review/comment/PR workflows do not silently miss the right skill. |
|
|
57
|
+
| **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. |
|
|
58
|
+
| **Shell safety** | Blocks destructive shell commands and asks for confirmation for sensitive operations. |
|
|
56
59
|
|
|
57
60
|
## Install
|
|
58
61
|
|
|
@@ -107,7 +110,27 @@ Typical flow:
|
|
|
107
110
|
| Unknown codebase area or context-heavy investigation | Focused subagent delegation. |
|
|
108
111
|
| Large, ambiguous, architectural, product-facing, or high-review-risk change | SDD/OpenSpec flow. |
|
|
109
112
|
|
|
110
|
-
The goal is not ceremony. The goal is to avoid accidental chaos.
|
|
113
|
+
The goal is not ceremony. The goal is to avoid accidental chaos. Once a task stops being small, delegation is expected rather than optional.
|
|
114
|
+
|
|
115
|
+
### Delegation triggers
|
|
116
|
+
|
|
117
|
+
`gentle-pi` keeps the parent session thin and uses subagents at the narrowest useful point:
|
|
118
|
+
|
|
119
|
+
| Trigger | Expected behavior |
|
|
120
|
+
| --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
121
|
+
| Reading 4+ files to understand a flow | Launch `scout` or `context-builder` and synthesize its handoff. |
|
|
122
|
+
| Touching 2+ non-trivial code files | Use one `worker`, or require fresh review before completion. |
|
|
123
|
+
| Commit, push, or PR after code changes | Run a fresh-context `reviewer` unless the diff is trivial docs/text. |
|
|
124
|
+
| Wrong cwd, worktree/git accident, merge recovery, confusing test/env issue | Stop and run a fresh audit reviewer before continuing. |
|
|
125
|
+
| Long monolithic session with accumulating complexity, roughly 20 tool calls, 5 exploratory reads, or 2 non-mechanical edits | Pause and delegate or explain why not. |
|
|
126
|
+
|
|
127
|
+
The intended balanced loop for a bounded bugfix is:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
parent git/status + clarify → scout when context-heavy → one worker writes → fresh reviewer audits → parent validates and reports
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Fresh reviewers are intentionally not token-saving devices; they buy independent judgment. `scout`/`context-builder` save parent context by compressing broad exploration. `worker` preserves a single writer thread.
|
|
111
134
|
|
|
112
135
|
## SDD/OpenSpec flow
|
|
113
136
|
|
|
@@ -275,11 +298,19 @@ pi install npm:gentle-engram
|
|
|
275
298
|
|
|
276
299
|
When memory tools are actually active, el Gentleman can save decisions, bug fixes, discoveries, user prompts, and session summaries across Pi sessions.
|
|
277
300
|
|
|
301
|
+
Memory contract for SDD delegation:
|
|
302
|
+
|
|
303
|
+
- parent/orchestrator owns memory retrieval and passes selected context into subagent prompts;
|
|
304
|
+
- subagents should not independently search memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation;
|
|
305
|
+
- subagents should save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning when memory tools are available;
|
|
306
|
+
- in memory/hybrid mode, SDD artifacts use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, and `sdd/<change>/verify-report`.
|
|
307
|
+
|
|
278
308
|
## Package contents
|
|
279
309
|
|
|
280
310
|
| Path | Purpose |
|
|
281
311
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
|
282
312
|
| `extensions/gentle-ai.ts` | Injects identity, installs assets, registers commands, applies model config, and protects shell execution. |
|
|
313
|
+
| `extensions/startup-banner.ts` | Shows the rose startup intro, compact runtime panel, and collaboration credit. |
|
|
283
314
|
| `extensions/sdd-init.ts` | Registers `/sdd-init` for OpenSpec initialization. |
|
|
284
315
|
| `extensions/skill-registry.ts` | Maintains `.atl/skill-registry.md` from project/user skills. |
|
|
285
316
|
| `assets/orchestrator.md` | Parent-session orchestration contract. |
|
|
@@ -302,6 +333,8 @@ Validate before publishing:
|
|
|
302
333
|
```bash
|
|
303
334
|
bun build extensions/skill-registry.ts --target=node --format=esm --outfile=/tmp/skill-registry.js
|
|
304
335
|
node --experimental-strip-types --check extensions/gentle-ai.ts
|
|
336
|
+
node --experimental-strip-types --check extensions/sdd-init.ts
|
|
337
|
+
node --experimental-strip-types --check extensions/startup-banner.ts
|
|
305
338
|
npm pack --dry-run
|
|
306
339
|
```
|
|
307
340
|
|
|
@@ -13,6 +13,12 @@ Use your assigned executor/phase skill for this SDD phase. For project/user skil
|
|
|
13
13
|
|
|
14
14
|
If Project Standards are missing, explicit fallback loading is allowed only as degraded self-healing. Report `skill_resolution` as `injected`, `fallback-registry`, `fallback-path`, or `none`; fallbacks mean the parent should inject compact rules next time.
|
|
15
15
|
|
|
16
|
+
## Memory Contract
|
|
17
|
+
|
|
18
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
19
|
+
|
|
20
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
## Before Writing Code
|
|
18
24
|
|
|
@@ -18,3 +18,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
18
18
|
- Preserve audit trail; never delete active artifacts silently.
|
|
19
19
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
20
20
|
- Return archived paths and any migration risks.
|
|
21
|
+
## Memory Contract
|
|
22
|
+
|
|
23
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
24
|
+
|
|
25
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
26
|
+
|
|
@@ -18,3 +18,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
18
18
|
- Keep design centered on `packages/coding-agent` unless scope explicitly expands.
|
|
19
19
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
20
20
|
- Return the SDD result contract.
|
|
21
|
+
## Memory Contract
|
|
22
|
+
|
|
23
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
24
|
+
|
|
25
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
26
|
+
|
|
@@ -18,3 +18,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
18
18
|
- Use OpenSpec artifacts and session context truthfully; persistent memory is optional and handled by separate packages.
|
|
19
19
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
20
20
|
- Keep output concise and return the SDD result contract.
|
|
21
|
+
## Memory Contract
|
|
22
|
+
|
|
23
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
24
|
+
|
|
25
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
26
|
+
|
|
@@ -20,3 +20,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
20
20
|
- Ensure `.atl/skill-registry.md` exists when skill registry data is available, or report that it is missing.
|
|
21
21
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
22
22
|
- Return the standard phase envelope with status, executive_summary, artifacts, next_recommended, risks, and skill_resolution.
|
|
23
|
+
## Memory Contract
|
|
24
|
+
|
|
25
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
26
|
+
|
|
27
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
28
|
+
|
|
@@ -19,3 +19,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
19
19
|
- Respect strict TDD when project testing capabilities are present.
|
|
20
20
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
21
21
|
- Return the standard phase envelope with status, executive_summary, artifacts, next_recommended, risks, and skill_resolution.
|
|
22
|
+
## Memory Contract
|
|
23
|
+
|
|
24
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
25
|
+
|
|
26
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
27
|
+
|
|
@@ -18,3 +18,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
18
18
|
- Include intent, scope, affected areas, risks, rollback, and success criteria.
|
|
19
19
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
20
20
|
- Persist planning output to OpenSpec artifacts; persistent memory is optional and handled by separate packages.
|
|
21
|
+
## Memory Contract
|
|
22
|
+
|
|
23
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
24
|
+
|
|
25
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
26
|
+
|
|
@@ -18,3 +18,9 @@ If Project Standards are missing, explicit fallback loading is allowed only as d
|
|
|
18
18
|
- Store deltas under `openspec/changes/{change}/specs/`.
|
|
19
19
|
- Do NOT launch child subagents. Parent/orchestrator owns delegation.
|
|
20
20
|
- Return exact artifact paths and risks.
|
|
21
|
+
## Memory Contract
|
|
22
|
+
|
|
23
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
24
|
+
|
|
25
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
26
|
+
|
|
@@ -13,6 +13,12 @@ Use your assigned executor/phase skill for this SDD phase. For project/user skil
|
|
|
13
13
|
|
|
14
14
|
If Project Standards are missing, explicit fallback loading is allowed only as degraded self-healing. Report `skill_resolution` as `injected`, `fallback-registry`, `fallback-path`, or `none`; fallbacks mean the parent should inject compact rules next time.
|
|
15
15
|
|
|
16
|
+
## Memory Contract
|
|
17
|
+
|
|
18
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
19
|
+
|
|
20
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
## Inputs
|
|
18
24
|
|
|
@@ -13,6 +13,12 @@ Use your assigned executor/phase skill for this SDD phase. For project/user skil
|
|
|
13
13
|
|
|
14
14
|
If Project Standards are missing, explicit fallback loading is allowed only as degraded self-healing. Report `skill_resolution` as `injected`, `fallback-registry`, `fallback-path`, or `none`; fallbacks mean the parent should inject compact rules next time.
|
|
15
15
|
|
|
16
|
+
## Memory Contract
|
|
17
|
+
|
|
18
|
+
The parent/orchestrator owns memory retrieval: use memory context passed in the prompt and do not independently search Engram/memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation.
|
|
19
|
+
|
|
20
|
+
When callable memory tools are available, save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning. In memory/hybrid mode, use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, or `sdd/<change>/verify-report`. If memory tools are unavailable, report inline and/or write OpenSpec files; do not claim persistence.
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
## Inputs
|
|
18
24
|
|
package/assets/orchestrator.md
CHANGED
|
@@ -34,9 +34,11 @@ el Gentleman is an ecosystem configurator and harness layer. After installation,
|
|
|
34
34
|
- User says "use sdd" / "hacelo con sdd": run the SDD flow.
|
|
35
35
|
- Parent session orchestrates; phase agents execute.
|
|
36
36
|
|
|
37
|
+
Delegation is not optional once complexity appears. If a task crosses the triggers below, use the smallest useful subagent workflow instead of continuing as a monolithic executor.
|
|
38
|
+
|
|
37
39
|
## Work Routing Ladder
|
|
38
40
|
|
|
39
|
-
Route work through the smallest harness that is safe.
|
|
41
|
+
Route work through the smallest harness that is safe. "Smallest" means minimal safe coordination, not zero delegation by default.
|
|
40
42
|
|
|
41
43
|
### 1. Inline Direct
|
|
42
44
|
|
|
@@ -49,7 +51,7 @@ Examples:
|
|
|
49
51
|
- focused verification over 1-3 files;
|
|
50
52
|
- bash for state, e.g. `git status` or `gh issue view`.
|
|
51
53
|
|
|
52
|
-
Do not add SDD ceremony. Do not delegate just to look sophisticated.
|
|
54
|
+
Do not add SDD ceremony. Do not delegate just to look sophisticated. But do not use this exception to avoid delegation after the task stops being small.
|
|
53
55
|
|
|
54
56
|
### 2. Simple Delegation
|
|
55
57
|
|
|
@@ -66,6 +68,14 @@ Examples:
|
|
|
66
68
|
|
|
67
69
|
Use `pi-subagents` when available. Prefer background/async for long exploration, implementation, tests, or review when the parent has independent work.
|
|
68
70
|
|
|
71
|
+
Default balanced pattern for bounded implementation:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
parent clarifies and checks git → scout/context-builder when context-heavy → one worker writes → fresh reviewer audits diff → parent validates and reports
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Do not make every task SDD. Do make non-trivial tasks multi-agent at the narrowest useful point.
|
|
78
|
+
|
|
69
79
|
### 3. SDD
|
|
70
80
|
|
|
71
81
|
Use SDD for large, ambiguous, architectural, product-facing, multi-area, or high-review-risk work.
|
|
@@ -85,15 +95,58 @@ If the request is large enough for SDD, do not jump directly to implementation.
|
|
|
85
95
|
|
|
86
96
|
Core question: does this inflate parent context without need?
|
|
87
97
|
|
|
88
|
-
| Action
|
|
89
|
-
|
|
|
90
|
-
| Read to decide/verify 1-3 files
|
|
91
|
-
| Read to explore/understand 4+ files
|
|
92
|
-
| Read as preparation for multi-file writing
|
|
93
|
-
| Write atomic one-file mechanical change
|
|
94
|
-
| Write with analysis across multiple files
|
|
95
|
-
| Bash for state, e.g. git status
|
|
96
|
-
| Bash for execution, e.g. tests/builds
|
|
98
|
+
| Action | Inline | Delegate |
|
|
99
|
+
| ---------------------------------------------------- | -----: | ----------------------: |
|
|
100
|
+
| Read to decide/verify 1-3 files | yes | no |
|
|
101
|
+
| Read to explore/understand 4+ files | no | yes |
|
|
102
|
+
| Read as preparation for multi-file writing | no | yes |
|
|
103
|
+
| Write atomic one-file mechanical change | yes | no |
|
|
104
|
+
| Write with analysis across multiple files | no | yes |
|
|
105
|
+
| Bash for state, e.g. git status | yes | no |
|
|
106
|
+
| Bash for execution, e.g. tests/builds | no | yes |
|
|
107
|
+
| Commit, push, or open PR after code changes | no | yes, fresh review first |
|
|
108
|
+
| Recover from wrong cwd/worktree/git/tooling incident | no | yes, fresh audit first |
|
|
109
|
+
|
|
110
|
+
### Mandatory Delegation Triggers
|
|
111
|
+
|
|
112
|
+
These are parent-orchestrator stop rules. Once any trigger fires, the parent must either delegate or explicitly tell the user why delegation would be unsafe or wasteful for this exact case. Do not inject these as child-agent permission to spawn subagents; children receive concrete role work and must not orchestrate.
|
|
113
|
+
|
|
114
|
+
1. **4-file rule**: if understanding requires reading 4+ files, launch `scout` or `context-builder` with fresh context and a narrow mapping task.
|
|
115
|
+
2. **Multi-file write rule**: if implementation will touch 2+ non-trivial files, use one `worker` or keep writing inline only if a fresh reviewer will audit before completion.
|
|
116
|
+
3. **PR rule**: before commit/push/PR for code changes, run a fresh-context `reviewer` unless the diff is a trivial docs/text-only change.
|
|
117
|
+
4. **Incident rule**: after wrong `cwd`, accidental repo/worktree mutation, failed merge recovery, confusing test command, or environment workaround, stop and run a fresh audit reviewer.
|
|
118
|
+
5. **Long-session rule**: if accumulating work is no longer clearly local — roughly 20 tool calls, 5 exploratory file reads, or 2 non-mechanical edits without delegation — pause and choose `scout`, `worker`, or `reviewer` instead of silently continuing monolithically.
|
|
119
|
+
6. **Fresh review rule**: use `context: "fresh"` for adversarial review of diffs, conflicts, PR readiness, and incident audits. Use forked context for continuity-oriented `worker`/`oracle` tasks.
|
|
120
|
+
|
|
121
|
+
### Cost and Context Balance
|
|
122
|
+
|
|
123
|
+
Prefer delegation when fresh context improves correctness more than token savings:
|
|
124
|
+
|
|
125
|
+
- Use `scout`/`context-builder` to compress broad repo exploration into a short handoff instead of loading many files into the parent.
|
|
126
|
+
- Use a single `worker` for one writer thread; do not run parallel writers unless isolated worktrees are explicitly approved.
|
|
127
|
+
- Use fresh `reviewer` agents after implementation, conflict resolution, or incidents because their value is independence from the parent's assumptions.
|
|
128
|
+
- Use `outputMode: "file-only"` for large child reports and summarize only decisions, blockers, and paths in the parent thread.
|
|
129
|
+
- Avoid delegation for truly local one-file fixes, quick state checks, and already-understood mechanical edits.
|
|
130
|
+
|
|
131
|
+
### Canonical Lightweight Workflows
|
|
132
|
+
|
|
133
|
+
Bugfix with unfamiliar flow:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
parent git/status + clarify → scout fresh maps flow/files → parent decides → worker fork implements + tests → reviewer fresh audits diff → parent validates
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Conflict or dependency-marker cleanup:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
parent reproduces/checks conflict → parent or worker resolves → reviewer fresh checks markers, package/lock consistency, and repo cleanliness → parent reports/pushes
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
After tooling/worktree incident:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
stop writes → parent captures git status → reviewer fresh audits affected repos/worktrees with no edits → parent applies only confirmed recovery steps
|
|
149
|
+
```
|
|
97
150
|
|
|
98
151
|
## SDD Workflow
|
|
99
152
|
|
|
@@ -141,6 +194,16 @@ This package does not provide persistent memory by itself.
|
|
|
141
194
|
- If a separate memory package is installed and callable, memory/hybrid flows may be used.
|
|
142
195
|
- Never claim memory exists because Gentle AI is installed.
|
|
143
196
|
|
|
197
|
+
## Memory Contract
|
|
198
|
+
|
|
199
|
+
When Engram or another callable memory package is available, the parent owns memory retrieval and subagents own write-back for significant findings.
|
|
200
|
+
|
|
201
|
+
- Read context: parent/orchestrator searches memory, selects relevant observations, and passes them into subagent prompts. Subagents should not independently search memory during normal runtime unless the parent explicitly instructs them to retrieve a specific artifact or observation.
|
|
202
|
+
- Write context: subagents MUST save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts to memory before returning when memory tools are available.
|
|
203
|
+
- Prompt forwarding: when delegating, add a concrete instruction such as: `If you make important discoveries, decisions, or fix bugs, save them to Engram via the available memory save tool with project: '<project>' before returning.`
|
|
204
|
+
- SDD artifact keys: in memory/hybrid mode, phase artifacts should use stable topic keys such as `sdd/<change>/proposal`, `sdd/<change>/spec`, `sdd/<change>/design`, `sdd/<change>/tasks`, `sdd/<change>/apply-progress`, and `sdd/<change>/verify-report`.
|
|
205
|
+
- If memory tools are unavailable, do not pretend persistence exists; return artifacts inline and/or write OpenSpec files.
|
|
206
|
+
|
|
144
207
|
## Execution Mode
|
|
145
208
|
|
|
146
209
|
For substantial SDD flows, choose or ask once per change:
|
package/extensions/gentle-ai.ts
CHANGED
|
@@ -8,18 +8,12 @@ import {
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { VERSION } from "@earendil-works/pi-coding-agent";
|
|
12
11
|
import type {
|
|
13
12
|
ExtensionAPI,
|
|
14
13
|
ExtensionContext,
|
|
15
|
-
Theme,
|
|
16
14
|
ToolCallEventResult,
|
|
17
15
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import {
|
|
19
|
-
matchesKey,
|
|
20
|
-
truncateToWidth,
|
|
21
|
-
visibleWidth,
|
|
22
|
-
} from "@earendil-works/pi-tui";
|
|
16
|
+
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
23
17
|
|
|
24
18
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
25
19
|
const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
@@ -49,139 +43,6 @@ const NEUTRAL_PERSONA_PROMPT = `Persona:
|
|
|
49
43
|
- Push back when the user asks for code without enough context or understanding.
|
|
50
44
|
- Correct errors directly, explain why, and show the better path.`;
|
|
51
45
|
|
|
52
|
-
const ROSE_LOGO_LINES = [
|
|
53
|
-
" ⣠⣾⣷⣶⣦⣤⣤⣄⣠⣄⣀ ⢀⣀⣀",
|
|
54
|
-
" ⢀⣴⣿⣿⠿⣋⣭⣭⣯⣭⣍⣭⣿⣟⠛⠛⠿⠿⣿⣷⣄",
|
|
55
|
-
" ⢀⣴⣾⡟⢻⣿⡟⠁⣼⣿⠏⣵⢻⣿⣻⣿⣿⢿⡻⣿⣿⣶⡌⢿⣿⣷⣦⣤⡄",
|
|
56
|
-
" ⣤⣶⣾⣿⣿⠏ ⠈⢿⣄ ⢹⣏⠠⠟⣾⣿⣿⣿⣿⣿⠷⣏⣼⠟⢡⣿⡟⠋⢻⣿⣿⡄",
|
|
57
|
-
" ⠈⣿⣿⣿⣿⡆ ⣽⢧⡘⠈⠳⣦⣍⠛⠛⢦⣉⣴⣛⣫⣭⣴⡟⠋ ⣾⣿⣿⡿",
|
|
58
|
-
" ⢀⠹⣿⣿⣿⣷⣤⡄ ⠋ ⠙⢆ ⣠⠴⠟⠛⣛⣛⣛⠟⠋⠁⠺⡇ ⣀⣴⣿⣿⡟⠁",
|
|
59
|
-
" ⠈⣀⠈⠛⠷⠿⣿⣿⣷⣤⣀ ⢠⠋ ⠈⠉⠉ ⣠⣴⣥⠾⠛⠉⣰⣿⣷",
|
|
60
|
-
" ⠹⣯⣝⠛⠛⠷⢶⣤⣤⣀ ⢀⡠⠖⠋⠉⢉⣀⣀⣴⣾⣿⠿⠟⠃ ⠠⠦",
|
|
61
|
-
"⠁ ⠖ ⠘⠻⢿⣦⣄⡀ ⠉⠛⢦⠠⢊⠤⠴⢒⣛⣛⣩⣽⡿⠟⠁⢀⡀",
|
|
62
|
-
"⠲⠶⣦⠴⠶⠶⠶⠶⡶⠶⢶⣤⣄⡀⠨⠭⠽⠟⣓⢦⣀⠈⢇⡥⠖⠛⠋⠉⠉⠉ ⠈ ⢠⡤",
|
|
63
|
-
" ⠈⢷ ⠐⠂⢤⣽⣄ ⠰⡎⠙⠳⣄⡀ ⠈⢣⠘⢦⠋⣀⡬⠟⠛⠛⠉⢀⣀⣀⣠⡤⠄⠃",
|
|
64
|
-
" ⠈⢳⣀⡒⠉⠉⣉⠙⡲⣽⣄ ⣏⠳⡄ ⠘⡇ ⡾⠁ ⢀⡤⠖⣻⣿⡏⢡⡎ ⠰⠄",
|
|
65
|
-
" ⠛⠻⢦⣄⣉⡁⣀⣀⣈⣙⣺⣌⡇⢠⢀⡇⡾ ⣴⣿⡷⠊ ⢲⣠⠟",
|
|
66
|
-
" ⠈⠉ ⠈⠳⡄⣸⢱⠇⢀⣰⣯⣭⣥⠭⠾⠛⠃",
|
|
67
|
-
" ⡷⠡⡯⢖⠉ ⢠⠤",
|
|
68
|
-
" ⡠⢊⡴⠤⠂⠃ ⠒",
|
|
69
|
-
" ⢀⡴⢪⠔⣉⠔⠋",
|
|
70
|
-
" ⠐⠈",
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const ROSE_FADE_STEPS = 12;
|
|
74
|
-
const ROSE_FADE_INTERVAL_MS = 45;
|
|
75
|
-
const ROSE_INTRO_HOLD_MS = 180;
|
|
76
|
-
|
|
77
|
-
function rgb(r: number, g: number, b: number, text: string): string {
|
|
78
|
-
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function italic(text: string): string {
|
|
82
|
-
return `\x1b[3m${text}\x1b[23m`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function bold(text: string): string {
|
|
86
|
-
return `\x1b[1m${text}\x1b[22m`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function centerLine(line: string, width: number): string {
|
|
90
|
-
const clipped = truncateToWidth(line, width, "");
|
|
91
|
-
const padding = Math.max(0, width - visibleWidth(clipped));
|
|
92
|
-
return `${" ".repeat(Math.floor(padding / 2))}${clipped}`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function pinkFade(text: string, frame: number): string {
|
|
96
|
-
const progress = Math.max(0, Math.min(1, frame / ROSE_FADE_STEPS));
|
|
97
|
-
const eased = 1 - (1 - progress) ** 3;
|
|
98
|
-
const r = Math.round(72 + (255 - 72) * eased);
|
|
99
|
-
const g = Math.round(38 + (122 - 38) * eased);
|
|
100
|
-
const b = Math.round(58 + (198 - 58) * eased);
|
|
101
|
-
return rgb(r, g, b, text);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function buildGentlemanTitle(width: number, frame: number): string[] {
|
|
105
|
-
const title = "✧ 𝓮𝓵 𝓖𝓮𝓷𝓽𝓵𝓮𝓶𝓪𝓷 ✧";
|
|
106
|
-
const version = `━━ v${VERSION} ━━`;
|
|
107
|
-
return [
|
|
108
|
-
pinkFade(bold(italic(centerLine(title, width))), frame),
|
|
109
|
-
pinkFade(italic(centerLine(version, width)), frame),
|
|
110
|
-
];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function buildRoseHeader(
|
|
114
|
-
_theme: Theme,
|
|
115
|
-
width: number,
|
|
116
|
-
frame: number,
|
|
117
|
-
): string[] {
|
|
118
|
-
return [
|
|
119
|
-
"",
|
|
120
|
-
...ROSE_LOGO_LINES.map((line) => pinkFade(centerLine(line, width), frame)),
|
|
121
|
-
...buildGentlemanTitle(width, frame),
|
|
122
|
-
"",
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function installRoseHeader(ctx: ExtensionContext): void {
|
|
127
|
-
if (!ctx.hasUI) return;
|
|
128
|
-
|
|
129
|
-
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
130
|
-
|
|
131
|
-
let closeIntro: (() => void) | undefined;
|
|
132
|
-
const closeIntroSafely = () => {
|
|
133
|
-
const close = closeIntro;
|
|
134
|
-
closeIntro = undefined;
|
|
135
|
-
try {
|
|
136
|
-
close?.();
|
|
137
|
-
} catch {
|
|
138
|
-
// Ignore shutdown races during startup/reload.
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
void ctx.ui
|
|
143
|
-
.custom((_tui, _theme, _keybindings, done) => {
|
|
144
|
-
closeIntro = () => done(undefined);
|
|
145
|
-
return {
|
|
146
|
-
render: () => [""],
|
|
147
|
-
invalidate: () => {},
|
|
148
|
-
handleInput: () => {},
|
|
149
|
-
};
|
|
150
|
-
})
|
|
151
|
-
.catch(() => {
|
|
152
|
-
closeIntro = undefined;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const state: { frame: number; timer?: NodeJS.Timeout } = { frame: 0 };
|
|
156
|
-
ctx.ui.setHeader((tui, theme) => {
|
|
157
|
-
if (state.timer) clearInterval(state.timer);
|
|
158
|
-
state.timer = setInterval(() => {
|
|
159
|
-
state.frame += 1;
|
|
160
|
-
tui.requestRender();
|
|
161
|
-
if (
|
|
162
|
-
state.frame <
|
|
163
|
-
ROSE_FADE_STEPS + Math.ceil(ROSE_INTRO_HOLD_MS / ROSE_FADE_INTERVAL_MS)
|
|
164
|
-
) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (state.timer) clearInterval(state.timer);
|
|
168
|
-
state.timer = undefined;
|
|
169
|
-
closeIntroSafely();
|
|
170
|
-
}, ROSE_FADE_INTERVAL_MS);
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
render(width: number): string[] {
|
|
174
|
-
return buildRoseHeader(theme, width, state.frame);
|
|
175
|
-
},
|
|
176
|
-
invalidate() {
|
|
177
|
-
if (state.timer) clearInterval(state.timer);
|
|
178
|
-
state.timer = undefined;
|
|
179
|
-
closeIntroSafely();
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
46
|
function buildGentlePrompt(persona: PersonaMode): string {
|
|
186
47
|
const personaPrompt =
|
|
187
48
|
persona === "neutral" ? NEUTRAL_PERSONA_PROMPT : GENTLEMAN_PERSONA_PROMPT;
|
|
@@ -911,7 +772,6 @@ async function handlePersonaCommand(ctx: ExtensionContext): Promise<void> {
|
|
|
911
772
|
|
|
912
773
|
export default function gentleAi(pi: ExtensionAPI): void {
|
|
913
774
|
pi.on("session_start", (_event, ctx) => {
|
|
914
|
-
installRoseHeader(ctx);
|
|
915
775
|
const result = installSddAssets(ctx.cwd, false);
|
|
916
776
|
const modelResult = applyModelConfig(ctx.cwd, readModelConfig(ctx.cwd));
|
|
917
777
|
if (
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
readdirSync,
|
|
7
|
-
statSync,
|
|
8
|
-
watch,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
} from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
|
|
11
3
|
import { homedir } from "node:os";
|
|
12
4
|
import { basename, join, relative } from "node:path";
|
|
13
5
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -77,11 +69,7 @@ function findSkillFiles(root: string): string[] {
|
|
|
77
69
|
return out;
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
function parseFrontmatter(source: string): {
|
|
81
|
-
name?: string;
|
|
82
|
-
description?: string;
|
|
83
|
-
body: string;
|
|
84
|
-
} {
|
|
72
|
+
function parseFrontmatter(source: string): { name?: string; description?: string; body: string } {
|
|
85
73
|
if (!source.startsWith("---\n")) return { body: source };
|
|
86
74
|
const end = source.indexOf("\n---", 4);
|
|
87
75
|
if (end === -1) return { body: source };
|
|
@@ -93,10 +81,7 @@ function parseFrontmatter(source: string): {
|
|
|
93
81
|
if (!m) continue;
|
|
94
82
|
const key = m[1];
|
|
95
83
|
let value = m[2].trim();
|
|
96
|
-
if (
|
|
97
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
98
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
99
|
-
) {
|
|
84
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
100
85
|
value = value.slice(1, -1);
|
|
101
86
|
}
|
|
102
87
|
if (key === "name") out.name = value;
|
|
@@ -123,10 +108,7 @@ function extractCompactRulesSection(body: string): string[] {
|
|
|
123
108
|
return rules;
|
|
124
109
|
}
|
|
125
110
|
|
|
126
|
-
function deriveSkillName(
|
|
127
|
-
file: string,
|
|
128
|
-
frontmatterName: string | undefined,
|
|
129
|
-
): string {
|
|
111
|
+
function deriveSkillName(file: string, frontmatterName: string | undefined): string {
|
|
130
112
|
if (frontmatterName) return frontmatterName;
|
|
131
113
|
return basename(join(file, ".."));
|
|
132
114
|
}
|
|
@@ -165,9 +147,7 @@ function loadSkill(file: string): SkillEntry | undefined {
|
|
|
165
147
|
rules:
|
|
166
148
|
rules.length > 0
|
|
167
149
|
? rules
|
|
168
|
-
: [
|
|
169
|
-
"No compact rules declared; delegators should load the full skill file before direct work, or pass an explicit fallback path only when Project Standards cannot be injected.",
|
|
170
|
-
],
|
|
150
|
+
: ["No compact rules declared; delegators should load the full skill file before direct work, or pass an explicit fallback path only when Project Standards cannot be injected."],
|
|
171
151
|
};
|
|
172
152
|
}
|
|
173
153
|
|
|
@@ -202,19 +182,13 @@ function fingerprint(files: string[]): string {
|
|
|
202
182
|
return createHash("sha1").update(lines.join("\n")).digest("hex");
|
|
203
183
|
}
|
|
204
184
|
|
|
205
|
-
function renderRegistry(
|
|
206
|
-
cwd: string,
|
|
207
|
-
sources: string[],
|
|
208
|
-
entries: SkillEntry[],
|
|
209
|
-
): string {
|
|
185
|
+
function renderRegistry(cwd: string, sources: string[], entries: SkillEntry[]): string {
|
|
210
186
|
const projectName = basename(cwd);
|
|
211
187
|
const today = new Date().toISOString().slice(0, 10);
|
|
212
188
|
const lines: string[] = [];
|
|
213
189
|
lines.push(`# Skill Registry — ${projectName}`);
|
|
214
190
|
lines.push("");
|
|
215
|
-
lines.push(
|
|
216
|
-
"<!-- Auto-generated by .pi/extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->",
|
|
217
|
-
);
|
|
191
|
+
lines.push("<!-- Auto-generated by .pi/extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->");
|
|
218
192
|
lines.push("");
|
|
219
193
|
lines.push(`Last updated: ${today}`);
|
|
220
194
|
lines.push("");
|
|
@@ -226,13 +200,9 @@ function renderRegistry(
|
|
|
226
200
|
lines.push("");
|
|
227
201
|
lines.push("## Contract");
|
|
228
202
|
lines.push("");
|
|
229
|
-
lines.push(
|
|
230
|
-
"**Delegator use only.** Any agent that launches subagents reads this registry to resolve compact rules, then injects matching rule text into subagent prompts under `## Project Standards (auto-resolved)`.",
|
|
231
|
-
);
|
|
203
|
+
lines.push("**Delegator use only.** Any agent that launches subagents reads this registry to resolve compact rules, then injects matching rule text into subagent prompts under `## Project Standards (auto-resolved)`.");
|
|
232
204
|
lines.push("");
|
|
233
|
-
lines.push(
|
|
234
|
-
"Subagents still read their assigned executor/phase skill. During normal runtime, they do **not** independently discover or load additional project/user `SKILL.md` files or this registry; project/user rules arrive pre-digested. Explicit fallback loading is degraded self-healing and must be reported in `skill_resolution` as `fallback-registry` or `fallback-path`.",
|
|
235
|
-
);
|
|
205
|
+
lines.push("Subagents still read their assigned executor/phase skill. During normal runtime, they do **not** independently discover or load additional project/user `SKILL.md` files or this registry; project/user rules arrive pre-digested. Explicit fallback loading is degraded self-healing and must be reported in `skill_resolution` as `fallback-registry` or `fallback-path`.");
|
|
236
206
|
lines.push("");
|
|
237
207
|
lines.push(SECTION_MARKER);
|
|
238
208
|
lines.push("");
|
|
@@ -269,20 +239,12 @@ function ensureAtlIgnored(cwd: string): void {
|
|
|
269
239
|
.some((line) => line === ".atl" || line === ATL_IGNORE_ENTRY);
|
|
270
240
|
if (hasAtlIgnore) return;
|
|
271
241
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
272
|
-
const header = existing.includes("# Local Pi runtime state")
|
|
273
|
-
|
|
274
|
-
: "# Local Pi runtime state\n";
|
|
275
|
-
writeFileSync(
|
|
276
|
-
gitignorePath,
|
|
277
|
-
`${existing}${prefix}${header}${ATL_IGNORE_ENTRY}\n`,
|
|
278
|
-
);
|
|
242
|
+
const header = existing.includes("# Local Pi runtime state") ? "" : "# Local Pi runtime state\n";
|
|
243
|
+
writeFileSync(gitignorePath, `${existing}${prefix}${header}${ATL_IGNORE_ENTRY}\n`);
|
|
279
244
|
}
|
|
280
245
|
|
|
281
246
|
function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
282
|
-
const existingDirs = uniqueExistingDirs([
|
|
283
|
-
...projectSkillDirs(cwd),
|
|
284
|
-
...userSkillDirs(),
|
|
285
|
-
]);
|
|
247
|
+
const existingDirs = uniqueExistingDirs([...projectSkillDirs(cwd), ...userSkillDirs()]);
|
|
286
248
|
const files = existingDirs.flatMap(findSkillFiles).sort();
|
|
287
249
|
const cachePath = join(cwd, CACHE_REL_PATH);
|
|
288
250
|
const registryPath = join(cwd, REGISTRY_REL_PATH);
|
|
@@ -290,9 +252,7 @@ function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
|
290
252
|
let cached: string | undefined;
|
|
291
253
|
if (existsSync(cachePath)) {
|
|
292
254
|
try {
|
|
293
|
-
cached = (
|
|
294
|
-
JSON.parse(readFileSync(cachePath, "utf8")) as { fingerprint?: string }
|
|
295
|
-
).fingerprint;
|
|
255
|
+
cached = (JSON.parse(readFileSync(cachePath, "utf8")) as { fingerprint?: string }).fingerprint;
|
|
296
256
|
} catch {
|
|
297
257
|
cached = undefined;
|
|
298
258
|
}
|
|
@@ -312,25 +272,15 @@ function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
|
312
272
|
mkdirSync(join(cwd, ".atl"), { recursive: true });
|
|
313
273
|
writeFileSync(registryPath, md);
|
|
314
274
|
writeFileSync(cachePath, JSON.stringify({ fingerprint: fp }, null, 2));
|
|
315
|
-
return {
|
|
316
|
-
regenerated: true,
|
|
317
|
-
skillCount: deduped.length,
|
|
318
|
-
reason: force ? "forced" : "fingerprint-changed",
|
|
319
|
-
};
|
|
275
|
+
return { regenerated: true, skillCount: deduped.length, reason: force ? "forced" : "fingerprint-changed" };
|
|
320
276
|
}
|
|
321
277
|
|
|
322
278
|
const watchedCwds = new Set<string>();
|
|
323
279
|
|
|
324
|
-
function startSkillRegistryWatcher(
|
|
325
|
-
cwd: string,
|
|
326
|
-
notify: (message: string) => void,
|
|
327
|
-
): void {
|
|
280
|
+
function startSkillRegistryWatcher(cwd: string, notify: (message: string) => void): void {
|
|
328
281
|
if (watchedCwds.has(cwd)) return;
|
|
329
282
|
watchedCwds.add(cwd);
|
|
330
|
-
const dirs = uniqueExistingDirs([
|
|
331
|
-
...projectSkillDirs(cwd),
|
|
332
|
-
...userSkillDirs(),
|
|
333
|
-
]);
|
|
283
|
+
const dirs = uniqueExistingDirs([...projectSkillDirs(cwd), ...userSkillDirs()]);
|
|
334
284
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
335
285
|
const refresh = () => {
|
|
336
286
|
if (timer) clearTimeout(timer);
|
|
@@ -360,10 +310,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
360
310
|
ensureAtlIgnored(ctx.cwd);
|
|
361
311
|
const result = regenerateRegistry(ctx.cwd, false);
|
|
362
312
|
if (result.regenerated && ctx.hasUI) {
|
|
363
|
-
ctx.ui.notify(
|
|
364
|
-
`Skill registry refreshed (${result.skillCount} skills)`,
|
|
365
|
-
"info",
|
|
366
|
-
);
|
|
313
|
+
ctx.ui.notify(`Skill registry refreshed (${result.skillCount} skills)`, "info");
|
|
367
314
|
}
|
|
368
315
|
startSkillRegistryWatcher(ctx.cwd, (message) => {
|
|
369
316
|
if (ctx.hasUI) ctx.ui.notify(message, "info");
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { VERSION } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import { exec } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
const TEXT_LOGO = [
|
|
13
|
+
" ▄▄▄▀▀▀▀▀██ ▄▄▀▄▄ ▄▄█▀▀▀██ ▀▀█▄ ▄▄▄",
|
|
14
|
+
" ▄▄█▀▀▒▒▒▒▒▄▄█▀▒ ▄██ ▄▄█▀█▄█▀▒▒ ▄█▀▀ ▒▒▒▄█▀▀▒ ▄██▒ ▄█▀▒▒▒",
|
|
15
|
+
" ▄▄██▀▒▒▒▒▒▄▄▄▀▀▒▒▒▒ ▄▄▄ ▀▀▀▀██▀▀▀▀▀███▀█▄▀▀▒▒▒▒ ██▒▒▒▒▄▄█▀▒▒▒▒▄▄█▀▀▄██▀▒▒▒",
|
|
16
|
+
" ▄██▀▒▒▒▒ ▒▒▄▄█ ▄▄▄▀██ ▄▄▄▀▀▀▄ ▄██▀▒▒▒▒▄██▀▀▀▒▄▄███ ▒▒ ▄███▄▄▄█▀▀▀▒▒▄██▀▒▒▒",
|
|
17
|
+
" ██▀▒▒▒ ▄▄▄███▀▄██▀▀▀▄▄██▀▀▄█▀▄▄██▀▒▒▒▄▄██▀▒▒▄██▀▀▀▄▄▀▀▀▀▀▀▀▀▀ ▄█▀▀▒▒▒▒▒▒▒▒▒▄██▒▒▒▒",
|
|
18
|
+
" ▀█▄▄▄▄▄▀▀▀█▄▄███▄▒▀▀▀▀▀▀▒▀▀▒▒▀▀▀▀▒██▄▄▀▀▀ ▀█▄▀▀▀ ▀▀▀▀▀▒▒▒▒▒▒▒▒▒▒▄██▀▒▒▒ ███▒▒",
|
|
19
|
+
" ▒▄▄▄█▀▀▀█▄█▀▀▒▒▒▒ ▒▒▒▒▒▒ ▒▒ ▒▒▒▒ ▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▀▀▀▒▒▒ ▒▒▒",
|
|
20
|
+
" ▄▄▀▀ ▒▒▒▒▄██▀▒▒▒▒ ▒▒▒",
|
|
21
|
+
" ▄█ ▒▒▒▄▄██▀▀▒▒▒▒",
|
|
22
|
+
" ▀▀▀▀▀▀▒▒▒▒▒▒",
|
|
23
|
+
" ▒▒▒▒▒▒",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const ROSE_LARGE_RAW = [
|
|
27
|
+
" ⣠⣾⣷⣶⣦⣤⣤⣄⣠⣄⣀ ⢀⣀⣀",
|
|
28
|
+
" ⢀⣴⣿⣿⠿⣋⣭⣭⣯⣭⣍⣭⣿⣟⠛⠛⠿⣿⣷⣄",
|
|
29
|
+
" ⢀⣴⣾⡟⢻⣿⡟⠁⣼⣿⠏⣵⢻⣿⣻⣿⣿⢿⡻⣿⣿⣶⡌⢿⣿⣷⣦⣤⡄",
|
|
30
|
+
" ⣤⣶⣾⣿⣿⠏ ⠈⢿⣄ ⢹⣏⠠⠟⣾⣿⣿⣿⣿⣿⠷⣏⣼⠟⢡⣿⡟⠋⢻⣿⣿⡄",
|
|
31
|
+
" ⠈⣿⣿⣿⣿⡆ ⣽⢧⡘⠈⠳⣦⣍⠛⠛⢦⣉⣴⣛⣫⣭⣴⡟⠋ ⣾⣿⣿⡿",
|
|
32
|
+
" ⢀⠹⣿⣿⣿⣷⣤⡄ ⠋ ⠙⢆ ⣠⠴⠟⠛⣛⣛⣛⠟⠋⠁⠺⡇ ⣀⣴⣿⣿⡟⠁",
|
|
33
|
+
" ⠈⣀⠈⠛⠷⠿⣿⣿⣷⣤⣀ ⢠⠋ ⠈⠉⠉ ⣠⣴⣥⠾⠛⠉⣰⣿⣷",
|
|
34
|
+
" ⠹⣯⣝⠛⠛⠷⢶⣤⣤⣀ ⢀⡠⠖⠋⠉⢉⣀⣀⣴⣾⣿⠿⠟⠃",
|
|
35
|
+
" ⠘⠻⢿⣦⣄⡀ ⠉⠛⢦⠠⢊⠤⠴⢒⣛⣛⣩⣽⡿⠟⠁",
|
|
36
|
+
" ⠶⢶⣤⣄⡀⠨⠭⠽⠟⣓⢦⣀⠈⢇⡥⠖⠛⠋⠉⠉",
|
|
37
|
+
" ⠈⢷ ⠐⠂⢤⣽⣄ ⠰⡎⠙⠳⣄⡀ ⠈⢣⠘⢦⠋",
|
|
38
|
+
" ⠈⢳⣀⡒⠉⠉⣉⠙⡲⣽⣄ ⣏⠳⡄ ⠘⡇ ⡾⠁",
|
|
39
|
+
" ⠛⠻⢦⣄⣉⡁⣀⣀⣈⣙⣺⣌⡇⢠⢀⡇⡾",
|
|
40
|
+
" ⠈⠉ ⠈⠳⡄⣸⢱⠇",
|
|
41
|
+
" ⡷⠡⡯⢖⠉",
|
|
42
|
+
" ⢀⡴⢪⠔⣉⠔⠋",
|
|
43
|
+
" ⠐⠈",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function rgb(r: number, g: number, b: number, text: string): string {
|
|
47
|
+
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeAscii(lines: string[]): string[] {
|
|
51
|
+
const trimmed = lines.map((l) => l.replace(/\s+$/g, ""));
|
|
52
|
+
const nonEmpty = trimmed.filter((l) => l.trim().length > 0);
|
|
53
|
+
const minLead = nonEmpty.length
|
|
54
|
+
? Math.min(...nonEmpty.map((l) => (l.match(/^\s*/) || [""])[0].length))
|
|
55
|
+
: 0;
|
|
56
|
+
return trimmed.map((l) => (l.length >= minLead ? l.slice(minLead) : l));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function padLines(lines: string[]): { lines: string[]; width: number } {
|
|
60
|
+
const width = Math.max(...lines.map((l) => l.length), 0);
|
|
61
|
+
return { lines: lines.map((l) => l.padEnd(width)), width };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type CellType =
|
|
65
|
+
| "banner"
|
|
66
|
+
| "rose"
|
|
67
|
+
| "label"
|
|
68
|
+
| "value"
|
|
69
|
+
| "dim"
|
|
70
|
+
| "accent"
|
|
71
|
+
| "none";
|
|
72
|
+
type LayoutCell = { char: string; type: CellType };
|
|
73
|
+
|
|
74
|
+
class LayoutBuilder {
|
|
75
|
+
lines: LayoutCell[][] = [];
|
|
76
|
+
|
|
77
|
+
addRow() {
|
|
78
|
+
this.lines.push([]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
add(type: CellType, text: string) {
|
|
82
|
+
const row = this.lines[this.lines.length - 1];
|
|
83
|
+
for (const char of text) row.push({ char, type });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
center(width: number) {
|
|
87
|
+
const row = this.lines[this.lines.length - 1];
|
|
88
|
+
const pad = Math.max(0, Math.floor((width - row.length) / 2));
|
|
89
|
+
const prefix = Array.from({ length: pad }, () => ({
|
|
90
|
+
char: " ",
|
|
91
|
+
type: "none" as const,
|
|
92
|
+
}));
|
|
93
|
+
this.lines[this.lines.length - 1] = prefix.concat(row);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default function (pi: ExtensionAPI) {
|
|
98
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
99
|
+
if (!ctx.hasUI) return;
|
|
100
|
+
|
|
101
|
+
// Si se está ejecutando un comando de CLI como "pi update" o "pi install", no mostramos la intro animada.
|
|
102
|
+
const isCLICommand =
|
|
103
|
+
process.argv.length > 2 &&
|
|
104
|
+
!process.argv.every((arg) => arg.startsWith("-") || arg.endsWith(".ts"));
|
|
105
|
+
if (isCLICommand) return;
|
|
106
|
+
|
|
107
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
108
|
+
|
|
109
|
+
let closeIntro: (() => void) | null = null;
|
|
110
|
+
const closeIntroSafely = () => {
|
|
111
|
+
if (!closeIntro) return;
|
|
112
|
+
const fn = closeIntro;
|
|
113
|
+
closeIntro = null;
|
|
114
|
+
try {
|
|
115
|
+
fn();
|
|
116
|
+
} catch {}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
void ctx.ui
|
|
120
|
+
.custom((_tui, _theme, _keybindings, done) => {
|
|
121
|
+
closeIntro = () => done(undefined);
|
|
122
|
+
return {
|
|
123
|
+
render: () => [""],
|
|
124
|
+
invalidate: () => {},
|
|
125
|
+
handleInput: () => {},
|
|
126
|
+
};
|
|
127
|
+
})
|
|
128
|
+
.catch(() => {
|
|
129
|
+
closeIntro = null;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const roseBase = padLines(normalizeAscii(ROSE_LARGE_RAW));
|
|
133
|
+
const logoBase = padLines(TEXT_LOGO);
|
|
134
|
+
|
|
135
|
+
let gitBranch = "Not a git repo";
|
|
136
|
+
let mcpServersCount = 0;
|
|
137
|
+
let extensionsCount = 0;
|
|
138
|
+
let packagesCount = 0;
|
|
139
|
+
|
|
140
|
+
const allCommands = pi.getCommands();
|
|
141
|
+
const skills = allCommands.filter((c) => c.source === "skill");
|
|
142
|
+
const allTools = pi.getAllTools();
|
|
143
|
+
const customTools = allTools.filter(
|
|
144
|
+
(t) => !["builtin", "sdk"].includes(t.sourceInfo.source),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
execAsync(`git -C "${ctx.cwd}" branch --show-current`)
|
|
149
|
+
.then(({ stdout }) => {
|
|
150
|
+
const b = stdout.trim();
|
|
151
|
+
gitBranch = b ? `On branch ${b}` : "Detached HEAD";
|
|
152
|
+
})
|
|
153
|
+
.catch(() => {});
|
|
154
|
+
}, 100);
|
|
155
|
+
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
(async () => {
|
|
158
|
+
try {
|
|
159
|
+
const raw = await readFile(
|
|
160
|
+
join(os.homedir(), ".pi", "agent", "mcp.json"),
|
|
161
|
+
"utf8",
|
|
162
|
+
);
|
|
163
|
+
const cfg = JSON.parse(raw);
|
|
164
|
+
mcpServersCount = Object.keys(cfg.mcpServers || {}).length;
|
|
165
|
+
} catch {
|
|
166
|
+
mcpServersCount = 0;
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
}, 150);
|
|
170
|
+
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
(async () => {
|
|
173
|
+
try {
|
|
174
|
+
const raw = await readFile(
|
|
175
|
+
join(os.homedir(), ".pi", "agent", "settings.json"),
|
|
176
|
+
"utf8",
|
|
177
|
+
);
|
|
178
|
+
const cfg = JSON.parse(raw);
|
|
179
|
+
extensionsCount = Array.isArray(cfg.extensions)
|
|
180
|
+
? cfg.extensions.length
|
|
181
|
+
: 0;
|
|
182
|
+
packagesCount = Array.isArray(cfg.packages) ? cfg.packages.length : 0;
|
|
183
|
+
} catch {
|
|
184
|
+
extensionsCount = 0;
|
|
185
|
+
packagesCount = 0;
|
|
186
|
+
}
|
|
187
|
+
})();
|
|
188
|
+
}, 200);
|
|
189
|
+
|
|
190
|
+
let tick = 0;
|
|
191
|
+
const state = { timer: null as NodeJS.Timeout | null };
|
|
192
|
+
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
ctx.ui.setHeader((tui, theme) => {
|
|
195
|
+
if (state.timer) clearInterval(state.timer);
|
|
196
|
+
|
|
197
|
+
state.timer = setInterval(() => {
|
|
198
|
+
tick++;
|
|
199
|
+
if (tick > 90) {
|
|
200
|
+
if (state.timer) {
|
|
201
|
+
clearInterval(state.timer);
|
|
202
|
+
state.timer = null;
|
|
203
|
+
}
|
|
204
|
+
closeIntroSafely();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
tui.requestRender();
|
|
209
|
+
} catch {
|
|
210
|
+
if (state.timer) {
|
|
211
|
+
clearInterval(state.timer);
|
|
212
|
+
state.timer = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}, 50);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
render(width: number): string[] {
|
|
219
|
+
const flashStartTick = 16;
|
|
220
|
+
const roseOpacity = Math.min(1, tick / 16);
|
|
221
|
+
const flashPhase =
|
|
222
|
+
tick >= flashStartTick
|
|
223
|
+
? Math.max(0, 1 - (tick - flashStartTick) / 20)
|
|
224
|
+
: 0;
|
|
225
|
+
const frame = Math.floor(tick / 2);
|
|
226
|
+
|
|
227
|
+
const sideBySideMinWidth = roseBase.width + 3 + logoBase.width + 4;
|
|
228
|
+
const wideStatsMinWidth = 122;
|
|
229
|
+
const horizontal = width >= sideBySideMinWidth;
|
|
230
|
+
const wideStats = width >= wideStatsMinWidth;
|
|
231
|
+
|
|
232
|
+
const b = new LayoutBuilder();
|
|
233
|
+
b.addRow();
|
|
234
|
+
b.center(width);
|
|
235
|
+
|
|
236
|
+
if (horizontal) {
|
|
237
|
+
const rowCount = Math.max(
|
|
238
|
+
roseBase.lines.length,
|
|
239
|
+
logoBase.lines.length,
|
|
240
|
+
);
|
|
241
|
+
const roseOffset = Math.max(
|
|
242
|
+
0,
|
|
243
|
+
Math.floor((rowCount - roseBase.lines.length) / 2),
|
|
244
|
+
);
|
|
245
|
+
const logoOffset = Math.max(
|
|
246
|
+
0,
|
|
247
|
+
Math.floor((rowCount - logoBase.lines.length) / 2),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
for (let i = 0; i < rowCount; i++) {
|
|
251
|
+
const roseI = i - roseOffset;
|
|
252
|
+
const logoI = i - logoOffset;
|
|
253
|
+
const roseLine =
|
|
254
|
+
roseI >= 0 && roseI < roseBase.lines.length
|
|
255
|
+
? roseBase.lines[roseI]
|
|
256
|
+
: " ".repeat(roseBase.width);
|
|
257
|
+
const logoLine =
|
|
258
|
+
logoI >= 0 && logoI < logoBase.lines.length
|
|
259
|
+
? logoBase.lines[logoI]
|
|
260
|
+
: " ".repeat(logoBase.width);
|
|
261
|
+
|
|
262
|
+
b.addRow();
|
|
263
|
+
b.add("rose", roseLine);
|
|
264
|
+
b.add("none", " ");
|
|
265
|
+
b.add("banner", logoLine);
|
|
266
|
+
b.center(width);
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const showBanner = width >= logoBase.width + 2;
|
|
270
|
+
const showRose = width >= roseBase.width + 2;
|
|
271
|
+
if (showBanner) {
|
|
272
|
+
for (const logoLine of logoBase.lines) {
|
|
273
|
+
b.addRow();
|
|
274
|
+
b.add("banner", logoLine);
|
|
275
|
+
b.center(width);
|
|
276
|
+
}
|
|
277
|
+
if (showRose) {
|
|
278
|
+
b.addRow();
|
|
279
|
+
b.center(width);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (showRose) {
|
|
283
|
+
for (const roseLine of roseBase.lines) {
|
|
284
|
+
b.addRow();
|
|
285
|
+
b.add("rose", roseLine);
|
|
286
|
+
b.center(width);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
b.addRow();
|
|
292
|
+
b.center(width);
|
|
293
|
+
|
|
294
|
+
const fit = (v: unknown, w: number) =>
|
|
295
|
+
String(v ?? "")
|
|
296
|
+
.replace(/\s+/g, " ")
|
|
297
|
+
.trim()
|
|
298
|
+
.slice(0, w)
|
|
299
|
+
.padEnd(w);
|
|
300
|
+
const addWideRow = (
|
|
301
|
+
l1: string,
|
|
302
|
+
v1: string,
|
|
303
|
+
l2: string,
|
|
304
|
+
v2: string,
|
|
305
|
+
) => {
|
|
306
|
+
b.addRow();
|
|
307
|
+
b.add("label", fit(l1, 10));
|
|
308
|
+
b.add("none", " ");
|
|
309
|
+
b.add("value", fit(v1, 48));
|
|
310
|
+
b.add("none", " ");
|
|
311
|
+
b.add("label", fit(l2, 12));
|
|
312
|
+
b.add("none", " ");
|
|
313
|
+
b.add("value", fit(v2, 46));
|
|
314
|
+
b.center(width);
|
|
315
|
+
};
|
|
316
|
+
const narrowRows: Array<[string, string]> = [
|
|
317
|
+
["GIT:", gitBranch],
|
|
318
|
+
["PATH:", ctx.cwd],
|
|
319
|
+
["MCP:", `${mcpServersCount} server(s)`],
|
|
320
|
+
["PLUGINS:", `${packagesCount} package(s)`],
|
|
321
|
+
["AGENTS:", `${skills.length} loaded`],
|
|
322
|
+
["EXTENSIONS:", `${extensionsCount} active`],
|
|
323
|
+
["VER:", `v${VERSION}`],
|
|
324
|
+
["TOOLS:", `${customTools.length} custom`],
|
|
325
|
+
];
|
|
326
|
+
const narrowLabelW = Math.max(...narrowRows.map(([l]) => l.length));
|
|
327
|
+
const narrowValueW = Math.max(
|
|
328
|
+
0,
|
|
329
|
+
Math.min(
|
|
330
|
+
Math.max(...narrowRows.map(([, v]) => v.length)),
|
|
331
|
+
Math.max(8, width - narrowLabelW - 4),
|
|
332
|
+
),
|
|
333
|
+
);
|
|
334
|
+
const addNarrowRow = (label: string, value: string) => {
|
|
335
|
+
b.addRow();
|
|
336
|
+
b.add("label", label.padEnd(narrowLabelW));
|
|
337
|
+
b.add("none", " ");
|
|
338
|
+
b.add("value", fit(value, narrowValueW));
|
|
339
|
+
b.center(width);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
if (wideStats) {
|
|
343
|
+
addWideRow("GIT:", gitBranch, "PATH:", ctx.cwd);
|
|
344
|
+
addWideRow(
|
|
345
|
+
"MCP:",
|
|
346
|
+
`${mcpServersCount} server(s)`,
|
|
347
|
+
"PLUGINS:",
|
|
348
|
+
`${packagesCount} package(s)`,
|
|
349
|
+
);
|
|
350
|
+
addWideRow(
|
|
351
|
+
"AGENTS:",
|
|
352
|
+
`${skills.length} loaded`,
|
|
353
|
+
"EXTENSIONS:",
|
|
354
|
+
`${extensionsCount} active`,
|
|
355
|
+
);
|
|
356
|
+
addWideRow(
|
|
357
|
+
"VER:",
|
|
358
|
+
`v${VERSION}`,
|
|
359
|
+
"TOOLS:",
|
|
360
|
+
`${customTools.length} custom`,
|
|
361
|
+
);
|
|
362
|
+
} else {
|
|
363
|
+
addNarrowRow("GIT:", gitBranch);
|
|
364
|
+
addNarrowRow("PATH:", ctx.cwd);
|
|
365
|
+
addNarrowRow("MCP:", `${mcpServersCount} server(s)`);
|
|
366
|
+
addNarrowRow("PLUGINS:", `${packagesCount} package(s)`);
|
|
367
|
+
addNarrowRow("AGENTS:", `${skills.length} loaded`);
|
|
368
|
+
addNarrowRow("EXTENSIONS:", `${extensionsCount} active`);
|
|
369
|
+
addNarrowRow("VER:", `v${VERSION}`);
|
|
370
|
+
addNarrowRow("TOOLS:", `${customTools.length} custom`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
b.addRow();
|
|
374
|
+
b.center(width);
|
|
375
|
+
|
|
376
|
+
const out: string[] = [];
|
|
377
|
+
const layout = b.lines;
|
|
378
|
+
|
|
379
|
+
for (let y = 0; y < layout.length; y++) {
|
|
380
|
+
const row = layout[y] || [];
|
|
381
|
+
const firstBannerX = row.findIndex((c) => c.type === "banner");
|
|
382
|
+
let line = "";
|
|
383
|
+
|
|
384
|
+
for (let x = 0; x < row.length; x++) {
|
|
385
|
+
const cell = row[x] || { char: " ", type: "none" as const };
|
|
386
|
+
if (cell.char === " ") {
|
|
387
|
+
line += " ";
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (cell.type === "rose") {
|
|
392
|
+
const pulse = 0.9 + Math.sin((x + y + frame) * 0.08) * 0.1;
|
|
393
|
+
const k = Math.max(0.01, roseOpacity * pulse);
|
|
394
|
+
const f = Math.pow(flashPhase, 0.4);
|
|
395
|
+
|
|
396
|
+
const rBase = Math.floor(255 * k);
|
|
397
|
+
const gBase = Math.floor(118 * k);
|
|
398
|
+
const bBase = Math.floor(195 * k);
|
|
399
|
+
|
|
400
|
+
if (f > 0.85) {
|
|
401
|
+
line += `\x1b[1m\x1b[38;2;255;255;255m${cell.char}\x1b[0m`;
|
|
402
|
+
} else {
|
|
403
|
+
const r = Math.floor(rBase + (255 - rBase) * f);
|
|
404
|
+
const g = Math.floor(gBase + (255 - gBase) * f);
|
|
405
|
+
const bColor = Math.floor(bBase + (255 - bBase) * f);
|
|
406
|
+
line += rgb(r, g, bColor, cell.char);
|
|
407
|
+
}
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (cell.type === "banner") {
|
|
412
|
+
if (cell.char === "▒") {
|
|
413
|
+
line += rgb(95, 30, 60, cell.char);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const localX = firstBannerX >= 0 ? x - firstBannerX : x;
|
|
417
|
+
const sweep = Math.floor((tick - 16) * 2.2);
|
|
418
|
+
const isFlashing =
|
|
419
|
+
tick >= 16 && localX >= sweep - 4 && localX <= sweep + 2;
|
|
420
|
+
|
|
421
|
+
if (isFlashing) {
|
|
422
|
+
line += `\x1b[1m\x1b[38;2;255;255;255m${cell.char}\x1b[0m`;
|
|
423
|
+
} else {
|
|
424
|
+
line += rgb(255, 120, 198, cell.char);
|
|
425
|
+
}
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
switch (cell.type) {
|
|
430
|
+
case "label":
|
|
431
|
+
line += rgb(200, 100, 160, cell.char);
|
|
432
|
+
break;
|
|
433
|
+
case "value":
|
|
434
|
+
line += rgb(255, 140, 210, cell.char);
|
|
435
|
+
break;
|
|
436
|
+
case "dim":
|
|
437
|
+
line += theme.fg("dim", cell.char);
|
|
438
|
+
break;
|
|
439
|
+
case "accent":
|
|
440
|
+
line += theme.fg("accent", cell.char);
|
|
441
|
+
break;
|
|
442
|
+
default:
|
|
443
|
+
line += cell.char;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
out.push(truncateToWidth(line, Math.max(1, width), ""));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return out;
|
|
451
|
+
},
|
|
452
|
+
invalidate() {
|
|
453
|
+
if (state.timer) {
|
|
454
|
+
clearInterval(state.timer);
|
|
455
|
+
state.timer = null;
|
|
456
|
+
}
|
|
457
|
+
closeIntroSafely();
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
});
|
|
461
|
+
}, 50);
|
|
462
|
+
});
|
|
463
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,5 +56,8 @@
|
|
|
56
56
|
"@earendil-works/pi-coding-agent": {
|
|
57
57
|
"optional": true
|
|
58
58
|
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -17,7 +17,8 @@ When asked who or what you are, answer as el Gentleman: a Pi-specific coding-age
|
|
|
17
17
|
- Use OpenSpec-style artifacts for proposal, specs, design, tasks, apply progress, verify report, and archive notes.
|
|
18
18
|
- If tests exist, follow strict TDD: RED, GREEN, TRIANGULATE, REFACTOR, and record evidence.
|
|
19
19
|
- Keep one parent session responsible for orchestration; child subagents should receive concrete phase work and must not spawn more subagents.
|
|
20
|
-
-
|
|
20
|
+
- Parent-only delegation triggers apply after complexity appears: 4+ files for understanding, 2+ non-trivial files to write, commit/PR after code changes, tooling/worktree incidents, or long sessions with accumulating complexity.
|
|
21
|
+
- As parent, prefer `scout`/`context-builder` for context-heavy exploration, one forked `worker` for implementation, and fresh-context `reviewer` agents for adversarial review before PRs and after incidents.
|
|
21
22
|
- Keep writes single-threaded unless the user explicitly approves isolated parallel worktrees.
|
|
22
23
|
- Forecast review workload before large changes; ask before producing oversized or multi-area diffs.
|
|
23
24
|
- Never claim persistent memory is available because of el Gentleman itself; memory is provided by separate packages/tools when active.
|
|
@@ -43,7 +44,15 @@ clarify → explore → proposal → spec → design → tasks → apply → ver
|
|
|
43
44
|
For bounded implementation with subagents:
|
|
44
45
|
|
|
45
46
|
```text
|
|
46
|
-
clarify →
|
|
47
|
+
clarify → scout/context-builder when context-heavy → one worker → fresh reviewers → worker fixes → verify
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
Hard delegation triggers:
|
|
51
|
+
|
|
52
|
+
- **4-file rule**: reading 4+ files to understand means delegate exploration.
|
|
53
|
+
- **Multi-file write rule**: touching 2+ non-trivial files means use one worker or at least fresh review before completion.
|
|
54
|
+
- **PR rule**: before commit/push/PR for code changes, run fresh review unless the diff is trivial docs/text.
|
|
55
|
+
- **Incident rule**: after wrong cwd, accidental worktree/repo mutation, merge recovery, confusing test command, or environment workaround, run fresh audit.
|
|
56
|
+
- **Long-session rule**: after roughly 20 tool calls, 5 exploratory reads, or 2 non-mechanical edits with no delegation and accumulating complexity, pause and choose a subagent or justify not doing so.
|
|
57
|
+
|
|
49
58
|
The package auto-installs SDD agents and chains into the project when a Pi session starts. Use `/gentle-ai:install-sdd --force` only for recovery or intentional overwrite.
|