agent-control-plane 0.1.9 → 0.1.13

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.
Files changed (40) hide show
  1. package/hooks/heartbeat-hooks.sh +147 -8
  2. package/hooks/issue-reconcile-hooks.sh +46 -0
  3. package/npm/bin/agent-control-plane.js +89 -8
  4. package/package.json +8 -2
  5. package/references/commands.md +1 -0
  6. package/tools/bin/agent-project-cleanup-session +133 -0
  7. package/tools/bin/agent-project-publish-issue-pr +178 -62
  8. package/tools/bin/agent-project-reconcile-issue-session +171 -3
  9. package/tools/bin/agent-project-run-codex-resilient +121 -16
  10. package/tools/bin/agent-project-run-codex-session +118 -10
  11. package/tools/bin/agent-project-run-openclaw-session +82 -8
  12. package/tools/bin/branch-verification-guard.sh +15 -2
  13. package/tools/bin/cleanup-worktree.sh +4 -1
  14. package/tools/bin/dashboard-launchd-bootstrap.sh +16 -4
  15. package/tools/bin/ensure-runtime-sync.sh +182 -0
  16. package/tools/bin/flow-config-lib.sh +76 -30
  17. package/tools/bin/flow-resident-worker-lib.sh +28 -2
  18. package/tools/bin/flow-shell-lib.sh +15 -1
  19. package/tools/bin/heartbeat-safe-auto.sh +32 -0
  20. package/tools/bin/issue-publish-localization-guard.sh +142 -0
  21. package/tools/bin/project-launchd-bootstrap.sh +17 -4
  22. package/tools/bin/project-runtime-supervisor.sh +7 -1
  23. package/tools/bin/project-runtimectl.sh +78 -15
  24. package/tools/bin/reuse-issue-worktree.sh +46 -0
  25. package/tools/bin/start-issue-worker.sh +110 -30
  26. package/tools/bin/start-resident-issue-loop.sh +1 -0
  27. package/tools/bin/sync-shared-agent-home.sh +50 -10
  28. package/tools/bin/test-smoke.sh +6 -1
  29. package/tools/dashboard/app.js +71 -1
  30. package/tools/dashboard/dashboard_snapshot.py +74 -0
  31. package/tools/dashboard/styles.css +43 -0
  32. package/tools/templates/issue-prompt-template.md +20 -65
  33. package/tools/templates/legacy/issue-prompt-template-pre-slim.md +109 -0
  34. package/bin/audit-issue-routing.sh +0 -74
  35. package/tools/bin/audit-agent-worktrees.sh +0 -310
  36. package/tools/bin/audit-issue-routing.sh +0 -11
  37. package/tools/bin/audit-retained-layout.sh +0 -58
  38. package/tools/bin/audit-retained-overlap.sh +0 -135
  39. package/tools/bin/audit-retained-worktrees.sh +0 -228
  40. package/tools/bin/check-skill-contracts.sh +0 -324
@@ -12,67 +12,31 @@ Implement issue #{ISSUE_ID} in `{REPO_SLUG}`.
12
12
  {ISSUE_RECURRING_CONTEXT}
13
13
  {ISSUE_BLOCKER_CONTEXT}
14
14
 
15
- # MANDATORY WORKFLOW (follow in order, no skipping)
15
+ # Required Contract
16
16
 
17
- You MUST complete ALL 5 phases in order. Do not skip any phase. Do not commit until Phase 4 passes.
17
+ Follow this order:
18
18
 
19
- ## Phase 1: READ & SCOPE
19
+ 1. Read `AGENTS.md`, choose one narrow target, and stay within that slice.
20
+ 2. If the issue is broader than one safe slice, stop and create follow-up issues instead of forcing a large patch:
21
+ ```bash
22
+ bash "$ACP_FLOW_TOOLS_DIR/create-follow-up-issue.sh" --parent {ISSUE_ID} --title "..." --body-file /tmp/follow-up.md
23
+ ```
24
+ 3. Implement the smallest root-cause fix in this worktree only.
25
+ 4. Run the narrowest relevant local verification for the files you changed, and record every successful command with `record-verification.sh`.
20
26
 
