Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
name: release
description: "Run the Harnss release workflow — review staged diff, bump version, commit, tag, push, and create a GitHub release. Use when releasing, bumping version, tagging, or creating a release. Argument: major, minor, or patch."
---

# Harnss Release Workflow

Run the full release pipeline. Bump type is passed as `$ARGUMENTS` (major, minor, or patch).

## Step 1: Pre-flight Checks

Run these commands and **read every line of output**:

```bash
git status
git diff --cached --stat
git diff --cached
```

If the diff is too large to read in one shot, read it in chunks (e.g., per-directory or line ranges). **You must read the entire diff before proceeding.**

Review for:
- Test files, scratch files, temp files, debug artifacts (e.g., `test-*.ts`, `scratch.*`, `*.tmp`, `random-*.md`)
- Files that shouldn't be committed (`.env`, credentials, large binaries)

If you find any:
- Unstage or remove them
- Tell the user what you removed

If there are no staged changes but unstaged changes exist, ask the user if they want to stage anything first.
If the working tree is completely clean (nothing to release), tell the user and stop.

## Step 2: Version Bump

### Determine new version

1. Read the current version from `package.json` (the `"version"` field)
2. Get the latest tag: `git tag --sort=-v:refname | head -1`
3. Parse the current version as `MAJOR.MINOR.PATCH`
4. Apply the bump type from `$ARGUMENTS`:
- `major` → `(MAJOR+1).0.0`
- `minor` → `MAJOR.(MINOR+1).0`
- `patch` → `MAJOR.MINOR.(PATCH+1)`
5. If `$ARGUMENTS` is empty or invalid, ask the user which bump type they want

### Check for SDK updates

```bash
npm view @anthropic-ai/Codex-agent-sdk version
```

Compare with the version in `package.json` under `dependencies["@anthropic-ai/Codex-agent-sdk"]` (strip the `^` prefix for comparison). If a newer version exists, update the dependency version (keep the `^` prefix) and tell the user.

### Apply changes

1. Edit `package.json` to set the new version number
2. Stage it: `git add package.json`
3. If the SDK was updated, also run:
```bash
pnpm install
git add package.json pnpm-lock.yaml
```

## Step 3: Commit

Choose the commit message based on what's staged:

### Feature/fix changes staged (not just version bump)

```
feat: short summary (2-4 key themes)

- Change description 1
- Change description 2
- ...

Co-Authored-By: Codex Opus 4.6 (1M context) <noreply@anthropic.com>
```

Use `fix:` instead of `feat:` if all changes are bug fixes.

### Only version bump staged

```
chore: bump version to X.Y.Z

Co-Authored-By: Codex Opus 4.6 (1M context) <noreply@anthropic.com>
```

If the SDK was also updated:

```
chore: bump version to X.Y.Z and update Codex-agent-sdk to A.B.C

Co-Authored-By: Codex Opus 4.6 (1M context) <noreply@anthropic.com>
```

### Always use a HEREDOC

```bash
git commit -m "$(cat <<'EOF'
<message here>

Co-Authored-By: Codex Opus 4.6 (1M context) <noreply@anthropic.com>
EOF
)"
```

## Step 4: Tag & Push

```bash
git tag vX.Y.Z HEAD
git push origin master && git push origin vX.Y.Z
```

If push fails, report the error and stop.

## Step 5: GitHub Release

### Gather context

Get the previous release tag:

```bash
git tag --sort=-v:refname | head -2 | tail -1
```

Read the full diff and commit log since the previous release:

```bash
git log v{prev}...HEAD --oneline
git diff v{prev}...HEAD --stat
git diff v{prev}...HEAD
```

Read ALL of this output. For the full diff, read it in chunks if needed — every line matters for writing accurate release notes.

### Write release notes

Load the template from [references/release-notes-template.md](references/release-notes-template.md) and follow its format exactly.

### Create the release

```bash
gh release create vX.Y.Z --title "vX.Y.Z — Short Descriptive Phrase" --notes "$(cat <<'EOF'
<release notes>
EOF
)"
```

The title uses an em dash (`—`), not a hyphen.

