akari-video 0.1.13 → 0.1.15

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 (64) hide show
  1. package/bin/akari.mjs +8 -3
  2. package/package.json +1 -1
  3. package/src/cli.mjs +12 -7
  4. package/src/messages.mjs +26 -2
  5. package/src/update-check.mjs +66 -25
  6. package/vendor/.akari-capability-sources.json +2 -0
  7. package/vendor/docs/contract-2026-07-17-data-contract-versioning.md +8 -0
  8. package/vendor/docs/contract-2026-07-25-r6-audio-tracks-and-trim.md +1 -1
  9. package/vendor/docs/contract-2026-08-23-captions-emphasis-words-v0.md +76 -0
  10. package/vendor/docs/contract-2026-08-23-stroke-persistence.md +75 -0
  11. package/vendor/packages/akari-launcher/package.json +1 -1
  12. package/vendor/packages/decision-cards/README.md +12 -0
  13. package/vendor/packages/decision-cards/package.json +3 -2
  14. package/vendor/packages/edit-lint/src/edit-lint.mjs +59 -60
  15. package/vendor/packages/edit-store/lib/cut-adjacency.d.ts +19 -0
  16. package/vendor/packages/edit-store/lib/cut-adjacency.js +28 -0
  17. package/vendor/packages/edit-store/lib/edit-store.d.ts +2 -2
  18. package/vendor/packages/edit-store/lib/edit-store.js +3 -1
  19. package/vendor/packages/edit-store/lib/edit-v2-item-write.d.ts +53 -0
  20. package/vendor/packages/edit-store/lib/edit-v2-item-write.js +162 -0
  21. package/vendor/packages/edit-store/lib/index.d.ts +4 -0
  22. package/vendor/packages/edit-store/lib/index.js +4 -0
  23. package/vendor/packages/edit-store/lib/internal-model.js +52 -53
  24. package/vendor/packages/edit-store/lib/legacy-audio-view.d.ts +16 -0
  25. package/vendor/packages/edit-store/lib/legacy-audio-view.js +60 -0
  26. package/vendor/packages/edit-store/lib/migrate/index.d.ts +8 -2
  27. package/vendor/packages/edit-store/lib/migrate/index.js +103 -13
  28. package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +3 -1
  29. package/vendor/packages/edit-store/lib/timeline-map.d.ts +16 -0
  30. package/vendor/packages/edit-store/lib/timeline-map.js +65 -15
  31. package/vendor/packages/edit-store/lib/track-transition-compatibility.d.ts +23 -0
  32. package/vendor/packages/edit-store/lib/track-transition-compatibility.js +79 -0
  33. package/vendor/packages/edit-store/lib/webview-kernel.js +65 -16
  34. package/vendor/packages/edit-store/lib/write-gate.d.ts +4 -0
  35. package/vendor/packages/edit-store/lib/write-gate.js +38 -1
  36. package/vendor/packages/overlay-runtime/package.json +2 -1
  37. package/vendor/packages/pen-visuals/package.json +1 -0
  38. package/vendor/packages/schemas/bin/validate-captions.mjs +51 -2
  39. package/vendor/packages/schemas/bin/validate-research-plan.mjs +55 -7
  40. package/vendor/packages/schemas/captions.schema.json +21 -2
  41. package/vendor/packages/schemas/examples/captions-emphasis-words-empty-word/captions.json +16 -0
  42. package/vendor/packages/schemas/examples/captions-emphasis-words-invalid-id/captions.json +16 -0
  43. package/vendor/packages/schemas/examples/captions-emphasis-words-missing-emotion/captions.json +16 -0
  44. package/vendor/packages/schemas/examples/captions-emphasis-words-range-invalid/captions.json +17 -0
  45. package/vendor/packages/schemas/examples/captions-emphasis-words-valid/captions.json +31 -0
  46. package/vendor/packages/schemas/fixtures/review/valid-strokes/review.json +1 -1
  47. package/vendor/packages/schemas/research-plan.schema.json +24 -2
  48. package/vendor/packages/schemas/review.schema.json +18 -0
  49. package/vendor/packages/schemas/test/fixtures/research-plan/invalid-nested-cutaway/research-plan.json +25 -0
  50. package/vendor/packages/schemas/test/fixtures/research-plan/valid-legacy-without-shot-ids/research-plan.json +29 -0
  51. package/vendor/packages/schemas/test/fixtures/research-plan/valid-visual-storyboard/research-plan.json +72 -0
  52. package/vendor/packages/schemas/test/validate-captions.test.mjs +37 -0
  53. package/vendor/packages/schemas/test/validate-research-plan.test.mjs +18 -0
  54. package/vendor/skills/address-review/bin/list.mjs +12 -0
  55. package/vendor/skills/address-review/dev-fixtures/fixture-project/review.json +3 -0
  56. package/vendor/skills/address-review/test/review-store.test.mjs +6 -0
  57. package/vendor/skills/compile-review-session/bin/core/compiler.mjs +5 -0
  58. package/vendor/skills/compile-review-session/test/compiler.test.mjs +9 -0
  59. package/vendor/skills/compile-review-session/test/ui-events.test.mjs +1 -0
  60. package/vendor/skills/edit-plan/SKILL.md +6 -3
  61. package/vendor/skills/edit-plan/beat-sync.md +17 -3
  62. package/vendor/skills/edit-plan/emphasis-detection.md +14 -9
  63. package/vendor/skills/research-plan/SKILL.md +1 -1
  64. package/vendor/skills/research-plan/storyboard.md +30 -1
