akari-video 0.1.12 → 0.1.13

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 (27) hide show
  1. package/package.json +1 -1
  2. package/src/cli.mjs +43 -10
  3. package/src/messages.mjs +55 -8
  4. package/src/self-update.mjs +4 -0
  5. package/src/status-command.mjs +10 -2
  6. package/src/update-check.mjs +49 -10
  7. package/vendor/docs/contract-2026-07-17-data-contract-versioning.md +6 -0
  8. package/vendor/packages/akari-launcher/package.json +1 -1
  9. package/vendor/packages/edit-lint/src/edit-lint.mjs +153 -332
  10. package/vendor/packages/edit-store/lib/edit-store.d.ts +13 -4
  11. package/vendor/packages/edit-store/lib/edit-store.js +62 -45
  12. package/vendor/packages/edit-store/lib/edit-v2.d.ts +45 -4
  13. package/vendor/packages/edit-store/lib/edit-v2.js +72 -1
  14. package/vendor/packages/edit-store/lib/internal-model.d.ts +9 -0
  15. package/vendor/packages/edit-store/lib/internal-model.js +381 -41
  16. package/vendor/packages/edit-store/lib/migrate/index.js +225 -18
  17. package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +5 -1
  18. package/vendor/packages/edit-store/lib/retime.js +14 -0
  19. package/vendor/packages/schemas/edit.schema.json +53 -3
  20. package/vendor/packages/schemas/examples/edit-v2-audio-track-valid/edit.json +56 -0
  21. package/vendor/packages/schemas/examples/edit-v2-valid/edit.json +38 -2
  22. package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +106 -5
  23. package/vendor/skills/edit-plan/SKILL.md +1 -1
  24. package/vendor/skills/edit-plan/beat-sync.md +1 -1
  25. package/vendor/skills/edit-plan/beats.md +1 -1
  26. package/vendor/skills/edit-plan/emphasis-detection.md +1 -1
  27. package/vendor/skills/edit-plan/execution.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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 { describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
9
+ import { 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,11 @@ import {
14
14
  compareVersions,
15
15
  isValidFeedShape,
16
16
  readCacheSync,
17
+ readInstalledAppVersion,
17
18
  readOwnVersion,
18
19
  recordDismissalSync,
19
20
  resolveCachePath,
21
+ resolveInstalledVersionInfo,
20
22
  triggerBackgroundRefresh
21
23
  } from './update-check.mjs';
22
24
  import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
@@ -41,7 +43,8 @@ export async function run(args, options = {}) {
41
43
  const spawnOpencode = options.spawnOpencode ?? defaultSpawnOpencode;
42
44
  const env = options.env ?? process.env;
43
45
  const platform = options.platform ?? process.platform;
44
- const currentVersion = options.currentVersion ?? readOwnVersion();
46
+ const versionInfo = resolveCommandVersionInfo(options, env);
47
+ const currentVersion = versionInfo.currentVersion;
45
48
  const now = options.now ?? new Date();
46
49
 
47
50
  // --opencode / --claude / --claudecode / --yes / --here フラグを解析
@@ -92,13 +95,13 @@ export async function run(args, options = {}) {
92
95
  } catch (error) {
93
96
  log(`接続確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
94
97
  }
95
- log(describeVersionStatus(currentVersion, readCacheSync(resolveCachePath(env))));
98
+ log(describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env))));
96
99
  }
97
100
 
98
101
  // 新版通知(契約 §4-1): キャッシュの読み比較のみ・ネットワークには一切触れない
99
102
  // (起動をブロックしない)。fetch は detached な子プロセスへ切り離し、
100
103
  // 結果は次回セッションで効く。
101
- const updateNotice = formatUpdateNotice((options.checkUpdate ?? checkForUpdateSync)({ currentVersion, env }));
104
+ const updateNotice = formatUpdateNotice((options.checkUpdate ?? checkForUpdateSync)({ currentVersion, versionInfo, env }));
102
105
  if (updateNotice) {
103
106
  log(updateNotice);
104
107
  }
@@ -178,22 +181,26 @@ function defaultSpawnOpencode(opencodePath, args, projectRoot) {
178
181
  /**
179
182
  * `akari update`: 新版があり、かつ自己更新の対象(app 経由実行 + フィードに
180
183
  * `components.app` がある)なら DL → sha256 検証 → 適用まで実行する(契約 §11。
184
+ * `app/.akari-install-ref` がある管理インストールは、npm 側 CLI から実行した場合も対象。
181
185
  * 「update は明示操作」なので U2 の沈黙原則は適用せず、失敗は必ず表示する)。
182
186
  * 対象外(npm グローバル / git checkout・旧フィード)のときは従来どおり
183
187
  * **案内するだけ**(自動実行はしない — 契約 §4-1)に縮退する。`--dismiss` は
184
188
  * 自己更新を試みず、キャッシュに載っている最新版の通知を今後出さないよう記録するだけ
185
- * (既存挙動を維持)。`--rollback` は直前 1 世代(`~/.akari/app-previous/`)へ戻す。
189
+ * (既存挙動を維持)。`--force` は同じ版の本体も再導入し、`--rollback` は直前 1 世代
190
+ * (`~/.akari/app-previous/`)へ戻す。
186
191
  * ネットワークに触れるのは自己更新の DL 区間のみ — フィード自体は既存キャッシュ由来
187
192
  * (最新情報は `akari` 起動時のバックグラウンド fetch で更新される)。
188
193
  */
189
194
  export async function runUpdateCommand(args, options = {}) {
190
195
  const log = options.log ?? ((line) => console.log(line));
191
196
  const env = options.env ?? process.env;
192
- const currentVersion = options.currentVersion ?? readOwnVersion();
197
+ const versionInfo = resolveCommandVersionInfo(options, env);
198
+ const currentVersion = versionInfo.currentVersion;
193
199
  const cachePath = resolveCachePath(env);
194
200
  const cache = readCacheSync(cachePath);
195
201
  const dismissRequested = args.includes('--dismiss');
196
202
  const rollbackRequested = args.includes('--rollback');
203
+ const forceRequested = args.includes('--force');
197
204
 
198
205
  if (rollbackRequested) {
199
206
  return (options.rollbackSelfUpdate ?? rollbackSelfUpdate)({ env, log });
@@ -206,7 +213,7 @@ export async function runUpdateCommand(args, options = {}) {
206
213
  dismissed = true;
207
214
  }
208
215
  const finalCache = dismissed ? readCacheSync(cachePath) : cache;
209
- for (const line of describeUpdateCommand({ currentVersion, cache: finalCache, dismissed })) {
216
+ for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache: finalCache, dismissed })) {
210
217
  log(line);
211
218
  }
212
219
  return { exitCode: 0 };
@@ -214,18 +221,28 @@ export async function runUpdateCommand(args, options = {}) {
214
221
 
215
222
  const feed = cache?.feed;
216
223
  const updateAvailable = isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) > 0;
217
- const selfUpdateEligible = updateAvailable
224
+ const reinstallRequested = forceRequested && isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) >= 0;
225
+ const hasManagedApp = !!readInstalledAppVersion(env);
226
+ const selfUpdateEligible = (updateAvailable || reinstallRequested)
218
227
  && !!feed.components?.app?.url
219
228
  && !!feed.components?.app?.sha256
220
- && (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot });
229
+ && (hasManagedApp || (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot }));
221
230
 
222
231
  if (!selfUpdateEligible) {
223
- for (const line of describeUpdateCommand({ currentVersion, cache, dismissed: false })) {
232
+ for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed: false })) {
224
233
  log(line);
225
234
  }
226
235
  return { exitCode: 0 };
227
236
  }
228
237
 
238
+ for (const line of describeInstalledVersions(versionInfo)) {
239
+ log(line);
240
+ }
241
+ log(`最新バージョン: v${feed.product}`);
242
+ if (forceRequested) {
243
+ log(`--force: 本体 v${currentVersion} を入れ直します。`);
244
+ }
245
+
229
246
  return (options.applySelfUpdate ?? applySelfUpdate)({
230
247
  env,
231
248
  feed,
@@ -234,3 +251,19 @@ export async function runUpdateCommand(args, options = {}) {
234
251
  runNpmInstall: options.runNpmInstall
235
252
  });
236
253
  }
254
+
255
+ function resolveCommandVersionInfo(options, env) {
256
+ // 既存テスト/埋め込み利用の currentVersion 注入は CLI 版注入としても扱う。
257
+ const cliVersion = options.cliVersion ?? options.currentVersion ?? readOwnVersion();
258
+ if (options.currentVersion !== undefined) {
259
+ const appVersion = options.appVersion ?? null;
260
+ return {
261
+ cliVersion,
262
+ appVersion,
263
+ currentVersion: options.currentVersion,
264
+ source: appVersion ? 'install-ref' : 'cli-fallback',
265
+ mismatch: appVersion !== null && compareVersions(cliVersion, appVersion) !== 0
266
+ };
267
+ }
268
+ return resolveInstalledVersionInfo({ env, cliVersion });
269
+ }
package/src/messages.mjs CHANGED
@@ -59,18 +59,64 @@ export function opencodeMissingGuidance() {
59
59
  */
60
60
  export function formatUpdateNotice(status) {
61
61
  if (!status?.available) {
62
+ if (status?.mismatch) {
63
+ return formatVersionMismatch(status);
64
+ }
62
65
  return null;
63
66
  }
64
- return `⬆ AKARI Video v${status.latestVersion}${channelSuffix(status.channel)}があります(現在 v${status.currentVersion})→ 詳細: akari update`;
67
+ const current = status.mismatch
68
+ ? `CLI v${status.cliVersion} / 本体 v${status.appVersion} → ${versionRelationLabel(status)}`
69
+ : `現在 v${status.currentVersion}`;
70
+ return `⬆ AKARI Video v${status.latestVersion}${channelSuffix(status.channel)}があります(${current})→ 詳細: akari update`;
65
71
  }
66
72
 
67
73
  /** `akari doctor` 系出力に足す 1 行(現在版 + フィード取得状態)。 */
68
- export function describeVersionStatus(currentVersion, cache) {
74
+ export function describeVersionStatus(versionOrInfo, cache) {
75
+ if (typeof versionOrInfo === 'string') {
76
+ if (!cache?.feed) {
77
+ return `バージョン: v${versionOrInfo}(更新フィード: 未取得)`;
78
+ }
79
+ const fetchedAt = typeof cache.fetched_at === 'string' ? cache.fetched_at : '不明';
80
+ return `バージョン: v${versionOrInfo}(更新フィード: 取得済み・${fetchedAt} 時点)`;
81
+ }
82
+ const info = normalizeVersionInfo(versionOrInfo);
83
+ const installed = describeInstalledVersions(info).join(' / ');
69
84
  if (!cache?.feed) {
70
- return `バージョン: v${currentVersion}(更新フィード: 未取得)`;
85
+ return `${installed}(更新フィード: 未取得)`;
71
86
  }
72
87
  const fetchedAt = typeof cache.fetched_at === 'string' ? cache.fetched_at : '不明';
73
- return `バージョン: v${currentVersion}(更新フィード: 取得済み・${fetchedAt} 時点)`;
88
+ return `${installed}(更新フィード: 取得済み・${fetchedAt} 時点)`;
89
+ }
90
+
91
+ export function describeInstalledVersions(versionOrInfo) {
92
+ const info = normalizeVersionInfo(versionOrInfo);
93
+ if (!info.appVersion) {
94
+ return [
95
+ `現在のバージョン: v${info.currentVersion}`,
96
+ `CLI バージョン: v${info.cliVersion}`,
97
+ `本体バージョン: 未記録(更新判定は CLI v${info.currentVersion} へフォールバック)`
98
+ ];
99
+ }
100
+ const lines = [`CLI バージョン: v${info.cliVersion}`, `本体バージョン: v${info.appVersion}(更新判定の基準)`];
101
+ if (info.mismatch) {
102
+ lines.push(`版のずれ: CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${versionRelationLabel(info)}`);
103
+ }
104
+ return lines;
105
+ }
106
+
107
+ function normalizeVersionInfo(value) {
108
+ if (typeof value === 'string') {
109
+ return { cliVersion: value, appVersion: null, currentVersion: value, mismatch: false };
110
+ }
111
+ return value;
112
+ }
113
+
114
+ function versionRelationLabel(info) {
115
+ return compareVersions(info.appVersion, info.cliVersion) < 0 ? '本体が古い' : 'CLI と本体の版が不一致';
116
+ }
117
+
118
+ function formatVersionMismatch(info) {
119
+ return `⚠ CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${versionRelationLabel(info)}。\`akari update\` で本体を更新してください。`;
74
120
  }
75
121
 
76
122
  /**
@@ -107,8 +153,9 @@ export function creatorRootPromptText(defaultPath) {
107
153
  * `akari update` の出力本文(複数行)。フィード未取得・最新・新版ありで案内が変わる。
108
154
  * `dismissed` は今回の実行で dismiss 記録を書いたかどうか(表示文言の切り替えのみに使う)。
109
155
  */
110
- export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
111
- const lines = [`現在のバージョン: v${currentVersion}`];
156
+ export function describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed }) {
157
+ const info = versionInfo ?? normalizeVersionInfo(currentVersion);
158
+ const lines = versionInfo ? describeInstalledVersions(info) : [`現在のバージョン: v${currentVersion}`];
112
159
  const feed = cache?.feed;
113
160
  if (!feed) {
114
161
  lines.push('最新情報をまだ取得できていません(オフライン、または初回起動直後の可能性があります)。');
@@ -121,7 +168,7 @@ export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
121
168
  lines.push(`リリースノート: ${feed.notes_url}`);
122
169
  }
123
170
 
124
- if (compareVersions(feed.product, currentVersion) <= 0) {
171
+ if (compareVersions(feed.product, info.currentVersion) <= 0) {
125
172
  lines.push('お使いのバージョンは最新です。');
126
173
  return lines;
127
174
  }
@@ -222,7 +269,7 @@ export function describeCliHelp() {
222
269
  ' (引数なし) プロジェクトを開いて AI エージェントを起動(未作成なら自動作成)',
223
270
  ' store connect アカウント連携(無料の素材パックと購入済み素材が使えるようになる)',
224
271
  ' sounds 公式音源ライブラリを一括ダウンロード(無料)',
225
- ' update 更新を確認する',
272
+ ' update [--force] 更新を確認する(--force で本体を入れ直す)',
226
273
  ' status 接続状態を確認する',
227
274
  ' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
228
275
  '',
@@ -378,6 +378,10 @@ export function rollbackSelfUpdate({ env = process.env, log = () => {} } = {}) {
378
378
  }
379
379
 
380
380
  const version = readVersionAt(appDir);
381
+ if (version) {
382
+ // install-ref が無い古い app-previous へ戻した場合も、以後の更新判定を本体版基準に保つ。
383
+ writeFileSync(join(appDir, '.akari-install-ref'), `v${version}\n`, 'utf8');
384
+ }
381
385
  log(version ? `v${version} へロールバックしました` : 'ロールバックしました');
382
386
  return { exitCode: 0, rolledBack: true, version };
383
387
  }
@@ -8,6 +8,8 @@ import {
8
8
  serializeWorkspaceStatus,
9
9
  formatWorkspaceStatusSummary,
10
10
  } from "./status-core/status.mjs";
11
+ import { describeVersionStatus } from "./messages.mjs";
12
+ import { readCacheSync, readOwnVersion, resolveCachePath, resolveInstalledVersionInfo } from "./update-check.mjs";
11
13
 
12
14
  export async function runStatusCommand(argv, options = {}) {
13
15
  const log = options.log ?? ((line) => process.stdout.write(line));
@@ -19,18 +21,24 @@ export async function runStatusCommand(argv, options = {}) {
19
21
  error(cause instanceof Error ? cause.message : String(cause));
20
22
  return { exitCode: 2 };
21
23
  }
24
+ const env = options.env ?? process.env;
25
+ const versionInfo = options.versionInfo ?? resolveInstalledVersionInfo({
26
+ env,
27
+ cliVersion: options.cliVersion ?? readOwnVersion(),
28
+ });
29
+ const versionLine = describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env)));
22
30
  if (detectStatusScope(parsed.projectRoot) === "workspace") {
23
31
  const workspace = resolveWorkspaceStatus(parsed.projectRoot);
24
32
  // fail-safe: a broken root.json falls through to the unchanged project-scope path below.
25
33
  if (workspace) {
26
- log(parsed.json ? serializeWorkspaceStatus(workspace) : `${formatWorkspaceStatusSummary(workspace)}\n`);
34
+ log(parsed.json ? serializeWorkspaceStatus(workspace) : `${versionLine}\n${formatWorkspaceStatusSummary(workspace)}\n`);
27
35
  return { exitCode: 0, workspace };
28
36
  }
29
37
  }
30
38
  const status = parsed.full
31
39
  ? await resolveFullProjectStatus(parsed.projectRoot)
32
40
  : resolveProjectStatus(parsed.projectRoot, { mode: "fast" });
33
- log(parsed.json ? serializeStatus(status) : `${formatStatusOutput(status)}\n`);
41
+ log(parsed.json ? serializeStatus(status) : `${versionLine}\n${formatStatusOutput(status)}\n`);
34
42
  return { exitCode: status.state_health === "inconclusive" ? 1 : 0, status };
35
43
  }
36
44
 
@@ -55,12 +55,43 @@ export function resolveCachePath(env = process.env) {
55
55
  return join(resolveAkariHome(env), 'update-check.json');
56
56
  }
57
57
 
58
+ export function resolveInstallRefPath(env = process.env) {
59
+ return join(resolveAkariHome(env), 'app', '.akari-install-ref');
60
+ }
61
+
58
62
  /** launcher 自身の package.json version(D3 裁定により現状はプロダクト版と一致)。 */
59
63
  export function readOwnVersion() {
60
64
  const raw = readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8');
61
65
  return JSON.parse(raw).version;
62
66
  }
63
67
 
68
+ /**
69
+ * 実際に render-cut / edit-lint を実行する本体の導入版。
70
+ * install.sh / self-update.mjs が書く `vX.Y.Z` を読み、無い・壊れている場合は null。
71
+ */
72
+ 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
+ }
80
+ }
81
+
82
+ /** 更新判定に使う版と、CLI / 本体のずれを一度に解決する。 */
83
+ export function resolveInstalledVersionInfo({ env = process.env, cliVersion = readOwnVersion() } = {}) {
84
+ const installedAppVersion = readInstalledAppVersion(env);
85
+ const currentVersion = installedAppVersion ?? cliVersion;
86
+ return {
87
+ cliVersion,
88
+ appVersion: installedAppVersion,
89
+ currentVersion,
90
+ source: installedAppVersion ? 'install-ref' : 'cli-fallback',
91
+ mismatch: installedAppVersion !== null && compareVersions(cliVersion, installedAppVersion) !== 0
92
+ };
93
+ }
94
+
64
95
  /** "major.minor.patch" の先頭 3 要素だけを数値比較する(prerelease 考慮不要 — 契約 D4: stable のみ)。 */
