autonomous-sdlc-harness 0.5.0 → 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 +17 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +27 -5
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +54 -11
- 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 +1 -0
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +317 -40
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +21 -11
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/remote/githubActions.js +63 -4
- 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 +7 -0
- package/templates/github/workflows/harness-run.yml +100 -1
- package/templates/github/workflows/harness-trigger.yml +5 -4
- package/templates/scripts/README.md +1 -1
- package/templates/scripts/autonomous-watcher.sh +29 -2
- package/templates/scripts/docs-search-server.sh +88 -17
- package/templates/scripts/lib/harness-run-lib.sh +205 -26
- package/templates/scripts/remote-run.sh +2617 -150
- 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
|
@@ -41,6 +41,33 @@
|
|
|
41
41
|
# cli/src/machine/paths.ts machineCacheDir, under which the retrieval
|
|
42
42
|
# runtime sits in `retrieval/`
|
|
43
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.
|
|
44
71
|
#
|
|
45
72
|
# ACTION PINS.
|
|
46
73
|
# actions/checkout@v5
|
|
@@ -62,7 +89,9 @@
|
|
|
62
89
|
# Secrets: CLAUDE_CODE_OAUTH_TOKEN and/or ANTHROPIC_API_KEY (one is required;
|
|
63
90
|
# billing follows the API key when both are set), HARNESS_GIT_TOKEN (optional:
|
|
64
91
|
# a personal or App token, so the job's pushes trigger your own CI, which
|
|
65
|
-
# 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).
|
|
66
95
|
# Variables: HARNESS_RUNNER, HARNESS_REMOTE_STOP (any value stops every job
|
|
67
96
|
# before it launches anything), HARNESS_STEP_TIMEOUT_MINUTES,
|
|
68
97
|
# HARNESS_SELF_PAUSE_AFTER_MINUTES, HARNESS_MAX_CHAIN, and the watcher
|
|
@@ -71,6 +100,20 @@
|
|
|
71
100
|
# harness.config.json, at run time: `scriptsDir` and whether docs retrieval
|
|
72
101
|
# applies. No configured value is frozen into this file.
|
|
73
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
|
+
#
|
|
74
117
|
# THE SELF-PAUSE POINT, MEASURED. On a hosted runner the job drops its own PAUSE
|
|
75
118
|
# REMOTE_SELF_PAUSE_AFTER_SECS after it started, the run yields at its next
|
|
76
119
|
# dispatch boundary, and `remote-run.sh continue` chains a new job.
|
|
@@ -183,6 +226,8 @@ on:
|
|
|
183
226
|
permissions:
|
|
184
227
|
contents: write
|
|
185
228
|
actions: write
|
|
229
|
+
issues: write
|
|
230
|
+
pull-requests: write
|
|
186
231
|
|
|
187
232
|
defaults:
|
|
188
233
|
run:
|
|
@@ -420,6 +465,13 @@ jobs:
|
|
|
420
465
|
overwrite: true
|
|
421
466
|
retention-days: 400
|
|
422
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
|
+
|
|
423
475
|
- name: Continue, wait or stop
|
|
424
476
|
if: ${{ !cancelled() && env.SCRIPTS_DIR != '' }}
|
|
425
477
|
run: bash "$SCRIPTS_DIR/remote-run.sh" continue "$HARNESS_INPUT_BRANCH" "$RUNNER_TEMP/harness-state"
|
|
@@ -430,6 +482,53 @@ jobs:
|
|
|
430
482
|
export HARNESS_REPO_SLUG="$HARNESS_REMOTE_SLUG"
|
|
431
483
|
bash "$SCRIPTS_DIR/autonomous-notify.sh" failed "$HARNESS_INPUT_BRANCH" "" \
|
|
432
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"
|
|
433
532
|
|
|
434
533
|
warm:
|
|
435
534
|
if: inputs.action == 'warm'
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
# issues, type `labeled` only, never `opened`: labelling an issue at creation
|
|
21
21
|
# raises `labeled` too, so listening to both would start twice
|
|
22
22
|
# (docs/github-integration-research.md, T1). The job runs only for the
|
|
23
|
-
# label the HARNESS_TRIGGER_LABEL variable names (`harness` when
|
|
23
|
+
# label the HARNESS_TRIGGER_LABEL variable names (`sdlc-harness` when
|
|
24
|
+
# unset), and passes the job that same label, never an empty one;
|
|
24
25
|
# every other label yields a skipped job, which runs no runner and bills
|
|
25
26
|
# nothing (docs/remote-execution.md, `### Verified in Gate 12 round 2`).
|
|
26
27
|
# repository_dispatch, type `harness-task`: the entry for any other adapter
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
# DECLARED MIRRORS — a rename on either side is an edit to both:
|
|
67
68
|
# cli/src/remote/githubActions.ts WORKFLOW_TRIGGER_FILE, HARNESS_RUNNER,
|
|
68
69
|
# HARNESS_REMOTE_STOP, HARNESS_TRIGGER_LABEL,
|
|
69
|
-
# DEFAULT_TRIGGER_LABEL (`harness`),
|
|
70
|
+
# DEFAULT_TRIGGER_LABEL (`sdlc-harness`),
|
|
70
71
|
# HARNESS_TRIGGER_ALLOWED_BOTS,
|
|
71
72
|
# TRIGGER_DISPATCH_EVENT_TYPE (`harness-task`)
|
|
72
73
|
# cli/src/config/model.ts DEFAULTS.scriptsDir
|
|
@@ -103,13 +104,13 @@ defaults:
|
|
|
103
104
|
|
|
104
105
|
jobs:
|
|
105
106
|
trigger:
|
|
106
|
-
if: github.event_name == 'repository_dispatch' || github.event.label.name == (vars.HARNESS_TRIGGER_LABEL || 'harness')
|
|
107
|
+
if: github.event_name == 'repository_dispatch' || github.event.label.name == (vars.HARNESS_TRIGGER_LABEL || 'sdlc-harness')
|
|
107
108
|
runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
|
|
108
109
|
env:
|
|
109
110
|
GH_TOKEN: ${{ github.token }}
|
|
110
111
|
HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
|
|
111
112
|
HARNESS_REMOTE_SLUG: ${{ github.repository }}
|
|
112
|
-
HARNESS_TRIGGER_LABEL: ${{ vars.HARNESS_TRIGGER_LABEL }}
|
|
113
|
+
HARNESS_TRIGGER_LABEL: ${{ vars.HARNESS_TRIGGER_LABEL || 'sdlc-harness' }}
|
|
113
114
|
HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
|
|
114
115
|
steps:
|
|
115
116
|
- name: Check out the default branch
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
The project-command wrapper scripts `init` writes into the configured `scriptsDir`: type-check, test and dev-server, configured through `commands.*` in `harness.config.json`, and deploy, configured through `deploy.command` on the separate `deploy` object — so no build tool and no hosting provider is hardcoded anywhere in the harness. They exist as scripts rather than as raw command lines because an unattended run's permission profile can allow-list a literal script path far more safely than an arbitrary command, which is why `commands.<key>` holds the **wrapper invocation** `bash <scriptsDir>/<name>.sh` — the literal the profile allow-lists — while the **raw** command line lives inside the script. Which raw line that is follows one precedence, and it is what keeps the pair from being circular: a raw command line already sitting in `commands.<key>` wins, because an adopter who edited it there meant it — and that state is **reported** rather than silently blessed, since the raw line is not the value the permission profile allow-lists: `init` names it as it writes the wrapper from it, `config set` names it as the value is stored, and `doctor`'s `command-wrappers` check grades it on every later run; otherwise, when that key holds this wrapper's own invocation — the normal state after a first `init` — the body is the line stack detection produced; and when neither resolves, the body names what to fix — the key to set, or for `deploy`, which detection never supplies, this script itself — and exits non-zero rather than appearing to have run a check it never ran. Two states get no wrapper at all, and therefore no allow entry: a key still holding the placeholder `init` writes for a command it could not detect, and `commands.typecheck` holding the `<none>` sentinel — the first is unfinished and is the one that asks the adopter to do something about it, the second is the answer that this repository has no such command and asks for nothing. **Each script prints its own verdict line**, so a caller never appends an exit-code probe to it: that compound form is exactly what stalls an unattended run on a permission prompt — except `start-dev-server.sh`, which has no verdict to give because it starts a long-running process. Every wrapper anchors itself to its own checkout before it runs and forwards whatever arguments it was given to the **last** command of its raw line — whether that command accepts a bare path or name filter is a property of the command, not of the wrapper, so a line ending in a sub-command's own flags takes none; `docs/cli.md` §5 is the full contract.
|
|
4
4
|
|
|
5
|
-
**The other family in this directory is not generated.** The run watcher, its restart wrapper, the notifier and its stream formatter, the commit / push / branch-refresh / worktree / cleanup wrappers, the scratch runner, the flow walker `flow-walker.sh` with its gate library `lib/flow-walker-gates.sh` and the planning flow's graph `flows/task_plan_writing.graph.json`, the test-suite runner `run-test-suite.sh`, the docs-retrieval server launcher, `remote-run.sh` (sends a remote run's dispatch, pause, warm-up and stop to GitHub; run by the watcher, the remote job and a person) and the shared library at `lib/harness-run-lib.sh` are copied byte for byte into the same `scriptsDir`, because each reads `harness.config.json` *at run time* rather than carrying a value frozen in when `init` ran — a guard whose protected-branch set was baked into a file at generation time enforces the wrong set the moment that list changes, and does it silently. They therefore carry no `{{token}}` at all. `cli/src/generators/outerLoopScripts.ts` declares that set and marks which of its rows an agent may invoke; `cli/scripts/README.md` records why they live under `scriptsDir` rather than in the installed package. **One of them runs a file it is given:** `scratch-run.sh` runs an agent's language probe or mutation check in the interpreter that file's extension names, and refuses any argument that does not resolve inside `<state_dir>/scratch/`. **Two of them are run by the orchestrating session:** `flow-walker.sh` prints the next routing step of the planning flow and keeps machine-local state at `<state_dir>/.flow_walker_state`; `run-test-suite.sh` is run once per Run gates phase, runs the configured test command, writes its output to a per-round log under `<state_dir>/test_run_logs/` and prints only `pass` or `fail <log path>`. **One of them is started by neither the watcher nor an agent:** `docs-search-server.sh` is started by the agent runner from `.mcp.json` when `docs.retrieval` is on, and `exec`s the machine-shared retrieval runtime's `docs serve
|
|
5
|
+
**The other family in this directory is not generated.** The run watcher, its restart wrapper, the notifier and its stream formatter, the commit / push / branch-refresh / worktree / cleanup wrappers, the scratch runner, the flow walker `flow-walker.sh` with its gate library `lib/flow-walker-gates.sh` and the planning flow's graph `flows/task_plan_writing.graph.json`, the test-suite runner `run-test-suite.sh`, the docs-retrieval server launcher, `remote-run.sh` (sends a remote run's dispatch, pause, warm-up and stop to GitHub; run by the watcher, the remote job and a person) and the shared library at `lib/harness-run-lib.sh` are copied byte for byte into the same `scriptsDir`, because each reads `harness.config.json` *at run time* rather than carrying a value frozen in when `init` ran — a guard whose protected-branch set was baked into a file at generation time enforces the wrong set the moment that list changes, and does it silently. They therefore carry no `{{token}}` at all. `cli/src/generators/outerLoopScripts.ts` declares that set and marks which of its rows an agent may invoke; `cli/scripts/README.md` records why they live under `scriptsDir` rather than in the installed package. **One of them runs a file it is given:** `scratch-run.sh` runs an agent's language probe or mutation check in the interpreter that file's extension names, and refuses any argument that does not resolve inside `<state_dir>/scratch/`. **Two of them are run by the orchestrating session:** `flow-walker.sh` prints the next routing step of the planning flow and keeps machine-local state at `<state_dir>/.flow_walker_state`; `run-test-suite.sh` is run once per Run gates phase, runs the configured test command, writes its output to a per-round log under `<state_dir>/test_run_logs/` and prints only `pass` or `fail <log path>`. **One of them is started by neither the watcher nor an agent:** `docs-search-server.sh` is started by the agent runner from `.mcp.json` when `docs.retrieval` is on, and starts the backend `docs.retrievalBackend` selects: it `exec`s the machine-shared retrieval runtime's `docs serve` by default, or runs the Python package's `harness-docs-retrieval serve-mcp` when the key is `python` (its header carries the full exit contract).
|
|
@@ -376,7 +376,12 @@
|
|
|
376
376
|
# and the next job no `resumed` for it: a chained continuation is not an
|
|
377
377
|
# event the user acts on. classify_run_exit's `paused` arm notifies only a
|
|
378
378
|
# `user` pause; job mode sends the others once it has decided. Their titles
|
|
379
|
-
# carry HARNESS_REMOTE_SLUG when set.
|
|
379
|
+
# carry HARNESS_REMOTE_SLUG when set. Each job-mode event except `failed` is
|
|
380
|
+
# also passed to `remote-run.sh report` as it happens, and `failed` once,
|
|
381
|
+
# when the job ends `failed`; `report` comments on the run's pull request or
|
|
382
|
+
# issue and moves its state label when `forge` is `github`; `autonomous-notify.sh`
|
|
383
|
+
# is unchanged, and `completed` is reported by the workflow's `deliver` step
|
|
384
|
+
# after the push.
|
|
380
385
|
# * THE INTERACTIVE-TEST PHASE IS SKIPPED, not run: a runner has no browser
|
|
381
386
|
# wiring, application dependencies or QA credentials for it. With
|
|
382
387
|
# `phases.qa` true, the task and user_review launch prompts gain one clause
|
|
@@ -1150,12 +1155,33 @@ unset USAGE_LANE_ENABLED_RETIRED
|
|
|
1150
1155
|
# Every lifecycle event goes out through here, so a notifier that is missing or
|
|
1151
1156
|
# not executable costs one log line instead of ending a pass. Best-effort by
|
|
1152
1157
|
# contract: the notifier itself never fails its caller.
|
|
1158
|
+
# JOB MODE ONLY: the event is also passed to `remote-run.sh report` via
|
|
1159
|
+
# job_report. `failed` is not reported here: run_job reports it once its
|
|
1160
|
+
# automatic resumes are ruled out.
|
|
1153
1161
|
notify() {
|
|
1154
1162
|
if [ ! -x "$NOTIFY" ]; then
|
|
1155
1163
|
log "notify: '$NOTIFY' is not executable — '${1:-?}' event for '${2:-?}' not delivered"
|
|
1164
|
+
else
|
|
1165
|
+
"$NOTIFY" "$@" || true
|
|
1166
|
+
fi
|
|
1167
|
+
[ "$JOB_MODE" = 1 ] || return 0
|
|
1168
|
+
[ "${1:-}" != "failed" ] || return 0
|
|
1169
|
+
job_report "$1" "$2" "${3:-}"
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
# job_report <event> <branch> [<log>] — the only route to GitHub. The detail is
|
|
1173
|
+
# withheld: it names local slash commands, and `report` words its comment from
|
|
1174
|
+
# the registry record itself.
|
|
1175
|
+
job_report() {
|
|
1176
|
+
if [ ! -r "$REMOTE_RUN" ]; then
|
|
1177
|
+
log "notify: '$REMOTE_RUN' is not readable — '${1:-?}' event for '${2:-?}' not reported on GitHub"
|
|
1156
1178
|
return 0
|
|
1157
1179
|
fi
|
|
1158
|
-
"$
|
|
1180
|
+
if [ -n "${3:-}" ]; then
|
|
1181
|
+
bash "$REMOTE_RUN" report "$1" "$2" --repo "$MAIN_REPO" >>"$3" 2>&1 || true
|
|
1182
|
+
else
|
|
1183
|
+
bash "$REMOTE_RUN" report "$1" "$2" --repo "$MAIN_REPO" >/dev/null 2>&1 || true
|
|
1184
|
+
fi
|
|
1159
1185
|
}
|
|
1160
1186
|
|
|
1161
1187
|
# -----------------------------------------------------------------------------
|
|
@@ -4222,6 +4248,7 @@ run_job() {
|
|
|
4222
4248
|
# A reason recorded for a pause the run finished before honouring.
|
|
4223
4249
|
[ "$final" = "paused" ] || registry_set "$branch" pause_reason ""
|
|
4224
4250
|
[ -n "$detail" ] || detail="the run ended $final in this job"
|
|
4251
|
+
[ "$final" != "failed" ] || job_report failed "$branch" "$log_path"
|
|
4225
4252
|
job_write_status "$branch" "$remote_status" "$decision" "$detail"
|
|
4226
4253
|
echo "job: $final $decision"
|
|
4227
4254
|
exit 0
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# docs-search-server.sh — start the docs-retrieval MCP server for the checkout
|
|
3
|
-
# this script sits in,
|
|
4
|
-
# `docs serve`.
|
|
3
|
+
# this script sits in, on the backend `docs.retrievalBackend` selects.
|
|
5
4
|
#
|
|
6
5
|
# WHO RUNS IT. The agent runner, from the `harness-docs` entry `init` writes into
|
|
7
6
|
# `.mcp.json` when `docs.retrieval` is on — never a dispatched agent's Bash call,
|
|
@@ -9,29 +8,52 @@
|
|
|
9
8
|
# `agentInvocable: false` and the generated permission profile carries no entry
|
|
10
9
|
# for it.
|
|
11
10
|
#
|
|
12
|
-
# STDOUT BELONGS TO THE MCP TRANSPORT. Nothing here may print to stdout
|
|
13
|
-
#
|
|
14
|
-
#
|
|
11
|
+
# STDOUT BELONGS TO THE MCP TRANSPORT. Nothing here may print to stdout: a stray
|
|
12
|
+
# byte there is a malformed frame for the client. Every diagnostic goes to
|
|
13
|
+
# stderr.
|
|
15
14
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
15
|
+
# WHICH SERVER. Two backends, chosen by `docs.retrievalBackend` read at run
|
|
16
|
+
# time, with no fallback between them and no fallback to any other
|
|
17
|
+
# installation. The key is read ONLY WHEN `phases.docs` and `docs.retrieval` are
|
|
18
|
+
# both `true` (`hr_docs_retrieval_applies`, the shell mirror of
|
|
19
|
+
# `retrievalApplies`): `init` merges `.mcp.json` and removes nothing, so this
|
|
20
|
+
# entry outlives turning retrieval off, and with the gate closed the TypeScript
|
|
21
|
+
# runtime is `exec`ed whatever the key holds. `typescript`, or the key absent,
|
|
22
|
+
# `exec`s the machine-shared runtime's `docs serve`; `doctor`'s
|
|
23
|
+
# `retrieval-dependencies` check and `init`'s install both key on the same entry
|
|
24
|
+
# file this script tests. `python` starts the Python package's stdio server.
|
|
25
|
+
# This script checks no database and runs no `self-check`: grading the backend's
|
|
26
|
+
# prerequisites is `doctor`'s. `PATH` is settled through the library's fallback
|
|
27
|
+
# list first, because the starter is the agent runner rather than a login shell.
|
|
28
|
+
#
|
|
29
|
+
# WHY THE PYTHON BRANCH IS NOT `exec`ED. Its failure must surface as exit 3,
|
|
30
|
+
# which an `exec` could not report. It runs as a child with stdin passed
|
|
31
|
+
# explicitly, and a `TERM` or an `INT` the launcher receives is passed on to it
|
|
32
|
+
# as `TERM`, the signal the server stops on: bash starts a background child with
|
|
33
|
+
# `SIGINT` ignored when job control is off, and Python installs no handler over
|
|
34
|
+
# an ignored `SIGINT`, so an `INT` passed on as itself would never arrive.
|
|
22
35
|
#
|
|
23
36
|
# MIRRORS — a change to any owner below is an edit here too:
|
|
24
37
|
# `retrieval/runtime` and `node_modules/autonomous-sdlc-harness/dist/cli.js`
|
|
25
38
|
# mirror `RETRIEVAL_CACHE_DIRNAME`, `RETRIEVAL_RUNTIME_DIRNAME` and
|
|
26
39
|
# `RUNTIME_CLI_RELATIVE` in `cli/src/retrieval/runtime.ts`;
|
|
27
|
-
# `hr_cache_dir` mirrors `machineCacheDir()` in `cli/src/machine/paths.ts
|
|
40
|
+
# `hr_cache_dir` mirrors `machineCacheDir()` in `cli/src/machine/paths.ts`;
|
|
41
|
+
# `harness-docs-retrieval`, `serve-mcp`, `HARNESS_DOCS_RETRIEVAL_DATABASE_URL`,
|
|
42
|
+
# the default connection string and exit 3 mirror `PYTHON_RETRIEVAL_COMMAND`,
|
|
43
|
+
# `PYTHON_SERVE_SUB_COMMAND`, `PYTHON_DATABASE_URL_VARIABLE`,
|
|
44
|
+
# `PYTHON_DEFAULT_DATABASE_URL` and `PYTHON_BACKEND_UNAVAILABLE_EXIT` in
|
|
45
|
+
# `cli/src/retrieval/pythonBackend.ts`.
|
|
28
46
|
#
|
|
29
47
|
# Exit contract:
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
48
|
+
# 0 the Python backend exited 0
|
|
49
|
+
# 1 no repository at this script's location; `docs.retrievalBackend`
|
|
50
|
+
# outside the enum; or, on the TypeScript backend, no runtime entry at
|
|
51
|
+
# the resolved path or no cache directory to resolve it under; stderr
|
|
52
|
+
# names which
|
|
53
|
+
# 3 the Python backend is selected but cannot serve: its command does not
|
|
54
|
+
# resolve on `PATH`, or it exited non-zero; stderr names which
|
|
55
|
+
# N on the TypeScript backend, `docs serve`'s own status, through `exec`; a
|
|
56
|
+
# failure to source the library exits non-zero before that, on stderr only
|
|
35
57
|
|
|
36
58
|
set -euo pipefail
|
|
37
59
|
|
|
@@ -50,6 +72,55 @@ if ! root="$(hr_repo_root "$script_dir")"; then
|
|
|
50
72
|
exit 1
|
|
51
73
|
fi
|
|
52
74
|
|
|
75
|
+
backend=typescript
|
|
76
|
+
config_status=0
|
|
77
|
+
hr_config_load "$root" || config_status=$?
|
|
78
|
+
if [ "$config_status" -eq 2 ]; then
|
|
79
|
+
echo "docs-search-server: $root/harness.config.json could not be read, so the default backend (typescript) was taken" >&2
|
|
80
|
+
elif [ "$config_status" -eq 0 ] && hr_docs_retrieval_applies "$root"; then
|
|
81
|
+
backend_status=0
|
|
82
|
+
backend="$(hr_docs_retrieval_backend "$root")" || backend_status=$?
|
|
83
|
+
if [ "$backend_status" -ne 0 ]; then
|
|
84
|
+
echo "docs-search-server: docs.retrievalBackend in $root/harness.config.json is neither \`typescript\` nor \`python\`; set one of them, then run \`npx autonomous-sdlc-harness doctor\`" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [ "$backend" = python ]; then
|
|
90
|
+
if ! command -v harness-docs-retrieval >/dev/null 2>&1; then
|
|
91
|
+
echo "docs-search-server: the Python backend is selected but \`harness-docs-retrieval\` does not resolve on PATH (interpreter or package missing); see docs/retrieval.md and run \`npx autonomous-sdlc-harness doctor\`" >&2
|
|
92
|
+
exit 3
|
|
93
|
+
fi
|
|
94
|
+
if [ -z "${HARNESS_DOCS_RETRIEVAL_DATABASE_URL-}" ]; then
|
|
95
|
+
HARNESS_DOCS_RETRIEVAL_DATABASE_URL='postgresql://harness:harness@127.0.0.1:5432/docs_retrieval'
|
|
96
|
+
fi
|
|
97
|
+
export HARNESS_DOCS_RETRIEVAL_DATABASE_URL
|
|
98
|
+
|
|
99
|
+
# A background job's stdin is /dev/null unless it is passed explicitly.
|
|
100
|
+
harness-docs-retrieval serve-mcp --repo "$root" <&0 &
|
|
101
|
+
child=$!
|
|
102
|
+
trapped=0
|
|
103
|
+
trap 'trapped=1; kill -TERM "$child" 2>/dev/null || true' TERM
|
|
104
|
+
trap 'trapped=1; kill -TERM "$child" 2>/dev/null || true' INT
|
|
105
|
+
|
|
106
|
+
# A trapped signal interrupts `wait` before the child's status is collected, so wait again. 127
|
|
107
|
+
# means the first `wait` had already collected it.
|
|
108
|
+
child_status=0
|
|
109
|
+
wait "$child" || child_status=$?
|
|
110
|
+
while [ "$trapped" -eq 1 ]; do
|
|
111
|
+
trapped=0
|
|
112
|
+
again=0
|
|
113
|
+
wait "$child" 2>/dev/null || again=$?
|
|
114
|
+
[ "$again" -eq 127 ] || child_status=$again
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
if [ "$child_status" -eq 0 ]; then
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
echo "docs-search-server: the Python backend exited with status $child_status before or while serving; the line above (\`harness-docs-retrieval: …\`) names why; run \`npx autonomous-sdlc-harness doctor\`" >&2
|
|
121
|
+
exit 3
|
|
122
|
+
fi
|
|
123
|
+
|
|
53
124
|
if ! cache_dir="$(hr_cache_dir)"; then
|
|
54
125
|
echo "docs-search-server: neither XDG_CACHE_HOME nor HOME is set, so the retrieval runtime cannot be located" >&2
|
|
55
126
|
exit 1
|