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.
- package/README.md +71 -692
- package/dist/cli-config.js.map +1 -0
- package/dist/cli-format.js.map +1 -0
- package/dist/{cli/cli-server.d.ts → cli-server.d.ts} +26 -1
- package/dist/{cli/cli-server.js → cli-server.js} +40 -5
- package/dist/cli-server.js.map +1 -0
- package/dist/cli-timeline.js.map +1 -0
- package/dist/{cli/cli.js → cli.js} +4 -3
- package/dist/cli.js.map +1 -0
- package/dist/{cli/fork-cli.d.ts → fork-cli.d.ts} +3 -3
- package/dist/{cli/fork-cli.js → fork-cli.js} +28 -27
- package/dist/fork-cli.js.map +1 -0
- package/dist/{cli/oauth-cli.js → oauth-cli.js} +1 -1
- package/dist/oauth-cli.js.map +1 -0
- package/dist/{cli/setup.js → setup.js} +10 -4
- package/dist/setup.js.map +1 -0
- package/dist/{cli/skills-install.d.ts → skills-install.d.ts} +6 -0
- package/dist/{cli/skills-install.js → skills-install.js} +6 -2
- package/dist/skills-install.js.map +1 -0
- package/package.json +20 -65
- package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
- package/agent-context/rules.md +0 -80
- package/agent-context/security.md +0 -69
- package/agent-context/soul.md +0 -21
- package/config/default.yaml +0 -119
- package/deploy/.env.production.example +0 -100
- package/deploy/entrypoint.sh +0 -82
- package/deploy/native/README.md +0 -160
- package/deploy/native/install.sh +0 -110
- package/deploy/native/lastlight.env.example +0 -95
- package/deploy/native/lastlight.service +0 -50
- package/deploy/sandbox-entrypoint.sh +0 -93
- package/dist/admin/auth.d.ts +0 -51
- package/dist/admin/auth.js +0 -121
- package/dist/admin/auth.js.map +0 -1
- package/dist/admin/chat-session-reader.d.ts +0 -39
- package/dist/admin/chat-session-reader.js +0 -84
- package/dist/admin/chat-session-reader.js.map +0 -1
- package/dist/admin/docker.d.ts +0 -61
- package/dist/admin/docker.js +0 -187
- package/dist/admin/docker.js.map +0 -1
- package/dist/admin/index.d.ts +0 -9
- package/dist/admin/index.js +0 -59
- package/dist/admin/index.js.map +0 -1
- package/dist/admin/routes.d.ts +0 -81
- package/dist/admin/routes.js +0 -1381
- package/dist/admin/routes.js.map +0 -1
- package/dist/admin/sessions.d.ts +0 -89
- package/dist/admin/sessions.js +0 -204
- package/dist/admin/sessions.js.map +0 -1
- package/dist/admin/tail.d.ts +0 -12
- package/dist/admin/tail.js +0 -145
- package/dist/admin/tail.js.map +0 -1
- package/dist/admin/version.d.ts +0 -22
- package/dist/admin/version.js +0 -92
- package/dist/admin/version.js.map +0 -1
- package/dist/cli/cli-config.js.map +0 -1
- package/dist/cli/cli-format.js.map +0 -1
- package/dist/cli/cli-server.js.map +0 -1
- package/dist/cli/cli-timeline.js.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/fork-cli.js.map +0 -1
- package/dist/cli/oauth-cli.js.map +0 -1
- package/dist/cli/setup.js.map +0 -1
- package/dist/cli/skills-install.js.map +0 -1
- package/dist/config/config-resolve.d.ts +0 -28
- package/dist/config/config-resolve.js +0 -47
- package/dist/config/config-resolve.js.map +0 -1
- package/dist/config/config.d.ts +0 -157
- package/dist/config/config.js +0 -581
- package/dist/config/config.js.map +0 -1
- package/dist/config/core-pin.d.ts +0 -16
- package/dist/config/core-pin.js +0 -85
- package/dist/config/core-pin.js.map +0 -1
- package/dist/config/overlay-assets.d.ts +0 -20
- package/dist/config/overlay-assets.js +0 -114
- package/dist/config/overlay-assets.js.map +0 -1
- package/dist/config/overlay-bootstrap.d.ts +0 -44
- package/dist/config/overlay-bootstrap.js +0 -304
- package/dist/config/overlay-bootstrap.js.map +0 -1
- package/dist/connectors/github-webhook.d.ts +0 -45
- package/dist/connectors/github-webhook.js +0 -321
- package/dist/connectors/github-webhook.js.map +0 -1
- package/dist/connectors/index.d.ts +0 -18
- package/dist/connectors/index.js +0 -41
- package/dist/connectors/index.js.map +0 -1
- package/dist/connectors/messaging/base.d.ts +0 -42
- package/dist/connectors/messaging/base.js +0 -139
- package/dist/connectors/messaging/base.js.map +0 -1
- package/dist/connectors/messaging/delivery.d.ts +0 -14
- package/dist/connectors/messaging/delivery.js +0 -38
- package/dist/connectors/messaging/delivery.js.map +0 -1
- package/dist/connectors/messaging/index.d.ts +0 -4
- package/dist/connectors/messaging/index.js +0 -4
- package/dist/connectors/messaging/index.js.map +0 -1
- package/dist/connectors/messaging/session-manager.d.ts +0 -53
- package/dist/connectors/messaging/session-manager.js +0 -255
- package/dist/connectors/messaging/session-manager.js.map +0 -1
- package/dist/connectors/messaging/types.d.ts +0 -58
- package/dist/connectors/messaging/types.js +0 -7
- package/dist/connectors/messaging/types.js.map +0 -1
- package/dist/connectors/slack/connector.d.ts +0 -142
- package/dist/connectors/slack/connector.js +0 -494
- package/dist/connectors/slack/connector.js.map +0 -1
- package/dist/connectors/slack/index.d.ts +0 -2
- package/dist/connectors/slack/index.js +0 -3
- package/dist/connectors/slack/index.js.map +0 -1
- package/dist/connectors/slack/mrkdwn.d.ts +0 -19
- package/dist/connectors/slack/mrkdwn.js +0 -240
- package/dist/connectors/slack/mrkdwn.js.map +0 -1
- package/dist/connectors/types.d.ts +0 -61
- package/dist/connectors/types.js +0 -2
- package/dist/connectors/types.js.map +0 -1
- package/dist/cron/fanout.d.ts +0 -33
- package/dist/cron/fanout.js +0 -55
- package/dist/cron/fanout.js.map +0 -1
- package/dist/cron/jobs.d.ts +0 -18
- package/dist/cron/jobs.js +0 -37
- package/dist/cron/jobs.js.map +0 -1
- package/dist/cron/scheduler.d.ts +0 -45
- package/dist/cron/scheduler.js +0 -105
- package/dist/cron/scheduler.js.map +0 -1
- package/dist/engine/agent-executor.d.ts +0 -34
- package/dist/engine/agent-executor.js +0 -246
- package/dist/engine/agent-executor.js.map +0 -1
- package/dist/engine/chat/chat-runner.d.ts +0 -132
- package/dist/engine/chat/chat-runner.js +0 -424
- package/dist/engine/chat/chat-runner.js.map +0 -1
- package/dist/engine/chat/chat-skills.d.ts +0 -45
- package/dist/engine/chat/chat-skills.js +0 -184
- package/dist/engine/chat/chat-skills.js.map +0 -1
- package/dist/engine/chat/chat.d.ts +0 -54
- package/dist/engine/chat/chat.js +0 -316
- package/dist/engine/chat/chat.js.map +0 -1
- package/dist/engine/chat/message-batcher.d.ts +0 -32
- package/dist/engine/chat/message-batcher.js +0 -109
- package/dist/engine/chat/message-batcher.js.map +0 -1
- package/dist/engine/dispatcher.d.ts +0 -73
- package/dist/engine/dispatcher.js +0 -663
- package/dist/engine/dispatcher.js.map +0 -1
- package/dist/engine/event-shim.d.ts +0 -117
- package/dist/engine/event-shim.js +0 -435
- package/dist/engine/event-shim.js.map +0 -1
- package/dist/engine/executors/orchestrator.d.ts +0 -81
- package/dist/engine/executors/orchestrator.js +0 -365
- package/dist/engine/executors/orchestrator.js.map +0 -1
- package/dist/engine/executors/shared.d.ts +0 -189
- package/dist/engine/executors/shared.js +0 -616
- package/dist/engine/executors/shared.js.map +0 -1
- package/dist/engine/github/git-auth.d.ts +0 -62
- package/dist/engine/github/git-auth.js +0 -161
- package/dist/engine/github/git-auth.js.map +0 -1
- package/dist/engine/github/github-app-client.d.ts +0 -23
- package/dist/engine/github/github-app-client.js +0 -33
- package/dist/engine/github/github-app-client.js.map +0 -1
- package/dist/engine/github/github-tools.d.ts +0 -18
- package/dist/engine/github/github-tools.js +0 -263
- package/dist/engine/github/github-tools.js.map +0 -1
- package/dist/engine/github/github.d.ts +0 -1193
- package/dist/engine/github/github.js +0 -357
- package/dist/engine/github/github.js.map +0 -1
- package/dist/engine/github/profiles.d.ts +0 -279
- package/dist/engine/github/profiles.js +0 -42
- package/dist/engine/github/profiles.js.map +0 -1
- package/dist/engine/github/review-poster.d.ts +0 -98
- package/dist/engine/github/review-poster.js +0 -177
- package/dist/engine/github/review-poster.js.map +0 -1
- package/dist/engine/llm.d.ts +0 -79
- package/dist/engine/llm.js +0 -269
- package/dist/engine/llm.js.map +0 -1
- package/dist/engine/oauth.d.ts +0 -43
- package/dist/engine/oauth.js +0 -103
- package/dist/engine/oauth.js.map +0 -1
- package/dist/engine/router.d.ts +0 -29
- package/dist/engine/router.js +0 -680
- package/dist/engine/router.js.map +0 -1
- package/dist/engine/screen/classifier.d.ts +0 -93
- package/dist/engine/screen/classifier.js +0 -215
- package/dist/engine/screen/classifier.js.map +0 -1
- package/dist/engine/screen/screen.d.ts +0 -41
- package/dist/engine/screen/screen.js +0 -93
- package/dist/engine/screen/screen.js.map +0 -1
- package/dist/evals-api.d.ts +0 -24
- package/dist/evals-api.js +0 -22
- package/dist/evals-api.js.map +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -940
- package/dist/index.js.map +0 -1
- package/dist/managed-repos.d.ts +0 -30
- package/dist/managed-repos.js +0 -72
- package/dist/managed-repos.js.map +0 -1
- package/dist/notify/blocks.d.ts +0 -23
- package/dist/notify/blocks.js +0 -118
- package/dist/notify/blocks.js.map +0 -1
- package/dist/notify/index.d.ts +0 -17
- package/dist/notify/index.js +0 -7
- package/dist/notify/index.js.map +0 -1
- package/dist/notify/model.d.ts +0 -57
- package/dist/notify/model.js +0 -89
- package/dist/notify/model.js.map +0 -1
- package/dist/notify/notifier.d.ts +0 -18
- package/dist/notify/notifier.js +0 -100
- package/dist/notify/notifier.js.map +0 -1
- package/dist/notify/render.d.ts +0 -21
- package/dist/notify/render.js +0 -59
- package/dist/notify/render.js.map +0 -1
- package/dist/notify/transports/github.d.ts +0 -26
- package/dist/notify/transports/github.js +0 -27
- package/dist/notify/transports/github.js.map +0 -1
- package/dist/notify/transports/slack.d.ts +0 -27
- package/dist/notify/transports/slack.js +0 -37
- package/dist/notify/transports/slack.js.map +0 -1
- package/dist/notify/types.d.ts +0 -119
- package/dist/notify/types.js +0 -16
- package/dist/notify/types.js.map +0 -1
- package/dist/providers.d.ts +0 -125
- package/dist/providers.js +0 -279
- package/dist/providers.js.map +0 -1
- package/dist/sandbox/docker.d.ts +0 -180
- package/dist/sandbox/docker.js +0 -517
- package/dist/sandbox/docker.js.map +0 -1
- package/dist/sandbox/egress-allowlist.d.ts +0 -74
- package/dist/sandbox/egress-allowlist.js +0 -164
- package/dist/sandbox/egress-allowlist.js.map +0 -1
- package/dist/sandbox/egress-firewall-config.d.ts +0 -127
- package/dist/sandbox/egress-firewall-config.js +0 -434
- package/dist/sandbox/egress-firewall-config.js.map +0 -1
- package/dist/sandbox/images.d.ts +0 -23
- package/dist/sandbox/images.js +0 -55
- package/dist/sandbox/images.js.map +0 -1
- package/dist/sandbox/index.d.ts +0 -112
- package/dist/sandbox/index.js +0 -332
- package/dist/sandbox/index.js.map +0 -1
- package/dist/sandbox/sandbox.d.ts +0 -214
- package/dist/sandbox/sandbox.js +0 -453
- package/dist/sandbox/sandbox.js.map +0 -1
- package/dist/sandbox/smol.d.ts +0 -162
- package/dist/sandbox/smol.js +0 -485
- package/dist/sandbox/smol.js.map +0 -1
- package/dist/session-log.d.ts +0 -63
- package/dist/session-log.js +0 -290
- package/dist/session-log.js.map +0 -1
- package/dist/state/approval-store.d.ts +0 -88
- package/dist/state/approval-store.js +0 -149
- package/dist/state/approval-store.js.map +0 -1
- package/dist/state/build-assets.d.ts +0 -50
- package/dist/state/build-assets.js +0 -154
- package/dist/state/build-assets.js.map +0 -1
- package/dist/state/db.d.ts +0 -81
- package/dist/state/db.js +0 -155
- package/dist/state/db.js.map +0 -1
- package/dist/state/execution-store.d.ts +0 -257
- package/dist/state/execution-store.js +0 -527
- package/dist/state/execution-store.js.map +0 -1
- package/dist/state/migrate.d.ts +0 -15
- package/dist/state/migrate.js +0 -175
- package/dist/state/migrate.js.map +0 -1
- package/dist/state/workflow-run-store.d.ts +0 -216
- package/dist/state/workflow-run-store.js +0 -403
- package/dist/state/workflow-run-store.js.map +0 -1
- package/dist/telemetry/index.d.ts +0 -38
- package/dist/telemetry/index.js +0 -253
- package/dist/telemetry/index.js.map +0 -1
- package/dist/telemetry/pi-events.d.ts +0 -12
- package/dist/telemetry/pi-events.js +0 -134
- package/dist/telemetry/pi-events.js.map +0 -1
- package/dist/workflows/dag.d.ts +0 -37
- package/dist/workflows/dag.js +0 -118
- package/dist/workflows/dag.js.map +0 -1
- package/dist/workflows/loader.d.ts +0 -53
- package/dist/workflows/loader.js +0 -421
- package/dist/workflows/loader.js.map +0 -1
- package/dist/workflows/loop-eval.d.ts +0 -28
- package/dist/workflows/loop-eval.js +0 -74
- package/dist/workflows/loop-eval.js.map +0 -1
- package/dist/workflows/phase-executor.d.ts +0 -222
- package/dist/workflows/phase-executor.js +0 -1153
- package/dist/workflows/phase-executor.js.map +0 -1
- package/dist/workflows/phase-ref.d.ts +0 -50
- package/dist/workflows/phase-ref.js +0 -91
- package/dist/workflows/phase-ref.js.map +0 -1
- package/dist/workflows/resume.d.ts +0 -58
- package/dist/workflows/resume.js +0 -313
- package/dist/workflows/resume.js.map +0 -1
- package/dist/workflows/runner.d.ts +0 -63
- package/dist/workflows/runner.js +0 -381
- package/dist/workflows/runner.js.map +0 -1
- package/dist/workflows/schema.d.ts +0 -344
- package/dist/workflows/schema.js +0 -460
- package/dist/workflows/schema.js.map +0 -1
- package/dist/workflows/simple.d.ts +0 -90
- package/dist/workflows/simple.js +0 -393
- package/dist/workflows/simple.js.map +0 -1
- package/dist/workflows/target-policy.d.ts +0 -74
- package/dist/workflows/target-policy.js +0 -80
- package/dist/workflows/target-policy.js.map +0 -1
- package/dist/workflows/templates.d.ts +0 -88
- package/dist/workflows/templates.js +0 -127
- package/dist/workflows/templates.js.map +0 -1
- package/dist/workflows/triggers.d.ts +0 -21
- package/dist/workflows/triggers.js +0 -45
- package/dist/workflows/triggers.js.map +0 -1
- package/dist/workflows/verdict.d.ts +0 -19
- package/dist/workflows/verdict.js +0 -30
- package/dist/workflows/verdict.js.map +0 -1
- package/dist/worktree/manager.d.ts +0 -43
- package/dist/worktree/manager.js +0 -127
- package/dist/worktree/manager.js.map +0 -1
- package/docker-compose.yml +0 -274
- package/sandbox.Dockerfile +0 -68
- package/skills/README.md +0 -42
- package/skills/browser-qa/SKILL.md +0 -208
- package/skills/browser-qa/scripts/agent-browser.mjs +0 -531
- package/skills/building/SKILL.md +0 -95
- package/skills/chat/SKILL.md +0 -36
- package/skills/code-review/SKILL.md +0 -82
- package/skills/debug-production/SKILL.md +0 -108
- package/skills/demo/SKILL.md +0 -228
- package/skills/demo/scripts/compose-demo.sh +0 -243
- package/skills/issue-answer/SKILL.md +0 -91
- package/skills/issue-comment/SKILL.md +0 -49
- package/skills/issue-triage/SKILL.md +0 -149
- package/skills/issue-triage/references/AGENT-BRIEF.md +0 -65
- package/skills/pr-comment/SKILL.md +0 -64
- package/skills/pr-review/SKILL.md +0 -148
- package/skills/pr-review/references/findings-schema.md +0 -89
- package/skills/qa-test/SKILL.md +0 -97
- package/skills/repo-health/SKILL.md +0 -67
- package/skills/security-feedback/SKILL.md +0 -173
- package/skills/security-feedback/references/templates.md +0 -77
- package/skills/security-review/SKILL.md +0 -215
- package/skills/security-review/references/issue-format.md +0 -302
- package/skills/verify/SKILL.md +0 -118
- package/workflows/answer.yaml +0 -44
- package/workflows/build.yaml +0 -129
- package/workflows/cron-health.yaml +0 -6
- package/workflows/cron-review.yaml +0 -8
- package/workflows/cron-security.yaml +0 -6
- package/workflows/cron-triage.yaml +0 -8
- package/workflows/demo.yaml +0 -62
- package/workflows/examples/bash-smoke.yaml +0 -41
- package/workflows/examples/parallel-review.yaml +0 -36
- package/workflows/examples/tdd-loop.yaml +0 -17
- package/workflows/explore.yaml +0 -80
- package/workflows/issue-comment.yaml +0 -15
- package/workflows/issue-triage.yaml +0 -22
- package/workflows/pr-comment.yaml +0 -16
- package/workflows/pr-fix.yaml +0 -17
- package/workflows/pr-review.yaml +0 -50
- package/workflows/prompts/answer.md +0 -68
- package/workflows/prompts/architect.md +0 -44
- package/workflows/prompts/classifier.md +0 -60
- package/workflows/prompts/classify-adds-info.md +0 -17
- package/workflows/prompts/demo.md +0 -125
- package/workflows/prompts/executor.md +0 -42
- package/workflows/prompts/explore-ask.md +0 -99
- package/workflows/prompts/explore-publish.md +0 -31
- package/workflows/prompts/explore-read.md +0 -120
- package/workflows/prompts/explore-synthesize.md +0 -88
- package/workflows/prompts/fix.md +0 -18
- package/workflows/prompts/guardrails.md +0 -96
- package/workflows/prompts/pr-fix.md +0 -26
- package/workflows/prompts/pr.md +0 -39
- package/workflows/prompts/qa-browser.md +0 -95
- package/workflows/prompts/qa-synth.md +0 -44
- package/workflows/prompts/qa-test.md +0 -60
- package/workflows/prompts/re-reviewer.md +0 -33
- package/workflows/prompts/reviewer.md +0 -60
- package/workflows/prompts/verify-browser.md +0 -93
- package/workflows/prompts/verify-synth.md +0 -44
- package/workflows/prompts/verify.md +0 -63
- package/workflows/qa-test.yaml +0 -94
- package/workflows/repo-health.yaml +0 -15
- package/workflows/security-feedback.yaml +0 -25
- package/workflows/security-review.yaml +0 -37
- package/workflows/verify.yaml +0 -93
- /package/dist/{cli/cli-config.d.ts → cli-config.d.ts} +0 -0
- /package/dist/{cli/cli-config.js → cli-config.js} +0 -0
- /package/dist/{cli/cli-format.d.ts → cli-format.d.ts} +0 -0
- /package/dist/{cli/cli-format.js → cli-format.js} +0 -0
- /package/dist/{cli/cli-timeline.d.ts → cli-timeline.d.ts} +0 -0
- /package/dist/{cli/cli-timeline.js → cli-timeline.js} +0 -0
- /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
- /package/dist/{cli/oauth-cli.d.ts → oauth-cli.d.ts} +0 -0
- /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.
|
package/skills/demo/SKILL.md
DELETED
|
@@ -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}}"
|