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.
@@ -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
- * ## Five non-obvious choices, and where each comes from
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 carry a `forceOverride`, upgrade mode included.
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":"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"}
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
- /** The verbs a command comment may carry after {@link COMMAND_HANDLE}. */
57
- export const COMMAND_VERBS = ['answer', 'pause', 'resume', 'stop', 'clear'];
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":"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"}
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.0",
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 request,
3
- # and turns a pull-request review requesting changes into the next user-review
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`. It is a
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, finds the run's branch, carries out the
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), 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`).
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
- # TWO RULES EVERY EDIT KEEPS.
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: (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)
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 review
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 ]