autonomous-sdlc-harness 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/commands/init.js +113 -16
  2. package/dist/commands/init.js.map +1 -1
  3. package/dist/config/check.js +28 -6
  4. package/dist/config/check.js.map +1 -1
  5. package/dist/config/model.js +64 -5
  6. package/dist/config/model.js.map +1 -1
  7. package/dist/core/pluginIdentity.js +2 -0
  8. package/dist/core/pluginIdentity.js.map +1 -1
  9. package/dist/core/writer.js +10 -5
  10. package/dist/core/writer.js.map +1 -1
  11. package/dist/core/yamlScalar.js +14 -0
  12. package/dist/core/yamlScalar.js.map +1 -0
  13. package/dist/doctor/checks.js +454 -23
  14. package/dist/doctor/checks.js.map +1 -1
  15. package/dist/generators/githubWorkflows.js +125 -20
  16. package/dist/generators/githubWorkflows.js.map +1 -1
  17. package/dist/generators/repoRoot.js +17 -8
  18. package/dist/generators/repoRoot.js.map +1 -1
  19. package/dist/remote/githubActions.js +110 -7
  20. package/dist/remote/githubActions.js.map +1 -1
  21. package/dist/retrieval/pythonBackend.js +114 -0
  22. package/dist/retrieval/pythonBackend.js.map +1 -0
  23. package/dist/retrieval/setup.js +8 -0
  24. package/dist/retrieval/setup.js.map +1 -1
  25. package/package.json +1 -1
  26. package/templates/README.md +1 -1
  27. package/templates/github/workflows/harness-control.yml +184 -0
  28. package/templates/github/workflows/harness-resume.yml +9 -0
  29. package/templates/github/workflows/harness-run.yml +127 -12
  30. package/templates/github/workflows/harness-trigger.yml +144 -0
  31. package/templates/repo/gitignore +5 -0
  32. package/templates/scripts/README.md +1 -1
  33. package/templates/scripts/autonomous-watcher.sh +121 -249
  34. package/templates/scripts/create-worktree.sh +52 -10
  35. package/templates/scripts/docs-search-server.sh +88 -17
  36. package/templates/scripts/lib/harness-run-lib.sh +614 -14
  37. package/templates/scripts/remote-run.sh +3684 -144
  38. package/templates/scripts/scratch-run.sh +54 -73
  39. package/templates/state-dir/README-root.md +1 -1
  40. package/templates/state-dir/scratch/README.md +4 -2
  41. package/templates/state-dir/user_reviews/README.md +2 -2
@@ -9,6 +9,21 @@
9
9
  # bootstrap it, then PUSH the branch so the remote has it from
10
10
  # the first moment. This is what the watcher calls when a prompt
11
11
  # arrives for a branch that does not exist yet.
12
+ # --no-bootstrap Skip the bootstrap. Alone, a variant of default mode: cut
13
+ # and push the new branch the same way. With `--existing`, check
14
+ # out the existing branch and never push. Its callers are
15
+ # `remote-run.sh start` (a new branch) and `remote-run.sh review`
16
+ # (an existing one), each of which only places and commits one
17
+ # file: such a copy runs nothing, so a dependency install would
18
+ # cost minutes and could fail the placement for a reason
19
+ # unrelated to the task. It installs no worktree-scoped pre-push
20
+ # backstop — the push a caller makes is of a branch it has
21
+ # already judged not protected, and every later commit goes
22
+ # through `commit-on-branch.sh` and `push-branch.sh`, which refuse
23
+ # a protected branch themselves. An existing branch's copy that a
24
+ # person or a local run works in still wants the bootstrap, so
25
+ # `--existing` alone keeps it; `--no-bootstrap` is for a copy that
26
+ # only places and commits one file.
12
27
  # --existing Check out an EXISTING branch — local, or DWIM-created from
13
28
  # `origin/<branch>` when only the remote ref exists — bootstrap
14
29
  # it, and NEVER push. This is what the watcher calls to recreate
@@ -33,7 +48,8 @@
33
48
  # probe stops doing its job. This one is a short, strictly ordered sequence in
34
49
  # which every step is a precondition of the next: bootstrapping a half-created
35
50
  # worktree, or pushing a branch whose dependency install failed, is worse than
36
- # stopping. So a failing step aborts the script with that step's own status —
51
+ # stopping (a `--no-bootstrap` cut simply has no bootstrap step in its
52
+ # sequence). So a failing step aborts the script with that step's own status —
37
53
  # and the worktree it had already created is LEFT IN PLACE for inspection,
38
54
  # because removing a working copy is a decision this script does not get to
39
55
  # make silently.
@@ -62,20 +78,25 @@
62
78
  # is neither a descriptor duplication (`2>&1`, `>&2`, `2>&-`) nor a redirection
63
79
  # to the literal `/dev/null`.
64
80
  #
65
- # Usage: create-worktree.sh [--existing] <branch-name> [worktree-dir]
81
+ # Usage: create-worktree.sh [--existing] [--no-bootstrap] <branch-name> [worktree-dir]
66
82
  # --existing check out an existing branch instead of creating a new one
83
+ # --no-bootstrap skip the bootstrap: alone, create and push a new branch; with
84
+ # --existing, check out the existing branch and never push
67
85
  # <branch-name> the branch to run on