Output the release URL when done so the user can verify.

## Important Notes

- **Never skip reading the full diff** in Step 1. Every line matters.
- The `Co-Authored-By` trailer is **mandatory** on every commit.
- Repo: `https://github.com/OpenSource03/harnss`
- Main branch: `master`
- Changelog URL format: `https://github.com/OpenSource03/harnss/compare/v{prev}...v{current}`
- Package manager: `pnpm` (never use npm or yarn for installs)
119 changes: 119 additions & 0 deletions .agents/skills/release/references/release-notes-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Harnss Release Notes Template

## Title Format

`v{X.Y.Z} — Short Descriptive Phrase`

- Use an em dash (`—`), not a hyphen
- Name 2-3 headline features, joined by commas and `&`
- Examples:
- `v0.21.0 — Virtualized Chat, Mermaid Diagrams & Deep Folder Tagging`
- `v0.15.0 — Slash Commands, Tool Grouping & Project Files`
- `v0.14.0 — Codex Engine Config, Auth Flow & Settings Refresh`
- `v0.13.1 — Windows Compatibility Fixes`

## Audience & Tone

**Write for users, not developers.** Release notes are read by people who use the app, not people who built it.

- ✅ "Long conversations are dramatically faster now"
- ❌ "Replaced `content-visibility: auto` with `@tanstack/react-virtual` windowing"
- ✅ "When Claude draws a diagram, it now actually renders as a visual diagram"
- ❌ "Mermaid fenced code blocks render as SVG via async `mermaid.render()` with LRU cache"
- ✅ "Type `/clear` in the composer and hit Enter to open a fresh chat"
- ❌ "Added `LOCAL_CLEAR_COMMAND` slash command with `source: 'local'` that calls `onClear()` callback"

**Rules of thumb:**
- Describe what the user *experiences*, not what the code does
- No internal names, no version numbers, no API terms, no implementation details
- If you can't explain it in plain English, simplify or skip it
- Bug fixes: describe the symptom the user saw, not the root cause

## Notes Structure

Sections can be free-form paragraphs OR bullet lists — pick whichever reads more naturally.

```markdown
## What's New

### {emoji} {User-Facing Section Title}
Short paragraph explaining what changed and why users care.

### {emoji} {User-Facing Section Title}
- **{Feature name}** — what it does for the user
- **{Feature name}** — what it does for the user

### 🐛 Bug Fixes
- Fixed a bug where [symptom the user experienced]
- Fixed [thing] that caused [user-visible problem]

---

**Full Changelog**: https://github.com/OpenSource03/harnss/compare/v{prev}...v{current}
```

## Rules

1. Use `## What's New` for feature releases, `## Changes` for patch/fix-only releases
2. Group under `### {emoji} {Category Title}` headers — keep titles plain, not technical
3. End with `---` separator and Full Changelog link
4. **Always write for users first.** Internal details, library names, and implementation notes belong in commit messages and CLAUDE.md, not release notes.

## Emoji Conventions

| Emoji | Category |
|-------|----------|
| ⚡ | Performance, speed, snappiness |
| 📦 | Grouping, packaging |
| 📂 | Files, folders, filesystem |
| 🔍 | Search, inspection |
| 📨 | Messages, queues, communication |
| 🛠 | Tools, integrations |
| 🎨 | UI, visual polish |
| ⚙️ | Settings, configuration |
| 🔐 | Auth, security, permissions |
| 🔄 | Updates, syncing |
| 🌳 | Git, version control |
| 🐛 | Bug fixes |
| ✨ | New features (generic) |

## Example: Feature Release (v0.21.0)

```markdown
## What's New

### ⚡ Much Faster Chat
Long conversations are dramatically faster now. We replaced the old rendering approach with a proper virtualized list — only the messages you can actually see are rendered at any time. Scrolling is smoother, switching sessions is snappier, and the app uses less memory overall.

### 📊 Mermaid Diagrams
When Claude draws a diagram using a mermaid code block, it now actually renders as a visual diagram — flowcharts, sequence diagrams, pie charts, git graphs, and more. Diagrams adapt to your light/dark theme automatically. While Claude is still typing, you see the raw source; once the message is complete, the diagram appears.

### 📂 Deep Folder Inclusion (`@#`)
You can now use `@#foldername` in the composer to include the full contents of a folder — not just the file tree, but every file inside it. Regular `@folder` still gives you the structure overview. If the folder is large, Harnss will warn you before sending.

