skill-family-engineering-kit 0.19.2 → 0.20.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/package.json CHANGED
@@ -8,8 +8,8 @@
8
8
  "url": "https://github.com/ifoohoo/skill-family-engineering-kit/issues"
9
9
  },
10
10
  "dependencies": {
11
- "skill-family-contracts": "0.19.2",
12
- "skill-family-harness-node": "0.19.2"
11
+ "skill-family-contracts": "0.20.0",
12
+ "skill-family-harness-node": "0.20.0"
13
13
  },
14
14
  "description": "Build-time scaffold, adoption planning, projection, and checks.",
15
15
  "engines": {
@@ -51,7 +51,7 @@
51
51
  "url": "https://github.com/ifoohoo/skill-family-engineering-kit.git"
52
52
  },
53
53
  "type": "module",
54
- "version": "0.19.2",
54
+ "version": "0.20.0",
55
55
  "scripts": {
56
56
  "check": "node --test",
57
57
  "test": "node --test"
@@ -0,0 +1,15 @@
1
+ version: 0.19.3
2
+ date: 2026-09-08
3
+ locales:
4
+ en:
5
+ summary: Engineering Kit 0.19.3 aligns its package, release documentation, and generated projections with the Foundation Adoption Review Qoder distribution patch. Public commands and behavior are unchanged.
6
+ changes:
7
+ changed:
8
+ - Aligns the package version, release documentation, and verified public package projection with Foundation 0.19.3 without adding a command, capability, or adapter path.
9
+ upgradeNotes: Pin all three Foundation packages to exactly 0.19.3 and rebuild official Bundles under the existing source-identity and integrity rules. The capability catalog, host descriptors, drivers, since values, and maturity are unchanged.
10
+ zh-CN:
11
+ summary: Engineering Kit 0.19.3 将包版本、发布文档和生成投影与 Foundation Adoption Review 的 Qoder 分发补丁对齐。公开命令和行为保持不变。
12
+ changes:
13
+ changed:
14
+ - 包版本、发布文档和经验证的公开 package.json 投影与 Foundation 0.19.3 数值对齐,不增加命令、能力或 adapter 路径。
15
+ upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.19.3,并按既有来源身份与完整性规则重建官方 Bundle。能力目录、宿主 descriptor、driver、since 字段和成熟度均不变。
@@ -0,0 +1,21 @@
1
+ version: 0.20.0
2
+ date: 2026-09-10
3
+ locales:
4
+ en:
5
+ summary: Engineering Kit 0.20.0 adds Cursor project Skill projection and a fixed Cursor Agent verification driver on the existing host-verification path.
6
+ changes:
7
+ added:
8
+ - Registers `.cursor/skills` as the Cursor project Skill root while preserving manual lifecycle planning.
9
+ - Adds `cursor-agent-print-v1` with fixed workspace, ask-mode, sandbox, JSON output, version, and failure checks.
10
+ changed:
11
+ - Qualifies project-level Skill discovery through Cursor CLI menu discovery and one explicit invocation, without requiring a duplicate IDE check.
12
+ upgradeNotes: Pin all three Foundation packages to exactly 0.20.0 and rebuild official Bundles. Cursor stays manual/developer-preview; the driver does not add automatic trust, model selection, or Marketplace lifecycle management.
13
+ zh-CN:
14
+ summary: Engineering Kit 0.20.0 在既有宿主验证路径上增加 Cursor 项目级 Skill 投影和固定 Cursor Agent 验证驱动。
15
+ changes:
16
+ added:
17
+ - 登记 `.cursor/skills` 作为 Cursor 项目级 Skill 根,生命周期计划继续保持手动。
18
+ - 增加 `cursor-agent-print-v1`,固定工作区、问答模式、沙箱、JSON 输出、版本与失败检查。
19
+ changed:
20
+ - 通过 Cursor CLI 菜单发现和一次显式调用取得项目级 Skill 发现资格,不重复执行 IDE 图形界面检查。
21
+ upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.20.0,并重建官方 Bundle。Cursor 继续保持 manual/developer-preview;驱动不增加自动信任、模型选择或 Marketplace 生命周期管理。
@@ -66,7 +66,7 @@ function workBuddyNativeLifecyclePlan({ request, roots, prompt }) {
66
66
  nativeStage("preflight", [{ step: "version-probe", args: ["--version"] }]),
67
67
  nativeStage("validate-v1", [manage("plugin-validate", "validate", roots.sourceRoot)]),
68
68
  nativeStage("marketplace-add", [manage("marketplace-add", "marketplace", "add", roots.sourceRoot, "--name", request.source.marketplaceId)]),
69
- nativeStage("install-v1", [manage("plugin-install", "install", plugin, "--scope", "local", "--json")]),
69
+ nativeStage("install-v1", [manage("plugin-install", "install", plugin, "--scope", "local")]),
70
70
  nativeStage("discover-v1", [manage("plugin-list", "list", "--json")]),
71
71
  nativeStage("invoke-v1", [invoke("plugin-invoke")]),
72
72
  nativeStage("disable", [
@@ -324,6 +324,27 @@ export const QODER_DRIVER = Object.freeze({
324
324
  versionPattern: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u,
325
325
  });
326
326
 
327
+ export const CURSOR_DRIVER = Object.freeze({
328
+ hostId: "cursor",
329
+ driverId: "cursor-agent-print-v1",
330
+ driverVersion: "1.0.0",
331
+ executableBasename: "cursor-agent",
332
+ probeArgs: Object.freeze(["--version"]),
333
+ // Frozen invocation (S-047): `cursor-agent --print --output-format json
334
+ // --mode ask --sandbox enabled --workspace <workspace> <prompt>`. The
335
+ // workspace is derived from `<workspace>/.cursor/skills/<skill-id>` and is
336
+ // also the process cwd. Never add force, trust, model, history, worktree,
337
+ // remote-session or MCP auto-approval flags.
338
+ promptFlag: "--print",
339
+ outputArgs: Object.freeze(["--output-format", "json", "--mode", "ask", "--sandbox", "enabled"]),
340
+ cwdFlag: "--workspace",
341
+ promptTrailing: true,
342
+ textProtocol: true,
343
+ // Cursor currently reports a date plus a seven-character lowercase commit
344
+ // abbreviation, for example `2026.09.08-6caf4ff`.
345
+ versionPattern: /^[0-9]{4}\.(?:0[1-9]|1[0-2])\.(?:0[1-9]|[12][0-9]|3[01])-[0-9a-f]{7}$/u,
346
+ });
347
+
327
348
  export const BUILT_IN_HOST_VERIFICATION_DRIVERS = Object.freeze({
328
349
  [KIMI_DRIVER.driverId]: KIMI_DRIVER,
329
350
  [WORKBUDDY_DRIVER.driverId]: WORKBUDDY_DRIVER,
@@ -331,6 +352,7 @@ export const BUILT_IN_HOST_VERIFICATION_DRIVERS = Object.freeze({
331
352
  [CLAUDE_DRIVER.driverId]: CLAUDE_DRIVER,
332
353
  [CODEX_DRIVER.driverId]: CODEX_DRIVER,
333
354
  [QODER_DRIVER.driverId]: QODER_DRIVER,
355
+ [CURSOR_DRIVER.driverId]: CURSOR_DRIVER,
334
356
  });
335
357
 
336
358
  export function getBuiltInHostVerificationDriver(driverId) {
@@ -354,7 +376,7 @@ export function getBuiltInHostVerificationDriver(driverId) {
354
376
  * - CodeBuddy points HOME at the existing state root only; its project skill
355
377
  * discovery is the fresh cwd `.codebuddy/skills` tree and therefore does
356
378
  * not set CODEBUDDY_CONFIG_DIR.
357
- * - claude, codex and qoder point HOME at the existing state root only
379
+ * - claude, codex, qoder and cursor point HOME at the existing state root only
358
380
  * (their login state lives under HOME); no model override, --config-dir or
359
381
  * CODEX_HOME override is ever projected.
360
382
  */
@@ -376,8 +398,8 @@ export function driverEnvironment({ driver, sessionRoot, existingUserStateRoot,
376
398
  }
377
399
 
378
400
  /**
379
- * Frozen stream-protocol evaluation, one per admitted driver. All three
380
- * 0.12.0 drivers share the minimal "exit status + frozen structure" shape
401
+ * Frozen stream-protocol evaluation, one per admitted driver. Text-protocol
402
+ * drivers share the minimal "exit status + frozen structure" shape
381
403
  * (R-04/R-05/R-06): the raw stream text is read from the private evidence
382
404
  * only and never enters the public result. The product never judges any
383
405
  * fixed output string: domain output semantics belong to the consumer and
@@ -398,6 +420,9 @@ export function driverEnvironment({ driver, sessionRoot, existingUserStateRoot,
398
420
  * `is_error`; `is_error: true` (including the frozen quota-exhausted
399
421
  * error_code 118) is a determined failure that maps to the existing
400
422
  * failed + execution-failed, with no account state entering the result.
423
+ * - cursor: `--output-format json` output must be one object with
424
+ * `type=result`, `subtype=success`, `is_error=false` and a string `result`;
425
+ * extra fields are allowed and the answer text is never judged.
401
426
  */
402
427
  export function evaluateDriverStreamProtocol({ driver, stdoutText, exitOk }) {
403
428
  if (driver.driverId === CLAUDE_DRIVER.driverId) {
@@ -409,6 +434,9 @@ export function evaluateDriverStreamProtocol({ driver, stdoutText, exitOk }) {
409
434
  if (driver.driverId === QODER_DRIVER.driverId) {
410
435
  return exitOk && qoderJsonOk(stdoutText);
411
436
  }
437
+ if (driver.driverId === CURSOR_DRIVER.driverId) {
438
+ return exitOk && cursorJsonOk(stdoutText);
439
+ }
412
440
  return exitOk;
413
441
  }
414
442
 
@@ -485,3 +513,19 @@ function qoderJsonOk(stdoutText) {
485
513
  if (typeof parsed.is_error !== "boolean") return false;
486
514
  return !parsed.is_error;
487
515
  }
516
+
517
+ function cursorJsonOk(stdoutText) {
518
+ let parsed;
519
+ try {
520
+ parsed = JSON.parse(stdoutText);
521
+ } catch {
522
+ return false;
523
+ }
524
+ return typeof parsed === "object"
525
+ && parsed !== null
526
+ && !Array.isArray(parsed)
527
+ && parsed.type === "result"
528
+ && parsed.subtype === "success"
529
+ && parsed.is_error === false
530
+ && typeof parsed.result === "string";
531
+ }
@@ -14,6 +14,7 @@ import {
14
14
  CLAUDE_DRIVER,
15
15
  CODEBUDDY_DRIVER,
16
16
  CODEX_DRIVER,
17
+ CURSOR_DRIVER,
17
18
  driverEnvironment,
18
19
  evaluateDriverStreamProtocol,
19
20
  getBuiltInHostVerificationDriver,
@@ -219,9 +220,10 @@ function assertRootIsolation({ inputRoots, writeRoots, existingUserStateRoot })
219
220
  * must be a real (non-symlink) directory. The WorkBuddy config root is a
220
221
  * fresh write root under the same one-way state rule.
221
222
  *
222
- * Codex and Qoder freeze their discovery layout structurally: the installed
223
- * parent must be `<workspace>/.codex/skills` / `<fresh-ws>/.qoder/skills`
224
- * (basenames `skills` and `.codex` / `.qoder`), so the discovery target can
223
+ * Codex, Qoder and Cursor freeze their discovery layout structurally: the
224
+ * installed parent must be `<workspace>/.codex/skills`,
225
+ * `<fresh-ws>/.qoder/skills` or `<workspace>/.cursor/skills` (basenames
226
+ * `skills` and `.codex` / `.qoder` / `.cursor`), so the discovery target can
225
227
  * never escape the fresh workspace or reach a real user skill root.
226
228
  *
227
229
  * Claude freezes the classic plugin layout structurally: the installed
@@ -274,6 +276,10 @@ async function assertDiscoveryLayout({ driver, installedRoot, existingUserStateR
274
276
  if (path.basename(skillsRoot) !== "skills") throw fail("Qoder skills directory must be named skills");
275
277
  if (path.basename(path.dirname(skillsRoot)) !== ".qoder") throw fail("Qoder discovery root must be named .qoder");
276
278
  }
279
+ if (driver.driverId === CURSOR_DRIVER.driverId) {
280
+ if (path.basename(skillsRoot) !== "skills") throw fail("Cursor skills directory must be named skills");
281
+ if (path.basename(path.dirname(skillsRoot)) !== ".cursor") throw fail("Cursor discovery root must be named .cursor");
282
+ }
277
283
  if (driver.driverId === CLAUDE_DRIVER.driverId) {
278
284
  // Classic Claude plugin layout `<plugin-root>/skills/<skill-id>/SKILL.md`:
279
285
  // the skills directory basename is pinned so the --plugin-dir target
@@ -299,7 +305,9 @@ async function assertDiscoveryLayout({ driver, installedRoot, existingUserStateR
299
305
  * (`promptTrailing`) so the vector matches the frozen argv byte-for-byte:
300
306
  * claude `-p --verbose --no-session-persistence --output-format stream-json
301
307
  * <prompt> --plugin-dir <dir>`, codex `exec --json --ephemeral <prompt>`,
302
- * qoder `-p -o json --no-session-persistence --cwd <fresh-ws> <prompt>`
308
+ * qoder `-p -o json --no-session-persistence --cwd <fresh-ws> <prompt>`,
309
+ * cursor `--print --output-format json --mode ask --sandbox enabled
310
+ * --workspace <workspace> <prompt>`
303
311
  * where the --cwd target is the fresh workspace two segments above the
304
312
  * installed parent (`<fresh-ws>/.qoder/skills/<skill-id>`). The claude
305
313
  * `--plugin-dir` target is the plugin root one segment above the installed
@@ -484,16 +492,16 @@ export async function runHostVerification({ request, bindings, hostsRoot } = {})
484
492
  const installedRoot = requiredBinding(bindings.installedSkillRoot, "installedSkillRoot");
485
493
  // The install target is a fresh write root: the same central isolation
486
494
  // check covers it against every protected input root, the three other
487
- // fresh write roots, and the one-way existing user state rule. Codex
488
- // freezes `<workspace>/.codex/skills/<skill-id>` as its sole discovery
489
- // projection (DES-013 §4.2, R-07): the installed target legitimately sits
495
+ // fresh write roots, and the one-way existing user state rule. Codex and
496
+ // Cursor freeze their workspace-local skill roots: the installed target
497
+ // legitimately sits
490
498
  // inside the candidate workspace, which itself sits inside the caller's
491
- // repository, and its exact position is pinned by the codex branch below
499
+ // repository, and its exact position is pinned by the driver branches below
492
500
  // (`dirname x3(installedRoot) === workspaceRoot`), so workspaceRoot and
493
501
  // repositoryRoot are not compared in this call; every other protected
494
502
  // input root stays bidirectionally disjoint.
495
503
  assertRootIsolation({
496
- inputRoots: driver.driverId === CODEX_DRIVER.driverId
504
+ inputRoots: [CODEX_DRIVER.driverId, CURSOR_DRIVER.driverId].includes(driver.driverId)
497
505
  ? inputRoots.filter(([name]) => name !== "workspaceRoot" && name !== "repositoryRoot")
498
506
  : inputRoots,
499
507
  writeRoots: [...baseWriteRoots, ["installedSkillRoot", installedRoot]],
@@ -539,12 +547,14 @@ export async function runHostVerification({ request, bindings, hostsRoot } = {})
539
547
  if (gitInfo === null) return rejected();
540
548
  if (path.dirname(path.dirname(path.dirname(installedRoot))) !== workspaceRoot) return rejected();
541
549
  }
550
+ if (driver.driverId === CURSOR_DRIVER.driverId
551
+ && path.dirname(path.dirname(path.dirname(installedRoot))) !== workspaceRoot) return rejected();
542
552
  if (await stat(installedRoot).then(() => true, () => false)) return rejected();
543
553
  const adapterMembers = requiredMembers(bindings, "adapterMembers");
544
- if (driver.driverId === QODER_DRIVER.driverId && !adapterMembers.includes("SKILL.md")) {
545
- // R-08: a missing SKILL.md in the adapter table is a statically
546
- // decidable preflight failure (the sole discovery projection cannot
547
- // exist) — rejected before materialization with zero side effects.
554
+ if ([QODER_DRIVER.driverId, CURSOR_DRIVER.driverId].includes(driver.driverId) && !adapterMembers.includes("SKILL.md")) {
555
+ // A missing SKILL.md in the adapter table is a statically decidable
556
+ // preflight failure for these sole project-level projections — reject
557
+ // before materialization with zero side effects.
548
558
  return rejected();
549
559
  }
550
560
  if (driver.driverId === CODEX_DRIVER.driverId) {
@@ -595,10 +605,10 @@ export async function runHostVerification({ request, bindings, hostsRoot } = {})
595
605
  existingUserStateRoot,
596
606
  installedParent: path.dirname(installedRoot),
597
607
  });
598
- // S-023: codex exec must start inside the trusted repository tree
599
- // (R-07), so its process cwd is the candidate workspace, never the fresh
600
- // session root. All other drivers keep the fresh session root as cwd.
601
- const processCwd = driver.driverId === CODEX_DRIVER.driverId
608
+ // Codex and Cursor must start in the candidate workspace selected by
609
+ // their frozen invocation vectors; all other drivers keep their existing
610
+ // cwd rules.
611
+ const processCwd = [CODEX_DRIVER.driverId, CURSOR_DRIVER.driverId].includes(driver.driverId)
602
612
  ? workspaceRoot
603
613
  : driver.driverId === CODEBUDDY_DRIVER.driverId
604
614
  ? discovery.projectRoot
@@ -734,8 +744,8 @@ export async function runHostVerification({ request, bindings, hostsRoot } = {})
734
744
  if (cause?.code !== "SFC2004") throw cause;
735
745
  return finishDeterminedProcess({ status: "failed", reason: "private-evidence-publication-failed", extra: { execution: publicExecution(invocation), snapshots } });
736
746
  }
737
- // Frozen output-protocol judgement. Only the three 0.12.0 drivers
738
- // (textProtocol) decode the invocation stdout once more from the
747
+ // Frozen output-protocol judgement. Drivers marked textProtocol decode
748
+ // the invocation stdout once more from the
739
749
  // private evidence (digest-bound) as UTF-8 text and judge the closed
740
750
  // driver protocol; the raw text itself never enters the public result.
741
751
  // Kimi and WorkBuddy keep their 0.11.0 raw-byte behavior: no decode, no