skill-family-engineering-kit 0.26.0 → 0.27.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.26.0",
12
- "skill-family-harness-node": "0.26.0",
11
+ "skill-family-contracts": "0.27.0",
12
+ "skill-family-harness-node": "0.27.0",
13
13
  "yaml": "2.9.0"
14
14
  },
15
15
  "description": "Build-time scaffold, adoption planning, projection, and checks.",
@@ -52,7 +52,7 @@
52
52
  "url": "https://github.com/ifoohoo/skill-family-engineering-kit.git"
53
53
  },
54
54
  "type": "module",
55
- "version": "0.26.0",
55
+ "version": "0.27.0",
56
56
  "scripts": {
57
57
  "check": "node --test",
58
58
  "test": "node --test"
@@ -0,0 +1,21 @@
1
+ version: 0.27.0
2
+ date: 2026-10-21
3
+ locales:
4
+ en:
5
+ summary: "Engineering Kit 0.27.0 adds the public function `runRegisterExisting` and the CLI sub-action `skill-family-kit check register-existing` under the existing `check` command. The four top-level commands remain `scaffold`, `adopt-plan`, `projection`, and `check`. The sub-action is the only legal first write of `.foundation/file-registry.json`, `skill-family.project-manifest.json`, and `skill-family.managed-file-lock.json` into a nonempty root that already has a valid `profile.json`. Relock never seeds. Scaffold still refuses nonempty directories. Adopt-plan remains read-only. Projection still requires an existing legal registry."
6
+ changes:
7
+ changed:
8
+ - "Add the public function `runRegisterExisting` and the CLI `skill-family-kit check register-existing`. The API `root` must be an absolute project working directory; the CLI resolves the current directory or a relative path to an absolute path first. `--handwritten` is required and non-empty."
9
+ - "Missing all three closed-world documents yields exclusive create. All three present and byte-identical yields idempotent success with zero write. Any other combination, class overlap, or drifted bytes refuses with zero overwrite."
10
+ - "Relock still never seeds a missing registry. Scaffold still refuses nonempty directories. Adopt-plan remains strictly read-only. Projection and later relock still require an existing legal registry."
11
+ - "The package moves to 0.27.0 with the other three units. Contracts specification remains 1.20.0. Published 0.26.0 capabilities are unchanged and are not redone."
12
+ upgradeNotes: "Pin the three Foundation npm packages to exactly 0.27.0 and keep the independent Plugin at that release version. Contracts specification 1.20.0 remains a separate coordinate from npm package versions. Consume the actual published version's public interfaces and sources. First nonempty registration does not prove business qualification or completed initialization. This note does not replace post-publish verification."
13
+ zh-CN:
14
+ summary: "Engineering Kit 0.27.0 在既有 `check` 下新增公共函数 `runRegisterExisting` 与 CLI 子动作 `skill-family-kit check register-existing`。四个顶层命令仍为 `scaffold`、`adopt-plan`、`projection`、`check`。该子动作是已有有效 `profile.json` 的非空根首次写入 `.foundation/file-registry.json`、`skill-family.project-manifest.json`、`skill-family.managed-file-lock.json` 的唯一合法入口。`relock` 不为缺锁工程播种。`scaffold` 仍拒绝非空目录。`adopt-plan` 仍只读。`projection` 仍要求已有合法登记。"
15
+ changes:
16
+ changed:
17
+ - "新增公共函数 `runRegisterExisting` 与 CLI `skill-family-kit check register-existing`。API 的 `root` 必须是绝对项目工作目录;CLI 会把当前目录或相对路径先解析成绝对路径。`--handwritten` 必填且非空。"
18
+ - "三份闭域文件均不存在时排他创建;三份均在且字节一致时幂等成功且零写入;任一其他组合、分类重叠或字节漂移均拒绝且零覆盖。"
19
+ - "`relock` 仍不为缺锁工程播种。`scaffold` 仍拒绝非空目录。`adopt-plan` 仍严格只读。`projection` 与后续 `relock` 仍要求已有合法登记。"
20
+ - "包版本与其他三单元锁步到 0.27.0。Contracts 规格仍为 1.20.0。已发布的 0.26.0 能力保持原样,本版本不重做。"
21
+ upgradeNotes: "三个 Foundation npm 包须精确锁定 0.27.0,独立插件与三包保持相同发布版本。Contracts 规格仍为 1.20.0,与 npm 包版本分别处理。消费以实际发布版本的公开接口和源码为准。首次非空登记不证明业务资格或初始化完成。本说明不替代发布后验证。"
@@ -360,7 +360,7 @@ export function buildProfileDraft({ inputs, migrationManifest, writeSet, entries
360
360
  ready: incompleteFields.length === 0,
361
361
  binding: { targetSetDigest, foundationPlanDigest },
362
362
  policyNote:
363
- "adoption-lock is deprecated (remediation handoff D-8): the kit no longer produces adoption-lock-form artifacts. The adoption proof is this project's adoption declaration, completed with real artifact paths and digests inside the profile write set and machine-verified by verifyProjectProfile (SPE1006 digest discipline, SPE1007 tightening-only overrides).",
363
+ "adoption-lock is deprecated (remediation handoff D-8): the kit no longer produces adoption-lock-form artifacts. The adoption proof is this project's adoption declaration, completed with real artifact paths and digests inside the profile write set and machine-verified by verifyProjectProfile (SPE1006 digest discipline, SPE1007 tightening-only overrides). profileDraft is a proposed draft only; an incomplete or empty draft does not invalidate a current valid profile.json.",
364
364
  };
365
365
  }
366
366
 
package/src/check.mjs CHANGED
@@ -52,10 +52,12 @@ import { loadEngineeringDeclaration } from "./engineering-declaration.mjs";
52
52
  * fail-closed semantics are unchanged: it remains a finding);