package/bin/akari.mjs CHANGED
@@ -11,14 +11,19 @@ import { runCapabilityCommand } from '../src/capability-command.mjs';
11
11
  import { runStoreCommand } from '../src/store-command.mjs';
12
12
  import { runAssetsCommand } from '../src/assets-command.mjs';
13
13
  import { runMigrateCommand } from '../src/migrate-command.mjs';
14
- import { maybeApplyPendingUpdateOnLaunch, readOwnVersion } from '../src/update-check.mjs';
15
- import { describeCliHelp } from '../src/messages.mjs';
14
+ import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
15
+ import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
16
16
 
17
17
  // `akari --version` / `-v`: インストール済みの版を表示するだけの最小コマンド
18
18
  // (タスク契約 2026-08-11-update-u4-cli-self-update の受け入れ条件 —
19
19
  // `akari update` / `--rollback` 後にインストール先の版を観測する手段として必要)。
20
20
  async function printVersion() {
21
- console.log(`v${readOwnVersion()}`);
21
+ const versionInfo = resolveInstalledVersionInfo({ env: process.env });
22
+ // 1 行目は update / rollback の既存機械観測契約として CLI 版だけを維持する。
23
+ console.log(`v${versionInfo.cliVersion}`);
24
+ for (const line of describeInstalledVersions(versionInfo)) {
25
+ console.log(line);
26
+ }
22
27
  return { exitCode: 0 };
23
28
  }
24
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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": {
package/src/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import { resolveLauncherAssets } from './repo-assets.mjs';
6
6
  import { detectProjectState } from './project-state.mjs';
7
7
  import { findClaudeExecutable, findOpencodeExecutable } from './path-lookup.mjs';
8
8
  import { loadTaskLabels } from './task-labels.mjs';
