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,531 +0,0 @@
1
- #!/usr/bin/env node
2
- // agent-browser.mjs — bundled headless-browser driver for Tier B browser QA.
3
- //
4
- // CONTRACT
5
- // --------
6
- // Runs inside the `lastlight-sandbox-qa:latest` docker image, which bakes in
7
- // Playwright + Chromium at PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers and
8
- // makes the global `playwright` package resolvable via NODE_PATH. The calling
9
- // agent has bash + file tools but NO vision: it reasons over this CLI's JSON
10
- // stdout (extracted text, assertion results, console errors). The PNG
11
- // screenshots are human evidence only — the agent never "sees" them.
12
- //
13
- // Subcommands:
14
- // doctor
15
- // Runtime probe. Launches headless Chromium (--no-sandbox), opens
16
- // about:blank, closes. On success prints {"ok":true,"chromium":"<version>"}
17
- // and exits 0; on ANY failure prints {"ok":false,"error":"..."} and exits 1.
18
- // The skill runs this first to decide browser-vs-text.
19
- //
20
- // run <flow.json> [--base-url URL] [--out-dir DIR] [--record-dir DIR]
21
- // [--demo] [--no-cursor] [--step-delay MS] [--type-delay MS]
22
- // [--move-steps N]
23
- // Executes a FLOW in ONE Chromium session (state preserved across steps) and
24
- // prints a single JSON report.
25
- //
26
- // With --record-dir DIR (or `"record": true` in the flow), the whole session
27
- // is screen-recorded via Playwright's native recordVideo and saved to
28
- // <record-dir>/session.webm (default: the out-dir). The saved path is
29
- // reported as `video` in the JSON. Used by the `/demo` workflow, which then
30
- // composites the raw webm into a titled mp4 with compose-demo.sh.
31
- //
32
- // DEMO MODE (auto-on whenever recording; also --demo or `"demo": true` in the
33
- // flow) makes the capture human-watchable — headless Chromium otherwise
34
- // paints no cursor and fires actions instantly:
35
- // - a synthetic cursor overlay (opt out with --no-cursor) that animates to
36
- // each target before acting, driven by real page.mouse events;
37
- // - `type` steps that key in char-by-char (--type-delay, default 70ms);
38
- // - a deliberate hold between steps (--step-delay, default 700ms).
39
- // Outside demo mode every one of these is a no-op, so screenshot QA runs
40
- // behave exactly as before. Env equivalents: LASTLIGHT_STEP_DELAY_MS,
41
- // LASTLIGHT_TYPE_DELAY_MS, LASTLIGHT_MOVE_STEPS.
42
- //
43
- // Flow shape:
44
- // { "baseUrl": "http://localhost:3000",
45
- // "viewport": {"width":1280,"height":800},
46
- // "steps": [
47
- // {"name":"home", "goto":"/"},
48
- // {"click":"text=Login"},
49
- // {"fill":["#email","a@b.com"]},
50
- // {"type":["#search","hello"]},
51
- // {"press":"Enter"},
52
- // {"waitFor":"#dashboard"},
53
- // {"assertText":"Welcome"},
54
- // {"pause": 1200},
55
- // {"text":"h1"},
56
- // {"screenshot":"after-login"}
57
- // ] }
58
- // A step may combine an action plus a trailing `screenshot`. `pause` holds
59
- // for N ms (a readable beat after a state change, for demo recordings);
60
- // `type` shows visible per-character typing, `fill` sets the value instantly.
61
- //
62
- // Per-step semantics:
63
- // - A step error (selector not found, assertion fail, timeout) is recorded
64
- // {ok:false} and the run CONTINUES (best-effort QA) — EXCEPT a `goto`
65
- // that throws is FATAL: remaining steps are marked skipped.
66
- // - assertText passes if the text is visible anywhere on the page; on miss
67
- // the step is {ok:false} but the run continues.
68
- // - text extracts a selector's textContent into the step result.
69
- // - screenshot writes <out-dir>/<basename>.png (full page).
70
- //
71
- // Final stdout: one JSON object
72
- // { ok, baseUrl, steps:[{index,action,ok,ms,text?,screenshot?,error?}],
73
- // consoleErrors:[...], screenshots:[paths] }
74
- // Exit 0 even when steps failed (the agent reads the JSON to judge). Exit
75
- // non-zero only on a FATAL harness error (bad flow file, launch failure).
76
- //
77
- // Dependency-free apart from playwright.
78
-
79
- import { readFileSync, mkdirSync } from 'node:fs';
80
- import { resolve, join } from 'node:path';
81
- import { createRequire } from 'node:module';
82
-
83
- // This file is ESM (.mjs); playwright is a CJS package. createRequire gives us
84
- // a `require` that resolves an absolute package-dir path (see loadPlaywright).
85
- const require = createRequire(import.meta.url);
86
-
87
- const WAIT_TIMEOUT = 10_000; // sane default for waitFor / assertText probes
88
- // When recording, hold the page open briefly after the last step before closing
89
- // the context (which flushes the .webm). A bare goto→waitFor→text flow finishes
90
- // in ~250 ms — too short for Playwright's video to capture a *painted* frame, so
91
- // the clip can come out blank (the /demo before/after's "before" panel did).
92
- // Settling lets late paints + async data ("Loading…") resolve and guarantees a
93
- // watchable tail. Override with --record-settle-ms / LASTLIGHT_RECORD_SETTLE_MS.
94
- const RECORD_SETTLE_MS = Number(process.env.LASTLIGHT_RECORD_SETTLE_MS) || 1500;
95
-
96
- // ── Demo-mode pacing ─────────────────────────────────────────────────────────
97
- // Headless Chromium paints NO cursor and fires actions instantly, so a raw
98
- // recording looks like the UI mutating on its own. "Demo mode" (auto-on whenever
99
- // the session is recorded — see run()) makes the capture human-watchable: a
100
- // synthetic cursor overlay that animates to each target, char-by-char typing,
101
- // and a deliberate pause between steps. These are no-ops outside demo mode, so
102
- // screenshot QA runs are untouched. All tunable via flags / env.
103
- const DEMO_STEP_DELAY_MS = Number(process.env.LASTLIGHT_STEP_DELAY_MS) || 700;
104
- const DEMO_TYPE_DELAY_MS = Number(process.env.LASTLIGHT_TYPE_DELAY_MS) || 70;
105
- const DEMO_MOVE_STEPS = Number(process.env.LASTLIGHT_MOVE_STEPS) || 25;
106
-
107
- // Synthetic cursor overlay, adapted from Puppeteer's mouse-helper (Apache-2.0),
108
- // made self-installing so it can be injected via context.addInitScript and picked
109
- // up by Playwright's recordVideo (it's page DOM, so it lands in the .webm). It
110
- // follows the REAL mouse events that page.mouse.move/down/up dispatch.
111
- const MOUSE_HELPER_SRC = `(() => {
112
- const install = () => {
113
- if (window.__mouseHelperInstalled || !document.body) return;
114
- window.__mouseHelperInstalled = true;
115
- const box = document.createElement('div');
116
- box.classList.add('mouse-helper');
117
- const style = document.createElement('style');
118
- style.innerHTML = \`
119
- .mouse-helper {
120
- pointer-events: none;
121
- position: absolute;
122
- z-index: 2147483647;
123
- width: 20px; height: 20px;
124
- margin-left: -10px; margin-top: -10px;
125
- border-radius: 10px;
126
- border: 2px solid rgba(255,255,255,.9);
127
- background: rgba(0,0,0,.35);
128
- box-shadow: 0 0 0 1px rgba(0,0,0,.4);
129
- transition: background .15s, border-radius .15s, transform .08s;
130
- }
131
- .mouse-helper.button-1 {
132
- transition: none;
133
- background: rgba(30,120,255,.7);
134
- transform: scale(.75);
135
- }
136
- \`;
137
- document.head.appendChild(style);
138
- document.body.appendChild(box);
139
- const move = (e) => { box.style.left = e.pageX + 'px'; box.style.top = e.pageY + 'px'; };
140
- document.addEventListener('mousemove', move, true);
141
- document.addEventListener('mousedown', (e) => { move(e); box.classList.add('button-1'); }, true);
142
- document.addEventListener('mouseup', (e) => { move(e); box.classList.remove('button-1'); }, true);
143
- };
144
- if (document.readyState === 'loading') {
145
- document.addEventListener('DOMContentLoaded', install, false);
146
- } else {
147
- install();
148
- }
149
- })();`;
150
-
151
- // Animate the synthetic cursor to the centre of a selector, best-effort. Returns
152
- // the target box when it landed (so the caller can click at those coords), or
153
- // null to fall back to Playwright's own targeting.
154
- async function moveCursorTo(page, selector, demo) {
155
- try {
156
- const loc = page.locator(selector).first();
157
- await loc.waitFor({ state: 'visible', timeout: WAIT_TIMEOUT });
158
- await loc.scrollIntoViewIfNeeded({ timeout: WAIT_TIMEOUT }).catch(() => {});
159
- const box = await loc.boundingBox();
160
- if (!box) return null;
161
- const cx = box.x + box.width / 2;
162
- const cy = box.y + box.height / 2;
163
- await page.mouse.move(cx, cy, { steps: demo.moveSteps });
164
- await page.waitForTimeout(140).catch(() => {});
165
- return { cx, cy };
166
- } catch {
167
- return null;
168
- }
169
- }
170
-
171
- function emit(obj) {
172
- process.stdout.write(JSON.stringify(obj) + '\n');
173
- }
174
-
175
- async function loadPlaywright() {
176
- // Resolve playwright via CJS `require`: it handles an absolute package-dir
177
- // path ($LASTLIGHT_PLAYWRIGHT, baked into the QA image) by reading the
178
- // package.json `main` — which ESM `import()` of a directory does NOT do — and
179
- // it honours NODE_PATH for the bare-specifier fallback. playwright ships a CJS
180
- // entry, so `require` returns { chromium, … } directly.
181
- const candidates = [process.env.LASTLIGHT_PLAYWRIGHT, 'playwright'].filter(Boolean);
182
- for (const spec of candidates) {
183
- try {
184
- return require(spec);
185
- } catch {
186
- // try the next candidate
187
- }
188
- }
189
- emit({
190
- ok: false,
191
- error:
192
- 'playwright not available — browser QA needs the lastlight-sandbox-qa image',
193
- });
194
- process.exit(1);
195
- }
196
-
197
- async function doctor() {
198
- const { chromium } = await loadPlaywright();
199
- let browser;
200
- try {
201
- browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
202
- const page = await browser.newPage();
203
- await page.goto('about:blank');
204
- const version = browser.version();
205
- await page.close();
206
- emit({ ok: true, chromium: version });
207
- process.exit(0);
208
- } catch (err) {
209
- emit({ ok: false, error: String(err && err.message ? err.message : err) });
210
- process.exit(1);
211
- } finally {
212
- if (browser) await browser.close().catch(() => {});
213
- }
214
- }
215
-
216
- // Resolve a goto target against the base URL. Absolute URLs pass through.
217
- function resolveUrl(target, baseUrl) {
218
- if (/^https?:\/\//i.test(target)) return target;
219
- if (!baseUrl) return target;
220
- return baseUrl.replace(/\/+$/, '') + '/' + String(target).replace(/^\/+/, '');
221
- }
222
-
223
- // Returns the action name for a step (for reporting) — first recognized key.
224
- function actionOf(step) {
225
- for (const k of [
226
- 'goto',
227
- 'click',
228
- 'fill',
229
- 'type',
230
- 'press',
231
- 'waitFor',
232
- 'assertText',
233
- 'text',
234
- 'pause',
235
- ]) {
236
- if (k in step) return k;
237
- }
238
- if ('screenshot' in step) return 'screenshot';
239
- return 'noop';
240
- }
241
-
242
- async function execStep(page, step, baseUrl, outDir, screenshots, demo) {
243
- const result = {};
244
- let fatal = false;
245
-
246
- if ('goto' in step) {
247
- // Navigation failure is FATAL.
248
- try {
249
- await page.goto(resolveUrl(step.goto, baseUrl), {
250
- waitUntil: 'load',
251
- timeout: WAIT_TIMEOUT * 3,
252
- });
253
- } catch (err) {
254
- throw Object.assign(new Error(`goto failed: ${err.message}`), {
255
- fatal: true,
256
- });
257
- }
258
- } else if ('click' in step) {
259
- // Demo mode: animate the cursor to the target and click at its coordinates
260
- // so the pointer travel + press are visible. Fall back to Playwright's own
261
- // targeting when the box can't be resolved (or outside demo mode).
262
- const at = demo.enabled ? await moveCursorTo(page, step.click, demo) : null;
263
- if (at) {
264
- await page.mouse.down();
265
- await page.mouse.up();
266
- } else {
267
- await page.click(step.click, { timeout: WAIT_TIMEOUT });
268
- }
269
- } else if ('fill' in step) {
270
- const [sel, val] = step.fill;
271
- if (demo.enabled) await moveCursorTo(page, sel, demo);
272
- await page.fill(sel, val, { timeout: WAIT_TIMEOUT });
273
- } else if ('type' in step) {
274
- const [sel, val] = step.type;
275
- if (demo.enabled) await moveCursorTo(page, sel, demo);
276
- await page.locator(sel).first().pressSequentially(val, {
277
- timeout: WAIT_TIMEOUT,
278
- delay: demo.enabled ? demo.typeDelay : 0,
279
- });
280
- } else if ('pause' in step) {
281
- // Explicit hold — for reading a state change before the next action.
282
- const ms = Number(step.pause) || 0;
283
- if (ms > 0) await page.waitForTimeout(ms);
284
- } else if ('press' in step) {
285
- await page.keyboard.press(step.press);
286
- } else if ('waitFor' in step) {
287
- await page.locator(step.waitFor).first().waitFor({
288
- state: 'visible',
289
- timeout: WAIT_TIMEOUT,
290
- });
291
- } else if ('assertText' in step) {
292
- // Pass if visible anywhere on the page; miss => step fails, run continues.
293
- try {
294
- await page
295
- .getByText(step.assertText, { exact: false })
296
- .first()
297
- .waitFor({ state: 'visible', timeout: WAIT_TIMEOUT });
298
- } catch {
299
- throw new Error(`assertText not found: ${JSON.stringify(step.assertText)}`);
300
- }
301
- } else if ('text' in step) {
302
- const txt = await page
303
- .locator(step.text)
304
- .first()
305
- .textContent({ timeout: WAIT_TIMEOUT });
306
- result.text = txt == null ? '' : txt.trim();
307
- }
308
-
309
- // A trailing screenshot can ride along with any action (or stand alone).
310
- if ('screenshot' in step) {
311
- const base = String(step.screenshot).replace(/[^a-zA-Z0-9._-]/g, '_');
312
- const path = join(outDir, `${base}.png`);
313
- await page.screenshot({ path, fullPage: true });
314
- result.screenshot = path;
315
- screenshots.push(path);
316
- }
317
-
318
- return { result, fatal };
319
- }
320
-
321
- async function run(flowPath, baseUrlArg, outDirArg, recordDirArg, opts = {}) {
322
- let flow;
323
- try {
324
- flow = JSON.parse(readFileSync(resolve(flowPath), 'utf8'));
325
- } catch (err) {
326
- emit({ ok: false, error: `cannot read flow file: ${err.message}` });
327
- process.exit(1);
328
- }
329
- if (!flow || !Array.isArray(flow.steps)) {
330
- emit({ ok: false, error: 'flow file must have a "steps" array' });
331
- process.exit(1);
332
- }
333
-
334
- const baseUrl = baseUrlArg || flow.baseUrl || '';
335
- const outDir = resolve(outDirArg || process.cwd());
336
- // Record the session when --record-dir is passed OR the flow opts in with
337
- // `"record": true`. The .webm lands in the record dir (default: out-dir).
338
- const doRecord = !!recordDirArg || flow.record === true;
339
- const videoDir = resolve(recordDirArg || outDir);
340
- // Demo mode = recording, an explicit --demo, or flow.demo. It slows the run
341
- // down (cursor animation, char-by-char typing, inter-step holds) so the
342
- // capture is watchable; it's a no-op for plain screenshot QA. Cursor overlay
343
- // is on by default in demo mode, opt out with --no-cursor.
344
- const demo = {
345
- enabled: opts.demo === true || flow.demo === true || doRecord,
346
- cursor: opts.cursor !== false,
347
- stepDelay: Number.isFinite(opts.stepDelay) ? opts.stepDelay : DEMO_STEP_DELAY_MS,
348
- typeDelay: Number.isFinite(opts.typeDelay) ? opts.typeDelay : DEMO_TYPE_DELAY_MS,
349
- moveSteps: Number.isFinite(opts.moveSteps) ? opts.moveSteps : DEMO_MOVE_STEPS,
350
- };
351
- try {
352
- mkdirSync(outDir, { recursive: true });
353
- } catch {
354
- /* best effort */
355
- }
356
- if (doRecord) {
357
- try {
358
- mkdirSync(videoDir, { recursive: true });
359
- } catch {
360
- /* best effort */
361
- }
362
- }
363
-
364
- const viewport = flow.viewport || { width: 1280, height: 800 };
365
- const { chromium } = await loadPlaywright();
366
- let browser;
367
- const steps = [];
368
- const screenshots = [];
369
- const consoleErrors = [];
370
- let videoPath = null;
371
-
372
- try {
373
- browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
374
- } catch (err) {
375
- emit({
376
- ok: false,
377
- error: `browser launch failed: ${err && err.message ? err.message : err}`,
378
- });
379
- process.exit(1);
380
- }
381
-
382
- try {
383
- const context = await browser.newContext({
384
- viewport,
385
- // Playwright records video per-context; the size matches the viewport so
386
- // the clip isn't letterboxed. The .webm is flushed on context.close().
387
- ...(doRecord ? { recordVideo: { dir: videoDir, size: viewport } } : {}),
388
- });
389
- // Inject the synthetic cursor before the first navigation so it's present on
390
- // every page (addInitScript re-runs on each document). Only in demo mode.
391
- if (demo.enabled && demo.cursor) {
392
- await context.addInitScript(MOUSE_HELPER_SRC).catch(() => {});
393
- }
394
- const page = await context.newPage();
395
-
396
- page.on('console', (msg) => {
397
- if (msg.type() === 'error') consoleErrors.push(msg.text());
398
- });
399
- page.on('pageerror', (err) => {
400
- consoleErrors.push(String(err && err.message ? err.message : err));
401
- });
402
-
403
- let fatalHit = false;
404
- for (let i = 0; i < flow.steps.length; i++) {
405
- const step = flow.steps[i];
406
- const action = actionOf(step);
407
-
408
- if (fatalHit) {
409
- steps.push({ index: i, action, ok: false, ms: 0, error: 'skipped (prior fatal step)' });
410
- continue;
411
- }
412
-
413
- const started = Date.now();
414
- try {
415
- const { result } = await execStep(page, step, baseUrl, outDir, screenshots, demo);
416
- steps.push({ index: i, action, ok: true, ms: Date.now() - started, ...result });
417
- } catch (err) {
418
- const entry = {
419
- index: i,
420
- action,
421
- ok: false,
422
- ms: Date.now() - started,
423
- error: String(err && err.message ? err.message : err),
424
- };
425
- steps.push(entry);
426
- if (err && err.fatal) fatalHit = true; // navigation failure: skip the rest
427
- }
428
-
429
- // Demo mode: hold between steps so the video breathes (skip after an
430
- // explicit `pause`, which already held, and after a fatal step).
431
- if (demo.enabled && demo.stepDelay > 0 && action !== 'pause' && !fatalHit) {
432
- await page.waitForTimeout(demo.stepDelay).catch(() => {});
433
- }
434
- }
435
-
436
- // Finalize a recording (if any) BEFORE emitting: saving the video requires
437
- // the context to close, which flushes the .webm to disk. A save failure is a
438
- // reported finding, not a fatal run error.
439
- if (doRecord) {
440
- // Settle before closing: let late paints + async data resolve and ensure
441
- // the .webm has a watchable, non-blank tail (see RECORD_SETTLE_MS).
442
- if (RECORD_SETTLE_MS > 0) {
443
- await page
444
- .waitForLoadState('networkidle', { timeout: RECORD_SETTLE_MS })
445
- .catch(() => {});
446
- await page.waitForTimeout(RECORD_SETTLE_MS).catch(() => {});
447
- }
448
- const video = page.video();
449
- await context.close();
450
- if (video) {
451
- const target = join(videoDir, 'session.webm');
452
- try {
453
- await video.saveAs(target);
454
- videoPath = target;
455
- await video.delete().catch(() => {});
456
- } catch (err) {
457
- consoleErrors.push(
458
- `video save failed: ${err && err.message ? err.message : err}`,
459
- );
460
- }
461
- }
462
- }
463
-
464
- emit({
465
- ok: steps.every((s) => s.ok),
466
- baseUrl,
467
- steps,
468
- consoleErrors,
469
- screenshots,
470
- ...(videoPath ? { video: videoPath } : {}),
471
- });
472
- process.exit(0);
473
- } catch (err) {
474
- // Unexpected harness error mid-run — still emit a JSON object.
475
- emit({
476
- ok: false,
477
- error: String(err && err.message ? err.message : err),
478
- baseUrl,
479
- steps,
480
- consoleErrors,
481
- screenshots,
482
- ...(videoPath ? { video: videoPath } : {}),
483
- });
484
- process.exit(1);
485
- } finally {
486
- if (browser) await browser.close().catch(() => {});
487
- }
488
- }
489
-
490
- async function main() {
491
- const [cmd, ...rest] = process.argv.slice(2);
492
-
493
- if (cmd === 'doctor') {
494
- await doctor();
495
- return;
496
- }
497
-
498
- if (cmd === 'run') {
499
- const positional = [];
500
- let baseUrl;
501
- let outDir;
502
- let recordDir;
503
- const opts = {};
504
- for (let i = 0; i < rest.length; i++) {
505
- const a = rest[i];
506
- if (a === '--base-url') baseUrl = rest[++i];
507
- else if (a === '--out-dir') outDir = rest[++i];
508
- else if (a === '--record-dir') recordDir = rest[++i];
509
- else if (a === '--demo') opts.demo = true;
510
- else if (a === '--no-cursor') opts.cursor = false;
511
- else if (a === '--step-delay') opts.stepDelay = Number(rest[++i]);
512
- else if (a === '--type-delay') opts.typeDelay = Number(rest[++i]);
513
- else if (a === '--move-steps') opts.moveSteps = Number(rest[++i]);
514
- else positional.push(a);
515
- }
516
- if (!positional[0]) {
517
- emit({ ok: false, error: 'usage: agent-browser.mjs run <flow.json> [--base-url URL] [--out-dir DIR] [--record-dir DIR] [--demo] [--no-cursor] [--step-delay MS] [--type-delay MS] [--move-steps N]' });
518
- process.exit(1);
519
- }
520
- await run(positional[0], baseUrl, outDir, recordDir, opts);
521
- return;
522
- }
523
-
524
- emit({ ok: false, error: 'usage: agent-browser.mjs <doctor|run> ...' });
525
- process.exit(1);
526
- }
527
-
528
- main().catch((err) => {
529
- emit({ ok: false, error: String(err && err.message ? err.message : err) });
530
- process.exit(1);
531
- });
@@ -1,95 +0,0 @@
1
- ---
2
- name: building
3
- description: Install dependencies and run the test/lint/typecheck gate for a change inside the sandbox — package-manager detection, install-first, and (when implementing) TDD discipline. Use when implementing, fixing, or verifying code in a pre-cloned repo.
4
- version: 1.0.0
5
- tags: [build, test, sandbox]
6
- ---
7
-
8
- # Building
9
-
10
- How to install, build, test, and gate a change in the sandbox. The same
11
- discipline whether you're implementing a feature, fixing review feedback, or
12
- verifying someone else's PR.
13
-
14
- ## Workspace & git
15
-
16
- The harness pre-cloned the repo; your cwd is the repo root (or a `<repo>/`
17
- subdirectory — check with `ls -la`). Git is configured for clone/push/pull/fetch.
18
- If auth fails after ~1 hour, call the `github_refresh_git_auth` MCP tool. Suppress
19
- noise where it helps: `git clone --quiet`, `git push --quiet`, `CI=true`.
20
-
21
- ## Install-first
22
-
23
- `node_modules` (and any other dependency dir) is **always** absent on arrival —
24
- by design, not a blocker. **Installing is the first step, not a reason to skip
25
- verification.** Detect the package manager from the lockfile and use the
26
- frozen/CI variant:
27
-
28
- - `package-lock.json` → `npm ci`
29
- - `pnpm-lock.yaml` → `corepack pnpm install --frozen-lockfile`
30
- - `yarn.lock` → `corepack yarn install --frozen-lockfile`
31
-
32
- Node is available via `fnm` + `corepack`; the egress allowlist permits the public
33
- package registries, so install works. For a monorepo, install at the root, then
34
- operate on the changed package. For non-Node repos, use the ecosystem's
35
- equivalent (`pip install`, `cargo build`, `go mod download`, …) read from the
36
- project's manifest.
37
-
38
- The **only** acceptable "couldn't verify" is when the install or build command
39
- *itself* fails — quote the exact command and error, and scope your work to what
40
- you could check. Never cite "deps aren't installed" as the reason: you install them.
41
-
42
- **If the repo's only test path needs an external service that won't run in the
43
- sandbox** (a live database, a cloud API, a running daemon), do **not** report
44
- that verification "could not run." Add a focused unit or CLI test project that
45
- exercises your change against in-memory fixtures or fakes, run *that*, and paste
46
- its output. A change you couldn't execute even once is unverified — building a
47
- runnable path is part of the work, not an optional extra.
48
-
49
- ## The gate
50
-
51
- While iterating, run **only the tests covering the files you touched** — not the
52
- whole suite on every edit. Then, **once before committing or claiming done, run
53
- the full gate and require all of it to pass:**
54
-
55
- 1. Full test command — zero failures.
56
- 2. Lint command (if present) — fix all errors.
57
- 3. Typecheck command (if present) — fix all errors.
58
-
59
- If any step fails, fix it and re-run only what failed until clean. Do not commit
60
- or report done until the full suite, lint, and typecheck all pass. Cite the
61
- actual command output — static reasoning is not verification.
62
-
63
- ## TDD (when implementing)
64
-
65
- When you are *writing* code (not just verifying a PR): write the **failing test
66
- first**, watch it go red, then implement until it goes green, then refactor.
67
- Test behaviour through the public interface, not implementation details. The red
68
- test is the proof the test can fail — a test that was never red proves nothing.
69
-
70
- ## Decomposition budget (when implementing)
71
-
72
- The gate passing is necessary, not sufficient — a green test does not excuse an
73
- unmaintainable function. Before you commit, decompose:
74
-
75
- - Keep each function under **roughly 15 cyclomatic complexity** (branches +
76
- loops + boolean operators). If you're past that, extract helpers.
77
- - One function = one responsibility. A function that **parses, validates, and
78
- emits is three functions** — split it before committing, not "later."
79
- - The refactor step of red-green-refactor is where this happens. Don't skip it
80
- because the tests already pass.
81
-
82
- ## Type safety — no compiler-silencing assertions
83
-
84
- The gate requires `tsc` (or the project's typechecker) to pass — but passing it
85
- by *suppressing* it is a regression, not a fix. **Never** use `as any`, an
86
- unchecked `as`-cast, `@ts-ignore`/`@ts-expect-error`, `# type: ignore`, or the
87
- equivalent to:
88
-
89
- - silence a compiler error instead of fixing the underlying type, or
90
- - bypass a validator/guard the same code path defines (e.g. casting past a Zod
91
- schema, a runtime type guard, or a parse function so the value skips the very
92
- check that file exists to enforce).
93
-
94
- If the types genuinely can't express something, narrow with a real type guard or
95
- fix the type — don't assert your way past it.
@@ -1,36 +0,0 @@
1
- ---
2
- name: chat
3
- description: Conversational assistant for messaging-platform threads (Slack, Discord). Answer questions about repos, PRs, and issues, explain code, and guide users to natural-language triggers (e.g. "build owner/repo#N", "triage owner/repo", "review PRs on owner/repo", "status").
4
- ---
5
-
6
- # Chat
7
-
8
- You are answering in a messaging thread (Slack, Discord). The conversation is
9
- the job — answer the question that was asked, don't expand it into a report.
10
-
11
- ## What you do
12
-
13
- - Answer questions about repositories, issues, pull requests, and code.
14
- - Explain how the bot's workflows behave.
15
- - Report status on running work when asked.
16
-
17
- ## What you don't do
18
-
19
- Chat is **read-and-explain only**. You don't review PRs, triage issues, run
20
- builds, or change anything. When the user wants an *action*, name the
21
- natural-language trigger and stop:
22
-
23
- - code changes → `build owner/repo#N`
24
- - issue triage → `triage owner/repo`
25
- - PR review → `review PRs on owner/repo`
26
- - security scan → `security review owner/repo`
27
- - running-task status → `status`
28
-
29
- Phrase triggers as natural language — never with a leading `/`, which Slack
30
- intercepts before it reaches Last Light.
31
-
32
- ## Style
33
-
34
- - Concise — messaging panes are narrow. A few sentences beats a wall of text.
35
- - Markdown sparingly: bold for emphasis, fenced blocks for code.
36
- - Lead with the answer. Cite `path:line` when pointing at code.