Skip to content
Open
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
154 changes: 154 additions & 0 deletions .agents/skills/git/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
name: git
description: >
Use for day-to-day Git workflow when creating commits, choosing commit scopes,
preparing PR titles, or deciding how changes should be merged. Enforces a
GitHub-only squash-merge policy, Conventional Commit subjects, concise commit
bodies, and a commit-often workflow during branch development.
---

# Git Workflow

Use this skill as a small operating policy for Git usage. The point is to make
different agents converge on the same workflow instead of inventing one from
training priors.

## Default stance

1. All integration merges happen on GitHub, never locally.
2. All GitHub merges should be squash merges.
3. PR titles must use Conventional Commit format, because the squash-merge
commit on the main branch will inherit the PR title.
4. Commit often while working on a branch. Branch history is temporary working
history, not the final shape of `main`.
5. Do not waste time trying to make every local commit perfect. Keep commits
useful, focused, and frequent, then rely on squash merge to produce the
final main-branch commit.

## Merge policy

- Do not locally merge feature branches into `main`, `master`, or other shared
integration branches.
- Do not treat a local merge commit as the final integration step.
- Use GitHub's squash merge button or `gh pr merge --squash`.
- The PR title must already be the final commit subject you want on the target
branch.

## Commit subject format

Use Conventional Commits:

```text
type(scope): short imperative summary
```

or, when no scope is needed:

```text
type: short imperative summary
```

Use standard types unless the repository clearly uses a different established
set:

- `feat`
- `fix`
- `refactor`
- `docs`
- `test`
- `ci`
- `build`
- `chore`
- `perf`
- `style`
- `revert`

Subject rules:

- Use imperative mood: `fix cache invalidation`, not `fixed` or `fixes`.
- Keep the type and scope lowercase.
- Do not end the subject with a period.
- Make the subject specific enough to stand alone in `git log`.

## Scope guidance

Scopes are optional in small repos and expected in larger or mixed repos.

Good natural scopes include:

- a project in a monorepo
- a Go package in a larger Go codebase
- a service, subsystem, or app in a multi-system repository
- a clearly named operational area such as `ci`, `release`, or `docs`

When in doubt, inspect recent history and reuse the existing vocabulary instead
of inventing a new scope:

```bash
git log --format='%s' -n 100
```

If the repository already has a consistent scope pattern, follow it.

## Commit bodies

There is no single standard body template. Keep bodies concise and use them to
cover what the diff alone does not explain clearly, usually:

- why the change exists
- any important constraint or tradeoff
- any reviewer or operator note that should survive in history

Default shape:

```text
type(scope): short imperative summary

Why this change is needed.
Any critical context or operational note.
```

Rules:

- Leave a blank line between subject and body.
- Omit the body entirely when the subject and diff are already obvious.
- Prefer short paragraphs or a few bullets over long prose.
- Do not invent author identity inside the message.
- If the repository requires trailers such as `Signed-off-by`, use Git's
configured identity and built-in support such as `git commit -s` instead of
typing made-up author lines by hand.

## PR titles

PR titles must always use Conventional Commit format.

Examples:

- `fix(api): handle empty webhook payload`
- `feat(cli): add JSON output for status`
- `docs: clarify release process`

Because the branch is squash-merged, the PR title is the final commit title on
the target branch. Treat the PR title as merge-ready, not as a temporary review
label.

## PR bodies

There is no universal PR body template worth enforcing rigidly. Keep the body
useful to reviewers and future readers. In most repos, a concise PR body should
cover:

- summary of the change
- linked issue or context
- testing or validation performed
- any rollout, migration, or reviewer notes

## Branch work cadence

- Commit early when you establish a direction.
- Commit again when a piece of work becomes testable or reviewable.
- Commit after meaningful fixes during review.
- Do not hold a large pile of unstaged work just to chase an ideal commit graph.

The branch history is allowed to be messy as long as it is understandable. The
main branch history is cleaned up by squash merge.
126 changes: 126 additions & 0 deletions .agents/skills/journal-sync/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
name: journal-sync
description: Synchronize remote teammate journal branches into local Worktrunk worktrees for read-only context discovery. Invoke only when the user explicitly asks to sync, fetch, refresh, or discover team journals. Never run automatically during startup or another lifecycle command.
---

