akari-video 1.2.0-beta.1 → 1.2.0-beta.3

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 (79) hide show
  1. package/package.json +1 -1
  2. package/src/clean-manifest.mjs +1 -0
  3. package/vendor/.akari-capability-sources.json +2 -0
  4. package/vendor/docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md +12 -2
  5. package/vendor/docs/contract-2026-08-30-motion-and-keyframes-v0.md +1 -1
  6. package/vendor/docs/contract-2026-09-02-audio-clip-fx-v1.md +20 -2
  7. package/vendor/docs/contract-2026-10-07-material-ranges-v0.md +13 -0
  8. package/vendor/docs/contract-2026-10-07-tasks-v0.md +74 -0
  9. package/vendor/packages/akari-launcher/package.json +1 -1
  10. package/vendor/packages/edit-lint/src/engine-capabilities.json +1 -0
  11. package/vendor/packages/edit-lint/src/lint/edit-v2.mjs +17 -0
  12. package/vendor/packages/edit-store/lib/canonical.js +12 -2
  13. package/vendor/packages/edit-store/lib/edit-v2-item-write.d.ts +5 -0
  14. package/vendor/packages/edit-store/lib/edit-v2-item-write.js +35 -1
  15. package/vendor/packages/edit-store/lib/edit-v2.d.ts +3 -0
  16. package/vendor/packages/edit-store/lib/edit-v2.js +13 -1
  17. package/vendor/packages/edit-store/lib/generated/edit-v2-keys.d.ts +2 -2
  18. package/vendor/packages/edit-store/lib/generated/edit-v2-keys.js +2 -1
  19. package/vendor/packages/edit-store/lib/internal-model.d.ts +3 -0
  20. package/vendor/packages/edit-store/lib/internal-model.js +1 -0
  21. package/vendor/packages/edit-store/lib/tasks-store.d.ts +36 -0
  22. package/vendor/packages/edit-store/lib/tasks-store.js +276 -0
  23. package/vendor/packages/edit-store/lib/webview-kernel.js +12 -1
  24. package/vendor/packages/media-bin/src/preview-audio-sidecar.mjs +5 -2
  25. package/vendor/packages/media-bin/test/audio-clip-fx.test.mjs +46 -10
  26. package/vendor/packages/overlay-runtime/src/element-selection.d.ts +6 -0
  27. package/vendor/packages/overlay-runtime/src/element-selection.mjs +61 -0
  28. package/vendor/packages/overlay-runtime/src/interaction.js +390 -13
  29. package/vendor/packages/overlay-runtime/src/parts.d.ts +6 -0
  30. package/vendor/packages/overlay-runtime/src/parts.mjs +75 -3
  31. package/vendor/packages/schemas/bin/validate-browser-engines.mjs +57 -0
  32. package/vendor/packages/schemas/bin/validate-tasks.mjs +100 -0
  33. package/vendor/packages/schemas/bin/validate-voice-dictionary.mjs +81 -0
  34. package/vendor/packages/schemas/browser-engines.schema.json +30 -0
  35. package/vendor/packages/schemas/edit.schema.json +10 -0
  36. package/vendor/packages/schemas/engine-capabilities.json +1 -0
  37. package/vendor/packages/schemas/examples/voice-dictionary-v0-invalid-jev-scope/voice-dictionary.json +3 -0
  38. package/vendor/packages/schemas/examples/voice-dictionary-v0-invalid-key-conflict/voice-dictionary.json +4 -0
  39. package/vendor/packages/schemas/examples/voice-dictionary-v0-invalid-version-1/voice-dictionary.json +1 -0
  40. package/vendor/packages/schemas/examples/voice-dictionary-v0-valid/voice-dictionary.json +4 -0
  41. package/vendor/packages/schemas/package.json +5 -2
  42. package/vendor/packages/schemas/tasks.schema.json +115 -0
  43. package/vendor/packages/schemas/test/element-overrides.test.mjs +17 -0
  44. package/vendor/packages/schemas/test/engine-capabilities.test.mjs +1 -1
  45. package/vendor/packages/schemas/test/fixtures/tasks/invalid/duplicate-id/expected.json +3 -0
  46. package/vendor/packages/schemas/test/fixtures/tasks/invalid/duplicate-id/tasks.json +17 -0
  47. package/vendor/packages/schemas/test/fixtures/tasks/invalid/invalid-id/expected.json +3 -0
  48. package/vendor/packages/schemas/test/fixtures/tasks/invalid/invalid-id/tasks.json +11 -0
  49. package/vendor/packages/schemas/test/fixtures/tasks/invalid/missing-source/expected.json +3 -0
  50. package/vendor/packages/schemas/test/fixtures/tasks/invalid/missing-source/tasks.json +10 -0
  51. package/vendor/packages/schemas/test/fixtures/tasks/invalid/missing-state/expected.json +3 -0
  52. package/vendor/packages/schemas/test/fixtures/tasks/invalid/missing-state/tasks.json +10 -0
  53. package/vendor/packages/schemas/test/fixtures/tasks/invalid/newer-version/expected.json +3 -0
  54. package/vendor/packages/schemas/test/fixtures/tasks/invalid/newer-version/tasks.json +4 -0
  55. package/vendor/packages/schemas/test/fixtures/tasks/invalid/outbound-auto/expected.json +3 -0
  56. package/vendor/packages/schemas/test/fixtures/tasks/invalid/outbound-auto/tasks.json +13 -0
  57. package/vendor/packages/schemas/test/fixtures/tasks/invalid/tasks-not-array/expected.json +3 -0
  58. package/vendor/packages/schemas/test/fixtures/tasks/invalid/tasks-not-array/tasks.json +4 -0
  59. package/vendor/packages/schemas/test/fixtures/tasks/invalid/top-array/expected.json +3 -0
  60. package/vendor/packages/schemas/test/fixtures/tasks/invalid/top-array/tasks.json +1 -0
  61. package/vendor/packages/schemas/test/fixtures/tasks/valid/all-fields/expected.json +3 -0
  62. package/vendor/packages/schemas/test/fixtures/tasks/valid/all-fields/tasks.json +92 -0
  63. package/vendor/packages/schemas/test/fixtures/tasks/valid/annotation-overlay/expected.json +3 -0
  64. package/vendor/packages/schemas/test/fixtures/tasks/valid/annotation-overlay/tasks.json +16 -0
  65. package/vendor/packages/schemas/test/fixtures/tasks/valid/e5/expected.json +3 -0
  66. package/vendor/packages/schemas/test/fixtures/tasks/valid/e5/tasks.json +43 -0
  67. package/vendor/packages/schemas/test/fixtures/tasks/valid/export/expected.json +3 -0
  68. package/vendor/packages/schemas/test/fixtures/tasks/valid/export/tasks.json +15 -0
  69. package/vendor/packages/schemas/test/fixtures/tasks/valid/lint/expected.json +3 -0
  70. package/vendor/packages/schemas/test/fixtures/tasks/valid/lint/tasks.json +15 -0
  71. package/vendor/packages/schemas/test/fixtures/tasks/valid/minimal/expected.json +3 -0
  72. package/vendor/packages/schemas/test/fixtures/tasks/valid/minimal/tasks.json +11 -0
  73. package/vendor/packages/schemas/test/fixtures/tasks/valid/unknown-fields/expected.json +3 -0
  74. package/vendor/packages/schemas/test/fixtures/tasks/valid/unknown-fields/tasks.json +18 -0
  75. package/vendor/packages/schemas/test/tasks-fixtures.test.mjs +42 -0
  76. package/vendor/packages/schemas/test/validate-browser-engines.test.mjs +18 -0
  77. package/vendor/packages/schemas/test/validate-voice-dictionary.test.mjs +37 -0
  78. package/vendor/packages/schemas/voice-dictionary.schema.json +39 -0
  79. package/vendor/packages/word-book/src/index.mjs +17 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "1.2.0-beta.1",
