@moretea-labs/forge 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +125 -101
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +69 -29
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +150 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +815 -1015
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +3 -2
- package/src/runtime/root/package-runtime-service.ts +48 -3
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -1,705 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
3
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
|
|
6
|
-
:
|
|
7
|
-
elif [[ "$SCRIPT_DIR" == */.ai/harness/scripts ]]; then
|
|
8
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
9
|
-
else
|
|
10
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
-
fi
|
|
12
|
-
cd "$REPO_ROOT"
|
|
13
|
-
|
|
14
|
-
usage() {
|
|
15
|
-
cat <<'USAGE_EOF'
|
|
16
|
-
Usage:
|
|
17
|
-
scripts/contract-worktree.sh start --plan <plan-file> [--path <worktree-path>] [--branch <branch-name>]
|
|
18
|
-
scripts/contract-worktree.sh finish [--merge|--no-merge] [--target <branch>] [--message <commit-message>]
|
|
19
|
-
scripts/contract-worktree.sh cleanup --slug <slug> [--target <branch>] [--dry-run]
|
|
20
|
-
scripts/contract-worktree.sh status
|
|
21
|
-
USAGE_EOF
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
json_escape() {
|
|
25
|
-
local value="$1"
|
|
26
|
-
value="${value//\\/\\\\}"
|
|
27
|
-
value="${value//\"/\\\"}"
|
|
28
|
-
value="${value//$'\n'/\\n}"
|
|
29
|
-
value="${value//$'\r'/\\r}"
|
|
30
|
-
value="${value//$'\t'/\\t}"
|
|
31
|
-
printf '%s' "$value"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
policy_get() {
|
|
35
|
-
local jq_path="$1"
|
|
36
|
-
local default_value="${2:-}"
|
|
37
|
-
local value=""
|
|
38
|
-
|
|
39
|
-
if [[ -f ".ai/harness/policy.json" ]] && command -v jq >/dev/null 2>&1; then
|
|
40
|
-
value="$(jq -r "$jq_path // empty" ".ai/harness/policy.json" 2>/dev/null || true)"
|
|
41
|
-
if [[ -n "$value" ]]; then
|
|
42
|
-
printf '%s' "$value"
|
|
43
|
-
return 0
|
|
44
|
-
fi
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
printf '%s' "$default_value"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
check_architecture_freshness() {
|
|
51
|
-
local target_branch="$1"
|
|
52
|
-
local mode
|
|
53
|
-
|
|
54
|
-
if [[ -x "scripts/check-architecture-sync.sh" ]]; then
|
|
55
|
-
bash "scripts/check-architecture-sync.sh" --target "$target_branch"
|
|
56
|
-
return $?
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
mode="$(policy_get '.architecture.freshness_gate' 'advisory')"
|
|
60
|
-
if [[ "$mode" == "strict" ]]; then
|
|
61
|
-
echo "contract-worktree: strict architecture freshness gate failed: missing scripts/check-architecture-sync.sh" >&2
|
|
62
|
-
return 1
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
echo "contract-worktree: WARN missing scripts/check-architecture-sync.sh; skipping advisory architecture freshness gate" >&2
|
|
66
|
-
return 0
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
normalize_slug() {
|
|
70
|
-
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g'
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
ACTIVE_PLAN_MARKER=".ai/harness/active-plan"
|
|
74
|
-
LEGACY_ACTIVE_PLAN_MARKER=".claude/.active-plan"
|
|
75
|
-
ACTIVE_WORKTREE_MARKER=".ai/harness/active-worktree"
|
|
76
|
-
|
|
77
|
-
derive_slug_from_plan() {
|
|
78
|
-
local plan_file="$1"
|
|
79
|
-
local plan_base slug
|
|
80
|
-
plan_base="$(basename "$plan_file")"
|
|
81
|
-
slug="$(printf '%s' "$plan_base" | sed -E 's/^plan-[0-9]{8}-[0-9]{4}-//; s/\.md$//')"
|
|
82
|
-
normalize_slug "${slug:-contract-task}"
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
derive_original_artifact_stem_from_plan() {
|
|
86
|
-
local plan_file="$1"
|
|
87
|
-
local plan_base stem
|
|
88
|
-
plan_base="$(basename "$plan_file")"
|
|
89
|
-
stem="$(printf '%s' "$plan_base" | sed -E 's/^plan-//; s/\.md$//')"
|
|
90
|
-
if [[ "$stem" =~ ^[0-9]{8}-[0-9]{4}-.+ ]]; then
|
|
91
|
-
printf '%s' "$stem"
|
|
92
|
-
else
|
|
93
|
-
derive_slug_from_plan "$plan_file"
|
|
94
|
-
fi
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
is_transient_plan_slug() {
|
|
98
|
-
case "$1" in
|
|
99
|
-
think-plan-[0-9]*|codex-plan-[0-9]*|approved-plan-[0-9]*)
|
|
100
|
-
return 0
|
|
101
|
-
;;
|
|
102
|
-
esac
|
|
103
|
-
return 1
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
derive_title_slug_from_plan() {
|
|
107
|
-
local plan_file="$1"
|
|
108
|
-
local title slug
|
|
109
|
-
[[ -f "$plan_file" ]] || return 1
|
|
110
|
-
title="$(awk '
|
|
111
|
-
/^# Plan:[[:space:]]*/ {
|
|
112
|
-
sub(/^# Plan:[[:space:]]*/, "")
|
|
113
|
-
print
|
|
114
|
-
exit
|
|
115
|
-
}
|
|
116
|
-
' "$plan_file" | xargs)"
|
|
117
|
-
[[ -n "$title" ]] || return 1
|
|
118
|
-
slug="$(normalize_slug "$title")"
|
|
119
|
-
[[ -n "$slug" ]] || return 1
|
|
120
|
-
printf '%s' "$slug"
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
derive_artifact_stem_from_plan() {
|
|
124
|
-
local plan_file="$1"
|
|
125
|
-
local stem stamp slug title_slug
|
|
126
|
-
stem="$(derive_original_artifact_stem_from_plan "$plan_file")"
|
|
127
|
-
if [[ "$stem" =~ ^[0-9]{8}-[0-9]{4}-.+ ]]; then
|
|
128
|
-
stamp="$(printf '%s' "$stem" | sed -E 's/^([0-9]{8}-[0-9]{4})-.+$/\1/')"
|
|
129
|
-
slug="$(printf '%s' "$stem" | sed -E 's/^[0-9]{8}-[0-9]{4}-//')"
|
|
130
|
-
if is_transient_plan_slug "$slug"; then
|
|
131
|
-
title_slug="$(derive_title_slug_from_plan "$plan_file" || true)"
|
|
132
|
-
if [[ -n "$title_slug" && "$title_slug" != "$slug" ]]; then
|
|
133
|
-
printf '%s-%s' "$stamp" "$title_slug"
|
|
134
|
-
return 0
|
|
135
|
-
fi
|
|
136
|
-
fi
|
|
137
|
-
printf '%s' "$stem"
|
|
138
|
-
else
|
|
139
|
-
derive_slug_from_plan "$plan_file"
|
|
140
|
-
fi
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
is_linked_worktree() {
|
|
144
|
-
local git_dir
|
|
145
|
-
git_dir="$(git rev-parse --git-dir 2>/dev/null || true)"
|
|
146
|
-
[[ "$git_dir" == *".git/worktrees/"* ]]
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
find_worktree_for_branch() {
|
|
150
|
-
local branch="$1"
|
|
151
|
-
git worktree list --porcelain | awk -v branch_ref="refs/heads/${branch}" '
|
|
152
|
-
$1 == "worktree" { path = $2; next }
|
|
153
|
-
$1 == "branch" && $2 == branch_ref { print path; exit }
|
|
154
|
-
'
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
default_worktree_path() {
|
|
158
|
-
local slug="$1"
|
|
159
|
-
local parent repo_name
|
|
160
|
-
parent="$(dirname "$REPO_ROOT")"
|
|
161
|
-
repo_name="$(basename "$REPO_ROOT")"
|
|
162
|
-
printf '%s/%s-wt-%s' "$parent" "$repo_name" "$slug"
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
write_start_metadata() {
|
|
166
|
-
local slug="$1"
|
|
167
|
-
local plan_file="$2"
|
|
168
|
-
local branch_name="$3"
|
|
169
|
-
local worktree_path="$4"
|
|
170
|
-
local base_branch="$5"
|
|
171
|
-
local metadata_dir=".ai/harness/worktrees"
|
|
172
|
-
local metadata_file="${metadata_dir}/${slug}.json"
|
|
173
|
-
|
|
174
|
-
mkdir -p "$metadata_dir"
|
|
175
|
-
cat > "$metadata_file" <<EOF_METADATA
|
|
176
|
-
{
|
|
177
|
-
"slug": "$(json_escape "$slug")",
|
|
178
|
-
"plan": "$(json_escape "$plan_file")",
|
|
179
|
-
"branch": "$(json_escape "$branch_name")",
|
|
180
|
-
"worktree": "$(json_escape "$worktree_path")",
|
|
181
|
-
"source_repo": "$(json_escape "$REPO_ROOT")",
|
|
182
|
-
"base_branch": "$(json_escape "$base_branch")",
|
|
183
|
-
"started_at": "$(date '+%Y-%m-%dT%H:%M:%S%z')"
|
|
184
|
-
}
|
|
185
|
-
EOF_METADATA
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
copy_plan_into_worktree() {
|
|
189
|
-
local plan_file="$1"
|
|
190
|
-
local worktree_path="$2"
|
|
191
|
-
local target_plan="$worktree_path/$plan_file"
|
|
192
|
-
|
|
193
|
-
mkdir -p "$(dirname "$target_plan")"
|
|
194
|
-
cp "$plan_file" "$target_plan"
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
remove_copied_untracked_source_plan() {
|
|
198
|
-
local plan_file="$1"
|
|
199
|
-
local worktree_path="$2"
|
|
200
|
-
|
|
201
|
-
if git ls-files --others --exclude-standard -- "$plan_file" | grep -Fxq "$plan_file" \
|
|
202
|
-
&& cmp -s "$plan_file" "$worktree_path/$plan_file"; then
|
|
203
|
-
rm -f "$plan_file"
|
|
204
|
-
echo "[ContractWorktree] Moved untracked source plan into contract worktree: $plan_file"
|
|
205
|
-
fi
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
marker_points_to_plan() {
|
|
209
|
-
local marker_file="$1"
|
|
210
|
-
local plan_file="$2"
|
|
211
|
-
local marker_plan
|
|
212
|
-
|
|
213
|
-
[[ -f "$marker_file" ]] || return 1
|
|
214
|
-
marker_plan="$(cat "$marker_file" 2>/dev/null | xargs)"
|
|
215
|
-
[[ "$marker_plan" == "$plan_file" || "$marker_plan" == "./$plan_file" ]]
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
clear_primary_markers_for_transferred_plan() {
|
|
219
|
-
local plan_file="$1"
|
|
220
|
-
|
|
221
|
-
if marker_points_to_plan "$ACTIVE_PLAN_MARKER" "$plan_file" \
|
|
222
|
-
|| marker_points_to_plan "$LEGACY_ACTIVE_PLAN_MARKER" "$plan_file"; then
|
|
223
|
-
rm -f "$ACTIVE_PLAN_MARKER" "$LEGACY_ACTIVE_PLAN_MARKER" "$ACTIVE_WORKTREE_MARKER"
|
|
224
|
-
echo "[ContractWorktree] Cleared primary active markers for transferred plan: $plan_file"
|
|
225
|
-
fi
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
start_worktree() {
|
|
229
|
-
local plan_file=""
|
|
230
|
-
local worktree_path=""
|
|
231
|
-
local branch_name=""
|
|
232
|
-
local run_plan_to_todo=1
|
|
233
|
-
|
|
234
|
-
while [[ $# -gt 0 ]]; do
|
|
235
|
-
case "$1" in
|
|
236
|
-
--plan)
|
|
237
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --plan requires a value" >&2; exit 2; }
|
|
238
|
-
plan_file="${2#./}"
|
|
239
|
-
shift 2
|
|
240
|
-
;;
|
|
241
|
-
--path)
|
|
242
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --path requires a value" >&2; exit 2; }
|
|
243
|
-
worktree_path="$2"
|
|
244
|
-
shift 2
|
|
245
|
-
;;
|
|
246
|
-
--branch)
|
|
247
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --branch requires a value" >&2; exit 2; }
|
|
248
|
-
branch_name="$2"
|
|
249
|
-
shift 2
|
|
250
|
-
;;
|
|
251
|
-
--no-plan-to-todo)
|
|
252
|
-
run_plan_to_todo=0
|
|
253
|
-
shift
|
|
254
|
-
;;
|
|
255
|
-
--help|-h)
|
|
256
|
-
usage
|
|
257
|
-
exit 0
|
|
258
|
-
;;
|
|
259
|
-
*)
|
|
260
|
-
echo "contract-worktree: unknown start argument: $1" >&2
|
|
261
|
-
usage
|
|
262
|
-
exit 2
|
|
263
|
-
;;
|
|
264
|
-
esac
|
|
265
|
-
done
|
|
266
|
-
|
|
267
|
-
[[ -n "$plan_file" ]] || { echo "contract-worktree: start requires --plan" >&2; exit 2; }
|
|
268
|
-
[[ -f "$plan_file" ]] || { echo "contract-worktree: plan file not found: $plan_file" >&2; exit 2; }
|
|
269
|
-
|
|
270
|
-
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
271
|
-
echo "contract-worktree: not inside a git repository" >&2
|
|
272
|
-
exit 2
|
|
273
|
-
fi
|
|
274
|
-
|
|
275
|
-
if is_linked_worktree; then
|
|
276
|
-
echo "[ContractWorktree] Already in a linked worktree: $REPO_ROOT"
|
|
277
|
-
return 0
|
|
278
|
-
fi
|
|
279
|
-
|
|
280
|
-
local slug branch_prefix base_branch existing_worktree
|
|
281
|
-
slug="$(derive_slug_from_plan "$plan_file")"
|
|
282
|
-
branch_prefix="$(policy_get '.worktree_strategy.branch_prefix' 'codex/')"
|
|
283
|
-
base_branch="$(policy_get '.worktree_strategy.base_branch' 'main')"
|
|
284
|
-
branch_name="${branch_name:-${branch_prefix}${slug}}"
|
|
285
|
-
worktree_path="${worktree_path:-$(default_worktree_path "$slug")}"
|
|
286
|
-
|
|
287
|
-
existing_worktree="$(find_worktree_for_branch "$branch_name" || true)"
|
|
288
|
-
if [[ -n "$existing_worktree" ]]; then
|
|
289
|
-
worktree_path="$existing_worktree"
|
|
290
|
-
echo "[ContractWorktree] Reusing existing worktree: $worktree_path"
|
|
291
|
-
elif git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
|
292
|
-
git worktree add "$worktree_path" "$branch_name"
|
|
293
|
-
echo "[ContractWorktree] Added worktree for existing branch: $worktree_path"
|
|
294
|
-
else
|
|
295
|
-
git worktree add "$worktree_path" -b "$branch_name" HEAD
|
|
296
|
-
echo "[ContractWorktree] Created worktree: $worktree_path"
|
|
297
|
-
fi
|
|
298
|
-
|
|
299
|
-
copy_plan_into_worktree "$plan_file" "$worktree_path"
|
|
300
|
-
remove_copied_untracked_source_plan "$plan_file" "$worktree_path"
|
|
301
|
-
clear_primary_markers_for_transferred_plan "$plan_file"
|
|
302
|
-
|
|
303
|
-
mkdir -p "$worktree_path/.ai/harness/worktrees"
|
|
304
|
-
(
|
|
305
|
-
cd "$worktree_path"
|
|
306
|
-
write_start_metadata "$slug" "$plan_file" "$branch_name" "$worktree_path" "$base_branch"
|
|
307
|
-
if [[ "$run_plan_to_todo" -eq 1 && -f "scripts/plan-to-todo.sh" ]]; then
|
|
308
|
-
FORGE_CONTRACT_WORKTREE=1 bash "scripts/plan-to-todo.sh" --plan "$plan_file"
|
|
309
|
-
fi
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
echo "[ContractWorktree] Branch: $branch_name"
|
|
313
|
-
echo "[ContractWorktree] Plan: $worktree_path/$plan_file"
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
status_worktree() {
|
|
317
|
-
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
318
|
-
echo "[ContractWorktree] Not in a git repository"
|
|
319
|
-
return 0
|
|
320
|
-
fi
|
|
321
|
-
|
|
322
|
-
if is_linked_worktree; then
|
|
323
|
-
echo "[ContractWorktree] linked worktree"
|
|
324
|
-
else
|
|
325
|
-
echo "[ContractWorktree] primary worktree"
|
|
326
|
-
fi
|
|
327
|
-
|
|
328
|
-
echo "branch: $(git branch --show-current 2>/dev/null || true)"
|
|
329
|
-
echo "root: $REPO_ROOT"
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
is_local_runtime_marker_path() {
|
|
333
|
-
case "$1" in
|
|
334
|
-
.ai/harness/active-plan|.ai/harness/active-worktree|.claude/.active-plan)
|
|
335
|
-
return 0
|
|
336
|
-
;;
|
|
337
|
-
esac
|
|
338
|
-
return 1
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
check_scope_against_contract() {
|
|
342
|
-
local contract_file="$1"
|
|
343
|
-
local changed_paths path blocked=0
|
|
344
|
-
|
|
345
|
-
[[ -f "$contract_file" ]] || return 0
|
|
346
|
-
if [[ ! -f ".ai/hooks/lib/workflow-state.sh" ]]; then
|
|
347
|
-
return 0
|
|
348
|
-
fi
|
|
349
|
-
|
|
350
|
-
# shellcheck source=/dev/null
|
|
351
|
-
. ".ai/hooks/lib/workflow-state.sh"
|
|
352
|
-
|
|
353
|
-
changed_paths="$(
|
|
354
|
-
{
|
|
355
|
-
git -c core.quotePath=false diff --name-only
|
|
356
|
-
git -c core.quotePath=false diff --cached --name-only
|
|
357
|
-
git -c core.quotePath=false ls-files --others --exclude-standard
|
|
358
|
-
} | awk 'NF && !seen[$0]++'
|
|
359
|
-
)"
|
|
360
|
-
|
|
361
|
-
while IFS= read -r path; do
|
|
362
|
-
[[ -n "$path" ]] || continue
|
|
363
|
-
if is_local_runtime_marker_path "$path"; then
|
|
364
|
-
continue
|
|
365
|
-
fi
|
|
366
|
-
if ! workflow_contract_allows_path "$contract_file" "$path"; then
|
|
367
|
-
echo "[ContractWorktree] Changed path is outside active contract allowed_paths: $path" >&2
|
|
368
|
-
blocked=1
|
|
369
|
-
fi
|
|
370
|
-
done <<< "$changed_paths"
|
|
371
|
-
|
|
372
|
-
[[ "$blocked" -eq 0 ]]
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
clean_matching_untracked_target_files() {
|
|
376
|
-
local target_worktree="$1"
|
|
377
|
-
local source_branch="$2"
|
|
378
|
-
local path tmp_file
|
|
379
|
-
|
|
380
|
-
while IFS= read -r path; do
|
|
381
|
-
[[ -n "$path" ]] || continue
|
|
382
|
-
tmp_file="$(mktemp)"
|
|
383
|
-
if git -C "$target_worktree" show "${source_branch}:${path}" > "$tmp_file" 2>/dev/null \
|
|
384
|
-
&& cmp -s "$tmp_file" "$target_worktree/$path"; then
|
|
385
|
-
rm -f "$target_worktree/$path"
|
|
386
|
-
echo "[ContractWorktree] Removed matching untracked target file before merge: $path"
|
|
387
|
-
fi
|
|
388
|
-
rm -f "$tmp_file"
|
|
389
|
-
done < <(git -C "$target_worktree" ls-files --others --exclude-standard)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
clean_local_runtime_markers() {
|
|
393
|
-
rm -f .ai/harness/active-plan .ai/harness/active-worktree .claude/.active-plan
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
latest_plan_for_slug() {
|
|
397
|
-
local slug="$1"
|
|
398
|
-
local latest
|
|
399
|
-
latest="$(find plans -maxdepth 1 -type f -name "plan-*-${slug}.md" 2>/dev/null | sort | tail -1)"
|
|
400
|
-
[[ -n "$latest" ]] || return 1
|
|
401
|
-
printf '%s' "$latest"
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
archive_finished_workflow() {
|
|
405
|
-
local plan_file="$1"
|
|
406
|
-
|
|
407
|
-
[[ -n "$plan_file" ]] || { echo "contract-worktree: no active plan found to archive" >&2; exit 1; }
|
|
408
|
-
[[ -f "$plan_file" ]] || { echo "contract-worktree: active plan not found for archive: $plan_file" >&2; exit 1; }
|
|
409
|
-
[[ -x "scripts/archive-workflow.sh" ]] || { echo "contract-worktree: scripts/archive-workflow.sh is missing or not executable" >&2; exit 1; }
|
|
410
|
-
|
|
411
|
-
echo "[ContractWorktree] Archiving completed workflow before merge: $plan_file"
|
|
412
|
-
bash "scripts/archive-workflow.sh" --plan "$plan_file" --outcome Completed
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
finish_worktree() {
|
|
416
|
-
local merge_back=1
|
|
417
|
-
local target_branch
|
|
418
|
-
local commit_message=""
|
|
419
|
-
|
|
420
|
-
target_branch="$(policy_get '.worktree_strategy.merge_back.target' 'main')"
|
|
421
|
-
|
|
422
|
-
while [[ $# -gt 0 ]]; do
|
|
423
|
-
case "$1" in
|
|
424
|
-
--merge)
|
|
425
|
-
merge_back=1
|
|
426
|
-
shift
|
|
427
|
-
;;
|
|
428
|
-
--no-merge)
|
|
429
|
-
merge_back=0
|
|
430
|
-
shift
|
|
431
|
-
;;
|
|
432
|
-
--target)
|
|
433
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --target requires a value" >&2; exit 2; }
|
|
434
|
-
target_branch="$2"
|
|
435
|
-
shift 2
|
|
436
|
-
;;
|
|
437
|
-
--message|-m)
|
|
438
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --message requires a value" >&2; exit 2; }
|
|
439
|
-
commit_message="$2"
|
|
440
|
-
shift 2
|
|
441
|
-
;;
|
|
442
|
-
--help|-h)
|
|
443
|
-
usage
|
|
444
|
-
exit 0
|
|
445
|
-
;;
|
|
446
|
-
*)
|
|
447
|
-
echo "contract-worktree: unknown finish argument: $1" >&2
|
|
448
|
-
usage
|
|
449
|
-
exit 2
|
|
450
|
-
;;
|
|
451
|
-
esac
|
|
452
|
-
done
|
|
453
|
-
|
|
454
|
-
if ! is_linked_worktree; then
|
|
455
|
-
echo "contract-worktree: finish must run from the linked contract worktree" >&2
|
|
456
|
-
exit 1
|
|
457
|
-
fi
|
|
458
|
-
|
|
459
|
-
local current_branch slug active_plan contract_file target_worktree artifact_stem
|
|
460
|
-
current_branch="$(git branch --show-current)"
|
|
461
|
-
[[ -n "$current_branch" ]] || { echo "contract-worktree: detached HEAD is not supported" >&2; exit 1; }
|
|
462
|
-
[[ "$current_branch" != "$target_branch" ]] || { echo "contract-worktree: already on target branch $target_branch" >&2; exit 1; }
|
|
463
|
-
slug="$(normalize_slug "${current_branch##*/}")"
|
|
464
|
-
commit_message="${commit_message:-feat(contract): complete ${slug}}"
|
|
465
|
-
|
|
466
|
-
if [[ -f ".ai/hooks/lib/workflow-state.sh" ]]; then
|
|
467
|
-
# shellcheck source=/dev/null
|
|
468
|
-
. ".ai/hooks/lib/workflow-state.sh"
|
|
469
|
-
active_plan="$(get_active_plan || true)"
|
|
470
|
-
if [[ -n "$active_plan" ]]; then
|
|
471
|
-
contract_file="$(workflow_active_contract || true)"
|
|
472
|
-
fi
|
|
473
|
-
fi
|
|
474
|
-
|
|
475
|
-
if [[ -z "${active_plan:-}" ]]; then
|
|
476
|
-
active_plan="$(latest_plan_for_slug "$slug" || true)"
|
|
477
|
-
fi
|
|
478
|
-
if [[ -n "${active_plan:-}" && -z "${contract_file:-}" ]]; then
|
|
479
|
-
artifact_stem="$(derive_artifact_stem_from_plan "$active_plan")"
|
|
480
|
-
if [[ -f "tasks/contracts/${artifact_stem}.contract.md" ]] || [[ ! -f "tasks/contracts/${slug}.contract.md" ]]; then
|
|
481
|
-
contract_file="tasks/contracts/${artifact_stem}.contract.md"
|
|
482
|
-
fi
|
|
483
|
-
fi
|
|
484
|
-
contract_file="${contract_file:-tasks/contracts/${slug}.contract.md}"
|
|
485
|
-
|
|
486
|
-
[[ -n "$contract_file" && -f "$contract_file" ]] || { echo "contract-worktree: no active sprint contract found" >&2; exit 1; }
|
|
487
|
-
|
|
488
|
-
check_architecture_freshness "$target_branch"
|
|
489
|
-
bash "scripts/verify-sprint.sh"
|
|
490
|
-
check_scope_against_contract "$contract_file"
|
|
491
|
-
archive_finished_workflow "$active_plan"
|
|
492
|
-
clean_local_runtime_markers
|
|
493
|
-
backfill_sprint_backlog "$active_plan" || true
|
|
494
|
-
|
|
495
|
-
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
|
|
496
|
-
git add -A
|
|
497
|
-
git commit -m "$commit_message"
|
|
498
|
-
else
|
|
499
|
-
echo "[ContractWorktree] No tracked changes to commit."
|
|
500
|
-
fi
|
|
501
|
-
|
|
502
|
-
if [[ "$merge_back" -eq 0 ]]; then
|
|
503
|
-
echo "[ContractWorktree] Merge skipped by --no-merge."
|
|
504
|
-
return 0
|
|
505
|
-
fi
|
|
506
|
-
|
|
507
|
-
target_worktree="$(find_worktree_for_branch "$target_branch" || true)"
|
|
508
|
-
[[ -n "$target_worktree" ]] || { echo "contract-worktree: target branch has no checked-out worktree: $target_branch" >&2; exit 1; }
|
|
509
|
-
|
|
510
|
-
clean_matching_untracked_target_files "$target_worktree" "$current_branch"
|
|
511
|
-
|
|
512
|
-
if [[ -n "$(git -C "$target_worktree" status --porcelain=v1 --untracked-files=all)" ]]; then
|
|
513
|
-
echo "contract-worktree: target worktree is dirty, refusing merge: $target_worktree" >&2
|
|
514
|
-
exit 1
|
|
515
|
-
fi
|
|
516
|
-
|
|
517
|
-
git -C "$target_worktree" merge --ff-only "$current_branch"
|
|
518
|
-
echo "[ContractWorktree] Merged $current_branch into $target_branch at $target_worktree"
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
# Warn-only sprint backlog back-fill: plans captured via sprint-backlog
|
|
522
|
-
# start-task carry "> **Source Ref**: sprint:<file>#<task>". After the
|
|
523
|
-
# workflow archives, flip that backlog row so the update merges with the
|
|
524
|
-
# slice. Any failure warns and never blocks finish.
|
|
525
|
-
backfill_sprint_backlog() {
|
|
526
|
-
local plan_file="$1"
|
|
527
|
-
local archived_plan source_ref sprint_path task_ref
|
|
528
|
-
|
|
529
|
-
[[ -f "scripts/sprint-backlog.sh" ]] || return 0
|
|
530
|
-
|
|
531
|
-
archived_plan="plans/archive/$(basename "$plan_file")"
|
|
532
|
-
if [[ ! -f "$archived_plan" ]]; then
|
|
533
|
-
# Archive may have renamed on collision (-vN suffix); fall back to the
|
|
534
|
-
# newest archived file sharing the stem, then to the original path.
|
|
535
|
-
archived_plan="$(find plans/archive -maxdepth 1 -type f -name "$(basename "$plan_file" .md)*.md" 2>/dev/null | sort | tail -1)"
|
|
536
|
-
fi
|
|
537
|
-
[[ -n "$archived_plan" && -f "$archived_plan" ]] || archived_plan="$plan_file"
|
|
538
|
-
if [[ ! -f "$archived_plan" ]]; then
|
|
539
|
-
echo "[ContractWorktree] Warning: cannot resolve archived plan for sprint back-fill: $plan_file" >&2
|
|
540
|
-
return 0
|
|
541
|
-
fi
|
|
542
|
-
|
|
543
|
-
source_ref="$(awk '/^> \*\*Source Ref\*\*:/ {sub(/^> \*\*Source Ref\*\*:[[:space:]]*/, ""); gsub(/\r/, ""); print; exit}' "$archived_plan" 2>/dev/null | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
544
|
-
case "$source_ref" in
|
|
545
|
-
sprint:*#*)
|
|
546
|
-
;;
|
|
547
|
-
*)
|
|
548
|
-
return 0
|
|
549
|
-
;;
|
|
550
|
-
esac
|
|
551
|
-
|
|
552
|
-
# Split on the FIRST '#': the sprint path is slug-generated and cannot
|
|
553
|
-
# contain '#', while the task name is free text and may.
|
|
554
|
-
sprint_path="${source_ref#sprint:}"
|
|
555
|
-
task_ref="${sprint_path#*#}"
|
|
556
|
-
sprint_path="${sprint_path%%#*}"
|
|
557
|
-
|
|
558
|
-
if bash scripts/sprint-backlog.sh complete-task --sprint "$sprint_path" --task "$task_ref" --plan "$archived_plan"; then
|
|
559
|
-
echo "[ContractWorktree] Sprint backlog updated: $sprint_path ($task_ref)"
|
|
560
|
-
else
|
|
561
|
-
echo "[ContractWorktree] Warning: sprint backlog back-fill failed for $sprint_path ($task_ref); update the row manually." >&2
|
|
562
|
-
fi
|
|
563
|
-
return 0
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
cleanup_worktree() {
|
|
567
|
-
local slug=""
|
|
568
|
-
local target_branch
|
|
569
|
-
local dry_run=0
|
|
570
|
-
|
|
571
|
-
target_branch="$(policy_get '.worktree_strategy.merge_back.target' 'main')"
|
|
572
|
-
|
|
573
|
-
while [[ $# -gt 0 ]]; do
|
|
574
|
-
case "$1" in
|
|
575
|
-
--slug)
|
|
576
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --slug requires a value" >&2; exit 2; }
|
|
577
|
-
slug="$2"
|
|
578
|
-
shift 2
|
|
579
|
-
;;
|
|
580
|
-
--target)
|
|
581
|
-
[[ -n "${2:-}" ]] || { echo "contract-worktree: --target requires a value" >&2; exit 2; }
|
|
582
|
-
target_branch="$2"
|
|
583
|
-
shift 2
|
|
584
|
-
;;
|
|
585
|
-
--dry-run)
|
|
586
|
-
dry_run=1
|
|
587
|
-
shift
|
|
588
|
-
;;
|
|
589
|
-
--help|-h)
|
|
590
|
-
usage
|
|
591
|
-
exit 0
|
|
592
|
-
;;
|
|
593
|
-
*)
|
|
594
|
-
echo "contract-worktree: unknown cleanup argument: $1" >&2
|
|
595
|
-
usage
|
|
596
|
-
exit 2
|
|
597
|
-
;;
|
|
598
|
-
esac
|
|
599
|
-
done
|
|
600
|
-
|
|
601
|
-
[[ -n "$slug" ]] || { echo "contract-worktree: cleanup requires --slug" >&2; exit 2; }
|
|
602
|
-
slug="${slug#codex/}"
|
|
603
|
-
slug="$(normalize_slug "$slug")"
|
|
604
|
-
[[ -n "$slug" ]] || { echo "contract-worktree: slug is empty after normalization" >&2; exit 2; }
|
|
605
|
-
|
|
606
|
-
if is_linked_worktree; then
|
|
607
|
-
echo "contract-worktree: cleanup must run from the target primary worktree, not a linked contract worktree" >&2
|
|
608
|
-
exit 1
|
|
609
|
-
fi
|
|
610
|
-
|
|
611
|
-
local target_worktree current_root branch_prefix branch_name worktree_path metadata_file
|
|
612
|
-
branch_prefix="$(policy_get '.worktree_strategy.branch_prefix' 'codex/')"
|
|
613
|
-
branch_name="${branch_prefix}${slug}"
|
|
614
|
-
metadata_file=".ai/harness/worktrees/${slug}.json"
|
|
615
|
-
target_worktree="$(find_worktree_for_branch "$target_branch" || true)"
|
|
616
|
-
[[ -n "$target_worktree" ]] || { echo "contract-worktree: target branch has no checked-out worktree: $target_branch" >&2; exit 1; }
|
|
617
|
-
|
|
618
|
-
current_root="$(pwd -P)"
|
|
619
|
-
target_worktree="$(cd "$target_worktree" && pwd -P)"
|
|
620
|
-
if [[ "$current_root" != "$target_worktree" ]]; then
|
|
621
|
-
echo "contract-worktree: cleanup must run from target worktree $target_worktree" >&2
|
|
622
|
-
exit 1
|
|
623
|
-
fi
|
|
624
|
-
|
|
625
|
-
worktree_path="$(find_worktree_for_branch "$branch_name" || true)"
|
|
626
|
-
if [[ -n "$worktree_path" ]]; then
|
|
627
|
-
worktree_path="$(cd "$worktree_path" && pwd -P)"
|
|
628
|
-
case "$current_root" in
|
|
629
|
-
"$worktree_path"|"$worktree_path"/*)
|
|
630
|
-
echo "contract-worktree: refusing to remove current working directory: $worktree_path" >&2
|
|
631
|
-
exit 1
|
|
632
|
-
;;
|
|
633
|
-
esac
|
|
634
|
-
fi
|
|
635
|
-
|
|
636
|
-
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
|
637
|
-
if ! git merge-base --is-ancestor "$branch_name" "$target_branch" >/dev/null 2>&1; then
|
|
638
|
-
echo "contract-worktree: branch $branch_name is not fully merged into $target_branch; refusing cleanup" >&2
|
|
639
|
-
exit 1
|
|
640
|
-
fi
|
|
641
|
-
else
|
|
642
|
-
echo "[ContractWorktree] Branch already absent, skipping: $branch_name"
|
|
643
|
-
fi
|
|
644
|
-
|
|
645
|
-
if [[ -n "$worktree_path" ]]; then
|
|
646
|
-
if [[ -n "$(git -C "$worktree_path" status --porcelain=v1 --untracked-files=all)" ]]; then
|
|
647
|
-
echo "contract-worktree: linked worktree is dirty, refusing cleanup: $worktree_path" >&2
|
|
648
|
-
echo "contract-worktree: pick/apply/commit useful changes first; scaffold-only discard belongs in scripts/ship-worktrees.sh --cleanup-merged --discard-scaffold-only" >&2
|
|
649
|
-
exit 1
|
|
650
|
-
fi
|
|
651
|
-
else
|
|
652
|
-
echo "[ContractWorktree] Worktree already absent, skipping: $branch_name"
|
|
653
|
-
fi
|
|
654
|
-
|
|
655
|
-
if [[ "$dry_run" -eq 1 ]]; then
|
|
656
|
-
echo "[ContractWorktree] dry-run cleanup slug=$slug target=$target_branch"
|
|
657
|
-
echo "[ContractWorktree] would remove worktree: ${worktree_path:-"(absent)"}"
|
|
658
|
-
echo "[ContractWorktree] would delete branch: $branch_name"
|
|
659
|
-
echo "[ContractWorktree] would remove metadata: $metadata_file"
|
|
660
|
-
return 0
|
|
661
|
-
fi
|
|
662
|
-
|
|
663
|
-
if [[ -n "$worktree_path" ]]; then
|
|
664
|
-
git worktree remove "$worktree_path"
|
|
665
|
-
echo "[ContractWorktree] Removed worktree: $worktree_path"
|
|
666
|
-
fi
|
|
667
|
-
|
|
668
|
-
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
|
669
|
-
git branch -d "$branch_name"
|
|
670
|
-
echo "[ContractWorktree] Deleted branch: $branch_name"
|
|
671
|
-
fi
|
|
672
|
-
|
|
673
|
-
if [[ -e "$metadata_file" ]]; then
|
|
674
|
-
rm -f "$metadata_file"
|
|
675
|
-
echo "[ContractWorktree] Removed metadata: $metadata_file"
|
|
676
|
-
else
|
|
677
|
-
echo "[ContractWorktree] Metadata already absent, skipping: $metadata_file"
|
|
678
|
-
fi
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
command_name="${1:-status}"
|
|
682
|
-
shift || true
|
|
683
|
-
|
|
684
|
-
case "$command_name" in
|
|
685
|
-
start)
|
|
686
|
-
start_worktree "$@"
|
|
687
|
-
;;
|
|
688
|
-
finish)
|
|
689
|
-
finish_worktree "$@"
|
|
690
|
-
;;
|
|
691
|
-
cleanup)
|
|
692
|
-
cleanup_worktree "$@"
|
|
693
|
-
;;
|
|
694
|
-
status)
|
|
695
|
-
status_worktree
|
|
696
|
-
;;
|
|
697
|
-
--help|-h|help)
|
|
698
|
-
usage
|
|
699
|
-
;;
|
|
700
|
-
*)
|
|
701
|
-
echo "contract-worktree: unknown command: $command_name" >&2
|
|
702
|
-
usage
|
|
703
|
-
exit 2
|
|
704
|
-
;;
|
|
705
|
-
esac
|
|
4
|
+
exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "contract-worktree.sh" "$@"
|