68
86
  # [worktree-dir] optional override; relative paths resolve against $PWD
69
87
  #
70
88
  # Exit map a caller can switch on:
71
89
  #
72
- # 0 the worktree is ready (and, in default mode, the branch was pushed)
90
+ # 0 the worktree is ready (and, in default mode or under `--no-bootstrap`
91
+ # without `--existing`, the branch was pushed)
73
92
  # 1 usage error / the library or the configuration could not be read
74
93
  # 2 refusal — nothing was created: `--existing` and the branch is nowhere;
75
94
  # the branch is already checked out in another working copy (which is
76
95
  # named); or default mode with no `origin` remote, or no
77
96
  # `origin/<default branch>` to branch from
78
- # 4 the working copy was created but could not be bootstrapped — it has no
97
+ # 4 the working copy was created but could not be bootstrapped (never under
98
+ # `--no-bootstrap`, with or without `--existing`, which resolves no
99
+ # bootstrap) — it has no
79
100
  # dependency install and no pre-push backstop, is LEFT IN PLACE for
80
101
  # inspection, and in default mode the branch was NOT pushed, because this
81
102
  # exit precedes the push step. In default mode the local
@@ -104,6 +125,13 @@
104
125
  # new branch bash "$d/scripts/create-worktree.sh" feat/x
105
126
  # -> "$w/demo-feat-x" on feat/x, bootstrapped, and `git -C "$b"
106
127
  # branch` now lists feat/x
128
+ # no-bootstrap bash "$d/scripts/create-worktree.sh" --no-bootstrap feat/q
129
+ # -> "$w/demo-feat-q" on feat/q, no deps.marker, and
130
+ # `git -C "$b" branch` lists feat/q
131
+ # existing, no bootstrap git -C "$d" worktree remove "$w/demo-feat-q"
132
+ # git -C "$d" branch -D feat/q
133
+ # bash "$d/scripts/create-worktree.sh" --existing --no-bootstrap feat/q
134
+ # -> "$w/demo-feat-q" on feat/q, no deps.marker, "$b" UNCHANGED
107
135
  # already there bash "$d/scripts/create-worktree.sh" --existing feat/x
108
136
  # -> exit 2 naming "$w/demo-feat-x"; nothing created
109
137
  # recreate git -C "$d" worktree remove "$w/demo-feat-x"
@@ -150,15 +178,18 @@ fi
150
178
  . "$hr_lib"
151
179
 
152
180
  usage() {
153
- echo " usage: create-worktree.sh [--existing] <branch-name> [worktree-dir]" >&2
181
+ echo " usage: create-worktree.sh [--existing] [--no-bootstrap] <branch-name> [worktree-dir]" >&2
154
182
  }
155
183
 
156
184
  existing=0
157
- if [ "${1:-}" = "--existing" ]; then
158
- existing=1
159
- shift
160
- fi
161
-
185
+ no_bootstrap=0
186
+ while [ "$#" -gt 0 ]; do
187
+ case "$1" in
188
+ --existing) existing=1; shift ;;
189
+ --no-bootstrap) no_bootstrap=1; shift ;;
190
+ *) break ;;
191
+ esac
192
+ done
162
193
  if [ "$#" -lt 1 ] || [ -z "${1:-}" ]; then
163
194
  echo "create-worktree.sh: no branch name given" >&2
164
195
  usage
@@ -325,6 +356,17 @@ fi
325
356
  # checkout's own copy is not run instead — setup-worktree.sh anchors on its own
326
357
  # location and takes no arguments, so running it would bootstrap THIS checkout
327
358
  # rather than the new one.
359
+ if [ "$no_bootstrap" -eq 1 ]; then
360
+ if [ "$existing" -eq 1 ]; then
361
+ echo "create-worktree.sh: worktree ready at $worktree_dir (not bootstrapped)"
362
+ echo "create-worktree.sh: branch $branch (existing branch; not pushed)"
363
+ else
364
+ git -C "$worktree_dir" push -u origin "$branch"
365
+ echo "create-worktree.sh: worktree ready at $worktree_dir (not bootstrapped)"
366
+ echo "create-worktree.sh: branch $branch (pushed to origin)"
367
+ fi
368
+ exit 0
369
+ fi
328
370
  scripts_rel="$(hr_scripts_dir "$worktree_dir")" || scripts_rel=""
329
371
  if [ -z "$scripts_rel" ]; then
330
372
  scripts_rel="$script_dir_rel"
@@ -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, by `exec`ing the machine-shared retrieval runtime's
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 before
13
- # the `exec`: a stray byte there is a malformed frame for the client. Every
14
- # diagnostic goes to stderr.
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
- # THE RUNTIME IS THE ONLY THING THIS RUNS, with no fallback to any other
17
- # installation: a committed `.mcp.json` cannot know where an adopter's own CLI
18
- # lives. `doctor`'s `retrieval-dependencies` check and `init`'s install both key
19
- # on the same entry file this script tests. `PATH` is settled through the
20
- # library's fallback list before the `exec`, because the starter is the agent
21
- # runner rather than a login shell.
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
- # 1 no runtime entry at the resolved path, no cache directory to resolve
31
- # it under, or no repository at this script's location; stderr names
32
- # which
33
- # N otherwise `docs serve`'s own status, through `exec`; a failure to
34
- # source the library exits non-zero before that, on stderr only
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