# Journal Sync

Mirror teammates' published journals locally without authoring journal content.
Canonical branch names are `journal/<github-login>`; paths such as
`.wt/journal-<github-login>` are Worktrunk-sanitized worktree paths, not branch
names.

## Guardrails

- Exclude the current user's `journal/<login>` branch from sync decisions,
per-worktree Git status inspection, worktree creation, and pulls. The shared
Worktrunk inventory may be used only to report whether it exists;
`session-setup` owns that personal worktree.
- Treat every teammate journal worktree as a read-only reference mirror. Never
edit journal files or stage, commit, push, rebase, stash, reset, clean,
force-update, delete, or prune a local branch or worktree during sync. Fetch
may prune stale `origin/journal/*` remote-tracking refs only.
- Leave local journal branches and worktrees that no longer exist remotely in
place. Report them as retained; cleanup is outside this skill.
- Isolate failures by branch. A dirty, ahead, diverged, or broken peer must not
prevent other peers from syncing.

## Workflow

1. Resolve and verify prerequisites:

```bash
command -v wt
gh auth status
login=$(gh api user --jq .login)
personal="journal/$login"
git remote get-url origin
wt config show --full
```

Require the Worktrunk path template
`{{ repo_path }}/.wt/{{ branch | sanitize }}`. Stop the entire operation if
identity, `origin`, Worktrunk, or its path configuration cannot be resolved;
the personal branch cannot otherwise be excluded safely.

2. Query the remote directly and retain that output as the authoritative sync
snapshot:

```bash
git ls-remote --heads origin 'refs/heads/journal/*'
```

Strip `refs/heads/` to obtain branch names. Accept canonical
`journal/<login>` refs only; report malformed nested or empty-owner refs
without creating worktrees for them. Exclude `$personal` before inspecting
any worktree.

3. Fetch and prune only the journal remote-tracking namespace, then inventory
local branches and worktrees:

```bash
git fetch origin --prune \
'+refs/heads/journal/*:refs/remotes/origin/journal/*'
wt list --format=json --branches
```

Use only branches from the remote snapshot for sync decisions; stale local
remote-tracking refs must not masquerade as current remote branches. Compare
the inventory with the snapshot so local peer branches or worktrees whose
remote disappeared can be reported as retained without touching them.

Use the inventory only to determine whether the exact personal worktree is
present. If it is missing, report that the user should run `session-setup`,
but continue syncing peers. Do not create, inspect the status of, or pull the
personal worktree.

4. For each teammate branch in the snapshot, locate its exact worktree in the
Worktrunk JSON. If none exists, create or open its tracking worktree:

```bash
wt switch --no-cd --no-hooks --format=json -y "journal/<login>"
```

Take the path from Worktrunk's JSON rather than constructing it. If creation
fails, refresh `wt list --format=json --branches` once: another process may
have created the worktree concurrently. Continue when the exact branch now
has a worktree; otherwise record a branch-local failure.

Hooks are disabled because repository hooks may author files or continue
asynchronously after Worktrunk returns, violating the read-only peer rule.

5. Verify the worktree checks out the expected branch. Record its starting
`HEAD`, then inspect it without changing it:

```bash
git -C "<worktree-path>" branch --show-current
git -C "<worktree-path>" status --porcelain=v1 --untracked-files=all
git -C "<worktree-path>" rev-list --left-right --count \
HEAD...refs/remotes/origin/journal/<login>
```

If the branch is wrong or status is non-empty, do not pull. Record the reason
and continue with the next teammate. Interpret the revision counts as
`<local-only> <remote-only>`; if the local-only count is nonzero, the branch
is ahead or diverged. Do not pull it. Record the counts and continue.

6. Fast-forward from the exact remote branch and verify an exact mirror:

```bash
git -C "<worktree-path>" -c core.hooksPath=/dev/null \
pull --ff-only origin "journal/<login>"
git -C "<worktree-path>" rev-parse HEAD
git -C "<worktree-path>" rev-parse "refs/remotes/origin/journal/<login>"
```

