lathe-cli 1.1.0 → 1.3.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/bin/lathe-plan ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ # lathe plan pr <pr#> — prepare a task and run target planning only.
3
+
4
+ set -euo pipefail
5
+ : "${LATHE_HOME:?must be set by lathe dispatcher}"
6
+ source "$LATHE_HOME/bin/_lathe-lib.sh"
7
+ source "$LATHE_HOME/bin/_lathe-task.sh"
8
+
9
+ usage() {
10
+ cat >&2 <<EOF
11
+ Usage: lathe plan [--print|--manual-prompt|--no-launch] pr <pr#>
12
+
13
+ Options:
14
+ --print Run Claude non-interactively with --print and a plan-only prompt.
15
+ Used by lathe watch.
16
+ --manual-prompt Launch Claude without submitting the initial prompt.
17
+ The prompt is printed for manual paste.
18
+ --no-launch Prepare tasks/<pr#>/ and print the Claude command, but do not run it.
19
+ EOF
20
+ }
21
+
22
+ MODE="auto"
23
+ SOURCE=""
24
+ ID=""
25
+ while [ $# -gt 0 ]; do
26
+ case "$1" in
27
+ --print|--auto-plan)
28
+ MODE="print"; shift ;;
29
+ --manual-prompt)
30
+ MODE="manual"; shift ;;
31
+ --no-launch|--prepare-only)
32
+ MODE="no-launch"; shift ;;
33
+ -h|--help)
34
+ usage; exit 0 ;;
35
+ pr)
36
+ SOURCE="pr"; shift
37
+ if [ $# -gt 0 ] && [ -z "$ID" ]; then
38
+ ID="$1"; shift
39
+ fi
40
+ ;;
41
+ -*)
42
+ echo "lathe plan: unknown flag $1" >&2
43
+ usage
44
+ exit 1 ;;
45
+ *)
46
+ if [ -z "$SOURCE" ]; then
47
+ SOURCE="pr"
48
+ fi
49
+ if [ -z "$ID" ]; then
50
+ ID="$1"
51
+ else
52
+ echo "lathe plan: extra argument $1" >&2
53
+ usage
54
+ exit 1
55
+ fi
56
+ shift ;;
57
+ esac
58
+ done
59
+
60
+ if [ "$SOURCE" != "pr" ] || [ -z "$ID" ]; then
61
+ usage
62
+ exit 1
63
+ fi
64
+
65
+ lathe_prepare_pr_task "$ID"
66
+ lathe_write_invocation plan
67
+ INITIAL_PROMPT="$(lathe_task_plan_prompt)"
68
+
69
+ cat <<EOF
70
+
71
+ Task worktree ready for planning.
72
+ Path: $TASK_WT
73
+ Local branch: $TASK_BRANCH
74
+ PR head branch: $HEAD_REF
75
+ Descriptor: $LATHE_TASK_DIR/task.json
76
+ Brief: $LATHE_TASK_DIR/brief.md
77
+ Invocation: $LATHE_TASK_DIR/invocation.json
78
+ Prompt: $INITIAL_PROMPT
79
+ EOF
80
+
81
+ case "$MODE" in
82
+ no-launch)
83
+ cat <<EOF
84
+
85
+ Run target planning when ready:
86
+ cd "$DEVELOP_WT"
87
+ claude --add-dir "$TASK_WT" "$INITIAL_PROMPT"
88
+ EOF
89
+ ;;
90
+
91
+ manual)
92
+ cat <<EOF
93
+
94
+ Manual prompt mode. Paste this into the Claude TUI:
95
+
96
+ $INITIAL_PROMPT
97
+
98
+ Launching target for planning...
99
+ EOF
100
+ cd "$DEVELOP_WT"
101
+ exec claude --add-dir "$TASK_WT"
102
+ ;;
103
+
104
+ print)
105
+ echo ""
106
+ echo "Running target planning non-interactively..."
107
+ cd "$DEVELOP_WT"
108
+ exec claude --print --permission-mode auto --add-dir "$TASK_WT" "$INITIAL_PROMPT"
109
+ ;;
110
+
111
+ auto)
112
+ echo ""
113
+ echo "Launching target for planning with the initial prompt already submitted..."
114
+ cd "$DEVELOP_WT"
115
+ exec claude --add-dir "$TASK_WT" "$INITIAL_PROMPT"
116
+ ;;
117
+ esac
package/bin/lathe-process CHANGED
@@ -1,88 +1,170 @@
1
1
  #!/usr/bin/env bash
