@ifrankwang/openspec-agents 0.122.0 → 0.122.1

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.
@@ -34345,21 +34345,43 @@ function loadWorkflow(yamlText) {
34345
34345
  import { readFileSync as readFileSync4 } from "node:fs";
34346
34346
 
34347
34347
  // src/skills/scan.ts
34348
- import { existsSync as existsSync3 } from "node:fs";
34348
+ import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
34349
34349
  import { resolve, dirname as dirname2, join as join2 } from "node:path";
34350
34350
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34351
34351
  import { homedir } from "node:os";
34352
34352
  var projectRoot = resolve(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
34353
+ function resolveSkillScanRoot(moduleUrl) {
34354
+ const startDir = dirname2(fileURLToPath2(moduleUrl));
34355
+ let dir = startDir;
34356
+ for (;; ) {
34357
+ for (const candidate of [resolve(dir, "assets", "skills"), resolve(dir, "skills")]) {
34358
+ if (isSkillRoot(candidate))
34359
+ return candidate;
34360
+ }
34361
+ const parent = dirname2(dir);
34362
+ if (parent === dir)
34363
+ break;
34364
+ dir = parent;
34365
+ }
34366
+ return null;
34367
+ }
34368
+ function isSkillRoot(candidate) {
34369
+ if (!existsSync3(candidate))
34370
+ return false;
34371
+ try {
34372
+ return readdirSync2(candidate, { withFileTypes: true }).some((entry) => entry.isDirectory() && existsSync3(join2(candidate, entry.name, "SKILL.md")));
34373
+ } catch {
34374
+ return false;
34375
+ }
34376
+ }
34377
+ var scanRoot = resolveSkillScanRoot(import.meta.url);
34353
34378
  var SKILL_SCAN_ROOTS = [
34354
- resolve(projectRoot, "assets", "skills"),
34379
+ ...scanRoot ? [scanRoot] : [],
34355
34380
  resolve(projectRoot, ".agents", "skills"),
34356
34381
  resolve(projectRoot, ".opencode", "skills"),
34357
34382
  join2(homedir(), ".agents", "skills"),
34358
34383
  join2(homedir(), ".config", "opencode", "skills")
34359
34384
  ];
34360
- var DISTRIBUTED_SKILL_ROOTS = [
34361
- resolve(projectRoot, "assets", "skills")
34362
- ];
34363
34385
  function findSkillPath(name) {
34364
34386
  for (const root of SKILL_SCAN_ROOTS) {
34365
34387
  const p = resolve(root, name, "SKILL.md");
@@ -34370,7 +34392,7 @@ function findSkillPath(name) {
34370
34392
  }
34371
34393
 
34372
34394
  // src/skills/resolve.ts
34373
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4 } from "node:fs";
34395
+ import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4 } from "node:fs";
34374
34396
  import { resolve as resolve2 } from "node:path";
34375
34397
  var cacheKey = null;
34376
34398
  var cacheIndex = null;
@@ -34385,7 +34407,7 @@ function scanSkillTags(roots = SKILL_SCAN_ROOTS) {
34385
34407
  for (const root of roots) {
34386
34408
  if (!existsSync4(root))
34387
34409
  continue;
34388
- for (const entry of readdirSync2(root, { withFileTypes: true })) {
34410
+ for (const entry of readdirSync3(root, { withFileTypes: true })) {
34389
34411
  if (!entry.isDirectory())
34390
34412
  continue;
34391
34413
  if (seen.has(entry.name))
@@ -37070,7 +37092,7 @@ async function ensureDefaultUnattended(args, ctx) {
37070
37092
  }
37071
37093
  } catch {}
37072
37094
  }
37073
- var PKG_VERSION = "0.122.0";
37095
+ var PKG_VERSION = "0.122.1";
37074
37096
  function buildMcpServer(worktree, opts = {}) {
37075
37097
  const mcp = new McpServer({ name: "openspec-agents", version: PKG_VERSION });
37076
37098
  for (const [name, spec] of Object.entries(TOOL_SPECS)) {
@@ -39211,7 +39233,7 @@ class StreamableHTTPServerTransport {
39211
39233
  import { createServer } from "node:http";
39212
39234
 
39213
39235
  // src/core/dashboard.ts
39214
- import { readdirSync as readdirSync3, existsSync as existsSync5 } from "node:fs";
39236
+ import { readdirSync as readdirSync4, existsSync as existsSync5 } from "node:fs";
39215
39237
  import { readFile as readFile5 } from "node:fs/promises";
39216
39238
  import path7 from "path";
39217
39239
  function toWorkItemCard(item) {
@@ -39268,7 +39290,7 @@ async function readDashboardState(worktree, changeId) {
39268
39290
  const stateDir = getStateDir(worktree);
39269
39291
  if (!existsSync5(stateDir))
39270
39292
  return null;
39271
- const files = readdirSync3(stateDir).filter((f) => f.endsWith(".json"));
39293
+ const files = readdirSync4(stateDir).filter((f) => f.endsWith(".json"));
39272
39294
  const results = [];
39273
39295
  for (const f of files) {
39274
39296
  try {
@@ -39356,7 +39378,7 @@ async function startDashboard(worktree) {
39356
39378
  console.error(`[dashboard] 无法启动编排进度看板:端口 ${BASE_PORT}-${BASE_PORT + MAX_ATTEMPTS - 1} 均被占用`);
39357
39379
  }
39358
39380
  // src/core/workflow/collector.ts
39359
- import { readdirSync as readdirSync4, readFileSync as readFileSync6 } from "node:fs";
39381
+ import { readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
39360
39382
  import { join as join4 } from "node:path";
39361
39383
  var DEFAULT_POLL_INTERVAL_MS = 30000;
39362
39384
  var SOURCE = "openspec";
@@ -39394,7 +39416,7 @@ class OpenSpecCollector {
39394
39416
  const changesDir = join4(this.openspecDir, "changes");
39395
39417
  let entries;
39396
39418
  try {
39397
- entries = readdirSync4(changesDir, { withFileTypes: true });
39419
+ entries = readdirSync5(changesDir, { withFileTypes: true });
39398
39420
  } catch {
39399
39421
  return [];
39400
39422
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifrankwang/openspec-agents",
3
- "version": "0.122.0",
3
+ "version": "0.122.1",
4
4
  "type": "module",
5
5
  "main": "./src/core/index.ts",
6
6
  "exports": {
@@ -1,12 +1,52 @@
1
- import { existsSync } from "node:fs"
1
+ import { existsSync, readdirSync } from "node:fs"
2
2
  import { resolve, dirname, join } from "node:path"
3
3
  import { fileURLToPath } from "node:url"
4
4
  import { homedir } from "node:os"
5
5
 
6
6
  const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..")
7
7
 
8
+ /**
9
+ * 定位随包分发的 skill 根目录:源码(src/skills/ 上溯 2 级=仓库根 assets/skills)、插件包 dist 形态
10
+ * (<插件根>/.mcp-server/ 上溯 1 级=插件根 skills/,plugin-common buildSkills 输出)与 ZCode 缓存嵌套形态
11
+ * (cache/<marketplace>/<plugin>/<version>/.mcp-server/ 上溯 1 级=版本目录 skills/)部署深度不同,
12
+ * 故从模块所在目录逐级上溯探测(与 resolveWorkflowFilePath 同范式)。每级按固定顺序探测两个候选:
13
+ * assets/skills 优先、skills 其次;命中要求目录下至少有一个子目录含 SKILL.md(防御空骨架目录,
14
+ * 如仓库根遗留的空 skills/)。首个命中即采用(首中即用,不做多根收集——避免 npm 形态上溯越过
15
+ * 包根误收消费方项目根)。bundle 单文件合并后 import.meta.url 指向部署位置(cli.mjs),源码形态
16
+ * 指向本文件自身,两者自然成立。
17
+ */
18
+ export function resolveSkillScanRoot(moduleUrl: string): string | null {
19
+ const startDir = dirname(fileURLToPath(moduleUrl))
20
+ let dir = startDir
21
+ for (;;) {
22
+ for (const candidate of [resolve(dir, "assets", "skills"), resolve(dir, "skills")]) {
23
+ if (isSkillRoot(candidate)) return candidate
24
+ }
25
+ const parent = dirname(dir)
26
+ if (parent === dir) break // 到达文件系统根
27
+ dir = parent
28
+ }
29
+ return null
30
+ }
31
+
32
+ /** 目录存在且至少有一个子目录含 SKILL.md 才算 skill 根;空骨架目录(子目录无 SKILL.md)不命中。 */
33
+ function isSkillRoot(candidate: string): boolean {
34
+ if (!existsSync(candidate)) return false
35
+ try {
36
+ return readdirSync(candidate, { withFileTypes: true }).some(
37
+ (entry) => entry.isDirectory() && existsSync(join(candidate, entry.name, "SKILL.md")),
38
+ )
39
+ } catch {
40
+ return false // 不可读目录按未命中处理(优雅降级,不阻断扫描)
41
+ }
42
+ }
43
+
44
+ // 随包分发 skill 缺失时优雅降级(返回 null):只影响视图加载建议与门禁索引,不阻断 server 启动;
45
+ // 与 loader 的 workflow 缺失即抛错差异是有意为之——opx_* 工具全部依赖 workflow,缺失时 server 无可用性。
46
+ const scanRoot = resolveSkillScanRoot(import.meta.url)
47
+
8
48
  export const SKILL_SCAN_ROOTS: string[] = [
9
- resolve(projectRoot, "assets", "skills"),
49
+ ...(scanRoot ? [scanRoot] : []),
10
50
  resolve(projectRoot, ".agents", "skills"),
11
51
  resolve(projectRoot, ".opencode", "skills"),
12
52
  join(homedir(), ".agents", "skills"),
@@ -14,9 +54,8 @@ export const SKILL_SCAN_ROOTS: string[] = [
14
54
  ]
15
55
 
16
56
  // 仅随插件分发注入 opencode 的 skill 根目录。.agents/skills 靠 opencode 标准发现对本仓库自用生效,非插件注入职责。
17
- export const DISTRIBUTED_SKILL_ROOTS: string[] = [
18
- resolve(projectRoot, "assets", "skills"),
19
- ]
57
+ // 源码形态探测根即仓库根 assets/skills(与改动前行为一致);插件形态为插件根 skills/。
58
+ export const DISTRIBUTED_SKILL_ROOTS: string[] = scanRoot ? [scanRoot] : []
20
59
 
21
60
  export function findSkillPath(name: string): string | null {
22
61
  for (const root of SKILL_SCAN_ROOTS) {
@@ -25,5 +64,3 @@ export function findSkillPath(name: string): string | null {
25
64
  }
26
65
  return null
27
66
  }
28
-
29
-