Success requires the two revisions to match. If the pull fails or a
concurrent change leaves the branch ahead/diverged, classify it again with
`git rev-list --left-right --count HEAD...refs/remotes/origin/journal/<login>`,
leave it untouched, and continue. Never repair divergence in this skill.
Disable Git hooks for the pull so a peer fast-forward cannot run a local
`post-merge` hook that authors files.

7. Return one aggregate report containing remote journals discovered, the
personal branch skipped, peer worktrees created, updated, already current,
retained because their remote disappeared, and blocked with exact reasons.
When no canonical teammate branches exist, return a successful no-op.
4 changes: 4 additions & 0 deletions .agents/skills/journal-sync/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Journal Sync"
short_description: "Safely refresh teammate journal worktrees"
default_prompt: "Use $journal-sync to discover and safely refresh teammate journal worktrees."
92 changes: 92 additions & 0 deletions .agents/skills/session-close/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: session-close
description: Close out a journal session. Invoke only when the user explicitly asks to close, wrap up, or end a session. Requires prior session-setup, commits work, opens PRs, waits for review, squash-merges, updates local default branches, then writes SUMMARY.md and updates INDEX.md in the developer's personal journal branch per .session.md. Accepts an optional session ID argument; without one it closes the current session bound to this task.
argument-hint: [session-id]
disable-model-invocation: true
---

The user is **closing a session**. Follow the session protocol defined in `.session.md` at the workspace root — do not restate it, follow it.

The close-out has three phases: **verify the personal journal worktree**, **land the work**, then **record the session**. Do them in order. Do not write `SUMMARY.md` before the PRs are merged — the summary is a postmortem of what actually happened, not what you intend to happen.

---

## Phase 0 — Verify the personal journal worktree

Resolve the developer identity with `gh api user --jq .login`, then locate an
existing Worktrunk worktree for `journal/<login>` with `wt list --format=json`
and use that worktree as the journal root. If no worktree exists, stop and tell
the developer to run `session-setup` before closing a session. Do not create the
journal branch here.

Verify `.journal/INDEX.md`, `.journal/SKILLS.md`, and `.journal/TECH_NOTES.md`
exist. If the root journal files are missing, stop and tell the developer to
rerun `session-setup`.

Resolve the session being closed before landing work:

- If the user provided a numeric session ID, normalize it to the zero-padded
folder name (`1` -> `001`) and use that session for this close operation. An
explicit ID selects the close target without replacing the current task-local
binding; if it names a different session, preserve the existing binding
unchanged.
- If no ID was provided, use the current session binding established earlier in
this task by a successful `session-new` or `session-continue`.
- If no ID was provided and this task has no current session binding, stop and
ask the user for an explicit session ID. Do not inspect or count
`in-progress` rows or incomplete folders to choose a target: those sessions
may belong to other concurrent tasks.
- Verify that `.journal/<ID>/NOTES.md` exists and that its `INDEX.md` row is
`in-progress` before continuing. (`SUMMARY.md` may still exist after a session
was reopened, so it is not the status authority.) If the resolved target is
invalid, missing, or already closed, report that exact target condition and
do not fall through to another session.

Read the resolved target's `.journal/<ID>/NOTES.md` in full before landing work;
it defines which repos and branches belong to the closeout. If a reopened
session still has a prior `SUMMARY.md`, read it as historical context while
treating the current `INDEX.md` status and `NOTES.md` as authoritative.

Treat both the task-local binding and the resolved close target as durable task
context. Preserve them across intermediate turns, context summarization, and
the Phase 1 pause for PR review. When the user returns after review, continue
closing the already resolved target instead of resolving it again.

Apply the concurrent journal ownership rule from `.session.md`. The default
write set is `.journal/<ID>/`; dirty paths in every other session folder are
expected, non-blocking parallel work and must remain untouched. Do not run
`pull --rebase` merely to prepare the shared journal worktree.

## Phase 1 — Land the work

For **every repo** with uncommitted changes or an unmerged branch that belongs to the target session, walk through these steps. Leave unrelated active-session branches alone. Do not skip a target-session repo because "nothing really changed" — run `git status` and verify.

