Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1e77037
Make intent mode self-healing
KristjanPikhof Jul 13, 2026
130c071
Document self-healing intent mode
KristjanPikhof Jul 13, 2026
8e49b9b
Refresh contributor and release guidance
KristjanPikhof Jul 13, 2026
0cda6db
Fix plugin timeout respawn test
KristjanPikhof Jul 13, 2026
2f7db76
Clarify commit-all daemon lock behavior
KristjanPikhof Jul 13, 2026
36eaf5a
Fix literal path handling in recovery
KristjanPikhof Jul 13, 2026
73d06a9
Preserve recovery materialization prefixes
KristjanPikhof Jul 13, 2026
66bcbc1
Test literal recovery path handling
KristjanPikhof Jul 13, 2026
00786f8
Redact planner error URL paths
KristjanPikhof Jul 13, 2026
2d69a1d
Preserve planner health on lock contention
KristjanPikhof Jul 13, 2026
4088185
Clarify commit-all help formatting
KristjanPikhof Jul 13, 2026
2e01dd0
Make caller cancellation take precedence
KristjanPikhof Jul 13, 2026
6bb187c
Allow declined prompts with daemon lock
KristjanPikhof Jul 13, 2026
2b0920a
Clarify prune test base identifiers
KristjanPikhof Jul 13, 2026
c049054
Isolate decision test base identifiers
KristjanPikhof Jul 13, 2026
a8c3139
Make cancellation checks direct
KristjanPikhof Jul 13, 2026
903bc42
Clean up daemon startup formatting
KristjanPikhof Jul 13, 2026
ff6f054
Add recovery prefix retention index
KristjanPikhof Jul 13, 2026
6afcccd
Test recovery index migration
KristjanPikhof Jul 13, 2026
15a6eb3
Preserve whitespace in literal pathspecs
KristjanPikhof Jul 13, 2026
3f1ce7a
Expand literal filename coverage
KristjanPikhof Jul 13, 2026
d210999
Align migration test with schema version
KristjanPikhof Jul 13, 2026
3ebb107
Document schema v13 recovery safeguards
KristjanPikhof Jul 13, 2026
b24a66b
Increase respawn test startup timeout
KristjanPikhof Jul 13, 2026
f05adcc
Document command lifecycle and recovery
KristjanPikhof Jul 13, 2026
a105770
Document intent recovery behavior
KristjanPikhof Jul 13, 2026
9aac43c
Clarify rewrite command usage
KristjanPikhof Jul 13, 2026
e127bd0
Clarify barrier recovery guidance
KristjanPikhof Jul 13, 2026
06056ab
Clarify offline command workflows
KristjanPikhof Jul 13, 2026
35909a6
Update adapter setup and uninstall docs
KristjanPikhof Jul 13, 2026
961e6bd
Document shell adapter workflows
KristjanPikhof Jul 13, 2026
223fdea
Clarify Codex hook setup steps
KristjanPikhof Jul 13, 2026
7adcfec
Clarify OpenCode uninstall steps
KristjanPikhof Jul 13, 2026
778828f
Update repository list guidance
KristjanPikhof Jul 13, 2026
41646f8
Remove obsolete force recovery examples
KristjanPikhof Jul 13, 2026
e454da5
Clarify workflow health states
KristjanPikhof Jul 13, 2026
c61030a
Ignore local build artifacts
KristjanPikhof Jul 13, 2026
473b1a5
Update Go baseline to 1.26.5
KristjanPikhof Jul 13, 2026
cd53786
Stabilize branch transition tests
KristjanPikhof Jul 13, 2026
736b438
Set identity for recovery commits
KristjanPikhof Jul 13, 2026
b7b4788
Configure CLI test Git identity
KristjanPikhof Jul 13, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
contents: read

env:
GO_VERSION: '1.26.4'
GO_VERSION: '1.26.5'

jobs:
test:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
contents: read

env:
GO_VERSION: '1.26.4'
GO_VERSION: '1.26.5'

jobs:
analyze:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ permissions:
contents: read

env:
GO_VERSION: '1.26.4'
GO_VERSION: '1.26.5'

jobs:
goreleaser:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
.idea/
.vscode/
.claude/scheduled_tasks.lock
.editorconfig
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,56 @@

## Unreleased

### Added

