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.4",
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 are `continue-on-error`, so a
141
- # failed install never blocks an exact-form command: `control` then finds no
142
- # agent binary or plugin and answers a mention with its exit-3 reply.
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: Set up Node
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>] (its own exit map: its paragraph)
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
- # `HARNESS_MENTION_PLUGIN_DIR`.
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 forge="" target="" status
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) control_comment_intake || return 0 ;;
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
- if [ -n "${HARNESS_REMOTE_STOP-}" ]; then
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