ultimate-pi 0.19.1 → 0.22.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 (147) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +68 -2
  2. package/.agents/skills/harness-git-commit/SKILL.md +72 -0
  3. package/.agents/skills/harness-governor/SKILL.md +2 -2
  4. package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
  5. package/.agents/skills/harness-plan/SKILL.md +13 -11
  6. package/.agents/skills/harness-review/SKILL.md +1 -1
  7. package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
  8. package/.agents/skills/sentrux/SKILL.md +4 -2
  9. package/.agents/skills/wiki-save/SKILL.md +1 -1
  10. package/.pi/PACKAGING.md +6 -0
  11. package/.pi/SYSTEM.md +21 -3
  12. package/.pi/agents/harness/ls-lint-steward.md +49 -0
  13. package/.pi/agents/harness/planning/decompose.md +4 -4
  14. package/.pi/agents/harness/reviewing/evaluator.md +1 -1
  15. package/.pi/agents/harness/running/executor.md +43 -2
  16. package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
  17. package/.pi/agents/pi-pi/prompt-expert.md +17 -2
  18. package/.pi/auto-commit.json +9 -2
  19. package/.pi/extensions/debate-orchestrator.ts +3 -0
  20. package/.pi/extensions/harness-anchored-edit.ts +139 -0
  21. package/.pi/extensions/harness-ask-user.ts +13 -34
  22. package/.pi/extensions/harness-debate-tools.ts +43 -4
  23. package/.pi/extensions/harness-live-widget.ts +28 -19
  24. package/.pi/extensions/harness-run-context.ts +278 -115
  25. package/.pi/extensions/harness-web-tools.ts +598 -471
  26. package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
  27. package/.pi/extensions/observation-bus.ts +4 -0
  28. package/.pi/extensions/policy-gate.ts +270 -229
  29. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  30. package/.pi/extensions/soundboard.ts +48 -48
  31. package/.pi/harness/README.md +4 -0
  32. package/.pi/harness/agents.manifest.json +15 -7
  33. package/.pi/harness/agents.policy.yaml +47 -81
  34. package/.pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md +41 -0
  35. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  36. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  37. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  38. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  39. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  40. package/.pi/harness/docs/adrs/README.md +7 -0
  41. package/.pi/harness/docs/practice-map.md +21 -5
  42. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  43. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  44. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  45. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  46. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  47. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  48. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  49. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  50. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  51. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  52. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  53. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  54. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  55. package/.pi/lib/agents-policy.d.mts +26 -47
  56. package/.pi/lib/agents-policy.mjs +84 -29
  57. package/.pi/lib/agents-policy.ts +1 -0
  58. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  59. package/.pi/lib/ask-user/constants.mjs +3 -0
  60. package/.pi/lib/ask-user/constants.ts +4 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  62. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  63. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  64. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  65. package/.pi/lib/ask-user/dialog.ts +2 -314
  66. package/.pi/lib/ask-user/fallback.ts +2 -78
  67. package/.pi/lib/ask-user/format.ts +85 -0
  68. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  69. package/.pi/lib/ask-user/index.ts +114 -0
  70. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  71. package/.pi/lib/ask-user/policy.mjs +43 -0
  72. package/.pi/lib/ask-user/policy.ts +104 -0
  73. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  74. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  75. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  76. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  77. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  78. package/.pi/lib/ask-user/render.ts +40 -9
  79. package/.pi/lib/ask-user/schema.ts +66 -13
  80. package/.pi/lib/ask-user/types.ts +60 -3
  81. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  82. package/.pi/lib/ask-user/validate.ts +53 -34
  83. package/.pi/lib/harness-anchored-edit/.hash_anchors +1721 -0
  84. package/.pi/lib/harness-anchored-edit/anchor-state.ts +320 -0
  85. package/.pi/lib/harness-anchored-edit/apply-anchored-edits.ts +161 -0
  86. package/.pi/lib/harness-anchored-edit/edit-executor.ts +146 -0
  87. package/.pi/lib/harness-anchored-edit/index.ts +9 -0
  88. package/.pi/lib/harness-anchored-edit/line-protocol.ts +38 -0
  89. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  90. package/.pi/lib/harness-anchored-edit/settings.ts +1 -0
  91. package/.pi/lib/harness-anchored-edit/task-id.ts +8 -0
  92. package/.pi/lib/harness-anchored-edit/types.ts +19 -0
  93. package/.pi/lib/harness-artifact-gate.ts +75 -21
  94. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  95. package/.pi/lib/harness-lens/clients/anchored-edit-autopatch.ts +158 -0
  96. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  97. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  98. package/.pi/lib/harness-lens/index.ts +246 -96
  99. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  100. package/.pi/lib/harness-repair-brief.ts +84 -25
  101. package/.pi/lib/harness-run-context.ts +42 -52
  102. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  103. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  104. package/.pi/lib/harness-slash-completions.ts +116 -0
  105. package/.pi/lib/harness-spawn-topology.ts +121 -87
  106. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  107. package/.pi/lib/harness-subagents-bridge.ts +11 -6
  108. package/.pi/lib/harness-ui-state.ts +95 -48
  109. package/.pi/lib/plan-approval/dialog.ts +5 -0
  110. package/.pi/lib/plan-approval/validate.ts +1 -1
  111. package/.pi/lib/plan-approval-readiness.ts +32 -0
  112. package/.pi/lib/plan-debate-gate.ts +154 -114
  113. package/.pi/lib/plan-task-clarification.ts +158 -0
  114. package/.pi/prompts/harness-auto.md +2 -2
  115. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  116. package/.pi/prompts/harness-plan.md +58 -8
  117. package/.pi/prompts/harness-review.md +40 -6
  118. package/.pi/prompts/harness-run.md +33 -11
  119. package/.pi/prompts/harness-setup.md +72 -3
  120. package/.pi/prompts/harness-steer.md +3 -2
  121. package/.pi/prompts/wiki-save.md +5 -4
  122. package/.pi/scripts/README.md +8 -0
  123. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  124. package/.pi/scripts/harness-anchored-edit-smoke.mjs +45 -0
  125. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  126. package/.pi/scripts/harness-cli-verify.sh +47 -0
  127. package/.pi/scripts/harness-git-churn.mjs +77 -0
  128. package/.pi/scripts/harness-git-commit.mjs +173 -0
  129. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  130. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  131. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  132. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  133. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  134. package/.pi/scripts/harness-verify.mjs +347 -117
  135. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  136. package/.pi/scripts/run-tests.mjs +65 -0
  137. package/.pi/settings.example.json +1 -0
  138. package/.sentrux/rules.toml +1 -1
  139. package/AGENTS.md +1 -0
  140. package/CHANGELOG.md +31 -0
  141. package/README.md +13 -4
  142. package/THIRD_PARTY_NOTICES.md +7 -0
  143. package/package.json +8 -3
  144. package/vendor/pi-subagents/src/agents.ts +5 -0
  145. package/vendor/pi-subagents/src/subagents.ts +22 -3
  146. package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
  147. package/.pi/scripts/release.sh +0 -338
