autonomous-sdlc-harness 0.1.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 (171) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +7 -0
  3. package/README.md +24 -0
  4. package/dist/cli.js +194 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/config.js +561 -0
  7. package/dist/commands/config.js.map +1 -0
  8. package/dist/commands/daemon.js +791 -0
  9. package/dist/commands/daemon.js.map +1 -0
  10. package/dist/commands/doctor.js +336 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/init.js +2023 -0
  13. package/dist/commands/init.js.map +1 -0
  14. package/dist/commands/registry.js +42 -0
  15. package/dist/commands/registry.js.map +1 -0
  16. package/dist/config/check.js +505 -0
  17. package/dist/config/check.js.map +1 -0
  18. package/dist/config/io.js +177 -0
  19. package/dist/config/io.js.map +1 -0
  20. package/dist/config/model.js +406 -0
  21. package/dist/config/model.js.map +1 -0
  22. package/dist/core/errors.js +71 -0
  23. package/dist/core/errors.js.map +1 -0
  24. package/dist/core/git.js +537 -0
  25. package/dist/core/git.js.map +1 -0
  26. package/dist/core/json.js +125 -0
  27. package/dist/core/json.js.map +1 -0
  28. package/dist/core/layerCoverage.js +141 -0
  29. package/dist/core/layerCoverage.js.map +1 -0
  30. package/dist/core/layerGapRemedy.js +62 -0
  31. package/dist/core/layerGapRemedy.js.map +1 -0
  32. package/dist/core/nameList.js +23 -0
  33. package/dist/core/nameList.js.map +1 -0
  34. package/dist/core/paths.js +153 -0
  35. package/dist/core/paths.js.map +1 -0
  36. package/dist/core/prompt.js +206 -0
  37. package/dist/core/prompt.js.map +1 -0
  38. package/dist/core/repoPaths.js +55 -0
  39. package/dist/core/repoPaths.js.map +1 -0
  40. package/dist/core/report.js +150 -0
  41. package/dist/core/report.js.map +1 -0
  42. package/dist/core/templating.js +88 -0
  43. package/dist/core/templating.js.map +1 -0
  44. package/dist/core/writer.js +479 -0
  45. package/dist/core/writer.js.map +1 -0
  46. package/dist/daemon/backend.js +180 -0
  47. package/dist/daemon/backend.js.map +1 -0
  48. package/dist/daemon/units.js +380 -0
  49. package/dist/daemon/units.js.map +1 -0
  50. package/dist/detect/nestedApplication.js +79 -0
  51. package/dist/detect/nestedApplication.js.map +1 -0
  52. package/dist/detect/presets.js +2033 -0
  53. package/dist/detect/presets.js.map +1 -0
  54. package/dist/detect/signals.js +1368 -0
  55. package/dist/detect/signals.js.map +1 -0
  56. package/dist/doctor/checks.js +3530 -0
  57. package/dist/doctor/checks.js.map +1 -0
  58. package/dist/generators/claudeContext.js +588 -0
  59. package/dist/generators/claudeContext.js.map +1 -0
  60. package/dist/generators/githooks.js +446 -0
  61. package/dist/generators/githooks.js.map +1 -0
  62. package/dist/generators/harnessConfig.js +632 -0
  63. package/dist/generators/harnessConfig.js.map +1 -0
  64. package/dist/generators/notifications.js +191 -0
  65. package/dist/generators/notifications.js.map +1 -0
  66. package/dist/generators/outerLoopScripts.js +165 -0
  67. package/dist/generators/outerLoopScripts.js.map +1 -0
  68. package/dist/generators/permissionProfile.js +1172 -0
  69. package/dist/generators/permissionProfile.js.map +1 -0
  70. package/dist/generators/projectSettings.js +322 -0
  71. package/dist/generators/projectSettings.js.map +1 -0
  72. package/dist/generators/repoRoot.js +417 -0
  73. package/dist/generators/repoRoot.js.map +1 -0
  74. package/dist/generators/scripts.js +557 -0
  75. package/dist/generators/scripts.js.map +1 -0
  76. package/dist/generators/stateDir.js +221 -0
  77. package/dist/generators/stateDir.js.map +1 -0
  78. package/dist/machine/paths.js +111 -0
  79. package/dist/machine/paths.js.map +1 -0
  80. package/dist/machine/plugins.js +224 -0
  81. package/dist/machine/plugins.js.map +1 -0
  82. package/dist/machine/registry.js +330 -0
  83. package/dist/machine/registry.js.map +1 -0
  84. package/package.json +23 -0
  85. package/scripts/README.md +13 -0
  86. package/scripts/daemon/launchd.plist.template +59 -0
  87. package/scripts/daemon/systemd.service.template +58 -0
  88. package/templates/README.md +15 -0
  89. package/templates/claude/CLAUDE.md +54 -0
  90. package/templates/claude/README.md +5 -0
  91. package/templates/claude/context/api.md +29 -0
  92. package/templates/claude/context/conventions.md +23 -0
  93. package/templates/claude/context/data-layer.md +28 -0
  94. package/templates/claude/context/data-storage.md +29 -0
  95. package/templates/claude/context/docs-catalog.md +29 -0
  96. package/templates/claude/context/domain.md +28 -0
  97. package/templates/claude/context/layer.md +20 -0
  98. package/templates/claude/context/module.md +30 -0
  99. package/templates/claude/context/package.md +29 -0
  100. package/templates/claude/context/presentation.md +32 -0
  101. package/templates/claude/context/state-slices.md +28 -0
  102. package/templates/claude/context/tests.md +28 -0
  103. package/templates/claude/harness-task-offer.md +58 -0
  104. package/templates/claude/push-notify.env.example +21 -0
  105. package/templates/claude/qa-accounts.env.example +38 -0
  106. package/templates/claude/qa_test_scenarios.md +110 -0
  107. package/templates/claude/settings.autonomous.json +93 -0
  108. package/templates/claude/settings.autonomous.qa.json +36 -0
  109. package/templates/githooks/README.md +3 -0
  110. package/templates/githooks/pre-push +72 -0
  111. package/templates/repo/README.md +3 -0
  112. package/templates/repo/gitattributes +16 -0
  113. package/templates/repo/gitignore +61 -0
  114. package/templates/repo/gitignore.qa +25 -0
  115. package/templates/repo/mcp.json +17 -0
  116. package/templates/scripts/README.md +5 -0
  117. package/templates/scripts/autonomous-format-stream.sh +95 -0
  118. package/templates/scripts/autonomous-notify.sh +337 -0
  119. package/templates/scripts/autonomous-watcher.sh +3087 -0
  120. package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
  121. package/templates/scripts/commit-on-branch.sh +288 -0
  122. package/templates/scripts/create-worktree.sh +360 -0
  123. package/templates/scripts/deploy.sh +47 -0
  124. package/templates/scripts/lib/harness-run-lib.sh +1481 -0
  125. package/templates/scripts/push-branch.sh +140 -0
  126. package/templates/scripts/refresh-branch.sh +244 -0
  127. package/templates/scripts/restart-watcher.sh +401 -0
  128. package/templates/scripts/scratch-run.sh +302 -0
  129. package/templates/scripts/setup-worktree.sh +262 -0
  130. package/templates/scripts/start-dev-server.sh +99 -0
  131. package/templates/scripts/test.sh +50 -0
  132. package/templates/scripts/typecheck.sh +50 -0
  133. package/templates/state-dir/README-root.md +13 -0
  134. package/templates/state-dir/README.md +9 -0
  135. package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
  136. package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
  137. package/templates/state-dir/architecture_reviews/README.md +9 -0
  138. package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
  139. package/templates/state-dir/autonomous_inbox/README.md +9 -0
  140. package/templates/state-dir/autonomous_logs/README.md +9 -0
  141. package/templates/state-dir/branch_statistics/README.md +9 -0
  142. package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
  143. package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
  144. package/templates/state-dir/business_parity_reviews/README.md +9 -0
  145. package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
  146. package/templates/state-dir/clarification_digests/README.md +9 -0
  147. package/templates/state-dir/clarifications/README.md +9 -0
  148. package/templates/state-dir/code_reviews/README.md +9 -0
  149. package/templates/state-dir/dispatch_additions/README.md +19 -0
  150. package/templates/state-dir/docs_catalog/README.md +9 -0
  151. package/templates/state-dir/flow_progress/README.md +9 -0
  152. package/templates/state-dir/improvement_observations/README.md +19 -0
  153. package/templates/state-dir/improvement_suggestions.md +29 -0
  154. package/templates/state-dir/lessons.md +23 -0
  155. package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
  156. package/templates/state-dir/qa_reviews/README.md +9 -0
  157. package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
  158. package/templates/state-dir/review_plan_reviews/README.md +9 -0
  159. package/templates/state-dir/scratch/README.md +11 -0
  160. package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
  161. package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
  162. package/templates/state-dir/skeptic_reviews/README.md +9 -0
  163. package/templates/state-dir/story_plans/README.md +9 -0
  164. package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
  165. package/templates/state-dir/task_plan_reviews/README.md +9 -0
  166. package/templates/state-dir/task_plans/README.md +9 -0
  167. package/templates/state-dir/task_prompts/README.md +9 -0
  168. package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
  169. package/templates/state-dir/ui_test_plans/README.md +9 -0
  170. package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
  171. package/templates/state-dir/user_reviews/README.md +9 -0
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env bash
2
+ # create-worktree.sh — create the sibling working copy a run executes in, and
3
+ # bootstrap it by handing off to setup-worktree.sh.
4
+ #
5
+ # THE TWO MODES, AND WHEN EACH IS USED.
6
+ #
7
+ # default Start a NEW branch: fetch the configured default branch, add a
8
+ # worktree with `-b <branch>` off `origin/<default branch>`,
9
+ # bootstrap it, then PUSH the branch so the remote has it from
10
+ # the first moment. This is what the watcher calls when a prompt
11
+ # arrives for a branch that does not exist yet.
12
+ # --existing Check out an EXISTING branch — local, or DWIM-created from
13
+ # `origin/<branch>` when only the remote ref exists — bootstrap
14
+ # it, and NEVER push. This is what the watcher calls to recreate
15
+ # a working copy for a branch whose original one was removed (a
16
+ # merged-and-cleaned-up branch coming back for a follow-up run),
17
+ # and the asymmetry is the point: the branch already has a
18
+ # history somebody else may have advanced, so this mode reads the
19
+ # remote and never writes to it. It also never names the default
20
+ # branch.
21
+ #
22
+ # WHERE THE WORKTREE GOES. `<work_root>/<projectName>-<sanitized branch>`,
23
+ # derived by the shared library rather than assembled here — the SAME
24
+ # derivation the generated permission profile's sibling-worktree glob is
25
+ # materialized from, so a working copy this creates is one an unattended run's
26
+ # profile matches. The optional second argument overrides the directory, and an
27
+ # override is for a person: a path outside that derivation is not what the
28
+ # profile matches, so an unattended run passes none.
29
+ #
30
+ # WHY `set -e` IS RIGHT HERE, AND NOWHERE ELSE IN THIS SET. Every other script
31
+ # written into the scripts directory runs `set -uo pipefail` without `-e`,
32
+ # because a wrapper or a long-lived watcher that exits on the first non-zero
33
+ # probe stops doing its job. This one is a short, strictly ordered sequence in
34
+ # which every step is a precondition of the next: bootstrapping a half-created
35
+ # 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 —
37
+ # and the worktree it had already created is LEFT IN PLACE for inspection,
38
+ # because removing a working copy is a decision this script does not get to
39
+ # make silently.
40
+ #
41
+ # FAIL CLOSED ON A CONFIGURATION IT CANNOT READ. The directory name, the default
42
+ # branch and the bootstrap path all come from `harness.config.json` at run time,
43
+ # through `lib/harness-run-lib.sh`, and none of them has a remembered fallback:
44
+ # a guessed default branch would branch a run off the wrong history, and a
45
+ # guessed directory would create a working copy the permission profile does not
46
+ # match. An unreadable or absent configuration is therefore a refusal that
47
+ # creates nothing.
48
+ #
49
+ # WHAT IT NEVER DOES. It never pushes in `--existing` mode. It never deletes,
50
+ # empties, reuses or moves an existing directory, and never passes a force flag
51
+ # to `git worktree add`. It never checks out a branch that is already checked
52
+ # out somewhere — it names that working copy and refuses, because git would
53
+ # refuse anyway and a bare git error does not say what to do about it. It is run
54
+ # by the watcher or by a person, not by a dispatched agent, so it has no entry
55
+ # in the generated permission profile — a CALLING CONVENTION, NOT A GATE: its
56
+ # basename is not on the script-allowlist guard's `DENY_SCRIPT_BASENAMES`, and
57
+ # that guard grants independently of the profile, so an agent invoking this path
58
+ # under `scriptsDir` is auto-allowed today — but only in a PLAIN invocation:
59
+ # that guard's construct scan withholds the allow, silently, from any command
60
+ # carrying `$(…)` (a branch name taken from `$(git rev-parse …)`, say), a
61
+ # backtick, `|`, `<`, a braced form other than a bare `${IDENT}`, or a `>` that
62
+ # is neither a descriptor duplication (`2>&1`, `>&2`, `2>&-`) nor a redirection
63
+ # to the literal `/dev/null`.
64
+ #
65
+ # Usage: create-worktree.sh [--existing] <branch-name> [worktree-dir]
66
+ # --existing check out an existing branch instead of creating a new one
67
+ # <branch-name> the branch to run on
68
+ # [worktree-dir] optional override; relative paths resolve against $PWD
69
+ #
70
+ # Exit map a caller can switch on:
71
+ #
72
+ # 0 the worktree is ready (and, in default mode, the branch was pushed)
73
+ # 1 usage error / the library or the configuration could not be read
74
+ # 2 refusal — nothing was created: `--existing` and the branch is nowhere;
75
+ # the branch is already checked out in another working copy (which is
76
+ # named); or default mode with no `origin` remote, or no
77
+ # `origin/<default branch>` to branch from
78
+ # 4 the working copy was created but could not be bootstrapped — it has no
79
+ # dependency install and no pre-push backstop, is LEFT IN PLACE for
80
+ # inspection, and in default mode the branch was NOT pushed, because this
81
+ # exit precedes the push step. In default mode the local
82
+ # `refs/heads/<branch>` that `worktree add -b` created is left behind WITH
83
+ # the directory and is NOT deleted by `git worktree remove`, so `4` denotes
84
+ # two artefacts and a re-run that clears only the first refuses with git's
85
+ # own `a branch named … already exists`. What buys the reach is exiting
86
+ # NON-ZERO at all — the watcher branches on that at each of its three call
87
+ # sites, logs a `create-worktree.sh … failed for '<branch>'` line and
88
+ # archives the drop, which a line on stderr never reached. The distinct
89
+ # status is for a caller that wants to tell an un-bootstrapped working copy
90
+ # apart from a refusal that created nothing; no shipped caller does that yet
91
+ # * any other status is the failing `git` or bootstrap step's own (`set -e`);
92
+ # the worktree may exist and be un-bootstrapped. Kept deliberately broad
93
+ # when the remote cases moved up to 2: the remote is the common cause, not
94
+ # the only one, and every other step still surfaces as git's own status
95
+ #
96
+ # REPRO — reproduce any decision by hand, against a throwaway fixture:
97
+ #
98
+ # w=$(mktemp -d); b="$w/origin.git"; git init -q --bare "$b"
99
+ # d="$w/demo"; git init -q -b trunk "$d"; git -C "$d" remote add origin "$b"
100
+ # printf '%s' '{"version":1,"projectName":"demo","defaultBranch":"trunk","stateDir":"sdlc-harness/","layers":[],"commands":{"depInstall":"printf x > deps.marker"}}' > "$d/harness.config.json"
101
+ # mkdir -p "$d/scripts/lib" # copy this script + setup-worktree.sh + lib/ there
102
+ # git -C "$d" add -A && git -C "$d" commit -qm seed && git -C "$d" push -q -u origin trunk
103
+ #
104
+ # new branch bash "$d/scripts/create-worktree.sh" feat/x
105
+ # -> "$w/demo-feat-x" on feat/x, bootstrapped, and `git -C "$b"
106
+ # branch` now lists feat/x
107
+ # already there bash "$d/scripts/create-worktree.sh" --existing feat/x
108
+ # -> exit 2 naming "$w/demo-feat-x"; nothing created
109
+ # recreate git -C "$d" worktree remove "$w/demo-feat-x"
110
+ # bash "$d/scripts/create-worktree.sh" --existing feat/x
111
+ # -> re-created from origin/feat/x, and "$b" is UNCHANGED
112
+ # no remote git -C "$d" remote remove origin
113
+ # bash "$d/scripts/create-worktree.sh" feat/y
114
+ # -> exit 2 naming the missing remote; nothing created
115
+ # (git -C "$d" remote add origin "$b" restores the fixture)
116
+ # no base ref git -C "$b" update-ref -d refs/heads/trunk
117
+ # git -C "$d" update-ref -d refs/remotes/origin/trunk
118
+ # bash "$d/scripts/create-worktree.sh" feat/y
119
+ # -> exit 2 naming origin/trunk; the failed fetch is tolerated
120
+ # and the absent ref is what refuses
121
+ # (git -C "$d" push -q origin trunk restores the fixture)
122
+ # no bootstrap # drop it from the COMMITTED tree only, so the copy this
123
+ # # script runs from stays on disk — `--cached` is the point
124
+ # git -C "$d" rm -qr --cached scripts
125
+ # git -C "$d" commit -qm drop && git -C "$d" push -q origin trunk
126
+ # bash "$d/scripts/create-worktree.sh" feat/z
127
+ # -> exit 4 naming the path(s) tried; "$w/demo-feat-z" EXISTS,
128
+ # un-bootstrapped (no deps.marker), and `git -C "$b" branch`
129
+ # does NOT list feat/z
130
+ # (git -C "$d" add scripts, commit and push restores the
131
+ # fixture; clear BOTH artefacts first — git -C "$d" worktree
132
+ # remove "$w/demo-feat-z" && git -C "$d" branch -D feat/z —
133
+ # because `worktree remove` does not delete the local branch
134
+ # `add -b` made, and re-running without the second command
135
+ # exits on `a branch named 'feat/z' already exists`)
136
+ # unresolvable printf 'x' > "$d/harness.config.json"; either mode -> exit 1,
137
+ # nothing created
138
+
139
+ set -euo pipefail
140
+
141
+ # The library is reached by a path computed from this script's own location, so
142
+ # a copy that was moved with its `lib/` still finds it, and no session root or
143
+ # runtime-substituted token is assumed.
144
+ hr_lib="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/harness-run-lib.sh"
145
+ if [ ! -r "$hr_lib" ]; then
146
+ echo "create-worktree.sh: cannot read '$hr_lib' — refusing to create a worktree" >&2
147
+ exit 1
148
+ fi
149
+ # shellcheck source=lib/harness-run-lib.sh
150
+ . "$hr_lib"
151
+
152
+ usage() {
153
+ echo " usage: create-worktree.sh [--existing] <branch-name> [worktree-dir]" >&2
154
+ }
155
+
156
+ existing=0
157
+ if [ "${1:-}" = "--existing" ]; then
158
+ existing=1
159
+ shift
160
+ fi
161
+
162
+ if [ "$#" -lt 1 ] || [ -z "${1:-}" ]; then
163
+ echo "create-worktree.sh: no branch name given" >&2
164
+ usage
165
+ exit 1
166
+ fi
167
+ branch="$1"
168
+ shift
169
+
170
+ override="${1:-}"
171
+ if [ "$#" -gt 1 ]; then
172
+ echo "create-worktree.sh: unexpected extra arguments after the worktree directory" >&2
173
+ usage
174
+ exit 1
175
+ fi
176
+
177
+ # --- Anchors. Derived, never remembered: this script may be running in the main
178
+ # checkout or in any sibling working copy of the same repository. `pwd -P` rather
179
+ # than `pwd`, because `top` below is git's own physical toplevel and the two are
180
+ # subtracted from each other at the bootstrap resolution: a logical path taken
181
+ # through a symlinked ancestor would not strip, and the difference would be
182
+ # spelled as an absolute path inside the new working copy. (`hr_lib` above keeps
183
+ # a plain `pwd` — it only builds a path to source and never subtracts.)
184
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
185
+ top="$(hr_repo_root "$script_dir")" || top=""
186
+ if [ -z "$top" ]; then
187
+ echo "create-worktree.sh: '$script_dir' is not inside a git repository — refusing" >&2
188
+ exit 1
189
+ fi
190
+
191
+ # This script's own repo-relative directory — the bootstrap resolution's second
192
+ # spelling. Subtracted once, here, and refused if the strip did not land: an
193
+ # unstripped result is still absolute, and joining it to the new working copy
194
+ # would produce a path that concatenates two absolute ones rather than fail.
195
+ # Refused BEFORE anything is created, because the derivation is knowable now.
196
+ if [ "$script_dir" = "$top" ]; then
197
+ script_dir_rel="." # a `scriptsDir` of "." — the toplevel is not its own prefix
198
+ else
199
+ script_dir_rel="${script_dir#"$top"/}"
200
+ fi
201
+ case "$script_dir_rel" in
202
+ /*)
203
+ echo "create-worktree.sh: '$script_dir' is not under '$top' — cannot derive this checkout's repo-relative scripts path, refusing" >&2
204
+ exit 1
205
+ ;;
206
+ esac
207
+
208
+ # Every configured value is read from the MAIN checkout: it is where `init` ran,
209
+ # so its `projectName` is the stem the permission profile's worktree glob was
210
+ # materialized from, and its `defaultBranch` is the one the flow branches off.
211
+ # Reading them from a feature branch's own copy would let a branch move where
212
+ # the next run's working copy lands.
213
+ main_repo="$(hr_main_repo "$top")" || main_repo=""
214
+ [ -n "$main_repo" ] || main_repo="$top"
215
+
216
+ # Warm the library's per-process cache once, unsubstituted, and make the
217
+ # fail-closed decision here rather than letting each reader fail separately.
218
+ cfg_status=0
219
+ hr_config_load "$main_repo" || cfg_status=$?
220
+ if [ "$cfg_status" -ne 0 ]; then
221
+ echo "create-worktree.sh: cannot resolve '$main_repo/harness.config.json' — refusing to create a worktree" >&2
222
+ echo " (no configuration there, invalid JSON, more than one document, no defaultBranch, or jq missing/older than 1.5)" >&2
223
+ exit 1
224
+ fi
225
+
226
+ if [ -n "$override" ]; then
227
+ # `git -C <repo>` would resolve a relative path against the repository, not
228
+ # against the caller's directory; anchor it where the caller meant it.
229
+ case "$override" in
230
+ /*) worktree_dir="$override" ;;
231
+ *) worktree_dir="$PWD/$override" ;;
232
+ esac
233
+ else
234
+ worktree_dir="$(hr_worktree_dir "$main_repo" "$branch")" || worktree_dir=""
235
+ if [ -z "$worktree_dir" ]; then
236
+ echo "create-worktree.sh: could not derive the worktree directory for '$branch' — refusing" >&2
237
+ exit 1
238
+ fi
239
+ fi
240
+
241
+ if [ "$existing" -eq 1 ]; then
242
+ # Existing-branch mode: tolerate a failed fetch (offline) iff the local branch
243
+ # already exists; otherwise the branch must be on the remote.
244
+ fetch_ok=1
245
+ git -C "$main_repo" fetch origin "$branch" || fetch_ok=0
246
+ if ! git -C "$main_repo" show-ref --verify --quiet "refs/heads/$branch"; then
247
+ if [ "$fetch_ok" -ne 1 ] || ! git -C "$main_repo" show-ref --verify --quiet "refs/remotes/origin/$branch"; then
248
+ echo "create-worktree.sh: branch '$branch' is not local and not on origin — cannot check out an existing branch" >&2
249
+ exit 2
250
+ fi
251
+ fi
252
+
253
+ # A branch can only be checked out in ONE working copy. If it already is —
254
+ # typically in the main checkout after a supervised session — `worktree add`
255
+ # would fail with a message that does not say what to do, so detect it first
256
+ # and name the checkout. The porcelain list is parsed without splitting on
257
+ # whitespace, because a working copy's path may contain spaces.
258
+ existing_wt=""
259
+ wt_list="$(git -C "$main_repo" worktree list --porcelain)" || wt_list=""
260
+ current=""
261
+ while IFS= read -r line; do
262
+ case "$line" in
263
+ "worktree "*) current="${line#worktree }" ;;
264
+ "branch refs/heads/$branch")
265
+ existing_wt="$current"
266
+ break
267
+ ;;
268
+ esac
269
+ done <<EOF
270
+ $wt_list
271
+ EOF
272
+ if [ -n "$existing_wt" ]; then
273
+ echo "create-worktree.sh: branch '$branch' is already checked out at $existing_wt — refusing" >&2
274
+ echo " switch that checkout to another branch (or run there instead) and retry" >&2
275
+ exit 2
276
+ fi
277
+
278
+ # No `-b`: this checks out the existing local branch, or DWIM-creates a local
279
+ # branch tracking origin/<branch> when only the remote ref exists.
280
+ git -C "$main_repo" worktree add "$worktree_dir" "$branch"
281
+ else
282
+ default_branch="$(hr_default_branch "$main_repo")" || default_branch=""
283
+ if [ -z "$default_branch" ]; then
284
+ echo "create-worktree.sh: no defaultBranch in '$main_repo/harness.config.json' — refusing" >&2
285
+ exit 1
286
+ fi
287
+ # The working copy is branched FROM `origin/<default branch>`, so the remote
288
+ # is a precondition and not a step. Refuse both states `doctor`'s `remote`
289
+ # check grades, in this script's own voice: unguarded, `set -e` would end the
290
+ # run at four lines of git naming neither this script, nor a remedy, nor the
291
+ # check that would have said so first.
292
+ if ! git -C "$main_repo" remote get-url origin >/dev/null 2>&1; then
293
+ echo "create-worktree.sh: '$main_repo' has no 'origin' remote — a working copy is created from origin/$default_branch, so there is nothing to branch from" >&2
294
+ echo " add one: git remote add origin <url> && git push -u origin $default_branch" >&2
295
+ echo " (\`npx autonomous-sdlc-harness doctor\` reports this as a failing \`remote\` check)" >&2
296
+ exit 2
297
+ fi
298
+ # Tolerate a failed fetch the way the `--existing` arm above does — offline,
299
+ # with the ref already present, is still a working copy this can create — and
300
+ # judge the ref the next line names rather than the fetch. git's own fetch
301
+ # stderr still reaches the log above whichever outcome follows.
302
+ git -C "$main_repo" fetch origin "$default_branch" || true
303
+ if ! git -C "$main_repo" show-ref --verify --quiet "refs/remotes/origin/$default_branch"; then
304
+ echo "create-worktree.sh: '$main_repo' has an 'origin' remote but no origin/$default_branch to branch from" >&2
305
+ echo " push the default branch: git push -u origin $default_branch" >&2
306
+ echo " (\`npx autonomous-sdlc-harness doctor\` reports this as a failing \`remote\` check)" >&2
307
+ exit 2
308
+ fi
309
+ git -C "$main_repo" worktree add -b "$branch" "$worktree_dir" "origin/$default_branch"
310
+ fi
311
+
312
+ # --- Bootstrap by running the NEW checkout's own setup-worktree.sh, which is
313
+ # the single source for what bootstrapping means (it also installs the
314
+ # worktree-scoped pre-push backstop). Its scripts directory is read from the new
315
+ # checkout's configuration, falling back to this script's own repo-relative path
316
+ # when that configuration is unreadable; the resolution is then judged on the
317
+ # BOOTSTRAP PATH, and an unreadable one is retried once at this script's own
318
+ # repo-relative path.
319
+ #
320
+ # WHAT THE RETRY COVERS, AND WHAT IT DOES NOT. It covers a working copy whose
321
+ # configuration names a DIFFERENT `scriptsDir` than this checkout's — the only
322
+ # case in which the two spellings differ at all. It does NOT rescue a working
323
+ # copy that carries no scripts directory: both spellings then resolve inside the
324
+ # same absent tree, and that case is the refusal below, not a skip. The origin
325
+ # checkout's own copy is not run instead — setup-worktree.sh anchors on its own
326
+ # location and takes no arguments, so running it would bootstrap THIS checkout
327
+ # rather than the new one.
328
+ scripts_rel="$(hr_scripts_dir "$worktree_dir")" || scripts_rel=""
329
+ if [ -z "$scripts_rel" ]; then
330
+ scripts_rel="$script_dir_rel"
331
+ fi
332
+ bootstrap="$worktree_dir/$scripts_rel/setup-worktree.sh"
333
+ bootstrap_retry="$worktree_dir/$script_dir_rel/setup-worktree.sh"
334
+ if [ -r "$bootstrap" ]; then
335
+ bash "$bootstrap"
336
+ elif [ -r "$bootstrap_retry" ]; then
337
+ bash "$bootstrap_retry"
338
+ else
339
+ # LOUD, NOT A SKIPPED LINE ON stderr. An un-bootstrapped working copy cannot
340
+ # satisfy any commit gate, and a warning here reaches no operator; a non-zero
341
+ # status reaches the watcher log, which is what a caller acts on.
342
+ echo "create-worktree.sh: no bootstrap script at '$bootstrap'" >&2
343
+ if [ "$bootstrap_retry" != "$bootstrap" ]; then
344
+ echo " nor at '$bootstrap_retry' (this checkout's own repo-relative path)" >&2
345
+ fi
346
+ echo " the working copy at $worktree_dir was created but NOT bootstrapped: no dependency install, so it cannot satisfy any commit gate, and no pre-push backstop" >&2
347
+ echo " it is left in place for inspection, and this exit precedes the push step, so in default mode '$branch' was not pushed" >&2
348
+ echo " commit a scripts directory carrying setup-worktree.sh (and its lib/) on the base this copy was cut from, then clear BOTH things this exit left behind before re-creating: 'git -C $main_repo worktree remove $worktree_dir' and, in default mode, 'git -C $main_repo branch -D $branch' — the copy was created with 'worktree add -b', so the local branch outlives the directory and 'worktree remove' does not delete it; a re-run that skips the second command refuses with git's own \"a branch named '$branch' already exists\"" >&2
349
+ exit 4
350
+ fi
351
+
352
+ if [ "$existing" -eq 1 ]; then
353
+ echo "create-worktree.sh: worktree ready at $worktree_dir"
354
+ echo "create-worktree.sh: branch $branch (existing branch; not pushed)"
355
+ else
356
+ git -C "$worktree_dir" push -u origin "$branch"
357
+ echo "create-worktree.sh: worktree ready at $worktree_dir"
358
+ echo "create-worktree.sh: branch $branch (pushed to origin)"
359
+ fi
360
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # Written by `autonomous-sdlc-harness init`, and yours from there on: edit it freely, a re-run
3
+ # keeps your copy. It is what `deploy.command` invokes: it runs the raw deploy command line below
4
+ # from the repository root, forwards whatever arguments it was given to it — a target or
5
+ # environment selector — and prints one verdict line.
6
+
7
+ # Deliberately no `-e`: this script has to outlive its own command's failure long enough to
8
+ # print the verdict line below.
9
+ set -uo pipefail
10
+
11
+ # Exit with a diagnostic and no verdict line: nothing ran, so there is no result to report. A
12
+ # function rather than an inline `echo … && exit 1`, because the command line below is handed the
13
+ # caller's arguments and `exit` refuses extra ones — `init` writes a call to it as that line when
14
+ # no command line resolved.
15
+ harness_fail() {
16
+ echo "$1" >&2
17
+ exit 1
18
+ }
19
+
20
+ # Anchor to the repository root, derived from this script's own location and never from the
21
+ # caller's directory: `commands.*` are command lines run from the repository root and every path
22
+ # in `harness.config.json` is repo-relative. `git -C` on the script's own directory answers with
23
+ # the checkout this copy belongs to, so all three forms the permission profile emits — the
24
+ # repo-relative one, its absolute twin and a sibling worktree's own copy — run at the root of the
25
+ # checkout they were invoked out of, whatever directory the caller stood in.
26
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ repo_root="$(git -C "$script_dir" rev-parse --show-toplevel 2>/dev/null)"
28
+ if [ -z "$repo_root" ] || ! cd "$repo_root"; then
29
+ harness_fail "{{name}}: could not resolve a repository root from '${script_dir}' (is git on PATH?)"
30
+ fi
31
+
32
+ # Forward the caller's arguments to the command; with none, the command runs as configured.
33
+ # `"$@"` is safe under `set -u` even when empty. They attach to the *last* command of the line
34
+ # below, so keep that line one command rather than a compound — and whether that command accepts a
35
+ # bare path or name filter is a property of the command, not of this wrapper.
36
+ {{command}} "$@"
37
+ status=$?
38
+
39
+ # Exactly one verdict line, so no caller ever appends an exit-code probe to this script. That
40
+ # compound form is what stalls an unattended run on a permission prompt.
41
+ if [ "$status" -eq 0 ]; then
42
+ echo "PASS: {{name}}"
43
+ else
44
+ echo "FAIL: {{name}} (exit $status)"
45
+ fi
46
+
47
+ exit "$status"