lastlight 0.16.0 → 0.16.1

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 (385) hide show
  1. package/README.md +71 -692
  2. package/dist/cli-config.js.map +1 -0
  3. package/dist/cli-format.js.map +1 -0
  4. package/dist/{cli/cli-server.d.ts → cli-server.d.ts} +26 -1
  5. package/dist/{cli/cli-server.js → cli-server.js} +40 -5
  6. package/dist/cli-server.js.map +1 -0
  7. package/dist/cli-timeline.js.map +1 -0
  8. package/dist/{cli/cli.js → cli.js} +4 -3
  9. package/dist/cli.js.map +1 -0
  10. package/dist/{cli/fork-cli.d.ts → fork-cli.d.ts} +3 -3
  11. package/dist/{cli/fork-cli.js → fork-cli.js} +28 -27
  12. package/dist/fork-cli.js.map +1 -0
  13. package/dist/{cli/oauth-cli.js → oauth-cli.js} +1 -1
  14. package/dist/oauth-cli.js.map +1 -0
  15. package/dist/{cli/setup.js → setup.js} +10 -4
  16. package/dist/setup.js.map +1 -0
  17. package/dist/{cli/skills-install.d.ts → skills-install.d.ts} +6 -0
  18. package/dist/{cli/skills-install.js → skills-install.js} +6 -2
  19. package/dist/skills-install.js.map +1 -0
  20. package/package.json +20 -65
  21. package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
  22. package/agent-context/rules.md +0 -80
  23. package/agent-context/security.md +0 -69
  24. package/agent-context/soul.md +0 -21
  25. package/config/default.yaml +0 -119
  26. package/deploy/.env.production.example +0 -100
  27. package/deploy/entrypoint.sh +0 -82
  28. package/deploy/native/README.md +0 -160
  29. package/deploy/native/install.sh +0 -110
  30. package/deploy/native/lastlight.env.example +0 -95
  31. package/deploy/native/lastlight.service +0 -50
  32. package/deploy/sandbox-entrypoint.sh +0 -93
  33. package/dist/admin/auth.d.ts +0 -51
  34. package/dist/admin/auth.js +0 -121
  35. package/dist/admin/auth.js.map +0 -1
  36. package/dist/admin/chat-session-reader.d.ts +0 -39
  37. package/dist/admin/chat-session-reader.js +0 -84
  38. package/dist/admin/chat-session-reader.js.map +0 -1
  39. package/dist/admin/docker.d.ts +0 -61
  40. package/dist/admin/docker.js +0 -187
  41. package/dist/admin/docker.js.map +0 -1
  42. package/dist/admin/index.d.ts +0 -9
  43. package/dist/admin/index.js +0 -59
  44. package/dist/admin/index.js.map +0 -1
  45. package/dist/admin/routes.d.ts +0 -81
  46. package/dist/admin/routes.js +0 -1381
  47. package/dist/admin/routes.js.map +0 -1
  48. package/dist/admin/sessions.d.ts +0 -89
  49. package/dist/admin/sessions.js +0 -204
  50. package/dist/admin/sessions.js.map +0 -1
  51. package/dist/admin/tail.d.ts +0 -12
  52. package/dist/admin/tail.js +0 -145
  53. package/dist/admin/tail.js.map +0 -1
  54. package/dist/admin/version.d.ts +0 -22
  55. package/dist/admin/version.js +0 -92
  56. package/dist/admin/version.js.map +0 -1
  57. package/dist/cli/cli-config.js.map +0 -1
  58. package/dist/cli/cli-format.js.map +0 -1
  59. package/dist/cli/cli-server.js.map +0 -1
  60. package/dist/cli/cli-timeline.js.map +0 -1
  61. package/dist/cli/cli.js.map +0 -1
  62. package/dist/cli/fork-cli.js.map +0 -1
  63. package/dist/cli/oauth-cli.js.map +0 -1
  64. package/dist/cli/setup.js.map +0 -1
  65. package/dist/cli/skills-install.js.map +0 -1
  66. package/dist/config/config-resolve.d.ts +0 -28
  67. package/dist/config/config-resolve.js +0 -47
  68. package/dist/config/config-resolve.js.map +0 -1
  69. package/dist/config/config.d.ts +0 -157
  70. package/dist/config/config.js +0 -581
  71. package/dist/config/config.js.map +0 -1
  72. package/dist/config/core-pin.d.ts +0 -16
  73. package/dist/config/core-pin.js +0 -85
  74. package/dist/config/core-pin.js.map +0 -1
  75. package/dist/config/overlay-assets.d.ts +0 -20
  76. package/dist/config/overlay-assets.js +0 -114
  77. package/dist/config/overlay-assets.js.map +0 -1
  78. package/dist/config/overlay-bootstrap.d.ts +0 -44
  79. package/dist/config/overlay-bootstrap.js +0 -304
  80. package/dist/config/overlay-bootstrap.js.map +0 -1
  81. package/dist/connectors/github-webhook.d.ts +0 -45
  82. package/dist/connectors/github-webhook.js +0 -321
  83. package/dist/connectors/github-webhook.js.map +0 -1
  84. package/dist/connectors/index.d.ts +0 -18
  85. package/dist/connectors/index.js +0 -41
  86. package/dist/connectors/index.js.map +0 -1
  87. package/dist/connectors/messaging/base.d.ts +0 -42
  88. package/dist/connectors/messaging/base.js +0 -139
  89. package/dist/connectors/messaging/base.js.map +0 -1
  90. package/dist/connectors/messaging/delivery.d.ts +0 -14
  91. package/dist/connectors/messaging/delivery.js +0 -38
  92. package/dist/connectors/messaging/delivery.js.map +0 -1
  93. package/dist/connectors/messaging/index.d.ts +0 -4
  94. package/dist/connectors/messaging/index.js +0 -4
  95. package/dist/connectors/messaging/index.js.map +0 -1
  96. package/dist/connectors/messaging/session-manager.d.ts +0 -53
  97. package/dist/connectors/messaging/session-manager.js +0 -255
  98. package/dist/connectors/messaging/session-manager.js.map +0 -1
  99. package/dist/connectors/messaging/types.d.ts +0 -58
  100. package/dist/connectors/messaging/types.js +0 -7
  101. package/dist/connectors/messaging/types.js.map +0 -1
  102. package/dist/connectors/slack/connector.d.ts +0 -142
  103. package/dist/connectors/slack/connector.js +0 -494
  104. package/dist/connectors/slack/connector.js.map +0 -1
  105. package/dist/connectors/slack/index.d.ts +0 -2
  106. package/dist/connectors/slack/index.js +0 -3
  107. package/dist/connectors/slack/index.js.map +0 -1
  108. package/dist/connectors/slack/mrkdwn.d.ts +0 -19
  109. package/dist/connectors/slack/mrkdwn.js +0 -240
  110. package/dist/connectors/slack/mrkdwn.js.map +0 -1
  111. package/dist/connectors/types.d.ts +0 -61
  112. package/dist/connectors/types.js +0 -2
  113. package/dist/connectors/types.js.map +0 -1
  114. package/dist/cron/fanout.d.ts +0 -33
  115. package/dist/cron/fanout.js +0 -55
  116. package/dist/cron/fanout.js.map +0 -1
  117. package/dist/cron/jobs.d.ts +0 -18
  118. package/dist/cron/jobs.js +0 -37
  119. package/dist/cron/jobs.js.map +0 -1
  120. package/dist/cron/scheduler.d.ts +0 -45
  121. package/dist/cron/scheduler.js +0 -105
  122. package/dist/cron/scheduler.js.map +0 -1
  123. package/dist/engine/agent-executor.d.ts +0 -34
  124. package/dist/engine/agent-executor.js +0 -246
  125. package/dist/engine/agent-executor.js.map +0 -1
  126. package/dist/engine/chat/chat-runner.d.ts +0 -132
  127. package/dist/engine/chat/chat-runner.js +0 -424
  128. package/dist/engine/chat/chat-runner.js.map +0 -1
  129. package/dist/engine/chat/chat-skills.d.ts +0 -45
  130. package/dist/engine/chat/chat-skills.js +0 -184
  131. package/dist/engine/chat/chat-skills.js.map +0 -1
  132. package/dist/engine/chat/chat.d.ts +0 -54
  133. package/dist/engine/chat/chat.js +0 -316
  134. package/dist/engine/chat/chat.js.map +0 -1
  135. package/dist/engine/chat/message-batcher.d.ts +0 -32
  136. package/dist/engine/chat/message-batcher.js +0 -109
  137. package/dist/engine/chat/message-batcher.js.map +0 -1
  138. package/dist/engine/dispatcher.d.ts +0 -73
  139. package/dist/engine/dispatcher.js +0 -663
  140. package/dist/engine/dispatcher.js.map +0 -1
  141. package/dist/engine/event-shim.d.ts +0 -117
  142. package/dist/engine/event-shim.js +0 -435
  143. package/dist/engine/event-shim.js.map +0 -1
  144. package/dist/engine/executors/orchestrator.d.ts +0 -81
  145. package/dist/engine/executors/orchestrator.js +0 -365
  146. package/dist/engine/executors/orchestrator.js.map +0 -1
  147. package/dist/engine/executors/shared.d.ts +0 -189
  148. package/dist/engine/executors/shared.js +0 -616
  149. package/dist/engine/executors/shared.js.map +0 -1
  150. package/dist/engine/github/git-auth.d.ts +0 -62
  151. package/dist/engine/github/git-auth.js +0 -161
  152. package/dist/engine/github/git-auth.js.map +0 -1
  153. package/dist/engine/github/github-app-client.d.ts +0 -23
  154. package/dist/engine/github/github-app-client.js +0 -33
  155. package/dist/engine/github/github-app-client.js.map +0 -1
  156. package/dist/engine/github/github-tools.d.ts +0 -18
  157. package/dist/engine/github/github-tools.js +0 -263
  158. package/dist/engine/github/github-tools.js.map +0 -1
  159. package/dist/engine/github/github.d.ts +0 -1193
  160. package/dist/engine/github/github.js +0 -357
  161. package/dist/engine/github/github.js.map +0 -1
  162. package/dist/engine/github/profiles.d.ts +0 -279
  163. package/dist/engine/github/profiles.js +0 -42
  164. package/dist/engine/github/profiles.js.map +0 -1
  165. package/dist/engine/github/review-poster.d.ts +0 -98
  166. package/dist/engine/github/review-poster.js +0 -177
  167. package/dist/engine/github/review-poster.js.map +0 -1
  168. package/dist/engine/llm.d.ts +0 -79
  169. package/dist/engine/llm.js +0 -269
  170. package/dist/engine/llm.js.map +0 -1
  171. package/dist/engine/oauth.d.ts +0 -43
  172. package/dist/engine/oauth.js +0 -103
  173. package/dist/engine/oauth.js.map +0 -1
  174. package/dist/engine/router.d.ts +0 -29
  175. package/dist/engine/router.js +0 -680
  176. package/dist/engine/router.js.map +0 -1
  177. package/dist/engine/screen/classifier.d.ts +0 -93
  178. package/dist/engine/screen/classifier.js +0 -215
  179. package/dist/engine/screen/classifier.js.map +0 -1
  180. package/dist/engine/screen/screen.d.ts +0 -41
  181. package/dist/engine/screen/screen.js +0 -93
  182. package/dist/engine/screen/screen.js.map +0 -1
  183. package/dist/evals-api.d.ts +0 -24
  184. package/dist/evals-api.js +0 -22
  185. package/dist/evals-api.js.map +0 -1
  186. package/dist/index.d.ts +0 -1
  187. package/dist/index.js +0 -940
  188. package/dist/index.js.map +0 -1
  189. package/dist/managed-repos.d.ts +0 -30
  190. package/dist/managed-repos.js +0 -72
  191. package/dist/managed-repos.js.map +0 -1
  192. package/dist/notify/blocks.d.ts +0 -23
  193. package/dist/notify/blocks.js +0 -118
  194. package/dist/notify/blocks.js.map +0 -1
  195. package/dist/notify/index.d.ts +0 -17
  196. package/dist/notify/index.js +0 -7
  197. package/dist/notify/index.js.map +0 -1
  198. package/dist/notify/model.d.ts +0 -57
  199. package/dist/notify/model.js +0 -89
  200. package/dist/notify/model.js.map +0 -1
  201. package/dist/notify/notifier.d.ts +0 -18
  202. package/dist/notify/notifier.js +0 -100
  203. package/dist/notify/notifier.js.map +0 -1
  204. package/dist/notify/render.d.ts +0 -21
  205. package/dist/notify/render.js +0 -59
  206. package/dist/notify/render.js.map +0 -1
  207. package/dist/notify/transports/github.d.ts +0 -26
  208. package/dist/notify/transports/github.js +0 -27
  209. package/dist/notify/transports/github.js.map +0 -1
  210. package/dist/notify/transports/slack.d.ts +0 -27
  211. package/dist/notify/transports/slack.js +0 -37
  212. package/dist/notify/transports/slack.js.map +0 -1
  213. package/dist/notify/types.d.ts +0 -119
  214. package/dist/notify/types.js +0 -16
  215. package/dist/notify/types.js.map +0 -1
  216. package/dist/providers.d.ts +0 -125
  217. package/dist/providers.js +0 -279
  218. package/dist/providers.js.map +0 -1
  219. package/dist/sandbox/docker.d.ts +0 -180
  220. package/dist/sandbox/docker.js +0 -517
  221. package/dist/sandbox/docker.js.map +0 -1
  222. package/dist/sandbox/egress-allowlist.d.ts +0 -74
  223. package/dist/sandbox/egress-allowlist.js +0 -164
  224. package/dist/sandbox/egress-allowlist.js.map +0 -1
  225. package/dist/sandbox/egress-firewall-config.d.ts +0 -127
  226. package/dist/sandbox/egress-firewall-config.js +0 -434
  227. package/dist/sandbox/egress-firewall-config.js.map +0 -1
  228. package/dist/sandbox/images.d.ts +0 -23
  229. package/dist/sandbox/images.js +0 -55
  230. package/dist/sandbox/images.js.map +0 -1
  231. package/dist/sandbox/index.d.ts +0 -112
  232. package/dist/sandbox/index.js +0 -332
  233. package/dist/sandbox/index.js.map +0 -1
  234. package/dist/sandbox/sandbox.d.ts +0 -214
  235. package/dist/sandbox/sandbox.js +0 -453
  236. package/dist/sandbox/sandbox.js.map +0 -1
  237. package/dist/sandbox/smol.d.ts +0 -162
  238. package/dist/sandbox/smol.js +0 -485
  239. package/dist/sandbox/smol.js.map +0 -1
  240. package/dist/session-log.d.ts +0 -63
  241. package/dist/session-log.js +0 -290
  242. package/dist/session-log.js.map +0 -1
  243. package/dist/state/approval-store.d.ts +0 -88
  244. package/dist/state/approval-store.js +0 -149
  245. package/dist/state/approval-store.js.map +0 -1
  246. package/dist/state/build-assets.d.ts +0 -50
  247. package/dist/state/build-assets.js +0 -154
  248. package/dist/state/build-assets.js.map +0 -1
  249. package/dist/state/db.d.ts +0 -81
  250. package/dist/state/db.js +0 -155
  251. package/dist/state/db.js.map +0 -1
  252. package/dist/state/execution-store.d.ts +0 -257
  253. package/dist/state/execution-store.js +0 -527
  254. package/dist/state/execution-store.js.map +0 -1
  255. package/dist/state/migrate.d.ts +0 -15
  256. package/dist/state/migrate.js +0 -175
  257. package/dist/state/migrate.js.map +0 -1
  258. package/dist/state/workflow-run-store.d.ts +0 -216
  259. package/dist/state/workflow-run-store.js +0 -403
  260. package/dist/state/workflow-run-store.js.map +0 -1
  261. package/dist/telemetry/index.d.ts +0 -38
  262. package/dist/telemetry/index.js +0 -253
  263. package/dist/telemetry/index.js.map +0 -1
  264. package/dist/telemetry/pi-events.d.ts +0 -12
  265. package/dist/telemetry/pi-events.js +0 -134
  266. package/dist/telemetry/pi-events.js.map +0 -1
  267. package/dist/workflows/dag.d.ts +0 -37
  268. package/dist/workflows/dag.js +0 -118
  269. package/dist/workflows/dag.js.map +0 -1
  270. package/dist/workflows/loader.d.ts +0 -53
  271. package/dist/workflows/loader.js +0 -421
  272. package/dist/workflows/loader.js.map +0 -1
  273. package/dist/workflows/loop-eval.d.ts +0 -28
  274. package/dist/workflows/loop-eval.js +0 -74
  275. package/dist/workflows/loop-eval.js.map +0 -1
  276. package/dist/workflows/phase-executor.d.ts +0 -222
  277. package/dist/workflows/phase-executor.js +0 -1153
  278. package/dist/workflows/phase-executor.js.map +0 -1
  279. package/dist/workflows/phase-ref.d.ts +0 -50
  280. package/dist/workflows/phase-ref.js +0 -91
  281. package/dist/workflows/phase-ref.js.map +0 -1
  282. package/dist/workflows/resume.d.ts +0 -58
  283. package/dist/workflows/resume.js +0 -313
  284. package/dist/workflows/resume.js.map +0 -1
  285. package/dist/workflows/runner.d.ts +0 -63
  286. package/dist/workflows/runner.js +0 -381
  287. package/dist/workflows/runner.js.map +0 -1
  288. package/dist/workflows/schema.d.ts +0 -344
  289. package/dist/workflows/schema.js +0 -460
  290. package/dist/workflows/schema.js.map +0 -1
  291. package/dist/workflows/simple.d.ts +0 -90
  292. package/dist/workflows/simple.js +0 -393
  293. package/dist/workflows/simple.js.map +0 -1
  294. package/dist/workflows/target-policy.d.ts +0 -74
  295. package/dist/workflows/target-policy.js +0 -80
  296. package/dist/workflows/target-policy.js.map +0 -1
  297. package/dist/workflows/templates.d.ts +0 -88
  298. package/dist/workflows/templates.js +0 -127
  299. package/dist/workflows/templates.js.map +0 -1
  300. package/dist/workflows/triggers.d.ts +0 -21
  301. package/dist/workflows/triggers.js +0 -45
  302. package/dist/workflows/triggers.js.map +0 -1
  303. package/dist/workflows/verdict.d.ts +0 -19
  304. package/dist/workflows/verdict.js +0 -30
  305. package/dist/workflows/verdict.js.map +0 -1
  306. package/dist/worktree/manager.d.ts +0 -43
  307. package/dist/worktree/manager.js +0 -127
  308. package/dist/worktree/manager.js.map +0 -1
  309. package/docker-compose.yml +0 -274
  310. package/sandbox.Dockerfile +0 -68
  311. package/skills/README.md +0 -42
  312. package/skills/browser-qa/SKILL.md +0 -208
  313. package/skills/browser-qa/scripts/agent-browser.mjs +0 -531
  314. package/skills/building/SKILL.md +0 -95
  315. package/skills/chat/SKILL.md +0 -36
  316. package/skills/code-review/SKILL.md +0 -82
  317. package/skills/debug-production/SKILL.md +0 -108
  318. package/skills/demo/SKILL.md +0 -228
  319. package/skills/demo/scripts/compose-demo.sh +0 -243
  320. package/skills/issue-answer/SKILL.md +0 -91
  321. package/skills/issue-comment/SKILL.md +0 -49
  322. package/skills/issue-triage/SKILL.md +0 -149
  323. package/skills/issue-triage/references/AGENT-BRIEF.md +0 -65
  324. package/skills/pr-comment/SKILL.md +0 -64
  325. package/skills/pr-review/SKILL.md +0 -148
  326. package/skills/pr-review/references/findings-schema.md +0 -89
  327. package/skills/qa-test/SKILL.md +0 -97
  328. package/skills/repo-health/SKILL.md +0 -67
  329. package/skills/security-feedback/SKILL.md +0 -173
  330. package/skills/security-feedback/references/templates.md +0 -77
  331. package/skills/security-review/SKILL.md +0 -215
  332. package/skills/security-review/references/issue-format.md +0 -302
  333. package/skills/verify/SKILL.md +0 -118
  334. package/workflows/answer.yaml +0 -44
  335. package/workflows/build.yaml +0 -129
  336. package/workflows/cron-health.yaml +0 -6
  337. package/workflows/cron-review.yaml +0 -8
  338. package/workflows/cron-security.yaml +0 -6
  339. package/workflows/cron-triage.yaml +0 -8
  340. package/workflows/demo.yaml +0 -62
  341. package/workflows/examples/bash-smoke.yaml +0 -41
  342. package/workflows/examples/parallel-review.yaml +0 -36
  343. package/workflows/examples/tdd-loop.yaml +0 -17
  344. package/workflows/explore.yaml +0 -80
  345. package/workflows/issue-comment.yaml +0 -15
  346. package/workflows/issue-triage.yaml +0 -22
  347. package/workflows/pr-comment.yaml +0 -16
  348. package/workflows/pr-fix.yaml +0 -17
  349. package/workflows/pr-review.yaml +0 -50
  350. package/workflows/prompts/answer.md +0 -68
  351. package/workflows/prompts/architect.md +0 -44
  352. package/workflows/prompts/classifier.md +0 -60
  353. package/workflows/prompts/classify-adds-info.md +0 -17
  354. package/workflows/prompts/demo.md +0 -125
  355. package/workflows/prompts/executor.md +0 -42
  356. package/workflows/prompts/explore-ask.md +0 -99
  357. package/workflows/prompts/explore-publish.md +0 -31
  358. package/workflows/prompts/explore-read.md +0 -120
  359. package/workflows/prompts/explore-synthesize.md +0 -88
  360. package/workflows/prompts/fix.md +0 -18
  361. package/workflows/prompts/guardrails.md +0 -96
  362. package/workflows/prompts/pr-fix.md +0 -26
  363. package/workflows/prompts/pr.md +0 -39
  364. package/workflows/prompts/qa-browser.md +0 -95
  365. package/workflows/prompts/qa-synth.md +0 -44
  366. package/workflows/prompts/qa-test.md +0 -60
  367. package/workflows/prompts/re-reviewer.md +0 -33
  368. package/workflows/prompts/reviewer.md +0 -60
  369. package/workflows/prompts/verify-browser.md +0 -93
  370. package/workflows/prompts/verify-synth.md +0 -44
  371. package/workflows/prompts/verify.md +0 -63
  372. package/workflows/qa-test.yaml +0 -94
  373. package/workflows/repo-health.yaml +0 -15
  374. package/workflows/security-feedback.yaml +0 -25
  375. package/workflows/security-review.yaml +0 -37
  376. package/workflows/verify.yaml +0 -93
  377. /package/dist/{cli/cli-config.d.ts → cli-config.d.ts} +0 -0
  378. /package/dist/{cli/cli-config.js → cli-config.js} +0 -0
  379. /package/dist/{cli/cli-format.d.ts → cli-format.d.ts} +0 -0
  380. /package/dist/{cli/cli-format.js → cli-format.js} +0 -0
  381. /package/dist/{cli/cli-timeline.d.ts → cli-timeline.d.ts} +0 -0
  382. /package/dist/{cli/cli-timeline.js → cli-timeline.js} +0 -0
  383. /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
  384. /package/dist/{cli/oauth-cli.d.ts → oauth-cli.d.ts} +0 -0
  385. /package/dist/{cli/setup.d.ts → setup.d.ts} +0 -0