3
+ "version": "1.2.0-beta.3",
4
4
  "description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -200,6 +200,7 @@ async function classifyAkari(akari, context) {
200
200
  for (const candidate of child.children) addDisposable(candidate, rule('.akari/render-tmp/*'), context);
201
201
  } else if (child.name === 'cache' && child.kind === 'directory') {
202
202
  for (const candidate of child.children) addDisposable(candidate, rule('.akari/cache/**'), context);
203
+ } else if (child.name === 'tasks.json.lock' && child.kind === 'directory') { addDisposable(child, manifest('.akari/tasks.json.lock', 'disposable', '書き込み中の一時ロック', 'AKARI Video'), context);
203
204
  } else if (child.name === 'diffs' && child.kind === 'directory') {
204
205
  for (const candidate of child.children) addDisposable(candidate, rule('.akari/diffs/*'), context);
205
206
  } else if (child.name === 'work' && child.kind === 'directory') {
@@ -83,6 +83,8 @@
83
83
  "docs/contract-2026-10-02-textstyle-v1-rich.md",
84
84
  "docs/contract-2026-10-04-asset-tier-v1.md",
85
85
  "docs/contract-2026-10-06-timeline-ripple-v0.md",
86
+ "docs/contract-2026-10-07-material-ranges-v0.md",
87
+ "docs/contract-2026-10-07-tasks-v0.md",
86
88
  "packages/akari-ear/package.json",
87
89
  "packages/akari-ear/README.md",
88
90
  "packages/akari-launcher/package.json",
@@ -31,7 +31,7 @@ updated: 2026-08-30
31
31
 
32
32
  ## 1. データ模型(`version: 2` のまま追加のみ)
33
33
 
34
- **新しいサブスキーマはここに列挙した以外増やさない。** 部品も字幕行もグループも「普通のアイテム」で、共通フィールド(`id` / `at` / `duration` / `transform` / `opacity` / `blend` / `crop` / `perspective` / `keyframes`)をそのまま使う。
34
+ **新しいサブスキーマはここに列挙した以外増やさない。** HTML source の `elements` は要素ごとの inline style 上書きである。部品も字幕行もグループも「普通のアイテム」で、共通フィールド(`id` / `at` / `duration` / `transform` / `opacity` / `blend` / `crop` / `perspective` / `keyframes`)をそのまま使う。
35
35
 
36
36
  ### 1.1 再帰 — アイテムは `items[]` を持てる
37
37
 
@@ -81,7 +81,7 @@ updated: 2026-08-30
81
81
  - **見える子 = 袋の名札から写す(projection)**。edit.json には書かない。上の例で `s01.html` に部品 A / B / C があれば、A は写し(触っていない・既定の時間 = 袋と同じ)、B は明示(触ったので `items[]` に居る)、C は `exclude`(袋の中では表示しない = 別の場所へ「出した」か、消した)
82
82
  - **触った子だけ明示アイテムになる**。明示アイテムは袋の子として `items[]` に置くか、木の別の場所に置く(= 分離 §3.1)。どちらでも袋側は `source.exclude` に id を持つ
83
83
  - **袋の中では並びを変えない・ばらさない**(HTML の DOM 順 / captions.json の行順が正)。変えたい部品は出す
84
- - 名札の無い断片は「袋ごと 1 アイテム」(`part` 無し・`items` 無し)。既存の overlays はこれ(**回帰なし**)
84
+ - 名札の無い断片は「袋ごと 1 アイテム」(`part` 無し・`items` 無し)。このアイテムも `source.elements` による要素の上書きを持てる。既存の overlays はこれ(**回帰なし**)
85
85
  - 名札の読み取り(HTML の走査)は**描画・プレビュー側の仕事**(§4)。edit.json の読み込み層は袋を「子を持ち得るアイテム」として扱うだけ
86
86
 
87
87
  ### 1.4 部品アイテム `source: { kind: "html", path, part }`
@@ -90,6 +90,14 @@ updated: 2026-08-30
90
90
  - `vars` / `params`(既存のツマミ経路)は部品アイテムでも使える。`style` / `text` は「設計済みのツマミが無い所を直す」ための逃げ道で、これで HTML を書き換えずに見た目の自由度を持つ
91
91
  - 部品アイテムの `at` / `duration` / `transform` / `opacity` / `keyframes` は共通フィールド。**部品専用のサブスキーマは無い**
92
92
 
93
+ ### 1.4a 要素の上書き `source.elements`
94
+
95
+ - HTML source は任意の `elements: { "<番地>": { "style": { "<css-prop>": "<value>" } } }` を持てる。CSS 値は検証しない。番地の値に許すキーは `style` だけ。空の `elements` と空の `style` は保存時に畳んで消す。
96
+ - 番地は `#id[0]` または `.class[2]` の形。書式は `^[#.][^\s\[\]]+\[(0|[1-9]\d*)\]$`。id 属性の完全一致、または class 属性の空白区切りトークンの一致を、断片ソースの文書順に 0 から数える。CSS セレクタとして解釈しない。コメントと script/style の中身、`<script>` / `<style>` 要素そのもの(番地で指せない)、マウント後に増える要素は数えない。
97
+ - 開始タグを親子とも文書順に数え、重複 id も出現番号で区別する。属性名は大小文字を区別せず、class の区切りはタブ・改行を含む空白とする(`tokenizeHtml` と同じ)。
98
+ - 解決できない番地は描画で読み飛ばし、lint の `v2.element-ref` warning にする。素材 HTML は編集しない。
99
+ - 袋の `elements` は各子へ継承する。同じ番地の同じ CSS プロパティは子の値が勝つ。部品ルートに `source.style` もあるときは `elements` の後に `style` を追記し、部品の値が勝つ。
100
+
93
101
  ### 1.5 字幕 = 袋グループ(専用トラックの廃止)
94
102
 
95
103
  ```jsonc
@@ -198,6 +206,7 @@ updated: 2026-08-30
198
206
  ## 4. 描画 — クローンマスク
199
207
 
200
208
  - **部品アイテム 1 つにつき断片全体を 1 回マウント**し、当該 `part` 以外の名札付き要素を `visibility: hidden` にする(`display: none` ではない — レイアウトと CSS の継承を壊さない)。各マウントが自分の時計(アイテム相対時間)・変形・z を持つ
209
+ - `source.elements` は要素を複製せず、同じマウント内の開始タグへ inline style を追記する。部品の複製がある場合は各複製へマスクより先に同じ上書きを適用する。`elements` が無い既存アイテムのレコードは変えない。
201
210
  - 袋グループの写しの子(触っていない部品)は、時間・位置が袋と揃っている限り**1 マウントにまとめる**(エンジン側の最適化。データは不変)。`exclude` の部品は `visibility: hidden`
202
211
  - `source.style` は部品ルート要素の inline style として適用、`source.text` は部品ルートの textContent を差し替える(子要素を持つ部品に `text` を指定したら lint warning・描画は最初のテキストノードだけ差し替え)
203
212
  - 部品内の CSS / GSAP アニメ(L3)は部品アイテムの時計で seek する(既存の HTML seek 規約のまま)
@@ -297,6 +306,7 @@ await p.save(); // 正規直列化 → lint ゲート
297
306
  | `v2.track-no-overlap` | error | 同一段の items が時間で重ならない(既存 → 明文化)|
298
307
  | `v2.group-bake-blocked` | error | `keyframes` / `motion` / `animator` を持つグループを ungroup しようとした(edit-store の操作時)|
299
308
  | `v2.part-ref` | warning | `source.part` / `source.exclude` の id が袋に存在するか(**文字列レベル**: HTML は `data-akari-part="…"` の grep、captions は行 id)|
309
+ | `v2.element-ref` | warning | `source.elements` の番地が `source.path` の HTML ソース内で解決できるか |
300
310
  | `v2.captions-content-deprecated` | warning | 旧形 `tracks[].content` を使っている |
301
311
  | `v2.caption-overlap` | warning | 分離した字幕行と袋の写しが同時刻に重なる |
302
312
  | `v2.keyframes-ref` | error | `keyframes: { path, count }` の袋が無い / `count` が実数と違う |
@@ -26,7 +26,7 @@ updated: 2026-09-06
26
26
  | **L2 アニメーター** | 文字 / 単語 / 行 / 文節ごとのずらし(範囲セレクタ + 量)| アイテムの `animator[]`(パラメータ数個)+ offset のキーフレームは袋 | offset のダイヤだけ(数個)。文字ごとの行は出ない | テロップ・字幕の演出 |
27
27
  | **L3 コード** | 断片内の CSS / GSAP アニメ | HTML の中(逃げ道)| 帯だけ(中は不透明)| AI / 上級者 |
28
28
 
29
- - **粒度 = 名札の粒度**。部品の中の一部だけ別に動かしたいなら、名札を細かく付けて(文節を部品にして)出す → L1 が効く。エディタ側に第 3 の粒度は作らない
29
+ - **時間と動きの粒度 = 名札の粒度**。部品の中の一部だけ別の時間で動かしたいなら、名札を細かく付けて(文節を部品にして)出す → L1 が効く。位置・回転・箱の上書きは名札なしの要素にも掛けられる(`source.elements`、2026-10-07 オーナー裁定)。それ以上の第 3 の粒度は作らない
30
30
  - **文字ごとのキーフレームはデータとして持たない**(100 文字 × 5 プロパティ × 3 点 = 1,500 個 / 文字を直すと全部ずれる / AI が扱えない)。文字単位の動きは L2 で表す
31
31
 
32
32
  ## 1. L0 プリセット `motion`
@@ -29,8 +29,8 @@ lint warning として無視する。legacy と v2 の投影は上表の値を
29
29
  lowcut は同一の 2 次 highpass を 2 段カスケードし、24 dB/oct の減衰特性で L1 ゲート
30
30
  (1 oct 下で 15 dB 以上の減衰)を満たすための裁定逸脱とする。
31
31
 
32
- denoise は `fft` なら `afftdn=nr=<12+strength*76>:nf=-30`、`nlm` なら
33
- `anlmdn=s=<0.00001+strength*0.0002>` とする。rubberband は `speed != 1` または
32
+ denoise は `fft` なら `afftdn=nr=<6+strength*18>:nf=-50:tn=1`、`nlm` なら
33
+ `anlmdn=s=<10^(-5+4*strength)>` とする。rubberband は `speed != 1` または
34
34
  `pitch_semitones != 0` のときだけ作る。全キーが既定なら入力もフィルタも追加せず、従来の
35
35
  filtergraph をバイト単位で維持する。
36
36
 
@@ -54,3 +54,21 @@ mtime・in/out・pad・recipe に加え、`atrim` を含む完全なフィルタ
54
54
  receipt の `provenance.audio.clip_fx` は `processed_items` と `filters` の件数を持つ。
55
55
  プレビューと書き出しで意図的に残す近似は、サイドカー FLAC の再圧縮とデコーダ/サンプル境界の
56
56
  差だけとする。
57
+
58
+ ## 2026-10-08 改訂 — ノイズ除去の対応式
59
+
60
+ - `fft`: 旧 `afftdn=nr=<12+strength*76>:nf=-30` から、ノイズ床を追従する
61
+ `afftdn=nr=<6+strength*18>:nf=-50:tn=1` へ変更した。旧式の固定ノイズ床は声の
62
+ 4〜8 kHz を 14〜18 dB、8〜16 kHz を 37〜50 dB 削ってこもらせていた。新式の声の
63
+ 4〜8 kHz の減少は実測 0.7〜1.7 dB。騒がしい録音の 8〜16 kHz は `nr=12` で最大
64
+ 5.9 dB、`nr=24` で最大 15.9 dB 減るため、`nr` の上限を 24 に抑える。
65
+ - `nlm`: 旧 `anlmdn=s=<0.00001+strength*0.0002>` から、強さを広く使える対数式
66
+ `anlmdn=s=<10^(-5+4*strength)>`(`s` は 0.00001〜0.1)へ変更した。実測では声の
67
+ 各帯域の変化は 0.4 dB 以内、弱い声の 8〜16 kHz の減少も 2.1 dB 以内。無音部の
68
+ ノイズは strength 0.5・SNR 38 dB で 21〜54 dB、strength 0.75・SNR 26 dB で
69
+ 10〜36 dB、strength 1・SNR 19 dB で 5〜20 dB 減少した。処理時間は `fft` の約
70
+ 10 倍だが、実時間の約 12 倍速で処理できた。
71
+ - 宣言済みの denoise を持つプロジェクトは、プレビューと書き出しの音が変わる。`fft` は
72
+ こもりが消え、効きは控えめになる。`nlm` は同じ strength で効きが強くなる。
73
+ サイドカーの cache key にはフィルタ文字列が入るため、自動で作り直される。
74
+ おすすめは `nlm`・`0.75`。
@@ -0,0 +1,13 @@
1
+ # Material ranges v0
2
+
3
+ The project stores non-destructive source selections in `.akari/material-ranges.json`:
4
+
5
+ ```json
6
+ { "version": 1, "ranges": { "assets/example.mp4": { "in": 2.5, "out": 8 } } }
7
+ ```
8
+
9
+ Keys are project-relative media paths. `in` and `out` are seconds in the original media; `out` is greater than `in`. Removing a selection deletes its key. A missing or malformed file means no selections. Updates preserve unrelated top-level keys and other material entries and replace the file by writing a temporary file and renaming it.
10
+
11
+ The selected list strip exposes two handles. Its minimum width is the larger of one second and 24 pixels expressed in source seconds, capped by the media duration. Dragging one handle keeps the other fixed. Double-clicking the strip or pressing × clears the selection. Changes are saved after 150 ms.
12
+
13
+ Material drag payloads and `akari.timeline.addMaterialAtPlayhead` requests may carry `in` and `out`. Drag placement uses the payload; playhead placement reads the saved range. Video and audio placement cap `out` at the media duration and use the full source when no valid range is present. `akari.materials.range.get` and `.set` expose the stored range to other views; changes emit `akari.materials.range.changed` with `{ relativePath, range }` on `window`. Range setters may include `source`; the event preserves it so a view can ignore echoes of its own changes.
@@ -0,0 +1,74 @@
1
+ # tasks.json v0 — 依頼タスクの運用情報
2
+
3
+ ## 位置づけ
4
+
5
+ `.akari/tasks.json` は、AI への依頼の運用情報を持つプロジェクトのサイドカーである。状態、優先度、送信先、戻すための履歴、および注釈以外の依頼を保存する。注釈の本文は `review.json` が正本である。
6
+
7
+ ## 置き場
8
+
9
+ プロジェクトの `.akari/tasks.json` に置く。ルート直下には置かない。これはプロジェクト構造契約の「契約サイドカー」に当たる。ファイルは履歴に残す。`.akari/cache/outbox/` の依頼文は再生成できる一時ファイルであり、送信後の記録は tasks.json に残す。
10
+
11
+ ## 形
12
+
13
+ top の必須フィールドは `version: 0` と `tasks` 配列である。各タスクの必須フィールドは `id`、`source`、`state`、`createdAt` のみ。`id` は `t-0001` のような連番で、既存の最大番号の次を使う。
14
+
15
+ | フィールド | 型・値 | 用途 |
16
+ |---|---|---|
17
+ | `source` | `annotation` / `lint` / `proposal` / `export` | タスクの出どころ |
18
+ | `state` | `unsent` / `sent` / `review` / `done` | 進行状態 |
19
+ | `via`, `priority`, `rank` | 文字列、`high` / `normal` / `low`、数値または null | 入口と並び |
20
+ | `outcome` | null または `edited` / `declined` / `failed` / `dismissed` | 結果 |
21
+ | `title`, `body`, `target` | 文字列、文字列、文字列または null | 表示とアンカーのない指示 |
22
+ | `anchor`, `ref` | object または null | 素材時刻と元データへの参照 |
23
+ | `attachments` | `{kind, path, external?}` の配列 | 添付ファイル |
24
+ | `gate`, `needsConfirm` | `auto-ok` / `ask`、真偽値 | 実行前の扱い |
25
+ | `undo` | `{reversible, historyId?, before?, after?, undoneAt?}` | 戻すための記録 |
26
+ | `batchId`, `sentAt`, `sentTo` | 文字列、文字列または null、`{agent, form, route}` | 送信記録 |
27
+ | `response` | null または `{summary, action, respondedAt}` | 応答。`action` は `edited` / `declined` |
28
+ | `createdBy`, `updatedAt`, `orphaned` | `human` / `app` / `ai`、文字列、真偽値 | 作成者と追跡 |
29
+ | `kind`, `targetDetail`, `evidence` | 文字列、object、object | 取り込み時の分類・対象・根拠 |
30
+ | `confidence`, `question`, `risk`, `route` | 文字列 | 確信度、質問、外部送出の危険、担当 |
31
+ | `dependsOn`, `origin`, `mergedFrom` | id 配列、object、id 配列 | 依存、出所、統合元 |
32
+
33
+ `anchor` は `{sourceT, sourceRange}`、`ref` は `{kind, id|key|path|rule}`。`targetDetail` は `refs[]`, `outputT`, `src`, `sourceT`, `cutIndex`, `region`(0〜1 の `[x,y,w,h]`)を持てる。`evidence` は `memo`, `speech`(`[開始秒, 終了秒]`), `quote`, `ink[]`、`origin` は `kind`, `memo`, `job`, `ref` を持てる。`risk: outbound` は `gate: ask` とし、自動実行しない。未知の任意列挙値は警告として扱う。
34
+
35
+ ```json
36
+ {
37
+ "version": 0,
38
+ "tasks": [
39
+ {
40
+ "id": "t-0001",
41
+ "source": "annotation",
42
+ "state": "unsent",
43
+ "createdAt": "2026-10-07T08:10:00.000Z",
44
+ "ref": { "kind": "annotation", "id": "a-0003" },
45
+ "priority": "normal",
46
+ "gate": "ask"
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ ## 状態と遷移
53
+
54
+ `unsent → sent → review → done` が基本形。リントの再検査で指摘が消えたときのみ `sent → done` とできる。人による再依頼・復元は `review → unsent`。人の無視は任意の状態から `done` とし、`outcome: dismissed` を記録する。AI は `done` に進めない。
55
+
56
+ ## 注釈との関係
57
+
58
+ `ref.kind: annotation` の本文、対象、時刻、ペンの線は `review.json` を正本とする。tasks.json は運用情報を重ねる。注釈の `open` は `unsent`、`addressed` は `review`、`resolved` は `done` に写す。`open` でも送信記録が `sent` なら `sent` を保つ。その他の食い違いは `review.json` を優先する。参照先の注釈が消えたタスクは `orphaned: true` として残す。`review.json` の形は変更しない。
59
+
60
+ ## 読み手の規約
61
+
62
+ ファイルが無ければ空の `version: 0` として読む。未知フィールドを保持し、未知の `source` / `state` は表示だけして変更しない。必須フィールドの欠落は警告に残し、タスクを捨てない。JSON が壊れたファイルは上書きしない。`version > 0` は「新しい形式です。スキル / アプリを更新してください」として拒否する。
63
+
64
+ ## 書き手の規約
65
+
66
+ `.akari/tasks.json.lock` ディレクトリを確保し、ロック内でファイル全文を読み直す。60 秒を超えた古いロックは回収する。`tasks.json.tmp` に 2 スペース整形・末尾改行で書き、rename で原子的に置き換える。注釈の参照があるタスクの本文は書き写さない。状態の変更ごとに個別の git commit は作らない。
67
+
68
+ ## 検証
69
+
70
+ `node packages/schemas/bin/validate-tasks.mjs <tasks.json>` を実行する。形式と意味のエラーは終了コード 1、警告のみなら 0、引数の誤りは 2。id の重複と `risk: outbound` + `gate: auto-ok` はエラーである。
71
+
72
+ ## 互換
73
+
74
+ 古いアプリやスキルは `review.json` をそのまま使える。tasks.json が無いプロジェクトでも注釈から仮想タスクを作る。「送った」注釈 id の既存保存は初回に取り込める。`.akari/tasks.json` は保持対象、ロックと `.akari/cache/outbox/` は一時データとして扱う。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "1.2.0-beta.1",
3
+ "version": "1.2.0-beta.3",
4
4
  "description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
5
5
  "type": "module",
6
6
  "files": [
@@ -103,6 +103,7 @@
103
103
  { "path": "tracks[].items[].source.part", "applies_to": ["overlays"], "gpu": "consumed", "osr": "consumed", "evidence": "packages/overlay-runtime/src/parts.mjs applyPartMask/expandBagOverlays" },
104
104
  { "path": "tracks[].items[].source.style", "applies_to": ["overlays"], "gpu": "consumed", "osr": "consumed", "evidence": "packages/overlay-runtime/src/parts.mjs applyPartMask/serializeInlineStyle" },
105
105
  { "path": "tracks[].items[].source.text", "applies_to": ["overlays"], "gpu": "consumed", "osr": "consumed", "evidence": "packages/overlay-runtime/src/parts.mjs applyPartMask" },
106
+ { "path": "tracks[].items[].source.elements", "applies_to": ["overlays"], "gpu": "consumed", "osr": "consumed", "evidence": "packages/overlay-runtime/src/parts.mjs applyElementOverrides" },
106
107
  { "path": "tracks[].items[].source.exclude", "applies_to": ["overlays"], "gpu": "consumed", "osr": "consumed", "evidence": "packages/overlay-runtime/src/parts.mjs projectBagChildren/expandBagOverlays" },
107
108
  { "path": "tracks[].items[].source.exclude", "applies_to": ["captions"], "gpu": "consumed", "osr": "consumed", "evidence": "packages/edit-store/src/tree-ops.ts collectExcludedCaptionIds/filterCaptionRootByExcludedIds; both page-builder loadAndBuild functions" },
108
109
  { "path": "tracks[].items[].source.derivedFrom", "applies_to": ["overlays"], "gpu": "other-subsystem", "osr": "other-subsystem", "consumer": "overlay authoring provenance", "evidence": "packages/edit-store/src/internal-model.ts buildV2VisualItem stores derivedFrom on InternalHtmlSource; packages/overlay-runtime/src/parts.mjs does not use it for pixels" },
@@ -5,6 +5,7 @@ import { EPSILON, addFinding, effectiveSourceOut, formatNumber, isFiniteNumber,
5
5
  import { isStillImageSourcePath, resolveItemAnchors, toAnchorCaptions } from "./external.mjs";
6
6
  import { validateAudioClipFxDeclaration, validateAudioDuckKeys, validateAudioEnvelopeDeclaration } from "./audio.mjs";
7
7
  import { validateAdjust, validateChromaKey, validateLook } from "./look-adjust.mjs";
8
+ import { resolveElementAddresses } from "../../../overlay-runtime/src/parts.mjs";
8
9
 
9
10
  const MOTION_IN_OUT_PRESETS = new Set(["fade", "slide-up", "slide-down", "slide-left", "slide-right", "scale", "wipe", "pop", "zoom", "twirl"]);
10
11
 
@@ -723,6 +724,22 @@ export async function validateV2ObjectTreeFiles(edit, findings, paths) {
723
724
  for (const { item, path: itemPath } of entries) {
724
725
  if (!isRecord(item.source)) continue;
725
726
  const source = item.source;
727
+ if (source.kind === "html" && isRecord(source.elements) && Object.keys(source.elements).length > 0) {
728
+ let html = "";
729
+ try {
730
+ html = await readFile(resolve(paths.projectRoot, source.path), "utf8");
731
+ } catch {
732
+ // The existing overlay file check reports a missing fragment.
733
+ }
734
+ for (const address of resolveElementAddresses(html, Object.keys(source.elements)).missing) {
735
+ addFinding(findings, {
736
+ severity: "warning",
737
+ check: "v2.element-ref",
738
+ message: `HTML element address was not found at source level: ${address}`,
739
+ path: `${itemPath}.source.elements[${JSON.stringify(address)}]`,
740
+ });
741
+ }
742
+ }
726
743
  if (source.kind === "html" && (isNonEmptyString(source.part) || Array.isArray(source.exclude))) {
727
744
  let html = "";
728
745
  try {
@@ -164,8 +164,18 @@ function inlineField(key, value, item) {
164
164
  if (item && key === 'transform' && isRecord(value) && (value.scaleX !== undefined || value.scaleY !== undefined)) {
165
165
  return inlineObject({ ...(0, transform_1.normalizeTransform)(value) }, ['x', 'y', 'scale', 'scaleX', 'scaleY', 'rotate']);
166
166
  }
167
- if (item && key === 'source' && isRecord(value))
168
- return inlineObject(value, ['kind', 'canvas']);
167
+ if (item && key === 'source' && isRecord(value)) {
168
+ const source = { ...value };
169
+ if (source.kind === 'html' && isRecord(source.elements)) {
170
+ const elements = Object.fromEntries(Object.entries(source.elements)
171
+ .filter(([, entry]) => isRecord(entry) && isRecord(entry.style) && Object.keys(entry.style).length > 0));
172
+ if (Object.keys(elements).length)
173
+ source.elements = elements;
174
+ else
175
+ delete source.elements;
176
+ }
177
+ return inlineObject(source, ['kind', 'canvas']);
178
+ }
169
179
  if (item && key === 'keyframes' && Array.isArray(value)) {
170
180
  return `[${value.map(point => inlineOrdered(point, edit_v2_keys_1.KEYFRAME_V2_KEYS)).join(', ')}]`;
171
181
  }
@@ -33,6 +33,11 @@ export type PreviewItemWriteCommand = ({
33
33
  y: number;
34
34
  };
35
35
  }[];
36
+ element?: {
37
+ ref: string;
38
+ tag: string;
39
+ style: Record<string, string | null>;
40
+ };
36
41
  };
37
42
  } | {
38
43
  kind: 'layer';
@@ -51,7 +51,7 @@ function resolvePreviewItemWriteBatch(editText, commands) {
51
51
  }
52
52
  let candidateText = editText;
53
53
  for (const command of commands) {
54
- if (command.kind === 'overlay' && 'html' in command.patch) {
54
+ if (command.kind === 'overlay' && ('html' in command.patch || 'element' in command.patch)) {
55
55
  throw new Error('バッチでは外部 HTML 本文を書き込めません');
56
56
  }
57
57
  const resolved = resolvePreviewItemWrite(candidateText, command);
@@ -142,6 +142,40 @@ function resolveV2Write(parsed, command) {
142
142
  }
143
143
  };
144
144
  if (command.kind === 'overlay') {
145
+ if (command.patch.element) {
146
+ const { ref, tag, style } = command.patch.element;
147
+ if (item.source.kind !== 'html' || item.source.part) {
148
+ throw new Error(`要素の書き戻し対象ではありません: ${itemId}`);
149
+ }
150
+ if (['transform', 'html', 'text', 'duplicate', 'params', 'vars', 'xyKeyframes']
151
+ .some(key => key in command.patch)
152
+ || !/^[#.][^\s\[\]]+\[(0|[1-9]\d*)\]$/u.test(ref)
153
+ || !/^[a-z][a-z0-9-]*$/u.test(tag)
154
+ || !isRecord(style) || Object.keys(style).length === 0) {
155
+ throw new Error('要素の書き戻し指定が不正です');
156
+ }
157
+ const source = item.source;
158
+ const elements = { ...source.elements };
159
+ const current = { ...elements[ref]?.style };
160
+ for (const [key, value] of Object.entries(style)) {
161
+ if (!/^[a-z][a-z0-9-]*$/u.test(key) || (value !== null && typeof value !== 'string')) {
162
+ throw new Error('要素の style が不正です');
163
+ }
164
+ if (value === null || value === '')
165
+ delete current[key];
166
+ else
167
+ current[key] = value;
168
+ }
169
+ if (Object.keys(current).length)
170
+ elements[ref] = { style: current };
171
+ else
172
+ delete elements[ref];
173
+ if (Object.keys(elements).length)
174
+ source.elements = elements;
175
+ else
176
+ delete source.elements;
177
+ return { candidateText: stringifyEdit(edit) };
178
+ }
145
179
  if ('text' in command.patch) {
146
180
  if (typeof command.patch.text !== 'string') {
147
181
  throw new Error('部品の text は文字列である必要があります');
@@ -118,6 +118,9 @@ export interface HtmlSourceV2 {
118
118
  part?: string;
119
119
  style?: Record<string, string>;
120
120
  text?: string;
121
+ elements?: Record<string, {
122
+ style: Record<string, string>;
123
+ }>;
121
124
  exclude?: string[];
122
125
  derivedFrom?: string;
123
126
  vars?: Record<string, unknown>;
@@ -491,7 +491,7 @@ function validateItemSource(value, path, sourceIds) {
491
491
  throw invalid(`${path}.mute`, 'boolean である必要があります');
492
492
  return;
493
493
  case 'html':
494
- requireExactKeys(value, new Set(['kind', 'path', 'part', 'style', 'text', 'exclude', 'derivedFrom', 'vars', 'params']), path);
494
+ requireExactKeys(value, new Set(['kind', 'path', 'part', 'style', 'text', 'elements', 'exclude', 'derivedFrom', 'vars', 'params']), path);
495
495
  requireText(value.path, `${path}.path`);
496
496
  for (const key of ['part', 'derivedFrom'])
497
497
  if (hasOwn(value, key))
@@ -500,6 +500,18 @@ function validateItemSource(value, path, sourceIds) {
500
500
  throw invalid(`${path}.text`, '文字列である必要があります');
501
501
  if (hasOwn(value, 'style'))
502
502
  validateStringMap(value.style, `${path}.style`);
503
+ if (hasOwn(value, 'elements')) {
504
+ requireRecord(value.elements, `${path}.elements`);
505
+ for (const [address, override] of Object.entries(value.elements)) {
506
+ if (!/^[#.][^\s\[\]]+\[(0|[1-9]\d*)\]$/.test(address))
507
+ throw invalid(`${path}.elements.${address}`, '要素の番地が不正です');
508
+ requireRecord(override, `${path}.elements.${address}`);
509
+ requireExactKeys(override, new Set(['style']), `${path}.elements.${address}`);
510
+ if (!hasOwn(override, 'style'))
511
+ throw invalid(`${path}.elements.${address}.style`, 'style が必要です');
512
+ validateStringMap(override.style, `${path}.elements.${address}.style`);
513
+ }
514
+ }
503
515
  if (hasOwn(value, 'exclude'))
504
516
  validateStringList(value.exclude, `${path}.exclude`);
505
517
  if (hasOwn(value, 'vars'))
@@ -1,5 +1,5 @@
1
1
  export declare const ITEM_V2_KEYS: readonly ["id", "name", "hidden", "locked", "at", "duration", "anchor", "transform", "opacity", "blend", "crop", "adjust", "perspective", "motion", "animator", "keyframes", "items", "mask", "maskFeather", "regions", "erase", "flip", "frame", "source", "audio", "role", "link", "cut_edge", "mute", "gain_db", "denoise", "lowcut_hz", "fade_in", "fade_out", "fade_in_shape", "fade_out_shape", "ducking", "duck_db", "duck_attack", "duck_release", "script", "reading", "caption_ref", "provenance"];
2
- export declare const ITEM_SOURCE_V2_KEYS: readonly ["kind", "src", "in", "out", "framing", "transition_out", "freeze", "fx", "speed", "gain_db", "mute", "chroma_key", "pitch_semitones", "formant", "path", "part", "style", "text", "exclude", "derivedFrom", "vars", "params", "shape", "preset", "baked", "from", "filter", "canvas", "id"];
2
+ export declare const ITEM_SOURCE_V2_KEYS: readonly ["kind", "src", "in", "out", "framing", "transition_out", "freeze", "fx", "speed", "gain_db", "mute", "chroma_key", "pitch_semitones", "formant", "path", "part", "style", "text", "elements", "exclude", "derivedFrom", "vars", "params", "shape", "preset", "baked", "from", "filter", "canvas", "id"];
3
3
  export declare const KEYFRAME_V2_KEYS: readonly ["t", "transform", "crop", "perspective", "opacity", "gain_db", "animator", "easing"];
4
4
  export declare const MOTION_V0_KEYS: readonly ["in", "out", "loop"];
5
5
  export declare const ANIMATOR_V0_KEYS: readonly ["id", "basis", "shape", "start", "end", "offset", "randomize", "amount", "ease"];
@@ -19,7 +19,7 @@ export declare const ITEM_V2_KEYS_BY_DEFINITION: {
19
19
  export declare const ITEM_SOURCE_V2_KEYS_BY_DEFINITION: {
20
20
  readonly itemSourceMediaV2: readonly ["kind", "src", "in", "out", "framing", "transition_out", "freeze", "fx", "speed", "gain_db", "mute", "chroma_key"];
21
21
  readonly itemSourceAudioMediaV2: readonly ["kind", "src", "in", "out", "speed", "pitch_semitones", "formant"];
22
- readonly itemSourceHtmlV2: readonly ["kind", "path", "part", "style", "text", "exclude", "derivedFrom", "vars", "params"];
22
+ readonly itemSourceHtmlV2: readonly ["kind", "path", "part", "style", "text", "elements", "exclude", "derivedFrom", "vars", "params"];
23
23
  readonly itemSourceShapeV2: readonly ["kind", "shape", "params"];
24
24
  readonly itemSourceTelopV2: readonly ["kind", "preset", "params", "baked", "from"];
25
25
  readonly itemSourceFilterV2: readonly ["kind", "filter"];
@@ -4,7 +4,7 @@
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.ITEM_SOURCE_V2_KEYS_BY_DEFINITION = exports.ITEM_V2_KEYS_BY_DEFINITION = exports.SOURCE_KIND_V2 = exports.MOTION_FILE_V0_KEYS = exports.ANIMATOR_V0_KEYS = exports.MOTION_V0_KEYS = exports.KEYFRAME_V2_KEYS = exports.ITEM_SOURCE_V2_KEYS = exports.ITEM_V2_KEYS = void 0;
6
6
  exports.ITEM_V2_KEYS = ["id", "name", "hidden", "locked", "at", "duration", "anchor", "transform", "opacity", "blend", "crop", "adjust", "perspective", "motion", "animator", "keyframes", "items", "mask", "maskFeather", "regions", "erase", "flip", "frame", "source", "audio", "role", "link", "cut_edge", "mute", "gain_db", "denoise", "lowcut_hz", "fade_in", "fade_out", "fade_in_shape", "fade_out_shape", "ducking", "duck_db", "duck_attack", "duck_release", "script", "reading", "caption_ref", "provenance"];
7
- exports.ITEM_SOURCE_V2_KEYS = ["kind", "src", "in", "out", "framing", "transition_out", "freeze", "fx", "speed", "gain_db", "mute", "chroma_key", "pitch_semitones", "formant", "path", "part", "style", "text", "exclude", "derivedFrom", "vars", "params", "shape", "preset", "baked", "from", "filter", "canvas", "id"];
7
+ exports.ITEM_SOURCE_V2_KEYS = ["kind", "src", "in", "out", "framing", "transition_out", "freeze", "fx", "speed", "gain_db", "mute", "chroma_key", "pitch_semitones", "formant", "path", "part", "style", "text", "elements", "exclude", "derivedFrom", "vars", "params", "shape", "preset", "baked", "from", "filter", "canvas", "id"];
8
8
  exports.KEYFRAME_V2_KEYS = ["t", "transform", "crop", "perspective", "opacity", "gain_db", "animator", "easing"];
9
9
  exports.MOTION_V0_KEYS = ["in", "out", "loop"];
10
10
  exports.ANIMATOR_V0_KEYS = ["id", "basis", "shape", "start", "end", "offset", "randomize", "amount", "ease"];
@@ -236,6 +236,7 @@ exports.ITEM_SOURCE_V2_KEYS_BY_DEFINITION = {
236
236
  "part",
237
237
  "style",
238
238
  "text",
239
+ "elements",
239
240
  "exclude",
240
241
  "derivedFrom",
241
242
  "vars",
@@ -28,6 +28,9 @@ export interface InternalHtmlSource {
28
28
  part?: string;
29
29
  style?: Record<string, string>;
30
30
  text?: string;
31
+ elements?: Record<string, {
32
+ style: Record<string, string>;
33
+ }>;
31
34
  exclude?: string[];
32
35
  derivedFrom?: string;
33
36
  }
@@ -806,6 +806,7 @@ function buildV2VisualItem(item, fps, ref, pathOf, chromaKeyOf, legacyIndexCount
806
806
  ...(item.source.part !== undefined ? { part: item.source.part } : {}),
807
807
  ...(item.source.style !== undefined ? { style: item.source.style } : {}),
808
808
  ...(item.source.text !== undefined ? { text: item.source.text } : {}),
809
+ ...(item.source.elements !== undefined ? { elements: item.source.elements } : {}),
809
810
  ...(item.source.exclude !== undefined ? { exclude: item.source.exclude } : {}),
810
811
  ...(item.source.derivedFrom !== undefined ? { derivedFrom: item.source.derivedFrom } : {})
811
812
  },
@@ -0,0 +1,36 @@
1
+ export interface TaskRecord {
2
+ id: string;
3
+ source: string;
4
+ state: string;
5
+ createdAt: string;
6
+ [key: string]: unknown;
7
+ }
8
+ export interface TasksDocument {
9
+ version: number;
10
+ tasks: TaskRecord[];
11
+ [key: string]: unknown;
12
+ }
13
+ export type TasksReadResult = {
14
+ ok: true;
15
+ exists: boolean;
16
+ doc: TasksDocument;
17
+ version: number;
18
+ tasks: TaskRecord[];
19
+ warnings: string[];
20
+ } | {
21
+ ok: false;
22
+ reason: 'broken' | 'newer';
23
+ version?: number;
24
+ warnings: string[];
25
+ };
26
+ export declare function readTasksFile(filePath: string): Promise<TasksReadResult>;
27
+ export declare function writeTasksFile(filePath: string, doc: TasksDocument): Promise<void>;
28
+ export declare function withTasksLock<T>(filePath: string, operation: () => Promise<T>): Promise<T>;
29
+ export declare function nextTaskId(tasks: readonly Partial<TaskRecord>[]): string;
30
+ export declare function applyTaskPatch(task: TaskRecord, patch: Record<string, unknown>, actor: 'human' | 'app' | 'agent'): TaskRecord;
31
+ export declare function deriveTasks(reviewJsonText: string | null | undefined, overlayDoc: TasksDocument): {
32
+ tasks: TaskRecord[];
33
+ warnings: string[];
34
+ };
35
+ export declare function toOverlayEntry(derivedTask: TaskRecord, existingTasks?: readonly TaskRecord[]): TaskRecord;
36
+ export declare function importSentAnnotationIds(overlay: TasksDocument, ids: readonly string[]): TasksDocument;