21
- - Read the repo instructions: `AGENTS.md`, relevant spec or design docs, and any repo conventions tied to this issue.
22
- - Identify the single primary product surface you will touch.
23
- - If the issue spans multiple surfaces, pick ONE and create follow-up issues for the rest using:
24
- ```bash
25
- bash "$ACP_FLOW_TOOLS_DIR/create-follow-up-issue.sh" --parent {ISSUE_ID} --title "..." --body-file /tmp/follow-up.md
26
- ```
27
- - Treat a broad umbrella issue as a coordination brief rather than permission to ship every slice in one PR.
28
- - Write down your scope decision before coding.
29
-
30
- ## Phase 2: IMPLEMENT
31
-
32
- - Make the smallest root-cause fix that satisfies the issue.
33
- - Work only inside the dedicated worktree.
34
- - Add or update tests when feasible.
35
- - STOP after implementation. Do not commit yet.
36
-
37
- ## Phase 3: VERIFY (MANDATORY)
38
-
39
- After implementing, you MUST run verification commands and record each one.
40
- Every successful command must be recorded or the host publish will fail.
41
- After each successful verification command, record it with `record-verification.sh`.
27
+ - Do not default to repo-wide verification such as `pnpm test` unless the issue body explicitly requires it.
28
+ - If unrelated repo-wide suites are already red, keep the cycle focused on targeted verification for your slice and let the host verification guard decide whether publication is safe.
42
29
 
43
30
  ```bash
44
31
  {ISSUE_VERIFICATION_COMMAND_SNIPPET}
45
32
  ```
46
33
 
47
- Required verification coverage:
48
- - Run the narrowest repo-supported `typecheck`, `build`, `test`, or `lint` command that proves the touched surface is safe.
49
- - If you changed tests only, run the most relevant targeted test command and record it.
50
- - If you changed localization resources or user-facing copy, run repo locale validation or hardcoded-copy scans if the repo provides them.
51
- - If a verification command fails, fix the issue and rerun until it passes.
52
-
53
- CRITICAL: `verification.jsonl` must exist in `$ACP_RUN_DIR` with at least one `pass` entry before you can write `OUTCOME=implemented`.
54
-
55
- ## Phase 4: SELF-REVIEW (MANDATORY)
56
-
57
- Before committing, perform this checklist:
58
-
59
- - [ ] Run `git diff --check`.
60
- - [ ] Count non-test product files: if the change is broad, stop and split scope instead of publishing one large PR.
61
- - [ ] If you touched auth, login, session, or reset flows, verify existing users and legacy data still behave correctly.
62
- - [ ] If you touched public endpoints, public routes, or operator workflows, search downstream consumers in `scripts/`, `docs/`, and specs.
63
- - [ ] If you changed localization resources or user-facing copy, confirm localization coverage and scanning are still valid.
64
- - [ ] If you touched mobile routes or screens, keep route scope narrow and verify loading, empty, and error states.
65
-
66
- Before committing, verify the journal exists:
67
- ```bash
68
- test -s "$ACP_RUN_DIR/verification.jsonl" && echo "OK: verification.jsonl exists" || echo "BLOCKED: missing verification.jsonl"
69
- ```
70
-
71
- ## Phase 5: COMMIT & REPORT
72
-
73
- - Commit with a conventional commit message.
74
- - Do NOT push or open a PR; the host handles that.
75
- - Write `$ACP_RESULT_FILE`:
34
+ 5. Before committing, run at least:
35
+ ```bash
36
+ git diff --check
37
+ test -s "$ACP_RUN_DIR/verification.jsonl" && echo "OK: verification.jsonl exists" || echo "BLOCKED: missing verification.jsonl"
38
+ ```
39
+ 6. If verification passes, commit locally and write `$ACP_RESULT_FILE`:
76
40
  ```bash
77
41
  cat > "$ACP_RESULT_FILE" <<'OUTER_EOF'
78
42
  OUTCOME=implemented
@@ -80,16 +44,7 @@ test -s "$ACP_RUN_DIR/verification.jsonl" && echo "OK: verification.jsonl exists
80
44
  ISSUE_ID={ISSUE_ID}
81
45
  OUTER_EOF
82
46
  ```
