@openspecui/core 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2,14 +2,14 @@ import { mkdir, readFile, rename, writeFile } from "fs/promises";
2
2
  import { join } from "path";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { readFile as readFile$1, readdir, stat } from "node:fs/promises";
5
- import { dirname, join as join$1, resolve } from "node:path";
5
+ import { dirname, join as join$1, matchesGlob, relative, resolve, sep } from "node:path";
6
6
  import { existsSync, realpathSync, utimesSync } from "node:fs";
7
7
  import { z } from "zod";
8
8
  import { watch } from "fs";
9
9
  import { EventEmitter } from "events";
10
10
  import { exec, spawn } from "child_process";
11
11
  import { promisify } from "util";
12
- import { homedir } from "node:os";
12
+ import { parse } from "yaml";
13
13
 
14
14
  //#region src/parser.ts
15
15
  /**
@@ -2201,6 +2201,31 @@ var CliExecutor = class {
2201
2201
  return this.execute(args);
2202
2202
  }
2203
2203
  /**
2204
+ * 执行 openspec schemas --json
2205
+ */
2206
+ async schemas() {
2207
+ return this.execute(["schemas", "--json"]);
2208
+ }
2209
+ /**
2210
+ * 执行 openspec schema which <name> --json
2211
+ */
2212
+ async schemaWhich(name) {
2213
+ return this.execute([
2214
+ "schema",
2215
+ "which",
2216
+ name,
2217
+ "--json"
2218
+ ]);
2219
+ }
2220
+ /**
2221
+ * 执行 openspec templates --json [--schema <name>]
2222
+ */
2223
+ async templates(schema) {
2224
+ const args = ["templates", "--json"];
2225
+ if (schema) args.push("--schema", schema);
2226
+ return this.execute(args);
2227
+ }
2228
+ /**
2204
2229
  * 流式执行 openspec validate
2205
2230
  */