53
53
  * closure — harness resource closure over declared inputs/outputs;
54
54
  * version — declared contracts version vs the frozen contracts version,
55
- * plus version single-source consistency (SG-13/14, VRG-001/002:
56
- * package.json is the single version authority; no double-headed
57
- * VERSION authority; every workspace package.json carries the
58
- * same single-source version);
55
+ * plus root-only version single-source facts when no
56
+ * engineering.releaseUnits declaration is in force (SG-13/14,
57
+ * VRG-001: package.json is the root version authority; no
58
+ * double-headed VERSION authority). Nested package.json files
59
+ * and historical snapshots are not current release units; actual
60
+ * units are checked only through declared versionSource/versionMirrors;
59
61
  * docs — documentation facts (README presence, identity consistency);
60
62
  * git — read-only Git pre-state (never a git write);
61
63
  * identity — licensing and identity drift checks (FND-045);
@@ -552,22 +554,27 @@ async function checkClosure(rootAbs, findings) {
552
554
  }
553
555
 
554
556
  /**
555
- * Version single-source consistency (SG-13/14; audit VRG-001/VRG-002).
557
+ * Legacy version facts used when engineering.releaseUnits is absent.
556
558
  *
557
- * The version truth source of a release unit is the `version` field of its
558
- * package.json. Two mechanical facts follow:
559
- * - VRG-001: a VERSION file alongside package.json is a double-headed
560
- * version authority; the authority must converge on package.json;
561
- * - VRG-002: the version value is declared once; every other occurrence is
562
- * mechanically synchronized — here: every nested workspace package.json
563
- * must carry exactly the root package.json version.
559
+ * Scope is the root package.json and an adjacent VERSION file only
560
+ * (VRG-001: no double-headed VERSION authority next to the root package).
561
+ * Nested package.json files, historical snapshots, and independent release
562
+ * units are not current version authorities; declare releaseUnits with
563
+ * versionSource/versionMirrors to check those objects. Invalid declarations
564
+ * never fall back to this path. Provider lockstep among Foundation's own
565
+ * four release units stays in the workspace check-structure gate.
564
566
  *
565
567
  * When no root package.json version exists there is no single source to
566
568
  * compare against and the sub-check reports nothing (never guesses).
567
- * Returns { rootVersion, versionFilePresent, drifted }.
569
+ * Returns { rootVersion, versionFilePresent, drifted, note }.
568
570
  */
