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.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +24 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +561 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.js +791 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.js +336 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +2023 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/check.js +505 -0
- package/dist/config/check.js.map +1 -0
- package/dist/config/io.js +177 -0
- package/dist/config/io.js.map +1 -0
- package/dist/config/model.js +406 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +537 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/json.js +125 -0
- package/dist/core/json.js.map +1 -0
- package/dist/core/layerCoverage.js +141 -0
- package/dist/core/layerCoverage.js.map +1 -0
- package/dist/core/layerGapRemedy.js +62 -0
- package/dist/core/layerGapRemedy.js.map +1 -0
- package/dist/core/nameList.js +23 -0
- package/dist/core/nameList.js.map +1 -0
- package/dist/core/paths.js +153 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt.js +206 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/repoPaths.js +55 -0
- package/dist/core/repoPaths.js.map +1 -0
- package/dist/core/report.js +150 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/templating.js +88 -0
- package/dist/core/templating.js.map +1 -0
- package/dist/core/writer.js +479 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/daemon/backend.js +180 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/units.js +380 -0
- package/dist/daemon/units.js.map +1 -0
- package/dist/detect/nestedApplication.js +79 -0
- package/dist/detect/nestedApplication.js.map +1 -0
- package/dist/detect/presets.js +2033 -0
- package/dist/detect/presets.js.map +1 -0
- package/dist/detect/signals.js +1368 -0
- package/dist/detect/signals.js.map +1 -0
- package/dist/doctor/checks.js +3530 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/generators/claudeContext.js +588 -0
- package/dist/generators/claudeContext.js.map +1 -0
- package/dist/generators/githooks.js +446 -0
- package/dist/generators/githooks.js.map +1 -0
- package/dist/generators/harnessConfig.js +632 -0
- package/dist/generators/harnessConfig.js.map +1 -0
- package/dist/generators/notifications.js +191 -0
- package/dist/generators/notifications.js.map +1 -0
- package/dist/generators/outerLoopScripts.js +165 -0
- package/dist/generators/outerLoopScripts.js.map +1 -0
- package/dist/generators/permissionProfile.js +1172 -0
- package/dist/generators/permissionProfile.js.map +1 -0
- package/dist/generators/projectSettings.js +322 -0
- package/dist/generators/projectSettings.js.map +1 -0
- package/dist/generators/repoRoot.js +417 -0
- package/dist/generators/repoRoot.js.map +1 -0
- package/dist/generators/scripts.js +557 -0
- package/dist/generators/scripts.js.map +1 -0
- package/dist/generators/stateDir.js +221 -0
- package/dist/generators/stateDir.js.map +1 -0
- package/dist/machine/paths.js +111 -0
- package/dist/machine/paths.js.map +1 -0
- package/dist/machine/plugins.js +224 -0
- package/dist/machine/plugins.js.map +1 -0
- package/dist/machine/registry.js +330 -0
- package/dist/machine/registry.js.map +1 -0
- package/package.json +23 -0
- package/scripts/README.md +13 -0
- package/scripts/daemon/launchd.plist.template +59 -0
- package/scripts/daemon/systemd.service.template +58 -0
- package/templates/README.md +15 -0
- package/templates/claude/CLAUDE.md +54 -0
- package/templates/claude/README.md +5 -0
- package/templates/claude/context/api.md +29 -0
- package/templates/claude/context/conventions.md +23 -0
- package/templates/claude/context/data-layer.md +28 -0
- package/templates/claude/context/data-storage.md +29 -0
- package/templates/claude/context/docs-catalog.md +29 -0
- package/templates/claude/context/domain.md +28 -0
- package/templates/claude/context/layer.md +20 -0
- package/templates/claude/context/module.md +30 -0
- package/templates/claude/context/package.md +29 -0
- package/templates/claude/context/presentation.md +32 -0
- package/templates/claude/context/state-slices.md +28 -0
- package/templates/claude/context/tests.md +28 -0
- package/templates/claude/harness-task-offer.md +58 -0
- package/templates/claude/push-notify.env.example +21 -0
- package/templates/claude/qa-accounts.env.example +38 -0
- package/templates/claude/qa_test_scenarios.md +110 -0
- package/templates/claude/settings.autonomous.json +93 -0
- package/templates/claude/settings.autonomous.qa.json +36 -0
- package/templates/githooks/README.md +3 -0
- package/templates/githooks/pre-push +72 -0
- package/templates/repo/README.md +3 -0
- package/templates/repo/gitattributes +16 -0
- package/templates/repo/gitignore +61 -0
- package/templates/repo/gitignore.qa +25 -0
- package/templates/repo/mcp.json +17 -0
- package/templates/scripts/README.md +5 -0
- package/templates/scripts/autonomous-format-stream.sh +95 -0
- package/templates/scripts/autonomous-notify.sh +337 -0
- package/templates/scripts/autonomous-watcher.sh +3087 -0
- package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
- package/templates/scripts/commit-on-branch.sh +288 -0
- package/templates/scripts/create-worktree.sh +360 -0
- package/templates/scripts/deploy.sh +47 -0
- package/templates/scripts/lib/harness-run-lib.sh +1481 -0
- package/templates/scripts/push-branch.sh +140 -0
- package/templates/scripts/refresh-branch.sh +244 -0
- package/templates/scripts/restart-watcher.sh +401 -0
- package/templates/scripts/scratch-run.sh +302 -0
- package/templates/scripts/setup-worktree.sh +262 -0
- package/templates/scripts/start-dev-server.sh +99 -0
- package/templates/scripts/test.sh +50 -0
- package/templates/scripts/typecheck.sh +50 -0
- package/templates/state-dir/README-root.md +13 -0
- package/templates/state-dir/README.md +9 -0
- package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
- package/templates/state-dir/architecture_reviews/README.md +9 -0
- package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
- package/templates/state-dir/autonomous_inbox/README.md +9 -0
- package/templates/state-dir/autonomous_logs/README.md +9 -0
- package/templates/state-dir/branch_statistics/README.md +9 -0
- package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
- package/templates/state-dir/clarification_digests/README.md +9 -0
- package/templates/state-dir/clarifications/README.md +9 -0
- package/templates/state-dir/code_reviews/README.md +9 -0
- package/templates/state-dir/dispatch_additions/README.md +19 -0
- package/templates/state-dir/docs_catalog/README.md +9 -0
- package/templates/state-dir/flow_progress/README.md +9 -0
- package/templates/state-dir/improvement_observations/README.md +19 -0
- package/templates/state-dir/improvement_suggestions.md +29 -0
- package/templates/state-dir/lessons.md +23 -0
- package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
- package/templates/state-dir/qa_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_reviews/README.md +9 -0
- package/templates/state-dir/scratch/README.md +11 -0
- package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_reviews/README.md +9 -0
- package/templates/state-dir/story_plans/README.md +9 -0
- package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/task_plan_reviews/README.md +9 -0
- package/templates/state-dir/task_plans/README.md +9 -0
- package/templates/state-dir/task_prompts/README.md +9 -0
- package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
- package/templates/state-dir/ui_test_plans/README.md +9 -0
- package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
- 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"
|