agent-control-plane 0.3.0 → 0.6.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/README.md +141 -28
- package/assets/workflow-catalog.json +1 -1
- package/bin/pr-risk.sh +22 -7
- package/bin/sync-pr-labels.sh +1 -1
- package/hooks/heartbeat-hooks.sh +125 -12
- package/hooks/issue-reconcile-hooks.sh +1 -1
- package/hooks/pr-reconcile-hooks.sh +1 -1
- package/npm/bin/agent-control-plane.js +257 -59
- package/package.json +39 -32
- package/tools/bin/debug-session.sh +106 -0
- package/tools/bin/flow-config-lib.sh +1203 -60
- package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
- package/tools/bin/flow-runtime-doctor.sh +5 -1
- package/tools/bin/flow-shell-lib.sh +32 -0
- package/tools/bin/github-core-rate-limit-state.sh +77 -0
- package/tools/bin/github-write-outbox.sh +470 -0
- package/tools/bin/heartbeat-loop-scheduling-lib.sh +7 -7
- package/tools/bin/heartbeat-safe-auto.sh +42 -0
- package/tools/bin/install-project-launchd.sh +17 -2
- package/tools/bin/install-project-systemd.sh +255 -0
- package/tools/bin/project-init.sh +21 -1
- package/tools/bin/project-launchd-bootstrap.sh +5 -1
- package/tools/bin/project-runtimectl.sh +91 -2
- package/tools/bin/project-systemd-bootstrap.sh +74 -0
- package/tools/bin/scaffold-profile.sh +61 -3
- package/tools/bin/uninstall-project-systemd.sh +87 -0
- package/tools/dashboard/app.js +228 -6
- package/tools/dashboard/dashboard_snapshot.py +55 -0
- package/tools/dashboard/issue_queue_state.py +101 -0
- package/tools/dashboard/server.py +123 -1
- package/tools/dashboard/styles.css +526 -455
- package/tools/templates/pr-fix-template.md +3 -1
- package/tools/templates/pr-merge-repair-template.md +2 -1
- package/references/architecture.md +0 -217
- package/references/commands.md +0 -128
- package/references/control-plane-map.md +0 -124
- package/references/docs-map.md +0 -73
- package/references/release-checklist.md +0 -65
- package/references/repo-map.md +0 -36
- package/tools/bin/agent-cleanup-worktree +0 -247
- package/tools/bin/agent-github-update-labels +0 -71
- package/tools/bin/agent-init-worktree +0 -216
- package/tools/bin/agent-project-archive-run +0 -52
- package/tools/bin/agent-project-capture-worker +0 -46
- package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
- package/tools/bin/agent-project-catch-up-merged-prs +0 -194
- package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
- package/tools/bin/agent-project-cleanup-session +0 -513
- package/tools/bin/agent-project-detached-launch +0 -127
- package/tools/bin/agent-project-heartbeat-loop +0 -1029
- package/tools/bin/agent-project-open-issue-worktree +0 -89
- package/tools/bin/agent-project-open-pr-worktree +0 -80
- package/tools/bin/agent-project-publish-issue-pr +0 -465
- package/tools/bin/agent-project-reconcile-issue-session +0 -1398
- package/tools/bin/agent-project-reconcile-pr-session +0 -1230
- package/tools/bin/agent-project-retry-state +0 -147
- package/tools/bin/agent-project-run-claude-session +0 -805
- package/tools/bin/agent-project-run-codex-resilient +0 -955
- package/tools/bin/agent-project-run-codex-session +0 -435
- package/tools/bin/agent-project-run-kilo-session +0 -369
- package/tools/bin/agent-project-run-ollama-session +0 -658
- package/tools/bin/agent-project-run-openclaw-session +0 -1309
- package/tools/bin/agent-project-run-opencode-session +0 -377
- package/tools/bin/agent-project-run-pi-session +0 -479
- package/tools/bin/agent-project-sync-anchor-repo +0 -139
- package/tools/bin/agent-project-worker-status +0 -188
- package/tools/bin/branch-verification-guard.sh +0 -364
- package/tools/bin/capture-worker.sh +0 -18
- package/tools/bin/cleanup-worktree.sh +0 -52
- package/tools/bin/codex-quota +0 -31
- package/tools/bin/create-follow-up-issue.sh +0 -114
- package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
- package/tools/bin/issue-publish-localization-guard.sh +0 -142
- package/tools/bin/issue-publish-scope-guard.sh +0 -242
- package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
- package/tools/bin/issue-resource-class.sh +0 -12
- package/tools/bin/kick-scheduler.sh +0 -75
- package/tools/bin/label-follow-up-issues.sh +0 -14
- package/tools/bin/new-pr-worktree.sh +0 -50
- package/tools/bin/new-worktree.sh +0 -49
- package/tools/bin/pr-risk.sh +0 -12
- package/tools/bin/prepare-worktree.sh +0 -142
- package/tools/bin/provider-cooldown-state.sh +0 -204
- package/tools/bin/publish-issue-worker.sh +0 -31
- package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
- package/tools/bin/reconcile-issue-worker.sh +0 -34
- package/tools/bin/reconcile-pr-worker.sh +0 -34
- package/tools/bin/record-verification.sh +0 -71
- package/tools/bin/render-flow-config.sh +0 -98
- package/tools/bin/resident-issue-controller-lib.sh +0 -448
- package/tools/bin/resident-issue-queue-status.py +0 -35
- package/tools/bin/retry-state.sh +0 -31
- package/tools/bin/reuse-issue-worktree.sh +0 -121
- package/tools/bin/run-codex-bypass.sh +0 -3
- package/tools/bin/run-codex-safe.sh +0 -3
- package/tools/bin/run-codex-task.sh +0 -280
- package/tools/bin/serve-dashboard.sh +0 -5
- package/tools/bin/split-retained-slice.sh +0 -124
- package/tools/bin/start-issue-worker.sh +0 -943
- package/tools/bin/start-pr-fix-worker.sh +0 -491
- package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
- package/tools/bin/start-pr-review-worker.sh +0 -261
- package/tools/bin/start-resident-issue-loop.sh +0 -499
- package/tools/bin/update-github-labels.sh +0 -14
- package/tools/bin/worker-status.sh +0 -19
- package/tools/bin/workflow-catalog.sh +0 -77
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
usage() {
|
|
5
|
-
cat <<'EOF'
|
|
6
|
-
Usage:
|
|
7
|
-
agent-project-worker-status --runs-root <path> --session <id> [--exit-marker <marker>]
|
|
8
|
-
|
|
9
|
-
Reads a project-lane run directory and reports session status using tmux plus the
|
|
10
|
-
worker log exit marker.
|
|
11
|
-
EOF
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
runs_root="${AGENT_PROJECT_RUNS_ROOT:-}"
|
|
15
|
-
session=""
|
|
16
|
-
exit_marker="__\\w+_EXIT__:"
|
|
17
|
-
|
|
18
|
-
while [[ $# -gt 0 ]]; do
|
|
19
|
-
case "$1" in
|
|
20
|
-
--runs-root) runs_root="${2:-}"; shift 2 ;;
|
|
21
|
-
--session) session="${2:-}"; shift 2 ;;
|
|
22
|
-
--exit-marker) exit_marker="${2:-}"; shift 2 ;;
|
|
23
|
-
--help|-h) usage; exit 0 ;;
|
|
24
|
-
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
|
|
25
|
-
esac
|
|
26
|
-
done
|
|
27
|
-
|
|
28
|
-
if [[ -z "$runs_root" || -z "$session" ]]; then
|
|
29
|
-
usage >&2
|
|
30
|
-
exit 1
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
run_dir="${runs_root}/${session}"
|
|
34
|
-
meta_file="${run_dir}/run.env"
|
|
35
|
-
result_file="${run_dir}/result.env"
|
|
36
|
-
runner_state_file="${run_dir}/runner.env"
|
|
37
|
-
output_file="${run_dir}/${session}.log"
|
|
38
|
-
|
|
39
|
-
status="UNKNOWN"
|
|
40
|
-
exit_code=""
|
|
41
|
-
result_only_completion="no"
|
|
42
|
-
failure_reason=""
|
|
43
|
-
runner_state=""
|
|
44
|
-
thread_id=""
|
|
45
|
-
last_exit_code=""
|
|
46
|
-
|
|
47
|
-
failure_reason_from_output() {
|
|
48
|
-
[[ -f "$output_file" ]] || return 1
|
|
49
|
-
|
|
50
|
-
if rg -qi "You've hit your usage limit|You have reached your Codex usage limits|visit https://chatgpt.com/codex/settings/usage|Upgrade to Pro|rate limit exceeded|quota exceeded|usage cap (reached|exceeded)|usage quota (reached|exceeded)" "$output_file"; then
|
|
51
|
-
printf 'usage-limit\n'
|
|
52
|
-
return 0
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
if rg -qi 'stale-run no-codex-output-before-stall-threshold|no-codex-output-before-stall-threshold' "$output_file"; then
|
|
56
|
-
printf 'no-codex-output-before-stall-threshold\n'
|
|
57
|
-
return 0
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
if rg -qi 'stale-run no-codex-progress-before-stall-threshold|no-codex-progress-before-stall-threshold' "$output_file"; then
|
|
61
|
-
printf 'no-codex-progress-before-stall-threshold\n'
|
|
62
|
-
return 0
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
# Recover Codex startup stalls when the wrapper was archived before it could
|
|
66
|
-
# flush a terminal runner.env state. This is intentionally narrow: the log
|
|
67
|
-
# must show a turn started, but no tool activity or turn completion.
|
|
68
|
-
if rg -q '"type":"turn.started"' "$output_file" \
|
|
69
|
-
&& ! rg -q '"type":"item.started"|"type":"item.completed"|"type":"turn.completed"' "$output_file"; then
|
|
70
|
-
printf 'no-codex-progress-before-stall-threshold\n'
|
|
71
|
-
return 0
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
return 1
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if tmux has-session -t "$session" 2>/dev/null; then
|
|
78
|
-
status="RUNNING"
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
if [[ -f "$runner_state_file" ]]; then
|
|
82
|
-
set -a
|
|
83
|
-
# shellcheck source=/dev/null
|
|
84
|
-
source "$runner_state_file"
|
|
85
|
-
set +a
|
|
86
|
-
runner_state="${RUNNER_STATE:-}"
|
|
87
|
-
thread_id="${THREAD_ID:-}"
|
|
88
|
-
last_exit_code="${LAST_EXIT_CODE:-}"
|
|
89
|
-
if [[ -z "$failure_reason" ]]; then
|
|
90
|
-
failure_reason="${LAST_FAILURE_REASON:-}"
|
|
91
|
-
fi
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
if [[ "$status" == "UNKNOWN" ]]; then
|
|
95
|
-
case "$runner_state" in
|
|
96
|
-
succeeded)
|
|
97
|
-
status="SUCCEEDED"
|
|
98
|
-
;;
|
|
99
|
-
failed)
|
|
100
|
-
status="FAILED"
|
|
101
|
-
if [[ -z "$exit_code" && -n "$last_exit_code" ]]; then
|
|
102
|
-
exit_code="$last_exit_code"
|
|
103
|
-
fi
|
|
104
|
-
;;
|
|
105
|
-
esac
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
if [[ "$status" == "UNKNOWN" && -f "$output_file" ]]; then
|
|
109
|
-
exit_match="$(rg -o "${exit_marker}[0-9]+" "$output_file" | tail -n 1 || true)"
|
|
110
|
-
if [[ -n "$exit_match" ]]; then
|
|
111
|
-
exit_code="${exit_match##*:}"
|
|
112
|
-
if [[ "$exit_code" == "0" ]]; then
|
|
113
|
-
status="SUCCEEDED"
|
|
114
|
-
else
|
|
115
|
-
status="FAILED"
|
|
116
|
-
fi
|
|
117
|
-
fi
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
if [[ "$status" == "UNKNOWN" && -n "$runner_state" ]]; then
|
|
121
|
-
case "$runner_state" in
|
|
122
|
-
running|waiting-auth-refresh|switching-account)
|
|
123
|
-
# Tmux session is gone and runner never reached a terminal state.
|
|
124
|
-
# This detects crashes where the worker process died before updating
|
|
125
|
-
# runner.env or writing an exit marker.
|
|
126
|
-
# Check BEFORE stale result.env to avoid false SUCCEEDED when a prior
|
|
127
|
-
# cycle's result.env happens to exist.
|
|
128
|
-
status="FAILED"
|
|
129
|
-
failure_reason="$(failure_reason_from_output || true)"
|
|
130
|
-
if [[ -z "$failure_reason" ]]; then
|
|
131
|
-
failure_reason="runner-aborted-before-completion"
|
|
132
|
-
fi
|
|
133
|
-
if [[ -z "$exit_code" && -n "$last_exit_code" ]]; then
|
|
134
|
-
exit_code="$last_exit_code"
|
|
135
|
-
fi
|
|
136
|
-
;;
|
|
137
|
-
esac
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
if [[ "$status" == "UNKNOWN" && -f "$result_file" ]]; then
|
|
141
|
-
# A worker that managed to persist result.env already completed its contract,
|
|
142
|
-
# even if the tmux session disappeared before the exit marker was flushed.
|
|
143
|
-
# Check BEFORE failure_reason_from_output so that a completed result.env
|
|
144
|
-
# is not overridden by transient failure text in the log.
|
|
145
|
-
status="SUCCEEDED"
|
|
146
|
-
result_only_completion="yes"
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
if [[ "$status" == "UNKNOWN" && -z "$failure_reason" ]]; then
|
|
150
|
-
failure_reason="$(failure_reason_from_output || true)"
|
|
151
|
-
if [[ -n "$failure_reason" ]]; then
|
|
152
|
-
status="FAILED"
|
|
153
|
-
fi
|
|
154
|
-
fi
|
|
155
|
-
|
|
156
|
-
if [[ "$status" == "UNKNOWN" && -f "$output_file" ]]; then
|
|
157
|
-
if rg -qi "You've hit your usage limit|You have reached your Codex usage limits|visit https://chatgpt.com/codex/settings/usage|Upgrade to Pro|rate limit exceeded|quota exceeded|usage cap (reached|exceeded)|usage quota (reached|exceeded)" "$output_file"; then
|
|
158
|
-
status="FAILED"
|
|
159
|
-
failure_reason="usage-limit"
|
|
160
|
-
fi
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
printf 'SESSION=%s\n' "$session"
|
|
164
|
-
printf 'STATUS=%s\n' "$status"
|
|
165
|
-
if [[ -f "$meta_file" ]]; then
|
|
166
|
-
cat "$meta_file"
|
|
167
|
-
printf 'META_FILE=%s\n' "$meta_file"
|
|
168
|
-
fi
|
|
169
|
-
if [[ -f "$runner_state_file" ]]; then
|
|
170
|
-
cat "$runner_state_file"
|
|
171
|
-
printf 'RUNNER_STATE_FILE=%s\n' "$runner_state_file"
|
|
172
|
-
fi
|
|
173
|
-
if [[ -f "$result_file" ]]; then
|
|
174
|
-
cat "$result_file"
|
|
175
|
-
printf 'RESULT_FILE=%s\n' "$result_file"
|
|
176
|
-
fi
|
|
177
|
-
if [[ "$result_only_completion" == "yes" ]]; then
|
|
178
|
-
printf 'RESULT_ONLY_COMPLETION=yes\n'
|
|
179
|
-
fi
|
|
180
|
-
if [[ -n "$exit_code" ]]; then
|
|
181
|
-
printf 'EXIT_CODE=%s\n' "$exit_code"
|
|
182
|
-
fi
|
|
183
|
-
if [[ -n "$failure_reason" ]]; then
|
|
184
|
-
printf 'FAILURE_REASON=%s\n' "$failure_reason"
|
|
185
|
-
fi
|
|
186
|
-
if [[ -n "$thread_id" ]]; then
|
|
187
|
-
printf 'THREAD_ID=%s\n' "$thread_id"
|
|
188
|
-
fi
|
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
usage() {
|
|
5
|
-
cat <<'EOF'
|
|
6
|
-
Usage:
|
|
7
|
-
branch-verification-guard.sh --worktree <path> --base-ref <git-ref> --run-dir <path>
|
|
8
|
-
|
|
9
|
-
Fail fast when a branch update is about to be pushed without sufficient local
|
|
10
|
-
verification evidence for the touched surface.
|
|
11
|
-
EOF
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
worktree=""
|
|
15
|
-
base_ref=""
|
|
16
|
-
run_dir=""
|
|
17
|
-
|
|
18
|
-
while [[ $# -gt 0 ]]; do
|
|
19
|
-
case "$1" in
|
|
20
|
-
--worktree) worktree="${2:-}"; shift 2 ;;
|
|
21
|
-
--base-ref) base_ref="${2:-}"; shift 2 ;;
|
|
22
|
-
--run-dir) run_dir="${2:-}"; shift 2 ;;
|
|
23
|
-
--help|-h) usage; exit 0 ;;
|
|
24
|
-
*)
|
|
25
|
-
echo "Unknown argument: $1" >&2
|
|
26
|
-
usage >&2
|
|
27
|
-
exit 1
|
|
28
|
-
;;
|
|
29
|
-
esac
|
|
30
|
-
done
|
|
31
|
-
|
|
32
|
-
if [[ -z "$worktree" || -z "$base_ref" || -z "$run_dir" ]]; then
|
|
33
|
-
usage >&2
|
|
34
|
-
exit 1
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
if [[ ! -d "$worktree" ]]; then
|
|
38
|
-
echo "missing worktree: $worktree" >&2
|
|
39
|
-
exit 1
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
changed_files="$(
|
|
43
|
-
{
|
|
44
|
-
git -C "$worktree" diff --name-only --diff-filter=ACMR "${base_ref}...HEAD"
|
|
45
|
-
git -C "$worktree" diff --name-only --diff-filter=ACMR
|
|
46
|
-
git -C "$worktree" diff --cached --name-only --diff-filter=ACMR
|
|
47
|
-
git -C "$worktree" ls-files --others --exclude-standard 2>/dev/null || true
|
|
48
|
-
} | awk '
|
|
49
|
-
NF == 0 { next }
|
|
50
|
-
!seen[$0]++ { print $0 }
|
|
51
|
-
'
|
|
52
|
-
)"
|
|
53
|
-
|
|
54
|
-
verification_file="${run_dir}/verification.jsonl"
|
|
55
|
-
|
|
56
|
-
CHANGED_FILES="$changed_files" VERIFICATION_FILE="$verification_file" node <<'EOF'
|
|
57
|
-
const fs = require('fs');
|
|
58
|
-
const path = require('path');
|
|
59
|
-
|
|
60
|
-
const files = String(process.env.CHANGED_FILES || '')
|
|
61
|
-
.split('\n')
|
|
62
|
-
.map((file) => file.trim())
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
const normalizePath = (file) => String(file || '').replace(/\\/g, '/').toLowerCase();
|
|
65
|
-
const stripCodeExtension = (file) => normalizePath(file).replace(/\.[cm]?[jt]sx?$/i, '');
|
|
66
|
-
const stripTestSuffix = (file) => stripCodeExtension(file).replace(/\.(spec|test)$/i, '');
|
|
67
|
-
const lastPathSegments = (file, count = 2) => {
|
|
68
|
-
const parts = normalizePath(file).split('/').filter(Boolean);
|
|
69
|
-
return parts.slice(-count).join('/');
|
|
70
|
-
};
|
|
71
|
-
const unique = (values) => [...new Set(values.filter(Boolean))];
|
|
72
|
-
|
|
73
|
-
const verificationFile = String(process.env.VERIFICATION_FILE || '');
|
|
74
|
-
const isDoc = (file) =>
|
|
75
|
-
/^openspec\//.test(file) ||
|
|
76
|
-
/^docs\//.test(file) ||
|
|
77
|
-
/^scripts\/README\.md$/.test(file) ||
|
|
78
|
-
/^AGENTS\.md$/.test(file) ||
|
|
79
|
-
/^openspec\/AGENT_RULES\.md$/.test(file) ||
|
|
80
|
-
/\.md$/i.test(file);
|
|
81
|
-
|
|
82
|
-
const isTest = (file) =>
|
|
83
|
-
/(?:^|\/)__tests__\//.test(file) ||
|
|
84
|
-
/(?:^|\/)e2e\//.test(file) ||
|
|
85
|
-
/\.(?:spec|test)\.[cm]?[jt]sx?$/.test(file);
|
|
86
|
-
|
|
87
|
-
const isLocaleResource = (file) =>
|
|
88
|
-
/^packages\/i18n\/src\/resources\/[^/]+\.json$/.test(file);
|
|
89
|
-
const isAgentGeneratedArtifact = (file) =>
|
|
90
|
-
/^\.agent-session\.env$/i.test(file) ||
|
|
91
|
-
/^(?:\.openclaw-artifacts|\.openclaw)(?:\/|$)/i.test(file) ||
|
|
92
|
-
/^(?:SOUL|TOOLS|IDENTITY|USER|HEARTBEAT|BOOTSTRAP)\.md$/i.test(file);
|
|
93
|
-
const isDependencyLockfile = (file) =>
|
|
94
|
-
/(?:^|\/)(?:pnpm-lock\.yaml|package-lock\.json|yarn\.lock|bun\.lockb|npm-shrinkwrap\.json)$/i.test(file);
|
|
95
|
-
const isDependencyManifest = (file) =>
|
|
96
|
-
/(?:^|\/)package\.json$/i.test(file) ||
|
|
97
|
-
/(?:^|\/)pnpm-workspace\.yaml$/i.test(file) ||
|
|
98
|
-
/(?:^|\/)\.npmrc$/i.test(file) ||
|
|
99
|
-
/(?:^|\/)\.yarnrc(?:\.yml)?$/i.test(file) ||
|
|
100
|
-
/(?:^|\/)bunfig\.toml$/i.test(file);
|
|
101
|
-
|
|
102
|
-
const productNonTestFiles = files.filter(
|
|
103
|
-
(file) => !isDoc(file) && !isTest(file) && !isLocaleResource(file),
|
|
104
|
-
);
|
|
105
|
-
const generatedArtifacts = unique(files.filter(isAgentGeneratedArtifact));
|
|
106
|
-
const dependencyLockfiles = unique(files.filter(isDependencyLockfile));
|
|
107
|
-
const dependencyInputsChanged = files.some(isDependencyManifest);
|
|
108
|
-
const apiTouched = productNonTestFiles.some((file) => /^apps\/api\//.test(file));
|
|
109
|
-
const webTouched = productNonTestFiles.some((file) => /^apps\/web\//.test(file));
|
|
110
|
-
const mobileTouched = productNonTestFiles.some((file) => /^apps\/mobile\//.test(file));
|
|
111
|
-
const apiProductFiles = productNonTestFiles.filter((file) => /^apps\/api\//.test(file));
|
|
112
|
-
const packageNames = [
|
|
113
|
-
...new Set(
|
|
114
|
-
productNonTestFiles
|
|
115
|
-
.filter((file) => /^packages\//.test(file))
|
|
116
|
-
.map((file) => file.split('/')[1])
|
|
117
|
-
.filter(Boolean),
|
|
118
|
-
),
|
|
119
|
-
];
|
|
120
|
-
const changedTestFiles = files.filter(isTest);
|
|
121
|
-
const localeTouched = files.some(isLocaleResource);
|
|
122
|
-
|
|
123
|
-
if (productNonTestFiles.length === 0 && !localeTouched && changedTestFiles.length === 0) {
|
|
124
|
-
process.stdout.write('VERIFICATION_GUARD_STATUS=ok\n');
|
|
125
|
-
process.stdout.write('VERIFICATION_REASON=docs-or-spec-only\n');
|
|
126
|
-
process.exit(0);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
let entries = [];
|
|
130
|
-
if (verificationFile && fs.existsSync(verificationFile)) {
|
|
131
|
-
const raw = fs.readFileSync(verificationFile, 'utf8');
|
|
132
|
-
entries = raw
|
|
133
|
-
.split('\n')
|
|
134
|
-
.map((line) => line.trim())
|
|
135
|
-
.filter(Boolean)
|
|
136
|
-
.flatMap((line) => {
|
|
137
|
-
try {
|
|
138
|
-
return [JSON.parse(line)];
|
|
139
|
-
} catch (error) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const passedCommands = entries
|
|
146
|
-
.filter((entry) => entry && entry.status === 'pass' && typeof entry.command === 'string')
|
|
147
|
-
.map((entry) => entry.command.trim())
|
|
148
|
-
.filter(Boolean);
|
|
149
|
-
const passedLower = passedCommands.map((command) => command.toLowerCase());
|
|
150
|
-
|
|
151
|
-
const escapeRegex = (value) => String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
152
|
-
const hasCommand = (...patterns) =>
|
|
153
|
-
passedLower.some((command) => patterns.some((pattern) => pattern.test(command)));
|
|
154
|
-
const hasScopedCommand = (scopePatterns, ...actionPatterns) =>
|
|
155
|
-
passedLower.some(
|
|
156
|
-
(command) =>
|
|
157
|
-
scopePatterns.some((pattern) => pattern.test(command)) &&
|
|
158
|
-
actionPatterns.some((pattern) => pattern.test(command)),
|
|
159
|
-
);
|
|
160
|
-
const workspaceScopePatterns = (workspace) => {
|
|
161
|
-
const name = escapeRegex(workspace);
|
|
162
|
-
return [
|
|
163
|
-
new RegExp(`--filter(?:=|\\s+)(?:@[^/\\s]+/)?${name}\\b`),
|
|
164
|
-
new RegExp(`(?:\\bapps/${name}\\b|(?:--dir|-C)\\s+apps/${name}\\b)`),
|
|
165
|
-
];
|
|
166
|
-
};
|
|
167
|
-
const packageScopePatterns = (pkg) => {
|
|
168
|
-
const name = escapeRegex(pkg);
|
|
169
|
-
return [
|
|
170
|
-
new RegExp(`--filter(?:=|\\s+)(?:@[^/\\s]+/)?${name}\\b`),
|
|
171
|
-
new RegExp(`@[^/\\s]+/${name}\\b`),
|
|
172
|
-
new RegExp(`(?:\\bpackages/${name}\\b|(?:--dir|-C)\\s+packages/${name}\\b)`),
|
|
173
|
-
];
|
|
174
|
-
};
|
|
175
|
-
const apiScopePattern = workspaceScopePatterns('api');
|
|
176
|
-
const webScopePattern = workspaceScopePatterns('web');
|
|
177
|
-
const mobileScopePattern = workspaceScopePatterns('mobile');
|
|
178
|
-
const targetedAnchorsForFile = (file) =>
|
|
179
|
-
unique([
|
|
180
|
-
stripCodeExtension(lastPathSegments(file, 2)),
|
|
181
|
-
stripCodeExtension(path.basename(file)),
|
|
182
|
-
path.basename(stripTestSuffix(file)),
|
|
183
|
-
]);
|
|
184
|
-
const hasScopedRunnerCoverage = (file, command) => {
|
|
185
|
-
if (/(?:^|\/)e2e\//.test(file)) {
|
|
186
|
-
return /\bplaywright\b/.test(command);
|
|
187
|
-
}
|
|
188
|
-
if (/^apps\/mobile\//.test(file)) {
|
|
189
|
-
return /\b(?:detox|maestro)\b/.test(command);
|
|
190
|
-
}
|
|
191
|
-
return false;
|
|
192
|
-
};
|
|
193
|
-
const changedTestCoverage = changedTestFiles.map((file) => {
|
|
194
|
-
const anchors = targetedAnchorsForFile(file);
|
|
195
|
-
const covered = passedLower.some(
|
|
196
|
-
(command) =>
|
|
197
|
-
anchors.some((anchor) => anchor && command.includes(anchor)) ||
|
|
198
|
-
hasScopedRunnerCoverage(file, command),
|
|
199
|
-
);
|
|
200
|
-
return { file, anchors, covered };
|
|
201
|
-
});
|
|
202
|
-
const missingChangedTestFiles = changedTestCoverage.filter(({ covered }) => !covered);
|
|
203
|
-
const apiChangedTests = changedTestCoverage.filter(({ file }) => /^apps\/api\//.test(file));
|
|
204
|
-
|
|
205
|
-
const rootTypecheck = hasCommand(/\bpnpm (?:run )?typecheck\b/, /\bturbo\b.*\btypecheck\b/);
|
|
206
|
-
const rootBuild = hasCommand(/\bpnpm (?:run )?build\b/, /\bturbo\b.*\bbuild\b/);
|
|
207
|
-
const rootLint = hasCommand(/\bpnpm (?:run )?lint\b/, /\bturbo\b.*\blint\b/);
|
|
208
|
-
const rootTest = hasCommand(/\bpnpm (?:run )?test\b/, /\bturbo\b.*\btest\b/);
|
|
209
|
-
const scopedApiTypecheck =
|
|
210
|
-
hasScopedCommand(apiScopePattern, /\btypecheck\b/, /\btsc --noemit\b/, /\btsc --noemit\b/);
|
|
211
|
-
const scopedApiConfidence =
|
|
212
|
-
hasScopedCommand(apiScopePattern, /\blint\b/, /\bbuild\b/, /\btest\b/, /\bjest\b/, /\bvitest\b/);
|
|
213
|
-
const apiNarrowSliceTargetedCoverage =
|
|
214
|
-
apiProductFiles.length > 0 &&
|
|
215
|
-
apiProductFiles.length <= 2 &&
|
|
216
|
-
apiProductFiles.every((file) => /(?:^|\/)(?:services?|utils?|helpers?|policies?)\/|(?:\.service|\.util|\.helper|\.policy)\.[cm]?[jt]s$/.test(file)) &&
|
|
217
|
-
apiChangedTests.length > 0 &&
|
|
218
|
-
apiChangedTests.every(({ covered }) => covered) &&
|
|
219
|
-
scopedApiConfidence;
|
|
220
|
-
|
|
221
|
-
const reasons = [];
|
|
222
|
-
if (passedCommands.length === 0) {
|
|
223
|
-
reasons.push('missing verification journal or no successful verification commands were recorded');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (generatedArtifacts.length > 0) {
|
|
227
|
-
reasons.push('generated agent/session artifacts were included in the branch diff');
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (dependencyLockfiles.length > 0 && !dependencyInputsChanged) {
|
|
231
|
-
reasons.push('lockfile changes were introduced without dependency manifest changes');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (localeTouched) {
|
|
235
|
-
if (
|
|
236
|
-
!hasCommand(
|
|
237
|
-
/(?:@[^/\s]+\/i18n\b|--filter(?:=|\s+)(?:@[^/\s]+\/i18n|i18n)\b).*?\bvalidate\b/,
|
|
238
|
-
/\bi18n-validation\b/,
|
|
239
|
-
/\bi18n-gate\b/,
|
|
240
|
-
/\b(?:i18n|locale|translation|translations)\b.*\bvalidate\b/,
|
|
241
|
-
)
|
|
242
|
-
) {
|
|
243
|
-
reasons.push('missing i18n validate command for locale resource changes');
|
|
244
|
-
}
|
|
245
|
-
if (
|
|
246
|
-
!hasCommand(
|
|
247
|
-
/(?:@[^/\s]+\/i18n\b|--filter(?:=|\s+)(?:@[^/\s]+\/i18n|i18n)\b).*?\b(?:scan-hardcoded|i18n:scan)\b/,
|
|
248
|
-
/\bi18n-checks\b/,
|
|
249
|
-
/\bi18n-gate\b/,
|
|
250
|
-
/\b(?:i18n|locale|translation|translations)\b.*\b(?:scan-hardcoded|i18n:scan|scan)\b/,
|
|
251
|
-
)
|
|
252
|
-
) {
|
|
253
|
-
reasons.push('missing i18n scan-hardcoded command for locale resource changes');
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (apiTouched) {
|
|
258
|
-
if (!(scopedApiTypecheck || rootTypecheck || apiNarrowSliceTargetedCoverage)) {
|
|
259
|
-
reasons.push('missing API typecheck or repo typecheck for API changes');
|
|
260
|
-
}
|
|
261
|
-
if (!(scopedApiConfidence || rootBuild || rootLint || rootTest)) {
|
|
262
|
-
reasons.push('missing API confidence verification (lint, build, or test) for API changes');
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (webTouched) {
|
|
267
|
-
if (!(hasScopedCommand(webScopePattern, /\blint\b/, /\btypecheck\b/, /\bbuild\b/, /\btest\b/, /\bjest\b/, /\bvitest\b/) || rootTypecheck || rootBuild || rootLint || rootTest)) {
|
|
268
|
-
reasons.push('missing Web verification command for web changes');
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (mobileTouched) {
|
|
273
|
-
if (!(hasScopedCommand(mobileScopePattern, /\blint\b/, /\btypecheck\b/, /\bbuild\b/, /\btest\b/, /\bjest\b/, /\bvitest\b/, /\bdetox\b/, /\bmaestro\b/) || rootTypecheck || rootBuild || rootLint || rootTest || hasCommand(/\bdetox\b/, /\bmaestro\b/))) {
|
|
274
|
-
reasons.push('missing Mobile verification command for mobile changes');
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (!apiTouched && !webTouched && !mobileTouched && packageNames.length > 0) {
|
|
279
|
-
for (const pkg of packageNames) {
|
|
280
|
-
if (
|
|
281
|
-
!(hasScopedCommand(
|
|
282
|
-
packageScopePatterns(pkg),
|
|
283
|
-
/\blint\b/,
|
|
284
|
-
/\btypecheck\b/,
|
|
285
|
-
/\bbuild\b/,
|
|
286
|
-
/\btest\b/,
|
|
287
|
-
/\bjest\b/,
|
|
288
|
-
/\bvitest\b/,
|
|
289
|
-
) || rootTypecheck || rootBuild || rootLint || rootTest)
|
|
290
|
-
) {
|
|
291
|
-
reasons.push(`missing shared package verification for packages/${pkg}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (missingChangedTestFiles.length > 0) {
|
|
297
|
-
reasons.push('changed test files were not covered by a targeted test command');
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (reasons.length === 0) {
|
|
301
|
-
process.stdout.write('VERIFICATION_GUARD_STATUS=ok\n');
|
|
302
|
-
process.stdout.write(`VERIFICATION_COMMAND_COUNT=${passedCommands.length}\n`);
|
|
303
|
-
process.exit(0);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const lines = [
|
|
307
|
-
'Verification guard blocked branch publication.',
|
|
308
|
-
'',
|
|
309
|
-
'Why it was blocked:',
|
|
310
|
-
...reasons.map((reason) => `- ${reason}`),
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
if (productNonTestFiles.length > 0) {
|
|
314
|
-
lines.push('', 'Changed product files:');
|
|
315
|
-
for (const file of productNonTestFiles.slice(0, 15)) {
|
|
316
|
-
lines.push(`- ${file}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (generatedArtifacts.length > 0) {
|
|
321
|
-
lines.push('', 'Generated artifacts that must be removed before publish:');
|
|
322
|
-
for (const file of generatedArtifacts.slice(0, 15)) {
|
|
323
|
-
lines.push(`- ${file}`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (dependencyLockfiles.length > 0 && !dependencyInputsChanged) {
|
|
328
|
-
lines.push('', 'Lockfiles changed without a matching dependency manifest update:');
|
|
329
|
-
for (const file of dependencyLockfiles.slice(0, 15)) {
|
|
330
|
-
lines.push(`- ${file}`);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (missingChangedTestFiles.length > 0) {
|
|
335
|
-
lines.push('', 'Changed test files still missing explicit coverage:');
|
|
336
|
-
for (const { file, anchors } of missingChangedTestFiles.slice(0, 15)) {
|
|
337
|
-
const acceptedAnchors = anchors.map((anchor) => `\`${anchor}\``);
|
|
338
|
-
if (/(?:^|\/)e2e\//.test(file)) {
|
|
339
|
-
acceptedAnchors.push('scoped `playwright` command');
|
|
340
|
-
} else if (/^apps\/mobile\//.test(file)) {
|
|
341
|
-
acceptedAnchors.push('scoped `detox` or `maestro` command');
|
|
342
|
-
}
|
|
343
|
-
lines.push(`- ${file} | accepted anchors: ${acceptedAnchors.join(', ')}`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (passedCommands.length > 0) {
|
|
348
|
-
lines.push('', 'Recorded verification commands:');
|
|
349
|
-
for (const command of passedCommands.slice(0, 20)) {
|
|
350
|
-
lines.push(`- ${command}`);
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
lines.push('', `Verification journal file: ${verificationFile || '(missing)'}`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
lines.push(
|
|
357
|
-
'',
|
|
358
|
-
'Required next step:',
|
|
359
|
-
'- rerun the narrowest relevant local verification, record each successful command into verification.jsonl, then publish again',
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
process.stderr.write(`${lines.join('\n')}\n`);
|
|
363
|
-
process.exit(43);
|
|
364
|
-
EOF
|
|
@@ -1,18 +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
|
-
SESSION="${1:?usage: capture-worker.sh SESSION [LINES]}"
|
|
9
|
-
LINES="${2:-80}"
|
|
10
|
-
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
11
|
-
RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
|
|
12
|
-
FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
13
|
-
FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
|
|
14
|
-
|
|
15
|
-
exec bash "${FLOW_TOOLS_DIR}/agent-project-capture-worker" \
|
|
16
|
-
--runs-root "$RUNS_ROOT" \
|
|
17
|
-
--session "$SESSION" \
|
|
18
|
-
--lines "$LINES"
|
|
@@ -1,52 +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
|
-
FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
9
|
-
FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
|
|
10
|
-
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
11
|
-
AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
|
|
12
|
-
AGENT_ROOT="$(flow_resolve_agent_root "${CONFIG_YAML}")"
|
|
13
|
-
RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
|
|
14
|
-
HISTORY_ROOT="$(flow_resolve_history_root "${CONFIG_YAML}")"
|
|
15
|
-
WORKTREE_ROOT="$(flow_resolve_worktree_root "${CONFIG_YAML}")"
|
|
16
|
-
RETAINED_REPO_ROOT="$(flow_resolve_retained_repo_root "${CONFIG_YAML}")"
|
|
17
|
-
VSCODE_WORKSPACE_FILE="$(flow_resolve_vscode_workspace_file "${CONFIG_YAML}")"
|
|
18
|
-
ISSUE_SESSION_PREFIX="$(flow_resolve_issue_session_prefix "${CONFIG_YAML}")"
|
|
19
|
-
PR_SESSION_PREFIX="$(flow_resolve_pr_session_prefix "${CONFIG_YAML}")"
|
|
20
|
-
WORKTREE="${1-}"
|
|
21
|
-
SESSION="${2:-}"
|
|
22
|
-
MODE="generic"
|
|
23
|
-
ARGS=(
|
|
24
|
-
--repo-root "$AGENT_REPO_ROOT"
|
|
25
|
-
--runs-root "$RUNS_ROOT"
|
|
26
|
-
--history-root "$HISTORY_ROOT"
|
|
27
|
-
--worktree "${WORKTREE:-}"
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
case "$SESSION" in
|
|
31
|
-
"${ISSUE_SESSION_PREFIX}"*) MODE="issue" ;;
|
|
32
|
-
"${PR_SESSION_PREFIX}"*) MODE="pr" ;;
|
|
33
|
-
esac
|
|
34
|
-
|
|
35
|
-
ARGS+=(--mode "$MODE")
|
|
36
|
-
if [[ -n "$SESSION" ]]; then
|
|
37
|
-
ARGS+=(--session "$SESSION")
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
cleanup_exit=0
|
|
41
|
-
AGENT_PROJECT_WORKTREE_ROOT="$WORKTREE_ROOT" \
|
|
42
|
-
F_LOSNING_WORKTREE_ROOT="$WORKTREE_ROOT" \
|
|
43
|
-
bash "${FLOW_TOOLS_DIR}/agent-project-cleanup-session" "${ARGS[@]}" >/dev/null || cleanup_exit=$?
|
|
44
|
-
|
|
45
|
-
F_LOSNING_AGENT_REPO_ROOT="$AGENT_REPO_ROOT" \
|
|
46
|
-
F_LOSNING_RETAINED_REPO_ROOT="$RETAINED_REPO_ROOT" \
|
|
47
|
-
F_LOSNING_VSCODE_WORKSPACE_FILE="$VSCODE_WORKSPACE_FILE" \
|
|
48
|
-
"${FLOW_TOOLS_DIR}/sync-vscode-workspace.sh" >/dev/null 2>&1 || true
|
|
49
|
-
|
|
50
|
-
if [[ "$cleanup_exit" -ne 0 ]]; then
|
|
51
|
-
exit "$cleanup_exit"
|
|
52
|
-
fi
|
package/tools/bin/codex-quota
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
QUOTA_ENTRY="${SCRIPT_DIR}/../vendor/codex-quota/codex-quota.js"
|
|
6
|
-
NODE_BIN="${ACP_CODEX_QUOTA_NODE_BIN:-${F_LOSNING_CODEX_QUOTA_NODE_BIN:-}}"
|
|
7
|
-
|
|
8
|
-
if [[ -z "${NODE_BIN}" ]]; then
|
|
9
|
-
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
|
10
|
-
fi
|
|
11
|
-
|
|
12
|
-
if [[ -z "${NODE_BIN}" ]]; then
|
|
13
|
-
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
|
|
14
|
-
if [[ -s "${NVM_DIR}/nvm.sh" ]]; then
|
|
15
|
-
# shellcheck source=/dev/null
|
|
16
|
-
. "${NVM_DIR}/nvm.sh"
|
|
17
|
-
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
|
18
|
-
fi
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
if [[ -z "${NODE_BIN}" ]]; then
|
|
22
|
-
echo "node is required to run the bundled codex-quota tool" >&2
|
|
23
|
-
exit 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
if [[ ! -f "${QUOTA_ENTRY}" ]]; then
|
|
27
|
-
echo "bundled codex-quota entrypoint not found: ${QUOTA_ENTRY}" >&2
|
|
28
|
-
exit 1
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
exec "${NODE_BIN}" "${QUOTA_ENTRY}" "$@"
|