@@ -1,82 +0,0 @@
1
- ---
2
- name: code-review
3
- description: The shared rubric for reviewing a code change — precision-first, high-signal findings only (Critical/Important), plus what to check (correctness, security, edge cases, regression risk, test coverage). Use when reviewing a PR or a branch diff.
4
- version: 2.0.0
5
- tags: [review, code-quality]
6
- ---
7
-
8
- # Code Review
9
-
10
- The shared rubric for assessing a code change. Used both by a PR review and by
11
- the build cycle's branch-diff reviewer — the *procedure* differs (where the diff
12
- comes from, how the verdict is recorded), but the rubric below is the same.
13
-
14
- Review the change **in full context** — read each changed file, not just the
15
- hunk. For a large change (>300 lines or >5 files): trace data flow through
16
- modified functions, check callers of anything whose signature or behaviour
17
- changed for regression risk, and check that tests cover the actual risk areas,
18
- not just the happy path.
19
-
20
- ## Precision first — post only what matters
21
-
22
- A review is only useful if people trust it. A noisy reviewer gets muted; every
23
- low-value comment you post spends the credibility of the ones that matter. So
24
- the bar is **high-signal only**:
25
-
26
- - **Post only Critical and Important findings.** Suggestions and Nits (below)
27
- are *not* worth a formal review comment on their own — drop them, or fold at
28
- most one genuinely valuable line into the summary. When in doubt, leave it out.
29
- - **If you cannot name the concrete impact — what breaks, and for which input or
30
- caller — do not post it.** "This could be cleaner" is not a finding; "this
31
- crashes when `items` is empty because line 42 indexes `[0]`" is.
32
- - **Confidence gate.** Before you finalise, re-read each finding against the
33
- actual code and try to *refute your own claim*. If you can't defend it against
34
- what the code actually does (not what you assumed), drop it. A missed marginal
35
- issue costs far less than a wrong one.
36
-
37
- ## Finding tiers
38
-
39
- Categorise every finding into exactly one tier. **Only Critical and Important
40
- are posted** (see Precision first):
41
-
42
- - **Critical** — security issues, data loss, breaking changes, silent
43
- data-dropping (see Correctness below). Blocks merge.
44
- - **Important** — missing tests, performance problems, type errors, **avoidable
45
- duplication**, **excessive complexity**, **compiler-silencing assertions**.
46
- Should fix.
47
- - **Suggestions** — clarity, naming, minor DRY tidy-ups. *Not posted* — noise
48
- in a formal review.
49
- - **Nits** — style, formatting. *Not posted* — this is the linter's job.
50
-
51
- Every posted finding carries a **one-line concrete impact**: the consequence
52
- (what breaks / for whom) and, where it helps, the fix. That local reasoning is
53
- what makes a comment actionable rather than a vague worry.
54
-
55
- ## What to check
56
-
57
- - **Correctness** — does it do what it claims? Logic errors, off-by-one, wrong
58
- conditions, mishandled async. **A silent default or a dropped output for an
59
- input the code doesn't support is a correctness bug, not graceful handling** —
60
- flag any unsupported case that is silently defaulted, skipped, or omitted
61
- instead of warned-and-skipped or warned-and-surfaced.
62
- - **Edge cases** — empty/null inputs, boundaries, error paths, concurrency.
63
- - **Security** — injection, auth/authorization, secret handling, untrusted input.
64
- - **Complexity** — flag functions past ~15 cyclomatic complexity or that mix
65
- parse/validate/emit responsibilities; ask for helper extraction. This is an
66
- **Important** finding, not a nit.
67
- - **Duplication** — flag avoidable duplicated logic (two or more clone groups of
68
- the same code/branching). DRY is **should-fix** here, not merely "nice to have."
69
- - **Type safety** — flag `as any`, unchecked `as`-casts, or `@ts-ignore` used to
70
- silence the compiler or to bypass a validator the same code path defines.
71
- - **Regression risk** — existing callers of changed functions; behaviour changes
72
- that ripple.
73
- - **Test coverage** — do the tests exercise the real risk, or just the happy path?
74
- - **Fit** — does it match the codebase's existing patterns and conventions?
75
-
76
- ## Calibration
77
-
78
- - Don't nitpick generated files (lockfiles, compiled assets).
79
- - Don't repeat what linters/CI already catch.
80
- - Don't block over style preferences alone.
81
- - Read the room: if a human reviewer already approved, lower the bar for blocking
82
- — prefer a comment over requesting changes on non-critical findings.
@@ -1,108 +0,0 @@
1
- ---
2
- name: debug-production
3
- description: Debug a running Last Light instance (usually production) via the `lastlight` CLI instead of SSH. Use when investigating a failed/stuck workflow, a bad agent run, a phase error, or "why didn't the bot do X" — anything that previously meant SSHing in to read SQLite or session logs.
4
- version: 1.0.0
5
- tags: [ops, debugging, cli]
6
- ---
7
-
8
- # Debug a Last Light instance via the CLI
9
-
10
- Read a running instance's state over its admin API with the `lastlight` CLI —
11
- no SSH, no poking SQLite or jsonl files on the box. Every command takes
12
- `--json` for machine-parseable output; prefer it when you need to extract
13
- fields.
14
-
15
- ## 0. Connect
16
-
17
- The CLI talks to whatever instance you logged into (saved in
18
- `~/.lastlight/config.json`). Confirm you're pointed at the right one and the
19
- token is valid:
20
-
21
- ```bash
22
- lastlight status # instance URL, server health, token validity
23
- ```
24
-
25
- If the token is missing/expired, authenticate (the prod instance URL lives in
26
- local agent memory — see the "Last Light production server" note):
27
-
28
- ```bash
29
- lastlight login https://<instance> # browser handoff (password/Slack/GitHub)
30
- lastlight login https://<instance> --password # headless fallback
31
- ```
32
-
33
- For one-off targeting without saving, pass `--url` / `--token` on any command,
34
- or set `LASTLIGHT_URL` / `LASTLIGHT_TOKEN` (env wins over the saved config).
35
-
36
- ## 1. Find the broken run
37
-
38
- ```bash
39
- lastlight workflow list --status failed # recent failures
40
- lastlight workflow list --status active # running + paused now
41
- lastlight workflow list --workflow pr-review --limit 50
42
- ```
43
-
44
- Copy the run `ID` from the table.
45
-
46
- ## 2. Inspect the run's phases
47
-
48
- ```bash
49
- lastlight workflow log <run-id> # run header + per-phase table (✓/✗, duration, session id, error)
50
- lastlight workflow log <run-id> --follow # then tail the current phase live
51
- ```
52
-
53
- The phase table shows which phase failed, its error snippet, and the `SESSION`
54
- id you need for the transcript.
55
-
56
- ## 3. Read / tail the agent transcript
57
-
58
- ```bash
59
- lastlight session list # recent sandbox sessions (TYPE, MODEL, LIVE)
60
- lastlight session log <session-id> # full transcript
61
- lastlight session log <session-id> --follow # live tail (SSE) of an in-flight phase
62
- ```
63
-
64
- ## 4. Search across runs
65
-
66
- ```bash
67
- lastlight logs search "ECONNREFUSED" # errors ledger (default)
68
- lastlight logs search "rate limit" --scope all # errors + transcript content
69
- lastlight logs search "could not clone" --scope messages --limit 20
70
- ```
71
-
72
- `--scope errors` (default) matches the executions ledger (error/skill/repo);
73
- `messages` greps recent session transcripts; `all` does both.
74
-
75
- ## 5. Raw server / docker logs
76
-
77
- When the workflow + session views aren't enough — a crash before any session
78
- was written, a sidecar misbehaving, harness boot errors — read the actual
79
- container logs (`docker logs`) over the API:
80
-
81
- ```bash
82
- lastlight server list # the lastlight-* containers + status
83
- lastlight server logs # the agent harness (default), last 200 lines
84
- lastlight server logs --tail 500 --since 10m
85
- lastlight server logs --follow # live tail (Ctrl-C to stop)
86
- lastlight server logs coredns-strict # a specific sidecar by service or container name
87
- ```
88
-
89
- ## 6. Approvals & health
90
-
91
- ```bash
92
- lastlight approvals list # paused gates waiting on a human
93
- lastlight approvals approve <id> --reason "looks good"
94
- lastlight approvals reject <id> --reason "wrong approach"
95
-
96
- lastlight stats # totals + per-skill success/fail
97
- lastlight stats --daily 14 # last 14 days of cost/tokens
98
- ```
99
-
100
- ## Tips
101
-
102
- - Add `--json` to any command to pipe into `jq` (e.g.
103
- `lastlight workflow list --status failed --json | jq -r '.workflowRuns[].id'`).
104
- - Triage flow: `workflow list --status failed` → `workflow log <id>` → grab the
105
- failed phase's session → `session log <session-id>` → if it's a recurring
106
- symptom, `logs search "<error text>"` to see how often it happens.
107
- - These are read-only except `approvals approve/reject` and the trigger
108
- commands (`build`/`triage`/`review`/…) — debugging never mutates the box.
@@ -1,228 +0,0 @@
1
- ---
2
- name: demo
3
- description: Record a short demo VIDEO of a PR or feature — drive the repo's web UI in a real headless browser, capture the session, and composite a titled, size-capped mp4 with ffmpeg. Use on the docker QA image when the deliverable is a playable demo clip (single walkthrough or before/after comparison), not a text/screenshot report.
4
- version: 1.0.0
5
- tags: [demo, video, browser, ffmpeg, evidence]
6
- ---
7
-
8
- # Demo
9
-
10
- Turn a PR or feature into a short, polished **demo video**: drive the repo's
11
- web UI in a real headless Chromium, screen-record the session, and composite a
12
- titled, size-capped **mp4** the maintainer can watch inline.
13
-
14
- This is the **video** sibling of `browser-qa` (screenshots) and `verify`/
15
- `qa-test` (text evidence). It only works on the `lastlight-sandbox-qa:latest`
16
- docker image, which bakes in Playwright + Chromium **and ffmpeg**. You have
17
- bash + file tools but **no vision** — you reason over the driver's JSON stdout
18
- and the ffprobe summary; you never "see" the video.
19
-
20
- Compositing is **ffmpeg only** — there is no Remotion / cinematic-effects
21
- pipeline. You get a title card, optional before/after side-by-side, trim/speed,
22
- and a size cap. That's the deliverable: a clear, honest walkthrough, not a
23
- marketing reel.
24
-
25
- This skill uses **browser-qa** (the headless driver) and **building** (install
26
- + start the dev-server).
27
-
28
- ## When this applies
29
-
30
- A phase on the docker QA image whose target is a **web app** the repo serves on
31
- `localhost`, where a *moving* demonstration adds value over a screenshot. For a
32
- CLI, a curl-able API, or pure pass/fail evidence, use `qa-test`/`verify`. For
33
- static screenshot evidence, use `browser-qa`. Don't record a video you don't
34
- need.
35
-
36
- ## FIRST — probe for a browser
37
-
38
- The browser driver lives in the **browser-qa** skill bundle at
39
- `<browser-qa-dir>/scripts/agent-browser.mjs`, and this skill's compositor at
40
- `<demo-dir>/scripts/compose-demo.sh`. Resolve both `<…-dir>` paths from the
41
- available-skills catalogue (it gives each skill's absolute staged path); do not
42
- assume a relative path from `$PWD` — your cwd is the checked-out repo, the
43
- bundles are siblings.
44
-
45
- Run the runtime probe before anything else:
46
-
47
- ```bash
48
- node <browser-qa-dir>/scripts/agent-browser.mjs doctor
49
- ```
50
-
51
- - Exit 0 with `{"ok":true,...}` → proceed.
52
- - Exit non-zero (lean image / gondolin / no Chromium) → **DO NOT attempt a demo.**
53
- Report that the demo path was unavailable on this host and stop. (The workflow
54
- gates this phase to the QA image, so this should be rare — but never fake a
55
- recording.)
56
-
57
- ## Understand what to prove
58
-
59
- Read the PR (description, diff, linked issue) from the context the prompt gives
60
- you. For each change, ask: *what could a viewer confuse this with?* Design the
61
- demo so the viewer sees something that **only happens if the feature works as
62
- claimed** — both states (before/after, input/result) must appear on screen.
63
-
64
- State your **commitments** up front, then honour them:
65
-
66
- - **Layout** — `single` (default: a new feature, a fix proof, a walkthrough) or
67
- `side-by-side` (only when the story is fundamentally a comparison: regression
68
- fixed, behaviour-preserving refactor). Never fabricate a "before" to justify
69
- side-by-side.
70
- - **Speed** — target a watchable length; speed up dead time rather than leaving
71
- it. 30–60s is a good single-feature target.
72
-
73
- ## Build and run the app
74
-
75
- Follow the **building** skill to install dependencies, then start the repo's
76
- dev-server **in the background** and poll with `curl` until it answers on
77
- `localhost:PORT`. Note the port — you pass it as `--base-url`.
78
-
79
- ## Capture the session
80
-
81
- Author a `flow.json` (same shape as **browser-qa**) that scripts the
82
- interaction as a *director*, not an operator:
83
-
84
- - **Record the baseline first** — the starting state is act one.
85
- - **Hold after state changes** — add a `{"pause": 1200}` (or `waitFor`) so the
86
- result is readable before moving on.
87
- - **Verify between steps** — `waitFor` the expected element before the next
88
- action; don't blindly fire keys into an unsettled page.
89
-
90
- Because recording auto-enables the driver's **demo mode**, you get a visible
91
- **synthetic cursor** that animates to each target, and holds between steps —
92
- you don't script those. To make typing visible on screen, prefer **`type`**
93
- (character-by-character) over `fill` (instant) for any input the viewer should
94
- watch being filled. Tune pacing with `--step-delay` / `--type-delay` if a flow
95
- feels rushed or draggy (see the browser-qa skill).
96
-
97
- Record the session by passing `--record-dir`:
98
-
99
- ```bash
100
- node <browser-qa-dir>/scripts/agent-browser.mjs run flow.json \
101
- --base-url http://localhost:PORT \
102
- --record-dir /tmp/demo-cap
103
- # → writes /tmp/demo-cap/session.webm, reports {"video":"…/session.webm", …}
104
- ```
105
-
106
- Pick the viewport to match the layout so the clip isn't letterboxed (the
107
- compositor renders at **1920×1080** by default): `1920x1080` for `single`;
108
- `~960x1000` per panel for `side-by-side` (set it in the flow's `viewport`).
109
- Recording at the output resolution keeps UI text crisp instead of upscaled.
110
-
111
- **Before/after comparison:** record the **same** scripted interaction against
112
- each branch — `session-after.webm` (PR head) and `session-before.webm` (base
113
- branch). Only the behaviour should differ.
114
-
115
- When the harness triggered this on a PR, your workspace is **already checked out
116
- at the PR head** — record the *after* from that current checkout first (no
117
- re-checkout). Then get the *before* from the base branch. The pre-clone is
118
- shallow + single-branch, so fetch the base **by ref** (the prompt passes its
119
- name) rather than trusting a local branch name, which may be a synthesized
120
- `lastlight/N-slug` that actually points at the default branch:
121
-
122
- ```bash
123
- git fetch --depth 1 origin <baseBranch>
124
- git checkout -B <baseBranch>-base FETCH_HEAD # re-install deps + restart the dev-server after switching
125
- ```
126
-
127
- **Verify the checkout did what you think before you composite:** read the
128
- disambiguating element on *both* recordings (the driver's `text`/`assertText`).
129
- It MUST be present in *after* and absent in *before*. If both look identical, the
130
- checkout is wrong (a stale/fictional branch, deps not rebuilt, or a dev-server
131
- still serving the old branch) — STOP and fix it. Never ship a side-by-side that
132
- shows no difference and call it a before/after; that is the failure this skill
133
- exists to prevent.
134
-
135
- ## Compose the video
136
-
137
- Composite the raw recording(s) into the final mp4 with the bundled wrapper:
138
-
139
- ```bash
140
- # Single walkthrough
141
- <demo-dir>/scripts/compose-demo.sh \
142
- --output <artifact-dir>/demo.mp4 \
143
- --title "PR #42 — Add dark mode toggle" \
144
- --subtitle "Toggling persists the theme across reloads" \
145
- --layout single --speed 1 \
146
- /tmp/demo-cap/session.webm
147
-
148
- # Before/after comparison — first clip = left/BEFORE, second = right/AFTER
149
- <demo-dir>/scripts/compose-demo.sh \
150
- --output <artifact-dir>/demo.mp4 \
151
- --title "PR #51 — Fix flicker on load" \
152
- --layout side-by-side --labels "BEFORE (main)" "AFTER (PR)" \
153
- /tmp/before/session-before.webm /tmp/after/session-after.webm
154
- ```
155
-
156
- Flags: `--title` (required, on the card), `--subtitle`, `--layout
157
- single|side-by-side`, `--labels A B` (side-by-side), `--speed N`, `--trim
158
- START:END` (seconds, single layout), `--crf` (default 18 — lower = higher
159
- quality), `--target-size-mb` (default 10), `--width/--height` (default
160
- 1920×1080), `--title-secs`. The script normalizes (lanczos scale), lays out, adds
161
- the title card, then does a **quality-first single-pass CRF** encode
162
- (`+faststart`, `yuv420p`); only if that overshoots `--target-size-mb` does it
163
- fall back to a size-capped two-pass encode. It prints a JSON summary line:
164
- `{"output":"…","resolution":"1920x1080","duration":"38.4","size_mb":3.1}`.
165
-
166
- ## Guardrails — self-check before reporting
167
-
168
- Confirm the output with `ffprobe` (the script's summary already reports these):
169
-
170
- - **Resolution** matches the requested size (default 1920×1080).
171
- - **Duration** is sane — not 0s, within a watchable band (~15–90s). Don't crank
172
- `--speed` so high (or so low) that the clip becomes a sub-second flash or a
173
- near-static crawl; the agent that shipped a 0.2s recording at `--speed 0.05`
174
- proved nothing.
175
- - **Size ≤ 10 MB** so GitHub embeds it inline (25 MB hard limit). The CRF encode
176
- usually lands far under this on its own; if it's over, the script auto-falls
177
- back to a size-capped encode, but you can also lower `--target-size-mb`, raise
178
- `--crf`, or raise `--speed`.
179
-
180
- If the recording is too short to be meaningful (a couple of seconds), go back
181
- and add interaction steps — don't ship a clip that proves nothing.
182
-
183
- ## Output location
184
-
185
- Write `demo.mp4` into the **artifact directory the prompt provides** (the run's
186
- `.lastlight/<issueKey>/` dir, referenced by the template variable the prompt
187
- passes). The harness harvests it into the dashboard Artifacts view, and the
188
- public artifact URL makes it playable inline on GitHub. Do **not** commit the
189
- mp4 into the repo, and do **not** post it yourself — the workflow embeds it.
190
-
191
- Video delivery requires **server-mode build-assets**. If the mp4 can't be
192
- persisted (no artifact dir provided, or repo-mode), say so in the report and
193
- describe what the demo showed instead.
194
-
195
- ## Evidence rules
196
-
197
- Mirror `verify`/`browser-qa` — you are demonstrating real behaviour, not selling
198
- it:
199
-
200
- - **Never fake it.** Don't edit the code under test to force a nicer demo, and
201
- don't stage a result the feature didn't actually produce.
202
- - **Show the disambiguating moment** — the thing that only happens if the change
203
- works. Off-camera claims don't count.
204
- - If something doesn't work, the honest demo shows that — report it; don't trim
205
- the failure out and call it a success.
206
-
207
- ## Report
208
-
209
- Produce your report as your **final message** — the workflow posts it (don't
210
- comment yourself). Keep it short:
211
-
212
- ```
213
- ## Demo: <target>
214
-
215
- **Environment:** <branch / commit, package manager, how served, Chromium version>
216
-
217
- <one or two sentences: what the video demonstrates and the key moment to watch>
218
-
219
- - **Layout:** single | side-by-side
220
- - **Video:** demo.mp4 (<resolution>, <duration>s, <size>MB)
221
-
222
- ### Not covered
223
- <one line: anything the demo doesn't show, and why>
224
- ```
225
-
226
- The workflow embeds the playable video from the artifact store; reference it as
227
- `demo.mp4`. If the browser or compositor was unavailable, say so plainly instead
228
- of pretending a clip exists.
@@ -1,243 +0,0 @@
1
- #!/usr/bin/env bash
2
- # compose-demo.sh — composite a raw browser screen recording into a titled,
3
- # size-capped demo mp4 using ffmpeg only (NO Remotion, NO headless render).
4
- #
5
- # Used by the `/demo` workflow: agent-browser records the session to a .webm,
6
- # this script turns it into a polished, GitHub-embeddable mp4.
7
- #
8
- # Usage:
9
- # compose-demo.sh --output demo.mp4 --title "PR #42 — Add dark mode" \
10
- # [--subtitle "..."] [--layout single|side-by-side] \
11
- # [--labels "BEFORE (main)" "AFTER (PR)"] [--speed 1.5] \
12
- # [--trim START:END] [--target-size-mb 10] [--crf 18] \
13
- # [--width 1920] [--height 1080] [--title-secs 2.5] <clip1.webm> [clip2.webm]
14
- #
15
- # What it does:
16
- # 1. Normalizes each input clip (optional trim, speed-up, scale+pad to the
17
- # panel size with lanczos, fps/pixfmt/SAR normalize, optional per-panel
18
- # label).
19
- # 2. Lays out the body: a single panel, or two panels side-by-side (hstack).
20
- # 3. Prepends a title/subtitle card (solid bg + drawtext).
21
- # 4. Quality-first H.264 encode: a single-pass CRF encode (sharp for UI
22
- # screencasts) that usually lands well under --target-size-mb; only if it
23
- # overshoots does it fall back to a size-targeted two-pass ABR encode.
24
- # yuv420p + +faststart for web streaming.
25
- # 5. Prints an ffprobe summary (resolution / duration / size).
26
- #
27
- # Prerequisites: ffmpeg, ffprobe (baked into lastlight-sandbox-qa:latest).
28
- # Runs fully offline.
29
-
30
- set -euo pipefail
31
-
32
- die() { echo "compose-demo.sh: error: $*" >&2; exit 1; }
33
- require_cmd() { command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"; }
34
- require_cmd ffmpeg
35
- require_cmd ffprobe
36
-
37
- # ── Defaults ────────────────────────────────────────────────────────────────
38
- OUTPUT=""
39
- TITLE=""
40
- SUBTITLE=""
41
- LAYOUT="single"
42
- LABEL1=""
43
- LABEL2=""
44
- SPEED="1"
45
- TRIM=""
46
- TARGET_MB="10"
47
- CRF="18"
48
- WIDTH="1920"
49
- HEIGHT="1080"
50
- TITLE_SECS="2.5"
51
- CLIPS=()
52
-
53
- # ── Parse args ──────────────────────────────────────────────────────────────
54
- while [[ $# -gt 0 ]]; do
55
- case "$1" in
56
- --output) OUTPUT="$2"; shift 2;;
57
- --title) TITLE="$2"; shift 2;;
58
- --subtitle) SUBTITLE="$2"; shift 2;;
59
- --layout) LAYOUT="$2"; shift 2;;
60
- --labels) LABEL1="$2"; LABEL2="$3"; shift 3;;
61
- --speed) SPEED="$2"; shift 2;;
62
- --trim) TRIM="$2"; shift 2;;
63
- --target-size-mb) TARGET_MB="$2"; shift 2;;
64
- --crf) CRF="$2"; shift 2;;
65
- --width) WIDTH="$2"; shift 2;;
66
- --height) HEIGHT="$2"; shift 2;;
67
- --title-secs) TITLE_SECS="$2"; shift 2;;
68
- -*) die "unknown flag: $1";;
69
- *) CLIPS+=("$1"); shift;;
70
- esac
71
- done
72
-
73
- [[ -n "$OUTPUT" ]] || die "--output is required"
74
- [[ ${#CLIPS[@]} -ge 1 ]] || die "at least one input clip is required"
75
- for c in "${CLIPS[@]}"; do [[ -f "$c" ]] || die "clip not found: $c"; done
76
- if [[ "$LAYOUT" == "side-by-side" ]]; then
77
- [[ ${#CLIPS[@]} -ge 2 ]] || die "side-by-side layout needs two clips"
78
- fi
79
-
80
- WORK="$(mktemp -d "${TMPDIR:-/tmp}/compose-demo-XXXXXX")"
81
- cleanup() { rm -rf "$WORK"; }
82
- trap cleanup EXIT
83
-
84
- # ── Font for drawtext ────────────────────────────────────────────────────────
85
- # Prefer an explicit fontfile (Liberation ships in the QA image via
86
- # fonts-liberation); fall back to fontconfig's default Sans family.
87
- FONT="${DEMO_FONT:-}"
88
- if [[ -z "$FONT" ]]; then
89
- for f in \
90
- /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf \
91
- /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf; do
92
- [[ -f "$f" ]] && FONT="$f" && break
93
- done
94
- fi
95
- if [[ -n "$FONT" ]]; then FONTARG="fontfile=${FONT}"; else FONTARG="font=Sans"; fi
96
-
97
- # drawtext drives the title card and per-panel labels. The Debian `ffmpeg`
98
- # package (the QA image) is built with libfreetype so it's present; but degrade
99
- # gracefully on a build without it (omit text overlays) rather than failing the
100
- # whole compose — the demo video is still worth shipping.
101
- HAS_DRAWTEXT=0
102
- # Detect the drawtext filter WITHOUT a pipe. Under `set -o pipefail`, any
103
- # `ffmpeg -filters | grep -q drawtext` (or even `printf "$var" | grep -q`) is a
104
- # false-negative trap: `grep -q` exits on the first match and closes the pipe,
105
- # the writer (still emitting — `drawtext` sits ~16 KB into ~38 KB of output)
106
- # dies with SIGPIPE (141), and `pipefail` promotes that to the pipeline's exit
107
- # status, so the `if` wrongly concludes drawtext is absent (~19 of 20 runs).
108
- # Capture into a variable and match with a pure bash `case` — no subprocess,
109
- # no pipe, no SIGPIPE.
110
- FFMPEG_FILTERS="$(ffmpeg -hide_banner -filters 2>/dev/null || true)"
111
- case "$FFMPEG_FILTERS" in
112
- *drawtext*) HAS_DRAWTEXT=1 ;;
113
- *) echo "compose-demo.sh: warning: this ffmpeg lacks the drawtext filter — title card and labels omitted." >&2 ;;
114
- esac
115
-
116
- # Panel size: side-by-side splits the width into two equal panels.
117
- PH="$HEIGHT"
118
- if [[ "$LAYOUT" == "side-by-side" ]]; then PW=$(( WIDTH / 2 )); else PW="$WIDTH"; fi
119
-
120
- # ── Stage A: normalize one clip into a body panel ─────────────────────────────
121
- # Args: <input> <outfile> <label-file-or-empty> <apply-trim:0|1>
122
- build_panel() {
123
- local input="$1" outfile="$2" labelfile="$3" apply_trim="$4"
124
- local trim_args=()
125
- if [[ "$apply_trim" == "1" && -n "$TRIM" ]]; then
126
- local start="${TRIM%%:*}" end="${TRIM##*:}"
127
- [[ -n "$start" ]] && trim_args+=(-ss "$start")
128
- [[ -n "$end" ]] && trim_args+=(-to "$end")
129
- fi
130
- local vf="setpts=PTS/${SPEED}"
131
- vf+=",scale=${PW}:${PH}:force_original_aspect_ratio=decrease:flags=lanczos"
132
- vf+=",pad=${PW}:${PH}:(ow-iw)/2:(oh-ih)/2:color=black"
133
- vf+=",fps=30,format=yuv420p,setsar=1"
134
- if [[ -n "$labelfile" && "$HAS_DRAWTEXT" == "1" ]]; then
135
- # Anchor labels to the TOP of the panel. At the bottom they're hidden behind
136
- # the browser/GitHub video player's control bar (scrubber + play button).
137
- vf+=",drawtext=${FONTARG}:textfile=${labelfile}:x=(w-text_w)/2:y=28"
138
- vf+=":fontsize=34:fontcolor=white:box=1:boxcolor=black@0.55:boxborderw=14"
139
- fi
140
- # `${arr[@]+"${arr[@]}"}` expands to nothing when the array is empty without
141
- # tripping `set -u` (an empty `"${arr[@]}"` errors on bash < 4.4, e.g. macOS).
142
- ffmpeg -y -loglevel error ${trim_args[@]+"${trim_args[@]}"} -i "$input" \
143
- -vf "$vf" -an -c:v libx264 -crf "$CRF" -preset medium "$outfile"
144
- }
145
-
146
- # Per-panel labels (side-by-side) written to files to avoid drawtext escaping.
147
- LABELFILE1=""; LABELFILE2=""
148
- if [[ "$LAYOUT" == "side-by-side" ]]; then
149
- if [[ -n "$LABEL1" ]]; then LABELFILE1="$WORK/label1.txt"; printf '%s' "$LABEL1" > "$LABELFILE1"; fi
150
- if [[ -n "$LABEL2" ]]; then LABELFILE2="$WORK/label2.txt"; printf '%s' "$LABEL2" > "$LABELFILE2"; fi
151
- fi
152
-
153
- echo "compose-demo.sh: normalizing clips (layout=$LAYOUT, speed=${SPEED}x)…" >&2
154
- build_panel "${CLIPS[0]}" "$WORK/panel0.mp4" "$LABELFILE1" 1
155
- BODY="$WORK/body.mp4"
156
- if [[ "$LAYOUT" == "side-by-side" ]]; then
157
- build_panel "${CLIPS[1]}" "$WORK/panel1.mp4" "$LABELFILE2" 1
158
- # Pad the shorter panel by freezing its last frame so both stay aligned for the
159
- # full body. Without this, hstack lets the shorter side end early and go
160
- # blank/black — which reads as "the before didn't render".
161
- D0="$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$WORK/panel0.mp4" || echo 0)"
162
- D1="$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$WORK/panel1.mp4" || echo 0)"
163
- PAD0="$(awk -v a="$D0" -v b="$D1" 'BEGIN{d=b-a; if(d<0)d=0; printf "%.3f", d}')"
164
- PAD1="$(awk -v a="$D0" -v b="$D1" 'BEGIN{d=a-b; if(d<0)d=0; printf "%.3f", d}')"
165
- ffmpeg -y -loglevel error -i "$WORK/panel0.mp4" -i "$WORK/panel1.mp4" \
166
- -filter_complex "[0:v]tpad=stop_mode=clone:stop_duration=${PAD0}[p0];[1:v]tpad=stop_mode=clone:stop_duration=${PAD1}[p1];[p0][p1]hstack=inputs=2,format=yuv420p,setsar=1[v]" \
167
- -map "[v]" -an -c:v libx264 -crf "$CRF" -preset medium "$BODY"
168
- else
169
- BODY="$WORK/panel0.mp4"
170
- fi
171
-
172
- # ── Stage B: title card (skipped when drawtext is unavailable) ───────────────
173
- TITLE_CLIP=""
174
- TITLE_DUR="0"
175
- if [[ "$HAS_DRAWTEXT" == "1" ]]; then
176
- TITLEFILE="$WORK/title.txt"; printf '%s' "${TITLE:-Demo}" > "$TITLEFILE"
177
- TITLE_VF="drawtext=${FONTARG}:textfile=${TITLEFILE}:x=(w-text_w)/2:y=(h/2)-th-10:fontsize=48:fontcolor=white"
178
- if [[ -n "$SUBTITLE" ]]; then
179
- SUBFILE="$WORK/subtitle.txt"; printf '%s' "$SUBTITLE" > "$SUBFILE"
180
- TITLE_VF+=",drawtext=${FONTARG}:textfile=${SUBFILE}:x=(w-text_w)/2:y=(h/2)+10:fontsize=26:fontcolor=0xb0b0b0"
181
- fi
182
- TITLE_VF+=",format=yuv420p"
183
- ffmpeg -y -loglevel error -f lavfi -i "color=c=0x101418:s=${WIDTH}x${HEIGHT}:d=${TITLE_SECS}:r=30" \
184
- -vf "$TITLE_VF" -c:v libx264 -crf "$CRF" -preset medium "$WORK/title.mp4"
185
- TITLE_CLIP="$WORK/title.mp4"
186
- TITLE_DUR="$TITLE_SECS"
187
- fi
188
-
189
- # ── Stage C: (concat title +) body → final mp4 ───────────────────────────────
190
- # With a title card: concat it ahead of the body. Without: pass the body
191
- # through (`null`) so the same encode applies either way.
192
- if [[ -n "$TITLE_CLIP" ]]; then
193
- IN_ARGS=(-i "$TITLE_CLIP" -i "$BODY")
194
- FILTER='[0:v][1:v]concat=n=2:v=1:a=0[v]'
195
- else
196
- IN_ARGS=(-i "$BODY")
197
- FILTER='[0:v]null[v]'
198
- fi
199
-
200
- # Quality-first: a single-pass CRF encode (visually crisp for UI screencasts —
201
- # constant quality, not a starved average bitrate) usually lands well under the
202
- # size cap. The old always-two-pass-ABR path starved short, near-static clips
203
- # down to a few tens of KB; CRF spends the bits the content needs.
204
- echo "compose-demo.sh: encoding (crf ${CRF}, preset slow)…" >&2
205
- ffmpeg -y -loglevel error "${IN_ARGS[@]}" \
206
- -filter_complex "$FILTER" -map "[v]" -an \
207
- -c:v libx264 -crf "$CRF" -preset slow \
208
- -pix_fmt yuv420p -movflags +faststart "$OUTPUT"
209
-
210
- # Size cap: only if the quality encode overshoots --target-size-mb do we redo it
211
- # as a size-targeted two-pass ABR encode (keeps it under GitHub's inline limit).
212
- OUT_BYTES="$(wc -c < "$OUTPUT" | tr -d ' ')"
213
- CAP_BYTES="$(awk -v mb="$TARGET_MB" 'BEGIN{printf "%d", mb*1024*1024}')"
214
- if [[ "$OUT_BYTES" -gt "$CAP_BYTES" ]]; then
215
- BODY_DUR="$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$BODY" || echo 0)"
216
- TOTAL_DUR="$(awk -v a="$TITLE_DUR" -v b="$BODY_DUR" 'BEGIN{printf "%.3f", a + b}')"
217
- # Target video bitrate (kbit/s) = target_bytes * 8 / duration / 1000, with a
218
- # small headroom factor for the muxer; floored so very short clips still encode.
219
- BR_K="$(awk -v mb="$TARGET_MB" -v d="$TOTAL_DUR" 'BEGIN{
220
- if (d <= 0) d = 1;
221
- br = (mb * 1024 * 1024 * 8) / d / 1000 * 0.92;
222
- if (br < 200) br = 200;
223
- printf "%d", br;
224
- }')"
225
- PASSLOG="$WORK/ff2pass"
226
- echo "compose-demo.sh: crf encode was $(awk -v b="$OUT_BYTES" 'BEGIN{printf "%.1f", b/1024/1024}')MB > ${TARGET_MB}MB cap — re-encoding size-capped (${TOTAL_DUR}s → ${BR_K}kbit/s, two-pass)…" >&2
227
- ffmpeg -y -loglevel error "${IN_ARGS[@]}" \
228
- -filter_complex "$FILTER" -map "[v]" -an \
229
- -c:v libx264 -b:v "${BR_K}k" -pass 1 -passlogfile "$PASSLOG" -preset medium -f mp4 /dev/null
230
- ffmpeg -y -loglevel error "${IN_ARGS[@]}" \
231
- -filter_complex "$FILTER" -map "[v]" -an \
232
- -c:v libx264 -b:v "${BR_K}k" -pass 2 -passlogfile "$PASSLOG" -preset medium \
233
- -pix_fmt yuv420p -movflags +faststart "$OUTPUT"
234
- fi
235
-
236
- # ── Stage D: report ───────────────────────────────────────────────────────────
237
- RES="$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0:s=x "$OUTPUT" || echo '?')"
238
- DUR="$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$OUTPUT" || echo '?')"
239
- BYTES="$(wc -c < "$OUTPUT" | tr -d ' ')"
240
- MB="$(awk -v b="$BYTES" 'BEGIN{printf "%.2f", b/1024/1024}')"
241
- echo "compose-demo.sh: wrote $OUTPUT (${RES}, ${DUR}s, ${MB}MB)" >&2
242
- # Machine-readable line for the agent to parse.
243
- echo "{\"output\":\"${OUTPUT}\",\"resolution\":\"${RES}\",\"duration\":\"${DUR}\",\"size_mb\":${MB}}"