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.
- package/hooks/heartbeat-hooks.sh +147 -8
- package/hooks/issue-reconcile-hooks.sh +46 -0
- package/npm/bin/agent-control-plane.js +89 -8
- package/package.json +8 -2
- package/references/commands.md +1 -0
- package/tools/bin/agent-project-cleanup-session +133 -0
- package/tools/bin/agent-project-publish-issue-pr +178 -62
- package/tools/bin/agent-project-reconcile-issue-session +171 -3
- package/tools/bin/agent-project-run-codex-resilient +121 -16
- package/tools/bin/agent-project-run-codex-session +118 -10
- package/tools/bin/agent-project-run-openclaw-session +82 -8
- package/tools/bin/branch-verification-guard.sh +15 -2
- package/tools/bin/cleanup-worktree.sh +4 -1
- package/tools/bin/dashboard-launchd-bootstrap.sh +16 -4
- package/tools/bin/ensure-runtime-sync.sh +182 -0
- package/tools/bin/flow-config-lib.sh +76 -30
- package/tools/bin/flow-resident-worker-lib.sh +28 -2
- package/tools/bin/flow-shell-lib.sh +15 -1
- package/tools/bin/heartbeat-safe-auto.sh +32 -0
- package/tools/bin/issue-publish-localization-guard.sh +142 -0
- package/tools/bin/project-launchd-bootstrap.sh +17 -4
- package/tools/bin/project-runtime-supervisor.sh +7 -1
- package/tools/bin/project-runtimectl.sh +78 -15
- package/tools/bin/reuse-issue-worktree.sh +46 -0
- package/tools/bin/start-issue-worker.sh +110 -30
- package/tools/bin/start-resident-issue-loop.sh +1 -0
- package/tools/bin/sync-shared-agent-home.sh +50 -10
- package/tools/bin/test-smoke.sh +6 -1
- package/tools/dashboard/app.js +71 -1
- package/tools/dashboard/dashboard_snapshot.py +74 -0
- package/tools/dashboard/styles.css +43 -0
- package/tools/templates/issue-prompt-template.md +20 -65
- package/tools/templates/legacy/issue-prompt-template-pre-slim.md +109 -0
- package/bin/audit-issue-routing.sh +0 -74
- package/tools/bin/audit-agent-worktrees.sh +0 -310
- package/tools/bin/audit-issue-routing.sh +0 -11
- package/tools/bin/audit-retained-layout.sh +0 -58
- package/tools/bin/audit-retained-overlap.sh +0 -135
- package/tools/bin/audit-retained-worktrees.sh +0 -228
- 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
|
-
#
|
|
15
|
+
# Required Contract
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Follow this order:
|
|
18
18
|
|
|
19
|
-
|
|
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
|
-
-
|
|
22
|
-
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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"
|