gentle-pi 0.1.22 → 0.2.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 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:
@@ -44,6 +46,7 @@ Most coding-agent sessions fail for operational reasons, not model reasons:
44
46
  | Capability | What it does |
45
47
  | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
46
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. |
47
50
  | **Work routing discipline** | Small tasks stay inline. Context-heavy exploration can be delegated. Large or risky changes go through SDD/OpenSpec. |
48
51
  | **SDD/OpenSpec assets** | Installs phase agents and chains for `init`, `explore`, `proposal`, `spec`, `design`, `tasks`, `apply`, `verify`, and `archive`. |
49
52
  | **Subagent orchestration** | Keeps one parent session responsible while child agents explore, implement, test, or review with focused context. |
@@ -173,10 +176,17 @@ Behavior:
173
176
  - the registry refreshes on session start;
174
177
  - `/skill-registry:refresh` forces regeneration;
175
178
  - a best-effort watcher refreshes when skill files change;
176
- - skills without `## Compact Rules` are still listed with an instruction to load the full skill file.
179
+ - skills without `## Compact Rules` are still listed, but delegators should inject project/user compact rules into subagents whenever possible.
177
180
 
178
181
  Skill discovery is a guardrail, not a workflow router: it helps Pi load the right skill without forcing extra ceremony.
179
182
 
183
+ Delegation contract:
184
+
185
+ - parent/orchestrator resolves project/user skills from the registry and injects compact rule text under `## Project Standards (auto-resolved)`;
186
+ - SDD subagents still use their assigned executor/phase skill;
187
+ - during normal runtime, subagents should not independently discover or load additional project/user `SKILL.md` files or the registry;
188
+ - fallback loading is degraded self-healing and must be reported via `skill_resolution` as `fallback-registry`, `fallback-path`, or `none`.
189
+
180
190
  ## Persona modes
181
191
 
182
192
  ```text
@@ -268,6 +278,13 @@ pi install npm:gentle-engram
268
278
 
269
279
  When memory tools are actually active, el Gentleman can save decisions, bug fixes, discoveries, user prompts, and session summaries across Pi sessions.
270
280
 
281
+ Memory contract for SDD delegation:
282
+
283
+ - parent/orchestrator owns memory retrieval and passes selected context into subagent prompts;
284
+ - subagents should not independently search memory during normal runtime unless explicitly instructed to retrieve a specific artifact or observation;
285
+ - subagents should save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts before returning when memory tools are available;
286
+ - 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`.
287
+
271
288
  ## Package contents
272
289
 
273
290
  | Path | Purpose |
@@ -7,6 +7,19 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD apply executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
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
+
22
+
10
23
  ## Before Writing Code
11
24
 
12
25
  Read proposal, specs, design, tasks, existing code, tests, `apply-progress.md` if present, and `openspec/config.yaml` when present.
@@ -7,8 +7,20 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD archive executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
10
16
  - Read verify report before archiving.
11
17
  - Merge accepted deltas into `openspec/specs/` and move the change to archive.
12
18
  - Preserve audit trail; never delete active artifacts silently.
13
19
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
14
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
+
@@ -7,8 +7,20 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD design executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
10
16
  - Read proposal, specs, and relevant code before designing.
11
17
  - Document decisions, data flow, file changes, contracts, tests, and rollout.
12
18
  - Keep design centered on `packages/coding-agent` unless scope explicitly expands.
13
19
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
14
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
+
@@ -7,8 +7,20 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD explore executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
10
16
  - Read OpenSpec/project context before conclusions.
11
17
  - Produce exploration notes only; do not implement.
12
18
  - Use OpenSpec artifacts and session context truthfully; persistent memory is optional and handled by separate packages.
13
19
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
14
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
+
@@ -8,9 +8,21 @@ inheritProjectContext: true
8
8
 
9
9
  You are the SDD init executor for Gentle AI.
10
10
 
11
+ ## Skill Resolution Contract
12
+
13
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
14
+
15
+ 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.
16
+
11
17
  - Inspect the project stack, test runner, conventions, and existing docs.
12
18
  - If `openspec/config.yaml` is missing, create it automatically with project context, `strict_tdd`, phase rules, and testing runner details.
13
19
  - If `openspec/config.yaml` already exists, read it, summarize the current SDD/testing configuration, and do not block the caller. Update only safe derived context when explicitly necessary; never destructively rewrite user-maintained SDD configuration.
14
20
  - Ensure `.atl/skill-registry.md` exists when skill registry data is available, or report that it is missing.
15
21
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
16
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
+
@@ -7,9 +7,21 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD onboard executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
10
16
  - Pick or ask for a small, real, low-risk improvement that can demonstrate the full SDD lifecycle.
11
17
  - Teach by doing: create real artifacts for explore, proposal, spec, design, tasks, apply, verify, and archive where appropriate.
12
18
  - Keep the walkthrough interactive and concise; explain why each phase exists before doing it.
