@erclx/canon 5.65.0 → 5.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/app-drive/REQUIREMENT.md +35 -0
- package/claude/skills/app-drive/SKILL.md +112 -0
- package/claude/skills/app-drive/references/config.md +38 -0
- package/claude/skills/app-drive/references/drive-record.md +70 -0
- package/claude/skills/app-drive/references/isolate.md +46 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/git-pr/references/labels.md +6 -2
- package/claude/skills/git-pr-close/REQUIREMENT.md +29 -0
- package/claude/skills/git-pr-close/SKILL.md +55 -0
- package/claude/skills/git-pr-close/references/close.md +54 -0
- package/claude/skills/plan-feature/references/plan.md +1 -1
- package/claude/skills/plan-intake/references/intake.md +12 -2
- package/claude/skills/pr-evidence/SKILL.md +2 -2
- package/claude/skills/pr-evidence/references/evidence.md +3 -1
- package/claude/skills/review-ui/SKILL.md +5 -4
- package/claude/skills/review-ui/references/drive-copy.md +4 -0
- package/claude/skills/review-ui/references/driving.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +15 -2
- package/claude/skills/role-planner/SKILL.md +2 -2
- package/claude/skills/role-worker/SKILL.md +6 -8
- package/claude/skills/session-map/REQUIREMENT.md +1 -1
- package/claude/skills/session-map/SKILL.md +8 -1
- package/claude/skills/session-map/references/session.md +19 -5
- package/claude/skills/ui-record/REQUIREMENT.md +34 -0
- package/claude/skills/ui-record/SKILL.md +103 -0
- package/claude/skills/ui-record/references/config.md +40 -0
- package/claude/skills/ui-record/references/handoff.md +48 -0
- package/claude/skills/ui-record/references/take-craft.md +92 -0
- package/docs/agents/records.md +1 -0
- package/docs/workflow/parallel-features.md +1 -1
- package/docs/workflow/skill-map-on-request.md +1 -0
- package/docs/workflow/skill-map.md +2 -0
- package/docs/workflow/visual-design-workflow.md +2 -1
- package/package.json +1 -1
- package/src/audits/catalog.ts +5 -1
- package/src/browser/agent-cli.ts +68 -0
- package/src/claude/cases/git.ts +4 -0
- package/src/claude/cases/workflow.ts +20 -0
- package/src/cli/help.ts +3 -0
- package/src/cli.ts +4 -0
- package/src/commands/browser.ts +102 -0
- package/src/commands/intake.ts +181 -1
- package/src/commands/labels.ts +152 -4
- package/src/commands/pr.ts +278 -8
- package/src/commands/records.ts +18 -1
- package/src/commands/take.ts +266 -0
- package/src/context/requirements.ts +1 -11
- package/src/git/status.ts +73 -0
- package/src/intake/drive-check.ts +355 -0
- package/src/intake/folder.ts +1 -1
- package/src/labels/apply.ts +10 -5
- package/src/labels/sync.ts +17 -0
- package/src/markdown/scan.ts +10 -0
- package/src/pr/close.ts +152 -0
- package/src/pr/drive-copy.ts +97 -84
- package/src/pr/health.ts +56 -0
- package/src/pr/local.ts +49 -8
- package/src/pr/rest.ts +10 -2
- package/src/records/session-map.ts +75 -0
- package/src/records/validate.ts +64 -8
- package/src/sandbox/replay/run.ts +30 -5
- package/src/take/camera.ts +309 -0
- package/src/take/encode.ts +170 -0
- package/src/take/film.ts +206 -0
- package/src/take/fixtures/fade.html +49 -0
- package/src/take/fixtures/fetch.html +38 -0
- package/src/take/log.ts +109 -0
- package/src/take/perform.ts +200 -0
- package/src/take/plan.ts +193 -0
- package/src/take/screencast.ts +94 -0
- package/src/take/shell.ts +287 -0
- package/src/take/stepper.ts +252 -0
- package/src/take/zoom/NOTICE.md +45 -0
- package/src/take/zoom/math-utils.ts +69 -0
- package/src/take/zoom/region-utils.ts +55 -0
- package/src/take/zoom/spring.ts +133 -0
- package/src/tasks/archive.ts +1 -3
- package/src/tooling/verify-stack.ts +4 -5
- package/tooling/base/reference.md +1 -0
- package/tooling/base/seeds/canon/config/pr-labels.toml +41 -0
- package/claude/skills/review-ui/scripts/pw.sh +0 -60
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Vendored from OpenScreen at ca8104c, MIT. See NOTICE.md beside this file.
|
|
2
|
+
// Source: src/components/video-editor/videoPlayback/zoomRegionUtils.ts and the
|
|
3
|
+
// two window constants from constants.ts. The zoom-out window and the chained
|
|
4
|
+
// pan's duration carry the operator's tuning, and NOTICE.md lists both.
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
clamp01,
|
|
8
|
+
cubicBezier,
|
|
9
|
+
easeOutScreenStudio,
|
|
10
|
+
} from '@/take/zoom/math-utils'
|
|
11
|
+
|
|
12
|
+
/** OpenScreen's 1015.05 ms, shortened to 700 ms for a quicker zoom-out. */
|
|
13
|
+
export const TRANSITION_WINDOW_MS = 700
|
|
14
|
+
/** OpenScreen's zoom-in window, 1.5 times its original 1015.05 ms window. */
|
|
15
|
+
export const ZOOM_IN_TRANSITION_WINDOW_MS = 1015.05 * 1.5
|
|
16
|
+
|
|
17
|
+
export const CHAINED_ZOOM_PAN_GAP_MS = 1500
|
|
18
|
+
/** OpenScreen's 1000 ms, shortened to 600 ms. */
|
|
19
|
+
export const CONNECTED_ZOOM_PAN_DURATION_MS = 600
|
|
20
|
+
export const ZOOM_IN_OVERLAP_MS = 500
|
|
21
|
+
|
|
22
|
+
export interface ZoomRegion {
|
|
23
|
+
startMs: number
|
|
24
|
+
endMs: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function lerp(start: number, end: number, amount: number) {
|
|
28
|
+
return start + (end - start) * amount
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function easeConnectedPan(value: number) {
|
|
32
|
+
return cubicBezier(0.1, 0.0, 0.2, 1.0, value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function computeRegionStrength(region: ZoomRegion, timeMs: number) {
|
|
36
|
+
const zoomInEnd = region.startMs + ZOOM_IN_OVERLAP_MS
|
|
37
|
+
const leadInStart = zoomInEnd - ZOOM_IN_TRANSITION_WINDOW_MS
|
|
38
|
+
const leadOutEnd = region.endMs + TRANSITION_WINDOW_MS
|
|
39
|
+
|
|
40
|
+
if (timeMs < leadInStart || timeMs > leadOutEnd) {
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (timeMs < zoomInEnd) {
|
|
45
|
+
const progress = (timeMs - leadInStart) / ZOOM_IN_TRANSITION_WINDOW_MS
|
|
46
|
+
return easeOutScreenStudio(progress)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (timeMs <= region.endMs) {
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const progress = clamp01((timeMs - region.endMs) / TRANSITION_WINDOW_MS)
|
|
54
|
+
return 1 - easeOutScreenStudio(progress)
|
|
55
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Adapted from OpenScreen at ca8104c, MIT. See NOTICE.md beside this file.
|
|
2
|
+
// Source: getZoomSpringConfig, stepSpringValue, and clampDeltaMs in
|
|
3
|
+
// src/components/video-editor/videoPlayback/motionSmoothing.ts, and stepAxis in
|
|
4
|
+
// zoomSpring.ts. OpenScreen steps its spring through the `motion` package, and
|
|
5
|
+
// this solves the same damped spring in closed form instead, so a target
|
|
6
|
+
// inherits no dependency for one function.
|
|
7
|
+
|
|
8
|
+
export interface SpringState {
|
|
9
|
+
value: number
|
|
10
|
+
velocity: number
|
|
11
|
+
initialized: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SpringConfig {
|
|
15
|
+
stiffness: number
|
|
16
|
+
damping: number
|
|
17
|
+
mass: number
|
|
18
|
+
restDelta: number
|
|
19
|
+
restSpeed: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ZOOM_SPRING: SpringConfig = {
|
|
23
|
+
stiffness: 320,
|
|
24
|
+
damping: 40,
|
|
25
|
+
mass: 0.92,
|
|
26
|
+
restDelta: 0.0005,
|
|
27
|
+
restSpeed: 0.015,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createSpringState(initialValue = 0): SpringState {
|
|
31
|
+
return { value: initialValue, velocity: 0, initialized: false }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clampDeltaMs(deltaMs: number, fallbackMs = 1000 / 60) {
|
|
35
|
+
if (!Number.isFinite(deltaMs) || deltaMs <= 0) {
|
|
36
|
+
return fallbackMs
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Math.min(80, Math.max(1, deltaMs))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Advances one axis toward `target` by `deltaMs`. The first call snaps to the
|
|
44
|
+
* target, a spring at rest stays there, and a step that would cross a moving
|
|
45
|
+
* target lands on it with no velocity, which keeps a quick chase from wobbling
|
|
46
|
+
* on a reversal.
|
|
47
|
+
*/
|
|
48
|
+
export function stepSpring(
|
|
49
|
+
state: SpringState,
|
|
50
|
+
target: number,
|
|
51
|
+
deltaMs: number,
|
|
52
|
+
config: SpringConfig = ZOOM_SPRING,
|
|
53
|
+
): number {
|
|
54
|
+
if (!state.initialized || !Number.isFinite(state.value)) {
|
|
55
|
+
state.value = target
|
|
56
|
+
state.velocity = 0
|
|
57
|
+
state.initialized = true
|
|
58
|
+
return state.value
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
Math.abs(target - state.value) <= config.restDelta &&
|
|
63
|
+
Math.abs(state.velocity) <= config.restSpeed
|
|
64
|
+
) {
|
|
65
|
+
state.value = target
|
|
66
|
+
state.velocity = 0
|
|
67
|
+
return state.value
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const before = state.value
|
|
71
|
+
const [offset, velocity] = solve(
|
|
72
|
+
state.value - target,
|
|
73
|
+
state.velocity,
|
|
74
|
+
clampDeltaMs(deltaMs) / 1000,
|
|
75
|
+
config,
|
|
76
|
+
)
|
|
77
|
+
const after = target + offset
|
|
78
|
+
|
|
79
|
+
const crossed =
|
|
80
|
+
(before <= target && after > target) || (before >= target && after < target)
|
|
81
|
+
if (crossed) {
|
|
82
|
+
state.value = target
|
|
83
|
+
state.velocity = 0
|
|
84
|
+
return target
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
state.value = after
|
|
88
|
+
state.velocity = velocity
|
|
89
|
+
return after
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The displacement and velocity of a damped spring `seconds` after it sat at
|
|
94
|
+
* `x0` from rest length moving at `v0`, for each of the three damping regimes.
|
|
95
|
+
*/
|
|
96
|
+
function solve(
|
|
97
|
+
x0: number,
|
|
98
|
+
v0: number,
|
|
99
|
+
seconds: number,
|
|
100
|
+
{ stiffness, damping, mass }: SpringConfig,
|
|
101
|
+
): [number, number] {
|
|
102
|
+
const omega = Math.sqrt(stiffness / mass)
|
|
103
|
+
const zeta = damping / (2 * Math.sqrt(stiffness * mass))
|
|
104
|
+
const t = seconds
|
|
105
|
+
|
|
106
|
+
if (zeta < 1) {
|
|
107
|
+
const omegaD = omega * Math.sqrt(1 - zeta * zeta)
|
|
108
|
+
const decay = Math.exp(-zeta * omega * t)
|
|
109
|
+
const b = (v0 + zeta * omega * x0) / omegaD
|
|
110
|
+
const cos = Math.cos(omegaD * t)
|
|
111
|
+
const sin = Math.sin(omegaD * t)
|
|
112
|
+
const x = decay * (x0 * cos + b * sin)
|
|
113
|
+
const v =
|
|
114
|
+
decay *
|
|
115
|
+
(-zeta * omega * (x0 * cos + b * sin) + omegaD * (b * cos - x0 * sin))
|
|
116
|
+
return [x, v]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (zeta === 1) {
|
|
120
|
+
const decay = Math.exp(-omega * t)
|
|
121
|
+
const b = v0 + omega * x0
|
|
122
|
+
return [(x0 + b * t) * decay, (b - omega * (x0 + b * t)) * decay]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const root = Math.sqrt(zeta * zeta - 1)
|
|
126
|
+
const r1 = -omega * (zeta - root)
|
|
127
|
+
const r2 = -omega * (zeta + root)
|
|
128
|
+
const c1 = (v0 - r2 * x0) / (r1 - r2)
|
|
129
|
+
const c2 = x0 - c1
|
|
130
|
+
const e1 = Math.exp(r1 * t)
|
|
131
|
+
const e2 = Math.exp(r2 * t)
|
|
132
|
+
return [c1 * e1 + c2 * e2, c1 * r1 * e1 + c2 * r2 * e2]
|
|
133
|
+
}
|
package/src/tasks/archive.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { regenOne } from '@/indexes/regen'
|
|
|
7
7
|
import { rerenderRow } from '@/tasks/board'
|
|
8
8
|
import { planBranch } from '@/tasks/branch'
|
|
9
9
|
import { isUnder } from '@/tasks/paths'
|
|
10
|
+
import { SESSION_MAP_PREFIX } from '@/records/session-map'
|
|
10
11
|
import { recordDir, recordDirs } from '@/roots/record'
|
|
11
12
|
|
|
12
13
|
const TASKS = 'tasks'
|
|
@@ -29,9 +30,6 @@ export const RESERVED_STEMS = [
|
|
|
29
30
|
'session',
|
|
30
31
|
] as const
|
|
31
32
|
|
|
32
|
-
/** The pre-compaction handoff takes one file per session, so its stems vary. */
|
|
33
|
-
const SESSION_MAP_PREFIX = 'session-'
|
|
34
|
-
|
|
35
33
|
export function isReservedStem(stem: string): boolean {
|
|
36
34
|
const reserved: readonly string[] = RESERVED_STEMS
|
|
37
35
|
|
|
@@ -95,19 +95,18 @@ async function run({
|
|
|
95
95
|
logStep(`Scaffolding ${stack}`)
|
|
96
96
|
// Manifests carry shell syntax (`&&`, `cd`, quoted globs), so the command
|
|
97
97
|
// runs under a shell rather than as a split argv.
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
runsDir,
|
|
101
|
-
)
|
|
98
|
+
const scaffoldCommand = manifest.scaffold.replaceAll('{{name}}', name)
|
|
99
|
+
const scaffold = await shell(scaffoldCommand, runsDir)
|
|
102
100
|
if (scaffold.failed) {
|
|
103
101
|
logWarn('Scaffold failed')
|
|
102
|
+
process.stderr.write(`$ ${scaffoldCommand}\n`)
|
|
104
103
|
writeTail(scaffold.all)
|
|
105
|
-
state.isFailed = true
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
if (!existsSync(state.dir)) {
|
|
109
107
|
return refuse(`Scaffold did not produce ${state.dir}`)
|
|
110
108
|
}
|
|
109
|
+
record(state, 'Scaffold', !scaffold.failed)
|
|
111
110
|
|
|
112
111
|
await shell(
|
|
113
112
|
'git init -q && git add . && git commit -m "chore: verify scaffold" -q --no-verify --allow-empty',
|
|
@@ -39,6 +39,7 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
|
|
|
39
39
|
- `.github/workflows/verify.yml` runs on pull requests targeting `main` and on `workflow_dispatch`. A multi-root project adds its own jobs here.
|
|
40
40
|
- `.github/pull_request_template.md` has `## Summary`, `## Key changes`, `## Technical context`, and `## Testing`. A project adds its own checklist lines.
|
|
41
41
|
- `canon/context/development/` holds the `setup` and `ci` entries under a folder index. Extend them with project-specific commands and workflows, and keep canonical rationale here.
|
|
42
|
+
- `canon/config/pr-labels.toml` is the starter pull request label map, covering what the toolkit installs. A project adds a row per surface of its own.
|
|
42
43
|
|
|
43
44
|
## Tool pairing
|
|
44
45
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Pull request domain labels.
|
|
2
|
+
#
|
|
3
|
+
# `git-pr` matches the paths a branch changed against the prefixes below and
|
|
4
|
+
# applies every label that matches. The rows here cover what the toolkit
|
|
5
|
+
# installs. Add a row per surface of your own, such as `app = ["src/"]`, and
|
|
6
|
+
# `canon labels audit` names any changed path no row reaches.
|
|
7
|
+
#
|
|
8
|
+
# Each label has to exist on the remote before a pull request can carry it.
|
|
9
|
+
# Run `canon labels sync` once to create the ones the remote lacks, and again
|
|
10
|
+
# after adding a row.
|
|
11
|
+
#
|
|
12
|
+
# The matching rule and the `[declined]` table are described in the `git-pr`
|
|
13
|
+
# skill's labels reference.
|
|
14
|
+
|
|
15
|
+
[domains]
|
|
16
|
+
tooling = [
|
|
17
|
+
".github/",
|
|
18
|
+
".husky/",
|
|
19
|
+
"scripts/",
|
|
20
|
+
".gitignore",
|
|
21
|
+
".lintstagedrc",
|
|
22
|
+
".oxfmtrc.json",
|
|
23
|
+
".prettierignore",
|
|
24
|
+
"commitlint.config.js",
|
|
25
|
+
"package.json",
|
|
26
|
+
"bun.lock",
|
|
27
|
+
"canon/config/",
|
|
28
|
+
]
|
|
29
|
+
claude = [".claude/", "CLAUDE.md"]
|
|
30
|
+
docs = [
|
|
31
|
+
"README.md",
|
|
32
|
+
"docs/",
|
|
33
|
+
"canon/REQUIREMENTS.md",
|
|
34
|
+
"canon/DESIGN.md",
|
|
35
|
+
"canon/context/",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Paths deliberately left unlabelled, keyed by the reason.
|
|
39
|
+
[declined]
|
|
40
|
+
# Release automation opens its own pull request and applies its own label.
|
|
41
|
+
release-managed = ["CHANGELOG.md", ".release-please-manifest.json"]
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Pins the agent browser CLI the UI pass drives through, so every call in a run
|
|
3
|
-
# and every run on a machine reach one version. `open` is forced onto the
|
|
4
|
-
# bundled Chromium, since the CLI's default channel is a branded Chrome most
|
|
5
|
-
# machines lack, and it refuses rather than downloading when that browser is
|
|
6
|
-
# absent, since a dispatched reviewer that installs mid-pass spends its budget
|
|
7
|
-
# on the download.
|
|
8
|
-
set -euo pipefail
|
|
9
|
-
|
|
10
|
-
PW_CLI="@playwright/cli@0.1.22"
|
|
11
|
-
# The Chromium revision that CLI version requires. It moves with the pin, and
|
|
12
|
-
# it is separate from the revision any other Playwright on the machine uses.
|
|
13
|
-
PW_CHROMIUM_REVISION="1247"
|
|
14
|
-
|
|
15
|
-
die() {
|
|
16
|
-
echo "pw.sh: $*" >&2
|
|
17
|
-
exit 1
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
browsers_root() {
|
|
21
|
-
if [ -n "${PLAYWRIGHT_BROWSERS_PATH:-}" ]; then
|
|
22
|
-
echo "$PLAYWRIGHT_BROWSERS_PATH"
|
|
23
|
-
elif [ "$(uname -s 2>/dev/null || echo unknown)" = Darwin ]; then
|
|
24
|
-
echo "$HOME/Library/Caches/ms-playwright"
|
|
25
|
-
else
|
|
26
|
-
echo "$HOME/.cache/ms-playwright"
|
|
27
|
-
fi
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
command -v bunx >/dev/null 2>&1 || die "bunx is not on PATH, so the pinned CLI cannot run"
|
|
31
|
-
|
|
32
|
-
# Global flags may lead the verb, as in `-s=<name> --raw open <url>`.
|
|
33
|
-
args=("$@")
|
|
34
|
-
verb_at=0
|
|
35
|
-
for i in "${!args[@]}"; do
|
|
36
|
-
case "${args[$i]}" in
|
|
37
|
-
-*) ;;
|
|
38
|
-
*)
|
|
39
|
-
verb_at=$i
|
|
40
|
-
break
|
|
41
|
-
;;
|
|
42
|
-
esac
|
|
43
|
-
done
|
|
44
|
-
|
|
45
|
-
if [ "${args[$verb_at]:-}" = open ]; then
|
|
46
|
-
if [ ! -d "$(browsers_root)/chromium-$PW_CHROMIUM_REVISION" ]; then
|
|
47
|
-
die "Chromium revision $PW_CHROMIUM_REVISION is not installed. Run once, outside the pass: bunx -y $PW_CLI install-browser chromium"
|
|
48
|
-
fi
|
|
49
|
-
has_browser=false
|
|
50
|
-
for arg in "${args[@]}"; do
|
|
51
|
-
case "$arg" in
|
|
52
|
-
--browser=* | --browser) has_browser=true ;;
|
|
53
|
-
esac
|
|
54
|
-
done
|
|
55
|
-
if [ "$has_browser" = false ]; then
|
|
56
|
-
args=("${args[@]:0:$((verb_at + 1))}" --browser=chromium "${args[@]:$((verb_at + 1))}")
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
exec bunx -y "$PW_CLI" "${args[@]}"
|