- Bare `acd` now gives a read-only health summary and one recommended next
action. `acd on` and `acd off` provide idempotent everyday controls while
preserving per-repo state.
- Intent mode now persists planner circuit health. Transport failures open the
circuit immediately, repeated invalid plans open it after three failures,
and deterministic planning keeps commits moving during 30-second, 2-minute,
and 10-minute cooldowns.
- Recovery snapshots and hidden `refs/acd/recovery/*` refs now preserve exact
unpublished branch-generation chains before ACD changes their queue state.

### Changed

- Replay, branch transitions, dead-branch cleanup, `acd fix`, and `commit-all`
now reconcile whole unpublished chains. A stable exact `HEAD` match is proven
as published; any unresolved chain is archived without changing the live
worktree, index, or branch.
- `acd fix --force` now selects archive-only recovery. It no longer purges
terminal barriers or retargets captures to a different branch generation.
- `acd status` and `acd diagnose` now show planner circuit state, failures,
deterministic bypasses, and the next automatic provider probe.
- Updated pinned GitHub Actions dependency `actions/setup-go` from `v6.4.0` to
`v6.5.0`.
- Raised the minimum Go toolchain to 1.26.5 to include the standard-library
fix for GO-2026-5856.

### Fixed

- `commit-all --dry-run`, JSON consent refusal, and declined confirmation now
leave ACD state and recovery refs unchanged and do not start the AI provider.
- `acd fix` backups now include committed SQLite WAL frames and pass
`PRAGMA quick_check` before recovery mutation continues.
- `acd fix` and `commit-all` now recheck branch, Git-operation, and manual-pause
safety at mutation boundaries instead of relying on an earlier plan snapshot.
- `commit-all` now exits non-zero when unpublished events remain, instead of
reporting a partial drain as successful.
- Intent `commit-all` now reuses the planner circuit across replay passes, so a
provider outage falls back once instead of repeating the remote timeout.
- Recovery now proves grouped same-path commit prefixes cumulatively and keeps
each hidden evidence ref locked through its SQLite lifecycle transition.
- Published-event retention now preserves same-base recovery prefixes. Schema
v13 adds a covering index so the safety check remains bounded at ledger cap.
- Recovery now treats captured filenames as literal Git pathspecs without
trimming legal whitespace or interpreting pathspec-magic characters.
- Planner circuit state now initializes only after daemon-lock ownership,
caller cancellation wins provider-error races, and persisted errors redact
URL paths that may contain credentials.
- Daemon shutdown now cancels and joins the asynchronous startup recovery sweep
before releasing the daemon lock or returning database ownership.

## v2026-06-26

Expand Down
331 changes: 141 additions & 190 deletions CLAUDE.md

Large diffs are not rendered by default.

99 changes: 72 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ flowchart TB

## Install

Building from source or using `go install` requires Go 1.26.4 or newer.
Building from source or using `go install` requires Go 1.26.5 or newer.

~~~bash
brew install KristjanPikhof/tap/acd
Expand Down Expand Up @@ -143,6 +143,14 @@ settle window lets a burst of related edits reach one planner-visible window,
while `acd flush --logical` still drains the current visible batch from an
active harness session.

If the configured planner times out or returns unsafe output, ACD commits with
the deterministic planner instead of stalling the queue. A persisted circuit
breaker pauses repeated provider calls for 30 seconds, then 2 minutes, then 10
minutes. One probe is allowed after each cooldown, and a successful validated
plan closes the circuit. Bare `acd` reports degraded fallback health;
`acd status` and `acd diagnose` show the circuit state, failure count, bypass
count, and next probe time.

When one visible window contains separate intents, the planner prompt asks for
ordered `commit_groups` so replay can publish atomic commits instead of forcing
the whole window into one commit.
Expand All @@ -151,7 +159,7 @@ Message format:

| Format | Example subject | Notes |
|---|---|---|
| `imperative` | `Add commit format selection` | Default and recommended. Existing behavior is unchanged. |
| `imperative` | `Add commit format selection` | Default. Subjects start with an imperative verb. |
| `conventional` | `feat: add commit format selection` | Optional scope-less Conventional Commit style. |

~~~bash
Expand All @@ -165,55 +173,84 @@ rules as the default format.

## Daily commands

After setup, leave ACD running in the background. Use the three short commands
below for normal health and lifecycle control; the hook protocol stays
automatic.