1. **Reject journal contamination.** In every non-journal implementation branch, run `git ls-files .journal`. If it prints any tracked file, stop and remove those files from the implementation branch before continuing. Only `journal/<login>` may force-add `.journal/` paths.
2. **Commit all changes.** In each worktree that has work, stage and commit with a clear message. If there are logically distinct changes, use multiple commits. Do not commit unrelated changes together (see the Worktrunk rule in `.session.md`: one worktree per PR).
3. **Push the branch.** `git push -u origin HEAD` if the branch has no upstream; `git push` otherwise.
4. **Open a PR.** Use `gh pr create` with a title and body that explain the change. If multiple repos have work, open one PR per repo (or per logical change, if a repo has more than one). Surface the PR URL to the user.
5. **Wait for user review.** After opening each PR, **stop and wait** for the user to review. Preserve the resolved close target and current task-local binding while paused. Do not proceed to merge on your own. The user will indicate when a PR is approved and ready to merge — possibly after requesting changes that you then address in additional commits. Never merge unreviewed.
6. **Squash-merge after approval.** Once the user approves, merge with `gh pr merge --squash --delete-branch`. **Always squash.** Never use `--merge` or `--rebase`. This applies to every PR in every repo for this session, without exception.
7. **Update the local default branch.** After merge, in the main (non-worktree) checkout of that repo, detect the default branch with `git remote show origin | sed -n 's/ HEAD branch: //p'` (do not assume `main` or `master`), then fetch and fast-forward: `git fetch origin && git checkout <default> && git pull --ff-only`. If fast-forward fails, stop and surface the problem to the user rather than resolving via merge or reset.
8. **Remove the session worktree.** Once the branch is merged and the local default is updated, clean up the Worktrunk worktree with `wt remove` (see the `worktrunk` skill). Do not leave stale worktrees.

Repeat for each repo. Only once **every** PR for the session is merged and **every** local default branch is fast-forwarded should you move to Phase 2.

If the user decides to abandon rather than merge one of the PRs, close it with `gh pr close` and delete the branch; record the abandonment explicitly in `SUMMARY.md` under Outcome or Open Threads.

## Phase 2 — Record the session

Only after Phase 1 is complete:

1. **Finalize the write set before writing anything.** It always contains `.journal/<ID>/` and `.journal/INDEX.md`; add `.journal/TECH_NOTES.md` only if the session produced durable technical context. Require each shared file in this write set—not the entire worktree—to have no pre-existing uncommitted edits. Dirty paths outside this write set remain non-blocking.
2. **Write `SUMMARY.md`** at `<journal-root>/.journal/<ID>/SUMMARY.md` using `references/session-artifacts.md`. It is a postmortem written for another agent reading cold — cover goal, outcome (state plainly whether it was met), key decisions with reasons, changes, open threads, and references.
3. **Update `<journal-root>/.journal/INDEX.md`.** Add or update the row for this session. Set `Status` to `complete` (or `abandoned` if the work was dropped). Keep the summary cell to one sentence. Rows stay ordered oldest → newest.
4. **Update `<journal-root>/.journal/TECH_NOTES.md` if it is in the write set.** Revise it so future agents inherit the durable context. Keep it small; do not copy the session log into it.
5. **Append a final `NOTES.md` entry** using `references/session-artifacts.md`, pointing at the merged PRs and summarizing the hand-off state. `NOTES.md` is append-only — do not rewrite prior entries.
6. **Commit and push the journal mutation.** Stage only the write set with `git add -f -- <write-set>`, then commit only it with `git commit --only -m "docs(journal): close session <ID>" -- <write-set>`. Push `journal/<login>` and use the bounded rejected-push retry from `.session.md`. Never include another session's dirty or staged files.
7. **Update the task-local binding.** Only after the closeout journal mutation is committed and pushed successfully, clear the current binding if it equals `<ID>`. If an explicit ID selected a different session, preserve the existing binding unchanged. Do not clear or switch the binding when closeout fails or remains paused.
8. **Confirm to the user** what was recorded: the session ID, the PRs merged, the repos whose local default was updated, the fact that `SUMMARY.md` and `INDEX.md` were written, whether `TECH_NOTES.md` was updated, which journal branch was pushed, and whether the current task-local binding was cleared or preserved.

---

Do not skip the review-and-wait step in Phase 1. The whole point of opening PRs instead of merging locally is that the user sees the changes on GitHub before they land. Merging without explicit approval is a principle violation even if the change looks obviously correct.
Loading
Loading