autonomous-sdlc-harness 0.4.1 → 0.5.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.
@@ -4,6 +4,8 @@
4
4
  # WHO WRITES IT. `autonomous-sdlc-harness init`, only when harness.config.json's
5
5
  # `execution.target` is `github-actions`. Written create-if-absent: from then on
6
6
  # it is yours to tune, and a re-run of `init` keeps your copy.
7
+ # `init --upgrade-workflows` re-renders it at that CLI's version, after a `.bak`,
8
+ # and only when the HARNESS_CLI_VERSION below differs.
7
9
  #
8
10
  # THE INPUT CONTRACT. Composed on the shell side by `remote-run.sh` alone:
9
11
  # action run | pause | warm | stop. Only `run` and `warm` start a
@@ -30,7 +32,9 @@
30
32
  # cli/src/remote/githubActions.ts the file names, HARNESS_RUNNER,
31
33
  # HARNESS_REMOTE_STOP, CLAUDE_CODE_OAUTH_TOKEN,
32
34
  # ANTHROPIC_API_KEY, HARNESS_PUSH_URL,
33
- # HARNESS_GIT_TOKEN, the artifact harness-state
35
+ # HARNESS_GIT_TOKEN, the artifact harness-state,
36
+ # HARNESS_CLI_VERSION
37
+ # cli/src/generators/githubWorkflows.ts --upgrade-workflows
34
38
  # cli/src/generators/projectSettings.ts the marketplace and plugin key
35
39
  # autonomous-sdlc-harness@autonomous-sdlc-harness
36
40
  # cli/src/config/model.ts DEFAULTS.scriptsDir and retrievalApplies
@@ -38,6 +42,22 @@
38
42
  # runtime sits in `retrieval/`
39
43
  # cli/src/retrieval/runtime.ts RETRIEVAL_CACHE_DIRNAME, that `retrieval/`
40
44
  #
45
+ # ACTION PINS.
46
+ # actions/checkout@v5
47
+ # actions/setup-node@v5
48
+ # actions/cache/restore@v5
49
+ # actions/cache@v5
50
+ # actions/upload-artifact@v6
51
+ # Each is the lowest major whose own action.yml declares `runs.using: node24`,
52
+ # per a maintainer's lookups of each action's action.yml and release notes on
53
+ # 2026-09-29T08:21Z (UTC). Each of those majors' release notes requires Actions
54
+ # Runner 2.327.1 or newer; a GitHub-hosted runner already has it, and a
55
+ # self-hosted one must run it (docs/remote-execution.md, section 8). A major
56
+ # tag, not a commit sha: every action here is GitHub's own `actions/`
57
+ # organisation, the owner of the runner and GITHUB_TOKEN this job already
58
+ # trusts, and a major tag takes the action's own patch and security releases
59
+ # where a sha would freeze your copy. Pin shas in your copy if you want them.
60
+ #
41
61
  # WHAT IT READS.
42
62
  # Secrets: CLAUDE_CODE_OAUTH_TOKEN and/or ANTHROPIC_API_KEY (one is required;
43
63
  # billing follows the API key when both are set), HARNESS_GIT_TOKEN (optional:
@@ -72,33 +92,40 @@
72
92
  # the step timeout. The job-level timeout-minutes is the self-hosted limit; a
73
93
  # hosted job is stopped at its own limit whatever that says.
74
94
  #
75
- # WHY THE TIMEOUT IS COMPUTED INTO GITHUB_ENV. Whether a step's timeout-minutes
76
- # accepts an expression over `runner.environment` could not be checked against
77
- # GitHub's workflow-syntax documentation when this file was written (no network
78
- # access), so the first step computes every budget value from
79
- # `runner.environment` into GITHUB_ENV, and the harness step reads a single
80
- # `env` value. Gate 12 records the real behaviour.
95
+ # WHY THE TIMEOUT IS COMPUTED INTO GITHUB_ENV. The first step computes every
96
+ # budget value from `runner.environment` into GITHUB_ENV, so the harness step's
97
+ # timeout-minutes reads a single `env` value rather than an expression over the
98
+ # runner kind. Gate 12 round 2, on 2026-09-29, found every harness-run.yml run
99
+ # accepted and its `Run the harness` step run: the expression-valued
100
+ # timeout-minutes is accepted.
81
101
  #