9
- import { describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
9
+ import { describeForceReinstall, describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
10
10
  import { resolveEffectiveProjectRoot } from './first-run.mjs';
11
11
  import { maybeShowAssetIntroNotice } from './sounds-setup.mjs';
12
12
  import {
@@ -14,9 +14,9 @@ import {
14
14
  compareVersions,
15
15
  isValidFeedShape,
16
16
  readCacheSync,
17
- readInstalledAppVersion,
18
17
  readOwnVersion,
19
18
  recordDismissalSync,
19
+ refreshUpdateFeed,
20
20
  resolveCachePath,
21
21
  resolveInstalledVersionInfo,
22
22
  triggerBackgroundRefresh
@@ -188,8 +188,8 @@ function defaultSpawnOpencode(opencodePath, args, projectRoot) {
188
188
  * 自己更新を試みず、キャッシュに載っている最新版の通知を今後出さないよう記録するだけ
189
189
  * (既存挙動を維持)。`--force` は同じ版の本体も再導入し、`--rollback` は直前 1 世代
190
190
  * (`~/.akari/app-previous/`)へ戻す。
191
- * ネットワークに触れるのは自己更新の DL 区間のみ — フィード自体は既存キャッシュ由来
192
- * (最新情報は `akari` 起動時のバックグラウンド fetch で更新される)。
191
+ * 通常時にネットワークへ触れるのは自己更新の DL 区間のみ。`--force` だけはキャッシュが
192
+ * 未取得なら、復旧経路を塞がないためフィードの同期取得を 1 回試す。
193
193
  */
194
194
  export async function runUpdateCommand(args, options = {}) {
195
195
  const log = options.log ?? ((line) => console.log(line));
@@ -197,7 +197,7 @@ export async function runUpdateCommand(args, options = {}) {
197
197
  const versionInfo = resolveCommandVersionInfo(options, env);
198
198
  const currentVersion = versionInfo.currentVersion;
199
199
  const cachePath = resolveCachePath(env);
200
- const cache = readCacheSync(cachePath);
200
+ let cache = readCacheSync(cachePath);
201
201
  const dismissRequested = args.includes('--dismiss');
202
202
  const rollbackRequested = args.includes('--rollback');
203
203
  const forceRequested = args.includes('--force');
@@ -219,10 +219,15 @@ export async function runUpdateCommand(args, options = {}) {
219
219
  return { exitCode: 0 };
220
220
  }
221
221
 
222
+ if (forceRequested && !cache?.feed) {
223
+ await (options.refreshUpdateFeed ?? refreshUpdateFeed)({ env, fetchImpl: options.fetchImpl });
224
+ cache = readCacheSync(cachePath);
225
+ }
226
+
222
227
  const feed = cache?.feed;
223
228
  const updateAvailable = isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) > 0;
224
229
  const reinstallRequested = forceRequested && isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) >= 0;
225
- const hasManagedApp = !!readInstalledAppVersion(env);
230
+ const hasManagedApp = versionInfo.managedApp === true;
226
231
  const selfUpdateEligible = (updateAvailable || reinstallRequested)
227
232
  && !!feed.components?.app?.url
228
233
  && !!feed.components?.app?.sha256
@@ -240,7 +245,7 @@ export async function runUpdateCommand(args, options = {}) {
240
245
  }
241
246
  log(`最新バージョン: v${feed.product}`);
242
247
  if (forceRequested) {
243
- log(`--force: 本体 v${currentVersion} を入れ直します。`);
248
+ log(describeForceReinstall(versionInfo, feed.product));
244
249
  }
245
250
 
246
251
  return (options.applySelfUpdate ?? applySelfUpdate)({
package/src/messages.mjs CHANGED
@@ -90,6 +90,14 @@ export function describeVersionStatus(versionOrInfo, cache) {
90
90
 
91
91
  export function describeInstalledVersions(versionOrInfo) {
92
92
  const info = normalizeVersionInfo(versionOrInfo);
93
+ if (info.installRefNeedsRepair) {
94
+ const installRefPath = info.installRefPath ?? '~/.akari/app/.akari-install-ref';
95
+ return [
96
+ `CLI バージョン: v${info.cliVersion}`,
97
+ `本体版を判定できません(\`${installRefPath}\` が壊れています)。`,
98
+ '修復するには `akari update --force` を実行してください。'
99
+ ];
100
+ }
93
101
  if (!info.appVersion) {
94
102
  return [
95
103
  `現在のバージョン: v${info.currentVersion}`,
@@ -104,6 +112,13 @@ export function describeInstalledVersions(versionOrInfo) {
104
112
  return lines;
105
113
  }
106
114
 
115
+ export function describeForceReinstall(versionOrInfo, targetVersion) {
116
+ const info = normalizeVersionInfo(versionOrInfo);
117
+ return info.installRefNeedsRepair
118
+ ? `--force: 版を判定できない本体 → v${targetVersion} を入れ直します。`
119
+ : `--force: 本体 v${info.currentVersion} → v${targetVersion} を入れ直します。`;
120
+ }
121
+
107
122
  function normalizeVersionInfo(value) {
108
123
  if (typeof value === 'string') {
109
124
  return { cliVersion: value, appVersion: null, currentVersion: value, mismatch: false };
@@ -112,11 +127,15 @@ function normalizeVersionInfo(value) {
112
127
  }
113
128
 
114
129
  function versionRelationLabel(info) {
115
- return compareVersions(info.appVersion, info.cliVersion) < 0 ? '本体が古い' : 'CLI と本体の版が不一致';
130
+ return compareVersions(info.appVersion, info.cliVersion) < 0 ? '本体が古い' : 'CLI が古い';
116
131
  }
117
132
 
118
133
  function formatVersionMismatch(info) {
119
- return `⚠ CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${versionRelationLabel(info)}。\`akari update\` で本体を更新してください。`;
134
+ const relation = versionRelationLabel(info);
135
+ const guidance = relation === '本体が古い'
136
+ ? '`akari update` で本体を更新してください。'
137
+ : '`npm i -g akari-video@latest` で CLI を更新してください。';
138
+ return `⚠ CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${relation}。${guidance}`;
120
139
  }
121
140
 
122
141
  /**
@@ -168,6 +187,11 @@ export function describeUpdateCommand({ currentVersion, versionInfo, cache, dism
168
187
  lines.push(`リリースノート: ${feed.notes_url}`);
169
188
  }
170
189
 
190
+ if (info.installRefNeedsRepair) {
191
+ lines.push('本体版を判定できないため、更新判定を行いません。');
192
+ return lines;
193
+ }
194
+
171
195
  if (compareVersions(feed.product, info.currentVersion) <= 0) {
172
196
  lines.push('お使いのバージョンは最新です。');
173
197
  return lines;
@@ -65,29 +65,49 @@ export function readOwnVersion() {
65
65
  return JSON.parse(raw).version;
66
66
  }
67
67
 
68
+ /** install-ref を「有効・未記録・破損」の 3 状態で読む内部表現。 */
69
+ function readInstalledAppVersionInfo(env = process.env) {
70
+ const path = resolveInstallRefPath(env);
71
+ try {
72
+ const raw = readFileSync(path, 'utf8').trim();
73
+ const match = raw.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
74
+ return match
75
+ ? { status: 'valid', version: match[1], path }
76
+ : { status: 'invalid', version: null, path };
77
+ } catch (error) {
78
+ return {
79
+ status: error?.code === 'ENOENT' ? 'missing' : 'invalid',
80
+ version: null,
81
+ path
82
+ };
83
+ }
84
+ }
85
+
68
86
  /**
69
87
  * 実際に render-cut / edit-lint を実行する本体の導入版。
70
- * install.sh / self-update.mjs が書く `vX.Y.Z` を読み、無い・壊れている場合は null
88
+ * install.sh / self-update.mjs が書く `vX.Y.Z` を読む。互換 API として版または null を返し、
89
+ * 未記録と破損の区別は `resolveInstalledVersionInfo()` が保持する。
71
90
  */
72
91
  export function readInstalledAppVersion(env = process.env) {
73
- try {
74
- const raw = readFileSync(resolveInstallRefPath(env), 'utf8').trim();
75
- const match = raw.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
76
- return match?.[1] ?? null;
77
- } catch {
78
- return null;
79
- }
92
+ return readInstalledAppVersionInfo(env).version;
80
93
  }
81
94
 
82
95
  /** 更新判定に使う版と、CLI / 本体のずれを一度に解決する。 */
83
96
  export function resolveInstalledVersionInfo({ env = process.env, cliVersion = readOwnVersion() } = {}) {
84
- const installedAppVersion = readInstalledAppVersion(env);
97
+ const installRef = readInstalledAppVersionInfo(env);
98
+ const installedAppVersion = installRef.version;
85
99
  const currentVersion = installedAppVersion ?? cliVersion;
86
100
  return {
87
101
  cliVersion,
88
102
  appVersion: installedAppVersion,
89
103
  currentVersion,
90
- source: installedAppVersion ? 'install-ref' : 'cli-fallback',
104
+ source: installRef.status === 'valid'
105
+ ? 'install-ref'
106
+ : installRef.status === 'invalid' ? 'invalid-install-ref' : 'cli-fallback',
107
+ installRefStatus: installRef.status,
108
+ installRefPath: installRef.path,
109
+ installRefNeedsRepair: installRef.status === 'invalid',
110
+ managedApp: installRef.status !== 'missing',
91
111
  mismatch: installedAppVersion !== null && compareVersions(cliVersion, installedAppVersion) !== 0
92
112
  };
93
113
  }
@@ -135,8 +155,18 @@ function writeCacheSync(cachePath, cache) {
135
155
  /**
136
156
  * キャッシュと現在版から、新版通知を出すべきかを判定する(同期・純粋関数・I/O なし)。
137
157
  */
138
- export function evaluateUpdateStatus({ currentVersion, cache, cliVersion = currentVersion, appVersion = null, source = 'cli-fallback', mismatch = false }) {
139
- const versionDetails = { currentVersion, cliVersion, appVersion, source, mismatch };
158
+ export function evaluateUpdateStatus({ currentVersion, cache, cliVersion = currentVersion, appVersion = null, source = 'cli-fallback', installRefStatus, installRefPath, installRefNeedsRepair, managedApp, mismatch = false }) {
159
+ const versionDetails = {
160
+ currentVersion,
161
+ cliVersion,
162
+ appVersion,
163
+ source,
164
+ installRefStatus,
165
+ installRefPath,
166
+ installRefNeedsRepair,
167
+ managedApp,
168
+ mismatch
169
+ };
140
170
  const feed = cache?.feed;
141
171
  if (!isValidFeedShape(feed)) {
142
172
  return { available: false, ...versionDetails };
@@ -215,16 +245,8 @@ export async function maybeStageInBackground({ env = process.env, feed, fetchImp
215
245
  return stageSelfUpdate({ env, feed, log: () => {}, fetchImpl, timeoutMs, extract });
216
246
  }
217
247
 
218
- /**
219
- * バックグラウンド fetch 本体。fetch 失敗・非 200・JSON パース失敗・スキーマ不明は
220
- * すべて沈黙して return する(何も throw しない)。`dismissed` は既存キャッシュから
221
- * 引き継ぐ(fetch のたびに既読状態が消えないように)。
222
- *
223
- * フィード取得・キャッシュ反映が成功した後、契約 §11 のバックグラウンド staging も
224
- * 同じ沈黙原則のもとで試みる(`maybeStageInBackground`)。成功したときだけ
225
- * キャッシュへ `staged`(版・sha256・staged_at)を追記する。
226
- */
227
- export async function runBackgroundFetch({ env = process.env, fetchImpl = globalThis.fetch, launcherRoot } = {}) {
248
+ /** フィードを 1 回取得し、正常なら既読状態を保ったままキャッシュへ反映する。 */
249
+ export async function refreshUpdateFeed({ env = process.env, fetchImpl = globalThis.fetch } = {}) {
228
250
  const feedUrl = resolveFeedUrl(env);
229
251
  const cachePath = resolveCachePath(env);
230
252
  try {
@@ -241,15 +263,34 @@ export async function runBackgroundFetch({ env = process.env, fetchImpl = global
241
263
  }
242
264
  const feed = await response.json();
243
265
  if (!isValidFeedShape(feed)) {
244
- return;
266
+ return null;
245
267
  }
246
268
  const existing = readCacheSync(cachePath);
247
- writeCacheSync(cachePath, {
269
+ const next = {
248
270
  schema: CACHE_SCHEMA,
249
271
  fetched_at: new Date().toISOString(),
250
272
  feed,
251
273
  dismissed: existing?.dismissed ?? {}
252
- });
274
+ };
275
+ writeCacheSync(cachePath, next);
276
+ return next;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * バックグラウンド fetch 本体。フィード取得失敗は沈黙し、成功後は契約 §11 の staging を
284
+ * 同じ沈黙原則で試す。成功したときだけキャッシュへ `staged` を追記する。
285
+ */
286
+ export async function runBackgroundFetch({ env = process.env, fetchImpl = globalThis.fetch, launcherRoot } = {}) {
287
+ const cachePath = resolveCachePath(env);
288
+ try {
289
+ const refreshed = await refreshUpdateFeed({ env, fetchImpl });
290
+ const feed = refreshed?.feed;
291
+ if (!feed) {
292
+ return;
293
+ }
253
294
 
254
295
  const staged = await maybeStageInBackground({ env, feed, fetchImpl, launcherRoot });
255
296
  if (staged?.ok) {
@@ -40,6 +40,8 @@
40
40
  "docs/contract-2026-08-13-avatar-drive-v0.md",
41
41
  "docs/contract-2026-08-14-avatar-vrm-v0.md",
42
42
  "docs/contract-2026-08-18-v1-render-parity.md",
43
+ "docs/contract-2026-08-23-captions-emphasis-words-v0.md",
44
+ "docs/contract-2026-08-23-stroke-persistence.md",
43
45
  "packages/akari-launcher/package.json",
44
46
  "packages/akari-launcher/README.md",
45
47
  "packages/akari-tools/package.json",
@@ -97,3 +97,11 @@ edit.json の `version` は変わらないし、edit.json が `version: 1` に
97
97
  task `2026-08-20-v2-audio-tracks` に限り、「機能追加禁止・バグ修正のみ」の明示承認済み・1 回限りの例外として、v0/v1 の `audio.sfx[]`・`audio.narration[]`・`audio.bgm` を v2 の audio lane `tracks[].items[]` へ移す変換を追加した。出力側の `at` / `duration` は整数フレームへ確定し、素材側の `in` / `out` と fade / `bgm.in` は秒のまま保持する。実尺が旧形式だけでは決まらない音声は `duration: 0` センチネルとし、この変換器では ffprobe しない。トップレベル `audio` は `master` が宣言されている場合だけ残す。
98
98
 
99
99
  これは凍結前に許可された最後の機能追加である。今後この変換器へさらに変換能力を足さず、§6 の期限どおり本体から分離・削除する。
100
+
101
+ ### 6.2 filter レイヤー転写と emphasis_words 移送(2026-08-23・1 回限りの例外)
102
+
103
+ task `2026-08-23-migrate-filter-emphasis-exception` に限り、2026-08-23 オーナー裁定による「機能追加禁止・バグ修正のみ」の明示承認済み・1 回限りの例外として、v0/v1 の独立した `kind: "filter"` レイヤーを v2 の filter source へ転写し、トップレベル `emphasis_words[]` を edit.json v2 から除外して object ルートの `captions.json.emphasis_words[]` へ移送する変換を追加した。media 系レイヤー(`video` / `baked`)に直付けされた `filter` は v2 に等価表現が無いため転写対象外とし、理由付きで変換を停止する。移送時は edit.json と captions.json の原文をそれぞれ退避し、1 手の revert で両方を復元する。移送先が無い、配列ルートである、または既に `emphasis_words` を持つ場合は理由付きで停止する。
104
+
105
+ 2026-08-21 の廃止裁定は fieldtest 60 本を母集団とし、本番リールでの使用が調査圏外だったという母集団瑕疵があったため、本例外で既存データを損失なく移す。
106
+
107
+ これ以後この変換器へさらに変換能力を足さず、§6 の期限どおり本体から分離・削除する。
@@ -66,7 +66,7 @@
66
66
 
67
67
  ## 5. §2 追記 — sfx フェード(audio-clip-fades, 2026-08-18・オーナー裁定「クリップ主義」T2)
68
68
 
69
- BGM をクリップ化する裁定(内部リポ `tasks/2026-08-18-bgm-clip-placement-ruling`)に伴い、
69
+ BGM をクリップ化する裁定(内部リポ `akari-video-internal` の該当タスク)に伴い、
70
70
  「音楽をクリップ(audio.sfx[])として置いても BGM ベッドと同じフェード表現ができる」を
71
71
  満たすため、`sfxItem` に optional の `fade_in` / `fade_out`(秒・0 以上)を追加のみ拡張する
72
72
  (`version` 不変・`contract-2026-07-17-data-contract-versioning.md` の原則に従う)。
@@ -0,0 +1,76 @@
1
+ # captions.json v0 語レベル演出(emphasis_words)データ契約
2
+
3
+ - 日付: 2026-08-23
4
+ - 状態: 実装ラウンドの SSOT(`captions.json` の席と優先順を確定)
5
+ - 前提: [edit.json v1 語レベル演出契約](contract-2026-07-23-edit-json-v1-emphasis-words.md)
6
+ - スコープ: `captions.json` の object ルートに置くトップレベル任意フィールド
7
+ `emphasis_words[]` と、旧 `edit.json` 席との読取優先順
8
+
9
+ ## 背景
10
+
11
+ 字幕表示に属する語レベル演出は、編集タイムラインではなく字幕の SSOT である `captions.json` 側へ置く。
12
+ `edit.json` v2 から `emphasis_words` を廃止した 2026-08-21 裁定は維持し、v2 の exact-keys に席を
13
+ 戻さない。一方、その裁定の根拠だった fieldtest 60 本の使用 0 件は母集団外の本番リールを含んでおらず、
14
+ 本番リールでは 15 語が実際に使用されていたため、「語レベル演出自体が不要」という根拠は訂正する。
15
+
16
+ ## 1. 席
17
+
18
+ `captions.json` が object ルートのときだけ、トップレベルに `emphasis_words[]` を任意で置ける。
19
+
20
+ ```jsonc
21
+ {
22
+ "emphasis_words": [
23
+ {
24
+ "id": "e-0001",
25
+ "src": "main",
26
+ "t_start": 12.08,
27
+ "t_end": 12.44,
28
+ "word": "最高",
29
+ "emotion": "joy",
30
+ "style_hint": "size-pulse"
31
+ }
32
+ ],
33
+ "captions": [ /* 既存のまま */ ]
34
+ }
35
+ ```
36
+
37
+ 従来の配列ルート(`[{ ...caption... }]`)にはトップレベルのフィールドを置けないため、
38
+ `emphasis_words[]` の席はない。語レベル演出を書く場合は object ルートへ移し、既存配列を
39
+ `captions[]` に包む。
40
+
41
+ ## 2. レコード契約と差分
42
+
43
+ レコード形、フィールド語彙、source 秒アンカー、実測 word-level タイムスタンプを写す規律、
44
+ 選定規律、劣化規約はすべて
45
+ [edit.json v1 語レベル演出契約](contract-2026-07-23-edit-json-v1-emphasis-words.md) と同じである。
46
+ 本契約が定める差分は次の 2 点だけである。
47
+
48
+ 1. 配列の置き場を `edit.json.emphasis_words` から、object ルートの
49
+ `captions.json.emphasis_words` へ移す。
50
+ 2. `edit.json` v2 へは書かない。v0/v1 の旧席は後方互換の読取専用として残す。
51
+
52
+ したがって各レコードは `{ id, word, emotion, src?, t_start, t_end, style_hint? }` で、`id` は
53
+ `^e-\d{4}$` かつファイル内一意、`word` と `emotion` は空でない文字列、時刻は source 秒で
54
+ `0 <= t_start < t_end` とする。`emotion` は `joy` / `pain` / `surprise` / `anger` / `sadness` /
55
+ `emphasis` を標準語彙として使うが、v1 契約どおり enum 強制はしない。`style_hint` も描画側への
56
+ 提案に留まる。
57
+
58
+ ## 3. 読取優先順
59
+
60
+ 消費側は次の優先順で 1 つの席だけを読む。
61
+
62
+ 1. object ルートの `captions.json` に `emphasis_words` キーが在れば、その値を採用する。
63
+ 2. キーが無ければ、v0/v1 の `edit.json.emphasis_words` を後方互換として読む。
64
+ 3. どちらにも無ければ語レベル演出なしとする。
65
+
66
+ 両方の席が在る場合もマージしない。`captions.json` 側だけを採用し、警告は出さない。これにより
67
+ 新しい字幕 SSOT が常に優先される一方、`captions.json` に席を持たない既存 v0/v1 プロジェクトは
68
+ 従来どおり `edit.json` 側だけで描画され、回帰しない。
69
+
70
+ ## 4. 検証責務
71
+
72
+ `captions.schema.json` は object ルートだけに任意の `emphasis_words[]` を定義し、要素の必須項目、
73
+ 型、`id` 形式、非負時刻、非空の `word` / `emotion` を検査する。JSON Schema 標準では兄弟値を
74
+ 比較できない `t_end > t_start` と、配列内の `id` 一意性は `validate-captions.mjs` が検査する。
75
+ 語と `captions[].words[]` の実測値の突き合わせは v1 契約と同じく書き手の規律であり、静的検証では
76
+ 行わない。
@@ -0,0 +1,75 @@
1
+ ---
2
+ lifecycle: implemented
3
+ created: 2026-08-23
4
+ updated: 2026-08-23
5
+ ---
6
+
7
+ # 注釈ストローク永続表示契約
8
+
9
+ - 日付: 2026-08-23
10
+ - 状態: **実装済み**
11
+ - 前提: `contract-2026-08-11-review-session-ui-events.md`、
12
+ `contract-2026-07-20-review-json-v1-annotation-model.md`
13
+ - スコープ: Theia shell の出力プレビュー、review session の `strokes.json` 読み出し、
14
+ compile-review-session と address-review の追跡導線
15
+
16
+ ## 1. セッション中の表示
17
+
18
+ - pen / rect は従来どおり正規化座標(プレビューフレーム左上を `(0, 0)`、右下を `(1, 1)`)で
19
+ 記録する。表示時に現在の content rect へ写像し直すため、ウィンドウのリサイズと出力比率の
20
+ 変更で座標は変わらない。
21
+ - pointerup 後は従来のグロー・きらめき・600 ms フェードをそのまま再生し、その後段の静的
22
+ ビットマップへ同じ正規化図形を残す。新しい録音セッションの開始時に前セッションの表示を
23
+ クリアし、録音終了では消さない。
24
+ - 描線 canvas は非描画モードで `pointer-events: none` とする。ペンまたは四角モードのドラッグ中
25
+ だけ既存どおり入力面になる。
26
+ - 注釈パネルの「描線を表示」チェックは既定 ON。OFF は残留描線と明示的に再表示した描線を隠し、
27
+ データを削除しない。ON に戻すと保持した正規化座標から即時再描画する。
28
+
29
+ ## 2. 既存セッションの読み出しと再表示
30
+
31
+ `readReviewSessionStrokes({projectRootUri, sessionId})` は
32
+ `review/sessions/<sessionId>/strokes.json` を読み、次を返す。
33
+
34
+ ```jsonc
35
+ {
36
+ "sessionId": "s-0001",
37
+ "strokes": [/* pen / rect。frame と recTStart/recTEnd を保持 */],
38
+ "warnings": []
39
+ }
40
+ ```
41
+
42
+ - `strokes.json` 欠落は `strokes: []` として正常終了する。
43
+ - 配列ルート、または `version: 1` でない `{strokes:[]}` は旧形式として寛容に読む。
44
+ - JSON 破損、未知要素、値域外要素は描線単位で除外して warning に残す。セッション一覧と他の
45
+ 描線を巻き込んで失敗させない。
46
+ - 注釈パネルの各録音済みセッションにある「描線」から再表示できる。表示メッセージには
47
+ `target.tab`(edit URI)と先頭ストロークの `target.recT` を添え、`frame.sourceT/cutIndex` で
48
+ プレビューを同じフレームへシークする。
49
+
50
+ ## 3. compile 後の原本参照
51
+
52
+ review.json の data `version` は 0 のまま据え置く。compile-review-session はペアになった pen / rect
53
+ へ、既存フィールドを変えず次の任意フィールドを追加する。
54
+
55
+ ```jsonc
56
+ "strokeRefs": [{
57
+ "sessionId": "s-0001",
58
+ "strokeId": "st-0001",
59
+ "sessionRef": "s-0001/st-0001"
60
+ }]
61
+ ```
62
+
63
+ - `strokeRefs` は `null`、省略、または 1 件以上の配列。欠落は従来データとして正常である。
64
+ - pen は従来どおり最大 100 点の `strokes[].points` も埋め込み、`strokeRefs` から無加工の原本へ
65
+ 戻れる。rect は従来どおり `targetKind:"region"` + `region.box` を埋め込み、同じ `strokeRefs`
66
+ から `strokes.json` 内の rect 原本へ戻れる。
67
+ - address-review の一覧は `strokeRefs` を
68
+ `review/sessions/<sessionId>/strokes.json#<strokeId>` として表示する。
69
+
70
+ ## 4. 互換性
71
+
72
+ - 追加フィールドのみを使い、既存フィールドの削除・意味変更・data version bump は行わない。
73
+ - 読み手は未知フィールドを保持し、任意フィールドの欠落を旧データとして扱い、既知より大きい
74
+ data version を推測変換しない。
75
+ - `packages/preview-server` は本契約の対象外であり、WebUI の表示挙動は変更しない。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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. Run them from a full AKARI Video app installation (normally ~/.akari/app) or a monorepo checkout.]",
5
5
  "type": "module",
6
6
  "files": [
@@ -9,6 +9,7 @@
9
9
  |---|---|
10
10
  | `report-helper.mjs` | `127.0.0.1` のみにバインドするローカル HTTP ヘルパー。`report.html` の配信・`decisions.json` の read/write・commit を仲介する |
11
11
  | `report-template.html` | data 属性でカードを宣言する report.html の雛形。単選・真偽チェック・整数スライダ・配列型の複数選択に対応する。カード種別のハードコードなし。ヘルパー経由なら操作可能、`file://` 直開き等で fetch 失敗時は全操作ボタンが disabled になり案内文言を出す(安全劣化) |
12
+ | `render-research-plan-report.mjs` | `research-plan.json` から固定 5 面の自己完結 HTML を生成する。絵コンテ面は画像 / 文字プレースホルダーのカード面と、主軸 + カットアウェイの読み取り専用 SVG 構造面を切り替えられる |
12
13
  | `examples/report.html` + `examples/report.html.decisions.json` | 動作サンプル一式(既存 4 カード + `direction`)。AI 推奨を既定値に入れた `decisions.json` 雛形付き |
13
14
  | `test/direction-card.test.mjs` | ローカル Chrome を headless 起動して、演出カードの保存と既存 4 カードの非退行を実測するテスト |
14
15
 
@@ -54,6 +55,17 @@ curl -X POST http://127.0.0.1:8791/api/commit
54
55
  npm test --workspace @akari-video/decision-cards
55
56
  ```
56
57
 
58
+ ### research-plan のビジュアル絵コンテ
59
+
60
+ ```sh
61
+ node packages/decision-cards/render-research-plan-report.mjs \
62
+ planning/research-plan.json \
63
+ planning/research-plan-report.html
64
+ node packages/decision-cards/report-helper.mjs planning/research-plan-report.html
65
+ ```
66
+
67
+ 画像は生成時に data URI へ埋め込むため、出力 HTML は外部依存を持たない。`image_path` が無い、または画像を読めないショットは `shot_type` + `description` のプレースホルダーへ安全に劣化する。旧形式に `sequence` / `cutaway_of` が無い場合も生成は成功し、構造面だけが「構造情報なし」になる。
68
+
57
69
  ## edit-plan スキルからの参照方法
58
70
 
59
71
  公開リポの `.claude/skills/edit-plan/` は本パッケージを以下のように参照する想定:
@@ -2,7 +2,7 @@
2
2
  "name": "@akari-video/decision-cards",
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
- "description": "決定カード v0 ランタイム。report-helper(ローカル HTTP サーバ)+ report-template(カード付きレポート HTML の雛形)+ 動作サンプル。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: report-helper.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Run them from a full AKARI Video app installation (normally ~/.akari/app) or a monorepo checkout.]",
5
+ "description": "決定カード v0 ランタイム。report-helper(ローカル HTTP サーバ)+ report-template(カード付きレポート HTML の雛形)+ 動作サンプル。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: report-helper.mjs is reference-only; render-research-plan-report.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Run them from a full AKARI Video app installation (normally ~/.akari/app) or a monorepo checkout.]",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "start": "node report-helper.mjs",
@@ -11,7 +11,8 @@
11
11
  "akariVideoVendor": {
12
12
  "execution": "reference-only",
13
13
  "omittedBin": {
14
- "decision-cards-helper": "report-helper.mjs"
14
+ "decision-cards-helper": "report-helper.mjs",
15
+ "research-plan-report": "render-research-plan-report.mjs"
15
16
  },
16
17
  "guidance": "These CLI entrypoints are not included in the akari-video npm package. Run them from a full AKARI Video app installation (normally ~/.akari/app) or a monorepo checkout."
17
18
  }