gentle-pi 0.1.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 +66 -0
- package/assets/agents/sdd-apply.md +71 -0
- package/assets/agents/sdd-archive.md +14 -0
- package/assets/agents/sdd-design.md +14 -0
- package/assets/agents/sdd-explore.md +14 -0
- package/assets/agents/sdd-init.md +14 -0
- package/assets/agents/sdd-onboard.md +15 -0
- package/assets/agents/sdd-proposal.md +14 -0
- package/assets/agents/sdd-spec.md +14 -0
- package/assets/agents/sdd-tasks.md +61 -0
- package/assets/agents/sdd-verify.md +55 -0
- package/assets/chains/sdd-full.chain.md +75 -0
- package/assets/chains/sdd-plan.chain.md +35 -0
- package/assets/chains/sdd-verify.chain.md +27 -0
- package/assets/orchestrator.md +191 -0
- package/assets/support/strict-tdd-verify.md +269 -0
- package/assets/support/strict-tdd.md +364 -0
- package/extensions/gentle-ai.ts +157 -0
- package/extensions/sdd-init.ts +83 -0
- package/extensions/skill-registry.ts +267 -0
- package/package.json +47 -0
- package/prompts/cl.md +54 -0
- package/prompts/is.md +25 -0
- package/prompts/pr.md +41 -0
- package/prompts/wr.md +31 -0
- package/skills/branch-pr/SKILL.md +202 -0
- package/skills/chained-pr/SKILL.md +50 -0
- package/skills/chained-pr/references/chaining-details.md +99 -0
- package/skills/cognitive-doc-design/SKILL.md +81 -0
- package/skills/comment-writer/SKILL.md +74 -0
- package/skills/gentle-ai/SKILL.md +43 -0
- package/skills/issue-creation/SKILL.md +223 -0
- package/skills/judgment-day/SKILL.md +52 -0
- package/skills/judgment-day/references/prompts-and-formats.md +75 -0
- package/skills/work-unit-commits/SKILL.md +86 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, join, relative } from "node:path";
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
|
|
7
|
+
const REGISTRY_REL_PATH = ".atl/skill-registry.md";
|
|
8
|
+
const CACHE_REL_PATH = ".atl/.skill-registry.cache.json";
|
|
9
|
+
const SECTION_MARKER = "## Selected skills and compact rules";
|
|
10
|
+
const EXCLUDE_NAMES = new Set(["_shared", "skill-registry"]);
|
|
11
|
+
const EXCLUDE_PREFIXES = ["sdd-"];
|
|
12
|
+
|
|
13
|
+
interface SkillEntry {
|
|
14
|
+
name: string;
|
|
15
|
+
path: string;
|
|
16
|
+
description: string;
|
|
17
|
+
rules: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function userSkillDirs(): string[] {
|
|
21
|
+
const home = homedir();
|
|
22
|
+
return [
|
|
23
|
+
join(home, ".pi/agent/skills"),
|
|
24
|
+
join(home, ".agents/skills"),
|
|
25
|
+
join(home, ".config/opencode/skills"),
|
|
26
|
+
join(home, ".claude/skills"),
|
|
27
|
+
join(home, ".gemini/skills"),
|
|
28
|
+
join(home, ".cursor/skills"),
|
|
29
|
+
join(home, ".copilot/skills"),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function projectSkillDirs(cwd: string): string[] {
|
|
34
|
+
return [
|
|
35
|
+
join(cwd, ".pi/skills"),
|
|
36
|
+
join(cwd, ".agents/skills"),
|
|
37
|
+
join(cwd, ".claude/skills"),
|
|
38
|
+
join(cwd, ".atl/skills"),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findSkillFiles(root: string): string[] {
|
|
43
|
+
if (!existsSync(root)) return [];
|
|
44
|
+
const out: string[] = [];
|
|
45
|
+
const stack: string[] = [root];
|
|
46
|
+
while (stack.length > 0) {
|
|
47
|
+
const dir = stack.pop()!;
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const full = join(dir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
stack.push(full);
|
|
58
|
+
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
59
|
+
out.push(full);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseFrontmatter(source: string): { name?: string; description?: string; body: string } {
|
|
67
|
+
if (!source.startsWith("---\n")) return { body: source };
|
|
68
|
+
const end = source.indexOf("\n---", 4);
|
|
69
|
+
if (end === -1) return { body: source };
|
|
70
|
+
const fm = source.slice(4, end);
|
|
71
|
+
const body = source.slice(end + 4).replace(/^\n/, "");
|
|
72
|
+
const out: { name?: string; description?: string } = {};
|
|
73
|
+
for (const line of fm.split("\n")) {
|
|
74
|
+
const m = line.match(/^(\w+):\s*(.*)$/);
|
|
75
|
+
if (!m) continue;
|
|
76
|
+
const key = m[1];
|
|
77
|
+
let value = m[2].trim();
|
|
78
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
79
|
+
value = value.slice(1, -1);
|
|
80
|
+
}
|
|
81
|
+
if (key === "name") out.name = value;
|
|
82
|
+
else if (key === "description") out.description = value;
|
|
83
|
+
}
|
|
84
|
+
return { ...out, body };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function extractCompactRulesSection(body: string): string[] {
|
|
88
|
+
const lines = body.split("\n");
|
|
89
|
+
let inSection = false;
|
|
90
|
+
const rules: string[] = [];
|
|
91
|
+
for (const raw of lines) {
|
|
92
|
+
const line = raw.trimEnd();
|
|
93
|
+
if (/^##\s+Compact Rules\s*$/i.test(line)) {
|
|
94
|
+
inSection = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!inSection) continue;
|
|
98
|
+
if (/^##\s+/.test(line)) break;
|
|
99
|
+
const m = line.match(/^-\s+(.+)$/);
|
|
100
|
+
if (m) rules.push(m[1].trim());
|
|
101
|
+
}
|
|
102
|
+
return rules;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function deriveSkillName(file: string, frontmatterName: string | undefined): string {
|
|
106
|
+
if (frontmatterName) return frontmatterName;
|
|
107
|
+
return basename(join(file, ".."));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isExcluded(name: string): boolean {
|
|
111
|
+
if (EXCLUDE_NAMES.has(name)) return true;
|
|
112
|
+
return EXCLUDE_PREFIXES.some((p) => name.startsWith(p));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function loadSkill(file: string): SkillEntry | undefined {
|
|
116
|
+
let source: string;
|
|
117
|
+
try {
|
|
118
|
+
source = readFileSync(file, "utf8");
|
|
119
|
+
} catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const fm = parseFrontmatter(source);
|
|
123
|
+
const name = deriveSkillName(file, fm.name);
|
|
124
|
+
if (isExcluded(name)) return undefined;
|
|
125
|
+
const rules = extractCompactRulesSection(fm.body);
|
|
126
|
+
if (rules.length === 0) return undefined;
|
|
127
|
+
return {
|
|
128
|
+
name,
|
|
129
|
+
path: file,
|
|
130
|
+
description: fm.description ?? "",
|
|
131
|
+
rules,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function dedupeBySkillName(entries: SkillEntry[], cwd: string): SkillEntry[] {
|
|
136
|
+
const projectPrefix = cwd.endsWith("/") ? cwd : `${cwd}/`;
|
|
137
|
+
const buckets = new Map<string, SkillEntry[]>();
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const list = buckets.get(entry.name) ?? [];
|
|
140
|
+
list.push(entry);
|
|
141
|
+
buckets.set(entry.name, list);
|
|
142
|
+
}
|
|
143
|
+
const out: SkillEntry[] = [];
|
|
144
|
+
for (const [, list] of buckets) {
|
|
145
|
+
const projectScoped = list.find((e) => e.path.startsWith(projectPrefix));
|
|
146
|
+
out.push(projectScoped ?? list[0]);
|
|
147
|
+
}
|
|
148
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function fingerprint(files: string[]): string {
|
|
152
|
+
const lines = files
|
|
153
|
+
.map((f) => {
|
|
154
|
+
try {
|
|
155
|
+
const stat = statSync(f);
|
|
156
|
+
return `${f}:${stat.mtimeMs}:${stat.size}`;
|
|
157
|
+
} catch {
|
|
158
|
+
return `${f}:missing`;
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
.sort();
|
|
162
|
+
return createHash("sha1").update(lines.join("\n")).digest("hex");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function renderRegistry(cwd: string, sources: string[], entries: SkillEntry[]): string {
|
|
166
|
+
const projectName = basename(cwd);
|
|
167
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
168
|
+
const lines: string[] = [];
|
|
169
|
+
lines.push(`# Skill Registry — ${projectName}`);
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push("<!-- Auto-generated by .pi/extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->");
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(`Last updated: ${today}`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push("## Sources scanned");
|
|
176
|
+
lines.push("");
|
|
177
|
+
for (const src of sources) {
|
|
178
|
+
lines.push(`- ${src}`);
|
|
179
|
+
}
|
|
180
|
+
lines.push("");
|
|
181
|
+
lines.push(SECTION_MARKER);
|
|
182
|
+
lines.push("");
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
lines.push(`### ${entry.name}`);
|
|
185
|
+
lines.push(`- Path: ${entry.path}`);
|
|
186
|
+
if (entry.description) {
|
|
187
|
+
lines.push(`- Trigger: ${entry.description}`);
|
|
188
|
+
}
|
|
189
|
+
lines.push("- Rules:");
|
|
190
|
+
for (const rule of entry.rules) {
|
|
191
|
+
lines.push(` - ${rule}`);
|
|
192
|
+
}
|
|
193
|
+
lines.push("");
|
|
194
|
+
}
|
|
195
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface RegenResult {
|
|
199
|
+
regenerated: boolean;
|
|
200
|
+
skillCount: number;
|
|
201
|
+
reason: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function regenerateRegistry(cwd: string, force: boolean): RegenResult {
|
|
205
|
+
const dirs = [...userSkillDirs(), ...projectSkillDirs(cwd)];
|
|
206
|
+
const existingDirs = dirs.filter((d) => existsSync(d));
|
|
207
|
+
const files = existingDirs.flatMap(findSkillFiles).sort();
|
|
208
|
+
const cachePath = join(cwd, CACHE_REL_PATH);
|
|
209
|
+
const registryPath = join(cwd, REGISTRY_REL_PATH);
|
|
210
|
+
const fp = fingerprint(files);
|
|
211
|
+
let cached: string | undefined;
|
|
212
|
+
if (existsSync(cachePath)) {
|
|
213
|
+
try {
|
|
214
|
+
cached = (JSON.parse(readFileSync(cachePath, "utf8")) as { fingerprint?: string }).fingerprint;
|
|
215
|
+
} catch {
|
|
216
|
+
cached = undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!force && cached === fp && existsSync(registryPath)) {
|
|
220
|
+
return { regenerated: false, skillCount: 0, reason: "cache-hit" };
|
|
221
|
+
}
|
|
222
|
+
const entries = files
|
|
223
|
+
.map(loadSkill)
|
|
224
|
+
.filter((e): e is SkillEntry => Boolean(e));
|
|
225
|
+
const deduped = dedupeBySkillName(entries, cwd);
|
|
226
|
+
const sources = existingDirs.map((d) => {
|
|
227
|
+
const rel = relative(cwd, d);
|
|
228
|
+
return rel.startsWith("..") ? d : rel || ".";
|
|
229
|
+
});
|
|
230
|
+
const md = renderRegistry(cwd, sources, deduped);
|
|
231
|
+
mkdirSync(join(cwd, ".atl"), { recursive: true });
|
|
232
|
+
writeFileSync(registryPath, md);
|
|
233
|
+
writeFileSync(cachePath, JSON.stringify({ fingerprint: fp }, null, 2));
|
|
234
|
+
return { regenerated: true, skillCount: deduped.length, reason: force ? "forced" : "fingerprint-changed" };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default function (pi: ExtensionAPI) {
|
|
238
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
239
|
+
try {
|
|
240
|
+
const result = regenerateRegistry(ctx.cwd, false);
|
|
241
|
+
if (result.regenerated && ctx.hasUI) {
|
|
242
|
+
ctx.ui.notify(`Skill registry refreshed (${result.skillCount} skills)`, "info");
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (ctx.hasUI) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
ctx.ui.notify(`Skill registry refresh failed: ${message}`, "warning");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
pi.registerCommand("skill-registry:refresh", {
|
|
253
|
+
description: "Regenerate .atl/skill-registry.md from local skill sources.",
|
|
254
|
+
handler: async (_args, ctx) => {
|
|
255
|
+
try {
|
|
256
|
+
const result = regenerateRegistry(ctx.cwd, true);
|
|
257
|
+
ctx.ui.notify(
|
|
258
|
+
`Skill registry: ${result.skillCount} skill(s) written to ${REGISTRY_REL_PATH}`,
|
|
259
|
+
"info",
|
|
260
|
+
);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
263
|
+
ctx.ui.notify(`Skill registry refresh failed: ${message}`, "warning");
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gentle-pi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opinionated Gentle Pi harness package: SDD/OpenSpec workflow, strict TDD guidance, subagent assets, and safety defaults.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package",
|
|
9
|
+
"pi",
|
|
10
|
+
"pi-coding-agent",
|
|
11
|
+
"gentle-pi",
|
|
12
|
+
"sdd",
|
|
13
|
+
"openspec",
|
|
14
|
+
"subagents"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Gentleman-Programming/gentle-pi.git",
|
|
19
|
+
"directory": "pi-packages/gentle-ai"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"assets/",
|
|
23
|
+
"extensions/",
|
|
24
|
+
"prompts/",
|
|
25
|
+
"skills/",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"pi": {
|
|
29
|
+
"extensions": [
|
|
30
|
+
"./extensions"
|
|
31
|
+
],
|
|
32
|
+
"prompts": [
|
|
33
|
+
"./prompts"
|
|
34
|
+
],
|
|
35
|
+
"skills": [
|
|
36
|
+
"./skills"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@earendil-works/pi-coding-agent": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/prompts/cl.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit changelog entries before release
|
|
3
|
+
---
|
|
4
|
+
Audit changelog entries for all commits since the last release.
|
|
5
|
+
|
|
6
|
+
## Process
|
|
7
|
+
|
|
8
|
+
1. **Find the last release tag:**
|
|
9
|
+
```bash
|
|
10
|
+
git tag --sort=-version:refname | head -1
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. **List all commits since that tag:**
|
|
14
|
+
```bash
|
|
15
|
+
git log <tag>..HEAD --oneline
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
3. **Read each package's [Unreleased] section:**
|
|
19
|
+
- packages/ai/CHANGELOG.md
|
|
20
|
+
- packages/tui/CHANGELOG.md
|
|
21
|
+
- packages/coding-agent/CHANGELOG.md
|
|
22
|
+
|
|
23
|
+
4. **For each commit, check:**
|
|
24
|
+
- Skip: changelog updates, doc-only changes, release housekeeping
|
|
25
|
+
- Skip: changes to generated model catalogs (for example `packages/ai/src/models.generated.ts`) unless accompanied by an intentional product-facing change in non-generated source/docs.
|
|
26
|
+
- Determine which package(s) the commit affects (use `git show <hash> --stat`)
|
|
27
|
+
- Verify a changelog entry exists in the affected package(s)
|
|
28
|
+
- For external contributions (PRs), verify format: `Description ([#N](url) by [@user](url))`
|
|
29
|
+
|
|
30
|
+
5. **Cross-package duplication rule:**
|
|
31
|
+
Changes in `ai`, `agent` or `tui` that affect end users should be duplicated to `coding-agent` changelog, since coding-agent is the user-facing package that depends on them.
|
|
32
|
+
|
|
33
|
+
6. **Add New Features section after changelog fixes:**
|
|
34
|
+
- Insert a `### New Features` section at the start of `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`.
|
|
35
|
+
- Propose the top new features to the user for confirmation before writing them.
|
|
36
|
+
- Link to relevant docs and sections whenever possible.
|
|
37
|
+
|
|
38
|
+
7. **Report:**
|
|
39
|
+
- List commits with missing entries
|
|
40
|
+
- List entries that need cross-package duplication
|
|
41
|
+
- Add any missing entries directly
|
|
42
|
+
|
|
43
|
+
## Changelog Format Reference
|
|
44
|
+
|
|
45
|
+
Sections (in order):
|
|
46
|
+
- `### Breaking Changes` - API changes requiring migration
|
|
47
|
+
- `### Added` - New features
|
|
48
|
+
- `### Changed` - Changes to existing functionality
|
|
49
|
+
- `### Fixed` - Bug fixes
|
|
50
|
+
- `### Removed` - Removed features
|
|
51
|
+
|
|
52
|
+
Attribution:
|
|
53
|
+
- Internal: `Fixed foo ([#123](https://github.com/earendil-works/pi-mono/issues/123))`
|
|
54
|
+
- External: `Added bar ([#456](https://github.com/earendil-works/pi-mono/pull/456) by [@user](https://github.com/user))`
|
package/prompts/is.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Analyze GitHub issues (bugs or feature requests)
|
|
3
|
+
argument-hint: "<issue>"
|
|
4
|
+
---
|
|
5
|
+
Analyze GitHub issue(s): $ARGUMENTS
|
|
6
|
+
|
|
7
|
+
For each issue:
|
|
8
|
+
|
|
9
|
+
1. Add the `inprogress` label to the issue via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
|
|
10
|
+
2. Read the issue in full, including all comments and linked issues/PRs.
|
|
11
|
+
3. Do not trust analysis written in the issue. Independently verify behavior and derive your own analysis from the code and execution path.
|
|
12
|
+
|
|
13
|
+
4. **For bugs**:
|
|
14
|
+
- Ignore any root cause analysis in the issue (likely wrong)
|
|
15
|
+
- Read all related code files in full (no truncation)
|
|
16
|
+
- Trace the code path and identify the actual root cause
|
|
17
|
+
- Propose a fix
|
|
18
|
+
|
|
19
|
+
5. **For feature requests**:
|
|
20
|
+
- Do not trust implementation proposals in the issue without verification
|
|
21
|
+
- Read all related code files in full (no truncation)
|
|
22
|
+
- Propose the most concise implementation approach
|
|
23
|
+
- List affected files and changes needed
|
|
24
|
+
|
|
25
|
+
Do NOT implement unless explicitly asked. Analyze and propose only.
|
package/prompts/pr.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review PRs from URLs with structured issue and code analysis
|
|
3
|
+
argument-hint: "<PR-URL>"
|
|
4
|
+
---
|
|
5
|
+
You are given one or more GitHub PR URLs: $@
|
|
6
|
+
|
|
7
|
+
For each PR URL, do the following in order:
|
|
8
|
+
1. Add the `inprogress` label to the PR via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
|
|
9
|
+
2. Read the PR page in full. Include description, all comments, all commits, and all changed files.
|
|
10
|
+
3. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
|
|
11
|
+
4. Analyze the PR diff. Read all relevant code files in full with no truncation from the current main branch and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
|
|
12
|
+
5. Check for a changelog entry in the relevant `packages/*/CHANGELOG.md` files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
|
|
13
|
+
- Entry uses correct section (`### Breaking Changes`, `### Added`, `### Fixed`, etc.)
|
|
14
|
+
- External contributions include PR link and author: `Fixed foo ([#123](https://github.com/earendil-works/pi-mono/pull/123) by [@user](https://github.com/user))`
|
|
15
|
+
- Breaking changes are in `### Breaking Changes`, not just `### Fixed`
|
|
16
|
+
6. Check if packages/coding-agent/README.md, packages/coding-agent/docs/*.md, packages/coding-agent/examples/**/*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
|
|
17
|
+
7. Provide a structured review with these sections:
|
|
18
|
+
- Good: solid choices or improvements
|
|
19
|
+
- Bad: concrete issues, regressions, missing tests, or risks
|
|
20
|
+
- Ugly: subtle or high impact problems
|
|
21
|
+
8. Add Questions or Assumptions if anything is unclear.
|
|
22
|
+
9. Add Change summary and Tests.
|
|
23
|
+
|
|
24
|
+
Output format per PR:
|
|
25
|
+
PR: <url>
|
|
26
|
+
Changelog:
|
|
27
|
+
- ...
|
|
28
|
+
Good:
|
|
29
|
+
- ...
|
|
30
|
+
Bad:
|
|
31
|
+
- ...
|
|
32
|
+
Ugly:
|
|
33
|
+
- ...
|
|
34
|
+
Questions or Assumptions:
|
|
35
|
+
- ...
|
|
36
|
+
Change summary:
|
|
37
|
+
- ...
|
|
38
|
+
Tests:
|
|
39
|
+
- ...
|
|
40
|
+
|
|
41
|
+
If no issues are found, say so under Bad and Ugly.
|
package/prompts/wr.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Finish the current task end-to-end with changelog, commit, and push
|
|
3
|
+
argument-hint: "[instructions]"
|
|
4
|
+
---
|
|
5
|
+
Wrap it.
|
|
6
|
+
|
|
7
|
+
Additional instructions: $ARGUMENTS
|
|
8
|
+
|
|
9
|
+
Determine context from the conversation history first.
|
|
10
|
+
|
|
11
|
+
Rules for context detection:
|
|
12
|
+
- If the conversation already mentions a GitHub issue or PR, use that existing context.
|
|
13
|
+
- If the work came from `/is` or `/pr`, assume the issue or PR context is already known from the conversation and from the analysis work already done.
|
|
14
|
+
- If there is no GitHub issue or PR in the conversation history, treat this as non-GitHub work.
|
|
15
|
+
|
|
16
|
+
Unless I explicitly override something in this request, do the following in order:
|
|
17
|
+
|
|
18
|
+
1. Add or update the relevant package changelog entry under `## [Unreleased]` using the repo changelog rules.
|
|
19
|
+
2. If this task is tied to a GitHub issue or PR and a final issue or PR comment has not already been posted in this session, draft it in my tone, preview it, and post exactly one final comment.
|
|
20
|
+
3. Commit only files you changed in this session.
|
|
21
|
+
4. If this task is tied to exactly one GitHub issue, include `closes #<issue>` in the commit message. If it is tied to multiple issues, stop and ask which one to use. If it is not tied to any issue, do not include `closes #` or `fixes #` in the commit message.
|
|
22
|
+
5. Check the current git branch. If it is not `main`, stop and ask what to do. Do not push from another branch unless I explicitly say so.
|
|
23
|
+
6. Push the current branch.
|
|
24
|
+
|
|
25
|
+
Constraints:
|
|
26
|
+
- Never stage unrelated files.
|
|
27
|
+
- Never use `git add .` or `git add -A`.
|
|
28
|
+
- Run required checks before committing if code changed.
|
|
29
|
+
- Do not open a PR unless I explicitly ask.
|
|
30
|
+
- If this is not GitHub issue or PR work, do not post a GitHub comment.
|
|
31
|
+
- If a final issue or PR comment was already posted in this session, do not post another one unless I explicitly ask.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: branch-pr
|
|
3
|
+
description: "Create Gentle AI pull requests with issue-first checks. Trigger: creating, opening, or preparing PRs for review."
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: gentleman-programming
|
|
7
|
+
version: "2.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
Use this skill when:
|
|
13
|
+
- Creating a pull request for any change
|
|
14
|
+
- Preparing a branch for submission
|
|
15
|
+
- Helping a contributor open a PR
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Critical Rules
|
|
20
|
+
|
|
21
|
+
1. **Every PR MUST link an approved issue** — no exceptions
|
|
22
|
+
2. **Every PR MUST have exactly one `type:*` label**
|
|
23
|
+
3. **Automated checks must pass** before merge is possible
|
|
24
|
+
4. **Blank PRs without issue linkage will be blocked** by GitHub Actions
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
1. Verify issue has `status:approved` label
|
|
32
|
+
2. Create branch: type/description (see Branch Naming below)
|
|
33
|
+
3. Implement changes with conventional commits
|
|
34
|
+
4. Run shellcheck on modified scripts
|
|
35
|
+
5. Open PR using the template
|
|
36
|
+
6. Add exactly one type:* label
|
|
37
|
+
7. Wait for automated checks to pass
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Branch Naming
|
|
43
|
+
|
|
44
|
+
Branch names MUST match this regex:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
^(feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\/[a-z0-9._-]+$
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Format:** `type/description` — lowercase, no spaces, only `a-z0-9._-` in description.
|
|
51
|
+
|
|
52
|
+
| Type | Branch pattern | Example |
|
|
53
|
+
|------|---------------|---------|
|
|
54
|
+
| Feature | `feat/<description>` | `feat/user-login` |
|
|
55
|
+
| Bug fix | `fix/<description>` | `fix/zsh-glob-error` |
|
|
56
|
+
| Chore | `chore/<description>` | `chore/update-ci-actions` |
|
|
57
|
+
| Docs | `docs/<description>` | `docs/installation-guide` |
|
|
58
|
+
| Style | `style/<description>` | `style/format-scripts` |
|
|
59
|
+
| Refactor | `refactor/<description>` | `refactor/extract-shared-logic` |
|
|
60
|
+
| Performance | `perf/<description>` | `perf/reduce-startup-time` |
|
|
61
|
+
| Test | `test/<description>` | `test/add-setup-coverage` |
|
|
62
|
+
| Build | `build/<description>` | `build/update-shellcheck` |
|
|
63
|
+
| CI | `ci/<description>` | `ci/add-branch-validation` |
|
|
64
|
+
| Revert | `revert/<description>` | `revert/broken-setup-change` |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## PR Body Format
|
|
69
|
+
|
|
70
|
+
The PR template is at `.github/PULL_REQUEST_TEMPLATE.md`. Every PR body MUST contain:
|
|
71
|
+
|
|
72
|
+
### 1. Linked Issue (REQUIRED)
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
Closes #<issue-number>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Valid keywords: `Closes #N`, `Fixes #N`, `Resolves #N` (case insensitive).
|
|
79
|
+
The linked issue MUST have the `status:approved` label.
|
|
80
|
+
|
|
81
|
+
### 2. PR Type (REQUIRED)
|
|
82
|
+
|
|
83
|
+
Check exactly ONE in the template and add the matching label:
|
|
84
|
+
|
|
85
|
+
| Checkbox | Label to add |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| Bug fix | `type:bug` |
|
|
88
|
+
| New feature | `type:feature` |
|
|
89
|
+
| Documentation only | `type:docs` |
|
|
90
|
+
| Code refactoring | `type:refactor` |
|
|
91
|
+
| Maintenance/tooling | `type:chore` |
|
|
92
|
+
| Breaking change | `type:breaking-change` |
|
|
93
|
+
|
|
94
|
+
### 3. Summary
|
|
95
|
+
|
|
96
|
+
1-3 bullet points of what the PR does.
|
|
97
|
+
|
|
98
|
+
### 4. Changes Table
|
|
99
|
+
|
|
100
|
+
```markdown
|
|
101
|
+
| File | Change |
|
|
102
|
+
|------|--------|
|
|
103
|
+
| `path/to/file` | What changed |
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 5. Test Plan
|
|
107
|
+
|
|
108
|
+
```markdown
|
|
109
|
+
- [x] Scripts run without errors: `shellcheck scripts/*.sh`
|
|
110
|
+
- [x] Manually tested the affected functionality
|
|
111
|
+
- [x] Skills load correctly in target agent
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 6. Contributor Checklist
|
|
115
|
+
|
|
116
|
+
All boxes must be checked:
|
|
117
|
+
- Linked an approved issue
|
|
118
|
+
- Added exactly one `type:*` label
|
|
119
|
+
- Ran shellcheck on modified scripts
|
|
120
|
+
- Skills tested in at least one agent
|
|
121
|
+
- Docs updated if behavior changed
|
|
122
|
+
- Conventional commit format
|
|
123
|
+
- No `Co-Authored-By` trailers
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Automated Checks (all must pass)
|
|
128
|
+
|
|
129
|
+
| Check | Job name | What it verifies |
|
|
130
|
+
|-------|----------|-----------------|
|
|
131
|
+
| PR Validation | `Check Issue Reference` | Body contains `Closes/Fixes/Resolves #N` |
|
|
132
|
+
| PR Validation | `Check Issue Has status:approved` | Linked issue has `status:approved` |
|
|
133
|
+
| PR Validation | `Check PR Has type:* Label` | PR has exactly one `type:*` label |
|
|
134
|
+
| CI | `Shellcheck` | Shell scripts pass `shellcheck` |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Conventional Commits
|
|
139
|
+
|
|
140
|
+
Commit messages MUST match this regex:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9\._-]+\))?!?: .+
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Format:** `type(scope): description` or `type: description`
|
|
147
|
+
|
|
148
|
+
- `type` — required, one of: `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`
|
|
149
|
+
- `(scope)` — optional, lowercase with `a-z0-9._-`
|
|
150
|
+
- `!` — optional, indicates breaking change
|
|
151
|
+
- `description` — required, starts after `: `
|
|
152
|
+
|
|
153
|
+
Type-to-label mapping:
|
|
154
|
+
|
|
155
|
+
| Commit type | PR label |
|
|
156
|
+
|-------------|----------|
|
|
157
|
+
| `feat` | `type:feature` |
|
|
158
|
+
| `fix` | `type:bug` |
|
|
159
|
+
| `docs` | `type:docs` |
|
|
160
|
+
| `refactor` | `type:refactor` |
|
|
161
|
+
| `chore` | `type:chore` |
|
|
162
|
+
| `style` | `type:chore` |
|
|
163
|
+
| `perf` | `type:feature` |
|
|
164
|
+
| `test` | `type:chore` |
|
|
165
|
+
| `build` | `type:chore` |
|
|
166
|
+
| `ci` | `type:chore` |
|
|
167
|
+
| `revert` | `type:bug` |
|
|
168
|
+
| `feat!` / `fix!` | `type:breaking-change` |
|
|
169
|
+
|
|
170
|
+
Examples:
|
|
171
|
+
```
|
|
172
|
+
feat(scripts): add Codex support to setup.sh
|
|
173
|
+
fix(skills): correct topic key format in sdd-apply
|
|
174
|
+
docs(readme): update multi-model configuration guide
|
|
175
|
+
refactor(skills): extract shared persistence logic
|
|
176
|
+
chore(ci): add shellcheck to PR validation workflow
|
|
177
|
+
perf(scripts): reduce setup.sh execution time
|
|
178
|
+
style(skills): fix markdown formatting
|
|
179
|
+
test(scripts): add setup.sh integration tests
|
|
180
|
+
ci(workflows): add branch name validation
|
|
181
|
+
revert: undo broken setup change
|
|
182
|
+
feat!: redesign skill loading system
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Commands
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Create branch
|
|
191
|
+
git checkout -b feat/my-feature main
|
|
192
|
+
|
|
193
|
+
# Run shellcheck before pushing
|
|
194
|
+
shellcheck scripts/*.sh
|
|
195
|
+
|
|
196
|
+
# Push and create PR
|
|
197
|
+
git push -u origin feat/my-feature
|
|
198
|
+
gh pr create --title "feat(scope): description" --body "Closes #N"
|
|
199
|
+
|
|
200
|
+
# Add type label to PR
|
|
201
|
+
gh pr edit <pr-number> --add-label "type:feature"
|
|
202
|
+
```
|