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.
Files changed (35) hide show
  1. package/README.md +66 -0
  2. package/assets/agents/sdd-apply.md +71 -0
  3. package/assets/agents/sdd-archive.md +14 -0
  4. package/assets/agents/sdd-design.md +14 -0
  5. package/assets/agents/sdd-explore.md +14 -0
  6. package/assets/agents/sdd-init.md +14 -0
  7. package/assets/agents/sdd-onboard.md +15 -0
  8. package/assets/agents/sdd-proposal.md +14 -0
  9. package/assets/agents/sdd-spec.md +14 -0
  10. package/assets/agents/sdd-tasks.md +61 -0
  11. package/assets/agents/sdd-verify.md +55 -0
  12. package/assets/chains/sdd-full.chain.md +75 -0
  13. package/assets/chains/sdd-plan.chain.md +35 -0
  14. package/assets/chains/sdd-verify.chain.md +27 -0
  15. package/assets/orchestrator.md +191 -0
  16. package/assets/support/strict-tdd-verify.md +269 -0
  17. package/assets/support/strict-tdd.md +364 -0
  18. package/extensions/gentle-ai.ts +157 -0
  19. package/extensions/sdd-init.ts +83 -0
  20. package/extensions/skill-registry.ts +267 -0
  21. package/package.json +47 -0
  22. package/prompts/cl.md +54 -0
  23. package/prompts/is.md +25 -0
  24. package/prompts/pr.md +41 -0
  25. package/prompts/wr.md +31 -0
  26. package/skills/branch-pr/SKILL.md +202 -0
  27. package/skills/chained-pr/SKILL.md +50 -0
  28. package/skills/chained-pr/references/chaining-details.md +99 -0
  29. package/skills/cognitive-doc-design/SKILL.md +81 -0
  30. package/skills/comment-writer/SKILL.md +74 -0
  31. package/skills/gentle-ai/SKILL.md +43 -0
  32. package/skills/issue-creation/SKILL.md +223 -0
  33. package/skills/judgment-day/SKILL.md +52 -0
  34. package/skills/judgment-day/references/prompts-and-formats.md +75 -0
  35. 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
+ ```