| Need | Command |
|---|---|
| Start or refresh the current repo daemon | `acd start` |
| Watch all registered repos | `acd list` |
| Check this repo and get one recommended next action | `acd` |
| Enable ACD and ensure the daemon is running | `acd on` |
| Disable ACD and stop the daemon without deleting state | `acd off` |
| Watch enabled repos | `acd list` |
| Show this repo state | `acd status` |
| Follow capture, group, publish, and block decisions | `acd events --watch` |
| Ask why a path behaved a certain way | `acd explain --path FILE` |
| Ask what ACD did for a commit | `acd explain --commit HEAD` |
| Flush the current visible intent batch from an active harness session | `acd flush --session-id "$ACD_SESSION_ID" --logical` |
| Nudge capture and replay without bypassing intent batch gates | `acd wake --session-id "$ACD_SESSION_ID"` |
| Stop this repo daemon | `acd stop` |
| Stop every registered daemon | `acd stop --all` |
| Tail the daemon log | `acd logs --follow` |
| Create a support bundle | `acd doctor --bundle` |

`acd start` resolves your current directory to the canonical Git worktree root,
so calling it from a subdirectory refreshes the same daemon.
Bare `acd` is read-only. `acd on` and `acd off` are idempotent, work from a
subdirectory, and preserve `.git/acd/state.db`. Harness integrations continue
to use the lower-level `start`, `wake`, `touch`, `flush`, and `stop` commands.
See the [command reference](docs/commands.md) for every public command and its
read or write behavior.

## When commits stop

Use the same ladder every time:
ACD first tries to heal the queue itself. It reconciles the complete unpublished
chain for one branch generation, never an isolated blocker:

- If stable `HEAD` already has the chain's exact final touched-path state, ACD
marks the chain published and keeps a hidden proof ref.
- Otherwise ACD writes the reconstructed tree to a hidden recovery ref, marks
the chain recovered, reseeds from `HEAD`, and captures the still-dirty
worktree again.
- If objects are missing, the branch changes during proof, or a recovery ref
collides, ACD leaves the queue, live `HEAD`, index, and worktree unchanged.
A hidden evidence ref may remain so a safe retry can reuse the same tree.

Start with the short read-only path:

~~~bash
acd status
acd
acd diagnose
acd events --watch
acd explain --path path/to/file
acd diagnose --json
~~~

Use `fix` when you want to preview or run the same recovery manually. Preview
first, turn ACD off so hooks cannot restart the daemon during repair, then turn
it on again:

~~~bash
acd fix --dry-run
acd off
acd fix --yes
acd status
acd on
acd
~~~

Only use the force path after the dry-run shows terminal barriers with pending
successors and you have checked that the blocked changes are already in `HEAD`
or should be discarded:
`--force` means archive-only recovery. It does not purge, retarget, or discard
captured events:

~~~bash
acd fix --force --dry-run
acd off
acd fix --force --yes
acd on
~~~

`acd fix` backs up `state.db` before it mutates state and refuses to run while a
live daemon owns the database. If the problem is only a manual pause marker,
run:
`acd fix` creates a SQLite-consistent backup before it mutates state and refuses
to run while a live daemon owns the database. If the problem is only a manual
pause marker, run:

~~~bash
acd resume --yes
~~~