569
571
  async function checkVersionSingleSource(rootAbs, findings) {
570
- const facts = { rootVersion: null, versionFilePresent: false, drifted: [] };
572
+ const facts = {
573
+ rootVersion: null,
574
+ versionFilePresent: false,
575
+ drifted: [],
576
+ note: "legacy version class reads the root package.json and an adjacent VERSION file only; nested package.json files and historical snapshots are not current release units. Declare engineering.releaseUnits to check actual units.",
577
+ };
571
578
  const packageDocument = await loadDocument(rootAbs, "package.json");
572
579
  if (packageDocument.state !== "ok" || typeof packageDocument.value?.version !== "string") {
573
580
  return facts;
@@ -587,27 +594,6 @@ async function checkVersionSingleSource(rootAbs, findings) {
587
594
  ),
588
595
  );
589
596
  }
590
-
591
- const entries = await listTargetEntries(rootAbs);
592
- for (const entry of entries) {
593
- if (entry.kind !== "file" || entry.path === "package.json" || !entry.path.endsWith("package.json")) {
594
- continue;
595
- }
596
- const nested = await loadDocument(rootAbs, entry.path);
597
- if (nested.state !== "ok" || typeof nested.value?.version !== "string") continue;
598
- if (nested.value.version !== facts.rootVersion) {
599
- facts.drifted.push({ path: entry.path, version: nested.value.version });
600
- findings.push(
601
- finding(
602
- "version",
603
- KIT_ERROR_KINDS.VERSION_SINGLE_SOURCE_DRIFT,
604
- "SFC2004",
605
- `${entry.path} declares version ${nested.value.version}; the single version source is the root package.json version ${facts.rootVersion} (VRG-002)`,
606
- { path: entry.path, declared: nested.value.version, expected: facts.rootVersion },
607
- ),
608
- );
609
- }
610
- }
611
597
  return facts;
612
598
  }
613
599
 
package/src/cli.mjs CHANGED
@@ -8,6 +8,7 @@ import { bundledProfilesRoot } from "./licensing.mjs";
8
8
  import { checkEntriesAction } from "./entry-check.mjs";
9
9
  import { checkHostLoadingAction } from "./host-loading-check.mjs";
10
10
  import { relockAction } from "./relock.mjs";
11
+ import { registerExistingAction } from "./register-existing.mjs";
11
12
  import { checkReportAction, renderReportAction } from "./report.mjs";