@@ -137,6 +137,87 @@ const REASON_MESSAGES: Record<OwnCutCancelReason, string> = {
137
137
  too_few_live_messages: "pi-vcc: Too few messages to compact",
138
138
  };
139
139
 
140
+
141
+ function collectLiveRolesForDiagnostics(
142
+ branchEntries: any[],
143
+ lastCompIdx: number,
144
+ lastKeptId: string | undefined,
145
+ ): string[] {
146
+ const hasPriorCompaction = lastCompIdx >= 0;
147
+ const hasValidKeptId = !!lastKeptId && branchEntries.some((e: any) => e.id === lastKeptId);
148
+ const diagOrphan = hasPriorCompaction && !hasValidKeptId;
149
+ const liveRoles: string[] = [];
150
+ if (diagOrphan) {
151
+ for (let i = lastCompIdx + 1; i < branchEntries.length; i++) {
152
+ const e = branchEntries[i];
153
+ if (e.type === "compaction") continue;
154
+ if (e.type === "message" && e.message) liveRoles.push(e.message.role);
155
+ }
156
+ return liveRoles;
157
+ }
158
+ let foundKept = !lastKeptId;
159
+ for (const e of branchEntries) {
160
+ if (!foundKept && e.id === lastKeptId) foundKept = true;
161
+ if (!foundKept || e.type === "compaction") continue;
162
+ if (e.type === "message" && e.message) liveRoles.push(e.message.role);
163
+ }
164
+ return liveRoles;
165
+ }
166
+
167
+ function handleOwnCutCancellation(args: {
168
+ ownCut: Extract<OwnCutResult, { ok: false }>;
169
+ isPiVcc: boolean;
170
+ settings: PiVccSettings;
171
+ branchEntries: any[];
172
+ ctx: any;
173
+ }): { cancel: true } {
174
+ const { ownCut, isPiVcc, settings, branchEntries, ctx } = args;
175
+ const lastComp = [...branchEntries].reverse().find((e: any) => e.type === "compaction");
176
+ const lastCompIdx = lastComp ? branchEntries.indexOf(lastComp) : -1;
177
+ const lastKeptId: string | undefined = lastComp?.firstKeptEntryId;
178
+ const liveRoles = collectLiveRolesForDiagnostics(branchEntries, lastCompIdx, lastKeptId);
179
+ const userIndices = liveRoles.reduce<number[]>((acc, r, i) => (r === "user" ? (acc.push(i), acc) : acc), []);
180
+
181
+ dbg(settings, {
182
+ cancelled: true,
183
+ reason: ownCut.reason,
184
+ isPiVcc,
185
+ counts: {
186
+ total: branchEntries.length,
187
+ messages: branchEntries.filter((e: any) => e.type === "message").length,
188
+ compactions: branchEntries.filter((e: any) => e.type === "compaction").length,
189
+ entriesAfterLastCompaction: lastCompIdx >= 0 ? branchEntries.length - lastCompIdx - 1 : null,
190
+ },
191
+ liveMessages: {
192
+ count: liveRoles.length,
193
+ userCount: userIndices.length,
194
+ firstUserIdx: userIndices[0] ?? null,
195
+ lastUserIdx: userIndices[userIndices.length - 1] ?? null,
196
+ roleSequence: liveRoles.length <= 30
197
+ ? liveRoles
198
+ : [...liveRoles.slice(0, 10), "...", ...liveRoles.slice(-10)],
199
+ },
200
+ lastCompaction: lastComp
201
+ ? {
202
+ hasFirstKeptEntryId: !!lastComp.firstKeptEntryId,
203
+ foundInBranch: lastComp.firstKeptEntryId
204
+ ? branchEntries.some((e: any) => e.id === lastComp.firstKeptEntryId)
205
+ : null,
206
+ }
207
+ : null,
208
+ tail: branchEntries.slice(-5).map((e: any) => ({
209
+ type: e.type,
210
+ role: e.type === "message" ? e.message?.role : undefined,
211
+ hasContent: e.type === "message" ? e.message?.content != null : undefined,
212
+ })),
213
+ });
214
+
215
+ try {
216
+ ctx?.ui?.notify?.(REASON_MESSAGES[ownCut.reason], "warning");
217
+ } catch {}
218
+ return { cancel: true };
219
+ }
220
+
140
221
  export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