To inspect or restore an archived chain, use the `/archive` ref printed by
`acd events`; see [Inspect or restore archived work](docs/user-workflows.md#inspect-or-restore-archived-work).

If `acd diagnose --json` reports generated pending deletes under a tracked
cache directory such as `.derivedData-provider-core`, `acd fix --yes` cleans
only ACD's queue. Record the Git cleanup separately after review:
Expand All @@ -234,17 +271,22 @@ acd commit-all --yes
acd commit-all --yes --json
~~~

It refuses on detached HEAD, in-progress Git operations, manual pause markers,
or while the per-repo daemon is alive.
Detached HEAD, in-progress Git operations, and manual pause markers are refused
for previews and apply. If an authorized run reaches its mutation phase, it
also refuses while the per-repo daemon is alive. Dry-run, a declined
confirmation, and a clean no-op do not acquire `daemon.lock`; the first two are
read-only and do not capture files, start the AI provider, create recovery refs,
or write ACD state. An incomplete drain exits non-zero and leaves the captured
queue protected for diagnosis.

## Repo registration

Most repos need no manual setup. Harness hooks call `acd start`, which creates
`<gitDir>/acd/state.db` and registers the repo.

Use explicit lifecycle commands when autodiscovery is disabled or when old rows
need cleanup, or when you want to keep global autodiscovery on but exclude one
repo:
For normal use, prefer `acd on` and `acd off`. Use the explicit lifecycle
commands below for bulk administration, registry cleanup, or when global
autodiscovery is disabled:

~~~bash
acd repo init
Expand Down Expand Up @@ -299,8 +341,10 @@ explicit local cleanup before sharing a branch:
~~~bash
acd rewrite-commits --from-nr 5 --plan-out rewrite.json --plan-only
acd rewrite-commits --show-plan rewrite.json
acd off
acd rewrite-commits --apply-plan rewrite.json --dry-run
acd rewrite-commits --apply-plan rewrite.json --yes
acd on
~~~

Use `--from-sha <sha>` when you want a commit-ish selector, `--range-nr 5-12`
Expand Down Expand Up @@ -333,7 +377,7 @@ git reset --hard <backup-ref-or-sha>
| `ACD_INTENT_RETRY_ON_INVALID` | `2` | Max correction retries after invalid planner output. |
| `ACD_SAFE_IGNORE` | enabled | Set false-like value to stop pruning generated trees. |
| `ACD_SAFE_IGNORE_EXTRA` | unset | Extra generated trees, such as `dist/,build/`. |
| `ACD_SENSITIVE_GLOBS` | built in | Extra sensitive path globs. Empty keeps defaults. |
| `ACD_SENSITIVE_GLOBS` | built in | Non-empty values replace the protected path globs. Unset or empty uses the defaults. |
| `ACD_TRACE` | off | Writes daemon decision summaries under `<gitDir>/acd/trace/`. |
| `ACD_AI_PROMPT_TRACE` | off | Writes local AI request diagnostics. Treat as sensitive. |

Expand All @@ -344,6 +388,7 @@ Restart a running daemon after changing daemon runtime environment.
| Doc | Use it for |
|---|---|
| [docs/overview.md](docs/overview.md) | A short system map. |
| [docs/commands.md](docs/commands.md) | Every public command, its side effects, and safe examples. |
| [docs/user-workflows.md](docs/user-workflows.md) | Daily status, recovery, support bundles, and `commit-all`. |
| [docs/capture-replay.md](docs/capture-replay.md) | Storage, replay, branch safety, blockers, and trace classes. |
| [docs/intent-commit-flow.md](docs/intent-commit-flow.md) | Intent grouping behavior and planner observability. |
Expand Down
58 changes: 48 additions & 10 deletions docs/ai-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ Before sending, it redacts common secret shapes and truncates:
Redaction is a backstop, not a guarantee. Do not enable diff egress for a
private repo unless the endpoint or plugin is trusted.

`ACD_AI_SEND_DIFF` is removed. Use `ACD_AI_DIFF_EGRESS=1`.

## Environment reference

| Variable | Default | Notes |
Expand All @@ -92,20 +90,21 @@ private repo unless the endpoint or plugin is trusted.
| `ACD_AI_DIFF_EGRESS` | off | Truthy sends redacted captured diffs when the provider can use them. |
| `ACD_AI_PROMPT_TRACE` | off | Writes local prompt diagnostics under `<gitDir>/acd/prompt-trace/`. |
| `ACD_COMMIT_STRATEGY` | `event` | Set `intent` to ask the planner to group captures. |
| `ACD_COMMIT_FORMAT` | `imperative` | `imperative` keeps the current subject rules; `conventional` opts into scope-less Conventional Commit subjects. |
| `ACD_COMMIT_FORMAT` | `imperative` | `imperative` uses verb-led subjects; `conventional` uses scope-less Conventional Commit subjects. |
| `ACD_INTENT_WINDOW` | `10` | Max captures offered to one planner pass. |
| `ACD_INTENT_MIN_PENDING` | `10` | Preferred pending count before planning. |
| `ACD_INTENT_SETTLE_WINDOW` | `10s` | Burst settle delay after the count gate. `0` disables it. |
| `ACD_INTENT_MAX_PENDING_AGE` | `5m` | Age trigger for sparse queues. |
| `ACD_INTENT_RECENT_COMMITS` | `5` | Recent commits sent as compact context. |
| `ACD_INTENT_DEFER_LIMIT` | `1` | Deferrals before forced one-capture planning. |
| `ACD_INTENT_PATH_COALESCE` | off | Truthy restores legacy folding of consecutive same-path captures into one planner offer. |
| `ACD_INTENT_PATH_COALESCE` | off | Truthy folds consecutive same-path captures into one planner offer. |
| `ACD_INTENT_RETRY_ON_INVALID` | `2` | Max correction retries after typed planner validation errors. `0` or false-like values disable retries. |
| `ACD_INTENT_REJECTS_RAW` | off | Truthy stores raw rejected planner responses. Sensitive. |
| `ACD_PATH_QUIESCENCE_SECONDS` | `0` | Waits for paths to go quiet before planner offer. Capture still persists. |
| `ACD_RECENT_COMMIT_AFFINITY_SECONDS` | `0` | Adds a recent-HEAD hint when enabled. Off avoids extra `git log` work. |

Restart the daemon after changing provider, format, or intent environment.
Provider, format, and intent environment settings are read when the daemon
starts.

`ACD_COMMIT_FORMAT=conventional` accepts only `feat`, `fix`, `docs`,
`refactor`, `test`, `build`, `ci`, `chore`, `perf`, `style`, and `revert`
Expand Down Expand Up @@ -218,8 +217,8 @@ For windows that contain several independent intents, return ordered
}
~~~