82
- # THE PLUGIN PIN. `claude plugin marketplace add --help` and
83
- # `claude plugin install --help` (Claude Code 2.1.282) offer no ref or version:
84
- # "Usage: claude plugin marketplace add [options] <source>" (options: --claudeai,
85
- # --scope, --sparse); "Usage: claude plugin install|i [options] <plugin>".
86
- # So the job installs the plugin, then refuses to run, naming both versions,
87
- # when `claude plugin list --json` reports a version other than this file's
88
- # rendered CLI version.
102
+ # THE PLUGIN PIN. The job clones the marketplace repository at the release tag
103
+ # autonomous-sdlc-harness--v<HARNESS_CLI_VERSION> (docs/development.md, section 7)
104
+ # and adds the clone as a directory source, so what it installs is the tag's
105
+ # plugin whatever the default branch carries. Both the add and the install run
106
+ # from $RUNNER_TEMP, outside the checkout, so the committed .claude/settings.json's
107
+ # project-scope entry of the same name is not in view while installing. The
108
+ # clone is the plugin's runtime root and the runner's cache is its install
109
+ # root; `init --plugin-root-entries` grants both and `doctor --remote-job` grades both. The check after install
110
+ # stays as the guard: the job refuses to run, naming both versions and the
111
+ # upgrade route, when `claude plugin list --json` reports another version. What
112
+ # the agent-runner CLI was measured to accept is recorded in
113
+ # docs/remote-execution.md, section 6.
89
114
  #
90
115
  # WHY retention-days IS SET. The `harness-state` bundle is the only remote copy
91
- # of a run's clarifications and carried counts; a run parked or paused longer
92
- # than its retention cannot be answered and loses those counts. 400 is the
116
+ # of a run's clarifications, uncommitted planning drafts and carried counts; a
117
+ # run parked or paused longer than its retention cannot be answered and loses
118
+ # those drafts and counts. 400 is the
93
119
  # largest retention any repository can configure, and `actions/upload-artifact`
94
120
  # caps a larger `retention-days` at the repository's own maximum, so the value
95
121
  # means "as long as this repository allows" — the same bound as leaving it
96
122
  # unset. It is explicit so that lowering it is a visible edit. The real bound is
97
123
  # the repository's Artifact and log retention setting, which
98
- # `autonomous-sdlc-harness doctor --check-github` reads. The cap (rather than a
99
- # rejection) is carried from the action's `@actions/artifact` retention code
100
- # and could not be re-checked against its v4 README when this file was written
101
- # (no network access); Gate 12 records the real behaviour.
124
+ # `autonomous-sdlc-harness doctor --check-github` reads. Gate 12 round 2, on
125
+ # 2026-09-29, observed the cap rather than a rejection on `actions/upload-artifact@v4`:
126
+ # the artifact's `expires_at` fell 90 days after its creation, with the
127
+ # repository's artifact-and-log-retention at {"days":90,"maximum_allowed_days":400}.
128
+ # That was v4; the v6 pinned above has not yet been observed by a gate round.
102
129
 
103
130
  name: harness-run
104
131
  run-name: harness ${{ inputs.action }} ${{ inputs.branch }}
@@ -248,7 +275,7 @@ jobs:
248
275
 
249
276
  - name: Check out the run's branch
250
277
  if: env.HARNESS_STOPPED != '1'
251
- uses: actions/checkout@v4
278
+ uses: actions/checkout@v5
252
279
  with:
253
280
  ref: ${{ inputs.branch }}
254
281
  fetch-depth: 0
@@ -280,9 +307,11 @@ jobs:
280
307
 
281
308
  - name: Set up Node
282
309
  if: env.HARNESS_STOPPED != '1'
283
- uses: actions/setup-node@v4
310
+ uses: actions/setup-node@v5
284
311
  with:
285
312
  node-version: '22'
313
+ # v5 caches by package.json's packageManager; the job installs nothing through npm's cache, and a lockfile-less repository must not fail this step.
314
+ package-manager-cache: false
286
315
 
287
316
  - name: Install the claude CLI when absent
288
317
  if: env.HARNESS_STOPPED != '1'
@@ -317,17 +346,24 @@ jobs:
317
346
  echo "::error::.claude/settings.json names no marketplace source for autonomous-sdlc-harness. Run init locally with --marketplace <owner>/<repo> and commit the file."
