gentle-pi 0.1.21 → 0.1.23
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 +8 -1
- package/assets/agents/sdd-apply.md +7 -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 +7 -0
- package/assets/agents/sdd-verify.md +7 -0
- package/assets/orchestrator.md +27 -16
- package/extensions/gentle-ai.ts +141 -1
- package/extensions/skill-registry.ts +74 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -173,10 +173,17 @@ Behavior:
|
|
|
173
173
|
- the registry refreshes on session start;
|
|
174
174
|
- `/skill-registry:refresh` forces regeneration;
|
|
175
175
|
- a best-effort watcher refreshes when skill files change;
|
|
176
|
-
- skills without `## Compact Rules` are still listed
|
|
176
|
+
- skills without `## Compact Rules` are still listed, but delegators should inject project/user compact rules into subagents whenever possible.
|
|
177
177
|
|
|
178
178
|
Skill discovery is a guardrail, not a workflow router: it helps Pi load the right skill without forcing extra ceremony.
|
|
179
179
|
|
|
180
|
+
Delegation contract:
|
|
181
|
+
|
|
182
|
+
- parent/orchestrator resolves project/user skills from the registry and injects compact rule text under `## Project Standards (auto-resolved)`;
|
|
183
|
+
- SDD subagents still use their assigned executor/phase skill;
|
|
184
|
+
- during normal runtime, subagents should not independently discover or load additional project/user `SKILL.md` files or the registry;
|
|
185
|
+
- fallback loading is degraded self-healing and must be reported via `skill_resolution` as `fallback-registry`, `fallback-path`, or `none`.
|
|
186
|
+
|
|
180
187
|
## Persona modes
|
|
181
188
|
|
|
182
189
|
```text
|
|
@@ -7,6 +7,13 @@ 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
|
+
|
|
10
17
|
## Before Writing Code
|
|
11
18
|
|
|
12
19
|
Read proposal, specs, design, tasks, existing code, tests, `apply-progress.md` if present, and `openspec/config.yaml` when present.
|
|
@@ -7,6 +7,12 @@ 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.
|
|
@@ -7,6 +7,12 @@ 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.
|
|
@@ -7,6 +7,12 @@ 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.
|
|
@@ -8,6 +8,12 @@ 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.
|
|
@@ -7,6 +7,12 @@ 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.
|
|
@@ -7,6 +7,12 @@ 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.
|
|
@@ -7,6 +7,12 @@ 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/`.
|
|
@@ -7,6 +7,13 @@ 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
|
+
|
|
10
17
|
## Inputs
|
|
11
18
|
|
|
12
19
|
Read proposal, specs, design, project testing capabilities, and `openspec/config.yaml` when present.
|
|
@@ -7,6 +7,13 @@ 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
|
+
|
|
10
17
|
## Inputs
|
|
11
18
|
|
|
12
19
|
Read specs, design, tasks, apply-progress, changed code, tests, and `openspec/config.yaml` when present.
|
package/assets/orchestrator.md
CHANGED
|
@@ -85,15 +85,15 @@ If the request is large enough for SDD, do not jump directly to implementation.
|
|
|
85
85
|
|
|
86
86
|
Core question: does this inflate parent context without need?
|
|
87
87
|
|
|
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
|
|
88
|
+
| Action | Inline | Delegate |
|
|
89
|
+
| ------------------------------------------ | -----: | -------: |
|
|
90
|
+
| Read to decide/verify 1-3 files | yes | no |
|
|
91
|
+
| Read to explore/understand 4+ files | no | yes |
|
|
92
|
+
| Read as preparation for multi-file writing | no | yes |
|
|
93
|
+
| Write atomic one-file mechanical change | yes | no |
|
|
94
|
+
| Write with analysis across multiple files | no | yes |
|
|
95
|
+
| Bash for state, e.g. git status | yes | no |
|
|
96
|
+
| Bash for execution, e.g. tests/builds | no | yes |
|
|
97
97
|
|
|
98
98
|
## SDD Workflow
|
|
99
99
|
|
|
@@ -178,7 +178,18 @@ The parent resolves skills once per session or before first delegation:
|
|
|
178
178
|
3. Inject matching rule text into subagent prompts under `## Project Standards (auto-resolved)`.
|
|
179
179
|
4. If the registry is absent, continue but mention that project-specific skill rules were unavailable.
|
|
180
180
|
|
|
181
|
-
Subagents should receive pre-digested rules. They should not have to rediscover the registry.
|
|
181
|
+
Subagents should receive pre-digested project/user rules. They should not have to rediscover the registry.
|
|
182
|
+
|
|
183
|
+
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)`.
|
|
184
|
+
|
|
185
|
+
If a subagent reports `skill_resolution`, interpret it as project/user skill resolution:
|
|
186
|
+
|
|
187
|
+
- `injected`: parent supplied `## Project Standards (auto-resolved)`.
|
|
188
|
+
- `fallback-registry`: subagent self-loaded compact rules from a registry because Project Standards were missing; degraded but auditable.
|
|
189
|
+
- `fallback-path`: subagent loaded explicit `SKILL: Load` paths because Project Standards were missing; degraded but auditable.
|
|
190
|
+
- `none`: no project/user skills were loaded.
|
|
191
|
+
|
|
192
|
+
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
193
|
|
|
183
194
|
## Intent-Driven Skill Discovery
|
|
184
195
|
|
|
@@ -194,12 +205,12 @@ Discovery order:
|
|
|
194
205
|
|
|
195
206
|
Common intent hints, not hard routing:
|
|
196
207
|
|
|
197
|
-
| User intent
|
|
198
|
-
|
|
199
|
-
| PR review / GitHub PR URL
|
|
200
|
-
| Post-ready review comments | `comment-writer`
|
|
201
|
-
| Create/open/prepare PR
|
|
202
|
-
| Split/stack/large PR
|
|
208
|
+
| User intent | Skill to check |
|
|
209
|
+
| -------------------------- | -------------------------------------- |
|
|
210
|
+
| PR review / GitHub PR URL | project review skill, then `pr-review` |
|
|
211
|
+
| Post-ready review comments | `comment-writer` |
|
|
212
|
+
| Create/open/prepare PR | `branch-pr` |
|
|
213
|
+
| Split/stack/large PR | `chained-pr` |
|
|
203
214
|
|
|
204
215
|
Keep this lightweight: loading a skill should improve the immediate task, not force extra ceremony.
|
|
205
216
|
|
package/extensions/gentle-ai.ts
CHANGED
|
@@ -8,12 +8,18 @@ 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";
|
|
11
12
|
import type {
|
|
12
13
|
ExtensionAPI,
|
|
13
14
|
ExtensionContext,
|
|
15
|
+
Theme,
|
|
14
16
|
ToolCallEventResult,
|
|
15
17
|
} from "@earendil-works/pi-coding-agent";
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
matchesKey,
|
|
20
|
+
truncateToWidth,
|
|
21
|
+
visibleWidth,
|
|
22
|
+
} from "@earendil-works/pi-tui";
|
|
17
23
|
|
|
18
24
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
19
25
|
const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
@@ -43,6 +49,139 @@ const NEUTRAL_PERSONA_PROMPT = `Persona:
|
|
|
43
49
|
- Push back when the user asks for code without enough context or understanding.
|
|
44
50
|
- Correct errors directly, explain why, and show the better path.`;
|
|
45
51
|
|
|
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
|
+
|
|
46
185
|
function buildGentlePrompt(persona: PersonaMode): string {
|
|
47
186
|
const personaPrompt =
|
|
48
187
|
persona === "neutral" ? NEUTRAL_PERSONA_PROMPT : GENTLEMAN_PERSONA_PROMPT;
|
|
@@ -772,6 +911,7 @@ async function handlePersonaCommand(ctx: ExtensionContext): Promise<void> {
|
|
|
772
911
|
|
|
773
912
|
export default function gentleAi(pi: ExtensionAPI): void {
|
|
774
913
|
pi.on("session_start", (_event, ctx) => {
|
|
914
|
+
installRoseHeader(ctx);
|
|
775
915
|
const result = installSddAssets(ctx.cwd, false);
|
|
776
916
|
const modelResult = applyModelConfig(ctx.cwd, readModelConfig(ctx.cwd));
|
|
777
917
|
if (
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
statSync,
|
|
8
|
+
watch,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
3
11
|
import { homedir } from "node:os";
|
|
4
12
|
import { basename, join, relative } from "node:path";
|
|
5
13
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -69,7 +77,11 @@ function findSkillFiles(root: string): string[] {
|
|
|
69
77
|
return out;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
function parseFrontmatter(source: string): {
|
|
80
|
+
function parseFrontmatter(source: string): {
|
|
81
|
+
name?: string;
|
|
82
|
+
description?: string;
|
|
83
|
+
body: string;
|
|
84
|
+
} {
|
|
73
85
|
if (!source.startsWith("---\n")) return { body: source };
|
|
74
86
|
const end = source.indexOf("\n---", 4);
|
|
75
87
|
if (end === -1) return { body: source };
|
|
@@ -81,7 +93,10 @@ function parseFrontmatter(source: string): { name?: string; description?: string
|
|
|
81
93
|
if (!m) continue;
|
|
82
94
|
const key = m[1];
|
|
83
95
|
let value = m[2].trim();
|
|
84
|
-
if (
|
|
96
|
+
if (
|
|
97
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
98
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
99
|
+
) {
|
|
85
100
|
value = value.slice(1, -1);
|
|
86
101
|
}
|
|
87
102
|
if (key === "name") out.name = value;
|
|
@@ -108,7 +123,10 @@ function extractCompactRulesSection(body: string): string[] {
|
|
|
108
123
|
return rules;
|
|
109
124
|
}
|
|
110
125
|
|
|
111
|
-
function deriveSkillName(
|
|
126
|
+
function deriveSkillName(
|
|
127
|
+
file: string,
|
|
128
|
+
frontmatterName: string | undefined,
|
|
129
|
+
): string {
|
|
112
130
|
if (frontmatterName) return frontmatterName;
|
|
113
131
|
return basename(join(file, ".."));
|
|
114
132
|
}
|
|
@@ -147,7 +165,9 @@ function loadSkill(file: string): SkillEntry | undefined {
|
|
|
147
165
|
rules:
|
|
148
166
|
rules.length > 0
|
|
149
167
|
? rules
|
|
150
|
-
: [
|
|
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
|
+
],
|
|
151
171
|
};
|
|
152
172
|
}
|
|
153
173
|
|
|
@@ -182,13 +202,19 @@ function fingerprint(files: string[]): string {
|
|
|
182
202
|
return createHash("sha1").update(lines.join("\n")).digest("hex");
|
|
183
203
|
}
|
|
184
204
|
|
|
185
|
-
function renderRegistry(
|
|
205
|
+
function renderRegistry(
|
|
206
|
+
cwd: string,
|
|
207
|
+
sources: string[],
|
|
208
|
+
entries: SkillEntry[],
|
|
209
|
+
): string {
|
|
186
210
|
const projectName = basename(cwd);
|
|
187
211
|
const today = new Date().toISOString().slice(0, 10);
|
|
188
212
|
const lines: string[] = [];
|
|
189
213
|
lines.push(`# Skill Registry — ${projectName}`);
|
|
190
214
|
lines.push("");
|
|
191
|
-
lines.push(
|
|
215
|
+
lines.push(
|
|
216
|
+
"<!-- Auto-generated by .pi/extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->",
|
|
217
|
+
);
|
|
192
218
|
lines.push("");
|
|
193
219
|
lines.push(`Last updated: ${today}`);
|
|
194
220
|
lines.push("");
|
|
@@ -198,6 +224,16 @@ function renderRegistry(cwd: string, sources: string[], entries: SkillEntry[]):
|
|
|
198
224
|
lines.push(`- ${src}`);
|
|
199
225
|
}
|
|
200
226
|
lines.push("");
|
|
227
|
+
lines.push("## Contract");
|
|
228
|
+
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
|
+
);
|
|
232
|
+
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
|
+
);
|
|
236
|
+
lines.push("");
|
|
201
237
|
lines.push(SECTION_MARKER);
|
|
202
238
|
lines.push("");
|
|
203
239
|
for (const entry of entries) {
|
|
@@ -233,12 +269,20 @@ function ensureAtlIgnored(cwd: string): void {
|
|
|
233
269
|
.some((line) => line === ".atl" || line === ATL_IGNORE_ENTRY);
|
|
234
270
|
if (hasAtlIgnore) return;
|
|
235
271
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
236
|
-
const header = existing.includes("# Local Pi runtime state")
|
|
237
|
-
|
|
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
|
+
);
|
|
238
279
|
}
|
|
239
280
|
|
|
240
281
|
function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
241
|
-
const existingDirs = uniqueExistingDirs([
|
|
282
|
+
const existingDirs = uniqueExistingDirs([
|
|
283
|
+
...projectSkillDirs(cwd),
|
|
284
|
+
...userSkillDirs(),
|
|
285
|
+
]);
|
|
242
286
|
const files = existingDirs.flatMap(findSkillFiles).sort();
|
|
243
287
|
const cachePath = join(cwd, CACHE_REL_PATH);
|
|
244
288
|
const registryPath = join(cwd, REGISTRY_REL_PATH);
|
|
@@ -246,7 +290,9 @@ function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
|
246
290
|
let cached: string | undefined;
|
|
247
291
|
if (existsSync(cachePath)) {
|
|
248
292
|
try {
|
|
249
|
-
cached = (
|
|
293
|
+
cached = (
|
|
294
|
+
JSON.parse(readFileSync(cachePath, "utf8")) as { fingerprint?: string }
|
|
295
|
+
).fingerprint;
|
|
250
296
|
} catch {
|
|
251
297
|
cached = undefined;
|
|
252
298
|
}
|
|
@@ -266,15 +312,25 @@ function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
|
266
312
|
mkdirSync(join(cwd, ".atl"), { recursive: true });
|
|
267
313
|
writeFileSync(registryPath, md);
|
|
268
314
|
writeFileSync(cachePath, JSON.stringify({ fingerprint: fp }, null, 2));
|
|
269
|
-
return {
|
|
315
|
+
return {
|
|
316
|
+
regenerated: true,
|
|
317
|
+
skillCount: deduped.length,
|
|
318
|
+
reason: force ? "forced" : "fingerprint-changed",
|
|
319
|
+
};
|
|
270
320
|
}
|
|
271
321
|
|
|
272
322
|
const watchedCwds = new Set<string>();
|
|
273
323
|
|
|
274
|
-
function startSkillRegistryWatcher(
|
|
324
|
+
function startSkillRegistryWatcher(
|
|
325
|
+
cwd: string,
|
|
326
|
+
notify: (message: string) => void,
|
|
327
|
+
): void {
|
|
275
328
|
if (watchedCwds.has(cwd)) return;
|
|
276
329
|
watchedCwds.add(cwd);
|
|
277
|
-
const dirs = uniqueExistingDirs([
|
|
330
|
+
const dirs = uniqueExistingDirs([
|
|
331
|
+
...projectSkillDirs(cwd),
|
|
332
|
+
...userSkillDirs(),
|
|
333
|
+
]);
|
|
278
334
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
279
335
|
const refresh = () => {
|
|
280
336
|
if (timer) clearTimeout(timer);
|
|
@@ -304,7 +360,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
304
360
|
ensureAtlIgnored(ctx.cwd);
|
|
305
361
|
const result = regenerateRegistry(ctx.cwd, false);
|
|
306
362
|
if (result.regenerated && ctx.hasUI) {
|
|
307
|
-
ctx.ui.notify(
|
|
363
|
+
ctx.ui.notify(
|
|
364
|
+
`Skill registry refreshed (${result.skillCount} skills)`,
|
|
365
|
+
"info",
|
|
366
|
+
);
|
|
308
367
|
}
|
|
309
368
|
startSkillRegistryWatcher(ctx.cwd, (message) => {
|
|
310
369
|
if (ctx.hasUI) ctx.ui.notify(message, "info");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
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",
|