autonomous-sdlc-harness 0.6.4 → 0.6.5
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autonomous-sdlc-harness",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "The outer loop of the autonomous SDLC harness: init, doctor, config and daemon management for the harness Claude Code plugin, plus opt-in local docs retrieval.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "firu-daniel",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
# `@sdlc-harness` anywhere, not in the exact command form, is read by one
|
|
24
24
|
# read-only agent session running the plugin's mention-reading slash command,
|
|
25
25
|
# which `remote-run.sh control` launches and whose decision it validates.
|
|
26
|
+
# A comment job first asks `remote-run.sh control --needs-agent` whether that
|
|
27
|
+
# session would start (THE AGENT CHECK below); the act step re-checks
|
|
28
|
+
# everything.
|
|
26
29
|
# Every decision lives in that script; this file carries only the events, the
|
|
27
30
|
# permissions, a prefilter, the concurrency group, and on a comment job the
|
|
28
31
|
# credential, the agent CLI and the pinned plugin that session needs.
|
|
@@ -137,9 +140,22 @@
|
|
|
137
140
|
# therefore the release the run job installs. A pinned release older than
|
|
138
141
|
# mentions carries no mention-reading command and gets `control`'s no-plugin
|
|
139
142
|
# reply; the route is `init --upgrade-workflows` (docs/remote-execution.md,
|
|
140
|
-
# `### Upgrading`). The three comment-only steps
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
+
# `### Upgrading`). The three comment-only steps run only when THE AGENT CHECK
|
|
144
|
+
# below did not answer `no`, so an exact-form command now skips them. They stay
|
|
145
|
+
# `continue-on-error`, so a failed install still never blocks one: `control`
|
|
146
|
+
# then finds no agent binary or plugin and answers a mention with its exit-3
|
|
147
|
+
# reply.
|
|
148
|
+
#
|
|
149
|
+
# THE AGENT CHECK. A comment job runs `remote-run.sh control --needs-agent`
|
|
150
|
+
# before the three comment-only steps. It tells the exact command form apart
|
|
151
|
+
# from a mention, then runs `control`'s gates 1 to 3 on a mention, and posts,
|
|
152
|
+
# dispatches and writes nothing. Only its exit 2 (no session would start: an
|
|
153
|
+
# ignored comment, an exact-form command or a refused commenter) writes
|
|
154
|
+
# `agent=no`, which skips those steps. Every other outcome installs, as
|
|
155
|
+
# before: an older script that does not know the flag, a failed permission
|
|
156
|
+
# call, a failed step. A mention by an admitted commenter costs one extra
|
|
157
|
+
# permission call. It is a saving, not the authority, as THE PREFILTER says of
|
|
158
|
+
# the `if:`: the act step re-checks every rule.
|
|
143
159
|
#
|
|
144
160
|
# A REFUSAL IS A SUCCESS. `remote-run.sh control` exits 2 for a refusal it has
|
|
145
161
|
# already replied to; that exit code is the script's contract with every other
|
|
@@ -187,7 +203,9 @@
|
|
|
187
203
|
# remote-run.sh (the scriptsDir copy) IN_OAUTH, IN_API and
|
|
188
204
|
# HARNESS_MENTION_PLUGIN_DIR, the env
|
|
189
205
|
# interface `control` reads (its MENTION
|
|
190
|
-
# paragraph)
|
|
206
|
+
# paragraph); `control --needs-agent`,
|
|
207
|
+
# whose exit 2 alone means no session
|
|
208
|
+
# would start
|
|
191
209
|
#
|
|
192
210
|
# ACTION PINS.
|
|
193
211
|
# actions/checkout@v5
|
|
@@ -269,9 +287,22 @@ jobs:
|
|
|
269
287
|
git config user.name "github-actions[bot]"
|
|
270
288
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
271
289
|
|
|
272
|
-
- name:
|
|
290
|
+
- name: Decide whether the comment needs the agent
|
|
291
|
+
id: needs
|
|
273
292
|
if: github.event_name == 'issue_comment'
|
|
274
293
|
continue-on-error: true
|
|
294
|
+
run: |
|
|
295
|
+
status=0
|
|
296
|
+
bash "$SCRIPTS_DIR/remote-run.sh" control --needs-agent || status=$?
|
|
297
|
+
if [ "$status" -eq 2 ]; then
|
|
298
|
+
echo "agent=no" >> "$GITHUB_OUTPUT"
|
|
299
|
+
else
|
|
300
|
+
echo "agent=yes" >> "$GITHUB_OUTPUT"
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
- name: Set up Node
|
|
304
|
+
if: github.event_name == 'issue_comment' && steps.needs.outputs.agent != 'no'
|
|
305
|
+
continue-on-error: true
|
|
275
306
|
uses: actions/setup-node@v5
|
|
276
307
|
with:
|
|
277
308
|
node-version: '22'
|
|
@@ -279,7 +310,7 @@ jobs:
|
|
|
279
310
|
package-manager-cache: false
|
|
280
311
|
|
|
281
312
|
- name: Install the claude CLI when absent
|
|
282
|
-
if: github.event_name == 'issue_comment'
|
|
313
|
+
if: github.event_name == 'issue_comment' && steps.needs.outputs.agent != 'no'
|
|
283
314
|
continue-on-error: true
|
|
284
315
|
run: |
|
|
285
316
|
if ! command -v claude >/dev/null 2>&1; then
|
|
@@ -288,7 +319,7 @@ jobs:
|
|
|
288
319
|
claude --version
|
|
289
320
|
|
|
290
321
|
- name: Fetch the pinned plugin
|
|
291
|
-
if: github.event_name == 'issue_comment'
|
|
322
|
+
if: github.event_name == 'issue_comment' && steps.needs.outputs.agent != 'no'
|
|
292
323
|
continue-on-error: true
|
|
293
324
|
run: |
|
|
294
325
|
pin=$(awk '/^[[:space:]]*HARNESS_CLI_VERSION:/ { sub(/^[[:space:]]*HARNESS_CLI_VERSION:[[:space:]]*/, ""); sub(/[[:space:]]+#.*$/, ""); sub(/[[:space:]]+$/, ""); gsub(/["\047]/, ""); print; exit }' .github/workflows/harness-run.yml 2>/dev/null || true)
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
# (always 0, 1 only on a usage error: its paragraph)
|
|
39
39
|
# remote-run.sh collect <branch> [--pr <n>] [--repo <root>]
|
|
40
40
|
# (always 0, 1 only on a usage error: its paragraph)
|
|
41
|
-
# remote-run.sh control [--repo <root>]
|
|
41
|
+
# remote-run.sh control [--needs-agent] [--repo <root>]
|
|
42
|
+
# (its own exit map: its paragraph)
|
|
42
43
|
# 0 sent (for stop: the action=stop marker was dispatched, and every
|
|
43
44
|
# queued, waiting or in-progress `harness run` run of that branch was
|
|
44
45
|
# asked to cancel, or there was none); for status and fetch: printed
|
|
@@ -534,6 +535,18 @@
|
|
|
534
535
|
# no such word exists today): the reply lists every command and names
|
|
535
536
|
# `docs/github-run-control.md`. Skipped for a mention.
|
|
536
537
|
# Then THE BRANCH, and the exact form's arm (`control_run_verb`) or MENTION.
|
|
538
|
+
# `--needs-agent` answers only whether MENTION would start a session: the
|
|
539
|
+
# intake above, then refusals 1 to 3 (`control_gates`, shared with `control`),
|
|
540
|
+
# run alone, posting, dispatching and creating nothing; its one `gh` call is
|
|
541
|
+
# `authorise_actor`'s permission call, and the branch is never resolved. One
|
|
542
|
+
# stdout line each: exit 0 `needs-agent: yes, a mention by @<login> on #<n>`;
|
|
543
|
+
# exit 2 `needs-agent: no, <reason>` for an event other than `issue_comment`,
|
|
544
|
+
# an ignored comment (after its own line), the exact form (before any gate,
|
|
545
|
+
# no `gh` call), or a refusal, whose <reason> is that refusal's reply text;
|
|
546
|
+
# exit 3 `needs-agent: undecided, <AUTH_WHY>` when the permission call failed
|
|
547
|
+
# (`control` still refuses that with exit 2); exit 1 as `control`'s.
|
|
548
|
+
# `WORKFLOW_CONTROL_FILE` runs it before installing the agent; the act step,
|
|
549
|
+
# plain `control`, still re-checks everything.
|
|
537
550
|
# MENTION. `verb_control`'s first statements, for every event, copy `IN_OAUTH`
|
|
538
551
|
# / `IN_API` into the non-exported `MENTION_OAUTH` / `MENTION_API` and unset
|
|
539
552
|
# them, with `CLAUDE_CODE_OAUTH_TOKEN` / `ANTHROPIC_API_KEY`, so no `gh`, `git`
|
|
@@ -611,8 +624,8 @@
|
|
|
611
624
|
# is `branch-resume`'s confirmation. Exits: 0 answered or nothing to do; 2
|
|
612
625
|
# refused; 3 a state read, plugin, binary or agent failure, or a credential
|
|
613
626
|
# value in `text` or `answer`; a carried-out verb, its arm's. The workflow's
|
|
614
|
-
# interface: `IN_OAUTH` / `IN_API` and
|
|
615
|
-
# `
|
|
627
|
+
# interface: `IN_OAUTH` / `IN_API`, `HARNESS_MENTION_PLUGIN_DIR`, and
|
|
628
|
+
# `control --needs-agent`'s exit 2, on which it skips installing the agent.
|
|
616
629
|
# THE BRANCH. On a pull request (`.issue.pull_request.url` set), its head, by
|
|
617
630
|
# `pr view`: a fork's pull request is refused, because this event carries the
|
|
618
631
|
# repository's secrets, and nothing from its head is checked out or run; one
|
|
@@ -1540,6 +1553,13 @@
|
|
|
1540
1553
|
# harness-run.yml --ref feat_x -f action=pause -f branch=feat_x`,
|
|
1541
1554
|
# then one comment on 12 opening `Read from your mention as
|
|
1542
1555
|
# `@sdlc-harness pause`.`
|
|
1556
|
+
# needs-agent the mention's c.json, `control --needs-agent` -> 0, one line
|
|
1557
|
+
# `needs-agent: yes, a mention by @alice on #12`; "$s.log" holds
|
|
1558
|
+
# only the permission call
|
|
1559
|
+
# needs-agent c.json's body `@sdlc-harness pause` -> 2, one `needs-agent: no`
|
|
1560
|
+
# exact line naming the exact form; "$s.log" unchanged
|
|
1561
|
+
# needs-agent the mention with HARNESS_RUN_ACTORS=bob -> 2, one `needs-agent:
|
|
1562
|
+
# unlisted no` line naming HARNESS_RUN_ACTORS; nothing posted
|
|
1543
1563
|
|
|
1544
1564
|
set -u
|
|
1545
1565
|
|
|
@@ -1664,7 +1684,7 @@ usage() {
|
|
|
1664
1684
|
echo " remote-run.sh open <branch> [--repo <root>]" >&2
|
|
1665
1685
|
echo " remote-run.sh deliver <branch> <bundle_dir> [--repo <root>]" >&2
|
|
1666
1686
|
echo " remote-run.sh collect <branch> [--pr <n>] [--repo <root>]" >&2
|
|
1667
|
-
echo " remote-run.sh control [--repo <root>]" >&2
|
|
1687
|
+
echo " remote-run.sh control [--needs-agent] [--repo <root>]" >&2
|
|
1668
1688
|
[ "${verb-}" != save ] || exit "$EXIT_OK"
|
|
1669
1689
|
exit "$EXIT_USAGE"
|
|
1670
1690
|
}
|
|
@@ -1764,6 +1784,7 @@ reviewers_arg=""
|
|
|
1764
1784
|
allow_no_run=0
|
|
1765
1785
|
pr_arg=""
|
|
1766
1786
|
branch_gone=0
|
|
1787
|
+
needs_agent=0
|
|
1767
1788
|
|
|
1768
1789
|
while [ "$#" -gt 0 ]; do
|
|
1769
1790
|
case "$1" in
|
|
@@ -1824,6 +1845,9 @@ while [ "$#" -gt 0 ]; do
|
|
|
1824
1845
|
--branch-gone)
|
|
1825
1846
|
[ "$verb" = stop ] || usage "$1 is a stop option"
|
|
1826
1847
|
branch_gone=1; shift ;;
|
|
1848
|
+
--needs-agent)
|
|
1849
|
+
[ "$verb" = control ] || usage "$1 is a control option"
|
|
1850
|
+
needs_agent=1; shift ;;
|
|
1827
1851
|
-*)
|
|
1828
1852
|
usage "unknown option '$1'" ;;
|
|
1829
1853
|
*)
|
|
@@ -7423,9 +7447,75 @@ control_mention() {
|
|
|
7423
7447
|
exit "$EXIT_GH"
|
|
7424
7448
|
}
|
|
7425
7449
|
|
|
7450
|
+
# control_gates — refusals 1 to 3 of `control`, in its order, for both
|
|
7451
|
+
# `control` and `--needs-agent`. 0 when all pass; otherwise 1 with CG_EXIT,
|
|
7452
|
+
# CG_WHY and CG_WAY set as `control_refuse` takes them, and CG_AUTH the
|
|
7453
|
+
# `authorise_actor` status (0 when an earlier gate refused). Posts nothing.
|
|
7454
|
+
CG_EXIT=0
|
|
7455
|
+
CG_WHY=""
|
|
7456
|
+
CG_WAY=""
|
|
7457
|
+
CG_AUTH=0
|
|
7458
|
+
control_gates() {
|
|
7459
|
+
local forge target
|
|
7460
|
+
CG_EXIT="$EXIT_REFUSED"
|
|
7461
|
+
CG_WHY=""
|
|
7462
|
+
CG_WAY=""
|
|
7463
|
+
CG_AUTH=0
|
|
7464
|
+
if [ -n "${HARNESS_REMOTE_STOP-}" ]; then
|
|
7465
|
+
CG_WHY="the repository variable \`HARNESS_REMOTE_STOP\` is set, which stops every command"
|
|
7466
|
+
CG_WAY="Clear it under **Settings → Secrets and variables → Actions → Variables**, then comment again."
|
|
7467
|
+
return 1
|
|
7468
|
+
fi
|
|
7469
|
+
|
|
7470
|
+
forge=$(hr_forge "$root") || forge=""
|
|
7471
|
+
target=$(hr_execution_target "$root") || target=""
|
|
7472
|
+
if [ "$forge" != github ] || [ "$target" != github-actions ]; then
|
|
7473
|
+
CG_WHY="the default branch's \`harness.config.json\` does not turn run control on: it needs \`forge\` set to \`github\` (it is ${forge:-not set or unreadable}) and \`execution.target\` set to \`github-actions\` (it is ${target:-unreadable})"
|
|
7474
|
+
CG_WAY="Set both keys on the default branch, then comment again."
|
|
7475
|
+
return 1
|
|
7476
|
+
fi
|
|
7477
|
+
|
|
7478
|
+
if ! rerun_actor_listed; then
|
|
7479
|
+
CG_WHY="${RUN_ACTORS_WHY%.}"
|
|
7480
|
+
CG_WAY="Only a person the repository variable \`HARNESS_RUN_ACTORS\` admits may re-run this job; one of them can comment again."
|
|
7481
|
+
return 1
|
|
7482
|
+
fi
|
|
7483
|
+
|
|
7484
|
+
authorise_actor "$CONTROL_ACTOR" "$CONTROL_SENDER_TYPE" || CG_AUTH=$?
|
|
7485
|
+
if [ "$CG_AUTH" -ne 0 ]; then
|
|
7486
|
+
CG_WHY="${AUTH_WHY%.}"
|
|
7487
|
+
CG_WAY="Only a collaborator with write, maintain or admin access whom the repository variable \`HARNESS_RUN_ACTORS\` admits (when unset, the owner alone of a repository a personal account owns, and nobody in an organisation-owned one), or a bot listed in \`HARNESS_TRIGGER_ALLOWED_BOTS\`, commands a run."
|
|
7488
|
+
return 1
|
|
7489
|
+
fi
|
|
7490
|
+
return 0
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
# control_needs_agent_no <reason> — the `--needs-agent` answer that no session
|
|
7494
|
+
# starts: one line, exit 2.
|
|
7495
|
+
control_needs_agent_no() {
|
|
7496
|
+
echo "remote-run.sh: control: needs-agent: no, $1"
|
|
7497
|
+
exit "$EXIT_REFUSED"
|
|
7498
|
+
}
|
|
7499
|
+
|
|
7500
|
+
# control_needs_agent — `--needs-agent` after the comment intake: the exact
|
|
7501
|
+
# form, then `control_gates`, answered as one line and an exit. Never returns.
|
|
7502
|
+
control_needs_agent() {
|
|
7503
|
+
[ -z "$CONTROL_VERB" ] \
|
|
7504
|
+
|| control_needs_agent_no "the comment is the exact form \`$COMMAND_HANDLE $CONTROL_VERB\`"
|
|
7505
|
+
if ! control_gates; then
|
|
7506
|
+
if [ "$CG_AUTH" -eq 4 ]; then
|
|
7507
|
+
echo "remote-run.sh: control: needs-agent: undecided, $AUTH_WHY"
|
|
7508
|
+
exit "$EXIT_GH"
|
|
7509
|
+
fi
|
|
7510
|
+
control_needs_agent_no "$CG_WHY"
|
|
7511
|
+
fi
|
|
7512
|
+
echo "remote-run.sh: control: needs-agent: yes, a mention by @$CONTROL_ACTOR on #$CONTROL_NUMBER"
|
|
7513
|
+
exit "$EXIT_OK"
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7426
7516
|
verb_control() {
|
|
7427
7517
|
local LC_ALL=C
|
|
7428
|
-
local review=0 close=0
|
|
7518
|
+
local review=0 close=0
|
|
7429
7519
|
# Unset first so an inherited export of either name cannot keep it exported.
|
|
7430
7520
|
unset MENTION_OAUTH MENTION_API MENTION_JOB_TOKEN_B64
|
|
7431
7521
|
MENTION_OAUTH="${IN_OAUTH-}"
|
|
@@ -7447,9 +7537,17 @@ verb_control() {
|
|
|
7447
7537
|
fi
|
|
7448
7538
|
hr_have_jq || { echo "remote-run.sh: control needs jq" >&2; exit "$EXIT_USAGE"; }
|
|
7449
7539
|
|
|
7540
|
+
if [ "$needs_agent" -eq 1 ] && [ "$GITHUB_EVENT_NAME" != issue_comment ]; then
|
|
7541
|
+
control_needs_agent_no "a \`$GITHUB_EVENT_NAME\` event is not a comment"
|
|
7542
|
+
fi
|
|
7543
|
+
|
|
7450
7544
|
case "$GITHUB_EVENT_NAME" in
|
|
7451
7545
|
pull_request_review) control_review_intake || return 0 ;;
|
|
7452
|
-
issue_comment)
|
|
7546
|
+
issue_comment)
|
|
7547
|
+
if ! control_comment_intake; then
|
|
7548
|
+
[ "$needs_agent" -eq 0 ] || control_needs_agent_no "the comment is ignored"
|
|
7549
|
+
return 0
|
|
7550
|
+
fi ;;
|
|
7453
7551
|
issues) control_issues_intake || return 0 ;;
|
|
7454
7552
|
pull_request) control_pull_request_intake || return 0 ;;
|
|
7455
7553
|
delete) control_delete_intake || return 0 ;;
|
|
@@ -7463,6 +7561,8 @@ verb_control() {
|
|
|
7463
7561
|
esac
|
|
7464
7562
|
fi
|
|
7465
7563
|
|
|
7564
|
+
[ "$needs_agent" -eq 0 ] || control_needs_agent
|
|
7565
|
+
|
|
7466
7566
|
control_tmp="${RUNNER_TEMP-}"
|
|
7467
7567
|
if [ -z "$control_tmp" ] || [ ! -d "$control_tmp" ]; then
|
|
7468
7568
|
control_tmp=$(mktemp -d) || { echo "remote-run.sh: control: mktemp failed" >&2; exit "$EXIT_USAGE"; }
|
|
@@ -7473,29 +7573,7 @@ verb_control() {
|
|
|
7473
7573
|
# Decided by the event name, never CONTROL_VERB: a comment can name `close`.
|
|
7474
7574
|
[ "$close" -eq 0 ] || control_close
|
|
7475
7575
|
|
|
7476
|
-
|
|
7477
|
-
control_refuse "$EXIT_REFUSED" "the repository variable \`HARNESS_REMOTE_STOP\` is set, which stops every command" \
|
|
7478
|
-
"Clear it under **Settings → Secrets and variables → Actions → Variables**, then comment again."
|
|
7479
|
-
fi
|
|
7480
|
-
|
|
7481
|
-
forge=$(hr_forge "$root") || forge=""
|
|
7482
|
-
target=$(hr_execution_target "$root") || target=""
|
|
7483
|
-
if [ "$forge" != github ] || [ "$target" != github-actions ]; then
|
|
7484
|
-
control_refuse "$EXIT_REFUSED" "the default branch's \`harness.config.json\` does not turn run control on: it needs \`forge\` set to \`github\` (it is ${forge:-not set or unreadable}) and \`execution.target\` set to \`github-actions\` (it is ${target:-unreadable})" \
|
|
7485
|
-
"Set both keys on the default branch, then comment again."
|
|
7486
|
-
fi
|
|
7487
|
-
|
|
7488
|
-
if ! rerun_actor_listed; then
|
|
7489
|
-
control_refuse "$EXIT_REFUSED" "${RUN_ACTORS_WHY%.}" \
|
|
7490
|
-
"Only a person the repository variable \`HARNESS_RUN_ACTORS\` admits may re-run this job; one of them can comment again."
|
|
7491
|
-
fi
|
|
7492
|
-
|
|
7493
|
-
status=0
|
|
7494
|
-
authorise_actor "$CONTROL_ACTOR" "$CONTROL_SENDER_TYPE" || status=$?
|
|
7495
|
-
if [ "$status" -ne 0 ]; then
|
|
7496
|
-
control_refuse "$EXIT_REFUSED" "${AUTH_WHY%.}" \
|
|
7497
|
-
"Only a collaborator with write, maintain or admin access whom the repository variable \`HARNESS_RUN_ACTORS\` admits (when unset, the owner alone of a repository a personal account owns, and nobody in an organisation-owned one), or a bot listed in \`HARNESS_TRIGGER_ALLOWED_BOTS\`, commands a run."
|
|
7498
|
-
fi
|
|
7576
|
+
control_gates || control_refuse "$CG_EXIT" "$CG_WHY" "$CG_WAY"
|
|
7499
7577
|
|
|
7500
7578
|
if [ "$review" -eq 0 ] && [ "$CONTROL_MENTION" != 1 ] \
|
|
7501
7579
|
&& { [ -z "$CONTROL_VERB" ] || ! control_verb_handled "$CONTROL_VERB"; }; then
|