autonomous-sdlc-harness 0.4.2 → 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.
- package/dist/commands/init.js +113 -16
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +28 -6
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +64 -5
- package/dist/config/model.js.map +1 -1
- package/dist/core/pluginIdentity.js +2 -0
- package/dist/core/pluginIdentity.js.map +1 -1
- package/dist/core/writer.js +10 -5
- package/dist/core/writer.js.map +1 -1
- package/dist/core/yamlScalar.js +14 -0
- package/dist/core/yamlScalar.js.map +1 -0
- package/dist/doctor/checks.js +454 -23
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +125 -20
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/generators/repoRoot.js +17 -8
- package/dist/generators/repoRoot.js.map +1 -1
- package/dist/remote/githubActions.js +110 -7
- package/dist/remote/githubActions.js.map +1 -1
- package/dist/retrieval/pythonBackend.js +114 -0
- package/dist/retrieval/pythonBackend.js.map +1 -0
- package/dist/retrieval/setup.js +8 -0
- package/dist/retrieval/setup.js.map +1 -1
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/github/workflows/harness-control.yml +184 -0
- package/templates/github/workflows/harness-resume.yml +9 -0
- package/templates/github/workflows/harness-run.yml +127 -12
- package/templates/github/workflows/harness-trigger.yml +144 -0
- package/templates/repo/gitignore +5 -0
- package/templates/scripts/README.md +1 -1
- package/templates/scripts/autonomous-watcher.sh +121 -249
- package/templates/scripts/create-worktree.sh +52 -10
- package/templates/scripts/docs-search-server.sh +88 -17
- package/templates/scripts/lib/harness-run-lib.sh +614 -14
- package/templates/scripts/remote-run.sh +3684 -144
- package/templates/scripts/scratch-run.sh +54 -73
- package/templates/state-dir/README-root.md +1 -1
- package/templates/state-dir/scratch/README.md +4 -2
- package/templates/state-dir/user_reviews/README.md +2 -2
package/dist/retrieval/setup.js
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* runtime is a warning rather than an `npm` call, so a test that forgets to plant one stays offline.
|
|
15
15
|
* - **A failed step is a warning.** The wiring the plan wrote stays valid; `doctor` fails until a
|
|
16
16
|
* re-run completes the step.
|
|
17
|
+
* - **The Python backend gets no install and no download here, and the TypeScript setup still runs.**
|
|
18
|
+
* Its package, weights and Postgres are provisioned by hand; the TypeScript runtime and models are
|
|
19
|
+
* kept so switching `docs.retrievalBackend` back costs nothing. With `python` selected, one note
|
|
20
|
+
* says both.
|
|
17
21
|
*
|
|
18
22
|
* Both steps put machine state outside the repository, not through the write engine
|
|
19
23
|
* (`cli/src/core/writer.ts` header). `setUpRuntime` writes the runtime itself, through `npm`;
|
|
@@ -25,6 +29,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
25
29
|
import { join } from 'node:path';
|
|
26
30
|
import { ownManifestString } from '../core/paths.js';
|
|
27
31
|
import { modelFilesPresent, RETRIEVAL_STUB_ENV, stubModelsSelected } from './models.js';
|
|
32
|
+
import { PYTHON_RETRIEVAL_COMMAND } from './pythonBackend.js';
|
|
28
33
|
import { retrievalCliEntry, retrievalModelCacheDir, retrievalPeers, retrievalRuntimeDir, retrievalRuntimeState, RUNTIME_CLI_RELATIVE, } from './runtime.js';
|
|
29
34
|
/** Both child processes may take minutes on a cold cache. */
|
|
30
35
|
const STEP_TIMEOUT_MS = 900000;
|
|
@@ -116,6 +121,9 @@ export function setUpRetrieval(options) {
|
|
|
116
121
|
const warnings = [];
|
|
117
122
|
setUpRuntime(options.dryRun, notes, warnings);
|
|
118
123
|
setUpModels(options.dryRun, notes, warnings);
|
|
124
|
+
if (options.backend === 'python') {
|
|
125
|
+
notes.push(`docs.retrievalBackend is python: init installed and downloaded nothing for it — the \`${PYTHON_RETRIEVAL_COMMAND}\` package, its weights and its Postgres are set up by hand (docs/retrieval.md → Turning on the Python backend); the TypeScript runtime and models above are kept so switching back costs nothing`);
|
|
126
|
+
}
|
|
119
127
|
return { notes, warnings };
|
|
120
128
|
}
|
|
121
129
|
//# sourceMappingURL=setup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/retrieval/setup.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/retrieval/setup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autonomous-sdlc-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|
package/templates/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The generator templates `init` copies into an adopted repository. Nothing here i
|
|
|
9
9
|
| `githooks/` | the configured `githooksDir` |
|
|
10
10
|
| `scripts/` | the configured `scriptsDir` |
|
|
11
11
|
| `state-dir/` | the configured `stateDir` |
|
|
12
|
-
| `github/` | `.github/` in the adopter's repo — `workflows/harness-run.yml` and `workflows/harness-resume.yml`, written only when `execution.target` is `github-actions` |
|
|
12
|
+
| `github/` | `.github/` in the adopter's repo — `workflows/harness-run.yml` and `workflows/harness-resume.yml`, written only when `execution.target` is `github-actions`, and `workflows/harness-trigger.yml` and `workflows/harness-control.yml`, written only when `forge` is also `github` |
|
|
13
13
|
|
|
14
14
|
**Naming rule for the whole tree: a template whose adopter-side name begins with a dot is stored here without the dot.** `repo/gitignore` is written as `.gitignore`, `repo/mcp.json` as `.mcp.json`, and the `claude/` subtree lands at `.claude/` and the `github/` subtree at `.github/`; `init` adds the dot when it writes. The rule is load-bearing twice over — a real `.gitignore` inside this repository would be honoured by git and could silently exclude files that are meant to ship, and npm rewrites a packaged `.gitignore` to `.npmignore` on publish, so a dot-named template does not survive the wire.
|
|
15
15
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# harness-control.yml — acts on an autonomous-sdlc-harness command comment
|
|
2
|
+
# (`@sdlc-harness answer|pause|resume|stop|clear`) on an issue or pull request,
|
|
3
|
+
# and turns a pull-request review requesting changes into the next user-review
|
|
4
|
+
# round.
|
|
5
|
+
#
|
|
6
|
+
# WHO WRITES IT. `autonomous-sdlc-harness init`, only when harness.config.json's
|
|
7
|
+
# `forge` is `github` and its `execution.target` is `github-actions`. Written
|
|
8
|
+
# create-if-absent: from then on it is yours to tune, and a re-run of `init`
|
|
9
|
+
# keeps your copy. `init --upgrade-workflows` does NOT re-render it: it carries
|
|
10
|
+
# no version pin and calls the scripts on the default branch, so it shares
|
|
11
|
+
# their re-run contract — `init --force` replaces it after a `.bak`. It is a
|
|
12
|
+
# file of its own, apart from harness-trigger.yml, so you can disable comment
|
|
13
|
+
# control alone: disabling a workflow disables every event in it.
|
|
14
|
+
#
|
|
15
|
+
# WHAT IT DOES. One job runs `remote-run.sh control`, which does everything
|
|
16
|
+
# else: it authorises the actor, finds the run's branch, carries out the
|
|
17
|
+
# command or places the review round, and replies. Every decision lives in that
|
|
18
|
+
# script; this file carries only the events, the permissions, a prefilter and
|
|
19
|
+
# the concurrency group.
|
|
20
|
+
# The `run-name` below is not harness-run.yml's `harness <action> <branch>`
|
|
21
|
+
# title, so no run lookup `remote-run.sh` makes by that title can match it.
|
|
22
|
+
#
|
|
23
|
+
# THE EVENTS.
|
|
24
|
+
# issue_comment, type `created` only: a command is a new comment. GitHub
|
|
25
|
+
# raises it for a comment on an issue and on a pull request alike.
|
|
26
|
+
# pull_request_review, type `submitted` only: the round is built from the
|
|
27
|
+
# review event, which carries the review body and identifies its inline
|
|
28
|
+
# comments (docs/github-integration-research.md, C1).
|
|
29
|
+
# Never pull_request_review_comment: one review raises one such event per
|
|
30
|
+
# inline comment, so listening to it would start one job per comment for a
|
|
31
|
+
# round the review event already covers (C1).
|
|
32
|
+
#
|
|
33
|
+
# THE PREFILTER. The job's `if:` runs it only for a comment that contains
|
|
34
|
+
# `@sdlc-harness` and not `<!-- sdlc-harness` (the hidden line every harness
|
|
35
|
+
# reply carries, so a reply quoting a command is never one), or for a review
|
|
36
|
+
# whose state is `changes_requested` on a pull request from this repository.
|
|
37
|
+
# Every other comment or review yields a skipped job, which runs no runner and
|
|
38
|
+
# bills nothing (docs/remote-execution.md, `### Verified in Gate 12 round 2`).
|
|
39
|
+
# It is a saving, not the authority: `control` re-checks every rule, so an
|
|
40
|
+
# edited copy of this filter cannot bypass one. That `contains()` compares
|
|
41
|
+
# case-insensitively is GitHub's documented behaviour, not verified here; a
|
|
42
|
+
# mixed-case handle the filter missed would only go unanswered, never obeyed.
|
|
43
|
+
#
|
|
44
|
+
# THE PERMISSIONS, declared because the default token may be read-only
|
|
45
|
+
# (docs/github-integration-research.md, T2):
|
|
46
|
+
# contents: write push a review round
|
|
47
|
+
# actions: write dispatch, pause, stop and cancel runs, and read
|
|
48
|
+
# runs and their artifacts
|
|
49
|
+
# issues: write reply on the issue or pull request, and label it
|
|
50
|
+
# pull-requests: write read the pull request, its reviews and comments,
|
|
51
|
+
# and label it
|
|
52
|
+
# The job reads no repository secret: it needs only its own token, so the
|
|
53
|
+
# credential secrets never reach the job that reads untrusted comment text.
|
|
54
|
+
# Every reply is posted as github-actions[bot] and starts no workflow (S3).
|
|
55
|
+
#
|
|
56
|
+
# FORK PULL REQUESTS. Never `pull_request_target`, which would run with this
|
|
57
|
+
# repository's write token on a fork's event (C2).
|
|
58
|
+
# A `pull_request_review` job runs this file as the pull request's merge
|
|
59
|
+
# commit carries it (docs/github-integration-research.md, C2), so the `if:`
|
|
60
|
+
# that skips a fork's review is the fork's own copy: a fork that edits it is
|
|
61
|
+
# not skipped, and can name any `runs-on:` label, a self-hosted runner's
|
|
62
|
+
# included. Such a job gets a read-only token and no secret (C2), and fork
|
|
63
|
+
# approval policies apply to it.
|
|
64
|
+
# A comment on a fork's pull request runs with a write token, and `control`
|
|
65
|
+
# refuses it.
|
|
66
|
+
# The checkout is always the default branch, never the pull request's merge
|
|
67
|
+
# commit a `pull_request_review` job checks out by default, so the script that
|
|
68
|
+
# decides is always the default branch's; what a pull request's head can change
|
|
69
|
+
# is this file's own definition for its own review events.
|
|
70
|
+
#
|
|
71
|
+
# THE CONCURRENCY GROUP SERIALIZES REVIEW JOBS PER BRANCH, NEVER COMMENT JOBS.
|
|
72
|
+
# * A comment job's group is its own run id, so it never waits and is never
|
|
73
|
+
# replaced: a group keeps at most one pending job and cancels an earlier
|
|
74
|
+
# pending one, which would drop a command.
|
|
75
|
+
# * Review jobs on one head branch share `harness-review-<branch>` and run one
|
|
76
|
+
# at a time, so two reviews submitted together give one round, not two
|
|
77
|
+
# pushes racing for the same round number.
|
|
78
|
+
# * A pending review job replaced by a newer one loses nothing: every job in
|
|
79
|
+
# the group collects cumulatively, every review requesting changes, every
|
|
80
|
+
# other review carrying a summary and every inline comment no earlier round
|
|
81
|
+
# recorded, and the round's comment names
|
|
82
|
+
# every reviewer it took.
|
|
83
|
+
# * The name `harness-review-<branch>` is reserved for every job that places a
|
|
84
|
+
# review round on that branch; any other workflow's job that does so joins
|
|
85
|
+
# the group under exactly that name, and both files' headers declare it.
|
|
86
|
+
# harness-run.yml's `collect` job is the one such job today (DECLARED
|
|
87
|
+
# MIRRORS below).
|
|
88
|
+
#
|
|
89
|
+
|
|
90
|
+
# TWO RULES EVERY EDIT KEEPS.
|
|
91
|
+
# * Event text and variables reach a shell line only through `env:` and the
|
|
92
|
+
# event file, never through a GitHub expression inside `run:` — a comment
|
|
93
|
+
# body is written by whoever posted it (script injection).
|
|
94
|
+
# * Every GitHub expression has a space after its opening braces. `init`
|
|
95
|
+
# renders its workflows with the CLI's double-brace token renderer, which
|
|
96
|
+
# reads a letter straight after the braces as a token; this file carries
|
|
97
|
+
# no token.
|
|
98
|
+
#
|
|
99
|
+
# DECLARED MIRRORS — a rename on either side is an edit to both:
|
|
100
|
+
# cli/src/remote/githubActions.ts WORKFLOW_CONTROL_FILE, COMMAND_HANDLE
|
|
101
|
+
# (`@sdlc-harness`), COMMENT_MARKER
|
|
102
|
+
# (`<!-- sdlc-harness`), REVIEW_ROUND_STATE
|
|
103
|
+
# (`changes_requested`), HARNESS_RUNNER,
|
|
104
|
+
# HARNESS_REMOTE_STOP,
|
|
105
|
+
# HARNESS_TRIGGER_ALLOWED_BOTS
|
|
106
|
+
# cli/src/config/model.ts DEFAULTS.scriptsDir
|
|
107
|
+
# harness-run.yml the concurrency group
|
|
108
|
+
# `harness-review-<branch>`, held by its
|
|
109
|
+
# `collect` job
|
|
110
|
+
#
|
|
111
|
+
# ACTION PINS.
|
|
112
|
+
# actions/checkout@v5
|
|
113
|
+
# The lowest major whose own action.yml declares `runs.using: node24`, per a
|
|
114
|
+
# maintainer's lookups of its action.yml and release notes on
|
|
115
|
+
# 2026-09-29T08:21Z (UTC). That major's release notes require Actions Runner
|
|
116
|
+
# 2.327.1 or newer; a GitHub-hosted runner already has it, and a self-hosted
|
|
117
|
+
# one must run it (docs/remote-execution.md, section 8). A major tag, not a
|
|
118
|
+
# commit sha: it is GitHub's own `actions/` organisation, the owner of the
|
|
119
|
+
# runner and GITHUB_TOKEN this job already trusts, and a major tag takes the
|
|
120
|
+
# action's own patch and security releases where a sha would freeze your copy.
|
|
121
|
+
# Pin a sha in your copy if you want one.
|
|
122
|
+
|
|
123
|
+
name: harness-control
|
|
124
|
+
run-name: harness control ${{ github.event.issue.number || github.event.pull_request.number }}
|
|
125
|
+
|
|
126
|
+
on:
|
|
127
|
+
issue_comment:
|
|
128
|
+
types: [created]
|
|
129
|
+
pull_request_review:
|
|
130
|
+
types: [submitted]
|
|
131
|
+
|
|
132
|
+
permissions:
|
|
133
|
+
contents: write
|
|
134
|
+
actions: write
|
|
135
|
+
issues: write
|
|
136
|
+
pull-requests: write
|
|
137
|
+
|
|
138
|
+
defaults:
|
|
139
|
+
run:
|
|
140
|
+
shell: bash
|
|
141
|
+
|
|
142
|
+
jobs:
|
|
143
|
+
control:
|
|
144
|
+
if: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@sdlc-harness') && !contains(github.event.comment.body, '<!-- sdlc-harness')) || (github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && github.event.pull_request.head.repo.full_name == github.repository)
|
|
145
|
+
runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
|
|
146
|
+
concurrency:
|
|
147
|
+
group: ${{ github.event_name == 'pull_request_review' && format('harness-review-{0}', github.event.pull_request.head.ref) || format('harness-control-{0}', github.run_id) }}
|
|
148
|
+
cancel-in-progress: false
|
|
149
|
+
env:
|
|
150
|
+
GH_TOKEN: ${{ github.token }}
|
|
151
|
+
HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
|
|
152
|
+
HARNESS_REMOTE_SLUG: ${{ github.repository }}
|
|
153
|
+
HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
|
|
154
|
+
steps:
|
|
155
|
+
- name: Check out the default branch
|
|
156
|
+
uses: actions/checkout@v5
|
|
157
|
+
with:
|
|
158
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
159
|
+
token: ${{ github.token }}
|
|
160
|
+
fetch-depth: 0
|
|
161
|
+
|
|
162
|
+
- name: Check for jq and gh
|
|
163
|
+
run: |
|
|
164
|
+
missing=""
|
|
165
|
+
for tool in jq gh; do
|
|
166
|
+
command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
|
|
167
|
+
done
|
|
168
|
+
if [ -n "$missing" ]; then
|
|
169
|
+
echo "::error::this runner lacks:$missing. Install them on the self-hosted runner, or use a GitHub-hosted one."
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
- name: Read the configuration
|
|
174
|
+
run: |
|
|
175
|
+
scripts_dir=$(jq -r '.scriptsDir // "scripts"' harness.config.json)
|
|
176
|
+
echo "SCRIPTS_DIR=${scripts_dir%/}" >> "$GITHUB_ENV"
|
|
177
|
+
|
|
178
|
+
- name: Set the git identity
|
|
179
|
+
run: |
|
|
180
|
+
git config user.name "github-actions[bot]"
|
|
181
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
182
|
+
|
|
183
|
+
- name: Act on the comment or review
|
|
184
|
+
run: bash "$SCRIPTS_DIR/remote-run.sh" control
|
|
@@ -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 together with the run workflow, after
|
|
8
|
+
# a `.bak`, carrying its `- cron:` lines.
|
|
7
9
|
#
|
|
8
10
|
# WHY A POLLER. A `schedule` trigger is a recurring cron read from the default
|
|
9
11
|
# branch, not a one-shot timer, so resuming at a recorded reset time means
|
|
@@ -46,6 +48,11 @@
|
|
|
46
48
|
# The job's `actions: write` covers listing and downloading its own
|
|
47
49
|
# workflow's artifacts.
|
|
48
50
|
#
|
|
51
|
+
# THE PERMISSIONS. `issues: write` and `pull-requests: write` serve `poll`'s
|
|
52
|
+
# reports, which comment on the waiting run's issue or pull request; a
|
|
53
|
+
# workflow-level `permissions:` block sets every permission it does not list to
|
|
54
|
+
# `none`.
|
|
55
|
+
#
|
|
49
56
|
# WHAT IT WRITES. The `harness-poll-state` artifact: the failed-dispatch count
|
|
50
57
|
# `poll` carries to the next tick, kept 7 days.
|
|
51
58
|
#
|
|
@@ -83,6 +90,8 @@ on:
|
|
|
83
90
|
permissions:
|
|
84
91
|
contents: read
|
|
85
92
|
actions: write
|
|
93
|
+
issues: write
|
|
94
|
+
pull-requests: write
|
|
86
95
|
|
|
87
96
|
defaults:
|
|
88
97
|
run:
|
|
@@ -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,13 +32,42 @@
|
|
|
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
|
|
37
41
|
# cli/src/machine/paths.ts machineCacheDir, under which the retrieval
|
|
38
42
|
# runtime sits in `retrieval/`
|
|
39
43
|
# cli/src/retrieval/runtime.ts RETRIEVAL_CACHE_DIRNAME, that `retrieval/`
|
|
44
|
+
# remote-run.sh (the scriptsDir copy) RUN_JOB_NAME (`run`): the `run` job has no
|
|
45
|
+
# `name:`, so GitHub names it by its key,
|
|
46
|
+
# and the settledness test reads that
|
|
47
|
+
# job's status by that name
|
|
48
|
+
# harness-control.yml the concurrency group
|
|
49
|
+
# `harness-review-<branch>`, shared by its
|
|
50
|
+
# review jobs and the `collect` job here
|
|
51
|
+
#
|
|
52
|
+
# THE COLLECT JOB. When a run ends, `remote-run.sh collect` starts the next
|
|
53
|
+
# user-review round from every review requesting changes that `control`
|
|
54
|
+
# answered "collected" while the run was in flight, so a reviewer waits at most
|
|
55
|
+
# for that run and never resubmits. It is a job of its own, not a step of
|
|
56
|
+
# `run`, for two reasons: a step cannot hold a concurrency group, and this one
|
|
57
|
+
# must share `harness-review-<branch>` with harness-control.yml's review jobs so
|
|
58
|
+
# the two never place a round at once; and the settledness test reads "the
|
|
59
|
+
# `run` job is completed", which no step inside that job could see. Under
|
|
60
|
+
# `!cancelled()` a failed `run` job still reaches it, and the run's state bundle
|
|
61
|
+
# decides whether the run ended or only paused or parked; a cancelled or
|
|
62
|
+
# stopped run skips it. The group name mirrors harness-control.yml's, and
|
|
63
|
+
# RUN_JOB_NAME in remote-run.sh mirrors the `run` job's key (DECLARED MIRRORS
|
|
64
|
+
# above). It reads no secret: its own token pushes the round, dispatches it and
|
|
65
|
+
# comments. It checks out the default branch, as harness-control.yml's review
|
|
66
|
+
# jobs do, never the run's branch: `collect` fetches that branch itself and
|
|
67
|
+
# `review` cuts its own working copy of it, which create-worktree.sh refuses
|
|
68
|
+
# for a branch already checked out. Its step is `continue-on-error` for the
|
|
69
|
+
# report step's reason below: scripts older than this file carry no `collect`
|
|
70
|
+
# verb.
|
|
40
71
|
#
|
|
41
72
|
# ACTION PINS.
|
|
42
73
|
# actions/checkout@v5
|
|
@@ -58,7 +89,9 @@
|
|
|
58
89
|
# Secrets: CLAUDE_CODE_OAUTH_TOKEN and/or ANTHROPIC_API_KEY (one is required;
|
|
59
90
|
# billing follows the API key when both are set), HARNESS_GIT_TOKEN (optional:
|
|
60
91
|
# a personal or App token, so the job's pushes trigger your own CI, which
|
|
61
|
-
# GITHUB_TOKEN pushes never do
|
|
92
|
+
# GITHUB_TOKEN pushes never do; it also opens the run's draft pull request,
|
|
93
|
+
# so your CI runs on it without an approval click), HARNESS_PUSH_URL
|
|
94
|
+
# (optional notifications).
|
|
62
95
|
# Variables: HARNESS_RUNNER, HARNESS_REMOTE_STOP (any value stops every job
|
|
63
96
|
# before it launches anything), HARNESS_STEP_TIMEOUT_MINUTES,
|
|
64
97
|
# HARNESS_SELF_PAUSE_AFTER_MINUTES, HARNESS_MAX_CHAIN, and the watcher
|
|
@@ -67,6 +100,20 @@
|
|
|
67
100
|
# harness.config.json, at run time: `scriptsDir` and whether docs retrieval
|
|
68
101
|
# applies. No configured value is frozen into this file.
|
|
69
102
|
#
|
|
103
|
+
# THE PERMISSIONS. A workflow-level `permissions:` block sets every permission
|
|
104
|
+
# it does not list to `none`, so each one the job uses is listed:
|
|
105
|
+
# contents to push the branch
|
|
106
|
+
# actions to dispatch, poll and enable the poller
|
|
107
|
+
# issues to comment and set labels on the issue and the pull request,
|
|
108
|
+
# which both use the issues API
|
|
109
|
+
# pull-requests to open the draft pull request and label it
|
|
110
|
+
#
|
|
111
|
+
# WHY THE REPORT STEP MAY FAIL. `Open the pull request and report` runs
|
|
112
|
+
# `remote-run.sh deliver` after the push, so the pull request's head carries the
|
|
113
|
+
# run's last commit. It is `continue-on-error`: a repository that re-rendered
|
|
114
|
+
# this file without `init --force` still runs scripts with no `deliver` verb,
|
|
115
|
+
# and a finished run must not be marked failed for its report.
|
|
116
|
+
#
|
|
70
117
|
# THE SELF-PAUSE POINT, MEASURED. On a hosted runner the job drops its own PAUSE
|
|
71
118
|
# REMOTE_SELF_PAUSE_AFTER_SECS after it started, the run yields at its next
|
|
72
119
|
# dispatch boundary, and `remote-run.sh continue` chains a new job.
|
|
@@ -95,13 +142,18 @@
|
|
|
95
142
|
# accepted and its `Run the harness` step run: the expression-valued
|
|
96
143
|
# timeout-minutes is accepted.
|
|
97
144
|
#
|
|
98
|
-
# THE PLUGIN PIN.
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
145
|
+
# THE PLUGIN PIN. The job clones the marketplace repository at the release tag
|
|
146
|
+
# autonomous-sdlc-harness--v<HARNESS_CLI_VERSION> (docs/development.md, section 7)
|
|
147
|
+
# and adds the clone as a directory source, so what it installs is the tag's
|
|
148
|
+
# plugin whatever the default branch carries. Both the add and the install run
|
|
149
|
+
# from $RUNNER_TEMP, outside the checkout, so the committed .claude/settings.json's
|
|
150
|
+
# project-scope entry of the same name is not in view while installing. The
|
|
151
|
+
# clone is the plugin's runtime root and the runner's cache is its install
|
|
152
|
+
# root; `init --plugin-root-entries` grants both and `doctor --remote-job` grades both. The check after install
|
|
153
|
+
# stays as the guard: the job refuses to run, naming both versions and the
|
|
154
|
+
# upgrade route, when `claude plugin list --json` reports another version. What
|
|
155
|
+
# the agent-runner CLI was measured to accept is recorded in
|
|
156
|
+
# docs/remote-execution.md, section 6.
|
|
105
157
|
#
|
|
106
158
|
# WHY retention-days IS SET. The `harness-state` bundle is the only remote copy
|
|
107
159
|
# of a run's clarifications, uncommitted planning drafts and carried counts; a
|
|
@@ -174,6 +226,8 @@ on:
|
|
|
174
226
|
permissions:
|
|
175
227
|
contents: write
|
|
176
228
|
actions: write
|
|
229
|
+
issues: write
|
|
230
|
+
pull-requests: write
|
|
177
231
|
|
|
178
232
|
defaults:
|
|
179
233
|
run:
|
|
@@ -337,11 +391,18 @@ jobs:
|
|
|
337
391
|
echo "::error::.claude/settings.json names no marketplace source for autonomous-sdlc-harness. Run init locally with --marketplace <owner>/<repo> and commit the file."
|
|
338
392
|
exit 1
|
|
339
393
|
fi
|
|
340
|
-
|
|
341
|
-
|
|
394
|
+
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)"
|
|
395
|
+
tag="autonomous-sdlc-harness--v$HARNESS_CLI_VERSION"
|
|
396
|
+
marketplace_dir="$RUNNER_TEMP/harness-marketplace"
|
|
397
|
+
# --quiet does not cover the detached-HEAD advice a tag checkout prints; only advice.detachedHead gates it.
|
|
398
|
+
if ! git -c advice.detachedHead=false clone --quiet --depth 1 --branch "$tag" "https://github.com/$source_repo.git" "$marketplace_dir"; then
|
|
399
|
+
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."
|
|
400
|
+
exit 1
|
|
401
|
+
fi
|
|
402
|
+
(cd "$RUNNER_TEMP" && claude plugin marketplace add ./harness-marketplace && claude plugin install autonomous-sdlc-harness@autonomous-sdlc-harness)
|
|
342
403
|
installed=$(claude plugin list --json | jq -r '[.[] | select(.id == "autonomous-sdlc-harness@autonomous-sdlc-harness" and .scope == "user")][0].version // empty')
|
|
343
404
|
if [ "$installed" != "$HARNESS_CLI_VERSION" ]; then
|
|
344
|
-
echo "::error::the installed plugin is version '${installed:-none}', but this workflow was rendered for $HARNESS_CLI_VERSION
|
|
405
|
+
echo "::error::the installed plugin is version '${installed:-none}', but this workflow was rendered for $HARNESS_CLI_VERSION and cloned $tag; $upgrade_route."
|
|
345
406
|
exit 1
|
|
346
407
|
fi
|
|
347
408
|
|
|
@@ -404,6 +465,13 @@ jobs:
|
|
|
404
465
|
overwrite: true
|
|
405
466
|
retention-days: 400
|
|
406
467
|
|
|
468
|
+
- name: Open the pull request and report
|
|
469
|
+
if: ${{ !cancelled() && env.SCRIPTS_DIR != '' }}
|
|
470
|
+
continue-on-error: true
|
|
471
|
+
env:
|
|
472
|
+
HARNESS_PR_TOKEN: ${{ secrets.HARNESS_GIT_TOKEN }}
|
|
473
|
+
run: bash "$SCRIPTS_DIR/remote-run.sh" deliver "$HARNESS_INPUT_BRANCH" "$RUNNER_TEMP/harness-state"
|
|
474
|
+
|
|
407
475
|
- name: Continue, wait or stop
|
|
408
476
|
if: ${{ !cancelled() && env.SCRIPTS_DIR != '' }}
|
|
409
477
|
run: bash "$SCRIPTS_DIR/remote-run.sh" continue "$HARNESS_INPUT_BRANCH" "$RUNNER_TEMP/harness-state"
|
|
@@ -414,6 +482,53 @@ jobs:
|
|
|
414
482
|
export HARNESS_REPO_SLUG="$HARNESS_REMOTE_SLUG"
|
|
415
483
|
bash "$SCRIPTS_DIR/autonomous-notify.sh" failed "$HARNESS_INPUT_BRANCH" "" \
|
|
416
484
|
"job cancelled: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" || true
|
|
485
|
+
bash "$SCRIPTS_DIR/remote-run.sh" report failed "$HARNESS_INPUT_BRANCH" --note "the job was cancelled: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" || true
|
|
486
|
+
|
|
487
|
+
collect:
|
|
488
|
+
needs: run
|
|
489
|
+
if: ${{ inputs.action == 'run' && !cancelled() }}
|
|
490
|
+
runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
|
|
491
|
+
concurrency:
|
|
492
|
+
group: harness-review-${{ inputs.branch }}
|
|
493
|
+
cancel-in-progress: false
|
|
494
|
+
env:
|
|
495
|
+
GH_TOKEN: ${{ github.token }}
|
|
496
|
+
HARNESS_REMOTE_SLUG: ${{ github.repository }}
|
|
497
|
+
HARNESS_INPUT_BRANCH: ${{ inputs.branch }}
|
|
498
|
+
HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
|
|
499
|
+
HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
|
|
500
|
+
steps:
|
|
501
|
+
- name: Check out the default branch for collecting
|
|
502
|
+
uses: actions/checkout@v5
|
|
503
|
+
with:
|
|
504
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
505
|
+
token: ${{ github.token }}
|
|
506
|
+
fetch-depth: 0
|
|
507
|
+
|
|
508
|
+
- name: Check for jq and gh before collecting
|
|
509
|
+
run: |
|
|
510
|
+
missing=""
|
|
511
|
+
for tool in jq gh; do
|
|
512
|
+
command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
|
|
513
|
+
done
|
|
514
|
+
if [ -n "$missing" ]; then
|
|
515
|
+
echo "::error::this runner lacks:$missing. Install them on the self-hosted runner, or use a GitHub-hosted one."
|
|
516
|
+
exit 1
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
- name: Read the configuration for collecting
|
|
520
|
+
run: |
|
|
521
|
+
scripts_dir=$(jq -r '.scriptsDir // "scripts"' harness.config.json)
|
|
522
|
+
echo "SCRIPTS_DIR=${scripts_dir%/}" >> "$GITHUB_ENV"
|
|
523
|
+
|
|
524
|
+
- name: Set the git identity for collecting
|
|
525
|
+
run: |
|
|
526
|
+
git config user.name "github-actions[bot]"
|
|
527
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
528
|
+
|
|
529
|
+
- name: Start the next round from collected reviews
|
|
530
|
+
continue-on-error: true
|
|
531
|
+
run: bash "$SCRIPTS_DIR/remote-run.sh" collect "$HARNESS_INPUT_BRANCH"
|
|
417
532
|
|
|
418
533
|
warm:
|
|
419
534
|
if: inputs.action == 'warm'
|
|
@@ -0,0 +1,144 @@
|
|
|
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 (`sdlc-harness` when
|
|
24
|
+
# unset), and passes the job that same label, never an empty one;
|
|
25
|
+
# every other label yields a skipped job, which runs no runner and bills
|
|
26
|
+
# nothing (docs/remote-execution.md, `### Verified in Gate 12 round 2`).
|
|
27
|
+
# repository_dispatch, type `harness-task`: the entry for any other adapter
|
|
28
|
+
# (a Jira rule, a script). Its body is
|
|
29
|
+
# {"event_type": "harness-task",
|
|
30
|
+
# "client_payload": {"title": …, "body": …, "source": …}}
|
|
31
|
+
# within GitHub's client_payload limits (10 top-level properties, under
|
|
32
|
+
# 64 KB). Sending one needs a token with Contents write, so its holder is
|
|
33
|
+
# the authority.
|
|
34
|
+
#
|
|
35
|
+
# WHO CAN START A RUN. `remote-run.sh`'s `trigger` paragraph owns the check —
|
|
36
|
+
# the labeller, bots and HARNESS_TRIGGER_ALLOWED_BOTS, the permission call —
|
|
37
|
+
# and the order its refusals are made in. Nothing here pre-filters on it.
|
|
38
|
+
#
|
|
39
|
+
# THE PERMISSIONS, declared because the default token may be read-only
|
|
40
|
+
# (docs/github-integration-research.md, T2):
|
|
41
|
+
# contents: write push the new branch carrying the task prompt
|
|
42
|
+
# actions: write dispatch harness-run.yml and look up the run it created
|
|
43
|
+
# issues: write comment on the issue and remove the trigger label
|
|
44
|
+
# The job reads no repository secret: it needs only its own token, so the
|
|
45
|
+
# credential secrets never reach the job that reads untrusted issue text.
|
|
46
|
+
#
|
|
47
|
+
# THE CHECKOUT. The default branch, with the job's own token and never
|
|
48
|
+
# HARNESS_GIT_TOKEN: a branch pushed with GITHUB_TOKEN starts no workflow
|
|
49
|
+
# (docs/remote-execution.md, `### Verified in Gate 12 round 3`), so the prompt
|
|
50
|
+
# commit does not run your CI. A full fetch, because pushing a new branch from
|
|
51
|
+
# a shallow clone has not been measured anywhere.
|
|
52
|
+
#
|
|
53
|
+
# NO CONCURRENCY GROUP. A group keeps at most one pending run and cancels an
|
|
54
|
+
# earlier pending one, which would drop a trigger. Two starts that race for one
|
|
55
|
+
# branch name are caught by the refused push of an existing branch, and the
|
|
56
|
+
# refusal is commented on the issue.
|
|
57
|
+
#
|
|
58
|
+
# TWO RULES EVERY EDIT KEEPS.
|
|
59
|
+
# * Event text and variables reach a shell line only through `env:` and the
|
|
60
|
+
# event file, never through a GitHub expression inside `run:` — an issue
|
|
61
|
+
# title is written by whoever opened the issue (script injection).
|
|
62
|
+
# * Every GitHub expression has a space after its opening braces. `init`
|
|
63
|
+
# renders its workflows with the CLI's double-brace token renderer, which
|
|
64
|
+
# reads a letter straight after the braces as a token; this file carries
|
|
65
|
+
# no token.
|
|
66
|
+
#
|
|
67
|
+
# DECLARED MIRRORS — a rename on either side is an edit to both:
|
|
68
|
+
# cli/src/remote/githubActions.ts WORKFLOW_TRIGGER_FILE, HARNESS_RUNNER,
|
|
69
|
+
# HARNESS_REMOTE_STOP, HARNESS_TRIGGER_LABEL,
|
|
70
|
+
# DEFAULT_TRIGGER_LABEL (`sdlc-harness`),
|
|
71
|
+
# HARNESS_TRIGGER_ALLOWED_BOTS,
|
|
72
|
+
# TRIGGER_DISPATCH_EVENT_TYPE (`harness-task`)
|
|
73
|
+
# cli/src/config/model.ts DEFAULTS.scriptsDir
|
|
74
|
+
#
|
|
75
|
+
# ACTION PINS.
|
|
76
|
+
# actions/checkout@v5
|
|
77
|
+
# The lowest major whose own action.yml declares `runs.using: node24`, per a
|
|
78
|
+
# maintainer's lookups of its action.yml and release notes on
|
|
79
|
+
# 2026-09-29T08:21Z (UTC). That major's release notes require Actions Runner
|
|
80
|
+
# 2.327.1 or newer; a GitHub-hosted runner already has it, and a self-hosted
|
|
81
|
+
# one must run it (docs/remote-execution.md, section 8). A major tag, not a
|
|
82
|
+
# commit sha: it is GitHub's own `actions/` organisation, the owner of the
|
|
83
|
+
# runner and GITHUB_TOKEN this job already trusts, and a major tag takes the
|
|
84
|
+
# action's own patch and security releases where a sha would freeze your copy.
|
|
85
|
+
# Pin a sha in your copy if you want one.
|
|
86
|
+
|
|
87
|
+
name: harness-trigger
|
|
88
|
+
run-name: harness trigger ${{ github.event.issue.number || github.event.action }}
|
|
89
|
+
|
|
90
|
+
on:
|
|
91
|
+
issues:
|
|
92
|
+
types: [labeled]
|
|
93
|
+
repository_dispatch:
|
|
94
|
+
types: [harness-task]
|
|
95
|
+
|
|
96
|
+
permissions:
|
|
97
|
+
contents: write
|
|
98
|
+
actions: write
|
|
99
|
+
issues: write
|
|
100
|
+
|
|
101
|
+
defaults:
|
|
102
|
+
run:
|
|
103
|
+
shell: bash
|
|
104
|
+
|
|
105
|
+
jobs:
|
|
106
|
+
trigger:
|
|
107
|
+
if: github.event_name == 'repository_dispatch' || github.event.label.name == (vars.HARNESS_TRIGGER_LABEL || 'sdlc-harness')
|
|
108
|
+
runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
|
|
109
|
+
env:
|
|
110
|
+
GH_TOKEN: ${{ github.token }}
|
|
111
|
+
HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
|
|
112
|
+
HARNESS_REMOTE_SLUG: ${{ github.repository }}
|
|
113
|
+
HARNESS_TRIGGER_LABEL: ${{ vars.HARNESS_TRIGGER_LABEL || 'sdlc-harness' }}
|
|
114
|
+
HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
|
|
115
|
+
steps:
|
|
116
|
+
- name: Check out the default branch
|
|
117
|
+
uses: actions/checkout@v5
|
|
118
|
+
with:
|
|
119
|
+
token: ${{ github.token }}
|
|
120
|
+
fetch-depth: 0
|
|
121
|
+
|
|
122
|
+
- name: Check for jq and gh
|
|
123
|
+
run: |
|
|
124
|
+
missing=""
|
|
125
|
+
for tool in jq gh; do
|
|
126
|
+
command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
|
|
127
|
+
done
|
|
128
|
+
if [ -n "$missing" ]; then
|
|
129
|
+
echo "::error::this runner lacks:$missing. Install them on the self-hosted runner, or use a GitHub-hosted one."
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
- name: Read the configuration
|
|
134
|
+
run: |
|
|
135
|
+
scripts_dir=$(jq -r '.scriptsDir // "scripts"' harness.config.json)
|
|
136
|
+
echo "SCRIPTS_DIR=${scripts_dir%/}" >> "$GITHUB_ENV"
|
|
137
|
+
|
|
138
|
+
- name: Set the git identity
|
|
139
|
+
run: |
|
|
140
|
+
git config user.name "github-actions[bot]"
|
|
141
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
142
|
+
|
|
143
|
+
- name: Start the run
|
|
144
|
+
run: bash "$SCRIPTS_DIR/remote-run.sh" trigger
|
package/templates/repo/gitignore
CHANGED
|
@@ -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}}
|