The top-level `selected_seqs`, `subject`, `body`, and `grouping_reason` remain
required for legacy compatibility. When `commit_groups` is present,
The top-level `selected_seqs`, `subject`, `body`, and `grouping_reason` are
required. When `commit_groups` is present,
`selected_seqs` must be the union of all group selections; the top-level
message can mirror the first group or summarize the selected window.

Expand All @@ -237,7 +236,7 @@ Rules:
| `deferred_reasons` may mention only deferred seqs | Reasons stay aligned with the plan. |
| `subject` must match `commit_format` | Wrong-format output gets rejected, corrected, or falls back deterministically. |
| Non-empty `error` is a soft error | ACD keeps the plugin alive and falls back for that request. |
| Timeout, EOF, crash, or I/O error is a hard error | ACD kills the plugin and respawns it on the next request. |
| Timeout, EOF, crash, or I/O error is a hard error | ACD kills the plugin. Event mode restarts it on the next request; intent mode waits until the circuit allows a provider probe. |

Minimal smoke test:

Expand Down Expand Up @@ -265,9 +264,48 @@ Expected output is one JSON line with a non-empty `subject` and an empty
| Provider unset | Deterministic provider. |
| `openai-compat` succeeds | Provider result is used. |
| Provider returns the wrong message format | ACD rejects the response, retries when configured, then falls back deterministically. |
| `openai-compat` fails, times out, or has no key | Deterministic fallback. |
| Intent planner transport failure | Open the persisted circuit immediately and use deterministic fallback. |
| Three consecutive intent validation failures | Open the persisted circuit after configured correction retries are exhausted. |
| Intent circuit open | Skip the remote planner and use deterministic fallback without repeated planner-error decisions. |
| Intent circuit half-open | Allow one provider probe; other windows use deterministic fallback. |
| `openai-compat` has no key | Deterministic provider. |
| Subprocess response has `error` | Deterministic fallback, plugin stays alive. |
| Subprocess crashes or times out | Deterministic fallback, plugin restarts next time. |
| Subprocess crashes or times out | Deterministic fallback; the plugin restarts on the next allowed provider probe. |

The deterministic provider is the final backstop and should always return a
message.

### Inspect the intent planner circuit

Use either read-only command:

~~~bash
acd status --repo . --json
acd diagnose --repo . --json
~~~

Both expose `intent_strategy.planner_health`. The useful fields are:

| Field | Meaning |
|---|---|
| `state` | `closed`, `open`, or `half_open`. |
| `consecutive_failures` | Failures counted toward the current open state. |
| `backoff_level` | Cooldown step: 30 seconds, 2 minutes, then 10 minutes. |
| `next_probe_ts` | Unix timestamp when one half-open probe may run. |
| `opened_ts` | Unix timestamp when the current circuit-open period began. |
| `last_failure_ts` | Unix timestamp of the most recent counted failure. |
| `last_failure_class` | `transport` or `validation`. |
| `last_error` | Bounded, redacted diagnostic text. |
| `bypass_count` | Cumulative windows served by fallback while the circuit denied a provider call. |
| `provider_fingerprint` | Hash of provider, model, sanitized endpoint, and provider mode. |
| `updated_ts` | Unix timestamp of the latest persisted health update. |

If the persisted record is empty, malformed, unsafe to expose, or uses an
unsupported version, `status` and `diagnose` omit `planner_health` and return
`planner_health_warning`. The read path never repairs or deletes the meta row.

The circuit record persists across daemon restarts. A successful half-open
probe closes it. A failed probe advances cooldown from 30 seconds to 2 minutes,
then caps at 10 minutes. API keys are never part of the provider fingerprint;
endpoint credentials, query strings, authorization values, and common token
shapes are removed from stored errors.
Loading
Loading