2206
2231
  validateStream(type, id, onEvent) {
@@ -2366,32 +2391,27 @@ var CliExecutor = class {
2366
2391
  /**
2367
2392
  * 工具配置检测模块
2368
2393
  *
2369
- * 完全对齐 @fission-ai/openspec 的官方实现
2370
- * 用于检测项目中已配置的 AI 工具
2371
- *
2372
- * 重要:使用响应式文件系统实现,监听配置目录,
2373
- * 当配置文件变化时会自动触发更新。
2374
- *
2375
- * @see references/openspec/src/core/config.ts (AI_TOOLS)
2376
- * @see references/openspec/src/core/configurators/slash/
2377
- * @see references/openspec/src/core/init.ts (isToolConfigured)
2394
+ * 对齐 @fission-ai/openspec 的 skills 体系,
2395
+ * 通过 `skills/<skill>/SKILL.md` 判断工具是否已配置。
2378
2396
  */
2379
2397
  /**
2380
- * 获取 Codex 全局 prompts 目录
2381
- * 优先使用 $CODEX_HOME 环境变量,否则使用 ~/.codex
2382
- * @see references/openspec/src/core/configurators/slash/codex.ts
2398
+ * OpenSpec 初始化生成的技能目录名称
2399
+ * @see references/openspec/src/core/shared/tool-detection.ts
2383
2400
  */
2384
- function getCodexGlobalPromptsDir() {
2385
- return join$1(process.env.CODEX_HOME?.trim() || join$1(homedir(), ".codex"), "prompts");
2386
- }
2401
+ const SKILL_NAMES = [
2402
+ "openspec-explore",
2403
+ "openspec-new-change",
2404
+ "openspec-continue-change",
2405
+ "openspec-apply-change",
2406
+ "openspec-ff-change",
2407
+ "openspec-sync-specs",
2408
+ "openspec-archive-change",
2409
+ "openspec-bulk-archive-change",
2410
+ "openspec-verify-change"
2411
+ ];
2387
2412
  /**
2388
2413
  * 所有支持的 AI 工具配置
2389
- *
2390
2414
  * 完全对齐官方 OpenSpec CLI 的 AI_TOOLS
2391
- * 按字母顺序排序(与官方一致)
2392
- *
2393
- * @see references/openspec/src/core/config.ts
2394
- * @see references/openspec/src/core/configurators/slash/registry.ts
2395
2415
  */
2396
2416
  const AI_TOOLS = [
2397
2417
  {
@@ -2399,168 +2419,160 @@ const AI_TOOLS = [
2399
2419
  value: "amazon-q",
2400
2420
  available: true,
2401
2421
  successLabel: "Amazon Q Developer",
2402
- scope: "project",
2403
- detectionPath: ".amazonq/prompts/openspec-proposal.md"
2422
+ skillsDir: ".amazonq"
2404
2423
  },
2405
2424
  {
2406
2425
  name: "Antigravity",
2407
2426
  value: "antigravity",
2408
2427
  available: true,
2409
2428
  successLabel: "Antigravity",
2410
- scope: "project",
2411
- detectionPath: ".agent/workflows/openspec-proposal.md"
2429
+ skillsDir: ".agent"
2412
2430
  },
2413
2431
  {
2414
2432
  name: "Auggie (Augment CLI)",
2415
2433
  value: "auggie",
2416
2434
  available: true,
2417
2435
  successLabel: "Auggie",
2418
- scope: "project",
2419
- detectionPath: ".augment/commands/openspec-proposal.md"
2436
+ skillsDir: ".augment"
2420
2437
  },
2421
2438
  {
2422
2439
  name: "Claude Code",
2423
2440
  value: "claude",
2424
2441
  available: true,
2425
2442
  successLabel: "Claude Code",
2426
- scope: "project",
2427
- detectionPath: ".claude/commands/openspec/proposal.md"
2443
+ skillsDir: ".claude"
2428
2444
  },
2429
2445
  {
2430
2446
  name: "Cline",
2431
2447
  value: "cline",
2432
2448
  available: true,
2433
2449
  successLabel: "Cline",
2434
- scope: "project",
2435
- detectionPath: ".clinerules/workflows/openspec-proposal.md"
2450
+ skillsDir: ".cline"
2436
2451
  },
2437
2452
  {
2438
2453
  name: "Codex",
2439
2454
  value: "codex",
2440
2455
  available: true,
2441
2456
  successLabel: "Codex",
2442
- scope: "global",
2443
- detectionPath: () => join$1(getCodexGlobalPromptsDir(), "openspec-proposal.md")
2457
+ skillsDir: ".codex"
2444
2458
  },
2445
2459
  {
2446
2460
  name: "CodeBuddy Code (CLI)",
2447
2461
  value: "codebuddy",
2448
2462
  available: true,
2449
2463
  successLabel: "CodeBuddy Code",
2450
- scope: "project",
2451
- detectionPath: ".codebuddy/commands/openspec/proposal.md"
2464
+ skillsDir: ".codebuddy"
2465
+ },
2466
+ {
2467
+ name: "Continue",
2468
+ value: "continue",
2469
+ available: true,
2470
+ successLabel: "Continue (VS Code / JetBrains / Cli)",
2471
+ skillsDir: ".continue"
2452
2472
  },
2453
2473
  {
2454
2474
  name: "CoStrict",
2455
2475
  value: "costrict",
2456
2476
  available: true,
2457
2477
  successLabel: "CoStrict",
2458
- scope: "project",
2459
- detectionPath: ".cospec/openspec/commands/openspec-proposal.md"
2478
+ skillsDir: ".cospec"
2460
2479
  },
2461
2480
  {
2462
2481
  name: "Crush",
2463
2482
  value: "crush",
2464
2483
  available: true,
2465
2484
  successLabel: "Crush",
2466
- scope: "project",
2467
- detectionPath: ".crush/commands/openspec/proposal.md"
2485
+ skillsDir: ".crush"
2468
2486
  },
2469
2487
  {
2470
2488
  name: "Cursor",
2471
2489
  value: "cursor",
2472
2490
  available: true,
2473
2491
  successLabel: "Cursor",
2474
- scope: "project",
2475
- detectionPath: ".cursor/commands/openspec-proposal.md"
2492
+ skillsDir: ".cursor"
2476
2493
  },
2477
2494
  {
2478
2495
  name: "Factory Droid",
2479
2496
  value: "factory",
2480
2497
  available: true,
2481
2498
  successLabel: "Factory Droid",
2482
- scope: "project",
2483
- detectionPath: ".factory/commands/openspec-proposal.md"
2499
+ skillsDir: ".factory"
2484
2500
  },
2485
2501
  {
2486
2502
  name: "Gemini CLI",
2487
2503
  value: "gemini",
2488
2504
  available: true,
2489
2505
  successLabel: "Gemini CLI",
2490
- scope: "project",
2491
- detectionPath: ".gemini/commands/openspec/proposal.toml"
2506
+ skillsDir: ".gemini"
2492
2507
  },
2493
2508
  {
2494
2509
  name: "GitHub Copilot",
2495
2510
  value: "github-copilot",
2496
2511
  available: true,
2497
2512
  successLabel: "GitHub Copilot",
2498
- scope: "project",
2499
- detectionPath: ".github/prompts/openspec-proposal.prompt.md"
2513
+ skillsDir: ".github"
2500
2514
  },
2501
2515
  {
2502
2516
  name: "iFlow",
2503
2517
  value: "iflow",
2504
2518
  available: true,
2505
2519
  successLabel: "iFlow",
2506
- scope: "project",
2507
- detectionPath: ".iflow/commands/openspec-proposal.md"
2520
+ skillsDir: ".iflow"
2508
2521
  },
2509
2522
  {
2510
2523
  name: "Kilo Code",
2511
2524
  value: "kilocode",
2512
2525
  available: true,
2513
2526
  successLabel: "Kilo Code",
2514
- scope: "project",
2515
- detectionPath: ".kilocode/workflows/openspec-proposal.md"
2527
+ skillsDir: ".kilocode"
2516
2528
  },
2517
2529
  {
2518
2530
  name: "OpenCode",
2519
2531
  value: "opencode",
2520
2532
  available: true,
2521
2533
  successLabel: "OpenCode",
2522
- scope: "project",
2523
- detectionPath: ".opencode/command/openspec-proposal.md"
2534
+ skillsDir: ".opencode"
2524
2535
  },
2525
2536
  {
2526
- name: "Qoder (CLI)",
2537
+ name: "Qoder",
2527
2538
  value: "qoder",
2528
2539
  available: true,
2529
2540
  successLabel: "Qoder",
2530
- scope: "project",
2531
- detectionPath: ".qoder/commands/openspec/proposal.md"
2541
+ skillsDir: ".qoder"
2532
2542
  },
2533
2543
  {
2534
2544
  name: "Qwen Code",
2535
2545
  value: "qwen",
2536
2546
  available: true,
2537
2547
  successLabel: "Qwen Code",
2538
- scope: "project",
2539
- detectionPath: ".qwen/commands/openspec-proposal.toml"
2548
+ skillsDir: ".qwen"
2540
2549
  },
2541
2550
  {
2542
2551
  name: "RooCode",
2543
2552
  value: "roocode",
2544
2553
  available: true,
2545
2554
  successLabel: "RooCode",
2546
- scope: "project",
2547
- detectionPath: ".roo/commands/openspec-proposal.md"
2555
+ skillsDir: ".roo"
2556
+ },
2557
+ {
2558
+ name: "Trae",
2559
+ value: "trae",
2560
+ available: true,
2561
+ successLabel: "Trae",
2562
+ skillsDir: ".trae"
2548
2563
  },
2549
2564
  {
2550
2565
  name: "Windsurf",
2551
2566
  value: "windsurf",
2552
2567
  available: true,
2553
2568
  successLabel: "Windsurf",
2554
- scope: "project",
2555
- detectionPath: ".windsurf/workflows/openspec-proposal.md"
2569
+ skillsDir: ".windsurf"
2556
2570
  },
2557
2571
  {
2558
2572
  name: "AGENTS.md (works with Amp, VS Code, …)",
2559
2573
  value: "agents",
2560
2574
  available: false,
2561
- successLabel: "your AGENTS.md-compatible assistant",
2562
- scope: "project",
2563
- detectionPath: "AGENTS.md"
2575
+ successLabel: "your AGENTS.md-compatible assistant"
2564
2576
  }
2565
2577
  ];
2566
2578
  /**
@@ -2597,71 +2609,39 @@ function getToolById(toolId) {
2597
2609
  const stateCache = /* @__PURE__ */ new Map();
2598
2610
  /** 监听器释放函数缓存 */
2599
2611
  const releaseCache = /* @__PURE__ */ new Map();
2600
- /**
2601
- * 检查文件是否存在
2602
- */
2603
- async function fileExists(filePath) {
2604
- try {
2605
- await stat(filePath);
2606
- return true;
2607
- } catch {
2608
- return false;
2609
- }
2612
+ function getSkillsDir(projectDir, tool) {
2613
+ if (!tool.skillsDir) return null;
2614
+ return join$1(projectDir, tool.skillsDir, "skills");
2610
2615
  }
2611
- /**
2612
- * 解析工具的检测路径
2613
- * @param config 工具配置
2614
- * @param projectDir 项目根目录
2615
- * @returns 绝对路径,如果无检测路径则返回 undefined
2616
- */
2617
- function resolveDetectionPath(config, projectDir) {
2618
- if (config.scope === "none" || !config.detectionPath) return;
2619
- if (config.scope === "global") return config.detectionPath();
2620
- return join$1(projectDir, config.detectionPath);
2616
+ async function getSkillCount(projectDir, tool) {
2617
+ const skillsDir = getSkillsDir(projectDir, tool);
2618
+ if (!skillsDir) return 0;
2619
+ let count = 0;
2620
+ for (const skillName of SKILL_NAMES) if (await reactiveExists(join$1(skillsDir, skillName, "SKILL.md"))) count++;
2621
+ return count;
2621
2622
  }
2622
2623
  /**
2623
2624
  * 扫描已配置的工具(并行检查)
2624
2625
  */
2625
2626
  async function scanConfiguredTools(projectDir) {
2626
2627
  return (await Promise.all(AI_TOOLS.map(async (config) => {
2627
- const filePath = resolveDetectionPath(config, projectDir);
2628
- if (!filePath) return null;
2629
- return await fileExists(filePath) ? config.value : null;
2628
+ if (!config.skillsDir) return null;
2629
+ return await getSkillCount(projectDir, config) > 0 ? config.value : null;
2630
2630
  }))).filter((id) => id !== null);
2631
2631
  }
2632
2632
  /**
2633
2633
  * 获取需要监听的项目级目录列表
2634
- * 只监听包含工具配置的一级隐藏目录
2635
2634
  */
2636
2635
  function getProjectWatchDirs(projectDir) {
2637
2636
  const dirs = /* @__PURE__ */ new Set();
2638
- for (const config of AI_TOOLS) if (config.scope === "project" && config.detectionPath) {
2639
- const firstDir = config.detectionPath.split("/")[0];
2640
- if (firstDir) dirs.add(join$1(projectDir, firstDir));
2641
- }
2642
- return Array.from(dirs);
2643
- }
2644
- /**
2645
- * 获取需要监听的全局目录列表
2646
- * 如 Codex 的 ~/.codex/prompts/
2647
- */
2648
- function getGlobalWatchDirs() {
2649
- const dirs = /* @__PURE__ */ new Set();
2650
- for (const config of AI_TOOLS) if (config.scope === "global" && config.detectionPath) {
2651
- const filePath = config.detectionPath();
2652
- dirs.add(dirname(filePath));
2637
+ for (const config of AI_TOOLS) {
2638
+ if (!config.skillsDir) continue;
2639
+ dirs.add(join$1(projectDir, config.skillsDir));
2653
2640
  }
2654
2641
  return Array.from(dirs);
2655
2642
  }
2656
2643
  /**
2657
2644
  * 检测项目中已配置的工具(响应式)
2658
- *
2659
- * 监听两类目录:
2660
- * 1. 项目级配置目录(如 .claude, .cursor 等)
2661
- * 2. 全局配置目录(如 ~/.codex/prompts/)
2662
- *
2663
- * @param projectDir 项目根目录
2664
- * @returns 已配置的工具 ID 列表
2665
2645
  */
2666
2646
  async function getConfiguredTools(projectDir) {
2667
2647
  const normalizedPath = resolve(projectDir);
@@ -2680,11 +2660,6 @@ async function getConfiguredTools(projectDir) {
2680
2660
  const release = acquireWatcher(dir, onUpdate, { recursive: true });
2681
2661
  releases.push(release);
2682
2662
  }
2683
- const globalWatchDirs = getGlobalWatchDirs();
2684
- for (const dir of globalWatchDirs) {
2685
- const release = acquireWatcher(dir, onUpdate, { recursive: false });
2686
- releases.push(release);
2687
- }
2688
2663
  const rootRelease = acquireWatcher(normalizedPath, onUpdate, { recursive: false });
2689
2664
  releases.push(rootRelease);
2690
2665
  releaseCache.set(key, () => releases.forEach((r) => r()));
@@ -2693,14 +2668,814 @@ async function getConfiguredTools(projectDir) {
2693
2668
  }
2694
2669
  /**
2695
2670
  * 检查特定工具是否已配置
2696
- *
2697
- * @param projectDir 项目根目录
2698
- * @param toolId 工具 ID
2699
- * @returns 是否已配置
2700
2671
  */
2701
2672
  async function isToolConfigured(projectDir, toolId) {
2702
2673
  return (await getConfiguredTools(projectDir)).includes(toolId);
2703
2674
  }
2704
2675
 
2705
2676
  //#endregion
2706
- export { AI_TOOLS, ChangeFileSchema, ChangeSchema, CliExecutor, ConfigManager, DEFAULT_CONFIG, DeltaOperationType, DeltaSchema, DeltaSpecSchema, MarkdownParser, OpenSpecAdapter, OpenSpecUIConfigSchema, OpenSpecWatcher, ProjectWatcher, ReactiveContext, ReactiveState, RequirementSchema, SpecSchema, TaskSchema, Validator, acquireWatcher, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };
2677
+ //#region src/opsx-types.ts
2678
+ /** Check if an outputPath contains glob pattern characters */
2679
+ function isGlobPattern(pattern) {
2680
+ return pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
2681
+ }
2682
+ const ArtifactStatusSchema = z.object({
2683
+ id: z.string(),
2684
+ outputPath: z.string(),
2685
+ status: z.enum([
2686
+ "done",
2687
+ "ready",
2688
+ "blocked"
2689
+ ]),
2690
+ missingDeps: z.array(z.string()).optional(),
2691
+ relativePath: z.string().optional()
2692
+ });
2693
+ const ChangeStatusSchema = z.object({
2694
+ changeName: z.string(),
2695
+ schemaName: z.string(),
2696
+ isComplete: z.boolean(),
2697
+ applyRequires: z.array(z.string()),
2698
+ artifacts: z.array(ArtifactStatusSchema)
2699
+ });
2700
+ const DependencyInfoSchema = z.object({
2701
+ id: z.string(),
2702
+ done: z.boolean(),
2703
+ path: z.string(),
2704
+ description: z.string()
2705
+ });
2706
+ const ApplyTaskSchema = z.object({
2707
+ id: z.string(),
2708
+ description: z.string(),
2709
+ done: z.boolean()
2710
+ });
2711
+ const ApplyInstructionsSchema = z.object({
2712
+ changeName: z.string(),
2713
+ changeDir: z.string(),
2714
+ schemaName: z.string(),
2715
+ contextFiles: z.record(z.string()),
2716
+ progress: z.object({
2717
+ total: z.number(),
2718
+ complete: z.number(),
2719
+ remaining: z.number()
2720
+ }),
2721
+ tasks: z.array(ApplyTaskSchema),
2722
+ state: z.enum([
2723
+ "blocked",
2724
+ "all_done",
2725
+ "ready"
2726
+ ]),
2727
+ missingArtifacts: z.array(z.string()).optional(),
2728
+ instruction: z.string()
2729
+ });
2730
+ const NullableString = z.string().nullable().optional();
2731
+ const ArtifactInstructionsSchema = z.object({
2732
+ changeName: z.string(),
2733
+ artifactId: z.string(),
2734
+ schemaName: z.string(),
2735
+ changeDir: z.string(),
2736
+ outputPath: z.string(),
2737
+ description: z.string(),
2738
+ instruction: NullableString,
2739
+ context: NullableString,
2740
+ rules: z.array(z.string()).optional().nullable(),
2741
+ template: z.string(),
2742
+ dependencies: z.array(DependencyInfoSchema),
2743
+ unlocks: z.array(z.string())
2744
+ });
2745
+ const SchemaInfoSchema = z.object({
2746
+ name: z.string(),
2747
+ description: z.string().optional(),
2748
+ artifacts: z.array(z.string()),
2749
+ source: z.enum([
2750
+ "project",
2751
+ "user",
2752
+ "package"
2753
+ ])
2754
+ });
2755
+ const SchemaResolutionSchema = z.object({
2756
+ name: z.string(),
2757
+ source: z.enum([
2758
+ "project",
2759
+ "user",
2760
+ "package"
2761
+ ]),
2762
+ path: z.string(),
2763
+ shadows: z.array(z.object({
2764
+ source: z.enum([
2765
+ "project",
2766
+ "user",
2767
+ "package"
2768
+ ]),
2769
+ path: z.string()
2770
+ }))
2771
+ });
2772
+ const TemplatesSchema = z.record(z.object({
2773
+ path: z.string(),
2774
+ source: z.enum([
2775
+ "project",
2776
+ "user",
2777
+ "package"
2778
+ ])
2779
+ }));
2780
+ const SchemaArtifactSchema = z.object({
2781
+ id: z.string(),
2782
+ outputPath: z.string(),
2783
+ description: z.string().optional(),
2784
+ template: z.string().optional(),
2785
+ instruction: z.string().optional(),
2786
+ requires: z.array(z.string())
2787
+ });
2788
+ const SchemaDetailSchema = z.object({
2789
+ name: z.string(),
2790
+ description: z.string().optional(),
2791
+ version: z.union([z.string(), z.number()]).optional(),
2792
+ artifacts: z.array(SchemaArtifactSchema),
2793
+ applyRequires: z.array(z.string()),
2794
+ applyTracks: z.string().optional(),
2795
+ applyInstruction: z.string().optional()
2796
+ });
2797
+
2798
+ //#endregion
2799
+ //#region src/opsx-kernel.ts
2800
+ function parseCliJson(raw, schema, label) {
2801
+ const trimmed = raw.trim();
2802
+ if (!trimmed) throw new Error(`${label} returned empty output`);
2803
+ let parsed;
2804
+ try {
2805
+ parsed = JSON.parse(trimmed);
2806
+ } catch (err) {
2807
+ const message = err instanceof Error ? err.message : String(err);
2808
+ throw new Error(`${label} returned invalid JSON: ${message}`);
2809
+ }
2810
+ const result = schema.safeParse(parsed);
2811
+ if (!result.success) throw new Error(`${label} returned unexpected JSON: ${result.error.message}`);
2812
+ return result.data;
2813
+ }
2814
+ function toRelativePath(root, absolutePath) {
2815
+ return relative(root, absolutePath).split(sep).join("/");
2816
+ }
2817
+ async function readEntriesUnderRoot(root) {
2818
+ if (!(await reactiveStat(root))?.isDirectory) return [];
2819
+ const collectEntries = async (dir) => {
2820
+ const names = await reactiveReadDir(dir, { includeHidden: false });
2821
+ const entries = [];
2822
+ for (const name of names) {
2823
+ const fullPath = join$1(dir, name);
2824
+ const statInfo = await reactiveStat(fullPath);
2825
+ if (!statInfo) continue;
2826
+ const relativePath = toRelativePath(root, fullPath);
2827
+ if (statInfo.isDirectory) {
2828
+ entries.push({
2829
+ path: relativePath,
2830
+ type: "directory"
2831
+ });
2832
+ entries.push(...await collectEntries(fullPath));
2833
+ } else {
2834
+ const content = await reactiveReadFile(fullPath);
2835
+ const size = content ? Buffer.byteLength(content, "utf-8") : void 0;
2836
+ entries.push({
2837
+ path: relativePath,
2838
+ type: "file",
2839
+ content: content ?? void 0,
2840
+ size
2841
+ });
2842
+ }
2843
+ }
2844
+ return entries;
2845
+ };
2846
+ return collectEntries(root);
2847
+ }
2848
+ async function readGlobArtifactFiles(projectDir, changeId, outputPath) {
2849
+ return (await readEntriesUnderRoot(join$1(projectDir, "openspec", "changes", changeId))).filter((entry) => entry.type === "file" && matchesGlob(entry.path, outputPath)).map((entry) => ({
2850
+ path: entry.path,
2851
+ type: "file",
2852
+ content: entry.content ?? ""
2853
+ }));
2854
+ }
2855
+ async function touchOpsxProjectDeps(projectDir) {
2856
+ const openspecDir = join$1(projectDir, "openspec");
2857
+ await reactiveReadFile(join$1(openspecDir, "config.yaml"));
2858
+ const schemaRoot = join$1(openspecDir, "schemas");
2859
+ const schemaDirs = await reactiveReadDir(schemaRoot, {
2860
+ directoriesOnly: true,
2861
+ includeHidden: true
2862
+ });
2863
+ await Promise.all(schemaDirs.map((name) => reactiveReadFile(join$1(schemaRoot, name, "schema.yaml"))));
2864
+ await reactiveReadDir(join$1(openspecDir, "changes"), {
2865
+ directoriesOnly: true,
2866
+ includeHidden: true,
2867
+ exclude: ["archive"]
2868
+ });
2869
+ }
2870
+ async function touchOpsxChangeDeps(projectDir, changeId) {
2871
+ const changeDir = join$1(projectDir, "openspec", "changes", changeId);
2872
+ await reactiveReadDir(changeDir, { includeHidden: true });
2873
+ await reactiveReadFile(join$1(changeDir, ".openspec.yaml"));
2874
+ }
2875
+ var OpsxKernel = class {
2876
+ projectDir;
2877
+ cliExecutor;
2878
+ controller = new AbortController();
2879
+ _statusList = new ReactiveState([]);
2880
+ _schemas = new ReactiveState([]);
2881
+ _changeIds = new ReactiveState([]);
2882
+ _projectConfig = new ReactiveState(null);
2883
+ _schemaResolutions = /* @__PURE__ */ new Map();
2884
+ _schemaDetails = /* @__PURE__ */ new Map();
2885
+ _schemaFiles = /* @__PURE__ */ new Map();
2886
+ _schemaYamls = /* @__PURE__ */ new Map();
2887
+ _templates = /* @__PURE__ */ new Map();
2888
+ _templateContents = /* @__PURE__ */ new Map();
2889
+ _statuses = /* @__PURE__ */ new Map();
2890
+ _instructions = /* @__PURE__ */ new Map();
2891
+ _applyInstructions = /* @__PURE__ */ new Map();
2892
+ _changeMetadata = /* @__PURE__ */ new Map();
2893
+ _artifactOutputs = /* @__PURE__ */ new Map();
2894
+ _globArtifactFiles = /* @__PURE__ */ new Map();
2895
+ _entityControllers = /* @__PURE__ */ new Map();
2896
+ constructor(projectDir, cliExecutor) {
2897
+ this.projectDir = projectDir;
2898
+ this.cliExecutor = cliExecutor;
2899
+ }
2900
+ async warmup() {
2901
+ const signal = this.controller.signal;
2902
+ await Promise.all([
2903
+ this.startStream(this._schemas, () => this.fetchSchemas(), signal),
2904
+ this.startStream(this._changeIds, () => this.fetchChangeIds(), signal),
2905
+ this.startStream(this._projectConfig, () => this.fetchProjectConfig(), signal)
2906
+ ]);
2907
+ const schemas = this._schemas.get();
2908
+ await Promise.all(schemas.map((s) => this.warmupSchema(s.name, signal)));
2909
+ const changeIds = this._changeIds.get();
2910
+ await Promise.all(changeIds.map((id) => this.warmupChange(id, signal)));
2911
+ await this.startStream(this._statusList, () => this.fetchStatusList(), signal);
2912
+ this.watchSchemaChanges(signal);
2913
+ this.watchChangeIdChanges(signal);
2914
+ }
2915
+ dispose() {
2916
+ this.controller.abort();
2917
+ for (const ctrl of this._entityControllers.values()) ctrl.abort();
2918
+ this._entityControllers.clear();
2919
+ }
2920
+ getStatusList() {
2921
+ return this._statusList.get();
2922
+ }
2923
+ getSchemas() {
2924
+ return this._schemas.get();
2925
+ }
2926
+ getChangeIds() {
2927
+ return this._changeIds.get();
2928
+ }
2929
+ getProjectConfig() {
2930
+ return this._projectConfig.get();
2931
+ }
2932
+ getTemplates(schema) {
2933
+ const key = schema ?? "";
2934
+ const state = this._templates.get(key);
2935
+ return state ? state.get() : {};
2936
+ }
2937
+ getTemplateContents(schema) {
2938
+ const key = schema ?? "";
2939
+ const state = this._templateContents.get(key);
2940
+ return state ? state.get() : {};
2941
+ }
2942
+ getStatus(changeId, schema) {
2943
+ const key = `${changeId}:${schema ?? ""}`;
2944
+ const state = this._statuses.get(key);
2945
+ if (!state) throw new Error(`Status not found for change "${changeId}"`);
2946
+ return state.get();
2947
+ }
2948
+ getInstructions(changeId, artifact, schema) {
2949
+ const key = `${changeId}:${artifact}:${schema ?? ""}`;
2950
+ const state = this._instructions.get(key);
2951
+ if (!state) throw new Error(`Instructions not found for change "${changeId}" artifact "${artifact}"`);
2952
+ return state.get();
2953
+ }
2954
+ getApplyInstructions(changeId, schema) {
2955
+ const key = `${changeId}:${schema ?? ""}`;
2956
+ const state = this._applyInstructions.get(key);
2957
+ if (!state) throw new Error(`Apply instructions not found for change "${changeId}"`);
2958
+ return state.get();
2959
+ }
2960
+ getSchemaResolution(name) {
2961
+ const state = this._schemaResolutions.get(name);
2962
+ if (!state) throw new Error(`Schema resolution not found for "${name}"`);
2963
+ return state.get();
2964
+ }
2965
+ getSchemaDetail(name) {
2966
+ const state = this._schemaDetails.get(name);
2967
+ if (!state) throw new Error(`Schema detail not found for "${name}"`);
2968
+ return state.get();
2969
+ }
2970
+ getSchemaFiles(name) {
2971
+ const state = this._schemaFiles.get(name);
2972
+ if (!state) throw new Error(`Schema files not found for "${name}"`);
2973
+ return state.get();
2974
+ }
2975
+ getSchemaYaml(name) {
2976
+ const state = this._schemaYamls.get(name);
2977
+ if (!state) throw new Error(`Schema yaml not found for "${name}"`);
2978
+ return state.get();
2979
+ }
2980
+ getChangeMetadata(changeId) {
2981
+ const state = this._changeMetadata.get(changeId);
2982
+ if (!state) return null;
2983
+ return state.get();
2984
+ }
2985
+ getArtifactOutput(changeId, outputPath) {
2986
+ const key = `${changeId}:${outputPath}`;
2987
+ const state = this._artifactOutputs.get(key);
2988
+ if (!state) return null;
2989
+ return state.get();
2990
+ }
2991
+ getGlobArtifactFiles(changeId, outputPath) {
2992
+ const key = `${changeId}:${outputPath}`;
2993
+ const state = this._globArtifactFiles.get(key);
2994
+ if (!state) return [];
2995
+ return state.get();
2996
+ }
2997
+ startStream(state, task, signal) {
2998
+ return new Promise((resolve$1) => {
2999
+ const context = new ReactiveContext();
3000
+ let first = true;
3001
+ (async () => {
3002
+ try {
3003
+ for await (const data of context.stream(task, signal)) {
3004
+ state.set(data);
3005
+ if (first) {
3006
+ first = false;
3007
+ resolve$1();
3008
+ }
3009
+ }
3010
+ } catch {}
3011
+ if (first) resolve$1();
3012
+ })();
3013
+ });
3014
+ }
3015
+ async warmupSchema(name, parentSignal) {
3016
+ const entityCtrl = new AbortController();
3017
+ this._entityControllers.set(`schema:${name}`, entityCtrl);
3018
+ const signal = this.combineSignals(parentSignal, entityCtrl.signal);
3019
+ if (!this._schemaResolutions.has(name)) this._schemaResolutions.set(name, new ReactiveState(null));
3020
+ if (!this._schemaDetails.has(name)) this._schemaDetails.set(name, new ReactiveState(null));
3021
+ if (!this._schemaFiles.has(name)) this._schemaFiles.set(name, new ReactiveState([]));
3022
+ if (!this._schemaYamls.has(name)) this._schemaYamls.set(name, new ReactiveState(null));
3023
+ if (!this._templates.has(name)) this._templates.set(name, new ReactiveState({}));
3024
+ if (!this._templateContents.has(name)) this._templateContents.set(name, new ReactiveState({}));
3025
+ if (!this._templates.has("")) this._templates.set("", new ReactiveState({}));
3026
+ if (!this._templateContents.has("")) this._templateContents.set("", new ReactiveState({}));
3027
+ await Promise.all([
3028
+ this.startStream(this._schemaResolutions.get(name), () => this.fetchSchemaResolution(name), signal),
3029
+ this.startStream(this._schemaDetails.get(name), () => this.fetchSchemaDetail(name), signal),
3030
+ this.startStream(this._schemaFiles.get(name), () => this.fetchSchemaFiles(name), signal),
3031
+ this.startStream(this._schemaYamls.get(name), () => this.fetchSchemaYaml(name), signal),
3032
+ this.startStream(this._templates.get(name), () => this.fetchTemplates(name), signal),
3033
+ this.startStream(this._templateContents.get(name), () => this.fetchTemplateContents(name), signal),
3034
+ this.startStream(this._templates.get(""), () => this.fetchTemplates(void 0), signal),
3035
+ this.startStream(this._templateContents.get(""), () => this.fetchTemplateContents(void 0), signal)
3036
+ ]);
3037
+ }
3038
+ async warmupChange(changeId, parentSignal) {
3039
+ const entityCtrl = new AbortController();
3040
+ this._entityControllers.set(`change:${changeId}`, entityCtrl);
3041
+ const signal = this.combineSignals(parentSignal, entityCtrl.signal);
3042
+ const statusKey = `${changeId}:`;
3043
+ if (!this._statuses.has(statusKey)) this._statuses.set(statusKey, new ReactiveState(null));
3044
+ if (!this._changeMetadata.has(changeId)) this._changeMetadata.set(changeId, new ReactiveState(null));
3045
+ const applyKey = `${changeId}:`;
3046
+ if (!this._applyInstructions.has(applyKey)) this._applyInstructions.set(applyKey, new ReactiveState(null));
3047
+ await Promise.all([
3048
+ this.startStream(this._statuses.get(statusKey), () => this.fetchStatus(changeId, void 0), signal),
3049
+ this.startStream(this._changeMetadata.get(changeId), () => this.fetchChangeMetadata(changeId), signal),
3050
+ this.startStream(this._applyInstructions.get(applyKey), () => this.fetchApplyInstructions(changeId, void 0), signal)
3051
+ ]);
3052
+ const status = this._statuses.get(statusKey)?.get();
3053
+ if (status?.artifacts) await Promise.all(status.artifacts.map(async (artifact) => {
3054
+ const instrKey = `${changeId}:${artifact.id}:`;
3055
+ if (!this._instructions.has(instrKey)) this._instructions.set(instrKey, new ReactiveState(null));
3056
+ await this.startStream(this._instructions.get(instrKey), () => this.fetchInstructions(changeId, artifact.id, void 0), signal);
3057
+ const outputKey = `${changeId}:${artifact.outputPath}`;
3058
+ if (!this._artifactOutputs.has(outputKey)) this._artifactOutputs.set(outputKey, new ReactiveState(null));
3059
+ await this.startStream(this._artifactOutputs.get(outputKey), () => this.fetchArtifactOutput(changeId, artifact.outputPath), signal);
3060
+ if (artifact.outputPath.includes("*") || artifact.outputPath.includes("?") || artifact.outputPath.includes("[")) {
3061
+ const globKey = `${changeId}:${artifact.outputPath}`;
3062
+ if (!this._globArtifactFiles.has(globKey)) this._globArtifactFiles.set(globKey, new ReactiveState([]));
3063
+ await this.startStream(this._globArtifactFiles.get(globKey), () => readGlobArtifactFiles(this.projectDir, changeId, artifact.outputPath), signal);
3064
+ }
3065
+ }));
3066
+ }
3067
+ watchSchemaChanges(signal) {
3068
+ const context = new ReactiveContext();
3069
+ (async () => {
3070
+ let prevNames = new Set(this._schemas.get().map((s) => s.name));
3071
+ try {
3072
+ for await (const schemas of context.stream(() => Promise.resolve(this._schemas.get()), signal)) {
3073
+ const newNames = new Set(schemas.map((s) => s.name));
3074
+ for (const name of newNames) if (!prevNames.has(name)) this.warmupSchema(name, signal);
3075
+ for (const name of prevNames) if (!newNames.has(name)) {
3076
+ this.teardownEntity(`schema:${name}`);
3077
+ this._schemaResolutions.delete(name);
3078
+ this._schemaDetails.delete(name);
3079
+ this._schemaFiles.delete(name);
3080
+ this._schemaYamls.delete(name);
3081
+ this._templates.delete(name);
3082
+ this._templateContents.delete(name);
3083
+ }
3084
+ prevNames = newNames;
3085
+ }
3086
+ } catch {}
3087
+ })();
3088
+ }
3089
+ watchChangeIdChanges(signal) {
3090
+ const context = new ReactiveContext();
3091
+ (async () => {
3092
+ let prevIds = new Set(this._changeIds.get());
3093
+ try {
3094
+ for await (const ids of context.stream(() => Promise.resolve(this._changeIds.get()), signal)) {
3095
+ const newIds = new Set(ids);
3096
+ for (const id of newIds) if (!prevIds.has(id)) this.warmupChange(id, signal);
3097
+ for (const id of prevIds) if (!newIds.has(id)) {
3098
+ this.teardownEntity(`change:${id}`);
3099
+ for (const key of this._statuses.keys()) if (key.startsWith(`${id}:`)) this._statuses.delete(key);
3100
+ for (const key of this._instructions.keys()) if (key.startsWith(`${id}:`)) this._instructions.delete(key);
3101
+ for (const key of this._applyInstructions.keys()) if (key.startsWith(`${id}:`)) this._applyInstructions.delete(key);
3102
+ this._changeMetadata.delete(id);
3103
+ for (const key of this._artifactOutputs.keys()) if (key.startsWith(`${id}:`)) this._artifactOutputs.delete(key);
3104
+ for (const key of this._globArtifactFiles.keys()) if (key.startsWith(`${id}:`)) this._globArtifactFiles.delete(key);
3105
+ }
3106
+ prevIds = newIds;
3107
+ }
3108
+ } catch {}
3109
+ })();
3110
+ }
3111
+ teardownEntity(key) {
3112
+ const ctrl = this._entityControllers.get(key);
3113
+ if (ctrl) {
3114
+ ctrl.abort();
3115
+ this._entityControllers.delete(key);
3116
+ }
3117
+ }
3118
+ async fetchSchemas() {
3119
+ await touchOpsxProjectDeps(this.projectDir);
3120
+ const result = await this.cliExecutor.schemas();
3121
+ if (!result.success) throw new Error(result.stderr || `openspec schemas failed (exit ${result.exitCode ?? "null"})`);
3122
+ return parseCliJson(result.stdout, z.array(SchemaInfoSchema), "openspec schemas");
3123
+ }
3124
+ async fetchChangeIds() {
3125
+ return reactiveReadDir(join$1(this.projectDir, "openspec", "changes"), {
3126
+ directoriesOnly: true,
3127
+ includeHidden: false,
3128
+ exclude: ["archive"]
3129
+ });
3130
+ }
3131
+ async fetchProjectConfig() {
3132
+ return reactiveReadFile(join$1(this.projectDir, "openspec", "config.yaml"));
3133
+ }
3134
+ async fetchStatus(changeId, schema) {
3135
+ await touchOpsxProjectDeps(this.projectDir);
3136
+ await touchOpsxChangeDeps(this.projectDir, changeId);
3137
+ const args = [
3138
+ "status",
3139
+ "--json",
3140
+ "--change",
3141
+ changeId
3142
+ ];
3143
+ if (schema) args.push("--schema", schema);
3144
+ const result = await this.cliExecutor.execute(args);
3145
+ if (!result.success) throw new Error(result.stderr || `openspec status failed (exit ${result.exitCode ?? "null"})`);
3146
+ const status = parseCliJson(result.stdout, ChangeStatusSchema, "openspec status");
3147
+ const changeRelDir = `openspec/changes/${changeId}`;
3148
+ for (const artifact of status.artifacts) artifact.relativePath = `${changeRelDir}/${artifact.outputPath}`;
3149
+ return status;
3150
+ }
3151
+ async fetchStatusList() {
3152
+ const changeIds = this._changeIds.get();
3153
+ return Promise.all(changeIds.map((id) => {
3154
+ const key = `${id}:`;
3155
+ const state = this._statuses.get(key);
3156
+ if (state) return Promise.resolve(state.get());
3157
+ return this.fetchStatus(id);
3158
+ }));
3159
+ }
3160
+ async fetchInstructions(changeId, artifact, schema) {
3161
+ await touchOpsxProjectDeps(this.projectDir);
3162
+ await touchOpsxChangeDeps(this.projectDir, changeId);
3163
+ const args = [
3164
+ "instructions",
3165
+ artifact,
3166
+ "--json",
3167
+ "--change",
3168
+ changeId
3169
+ ];
3170
+ if (schema) args.push("--schema", schema);
3171
+ const result = await this.cliExecutor.execute(args);
3172
+ if (!result.success) throw new Error(result.stderr || `openspec instructions failed (exit ${result.exitCode ?? "null"})`);
3173
+ return parseCliJson(result.stdout, ArtifactInstructionsSchema, "openspec instructions");
3174
+ }
3175
+ async fetchApplyInstructions(changeId, schema) {
3176
+ await touchOpsxProjectDeps(this.projectDir);
3177
+ await touchOpsxChangeDeps(this.projectDir, changeId);
3178
+ const args = [
3179
+ "instructions",
3180
+ "apply",
3181
+ "--json",
3182
+ "--change",
3183
+ changeId
3184
+ ];
3185
+ if (schema) args.push("--schema", schema);
3186
+ const result = await this.cliExecutor.execute(args);
3187
+ if (!result.success) throw new Error(result.stderr || `openspec instructions apply failed (exit ${result.exitCode ?? "null"})`);
3188
+ return parseCliJson(result.stdout, ApplyInstructionsSchema, "openspec instructions apply");
3189
+ }
3190
+ async fetchSchemaResolution(name) {
3191
+ await touchOpsxProjectDeps(this.projectDir);
3192
+ const result = await this.cliExecutor.schemaWhich(name);
3193
+ if (!result.success) throw new Error(result.stderr || `openspec schema which failed (exit ${result.exitCode ?? "null"})`);
3194
+ return parseCliJson(result.stdout, SchemaResolutionSchema, "openspec schema which");
3195
+ }
3196
+ async fetchSchemaDetail(name) {
3197
+ await touchOpsxProjectDeps(this.projectDir);
3198
+ const schemaPath = join$1((await this.fetchSchemaResolution(name)).path, "schema.yaml");
3199
+ const content = await reactiveReadFile(schemaPath);
3200
+ if (!content) throw new Error(`schema.yaml not found at ${schemaPath}`);
3201
+ return parseSchemaYamlInline(content);
3202
+ }
3203
+ async fetchSchemaFiles(name) {
3204
+ await touchOpsxProjectDeps(this.projectDir);
3205
+ return readEntriesUnderRoot((await this.fetchSchemaResolution(name)).path);
3206
+ }
3207
+ async fetchSchemaYaml(name) {
3208
+ await touchOpsxProjectDeps(this.projectDir);
3209
+ return reactiveReadFile(join$1((await this.fetchSchemaResolution(name)).path, "schema.yaml"));
3210
+ }
3211
+ async fetchTemplates(schema) {
3212
+ await touchOpsxProjectDeps(this.projectDir);
3213
+ const result = await this.cliExecutor.templates(schema);
3214
+ if (!result.success) throw new Error(result.stderr || `openspec templates failed (exit ${result.exitCode ?? "null"})`);
3215
+ return parseCliJson(result.stdout, TemplatesSchema, "openspec templates");
3216
+ }
3217
+ async fetchTemplateContents(schema) {
3218
+ const templates = await this.fetchTemplates(schema);
3219
+ const entries = await Promise.all(Object.entries(templates).map(async ([artifactId, info]) => {
3220
+ return [artifactId, {
3221
+ content: await reactiveReadFile(info.path),
3222
+ path: info.path,
3223
+ source: info.source
3224
+ }];
3225
+ }));
3226
+ return Object.fromEntries(entries);
3227
+ }
3228
+ async fetchChangeMetadata(changeId) {
3229
+ return reactiveReadFile(join$1(this.projectDir, "openspec", "changes", changeId, ".openspec.yaml"));
3230
+ }
3231
+ async fetchArtifactOutput(changeId, outputPath) {
3232
+ return reactiveReadFile(join$1(this.projectDir, "openspec", "changes", changeId, outputPath));
3233
+ }
3234
+ /**
3235
+ * Ensure a per-change status stream exists. If not yet warmed up,
3236
+ * creates the state and starts a stream lazily.
3237
+ */
3238
+ ensureStatus(changeId, schema) {
3239
+ const key = `${changeId}:${schema ?? ""}`;
3240
+ if (!this._statuses.has(key)) {
3241
+ this._statuses.set(key, new ReactiveState(null));
3242
+ this.startStream(this._statuses.get(key), () => this.fetchStatus(changeId, schema), this.controller.signal);
3243
+ }
3244
+ }
3245
+ ensureInstructions(changeId, artifact, schema) {
3246
+ const key = `${changeId}:${artifact}:${schema ?? ""}`;
3247
+ if (!this._instructions.has(key)) {
3248
+ this._instructions.set(key, new ReactiveState(null));
3249
+ this.startStream(this._instructions.get(key), () => this.fetchInstructions(changeId, artifact, schema), this.controller.signal);
3250
+ }
3251
+ }
3252
+ ensureApplyInstructions(changeId, schema) {
3253
+ const key = `${changeId}:${schema ?? ""}`;
3254
+ if (!this._applyInstructions.has(key)) {
3255
+ this._applyInstructions.set(key, new ReactiveState(null));
3256
+ this.startStream(this._applyInstructions.get(key), () => this.fetchApplyInstructions(changeId, schema), this.controller.signal);
3257
+ }
3258
+ }
3259
+ ensureArtifactOutput(changeId, outputPath) {
3260
+ const key = `${changeId}:${outputPath}`;
3261
+ if (!this._artifactOutputs.has(key)) {
3262
+ this._artifactOutputs.set(key, new ReactiveState(null));
3263
+ this.startStream(this._artifactOutputs.get(key), () => this.fetchArtifactOutput(changeId, outputPath), this.controller.signal);
3264
+ }
3265
+ }
3266
+ ensureGlobArtifactFiles(changeId, outputPath) {
3267
+ const key = `${changeId}:${outputPath}`;
3268
+ if (!this._globArtifactFiles.has(key)) {
3269
+ this._globArtifactFiles.set(key, new ReactiveState([]));
3270
+ this.startStream(this._globArtifactFiles.get(key), () => readGlobArtifactFiles(this.projectDir, changeId, outputPath), this.controller.signal);
3271
+ }
3272
+ }
3273
+ ensureSchemaResolution(name) {
3274
+ if (!this._schemaResolutions.has(name)) {
3275
+ this._schemaResolutions.set(name, new ReactiveState(null));
3276
+ this.startStream(this._schemaResolutions.get(name), () => this.fetchSchemaResolution(name), this.controller.signal);
3277
+ }
3278
+ }
3279
+ ensureSchemaDetail(name) {
3280
+ if (!this._schemaDetails.has(name)) {
3281
+ this._schemaDetails.set(name, new ReactiveState(null));
3282
+ this.startStream(this._schemaDetails.get(name), () => this.fetchSchemaDetail(name), this.controller.signal);
3283
+ }
3284
+ }
3285
+ ensureSchemaFiles(name) {
3286
+ if (!this._schemaFiles.has(name)) {
3287
+ this._schemaFiles.set(name, new ReactiveState([]));
3288
+ this.startStream(this._schemaFiles.get(name), () => this.fetchSchemaFiles(name), this.controller.signal);
3289
+ }
3290
+ }
3291
+ ensureSchemaYaml(name) {
3292
+ if (!this._schemaYamls.has(name)) {
3293
+ this._schemaYamls.set(name, new ReactiveState(null));
3294
+ this.startStream(this._schemaYamls.get(name), () => this.fetchSchemaYaml(name), this.controller.signal);
3295
+ }
3296
+ }
3297
+ ensureTemplates(schema) {
3298
+ const key = schema ?? "";
3299
+ if (!this._templates.has(key)) {
3300
+ this._templates.set(key, new ReactiveState({}));
3301
+ this.startStream(this._templates.get(key), () => this.fetchTemplates(schema), this.controller.signal);
3302
+ }
3303
+ }
3304
+ ensureTemplateContents(schema) {
3305
+ const key = schema ?? "";
3306
+ if (!this._templateContents.has(key)) {
3307
+ this._templateContents.set(key, new ReactiveState({}));
3308
+ this.startStream(this._templateContents.get(key), () => this.fetchTemplateContents(schema), this.controller.signal);
3309
+ }
3310
+ }
3311
+ ensureChangeMetadata(changeId) {
3312
+ if (!this._changeMetadata.has(changeId)) {
3313
+ this._changeMetadata.set(changeId, new ReactiveState(null));
3314
+ this.startStream(this._changeMetadata.get(changeId), () => this.fetchChangeMetadata(changeId), this.controller.signal);
3315
+ }
3316
+ }
3317
+ combineSignals(a, b) {
3318
+ const ctrl = new AbortController();
3319
+ const abort = () => ctrl.abort();
3320
+ if (a.aborted || b.aborted) {
3321
+ ctrl.abort();
3322
+ return ctrl.signal;
3323
+ }
3324
+ a.addEventListener("abort", abort, { once: true });
3325
+ b.addEventListener("abort", abort, { once: true });
3326
+ return ctrl.signal;
3327
+ }
3328
+ };
3329
+ const SchemaYamlArtifactSchema = z.object({
3330
+ id: z.string(),
3331
+ generates: z.string(),
3332
+ description: z.string().optional(),
3333
+ template: z.string().optional(),
3334
+ instruction: z.string().optional(),
3335
+ requires: z.array(z.string()).optional()
3336
+ });
3337
+ const SchemaYamlSchema = z.object({
3338
+ name: z.string(),
3339
+ version: z.union([z.string(), z.number()]).optional(),
3340
+ description: z.string().optional(),
3341
+ artifacts: z.array(SchemaYamlArtifactSchema),
3342
+ apply: z.object({
3343
+ requires: z.array(z.string()).optional(),
3344
+ tracks: z.string().optional(),
3345
+ instruction: z.string().optional()
3346
+ }).optional()
3347
+ });
3348
+ function parseSchemaYamlInline(content) {
3349
+ const raw = parse(content);
3350
+ const parsed = SchemaYamlSchema.safeParse(raw);
3351
+ if (!parsed.success) throw new Error(`Invalid schema.yaml: ${parsed.error.message}`);
3352
+ const { artifacts, apply, name, description, version } = parsed.data;
3353
+ const detail = {
3354
+ name,
3355
+ description,
3356
+ version,
3357
+ artifacts: artifacts.map((artifact) => ({
3358
+ id: artifact.id,
3359
+ outputPath: artifact.generates,
3360
+ description: artifact.description,
3361
+ template: artifact.template,
3362
+ instruction: artifact.instruction,
3363
+ requires: artifact.requires ?? []
3364
+ })),
3365
+ applyRequires: apply?.requires ?? [],
3366
+ applyTracks: apply?.tracks,
3367
+ applyInstruction: apply?.instruction
3368
+ };
3369
+ const validated = SchemaDetailSchema.safeParse(detail);
3370
+ if (!validated.success) throw new Error(`Invalid schema detail: ${validated.error.message}`);
3371
+ return validated.data;
3372
+ }
3373
+
3374
+ //#endregion
3375
+ //#region src/pty-protocol.ts
3376
+ const PositiveInt = z.number().int().positive();
3377
+ const PtyPlatformSchema = z.enum([
3378
+ "windows",
3379
+ "macos",
3380
+ "common"
3381
+ ]);
3382
+ const PtySessionInfoSchema = z.object({
3383
+ id: z.string().min(1),
3384
+ title: z.string(),
3385
+ command: z.string(),
3386
+ args: z.array(z.string()),
3387
+ platform: PtyPlatformSchema,
3388
+ isExited: z.boolean(),
3389
+ exitCode: z.number().int().nullable()
3390
+ });
3391
+ const PtyCreateMessageSchema = z.object({
3392
+ type: z.literal("create"),
3393
+ requestId: z.string().min(1),
3394
+ cols: PositiveInt.optional(),
3395
+ rows: PositiveInt.optional(),
3396
+ command: z.string().min(1).optional(),
3397
+ args: z.array(z.string()).optional()
3398
+ });
3399
+ const PtyInputMessageSchema = z.object({
3400
+ type: z.literal("input"),
3401
+ sessionId: z.string().min(1),
3402
+ data: z.string()
3403
+ });
3404
+ const PtyResizeMessageSchema = z.object({
3405
+ type: z.literal("resize"),
3406
+ sessionId: z.string().min(1),
3407
+ cols: PositiveInt,
3408
+ rows: PositiveInt
3409
+ });
3410
+ const PtyCloseMessageSchema = z.object({
3411
+ type: z.literal("close"),
3412
+ sessionId: z.string().min(1)
3413
+ });
3414
+ const PtyAttachMessageSchema = z.object({
3415
+ type: z.literal("attach"),
3416
+ sessionId: z.string().min(1),
3417
+ cols: PositiveInt.optional(),
3418
+ rows: PositiveInt.optional()
3419
+ });
3420
+ const PtyListMessageSchema = z.object({ type: z.literal("list") });
3421
+ const PtyClientMessageSchema = z.discriminatedUnion("type", [
3422
+ PtyCreateMessageSchema,
3423
+ PtyInputMessageSchema,
3424
+ PtyResizeMessageSchema,
3425
+ PtyCloseMessageSchema,
3426
+ PtyAttachMessageSchema,
3427
+ PtyListMessageSchema
3428
+ ]);
3429
+ const PtyCreatedResponseSchema = z.object({
3430
+ type: z.literal("created"),
3431
+ requestId: z.string().min(1),
3432
+ sessionId: z.string().min(1),
3433
+ platform: PtyPlatformSchema
3434
+ });
3435
+ const PtyOutputResponseSchema = z.object({
3436
+ type: z.literal("output"),
3437
+ sessionId: z.string().min(1),
3438
+ data: z.string()
3439
+ });
3440
+ const PtyExitResponseSchema = z.object({
3441
+ type: z.literal("exit"),
3442
+ sessionId: z.string().min(1),
3443
+ exitCode: z.number().int()
3444
+ });
3445
+ const PtyTitleResponseSchema = z.object({
3446
+ type: z.literal("title"),
3447
+ sessionId: z.string().min(1),
3448
+ title: z.string()
3449
+ });
3450
+ const PtyBufferResponseSchema = z.object({
3451
+ type: z.literal("buffer"),
3452
+ sessionId: z.string().min(1),
3453
+ data: z.string()
3454
+ });
3455
+ const PtyListResponseSchema = z.object({
3456
+ type: z.literal("list"),
3457
+ sessions: z.array(PtySessionInfoSchema)
3458
+ });
3459
+ const PtyErrorCodeSchema = z.enum([
3460
+ "INVALID_JSON",
3461
+ "INVALID_MESSAGE",
3462
+ "SESSION_NOT_FOUND"
3463
+ ]);
3464
+ const PtyErrorResponseSchema = z.object({
3465
+ type: z.literal("error"),
3466
+ code: PtyErrorCodeSchema,
3467
+ message: z.string().min(1),
3468
+ sessionId: z.string().min(1).optional()
3469
+ });
3470
+ const PtyServerMessageSchema = z.discriminatedUnion("type", [
3471
+ PtyCreatedResponseSchema,
3472
+ PtyOutputResponseSchema,
3473
+ PtyExitResponseSchema,
3474
+ PtyTitleResponseSchema,
3475
+ PtyBufferResponseSchema,
3476
+ PtyListResponseSchema,
3477
+ PtyErrorResponseSchema
3478
+ ]);
3479
+
3480
+ //#endregion
3481
+ export { AI_TOOLS, ApplyInstructionsSchema, ApplyTaskSchema, ArtifactInstructionsSchema, ArtifactStatusSchema, ChangeFileSchema, ChangeSchema, ChangeStatusSchema, CliExecutor, ConfigManager, DEFAULT_CONFIG, DeltaOperationType, DeltaSchema, DeltaSpecSchema, DependencyInfoSchema, MarkdownParser, OpenSpecAdapter, OpenSpecUIConfigSchema, OpenSpecWatcher, OpsxKernel, ProjectWatcher, PtyAttachMessageSchema, PtyBufferResponseSchema, PtyClientMessageSchema, PtyCloseMessageSchema, PtyCreateMessageSchema, PtyCreatedResponseSchema, PtyErrorCodeSchema, PtyErrorResponseSchema, PtyExitResponseSchema, PtyInputMessageSchema, PtyListMessageSchema, PtyListResponseSchema, PtyOutputResponseSchema, PtyPlatformSchema, PtyResizeMessageSchema, PtyServerMessageSchema, PtyTitleResponseSchema, ReactiveContext, ReactiveState, RequirementSchema, SchemaArtifactSchema, SchemaDetailSchema, SchemaInfoSchema, SchemaResolutionSchema, SpecSchema, TaskSchema, TemplatesSchema, Validator, acquireWatcher, clearCache, closeAllProjectWatchers, closeAllWatchers, contextStorage, createFileChangeObservable, getActiveWatcherCount, getAllToolIds, getAllTools, getAvailableToolIds, getAvailableTools, getCacheSize, getConfiguredTools, getDefaultCliCommand, getDefaultCliCommandString, getProjectWatcher, getToolById, getWatchedProjectDir, initWatcherPool, isGlobPattern, isToolConfigured, isWatcherPoolInitialized, parseCliCommand, reactiveExists, reactiveReadDir, reactiveReadFile, reactiveStat, sniffGlobalCli };