2
- # lathe process <pr#> — pick up a PR for target processing.
2
+ # lathe process [--manual-prompt|--no-launch] <pr#> — pick up a PR for target processing.
3
3
  # 1. fetch PR head + body via gh
4
- # 2. add a worktree of the PR's branch under <project>/tasks/<pr#>
5
- # 3. write the task brief into that worktree as .lathe-task.md
6
- # 4. cd into develop worktree's target/, launch claude with --add-dir <task-wt>
4
+ # 2. add a task/pr-<pr#> worktree from the PR head under <project>/tasks/<pr#>
5
+ # 3. write the Lathe transport envelope into .lathe/task.json + .lathe/brief.md
6
+ # 4. cd into develop worktree, launch claude with --add-dir <task-wt>
7
+ # and submit the initial prompt as a CLI argument
7
8
  #
8
- # Interactive launch: the user pastes a "read .lathe-task.md and process it"
9
- # prompt into the resulting claude TUI. (Auto-injection of the initial prompt
10
- # is unreliable in claude's interactive mode.)
9
+ # If a user's Claude Code build does not accept an initial prompt argument,
10
+ # --manual-prompt preserves the old paste-it-yourself flow.
11
11
 
12
12
  set -euo pipefail
13
13
  : "${LATHE_HOME:?must be set by lathe dispatcher}"
14
14
  source "$LATHE_HOME/bin/_lathe-lib.sh"
15
+ source "$LATHE_HOME/bin/_lathe-task.sh"
15
16
 
16
- PR="${1:-}"
17
- if [ -z "$PR" ]; then
18
- echo "Usage: lathe process <pr#>" >&2
19
- exit 1
20
- fi
21
-
22
- command -v gh >/dev/null 2>&1 || {
23
- echo "lathe process: gh (GitHub CLI) is required" >&2
24
- exit 1
25
- }
17
+ usage() {
18
+ cat >&2 <<EOF
19
+ Usage: lathe process [--manual-prompt|--no-launch] <pr#>
26
20
 
27
- PROJECT_ROOT="$(lathe_project_root)" || {
28
- echo "lathe process: not inside a Lathe project" >&2
29
- exit 1
30
- }
31
- DEVELOP_WT="$(lathe_wt_for_branch develop)" || {
32
- echo "lathe process: develop worktree not found" >&2
33
- exit 1
21
+ Options:
22
+ --manual-prompt Launch Claude without submitting the initial prompt.
23
+ The prompt is printed and copied to the clipboard when possible.
24
+ --no-launch Prepare tasks/<pr#>/ and print the Claude command, but do not run it.
25
+ EOF
34
26
  }
35
27
 
36
- echo "==> Fetching PR #$PR ..."
37
- HEAD_REF="$(gh pr view "$PR" --json headRefName -q .headRefName)"
38
- TITLE="$(gh pr view "$PR" --json title -q .title)"
39
- BODY="$(gh pr view "$PR" --json body -q .body)"
28
+ PR=""
29
+ MODE="auto"
30
+ while [ $# -gt 0 ]; do
31
+ case "$1" in
32
+ --manual-prompt)
33
+ MODE="manual"; shift ;;
34
+ --no-launch|--prepare-only)
35
+ MODE="no-launch"; shift ;;
36
+ -h|--help)
37
+ usage; exit 0 ;;
38
+ -*)
39
+ echo "lathe process: unknown flag $1" >&2
40
+ usage
41
+ exit 1 ;;
42
+ *)
43
+ if [ -z "$PR" ]; then
44
+ PR="$1"
45
+ else
46
+ echo "lathe process: extra argument $1" >&2
47
+ usage
48
+ exit 1
49
+ fi
50
+ shift ;;
51
+ esac
52
+ done
40
53
 
41
- git --git-dir="$PROJECT_ROOT/.git" fetch origin "$HEAD_REF" >/dev/null 2>&1 || true
42
-
43
- mkdir -p "$PROJECT_ROOT/tasks"
44
- TASK_WT="$PROJECT_ROOT/tasks/$PR"
45
- if [ -e "$TASK_WT" ]; then
46
- echo "lathe process: $TASK_WT already exists. Remove it or pick another PR." >&2
54
+ if [ -z "$PR" ]; then
55
+ usage
47
56
  exit 1
48
57
  fi
