rvlib-shipkit 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/package.json +2 -1
  3. package/src/agent-time/mask.ts +43 -0
  4. package/src/agent-time/render.ts +187 -0
  5. package/src/agent-time/report.ts +335 -0
  6. package/src/agent-time/shape.ts +271 -0
  7. package/src/agent-time/signals.ts +84 -0
  8. package/src/agent-time/store.ts +379 -0
  9. package/src/agent-time/timeline.ts +218 -0
  10. package/src/agent-time/turns.ts +135 -0
  11. package/src/assets/kits/identity-kit.ts +7 -3
  12. package/src/assets/run-assets.ts +18 -4
  13. package/src/checks/SK004-gitignore-policy.check.ts +11 -6
  14. package/src/checks/SK013-one-way-in-policy.check.ts +13 -8
  15. package/src/cli/cmd_Calls.ts +53 -0
  16. package/src/cli/cmd_Commands.ts +163 -43
  17. package/src/cli/cmd_Review.ts +948 -0
  18. package/src/cli/cmd_Shot.ts +63 -0
  19. package/src/cli/cmd_Start.ts +8 -4
  20. package/src/cli/cmd_Time.ts +112 -0
  21. package/src/cli/cmd_Trailer.ts +361 -0
  22. package/src/cli/kit/builtins.ts +40 -0
  23. package/src/cli/kit/calls.ts +177 -0
  24. package/src/cli/kit/families.ts +67 -0
  25. package/src/cli/kit/index.ts +8 -0
  26. package/src/cli/kit/pages-check.ts +36 -0
  27. package/src/cli/kit/project-cli.ts +73 -0
  28. package/src/cli/kit/verb.ts +167 -0
  29. package/src/cli.ts +15 -2
  30. package/src/collections/collection-types.ts +6 -1
  31. package/src/collections/providers.ts +3 -0
  32. package/src/collections/review.ts +70 -0
  33. package/src/core/layout.ts +12 -2
  34. package/src/core/migrate-layout.ts +31 -8
  35. package/src/core/one-way-in-policy.ts +12 -4
  36. package/src/reflect/prompt-corpus.ts +15 -114
  37. package/src/reflect/transcript.ts +254 -0
  38. package/src/review/anim.ts +228 -0
  39. package/src/review/auto.ts +141 -0
  40. package/src/review/format.ts +150 -0
  41. package/src/review/http.ts +23 -0
  42. package/src/review/marks.ts +43 -0
  43. package/src/review/media.ts +45 -0
  44. package/src/review/model.ts +854 -0
  45. package/src/review/project.ts +110 -0
  46. package/src/review/role.ts +48 -0
  47. package/src/review/routes.ts +228 -0
  48. package/src/review/store.ts +1002 -0
  49. package/src/review/threads.ts +237 -0
  50. package/src/review/trailer-desk.ts +385 -0
  51. package/src/review/trailer-model.ts +183 -0
  52. package/src/review/trailer-routes.ts +136 -0
  53. package/src/review/unit.ts +165 -0
  54. package/src/review/watchers.ts +54 -0
  55. package/src/scaffold/cli-skeleton.ts +149 -0
  56. package/src/scaffold/files/agent-index.md +2 -2
  57. package/src/scaffold/files/tsconfig.json +3 -0
  58. package/src/scaffold/files/verify-skill.md +1 -1
  59. package/src/scaffold/project-scaffold.ts +90 -12
  60. package/src/serve/embed.ts +35 -0
  61. package/src/serve/hub-app/activity-view.tsx +1 -1
  62. package/src/serve/hub-app/activity.css +0 -4
  63. package/src/serve/hub-app/dock-view.tsx +5 -4
  64. package/src/serve/hub-app/hash-route.ts +28 -1
  65. package/src/serve/hub-app/header-bar.tsx +3 -1
  66. package/src/serve/hub-app/home-st.ts +10 -2
  67. package/src/serve/hub-app/home-strip.tsx +3 -2
  68. package/src/serve/hub-app/home.html +1 -0
  69. package/src/serve/hub-app/hub-app.tsx +80 -11
  70. package/src/serve/hub-app/hub.css +45 -13
  71. package/src/serve/hub-app/icons.tsx +9 -0
  72. package/src/serve/hub-app/index.html +1 -0
  73. package/src/serve/hub-app/insights-view.tsx +4 -3
  74. package/src/serve/hub-app/keys.ts +14 -10
  75. package/src/serve/hub-app/layout-st.ts +63 -2
  76. package/src/serve/hub-app/planned-view.tsx +3 -2
  77. package/src/serve/hub-app/planned.css +0 -1
  78. package/src/serve/hub-app/project-list.css +22 -4
  79. package/src/serve/hub-app/project-list.tsx +41 -8
  80. package/src/serve/hub-app/projects-svc.ts +4 -0
  81. package/src/serve/hub-app/repo-sections.ts +15 -3
  82. package/src/serve/hub-app/repo-st.ts +45 -3
  83. package/src/serve/hub-app/review/clip-st.ts +208 -0
  84. package/src/serve/hub-app/review/kind-view.tsx +171 -0
  85. package/src/serve/hub-app/review/kit.tsx +113 -0
  86. package/src/serve/hub-app/review/review-answer.tsx +239 -0
  87. package/src/serve/hub-app/review/review-board.tsx +46 -0
  88. package/src/serve/hub-app/review/review-list.tsx +156 -0
  89. package/src/serve/hub-app/review/review-page.tsx +101 -0
  90. package/src/serve/hub-app/review/review-st.ts +779 -0
  91. package/src/serve/hub-app/review/review-views.tsx +503 -0
  92. package/src/serve/hub-app/review/trailer-page.tsx +416 -0
  93. package/src/serve/hub-app/review/trailer-st.ts +602 -0
  94. package/src/serve/hub-app/review/trailer-timeline.tsx +207 -0
  95. package/src/serve/hub-app/review.css +329 -0
  96. package/src/serve/hub-app/theme-st.ts +56 -0
  97. package/src/serve/hub-app/tree-build.ts +4 -0
  98. package/src/serve/hub-app/tree-row.tsx +1 -1
  99. package/src/serve/hub-app/tree-svc.ts +4 -0
  100. package/src/serve/hub-runtime.ts +5 -3
  101. package/src/serve/same-origin.ts +5 -1
  102. package/src/serve/serve-hub.ts +8 -0
  103. package/src/shipkit-files.ts +12 -4
  104. package/src/shot/client.ts +67 -0
  105. package/src/shot/clip-encoder.ts +146 -0
  106. package/src/shot/dom-clip.ts +124 -0
  107. package/src/shot/serve-shot.ts +54 -0
  108. package/src/shot/shooter.ts +343 -0
  109. package/src/shot/shot-ask.ts +189 -0
  110. package/src/shot/shot.shipkit.ts +14 -0
  111. package/src/smoke/run-smoke.ts +2 -2
  112. package/src/start-repo.ts +29 -14
  113. package/src/suite/judge-suites.ts +56 -10
  114. package/src/suite/select.ts +2 -5
  115. package/src/suite/suite-cli.ts +3 -2
  116. package/src/trailer/adapter.ts +78 -0
  117. package/src/trailer/build.ts +603 -0
  118. package/src/trailer/ffmpeg.ts +72 -0
  119. package/src/trailer/footage.ts +173 -0
  120. package/src/trailer/grammar.ts +1290 -0
  121. package/src/trailer/graph.ts +508 -0
  122. package/src/trailer/holes.ts +72 -0
  123. package/src/trailer/load-unit.ts +31 -0
  124. package/src/trailer/look.ts +124 -0
  125. package/src/trailer/passage.ts +129 -0
  126. package/src/trailer/plan.ts +192 -0
  127. package/src/trailer/prune.ts +193 -0
  128. package/src/trailer/score/compose.ts +240 -0
  129. package/src/trailer/score/dsp.ts +272 -0
  130. package/src/trailer/score/palette.ts +188 -0
  131. package/src/trailer/score/pattern.ts +91 -0
  132. package/src/trailer/score/score.ts +214 -0
  133. package/src/trailer/score/sound.ts +112 -0
  134. package/src/trailer/score/synth.ts +361 -0
  135. package/src/trailer/states.ts +76 -0
  136. package/src/trailer/stitch.ts +233 -0
  137. package/src/trailer/titles.ts +379 -0
  138. package/src/trailer/url.ts +108 -0
  139. package/src/trailer/vertical-draw.ts +286 -0
  140. package/src/trailer/vertical.ts +148 -0
  141. package/src/trailer/vocab.ts +59 -0
  142. package/src/trailer/words.ts +254 -0
