@miller-tech/uap 1.165.0 → 1.168.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +47 -2
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/coord.d.ts +1 -1
- package/dist/cli/coord.d.ts.map +1 -1
- package/dist/cli/coord.js +59 -0
- package/dist/cli/coord.js.map +1 -1
- package/dist/cli/merge-queue.d.ts +53 -0
- package/dist/cli/merge-queue.d.ts.map +1 -0
- package/dist/cli/merge-queue.js +239 -0
- package/dist/cli/merge-queue.js.map +1 -0
- package/dist/cli/worktree.d.ts +69 -1
- package/dist/cli/worktree.d.ts.map +1 -1
- package/dist/cli/worktree.js +560 -26
- package/dist/cli/worktree.js.map +1 -1
- package/dist/config/policy-recommendations.d.ts.map +1 -1
- package/dist/config/policy-recommendations.js +4 -0
- package/dist/config/policy-recommendations.js.map +1 -1
- package/dist/coordination/index.d.ts +1 -0
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +1 -0
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/ownership.d.ts +51 -0
- package/dist/coordination/ownership.d.ts.map +1 -0
- package/dist/coordination/ownership.js +175 -0
- package/dist/coordination/ownership.js.map +1 -0
- package/docs/INDEX.md +2 -1
- package/docs/guides/PARALLEL_AGENTS.md +209 -0
- package/docs/guides/POLICIES.md +1 -0
- package/docs/guides/WORKTREE_WORKFLOW.md +5 -2
- package/docs/reference/CLI.md +24 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/branch_freshness.py +134 -0
- package/src/policies/enforcers/coord_overlap.py +126 -47
- package/src/policies/schemas/policies/branch-freshness.md +81 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/coordinate-file.sh +153 -3
- package/templates/hooks/session-end.sh +15 -0
- package/templates/hooks/session-start.sh +24 -2
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Policy: Branch Freshness
|
|
2
|
+
|
|
3
|
+
**ID**: `policy-branch-freshness`
|
|
4
|
+
**Name**: Work Against the Latest Integration Branch
|
|
5
|
+
**Category**: workflow
|
|
6
|
+
**Level**: REQUIRED
|
|
7
|
+
**Enforcement Stage**: pre-exec
|
|
8
|
+
**Version**: 1.0
|
|
9
|
+
|
|
10
|
+
## Purpose
|
|
11
|
+
|
|
12
|
+
Multiple agents working the same codebase in parallel lose each other's work in two
|
|
13
|
+
distinct ways. Only one of them is a "merge conflict".
|
|
14
|
+
|
|
15
|
+
1. **Concurrent collision** — two agents hold the same file at the same moment.
|
|
16
|
+
Git surfaces this loudly at merge time. Handled by `coordinate-file.sh`'s
|
|
17
|
+
live-agent lock.
|
|
18
|
+
2. **Sequential drift** — agent A lands a change; agent B's branch was cut before
|
|
19
|
+
that and never re-synced, so B edits a stale copy. B's merge silently reverts
|
|
20
|
+
A's work, or forces a conflict resolution that a model resolves by picking its
|
|
21
|
+
own version. Git does NOT warn about this. This is the expensive one.
|
|
22
|
+
|
|
23
|
+
This policy addresses (2): work must be based on, and kept close to, the current
|
|
24
|
+
integration branch.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
```rules
|
|
29
|
+
- title: "Fresh Base"
|
|
30
|
+
keywords: ["worktree", "branch", "create", "start work", "new feature"]
|
|
31
|
+
antiPatterns: ["branch from stale local", "no fetch before branching", "based on local master", "detached stale base"]
|
|
32
|
+
|
|
33
|
+
- title: "Stale File Guard"
|
|
34
|
+
keywords: ["edit", "write", "modify", "change file"]
|
|
35
|
+
antiPatterns: ["edit file changed upstream", "ignore upstream change", "overwrite landed work", "revert merged change"]
|
|
36
|
+
|
|
37
|
+
- title: "Drift Ceiling"
|
|
38
|
+
keywords: ["edit", "write", "worktree", "long-running branch"]
|
|
39
|
+
antiPatterns: ["hundreds of commits behind", "never synced", "stale worktree", "abandoned branch still edited"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Enforcement Behavior
|
|
43
|
+
|
|
44
|
+
### When Triggered
|
|
45
|
+
|
|
46
|
+
- **Worktree creation** — `uap worktree create` fetches and bases the new branch on
|
|
47
|
+
`origin/<default>` rather than whatever the local checkout has at HEAD.
|
|
48
|
+
- **Every worktree file edit** — two independent checks:
|
|
49
|
+
- *file-precise* (`coordinate-file.sh`): blocks when the specific file being
|
|
50
|
+
edited has changed on the integration branch since this branch's merge-base.
|
|
51
|
+
- *branch-coarse* (`branch_freshness.py`): warns at 50 commits behind, blocks at 200.
|
|
52
|
+
|
|
53
|
+
### Required Actions
|
|
54
|
+
|
|
55
|
+
1. Cut branches from the fetched remote tip, never from a stale local ref.
|
|
56
|
+
2. Before editing a file that moved upstream, run `uap worktree sync` and re-apply
|
|
57
|
+
the change on top of the landed version.
|
|
58
|
+
3. Keep a branch within the drift ceiling for its whole life; sync routinely rather
|
|
59
|
+
than once at `finish`, when resolution is most expensive.
|
|
60
|
+
4. Land PRs through `uap merge queue` so each merge re-syncs the PRs it impacts.
|
|
61
|
+
|
|
62
|
+
### Overrides
|
|
63
|
+
|
|
64
|
+
| Variable | Effect |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `UAP_COORD_DRIFT=warn` | file-precise check warns instead of blocking |
|
|
67
|
+
| `UAP_COORD_DRIFT=off` | disable the file-precise check |
|
|
68
|
+
| `UAP_NO_FRESHNESS=1` | disable the branch-coarse check |
|
|
69
|
+
| `UAP_FRESHNESS_WARN` / `UAP_FRESHNESS_BLOCK` | tune the drift thresholds |
|
|
70
|
+
| `uap worktree create --no-fetch` | branch offline, accepting a possibly stale base |
|
|
71
|
+
|
|
72
|
+
All checks fail **open**: no remote, no git, or an unreadable coordination DB
|
|
73
|
+
allows the edit. Coordination being unavailable must never stop work.
|
|
74
|
+
|
|
75
|
+
## Rationale
|
|
76
|
+
|
|
77
|
+
Measured on this repository before these gates existed: 151 worktrees, the worst
|
|
78
|
+
1241 commits behind `origin/master`, 23 holding unmerged commits and 15 with
|
|
79
|
+
uncommitted files. A worktree created during that audit was born 1 commit behind
|
|
80
|
+
because creation read local `HEAD` and never fetched. None of that drift was
|
|
81
|
+
visible to any gate, and none of it produced a warning at edit time.
|
|
Binary file
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
# different worktrees collides (that IS the future merge conflict).
|
|
12
12
|
#
|
|
13
13
|
# Exit 0 = allow (no conflict, or only a stale/self-healed announcement → warn).
|
|
14
|
-
# Exit 2 = BLOCK: another LIVE agent (heartbeat < THRESHOLD) holds this file
|
|
14
|
+
# Exit 2 = BLOCK: another LIVE agent (heartbeat < THRESHOLD) holds this file, OR
|
|
15
|
+
# the file has MOVED on the integration branch since this branch's
|
|
16
|
+
# merge-base (editing it now would silently revert landed work).
|
|
15
17
|
#
|
|
16
18
|
# Always fails OPEN: any missing dependency or DB error allows the edit. This
|
|
17
19
|
# hook must never break editing because coordination is unavailable.
|
|
@@ -27,10 +29,158 @@ ABS="${6:-}"
|
|
|
27
29
|
# Seconds since another agent's last heartbeat for it to count as "live".
|
|
28
30
|
THRESHOLD="${UAP_COORD_LIVE_SECONDS:-120}"
|
|
29
31
|
|
|
30
|
-
#
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Merge-base drift check (sequential-overwrite protection).
|
|
34
|
+
#
|
|
35
|
+
# The live-lock below only covers CONCURRENT edits — two agents holding the same
|
|
36
|
+
# file at the same moment. The far more common loss is SEQUENTIAL: agent A landed
|
|
37
|
+
# a change to foo.ts an hour ago; agent B's worktree was cut before that and never
|
|
38
|
+
# re-synced, so B edits a stale copy and its merge quietly reverts A's work.
|
|
39
|
+
#
|
|
40
|
+
# So: if THIS exact file changed on the integration branch since our merge-base,
|
|
41
|
+
# block until the agent syncs. Scoped to the single file being edited, so a stale
|
|
42
|
+
# worktree touching untouched files keeps working — no blanket freeze.
|
|
43
|
+
#
|
|
44
|
+
# UAP_COORD_DRIFT=block|warn|off (default block)
|
|
45
|
+
# UAP_COORD_FETCH_SECONDS=<n> throttle for the background fetch (default 600)
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
DRIFT_MODE="${UAP_COORD_DRIFT:-block}"
|
|
48
|
+
FETCH_THROTTLE="${UAP_COORD_FETCH_SECONDS:-600}"
|
|
49
|
+
|
|
50
|
+
# Validate every numeric knob before it reaches SQL or arithmetic. THRESHOLD is
|
|
51
|
+
# interpolated into the liveness SQL below; a non-numeric value there produced a
|
|
52
|
+
# silent SQL error, which fails the query open and disables the lock entirely.
|
|
53
|
+
case "$THRESHOLD" in ''|*[!0-9]*) THRESHOLD=120 ;; esac
|
|
54
|
+
case "$FETCH_THROTTLE" in ''|*[!0-9]*) FETCH_THROTTLE=600 ;; esac
|
|
55
|
+
|
|
56
|
+
# Bound the git calls in check_drift. Only the fetch was bounded before, so a
|
|
57
|
+
# slow merge-base/diff on a huge repo or a stalled network FS could hang the
|
|
58
|
+
# agent's Edit tool with no diagnostic. macOS has no `timeout` by default.
|
|
59
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
60
|
+
DRIFT_TIMEOUT="timeout -k 1 10"
|
|
61
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
62
|
+
DRIFT_TIMEOUT="gtimeout -k 1 10"
|
|
63
|
+
else
|
|
64
|
+
DRIFT_TIMEOUT=""
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
[ -n "$ME" ] && [ -n "$REL" ] || exit 0
|
|
68
|
+
|
|
69
|
+
# Resolve the integration ref (origin/HEAD → origin/master → origin/main).
|
|
70
|
+
integration_ref() {
|
|
71
|
+
local d="$1" r
|
|
72
|
+
r=$(git -C "$d" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null) && {
|
|
73
|
+
printf '%s' "${r#refs/remotes/}"; return 0; }
|
|
74
|
+
for c in master main; do
|
|
75
|
+
git -C "$d" rev-parse --verify --quiet "refs/remotes/origin/$c" >/dev/null 2>&1 && {
|
|
76
|
+
printf 'origin/%s' "$c"; return 0; }
|
|
77
|
+
done
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
check_drift() {
|
|
82
|
+
[ "$DRIFT_MODE" = "off" ] && return 0
|
|
83
|
+
command -v git >/dev/null 2>&1 || return 0
|
|
84
|
+
[ -n "$ABS" ] || return 0
|
|
85
|
+
|
|
86
|
+
local dir; dir="${ABS%/*}"
|
|
87
|
+
[ -d "$dir" ] || return 0
|
|
88
|
+
|
|
89
|
+
# Anchor EVERY git call to the working tree that actually contains the file.
|
|
90
|
+
# Running them from the file's own directory was a latent no-op: git resolves a
|
|
91
|
+
# pathspec against the process prefix, so `-C <wt>/src/cli ... -- src/cli/x.ts`
|
|
92
|
+
# looked for <wt>/src/cli/src/cli/x.ts and matched nothing. Drift then "passed"
|
|
93
|
+
# for every file below the repo root — i.e. all real source — silently and
|
|
94
|
+
# fail-open. It only appeared to work for files sitting at the root, which is
|
|
95
|
+
# exactly what the first round of tests used.
|
|
96
|
+
local top; top=$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null) || return 0
|
|
97
|
+
[ -n "$top" ] || return 0
|
|
98
|
+
|
|
99
|
+
# Never fight an in-progress merge/rebase: the conflict markers the agent has to
|
|
100
|
+
# edit are, by definition, in files that moved upstream. Blocking there deadlocks
|
|
101
|
+
# the very resolution `uap worktree sync` just asked for.
|
|
102
|
+
local gitdir; gitdir=$(git -C "$top" rev-parse --absolute-git-dir 2>/dev/null) || return 0
|
|
103
|
+
for marker in MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD REVERT_HEAD; do
|
|
104
|
+
[ -e "$gitdir/$marker" ] && return 0
|
|
105
|
+
done
|
|
106
|
+
[ -d "$gitdir/rebase-merge" ] || [ -d "$gitdir/rebase-apply" ] && return 0
|
|
107
|
+
|
|
108
|
+
# Path relative to THIS working tree, derived here rather than trusting the
|
|
109
|
+
# caller's REL — which is relative to the agent's cwd and is wrong whenever the
|
|
110
|
+
# agent stands in the main checkout while editing into a worktree.
|
|
111
|
+
local rel="${ABS#"$top"/}"
|
|
112
|
+
[ "$rel" != "$ABS" ] || return 0
|
|
113
|
+
|
|
114
|
+
local ref; ref=$(integration_ref "$top") || return 0
|
|
115
|
+
|
|
116
|
+
# Throttled fetch: an edit-time network call on EVERY keystroke-scale edit would
|
|
117
|
+
# be intolerable, so refresh at most once per FETCH_THROTTLE seconds. Between
|
|
118
|
+
# refreshes we compare against the last known remote tip — still catches the
|
|
119
|
+
# overwhelming majority of drift, and `uap worktree sync` refreshes on demand.
|
|
120
|
+
local common; common=$(git -C "$top" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) \
|
|
121
|
+
|| common=$(git -C "$top" rev-parse --git-common-dir 2>/dev/null) || return 0
|
|
122
|
+
case "$common" in /*) ;; *) common="$top/$common" ;; esac
|
|
123
|
+
# If the stamp dir is not real, DISABLE the fetch rather than the throttle —
|
|
124
|
+
# otherwise a bad path silently means "fetch on every single edit".
|
|
125
|
+
if [ -d "$common" ]; then
|
|
126
|
+
local stamp="$common/.uap-drift-fetch"
|
|
127
|
+
local now; now=$(date +%s)
|
|
128
|
+
local last=0
|
|
129
|
+
[ -f "$stamp" ] && read -r last < "$stamp" 2>/dev/null
|
|
130
|
+
case "$last" in ''|*[!0-9]*) last=0 ;; esac
|
|
131
|
+
# A future-dated stamp (clock skew, or a stray write) would otherwise wedge
|
|
132
|
+
# fetching off indefinitely.
|
|
133
|
+
[ "$last" -gt "$now" ] 2>/dev/null && last=0
|
|
134
|
+
# mkdir is atomic on POSIX and NFS: with many agents sharing one common dir,
|
|
135
|
+
# a read-compare-write throttle lets every agent in the window fetch at once.
|
|
136
|
+
if [ $((now - last)) -ge "$FETCH_THROTTLE" ] && mkdir "$stamp.lock" 2>/dev/null; then
|
|
137
|
+
printf '%s' "$now" > "$stamp.tmp.$$" 2>/dev/null &&
|
|
138
|
+
mv -f "$stamp.tmp.$$" "$stamp" 2>/dev/null || true
|
|
139
|
+
# Detached: nothing in THIS edit depends on the fetch landing — we compare
|
|
140
|
+
# against the last known tip either way. Redirections are load-bearing; a
|
|
141
|
+
# background job holding the hook's stdout makes the harness wait on it.
|
|
142
|
+
(
|
|
143
|
+
GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND='ssh -oBatchMode=yes -oConnectTimeout=5' \
|
|
144
|
+
$DRIFT_TIMEOUT git -C "$top" fetch -q origin "${ref#origin/}" >/dev/null 2>&1
|
|
145
|
+
rmdir "$stamp.lock" 2>/dev/null
|
|
146
|
+
) </dev/null >/dev/null 2>&1 &
|
|
147
|
+
fi
|
|
148
|
+
# Reap a lock orphaned by a killed agent (older than 10 min).
|
|
149
|
+
if [ -d "$stamp.lock" ]; then
|
|
150
|
+
local lockage; lockage=$(find "$stamp.lock" -maxdepth 0 -mmin +10 2>/dev/null || true)
|
|
151
|
+
[ -n "$lockage" ] && rmdir "$stamp.lock" 2>/dev/null
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
local mb; mb=$($DRIFT_TIMEOUT git -C "$top" merge-base HEAD "$ref" 2>/dev/null) || return 0
|
|
156
|
+
[ -n "$mb" ] || return 0
|
|
157
|
+
|
|
158
|
+
# Did this specific path change on the integration branch since we branched?
|
|
159
|
+
# diff-tree (plumbing) skips the work-tree setup and index read that `git diff`
|
|
160
|
+
# performs, and ":(top)" pins the pathspec to the repo root regardless of cwd.
|
|
161
|
+
local moved; moved=$($DRIFT_TIMEOUT git -C "$top" diff-tree -r --name-only --no-renames \
|
|
162
|
+
"$mb" "$ref" -- ":(top)$rel" 2>/dev/null)
|
|
163
|
+
[ -n "$moved" ] || return 0
|
|
164
|
+
|
|
165
|
+
local n; n=$($DRIFT_TIMEOUT git -C "$top" rev-list --count "$mb..$ref" 2>/dev/null || echo '?')
|
|
166
|
+
|
|
167
|
+
if [ "$DRIFT_MODE" = "warn" ]; then
|
|
168
|
+
echo "COORDINATION WARNING: ${rel} changed on ${ref} since your branch point (${n} commits behind). Run 'uap worktree sync' before editing or your merge may revert landed work." >&2
|
|
169
|
+
return 0
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
echo "{\"decision\":\"block\",\"reason\":\"STALE FILE: ${rel} has changed on ${ref} since your branch point (you are ${n} commits behind). Editing this copy risks silently reverting work that already landed. Run 'uap worktree sync' first, then re-apply your change on top. Override: UAP_COORD_DRIFT=warn.\"}" >&2
|
|
173
|
+
return 2
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# 0) Sequential-drift check FIRST. It depends only on git, never on the
|
|
177
|
+
# coordination DB — a missing/unwritable DB must not silently disable
|
|
178
|
+
# overwrite protection (it did: the DB prerequisite used to exit 0 above it).
|
|
179
|
+
check_drift || exit 2
|
|
180
|
+
|
|
181
|
+
# The announcement half needs sqlite3 and a real DB file; fail open without them.
|
|
31
182
|
command -v sqlite3 >/dev/null 2>&1 || exit 0
|
|
32
183
|
[ -n "$DB" ] && [ -f "$DB" ] || exit 0
|
|
33
|
-
[ -n "$ME" ] && [ -n "$REL" ] || exit 0
|
|
34
184
|
|
|
35
185
|
# SQL-escape single quotes.
|
|
36
186
|
q() { printf '%s' "${1:-}" | sed "s/'/''/g"; }
|
|
@@ -50,6 +50,21 @@ if [ -f "$COORD_DB" ]; then
|
|
|
50
50
|
OR (strftime('%s','now') - strftime('%s', last_heartbeat)) >= $STALE_SECS
|
|
51
51
|
);
|
|
52
52
|
" 2>/dev/null || true
|
|
53
|
+
|
|
54
|
+
# Release THIS agent's own holds immediately. The stale sweep above only frees
|
|
55
|
+
# an agent once its heartbeat has been dead for STALE_SECS — so a session that
|
|
56
|
+
# ended cleanly seconds after an edit kept its files locked against every peer
|
|
57
|
+
# for the full window, and peers saw a "live agent" that had already exited.
|
|
58
|
+
# We know our own id, so there is no reason to wait for it to look dead.
|
|
59
|
+
if [ -n "${UAP_AGENT_ID:-}" ]; then
|
|
60
|
+
ME_Q=$(printf '%s' "$UAP_AGENT_ID" | sed "s/'/''/g")
|
|
61
|
+
sqlite3 "$COORD_DB" "
|
|
62
|
+
UPDATE work_announcements SET completed_at = '$TIMESTAMP'
|
|
63
|
+
WHERE completed_at IS NULL AND agent_id = '$ME_Q';
|
|
64
|
+
UPDATE agent_registry SET status = 'completed'
|
|
65
|
+
WHERE id = '$ME_Q';
|
|
66
|
+
" 2>/dev/null || true
|
|
67
|
+
fi
|
|
53
68
|
fi
|
|
54
69
|
|
|
55
70
|
# Clean up backup files older than 7 days (retention policy)
|
|
@@ -146,9 +146,27 @@ fi
|
|
|
146
146
|
GIT_BRANCH=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "?")
|
|
147
147
|
GIT_DIRTY=$(git -C "$PROJECT_DIR" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
148
148
|
|
|
149
|
+
# .worktrees/ lives in the MAIN checkout, not in a linked worktree — so when the
|
|
150
|
+
# session starts INSIDE a worktree (the normal case under the worktree policy),
|
|
151
|
+
# PROJECT_DIR has no .worktrees/ and the count silently read 0. COORD_ROOT is
|
|
152
|
+
# already resolved from git-common-dir, i.e. the main checkout; reuse it.
|
|
149
153
|
WORKTREE_COUNT=0
|
|
150
|
-
if [ -d "${
|
|
151
|
-
WORKTREE_COUNT=$(find "${
|
|
154
|
+
if [ -d "${COORD_ROOT}/.worktrees" ]; then
|
|
155
|
+
WORKTREE_COUNT=$(find "${COORD_ROOT}/.worktrees" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Worktree drift advisory. A count of worktrees says nothing about whether any of
|
|
159
|
+
# them still hold work — silent accumulation is exactly how parallel-agent work
|
|
160
|
+
# gets lost. Measured here before this line existed: 151 worktrees, worst 1241
|
|
161
|
+
# commits behind origin/master, 23 holding unmerged commits. Purely advisory and
|
|
162
|
+
# time-boxed, so a slow git call can never delay session start.
|
|
163
|
+
WORKTREE_DRIFT=""
|
|
164
|
+
if [ "$WORKTREE_COUNT" -gt 0 ] && command -v uap >/dev/null 2>&1; then
|
|
165
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
166
|
+
WORKTREE_DRIFT=$(cd "$COORD_ROOT" 2>/dev/null && timeout 15 uap worktree hygiene --brief 2>/dev/null || true)
|
|
167
|
+
else
|
|
168
|
+
WORKTREE_DRIFT=$(cd "$COORD_ROOT" 2>/dev/null && uap worktree hygiene --brief 2>/dev/null || true)
|
|
169
|
+
fi
|
|
152
170
|
fi
|
|
153
171
|
|
|
154
172
|
PATTERN_COUNT=0
|
|
@@ -200,6 +218,10 @@ output+="- Worktree gate: run uap worktree ensure --strict (or uap worktree crea
|
|
|
200
218
|
output+="- Backup gate: copy files to .uap-backups/$(date +%Y-%m-%d)/ before modification."$'\n'
|
|
201
219
|
output+="- Coordination: agent=${AGENT_ID}; announce work, check overlaps, complete announcement when done."$'\n'
|
|
202
220
|
output+="- Validation: run relevant build/tests for changed code before finalizing."$'\n'
|
|
221
|
+
if [ -n "$WORKTREE_DRIFT" ]; then
|
|
222
|
+
output+="- ${WORKTREE_DRIFT}"$'\n'
|
|
223
|
+
output+="- Freshness: branch from the fetched remote tip; \`uap worktree sync\` before editing a file that moved upstream."$'\n'
|
|
224
|
+
fi
|
|
203
225
|
output+=""$'\n'
|
|
204
226
|
output+="</system-reminder>"$'\n\n'
|
|
205
227
|
|