141
222
  pi.on("session_before_compact", (event, ctx) => {
142
223
  const { preparation, branchEntries, customInstructions } = event;
@@ -149,68 +230,13 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
149
230
 
150
231
  const ownCut = buildOwnCut(branchEntries as any[]);
151
232
  if (!ownCut.ok) {
152
- const lastComp = [...branchEntries].reverse().find((e: any) => e.type === "compaction");
153
- const lastCompIdx = lastComp ? (branchEntries as any[]).indexOf(lastComp) : -1;
154
-
155
- // Recompute liveMessages view (same logic as buildOwnCut) for diagnostic
156
- const lastKeptId: string | undefined = lastComp?.firstKeptEntryId;
157
- const hasPriorCompaction = lastCompIdx >= 0;
158
- const hasValidKeptId = !!lastKeptId && (branchEntries as any[]).some((e: any) => e.id === lastKeptId);
159
- const diagOrphan = hasPriorCompaction && !hasValidKeptId;
160
- const liveRoles: string[] = [];
161
- if (diagOrphan) {
162
- for (let i = lastCompIdx + 1; i < branchEntries.length; i++) {
163
- const e = (branchEntries as any[])[i];
164
- if (e.type === "compaction") continue;
165
- if (e.type === "message" && e.message) liveRoles.push(e.message.role);
166
- }
167
- } else {
168
- let foundKept = !lastKeptId;
169
- for (const e of branchEntries as any[]) {
170
- if (!foundKept && e.id === lastKeptId) foundKept = true;
171
- if (!foundKept) continue;
172
- if (e.type === "compaction") continue;
173
- if (e.type === "message" && e.message) liveRoles.push(e.message.role);
174
- }
175
- }
176
- const userIndices = liveRoles.reduce<number[]>((acc, r, i) => (r === "user" ? (acc.push(i), acc) : acc), []);
177
-
178
- dbg(settings, {
179
- cancelled: true,
180
- reason: ownCut.reason,
233
+ return handleOwnCutCancellation({
234
+ ownCut,
181
235
  isPiVcc,
182
- counts: {
183
- total: branchEntries.length,
184
- messages: (branchEntries as any[]).filter((e: any) => e.type === "message").length,
185
- compactions: (branchEntries as any[]).filter((e: any) => e.type === "compaction").length,
186
- entriesAfterLastCompaction: lastCompIdx >= 0 ? branchEntries.length - lastCompIdx - 1 : null,
187
- },
188
- liveMessages: {
189
- count: liveRoles.length,
190
- userCount: userIndices.length,
191
- firstUserIdx: userIndices[0] ?? null,
192
- lastUserIdx: userIndices[userIndices.length - 1] ?? null,
193
- roleSequence: liveRoles.length <= 30
194
- ? liveRoles
195
- : [...liveRoles.slice(0, 10), "...", ...liveRoles.slice(-10)],
196
- },
197
- lastCompaction: lastComp ? {
198
- hasFirstKeptEntryId: !!lastComp.firstKeptEntryId,
199
- foundInBranch: lastComp.firstKeptEntryId
200
- ? (branchEntries as any[]).some((e: any) => e.id === lastComp.firstKeptEntryId)
201
- : null,
202
- } : null,
203
- tail: (branchEntries as any[]).slice(-5).map((e: any) => ({
204
- type: e.type,
205
- role: e.type === "message" ? e.message?.role : undefined,
206
- hasContent: e.type === "message" ? e.message?.content != null : undefined,
207
- })),
236
+ settings,
237
+ branchEntries: branchEntries as any[],
238
+ ctx,
208
239
  });
209
-
210
- try {
211
- ctx?.ui?.notify?.(REASON_MESSAGES[ownCut.reason], "warning");
212
- } catch {}
213
- return { cancel: true };
214
240
  }
215
241
 
216
242
  const agentMessages = ownCut.messages;
@@ -1,338 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # release.sh — Version bump, changelog, tag, and push
4
- # Usage: ./.pi/scripts/release.sh [patch|minor|major] [--dry-run]
5
- #
6
- set -euo pipefail
7
-
8
- # ─── Helpers ──────────────────────────────────────────────────────────────────
9
- warn() { echo "⚠ $*" >&2; }
10
- abort() { echo "✗ $*" >&2; exit 1; }
11
- ok() { echo "✓ $*"; }
12
-
13
- # ─── Step 0 — Parse arguments ─────────────────────────────────────────────────
14
- BUMP_TYPE=""
15
- DRY_RUN=false
16
-
17
- for arg in "$@"; do
18
- case "$arg" in
19
- patch|minor|major) BUMP_TYPE="$arg" ;;
20
- --dry-run) DRY_RUN=true ;;
21
- *) abort "Unknown argument: $arg" ;;
22
- esac
23
- done
24
-
25
- # ─── Step 1 — Infer bump type from commits if not provided ────────────────────
26
- LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
27
-
28
- if [ -z "$BUMP_TYPE" ]; then
29
- ok "No bump type provided. Scanning commits since last tag…"
30
-
31
- if [ -z "$LAST_TAG" ]; then
32
- COMMIT_LOG=$(git log --format="%s" HEAD 2>/dev/null || true)
33
- else
34
- COMMIT_LOG=$(git log --format="%s" "${LAST_TAG}..HEAD" 2>/dev/null || true)
35
- fi
36
-
37
- if [ -z "$COMMIT_LOG" ]; then
38
- abort "No commits since last tag. Nothing to release."
39
- fi
40
-
41
- # Inference rules
42
- if echo "$COMMIT_LOG" | grep -qE '^feat!:|BREAKING CHANGE'; then
43
- BUMP_TYPE="major"
44
- elif echo "$COMMIT_LOG" | grep -qE '^feat:'; then
45
- BUMP_TYPE="minor"
46
- else
47
- BUMP_TYPE="patch"
48
- fi
49
-
50
- ok "Inferred bump type: $BUMP_TYPE"
51
- fi
52
-
53
- # ─── Step 2 — Read current version and validate semver ────────────────────────
54
- CURRENT_VERSION=$(node -e "console.log(require('./package.json').version)" 2>/dev/null) \
55
- || abort "Failed to read version from package.json"
56
-
57
- if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
58
- abort "Invalid semver in package.json: $CURRENT_VERSION"
59
- fi
60
-
61
- NEW_VERSION=$(node -e "
62
- const [maj, min, pat] = '$CURRENT_VERSION'.split('.').map(Number);
63
- const bump = '$BUMP_TYPE';
64
- if (bump === 'major') console.log((maj + 1) + '.0.0');
65
- else if (bump === 'minor') console.log(maj + '.' + (min + 1) + '.0');
66
- else console.log(maj + '.' + min + '.' + (pat + 1));
67
- ")
68
-
69
- ok "Version: $CURRENT_VERSION → $NEW_VERSION"
70
-
71
- # ─── Step 3 — Pre-flight checks ───────────────────────────────────────────────
72
-
73
- # Must be in a git repo
74
- git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
75
- || abort "Not a git repository."
76
-
77
- # Must have origin remote
78
- git remote -v | grep -q origin \
79
- || abort "No 'origin' remote configured."
80
-
81
- # Must be on a branch (not detached HEAD)
82
- BRANCH=$(git symbolic-ref -q HEAD 2>/dev/null | sed 's|^refs/heads/||') \
83
- || abort "Detached HEAD. Switch to a branch first."
84
-
85
- # Must have clean working tree (warn only in dry-run)
86
- git diff --quiet && git diff --cached --quiet
87
- if [ $? -ne 0 ]; then
88
- if [ "$DRY_RUN" = true ]; then
89
- warn "Working tree is dirty — actual release would be blocked."
90
- else
91
- abort "Working tree is dirty. Commit or stash changes first."
92
- fi
93
- fi
94
-
95
- # No duplicate tag locally or on remote
96
- if git rev-parse "v$NEW_VERSION" >/dev/null 2>&1; then
97
- abort "Tag v$NEW_VERSION already exists locally."
98
- fi
99
- if git ls-remote --tags origin "refs/tags/v$NEW_VERSION" >/dev/null 2>&1; then
100
- abort "Tag v$NEW_VERSION already exists on remote."
101
- fi
102
-
103
- # ─── Step 4 — Gather commits since last tag ───────────────────────────────────
104
- if [ -z "$LAST_TAG" ]; then
105
- COMMITS=$(git log --oneline --no-merges HEAD)
106
- else
107
- COMMITS=$(git log --oneline --no-merges "${LAST_TAG}..HEAD")
108
- fi
109
-
110
- COMMIT_COUNT=$(echo "$COMMITS" | grep -c '^' || echo 0)
111
-
112
- if [ "$COMMIT_COUNT" -eq 0 ]; then
113
- abort "No commits since last tag. Nothing to release."
114
- fi
115
-
116
- # ─── Step 5 — Generate changelog entry ────────────────────────────────────────
117
-
118
- # Map conventional commit prefix → section
119
- map_prefix() {
120
- local msg="$1"
121
- case "$msg" in
122
- feat!:*|*"BREAKING CHANGE"*) echo "breaking" ;;
123
- feat:*) echo "features" ;;
124
- fix:*) echo "fixes" ;;
125
- perf:*) echo "perf" ;;
126
- refactor:*) echo "refactor" ;;
127
- docs:*) echo "docs" ;;
128
- style:*) echo "style" ;;
129
- test:*) echo "tests" ;;
130
- chore:*) echo "chores" ;;
131
- ci:*) echo "ci" ;;
132
- build:*) echo "build" ;;
133
- *) echo "chores" ;;
134
- esac
135
- }
136
-
137
- declare -A SECTIONS=(
138
- [breaking]="⚠️ Breaking Changes"
139
- [features]="✨ Features"
140
- [fixes]="🐛 Fixes"
141
- [perf]="⚡ Performance"
142
- [refactor]="♻️ Refactoring"
143
- [docs]="📖 Documentation"
144
- [style]="🎨 Style"
145
- [tests]="✅ Tests"
146
- [chores]="🔧 Chores"
147
- [ci]="🔄 CI/CD"
148
- [build]="📦 Build"
149
- )
150
-
151
- # Build per-section entries
152
- entries_breaking=""
153
- entries_features=""
154
- entries_fixes=""
155
- entries_perf=""
156
- entries_refactor=""
157
- entries_docs=""
158
- entries_style=""
159
- entries_tests=""
160
- entries_chores=""
161
- entries_ci=""
162
- entries_build=""
163
-
164
- while IFS= read -r line; do
165
- [ -z "$line" ] && continue
166
- # Strip the short sha prefix (first word)
167
- msg="${line#* }"
168
- # Strip conventional commit prefix for display
169
- display="$msg"
170
- display=$(echo "$display" | sed -E 's/^[a-z]+(\([a-z0-9_-]+\))?!?:\s*//')
171
- prefix=$(map_prefix "$msg")
172
- case "$prefix" in
173
- breaking) entries_breaking="${entries_breaking}- $display
174
- " ;;
175
- features) entries_features="${entries_features}- $display
176
- " ;;
177
- fixes) entries_fixes="${entries_fixes}- $display
178
- " ;;
179
- perf) entries_perf="${entries_perf}- $display
180
- " ;;
181
- refactor) entries_refactor="${entries_refactor}- $display
182
- " ;;
183
- docs) entries_docs="${entries_docs}- $display
184
- " ;;
185
- style) entries_style="${entries_style}- $display
186
- " ;;
187
- tests) entries_tests="${entries_tests}- $display
188
- " ;;
189
- ci) entries_ci="${entries_ci}- $display
190
- " ;;
191
- build) entries_build="${entries_build}- $display
192
- " ;;
193
- *) entries_chores="${entries_chores}- $display
194
- " ;;
195
- esac
196
- done <<< "$COMMITS"
197
-
198
- # Assemble the changelog block
199
- TODAY=$(date +%Y-%m-%d)
200
- CHANGELOG_BLOCK="## [v$NEW_VERSION] — $TODAY
201
- "
202
-
203
- for key in breaking features fixes perf refactor docs style tests ci build chores; do
204
- eval "content=\"\$entries_$key\""
205
- if [ -n "$content" ]; then
206
- CHANGELOG_BLOCK="${CHANGELOG_BLOCK}
207
- ### ${SECTIONS[$key]}
208
-
209
- $content"
210
- fi
211
- done
212
-
213
- # ─── Step 6 — Dry run check ───────────────────────────────────────────────────
214
- if [ "$DRY_RUN" = true ]; then
215
- echo ""
216
- echo "═══════════════════════════════════════════════════════════════"
217
- echo " DRY RUN — no changes made"
218
- echo "═══════════════════════════════════════════════════════════════"
219
- echo " Version: $CURRENT_VERSION → $NEW_VERSION"
220
- echo " Bump: $BUMP_TYPE"
221
- echo " Commits: $COMMIT_COUNT since ${LAST_TAG:-<none>}"
222
- echo " Branch: $BRANCH"
223
- echo ""
224
- echo " Files that would change:"
225
- echo " - package.json (version → $NEW_VERSION)"
226
- echo " - CHANGELOG.md (new entry below)"
227
- echo ""
228
- echo " Tag that would be created: v$NEW_VERSION"
229
- echo ""
230
- echo " Changelog entry:"
231
- echo "───────────────────────────────────────────────────────────────"
232
- echo "$CHANGELOG_BLOCK"
233
- echo "───────────────────────────────────────────────────────────────"
234
- exit 0
235
- fi
236
-
237
- # ─── Step 7 — Bump version in package.json ────────────────────────────────────
238
- npm pkg set version="$NEW_VERSION"
239
-
240
- node -e "
241
- const v = require('./package.json').version;
242
- if (v !== '$NEW_VERSION') {
243
- console.error('✗ version mismatch: expected $NEW_VERSION, got ' + v);
244
- process.exit(1);
245
- }
246
- console.log('✓ version bumped to $NEW_VERSION');
247
- "
248
-
249
- # ─── Step 8 — Write CHANGELOG.md ──────────────────────────────────────────────
250
- if [ -f CHANGELOG.md ]; then
251
- # Prepend after the first heading line
252
- {
253
- head -n 1 CHANGELOG.md
254
- echo ""
255
- echo "$CHANGELOG_BLOCK"
256
- tail -n +2 CHANGELOG.md
257
- } > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
258
- else
259
- {
260
- echo "# Changelog"
261
- echo ""
262
- echo "All notable changes to this project are documented in this file."
263
- echo ""
264
- echo "$CHANGELOG_BLOCK"
265
- } > CHANGELOG.md
266
- fi
267
-
268
- ok "CHANGELOG.md updated"
269
-
270
- # ─── Step 9 — Read co-author config ───────────────────────────────────────────
271
- CO_AUTHOR="pi-mono <261679550+pi-mono@users.noreply.github.com>"
272
- if [ -f .pi/auto-commit.json ]; then
273
- CO_AUTHOR=$(node -e "
274
- const fs = require('fs');
275
- const cfg = JSON.parse(fs.readFileSync('.pi/auto-commit.json', 'utf8'));
276
- const ca = cfg.coAuthor || {};
277
- console.log((ca.login || 'pi-mono') + ' <' + (ca.email || '261679550+pi-mono@users.noreply.github.com') + '>');
278
- " 2>/dev/null) || true
279
- fi
280
-
281
- # ─── Step 10 — Commit ─────────────────────────────────────────────────────────
282
- git add package.json CHANGELOG.md
283
-
284
- COMMIT_BODY=$(cat <<EOF
285
- - Bump version in package.json
286
- - Add changelog entry for v$NEW_VERSION
287
-
288
- Commits included:
289
- $(echo "$COMMITS" | sed 's/^/- /')
290
- EOF
291
- )
292
-
293
- git commit -m "chore(release): bump to v$NEW_VERSION" \
294
- -m "$COMMIT_BODY" \
295
- -m "Co-authored-by: $CO_AUTHOR"
296
-
297
- ok "Committed version bump + changelog"
298
-
299
- # ─── Step 11 — Create and push tag ────────────────────────────────────────────
300
- TAG_BODY=$(cat <<EOF
301
- Release v$NEW_VERSION — $BUMP_TYPE bump
302
-
303
- $COMMITS
304
- EOF
305
- )
306
-
307
- git tag -a "v$NEW_VERSION" -m "$TAG_BODY"
308
- ok "Created tag v$NEW_VERSION"
309
-
310
- git push origin "v$NEW_VERSION"
311
- ok "Pushed tag v$NEW_VERSION to origin"
312
-
313
- # ─── Step 12 — Optionally push branch commit ──────────────────────────────────
314
- echo ""
315
- read -rp "Push the version-bump commit to the current branch ($BRANCH) too? [Y/n] " PUSH_BRANCH
316
- if [[ "$PUSH_BRANCH" =~ ^[Yy]?$ ]]; then
317
- git push origin "$BRANCH"
318
- ok "Pushed commit to $BRANCH"
319
- else
320
- echo "Skipped branch push."
321
- fi
322
-
323
- # ─── Step 13 — Report ─────────────────────────────────────────────────────────
324
- echo ""
325
- echo "═══════════════════════════════════════════════════════════════"
326
- echo " ✓ Released v$NEW_VERSION ($BUMP_TYPE)"
327
- echo "═══════════════════════════════════════════════════════════════"
328
- echo " Tag: v$NEW_VERSION — pushed to origin"
329
- echo " Commit: $(git rev-parse --short HEAD)"
330
- echo " Branch: $BRANCH"
331
- echo ""
332
- echo " Workflows triggered:"
333
- echo " - .github/workflows/publish-github-packages.yml"
334
- echo " - .github/workflows/publish-npm.yml"
335
- echo ""
336
- echo " Changelog: CHANGELOG.md updated"
337
- echo " Monitor: https://github.com/aryaniyaps/ultimate-pi/actions"
338
- echo "═══════════════════════════════════════════════════════════════"