### ⌨️ `/clear` Command
Type `/clear` in the composer and hit Enter to instantly open a fresh chat — without sending anything to the agent.

### 🐛 Bug Fixes
- Fixed a bug where switching out of plan mode could reset the permission level incorrectly
- Fixed markdown characters occasionally getting eaten during streaming (apostrophes, backticks, etc.)
- Permission prompts now show a notification if something goes wrong, instead of failing silently

---

**Full Changelog**: https://github.com/OpenSource03/harnss/compare/v0.20.0...v0.21.0
```

## Example: Patch Release

```markdown
## Changes

### 🐛 Bug Fixes
- Fixed the app hanging when switching sessions during an active stream
- Fixed copy button not working in certain sandboxed contexts

---

**Full Changelog**: https://github.com/OpenSource03/harnss/compare/v0.21.0...v0.21.1
```
32 changes: 31 additions & 1 deletion electron/src/ipc/claude-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,27 @@ async function setSessionPermissionMode(
log(logLabel, `session=${sessionId.slice(0, 8)} mode=${permissionMode}`);
}

/**
* Explicitly set permissionMode on a freshly created query handle.
* The SDK CLI may ignore the `permissionMode` query option for resumed sessions
* (it loads saved state from disk). Calling setPermissionMode() on the live handle
* is guaranteed to take effect.
*/
async function enforcePermissionMode(
sessionId: string,
queryHandle: QueryHandle,
permissionMode: string | undefined,
context: string,
): Promise<void> {
if (!permissionMode || permissionMode === "default") return;
try {
await queryHandle.setPermissionMode(permissionMode);
log("PERMISSION_MODE_ENFORCED", `session=${sessionId.slice(0, 8)} mode=${permissionMode} (${context})`);
} catch (err) {
reportError("PERMISSION_MODE_ENFORCE_ERR", err, { engine: "claude", sessionId, permissionMode, context });
}
}

function summarizeSpawnOptions(options: Record<string, unknown>): Record<string, unknown> {
const mcpServers = options.mcpServers;
const mcpSummary = mcpServers && typeof mcpServers === "object"
Expand Down Expand Up @@ -115,7 +136,7 @@ function summarizeEvent(event: Record<string, unknown>): string {
switch (event.type) {
case "system": {
if (event.subtype === "init") {
return `system/init session=${(event.session_id as string)?.slice(0, 8)} model=${event.model}`;
return `system/init session=${(event.session_id as string)?.slice(0, 8)} model=${event.model} permMode=${event.permissionMode ?? "?"}`;
}
if (event.subtype === "task_started") {
return `system/task_started task=${(event.task_id as string)?.slice(0, 8)} tool_use=${(event.tool_use_id as string)?.slice(0, 12)} desc="${event.description}"`;
Expand Down Expand Up @@ -593,6 +614,9 @@ async function restartSession(
return { error: `Restart failed: ${errMsg}` };
}

// Restarted sessions always resume — enforce permission mode on the live handle.
await enforcePermissionMode(sessionId, q, opts.permissionMode, "restart");

startEventLoop(sessionId, q, newSession, getMainWindow);

return { ok: true, restarted: true };
Expand Down Expand Up @@ -692,6 +716,12 @@ export function register(getMainWindow: () => BrowserWindow | null): void {
const q = query({ prompt: channel, options: queryOptions });
session.queryHandle = q;

// For resumed sessions, explicitly enforce the permission mode on the live
// handle — the SDK CLI may load its own saved state and ignore the query option.
if (options.resume) {
await enforcePermissionMode(sessionId, q, options.permissionMode, "start-resume");
}

startEventLoop(sessionId, q, session, getMainWindow);

void captureEvent("session_created", {
Expand Down
Loading