65
96
  export function compareVersions(a, b) {
66
97
  const pa = parseVersionTriplet(a);
@@ -104,23 +135,25 @@ function writeCacheSync(cachePath, cache) {
104
135
  /**
105
136
  * キャッシュと現在版から、新版通知を出すべきかを判定する(同期・純粋関数・I/O なし)。
106
137
  */
107
- export function evaluateUpdateStatus({ currentVersion, cache }) {
138
+ export function evaluateUpdateStatus({ currentVersion, cache, cliVersion = currentVersion, appVersion = null, source = 'cli-fallback', mismatch = false }) {
139
+ const versionDetails = { currentVersion, cliVersion, appVersion, source, mismatch };
108
140
  const feed = cache?.feed;
109
141
  if (!isValidFeedShape(feed)) {
110
- return { available: false };
142
+ return { available: false, ...versionDetails };
111
143
  }
112
144
  const latest = feed.product;
113
145
  if (compareVersions(latest, currentVersion) <= 0) {
114
- return { available: false };
146
+ return { available: false, latestVersion: latest, ...versionDetails };
115
147
  }
116
148
  const dismissedAt = cache?.dismissed?.[latest];
117
- if (dismissedAt) {
118
- return { available: false, dismissed: true, latestVersion: latest };
149
+ // CLI と本体の取り残しは release 通知の dismiss より強い。黙って古い本体を使わせない。
150
+ if (dismissedAt && !mismatch) {
151
+ return { available: false, dismissed: true, latestVersion: latest, ...versionDetails };
119
152
  }
120
153
  return {
121
154
  available: true,
122
155
  latestVersion: latest,
123
- currentVersion,
156
+ ...versionDetails,
124
157
  channel: typeof feed.channel === 'string' ? feed.channel : undefined,
125
158
  notesUrl: typeof feed.notes_url === 'string' ? feed.notes_url : undefined,
126
159
  cli: feed.components?.cli
@@ -131,9 +164,13 @@ export function evaluateUpdateStatus({ currentVersion, cache }) {
131
164
  * `akari` 起動時の同期チェック。ファイルの読み比較のみ(ネットワーク I/O 無し)。
132
165
  * fetch 関数への参照すら持たない — 「起動をブロックしない」を構造で保証する。
133
166
  */
134
- export function checkForUpdateSync({ currentVersion, env = process.env } = {}) {
167
+ export function checkForUpdateSync({ currentVersion, versionInfo, env = process.env } = {}) {
135
168
  const cache = readCacheSync(resolveCachePath(env));
136
- return evaluateUpdateStatus({ currentVersion, cache });
169
+ const versions = versionInfo
170
+ ?? (currentVersion === undefined
171
+ ? resolveInstalledVersionInfo({ env })
172
+ : { cliVersion: currentVersion, appVersion: null, currentVersion, source: 'cli-fallback', mismatch: false });
173
+ return evaluateUpdateStatus({ ...versions, currentVersion: currentVersion ?? versions.currentVersion, cache });
137
174
  }
138
175
 
139
176
  /** 「この版の通知を今後出さない」を記録する(同期・ローカル I/O のみ)。 */
@@ -157,6 +194,7 @@ export function recordDismissalSync({ version, env = process.env, now = new Date
157
194
  *
158
195
  * `launcherRoot` はテスト用の注入口(`isRunningFromAppDir` へそのまま渡す。
159
196
  * `runUpdateCommand` の `options.launcherRoot` と同じ流儀)。
197
+ * app 外から動く npm CLI でも install-ref があれば管理本体を staging 対象にする。
160
198
  */
161
199
  export async function maybeStageInBackground({ env = process.env, feed, fetchImpl = globalThis.fetch, timeoutMs, extract, launcherRoot } = {}) {
162
200
  if (env.AKARI_NO_AUTO_UPDATE === '1') {
@@ -166,10 +204,11 @@ export async function maybeStageInBackground({ env = process.env, feed, fetchImp
166
204
  if (!appComponent?.url || !appComponent?.sha256) {
167
205
  return null;
168
206
  }
169
- if (compareVersions(feed.product, readOwnVersion()) <= 0) {
207
+ if (compareVersions(feed.product, resolveInstalledVersionInfo({ env }).currentVersion) <= 0) {
170
208
  return null;
171
209
  }
172
- if (!isRunningFromAppDir({ env, launcherRoot })) {
210
+ // npm 側 CLI と本体が分離していても、install-ref があれば管理対象の本体を更新できる。
211
+ if (!isRunningFromAppDir({ env, launcherRoot }) && !readInstalledAppVersion(env)) {
173
212
  return null;
174
213
  }
175
214
  // 沈黙原則(U2 を継承): staging の失敗メッセージはどこにも表示しない(log は no-op)。
@@ -91,3 +91,9 @@ edit.json の `version` は変わらないし、edit.json が `version: 1` に
91
91
  - 本体から外した後のエラー文言は次で固定する。
92
92
 
93
93
  > このプロジェクトは古い形式です。`npx akari-migrate@<版> <dir>` で変換してから開いてください。
94
+
95
+ ### 6.1 音声トラック化(2026-08-21・凍結前の最後の追加)
96
+
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
+
99
+ これは凍結前に許可された最後の機能追加である。今後この変換器へさらに変換能力を足さず、§6 の期限どおり本体から分離・削除する。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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": [