12
13
  import {
13
14
  COMMAND_SIDE_EFFECTS,
@@ -77,6 +78,10 @@ const HELP_TEXT = `skill-family-engineering-kit —— 构建期工程工具包
77
78
  把新手写文件登记进 .foundation/file-registry.json,并以当前字节
78
79
  重算 skill-family.managed-file-lock.json(单次 fail-closed 事务;
79
80
  只写这两个收容状态文档,任何拒绝都零写入)。
81
+ 首次非空登记子动作: check register-existing 为已有非空工程生成
82
+ 三份闭世界登记(file-registry / project-manifest / managed-file-lock)。
83
+ 消费已确认的 profile 身份与调用方分类/engineering;重复执行幂等,
84
+ 冲突拒绝且零覆盖。不走 scaffold,也不把 adopt-plan 写成 apply。
80
85
  显式资格子动作: check qualification 仅在显式提供
81
86
  capability/request/bindings/native 后适配目录声明的能力特定入口,
82
87
  可在预检后调用能力特定的原生宿主。
@@ -158,6 +163,12 @@ function commandHelp(command) {
158
163
  " --registration-class <handwritten|trackedToolLocks> 登记类别,默认 handwritten",
159
164
  " trackedToolLocks 必须提供非空 --files 清单,只登记路径,不锁定工具锁内容。",
160
165
  "",
166
+ "首次非空登记: skill-family-kit check register-existing --root <dir> --handwritten <globs>",
167
+ " --engineering <json> 可选,现有 project-manifest.engineering 对象",
168
+ " --tracked-tool-locks <paths> 可选,已存在的持久工具锁路径",
169
+ " --artifacts <patterns> 可选,额外忽略模式;不得把已跟踪生成物列为忽略运行目录",
170
+ " 目标必须已有合法 profile.json;三份登记全部缺失则独占创建,全部匹配则幂等,其它情况拒绝零覆盖。",
171
+ "",
161
172
  "选项:",
162
173
  " --root <dir> 目标工作区(默认当前目录)",
163
174
  " --only <class> 只运行一个诊断类: contracts|drift|closure|version|docs|git|identity|boundary|platform",
@@ -347,6 +358,16 @@ const COMMAND_SPECS = {
347
358
  "--registration-class": { key: "registrationClass", value: true },
348
359
  },
349
360
  },
361
+ "check register-existing": {
362
+ rejectDuplicates: true,
363
+ flags: {
364
+ "--root": { key: "root", value: true },
365
+ "--handwritten": { key: "handwritten", value: true },
366
+ "--engineering": { key: "engineeringJson", value: true },
367
+ "--tracked-tool-locks": { key: "trackedToolLocks", value: true },
368
+ "--artifacts": { key: "artifacts", value: true },
369
+ },
370
+ },
350
371
  "check qualification": {
351
372
  rejectDuplicates: true,
352
373
  flags: {
@@ -615,6 +636,37 @@ async function runRelockSubAction(rest) {
615
636
  }
616
637
  }
617
638
 
639
+ async function runRegisterExistingSubAction(rest) {
640
+ const specName = "check register-existing";
641
+ try {
642
+ const options = parseOptions(rest, { command: specName, flags: COMMAND_SPECS[specName].flags, rejectDuplicates: true });
643
+ if (options.help) {
644
+ process.stdout.write(`${commandHelp("check")}\n`);
645
+ return KIT_EXIT_CODES.ok;
646
+ }
647
+ let engineering;
648
+ if (options.engineeringJson !== undefined) {
649
+ try {
650
+ engineering = JSON.parse(options.engineeringJson);
651
+ } catch {
652
+ throw invalidParamsError("--engineering must be a JSON engineering object", { flag: "--engineering" });
653
+ }
654
+ }
655
+ const { status, output } = await registerExistingAction({
656
+ root: options.root,
657
+ handwritten: options.handwritten,
658
+ engineering,
659
+ trackedToolLocks: options.trackedToolLocks,
660
+ artifacts: options.artifacts,
661
+ });
662
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
663
+ return status === "ok" ? KIT_EXIT_CODES.ok : KIT_EXIT_CODES.rejected;
664
+ } catch (error) {
665
+ printError(error);
666
+ return KIT_EXIT_CODES.rejected;
667
+ }
668
+ }
669
+
618
670
  function requireJsonFlag(options, specName) {
619
671
  if (options.json !== true) {
620
672
  throw invalidParamsError(`${specName} requires --json`, { reason: "missing-option-value", flag: "--json" });
@@ -788,6 +840,9 @@ export async function cliMain(argv) {
788
840
  if (command === "check" && argv[1] === "relock") {
789
841
  return runRelockSubAction(argv.slice(2));
790
842
  }
843
+ if (command === "check" && argv[1] === "register-existing") {
844
+ return runRegisterExistingSubAction(argv.slice(2));
845
+ }
791
846
  if (command === "check" && argv[1] === "qualification") {
792
847
  return runQualificationSubAction(argv.slice(2));
793
848
  }
package/src/errors.mjs CHANGED
@@ -92,6 +92,8 @@ export const KIT_ERROR_KINDS = Object.freeze({
92
92
  // controlled relock channel (SG-36; audit friction F5): `check relock`
93
93
  RELOCK_REGISTRATION_REFUSED: "relock-registration-refused",
94
94
  RELOCK_LOCK_DRIFT: "relock-lock-drift",
95
+ // first nonempty closed-world seed (check register-existing)
96
+ REGISTER_EXISTING_REFUSED: "register-existing-refused",
95
97
  // read-only host integration slice
96
98
  HOST_CONTRACT_INVALID: "host-contract-invalid",
97
99
  HOST_PROBE_FAILED: "host-probe-failed",
package/src/index.mjs CHANGED
@@ -24,6 +24,11 @@ import {
24
24
  relockAction,
25
25
  runRelock,
26
26
  } from "./relock.mjs";
27
+ import {
28
+ REGISTER_EXISTING_REPORT_KIND,
29
+ registerExistingAction,
30
+ runRegisterExisting,
31
+ } from "./register-existing.mjs";
27
32
  import {
28
33
  invalidParamsError,
29
34
  KitError,
@@ -164,7 +169,7 @@ export const COMMAND_SIDE_EFFECTS = Object.freeze({
164
169
  check: Object.freeze({
165
170
  summary: "契约/漂移/闭包/版本/文档事实/Git 前置状态诊断。",
166
171
  sideEffect:
167
- "none for default diagnosis and the pure runChecks/runEntryContractCheck functions — they never write or auto-fix, and git is probed read-only (one frozen status query at most); with policy declared, only the runCommand('check') and checkEntriesAction wrappers may exclusively create one conclusion when callers explicitly provide an existing output root plus a contained relative path, never creating parent directories or overwriting an existing entry; check proof is read-only proof interpretation and never writes; check proof-create exclusively creates one conclusion from a Skill review-input under the same exclusive-write rules; check host-loading is a strictly read-only sub-action that never writes, spawns, or contacts a network; relock remains a separately explicit controlled write transaction; qualification remains separately explicit and may invoke the capability-specific native host only after its preflight. Foundation itself does not issue network requests, but a bound executable may access the network; sandbox and egress controls remain with callers and the execution environment",
172
+ "none for default diagnosis and the pure runChecks/runEntryContractCheck functions — they never write or auto-fix, and git is probed read-only (one frozen status query at most); with policy declared, only the runCommand('check') and checkEntriesAction wrappers may exclusively create one conclusion when callers explicitly provide an existing output root plus a contained relative path, never creating parent directories or overwriting an existing entry; check proof is read-only proof interpretation and never writes; check proof-create exclusively creates one conclusion from a Skill review-input under the same exclusive-write rules; check host-loading is a strictly read-only sub-action that never writes, spawns, or contacts a network; relock remains a separately explicit controlled write transaction; check register-existing remains a separately explicit first-nonempty registration write of the three closed-world documents and never overwrites mismatched bytes; qualification remains separately explicit and may invoke the capability-specific native host only after its preflight. Foundation itself does not issue network requests, but a bound executable may access the network; sandbox and egress controls remain with callers and the execution environment",
168
173
  exitCodes: "0 无发现;1 有发现;2 拒绝/用法/机制错误",
169
174
  }),
170
175
  });
@@ -262,6 +267,9 @@ export {
262
267
  relockAction,
263
268
  RELOCK_LOCK_PATH_PATTERN,
264
269
  RELOCK_REPORT_KIND,
270
+ runRegisterExisting,
271
+ registerExistingAction,
272
+ REGISTER_EXISTING_REPORT_KIND,
265
273
  runChecks,
266
274
  CHECK_CLASSES,
267
275
  DOCUMENT_STATES,