318
347
  exit 1
319
348
  fi
320
- claude plugin marketplace add "$source_repo"
321
- claude plugin install autonomous-sdlc-harness@autonomous-sdlc-harness
349
+ upgrade_route="to run another version, re-render this repository's workflows with 'npx autonomous-sdlc-harness@<version> init --upgrade-workflows', then commit the paths its printed 'git add' names and push them to the default branch (docs/remote-execution.md, section 7, Upgrading)"
350
+ tag="autonomous-sdlc-harness--v$HARNESS_CLI_VERSION"
351
+ marketplace_dir="$RUNNER_TEMP/harness-marketplace"
352
+ # --quiet does not cover the detached-HEAD advice a tag checkout prints; only advice.detachedHead gates it.
353
+ if ! git -c advice.detachedHead=false clone --quiet --depth 1 --branch "$tag" "https://github.com/$source_repo.git" "$marketplace_dir"; then
354
+ echo "::error::$source_repo has no release tag $tag, so the plugin this workflow was rendered for cannot be installed. If $HARNESS_CLI_VERSION was released, its release is missing the tag; otherwise, $upgrade_route."
355
+ exit 1
356
+ fi
357
+ (cd "$RUNNER_TEMP" && claude plugin marketplace add ./harness-marketplace && claude plugin install autonomous-sdlc-harness@autonomous-sdlc-harness)
322
358
  installed=$(claude plugin list --json | jq -r '[.[] | select(.id == "autonomous-sdlc-harness@autonomous-sdlc-harness" and .scope == "user")][0].version // empty')
323
359
  if [ "$installed" != "$HARNESS_CLI_VERSION" ]; then
324
- echo "::error::the installed plugin is version '${installed:-none}', but this workflow was rendered for $HARNESS_CLI_VERSION. Publish the matching plugin, or re-run init with the installed version and commit the workflow."
360
+ echo "::error::the installed plugin is version '${installed:-none}', but this workflow was rendered for $HARNESS_CLI_VERSION and cloned $tag; $upgrade_route."
325
361
  exit 1
326
362
  fi
327
363
 
328
364
  - name: Restore the docs-retrieval cache
329
365
  if: env.HARNESS_STOPPED != '1' && steps.config.outputs.retrieval == 'true'
330
- uses: actions/cache/restore@v4
366
+ uses: actions/cache/restore@v5
331
367
  with:
332
368
  path: ${{ env.HARNESS_RETRIEVAL_CACHE }}
333
369
  key: harness-retrieval-${{ runner.os }}-${{ env.HARNESS_CLI_VERSION }}
@@ -377,7 +413,7 @@ jobs:
377
413
 
378
414
  - name: Upload the state bundle
379
415
  if: always() && env.SCRIPTS_DIR != ''
380
- uses: actions/upload-artifact@v4
416
+ uses: actions/upload-artifact@v6
381
417
  with:
382
418
  name: harness-state
383
419
  path: ${{ runner.temp }}/harness-state
@@ -411,7 +447,7 @@ jobs:
411
447
 
412
448
  - name: Check out the dispatched ref
413
449
  if: env.HARNESS_STOPPED != '1'
414
- uses: actions/checkout@v4
450
+ uses: actions/checkout@v5
415
451
 
416
452
  - name: Read the configuration
417
453
  id: config
@@ -430,13 +466,15 @@ jobs:
430
466
 
431
467
  - name: Set up Node
432
468
  if: env.HARNESS_STOPPED != '1' && steps.config.outputs.retrieval == 'true'
433
- uses: actions/setup-node@v4
469
+ uses: actions/setup-node@v5
434
470
  with:
435
471
  node-version: '22'
472
+ # As in the run job: no npm cache is used, and a lockfile-less repository must not fail this step.
473
+ package-manager-cache: false
436
474
 
437
475
  - name: Restore and save the docs-retrieval cache
438
476
  if: env.HARNESS_STOPPED != '1' && steps.config.outputs.retrieval == 'true'
439
- uses: actions/cache@v4
477
+ uses: actions/cache@v5
440
478
  with:
441
479
  path: ${{ env.HARNESS_RETRIEVAL_CACHE }}
