gsd-pi 2.45.0-dev.6b9da3e → 2.45.0-dev.e0ee972

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 (104) hide show
  1. package/dist/help-text.js +1 -1
  2. package/dist/loader.js +34 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +16 -10
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
  6. package/dist/resources/extensions/gsd/auto.js +4 -5
  7. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +13 -12
  8. package/dist/resources/extensions/gsd/db-writer.js +9 -9
  9. package/dist/resources/extensions/gsd/doctor-checks.js +1 -1
  10. package/dist/resources/extensions/gsd/doctor.js +2 -2
  11. package/dist/resources/extensions/gsd/gsd-db.js +5 -1
  12. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  13. package/dist/resources/extensions/gsd/preferences.js +8 -4
  14. package/dist/resources/extensions/gsd/workflow-logger.js +138 -0
  15. package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
  16. package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
  17. package/dist/resources/extensions/voice/index.js +11 -16
  18. package/dist/resources/extensions/voice/linux-ready.js +67 -0
  19. package/dist/web/standalone/.next/BUILD_ID +1 -1
  20. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  21. package/dist/web/standalone/.next/build-manifest.json +2 -2
  22. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  23. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  24. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.html +1 -1
  40. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  47. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  48. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  49. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  50. package/package.json +1 -1
  51. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
  53. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  55. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  57. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
  58. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
  60. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
  62. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
  63. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
  64. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
  65. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
  66. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
  68. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
  70. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
  72. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
  73. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
  74. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  75. package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
  76. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
  77. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
  78. package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
  79. package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
  80. package/src/resources/extensions/gsd/auto/phases.ts +16 -12
  81. package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
  82. package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
  83. package/src/resources/extensions/gsd/auto.ts +3 -3
  84. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +13 -12
  85. package/src/resources/extensions/gsd/db-writer.ts +9 -17
  86. package/src/resources/extensions/gsd/doctor-checks.ts +1 -1
  87. package/src/resources/extensions/gsd/doctor.ts +2 -2
  88. package/src/resources/extensions/gsd/gsd-db.ts +5 -1
  89. package/src/resources/extensions/gsd/journal.ts +6 -1
  90. package/src/resources/extensions/gsd/preferences-types.ts +2 -2
  91. package/src/resources/extensions/gsd/preferences.ts +7 -3
  92. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
  93. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
  94. package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
  95. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
  96. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
  97. package/src/resources/extensions/gsd/workflow-logger.ts +193 -0
  98. package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
  99. package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
  100. package/src/resources/extensions/voice/index.ts +11 -21
  101. package/src/resources/extensions/voice/linux-ready.ts +87 -0
  102. package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
  103. /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → dFMji9G1LZ-Tv36el9pRT}/_buildManifest.js +0 -0
  104. /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → dFMji9G1LZ-Tv36el9pRT}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@ import { importExtensionModule, loadExtensions, } from "./extensions/index.js";
8
8
  function toScope(local) {
9
9
  return local ? "project" : "user";
10
10
  }
