llm-wb 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/.agentic/00.chat/README.md +78 -0
  2. package/.agentic/00.chat/checklists/before-commit.md +195 -0
  3. package/.agentic/00.chat/checklists/llm-workbench-public-beta.md +94 -0
  4. package/.agentic/00.chat/commands/README.md +108 -0
  5. package/.agentic/00.chat/migration-plan.md +132 -0
  6. package/.agentic/00.chat/skills/session-summary.md +48 -0
  7. package/.agentic/00.chat/standards/llm-workbench-public-beta-contract.md +216 -0
  8. package/.agentic/00.chat/standards/main-refresh-conflict-types.md +358 -0
  9. package/.agentic/00.chat/workflows/README.md +40 -0
  10. package/.agentic/00.chat/workflows/bootstrap-chat-workbench-repo.md +212 -0
  11. package/.agentic/00.chat/workflows/chat-cleanup.md +102 -0
  12. package/.agentic/00.chat/workflows/chat-commit.md +56 -0
  13. package/.agentic/00.chat/workflows/chat-promote-to-main.md +169 -0
  14. package/.agentic/00.chat/workflows/chat-refresh-from-main.md +242 -0
  15. package/.agentic/00.chat/workflows/chat-reporting.md +69 -0
  16. package/.agentic/00.chat/workflows/chat-start.md +173 -0
  17. package/.agentic/00.chat/workflows/chat-upstream-reusable-lesson.md +123 -0
  18. package/.agentic/shared/standards/README.md +32 -0
  19. package/.agentic/shared/standards/upstream-repo-bootstrap.md +131 -0
  20. package/.agentic/shared/workflows/README.md +35 -0
  21. package/.agentic/shared/workflows/capability-resolution-workflow.md +189 -0
  22. package/.agentic/shared/workflows/change-shared-process.md +92 -0
  23. package/.cursor/rules/llm-workbench.mdc +17 -0
  24. package/.github/copilot-instructions.md +16 -0
  25. package/AGENTS.md +63 -0
  26. package/CLAUDE.md +16 -0
  27. package/CONTRIBUTING.md +57 -0
  28. package/LICENSE +21 -0
  29. package/LLM_WORKBENCH.md +17 -0
  30. package/README.md +98 -0
  31. package/SECURITY.md +44 -0
  32. package/bin/llm-workbench.js +672 -0
  33. package/docs/00.chat/README.md +47 -0
  34. package/docs/00.chat/llm-workbench-acceptance-matrix.md +55 -0
  35. package/docs/00.chat/script-layout.md +107 -0
  36. package/docs/adapting-to-your-repo.md +29 -0
  37. package/docs/concepts.md +38 -0
  38. package/docs/install.md +114 -0
  39. package/docs/public-beta-contract.md +45 -0
  40. package/docs/workflows.md +103 -0
  41. package/examples/minimal-repo/README.md +13 -0
  42. package/package.json +93 -0
  43. package/scripts/00.chat/README.md +46 -0
  44. package/scripts/00.chat/bootstrap/README.md +35 -0
  45. package/scripts/00.chat/bootstrap/audit-chat-bootstrap-file-set/README.md +39 -0
  46. package/scripts/00.chat/bootstrap/audit-chat-bootstrap-file-set/script.sh +213 -0
  47. package/scripts/00.chat/closeout/README.md +30 -0
  48. package/scripts/00.chat/closeout/build-closeout-prompt/README.md +35 -0
  49. package/scripts/00.chat/closeout/build-closeout-prompt/script.sh +124 -0
  50. package/scripts/00.chat/command/README.md +31 -0
  51. package/scripts/00.chat/command/close/README.md +30 -0
  52. package/scripts/00.chat/command/close/script.sh +25 -0
  53. package/scripts/00.chat/command/dispatcher/README.md +46 -0
  54. package/scripts/00.chat/command/dispatcher/script.sh +91 -0
  55. package/scripts/00.chat/command/dispatcher/smoke-test.sh +168 -0
  56. package/scripts/00.chat/command/new/README.md +32 -0
  57. package/scripts/00.chat/command/new/script.sh +28 -0
  58. package/scripts/00.chat/command/open-window/README.md +38 -0
  59. package/scripts/00.chat/command/open-window/script.sh +25 -0
  60. package/scripts/00.chat/command/package-scripts/README.md +34 -0
  61. package/scripts/00.chat/command/package-scripts/smoke-test.sh +113 -0
  62. package/scripts/00.chat/git/README.md +30 -0
  63. package/scripts/00.chat/git/cleanup-empty-chat-branches/README.md +36 -0
  64. package/scripts/00.chat/git/cleanup-empty-chat-branches/script.sh +243 -0
  65. package/scripts/00.chat/git/cleanup-empty-chat-branches/smoke-test.sh +136 -0
  66. package/scripts/00.chat/local-merge/README.md +30 -0
  67. package/scripts/00.chat/local-merge/list-active-chat-branches/README.md +29 -0
  68. package/scripts/00.chat/local-merge/list-active-chat-branches/script.sh +109 -0
  69. package/scripts/00.chat/local-merge/report-chat-branch-overlaps/README.md +29 -0
  70. package/scripts/00.chat/local-merge/report-chat-branch-overlaps/script.sh +142 -0
  71. package/scripts/00.chat/local-merge/verify-chat-ready-to-merge-local-main/README.md +33 -0
  72. package/scripts/00.chat/local-merge/verify-chat-ready-to-merge-local-main/script.sh +345 -0
  73. package/scripts/00.chat/local-merge/verify-chat-ready-to-merge-local-main/smoke-test.sh +244 -0
  74. package/scripts/00.chat/main-refresh/README.md +39 -0
  75. package/scripts/00.chat/main-refresh/apply-rehearsed-refresh/README.md +32 -0
  76. package/scripts/00.chat/main-refresh/apply-rehearsed-refresh/script.sh +198 -0
  77. package/scripts/00.chat/main-refresh/check-chat-is-current-with-main/README.md +30 -0
  78. package/scripts/00.chat/main-refresh/check-chat-is-current-with-main/script.sh +121 -0
  79. package/scripts/00.chat/main-refresh/classify-conflict/README.md +39 -0
  80. package/scripts/00.chat/main-refresh/classify-conflict/script.sh +169 -0
  81. package/scripts/00.chat/main-refresh/classify-conflict/smoke-test.sh +137 -0
  82. package/scripts/00.chat/main-refresh/classify-refresh-readiness/README.md +35 -0
  83. package/scripts/00.chat/main-refresh/classify-refresh-readiness/script.sh +171 -0
  84. package/scripts/00.chat/main-refresh/classify-refresh-readiness/smoke-test.sh +132 -0
  85. package/scripts/00.chat/main-refresh/rehearse-refresh-from-main/README.md +34 -0
  86. package/scripts/00.chat/main-refresh/rehearse-refresh-from-main/script.sh +124 -0
  87. package/scripts/00.chat/main-refresh/rehearse-refresh-from-main/smoke-test.sh +257 -0
  88. package/scripts/00.chat/main-refresh/show-main-update-status/README.md +31 -0
  89. package/scripts/00.chat/main-refresh/show-main-update-status/script.sh +73 -0
  90. package/scripts/00.chat/main-refresh/verify-conflict-audit/README.md +37 -0
  91. package/scripts/00.chat/main-refresh/verify-conflict-audit/script.sh +154 -0
  92. package/scripts/00.chat/main-refresh/verify-conflict-audit/smoke-test.sh +99 -0
  93. package/scripts/00.chat/metrics/README.md +35 -0
  94. package/scripts/00.chat/metrics/data/chat-pricing.json +107 -0
  95. package/scripts/00.chat/metrics/data/chat-pricing.schema.json +63 -0
  96. package/scripts/00.chat/metrics/estimate-chat-cost/README.md +40 -0
  97. package/scripts/00.chat/metrics/estimate-chat-cost/script.js +130 -0
  98. package/scripts/00.chat/migration/README.md +30 -0
  99. package/scripts/00.chat/migration/audit-chat-layer-migration/README.md +33 -0
  100. package/scripts/00.chat/migration/audit-chat-layer-migration/script.sh +127 -0
  101. package/scripts/00.chat/recovery/README.md +30 -0
  102. package/scripts/00.chat/recovery/import-active-paths-to-chat-worktree/README.md +76 -0
  103. package/scripts/00.chat/recovery/import-active-paths-to-chat-worktree/script.sh +212 -0
  104. package/scripts/00.chat/recovery/import-active-paths-to-chat-worktree/smoke-test.sh +162 -0
  105. package/scripts/00.chat/reporting/README.md +30 -0
  106. package/scripts/00.chat/reporting/generate-commit-log-summary/README.md +35 -0
  107. package/scripts/00.chat/reporting/generate-commit-log-summary/script.sh +299 -0
  108. package/scripts/00.chat/reporting/generate-commit-log-summary/smoke-test.sh +93 -0
  109. package/scripts/00.chat/reporting/report-chat-workspaces/README.md +32 -0
  110. package/scripts/00.chat/reporting/report-chat-workspaces/script.sh +82 -0
  111. package/scripts/00.chat/session-log/README.md +33 -0
  112. package/scripts/00.chat/session-log/check-commit-prerequisites/README.md +89 -0
  113. package/scripts/00.chat/session-log/check-commit-prerequisites/script.sh +121 -0
  114. package/scripts/00.chat/session-log/check-commit-prerequisites/smoke-test.sh +119 -0
  115. package/scripts/00.chat/session-log/check-commitlog-deletions/README.md +90 -0
  116. package/scripts/00.chat/session-log/check-commitlog-deletions/script.sh +131 -0
  117. package/scripts/00.chat/session-log/check-commitlog-deletions/smoke-test.sh +123 -0
  118. package/scripts/00.chat/session-log/checkpoint-chat-session-log/README.md +98 -0
  119. package/scripts/00.chat/session-log/checkpoint-chat-session-log/script.sh +126 -0
  120. package/scripts/00.chat/session-log/paths/README.md +38 -0
  121. package/scripts/00.chat/session-log/paths/lib.sh +133 -0
  122. package/scripts/00.chat/session-log/prepare-chat-session-before-commit/README.md +90 -0
  123. package/scripts/00.chat/session-log/prepare-chat-session-before-commit/script.sh +145 -0
  124. package/scripts/00.chat/session-log/read-current-chat-log/README.md +44 -0
  125. package/scripts/00.chat/session-log/read-current-chat-log/script.sh +92 -0
  126. package/scripts/00.chat/session-log/read-current-chat-log/smoke-test.sh +127 -0
  127. package/scripts/00.chat/session-log/record-chat-commit/README.md +133 -0
  128. package/scripts/00.chat/session-log/record-chat-commit/script.sh +394 -0
  129. package/scripts/00.chat/session-log/record-chat-commit/smoke-test.sh +227 -0
  130. package/scripts/00.chat/session-log/record-main-refresh-conflict/README.md +34 -0
  131. package/scripts/00.chat/session-log/record-main-refresh-conflict/script.sh +239 -0
  132. package/scripts/00.chat/session-log/rename-current-chat-log-folder/README.md +32 -0
  133. package/scripts/00.chat/session-log/rename-current-chat-log-folder/script.sh +112 -0
  134. package/scripts/00.chat/session-log/update-chat-log/README.md +32 -0
  135. package/scripts/00.chat/session-log/update-chat-log/script.sh +294 -0
  136. package/scripts/00.chat/startup/README.md +37 -0
  137. package/scripts/00.chat/startup/auto-start-missing-session/README.md +113 -0
  138. package/scripts/00.chat/startup/auto-start-missing-session/script.sh +54 -0
  139. package/scripts/00.chat/startup/resolve-current-chat-session/README.md +57 -0
  140. package/scripts/00.chat/startup/resolve-current-chat-session/script.sh +47 -0
  141. package/scripts/00.chat/startup/resolve-current-chat-session/smoke-test.sh +130 -0
  142. package/scripts/00.chat/startup/start-chat-session/README.md +197 -0
  143. package/scripts/00.chat/startup/start-chat-session/script.sh +330 -0
  144. package/scripts/00.chat/startup/start-chat-session/smoke-test.sh +182 -0
  145. package/scripts/00.chat/startup/start-new-chat/README.md +31 -0
  146. package/scripts/00.chat/startup/start-new-chat/script.sh +29 -0
  147. package/scripts/00.chat/transcript/README.md +36 -0
  148. package/scripts/00.chat/transcript/discover-codex-session-log/README.md +32 -0
  149. package/scripts/00.chat/transcript/discover-codex-session-log/script.sh +106 -0
  150. package/scripts/00.chat/transcript/register-codex-session-log/README.md +32 -0
  151. package/scripts/00.chat/transcript/register-codex-session-log/script.sh +115 -0
  152. package/scripts/00.chat/worktree/README.md +32 -0
  153. package/scripts/00.chat/worktree/check-write-location/README.md +87 -0
  154. package/scripts/00.chat/worktree/check-write-location/script.sh +95 -0
  155. package/scripts/00.chat/worktree/dirty-worktree-check/README.md +77 -0
  156. package/scripts/00.chat/worktree/dirty-worktree-check/script.sh +93 -0
  157. package/scripts/00.chat/worktree/ensure-chat-worktree/README.md +33 -0
  158. package/scripts/00.chat/worktree/ensure-chat-worktree/script.sh +132 -0
  159. package/scripts/00.chat/worktree/open-window/README.md +34 -0
  160. package/scripts/00.chat/worktree/open-window/script.sh +131 -0
  161. package/scripts/00.chat/worktree/paths/README.md +32 -0
  162. package/scripts/00.chat/worktree/paths/lib.sh +71 -0
  163. package/scripts/01.harness/artifact-metadata/check-headers/script.sh +522 -0
  164. package/scripts/01.harness/artifact-metadata/check-headers/smoke-test.sh +48 -0
  165. package/scripts/01.harness/check-deterministic-process-drift.sh +416 -0
  166. package/scripts/01.harness/check-governed-script-command-drift.sh +184 -0
  167. package/scripts/01.harness/run-governed-script.sh +178 -0
  168. package/scripts/install.sh +503 -0
  169. package/scripts/uninstall.sh +199 -0
  170. package/tests/smoke-test-install.sh +70 -0
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # agentic-artifact:
5
+ # schema: agentic-artifact/v2
6
+ # id: chat.script.local-merge.report-chat-branch-overlaps
7
+ # version: 1
8
+ # status: active
9
+ # layer: 00.chat
10
+ # domain: local-merge
11
+ # disciplines:
12
+ # - agentic
13
+ # kind: script
14
+ # purpose: Report changed-path overlap between active worktree changes and chat branches.
15
+ # portability:
16
+ # class: required
17
+ # targets:
18
+ # - llm-workbench
19
+ # used_by:
20
+ # - id: chat.workflows.chat-refresh-from-main
21
+ # path: .agentic/00.chat/workflows/chat-refresh-from-main.md
22
+ # - id: harness.script.run-governed-script
23
+ # path: scripts/01.harness/run-governed-script.sh
24
+ # effects:
25
+ # - read-only
26
+
27
+ BASE_BRANCH="${1:-main}"
28
+
29
+ if ! git show-ref --verify --quiet "refs/heads/${BASE_BRANCH}"; then
30
+ echo "ERROR: base branch does not exist: ${BASE_BRANCH}" >&2
31
+ exit 1
32
+ fi
33
+
34
+ tmp_dir="$(mktemp -d)"
35
+
36
+ cleanup() {
37
+ rm -rf "$tmp_dir"
38
+ }
39
+
40
+ trap cleanup EXIT
41
+
42
+ labels_file="${tmp_dir}/labels"
43
+ current_branch="$(git branch --show-current)"
44
+
45
+ if [ -z "$current_branch" ]; then
46
+ echo "ERROR: current HEAD is detached." >&2
47
+ exit 1
48
+ fi
49
+
50
+ safe_name() {
51
+ printf '%s\n' "$1" | tr -c 'A-Za-z0-9._-' '_'
52
+ }
53
+
54
+ write_branch_paths() {
55
+ local label="$1"
56
+ local branch="$2"
57
+ local path_file="${tmp_dir}/$(safe_name "$label").paths"
58
+
59
+ git diff --name-only "${BASE_BRANCH}...${branch}" | sort -u > "$path_file"
60
+
61
+ if [ -s "$path_file" ]; then
62
+ printf '%s\t%s\n' "$label" "$path_file" >> "$labels_file"
63
+ fi
64
+ }
65
+
66
+ write_worktree_paths() {
67
+ local label="worktree:${current_branch}"
68
+ local path_file="${tmp_dir}/$(safe_name "$label").paths"
69
+
70
+ {
71
+ git diff --name-only
72
+ git diff --cached --name-only
73
+ git ls-files --others --exclude-standard
74
+ } | sort -u > "$path_file"
75
+
76
+ if [ -s "$path_file" ]; then
77
+ printf '%s\t%s\n' "$label" "$path_file" >> "$labels_file"
78
+ fi
79
+ }
80
+
81
+ : > "$labels_file"
82
+
83
+ git branch --format='%(refname:short)' | while IFS= read -r branch; do
84
+ case "$branch" in
85
+ chat/*)
86
+ write_branch_paths "$branch" "$branch"
87
+ ;;
88
+ esac
89
+ done
90
+
91
+ write_worktree_paths
92
+
93
+ if [ ! -s "$labels_file" ]; then
94
+ echo "No branch-only or worktree path changes found relative to ${BASE_BRANCH}."
95
+ exit 0
96
+ fi
97
+
98
+ echo "Base branch: ${BASE_BRANCH}"
99
+ echo "Current branch: ${current_branch}"
100
+ echo
101
+ echo "Changed path sets:"
102
+ while IFS="$(printf '\t')" read -r label path_file; do
103
+ count="$(wc -l < "$path_file" | tr -d ' ')"
104
+ printf '%5s %s\n' "$count" "$label"
105
+ done < "$labels_file"
106
+
107
+ echo
108
+ echo "Overlaps:"
109
+
110
+ overlap_count=0
111
+ line_count="$(wc -l < "$labels_file" | tr -d ' ')"
112
+
113
+ for left_index in $(seq 1 "$line_count"); do
114
+ left_line="$(sed -n "${left_index}p" "$labels_file")"
115
+ left_label="${left_line%% *}"
116
+ left_file="${left_line#* }"
117
+
118
+ right_start=$((left_index + 1))
119
+ if [ "$right_start" -gt "$line_count" ]; then
120
+ continue
121
+ fi
122
+
123
+ for right_index in $(seq "$right_start" "$line_count"); do
124
+ right_line="$(sed -n "${right_index}p" "$labels_file")"
125
+ right_label="${right_line%% *}"
126
+ right_file="${right_line#* }"
127
+ overlap_file="${tmp_dir}/overlap-${left_index}-${right_index}"
128
+
129
+ comm -12 "$left_file" "$right_file" > "$overlap_file"
130
+
131
+ if [ -s "$overlap_file" ]; then
132
+ overlap_count=$((overlap_count + 1))
133
+ overlap_paths="$(wc -l < "$overlap_file" | tr -d ' ')"
134
+ printf '\n%s <-> %s (%s paths)\n' "$left_label" "$right_label" "$overlap_paths"
135
+ sed 's/^/ /' "$overlap_file"
136
+ fi
137
+ done
138
+ done
139
+
140
+ if [ "$overlap_count" -eq 0 ]; then
141
+ echo "No overlapping changed paths found."
142
+ fi
@@ -0,0 +1,33 @@
1
+ <!-- agentic-artifact:
2
+ schema: agentic-artifact/v2
3
+ id: chat.script.local-merge.verify-chat-ready-to-merge-local-main.readme
4
+ version: 1
5
+ status: active
6
+ layer: 00.chat
7
+ domain: local-merge
8
+ disciplines:
9
+ - agentic
10
+ kind: guide
11
+ purpose: Explain the read-only gate that checks whether a chat branch can merge into
12
+ local main.
13
+ portability:
14
+ class: required
15
+ targets:
16
+ - llm-workbench
17
+ used_by:
18
+ - id: chat.script.local-merge.verify-chat-ready-to-merge-local-main
19
+ path: scripts/00.chat/local-merge/verify-chat-ready-to-merge-local-main/script.sh
20
+ - id: chat.script.local-merge.verify-chat-ready-to-merge-local-main.smoke-test
21
+ path: scripts/00.chat/local-merge/verify-chat-ready-to-merge-local-main/smoke-test.sh
22
+ -->
23
+ # Verify Chat Ready To Merge Local Main
24
+
25
+ This capability answers: can this completed chat branch be merged into local
26
+ `main` right now?
27
+
28
+ It is read-only. It checks the root integration worktree, the chat-owned
29
+ worktree, session-log metadata, branch freshness, dirty state, and recorded
30
+ commit evidence. If any requirement is missing, it prints a deterministic
31
+ blocked state and the recovery action to take before retrying.
32
+
33
+ Use this before an explicit local merge from a chat branch into `main`.
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # agentic-artifact:
5
+ # schema: agentic-artifact/v2
6
+ # id: chat.script.local-merge.verify-chat-ready-to-merge-local-main
7
+ # version: 1
8
+ # status: active
9
+ # layer: 00.chat
10
+ # domain: local-merge
11
+ # disciplines:
12
+ # - agentic
13
+ # kind: script
14
+ # purpose: Verify whether a completed chat branch is ready to merge into local main.
15
+ # portability:
16
+ # class: required
17
+ # targets:
18
+ # - llm-workbench
19
+ # used_by:
20
+ # - id: chat.workflows.chat-promote-to-main
21
+ # path: .agentic/00.chat/workflows/chat-promote-to-main.md
22
+ # - id: harness.architecture.adr.0011-use-chat-owned-worktrees-for-local-convergence
23
+ # effects:
24
+ # - read-only
25
+
26
+ usage() {
27
+ cat <<'EOF'
28
+ Usage:
29
+ script.sh [--base <branch>] <chat-branch>
30
+
31
+ Read-only gate for merging a completed chat branch into local main.
32
+ Classifies deterministic blocked states and exits non-zero unless the branch is
33
+ eligible for explicit, user-approved local merge.
34
+ EOF
35
+ }
36
+
37
+ BASE_BRANCH="main"
38
+ TARGET_BRANCH=""
39
+
40
+ while [ $# -gt 0 ]; do
41
+ case "$1" in
42
+ --base)
43
+ if [ $# -lt 2 ]; then
44
+ usage >&2
45
+ exit 2
46
+ fi
47
+ BASE_BRANCH="$2"
48
+ shift 2
49
+ ;;
50
+ -h|--help)
51
+ usage
52
+ exit 0
53
+ ;;
54
+ *)
55
+ if [ -n "$TARGET_BRANCH" ]; then
56
+ usage >&2
57
+ exit 2
58
+ fi
59
+ TARGET_BRANCH="$1"
60
+ shift
61
+ ;;
62
+ esac
63
+ done
64
+
65
+ if [ -z "$TARGET_BRANCH" ]; then
66
+ usage >&2
67
+ exit 2
68
+ fi
69
+
70
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
71
+ REPO_ROOT="$(cd "$REPO_ROOT" && pwd -P)"
72
+
73
+ # shellcheck source=../../worktree/paths/lib.sh
74
+ source "$REPO_ROOT/scripts/00.chat/worktree/paths/lib.sh"
75
+ # shellcheck source=../../session-log/paths/lib.sh
76
+ source "$REPO_ROOT/scripts/00.chat/session-log/paths/lib.sh"
77
+
78
+ tmp_dir="$(mktemp -d)"
79
+
80
+ cleanup() {
81
+ rm -rf "$tmp_dir"
82
+ }
83
+
84
+ trap cleanup EXIT
85
+
86
+ block() {
87
+ local state="$1"
88
+ local condition="$2"
89
+ local action="$3"
90
+
91
+ echo "State: $state"
92
+ echo "Branch: $TARGET_BRANCH"
93
+ echo "Blocking condition: $condition"
94
+ echo "Required action: $action"
95
+ exit 1
96
+ }
97
+
98
+ info() {
99
+ printf '%s: %s\n' "$1" "$2"
100
+ }
101
+
102
+ find_worktree_log_by_metadata() {
103
+ local grouped_parent="$1"
104
+ local candidate
105
+
106
+ if [ ! -d "$REPO_ROOT/$grouped_parent" ]; then
107
+ return 1
108
+ fi
109
+
110
+ while IFS= read -r candidate; do
111
+ if [ "$(chat_log_metadata_value "$REPO_ROOT/$candidate" "id")" = "$SESSION_ID" ] \
112
+ || [ "$(chat_log_metadata_value "$REPO_ROOT/$candidate" "branch")" = "$TARGET_BRANCH" ]; then
113
+ printf '%s\n' "$candidate"
114
+ return 0
115
+ fi
116
+ done < <(find "$REPO_ROOT/$grouped_parent" -mindepth 2 -maxdepth 2 -type f -name README.md \
117
+ | sed "s#^$REPO_ROOT/##" \
118
+ | sort)
119
+
120
+ return 1
121
+ }
122
+
123
+ find_branch_log_by_metadata() {
124
+ local grouped_parent="$1"
125
+ local candidate tmp_log
126
+
127
+ while IFS= read -r candidate; do
128
+ tmp_log="${tmp_dir}/candidate-log.md"
129
+ git -C "$REPO_ROOT" show "${TARGET_BRANCH}:${candidate}" > "$tmp_log"
130
+ if [ "$(chat_log_metadata_value "$tmp_log" "id")" = "$SESSION_ID" ] \
131
+ || [ "$(chat_log_metadata_value "$tmp_log" "branch")" = "$TARGET_BRANCH" ]; then
132
+ printf '%s\n' "$candidate"
133
+ return 0
134
+ fi
135
+ done < <(git -C "$REPO_ROOT" ls-tree -r --name-only "$TARGET_BRANCH" -- "$grouped_parent" \
136
+ | awk '/\/README\.md$/ { print }' \
137
+ | sort)
138
+
139
+ return 1
140
+ }
141
+
142
+ case "$TARGET_BRANCH" in
143
+ chat/*) ;;
144
+ *)
145
+ block "blocked-invalid-branch" \
146
+ "target is not a chat branch" \
147
+ "Choose a local chat/* branch."
148
+ ;;
149
+ esac
150
+
151
+ if ! git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/${BASE_BRANCH}"; then
152
+ block "blocked-missing-base" \
153
+ "base branch does not exist: $BASE_BRANCH" \
154
+ "Create or select the local base branch before convergence."
155
+ fi
156
+
157
+ if ! git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/${TARGET_BRANCH}"; then
158
+ block "blocked-missing-branch" \
159
+ "target branch does not exist locally" \
160
+ "Create or fetch the chat branch before convergence."
161
+ fi
162
+
163
+ PRIMARY_PATH="$(chat_worktree_primary_path)"
164
+ PRIMARY_PATH="$(cd "$PRIMARY_PATH" && pwd -P)"
165
+
166
+ if [ "$REPO_ROOT" != "$PRIMARY_PATH" ]; then
167
+ block "blocked-not-root-integration-worktree" \
168
+ "verification is not running from the root integration worktree" \
169
+ "Run local merge verification from the root integration worktree."
170
+ fi
171
+
172
+ current_branch="$(git -C "$REPO_ROOT" branch --show-current)"
173
+ if [ "$current_branch" != "$BASE_BRANCH" ]; then
174
+ block "blocked-root-not-main" \
175
+ "root integration worktree is on '$current_branch', expected '$BASE_BRANCH'" \
176
+ "Switch the root integration worktree to $BASE_BRANCH before convergence."
177
+ fi
178
+
179
+ if [ -n "$(git -C "$REPO_ROOT" status --porcelain)" ]; then
180
+ block "blocked-dirty-root" \
181
+ "root integration worktree is dirty" \
182
+ "Clean or explicitly resolve root worktree changes before convergence."
183
+ fi
184
+
185
+ SESSION_ID="${TARGET_BRANCH#chat/}"
186
+ GROUPED_DIR="$(chat_log_grouped_dir_for_session "$SESSION_ID")"
187
+ GROUPED_PARENT="${GROUPED_DIR%/*}"
188
+ GROUPED_LOG="${GROUPED_DIR}/README.md"
189
+ FLAT_LOG="commitLogs/${SESSION_ID}/README.md"
190
+ LOG_FILE="${tmp_dir}/session-log.md"
191
+ LOG_SOURCE=""
192
+ LOG_PATH=""
193
+ metadata_log_path=""
194
+
195
+ if git -C "$REPO_ROOT" cat-file -e "${TARGET_BRANCH}:${GROUPED_LOG}" 2>/dev/null; then
196
+ git -C "$REPO_ROOT" show "${TARGET_BRANCH}:${GROUPED_LOG}" > "$LOG_FILE"
197
+ LOG_SOURCE="branch"
198
+ LOG_PATH="$GROUPED_LOG"
199
+ elif git -C "$REPO_ROOT" cat-file -e "${TARGET_BRANCH}:${FLAT_LOG}" 2>/dev/null; then
200
+ git -C "$REPO_ROOT" show "${TARGET_BRANCH}:${FLAT_LOG}" > "$LOG_FILE"
201
+ LOG_SOURCE="branch"
202
+ LOG_PATH="$FLAT_LOG"
203
+ elif metadata_log_path="$(find_branch_log_by_metadata "$GROUPED_PARENT")"; then
204
+ git -C "$REPO_ROOT" show "${TARGET_BRANCH}:${metadata_log_path}" > "$LOG_FILE"
205
+ LOG_SOURCE="branch"
206
+ LOG_PATH="$metadata_log_path"
207
+ elif [ -f "$REPO_ROOT/$GROUPED_LOG" ]; then
208
+ cp "$REPO_ROOT/$GROUPED_LOG" "$LOG_FILE"
209
+ LOG_SOURCE="worktree"
210
+ LOG_PATH="$GROUPED_LOG"
211
+ elif [ -f "$REPO_ROOT/$FLAT_LOG" ]; then
212
+ cp "$REPO_ROOT/$FLAT_LOG" "$LOG_FILE"
213
+ LOG_SOURCE="worktree"
214
+ LOG_PATH="$FLAT_LOG"
215
+ elif metadata_log_path="$(find_worktree_log_by_metadata "$GROUPED_PARENT")"; then
216
+ cp "$REPO_ROOT/$metadata_log_path" "$LOG_FILE"
217
+ LOG_SOURCE="worktree"
218
+ LOG_PATH="$metadata_log_path"
219
+ else
220
+ block "blocked-missing-log" \
221
+ "session log is missing from root $BASE_BRANCH and target branch" \
222
+ "Restore or create the chat session log before convergence."
223
+ fi
224
+
225
+ metadata_branch="$(chat_log_metadata_value "$LOG_FILE" "branch")"
226
+ metadata_worktree="$(chat_log_metadata_value "$LOG_FILE" "worktree")"
227
+ metadata_latest_sha="$(chat_log_metadata_value "$LOG_FILE" "latest_commit_sha")"
228
+
229
+ if [ "$metadata_branch" != "$TARGET_BRANCH" ]; then
230
+ block "blocked-invalid-metadata" \
231
+ "session log branch metadata is '$metadata_branch', expected '$TARGET_BRANCH'" \
232
+ "Fix the session log metadata before convergence."
233
+ fi
234
+
235
+ WORKTREE_PATH="$(chat_worktree_path_for_branch "$REPO_ROOT" "$TARGET_BRANCH")"
236
+ if [ "$metadata_worktree" != "$WORKTREE_PATH" ]; then
237
+ block "blocked-invalid-metadata" \
238
+ "session log worktree metadata is '$metadata_worktree', expected '$WORKTREE_PATH'" \
239
+ "Fix the session log worktree metadata before convergence."
240
+ fi
241
+
242
+ branch_worktrees="$(
243
+ git -C "$REPO_ROOT" worktree list --porcelain \
244
+ | awk -v branch="refs/heads/${TARGET_BRANCH}" '
245
+ /^worktree / { path = substr($0, 10) }
246
+ /^branch / && substr($0, 8) == branch { print path }
247
+ '
248
+ )"
249
+
250
+ found_canonical_worktree="no"
251
+ while IFS= read -r branch_worktree; do
252
+ if [ -z "${branch_worktree// }" ]; then
253
+ continue
254
+ fi
255
+
256
+ branch_worktree="$(cd "$branch_worktree" && pwd -P)"
257
+ if [ "$branch_worktree" = "$PRIMARY_PATH" ]; then
258
+ block "blocked-branch-in-root-worktree" \
259
+ "target chat branch is checked out in the root integration worktree" \
260
+ "Switch root back to $BASE_BRANCH before convergence."
261
+ fi
262
+
263
+ if [ "$branch_worktree" != "$WORKTREE_PATH" ]; then
264
+ block "blocked-wrong-worktree" \
265
+ "target chat branch is checked out in '$branch_worktree', expected '$WORKTREE_PATH'" \
266
+ "Move or recreate the chat-owned worktree before convergence."
267
+ fi
268
+
269
+ found_canonical_worktree="yes"
270
+ done <<< "$branch_worktrees"
271
+
272
+ if [ "$found_canonical_worktree" != "yes" ]; then
273
+ block "blocked-missing-worktree" \
274
+ "canonical chat-owned worktree is missing" \
275
+ "Run ensure-chat-worktree after session log verification, then rerun convergence verification."
276
+ fi
277
+
278
+ if ! git -C "$WORKTREE_PATH" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
279
+ block "blocked-missing-worktree" \
280
+ "canonical chat-owned worktree path is not a git worktree" \
281
+ "Recreate the chat-owned worktree before convergence."
282
+ fi
283
+
284
+ worktree_branch="$(git -C "$WORKTREE_PATH" branch --show-current)"
285
+ if [ "$worktree_branch" != "$TARGET_BRANCH" ]; then
286
+ block "blocked-wrong-worktree" \
287
+ "chat worktree is on '$worktree_branch', expected '$TARGET_BRANCH'" \
288
+ "Check out the target chat branch in its canonical worktree."
289
+ fi
290
+
291
+ if [ -n "$(git -C "$WORKTREE_PATH" status --porcelain)" ]; then
292
+ block "blocked-dirty-chat-worktree" \
293
+ "chat-owned worktree is dirty" \
294
+ "Commit, inspect, preserve, or explicitly discard chat work before convergence."
295
+ fi
296
+
297
+ ahead="$(git -C "$REPO_ROOT" rev-list --count "${BASE_BRANCH}..${TARGET_BRANCH}")"
298
+ behind="$(git -C "$REPO_ROOT" rev-list --count "${TARGET_BRANCH}..${BASE_BRANCH}")"
299
+
300
+ if [ "$behind" != "0" ] && [ "$ahead" != "0" ]; then
301
+ block "blocked-diverged" \
302
+ "chat branch and $BASE_BRANCH both have unique commits" \
303
+ "Use the governed refresh path before convergence."
304
+ fi
305
+
306
+ if [ "$behind" != "0" ]; then
307
+ block "blocked-behind" \
308
+ "chat branch is behind $BASE_BRANCH" \
309
+ "Merge $BASE_BRANCH into the chat branch from the chat-owned worktree before convergence."
310
+ fi
311
+
312
+ if [ "$ahead" = "0" ]; then
313
+ block "blocked-even" \
314
+ "chat branch has no commits beyond $BASE_BRANCH" \
315
+ "Do not merge; use cleanup or reporting flow instead."
316
+ fi
317
+
318
+ if [ -z "${metadata_latest_sha// }" ]; then
319
+ block "blocked-unrecorded-commit" \
320
+ "session log does not record latest_commit_sha" \
321
+ "Record the latest task commit, or explain the no-task-commit case before convergence."
322
+ fi
323
+
324
+ if ! git -C "$REPO_ROOT" cat-file -e "${metadata_latest_sha}^{commit}" 2>/dev/null; then
325
+ block "blocked-log-head-mismatch" \
326
+ "recorded latest_commit_sha does not resolve to a commit: $metadata_latest_sha" \
327
+ "Fix the session log commit evidence before convergence."
328
+ fi
329
+
330
+ if ! git -C "$REPO_ROOT" merge-base --is-ancestor "$metadata_latest_sha" "$TARGET_BRANCH"; then
331
+ block "blocked-log-head-mismatch" \
332
+ "recorded latest_commit_sha is not contained in the chat branch" \
333
+ "Record a commit that is present on the target chat branch before convergence."
334
+ fi
335
+
336
+ echo "State: eligible"
337
+ info "Base branch" "$BASE_BRANCH"
338
+ info "Branch" "$TARGET_BRANCH"
339
+ info "Ahead of base" "$ahead"
340
+ info "Behind base" "$behind"
341
+ info "Session log source" "$LOG_SOURCE"
342
+ info "Session log path" "$LOG_PATH"
343
+ info "Chat worktree" "$WORKTREE_PATH"
344
+ info "Recorded latest task commit" "$metadata_latest_sha"
345
+ echo "Next step: ask for explicit approval, then merge from the root integration worktree."