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,114 +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
- usage() {
9
- cat <<'EOF'
10
- Usage:
11
- create-follow-up-issue.sh --parent ISSUE_ID --title "Title" [--body "text" | --body-file path] [--label LABEL ...]
12
-
13
- Create a focused follow-up issue linked back to the umbrella issue. By default the
14
- new issue is left unlabeled so the scheduler can pick it up normally.
15
- EOF
16
- }
17
-
18
- FLOW_TOOLS_DIR="${SCRIPT_DIR}"
19
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
20
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
21
- UPDATE_LABELS_BIN="${UPDATE_LABELS_BIN:-${FLOW_TOOLS_DIR}/agent-github-update-labels}"
22
-
23
- parent_issue=""
24
- title=""
25
- body=""
26
- body_file=""
27
- labels=()
28
-
29
- while [[ $# -gt 0 ]]; do
30
- case "$1" in
31
- --parent)
32
- parent_issue="${2:-}"
33
- shift 2
34
- ;;
35
- --title)
36
- title="${2:-}"
37
- shift 2
38
- ;;
39
- --body)
40
- body="${2:-}"
41
- shift 2
42
- ;;
43
- --body-file)
44
- body_file="${2:-}"
45
- shift 2
46
- ;;
47
- --label)
48
- labels+=("${2:-}")
49
- shift 2
50
- ;;
51
- --help|-h)
52
- usage
53
- exit 0
54
- ;;
55
- *)
56
- echo "Unknown argument: $1" >&2
57
- usage >&2
58
- exit 1
59
- ;;
60
- esac
61
- done
62
-
63
- if [[ -z "$parent_issue" || -z "$title" ]]; then
64
- usage >&2
65
- exit 1
66
- fi
67
-
68
- if [[ -n "$body" && -n "$body_file" ]]; then
69
- echo "Provide either --body or --body-file, not both." >&2
70
- exit 1
71
- fi
72
-
73
- tmp_body_file="$(mktemp)"
74
- cleanup() {
75
- rm -f "$tmp_body_file"
76
- }
77
- trap cleanup EXIT
78
-
79
- {
80
- printf 'Parent issue: #%s\n\n' "$parent_issue"
81
- if [[ -n "$body_file" ]]; then
82
- cat "$body_file"
83
- elif [[ -n "$body" ]]; then
84
- printf '%s\n' "$body"
85
- else
86
- printf 'Follow-up slice decomposed from umbrella issue #%s.\n' "$parent_issue"
87
- fi
88
- } >"$tmp_body_file"
89
-
90
- issue_url="$(flow_github_issue_create "$REPO_SLUG" "$title" "$tmp_body_file")"
91
- issue_url="$(printf '%s' "$issue_url" | tail -n 1)"
92
- issue_number="$(sed -nE 's#.*/issues/([0-9]+)$#\1#p' <<<"$issue_url" | tail -n 1)"
93
-
94
- if [[ -z "$issue_number" ]]; then
95
- echo "Unable to determine created issue number from gh output: $issue_url" >&2
96
- exit 1
97
- fi
98
-
99
- if [[ ${#labels[@]} -gt 0 ]]; then
100
- update_args=()
101
- for label in "${labels[@]}"; do
102
- [[ -n "$label" ]] || continue
103
- update_args+=(--add "$label")
104
- done
105
- if [[ ${#update_args[@]} -gt 0 ]]; then
106
- bash "${UPDATE_LABELS_BIN}" \
107
- --repo-slug "$REPO_SLUG" \
108
- --number "$issue_number" \
109
- "${update_args[@]}" >/dev/null || true
110
- fi
111
- fi
112
-
113
- printf 'ISSUE_NUMBER=%s\n' "$issue_number"
114
- printf 'ISSUE_URL=%s\n' "$issue_url"
@@ -1,50 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- FLOW_SKILL_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
6
- HOME_DIR="${ACP_DASHBOARD_HOME_DIR:-${HOME:-}}"
7
- SOURCE_HOME="${ACP_DASHBOARD_SOURCE_HOME:-}"
8
- RUNTIME_HOME="${ACP_DASHBOARD_RUNTIME_HOME:-${HOME_DIR}/.agent-runtime/runtime-home}"
9
- PROFILE_REGISTRY_ROOT="${ACP_DASHBOARD_PROFILE_REGISTRY_ROOT:-${ACP_PROFILE_REGISTRY_ROOT:-${HOME_DIR}/.agent-runtime/control-plane/profiles}}"
10
- HOST="${ACP_DASHBOARD_HOST:-127.0.0.1}"
11
- PORT="${ACP_DASHBOARD_PORT:-8765}"
12
- BASE_PATH="${ACP_DASHBOARD_PATH:-/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin}"
13
- SYNC_SCRIPT="${ACP_DASHBOARD_SYNC_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/sync-shared-agent-home.sh}"
14
- ENSURE_SYNC_SCRIPT="${ACP_DASHBOARD_ENSURE_SYNC_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/ensure-runtime-sync.sh}"
15
- RUNTIME_SERVE_SCRIPT="${ACP_DASHBOARD_RUNTIME_SERVE_SCRIPT:-${RUNTIME_HOME}/skills/openclaw/agent-control-plane/tools/bin/serve-dashboard.sh}"
16
-
17
- if [[ -z "${HOME_DIR}" ]]; then
18
- echo "dashboard launchd bootstrap requires HOME or ACP_DASHBOARD_HOME_DIR" >&2
19
- exit 64
20
- fi
21
-
22
- export HOME="${HOME_DIR}"
23
- export PATH="${BASE_PATH}"
24
- export ACP_PROFILE_REGISTRY_ROOT="${PROFILE_REGISTRY_ROOT}"
25
- export PYTHONDONTWRITEBYTECODE=1
26
-
27
- if [[ ! -x "${ENSURE_SYNC_SCRIPT}" && ! -x "${SYNC_SCRIPT}" ]]; then
28
- echo "dashboard launchd bootstrap missing sync helper: ${ENSURE_SYNC_SCRIPT}" >&2
29
- exit 65
30
- fi
31
-
32
- if [[ -x "${ENSURE_SYNC_SCRIPT}" ]]; then
33
- ensure_args=(--runtime-home "${RUNTIME_HOME}" --quiet)
34
- if [[ -n "${SOURCE_HOME}" ]]; then
35
- ensure_args=(--source-home "${SOURCE_HOME}" "${ensure_args[@]}")
36
- fi
37
- bash "${ENSURE_SYNC_SCRIPT}" "${ensure_args[@]}"
38
- else
39
- if [[ -z "${SOURCE_HOME}" ]]; then
40
- SOURCE_HOME="${FLOW_SKILL_DIR}"
41
- fi
42
- bash "${SYNC_SCRIPT}" "${SOURCE_HOME}" "${RUNTIME_HOME}" >/dev/null
43
- fi
44
-
45
- if [[ ! -x "${RUNTIME_SERVE_SCRIPT}" ]]; then
46
- echo "dashboard launchd bootstrap missing runtime serve script: ${RUNTIME_SERVE_SCRIPT}" >&2
47
- exit 66
48
- fi
49
-
50
- exec bash "${RUNTIME_SERVE_SCRIPT}" --host "${HOST}" --port "${PORT}"
@@ -1,142 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- usage() {
5
- cat <<'EOF'
6
- Usage:
7
- issue-publish-localization-guard.sh --worktree <path> --base-ref <git-ref>
8
-
9
- Fail fast when an issue branch updates locale resources but still leaves obvious
10
- hardcoded user-facing strings in the touched UI files.
11
- EOF
12
- }
13
-
14
- worktree=""
15
- base_ref=""
16
-
17
- while [[ $# -gt 0 ]]; do
18
- case "$1" in
19
- --worktree) worktree="${2:-}"; shift 2 ;;
20
- --base-ref) base_ref="${2:-}"; shift 2 ;;
21
- --help|-h) usage; exit 0 ;;
22
- *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
23
- esac
24
- done
25
-
26
- if [[ -z "$worktree" || -z "$base_ref" ]]; then
27
- usage >&2
28
- exit 1
29
- fi
30
-
31
- if [[ ! -d "$worktree" ]]; then
32
- echo "missing worktree: $worktree" >&2
33
- exit 1
34
- fi
35
-
36
- changed_files="$(
37
- git -C "$worktree" diff --name-only --diff-filter=ACMR "${base_ref}...HEAD"
38
- )"
39
-
40
- CHANGED_FILES="${changed_files}" WORKTREE="${worktree}" node <<'EOF'
41
- const fs = require('fs');
42
- const path = require('path');
43
-
44
- const changedFiles = String(process.env.CHANGED_FILES || '')
45
- .split('\n')
46
- .map((file) => file.trim())
47
- .filter(Boolean);
48
- const worktree = String(process.env.WORKTREE || '');
49
-
50
- const localeFiles = changedFiles.filter((file) =>
51
- /^packages\/i18n\/src\/resources\/[^/]+\.json$/.test(file),
52
- );
53
- if (localeFiles.length === 0) {
54
- process.stdout.write('LOCALIZATION_GUARD_STATUS=skipped-no-locale-files\n');
55
- process.exit(0);
56
- }
57
-
58
- const uiFiles = changedFiles.filter((file) =>
59
- /^(?:apps\/web\/|apps\/mobile\/|packages\/ui\/).+\.[cm]?[jt]sx?$/.test(file),
60
- );
61
- if (uiFiles.length === 0) {
62
- process.stdout.write('LOCALIZATION_GUARD_STATUS=skipped-no-ui-files\n');
63
- process.exit(0);
64
- }
65
-
66
- const suspiciousPatterns = [
67
- {
68
- reason: 'validation_literal',
69
- test: (line) =>
70
- /\.(?:min|max|length|email|regex|nonempty)\([^)]*,\s*['"`][^'"`]*[A-Za-z][^'"`]*['"`]/.test(line),
71
- },
72
- {
73
- reason: 'string_prop',
74
- test: (line) =>
75
- /\b(?:title|description|actionLabel|aria-label|placeholder)\s*=\s*['"][^'{"][^'"]*[A-Za-z][^'"]*['"]/.test(
76
- line,
77
- ),
78
- },
79
- {
80
- reason: 'object_label_literal',
81
- test: (line) =>
82
- /\blabel\s*:\s*['"][A-Za-z][^'"]*['"]/.test(line),
83
- },
84
- ];
85
-
86
- const ignoreLine = (line) => {
87
- const trimmed = line.trim();
88
- if (!trimmed) return true;
89
- if (/^\s*\/\//.test(trimmed)) return true;
90
- if (/\bt\(/.test(trimmed)) return true;
91
- if (/\buseSafeTranslation\b|\buseTranslation\b|\bi18nKey=/.test(trimmed)) return true;
92
- if (/^import\s/.test(trimmed)) return true;
93
- return false;
94
- };
95
-
96
- const findings = [];
97
- for (const relativeFile of uiFiles) {
98
- const absoluteFile = path.join(worktree, relativeFile);
99
- if (!fs.existsSync(absoluteFile)) continue;
100
- const lines = fs.readFileSync(absoluteFile, 'utf8').split(/\r?\n/);
101
- lines.forEach((line, index) => {
102
- if (ignoreLine(line)) return;
103
- for (const pattern of suspiciousPatterns) {
104
- if (pattern.test(line)) {
105
- findings.push({
106
- file: relativeFile,
107
- line: index + 1,
108
- reason: pattern.reason,
109
- text: line.trim(),
110
- });
111
- return;
112
- }
113
- }
114
- });
115
- }
116
-
117
- if (findings.length === 0) {
118
- process.stdout.write('LOCALIZATION_GUARD_STATUS=ok\n');
119
- process.stdout.write(`LOCALE_RESOURCE_COUNT=${localeFiles.length}\n`);
120
- process.stdout.write(`UI_FILE_COUNT=${uiFiles.length}\n`);
121
- process.exit(0);
122
- }
123
-
124
- const lines = [
125
- 'Localization guard blocked branch publication.',
126
- '',
127
- 'The branch updates locale resources but still leaves obvious hardcoded user-facing strings in touched UI files.',
128
- '',
129
- 'Why it was blocked:',
130
- ];
131
- for (const finding of findings.slice(0, 20)) {
132
- lines.push(`- ${finding.reason}: ${finding.file}:${finding.line} -> ${finding.text}`);
133
- }
134
- lines.push(
135
- '',
136
- 'Required next step:',
137
- '- move the remaining user-facing literals behind translation keys before publishing this issue branch',
138
- );
139
-
140
- process.stderr.write(`${lines.join('\n')}\n`);
141
- process.exit(44);
142
- EOF
@@ -1,242 +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
- usage() {
9
- cat <<'EOF'
10
- Usage:
11
- issue-publish-scope-guard.sh --worktree <path> --base-ref <git-ref> [--issue-id <number>]
12
-
13
- Fail fast when an issue worker branch is too broad to publish safely as a single
14
- PR slice.
15
- EOF
16
- }
17
-
18
- worktree=""
19
- base_ref=""
20
- issue_id=""
21
-
22
- while [[ $# -gt 0 ]]; do
23
- case "$1" in
24
- --worktree) worktree="${2:-}"; shift 2 ;;
25
- --base-ref) base_ref="${2:-}"; shift 2 ;;
26
- --issue-id) issue_id="${2:-}"; shift 2 ;;
27
- --help|-h) usage; exit 0 ;;
28
- *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
29
- esac
30
- done
31
-
32
- if [[ -z "$worktree" || -z "$base_ref" ]]; 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
- git -C "$worktree" diff --name-only --diff-filter=ACMR "${base_ref}...HEAD"
44
- )"
45
-
46
- issue_title=""
47
- issue_body=""
48
- if [[ -n "$issue_id" ]]; then
49
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
50
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
51
- issue_json="$(flow_github_issue_view_json "${REPO_SLUG}" "${issue_id}" 2>/dev/null || true)"
52
- if [[ -n "$issue_json" ]]; then
53
- issue_title="$(jq -r '.title // ""' <<<"$issue_json" 2>/dev/null || true)"
54
- issue_body="$(jq -r '.body // ""' <<<"$issue_json" 2>/dev/null || true)"
55
- fi
56
- fi
57
-
58
- CHANGED_FILES="$changed_files" ISSUE_ID="$issue_id" ISSUE_TITLE="$issue_title" ISSUE_BODY="$issue_body" node <<'EOF'
59
- const files = String(process.env.CHANGED_FILES || '')
60
- .split('\n')
61
- .map((file) => file.trim())
62
- .filter(Boolean);
63
-
64
- const issueId = String(process.env.ISSUE_ID || '').trim();
65
- const issueTitle = String(process.env.ISSUE_TITLE || '').trim();
66
- const issueBody = String(process.env.ISSUE_BODY || '').trim();
67
-
68
- const isDoc = (file) =>
69
- /^openspec\//.test(file) ||
70
- /^docs\//.test(file) ||
71
- /^scripts\/README\.md$/.test(file) ||
72
- /\.md$/i.test(file);
73
-
74
- const isTest = (file) =>
75
- /(?:^|\/)__tests__\//.test(file) ||
76
- /(?:^|\/)e2e\//.test(file) ||
77
- /\.(?:spec|test)\.[cm]?[jt]sx?$/.test(file);
78
-
79
- const isLocaleResource = (file) =>
80
- /^packages\/i18n\/src\/resources\/[^/]+\.json$/.test(file);
81
-
82
- const isProductNonTest = (file) =>
83
- !isDoc(file) &&
84
- !isTest(file) &&
85
- !isLocaleResource(file) &&
86
- (/^apps\//.test(file) || /^packages\//.test(file));
87
-
88
- const isMobileRouteFile = (file) =>
89
- /^apps\/mobile\/app\/.+\.[cm]?[jt]sx?$/.test(file) &&
90
- !isTest(file);
91
-
92
- const mobileSurfaceKey = (file) => {
93
- const relative = file
94
- .replace(/^apps\/mobile\/app\//, '')
95
- .replace(/\.[cm]?[jt]sx?$/, '');
96
- const segments = relative
97
- .split('/')
98
- .filter(Boolean)
99
- .filter((segment) => !/^\(.+\)$/.test(segment));
100
- if (segments.length === 0) return relative;
101
- return segments[0];
102
- };
103
-
104
- const isAuthCriticalProductFile = (file) =>
105
- /^apps\/api\/src\/modules\/auth\//.test(file) ||
106
- file === 'apps/api/src/entities/user.entity.ts' ||
107
- /^apps\/api\/src\/migrations\/.*(?:Email|Phone|Auth|User).*\.[cm]?[jt]s$/.test(file) ||
108
- /^apps\/api\/src\/common\/utils\/(?:phone|tenant|email|auth)[^/]*\.[cm]?[jt]s$/.test(file);
109
-
110
- const isAuthAdjacentProductFile = (file) =>
111
- isAuthCriticalProductFile(file) ||
112
- /^apps\/web\/src\/app\/\(auth\)\//.test(file) ||
113
- /^apps\/api\/src\/modules\/organization\//.test(file);
114
-
115
- const productNonTestFiles = files.filter(isProductNonTest);
116
- const mobileProductFiles = productNonTestFiles.filter((file) => /^apps\/mobile\//.test(file));
117
- const localeResourceFiles = files.filter(isLocaleResource);
118
- const mobileRouteFiles = productNonTestFiles.filter(isMobileRouteFile);
119
- const mobileSurfaceKeys = [...new Set(mobileRouteFiles.map(mobileSurfaceKey))];
120
- const authCriticalTouched = productNonTestFiles.some(isAuthCriticalProductFile);
121
- const authMixedScopeFiles = authCriticalTouched
122
- ? productNonTestFiles.filter((file) => !isAuthAdjacentProductFile(file))
123
- : [];
124
- const openspecChangeFiles = files.filter((file) => /^openspec\/changes\//.test(file));
125
- const workflowRuleFiles = files.filter((file) =>
126
- /^(?:AGENTS\.md|openspec\/AGENT_RULES\.md)$/.test(file),
127
- );
128
- const docsDeclaredScope =
129
- /^(?:docs?|documentation)\b/i.test(issueTitle) ||
130
- /^docs?\(/i.test(issueTitle) ||
131
- /\b(?:docs?|documentation|openspec)[ -]?only\b/i.test(issueTitle) ||
132
- /(?:^|\n)\s*(?:scope|mode|type)\s*:\s*(?:docs?|documentation|openspec)(?:[- ]only)?\b/i.test(issueBody) ||
133
- /(?:^|\n)\s*(?:docs?|documentation|openspec)[ -]?only\b/i.test(issueBody);
134
-
135
- const reasons = [];
136
- if (productNonTestFiles.length > 14) {
137
- reasons.push(`product_non_test_count=${productNonTestFiles.length} exceeds max=14`);
138
- }
139
- if (mobileProductFiles.length >= 8) {
140
- reasons.push(`mobile_product_count=${mobileProductFiles.length} exceeds max=7`);
141
- }
142
- if (localeResourceFiles.length > 8 && productNonTestFiles.length >= 6) {
143
- reasons.push(
144
- `locale_resource_count=${localeResourceFiles.length} with product_non_test_count=${productNonTestFiles.length} exceeds mixed-scope limit`,
145
- );
146
- }
147
- if (mobileRouteFiles.length > 3) {
148
- reasons.push(`mobile_route_file_count=${mobileRouteFiles.length} exceeds max=3`);
149
- }
150
- if (mobileSurfaceKeys.length > 2) {
151
- reasons.push(`mobile_surface_count=${mobileSurfaceKeys.length} exceeds max=2`);
152
- }
153
- if (authMixedScopeFiles.length > 0) {
154
- reasons.push(
155
- `auth_mixed_scope_count=${authMixedScopeFiles.length} requires a dedicated auth slice before publish`,
156
- );
157
- }
158
- if (docsDeclaredScope && productNonTestFiles.length > 0) {
159
- reasons.push(`docs_declared_scope_contains_product_changes=${productNonTestFiles.length}`);
160
- }
161
- if (openspecChangeFiles.length > 0 && productNonTestFiles.length > 0) {
162
- reasons.push(
163
- `product_and_openspec_change_mix=product:${productNonTestFiles.length},openspec_change:${openspecChangeFiles.length}`,
164
- );
165
- }
166
- if (workflowRuleFiles.length > 0 && productNonTestFiles.length > 0) {
167
- reasons.push(
168
- `workflow_rule_files_mixed_with_product_changes=${workflowRuleFiles.length}`,
169
- );
170
- }
171
-
172
- if (reasons.length === 0) {
173
- process.stdout.write('SCOPE_GUARD_STATUS=ok\n');
174
- process.stdout.write(`PRODUCT_NON_TEST_COUNT=${productNonTestFiles.length}\n`);
175
- process.stdout.write(`MOBILE_PRODUCT_COUNT=${mobileProductFiles.length}\n`);
176
- process.stdout.write(`LOCALE_RESOURCE_COUNT=${localeResourceFiles.length}\n`);
177
- process.stdout.write(`MOBILE_ROUTE_FILE_COUNT=${mobileRouteFiles.length}\n`);
178
- process.stdout.write(`MOBILE_SURFACE_COUNT=${mobileSurfaceKeys.length}\n`);
179
- process.stdout.write(`AUTH_MIXED_SCOPE_COUNT=${authMixedScopeFiles.length}\n`);
180
- process.exit(0);
181
- }
182
-
183
- const lines = [
184
- `Scope guard blocked issue${issueId ? ` #${issueId}` : ''} from publishing as a single PR.`,
185
- '',
186
- 'The branch is too broad for the current flow and should be split into a smaller slice before publish.',
187
- '',
188
- 'Why it was blocked:',
189
- ...reasons.map((reason) => `- ${reason}`),
190
- ];
191
-
192
- if (productNonTestFiles.length > 0) {
193
- lines.push('', 'Representative product files:');
194
- for (const file of productNonTestFiles.slice(0, 12)) {
195
- lines.push(`- ${file}`);
196
- }
197
- }
198
-
199
- if (localeResourceFiles.length > 0) {
200
- lines.push('', 'Locale resource files touched:');
201
- for (const file of localeResourceFiles.slice(0, 12)) {
202
- lines.push(`- ${file}`);
203
- }
204
- }
205
-
206
- if (openspecChangeFiles.length > 0) {
207
- lines.push('', 'OpenSpec change files touched:');
208
- for (const file of openspecChangeFiles.slice(0, 12)) {
209
- lines.push(`- ${file}`);
210
- }
211
- }
212
-
213
- if (workflowRuleFiles.length > 0) {
214
- lines.push('', 'Workflow or repo-rule files touched:');
215
- for (const file of workflowRuleFiles.slice(0, 12)) {
216
- lines.push(`- ${file}`);
217
- }
218
- }
219
-
220
- if (mobileRouteFiles.length > 0) {
221
- lines.push('', 'Mobile route files touched:');
222
- for (const file of mobileRouteFiles.slice(0, 12)) {
223
- lines.push(`- ${file}`);
224
- }
225
- }
226
-
227
- if (authMixedScopeFiles.length > 0) {
228
- lines.push('', 'Files that should move to a separate follow-up PR from the auth slice:');
229
- for (const file of authMixedScopeFiles.slice(0, 12)) {
230
- lines.push(`- ${file}`);
231
- }
232
- }
233
-
234
- lines.push(
235
- '',
236
- 'Required next step:',
237
- '- re-run the issue as a narrower slice that stays within one primary product surface, at most two mobile route surfaces, one dedicated auth/security contract change, or one docs/OpenSpec-only branch with no product code',
238
- );
239
-
240
- process.stderr.write(`${lines.join('\n')}\n`);
241
- process.exit(42);
242
- EOF
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ISSUE_BODY="${ISSUE_BODY:-${1:-}}"
5
-
6
- ISSUE_BODY="$ISSUE_BODY" node <<'EOF'
7
- const body = process.env.ISSUE_BODY || '';
8
- const scheduled = /^\s*(?:Agent schedule|Schedule|Cadence)\s*:\s*(?:every\s+)?(\d+)\s*([mhd])\s*$/im.test(body);
9
-
10
- if (!scheduled) {
11
- process.stdout.write('no\n');
12
- process.exit(0);
13
- }
14
-
15
- if (/^\s*(?:Local workspace install|Worktree local install)\s*:\s*yes\s*$/im.test(body)) {
16
- process.stdout.write('yes\n');
17
- process.exit(0);
18
- }
19
-
20
- const installLikePatterns = [
21
- /(^|\n)\s*\d+\.\s*`(?:pnpm|npm|yarn)\s+(?:install|i|ci|add|remove|rm|up|update|rebuild|dlx)\b/im,
22
- /(^|\n)\s*\d+\.\s*`pnpm\s+exec\s+pod\s+install\b/im,
23
- /(^|\n)\s*\d+\.\s*`(?:npx\s+pod-install|expo\s+prebuild|pod\s+install|bundle\s+install)\b/im,
24
- /`(?:pnpm|npm|yarn)\s+(?:install|i|ci|add|remove|rm|up|update|rebuild|dlx)\b/im,
25
- /`pnpm\s+exec\s+pod\s+install\b/im,
26
- /`(?:npx\s+pod-install|expo\s+prebuild|pod\s+install|bundle\s+install)\b/im,
27
- ];
28
-
29
- const needsLocalInstall = installLikePatterns.some((pattern) => pattern.test(body));
30
- process.stdout.write(needsLocalInstall ? 'yes\n' : 'no\n');
31
- EOF
@@ -1,12 +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
- ISSUE_ID="${1:?usage: issue-resource-class.sh ISSUE_ID}"
9
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
10
- ADAPTER_BIN_DIR="${FLOW_SKILL_DIR}/bin"
11
-
12
- "${ADAPTER_BIN_DIR}/issue-resource-class.sh" "$ISSUE_ID"
@@ -1,75 +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
- DELAY_SECONDS="${1:-5}"
9
- WORKSPACE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
10
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
11
- if ! flow_require_explicit_profile_selection "${FLOW_SKILL_DIR}" "kick-scheduler.sh"; then
12
- exit 64
13
- fi
14
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
15
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
16
- STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
17
- BOOTSTRAP_SCRIPT="${ACP_BOOTSTRAP_SCRIPT:-${F_LOSNING_BOOTSTRAP_SCRIPT:-${AGENT_SCHEDULER_BOOTSTRAP_SCRIPT:-$HOME/.agent-runtime/control-plane/workspace/bin/agent-scheduler-launchd.sh}}}"
18
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
19
- FLOW_SAFE_AUTO_SCRIPT="${ACP_FLOW_HEARTBEAT_SCRIPT:-${F_LOSNING_FLOW_HEARTBEAT_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/heartbeat-safe-auto.sh}}"
20
- STATE_DIR="${ACP_SCHEDULER_KICK_STATE_DIR:-${STATE_ROOT}/kick-scheduler}"
21
- PID_FILE="${STATE_DIR}/pid"
22
-
23
- mkdir -p "${STATE_DIR}"
24
-
25
- active_heartbeat_pid() {
26
- ps -ax -o pid=,command= \
27
- | while read -r pid command; do
28
- [[ -n "${pid:-}" ]] || continue
29
- case "$command" in
30
- *"${BOOTSTRAP_SCRIPT}"*|*"${WORKSPACE_DIR}/bin/heartbeat-safe-auto.sh"*|*"${FLOW_SAFE_AUTO_SCRIPT}"*|*"agent-project-heartbeat-loop --repo-slug ${REPO_SLUG}"*)
31
- printf '%s\n' "$pid"
32
- return 0
33
- ;;
34
- esac
35
- done
36
- }
37
-
38
- if active_pid="$(active_heartbeat_pid)"; [[ -n "$active_pid" ]]; then
39
- printf 'KICK_STATUS=active-heartbeat\n'
40
- printf 'PID=%s\n' "$active_pid"
41
- exit 0
42
- fi
43
-
44
- if [[ -f "${PID_FILE}" ]]; then
45
- existing_pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
46
- if [[ -n "${existing_pid}" ]] && kill -0 "${existing_pid}" 2>/dev/null; then
47
- printf 'KICK_STATUS=already-pending\n'
48
- printf 'PID=%s\n' "${existing_pid}"
49
- exit 0
50
- fi
51
- fi
52
-
53
- export DELAY_SECONDS BOOTSTRAP_SCRIPT FLOW_SAFE_AUTO_SCRIPT PID_FILE REPO_SLUG
54
- nohup bash -lc '
55
- trap '\''rm -f "$PID_FILE"'\'' EXIT
56
- sleep "$DELAY_SECONDS"
57
- active_pid="$(ps -ax -o pid=,command= | while read -r pid command; do
58
- [[ -n "${pid:-}" ]] || continue
59
- case "$command" in
60
- *"$BOOTSTRAP_SCRIPT"*|*"$FLOW_SAFE_AUTO_SCRIPT"*|*"agent-project-heartbeat-loop --repo-slug $REPO_SLUG"*)
61
- printf "%s\n" "$pid"
62
- break
63
- ;;
64
- esac
65
- done)"
66
- if [[ -n "$active_pid" ]]; then
67
- exit 0
68
- fi
69
- "$BOOTSTRAP_SCRIPT" >/dev/null 2>&1 || true
70
- ' >/dev/null 2>&1 &
71
-
72
- bg_pid="$!"
73
- printf '%s\n' "${bg_pid}" >"${PID_FILE}"
74
- printf 'KICK_STATUS=scheduled\n'
75
- printf 'PID=%s\n' "${bg_pid}"
@@ -1,14 +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: label-follow-up-issues.sh SESSION}"
9
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
10
- RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
11
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
12
- ADAPTER_BIN_DIR="${FLOW_SKILL_DIR}/bin"
13
-
14
- ACP_RUNS_ROOT="$RUNS_ROOT" F_LOSNING_RUNS_ROOT="$RUNS_ROOT" "${ADAPTER_BIN_DIR}/label-follow-up-issues.sh" "$SESSION"