58
+ PR="${PR#\#}"
59
+
60
+ lathe_prepare_pr_task "$PR"
61
+ lathe_write_invocation process
62
+ INITIAL_PROMPT="$(lathe_task_process_prompt)"
63
+
64
+ copy_prompt_to_clipboard() {
65
+ if command -v pbcopy >/dev/null 2>&1; then
66
+ if printf '%s' "$INITIAL_PROMPT" | pbcopy 2>/dev/null; then
67
+ echo " Clipboard: copied via pbcopy"
68
+ else
69
+ echo " Clipboard: pbcopy failed"
70
+ fi
71
+ elif command -v wl-copy >/dev/null 2>&1; then
72
+ if printf '%s' "$INITIAL_PROMPT" | wl-copy 2>/dev/null; then
73
+ echo " Clipboard: copied via wl-copy"
74
+ else
75
+ echo " Clipboard: wl-copy failed"
76
+ fi
77
+ elif command -v xclip >/dev/null 2>&1; then
78
+ if printf '%s' "$INITIAL_PROMPT" | xclip -selection clipboard 2>/dev/null; then
79
+ echo " Clipboard: copied via xclip"
80
+ else
81
+ echo " Clipboard: xclip failed"
82
+ fi
83
+ else
84
+ echo " Clipboard: unavailable"
85
+ fi
86
+ }
49
87
 