442
480
  key: harness-retrieval-${{ runner.os }}-${{ env.HARNESS_CLI_VERSION }}
@@ -0,0 +1,143 @@
1
+ # harness-trigger.yml — starts an autonomous-sdlc-harness task run when an
2
+ # issue is labelled, or when a `repository_dispatch` asks for one.
3
+ #
4
+ # WHO WRITES IT. `autonomous-sdlc-harness init`, only when harness.config.json's
5
+ # `forge` is `github` and its `execution.target` is `github-actions`. Written
6
+ # create-if-absent: from then on it is yours to tune, and a re-run of `init`
7
+ # keeps your copy. `init --upgrade-workflows` does NOT re-render it: it carries
8
+ # no version pin and calls the scripts on the default branch, so it shares
9
+ # their re-run contract — `init --force` replaces it after a `.bak`.
10
+ #
11
+ # WHAT IT DOES. One job runs `remote-run.sh trigger`, which does everything
12
+ # else: it authorises the start, derives the branch, places and commits the task
13
+ # prompt, pushes, dispatches harness-run.yml, comments on the issue and removes
14
+ # the label. Every decision lives in that script; this file carries only the
15
+ # events, the permissions and the label filter. The `run-name` below is not
16
+ # harness-run.yml's `harness <action> <branch>` title, so no run lookup
17
+ # `remote-run.sh` makes by that title can match a trigger run.
18
+ #
19
+ # THE EVENTS.
20
+ # issues, type `labeled` only, never `opened`: labelling an issue at creation
21
+ # raises `labeled` too, so listening to both would start twice
22
+ # (docs/github-integration-research.md, T1). The job runs only for the
23
+ # label the HARNESS_TRIGGER_LABEL variable names (`harness` when unset);
24
+ # every other label yields a skipped job, which runs no runner and bills
25
+ # nothing (docs/remote-execution.md, `### Verified in Gate 12 round 2`).
26
+ # repository_dispatch, type `harness-task`: the entry for any other adapter
27
+ # (a Jira rule, a script). Its body is
28
+ # {"event_type": "harness-task",
29
+ # "client_payload": {"title": …, "body": …, "source": …}}
30
+ # within GitHub's client_payload limits (10 top-level properties, under
31
+ # 64 KB). Sending one needs a token with Contents write, so its holder is
32
+ # the authority.
33
+ #
34
+ # WHO CAN START A RUN. `remote-run.sh`'s `trigger` paragraph owns the check —
35
+ # the labeller, bots and HARNESS_TRIGGER_ALLOWED_BOTS, the permission call —
36
+ # and the order its refusals are made in. Nothing here pre-filters on it.
37
+ #
38
+ # THE PERMISSIONS, declared because the default token may be read-only
39
+ # (docs/github-integration-research.md, T2):
40
+ # contents: write push the new branch carrying the task prompt
41
+ # actions: write dispatch harness-run.yml and look up the run it created
42
+ # issues: write comment on the issue and remove the trigger label
43
+ # The job reads no repository secret: it needs only its own token, so the
44
+ # credential secrets never reach the job that reads untrusted issue text.
45
+ #
46
+ # THE CHECKOUT. The default branch, with the job's own token and never
47
+ # HARNESS_GIT_TOKEN: a branch pushed with GITHUB_TOKEN starts no workflow
48
+ # (docs/remote-execution.md, `### Verified in Gate 12 round 3`), so the prompt
49
+ # commit does not run your CI. A full fetch, because pushing a new branch from
50
+ # a shallow clone has not been measured anywhere.
51
+ #
52
+ # NO CONCURRENCY GROUP. A group keeps at most one pending run and cancels an
53
+ # earlier pending one, which would drop a trigger. Two starts that race for one
54
+ # branch name are caught by the refused push of an existing branch, and the
55
+ # refusal is commented on the issue.
56
+ #
57
+ # TWO RULES EVERY EDIT KEEPS.
58
+ # * Event text and variables reach a shell line only through `env:` and the
59
+ # event file, never through a GitHub expression inside `run:` — an issue
60
+ # title is written by whoever opened the issue (script injection).
61
+ # * Every GitHub expression has a space after its opening braces. `init`
62
+ # renders its workflows with the CLI's double-brace token renderer, which
63
+ # reads a letter straight after the braces as a token; this file carries
64
+ # no token.
65
+ #
66
+ # DECLARED MIRRORS — a rename on either side is an edit to both:
67
+ # cli/src/remote/githubActions.ts WORKFLOW_TRIGGER_FILE, HARNESS_RUNNER,
68
+ # HARNESS_REMOTE_STOP, HARNESS_TRIGGER_LABEL,
69
+ # DEFAULT_TRIGGER_LABEL (`harness`),
70
+ # HARNESS_TRIGGER_ALLOWED_BOTS,
71
+ # TRIGGER_DISPATCH_EVENT_TYPE (`harness-task`)
72
+ # cli/src/config/model.ts DEFAULTS.scriptsDir
73
+ #
74
+ # ACTION PINS.
75
+ # actions/checkout@v5
76
+ # The lowest major whose own action.yml declares `runs.using: node24`, per a
77
+ # maintainer's lookups of its action.yml and release notes on
78
+ # 2026-09-29T08:21Z (UTC). That major's release notes require Actions Runner
79
+ # 2.327.1 or newer; a GitHub-hosted runner already has it, and a self-hosted
80
+ # one must run it (docs/remote-execution.md, section 8). A major tag, not a
81
+ # commit sha: it is GitHub's own `actions/` organisation, the owner of the
82
+ # runner and GITHUB_TOKEN this job already trusts, and a major tag takes the
83
+ # action's own patch and security releases where a sha would freeze your copy.
84
+ # Pin a sha in your copy if you want one.
85
+
86
+ name: harness-trigger
87
+ run-name: harness trigger ${{ github.event.issue.number || github.event.action }}
88
+
89
+ on:
90
+ issues:
91
+ types: [labeled]
92
+ repository_dispatch:
93
+ types: [harness-task]
94
+
95
+ permissions:
96
+ contents: write
97
+ actions: write
98
+ issues: write
99
+
100
+ defaults:
101
+ run:
102
+ shell: bash
103
+
104
+ jobs:
105
+ trigger:
106
+ if: github.event_name == 'repository_dispatch' || github.event.label.name == (vars.HARNESS_TRIGGER_LABEL || 'harness')
107
+ runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
108
+ env:
109
+ GH_TOKEN: ${{ github.token }}
110
+ HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
111
+ HARNESS_REMOTE_SLUG: ${{ github.repository }}
112
+ HARNESS_TRIGGER_LABEL: ${{ vars.HARNESS_TRIGGER_LABEL }}
113
+ HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
114
+ steps:
115
+ - name: Check out the default branch
116
+ uses: actions/checkout@v5
117
+ with:
118
+ token: ${{ github.token }}
119
+ fetch-depth: 0
120
+
121
+ - name: Check for jq and gh
122
+ run: |
123
+ missing=""
124
+ for tool in jq gh; do
125
+ command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
126
+ done
127
+ if [ -n "$missing" ]; then
128
+ echo "::error::this runner lacks:$missing. Install them on the self-hosted runner, or use a GitHub-hosted one."
129
+ exit 1
130
+ fi
131
+
132
+ - name: Read the configuration
133
+ run: |
134
+ scripts_dir=$(jq -r '.scriptsDir // "scripts"' harness.config.json)
135
+ echo "SCRIPTS_DIR=${scripts_dir%/}" >> "$GITHUB_ENV"
136
+
137
+ - name: Set the git identity
138
+ run: |
139
+ git config user.name "github-actions[bot]"
140
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
141
+
142
+ - name: Start the run
143
+ run: bash "$SCRIPTS_DIR/remote-run.sh" trigger
@@ -68,5 +68,10 @@
68
68
  # rule a wired repository carries an untracked file from its first `config set` onward. The leading
69
69
  # slash anchors it at the repository root, the only place the configuration is written.
70
70
  /{{configBackupFile}}
71
+ # The copies `init --upgrade-workflows` and `init --force` take of the two remote-execution workflows
72
+ # and of the permission profile. The upgrade's report names each workflow copy in a diff command, and
73
+ # the profile copy carries this machine's absolute paths just as the profile does. Every other .bak a
74
+ # forced run writes is left visible on purpose.
75
+ {{harnessBackups}}
71
76
  {{qaBrowserArtifacts}}
72
77
  {{docsRetrievalIndex}}