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.
Files changed (106) hide show
  1. package/README.md +141 -28
  2. package/assets/workflow-catalog.json +1 -1
  3. package/bin/pr-risk.sh +22 -7
  4. package/bin/sync-pr-labels.sh +1 -1
  5. package/hooks/heartbeat-hooks.sh +125 -12
  6. package/hooks/issue-reconcile-hooks.sh +1 -1
  7. package/hooks/pr-reconcile-hooks.sh +1 -1
  8. package/npm/bin/agent-control-plane.js +257 -59
  9. package/package.json +39 -32
  10. package/tools/bin/debug-session.sh +106 -0
  11. package/tools/bin/flow-config-lib.sh +1203 -60
  12. package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
  13. package/tools/bin/flow-runtime-doctor.sh +5 -1
  14. package/tools/bin/flow-shell-lib.sh +32 -0
  15. package/tools/bin/github-core-rate-limit-state.sh +77 -0
  16. package/tools/bin/github-write-outbox.sh +470 -0
  17. package/tools/bin/heartbeat-loop-scheduling-lib.sh +7 -7
  18. package/tools/bin/heartbeat-safe-auto.sh +42 -0
  19. package/tools/bin/install-project-launchd.sh +17 -2
  20. package/tools/bin/install-project-systemd.sh +255 -0
  21. package/tools/bin/project-init.sh +21 -1
  22. package/tools/bin/project-launchd-bootstrap.sh +5 -1
  23. package/tools/bin/project-runtimectl.sh +91 -2
  24. package/tools/bin/project-systemd-bootstrap.sh +74 -0
  25. package/tools/bin/scaffold-profile.sh +61 -3
  26. package/tools/bin/uninstall-project-systemd.sh +87 -0
  27. package/tools/dashboard/app.js +228 -6
  28. package/tools/dashboard/dashboard_snapshot.py +55 -0
  29. package/tools/dashboard/issue_queue_state.py +101 -0
  30. package/tools/dashboard/server.py +123 -1
  31. package/tools/dashboard/styles.css +526 -455
  32. package/tools/templates/pr-fix-template.md +3 -1
  33. package/tools/templates/pr-merge-repair-template.md +2 -1
  34. package/references/architecture.md +0 -217
  35. package/references/commands.md +0 -128
  36. package/references/control-plane-map.md +0 -124
  37. package/references/docs-map.md +0 -73
  38. package/references/release-checklist.md +0 -65
  39. package/references/repo-map.md +0 -36
  40. package/tools/bin/agent-cleanup-worktree +0 -247
  41. package/tools/bin/agent-github-update-labels +0 -71
  42. package/tools/bin/agent-init-worktree +0 -216
  43. package/tools/bin/agent-project-archive-run +0 -52
  44. package/tools/bin/agent-project-capture-worker +0 -46
  45. package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
  46. package/tools/bin/agent-project-catch-up-merged-prs +0 -194
  47. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
  48. package/tools/bin/agent-project-cleanup-session +0 -513
  49. package/tools/bin/agent-project-detached-launch +0 -127
  50. package/tools/bin/agent-project-heartbeat-loop +0 -1029
  51. package/tools/bin/agent-project-open-issue-worktree +0 -89
  52. package/tools/bin/agent-project-open-pr-worktree +0 -80
  53. package/tools/bin/agent-project-publish-issue-pr +0 -465
  54. package/tools/bin/agent-project-reconcile-issue-session +0 -1398
  55. package/tools/bin/agent-project-reconcile-pr-session +0 -1230
  56. package/tools/bin/agent-project-retry-state +0 -147
  57. package/tools/bin/agent-project-run-claude-session +0 -805
  58. package/tools/bin/agent-project-run-codex-resilient +0 -955
  59. package/tools/bin/agent-project-run-codex-session +0 -435
  60. package/tools/bin/agent-project-run-kilo-session +0 -369
  61. package/tools/bin/agent-project-run-ollama-session +0 -658
  62. package/tools/bin/agent-project-run-openclaw-session +0 -1309
  63. package/tools/bin/agent-project-run-opencode-session +0 -377
  64. package/tools/bin/agent-project-run-pi-session +0 -479
  65. package/tools/bin/agent-project-sync-anchor-repo +0 -139
  66. package/tools/bin/agent-project-worker-status +0 -188
  67. package/tools/bin/branch-verification-guard.sh +0 -364
  68. package/tools/bin/capture-worker.sh +0 -18
  69. package/tools/bin/cleanup-worktree.sh +0 -52
  70. package/tools/bin/codex-quota +0 -31
  71. package/tools/bin/create-follow-up-issue.sh +0 -114
  72. package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
  73. package/tools/bin/issue-publish-localization-guard.sh +0 -142
  74. package/tools/bin/issue-publish-scope-guard.sh +0 -242
  75. package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
  76. package/tools/bin/issue-resource-class.sh +0 -12
  77. package/tools/bin/kick-scheduler.sh +0 -75
  78. package/tools/bin/label-follow-up-issues.sh +0 -14
  79. package/tools/bin/new-pr-worktree.sh +0 -50
  80. package/tools/bin/new-worktree.sh +0 -49
  81. package/tools/bin/pr-risk.sh +0 -12
  82. package/tools/bin/prepare-worktree.sh +0 -142
  83. package/tools/bin/provider-cooldown-state.sh +0 -204
  84. package/tools/bin/publish-issue-worker.sh +0 -31
  85. package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
  86. package/tools/bin/reconcile-issue-worker.sh +0 -34
  87. package/tools/bin/reconcile-pr-worker.sh +0 -34
  88. package/tools/bin/record-verification.sh +0 -71
  89. package/tools/bin/render-flow-config.sh +0 -98
  90. package/tools/bin/resident-issue-controller-lib.sh +0 -448
  91. package/tools/bin/resident-issue-queue-status.py +0 -35
  92. package/tools/bin/retry-state.sh +0 -31
  93. package/tools/bin/reuse-issue-worktree.sh +0 -121
  94. package/tools/bin/run-codex-bypass.sh +0 -3
  95. package/tools/bin/run-codex-safe.sh +0 -3
  96. package/tools/bin/run-codex-task.sh +0 -280
  97. package/tools/bin/serve-dashboard.sh +0 -5
  98. package/tools/bin/split-retained-slice.sh +0 -124
  99. package/tools/bin/start-issue-worker.sh +0 -943
  100. package/tools/bin/start-pr-fix-worker.sh +0 -491
  101. package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
  102. package/tools/bin/start-pr-review-worker.sh +0 -261
  103. package/tools/bin/start-resident-issue-loop.sh +0 -499
  104. package/tools/bin/update-github-labels.sh +0 -14
  105. package/tools/bin/worker-status.sh +0 -19
  106. 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
@@ -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}" "$@"