carson 4.3.0 → 4.3.2
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.
- checksums.yaml +4 -4
- data/.github/workflows/carson_policy.yml +0 -4
- data/API.md +2 -3
- data/MANUAL.md +8 -8
- data/README.md +1 -1
- data/RELEASE.md +31 -0
- data/VERSION +1 -1
- data/config/hooks/pre-commit +2 -17
- data/config/hooks/pre-push +9 -55
- data/lib/carson/adapters/agent.rb +1 -1
- data/lib/carson/config.rb +3 -14
- data/lib/carson/courier.rb +14 -17
- data/lib/carson/runtime/deliver.rb +1 -1
- data/lib/carson/runtime/local/onboard.rb +9 -43
- data/lib/carson/runtime/receive.rb +1 -1
- data/lib/carson/runtime/recover.rb +3 -3
- data/lib/carson/runtime/review/sweep_support.rb +1 -1
- data/lib/carson/runtime.rb +2 -5
- data/lib/carson/warehouse/vault.rb +15 -5
- data/lib/cli.rb +38 -70
- metadata +1 -2
- data/lib/carson/runtime/audit.rb +0 -603
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cef1919871b9d457a8b1ad95b2faa127cbb1a740fa211bf48e1b18110141be5a
|
|
4
|
+
data.tar.gz: bd4f1107862848b8a60d5ef794f43127a6b8f14146ed781bfe72d841220f4190
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c4047e729026abc15be0601316a3521249cdc392c60b24b6ebf61bf5454746d48d649f16f2cda705e5a01ed2c9cce91ac2b407fd8d10ad670453fd2d0742d243
|
|
7
|
+
data.tar.gz: 9dee7343ec14dbf351fce53e0da6b406e52fe64bb71e736bb70ad23dc22c2e82013d34705d901d7b304d79b6cb40ef1cb8cc0c3412da9796ab6d853e81648156
|
data/API.md
CHANGED
|
@@ -21,7 +21,7 @@ Repo-scoped commands: `carson <repo> <command>` or `carson <command>` when CWD i
|
|
|
21
21
|
|---|---|
|
|
22
22
|
| `carson setup` | Interactive quiz to configure remote, main branch, workflow, and canonical lint-policy path. Writes `~/.carson/config.json`. |
|
|
23
23
|
| `carson onboard <repo_path>` | Apply one-command baseline setup for a target git repository. Auto-triggers `setup` on first run. Installs or refreshes Carson-managed global hooks. |
|
|
24
|
-
| `carson refresh` | Re-apply hooks
|
|
24
|
+
| `carson refresh` | Re-apply hooks and templates across all governed repos after upgrading Carson. Auto-propagates template updates to the remote via worktree (branch workflow: PR on `carson/template-sync`; trunk workflow: push to main). Skips repos with active worktrees or uncommitted changes. |
|
|
25
25
|
| `carson offboard <repo_path>` | Remove Carson-managed host artefacts, detach Carson hooks path, and deregister from `govern.repos`. |
|
|
26
26
|
| `carson list [--json]` | List all governed repositories. |
|
|
27
27
|
|
|
@@ -29,9 +29,8 @@ Repo-scoped commands: `carson <repo> <command>` or `carson <command>` when CWD i
|
|
|
29
29
|
|
|
30
30
|
| Command | Purpose |
|
|
31
31
|
|---|---|
|
|
32
|
-
| `carson audit` | Evaluate governance status and generate report output. |
|
|
33
32
|
| `carson deliver [--commit MESSAGE]` | Run Carson-owned branch delivery for the current checkout. Plain `deliver` transports existing commits only; `--commit` creates one all-dirty delivery commit first. Before push, Carson verifies the branch is fresh against the configured remote `main`; behind or unknown freshness blocks delivery without creating or refreshing a PR. If freshness is good, Carson pushes, creates or refreshes the PR, watches the delivery for a bounded settle window, merges when clear, syncs local `main`, and reports merge proof for the delivered branch. Non-integrated exits report `Merge deferred` or `Merge blocked` with explicit handoff commands. |
|
|
34
|
-
| `carson recover --check NAME [--json]` | Run the exceptional governed recovery path when one governance-owned required check is already red on the default branch. Carson proves the named baseline failure, keeps every other gate intact, merges through the recovery path
|
|
33
|
+
| `carson recover --check NAME [--json]` | Run the exceptional governed recovery path when one governance-owned required check is already red on the default branch. Carson proves the named baseline failure, keeps every other gate intact, and merges through the recovery path. |
|
|
35
34
|
| `carson sync` | Fast-forward local `main` from configured remote when tree is clean. |
|
|
36
35
|
| `carson prune` | Remove stale local branches whose upstream refs no longer exist. |
|
|
37
36
|
| `carson housekeep [--json] [--dry-run]` | Attempt to sync the current repo, then reap worktrees with strong abandonment evidence, reconcile integrated delivery worktree records from the ledger, and prune stale branches. Safe cleanup still runs when sync is blocked. |
|
data/MANUAL.md
CHANGED
|
@@ -40,7 +40,7 @@ On first run (no `~/.carson/config.json` exists), `onboard` launches `carson set
|
|
|
40
40
|
- Repository `core.hooksPath` alignment to Carson global hooks.
|
|
41
41
|
- Commit-time governance gate via managed `pre-commit` hook.
|
|
42
42
|
- Canonical `.github/*` template synchronisation (when `lint.canonical` is configured).
|
|
43
|
-
- Initial governance
|
|
43
|
+
- Initial governance status check.
|
|
44
44
|
|
|
45
45
|
### Reconfigure later
|
|
46
46
|
|
|
@@ -78,7 +78,7 @@ jobs:
|
|
|
78
78
|
Notes:
|
|
79
79
|
- When upgrading Carson, update both `carson_ref` and `carson_version` together.
|
|
80
80
|
- `CARSON_READ_TOKEN` must have read access to your policy source repository.
|
|
81
|
-
- The reusable workflow installs a pinned RuboCop gem before
|
|
81
|
+
- The reusable workflow installs a pinned RuboCop gem before lint checks; mirror the same pin in host governance workflows for deterministic checks.
|
|
82
82
|
|
|
83
83
|
### Canonical Templates
|
|
84
84
|
|
|
@@ -112,7 +112,7 @@ Carson discovers files in this directory and syncs them to governed repos. Root
|
|
|
112
112
|
|
|
113
113
|
## Operating Strategies
|
|
114
114
|
|
|
115
|
-
These strategies are the
|
|
115
|
+
These strategies are the design lens for Carson. If behaviour departs from them, either the product model or the implementation needs attention.
|
|
116
116
|
|
|
117
117
|
### Git Strategist
|
|
118
118
|
|
|
@@ -181,7 +181,7 @@ When a governance-owned required check is already red on the default branch, the
|
|
|
181
181
|
carson recover --check "Carson governance"
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
Recovery is narrow. Carson proves that the named check is red on the default branch, verifies that the current branch is repairing the governance surface, requires every other required check and the review gate to pass, then records a
|
|
184
|
+
Recovery is narrow. Carson proves that the named check is red on the default branch, verifies that the current branch is repairing the governance surface, requires every other required check and the review gate to pass, then records a recovery event before reporting success.
|
|
185
185
|
|
|
186
186
|
If Carson refuses recovery, the message explains the exact missing proof or remaining gate and tells you what to do next.
|
|
187
187
|
|
|
@@ -255,13 +255,13 @@ The two tools serve different layers. EnterWorktree owns the session (CWD switch
|
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
257
|
carson sync # fast-forward local main
|
|
258
|
-
carson
|
|
258
|
+
carson status # check repository state
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
**Before push or PR update:**
|
|
262
262
|
|
|
263
263
|
```bash
|
|
264
|
-
carson
|
|
264
|
+
carson status
|
|
265
265
|
carson template check
|
|
266
266
|
```
|
|
267
267
|
|
|
@@ -287,7 +287,7 @@ carson list --json # machine-readable output
|
|
|
287
287
|
**Portfolio maintenance:**
|
|
288
288
|
|
|
289
289
|
```bash
|
|
290
|
-
carson refresh # re-apply hooks
|
|
290
|
+
carson refresh # re-apply hooks and templates across all repos
|
|
291
291
|
carson list # list all governed repositories
|
|
292
292
|
```
|
|
293
293
|
|
|
@@ -411,7 +411,7 @@ Change: `CARSON_REVIEW_DISPOSITION`.
|
|
|
411
411
|
|
|
412
412
|
How much Carson prints.
|
|
413
413
|
|
|
414
|
-
- Default: **concise**. A healthy
|
|
414
|
+
- Default: **concise**. A healthy run prints one line. Problems print actionable summaries with cause and fix.
|
|
415
415
|
- `--verbose` restores full diagnostic key-value output for debugging.
|
|
416
416
|
|
|
417
417
|
## Configuration
|
data/README.md
CHANGED
|
@@ -66,7 +66,7 @@ carson housekeep
|
|
|
66
66
|
|
|
67
67
|
`carson deliver` runs Carson-owned branch delivery. Before any push, Carson verifies that the branch is fresh against the configured remote `main` using local git. If freshness is behind or unknown, delivery stops with an explicit block and no PR side effect. After a PR exists, Carson delegates merge eligibility to GitHub's `mergeStateStatus` — if GitHub reports `CLEAN`, Carson proceeds regardless of local ancestor status. Plain `deliver` transports existing commits only; `carson deliver --commit "..."` creates one all-dirty delivery commit first, then continues the same flow. If the branch is fresh, Carson pushes it, creates or refreshes the PR, watches the delivery for a bounded settle window, merges when clear, and syncs local `main`. If the settle window expires without integration, Carson exits with an explicit `Merge deferred` or `Merge blocked` handoff instead of leaving the PR mysteriously open. Deferred and blocked exits say whether Carson attempted merge and list the next commands in order.
|
|
68
68
|
|
|
69
|
-
When one Carson-governed required check is already red on the default branch and the current PR is the repair, use `carson recover --check "..."`. Recovery is the explicit exceptional path: Carson proves the baseline failure, keeps every other gate intact, records
|
|
69
|
+
When one Carson-governed required check is already red on the default branch and the current PR is the repair, use `carson recover --check "..."`. Recovery is the explicit exceptional path: Carson proves the baseline failure, keeps every other gate intact, records a recovery event, and never teaches operators to step outside Carson first.
|
|
70
70
|
|
|
71
71
|
`carson worktree list` is the visibility surface for cleanup: it shows every registered worktree, the branch, PR state, whether the content is already on `main`, and Carson's keep or reap recommendation. Content matching `main` is diagnostic, not standalone proof that a worktree is abandoned. `carson housekeep` is the main cleanup pass: sync main, reap worktrees with strong abandonment evidence (for example a missing directory, merged PR, or closed abandoned PR), and prune stale branches. When work needs to be abandoned instead of landed, use `carson abandon <pr-number|pr-url|branch>` to close the PR and clean up the branch/worktree safely.
|
|
72
72
|
|
data/RELEASE.md
CHANGED
|
@@ -7,6 +7,37 @@ Release-note scope rule:
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- **`carson audit` dissolved.** The audit command and its pre-commit gate have been removed entirely. Three of its seven checks were already enforced by the commands that need them (sealed workbench by `pack!`, working tree by Courier, main sync by `sync!`). The remaining four (PR status, CI baseline, hooks health, outsider fingerprints) were either unproven at commit time or already called by individual commands. Bare `carson` now defaults to `status` instead of `audit`. The pre-commit hook is now a no-op reserved for future use.
|
|
13
|
+
|
|
14
|
+
### Why
|
|
15
|
+
|
|
16
|
+
Audit was a remote-centred pre-commit gate — it blocked commits based on delivery-time concerns (PR checks, CI baseline) that don't belong at commit time. In local-centred mode it was purely dead weight. Rather than relocate it to bureau, we verified that none of its unique checks had proven value at any gate, and dissolved the whole thing. Scars, not speculation.
|
|
17
|
+
|
|
18
|
+
## 4.3.2
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Vault gives correct recovery when main worktree is dirty.** `vault_blocked` now inspects stderr from `git merge --ff-only` to distinguish dirty-tree conflicts from diverged-history blocks. Previously, both cases got the generic "rebase" advice — misleading when the branch was already a valid fast-forward descendant and the real problem was uncommitted files in the main worktree.
|
|
23
|
+
|
|
24
|
+
### Why
|
|
25
|
+
|
|
26
|
+
An agent rebased three times on an already-correct branch because Carson misdiagnosed a dirty-tree block as divergence. The fix: read stderr, give the right advice. Scars, not speculation.
|
|
27
|
+
|
|
28
|
+
## 4.3.1
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Bureau is an enhancement, not a mode.** Config simplified from `workstyle: local/remote` to `bureau: true/false` (default: false). Local delivery is always the foundation. Bureau adds PR + CI on top.
|
|
33
|
+
- **Pre-push hook simplified to no-op.** Pushes to main are always legitimate — local delivery is the base. The hook no longer needs workstyle detection or push guards.
|
|
34
|
+
- **Parcel-on-main guard added.** `carson deliver` blocks when run from main itself — agents must work on a workbench.
|
|
35
|
+
- **Output: "Synced to remote"** replaces "Pushed to remote". Sync is objective.
|
|
36
|
+
|
|
37
|
+
### Why
|
|
38
|
+
|
|
39
|
+
The key insight: remote-centred is not a different path — it's local delivery with Bureau enhancement bolted on. One path, optional layer. `workstyle` dissolved into a single `bureau` toggle. 72 insertions, 166 deletions.
|
|
40
|
+
|
|
10
41
|
## 4.3.0
|
|
11
42
|
|
|
12
43
|
### New
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.3.
|
|
1
|
+
4.3.2
|
data/config/hooks/pre-commit
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
carson_command=( "$CARSON_BIN" )
|
|
6
|
-
elif command -v carson >/dev/null 2>&1; then
|
|
7
|
-
carson_command=( "carson" )
|
|
8
|
-
else
|
|
9
|
-
echo "Carson policy: 'carson' command is required for pre-commit governance checks." >&2
|
|
10
|
-
echo "Install Carson and rerun 'carson prepare'." >&2
|
|
11
|
-
exit 1
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
if ! "${carson_command[@]}" audit; then
|
|
15
|
-
echo "Carson policy: commit blocked by governance audit." >&2
|
|
16
|
-
echo "Resolve reported policy blocks before committing." >&2
|
|
17
|
-
echo "If lint policy files are missing, run: carson lint policy --source <path-or-git-url>" >&2
|
|
18
|
-
exit 1
|
|
19
|
-
fi
|
|
3
|
+
# Pre-commit hook — reserved for future use.
|
|
4
|
+
exit 0
|
data/config/hooks/pre-push
CHANGED
|
@@ -1,60 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Carson pre-push hook
|
|
2
|
+
# Carson pre-push hook.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
4
|
+
# Local delivery is always the base — pushes to main are legitimate.
|
|
5
|
+
# When bureau enhancement is enabled, the Bureau path runs through
|
|
6
|
+
# Runtime.deliver!, not through raw git push. So the hook has nothing
|
|
7
|
+
# to enforce in either case.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
9
|
+
# The hook remains installed for future use (e.g. bureau-specific guards).
|
|
10
|
+
# For now: consume stdin and exit.
|
|
10
11
|
set -euo pipefail
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
[ "$style" = "trunk" ] && exit 0
|
|
15
|
-
|
|
16
|
-
# --- Guard 1: block pushes to main/master refs ---
|
|
17
|
-
|
|
18
|
-
remote_name="${1:-unknown}"
|
|
19
|
-
remote_url="${2:-unknown}"
|
|
20
|
-
has_commit_push=false
|
|
21
|
-
while read -r local_ref local_sha remote_ref remote_sha; do
|
|
22
|
-
case "$remote_ref" in
|
|
23
|
-
refs/heads/main|refs/heads/master)
|
|
24
|
-
echo "Pushes to ${remote_ref#refs/heads/} go through PRs, not direct push." >&2
|
|
25
|
-
echo "Use \`carson deliver\` instead — it handles push, PR, and merge with safety guards." >&2
|
|
26
|
-
echo "Bypass: git push --no-verify" >&2
|
|
27
|
-
exit 1
|
|
28
|
-
;;
|
|
29
|
-
esac
|
|
30
|
-
[[ "$local_sha" != "0000000000000000000000000000000000000000" ]] && has_commit_push=true
|
|
31
|
-
done
|
|
32
|
-
|
|
33
|
-
# --- Guard 2: block raw git push in governed repos ---
|
|
34
|
-
# All pushes in governed repos are blocked unconditionally.
|
|
35
|
-
# Carson bypasses this hook via --no-verify when pushing internally.
|
|
36
|
-
# No env-var bypass — cannot be spoofed.
|
|
37
|
-
|
|
38
|
-
config_file="${HOME}/.carson/config.json"
|
|
39
|
-
if [[ -f "$config_file" ]]; then
|
|
40
|
-
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
41
|
-
if [[ -n "$repo_root" ]]; then
|
|
42
|
-
normalised="$(cd "$repo_root" && pwd -P)"
|
|
43
|
-
if grep -qF "\"$normalised\"" "$config_file" 2>/dev/null; then
|
|
44
|
-
echo "This repo is Carson-governed — use \`carson deliver\` instead of raw \`git push\`." >&2
|
|
45
|
-
echo "Use \`carson deliver\` instead — it handles push, PR, and merge with safety guards." >&2
|
|
46
|
-
echo "Bypass: git push --no-verify" >&2
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
fi
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
# --- Template sync on push ---
|
|
53
|
-
|
|
54
|
-
if $has_commit_push; then
|
|
55
|
-
if [[ -n "${CARSON_BIN:-}" ]]; then
|
|
56
|
-
ruby "$CARSON_BIN" template apply --push-prep || exit 1
|
|
57
|
-
else
|
|
58
|
-
carson template apply --push-prep || exit 1
|
|
59
|
-
fi
|
|
60
|
-
fi
|
|
13
|
+
cat > /dev/null
|
|
14
|
+
exit 0
|
|
@@ -3,7 +3,7 @@ module Carson
|
|
|
3
3
|
module Adapters
|
|
4
4
|
module Agent
|
|
5
5
|
WorkOrder = Struct.new( :repo, :branch, :pr_number, :objective, :context, :acceptance_checks, keyword_init: true )
|
|
6
|
-
# objective: "fix_ci" | "address_review"
|
|
6
|
+
# objective: "fix_ci" | "address_review"
|
|
7
7
|
# context: String (legacy — PR title) or Hash with structured evidence:
|
|
8
8
|
# fix_ci: { title:, ci_logs:, ci_run_url:, prior_attempt: { summary:, dispatched_at: } }
|
|
9
9
|
# address_review: { title:, review_findings: [{ kind:, url:, body: }], prior_attempt: ... }
|
data/lib/carson/config.rb
CHANGED
|
@@ -28,13 +28,12 @@ module Carson
|
|
|
28
28
|
:review_wait_seconds, :review_poll_seconds, :review_max_polls, :review_sweep_window_days,
|
|
29
29
|
:review_sweep_states, :review_disposition, :review_risk_keywords,
|
|
30
30
|
:review_tracking_issue_title, :review_tracking_issue_label, :review_bot_usernames,
|
|
31
|
-
:audit_advisory_check_names,
|
|
32
31
|
:workflow_style,
|
|
33
32
|
:govern_repos, :govern_merge_method,
|
|
34
33
|
:govern_agent_provider, :govern_state_path,
|
|
35
34
|
:govern_check_wait,
|
|
36
35
|
:poll_interval_at_bureau,
|
|
37
|
-
:
|
|
36
|
+
:bureau
|
|
38
37
|
|
|
39
38
|
def self.load( repo_root: )
|
|
40
39
|
base_data = default_data
|
|
@@ -80,13 +79,10 @@ module Carson
|
|
|
80
79
|
"label" => "carson-review-sweep"
|
|
81
80
|
}
|
|
82
81
|
},
|
|
83
|
-
"audit" => {
|
|
84
|
-
"advisory_check_names" => [ "Scheduled review sweep", "Carson governance", "Tag, release, publish" ]
|
|
85
|
-
},
|
|
86
82
|
"deliver" => {
|
|
87
83
|
"poll_interval_at_bureau" => 30
|
|
88
84
|
},
|
|
89
|
-
"
|
|
85
|
+
"bureau" => false,
|
|
90
86
|
"govern" => {
|
|
91
87
|
"repos" => [],
|
|
92
88
|
"merge" => {
|
|
@@ -170,9 +166,6 @@ module Carson
|
|
|
170
166
|
sweep[ "states" ] = states unless states.empty?
|
|
171
167
|
bot_usernames = env_string_array( key: "CARSON_REVIEW_BOT_USERNAMES" )
|
|
172
168
|
review[ "bot_usernames" ] = bot_usernames unless bot_usernames.empty?
|
|
173
|
-
audit = fetch_hash_section( data: copy, key: "audit" )
|
|
174
|
-
advisory_names = env_string_array( key: "CARSON_AUDIT_ADVISORY_CHECK_NAMES" )
|
|
175
|
-
audit[ "advisory_check_names" ] = advisory_names unless advisory_names.empty?
|
|
176
169
|
deliver = fetch_hash_section( data: copy, key: "deliver" )
|
|
177
170
|
deliver[ "poll_interval_at_bureau" ] = env_integer( key: "CARSON_POLL_INTERVAL_AT_BUREAU", fallback: deliver.fetch( "poll_interval_at_bureau" ) )
|
|
178
171
|
govern = fetch_hash_section( data: copy, key: "govern" )
|
|
@@ -243,14 +236,10 @@ module Carson
|
|
|
243
236
|
@review_tracking_issue_title = fetch_string( hash: tracking_issue_hash, key: "title" )
|
|
244
237
|
@review_tracking_issue_label = fetch_string( hash: tracking_issue_hash, key: "label" )
|
|
245
238
|
@review_bot_usernames = fetch_optional_string_array( hash: review_hash, key: "bot_usernames" )
|
|
246
|
-
audit_hash = fetch_hash( hash: data, key: "audit" )
|
|
247
|
-
@audit_advisory_check_names = fetch_optional_string_array( hash: audit_hash, key: "advisory_check_names" )
|
|
248
|
-
|
|
249
239
|
deliver_hash = fetch_hash( hash: data, key: "deliver" )
|
|
250
240
|
@poll_interval_at_bureau = fetch_non_negative_integer( hash: deliver_hash, key: "poll_interval_at_bureau" )
|
|
251
241
|
|
|
252
|
-
|
|
253
|
-
@workstyle = [ "local", "remote" ].include?( workstyle_raw ) ? workstyle_raw.to_sym : :local
|
|
242
|
+
@bureau = !!data.fetch( "bureau", false )
|
|
254
243
|
|
|
255
244
|
govern_hash = fetch_hash( hash: data, key: "govern" )
|
|
256
245
|
@govern_repos = fetch_optional_string_array( hash: govern_hash, key: "repos" ).map { |path| safe_expand_path( path ) }
|
data/lib/carson/courier.rb
CHANGED
|
@@ -64,11 +64,10 @@ module Carson
|
|
|
64
64
|
# == Workstyle
|
|
65
65
|
#
|
|
66
66
|
# The courier's gesture depends on the workstyle:
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# it just delivers to wherever the workstyle dictates.
|
|
67
|
+
# The courier delivers parcels. The default gesture is local: sync the
|
|
68
|
+
# vault to the remote. When bureau enhancement is enabled, the courier
|
|
69
|
+
# also files a PR and waits for Bureau checks — but that path runs
|
|
70
|
+
# through Runtime.deliver!, not through this default gesture.
|
|
72
71
|
class Courier
|
|
73
72
|
# Exit codes — shared contract between Carson employees and the CLI.
|
|
74
73
|
OK = 0
|
|
@@ -80,9 +79,9 @@ module Carson
|
|
|
80
79
|
# The courier checks the bureau up to 6 times before leaving.
|
|
81
80
|
MAX_CHECKS_AT_BUREAU = 6
|
|
82
81
|
|
|
83
|
-
def initialize( warehouse,
|
|
82
|
+
def initialize( warehouse, bureau: false, ledger: nil, merge_method: "rebase", poll_interval_at_bureau: 30, output: $stdout )
|
|
84
83
|
@warehouse = warehouse
|
|
85
|
-
@
|
|
84
|
+
@bureau = bureau
|
|
86
85
|
@ledger = ledger
|
|
87
86
|
@merge_method = merge_method
|
|
88
87
|
@poll_interval_at_bureau = poll_interval_at_bureau
|
|
@@ -90,10 +89,10 @@ module Carson
|
|
|
90
89
|
end
|
|
91
90
|
|
|
92
91
|
# Deliver a parcel.
|
|
93
|
-
#
|
|
94
|
-
#
|
|
92
|
+
# Default: sync the vault to the remote.
|
|
93
|
+
# Bureau enhancement: also file waybill, poll, register.
|
|
95
94
|
def deliver( parcel, title: nil, body_file: nil, commit_message: nil )
|
|
96
|
-
return deliver_locally( parcel )
|
|
95
|
+
return deliver_locally( parcel ) unless @bureau
|
|
97
96
|
|
|
98
97
|
result = {
|
|
99
98
|
command: "deliver",
|
|
@@ -255,9 +254,9 @@ module Carson
|
|
|
255
254
|
result[ :diagnostic ] = waybill.ci_diagnostic
|
|
256
255
|
end
|
|
257
256
|
|
|
258
|
-
# Local gesture:
|
|
257
|
+
# Local gesture: sync the vault to the remote.
|
|
259
258
|
# The parcel is already in the vault (accepted by the Warehouse).
|
|
260
|
-
# The courier's job is to push the vault state to the remote
|
|
259
|
+
# The courier's job is to push the vault state to the remote.
|
|
261
260
|
def deliver_locally( parcel )
|
|
262
261
|
result = {
|
|
263
262
|
command: "deliver",
|
|
@@ -269,11 +268,9 @@ module Carson
|
|
|
269
268
|
main = @warehouse.main_label
|
|
270
269
|
root = @warehouse.main_worktree_root
|
|
271
270
|
|
|
272
|
-
#
|
|
273
|
-
# to main in governed repos. This IS Carson's delivery — the vault
|
|
274
|
-
# accepted the parcel, now the courier backs it up.
|
|
271
|
+
# The pre-push hook understands local workstyle — no bypass needed.
|
|
275
272
|
_, stderr, status = Open3.capture3(
|
|
276
|
-
"git", "-C", root, "push",
|
|
273
|
+
"git", "-C", root, "push", remote, main
|
|
277
274
|
)
|
|
278
275
|
|
|
279
276
|
if status.success?
|
|
@@ -284,7 +281,7 @@ module Carson
|
|
|
284
281
|
result[ :exit ] = OK
|
|
285
282
|
result[ :outcome ] = "delivered"
|
|
286
283
|
result[ :synced ] = false
|
|
287
|
-
result[ :
|
|
284
|
+
result[ :sync_error ] = stderr.strip
|
|
288
285
|
end
|
|
289
286
|
|
|
290
287
|
result
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Repository onboarding and refresh lifecycle.
|
|
2
|
-
# Onboard: detect remote, install hooks, apply templates
|
|
2
|
+
# Onboard: detect remote, install hooks, apply templates.
|
|
3
3
|
# Refresh: re-apply hooks and templates after Carson upgrade.
|
|
4
4
|
# Refresh all: batch refresh across governed portfolio with safety checks.
|
|
5
5
|
module Carson
|
|
6
6
|
class Runtime
|
|
7
7
|
module Local
|
|
8
8
|
# One-command onboarding for new repositories: detect remote, install hooks,
|
|
9
|
-
# apply templates
|
|
9
|
+
# and apply templates.
|
|
10
10
|
def onboard!
|
|
11
11
|
fingerprint_status = block_if_outsider_fingerprints!
|
|
12
12
|
return fingerprint_status unless fingerprint_status.nil?
|
|
@@ -32,7 +32,7 @@ module Carson
|
|
|
32
32
|
onboard_apply!
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# Re-applies hooks
|
|
35
|
+
# Re-applies hooks and templates after upgrading Carson.
|
|
36
36
|
def refresh!
|
|
37
37
|
fingerprint_status = block_if_outsider_fingerprints!
|
|
38
38
|
return fingerprint_status unless fingerprint_status.nil?
|
|
@@ -55,13 +55,8 @@ module Carson
|
|
|
55
55
|
|
|
56
56
|
@template_sync_result = template_propagate!( drift_count: drift_count + stale_count )
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
puts_line "OK: Carson refresh completed for #{repo_root}."
|
|
61
|
-
elsif audit_status == EXIT_BLOCK
|
|
62
|
-
puts_line "Refresh complete — some checks need attention. Run carson audit for details."
|
|
63
|
-
end
|
|
64
|
-
return audit_status
|
|
58
|
+
puts_line "OK: Carson refresh completed for #{repo_root}."
|
|
59
|
+
return EXIT_OK
|
|
65
60
|
end
|
|
66
61
|
|
|
67
62
|
puts_line "Refresh"
|
|
@@ -82,12 +77,11 @@ module Carson
|
|
|
82
77
|
|
|
83
78
|
@template_sync_result = template_propagate!( drift_count: total_drift )
|
|
84
79
|
|
|
85
|
-
audit_status = audit!
|
|
86
80
|
puts_line "Refresh complete."
|
|
87
|
-
|
|
81
|
+
EXIT_OK
|
|
88
82
|
end
|
|
89
83
|
|
|
90
|
-
# Re-applies hooks
|
|
84
|
+
# Re-applies hooks and templates across all governed repositories.
|
|
91
85
|
# Checks each repo for safety (active worktrees, uncommitted changes) and
|
|
92
86
|
# marks unsafe repos as pending to avoid disrupting active work.
|
|
93
87
|
def refresh_all!
|
|
@@ -202,7 +196,7 @@ module Carson
|
|
|
202
196
|
|
|
203
197
|
private
|
|
204
198
|
|
|
205
|
-
# Concise onboard orchestration: hooks, templates, remote,
|
|
199
|
+
# Concise onboard orchestration: hooks, templates, remote, guidance.
|
|
206
200
|
def onboard_apply!
|
|
207
201
|
hook_status = with_captured_output { prepare! }
|
|
208
202
|
return hook_status unless hook_status == EXIT_OK
|
|
@@ -218,7 +212,6 @@ module Carson
|
|
|
218
212
|
end
|
|
219
213
|
|
|
220
214
|
onboard_report_remote!
|
|
221
|
-
audit_status = onboard_run_audit!
|
|
222
215
|
|
|
223
216
|
puts_line ""
|
|
224
217
|
puts_line "Carson at your service."
|
|
@@ -235,7 +228,7 @@ module Carson
|
|
|
235
228
|
puts_line ""
|
|
236
229
|
puts_line "To adjust any setting: carson setup"
|
|
237
230
|
|
|
238
|
-
|
|
231
|
+
EXIT_OK
|
|
239
232
|
end
|
|
240
233
|
|
|
241
234
|
# Friendly remote status for onboard output.
|
|
@@ -247,33 +240,6 @@ module Carson
|
|
|
247
240
|
end
|
|
248
241
|
end
|
|
249
242
|
|
|
250
|
-
# Runs audit with captured output; reports summary instead of full detail.
|
|
251
|
-
def onboard_run_audit!
|
|
252
|
-
audit_error = nil
|
|
253
|
-
audit_status = with_captured_output { audit! }
|
|
254
|
-
rescue StandardError => exception
|
|
255
|
-
audit_error = exception
|
|
256
|
-
audit_status = EXIT_OK
|
|
257
|
-
ensure
|
|
258
|
-
return onboard_print_audit_result( status: audit_status, error: audit_error )
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def onboard_print_audit_result( status:, error: )
|
|
262
|
-
if error
|
|
263
|
-
if error.message.to_s.match?( /HEAD|rev-parse/ )
|
|
264
|
-
puts_line "No commits yet — run carson audit after your first commit."
|
|
265
|
-
else
|
|
266
|
-
puts_line "Audit skipped — run carson audit for details."
|
|
267
|
-
end
|
|
268
|
-
return EXIT_OK
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
if status == EXIT_BLOCK
|
|
272
|
-
puts_line "Some checks need attention — run carson audit for details."
|
|
273
|
-
end
|
|
274
|
-
status
|
|
275
|
-
end
|
|
276
|
-
|
|
277
243
|
# Verifies configured remote exists and logs status without mutating remotes.
|
|
278
244
|
def report_detected_remote!
|
|
279
245
|
if git_remote_exists?( remote_name: config.git_remote )
|
|
@@ -84,14 +84,14 @@ module Carson
|
|
|
84
84
|
}
|
|
85
85
|
if baseline.fetch( :status ) == "skipped"
|
|
86
86
|
result[ :error ] = "unable to verify the default-branch baseline: #{baseline.fetch( :skip_reason )}"
|
|
87
|
-
result[ :recovery ] = "run carson
|
|
87
|
+
result[ :recovery ] = "run carson status after fixing GitHub access"
|
|
88
88
|
return recover_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
baseline_entry = recovery_baseline_entry( baseline: baseline, check_name: check_name )
|
|
92
92
|
if baseline_entry.nil?
|
|
93
93
|
result[ :error ] = "#{check_name} is not red on #{baseline.fetch( :default_branch, config.main_branch )}"
|
|
94
|
-
result[ :recovery ] = "run carson
|
|
94
|
+
result[ :recovery ] = "run carson status to confirm the baseline check state"
|
|
95
95
|
return recover_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
|
|
96
96
|
end
|
|
97
97
|
|
|
@@ -408,7 +408,7 @@ module Carson
|
|
|
408
408
|
elsif result[ :synced ]
|
|
409
409
|
puts_line "Synced local #{result.fetch( :main_branch )}."
|
|
410
410
|
end
|
|
411
|
-
puts_line "Recorded recovery
|
|
411
|
+
puts_line "Recorded recovery for #{result.fetch( :check )}."
|
|
412
412
|
puts_line "Check back with #{result.fetch( :next_step )}" if result[ :next_step ]
|
|
413
413
|
end
|
|
414
414
|
end
|
|
@@ -163,7 +163,7 @@ module Carson
|
|
|
163
163
|
}
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
-
# When sweep is clear, close prior tracking issue and add
|
|
166
|
+
# When sweep is clear, close prior tracking issue and add a clear comment.
|
|
167
167
|
def close_review_sweep_issue_if_open( repo_slug:, issue: )
|
|
168
168
|
return { action: "none", issue: nil } if issue.nil?
|
|
169
169
|
return { action: "none", issue: issue } unless issue.fetch( :state ) == "OPEN"
|
data/lib/carson/runtime.rb
CHANGED
|
@@ -14,8 +14,6 @@ module Carson
|
|
|
14
14
|
EXIT_ERROR = 1
|
|
15
15
|
EXIT_BLOCK = 2
|
|
16
16
|
|
|
17
|
-
REPORT_MD = "pr_report_latest.md".freeze
|
|
18
|
-
REPORT_JSON = "pr_report_latest.json".freeze
|
|
19
17
|
REVIEW_GATE_REPORT_MD = "review_gate_latest.md".freeze
|
|
20
18
|
REVIEW_GATE_REPORT_JSON = "review_gate_latest.json".freeze
|
|
21
19
|
REVIEW_SWEEP_REPORT_MD = "review_sweep_latest.md".freeze
|
|
@@ -43,7 +41,7 @@ module Carson
|
|
|
43
41
|
attr_reader :template_sync_result
|
|
44
42
|
|
|
45
43
|
# Lazy ledger: only constructed when a command actually needs delivery state.
|
|
46
|
-
# Read-only commands (worktree list,
|
|
44
|
+
# Read-only commands (worktree list, prune, sync) never touch the
|
|
47
45
|
# govern state lock file.
|
|
48
46
|
def ledger
|
|
49
47
|
@ledger ||= Ledger.new( path: @config.govern_state_path )
|
|
@@ -115,7 +113,7 @@ module Carson
|
|
|
115
113
|
success
|
|
116
114
|
end
|
|
117
115
|
|
|
118
|
-
# Human-readable plural suffix helper
|
|
116
|
+
# Human-readable plural suffix helper.
|
|
119
117
|
def plural_suffix( count: )
|
|
120
118
|
count.to_i == 1 ? "" : "s"
|
|
121
119
|
end
|
|
@@ -366,7 +364,6 @@ module Carson
|
|
|
366
364
|
end
|
|
367
365
|
|
|
368
366
|
require_relative "runtime/local"
|
|
369
|
-
require_relative "runtime/audit"
|
|
370
367
|
require_relative "runtime/loop_runner"
|
|
371
368
|
require_relative "runtime/housekeep"
|
|
372
369
|
require_relative "runtime/list"
|
|
@@ -63,12 +63,22 @@ module Carson
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Build the blocked/error result when vault acceptance fails.
|
|
66
|
+
# Distinguishes dirty-tree conflicts from diverged-history blocks
|
|
67
|
+
# so the agent gets the correct recovery advice.
|
|
66
68
|
def vault_blocked( parcel, stderr )
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
if stderr.to_s.include?( "would be overwritten" )
|
|
70
|
+
{
|
|
71
|
+
status: "block",
|
|
72
|
+
error: "Main worktree has uncommitted changes that conflict with #{parcel.label}.",
|
|
73
|
+
recovery: "Commit or discard the dirty files in the main worktree, then deliver again."
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
{
|
|
77
|
+
status: "block",
|
|
78
|
+
error: "#{parcel.label} cannot be fast-forwarded into #{@main_label}.",
|
|
79
|
+
recovery: "Rebase onto #{@main_label} and deliver again."
|
|
80
|
+
}
|
|
81
|
+
end
|
|
72
82
|
end
|
|
73
83
|
|
|
74
84
|
end
|