@geminixiang/pi-simplify 0.1.0 → 0.1.3
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/CHANGELOG.md +20 -0
- package/README.md +18 -7
- package/package.json +12 -16
- package/src/code-smell/workflow.ts +7 -1
- package/src/repository-path.ts +19 -0
- package/src/simplify/workflow.ts +54 -10
- package/.github/workflows/publish.yml +0 -56
- package/.pi/skills/release/SKILL.md +0 -146
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.3] - 2026-09-07
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Move repository-relative path validation from tool-schema regex lookaheads to runtime checks for better provider compatibility in `/simplify` and `/code-smell`.
|
|
14
|
+
- Reject absolute paths and parent-directory traversal using either forward slashes or backslashes.
|
|
15
|
+
|
|
16
|
+
## [0.1.2] - 2026-07-26
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Move package development and trusted publishing to the `geminixiang/pi-stuff` monorepo.
|
|
21
|
+
- Declare Pi runtime packages as peer dependencies to follow Pi package conventions.
|
|
22
|
+
|
|
23
|
+
## [0.1.1] - 2026-06-09
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Add `/simplify previous` mode to review only the last commit (`HEAD~1..HEAD`), with aliases for `previous-commit`, `prev`, and `last-commit`.
|
|
28
|
+
|
|
9
29
|
## [0.1.0] - 2026-06-03
|
|
10
30
|
|
|
11
31
|
### Added
|
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Scans specific paths directly.
|
|
|
58
58
|
/simplify
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Opens a preset selector. Choose uncommitted changes or a folder snapshot.
|
|
61
|
+
Opens a preset selector. Choose uncommitted changes, the previous commit, or a folder snapshot.
|
|
62
62
|
|
|
63
63
|
Uncommitted mode analyzes all git changes and presents cleanup candidates:
|
|
64
64
|
|
|
@@ -66,6 +66,16 @@ Uncommitted mode analyzes all git changes and presents cleanup candidates:
|
|
|
66
66
|
- **Confirm** (yellow) - delete after user confirms
|
|
67
67
|
- **Review** (orange) - user should review first
|
|
68
68
|
|
|
69
|
+
### Simplify Previous Commit
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
/simplify previous
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Analyzes only the last commit (`git diff HEAD~1..HEAD`), even if there are uncommitted local changes.
|
|
76
|
+
|
|
77
|
+
Aliases: `/simplify previous-commit`, `/simplify prev`, `/simplify last-commit`.
|
|
78
|
+
|
|
69
79
|
### Simplify Folders
|
|
70
80
|
|
|
71
81
|
```
|
|
@@ -83,12 +93,13 @@ Analyzes the specified folders/files as a snapshot (not a diff), even when there
|
|
|
83
93
|
|
|
84
94
|
## Commands
|
|
85
95
|
|
|
86
|
-
| Command
|
|
87
|
-
|
|
|
88
|
-
| `/simplify`
|
|
89
|
-
| `/simplify
|
|
90
|
-
| `/
|
|
91
|
-
| `/code-smell
|
|
96
|
+
| Command | Purpose |
|
|
97
|
+
| -------------------------- | --------------------------------------------------------------------------- |
|
|
98
|
+
| `/simplify` | Delete excess after a feature: dead code, debug remnants, thin wrappers |
|
|
99
|
+
| `/simplify previous` | Same as above but scoped to the previous commit (`HEAD~1..HEAD`) |
|
|
100
|
+
| `/simplify folder <paths>` | Same as above but snapshot mode (no git diff needed) |
|
|
101
|
+
| `/code-smell` | Find structural problems: coupling, complexity, error handling, performance |
|
|
102
|
+
| `/code-smell <paths>` | Scan specific paths for code smells |
|
|
92
103
|
|
|
93
104
|
## Comparison with pi-review
|
|
94
105
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geminixiang/pi-simplify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Simplify: Review changed code for reuse, quality, and efficiency",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -9,24 +9,20 @@
|
|
|
9
9
|
"author": "geminixiang",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/geminixiang/pi-
|
|
12
|
+
"url": "https://github.com/geminixiang/pi-stuff.git",
|
|
13
|
+
"directory": "packages/pi-simplify"
|
|
13
14
|
},
|
|
14
15
|
"main": "index.ts",
|
|
15
|
-
"scripts": {
|
|
16
|
-
"lint": "oxlint",
|
|
17
|
-
"lint:fix": "oxlint --fix",
|
|
18
|
-
"fmt": "oxfmt",
|
|
19
|
-
"fmt:check": "oxfmt --check"
|
|
20
|
-
},
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"@earendil-works/pi-coding-agent": ">=0.74.0",
|
|
23
|
-
"@earendil-works/pi-tui": ">=0.74.0",
|
|
24
|
-
"typebox": "^1.1.38"
|
|
25
|
-
},
|
|
26
16
|
"devDependencies": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
17
|
+
"@earendil-works/pi-coding-agent": "^0.82.1",
|
|
18
|
+
"@earendil-works/pi-tui": "^0.82.1",
|
|
19
|
+
"typebox": "^1.1.38",
|
|
20
|
+
"typescript": "7.0.2"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
24
|
+
"@earendil-works/pi-tui": "*",
|
|
25
|
+
"typebox": "*"
|
|
30
26
|
},
|
|
31
27
|
"pi": {
|
|
32
28
|
"extensions": [
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { Container, SelectList, Text, type SelectItem } from "@earendil-works/pi-tui";
|
|
10
10
|
import { Type } from "typebox";
|
|
11
11
|
|
|
12
|
+
import { assertRepositoryRelativePaths } from "../repository-path.js";
|
|
12
13
|
import { runProgrammaticChecks } from "./checks.js";
|
|
13
14
|
import { CODE_SMELL_PROMPT } from "./prompt.js";
|
|
14
15
|
import { showFindingSelector } from "./selector.js";
|
|
@@ -164,7 +165,10 @@ function registerFindingsTool(
|
|
|
164
165
|
category: categorySchema,
|
|
165
166
|
severity: severitySchema,
|
|
166
167
|
confidence: confidenceSchema,
|
|
167
|
-
file: Type.String({
|
|
168
|
+
file: Type.String({
|
|
169
|
+
description: "Repository-relative path without '..' segments",
|
|
170
|
+
minLength: 1,
|
|
171
|
+
}),
|
|
168
172
|
lines: Type.String(),
|
|
169
173
|
smell: Type.String({ minLength: 1 }),
|
|
170
174
|
evidence: Type.String({ minLength: 1 }),
|
|
@@ -176,6 +180,8 @@ function registerFindingsTool(
|
|
|
176
180
|
}),
|
|
177
181
|
async execute(_toolCallId, params) {
|
|
178
182
|
const findings: CodeSmellFinding[] = params.findings;
|
|
183
|
+
assertRepositoryRelativePaths(findings.map((finding) => finding.file));
|
|
184
|
+
|
|
179
185
|
const resolve = state.getResolver();
|
|
180
186
|
if (!resolve) {
|
|
181
187
|
return {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function isRepositoryRelativePath(file: string): boolean {
|
|
2
|
+
const normalized = file.replaceAll("\\", "/");
|
|
3
|
+
|
|
4
|
+
return (
|
|
5
|
+
file.length > 0 &&
|
|
6
|
+
!normalized.startsWith("/") &&
|
|
7
|
+
!/^[A-Za-z]:\//.test(normalized) &&
|
|
8
|
+
!normalized.split("/").includes("..")
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function assertRepositoryRelativePaths(files: string[]): void {
|
|
13
|
+
const invalidFiles = files.filter((file) => !isRepositoryRelativePath(file));
|
|
14
|
+
if (invalidFiles.length === 0) return;
|
|
15
|
+
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Expected repository-relative file paths without '..' segments; received: ${invalidFiles.join(", ")}`,
|
|
18
|
+
);
|
|
19
|
+
}
|
package/src/simplify/workflow.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { Container, SelectList, Text, type SelectItem } from "@earendil-works/pi-tui";
|
|
9
9
|
import { Type } from "typebox";
|
|
10
10
|
|
|
11
|
+
import { assertRepositoryRelativePaths } from "../repository-path.js";
|
|
11
12
|
import { SIMPLIFY_PROMPT } from "./prompt.js";
|
|
12
13
|
import { showCandidateSelector } from "./selector.js";
|
|
13
14
|
import type { SimplifyResult } from "./types.js";
|
|
@@ -29,7 +30,10 @@ const actionSchema = Type.Union([
|
|
|
29
30
|
Type.Literal("parallelize"),
|
|
30
31
|
]);
|
|
31
32
|
|
|
32
|
-
type SimplifyTarget =
|
|
33
|
+
type SimplifyTarget =
|
|
34
|
+
| { type: "uncommitted" }
|
|
35
|
+
| { type: "previous-commit" }
|
|
36
|
+
| { type: "folder"; paths: string[] };
|
|
33
37
|
type ParsedArgs = {
|
|
34
38
|
target: SimplifyTarget | null;
|
|
35
39
|
additionalFocus?: string;
|
|
@@ -38,6 +42,11 @@ type ParsedArgs = {
|
|
|
38
42
|
|
|
39
43
|
const SIMPLIFY_PRESETS = [
|
|
40
44
|
{ value: "uncommitted", label: "Simplify uncommitted changes", description: "" },
|
|
45
|
+
{
|
|
46
|
+
value: "previous-commit",
|
|
47
|
+
label: "Simplify previous commit",
|
|
48
|
+
description: "(diff: HEAD~1..HEAD)",
|
|
49
|
+
},
|
|
41
50
|
{ value: "folder", label: "Simplify a folder (or more)", description: "(snapshot, not diff)" },
|
|
42
51
|
] as const;
|
|
43
52
|
|
|
@@ -55,14 +64,25 @@ function parseArgs(args: string): ParsedArgs {
|
|
|
55
64
|
if (!trimmed) return { target: null };
|
|
56
65
|
|
|
57
66
|
const [subcommand, ...rest] = trimmed.split(/\s+/);
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
const normalizedSubcommand = subcommand?.toLowerCase();
|
|
68
|
+
|
|
69
|
+
if (normalizedSubcommand === "folder") {
|
|
70
|
+
const paths = parseSimplifyPaths(rest.join(" "));
|
|
71
|
+
if (paths.length === 0)
|
|
72
|
+
return { target: null, error: "Usage: /simplify folder <path> [path...]" };
|
|
73
|
+
return { target: { type: "folder", paths } };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
normalizedSubcommand === "previous" ||
|
|
78
|
+
normalizedSubcommand === "previous-commit" ||
|
|
79
|
+
normalizedSubcommand === "prev" ||
|
|
80
|
+
normalizedSubcommand === "last-commit"
|
|
81
|
+
) {
|
|
82
|
+
return { target: { type: "previous-commit" }, additionalFocus: rest.join(" ") || undefined };
|
|
60
83
|
}
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
if (paths.length === 0)
|
|
64
|
-
return { target: null, error: "Usage: /simplify folder <path> [path...]" };
|
|
65
|
-
return { target: { type: "folder", paths } };
|
|
85
|
+
return { target: { type: "uncommitted" }, additionalFocus: trimmed };
|
|
66
86
|
}
|
|
67
87
|
|
|
68
88
|
async function hasUncommittedChanges(pi: ExtensionAPI): Promise<boolean> {
|
|
@@ -70,6 +90,11 @@ async function hasUncommittedChanges(pi: ExtensionAPI): Promise<boolean> {
|
|
|
70
90
|
return code === 0 && stdout.trim().length > 0;
|
|
71
91
|
}
|
|
72
92
|
|
|
93
|
+
async function hasPreviousCommit(pi: ExtensionAPI): Promise<boolean> {
|
|
94
|
+
const { code } = await pi.exec("git", ["rev-parse", "--verify", "HEAD~1"]);
|
|
95
|
+
return code === 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
73
98
|
function buildSimplifyPrompt(target: SimplifyTarget, additionalFocus?: string): string {
|
|
74
99
|
let fullPrompt = SIMPLIFY_PROMPT;
|
|
75
100
|
|
|
@@ -77,6 +102,11 @@ function buildSimplifyPrompt(target: SimplifyTarget, additionalFocus?: string):
|
|
|
77
102
|
fullPrompt += `\n\n## Scope Override: Folder Snapshot\n\nReview only the code in these repository-relative paths: ${target.paths.join(", ")}\n\nThis is a snapshot review, not a git diff review. Read files directly under these paths instead of relying on \`git diff\`. Only return candidates whose \`file\` is inside one of these paths.`;
|
|
78
103
|
}
|
|
79
104
|
|
|
105
|
+
if (target.type === "previous-commit") {
|
|
106
|
+
fullPrompt +=
|
|
107
|
+
"\n\n## Scope Override: Previous Commit\n\nReview the previous commit only. Run `git diff HEAD~1..HEAD` to see what changed in the last commit, and treat that diff as the review scope. Do not use uncommitted changes as the analysis target. Only return candidates caused by the previous commit's diff.";
|
|
108
|
+
}
|
|
109
|
+
|
|
80
110
|
if (additionalFocus?.trim()) fullPrompt += `\n\n## Additional Focus\n\n${additionalFocus.trim()}`;
|
|
81
111
|
return fullPrompt;
|
|
82
112
|
}
|
|
@@ -95,9 +125,8 @@ function registerSimplifyCandidatesTool(
|
|
|
95
125
|
category: categorySchema,
|
|
96
126
|
risk: riskSchema,
|
|
97
127
|
file: Type.String({
|
|
98
|
-
description: "Repository-relative path",
|
|
128
|
+
description: "Repository-relative path without '..' segments",
|
|
99
129
|
minLength: 1,
|
|
100
|
-
pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$",
|
|
101
130
|
}),
|
|
102
131
|
lines: Type.String({ description: "Line number or range, or empty string if unknown" }),
|
|
103
132
|
rootIssue: Type.String({
|
|
@@ -133,6 +162,8 @@ function registerSimplifyCandidatesTool(
|
|
|
133
162
|
parameters,
|
|
134
163
|
async execute(_toolCallId, params) {
|
|
135
164
|
const candidates: SimplifyResult[] = params.candidates;
|
|
165
|
+
assertRepositoryRelativePaths(candidates.map((candidate) => candidate.file));
|
|
166
|
+
|
|
136
167
|
const resolve = state.getResolver();
|
|
137
168
|
if (!resolve) {
|
|
138
169
|
return {
|
|
@@ -243,6 +274,7 @@ async function showSimplifyTargetSelector(
|
|
|
243
274
|
|
|
244
275
|
if (!result) return null;
|
|
245
276
|
if (result === "uncommitted") return { type: "uncommitted" };
|
|
277
|
+
if (result === "previous-commit") return { type: "previous-commit" };
|
|
246
278
|
|
|
247
279
|
const target = await showFolderInput(ctx);
|
|
248
280
|
if (target) return target;
|
|
@@ -354,8 +386,20 @@ export function registerSimplifyWorkflow(pi: ExtensionAPI) {
|
|
|
354
386
|
return;
|
|
355
387
|
}
|
|
356
388
|
|
|
389
|
+
if (target.type === "previous-commit" && !(await hasPreviousCommit(pi))) {
|
|
390
|
+
ctx.ui.notify(
|
|
391
|
+
"No previous commit found. Need at least two commits to use this mode.",
|
|
392
|
+
"info",
|
|
393
|
+
);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
357
397
|
const targetHint =
|
|
358
|
-
target.type === "folder"
|
|
398
|
+
target.type === "folder"
|
|
399
|
+
? `folders: ${target.paths.join(", ")}`
|
|
400
|
+
: target.type === "previous-commit"
|
|
401
|
+
? "previous commit"
|
|
402
|
+
: "uncommitted changes";
|
|
359
403
|
ctx.ui.notify(`Analyzing ${targetHint} for review findings...`, "info");
|
|
360
404
|
|
|
361
405
|
const fullPrompt = buildSimplifyPrompt(target, parsed.additionalFocus);
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
name: CI / Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
push:
|
|
7
|
-
branches: [main]
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
permissions:
|
|
11
|
-
id-token: write
|
|
12
|
-
contents: read
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
ci:
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
|
|
20
|
-
- uses: actions/setup-node@v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: "24"
|
|
23
|
-
registry-url: "https://registry.npmjs.org"
|
|
24
|
-
|
|
25
|
-
- name: Install dependencies
|
|
26
|
-
run: npm ci
|
|
27
|
-
|
|
28
|
-
- name: Type check
|
|
29
|
-
run: npx tsc --noEmit --skipLibCheck || echo "No TypeScript, skipping type check"
|
|
30
|
-
|
|
31
|
-
publish:
|
|
32
|
-
needs: ci
|
|
33
|
-
runs-on: ubuntu-latest
|
|
34
|
-
if: github.event_name == 'release'
|
|
35
|
-
steps:
|
|
36
|
-
- uses: actions/checkout@v4
|
|
37
|
-
|
|
38
|
-
- uses: actions/setup-node@v4
|
|
39
|
-
with:
|
|
40
|
-
node-version: "24"
|
|
41
|
-
registry-url: "https://registry.npmjs.org"
|
|
42
|
-
|
|
43
|
-
- name: Update npm
|
|
44
|
-
run: npm install -g npm@latest
|
|
45
|
-
|
|
46
|
-
- name: Install dependencies
|
|
47
|
-
run: npm ci
|
|
48
|
-
|
|
49
|
-
- name: Publish to npm
|
|
50
|
-
run: |
|
|
51
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
52
|
-
if [[ "$VERSION" == *-* ]]; then
|
|
53
|
-
npm publish --provenance --access public --tag beta
|
|
54
|
-
else
|
|
55
|
-
npm publish --provenance --access public
|
|
56
|
-
fi
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: release
|
|
3
|
-
description: Prepare and publish a new pi-simplify release. Use when bumping the version, committing and pushing it, and creating a GitHub release in the style of earlier pi-simplify releases.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# pi-simplify Release
|
|
7
|
-
|
|
8
|
-
Repo defaults:
|
|
9
|
-
|
|
10
|
-
- branch: `main`
|
|
11
|
-
- remote: `origin`
|
|
12
|
-
- repo: `geminixiang/pi-simplify`
|
|
13
|
-
- npm package: `@geminixiang/pi-simplify`
|
|
14
|
-
- use `package.json` version for npm/package files, and `v<version>` for the git tag and GitHub release title
|
|
15
|
-
- versions with `-alpha.`, `-beta.`, or `-rc.` are prereleases by default
|
|
16
|
-
|
|
17
|
-
## Version rules
|
|
18
|
-
|
|
19
|
-
Examples:
|
|
20
|
-
|
|
21
|
-
- stable package version: `0.2.1`, `0.3.0`, `1.0.0` → release tag/title: `v0.2.1`, `v0.3.0`, `v1.0.0`
|
|
22
|
-
- prerelease package version: `0.2.0-beta.8`, `0.3.0-rc.1`, `1.0.0-alpha.2` → release tag/title: `v0.2.0-beta.8`, `v0.3.0-rc.1`, `v1.0.0-alpha.2`
|
|
23
|
-
|
|
24
|
-
Guidance:
|
|
25
|
-
|
|
26
|
-
- `patch` = bugfix / small maintenance
|
|
27
|
-
- `minor` = backward-compatible features
|
|
28
|
-
- `major` = breaking changes
|
|
29
|
-
- prerelease numbers increase within the same line, e.g. `beta.8 -> beta.9`
|
|
30
|
-
- promote prerelease to stable by dropping the suffix, e.g. `0.2.0-beta.8 -> 0.2.0`
|
|
31
|
-
|
|
32
|
-
## Flow
|
|
33
|
-
|
|
34
|
-
### 1. Check state
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
git status --short
|
|
38
|
-
git branch --show-current
|
|
39
|
-
git remote -v
|
|
40
|
-
git tag --list | tail -20
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Read `package.json` and `package-lock.json`. If unrelated files are modified, ask before committing.
|
|
44
|
-
|
|
45
|
-
### 2. Sync version files
|
|
46
|
-
|
|
47
|
-
Preferred:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm version <version> --no-git-tag-version
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Use this to sync `package.json` and `package-lock.json` without creating an automatic commit or tag. If the user already edited `package.json`, just verify `package-lock.json` matches.
|
|
54
|
-
|
|
55
|
-
### 3. Update CHANGELOG
|
|
56
|
-
|
|
57
|
-
`CHANGELOG.md` follows Keep a Changelog with `### Added / Changed / Fixed / Removed / Security / Performance / Tests` subsections. The newest release sits at the top under an `## [Unreleased]` placeholder.
|
|
58
|
-
|
|
59
|
-
Gather user-visible changes since the previous tag:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
git log --pretty=format:'%h %s' <previous-tag>..HEAD | grep -v "^[a-f0-9]* chore: bump version"
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Then in `CHANGELOG.md`:
|
|
66
|
-
|
|
67
|
-
- Keep `## [Unreleased]` at the top as an empty placeholder.
|
|
68
|
-
- Insert a new section `## [<version>] - <YYYY-MM-DD>` (use today's date for stable, omit the date for prereleases to match the existing style).
|
|
69
|
-
- Group entries by subsection; one bullet per user-visible change, imperative voice, no commit hashes.
|
|
70
|
-
- Skip pure internal refactors that don't change behavior unless they affect contributors (then list under `### Changed`).
|
|
71
|
-
|
|
72
|
-
If you generated draft release notes in step 5 first, copy the same wording into CHANGELOG — they should match.
|
|
73
|
-
|
|
74
|
-
### 4. Commit and push
|
|
75
|
-
|
|
76
|
-
Stage version files and CHANGELOG together — the bump and the changelog entry belong in the same commit.
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
git add package.json package-lock.json CHANGELOG.md
|
|
80
|
-
git commit -m "chore: bump version to <version>"
|
|
81
|
-
git push origin main
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 5. Draft release notes
|
|
85
|
-
|
|
86
|
-
Use the previous GitHub release as the style reference.
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
gh release list --repo geminixiang/pi-simplify --limit 10
|
|
90
|
-
gh release view <previous-tag> --repo geminixiang/pi-simplify --json tagName,name,body,url,publishedAt
|
|
91
|
-
git log --pretty=format:'%h %s' <previous-tag>..HEAD
|
|
92
|
-
git diff --stat <previous-tag>..HEAD
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Write concise notes focused on user-visible changes, usually with:
|
|
96
|
-
|
|
97
|
-
- `## What's changed`
|
|
98
|
-
- `### Highlights`
|
|
99
|
-
- `### Notable changes`
|
|
100
|
-
- `### Docs and maintenance`
|
|
101
|
-
- `### Verification`
|
|
102
|
-
|
|
103
|
-
Write notes to `/tmp/pi-simplify-release-<version>.md`. Keep them consistent with the CHANGELOG entry from step 3. Use `v<previous-version>...v<version>` in compare links.
|
|
104
|
-
|
|
105
|
-
### 6. Create or update release
|
|
106
|
-
|
|
107
|
-
Prerelease:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
gh release create v<version> \
|
|
111
|
-
--repo geminixiang/pi-simplify \
|
|
112
|
-
--target main \
|
|
113
|
-
--title v<version> \
|
|
114
|
-
--notes-file /tmp/pi-simplify-release-<version>.md \
|
|
115
|
-
--prerelease
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Stable:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
gh release create v<version> \
|
|
122
|
-
--repo geminixiang/pi-simplify \
|
|
123
|
-
--target main \
|
|
124
|
-
--title v<version> \
|
|
125
|
-
--notes-file /tmp/pi-simplify-release-<version>.md
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
If it already exists, use `gh release edit v<version> ...` and keep prerelease/stable intent consistent.
|
|
129
|
-
|
|
130
|
-
## Report back
|
|
131
|
-
|
|
132
|
-
Return:
|
|
133
|
-
|
|
134
|
-
- released version
|
|
135
|
-
- stable or prerelease
|
|
136
|
-
- version-bump commit hash
|
|
137
|
-
- push status
|
|
138
|
-
- release URL
|
|
139
|
-
|
|
140
|
-
## Guardrails
|
|
141
|
-
|
|
142
|
-
- Always use `geminixiang/pi-simplify`.
|
|
143
|
-
- Infer stable vs prerelease from the version, or ask.
|
|
144
|
-
- Do not include raw commit hashes in release notes unless requested.
|
|
145
|
-
- If hooks fail during commit, fix or report before retrying.
|
|
146
|
-
- Never publish a release without a corresponding CHANGELOG entry — the version bump commit must include the new CHANGELOG section.
|