@@ -0,0 +1,254 @@
1
+ // a film row's declared words (wiki build/trailer.md, A row's words): the row says which words it takes and their
2
+ // types, the core reads every word of a film line at its own line against that, refuses an unknown one with the row's
3
+ // list, and hands the row its values made plain (argv stays the transport). Pure: times arrive as moments the grammar
4
+ // resolves once the shots have landed
5
+
6
+ export type WordType = 'number' | 'int' | 'enum' | 'point' | 'time' | 'cues' | 'file' | 'text' | 'word' | 'flag'
7
+ export const WORD_TYPES: readonly WordType[] = ['number', 'int', 'enum', 'point', 'time', 'cues', 'file', 'text', 'word', 'flag']
8
+
9
+ /** one word a row takes: its type and bounds; `arg: n` makes it the n-th positional */
10
+ export interface WordSpec {
11
+ type: WordType
12
+ arg?: number
13
+ optional?: boolean
14
+ min?: number
15
+ max?: number
16
+ values?: string[]
17
+ /** a point's numbers, 2 by default */
18
+ count?: number
19
+ /** a word's shape, a regular expression */
20
+ pattern?: string
21
+ /** what it means, for the error that lists the row's words */
22
+ doc?: string
23
+ }
24
+
25
+ export type Words = Record<string, WordSpec>
26
+
27
+ /** a word of a film line, with the line it sits on (an option line under the film line keeps its own) */
28
+ export interface LineWord {
29
+ text: string
30
+ quoted: boolean
31
+ line: number
32
+ }
33
+
34
+ /** a time: seconds, or a section or mark of the cut and a time from it */
35
+ export type Moment = number | { mark: string; offset: number }
36
+
37
+ export interface Typed {
38
+ /** the row's path, then its words in the order written, each value plain; a time's slot is filled by `times` */
39
+ argv: string[]
40
+ /** argv slots made of moments: a time (`name` null) or cues (`name@seconds`, comma joined) */
41
+ times: { index: number; parts: { name: string | null; at: Moment; line: number }[] }[]
42
+ /** argv slots that are files, as written: the build finds them and keys their content */
43
+ files: number[]
44
+ errors: { line: number; message: string }[]
45
+ }
46
+
47
+ /** the row's words checked as a declaration: every type known, its bounds sane; null when it is one */
48
+ export function wordsProblem(words: unknown): string | null {
49
+ if (typeof words !== 'object' || words === null) return 'words is a record of word → { type }'
50
+ for (const [k, v] of Object.entries(words as Record<string, unknown>)) {
51
+ const w = v as Partial<WordSpec> | null
52
+ if (!/^[a-z][a-z0-9-]*$/.test(k)) return `word ${k}: a word is lowercase letters, digits and -`
53
+ if (!w || !WORD_TYPES.includes(w.type as WordType)) return `word ${k}: type is one of ${WORD_TYPES.join(', ')}`
54
+ if (w.type === 'enum' && (!Array.isArray(w.values) || w.values.length === 0)) return `word ${k}: an enum names its values`
55
+ if (w.pattern !== undefined) {
56
+ try {
57
+ new RegExp(w.pattern)
58
+ } catch {
59
+ return `word ${k}: pattern ${w.pattern} is no regular expression`
60
+ }
61
+ }
62
+ }
63
+ return null
64
+ }
65
+
66
+ const n4 = (x: number): string => String(+x.toFixed(4))
67
+
68
+ /** one value read by its type: its plain text, a moment, or why it does not fit */
69
+ function valueOf(key: string, spec: WordSpec, w: LineWord, time: (text: string) => Moment | null): { text: string } | { at: Moment } | { error: string } {
70
+ const t = w.text
71
+ const what = `${key} wants ${describe(spec)}, got ${w.quoted ? `"${t}"` : t}`
72
+ switch (spec.type) {
73
+ case 'number':
74
+ case 'int': {
75
+ const v = w.quoted ? Number.NaN : Number(t)
76
+ if (!Number.isFinite(v) || (spec.type === 'int' && !Number.isInteger(v)) || (spec.min !== undefined && v < spec.min) || (spec.max !== undefined && v > spec.max)) return { error: what }
77
+ return { text: t }
78
+ }
79
+ case 'enum':
80
+ return !w.quoted && spec.values!.includes(t) ? { text: t } : { error: what }
81
+ case 'point': {
82
+ const parts = w.quoted ? [] : t.split(',')
83
+ const ok = parts.length === (spec.count ?? 2) && parts.every((p) => p !== '' && Number.isFinite(Number(p)))
84
+ return ok ? { text: t } : { error: what }
85
+ }
86
+ case 'time': {
87
+ const at = w.quoted ? null : time(t)
88
+ return at === null ? { error: what } : { at }
89
+ }
90
+ case 'file':
91
+ case 'word':
92
+ if (w.quoted || t === '' || (spec.pattern !== undefined && !new RegExp(spec.pattern).test(t))) return { error: what }
93
+ return { text: t }
94
+ case 'text':
95
+ return t.trim() === '' ? { error: `${key}: the text is empty` } : { text: t }
96
+ default:
97
+ return { error: what }
98
+ }
99
+ }
100
+
101
+ function describe(s: WordSpec): string {
102
+ switch (s.type) {
103
+ case 'number':
104
+ case 'int':
105
+ return `${s.type === 'int' ? 'a whole number' : 'a number'}${s.min !== undefined || s.max !== undefined ? ` from ${s.min ?? '-∞'} to ${s.max ?? '∞'}` : ''}`
106
+ case 'enum':
107
+ return s.values!.join('|')
108
+ case 'point':
109
+ return `${s.count ?? 2} numbers joined by commas`
110
+ case 'time':
111
+ return 'a time (1.5s, 400ms, 0:04, 2beats, a mark, mark+1beat)'
112
+ case 'cues':
113
+ return 'cues, name@<time> each'
114
+ case 'file':
115
+ return 'a file'
116
+ case 'word':
117
+ return `a word${s.pattern ? ` like ${s.pattern}` : ''}`
118
+ case 'text':
119
+ return 'a text'
120
+ case 'flag':
121
+ return 'no value'
122
+ }
123
+ }
124
+
125
+ /** the row's words, for an error: positionals in order, then keys */
126
+ export const wordList = (words: Words): string =>
127
+ [
128
+ ...Object.entries(words)
129
+ .filter(([, s]) => s.arg !== undefined)
130
+ .sort((a, b) => a[1].arg! - b[1].arg!)
131
+ .map(([k, s]) => `<${k}>${s.optional ? '?' : ''}`),
132
+ ...Object.entries(words)
133
+ .filter(([, s]) => s.arg === undefined)
134
+ .map(([k]) => k),
135
+ ].join(' ')
136
+
137
+ interface Entry {
138
+ key: string
139
+ /** a positional's words carry no key on the line */
140
+ positional: boolean
141
+ values: LineWord[]
142
+ line: number
143
+ }
144
+
145
+ /** the entries of a run of words: a word that names a key opens it (its value words follow), any other word fills the
146
+ * next positional that takes it */
147
+ function entriesOf(path: string, words: Words, ws: LineWord[], time: (text: string) => Moment | null, errors: Typed['errors']): Entry[] {
148
+ const args = Object.entries(words)
149
+ .filter(([, s]) => s.arg !== undefined)
150
+ .sort((a, b) => a[1].arg! - b[1].arg!)
151
+ let next = 0
152
+ const out: Entry[] = []
153
+ for (let i = 0; i < ws.length; i++) {
154
+ const w = ws[i]!
155
+ const spec = !w.quoted && words[w.text] && words[w.text]!.arg === undefined ? words[w.text]! : null
156
+ if (spec) {
157
+ if (spec.type === 'flag') out.push({ key: w.text, positional: false, values: [], line: w.line })
158
+ else if (spec.type === 'cues') {
159
+ const values: LineWord[] = []
160
+ while (i + 1 < ws.length && !ws[i + 1]!.quoted && /^[^@\s]+@\S+$/.test(ws[i + 1]!.text) && !words[ws[i + 1]!.text]) {
161
+ const c = ws[++i]!
162
+ for (const text of c.text.split(',')) values.push({ ...c, text })
163
+ }
164
+ if (values.length === 0) errors.push({ line: w.line, message: `film ${path} ${w.text} wants cues, name@<time> each` })
165
+ out.push({ key: w.text, positional: false, values, line: w.line })
166
+ } else {
167
+ const v = ws[i + 1]
168
+ if (!v || v.line !== w.line) errors.push({ line: w.line, message: `film ${path} ${w.text} wants ${describe(spec)}` })
169
+ else {
170
+ out.push({ key: w.text, positional: false, values: [v], line: w.line })
171
+ i++
172
+ }
173
+ }
174
+ continue
175
+ }
176
+ // the next positional that takes this word; an optional one that does not is passed over
177
+ let placed = false
178
+ while (next < args.length) {
179
+ const [k, s] = args[next++]!
180
+ if (!('error' in valueOf(k, s, w, time))) {
181
+ out.push({ key: k, positional: true, values: [w], line: w.line })
182
+ placed = true
183
+ break
184
+ }
185
+ if (!s.optional) {
186
+ errors.push({ line: w.line, message: `film ${path}: ${w.quoted ? `"${w.text}"` : w.text} fits no word here, <${k}> wants ${describe(s)} (${path} takes ${wordList(words)})` })
187
+ placed = true
188
+ break
189
+ }
190
+ }
191
+ if (!placed) {
192
+ errors.push({ line: w.line, message: `film ${path}: unknown word ${w.quoted ? `"${w.text}"` : w.text} (${path} takes ${wordList(words)})` })
193
+ // its value goes with it: one error a word
194
+ const v = ws[i + 1]
195
+ if (v && v.line === w.line && !(!v.quoted && words[v.text])) i++
196
+ }
197
+ }
198
+ return out
199
+ }
200
+
201
+ /**
202
+ * a film line of a row that declares its words: `ws` its words after the row's path, `defaults` its section's (a key
203
+ * the line gives itself wins). The plain argv, the slots that wait on a moment, the file slots, every error at its line
204
+ */
205
+ export function typedFilm(path: string[], words: Words, ws: LineWord[], defaults: LineWord[], time: (text: string) => Moment | null): Typed {
206
+ const errors: Typed['errors'] = []
207
+ const own = entriesOf(path.join(' '), words, ws, time, errors)
208
+ const given = new Set(own.map((e) => e.key))
209
+ const fromDefaults = defaults.length > 0 ? entriesOf(path.join(' '), words, defaults, time, errors).filter((e) => !given.has(e.key)) : []
210
+ const all = [...fromDefaults, ...own]
211
+ const seen = new Set<string>()
212
+ const argv = [...path]
213
+ const times: Typed['times'] = []
214
+ const files: number[] = []
215
+ for (const e of all) {
216
+ if (seen.has(e.key)) {
217
+ errors.push({ line: e.line, message: `film ${path.join(' ')}: ${e.key} given twice` })
218
+ continue
219
+ }
220
+ seen.add(e.key)
221
+ const spec = words[e.key]!
222
+ if (!e.positional) argv.push(e.key)
223
+ if (spec.type === 'flag') continue
224
+ if (spec.type === 'cues') {
225
+ const parts: Typed['times'][number]['parts'] = []
226
+ for (const v of e.values) {
227
+ const at = v.text.indexOf('@')
228
+ const name = v.text.slice(0, at)
229
+ const m = time(v.text.slice(at + 1))
230
+ if (m === null) errors.push({ line: v.line, message: `film ${path.join(' ')} ${e.key}: ${v.text}, the time after @ is no time (1.5s, 2beats, a mark, mark+1beat)` })
231
+ else parts.push({ name, at: m, line: v.line })
232
+ }
233
+ times.push({ index: argv.length, parts })
234
+ argv.push('')
235
+ continue
236
+ }
237
+ const got = valueOf(e.key, spec, e.values[0]!, time)
238
+ if ('error' in got) {
239
+ errors.push({ line: e.values[0]!.line, message: `film ${path.join(' ')}: ${got.error}` })
240
+ argv.push(e.values[0]!.text)
241
+ } else if ('at' in got) {
242
+ times.push({ index: argv.length, parts: [{ name: null, at: got.at, line: e.values[0]!.line }] })
243
+ argv.push('')
244
+ } else {
245
+ if (spec.type === 'file') files.push(argv.length)
246
+ argv.push(got.text)
247
+ }
248
+ }
249
+ for (const [k, s] of Object.entries(words)) if (s.arg !== undefined && !s.optional && !seen.has(k)) errors.push({ line: ws[0]?.line ?? defaults[0]?.line ?? 0, message: `film ${path.join(' ')}: <${k}> is missing (${path.join(' ')} takes ${wordList(words)})` })
250
+ return { argv, times, files, errors }
251
+ }
252
+
253
+ /** a slot's text once its moments are seconds: a time, or cues joined by commas */
254
+ export const slotText = (parts: readonly { name: string | null; seconds: number }[]): string => parts.map((p) => (p.name === null ? n4(p.seconds) : `${p.name}@${n4(p.seconds)}`)).join(',')