50
- echo "==> Creating task worktree at $TASK_WT (branch: $HEAD_REF)..."
51
- git --git-dir="$PROJECT_ROOT/.git" worktree add --quiet "$TASK_WT" "$HEAD_REF"
88
+ print_aftercare() {
89
+ local feature_name=""
90
+ case "$HEAD_REF" in
91
+ feature/*) feature_name="${HEAD_REF#feature/}" ;;
92
+ esac
52
93
 
53
- PROMPT_FILE="$TASK_WT/.lathe-task.md"
54
- cat > "$PROMPT_FILE" <<EOF
55
- # PR #$PR: $TITLE
94
+ cat <<EOF
56
95
 
57
- You are processing pull request #$PR. The vibe-coded changes are in this worktree.
96
+ Target session exited.
97
+
98
+ Typical next steps:
99
+ gh pr view $PR --web
100
+ gh pr merge $PR --merge
101
+ cd "$DEVELOP_WT" && git pull origin develop
102
+ EOF
58
103
 
59
- **Task description (PR body):**
60
- $BODY
104
+ if [ -n "$feature_name" ]; then
105
+ cat <<EOF
106
+ cd "$PROJECT_ROOT" && lathe feature-done "$feature_name"
107
+ EOF
108
+ fi
61
109
 
62
- **Your job:**
63
- 1. Read the existing changes in this worktree (the vibe-coded prototype)
64
- 2. Plan a polished implementation (planning skill, plans/<run_id>.html)
65
- 3. After approval, dispatch coder/reviewer to produce a clean implementation
66
- 4. Coder commits and pushes to branch \`$HEAD_REF\` so the PR updates
67
- 5. Do NOT delete the original vibe commits — your polish goes ON TOP
110
+ cat <<EOF
68
111
 
69
- The task worktree is at: $TASK_WT
112
+ When the PR is merged and no more target work is needed:
113
+ git --git-dir="$PROJECT_ROOT/.git" worktree remove "$TASK_WT"
114
+ git --git-dir="$PROJECT_ROOT/.git" branch -D "$TASK_BRANCH"
70
115
  EOF
116
+ }
71
117
 
72
118
  cat <<EOF
73
119
 
74
120
  Task worktree ready.
75
121
  Path: $TASK_WT
76
- Branch: $HEAD_REF
77
- Prompt file: $PROMPT_FILE
78
-
79
- Launching target. Paste this into the claude TUI:
122
+ Local branch: $TASK_BRANCH
123
+ PR head branch: $HEAD_REF
124
+ Descriptor: $LATHE_TASK_DIR/task.json
125
+ Brief: $LATHE_TASK_DIR/brief.md
126
+ Invocation: $LATHE_TASK_DIR/invocation.json
127
+ Prompt: $INITIAL_PROMPT
128
+ EOF
80
129
 
81
- Read .lathe-task.md from the added directory ($TASK_WT) and process it.
130
+ case "$MODE" in
131
+ no-launch)
132
+ cat <<EOF
82
133
 
83
- Press Enter to launch claude (Ctrl-C to abort)...
134
+ Run target when ready:
135
+ cd "$DEVELOP_WT"
136
+ claude --add-dir "$TASK_WT" "$INITIAL_PROMPT"
84
137
  EOF
85
- read -r _
86
-
87
- cd "$DEVELOP_WT/target"
88
- exec claude --add-dir "$TASK_WT"
138
+ exit 0
139
+ ;;
140
+
141
+ manual)
142
+ echo ""
143
+ echo "Manual prompt mode. Paste this into the Claude TUI:"
144
+ echo ""
145
+ echo " $INITIAL_PROMPT"
146
+ echo ""
147
+ copy_prompt_to_clipboard
148
+ echo ""
149
+ echo "Launching target..."
150
+ cd "$DEVELOP_WT"
151
+ set +e
152
+ claude --add-dir "$TASK_WT"
153
+ status=$?
154
+ set -e
155
+ print_aftercare
156
+ exit "$status"
157
+ ;;
158
+
159
+ auto)
160
+ echo ""
161
+ echo "Launching target with the initial prompt already submitted..."
162
+ cd "$DEVELOP_WT"
163
+ set +e
164
+ claude --add-dir "$TASK_WT" "$INITIAL_PROMPT"
165
+ status=$?
166
+ set -e
167
+ print_aftercare
168
+ exit "$status"
169
+ ;;
170
+ esac
package/bin/lathe-sync CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # lathe sync — re-sync harness/ to target/.claude/ in the develop worktree.
2
+ # lathe sync — re-sync harness/ to .claude/ in the develop worktree.
3
3
  # Useful after manual harness edits, or if post-merge hook didn't fire (e.g.,
4
4
  # merge happened via GitHub UI).
5
5
 
package/bin/lathe-target CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # lathe target — cd into develop worktree's target/ and launch claude.
2
+ # lathe target — cd into the develop worktree and launch claude.
3
3
 
4
4
  set -euo pipefail
5
5
  : "${LATHE_HOME:?must be set by lathe dispatcher}"
@@ -10,5 +10,5 @@ DEVELOP_WT="$(lathe_wt_for_branch develop)" || {
10
10
  exit 1
11
11
  }
12
12
 
13
- cd "$DEVELOP_WT/target"
13
+ cd "$DEVELOP_WT"
14
14
  exec claude "$@"
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bash
2
+ # lathe watch — poll GitHub PRs targeting develop and plan new ones locally.
3
+
4
+ set -euo pipefail
5
+ : "${LATHE_HOME:?must be set by lathe dispatcher}"
6
+ source "$LATHE_HOME/bin/_lathe-lib.sh"
7
+ source "$LATHE_HOME/bin/_lathe-task.sh"
8
+
9
+ usage() {
10
+ cat >&2 <<EOF
11
+ Usage: lathe watch [--once] [--interval <seconds>] [--base <branch>] [--dry-run]
12
+
13
+ Poll open GitHub PRs targeting <branch> (default: develop). For each PR that has
14
+ not already been planned by this watcher, run:
15
+
16
+ lathe plan --print pr <pr#>
17
+
18
+ Options:
19
+ --once Poll once and exit.
20
+ --interval <seconds> Poll interval for continuous mode. Default: 60.
21
+ --base <branch> PR base branch to watch. Default: develop.
22
+ --dry-run Print what would be planned without running Claude.
23
+ EOF
24
+ }
25
+
26
+ ONCE=0
27
+ INTERVAL=60
28
+ BASE_BRANCH="develop"
29
+ DRY_RUN=0
30
+ while [ $# -gt 0 ]; do
31
+ case "$1" in
32
+ --once)
33
+ ONCE=1; shift ;;
34
+ --interval)
35
+ INTERVAL="$2"; shift 2 ;;
36
+ --interval=*)
37
+ INTERVAL="${1#--interval=}"; shift ;;
38
+ --base)
39
+ BASE_BRANCH="$2"; shift 2 ;;
40
+ --base=*)
41
+ BASE_BRANCH="${1#--base=}"; shift ;;
42
+ --dry-run)
43
+ DRY_RUN=1; shift ;;
44
+ -h|--help)
45
+ usage; exit 0 ;;
46
+ -*)
47
+ echo "lathe watch: unknown flag $1" >&2
48
+ usage
49
+ exit 1 ;;
50
+ *)
51
+ echo "lathe watch: extra argument $1" >&2
52
+ usage
53
+ exit 1 ;;
54
+ esac
55
+ done
56
+
57
+ case "$INTERVAL" in
58
+ ''|*[!0-9]*)
59
+ echo "lathe watch: --interval must be a positive integer" >&2
60
+ exit 1 ;;
61
+ esac
62
+ if [ "$INTERVAL" -lt 1 ]; then
63
+ echo "lathe watch: --interval must be >= 1" >&2
64
+ exit 1
65
+ fi
66
+
67
+ lathe_require_gh
68
+ PROJECT_ROOT="$(lathe_project_root)" || {
69
+ echo "lathe watch: not inside a Lathe project" >&2
70
+ exit 1
71
+ }
72
+
73
+ STATE_DIR="$PROJECT_ROOT/.lathe/watch"
74
+ STATE_FILE="$STATE_DIR/planned-prs"
75
+ mkdir -p "$STATE_DIR"
76
+ touch "$STATE_FILE"
77
+
78
+ already_planned() {
79
+ grep -qxF "$1" "$STATE_FILE" 2>/dev/null
80
+ }
81
+
82
+ mark_planned() {
83
+ if ! already_planned "$1"; then
84
+ printf '%s\n' "$1" >> "$STATE_FILE"
85
+ fi
86
+ }
87
+
88
+ poll_once() {
89
+ local prs pr
90
+ prs="$(gh pr list --base "$BASE_BRANCH" --state open --json number -q '.[].number')"
91
+ if [ -z "$prs" ]; then
92
+ echo "lathe watch: no open PRs targeting $BASE_BRANCH"
93
+ return 0
94
+ fi
95
+
96
+ while IFS= read -r pr; do
97
+ [ -n "$pr" ] || continue
98
+ if already_planned "$pr"; then
99
+ echo "lathe watch: PR #$pr already planned; skipping"
100
+ continue
101
+ fi
102
+
103
+ if [ "$DRY_RUN" = 1 ]; then
104
+ echo "lathe watch: would plan PR #$pr"
105
+ continue
106
+ fi
107
+
108
+ echo "lathe watch: planning PR #$pr"
109
+ if "$LATHE_HOME/bin/lathe-plan" --print pr "$pr"; then
110
+ mark_planned "$pr"
111
+ echo "lathe watch: PR #$pr marked planned"
112
+ else
113
+ echo "lathe watch: planning PR #$pr failed; will retry on next poll" >&2
114
+ fi
115
+ done <<EOF
116
+ $prs
117
+ EOF
118
+ }
119
+
120
+ while true; do
121
+ poll_once
122
+ if [ "$ONCE" = 1 ]; then
123
+ exit 0
124
+ fi
125
+ sleep "$INTERVAL"
126
+ done
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "lathe-cli",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Agent harness CLI: target/meta git worktrees driven by Claude Code",
5
5
  "bin": {
6
- "lathe": "./bin/lathe"
6
+ "lathe": "bin/lathe"
7
7
  },
8
8
  "files": [
9
9
  "bin/",
@@ -1,50 +1,80 @@
1
1
  #!/usr/bin/env bash
2
- # Sync harness/ (source of truth, editable by meta) into target/ (where
3
- # Claude Code reads from, partly under .claude/ which Claude Code's
4
- # sensitive-file guard blocks from in-tool edits).
2
+ # Sync harness/ worktree root (gitignored runtime files).
3
+ # Branch-aware:
4
+ # develop branch sync target part (everything in harness/ except harness/meta/)
5
+ # meta branch → sync meta part (harness/meta/ only)
5
6
  #
6
- # Run this whenever harness/ changes:
7
- # - Manually after editing harness/ by hand
8
- # - Automatically by meta-side hooks after meta improves harness/
9
- # - On fresh clone before launching target the first time
7
+ # Cleans stale runtime files first (rm before cp), so renames/removals on the
8
+ # harness side propagate.
9
+ #
10
+ # Run when:
11
+ # - Manually after editing harness/ (rare; usually meta does this via PR)
12
+ # - Automatically by the post-merge git hook on merges into develop or meta
13
+ # - Once at lathe init for the initial population
10
14
 
11
15
  set -euo pipefail
12
16
 
13
17
  REPO="$(cd "$(dirname "$0")/.." && pwd)"
14
18
  HARNESS="$REPO/harness"
15
- TARGET="$REPO/target"
16
19
 
17
20
  if [ ! -d "$HARNESS" ]; then
18
- echo "sync-harness: $HARNESS does not exist" >&2
21
+ echo "sync.sh: $HARNESS does not exist" >&2
19
22
  exit 1
20
23
  fi
21
24
 
22
- # Top-level files at target/ root.
23
- mkdir -p "$TARGET"
24
- cp -f "$HARNESS/CLAUDE.md" "$TARGET/CLAUDE.md"
25
- cp -f "$HARNESS/plan_template.html" "$TARGET/plan_template.html"
26
-
27
- # workflow/ (yaml templates).
28
- mkdir -p "$TARGET/workflow"
29
- rm -rf "$TARGET/workflow"/*
30
- cp -R "$HARNESS/workflow/." "$TARGET/workflow/"
31
-
32
- # hooks/ (shell scripts — executable bit must be preserved).
33
- mkdir -p "$TARGET/hooks"
34
- rm -rf "$TARGET/hooks"/*
35
- cp -R "$HARNESS/hooks/." "$TARGET/hooks/"
36
- chmod +x "$TARGET/hooks"/*.sh
37
-
38
- # .claude/ — the path Claude Code reads at startup. Sensitive-file guard
39
- # blocks tool-based edits here, but cp from outside the claude process
40
- # is fine. We rebuild settings.json + skills/ + agents/.
41
- mkdir -p "$TARGET/.claude/skills" "$TARGET/.claude/agents"
42
- rm -rf "$TARGET/.claude/skills"/* "$TARGET/.claude/agents"/*
43
- cp -f "$HARNESS/settings.json" "$TARGET/.claude/settings.json"
44
- cp -R "$HARNESS/skills/." "$TARGET/.claude/skills/"
45
- cp -R "$HARNESS/agents/." "$TARGET/.claude/agents/"
46
-
47
- # plans/ is runtime, NOT from harness. Keep what's there.
48
- mkdir -p "$TARGET/plans"
49
-
50
- echo "sync-harness: target/ rebuilt from harness/"
25
+ # Branch detection. Detached HEAD = abort with no-op (don't mangle anything).
26
+ BRANCH="$(git -C "$REPO" symbolic-ref --short HEAD 2>/dev/null || true)"
27
+ if [ -z "$BRANCH" ]; then
28
+ echo "sync.sh: HEAD is detached (no branch). Skipping sync." >&2
29
+ exit 0
30
+ fi
31
+
32
+ case "$BRANCH" in
33
+ develop)
34
+ # ---- target side: harness/* (excluding harness/meta/) → worktree root ----
35
+ rm -f "$REPO/CLAUDE.md" "$REPO/plan_template.html"
36
+ rm -rf "$REPO/.claude" "$REPO/workflow" "$REPO/hooks"
37
+
38
+ cp -f "$HARNESS/CLAUDE.md" "$REPO/CLAUDE.md"
39
+ cp -f "$HARNESS/plan_template.html" "$REPO/plan_template.html"
40
+
41
+ mkdir -p "$REPO/.claude/skills" "$REPO/.claude/agents"
42
+ cp -f "$HARNESS/settings.json" "$REPO/.claude/settings.json"
43
+ cp -R "$HARNESS/skills/." "$REPO/.claude/skills/"
44
+ cp -R "$HARNESS/agents/." "$REPO/.claude/agents/"
45
+
46
+ mkdir -p "$REPO/workflow"
47
+ cp -R "$HARNESS/workflow/." "$REPO/workflow/"
48
+
49
+ mkdir -p "$REPO/hooks"
50
+ cp -R "$HARNESS/hooks/." "$REPO/hooks/"
51
+ chmod +x "$REPO/hooks"/*.sh
52
+
53
+ mkdir -p "$REPO/plans"
54
+ echo "sync.sh: develop runtime rebuilt from harness/"
55
+ ;;
56
+
57
+ meta)
58
+ # ---- meta side: harness/meta/* → worktree root ----
59
+ if [ ! -d "$HARNESS/meta" ]; then
60
+ echo "sync.sh: $HARNESS/meta does not exist (this lathe install may be older than v1.2)" >&2
61
+ exit 1
62
+ fi
63
+
64
+ rm -f "$REPO/CLAUDE.md"
65
+ rm -rf "$REPO/.claude"
66
+
67
+ cp -f "$HARNESS/meta/CLAUDE.md" "$REPO/CLAUDE.md"
68
+
69
+ mkdir -p "$REPO/.claude/skills"
70
+ cp -f "$HARNESS/meta/settings.json" "$REPO/.claude/settings.json"
71
+ cp -R "$HARNESS/meta/skills/." "$REPO/.claude/skills/"
72
+
73
+ echo "sync.sh: meta runtime rebuilt from harness/meta/"
74
+ ;;
75
+
76
+ *)
77
+ echo "sync.sh: branch '$BRANCH' is neither develop nor meta. Skipping." >&2
78
+ exit 0
79
+ ;;
80
+ esac