11
- function readManifestRuntimeDeps(dir) {
11
+ export function readManifestRuntimeDeps(dir) {
12
12
  const manifestPath = join(dir, "extension-manifest.json");
13
13
  if (!existsSync(manifestPath))
14
14
  return [];
@@ -20,7 +20,7 @@ function readManifestRuntimeDeps(dir) {
20
20
  return [];
21
21
  }
22
22
  }
23
- function collectRuntimeDependencies(installedPath, entryPaths) {
23
+ export function collectRuntimeDependencies(installedPath, entryPaths) {
24
24
  const deps = new Set();
25
25
  const candidateDirs = new Set([installedPath, ...entryPaths.map((entryPath) => dirname(entryPath))]);
26
26
  for (const dir of candidateDirs) {
@@ -30,7 +30,7 @@ function collectRuntimeDependencies(installedPath, entryPaths) {
30
30
  }
31
31
  return Array.from(deps);
32
32
  }
33
- function verifyRuntimeDependencies(runtimeDeps, source, appName) {
33
+ export function verifyRuntimeDependencies(runtimeDeps, source, appName) {
34
34
  const missing = [];
35
35
  for (const dep of runtimeDeps) {
36
36
  const result = spawnSync(dep, ["--version"], { encoding: "utf-8", timeout: 5000 });
@@ -43,7 +43,7 @@ function verifyRuntimeDependencies(runtimeDeps, source, appName) {
43
43
  throw new Error(`Missing runtime dependencies: ${missing.join(", ")}.\n` +
44
44
  `Install them and retry: ${appName} install ${source}`);
45
45
  }
46
- function resolveLocalSourcePath(source, cwd) {
46
+ export function resolveLocalSourcePath(source, cwd) {
47
47
  const trimmed = source.trim();
48
48
  if (!trimmed)
49
49
  return undefined;
@@ -120,9 +120,14 @@ async function runHookSafe(hook, context, stderr) {
120
120
  function getLegacyExportCandidates(phase) {
121
121
  return [phase];
122
122
  }
123
+ const _legacyModuleCache = new Map();
123
124
  async function runLegacyExportHook(entryPath, phase, context) {
124
125
  try {
125
- const module = await importExtensionModule(import.meta.url, pathToFileURL(entryPath).href);
126
+ let module = _legacyModuleCache.get(entryPath);
127
+ if (!module) {
128
+ module = await importExtensionModule(import.meta.url, pathToFileURL(entryPath).href);
129
+ _legacyModuleCache.set(entryPath, module);
130
+ }
126
131
  for (const exportName of getLegacyExportCandidates(phase)) {
127
132
  const candidate = module[exportName];
128
133
  if (typeof candidate === "function") {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle-hooks.js","sourceRoot":"","sources":["../../src/core/lifecycle-hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { parseGitUrl } from \"../utils/git.js\";\nimport {\n\timportExtensionModule,\n\tloadExtensions,\n\ttype LifecycleHookContext,\n\ttype LifecycleHookMap,\n\ttype LifecycleHookHandler,\n\ttype LifecycleHookPhase,\n\ttype LifecycleHookScope,\n} from \"./extensions/index.js\";\nimport type { DefaultPackageManager } from \"./package-manager.js\";\n\ninterface ExtensionManifest {\n\tdependencies?: {\n\t\truntime?: string[];\n\t};\n}\n\nexport interface PackageLifecycleHooksOptions {\n\tsource: string;\n\tlocal: boolean;\n\tcwd: string;\n\tagentDir: string;\n\tappName: string;\n\tpackageManager: DefaultPackageManager;\n\tstdout: NodeJS.WriteStream;\n\tstderr: NodeJS.WriteStream;\n}\n\nexport type LifecycleHooksTarget = \"source\" | \"installed\";\n\nexport interface PrepareLifecycleHooksOptions {\n\tverifyRuntimeDependencies?: boolean;\n}\n\nexport interface LifecycleHooksRunResult {\n\tphase: LifecycleHookPhase;\n\thooksRun: number;\n\thookErrors: number;\n\tlegacyHooksRun: number;\n\tentryPathCount: number;\n\tskipped: boolean;\n}\n\ninterface LoadedLifecycleHooks {\n\tsource: string;\n\tscope: LifecycleHookScope;\n\tinstalledPath?: string;\n\tcwd: string;\n\tstdout: NodeJS.WriteStream;\n\tstderr: NodeJS.WriteStream;\n\tentryPaths: string[];\n\thooksByPath: Map<string, LifecycleHookMap>;\n}\n\nfunction toScope(local: boolean): LifecycleHookScope {\n\treturn local ? \"project\" : \"user\";\n}\n\nfunction readManifestRuntimeDeps(dir: string): string[] {\n\tconst manifestPath = join(dir, \"extension-manifest.json\");\n\tif (!existsSync(manifestPath)) return [];\n\ttry {\n\t\tconst manifest = JSON.parse(readFileSync(manifestPath, \"utf-8\")) as ExtensionManifest;\n\t\treturn manifest.dependencies?.runtime?.filter((dep): dep is string => typeof dep === \"string\") ?? [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction collectRuntimeDependencies(installedPath: string, entryPaths: string[]): string[] {\n\tconst deps = new Set<string>();\n\tconst candidateDirs = new Set<string>([installedPath, ...entryPaths.map((entryPath) => dirname(entryPath))]);\n\tfor (const dir of candidateDirs) {\n\t\tfor (const dep of readManifestRuntimeDeps(dir)) {\n\t\t\tdeps.add(dep);\n\t\t}\n\t}\n\treturn Array.from(deps);\n}\n\nfunction verifyRuntimeDependencies(runtimeDeps: string[], source: string, appName: string): void {\n\tconst missing: string[] = [];\n\tfor (const dep of runtimeDeps) {\n\t\tconst result = spawnSync(dep, [\"--version\"], { encoding: \"utf-8\", timeout: 5000 });\n\t\tif (result.error || result.status !== 0) {\n\t\t\tmissing.push(dep);\n\t\t}\n\t}\n\tif (missing.length === 0) return;\n\tthrow new Error(\n\t\t`Missing runtime dependencies: ${missing.join(\", \")}.\\n` +\n\t\t\t`Install them and retry: ${appName} install ${source}`,\n\t);\n}\n\nfunction resolveLocalSourcePath(source: string, cwd: string): string | undefined {\n\tconst trimmed = source.trim();\n\tif (!trimmed) return undefined;\n\tif (trimmed.startsWith(\"npm:\")) return undefined;\n\tif (parseGitUrl(trimmed)) return undefined;\n\n\tlet normalized = trimmed;\n\tif (normalized === \"~\") {\n\t\tnormalized = homedir();\n\t} else if (normalized.startsWith(\"~/\")) {\n\t\tnormalized = join(homedir(), normalized.slice(2));\n\t}\n\n\tconst absolutePath = resolve(cwd, normalized);\n\treturn existsSync(absolutePath) ? absolutePath : undefined;\n}\n\nasync function resolveEntryPathsFromTarget(\n\toptions: PackageLifecycleHooksOptions,\n\ttarget: LifecycleHooksTarget,\n\tscope: LifecycleHookScope,\n): Promise<{ entryPaths: string[]; installedPath?: string }> {\n\tif (target === \"source\") {\n\t\tconst localSourcePath = resolveLocalSourcePath(options.source, options.cwd);\n\t\tif (!localSourcePath) return { entryPaths: [] };\n\t\tconst resolved = await options.packageManager.resolveExtensionSources([localSourcePath], { local: true });\n\t\tconst entryPaths = resolved.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);\n\t\treturn { entryPaths, installedPath: localSourcePath };\n\t}\n\n\tconst installedPath = options.packageManager.getInstalledPath(options.source, scope);\n\tif (!installedPath) return { entryPaths: [] };\n\tconst resolved = await options.packageManager.resolveExtensionSources([installedPath], { local: true });\n\tconst entryPaths = resolved.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);\n\treturn { entryPaths, installedPath };\n}\n\nexport async function prepareLifecycleHooks(\n\toptions: PackageLifecycleHooksOptions,\n\ttarget: LifecycleHooksTarget,\n\tprepareOptions?: PrepareLifecycleHooksOptions,\n): Promise<LoadedLifecycleHooks | null> {\n\tconst scope = toScope(options.local);\n\tconst { entryPaths, installedPath } = await resolveEntryPathsFromTarget(options, target, scope);\n\tif (entryPaths.length === 0) {\n\t\treturn null;\n\t}\n\n\tif (prepareOptions?.verifyRuntimeDependencies && installedPath) {\n\t\tconst runtimeDeps = collectRuntimeDependencies(installedPath, entryPaths);\n\t\tverifyRuntimeDependencies(runtimeDeps, options.source, options.appName);\n\t}\n\n\tconst loaded = await loadExtensions(entryPaths, options.cwd);\n\tfor (const { path, error } of loaded.errors) {\n\t\toptions.stderr.write(`[lifecycle-hooks] Failed to load extension \"${path}\": ${error}\\n`);\n\t}\n\n\tconst hooksByPath = new Map<string, LifecycleHookMap>();\n\tfor (const extension of loaded.extensions) {\n\t\thooksByPath.set(extension.path, extension.lifecycleHooks);\n\t}\n\n\treturn {\n\t\tsource: options.source,\n\t\tscope,\n\t\tinstalledPath,\n\t\tcwd: options.cwd,\n\t\tstdout: options.stdout,\n\t\tstderr: options.stderr,\n\t\tentryPaths,\n\t\thooksByPath,\n\t};\n}\n\nasync function runHookSafe(\n\thook: LifecycleHookHandler,\n\tcontext: LifecycleHookContext,\n\tstderr: NodeJS.WriteStream,\n): Promise<boolean> {\n\ttry {\n\t\tawait hook(context);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tstderr.write(`[lifecycle-hooks:${context.phase}] Hook failed: ${message}\\n`);\n\t\treturn false;\n\t}\n}\n\nfunction getLegacyExportCandidates(phase: LifecycleHookPhase): string[] {\n\treturn [phase];\n}\n\nasync function runLegacyExportHook(\n\tentryPath: string,\n\tphase: LifecycleHookPhase,\n\tcontext: LifecycleHookContext,\n): Promise<LifecycleHookHandler | null> {\n\ttry {\n\t\tconst module = await importExtensionModule<Record<string, unknown>>(import.meta.url, pathToFileURL(entryPath).href);\n\t\tfor (const exportName of getLegacyExportCandidates(phase)) {\n\t\t\tconst candidate = module[exportName];\n\t\t\tif (typeof candidate === \"function\") {\n\t\t\t\treturn candidate as LifecycleHookHandler;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function runLifecycleHooks(\n\tloaded: LoadedLifecycleHooks | null,\n\tphase: LifecycleHookPhase,\n): Promise<LifecycleHooksRunResult> {\n\tif (!loaded) {\n\t\treturn {\n\t\t\tphase,\n\t\t\thooksRun: 0,\n\t\t\thookErrors: 0,\n\t\t\tlegacyHooksRun: 0,\n\t\t\tentryPathCount: 0,\n\t\t\tskipped: true,\n\t\t};\n\t}\n\n\tconst context: LifecycleHookContext = {\n\t\tphase,\n\t\tsource: loaded.source,\n\t\tinstalledPath: loaded.installedPath,\n\t\tscope: loaded.scope,\n\t\tcwd: loaded.cwd,\n\t\tinteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n\t\tlog: (message) => loaded.stdout.write(`${message}\\n`),\n\t\twarn: (message) => loaded.stderr.write(`${message}\\n`),\n\t\terror: (message) => loaded.stderr.write(`${message}\\n`),\n\t};\n\n\tlet hooksRun = 0;\n\tlet hookErrors = 0;\n\tlet legacyHooksRun = 0;\n\n\tfor (const entryPath of loaded.entryPaths) {\n\t\tconst hookMap = loaded.hooksByPath.get(entryPath);\n\t\tconst registeredHooks = hookMap?.[phase] ?? [];\n\t\tif (registeredHooks.length > 0) {\n\t\t\tfor (const hook of registeredHooks) {\n\t\t\t\thooksRun += 1;\n\t\t\t\tconst ok = await runHookSafe(hook, context, loaded.stderr);\n\t\t\t\tif (!ok) hookErrors += 1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst legacyHook = await runLegacyExportHook(entryPath, phase, context);\n\t\tif (!legacyHook) continue;\n\n\t\tlegacyHooksRun += 1;\n\t\tconst ok = await runHookSafe(legacyHook, context, loaded.stderr);\n\t\tif (!ok) hookErrors += 1;\n\t}\n\n\treturn {\n\t\tphase,\n\t\thooksRun,\n\t\thookErrors,\n\t\tlegacyHooksRun,\n\t\tentryPathCount: loaded.entryPaths.length,\n\t\tskipped: false,\n\t};\n}\n"]}
1
+ {"version":3,"file":"lifecycle-hooks.js","sourceRoot":"","sources":["../../src/core/lifecycle-hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { parseGitUrl } from \"../utils/git.js\";\nimport {\n\timportExtensionModule,\n\tloadExtensions,\n\ttype LifecycleHookContext,\n\ttype LifecycleHookMap,\n\ttype LifecycleHookHandler,\n\ttype LifecycleHookPhase,\n\ttype LifecycleHookScope,\n} from \"./extensions/index.js\";\nimport type { DefaultPackageManager } from \"./package-manager.js\";\n\ninterface ExtensionManifest {\n\tdependencies?: {\n\t\truntime?: string[];\n\t};\n}\n\nexport interface PackageLifecycleHooksOptions {\n\tsource: string;\n\tlocal: boolean;\n\tcwd: string;\n\tagentDir: string;\n\tappName: string;\n\tpackageManager: DefaultPackageManager;\n\tstdout: NodeJS.WriteStream;\n\tstderr: NodeJS.WriteStream;\n}\n\nexport type LifecycleHooksTarget = \"source\" | \"installed\";\n\nexport interface PrepareLifecycleHooksOptions {\n\tverifyRuntimeDependencies?: boolean;\n}\n\nexport interface LifecycleHooksRunResult {\n\tphase: LifecycleHookPhase;\n\thooksRun: number;\n\thookErrors: number;\n\tlegacyHooksRun: number;\n\tentryPathCount: number;\n\tskipped: boolean;\n}\n\ninterface LoadedLifecycleHooks {\n\tsource: string;\n\tscope: LifecycleHookScope;\n\tinstalledPath?: string;\n\tcwd: string;\n\tstdout: NodeJS.WriteStream;\n\tstderr: NodeJS.WriteStream;\n\tentryPaths: string[];\n\thooksByPath: Map<string, LifecycleHookMap>;\n}\n\nfunction toScope(local: boolean): LifecycleHookScope {\n\treturn local ? \"project\" : \"user\";\n}\n\nexport function readManifestRuntimeDeps(dir: string): string[] {\n\tconst manifestPath = join(dir, \"extension-manifest.json\");\n\tif (!existsSync(manifestPath)) return [];\n\ttry {\n\t\tconst manifest = JSON.parse(readFileSync(manifestPath, \"utf-8\")) as ExtensionManifest;\n\t\treturn manifest.dependencies?.runtime?.filter((dep): dep is string => typeof dep === \"string\") ?? [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nexport function collectRuntimeDependencies(installedPath: string, entryPaths: string[]): string[] {\n\tconst deps = new Set<string>();\n\tconst candidateDirs = new Set<string>([installedPath, ...entryPaths.map((entryPath) => dirname(entryPath))]);\n\tfor (const dir of candidateDirs) {\n\t\tfor (const dep of readManifestRuntimeDeps(dir)) {\n\t\t\tdeps.add(dep);\n\t\t}\n\t}\n\treturn Array.from(deps);\n}\n\nexport function verifyRuntimeDependencies(runtimeDeps: string[], source: string, appName: string): void {\n\tconst missing: string[] = [];\n\tfor (const dep of runtimeDeps) {\n\t\tconst result = spawnSync(dep, [\"--version\"], { encoding: \"utf-8\", timeout: 5000 });\n\t\tif (result.error || result.status !== 0) {\n\t\t\tmissing.push(dep);\n\t\t}\n\t}\n\tif (missing.length === 0) return;\n\tthrow new Error(\n\t\t`Missing runtime dependencies: ${missing.join(\", \")}.\\n` +\n\t\t\t`Install them and retry: ${appName} install ${source}`,\n\t);\n}\n\nexport function resolveLocalSourcePath(source: string, cwd: string): string | undefined {\n\tconst trimmed = source.trim();\n\tif (!trimmed) return undefined;\n\tif (trimmed.startsWith(\"npm:\")) return undefined;\n\tif (parseGitUrl(trimmed)) return undefined;\n\n\tlet normalized = trimmed;\n\tif (normalized === \"~\") {\n\t\tnormalized = homedir();\n\t} else if (normalized.startsWith(\"~/\")) {\n\t\tnormalized = join(homedir(), normalized.slice(2));\n\t}\n\n\tconst absolutePath = resolve(cwd, normalized);\n\treturn existsSync(absolutePath) ? absolutePath : undefined;\n}\n\nasync function resolveEntryPathsFromTarget(\n\toptions: PackageLifecycleHooksOptions,\n\ttarget: LifecycleHooksTarget,\n\tscope: LifecycleHookScope,\n): Promise<{ entryPaths: string[]; installedPath?: string }> {\n\tif (target === \"source\") {\n\t\tconst localSourcePath = resolveLocalSourcePath(options.source, options.cwd);\n\t\tif (!localSourcePath) return { entryPaths: [] };\n\t\tconst resolved = await options.packageManager.resolveExtensionSources([localSourcePath], { local: true });\n\t\tconst entryPaths = resolved.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);\n\t\treturn { entryPaths, installedPath: localSourcePath };\n\t}\n\n\tconst installedPath = options.packageManager.getInstalledPath(options.source, scope);\n\tif (!installedPath) return { entryPaths: [] };\n\tconst resolved = await options.packageManager.resolveExtensionSources([installedPath], { local: true });\n\tconst entryPaths = resolved.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);\n\treturn { entryPaths, installedPath };\n}\n\nexport async function prepareLifecycleHooks(\n\toptions: PackageLifecycleHooksOptions,\n\ttarget: LifecycleHooksTarget,\n\tprepareOptions?: PrepareLifecycleHooksOptions,\n): Promise<LoadedLifecycleHooks | null> {\n\tconst scope = toScope(options.local);\n\tconst { entryPaths, installedPath } = await resolveEntryPathsFromTarget(options, target, scope);\n\tif (entryPaths.length === 0) {\n\t\treturn null;\n\t}\n\n\tif (prepareOptions?.verifyRuntimeDependencies && installedPath) {\n\t\tconst runtimeDeps = collectRuntimeDependencies(installedPath, entryPaths);\n\t\tverifyRuntimeDependencies(runtimeDeps, options.source, options.appName);\n\t}\n\n\tconst loaded = await loadExtensions(entryPaths, options.cwd);\n\tfor (const { path, error } of loaded.errors) {\n\t\toptions.stderr.write(`[lifecycle-hooks] Failed to load extension \"${path}\": ${error}\\n`);\n\t}\n\n\tconst hooksByPath = new Map<string, LifecycleHookMap>();\n\tfor (const extension of loaded.extensions) {\n\t\thooksByPath.set(extension.path, extension.lifecycleHooks);\n\t}\n\n\treturn {\n\t\tsource: options.source,\n\t\tscope,\n\t\tinstalledPath,\n\t\tcwd: options.cwd,\n\t\tstdout: options.stdout,\n\t\tstderr: options.stderr,\n\t\tentryPaths,\n\t\thooksByPath,\n\t};\n}\n\nasync function runHookSafe(\n\thook: LifecycleHookHandler,\n\tcontext: LifecycleHookContext,\n\tstderr: NodeJS.WriteStream,\n): Promise<boolean> {\n\ttry {\n\t\tawait hook(context);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tstderr.write(`[lifecycle-hooks:${context.phase}] Hook failed: ${message}\\n`);\n\t\treturn false;\n\t}\n}\n\nfunction getLegacyExportCandidates(phase: LifecycleHookPhase): string[] {\n\treturn [phase];\n}\n\nconst _legacyModuleCache = new Map<string, Record<string, unknown>>();\n\nasync function runLegacyExportHook(\n\tentryPath: string,\n\tphase: LifecycleHookPhase,\n\tcontext: LifecycleHookContext,\n): Promise<LifecycleHookHandler | null> {\n\ttry {\n\t\tlet module = _legacyModuleCache.get(entryPath);\n\t\tif (!module) {\n\t\t\tmodule = await importExtensionModule<Record<string, unknown>>(import.meta.url, pathToFileURL(entryPath).href);\n\t\t\t_legacyModuleCache.set(entryPath, module);\n\t\t}\n\t\tfor (const exportName of getLegacyExportCandidates(phase)) {\n\t\t\tconst candidate = module[exportName];\n\t\t\tif (typeof candidate === \"function\") {\n\t\t\t\treturn candidate as LifecycleHookHandler;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function runLifecycleHooks(\n\tloaded: LoadedLifecycleHooks | null,\n\tphase: LifecycleHookPhase,\n): Promise<LifecycleHooksRunResult> {\n\tif (!loaded) {\n\t\treturn {\n\t\t\tphase,\n\t\t\thooksRun: 0,\n\t\t\thookErrors: 0,\n\t\t\tlegacyHooksRun: 0,\n\t\t\tentryPathCount: 0,\n\t\t\tskipped: true,\n\t\t};\n\t}\n\n\tconst context: LifecycleHookContext = {\n\t\tphase,\n\t\tsource: loaded.source,\n\t\tinstalledPath: loaded.installedPath,\n\t\tscope: loaded.scope,\n\t\tcwd: loaded.cwd,\n\t\tinteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n\t\tlog: (message) => loaded.stdout.write(`${message}\\n`),\n\t\twarn: (message) => loaded.stderr.write(`${message}\\n`),\n\t\terror: (message) => loaded.stderr.write(`${message}\\n`),\n\t};\n\n\tlet hooksRun = 0;\n\tlet hookErrors = 0;\n\tlet legacyHooksRun = 0;\n\n\tfor (const entryPath of loaded.entryPaths) {\n\t\tconst hookMap = loaded.hooksByPath.get(entryPath);\n\t\tconst registeredHooks = hookMap?.[phase] ?? [];\n\t\tif (registeredHooks.length > 0) {\n\t\t\tfor (const hook of registeredHooks) {\n\t\t\t\thooksRun += 1;\n\t\t\t\tconst ok = await runHookSafe(hook, context, loaded.stderr);\n\t\t\t\tif (!ok) hookErrors += 1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst legacyHook = await runLegacyExportHook(entryPath, phase, context);\n\t\tif (!legacyHook) continue;\n\n\t\tlegacyHooksRun += 1;\n\t\tconst ok = await runHookSafe(legacyHook, context, loaded.stderr);\n\t\tif (!ok) hookErrors += 1;\n\t}\n\n\treturn {\n\t\tphase,\n\t\thooksRun,\n\t\thookErrors,\n\t\tlegacyHooksRun,\n\t\tentryPathCount: loaded.entryPaths.length,\n\t\tskipped: false,\n\t};\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lifecycle-hooks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-hooks.test.d.ts","sourceRoot":"","sources":["../../src/core/lifecycle-hooks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,185 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
3
+ import { homedir, tmpdir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import { describe, it } from "node:test";
6
+ import { readManifestRuntimeDeps, collectRuntimeDependencies, verifyRuntimeDependencies, resolveLocalSourcePath, } from "./lifecycle-hooks.js";
7
+ function tmpDir(prefix, t) {
8
+ const dir = mkdtempSync(join(tmpdir(), `pi-lh-${prefix}-`));
9
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
10
+ return dir;
11
+ }
12
+ // ─── readManifestRuntimeDeps ──────────────────────────────────────────────────
13
+ describe("readManifestRuntimeDeps", () => {
14
+ it("returns empty array when manifest file is missing", (t) => {
15
+ const dir = tmpDir("no-manifest", t);
16
+ assert.deepEqual(readManifestRuntimeDeps(dir), []);
17
+ });
18
+ it("returns empty array for malformed JSON", (t) => {
19
+ const dir = tmpDir("bad-json", t);
20
+ writeFileSync(join(dir, "extension-manifest.json"), "not json{{{", "utf-8");
21
+ assert.deepEqual(readManifestRuntimeDeps(dir), []);
22
+ });
23
+ it("returns runtime deps from valid manifest", (t) => {
24
+ const dir = tmpDir("valid", t);
25
+ writeFileSync(join(dir, "extension-manifest.json"), JSON.stringify({
26
+ dependencies: { runtime: ["claude", "node"] },
27
+ }), "utf-8");
28
+ assert.deepEqual(readManifestRuntimeDeps(dir), ["claude", "node"]);
29
+ });
30
+ it("returns empty array when dependencies exists but runtime is missing", (t) => {
31
+ const dir = tmpDir("no-runtime", t);
32
+ writeFileSync(join(dir, "extension-manifest.json"), JSON.stringify({
33
+ dependencies: {},
34
+ }), "utf-8");
35
+ assert.deepEqual(readManifestRuntimeDeps(dir), []);
36
+ });
37
+ it("returns empty array when runtime is empty", (t) => {
38
+ const dir = tmpDir("empty-runtime", t);
39
+ writeFileSync(join(dir, "extension-manifest.json"), JSON.stringify({
40
+ dependencies: { runtime: [] },
41
+ }), "utf-8");
42
+ assert.deepEqual(readManifestRuntimeDeps(dir), []);
43
+ });
44
+ it("filters out non-string entries in runtime array", (t) => {
45
+ const dir = tmpDir("mixed-types", t);
46
+ writeFileSync(join(dir, "extension-manifest.json"), JSON.stringify({
47
+ dependencies: { runtime: [123, null, "node", false, "python"] },
48
+ }), "utf-8");
49
+ assert.deepEqual(readManifestRuntimeDeps(dir), ["node", "python"]);
50
+ });
51
+ it("returns empty array when no dependencies field at all", (t) => {
52
+ const dir = tmpDir("no-deps-field", t);
53
+ writeFileSync(join(dir, "extension-manifest.json"), JSON.stringify({
54
+ id: "test",
55
+ name: "Test",
56
+ }), "utf-8");
57
+ assert.deepEqual(readManifestRuntimeDeps(dir), []);
58
+ });
59
+ });
60
+ // ─── collectRuntimeDependencies ───────────────────────────────────────────────
61
+ describe("collectRuntimeDependencies", () => {
62
+ it("aggregates deps from installedPath manifest", (t) => {
63
+ const dir = tmpDir("collect-installed", t);
64
+ writeFileSync(join(dir, "extension-manifest.json"), JSON.stringify({
65
+ dependencies: { runtime: ["claude"] },
66
+ }), "utf-8");
67
+ assert.deepEqual(collectRuntimeDependencies(dir, []), ["claude"]);
68
+ });
69
+ it("aggregates deps from entry path directory manifests", (t) => {
70
+ const root = tmpDir("collect-entry", t);
71
+ const installedDir = join(root, "installed");
72
+ const entryDir = join(root, "entry");
73
+ mkdirSync(installedDir, { recursive: true });
74
+ mkdirSync(entryDir, { recursive: true });
75
+ writeFileSync(join(entryDir, "extension-manifest.json"), JSON.stringify({
76
+ dependencies: { runtime: ["python"] },
77
+ }), "utf-8");
78
+ const deps = collectRuntimeDependencies(installedDir, [join(entryDir, "index.ts")]);
79
+ assert.deepEqual(deps, ["python"]);
80
+ });
81
+ it("deduplicates across multiple directories", (t) => {
82
+ const root = tmpDir("collect-dedup", t);
83
+ const dir1 = join(root, "dir1");
84
+ const dir2 = join(root, "dir2");
85
+ mkdirSync(dir1, { recursive: true });
86
+ mkdirSync(dir2, { recursive: true });
87
+ writeFileSync(join(dir1, "extension-manifest.json"), JSON.stringify({
88
+ dependencies: { runtime: ["node", "python"] },
89
+ }), "utf-8");
90
+ writeFileSync(join(dir2, "extension-manifest.json"), JSON.stringify({
91
+ dependencies: { runtime: ["python", "claude"] },
92
+ }), "utf-8");
93
+ const deps = collectRuntimeDependencies(dir1, [join(dir2, "index.ts")]);
94
+ assert.equal(deps.length, 3);
95
+ assert.ok(deps.includes("node"));
96
+ assert.ok(deps.includes("python"));
97
+ assert.ok(deps.includes("claude"));
98
+ });
99
+ it("returns empty when no directories have manifests", (t) => {
100
+ const dir = tmpDir("collect-empty", t);
101
+ assert.deepEqual(collectRuntimeDependencies(dir, []), []);
102
+ });
103
+ });
104
+ // ─── verifyRuntimeDependencies ────────────────────────────────────────────────
105
+ describe("verifyRuntimeDependencies", () => {
106
+ it("does not throw for empty deps array", () => {
107
+ assert.doesNotThrow(() => verifyRuntimeDependencies([], "test-source", "pi"));
108
+ });
109
+ it("does not throw when all deps are present", () => {
110
+ assert.doesNotThrow(() => verifyRuntimeDependencies(["node"], "test-source", "pi"));
111
+ });
112
+ it("throws for missing dep with 'Missing runtime dependencies' message", () => {
113
+ assert.throws(() => verifyRuntimeDependencies(["__nonexistent_dep_for_test__"], "test-source", "pi"), (err) => {
114
+ assert.ok(err.message.includes("Missing runtime dependencies"));
115
+ assert.ok(err.message.includes("__nonexistent_dep_for_test__"));
116
+ return true;
117
+ });
118
+ });
119
+ it("lists all missing deps in error message", () => {
120
+ assert.throws(() => verifyRuntimeDependencies(["__missing_1__", "__missing_2__"], "test-source", "pi"), (err) => {
121
+ assert.ok(err.message.includes("__missing_1__"));
122
+ assert.ok(err.message.includes("__missing_2__"));
123
+ return true;
124
+ });
125
+ });
126
+ it("includes appName and source in error for retry hint", () => {
127
+ assert.throws(() => verifyRuntimeDependencies(["__missing__"], "github:user/repo", "gsd"), (err) => {
128
+ assert.ok(err.message.includes("gsd"));
129
+ assert.ok(err.message.includes("github:user/repo"));
130
+ return true;
131
+ });
132
+ });
133
+ });
134
+ // ─── resolveLocalSourcePath ───────────────────────────────────────────────────
135
+ describe("resolveLocalSourcePath", () => {
136
+ it("returns undefined for empty string", () => {
137
+ assert.equal(resolveLocalSourcePath("", "/tmp"), undefined);
138
+ });
139
+ it("returns undefined for npm: source", () => {
140
+ assert.equal(resolveLocalSourcePath("npm:@foo/bar", "/tmp"), undefined);
141
+ });
142
+ it("returns undefined for git URL", () => {
143
+ assert.equal(resolveLocalSourcePath("git:github.com/user/repo", "/tmp"), undefined);
144
+ });
145
+ it("returns undefined for https git URL", () => {
146
+ assert.equal(resolveLocalSourcePath("https://github.com/user/repo", "/tmp"), undefined);
147
+ });
148
+ it("resolves ~ to homedir", () => {
149
+ const result = resolveLocalSourcePath("~", "/tmp");
150
+ if (existsSync(homedir())) {
151
+ assert.equal(result, homedir());
152
+ }
153
+ else {
154
+ assert.equal(result, undefined);
155
+ }
156
+ });
157
+ it("resolves ~/path relative to homedir", () => {
158
+ const result = resolveLocalSourcePath("~/", "/tmp");
159
+ if (existsSync(homedir())) {
160
+ assert.equal(result, homedir());
161
+ }
162
+ else {
163
+ assert.equal(result, undefined);
164
+ }
165
+ });
166
+ it("resolves relative path that exists", (t) => {
167
+ const dir = tmpDir("resolve-rel", t);
168
+ const sub = join(dir, "myext");
169
+ mkdirSync(sub, { recursive: true });
170
+ const result = resolveLocalSourcePath("myext", dir);
171
+ assert.equal(result, resolve(dir, "myext"));
172
+ });
173
+ it("returns undefined for relative path that does not exist", (t) => {
174
+ const dir = tmpDir("resolve-noexist", t);
175
+ assert.equal(resolveLocalSourcePath("nonexistent", dir), undefined);
176
+ });
177
+ it("resolves absolute path that exists", (t) => {
178
+ const dir = tmpDir("resolve-abs", t);
179
+ assert.equal(resolveLocalSourcePath(dir, "/irrelevant"), dir);
180
+ });
181
+ it("returns undefined for absolute path that does not exist", () => {
182
+ assert.equal(resolveLocalSourcePath("/tmp/__nonexistent_path_for_test__", "/tmp"), undefined);
183
+ });
184
+ });
185
+ //# sourceMappingURL=lifecycle-hooks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-hooks.test.js","sourceRoot":"","sources":["../../src/core/lifecycle-hooks.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { describe, it } from \"node:test\";\nimport {\n\treadManifestRuntimeDeps,\n\tcollectRuntimeDependencies,\n\tverifyRuntimeDependencies,\n\tresolveLocalSourcePath,\n} from \"./lifecycle-hooks.js\";\n\nfunction tmpDir(prefix: string, t: { after: (fn: () => void) => void }): string {\n\tconst dir = mkdtempSync(join(tmpdir(), `pi-lh-${prefix}-`));\n\tt.after(() => rmSync(dir, { recursive: true, force: true }));\n\treturn dir;\n}\n\n// ─── readManifestRuntimeDeps ──────────────────────────────────────────────────\n\ndescribe(\"readManifestRuntimeDeps\", () => {\n\tit(\"returns empty array when manifest file is missing\", (t) => {\n\t\tconst dir = tmpDir(\"no-manifest\", t);\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), []);\n\t});\n\n\tit(\"returns empty array for malformed JSON\", (t) => {\n\t\tconst dir = tmpDir(\"bad-json\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), \"not json{{{\", \"utf-8\");\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), []);\n\t});\n\n\tit(\"returns runtime deps from valid manifest\", (t) => {\n\t\tconst dir = tmpDir(\"valid\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [\"claude\", \"node\"] },\n\t\t}), \"utf-8\");\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), [\"claude\", \"node\"]);\n\t});\n\n\tit(\"returns empty array when dependencies exists but runtime is missing\", (t) => {\n\t\tconst dir = tmpDir(\"no-runtime\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: {},\n\t\t}), \"utf-8\");\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), []);\n\t});\n\n\tit(\"returns empty array when runtime is empty\", (t) => {\n\t\tconst dir = tmpDir(\"empty-runtime\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [] },\n\t\t}), \"utf-8\");\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), []);\n\t});\n\n\tit(\"filters out non-string entries in runtime array\", (t) => {\n\t\tconst dir = tmpDir(\"mixed-types\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [123, null, \"node\", false, \"python\"] },\n\t\t}), \"utf-8\");\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), [\"node\", \"python\"]);\n\t});\n\n\tit(\"returns empty array when no dependencies field at all\", (t) => {\n\t\tconst dir = tmpDir(\"no-deps-field\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tid: \"test\",\n\t\t\tname: \"Test\",\n\t\t}), \"utf-8\");\n\t\tassert.deepEqual(readManifestRuntimeDeps(dir), []);\n\t});\n});\n\n// ─── collectRuntimeDependencies ───────────────────────────────────────────────\n\ndescribe(\"collectRuntimeDependencies\", () => {\n\tit(\"aggregates deps from installedPath manifest\", (t) => {\n\t\tconst dir = tmpDir(\"collect-installed\", t);\n\t\twriteFileSync(join(dir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [\"claude\"] },\n\t\t}), \"utf-8\");\n\t\tassert.deepEqual(collectRuntimeDependencies(dir, []), [\"claude\"]);\n\t});\n\n\tit(\"aggregates deps from entry path directory manifests\", (t) => {\n\t\tconst root = tmpDir(\"collect-entry\", t);\n\t\tconst installedDir = join(root, \"installed\");\n\t\tconst entryDir = join(root, \"entry\");\n\t\tmkdirSync(installedDir, { recursive: true });\n\t\tmkdirSync(entryDir, { recursive: true });\n\t\twriteFileSync(join(entryDir, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [\"python\"] },\n\t\t}), \"utf-8\");\n\t\tconst deps = collectRuntimeDependencies(installedDir, [join(entryDir, \"index.ts\")]);\n\t\tassert.deepEqual(deps, [\"python\"]);\n\t});\n\n\tit(\"deduplicates across multiple directories\", (t) => {\n\t\tconst root = tmpDir(\"collect-dedup\", t);\n\t\tconst dir1 = join(root, \"dir1\");\n\t\tconst dir2 = join(root, \"dir2\");\n\t\tmkdirSync(dir1, { recursive: true });\n\t\tmkdirSync(dir2, { recursive: true });\n\t\twriteFileSync(join(dir1, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [\"node\", \"python\"] },\n\t\t}), \"utf-8\");\n\t\twriteFileSync(join(dir2, \"extension-manifest.json\"), JSON.stringify({\n\t\t\tdependencies: { runtime: [\"python\", \"claude\"] },\n\t\t}), \"utf-8\");\n\t\tconst deps = collectRuntimeDependencies(dir1, [join(dir2, \"index.ts\")]);\n\t\tassert.equal(deps.length, 3);\n\t\tassert.ok(deps.includes(\"node\"));\n\t\tassert.ok(deps.includes(\"python\"));\n\t\tassert.ok(deps.includes(\"claude\"));\n\t});\n\n\tit(\"returns empty when no directories have manifests\", (t) => {\n\t\tconst dir = tmpDir(\"collect-empty\", t);\n\t\tassert.deepEqual(collectRuntimeDependencies(dir, []), []);\n\t});\n});\n\n// ─── verifyRuntimeDependencies ────────────────────────────────────────────────\n\ndescribe(\"verifyRuntimeDependencies\", () => {\n\tit(\"does not throw for empty deps array\", () => {\n\t\tassert.doesNotThrow(() => verifyRuntimeDependencies([], \"test-source\", \"pi\"));\n\t});\n\n\tit(\"does not throw when all deps are present\", () => {\n\t\tassert.doesNotThrow(() => verifyRuntimeDependencies([\"node\"], \"test-source\", \"pi\"));\n\t});\n\n\tit(\"throws for missing dep with 'Missing runtime dependencies' message\", () => {\n\t\tassert.throws(\n\t\t\t() => verifyRuntimeDependencies([\"__nonexistent_dep_for_test__\"], \"test-source\", \"pi\"),\n\t\t\t(err: Error) => {\n\t\t\t\tassert.ok(err.message.includes(\"Missing runtime dependencies\"));\n\t\t\t\tassert.ok(err.message.includes(\"__nonexistent_dep_for_test__\"));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t);\n\t});\n\n\tit(\"lists all missing deps in error message\", () => {\n\t\tassert.throws(\n\t\t\t() => verifyRuntimeDependencies([\"__missing_1__\", \"__missing_2__\"], \"test-source\", \"pi\"),\n\t\t\t(err: Error) => {\n\t\t\t\tassert.ok(err.message.includes(\"__missing_1__\"));\n\t\t\t\tassert.ok(err.message.includes(\"__missing_2__\"));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t);\n\t});\n\n\tit(\"includes appName and source in error for retry hint\", () => {\n\t\tassert.throws(\n\t\t\t() => verifyRuntimeDependencies([\"__missing__\"], \"github:user/repo\", \"gsd\"),\n\t\t\t(err: Error) => {\n\t\t\t\tassert.ok(err.message.includes(\"gsd\"));\n\t\t\t\tassert.ok(err.message.includes(\"github:user/repo\"));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t);\n\t});\n});\n\n// ─── resolveLocalSourcePath ───────────────────────────────────────────────────\n\ndescribe(\"resolveLocalSourcePath\", () => {\n\tit(\"returns undefined for empty string\", () => {\n\t\tassert.equal(resolveLocalSourcePath(\"\", \"/tmp\"), undefined);\n\t});\n\n\tit(\"returns undefined for npm: source\", () => {\n\t\tassert.equal(resolveLocalSourcePath(\"npm:@foo/bar\", \"/tmp\"), undefined);\n\t});\n\n\tit(\"returns undefined for git URL\", () => {\n\t\tassert.equal(resolveLocalSourcePath(\"git:github.com/user/repo\", \"/tmp\"), undefined);\n\t});\n\n\tit(\"returns undefined for https git URL\", () => {\n\t\tassert.equal(resolveLocalSourcePath(\"https://github.com/user/repo\", \"/tmp\"), undefined);\n\t});\n\n\tit(\"resolves ~ to homedir\", () => {\n\t\tconst result = resolveLocalSourcePath(\"~\", \"/tmp\");\n\t\tif (existsSync(homedir())) {\n\t\t\tassert.equal(result, homedir());\n\t\t} else {\n\t\t\tassert.equal(result, undefined);\n\t\t}\n\t});\n\n\tit(\"resolves ~/path relative to homedir\", () => {\n\t\tconst result = resolveLocalSourcePath(\"~/\", \"/tmp\");\n\t\tif (existsSync(homedir())) {\n\t\t\tassert.equal(result, homedir());\n\t\t} else {\n\t\t\tassert.equal(result, undefined);\n\t\t}\n\t});\n\n\tit(\"resolves relative path that exists\", (t) => {\n\t\tconst dir = tmpDir(\"resolve-rel\", t);\n\t\tconst sub = join(dir, \"myext\");\n\t\tmkdirSync(sub, { recursive: true });\n\t\tconst result = resolveLocalSourcePath(\"myext\", dir);\n\t\tassert.equal(result, resolve(dir, \"myext\"));\n\t});\n\n\tit(\"returns undefined for relative path that does not exist\", (t) => {\n\t\tconst dir = tmpDir(\"resolve-noexist\", t);\n\t\tassert.equal(resolveLocalSourcePath(\"nonexistent\", dir), undefined);\n\t});\n\n\tit(\"resolves absolute path that exists\", (t) => {\n\t\tconst dir = tmpDir(\"resolve-abs\", t);\n\t\tassert.equal(resolveLocalSourcePath(dir, \"/irrelevant\"), dir);\n\t});\n\n\tit(\"returns undefined for absolute path that does not exist\", () => {\n\t\tassert.equal(resolveLocalSourcePath(\"/tmp/__nonexistent_path_for_test__\", \"/tmp\"), undefined);\n\t});\n});\n"]}