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,672 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { spawnSync } = require('node:child_process');
7
+
8
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
9
+ const INSTALL_SCRIPT = path.join(PACKAGE_ROOT, 'scripts', 'install.sh');
10
+
11
+ const INSTALLED_MANIFEST = path.join('.llm-workbench', 'install-manifest.tsv');
12
+ const DISPATCHER_SCRIPT = path.join('scripts', '00.chat', 'command', 'dispatcher', 'script.sh');
13
+ const NEW_COMMAND_SCRIPT = path.join('scripts', '00.chat', 'command', 'new', 'script.sh');
14
+ const START_NEW_CHAT_SCRIPT = path.join('scripts', '00.chat', 'startup', 'start-new-chat', 'script.sh');
15
+ const START_CHAT_SESSION_SCRIPT = path.join('scripts', '00.chat', 'startup', 'start-chat-session', 'script.sh');
16
+ const CHECK_WRITE_LOCATION_SCRIPT = path.join('scripts', '00.chat', 'worktree', 'check-write-location', 'script.sh');
17
+ const CHECK_COMMIT_PREREQUISITES_SCRIPT = path.join('scripts', '00.chat', 'session-log', 'check-commit-prerequisites', 'script.sh');
18
+ const PREPARE_COMMIT_SCRIPT = path.join('scripts', '00.chat', 'session-log', 'prepare-chat-session-before-commit', 'script.sh');
19
+ const RECORD_CHAT_COMMIT_SCRIPT = path.join('scripts', '00.chat', 'session-log', 'record-chat-commit', 'script.sh');
20
+ const CHECKPOINT_CHAT_SESSION_LOG_SCRIPT = path.join('scripts', '00.chat', 'session-log', 'checkpoint-chat-session-log', 'script.sh');
21
+ const VERIFY_MERGE_READY_SCRIPT = path.join('scripts', '00.chat', 'local-merge', 'verify-chat-ready-to-merge-local-main', 'script.sh');
22
+ const LIST_ACTIVE_CHAT_BRANCHES_SCRIPT = path.join('scripts', '00.chat', 'local-merge', 'list-active-chat-branches', 'script.sh');
23
+
24
+ function printHelp(stream = process.stdout) {
25
+ stream.write(`llm-wb
26
+
27
+ Usage:
28
+ llm-wb <command> [options]
29
+
30
+ Commands:
31
+ init [--target <repo>] [--dry-run|--apply] [--init-commit]
32
+ Install the workbench harness into a Git repo. Defaults to the current repo.
33
+
34
+ list
35
+ List available installed chat commands from the current Git repo.
36
+
37
+ new [--json] "prompt"
38
+ Start a new governed chat session using the installed chat:new flow.
39
+
40
+ sessions list [--base <branch>]
41
+ List active chat sessions/branches using the current Git repo.
42
+
43
+ commit -m "message" [--summary <text>] [--adr-impact <text>]
44
+ Safely commit current chat work, record it, and checkpoint session evidence.
45
+
46
+ merge-main [--base <branch>] [chat-branch]
47
+ Verify and locally merge a completed chat branch into local main.
48
+
49
+ help
50
+ Show this help.
51
+
52
+ Examples:
53
+ llm-wb init --dry-run
54
+ llm-wb init --target /path/to/repo
55
+ llm-wb new "implement the checkout flow"
56
+ llm-wb sessions list
57
+ llm-wb commit -m "Implement checkout flow"
58
+ llm-wb merge-main
59
+ llm-wb list
60
+ `);
61
+ }
62
+
63
+ function printSessionsHelp(stream = process.stdout) {
64
+ stream.write(`llm-wb sessions
65
+
66
+ Usage:
67
+ llm-wb sessions list [--base <branch>]
68
+
69
+ Commands:
70
+ list
71
+ List active chat sessions/branches using the existing local-merge report.
72
+ `);
73
+ }
74
+
75
+ function fail(message, exitCode = 1, showHelp = false) {
76
+ console.error(`ERROR: ${message}`);
77
+ if (showHelp) {
78
+ console.error('');
79
+ printHelp(process.stderr);
80
+ }
81
+ process.exit(exitCode);
82
+ }
83
+
84
+ function relativeMissing(paths) {
85
+ return paths.map((filePath) => ` ${filePath}`).join('\n');
86
+ }
87
+
88
+ function run(command, args, options = {}) {
89
+ const result = spawnSync(command, args, {
90
+ cwd: options.cwd || process.cwd(),
91
+ env: options.env || process.env,
92
+ stdio: 'inherit',
93
+ });
94
+
95
+ if (result.error) {
96
+ fail(`failed to run ${command}: ${result.error.message}`);
97
+ }
98
+
99
+ if (result.signal) {
100
+ fail(`${command} exited after signal ${result.signal}`);
101
+ }
102
+
103
+ process.exit(result.status === null ? 1 : result.status);
104
+ }
105
+
106
+ function runChecked(command, args, options = {}) {
107
+ const result = spawnSync(command, args, {
108
+ cwd: options.cwd || process.cwd(),
109
+ env: options.env || process.env,
110
+ stdio: options.stdio || 'inherit',
111
+ encoding: options.encoding,
112
+ });
113
+
114
+ if (result.error) {
115
+ fail(`failed to run ${command}: ${result.error.message}`);
116
+ }
117
+
118
+ if (result.signal) {
119
+ fail(`${command} exited after signal ${result.signal}`);
120
+ }
121
+
122
+ if (result.status !== 0) {
123
+ process.exit(result.status === null ? 1 : result.status);
124
+ }
125
+
126
+ return result;
127
+ }
128
+
129
+ function capture(command, args, options = {}) {
130
+ const result = spawnSync(command, args, {
131
+ cwd: options.cwd || process.cwd(),
132
+ env: options.env || process.env,
133
+ encoding: 'utf8',
134
+ stdio: ['ignore', 'pipe', 'pipe'],
135
+ });
136
+
137
+ if (result.error) {
138
+ fail(`failed to run ${command}: ${result.error.message}`);
139
+ }
140
+
141
+ if (result.status !== 0) {
142
+ const detail = (result.stderr || result.stdout || '').trim();
143
+ fail(detail || `${command} exited with status ${result.status}`);
144
+ }
145
+
146
+ return result.stdout.trim();
147
+ }
148
+
149
+ function gitRoot(startPath) {
150
+ const result = spawnSync('git', ['-C', startPath, 'rev-parse', '--show-toplevel'], {
151
+ encoding: 'utf8',
152
+ stdio: ['ignore', 'pipe', 'pipe'],
153
+ });
154
+
155
+ if (result.status !== 0) {
156
+ return null;
157
+ }
158
+
159
+ return path.resolve(result.stdout.trim());
160
+ }
161
+
162
+ function requireGitRepo(targetPath) {
163
+ const root = gitRoot(targetPath);
164
+ if (!root) {
165
+ fail(`target repo is not a git repo: ${targetPath}`);
166
+ }
167
+ return root;
168
+ }
169
+
170
+ function requirePackageScript(relativePath) {
171
+ const absolutePath = path.join(PACKAGE_ROOT, relativePath);
172
+ if (!fs.existsSync(absolutePath)) {
173
+ fail(`required llm-workbench package script is missing: ${relativePath}`);
174
+ }
175
+ return absolutePath;
176
+ }
177
+
178
+ function requireInstalledWorkbench(repoRoot) {
179
+ if (!fs.existsSync(path.join(repoRoot, INSTALLED_MANIFEST))) {
180
+ fail(`llm-workbench install has not been run in this repo: ${repoRoot}\nRun: llm-wb init`);
181
+ }
182
+ }
183
+
184
+ function requireTargetScripts(repoRoot, relativePaths) {
185
+ const missing = relativePaths.filter((relativePath) => {
186
+ return !fs.existsSync(path.join(repoRoot, relativePath));
187
+ });
188
+
189
+ if (missing.length > 0) {
190
+ fail(`required llm-workbench scripts are missing:\n${relativeMissing(missing)}\nRun: llm-wb init --dry-run`);
191
+ }
192
+ }
193
+
194
+ function currentBranch(repoRoot) {
195
+ return capture('git', ['-C', repoRoot, 'branch', '--show-current']);
196
+ }
197
+
198
+ function requireChatBranch(repoRoot) {
199
+ const branch = currentBranch(repoRoot);
200
+ if (!branch.startsWith('chat/')) {
201
+ fail(`current branch is not a chat branch: ${branch || '<detached>'}`);
202
+ }
203
+ return branch;
204
+ }
205
+
206
+ function sessionIdFromBranch(branch) {
207
+ if (!branch.startsWith('chat/')) {
208
+ return null;
209
+ }
210
+ return branch.slice('chat/'.length);
211
+ }
212
+
213
+ function monthName(month) {
214
+ const names = {
215
+ '01': 'jan',
216
+ '02': 'feb',
217
+ '03': 'mar',
218
+ '04': 'apr',
219
+ '05': 'may',
220
+ '06': 'jun',
221
+ '07': 'jul',
222
+ '08': 'aug',
223
+ '09': 'sep',
224
+ '10': 'oct',
225
+ '11': 'nov',
226
+ '12': 'dec',
227
+ };
228
+ return names[month] || null;
229
+ }
230
+
231
+ function metadataValue(contents, key) {
232
+ const block = contents.match(/<!-- agentic-session\n([\s\S]*?)\n-->/);
233
+ if (!block) {
234
+ return '';
235
+ }
236
+
237
+ const line = block[1].split('\n').find((candidate) => candidate.startsWith(`${key}: `));
238
+ return line ? line.slice(key.length + 2) : '';
239
+ }
240
+
241
+ function findSessionLog(repoRoot, sessionId, branch) {
242
+ const year = sessionId.slice(0, 4);
243
+ const month = sessionId.slice(5, 7);
244
+ const day = sessionId.slice(8, 10);
245
+ const monthSlug = monthName(month);
246
+ const candidates = [];
247
+
248
+ if (year && monthSlug && day) {
249
+ candidates.push(path.join('commitLogs', year, monthSlug, day, sessionId, 'README.md'));
250
+ }
251
+ candidates.push(path.join('commitLogs', sessionId, 'README.md'));
252
+
253
+ for (const candidate of candidates) {
254
+ if (fs.existsSync(path.join(repoRoot, candidate))) {
255
+ return candidate;
256
+ }
257
+ }
258
+
259
+ if (!year || !monthSlug || !day) {
260
+ fail(`could not derive session log path from branch: ${branch}`);
261
+ }
262
+
263
+ const parent = path.join(repoRoot, 'commitLogs', year, monthSlug, day);
264
+ if (fs.existsSync(parent)) {
265
+ for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
266
+ if (!entry.isDirectory()) {
267
+ continue;
268
+ }
269
+
270
+ const relativePath = path.join('commitLogs', year, monthSlug, day, entry.name, 'README.md');
271
+ const absolutePath = path.join(repoRoot, relativePath);
272
+ if (!fs.existsSync(absolutePath)) {
273
+ continue;
274
+ }
275
+
276
+ const contents = fs.readFileSync(absolutePath, 'utf8');
277
+ if (metadataValue(contents, 'id') === sessionId || metadataValue(contents, 'branch') === branch) {
278
+ return relativePath;
279
+ }
280
+ }
281
+ }
282
+
283
+ fail(`missing chat log for ${branch}`);
284
+ }
285
+
286
+ function primaryWorktree(repoRoot) {
287
+ const output = capture('git', ['-C', repoRoot, 'worktree', 'list', '--porcelain']);
288
+ const first = output.split('\n').find((line) => line.startsWith('worktree '));
289
+ if (!first) {
290
+ fail('could not resolve root integration worktree');
291
+ }
292
+ return path.resolve(first.slice('worktree '.length));
293
+ }
294
+
295
+ function worktreeForBranch(repoRoot, branch) {
296
+ const output = capture('git', ['-C', repoRoot, 'worktree', 'list', '--porcelain']);
297
+ let currentPath = '';
298
+
299
+ for (const line of output.split('\n')) {
300
+ if (line.startsWith('worktree ')) {
301
+ currentPath = line.slice('worktree '.length);
302
+ } else if (line === `branch refs/heads/${branch}`) {
303
+ return path.resolve(currentPath);
304
+ }
305
+ }
306
+
307
+ return '';
308
+ }
309
+
310
+ function envWithWorktreeRoot(repoRoot) {
311
+ return {
312
+ ...process.env,
313
+ AGENTIC_CHAT_WORKTREE_ROOT: path.dirname(repoRoot),
314
+ };
315
+ }
316
+
317
+ function parseCommitArgs(args) {
318
+ let message = '';
319
+ let summary = '';
320
+ let adrImpact = '';
321
+ let checkpoint = true;
322
+ const positional = [];
323
+
324
+ for (let index = 0; index < args.length; index += 1) {
325
+ const arg = args[index];
326
+
327
+ switch (arg) {
328
+ case '-h':
329
+ case '--help':
330
+ printHelp();
331
+ process.exit(0);
332
+ break;
333
+ case '-m':
334
+ case '--message':
335
+ if (index + 1 >= args.length) {
336
+ fail(`${arg} requires a commit message`, 2);
337
+ }
338
+ message = args[index + 1];
339
+ index += 1;
340
+ break;
341
+ case '--summary':
342
+ if (index + 1 >= args.length) {
343
+ fail('--summary requires text', 2);
344
+ }
345
+ summary = args[index + 1];
346
+ index += 1;
347
+ break;
348
+ case '--adr-impact':
349
+ if (index + 1 >= args.length) {
350
+ fail('--adr-impact requires text', 2);
351
+ }
352
+ adrImpact = args[index + 1];
353
+ index += 1;
354
+ break;
355
+ case '--no-checkpoint':
356
+ checkpoint = false;
357
+ break;
358
+ default:
359
+ if (arg.startsWith('-')) {
360
+ fail(`unknown commit option: ${arg}`, 2);
361
+ }
362
+ positional.push(arg);
363
+ }
364
+ }
365
+
366
+ if (message && positional.length > 0) {
367
+ fail('commit message was provided twice', 2);
368
+ }
369
+
370
+ if (!message) {
371
+ message = positional.join(' ');
372
+ }
373
+
374
+ if (!message.trim()) {
375
+ fail('commit requires a message. Use: llm-wb commit -m "message"', 2);
376
+ }
377
+
378
+ return {
379
+ message,
380
+ summary: summary || message,
381
+ adrImpact,
382
+ checkpoint,
383
+ };
384
+ }
385
+
386
+ function commandCommit(args) {
387
+ const { message, summary, adrImpact, checkpoint } = parseCommitArgs(args);
388
+ const repoRoot = requireGitRepo(process.cwd());
389
+ requireInstalledWorkbench(repoRoot);
390
+ requireTargetScripts(repoRoot, [
391
+ CHECK_WRITE_LOCATION_SCRIPT,
392
+ CHECK_COMMIT_PREREQUISITES_SCRIPT,
393
+ PREPARE_COMMIT_SCRIPT,
394
+ RECORD_CHAT_COMMIT_SCRIPT,
395
+ CHECKPOINT_CHAT_SESSION_LOG_SCRIPT,
396
+ ]);
397
+
398
+ const branch = requireChatBranch(repoRoot);
399
+ const sessionId = sessionIdFromBranch(branch);
400
+ const logFile = findSessionLog(repoRoot, sessionId, branch);
401
+ const commandEnv = envWithWorktreeRoot(repoRoot);
402
+
403
+ runChecked('bash', [path.join(repoRoot, CHECK_WRITE_LOCATION_SCRIPT)], { cwd: repoRoot, env: commandEnv });
404
+ runChecked('bash', [path.join(repoRoot, CHECK_COMMIT_PREREQUISITES_SCRIPT)], { cwd: repoRoot, env: commandEnv });
405
+
406
+ runChecked('git', ['-C', repoRoot, 'add', '-A'], { cwd: repoRoot, env: commandEnv });
407
+ runChecked('git', ['-C', repoRoot, 'reset', '--', logFile], { cwd: repoRoot, env: commandEnv, stdio: 'ignore' });
408
+
409
+ const stagedResult = spawnSync('git', ['-C', repoRoot, 'diff', '--cached', '--quiet'], {
410
+ env: commandEnv,
411
+ stdio: 'ignore',
412
+ });
413
+
414
+ if (stagedResult.status === 0) {
415
+ fail(`no task changes to commit after excluding the session log: ${logFile}`);
416
+ }
417
+
418
+ runChecked('bash', [path.join(repoRoot, PREPARE_COMMIT_SCRIPT)], { cwd: repoRoot, env: commandEnv });
419
+ runChecked('git', ['-C', repoRoot, 'commit', '-m', message], { cwd: repoRoot, env: commandEnv });
420
+
421
+ const commitSha = capture('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { env: commandEnv });
422
+ const recordArgs = [path.join(repoRoot, RECORD_CHAT_COMMIT_SCRIPT), commitSha, message, summary];
423
+ if (adrImpact) {
424
+ recordArgs.push(adrImpact);
425
+ }
426
+ runChecked('bash', recordArgs, { cwd: repoRoot, env: commandEnv });
427
+
428
+ if (checkpoint) {
429
+ runChecked('bash', [
430
+ path.join(repoRoot, CHECKPOINT_CHAT_SESSION_LOG_SCRIPT),
431
+ `chore(session): checkpoint ${commitSha.slice(0, 7)}`,
432
+ ], { cwd: repoRoot, env: commandEnv });
433
+ }
434
+
435
+ console.log(`Committed chat task work: ${commitSha}`);
436
+ }
437
+
438
+ function parseMergeMainArgs(args) {
439
+ let base = 'main';
440
+ let branch = '';
441
+
442
+ for (let index = 0; index < args.length; index += 1) {
443
+ const arg = args[index];
444
+
445
+ switch (arg) {
446
+ case '-h':
447
+ case '--help':
448
+ printHelp();
449
+ process.exit(0);
450
+ break;
451
+ case '--base':
452
+ if (index + 1 >= args.length) {
453
+ fail('--base requires a branch name', 2);
454
+ }
455
+ base = args[index + 1];
456
+ index += 1;
457
+ break;
458
+ default:
459
+ if (arg.startsWith('-')) {
460
+ fail(`unknown merge-main option: ${arg}`, 2);
461
+ }
462
+ if (branch) {
463
+ fail('merge-main accepts at most one chat branch', 2);
464
+ }
465
+ branch = arg;
466
+ }
467
+ }
468
+
469
+ return { base, branch };
470
+ }
471
+
472
+ function parseSessionsListArgs(args) {
473
+ let base = 'main';
474
+
475
+ for (let index = 0; index < args.length; index += 1) {
476
+ const arg = args[index];
477
+
478
+ switch (arg) {
479
+ case '-h':
480
+ case '--help':
481
+ printSessionsHelp();
482
+ process.exit(0);
483
+ break;
484
+ case '--base':
485
+ if (index + 1 >= args.length) {
486
+ fail('--base requires a branch name', 2);
487
+ }
488
+ base = args[index + 1];
489
+ index += 1;
490
+ break;
491
+ default:
492
+ if (arg.startsWith('-')) {
493
+ fail(`unknown sessions list option: ${arg}`, 2);
494
+ }
495
+ fail(`unexpected sessions list argument: ${arg}`, 2);
496
+ }
497
+ }
498
+
499
+ return { base };
500
+ }
501
+
502
+ function commandSessions(args) {
503
+ const [subcommand, ...subcommandArgs] = args;
504
+
505
+ if (!subcommand || subcommand === '-h' || subcommand === '--help' || subcommand === 'help') {
506
+ printSessionsHelp();
507
+ return;
508
+ }
509
+
510
+ if (subcommand !== 'list') {
511
+ fail(`unknown sessions command: ${subcommand}`, 2);
512
+ }
513
+
514
+ const { base } = parseSessionsListArgs(subcommandArgs);
515
+ const repoRoot = requireGitRepo(process.cwd());
516
+ requireInstalledWorkbench(repoRoot);
517
+ requireTargetScripts(repoRoot, [LIST_ACTIVE_CHAT_BRANCHES_SCRIPT]);
518
+
519
+ run('bash', [path.join(repoRoot, LIST_ACTIVE_CHAT_BRANCHES_SCRIPT), base], { cwd: repoRoot });
520
+ }
521
+
522
+ function commandMergeMain(args) {
523
+ const { base, branch: requestedBranch } = parseMergeMainArgs(args);
524
+ const currentRepoRoot = requireGitRepo(process.cwd());
525
+ const current = currentBranch(currentRepoRoot);
526
+ const targetBranch = requestedBranch || (current.startsWith('chat/') ? current : '');
527
+
528
+ if (!targetBranch) {
529
+ fail('merge-main needs a chat branch when not run from a chat worktree', 2);
530
+ }
531
+
532
+ if (!targetBranch.startsWith('chat/')) {
533
+ fail(`merge-main target is not a chat branch: ${targetBranch}`, 2);
534
+ }
535
+
536
+ const rootWorktree = primaryWorktree(currentRepoRoot);
537
+ requireInstalledWorkbench(rootWorktree);
538
+ requireTargetScripts(rootWorktree, [VERIFY_MERGE_READY_SCRIPT]);
539
+
540
+ const branchWorktree = worktreeForBranch(rootWorktree, targetBranch);
541
+ const commandEnv = branchWorktree
542
+ ? { ...process.env, AGENTIC_CHAT_WORKTREE_ROOT: path.dirname(branchWorktree) }
543
+ : process.env;
544
+
545
+ runChecked('bash', [
546
+ path.join(rootWorktree, VERIFY_MERGE_READY_SCRIPT),
547
+ '--base',
548
+ base,
549
+ targetBranch,
550
+ ], { cwd: rootWorktree, env: commandEnv });
551
+
552
+ runChecked('git', ['-C', rootWorktree, 'merge', '--no-ff', '--no-edit', targetBranch], {
553
+ cwd: rootWorktree,
554
+ env: commandEnv,
555
+ });
556
+
557
+ console.log(`Merged ${targetBranch} into local ${base}.`);
558
+ console.log('No remote push was performed.');
559
+ }
560
+
561
+ function parseInitArgs(args) {
562
+ let target = process.cwd();
563
+ let mode = '--apply';
564
+ let sawMode = false;
565
+ const installArgs = [];
566
+
567
+ for (let index = 0; index < args.length; index += 1) {
568
+ const arg = args[index];
569
+
570
+ switch (arg) {
571
+ case '-h':
572
+ case '--help':
573
+ printHelp();
574
+ process.exit(0);
575
+ break;
576
+ case '--target':
577
+ if (index + 1 >= args.length) {
578
+ fail('--target requires a repo path', 2);
579
+ }
580
+ target = args[index + 1];
581
+ index += 1;
582
+ break;
583
+ case '--dry-run':
584
+ case '--apply':
585
+ if (sawMode) {
586
+ fail('choose only one of --dry-run or --apply', 2);
587
+ }
588
+ mode = arg;
589
+ sawMode = true;
590
+ break;
591
+ case '--init-commit':
592
+ installArgs.push(arg);
593
+ break;
594
+ default:
595
+ if (arg.startsWith('-')) {
596
+ fail(`unknown init option: ${arg}`, 2);
597
+ }
598
+ fail(`unexpected init argument: ${arg}`, 2);
599
+ }
600
+ }
601
+
602
+ return { target, mode, installArgs };
603
+ }
604
+
605
+ function commandInit(args) {
606
+ const { target, mode, installArgs } = parseInitArgs(args);
607
+ const repoRoot = requireGitRepo(path.resolve(target));
608
+ const installScript = requirePackageScript(path.relative(PACKAGE_ROOT, INSTALL_SCRIPT));
609
+
610
+ // The npm package invokes its own bundled installer, while the installer
611
+ // writes into the caller's Git repository.
612
+ run('bash', [installScript, mode, ...installArgs, repoRoot], { cwd: PACKAGE_ROOT });
613
+ }
614
+
615
+ function commandList(args) {
616
+ if (args.length > 0) {
617
+ fail('list does not accept arguments', 2);
618
+ }
619
+
620
+ const repoRoot = requireGitRepo(process.cwd());
621
+ requireInstalledWorkbench(repoRoot);
622
+ requireTargetScripts(repoRoot, [DISPATCHER_SCRIPT]);
623
+
624
+ run('bash', [path.join(repoRoot, DISPATCHER_SCRIPT), 'list'], { cwd: repoRoot });
625
+ }
626
+
627
+ function commandNew(args) {
628
+ const repoRoot = requireGitRepo(process.cwd());
629
+ requireInstalledWorkbench(repoRoot);
630
+ requireTargetScripts(repoRoot, [
631
+ DISPATCHER_SCRIPT,
632
+ NEW_COMMAND_SCRIPT,
633
+ START_NEW_CHAT_SCRIPT,
634
+ START_CHAT_SESSION_SCRIPT,
635
+ ]);
636
+
637
+ run('bash', [path.join(repoRoot, DISPATCHER_SCRIPT), 'new', ...args], { cwd: repoRoot });
638
+ }
639
+
640
+ function main(argv) {
641
+ const [command, ...args] = argv;
642
+
643
+ if (!command || command === '-h' || command === '--help' || command === 'help') {
644
+ printHelp();
645
+ return;
646
+ }
647
+
648
+ switch (command) {
649
+ case 'init':
650
+ commandInit(args);
651
+ break;
652
+ case 'list':
653
+ commandList(args);
654
+ break;
655
+ case 'new':
656
+ commandNew(args);
657
+ break;
658
+ case 'sessions':
659
+ commandSessions(args);
660
+ break;
661
+ case 'commit':
662
+ commandCommit(args);
663
+ break;
664
+ case 'merge-main':
665
+ commandMergeMain(args);
666
+ break;
667
+ default:
668
+ fail(`unknown command: ${command}`, 2, true);
669
+ }
670
+ }
671
+
672
+ main(process.argv.slice(2));
@@ -0,0 +1,47 @@
1
+ <!-- agentic-artifact:
2
+ schema: agentic-artifact/v2
3
+ id: chat.docs.readme
4
+ version: 1
5
+ status: active
6
+ layer: 00.chat
7
+ domain: documentation
8
+ disciplines:
9
+ - agentic
10
+ kind: readme
11
+ purpose: Index chat workbench documentation that supports the portable chat harness
12
+ and public bootstrap.
13
+ portability:
14
+ class: required
15
+ targets:
16
+ - llm-workbench
17
+ used_by:
18
+ - id: chat.workflows.bootstrap-chat-workbench-repo
19
+ path: .agentic/00.chat/workflows/bootstrap-chat-workbench-repo.md
20
+ - id: chat.script.upstream.bootstrap-llm-workbench-repo
21
+ path: scripts/00.chat/upstream/bootstrap-llm-workbench-repo/script.sh
22
+ -->
23
+ # Chat Workbench Docs
24
+
25
+ This folder contains documentation owned by the chat layer.
26
+
27
+ These files explain the portable chat workbench shape, not the source product
28
+ repo as a whole. Keep docs here when they are about chat lifecycle scripts,
29
+ public `llm-workbench` bootstrap boundaries, or current public-beta
30
+ portability behavior.
31
+
32
+ ## Files
33
+
34
+ - `script-layout.md` explains the numbered `scripts/` layer command-surface
35
+ convention, including the current `scripts/00.chat/` and
36
+ `scripts/01.harness/` split.
37
+ - `llm-workbench-acceptance-matrix.md` maps the public-beta contract to the
38
+ artifacts and checks that enforce it.
39
+ - `bootstrap/llm-workbench-template/` contains starter public repo shell files
40
+ for the first `llm-workbench` bootstrap.
41
+
42
+ ## Maintainer History
43
+
44
+ Maintainer decision history remains in the source repo and is not exported.
45
+ Public workbench exports should be understandable from current public docs such
46
+ as `docs/install.md`, `docs/workflows.md`, `docs/concepts.md`, and
47
+ `docs/adapting-to-your-repo.md`.