83
- - In your final output, include the changed files, verification commands actually run, and one short self-review note naming the main regression risk you checked.
84
-
85
- # STOP CONDITIONS
86
-
87
- Stop and report blocked if:
88
- - The issue is ambiguous, blocked by missing credentials, or expands into high-risk scope.
89
- - You cannot complete verification successfully.
90
- - The issue needs full decomposition into focused follow-up issues.
91
-
92
- If stopped blocked, write `$ACP_RUN_DIR/issue-comment.md` with a blocker summary, then:
47
+ 7. If blocked, write `$ACP_RUN_DIR/issue-comment.md` and then write:
93
48
  ```bash
94
49
  cat > "$ACP_RESULT_FILE" <<'OUTER_EOF'
95
50
  OUTCOME=blocked
@@ -98,7 +53,7 @@ ISSUE_ID={ISSUE_ID}
98
53
  OUTER_EOF
99
54
  ```
100
55
 
101
- If fully decomposed into follow-up issues, start the first line of `issue-comment.md` with exactly:
56
+ If you fully decompose the work, the first line of `issue-comment.md` must be:
102
57
  `Superseded by focused follow-up issues: #...`
103
58
 
104
59
  # Git Rules
@@ -106,4 +61,4 @@ If fully decomposed into follow-up issues, start the first line of `issue-commen
106
61
  - Do NOT push the branch from inside the worker.
107
62
  - Do NOT open a PR from inside the worker.
108
63
  - Do NOT comment on the source issue with a PR URL from inside the worker.
109
- - Exit successfully after writing the result file.
64
+ - Exit after writing the result file.
@@ -0,0 +1,109 @@
1
+ # Task
2
+
3
+ Implement issue #{ISSUE_ID} in `{REPO_SLUG}`.
4
+
5
+ # Issue Context
6
+
7
+ - Title: {ISSUE_TITLE}
8
+ - URL: {ISSUE_URL}
9
+ - Auto-merge requested: {ISSUE_AUTOMERGE}
10
+
11
+ {ISSUE_BODY}
12
+ {ISSUE_RECURRING_CONTEXT}
13
+ {ISSUE_BLOCKER_CONTEXT}
14
+
15
+ # MANDATORY WORKFLOW (follow in order, no skipping)
16
+
17
+ You MUST complete ALL 5 phases in order. Do not skip any phase. Do not commit until Phase 4 passes.
18
+
19
+ ## Phase 1: READ & SCOPE
20
+
21
+ - Read the repo instructions: `AGENTS.md`, relevant spec or design docs, and any repo conventions tied to this issue.
22
+ - Identify the single primary product surface you will touch.
23
+ - If the issue spans multiple surfaces, pick ONE and create follow-up issues for the rest using:
24
+ ```bash
25
+ bash "$ACP_FLOW_TOOLS_DIR/create-follow-up-issue.sh" --parent {ISSUE_ID} --title "..." --body-file /tmp/follow-up.md
26
+ ```
27
+ - Treat a broad umbrella issue as a coordination brief rather than permission to ship every slice in one PR.
28
+ - Write down your scope decision before coding.
29
+
30
+ ## Phase 2: IMPLEMENT
31
+
32
+ - Make the smallest root-cause fix that satisfies the issue.
33
+ - Work only inside the dedicated worktree.
34
+ - Add or update tests when feasible.
35
+ - STOP after implementation. Do not commit yet.
36
+
37
+ ## Phase 3: VERIFY (MANDATORY)
38
+
39
+ After implementing, you MUST run verification commands and record each one.
40
+ Every successful command must be recorded or the host publish will fail.
41
+ After each successful verification command, record it with `record-verification.sh`.
42
+
43
+ ```bash
44
+ {ISSUE_VERIFICATION_COMMAND_SNIPPET}
45
+ ```
46
+
47
+ Required verification coverage:
48
+ - Run the narrowest repo-supported `typecheck`, `build`, `test`, or `lint` command that proves the touched surface is safe.
49
+ - If you changed tests only, run the most relevant targeted test command and record it.
50
+ - If you changed localization resources or user-facing copy, run repo locale validation or hardcoded-copy scans if the repo provides them.
51
+ - If a verification command fails, fix the issue and rerun until it passes.
52
+
53
+ CRITICAL: `verification.jsonl` must exist in `$ACP_RUN_DIR` with at least one `pass` entry before you can write `OUTCOME=implemented`.
54
+
55
+ ## Phase 4: SELF-REVIEW (MANDATORY)
56
+
57
+ Before committing, perform this checklist:
58
+
59
+ - [ ] Run `git diff --check`.
60
+ - [ ] Count non-test product files: if the change is broad, stop and split scope instead of publishing one large PR.
61
+ - [ ] If you touched auth, login, session, or reset flows, verify existing users and legacy data still behave correctly.
62
+ - [ ] If you touched public endpoints, public routes, or operator workflows, search downstream consumers in `scripts/`, `docs/`, and specs.
63
+ - [ ] If you changed localization resources or user-facing copy, confirm localization coverage and scanning are still valid.
64
+ - [ ] If you touched mobile routes or screens, keep route scope narrow and verify loading, empty, and error states.
65
+
66
+ Before committing, verify the journal exists:
67
+ ```bash
68
+ test -s "$ACP_RUN_DIR/verification.jsonl" && echo "OK: verification.jsonl exists" || echo "BLOCKED: missing verification.jsonl"
69
+ ```
70
+
71
+ ## Phase 5: COMMIT & REPORT
72
+
73
+ - Commit with a conventional commit message.
74
+ - Do NOT push or open a PR; the host handles that.
75
+ - Write `$ACP_RESULT_FILE`:
76
+ ```bash
77
+ cat > "$ACP_RESULT_FILE" <<'OUTER_EOF'
78
+ OUTCOME=implemented
79
+ ACTION=host-publish-issue-pr
80
+ ISSUE_ID={ISSUE_ID}
81
+ OUTER_EOF
82
+ ```
83
+ - In your final output, include the changed files, verification commands actually run, and one short self-review note naming the main regression risk you checked.
84
+
85
+ # STOP CONDITIONS
86
+
87
+ Stop and report blocked if:
88
+ - The issue is ambiguous, blocked by missing credentials, or expands into high-risk scope.
89
+ - You cannot complete verification successfully.
90
+ - The issue needs full decomposition into focused follow-up issues.
91
+
92
+ If stopped blocked, write `$ACP_RUN_DIR/issue-comment.md` with a blocker summary, then:
93
+ ```bash
94
+ cat > "$ACP_RESULT_FILE" <<'OUTER_EOF'
95
+ OUTCOME=blocked
96
+ ACTION=host-comment-blocker
97
+ ISSUE_ID={ISSUE_ID}
98
+ OUTER_EOF
99
+ ```
100
+
101
+ If fully decomposed into follow-up issues, start the first line of `issue-comment.md` with exactly:
102
+ `Superseded by focused follow-up issues: #...`
103
+
104
+ # Git Rules
105
+
106
+ - Do NOT push the branch from inside the worker.
107
+ - Do NOT open a PR from inside the worker.
108
+ - Do NOT comment on the source issue with a PR URL from inside the worker.
109
+ - Exit successfully after writing the result file.
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/../tools/bin/flow-config-lib.sh"
7
-
8
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
9
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
10
- AGENT_PR_PREFIXES_JSON="$(flow_managed_pr_prefixes_json "${CONFIG_YAML}")"
11
- AGENT_PR_ISSUE_CAPTURE_REGEX="$(flow_managed_issue_branch_regex "${CONFIG_YAML}")"
12
- MIN_AGE_MINUTES="${1:-30}"
13
-
14
- open_agent_pr_issue_ids="$(
15
- gh pr list -R "$REPO_SLUG" --state open --limit 100 --json headRefName,body,labels,comments \
16
- | jq --argjson agentPrPrefixes "${AGENT_PR_PREFIXES_JSON}" --arg branchIssueRegex "${AGENT_PR_ISSUE_CAPTURE_REGEX}" '
17
- map(
18
- . as $pr
19
- | select(
20
- any($agentPrPrefixes[]; (($pr.headRefName // "") | startswith(.)))
21
- or any(($pr.labels // [])[]?; .name == "agent-handoff")
22
- or any(($pr.comments // [])[]?; ((.body // "") | test("^## PR (final review blocker|repair worker summary|repair summary|repair update)"; "i")))
23
- )
24
- | [
25
- (
26
- $pr.headRefName
27
- | capture($branchIssueRegex)?
28
- | .id
29
- ),
30
- (
31
- ($pr.body // "")
32
- | capture("(?i)\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s+#(?<id>[0-9]+)\\b")?
33
- | .id
34
- )
35
- ]
36
- | .[]
37
- | select(. != null and . != "")
38
- )
39
- | unique
40
- '
41
- )"
42
-
43
- gh issue list -R "$REPO_SLUG" --state open --limit 100 --json number,title,createdAt,updatedAt,labels \
44
- | jq -r --argjson openAgentPrIssueIds "$open_agent_pr_issue_ids" --argjson minAgeMinutes "$MIN_AGE_MINUTES" '
45
- def label_names: [.labels[]?.name];
46
- def age_minutes:
47
- ((now - ((.createdAt | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601))) / 60);
48
- def has_open_agent_pr:
49
- ((.number | tostring) as $issueId | ($openAgentPrIssueIds | index($issueId)) != null);
50
- map(
51
- . + {
52
- reason:
53
- (if any(label_names[]?; . == "agent-running") and (has_open_agent_pr | not) then
54
- "stale-agent-running"
55
- elif any(label_names[]?; . == "agent-blocked") then
56
- "blocked-manual-review"
57
- else
58
- ""
59
- end)
60
- }
61
- )
62
- | map(select(.reason != "" and (age_minutes >= $minAgeMinutes)))
63
- | sort_by(.createdAt, .number)
64
- | .[]
65
- | [
66
- (.number | tostring),
67
- .reason,
68
- .createdAt,
69
- .updatedAt,
70
- (label_names | join(",")),
71
- .title
72
- ]
73
- | @tsv
74
- '
@@ -1,310 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
-
8
- WORKER_STATUS_TOOL="${BASH_SOURCE[0]%/*}/agent-project-worker-status"
9
- WORKTREE_CLEANUP_TOOL="${BASH_SOURCE[0]%/*}/agent-cleanup-worktree"
10
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
11
- AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
12
- WORKTREE_ROOT="$(flow_resolve_worktree_root "${CONFIG_YAML}")"
13
- AGENT_ROOT="$(flow_resolve_agent_root "${CONFIG_YAML}")"
14
- RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
15
- PENDING_LAUNCH_DIR="${ACP_PENDING_LAUNCH_DIR:-${F_LOSNING_PENDING_LAUNCH_DIR:-$(flow_resolve_state_root "${CONFIG_YAML}")/pending-launches}}"
16
- ISSUE_SESSION_PREFIX="$(flow_resolve_issue_session_prefix "${CONFIG_YAML}")"
17
- PR_SESSION_PREFIX="$(flow_resolve_pr_session_prefix "${CONFIG_YAML}")"
18
- ISSUE_BRANCH_PREFIX="$(flow_resolve_issue_branch_prefix "${CONFIG_YAML}")"
19
- PR_WORKTREE_BRANCH_PREFIX="$(flow_resolve_pr_worktree_branch_prefix "${CONFIG_YAML}")"
20
- MANAGED_PR_BRANCH_GLOBS="$(flow_resolve_managed_pr_branch_globs "${CONFIG_YAML}")"
21
- ISSUE_BRANCH_PREFIX_REGEX="$(flow_escape_regex "${ISSUE_BRANCH_PREFIX}")"
22
- PR_WORKTREE_BRANCH_PREFIX_REGEX="$(flow_escape_regex "${PR_WORKTREE_BRANCH_PREFIX}")"
23
-
24
- cleanup="false"
25
- strict="false"
26
-
27
- usage() {
28
- cat <<'EOF'
29
- Usage:
30
- audit-agent-worktrees.sh [--cleanup] [--strict]
31
-
32
- Audits agent-managed worktrees attached to the anchor repo and removes stale
33
- ones when they no longer have a running owner and only contain generated
34
- artifacts such as node_modules symlinks or .openclaw-artifacts.
35
-
36
- Options:
37
- --cleanup Remove clean/orphaned agent worktrees automatically.
38
- --strict Exit non-zero when any stale agent worktree is found.
39
- EOF
40
- }
41
-
42
- while [[ $# -gt 0 ]]; do
43
- case "$1" in
44
- --cleanup) cleanup="true"; shift ;;
45
- --strict) strict="true"; shift ;;
46
- --help|-h) usage; exit 0 ;;
47
- *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
48
- esac
49
- done
50
-
51
- if [[ ! -d "$AGENT_REPO_ROOT/.git" && ! -f "$AGENT_REPO_ROOT/.git" ]]; then
52
- echo "agent repo root is not a Git checkout: $AGENT_REPO_ROOT" >&2
53
- exit 1
54
- fi
55
-
56
- session_for_worktree() {
57
- local worktree_path="${1:-}"
58
- local branch_ref="${2:-}"
59
- local base
60
-
61
- base="$(basename "$worktree_path")"
62
- if [[ "$base" =~ ^pr-([0-9]+)- ]]; then
63
- printf '%s%s\n' "${PR_SESSION_PREFIX}" "${BASH_REMATCH[1]}"
64
- return 0
65
- fi
66
- if [[ "$base" =~ ^issue-([0-9]+)- ]]; then
67
- printf '%s%s\n' "${ISSUE_SESSION_PREFIX}" "${BASH_REMATCH[1]}"
68
- return 0
69
- fi
70
- if [[ "$branch_ref" =~ ^refs/heads/${PR_WORKTREE_BRANCH_PREFIX_REGEX}-([0-9]+)- ]]; then
71
- printf '%s%s\n' "${PR_SESSION_PREFIX}" "${BASH_REMATCH[1]}"
72
- return 0
73
- fi
74
- if [[ "$branch_ref" =~ ^refs/heads/${ISSUE_BRANCH_PREFIX_REGEX}-([0-9]+)- ]]; then
75
- printf '%s%s\n' "${ISSUE_SESSION_PREFIX}" "${BASH_REMATCH[1]}"
76
- return 0
77
- fi
78
- return 1
79
- }
80
-
81
- branch_name_is_managed() {
82
- local branch_name="${1:-}"
83
- local branch_glob=""
84
-
85
- for branch_glob in ${MANAGED_PR_BRANCH_GLOBS}; do
86
- case "$branch_name" in
87
- ${branch_glob}) return 0 ;;
88
- esac
89
- done
90
-
91
- return 1
92
- }
93
-
94
- is_agent_managed_worktree() {
95
- local worktree_path="${1:-}"
96
- local branch_ref="${2:-}"
97
- local branch_name=""
98
-
99
- [[ "$worktree_path" == "$WORKTREE_ROOT/"* ]] || return 1
100
- if [[ "$branch_ref" == refs/heads/* ]]; then
101
- branch_name="${branch_ref#refs/heads/}"
102
- if branch_name_is_managed "$branch_name"; then
103
- return 0
104
- fi
105
- fi
106
- case "$(basename "$worktree_path")" in
107
- pr-*|issue-*|shared-*) return 0 ;;
108
- esac
109
- return 1
110
- }
111
-
112
- worktree_effectively_dirty() {
113
- local worktree_path="${1:?worktree path required}"
114
- local filtered=""
115
-
116
- if ! git -C "$worktree_path" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
117
- return 1
118
- fi
119
-
120
- filtered="$(
121
- git -C "$worktree_path" status --short --untracked-files=normal \
122
- | awk '
123
- /^\?\? node_modules$/ {next}
124
- /^\?\? .+\/node_modules$/ {next}
125
- /^\?\? \.openclaw-artifacts$/ {next}
126
- /^\?\? \.openclaw-artifacts\// {next}
127
- {print}
128
- '
129
- )"
130
- [[ -n "$filtered" ]]
131
- }
132
-
133
- worktree_is_broken() {
134
- local worktree_path="${1:?worktree path required}"
135
- [[ -d "$worktree_path" ]] || return 0
136
- git -C "$worktree_path" rev-parse --is-inside-work-tree >/dev/null 2>&1 && return 1
137
- return 0
138
- }
139
-
140
- worktree_has_active_owner() {
141
- local session_name="${1:-}"
142
- local run_dir=""
143
- local status_output=""
144
- local status=""
145
-
146
- [[ -n "$session_name" ]] || return 1
147
-
148
- if tmux has-session -t "$session_name" 2>/dev/null; then
149
- return 0
150
- fi
151
-
152
- run_dir="${RUNS_ROOT}/${session_name}"
153
- if [[ ! -d "$run_dir" ]]; then
154
- return 1
155
- fi
156
-
157
- # Completed workers still own their worktree until host-side reconcile
158
- # archives the run or writes reconciled.ok. Otherwise audit can delete the
159
- # worktree before publish/retry transitions consume it.
160
- if [[ ! -f "${run_dir}/reconciled.ok" ]]; then
161
- return 0
162
- fi
163
-
164
- status_output="$(
165
- "$WORKER_STATUS_TOOL" \
166
- --runs-root "$RUNS_ROOT" \
167
- --session "$session_name" 2>/dev/null || true
168
- )"
169
- status="$(awk -F= '/^STATUS=/{print $2}' <<<"$status_output" | tail -n 1)"
170
- [[ "$status" == "RUNNING" ]]
171
- }
172
-
173
- worktree_has_active_launch() {
174
- local session_name="${1:-}"
175
- local pending_file=""
176
- local pending_pid=""
177
-
178
- [[ -n "$session_name" ]] || return 1
179
-
180
- case "$session_name" in
181
- "${ISSUE_SESSION_PREFIX}"*)
182
- pending_file="${PENDING_LAUNCH_DIR}/issue-${session_name#${ISSUE_SESSION_PREFIX}}.pid"
183
- ;;
184
- "${PR_SESSION_PREFIX}"*)
185
- pending_file="${PENDING_LAUNCH_DIR}/pr-${session_name#${PR_SESSION_PREFIX}}.pid"
186
- ;;
187
- *)
188
- return 1
189
- ;;
190
- esac
191
-
192
- [[ -f "$pending_file" ]] || return 1
193
- pending_pid="$(tr -d '[:space:]' <"$pending_file" 2>/dev/null || true)"
194
- [[ -n "$pending_pid" ]] || return 1
195
- kill -0 "$pending_pid" 2>/dev/null
196
- }
197
-
198
- remove_agent_worktree() {
199
- local worktree_path="${1:-}"
200
- local branch_ref="${2:-}"
201
- local branch_name=""
202
- local cleanup_args=()
203
- local cleanup_failed="false"
204
-
205
- if [[ "$branch_ref" == refs/heads/* ]]; then
206
- branch_name="${branch_ref#refs/heads/}"
207
- cleanup_args=(--branch "$branch_name" --path "$worktree_path" --allow-unmerged)
208
- if [[ "$branch_name" == "${ISSUE_BRANCH_PREFIX}"-* ]]; then
209
- cleanup_args+=(--keep-remote)
210
- fi
211
- if ! (
212
- cd "$AGENT_REPO_ROOT"
213
- "$WORKTREE_CLEANUP_TOOL" "${cleanup_args[@]}"
214
- ) >/dev/null 2>&1; then
215
- cleanup_failed="true"
216
- fi
217
- else
218
- git -C "$AGENT_REPO_ROOT" worktree remove "$worktree_path" --force || true
219
- git -C "$AGENT_REPO_ROOT" worktree prune
220
- return 0
221
- fi
222
-
223
- if [[ "$cleanup_failed" == "true" ]]; then
224
- rm -rf "$worktree_path"
225
- if [[ -n "$branch_name" ]]; then
226
- git -C "$AGENT_REPO_ROOT" branch -D "$branch_name" >/dev/null 2>&1 || true
227
- fi
228
- fi
229
-
230
- git -C "$AGENT_REPO_ROOT" worktree prune >/dev/null 2>&1 || true
231
- }
232
-
233
- issue_count=0
234
- cleaned_count=0
235
-
236
- current_worktree=""
237
- current_branch_ref=""
238
- current_head=""
239
-
240
- while IFS= read -r line || [[ -n "$line" ]]; do
241
- if [[ -z "$line" ]]; then
242
- if [[ -n "$current_worktree" ]]; then
243
- if [[ "$current_worktree" != "$AGENT_REPO_ROOT" ]] && is_agent_managed_worktree "$current_worktree" "$current_branch_ref"; then
244
- session_name="$(session_for_worktree "$current_worktree" "$current_branch_ref" || true)"
245
- active_owner="no"
246
- if worktree_has_active_owner "$session_name"; then
247
- active_owner="yes"
248
- fi
249
- active_launch="no"
250
- if worktree_has_active_launch "$session_name"; then
251
- active_launch="yes"
252
- fi
253
-
254
- if [[ "$active_owner" == "yes" || "$active_launch" == "yes" ]]; then
255
- current_worktree=""
256
- current_branch_ref=""
257
- current_head=""
258
- continue
259
- fi
260
-
261
- broken_worktree="no"
262
- if worktree_is_broken "$current_worktree"; then
263
- broken_worktree="yes"
264
- fi
265
- dirty="no"
266
- if [[ "$broken_worktree" == "no" ]] && worktree_effectively_dirty "$current_worktree"; then
267
- dirty="yes"
268
- fi
269
-
270
- issue_count=$((issue_count + 1))
271
-
272
- printf 'AGENT_WORKTREE=%s\n' "$current_worktree"
273
- printf 'BRANCH_REF=%s\n' "${current_branch_ref:-<detached>}"
274
- printf 'HEAD=%s\n' "${current_head:-<unknown>}"
275
- printf 'SESSION=%s\n' "${session_name:-<none>}"
276
- printf 'BROKEN_WORKTREE=%s\n' "$broken_worktree"
277
- printf 'DIRTY=%s\n' "$dirty"
278
- printf 'ACTIVE_OWNER=%s\n' "$active_owner"
279
- printf 'ACTIVE_LAUNCH=%s\n' "$active_launch"
280
-
281
- if [[ "$cleanup" == "true" && "$dirty" == "no" && "$active_owner" == "no" && "$active_launch" == "no" ]]; then
282
- remove_agent_worktree "$current_worktree" "$current_branch_ref"
283
- cleaned_count=$((cleaned_count + 1))
284
- printf 'CLEANUP=removed\n'
285
- else
286
- printf 'CLEANUP=skipped\n'
287
- fi
288
- printf '\n'
289
- fi
290
-
291
- current_worktree=""
292
- current_branch_ref=""
293
- current_head=""
294
- fi
295
- continue
296
- fi
297
-
298
- case "$line" in
299
- worktree\ *) current_worktree="${line#worktree }" ;;
300
- HEAD\ *) current_head="${line#HEAD }" ;;
301
- branch\ *) current_branch_ref="${line#branch }" ;;
302
- esac
303
- done < <(git -C "$AGENT_REPO_ROOT" worktree list --porcelain; printf '\n')
304
-
305
- printf 'LEGACY_AGENT_WORKTREE_COUNT=%s\n' "$issue_count"
306
- printf 'LEGACY_AGENT_WORKTREE_CLEANED=%s\n' "$cleaned_count"
307
-
308
- if [[ "$strict" == "true" && "$issue_count" -gt 0 ]]; then
309
- exit 2
310
- fi
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-shell-lib.sh"
7
-
8
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
9
- ADAPTER_BIN_DIR="${FLOW_SKILL_DIR}/bin"
10
-
11
- exec "${ADAPTER_BIN_DIR}/audit-issue-routing.sh" "$@"
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
-
8
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
9
- RETAINED_ROOT="$(flow_resolve_retained_repo_root "${CONFIG_YAML}")"
10
- ADAPTER_ID="$(flow_resolve_adapter_id "${CONFIG_YAML}")"
11
-
12
- classify_worktree() {
13
- local path="${1:-}"
14
- case "$path" in
15
- */${ADAPTER_ID}-pr-*|*/${ADAPTER_ID}-issue-*|*/${ADAPTER_ID}-main-clean)
16
- printf 'legacy-automation\n'
17
- ;;
18
- "$RETAINED_ROOT")
19
- printf 'retained-main\n'
20
- ;;
21
- *)
22
- printf 'retained-manual\n'
23
- ;;
24
- esac
25
- }
26
-
27
- count_dirty_paths() {
28
- local path="${1:?path required}"
29
- git -C "$path" status --porcelain 2>/dev/null | wc -l | tr -d ' '
30
- }
31
-
32
- current_branch() {
33
- local path="${1:?path required}"
34
- git -C "$path" rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'unknown\n'
35
- }
36
-
37
- current_head() {
38
- local path="${1:?path required}"
39
- git -C "$path" rev-parse --short HEAD 2>/dev/null || printf 'unknown\n'
40
- }
41
-
42
- worktree_list="$(
43
- git -C "$RETAINED_ROOT" worktree list --porcelain |
44
- awk '/^worktree /{print substr($0,10)}'
45
- )"
46
-
47
- printf 'RETAINED_ROOT=%s\n' "$RETAINED_ROOT"
48
- printf 'RETAINED_WORKTREE_COUNT=%s\n' "$(printf '%s\n' "$worktree_list" | sed '/^$/d' | wc -l | tr -d ' ')"
49
-
50
- while IFS= read -r worktree; do
51
- [[ -n "$worktree" ]] || continue
52
- printf '\n[worktree]\n'
53
- printf 'path=%s\n' "$worktree"
54
- printf 'class=%s\n' "$(classify_worktree "$worktree")"
55
- printf 'branch=%s\n' "$(current_branch "$worktree")"
56
- printf 'head=%s\n' "$(current_head "$worktree")"
57
- printf 'dirty_paths=%s\n' "$(count_dirty_paths "$worktree")"
58
- done <<<"$worktree_list"