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 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 with an instruction to load the full skill file.
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.
@@ -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 | 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 |
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 | Skill to check |
198
- |---|---|
199
- | PR review / GitHub PR URL | project review skill, then `pr-review` |
200
- | Post-ready review comments | `comment-writer` |
201
- | Create/open/prepare PR | `branch-pr` |
202
- | Split/stack/large PR | `chained-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
 
@@ -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 { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
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 { existsSync, mkdirSync, readFileSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
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): { name?: string; description?: string; body: 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 ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
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(file: string, frontmatterName: string | undefined): string {
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
- : ["No compact rules declared; load the full skill file before doing work that matches this trigger."],
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(cwd: string, sources: string[], entries: SkillEntry[]): string {
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("<!-- Auto-generated by .pi/extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->");
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") ? "" : "# Local Pi runtime state\n";
237
- writeFileSync(gitignorePath, `${existing}${prefix}${header}${ATL_IGNORE_ENTRY}\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
+ );
238
279
  }
239
280
 
240
281
  function regenerateRegistry(cwd: string, force: boolean): RegenResult {
241
- const existingDirs = uniqueExistingDirs([...projectSkillDirs(cwd), ...userSkillDirs()]);
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 = (JSON.parse(readFileSync(cachePath, "utf8")) as { fingerprint?: string }).fingerprint;
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 { regenerated: true, skillCount: deduped.length, reason: force ? "forced" : "fingerprint-changed" };
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(cwd: string, notify: (message: string) => void): void {
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([...projectSkillDirs(cwd), ...userSkillDirs()]);
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(`Skill registry refreshed (${result.skillCount} skills)`, "info");
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.21",
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",