autonomous-sdlc-harness 0.6.0 → 0.6.2
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/cli.js +0 -0
- package/dist/commands/init.js +72 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/core/writer.js +3 -2
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +255 -12
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +60 -2
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/remote/githubActions.js +61 -3
- package/dist/remote/githubActions.js.map +1 -1
- package/package.json +1 -1
- package/templates/claude/context/conventions.md +1 -1
- package/templates/claude/context/layer.md +1 -1
- package/templates/github/workflows/harness-control.yml +63 -17
- package/templates/github/workflows/harness-run.yml +144 -5
- package/templates/github/workflows/harness-trigger.yml +4 -1
- package/templates/scripts/autonomous-watcher.sh +133 -28
- package/templates/scripts/lib/harness-run-lib.sh +59 -27
- package/templates/scripts/remote-run.sh +872 -122
- package/templates/state-dir/clarifications/README.md +1 -1
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* existed. Every name — the directory, the file names, the template directory — is imported from
|
|
14
14
|
* `remote/githubActions.ts`, which owns them.
|
|
15
15
|
*
|
|
16
|
-
* ##
|
|
16
|
+
* ## Six non-obvious choices, and where each comes from
|
|
17
17
|
*
|
|
18
18
|
* 1. **No configured value is rendered into either file; only this CLI's own version is.** The job
|
|
19
19
|
* reads `harness.config.json` at run time, so a workflow frozen with a configured value would go
|
|
@@ -38,7 +38,25 @@
|
|
|
38
38
|
* 5. **The two forge workflows — the trigger and the control workflow — are gated on
|
|
39
39
|
* `forgeTriggerApplies`, carry no pin and are not upgraded,** because each calls the scripts on the
|
|
40
40
|
* default branch and shares their re-run contract. Both are copied verbatim like
|
|
41
|
-
* `harness-resume.yml` (choice 3) and never
|
|
41
|
+
* `harness-resume.yml` (choice 3), and the trigger never carries a `forceOverride`, upgrade mode
|
|
42
|
+
* included. Neither is ever upgraded by pin; the control workflow's one exception is the
|
|
43
|
+
* byte-identical repair of choice 6.
|
|
44
|
+
* 6. **Any `init` replaces a `harness-control.yml` byte-identical to a release's copy GitHub could
|
|
45
|
+
* not parse ({@link UNPARSEABLE_CONTROL_RELEASES}), after a `.bak`.** The match is an exact
|
|
46
|
+
* SHA-256 over the LF-normalised bytes, not a parse: the package carries no YAML parser at run
|
|
47
|
+
* time (`.claude/context/conventions.md` → `## The stack…`, the no-runtime-dependency rule), and an
|
|
48
|
+
* exact match is the only copy known to be unedited, so it is the only one replacing loses nothing
|
|
49
|
+
* for. A copy still carrying {@link UNPARSEABLE_CONTROL_IF_LINE} but otherwise edited is kept and
|
|
50
|
+
* reported as `controlRepair: { kind: 'edited' }`. It runs on every `init`, not only under
|
|
51
|
+
* {@link UPGRADE_WORKFLOWS_FLAG}, because the file carries no pin for that mode to key on and an
|
|
52
|
+
* unparseable copy disables comment control whichever command the adopter reaches for. It is a
|
|
53
|
+
* per-request `forceOverride: 'always'`, not a new `WritePolicy`, on choice 4's precedent: the
|
|
54
|
+
* engine's `.bak`-then-replace is the operation, and the re-run contract stays `create-if-absent`.
|
|
55
|
+
* The repair's `.bak` is deliberately not in the managed `.gitignore` block: the repair happens
|
|
56
|
+
* once, and its `.bak` is meant to be seen and deleted.
|
|
57
|
+
*
|
|
58
|
+
* **This module owns {@link unparseableControlRoute}.** `init`'s warning and `doctor`'s failure both
|
|
59
|
+
* print it, and neither re-spells it.
|
|
42
60
|
*
|
|
43
61
|
* **Declared mirror.** `cli/templates/github/workflows/harness-run.yml`'s `Install the pinned plugin`
|
|
44
62
|
* step spells the route {@link upgradeWorkflowsCommand} produces,
|
|
@@ -52,6 +70,7 @@
|
|
|
52
70
|
* Every file is `create-if-absent`: the adopter tunes the cron, the timeouts and the runner, and a
|
|
53
71
|
* re-run keeps that edit; `--force` replaces each after a `.bak` (`core/writer.ts`'s re-run table).
|
|
54
72
|
*/
|
|
73
|
+
import { createHash } from 'node:crypto';
|
|
55
74
|
import { existsSync, readFileSync } from 'node:fs';
|
|
56
75
|
import { join } from 'node:path';
|
|
57
76
|
import { forgeTriggerApplies, remoteExecutionApplies } from '../config/model.js';
|
|
@@ -75,6 +94,40 @@ export function upgradeWorkflowsCommand(version) {
|
|
|
75
94
|
* space. Its citation is spelled as `harness-run.yml`'s `upgrade_route` spells it.
|
|
76
95
|
*/
|
|
77
96
|
export const IN_FLIGHT_RUNS_NOTE = "An upgrade reaches only the runs dropped after it is pushed: each run's branch carries the workflows it was cut with, so a run already in flight finishes on the version it started with, and moving one on purpose is a separate step (docs/remote-execution.md, section 7, Upgrading).";
|
|
97
|
+
/**
|
|
98
|
+
* Each release whose shipped `harness-control.yml` GitHub could not parse, to the SHA-256 hex of
|
|
99
|
+
* that file's LF-normalised bytes (choice 6). The 0.6.1 digest is of git blob
|
|
100
|
+
* `1b4f0fc33200d876e4089ebe4013120e48a45335`, `cli/templates/github/workflows/harness-control.yml`
|
|
101
|
+
* from `a4ae3c8` through `31a2d55`.
|
|
102
|
+
*/
|
|
103
|
+
export const UNPARSEABLE_CONTROL_RELEASES = {
|
|
104
|
+
'0.6.1': 'dd014dc14bf19947182f4c95fa0bf011aba04fda354d9a6fa94242e86082bfa5',
|
|
105
|
+
};
|
|
106
|
+
/** The job-level `if:` line, trimmed, that made 0.6.1's `harness-control.yml` unparseable. */
|
|
107
|
+
const UNPARSEABLE_CONTROL_IF_LINE = "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) || (github.event_name == 'issues' && contains(join(github.event.issue.labels.*.name, ','), 'sdlc-harness: ')) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'delete' && github.event.ref_type == 'branch')";
|
|
108
|
+
/**
|
|
109
|
+
* The one producer of the route from an unparseable `harness-control.yml` to the fixed one. Complete
|
|
110
|
+
* sentences ending in one `.`, ASCII only, no line break.
|
|
111
|
+
*/
|
|
112
|
+
export function unparseableControlRoute(version) {
|
|
113
|
+
const pinned = pinnedCliCommand(version);
|
|
114
|
+
const releases = Object.keys(UNPARSEABLE_CONTROL_RELEASES).join(' or ');
|
|
115
|
+
return (`Run \`${pinned} init\`, which replaces a harness-control.yml that ${releases} wrote and nobody edited, keeping the previous copy as a .bak; ` +
|
|
116
|
+
`for an edited copy, write the control job's if: as a folded block scalar (if: >- with the expression on the next line, indented), ` +
|
|
117
|
+
`or run \`${pinned} init --force\`, which regenerates every generated file after a .bak; ` +
|
|
118
|
+
`then commit the file and push it to the repository's default branch.`);
|
|
119
|
+
}
|
|
120
|
+
/** Classify an existing control workflow's text against {@link UNPARSEABLE_CONTROL_RELEASES}. */
|
|
121
|
+
function classifyControl(existing) {
|
|
122
|
+
const normalised = existing.replace(/\r\n/g, '\n');
|
|
123
|
+
const digest = createHash('sha256').update(normalised, 'utf8').digest('hex');
|
|
124
|
+
const release = Object.keys(UNPARSEABLE_CONTROL_RELEASES).find((name) => UNPARSEABLE_CONTROL_RELEASES[name] === digest);
|
|
125
|
+
if (release !== undefined)
|
|
126
|
+
return { kind: 'replaced', release };
|
|
127
|
+
if (normalised.split('\n').some((line) => line.trim() === UNPARSEABLE_CONTROL_IF_LINE))
|
|
128
|
+
return { kind: 'edited' };
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
78
131
|
const CRON_LINE = /^\s*- cron: /;
|
|
79
132
|
/** The schedule a `- cron:` line carries, quotes stripped. */
|
|
80
133
|
function cronExpression(line) {
|
|
@@ -143,6 +196,7 @@ export function writeGithubWorkflows({ repoRoot, config, plan, upgrade = false,
|
|
|
143
196
|
{ absolute: resumePath, repoPath: WORKFLOW_RESUME_PATH },
|
|
144
197
|
];
|
|
145
198
|
const trigger = forgeTriggerApplies(config);
|
|
199
|
+
let controlRepair;
|
|
146
200
|
if (trigger) {
|
|
147
201
|
const triggerPath = join(repoRoot, ...WORKFLOW_TRIGGER_PATH.split('/'));
|
|
148
202
|
plan.add({
|
|
@@ -153,11 +207,14 @@ export function writeGithubWorkflows({ repoRoot, config, plan, upgrade = false,
|
|
|
153
207
|
});
|
|
154
208
|
workflows.push({ absolute: triggerPath, repoPath: WORKFLOW_TRIGGER_PATH });
|
|
155
209
|
const controlPath = join(repoRoot, ...WORKFLOW_CONTROL_PATH.split('/'));
|
|
210
|
+
if (existsSync(controlPath))
|
|
211
|
+
controlRepair = classifyControl(readFileSync(controlPath, 'utf8'));
|
|
156
212
|
plan.add({
|
|
157
213
|
path: controlPath,
|
|
158
214
|
policy: 'create-if-absent',
|
|
159
215
|
content: readTemplate(`${WORKFLOW_TEMPLATE_DIR}/${WORKFLOW_CONTROL_FILE}`),
|
|
160
216
|
label: `workflow ${WORKFLOW_CONTROL_FILE}`,
|
|
217
|
+
...(controlRepair?.kind === 'replaced' ? { forceOverride: 'always' } : {}),
|
|
161
218
|
});
|
|
162
219
|
workflows.push({ absolute: controlPath, repoPath: WORKFLOW_CONTROL_PATH });
|
|
163
220
|
}
|
|
@@ -166,6 +223,7 @@ export function writeGithubWorkflows({ repoRoot, config, plan, upgrade = false,
|
|
|
166
223
|
workflows,
|
|
167
224
|
trigger,
|
|
168
225
|
...(upgradeResult === undefined ? {} : { upgrade: upgradeResult }),
|
|
226
|
+
...(controlRepair === undefined ? {} : { controlRepair }),
|
|
169
227
|
};
|
|
170
228
|
}
|
|
171
229
|
//# sourceMappingURL=githubWorkflows.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubWorkflows.js","sourceRoot":"","sources":["../../src/generators/githubWorkflows.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"githubWorkflows.js","sourceRoot":"","sources":["../../src/generators/githubWorkflows.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
* the repository secret and variable names, the two artifact names and the `gh` test-seam variable
|
|
7
7
|
* and the rendered CLI-version pin are declared here once; the generator that writes the workflows and the `doctor` checks that grade
|
|
8
8
|
* them read these constants rather than retyping a literal, and so does `generators/repoRoot.ts`,
|
|
9
|
-
* which spells the two workflow `.bak` ignore rules whatever `execution.target` says.
|
|
9
|
+
* which spells the two workflow `.bak` ignore rules whatever `execution.target` says. It owns the
|
|
10
|
+
* {@link RUN_ACTORS_VARIABLE} allow-list's semantics as well as its name — {@link effectiveRunActors}
|
|
11
|
+
* and {@link runActorAdmitted} — and `remote-run.sh` and `harness-run.yml`'s gate step each restate
|
|
12
|
+
* that rule in shell.
|
|
10
13
|
*
|
|
11
14
|
* Except in `generators/repoRoot.ts`, nothing here is consulted unless `config/model.ts` →
|
|
12
15
|
* `remoteExecutionApplies(config)` is true: every other consumer tests that switch first. That one
|
|
@@ -53,8 +56,11 @@ export const WORKFLOW_CONTROL_FILE = 'harness-control.yml';
|
|
|
53
56
|
export const WORKFLOW_CONTROL_PATH = `${WORKFLOWS_DIR}/${WORKFLOW_CONTROL_FILE}`;
|
|
54
57
|
/** The first word of a command comment, matched case-insensitively. */
|
|
55
58
|
export const COMMAND_HANDLE = '@sdlc-harness';
|
|
56
|
-
/**
|
|
57
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The verbs a command comment may carry after {@link COMMAND_HANDLE}. `status` is read-only: it
|
|
61
|
+
* replies with the run's state and changes no label and dispatches nothing.
|
|
62
|
+
*/
|
|
63
|
+
export const COMMAND_VERBS = ['answer', 'pause', 'resume', 'stop', 'clear', 'status'];
|
|
58
64
|
/**
|
|
59
65
|
* The prefix of the hidden line every harness comment carries; the whole line is
|
|
60
66
|
* `<!-- sdlc-harness event=<event> branch=<branch>[ question=<n>] -->`. A comment containing the
|
|
@@ -114,6 +120,58 @@ export function triggerFallbackLabel(text) {
|
|
|
114
120
|
}
|
|
115
121
|
/** Repository variable: comma-separated bot logins that may start a run; unset or empty admits none. */
|
|
116
122
|
export const TRIGGER_ALLOWED_BOTS_VARIABLE = 'HARNESS_TRIGGER_ALLOWED_BOTS';
|
|
123
|
+
/** Repository variable: comma-separated GitHub logins allowed to start, command, answer and review a run; `*` admits every writer; unset admits the owner of a user-owned repository alone, and nobody in an organisation-owned one. */
|
|
124
|
+
export const RUN_ACTORS_VARIABLE = 'HARNESS_RUN_ACTORS';
|
|
125
|
+
/** The {@link RUN_ACTORS_VARIABLE} entry that admits every collaborator with write access. */
|
|
126
|
+
export const RUN_ACTORS_EVERY_WRITER = '*';
|
|
127
|
+
/**
|
|
128
|
+
* Who a {@link RUN_ACTORS_VARIABLE} value admits. The value is split on `,`, each entry trimmed and
|
|
129
|
+
* empty entries dropped. An entry {@link RUN_ACTORS_EVERY_WRITER} anywhere gives `every-writer`;
|
|
130
|
+
* otherwise any entries give `listed`, as written and in order. No entries — `undefined`, `''`, only
|
|
131
|
+
* whitespace or only commas — counts as unset: an owner whose `type` is exactly `User` with a
|
|
132
|
+
* non-empty login gives `owner`, and anything else (an `Organization`, an unknown type, no owner)
|
|
133
|
+
* gives `nobody`. Pure — the caller reads the variable and the owner.
|
|
134
|
+
*/
|
|
135
|
+
export function effectiveRunActors(value, owner) {
|
|
136
|
+
const entries = (value ?? '')
|
|
137
|
+
.split(',')
|
|
138
|
+
.map((entry) => entry.trim())
|
|
139
|
+
.filter((entry) => entry !== '');
|
|
140
|
+
if (entries.includes(RUN_ACTORS_EVERY_WRITER))
|
|
141
|
+
return { kind: 'every-writer' };
|
|
142
|
+
if (entries.length > 0)
|
|
143
|
+
return { kind: 'listed', logins: entries };
|
|
144
|
+
if (owner !== undefined && owner.type === 'User' && owner.login !== '')
|
|
145
|
+
return { kind: 'owner', login: owner.login };
|
|
146
|
+
return { kind: 'nobody', owner };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Whether `login` is admitted by `actors`, compared case-insensitively. An empty login is never
|
|
150
|
+
* admitted, `nobody` admits no one, and `every-writer` admits any non-empty login — whether that login
|
|
151
|
+
* has write access is GitHub's to decide, not this function's.
|
|
152
|
+
*/
|
|
153
|
+
export function runActorAdmitted(actors, login) {
|
|
154
|
+
if (login === '')
|
|
155
|
+
return false;
|
|
156
|
+
const wanted = login.toLowerCase();
|
|
157
|
+
switch (actors.kind) {
|
|
158
|
+
case 'every-writer':
|
|
159
|
+
return true;
|
|
160
|
+
case 'listed':
|
|
161
|
+
return actors.logins.some((entry) => entry.toLowerCase() === wanted);
|
|
162
|
+
case 'owner':
|
|
163
|
+
return actors.login.toLowerCase() === wanted;
|
|
164
|
+
case 'nobody':
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Whether a workflow's text reads `vars.HARNESS_RUN_ACTORS`; `false` marks a copy written before the
|
|
170
|
+
* allow-list existed. Pure — the caller reads the file.
|
|
171
|
+
*/
|
|
172
|
+
export function carriesRunActors(workflowText) {
|
|
173
|
+
return workflowText.includes(`vars.${RUN_ACTORS_VARIABLE}`);
|
|
174
|
+
}
|
|
117
175
|
/** The `repository_dispatch` `event_type` the trigger workflow listens to. */
|
|
118
176
|
export const TRIGGER_DISPATCH_EVENT_TYPE = 'harness-task';
|
|
119
177
|
/** Repository secret for subscription billing. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubActions.js","sourceRoot":"","sources":["../../src/remote/githubActions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"githubActions.js","sourceRoot":"","sources":["../../src/remote/githubActions.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.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- How a change flows through those layers, and which direction a dependency is allowed to point.
|
|
11
11
|
- The order files are created in when a feature is added, so a half-built feature is still a coherent tree.
|
|
12
12
|
- Where a new responsibility goes when it fits no file that already exists.
|
|
13
|
-
- Logging, error handling, and the testing bar a change clears before it counts as done — including **the one command that runs a single test file on its own**, or the statement that this project has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed.
|
|
13
|
+
- Logging, error handling, and the testing bar a change clears before it counts as done — including **the one command that runs a single test file on its own**, or the statement that this project has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed. Name it as one command run from the repository root — never `cd <dir> && …`, never piped, and never with its output redirected to a file — because a unit runs it unattended, where any of those shapes can be refused and nobody can approve it. Do not quote a terminal run's output as what a run prints: a runner may print a different format when its output is captured rather than shown on a terminal.
|
|
14
14
|
- **The commit-message policy**, which is the whole of what the committer agent reads from this document: the subject-prefix vocabulary a caller draws its prefix from, whether the first word after the prefix is capitalized, the subject-length cap, the fixed-form subjects the flow emits, and the attribution-trailer policy: whether a trailer naming the agent as an author is required, forbidden or optional. The harness takes no position on that trailer, because the rule is actively reversed between projects: some forbid one outright, others require it on every agent-assisted commit, and unanswered here it has no default to fall back on. **The fixed-form subjects are derived here, never copied from a list** — run ``grep -rnE '(chore|feat|fix|refactor|docs|test): [A-Za-z][^"'"'"'`]*(<branch>|\$branch|\$\{branch\}|<entry-id>|K passing)' "${CLAUDE_PLUGIN_ROOT}" <scripts_dir>``, substituting `<scripts_dir>` with this repository's `scriptsDir` value from `harness.config.json` before running it, and write the policy so that **every** subject that command emits is exempt from the capitalisation rule and from the subject-length cap — **minus two classes the probe reaches but no commit point owns**: a subject the surrounding text tells the flow **not** to emit (its own line says it is forbidden), and a subject quoted as a worked example by a guard, a verification document or a prose restatement of another home's string. A subject with no emitting home is not a fixed form; check the line the probe emitted before exempting it. Each exempted subject is passed byte-for-byte by the commit point that owns it, so a policy that re-cases or truncates one breaks that commit point. `review_plan_file` and `ui_test_pass` are the committer's own commit modes and the dispatching flow files quote both, so changing either one is a two-file edit; the flow's other commit points own theirs the same way. The probe reaches **branch-scoped** subjects only — its alternation requires a `<branch>` / `$branch` / `${branch}` / `<entry-id>` / `K passing` token — so a fixed subject carrying none of them falls outside it, and widening the probe is part of the cost of adding one. **Per commit class, which prefix a commit of that class takes** — the vocabulary above names the set, this names the selection, and a policy giving only the set leaves a caller nothing to read: answer it for every class the flows commit in, at minimum a commit **fixing existing work**, the class the review-fix loops commit in, and a commit **adding new work**, the class a story task's commit falls in; and answer as well whether **any class carries no prefix at all, and which class that is**, which is what the committer's `commit_prefix` argument spells as the literal `none`. Designate by class and never by file kind — the classes are the flows', the tokens are yours.
|
|
15
15
|
- **How this project stays in step with its reference implementation, if it has one** — which behaviour must match it exactly, which parts may deviate and why, and how a review finding cites the source it compared against. A project with a single implementation says so here and leaves `phases.parity` off; the section is optional, not missing.
|
|
16
16
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- What this layer is responsible for and — just as usefully — what it is not, so a task that has drifted into it is recognisable.
|
|
10
10
|
- What it may depend on, and which direction a dependency between layers is allowed to point.
|
|
11
11
|
- Naming and file-layout rules that apply inside it.
|
|
12
|
-
- What "done" means here: the tests to write, the checks to run, the bar a review holds the change to — including **the one command that runs a single test file of this layer on its own**, or the statement that this layer has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed.
|
|
12
|
+
- What "done" means here: the tests to write, the checks to run, the bar a review holds the change to — including **the one command that runs a single test file of this layer on its own**, or the statement that this layer has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed. Name it as one command run from the repository root — never `cd <dir> && …`, never piped, and never with its output redirected to a file — because a unit runs it unattended, where any of those shapes can be refused and nobody can approve it. Do not quote a terminal run's output as what a run prints: a runner may print a different format when its output is captured rather than shown on a terminal.
|
|
13
13
|
|
|
14
14
|
**One generic example — the shape a rule takes here**
|
|
15
15
|
|
|
@@ -1,19 +1,24 @@
|
|
|
1
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
|
|
3
|
-
#
|
|
4
|
-
# round
|
|
2
|
+
# (`@sdlc-harness answer|pause|resume|stop|clear|status`) on an issue or pull
|
|
3
|
+
# request, turns a pull-request review requesting changes into the next
|
|
4
|
+
# user-review round, and stops a run when its issue or pull request is closed
|
|
5
|
+
# or its branch is deleted.
|
|
5
6
|
#
|
|
6
7
|
# WHO WRITES IT. `autonomous-sdlc-harness init`, only when harness.config.json's
|
|
7
8
|
# `forge` is `github` and its `execution.target` is `github-actions`. Written
|
|
8
9
|
# create-if-absent: from then on it is yours to tune, and a re-run of `init`
|
|
9
10
|
# keeps your copy. `init --upgrade-workflows` does NOT re-render it: it carries
|
|
10
11
|
# 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`.
|
|
12
|
+
# their re-run contract — `init --force` replaces it after a `.bak`. The one
|
|
13
|
+
# exception: any `init` replaces, after a `.bak`, a copy that is byte for byte
|
|
14
|
+
# the one 0.6.1 wrote, which GitHub could not parse; an edited copy of that one
|
|
15
|
+
# is kept and named in `init`'s output. It is a
|
|
12
16
|
# file of its own, apart from harness-trigger.yml, so you can disable comment
|
|
13
17
|
# control alone: disabling a workflow disables every event in it.
|
|
14
18
|
#
|
|
15
19
|
# WHAT IT DOES. One job runs `remote-run.sh control`, which does everything
|
|
16
|
-
# else: it authorises the actor
|
|
20
|
+
# else: it authorises the actor — their write access, then the allow-list
|
|
21
|
+
# HARNESS_RUN_ACTORS — finds the run's branch, carries out the
|
|
17
22
|
# command or places the review round, and replies. Every decision lives in that
|
|
18
23
|
# script; this file carries only the events, the permissions, a prefilter and
|
|
19
24
|
# the concurrency group.
|
|
@@ -26,24 +31,43 @@
|
|
|
26
31
|
# pull_request_review, type `submitted` only: the round is built from the
|
|
27
32
|
# review event, which carries the review body and identifies its inline
|
|
28
33
|
# comments (docs/github-integration-research.md, C1).
|
|
34
|
+
# issues, type `closed` only: a run's issue was closed, which stops the run.
|
|
35
|
+
# pull_request, type `closed` only: a run's pull request was closed or
|
|
36
|
+
# merged, which stops the run. GitHub starts no workflow for activity on
|
|
37
|
+
# a pull request with a merge conflict, so closing a conflicting one
|
|
38
|
+
# stops nothing (docs/github-run-control.md, section 8).
|
|
39
|
+
# delete: a branch or tag was deleted; a deleted run branch stops the run.
|
|
29
40
|
# Never pull_request_review_comment: one review raises one such event per
|
|
30
41
|
# inline comment, so listening to it would start one job per comment for a
|
|
31
42
|
# round the review event already covers (C1).
|
|
32
43
|
#
|
|
33
44
|
# THE PREFILTER. The job's `if:` runs it only for a comment that contains
|
|
34
45
|
# `@sdlc-harness` and not `<!-- sdlc-harness` (the hidden line every harness
|
|
35
|
-
# reply carries, so a reply quoting a command is never one)
|
|
36
|
-
# whose state is `changes_requested` on a pull request from this repository
|
|
37
|
-
#
|
|
38
|
-
#
|
|
46
|
+
# reply carries, so a reply quoting a command is never one); for a review
|
|
47
|
+
# whose state is `changes_requested` on a pull request from this repository;
|
|
48
|
+
# for a closed issue carrying a `sdlc-harness: ` state label, which every
|
|
49
|
+
# harness issue carries from its trigger on; for a closed pull request from
|
|
50
|
+
# this repository; or for a deleted ref whose type is `branch`. Every other
|
|
51
|
+
# event yields a skipped job, which runs no runner and bills nothing
|
|
52
|
+
# (docs/remote-execution.md, `### Verified in Gate 12 round 2`). No label or
|
|
53
|
+
# name marks a run branch at deletion, and none reliably marks a run's pull
|
|
54
|
+
# request before its first lifecycle event, so every branch deletion and
|
|
55
|
+
# every closed same-repository pull request in the repository starts a job,
|
|
56
|
+
# which `control` ends in one line when the branch had no run.
|
|
39
57
|
# It is a saving, not the authority: `control` re-checks every rule, so an
|
|
40
58
|
# edited copy of this filter cannot bypass one. That `contains()` compares
|
|
41
59
|
# case-insensitively is GitHub's documented behaviour, not verified here; a
|
|
42
60
|
# mixed-case handle the filter missed would only go unanswered, never obeyed.
|
|
61
|
+
# The `if:` is a folded block scalar (`>-`) because its expression carries
|
|
62
|
+
# `: ` (the state-label prefix `'sdlc-harness: '`), which a plain scalar reads
|
|
63
|
+
# as a mapping indicator. 0.6.1 shipped it plain, so GitHub could not parse
|
|
64
|
+
# the file and ran it for no event (docs/development.md, Gate 12, Round 7,
|
|
65
|
+
# finding 1).
|
|
43
66
|
#
|
|
44
67
|
# THE PERMISSIONS, declared because the default token may be read-only
|
|
45
68
|
# (docs/github-integration-research.md, T2):
|
|
46
|
-
# contents: write push a review round
|
|
69
|
+
# contents: write push a review round, and the contents read a
|
|
70
|
+
# `stop --branch-gone` makes
|
|
47
71
|
# actions: write dispatch, pause, stop and cancel runs, and read
|
|
48
72
|
# runs and their artifacts
|
|
49
73
|
# issues: write reply on the issue or pull request, and label it
|
|
@@ -61,6 +85,8 @@
|
|
|
61
85
|
# not skipped, and can name any `runs-on:` label, a self-hosted runner's
|
|
62
86
|
# included. Such a job gets a read-only token and no secret (C2), and fork
|
|
63
87
|
# approval policies apply to it.
|
|
88
|
+
# A `pull_request` close job runs the pull request's merge-commit copy of this
|
|
89
|
+
# file too, as a review job does (C2); the `if:` skips a fork's.
|
|
64
90
|
# A comment on a fork's pull request runs with a write token, and `control`
|
|
65
91
|
# refuses it.
|
|
66
92
|
# The checkout is always the default branch, never the pull request's merge
|
|
@@ -71,7 +97,8 @@
|
|
|
71
97
|
# THE CONCURRENCY GROUP SERIALIZES REVIEW JOBS PER BRANCH, NEVER COMMENT JOBS.
|
|
72
98
|
# * A comment job's group is its own run id, so it never waits and is never
|
|
73
99
|
# replaced: a group keeps at most one pending job and cancels an earlier
|
|
74
|
-
# pending one, which would drop a command.
|
|
100
|
+
# pending one, which would drop a command. A close or deletion job takes
|
|
101
|
+
# the same run-id group, for the same reason.
|
|
75
102
|
# * Review jobs on one head branch share `harness-review-<branch>` and run one
|
|
76
103
|
# at a time, so two reviews submitted together give one round, not two
|
|
77
104
|
# pushes racing for the same round number.
|
|
@@ -86,8 +113,15 @@
|
|
|
86
113
|
# harness-run.yml's `collect` job is the one such job today (DECLARED
|
|
87
114
|
# MIRRORS below).
|
|
88
115
|
#
|
|
116
|
+
# A REFUSAL IS A SUCCESS. `remote-run.sh control` exits 2 for a refusal it has
|
|
117
|
+
# already replied to; that exit code is the script's contract with every other
|
|
118
|
+
# caller, so the step, not the script, maps it: `|| [ $? -eq 2 ]` turns a
|
|
119
|
+
# replied refusal into a green job with no failure e-mail, while 1, 3 and 4
|
|
120
|
+
# still fail it. Under the step's `bash -e -o pipefail` a failure on the left
|
|
121
|
+
# of `||` does not exit the shell, and `$?` is the script's status.
|
|
122
|
+
#
|
|
89
123
|
|
|
90
|
-
#
|
|
124
|
+
# THREE RULES EVERY EDIT KEEPS.
|
|
91
125
|
# * Event text and variables reach a shell line only through `env:` and the
|
|
92
126
|
# event file, never through a GitHub expression inside `run:` — a comment
|
|
93
127
|
# body is written by whoever posted it (script injection).
|
|
@@ -95,6 +129,10 @@
|
|
|
95
129
|
# renders its workflows with the CLI's double-brace token renderer, which
|
|
96
130
|
# reads a letter straight after the braces as a token; this file carries
|
|
97
131
|
# no token.
|
|
132
|
+
# * A value carrying `: ` or ` #` is never a plain scalar: write it as a
|
|
133
|
+
# block scalar (`>-`) or quote it. A plain scalar reads `: ` as a mapping
|
|
134
|
+
# indicator, which leaves the file unparseable, and ` #` as a comment,
|
|
135
|
+
# which silently truncates the value (THE PREFILTER above).
|
|
98
136
|
#
|
|
99
137
|
# DECLARED MIRRORS — a rename on either side is an edit to both:
|
|
100
138
|
# cli/src/remote/githubActions.ts WORKFLOW_CONTROL_FILE, COMMAND_HANDLE
|
|
@@ -102,7 +140,8 @@
|
|
|
102
140
|
# (`<!-- sdlc-harness`), REVIEW_ROUND_STATE
|
|
103
141
|
# (`changes_requested`), HARNESS_RUNNER,
|
|
104
142
|
# HARNESS_REMOTE_STOP,
|
|
105
|
-
# HARNESS_TRIGGER_ALLOWED_BOTS
|
|
143
|
+
# HARNESS_TRIGGER_ALLOWED_BOTS,
|
|
144
|
+
# HARNESS_RUN_ACTORS
|
|
106
145
|
# cli/src/config/model.ts DEFAULTS.scriptsDir
|
|
107
146
|
# harness-run.yml the concurrency group
|
|
108
147
|
# `harness-review-<branch>`, held by its
|
|
@@ -121,13 +160,18 @@
|
|
|
121
160
|
# Pin a sha in your copy if you want one.
|
|
122
161
|
|
|
123
162
|
name: harness-control
|
|
124
|
-
run-name: harness control ${{ github.event.issue.number || github.event.pull_request.number }}
|
|
163
|
+
run-name: harness control ${{ github.event.issue.number || github.event.pull_request.number || github.event.ref }}
|
|
125
164
|
|
|
126
165
|
on:
|
|
127
166
|
issue_comment:
|
|
128
167
|
types: [created]
|
|
129
168
|
pull_request_review:
|
|
130
169
|
types: [submitted]
|
|
170
|
+
issues:
|
|
171
|
+
types: [closed]
|
|
172
|
+
pull_request:
|
|
173
|
+
types: [closed]
|
|
174
|
+
delete:
|
|
131
175
|
|
|
132
176
|
permissions:
|
|
133
177
|
contents: write
|
|
@@ -141,7 +185,8 @@ defaults:
|
|
|
141
185
|
|
|
142
186
|
jobs:
|
|
143
187
|
control:
|
|
144
|
-
if:
|
|
188
|
+
if: >-
|
|
189
|
+
(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) || (github.event_name == 'issues' && contains(join(github.event.issue.labels.*.name, ','), 'sdlc-harness: ')) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'delete' && github.event.ref_type == 'branch')
|
|
145
190
|
runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
|
|
146
191
|
concurrency:
|
|
147
192
|
group: ${{ github.event_name == 'pull_request_review' && format('harness-review-{0}', github.event.pull_request.head.ref) || format('harness-control-{0}', github.run_id) }}
|
|
@@ -151,6 +196,7 @@ jobs:
|
|
|
151
196
|
HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
|
|
152
197
|
HARNESS_REMOTE_SLUG: ${{ github.repository }}
|
|
153
198
|
HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
|
|
199
|
+
HARNESS_RUN_ACTORS: ${{ vars.HARNESS_RUN_ACTORS }}
|
|
154
200
|
steps:
|
|
155
201
|
- name: Check out the default branch
|
|
156
202
|
uses: actions/checkout@v5
|
|
@@ -180,5 +226,5 @@ jobs:
|
|
|
180
226
|
git config user.name "github-actions[bot]"
|
|
181
227
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
182
228
|
|
|
183
|
-
- name: Act on the comment or
|
|
184
|
-
run: bash "$SCRIPTS_DIR/remote-run.sh" control
|
|
229
|
+
- name: Act on the comment, review, close or deletion
|
|
230
|
+
run: bash "$SCRIPTS_DIR/remote-run.sh" control || [ $? -eq 2 ]
|