13
19
  - Respect strict TDD when project testing capabilities are present.
14
20
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
15
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
+
@@ -7,8 +7,20 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD proposal executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
10
16
  - Read exploration and project standards before writing.
11
17
  - Write `openspec/changes/{change}/proposal.md`.
12
18
  - Include intent, scope, affected areas, risks, rollback, and success criteria.
13
19
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
14
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
+
@@ -7,8 +7,20 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD spec executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
10
16
  - Read proposal and existing specs first.
11
17
  - Write RFC 2119 requirements and Given/When/Then scenarios.
12
18
  - Store deltas under `openspec/changes/{change}/specs/`.
13
19
  - Do NOT launch child subagents. Parent/orchestrator owns delegation.
14
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
+
@@ -7,6 +7,19 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD tasks executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
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
+
22
+
10
23
  ## Inputs
11
24
 
12
25
  Read proposal, specs, design, project testing capabilities, and `openspec/config.yaml` when present.
@@ -7,6 +7,19 @@ inheritProjectContext: true
7
7
 
8
8
  You are the SDD verify executor for Gentle AI.
9
9
 
10
+ ## Skill Resolution Contract
11
+
12
+ Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer the parent-injected `## Project Standards (auto-resolved)` block; do not independently discover or load additional project/user `SKILL.md` files or the registry during normal runtime.
13
+
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
+
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
+
22
+
10
23
  ## Inputs
11
24
 
12
25
  Read specs, design, tasks, apply-progress, changed code, tests, and `openspec/config.yaml` when present.
@@ -141,6 +141,16 @@ This package does not provide persistent memory by itself.
141
141
  - If a separate memory package is installed and callable, memory/hybrid flows may be used.
142
142
  - Never claim memory exists because Gentle AI is installed.
143
143
 
144
+ ## Memory Contract
145
+
146
+ When Engram or another callable memory package is available, the parent owns memory retrieval and subagents own write-back for significant findings.
147
+
148
+ - 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.
149
+ - Write context: subagents MUST save significant discoveries, decisions, bug fixes, and completed SDD phase artifacts to memory before returning when memory tools are available.
150
+ - 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.`
151
+ - 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`.
152
+ - If memory tools are unavailable, do not pretend persistence exists; return artifacts inline and/or write OpenSpec files.
153
+
144
154
  ## Execution Mode
145
155
 
146
156
  For substantial SDD flows, choose or ask once per change:
@@ -178,7 +188,18 @@ The parent resolves skills once per session or before first delegation:
178
188
  3. Inject matching rule text into subagent prompts under `## Project Standards (auto-resolved)`.
179
189
  4. If the registry is absent, continue but mention that project-specific skill rules were unavailable.
180
190
 
181
- Subagents should receive pre-digested rules. They should not have to rediscover the registry.
191
+ Subagents should receive pre-digested project/user rules. They should not have to rediscover the registry.
192
+
193
+ Important distinction: SDD subagents still use their assigned executor/phase skill (for example `sdd-apply`, `sdd-design`, or `sdd-verify`). What they should not do during normal runtime is independently discover or load additional project/user `SKILL.md` files or the registry. Those project/user rules arrive pre-digested from the parent under `## Project Standards (auto-resolved)`.
194
+
195
+ If a subagent reports `skill_resolution`, interpret it as project/user skill resolution:
196
+
197
+ - `injected`: parent supplied `## Project Standards (auto-resolved)`.
198
+ - `fallback-registry`: subagent self-loaded compact rules from a registry because Project Standards were missing; degraded but auditable.
199
+ - `fallback-path`: subagent loaded explicit `SKILL: Load` paths because Project Standards were missing; degraded but auditable.
200
+ - `none`: no project/user skills were loaded.
201
+
202
+ If any subagent reports a fallback instead of `injected`, treat it as an orchestration gap and correct future delegations by injecting the compact rules directly.
182
203
 
183
204
  ## Intent-Driven Skill Discovery
184
205
 
@@ -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 (
@@ -147,7 +147,7 @@ function loadSkill(file: string): SkillEntry | undefined {
147
147
  rules:
148
148
  rules.length > 0
149
149
  ? rules
150
- : ["No compact rules declared; load the full skill file before doing work that matches this trigger."],
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."],
151
151
  };
152
152
  }
153
153
 
@@ -198,6 +198,12 @@ function renderRegistry(cwd: string, sources: string[], entries: SkillEntry[]):
198
198
  lines.push(`- ${src}`);
199
199
  }
200
200
  lines.push("");
201
+ lines.push("## Contract");
202
+ lines.push("");
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)`.");
204
+ lines.push("");
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`.");
206
+ lines.push("");
201
207
  lines.push(SECTION_MARKER);
202
208
  lines.push("");
203
209
  for (const entry of entries) {
@@ -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.22",
3
+ "version": "0.2.0",
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
  }