create-yss-spec 3.4.10 → 3.5.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.
Files changed (174) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/api/_sync-service.js +111 -18
  4. package/src/cli/args.js +1 -0
  5. package/src/cli/help.js +10 -1
  6. package/src/cli/index.js +3 -0
  7. package/src/cli/prompts.js +5 -1
  8. package/src/cli/router.js +2 -0
  9. package/src/commands/attach.js +12 -2
  10. package/src/commands/init.js +2 -0
  11. package/src/commands/skills.js +253 -0
  12. package/src/commands/sync.js +3 -0
  13. package/src/family-identity.js +1 -1
  14. package/src/template/asset-runtime.js +220 -0
  15. package/src/template/distribution-runtime.js +165 -0
  16. package/src/template/instance-runtime.js +76 -29
  17. package/src/template/ownership-policy.js +17 -1
  18. package/src/template/prune-planner.js +6 -1
  19. package/src/template/sync-planner.js +3 -0
  20. package/src/template/verification-runtime.js +56 -1
  21. package/src/validation/metadata.js +24 -1
  22. package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
  23. package/template/.agents/skills/yss-cache/SKILL.md +6 -5
  24. package/template/.agents/skills/yss-cache/references/annotations.md +3 -1
  25. package/template/.agents/skills/yss-cache/references/architecture.md +4 -2
  26. package/template/.agents/skills/yss-cache/references/configuration.md +6 -3
  27. package/template/.agents/skills/yss-cache/references/jetcache.md +21 -0
  28. package/template/.agents/skills/yss-cache/references/redis-fallback.md +5 -3
  29. package/template/.agents/skills/yss-cache/references/verification.md +10 -4
  30. package/template/.agents/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  31. package/template/.agents/skills/yss-distributed-id/SKILL.md +14 -58
  32. package/template/.agents/skills/yss-distributed-id/agents/openai.yaml +2 -2
  33. package/template/.agents/skills/yss-distributed-id/references/README.md +11 -47
  34. package/template/.agents/skills/yss-mybatis/SKILL.md +14 -2
  35. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
  36. package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -8
  37. package/template/.agents/skills/yss-prototype-stage/SKILL.md +2 -2
  38. package/template/.agents/skills/yss-repository/SKILL.md +1 -0
  39. package/template/.codex/skills/yss-cache/SKILL.md +6 -5
  40. package/template/.codex/skills/yss-cache/references/annotations.md +3 -1
  41. package/template/.codex/skills/yss-cache/references/architecture.md +4 -2
  42. package/template/.codex/skills/yss-cache/references/configuration.md +6 -3
  43. package/template/.codex/skills/yss-cache/references/jetcache.md +21 -0
  44. package/template/.codex/skills/yss-cache/references/redis-fallback.md +5 -3
  45. package/template/.codex/skills/yss-cache/references/verification.md +10 -4
  46. package/template/.codex/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  47. package/template/.codex/skills/yss-distributed-id/SKILL.md +14 -58
  48. package/template/.codex/skills/yss-distributed-id/agents/openai.yaml +2 -2
  49. package/template/.codex/skills/yss-distributed-id/references/README.md +11 -47
  50. package/template/.codex/skills/yss-mybatis/SKILL.md +14 -2
  51. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
  52. package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -8
  53. package/template/.codex/skills/yss-prototype-stage/SKILL.md +2 -2
  54. package/template/.codex/skills/yss-repository/SKILL.md +1 -0
  55. package/template/.cursor/skills/yss-cache/SKILL.md +6 -5
  56. package/template/.cursor/skills/yss-cache/references/annotations.md +3 -1
  57. package/template/.cursor/skills/yss-cache/references/architecture.md +4 -2
  58. package/template/.cursor/skills/yss-cache/references/configuration.md +6 -3
  59. package/template/.cursor/skills/yss-cache/references/jetcache.md +21 -0
  60. package/template/.cursor/skills/yss-cache/references/redis-fallback.md +5 -3
  61. package/template/.cursor/skills/yss-cache/references/verification.md +10 -4
  62. package/template/.cursor/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  63. package/template/.cursor/skills/yss-distributed-id/SKILL.md +14 -58
  64. package/template/.cursor/skills/yss-distributed-id/agents/openai.yaml +2 -2
  65. package/template/.cursor/skills/yss-distributed-id/references/README.md +11 -47
  66. package/template/.cursor/skills/yss-mybatis/SKILL.md +14 -2
  67. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
  68. package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +8 -8
  69. package/template/.cursor/skills/yss-prototype-stage/SKILL.md +2 -2
  70. package/template/.cursor/skills/yss-repository/SKILL.md +1 -0
  71. package/template/.pi/skills/yss-cache/SKILL.md +6 -5
  72. package/template/.pi/skills/yss-cache/references/annotations.md +3 -1
  73. package/template/.pi/skills/yss-cache/references/architecture.md +4 -2
  74. package/template/.pi/skills/yss-cache/references/configuration.md +6 -3
  75. package/template/.pi/skills/yss-cache/references/jetcache.md +21 -0
  76. package/template/.pi/skills/yss-cache/references/redis-fallback.md +5 -3
  77. package/template/.pi/skills/yss-cache/references/verification.md +10 -4
  78. package/template/.pi/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  79. package/template/.pi/skills/yss-distributed-id/SKILL.md +14 -58
  80. package/template/.pi/skills/yss-distributed-id/agents/openai.yaml +2 -2
  81. package/template/.pi/skills/yss-distributed-id/references/README.md +11 -47
  82. package/template/.pi/skills/yss-mybatis/SKILL.md +14 -2
  83. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
  84. package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -8
  85. package/template/.pi/skills/yss-prototype-stage/SKILL.md +2 -2
  86. package/template/.pi/skills/yss-repository/SKILL.md +1 -0
  87. package/template/README.md +2 -5
  88. package/template/docs/agents/skill-migrations.md +4 -0
  89. package/template/docs/agents/yss-skill-registry.yaml +7 -6
  90. package/template/docs/design/README.md +0 -1
  91. package/template/docs/design/design.md +2 -2
  92. package/template/docs/design/templates/interaction-spec-template.md +1 -1
  93. package/template/docs/engineering/evidence/aliyun-artifact-resolution.json +446 -0
  94. package/template/scripts/design-md +2 -0
  95. package/template/scripts/lib/design-md.mjs +230 -0
  96. package/template/scripts/lib/skill-registry.mjs +9 -2
  97. package/template/scripts/lib/skill-supply-chain.mjs +33 -14
  98. package/template/scripts/verify-project-instance +11 -10
  99. package/template/skills-lock.json +11 -26
  100. package/template.manifest.json +18 -3
  101. package/template.snapshot.json +5 -5
  102. package/template/.agents/skills/grill-me/SKILL.md +0 -7
  103. package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
  104. package/template/.agents/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  105. package/template/.agents/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  106. package/template/.agents/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  107. package/template/.codex/skills/grill-me/SKILL.md +0 -7
  108. package/template/.codex/skills/grill-me/agents/openai.yaml +0 -5
  109. package/template/.codex/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  110. package/template/.codex/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  111. package/template/.codex/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  112. package/template/.cursor/skills/grill-me/SKILL.md +0 -7
  113. package/template/.cursor/skills/grill-me/agents/openai.yaml +0 -5
  114. package/template/.cursor/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  115. package/template/.cursor/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  116. package/template/.cursor/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  117. package/template/.pi/skills/grill-me/SKILL.md +0 -7
  118. package/template/.pi/skills/grill-me/agents/openai.yaml +0 -5
  119. package/template/.pi/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  120. package/template/.pi/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  121. package/template/.pi/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  122. package/template/docs/agents/yss-plugin-dependency-contract.md +0 -45
  123. package/template/docs/api/.gitkeep +0 -0
  124. package/template/docs/api/specs/.gitkeep +0 -0
  125. package/template/docs/architecture/.gitkeep +0 -0
  126. package/template/docs/design/facts/antdv-next/1.5.2/cli-help.txt +0 -38
  127. package/template/docs/design/facts/antdv-next/1.5.2/component-list.json +0 -434
  128. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/demo-basic.json +0 -7
  129. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/info.json +0 -246
  130. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/semantic.json +0 -40
  131. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/token.json +0 -32
  132. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/demo-basic.json +0 -7
  133. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/info.json +0 -190
  134. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/semantic.json +0 -20
  135. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/token.json +0 -256
  136. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/demo-basic.json +0 -7
  137. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/info.json +0 -460
  138. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/semantic.json +0 -70
  139. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/token.json +0 -123
  140. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/demo-basic.json +0 -7
  141. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/info.json +0 -646
  142. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/semantic.json +0 -50
  143. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/token.json +0 -130
  144. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/demo-basic.json +0 -7
  145. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/info.json +0 -640
  146. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/semantic.json +0 -70
  147. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/token.json +0 -172
  148. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/demo-basic.json +0 -7
  149. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/info.json +0 -1010
  150. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/semantic.json +0 -70
  151. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/token.json +0 -263
  152. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/demo-basic.json +0 -7
  153. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/info.json +0 -321
  154. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/semantic.json +0 -25
  155. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/token.json +0 -25
  156. package/template/docs/design/facts/antdv-next/1.5.2/design-md.json +0 -3
  157. package/template/docs/design/facts/antdv-next/1.5.2/manifest.json +0 -215
  158. package/template/docs/design/facts/antdv-next/1.5.2/resolution-probe.json +0 -5
  159. package/template/docs/design/prototypes/.gitkeep +0 -1
  160. package/template/docs/implementation/.gitkeep +0 -1
  161. package/template/docs/plan/IDEATION.md +0 -96
  162. package/template/docs/process/PDCA-SCRUM.md +0 -10
  163. package/template/docs/process/harness-executive-blueprint.md +0 -10
  164. package/template/docs/releases/.gitkeep +0 -1
  165. package/template/docs/requirements/README.md +0 -85
  166. package/template/docs/requirements/tickets/.gitkeep +0 -1
  167. package/template/docs/templates/agent-brief-template.md +0 -46
  168. package/template/docs/templates/architecture-proposal-template.md +0 -18
  169. package/template/docs/templates/implementation-plan-template.md +0 -16
  170. package/template/docs/templates/implementation-routing-template.md +0 -350
  171. package/template/docs/templates/requirement-freeze-template.md +0 -63
  172. package/template/docs/templates/risk-register-template.md +0 -11
  173. package/template/docs/templates/user-story-template.md +0 -20
  174. package/template/docs/testing/README.md +0 -110
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { runInTransaction } = require("../filesystem/transaction-runner");
6
+ const { applyManagedOperation } = require("../filesystem/apply-plan");
7
+ const { pathKind, targetPath } = require("../filesystem/path-utils");
8
+ const { assertTargetFamily } = require("../family-runtime");
9
+ const { assertTargetWorkingTreeWritable } = require("../validation/security");
10
+ const { readTemplateSnapshot, loadTemplateMetadata, writeTemplateMetadata, buildDesiredManagedOperations, BUNDLED_TEMPLATE_ROOT, fileHash } = require("../template/instance-runtime");
11
+ const { RUNTIMES, assertRuntime, readDistributionRegistry, requiredSkills, renderInstanceDesignSkillFile } = require("../template/distribution-runtime");
12
+ const { ASSET_PROFILE, STAGES } = require("../template/asset-runtime");
13
+ const { refreshGeneratedProjectInstance, verifyGeneratedProjectInstance } = require("../template/verification-runtime");
14
+
15
+ function parseSkillArgs(argv) {
16
+ const options = { names: [], triggers: [], targetDir: "." };
17
+ for (let index = 0; index < argv.length; index++) {
18
+ const arg = argv[index];
19
+ if (arg === "--plan") options.plan = true;
20
+ else if (arg === "--apply") options.apply = true;
21
+ else if (arg === "--target-dir" || arg === "--when") {
22
+ const value = argv[++index];
23
+ if (!value || value.startsWith("--")) throw new Error(`${arg} 需要一个值`);
24
+ if (arg === "--target-dir") options.targetDir = value;
25
+ else options.triggers.push(value);
26
+ } else if (arg.startsWith("--")) throw new Error(`不支持的参数:${arg}`);
27
+ else options.names.push(arg);
28
+ }
29
+ if (options.plan && options.apply) throw new Error("--plan 与 --apply 互斥");
30
+ if (!options.plan && !options.apply) throw new Error("skills 命令必须显式选择 --plan 或 --apply");
31
+ return options;
32
+ }
33
+
34
+ function listFiles(directory, prefix = "") {
35
+ const files = [];
36
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
37
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
38
+ if (entry.isDirectory()) files.push(...listFiles(path.join(directory, entry.name), relative));
39
+ else if (entry.isFile()) files.push(relative);
40
+ }
41
+ return files;
42
+ }
43
+
44
+ function addManagedTree(metadata, targetDir, relativeDir) {
45
+ for (const file of listFiles(targetPath(targetDir, relativeDir))) {
46
+ const relative = `${relativeDir}/${file}`;
47
+ metadata.managedFiles[relative] = { type: "copy", contentHash: fileHash(targetPath(targetDir, relative)) };
48
+ }
49
+ }
50
+
51
+ function context(targetDir) {
52
+ assertTargetFamily(targetDir);
53
+ const { metadata } = loadTemplateMetadata(targetDir);
54
+ if (metadata.metadataSchemaVersion !== 3 || metadata.distribution?.mode !== "selected") {
55
+ throw new Error("按需 Skill 命令仅适用于 v3 精简实例;v2 实例继续 legacy-all 分发");
56
+ }
57
+ const snapshot = readTemplateSnapshot();
58
+ if (metadata.templateCommit !== snapshot.templateCommit || metadata.snapshotHash !== snapshot.snapshotHash) {
59
+ throw new Error("CLI 快照与实例模板提交不一致,请先运行 create-yss-spec sync");
60
+ }
61
+ const lockPath = targetPath(targetDir, "skills-lock.json");
62
+ const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
63
+ if (JSON.stringify(lock.projectionRoots) !== JSON.stringify(metadata.distribution.runtimes.map((name) => RUNTIMES[name]))) {
64
+ throw new Error("实例 Skill 锁与元数据的平台选择不一致");
65
+ }
66
+ return { metadata, lock, lockPath };
67
+ }
68
+
69
+ function runAssetSelection({ targetDir, requestedSkills = [], triggers = [], stage = null, plan = false }) {
70
+ const { metadata, lock, lockPath } = context(targetDir);
71
+ const distribution = metadata.distribution;
72
+ if (distribution.assetProfile !== ASSET_PROFILE) {
73
+ throw new Error("阶段资产命令仅适用于新建的按阶段分发实例;旧实例保持原分发范围");
74
+ }
75
+ if (stage && !Object.hasOwn(STAGES, stage)) throw new Error(`未知生命周期阶段:${stage}`);
76
+ const stageSkills = stage ? STAGES[stage].skills : [];
77
+ const registry = readDistributionRegistry(BUNDLED_TEMPLATE_ROOT);
78
+ const requested = [...new Set([...requestedSkills, ...stageSkills])];
79
+ const addStage = stage && !distribution.installedStages.includes(stage) ? stage : null;
80
+ const variables = { ...metadata.variables };
81
+ const before = buildDesiredManagedOperations(targetDir, { ...variables, distribution }, "init");
82
+ for (const operation of before) {
83
+ if (metadata.managedFiles[operation.relativePath] && pathKind(operation.targetPath) === "missing") {
84
+ throw new Error(`实例受管资产缺失,请先运行 create-yss-spec sync:${operation.relativePath}`);
85
+ }
86
+ }
87
+ let names, missingSkills, nextDistribution, after;
88
+ for (let attempt = 0; attempt <= 50; attempt++) {
89
+ names = requiredSkills(registry.text, requested, triggers, BUNDLED_TEMPLATE_ROOT);
90
+ missingSkills = names.filter((name) => !distribution.installedSkills.includes(name));
91
+ nextDistribution = {
92
+ ...distribution,
93
+ installedSkills: [...distribution.installedSkills, ...missingSkills].sort(),
94
+ installedStages: addStage ? [...distribution.installedStages, addStage] : [...distribution.installedStages],
95
+ resourceSkills: [...new Set([...distribution.resourceSkills, ...missingSkills])].sort(),
96
+ };
97
+ try {
98
+ after = buildDesiredManagedOperations(targetDir, { ...variables, distribution: nextDistribution }, "init");
99
+ break;
100
+ } catch (error) {
101
+ if (!error.requiredSkill || requested.includes(error.requiredSkill) || attempt === 50) throw error;
102
+ requested.push(error.requiredSkill);
103
+ }
104
+ }
105
+ const beforePaths = new Set(before.map((operation) => operation.relativePath));
106
+ const newOperations = after.filter((operation) => !beforePaths.has(operation.relativePath) && operation.relativePath !== "skills-lock.json");
107
+ const paths = newOperations.map((operation) => operation.relativePath);
108
+ for (const relative of paths) {
109
+ if (pathKind(targetPath(targetDir, relative)) !== "missing") throw new Error(`目标阶段资产路径已存在,拒绝覆盖:${relative}`);
110
+ }
111
+ if (plan) {
112
+ console.log(JSON.stringify({ operation: stage ? "assets ensure" : "skills ensure", targetDir, requested: stage || requestedSkills,
113
+ dependencies: names, addSkills: missingSkills, addStage, paths }, null, 2));
114
+ return;
115
+ }
116
+ if (!addStage && !missingSkills.length && !paths.length) {
117
+ verifyGeneratedProjectInstance(targetDir);
118
+ console.log("阶段资产已满足,无需写入");
119
+ return;
120
+ }
121
+ assertTargetWorkingTreeWritable(targetDir, { packageRoot: path.resolve(__dirname, "../..") });
122
+ const sourceLock = JSON.parse(fs.readFileSync(path.join(BUNDLED_TEMPLATE_ROOT, "skills-lock.json"), "utf8"));
123
+ runInTransaction({
124
+ targetDir, operation: stage ? "assets" : "skills", affectedPaths: [...paths, "skills-lock.json", ".yss-template.json"],
125
+ execute(transaction) {
126
+ const nextMetadata = structuredClone(metadata);
127
+ const nextLock = structuredClone(lock);
128
+ for (const operation of newOperations) {
129
+ applyManagedOperation(operation, transaction);
130
+ nextMetadata.managedFiles[operation.relativePath] = { type: operation.type, contentHash: fileHash(operation.targetPath) };
131
+ }
132
+ for (const name of missingSkills) {
133
+ if (!sourceLock.skills.shared[name]) throw new Error(`CLI 快照缺少 Skill 锁记录:${name}`);
134
+ nextLock.skills.shared[name] = { ...sourceLock.skills.shared[name], targets: [".agents/skills", ...nextLock.projectionRoots] };
135
+ }
136
+ nextMetadata.distribution = nextDistribution;
137
+ transaction.writeFile(lockPath, `${JSON.stringify(nextLock, null, 2)}\n`);
138
+ refreshGeneratedProjectInstance(targetDir);
139
+ nextMetadata.managedFiles["skills-lock.json"] = { type: "render", contentHash: fileHash(lockPath) };
140
+ verifyGeneratedProjectInstance(targetDir);
141
+ writeTemplateMetadata(targetDir, nextMetadata, transaction);
142
+ },
143
+ });
144
+ console.log(stage ? `已安装阶段资产 ${stage}` : `已补装 ${missingSkills.join(", ")}`);
145
+ }
146
+
147
+ function runAssets(argv = []) {
148
+ const [command, stage, ...rest] = argv;
149
+ if (command !== "ensure" || !stage || stage.startsWith("--")) throw new Error("用法:assets ensure <stage-id> --plan|--apply");
150
+ const options = parseSkillArgs(rest);
151
+ if (options.names.length || options.triggers.length) throw new Error("assets ensure 只接受一个阶段 ID");
152
+ runAssetSelection({ targetDir: path.resolve(options.targetDir), stage, plan: Boolean(options.plan) });
153
+ }
154
+
155
+ function runSkills(argv = []) {
156
+ const [command, maybeAction, ...rest] = argv;
157
+ const runtimeAdd = command === "runtime" && maybeAction === "add";
158
+ if (command !== "ensure" && !runtimeAdd) throw new Error("用法:skills ensure <skill-id...> --plan|--apply,或 skills runtime add <runtime> --plan|--apply");
159
+ const options = parseSkillArgs(runtimeAdd ? rest : [maybeAction, ...rest].filter((value) => value !== undefined));
160
+ if (!options.names.length) throw new Error(runtimeAdd ? "runtime add 需要平台名" : "ensure 需要至少一个 Skill ID");
161
+ if (runtimeAdd && options.names.length !== 1) throw new Error("runtime add 一次只接受一个平台");
162
+ const targetDir = path.resolve(options.targetDir);
163
+ const { metadata, lock, lockPath } = context(targetDir);
164
+ const distribution = metadata.distribution;
165
+ if (!runtimeAdd && distribution.assetProfile === ASSET_PROFILE) {
166
+ runAssetSelection({ targetDir, requestedSkills: options.names, triggers: options.triggers, plan: Boolean(options.plan) });
167
+ return;
168
+ }
169
+ const registry = readDistributionRegistry(BUNDLED_TEMPLATE_ROOT);
170
+ const names = runtimeAdd ? [] : requiredSkills(registry.text, options.names, options.triggers, BUNDLED_TEMPLATE_ROOT);
171
+ const runtime = runtimeAdd ? assertRuntime(options.names[0]) : null;
172
+ const missing = names.filter((name) => !distribution.installedSkills.includes(name));
173
+ const addedRuntime = runtime && !distribution.runtimes.includes(runtime) ? runtime : null;
174
+ const roots = distribution.runtimes.map((name) => RUNTIMES[name]);
175
+ const runtimeFiles = addedRuntime ? buildDesiredManagedOperations(targetDir, {
176
+ ...metadata.variables,
177
+ distribution: { mode: "selected", runtimes: [...distribution.runtimes, addedRuntime], installedSkills: distribution.installedSkills },
178
+ }, "init").filter((operation) => {
179
+ const runtimeRoot = RUNTIMES[addedRuntime].split("/")[0];
180
+ return (operation.relativePath.startsWith(`${runtimeRoot}/`) || (addedRuntime === "cursor" && operation.relativePath === ".cursorrules")) &&
181
+ !distribution.installedSkills.some((name) => operation.relativePath.startsWith(`${RUNTIMES[addedRuntime]}/${name}/`));
182
+ }) : [];
183
+ const paths = ["skills-lock.json", ".yss-template.json"];
184
+ for (const name of missing) {
185
+ paths.push(`.agents/skills/${name}`, ...roots.map((root) => `${root}/${name}`));
186
+ }
187
+ if (addedRuntime) paths.push(...Object.keys(lock.skills.shared).map((name) => `${RUNTIMES[addedRuntime]}/${name}`), ...runtimeFiles.map((operation) => operation.relativePath));
188
+ for (const relative of paths.slice(2)) {
189
+ if (pathKind(targetPath(targetDir, relative)) !== "missing") throw new Error(`目标 Skill 路径已存在,拒绝覆盖:${relative}`);
190
+ }
191
+ if (options.plan) {
192
+ console.log(JSON.stringify({ operation: runtimeAdd ? "runtime add" : "ensure", targetDir, requested: options.names, dependencies: names, addSkills: missing, addRuntime: addedRuntime, paths }, null, 2));
193
+ return;
194
+ }
195
+ if (!missing.length && !addedRuntime) {
196
+ verifyGeneratedProjectInstance(targetDir);
197
+ console.log("Skill 分发已满足,无需写入");
198
+ return;
199
+ }
200
+ assertTargetWorkingTreeWritable(targetDir, { packageRoot: path.resolve(__dirname, "../..") });
201
+ const sourceLock = JSON.parse(fs.readFileSync(path.join(BUNDLED_TEMPLATE_ROOT, "skills-lock.json"), "utf8"));
202
+ runInTransaction({
203
+ targetDir, operation: "skills", affectedPaths: paths,
204
+ execute(transaction) {
205
+ const nextMetadata = structuredClone(metadata);
206
+ const nextLock = structuredClone(lock);
207
+ if (addedRuntime) {
208
+ nextMetadata.distribution.runtimes.push(addedRuntime);
209
+ nextLock.projectionRoots.push(RUNTIMES[addedRuntime]);
210
+ }
211
+ for (const name of missing) {
212
+ const source = path.join(BUNDLED_TEMPLATE_ROOT, ".agents/skills", name);
213
+ if (pathKind(source) !== "directory" || !sourceLock.skills.shared[name]) throw new Error(`CLI 快照缺少 Skill:${name}`);
214
+ transaction.copyFile(source, targetPath(targetDir, `.agents/skills/${name}`));
215
+ for (const root of roots) transaction.copyFile(source, targetPath(targetDir, `${root}/${name}`));
216
+ if (name === "yss-design-system") {
217
+ for (const root of [".agents/skills", ...roots]) {
218
+ for (const file of ["SKILL.md", "references/data-quality-theme.md"]) {
219
+ const target = targetPath(targetDir, `${root}/${name}/${file}`);
220
+ transaction.writeFile(target, renderInstanceDesignSkillFile(fs.readFileSync(target, "utf8")));
221
+ }
222
+ }
223
+ }
224
+ nextMetadata.distribution.installedSkills.push(name);
225
+ nextLock.skills.shared[name] = { ...sourceLock.skills.shared[name], targets: [".agents/skills", ...nextLock.projectionRoots] };
226
+ addManagedTree(nextMetadata, targetDir, `.agents/skills/${name}`);
227
+ for (const root of roots) addManagedTree(nextMetadata, targetDir, `${root}/${name}`);
228
+ }
229
+ if (addedRuntime) {
230
+ const root = RUNTIMES[addedRuntime];
231
+ for (const name of Object.keys(nextLock.skills.shared)) {
232
+ const source = targetPath(targetDir, `.agents/skills/${name}`);
233
+ transaction.copyFile(source, targetPath(targetDir, `${root}/${name}`));
234
+ if (nextMetadata.distribution.installedSkills.includes(name)) addManagedTree(nextMetadata, targetDir, `${root}/${name}`);
235
+ }
236
+ for (const operation of runtimeFiles) {
237
+ transaction.copyFile(operation.sourcePath, operation.targetPath);
238
+ nextMetadata.managedFiles[operation.relativePath] = { type: operation.type, contentHash: fileHash(operation.targetPath) };
239
+ }
240
+ }
241
+ for (const record of Object.values(nextLock.skills.shared)) record.targets = [".agents/skills", ...nextLock.projectionRoots];
242
+ nextMetadata.distribution.installedSkills.sort();
243
+ transaction.writeFile(lockPath, `${JSON.stringify(nextLock, null, 2)}\n`);
244
+ refreshGeneratedProjectInstance(targetDir);
245
+ nextMetadata.managedFiles["skills-lock.json"] = { type: "render", contentHash: fileHash(lockPath) };
246
+ verifyGeneratedProjectInstance(targetDir);
247
+ writeTemplateMetadata(targetDir, nextMetadata, transaction);
248
+ },
249
+ });
250
+ console.log(runtimeAdd ? `已增加平台 ${runtime}` : `已补装 ${missing.join(", ")}`);
251
+ }
252
+
253
+ module.exports = { runSkills, runAssets };
@@ -56,6 +56,9 @@ function printSyncDryRun(context) {
56
56
  syncPlan.updated,
57
57
  (operation) => `update: ${operation.relativePath}`,
58
58
  );
59
+ if ([...syncPlan.updated, ...syncPlan.added].some((operation) => operation.relativePath === "skills-lock.json")) {
60
+ console.log("提示:skills-lock.json 为生成文件;最终校验后内容一致时不会重新写入。");
61
+ }
59
62
  printLimitedOperations(
60
63
  syncPlan.added,
61
64
  (operation) => `add: ${operation.relativePath}`,
@@ -283,7 +283,7 @@ function createFamilyGuard(_packageRoot, expectedName) {
283
283
  Object.hasOwn(value, "metadataSchemaVersion") &&
284
284
  (!Number.isInteger(value.metadataSchemaVersion) ||
285
285
  value.metadataSchemaVersion < 1 ||
286
- value.metadataSchemaVersion > (expectedName === "create-yss-spec" ? 2 : 1))
286
+ value.metadataSchemaVersion > (expectedName === "create-yss-spec" ? 3 : 1))
287
287
  ) {
288
288
  throw new Error("身份 metadata schema 版本非法或不支持");
289
289
  }
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const ASSET_PROFILE = "stage-selective";
7
+ const INITIAL_STAGES = ["stage.entry-triage", "stage.plan"];
8
+ const TEMPLATE_ONLY_IMPORTS = new Set(["scripts/lib/maintenance-intensity.mjs"]);
9
+ const RENDERED_SKILL_SCRIPTS = Object.freeze({ "yss-design-system": ["scripts/design-md"] });
10
+ const SKILL_ASSET_PREFIXES = Object.freeze({ "yss-design-system": ["docs/design/tokens/"] });
11
+
12
+ // Paths here are the entry points for each stage. Relative JavaScript imports
13
+ // are included by moduleClosure; files named by an explicitly installed Skill
14
+ // are included by skillReferences. The CLI snapshot retains every source file.
15
+ const COMMON = {
16
+ files: [
17
+ "docs/agents/yss-skill-registry.yaml",
18
+ "docs/agents/digital-human-roles.yaml",
19
+ "docs/agents/digital-human-roles.md",
20
+ "docs/agents/issue-tracker.md",
21
+ "docs/agents/triage-labels.md",
22
+ "docs/agents/domain.md",
23
+ "docs/process/lifecycle-registry.yaml",
24
+ "docs/process/lifecycle-registry-baseline.json",
25
+ "docs/process/lifecycle-artifact-map.md",
26
+ "docs/process/harness-process-tailoring.md",
27
+ "docs/process/document-writing.md",
28
+ "docs/process/contract-reading.md",
29
+ "docs/process/stage-tracking.md",
30
+ "docs/process/schemas/context-reconciliation.schema.json",
31
+ "docs/process/schemas/digital-human-task-package.schema.json",
32
+ "docs/process/schemas/lifecycle-checkpoint.schema.json",
33
+ "docs/process/schemas/stage-tracking.schema.json",
34
+ "docs/process/schemas/user-decision.schema.json",
35
+ "docs/process/templates/context-reconciliation-template.yaml",
36
+ "docs/process/templates/lifecycle-checkpoint-template.yaml",
37
+ "docs/process/templates/stage-checkpoint-template.md",
38
+ "docs/process/templates/user-decision-template.yaml",
39
+ "docs/templates/local-parent-ticket-template.md",
40
+ "docs/templates/approval-record-template.yaml",
41
+ "docs/templates/review-report-template.md",
42
+ "docs/user-guide/用户手册.md",
43
+ ],
44
+ scripts: [
45
+ "scripts/verify-project-instance",
46
+ "scripts/sync-skills",
47
+ "scripts/update-skill-lock",
48
+ "scripts/verify-context-contract",
49
+ "scripts/verify-context-reconciliation",
50
+ "scripts/verify-digital-human-roles",
51
+ "scripts/verify-digital-human-task-package",
52
+ "scripts/verify-lifecycle-checkpoint",
53
+ "scripts/verify-user-decision",
54
+ "scripts/verify-approval-record",
55
+ "scripts/query-lifecycle-context",
56
+ "scripts/contract",
57
+ "scripts/stage-tracking",
58
+ "scripts/gitworks",
59
+ ],
60
+ };
61
+
62
+ const STAGES = Object.freeze({
63
+ "stage.entry-triage": { files: [], scripts: [], skills: [] },
64
+ "stage.plan": {
65
+ prefixes: ["docs/plan/"],
66
+ files: ["docs/process/plan-migration.md"],
67
+ scripts: ["scripts/verify-plan-spec-entry"],
68
+ skills: [],
69
+ },
70
+ "stage.spec-architecture": {
71
+ files: [
72
+ "docs/templates/spec-template.md",
73
+ "docs/templates/spec-delta-template.md",
74
+ "docs/architecture/templates/business-architecture-template.md",
75
+ "docs/architecture/templates/functional-architecture-template.md",
76
+ ],
77
+ scripts: [],
78
+ skills: [],
79
+ },
80
+ "stage.product-design": {
81
+ prefixes: ["docs/design/"],
82
+ files: [],
83
+ scripts: ["scripts/design-md"],
84
+ skills: ["yss-prototype-stage"],
85
+ },
86
+ "stage.system-data-engineering": {
87
+ prefixes: ["docs/engineering/"],
88
+ files: [
89
+ "docs/process/implementation-repo-integration.md",
90
+ "docs/process/existing-backend-architecture.md",
91
+ "docs/process/existing-ui-baseline.md",
92
+ "docs/process/frontend-backend-delivery.md",
93
+ "docs/templates/implementation-repo-registry-template.md",
94
+ ],
95
+ scripts: [
96
+ "scripts/backend-platforms",
97
+ "scripts/backend-delivery",
98
+ "scripts/repository-scope-policy",
99
+ "scripts/verify-scaffold-architecture-decisions",
100
+ ],
101
+ skills: ["yss-implementation-contract-compiler"],
102
+ },
103
+ "stage.ticket-formalization": {
104
+ files: ["docs/templates/vertical-slice-ticket-template.md", "docs/templates/cross-repo-slice-template.md"],
105
+ scripts: ["scripts/dispatch-slice-task", "scripts/slice-contract"],
106
+ skills: [],
107
+ },
108
+ "stage.vertical-slice-implementation": {
109
+ files: ["docs/templates/verification-record-template.md"],
110
+ scripts: ["scripts/implementation-path-policy", "scripts/complete-backend-delivery"],
111
+ skills: [],
112
+ },
113
+ "stage.verification-release-retrospective": {
114
+ files: [
115
+ "docs/process/delivery-preflight.md",
116
+ "docs/templates/release-note-template.md",
117
+ "docs/templates/retro-report-template.md",
118
+ "docs/templates/review-bundle-template.yaml",
119
+ ],
120
+ scripts: ["scripts/preflight-delivery"],
121
+ skills: [],
122
+ },
123
+ });
124
+
125
+ function sourceFile(root, ref) {
126
+ const file = path.join(root, ref);
127
+ if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`CLI 快照缺少阶段资产:${ref}`);
128
+ return file;
129
+ }
130
+
131
+ function addFile(root, result, ref) {
132
+ sourceFile(root, ref);
133
+ result.add(ref);
134
+ }
135
+
136
+ function addPrefix(root, result, prefix) {
137
+ const directory = path.join(root, prefix);
138
+ if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) throw new Error(`CLI 快照缺少阶段目录:${prefix}`);
139
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
140
+ const ref = `${prefix}${entry.name}`;
141
+ if (entry.isDirectory()) addPrefix(root, result, `${ref}/`);
142
+ else if (entry.isFile()) result.add(ref);
143
+ }
144
+ }
145
+
146
+ function moduleClosure(root, result, ref, selectedSkills) {
147
+ if (result.has(ref)) return;
148
+ const source = fs.readFileSync(sourceFile(root, ref), "utf8");
149
+ result.add(ref);
150
+ const expressions = [
151
+ /\b(?:import|export)\s+(?:[^'"\n]*?\s+from\s*)?['"]([^'"]+)['"]/g,
152
+ /\b(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
153
+ ];
154
+ for (const expression of expressions) {
155
+ for (const match of source.matchAll(expression)) {
156
+ if (!match[1].startsWith(".")) continue;
157
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(ref), match[1]));
158
+ const candidate = [resolved, `${resolved}.mjs`, `${resolved}.js`].find((name) => fs.existsSync(path.join(root, name)));
159
+ if (!candidate && TEMPLATE_ONLY_IMPORTS.has(resolved)) continue;
160
+ if (candidate?.startsWith(".agents/skills/")) {
161
+ const skill = candidate.split("/")[2];
162
+ if (!selectedSkills.has(skill)) {
163
+ const error = new Error(`阶段脚本依赖未安装 Skill:${ref} -> ${skill}`);
164
+ error.requiredSkill = skill;
165
+ throw error;
166
+ }
167
+ continue;
168
+ }
169
+ if (!candidate || !candidate.startsWith("scripts/")) throw new Error(`阶段脚本依赖未分发:${ref} -> ${match[1]}`);
170
+ moduleClosure(root, result, candidate, selectedSkills);
171
+ }
172
+ }
173
+ }
174
+
175
+ function skillReferences(root, result, skillId, selectedSkills) {
176
+ const skillRoot = path.join(root, ".agents/skills", skillId);
177
+ if (!fs.existsSync(skillRoot)) throw new Error(`CLI 快照缺少 Skill:${skillId}`);
178
+ function visit(directory) {
179
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
180
+ const file = path.join(directory, entry.name);
181
+ if (entry.isDirectory()) visit(file);
182
+ else if (entry.isFile() && /\.(?:md|yaml|json)$/.test(entry.name)) {
183
+ const source = fs.readFileSync(file, "utf8");
184
+ for (const match of source.matchAll(/(?:docs|scripts)\/[\p{L}\p{N}_.\/-]+/gu)) {
185
+ const ref = match[0].replace(/[.,;]+$/, "");
186
+ if (!fs.existsSync(path.join(root, ref)) || !fs.statSync(path.join(root, ref)).isFile()) continue;
187
+ if (ref.startsWith("scripts/")) moduleClosure(root, result, ref, selectedSkills);
188
+ else addFile(root, result, ref);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ visit(skillRoot);
194
+ for (const prefix of SKILL_ASSET_PREFIXES[skillId] || []) addPrefix(root, result, prefix);
195
+ for (const ref of RENDERED_SKILL_SCRIPTS[skillId] || []) moduleClosure(root, result, ref, selectedSkills);
196
+ }
197
+
198
+ function assetPaths(root, distribution) {
199
+ if (distribution.mode !== "selected" || distribution.assetProfile !== ASSET_PROFILE) return null;
200
+ const lifecycle = fs.readFileSync(sourceFile(root, "docs/process/lifecycle-registry.yaml"), "utf8");
201
+ const stageIds = [...lifecycle.matchAll(/^ - id: (stage\.[a-z0-9-]+)\s*$/gm)].map((match) => match[1]).sort();
202
+ if (JSON.stringify(stageIds) !== JSON.stringify(Object.keys(STAGES).sort())) {
203
+ throw new Error("CLI 阶段资产映射与生命周期注册表阶段 ID 不一致");
204
+ }
205
+ const result = new Set();
206
+ const selectedSkills = new Set(distribution.installedSkills);
207
+ for (const ref of COMMON.files) addFile(root, result, ref);
208
+ for (const ref of COMMON.scripts) moduleClosure(root, result, ref, selectedSkills);
209
+ for (const stage of distribution.installedStages) {
210
+ const bundle = STAGES[stage];
211
+ if (!bundle) throw new Error(`未知生命周期阶段:${stage}`);
212
+ for (const prefix of bundle.prefixes || []) addPrefix(root, result, prefix);
213
+ for (const ref of bundle.files || []) addFile(root, result, ref);
214
+ for (const ref of bundle.scripts || []) moduleClosure(root, result, ref, selectedSkills);
215
+ }
216
+ for (const skill of distribution.resourceSkills || []) skillReferences(root, result, skill, selectedSkills);
217
+ return result;
218
+ }
219
+
220
+ module.exports = { ASSET_PROFILE, INITIAL_STAGES, STAGES, assetPaths };
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { ASSET_PROFILE, INITIAL_STAGES } = require("./asset-runtime");
6
+
7
+ const RUNTIMES = Object.freeze({ codex: ".codex/skills", cursor: ".cursor/skills", pi: ".pi/skills" });
8
+ const OMIT_INSTANCE_PATHS = [
9
+ "docs/process/github-workflows.md",
10
+ "docs/process/harness-executive-blueprint.md",
11
+ "docs/process/PDCA-SCRUM.md",
12
+ "docs/design/preview.html", "docs/design/preview.css", "docs/design/preview.js", "docs/design/preview-dark.html",
13
+ "docs/design/facts/antdv-next", "docs/user-guide/设备借用贯穿案例.md",
14
+ "docs/design/design-system-sync.yaml",
15
+ ];
16
+
17
+ function readDistributionRegistry(templateRoot) {
18
+ const registry = fs.readFileSync(path.join(templateRoot, "docs/agents/yss-skill-registry.yaml"), "utf8");
19
+ const match = registry.match(/^instance_distribution:\n([\s\S]*?)(?=^[^\s#][^\n]*:\s*$)/m);
20
+ if (!match) throw new Error("Skill 注册表缺少 instance_distribution");
21
+ const initial = match[1].match(/^ initial_skills: \[([^\]]+)\]/m);
22
+ if (!initial) throw new Error("Skill 注册表缺少 initial_skills");
23
+ return {
24
+ initialSkills: initial[1].split(",").map((item) => item.trim()),
25
+ text: registry,
26
+ };
27
+ }
28
+
29
+ function assertRuntime(runtime) {
30
+ if (!Object.hasOwn(RUNTIMES, runtime)) throw new Error(`--agent-runtime 必须是 ${Object.keys(RUNTIMES).join("| ")}`);
31
+ return runtime;
32
+ }
33
+
34
+ function distributionForVariables(variables, templateRoot) {
35
+ if (variables.distribution?.mode === "legacy-all") return { mode: "legacy-all" };
36
+ if (variables.distribution?.mode === "selected") return variables.distribution;
37
+ const runtime = assertRuntime(variables.agentRuntime);
38
+ return {
39
+ mode: "selected",
40
+ runtimes: [runtime],
41
+ installedSkills: [...readDistributionRegistry(templateRoot).initialSkills],
42
+ assetProfile: ASSET_PROFILE,
43
+ installedStages: [...INITIAL_STAGES],
44
+ resourceSkills: [],
45
+ };
46
+ }
47
+
48
+ function isIncludedInstancePath(relativePath, distribution) {
49
+ if (distribution.mode === "legacy-all") return true;
50
+ if (relativePath === ".cursorrules" && !distribution.runtimes.includes("cursor")) return false;
51
+ if (OMIT_INSTANCE_PATHS.some((excluded) => relativePath === excluded || relativePath.startsWith(`${excluded}/`))) return false;
52
+ if (distribution.assetProfile === ASSET_PROFILE &&
53
+ (relativePath.startsWith("docs/") || relativePath.startsWith("scripts/") || relativePath.startsWith(".vscode/"))) {
54
+ return distribution.assetPaths.has(relativePath) ||
55
+ [...distribution.assetPaths].some((ref) => ref.startsWith(`${relativePath}/`));
56
+ }
57
+ for (const [runtime, root] of Object.entries(RUNTIMES)) {
58
+ const runtimeRoot = root.split("/")[0];
59
+ if (relativePath === runtimeRoot || relativePath.startsWith(`${runtimeRoot}/`)) {
60
+ if (!distribution.runtimes.includes(runtime)) return false;
61
+ if (relativePath.startsWith(`${root}/`)) {
62
+ const name = relativePath.slice(root.length + 1).split("/")[0];
63
+ if (name && !name.startsWith(".") && !distribution.installedSkills.includes(name)) return false;
64
+ }
65
+ }
66
+ }
67
+ if (relativePath.startsWith(".agents/skills/")) {
68
+ const name = relativePath.slice(".agents/skills/".length).split("/")[0];
69
+ if (name && !name.startsWith(".") && !distribution.installedSkills.includes(name)) return false;
70
+ }
71
+ return true;
72
+ }
73
+
74
+ function selectedSkillLock(sourceText, distribution) {
75
+ if (distribution.mode === "legacy-all") return sourceText;
76
+ const source = JSON.parse(sourceText);
77
+ const roots = distribution.runtimes.map((runtime) => RUNTIMES[assertRuntime(runtime)]);
78
+ source.projectionRoots = roots;
79
+ source.skills.shared = Object.fromEntries(Object.entries(source.skills.shared).filter(([name]) => distribution.installedSkills.includes(name)).map(([name, record]) => [name, { ...record, targets: [".agents/skills", ...roots] }]));
80
+ source.skills.platform = Object.fromEntries(roots.map((root) => [root, Object.fromEntries(Object.entries(source.skills.platform?.[root] || {}).filter(([name]) => distribution.installedSkills.includes(name)))]).filter(([, entries]) => Object.keys(entries).length));
81
+ return `${JSON.stringify(source, null, 2)}\n`;
82
+ }
83
+
84
+ function renderInstanceSkillSupplyChain(sourceText) {
85
+ const additions = `\nfunction projectionRootsFor(lock) {
86
+ const identityPath = path.join(ROOT, "yss-project.yaml");
87
+ const isInstance = existsSync(identityPath) && /^repository_mode:\\s*project-instance\\s*$/m.test(readFileSync(identityPath, "utf8"));
88
+ if (!isInstance) return PROJECTION_ROOTS;
89
+ const roots = lock?.projectionRoots;
90
+ if (!Array.isArray(roots) || !roots.length || roots.some((root) => !PROJECTION_ROOTS.includes(root))) throw new TypeError("project-instance skills-lock.json 缺少有效 projectionRoots");
91
+ return [...new Set(roots)];
92
+ }\n`;
93
+ const marker = 'export const PROJECTION_ROOTS = [".codex/skills", ".cursor/skills", ".pi/skills"];';
94
+ if (!sourceText.includes(marker)) throw new Error("Skill 投影脚本分发基线已变化");
95
+ return sourceText.replace(marker, marker + additions)
96
+ .replace(" const shared = sharedFromLock(lock);", " const shared = sharedFromLock(lock);\n const projectionRoots = projectionRootsFor(lock);")
97
+ .replace(" const oldLock = parseLock(); const previous = priorMetadata(oldLock);", " const oldLock = parseLock(); const projectionRoots = projectionRootsFor(oldLock); const previous = priorMetadata(oldLock);")
98
+ .replaceAll("for (const root of PROJECTION_ROOTS) {", "for (const root of projectionRoots) {")
99
+ .replace("const targets = [\".agents/skills\", ...PROJECTION_ROOTS];", "const targets = [\".agents/skills\", ...projectionRoots];")
100
+ .replace("projectionRoots: PROJECTION_ROOTS, sources, skills", "projectionRoots, sources, skills");
101
+ }
102
+
103
+ function renderInstanceDesignSkillFile(sourceText) {
104
+ return sourceText
105
+ .replaceAll("node .template-source/tooling/node/scripts/design-md.mjs", "scripts/design-md")
106
+ .replace("运行前先更新 `docs/design/design-system-sync.yaml` 的规范源摘要。", "项目实例无需模板间的 design-system-sync 摘要。")
107
+ .replace("修改根 DESIGN.md → 更新 design-system-sync.yaml 摘要 →", "修改根 DESIGN.md →");
108
+ }
109
+
110
+ function linkedSkillDependencies(templateRoot, name) {
111
+ const skillRoot = path.join(templateRoot, ".agents/skills");
112
+ const current = path.join(skillRoot, name);
113
+ const result = new Set();
114
+ function visitDir(directory) {
115
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
116
+ const file = path.join(directory, entry.name);
117
+ if (entry.isDirectory()) visitDir(file);
118
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
119
+ const content = fs.readFileSync(file, "utf8");
120
+ for (const match of content.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) {
121
+ const ref = match[1].split("#")[0];
122
+ if (!ref || ref.startsWith("/") || ref.includes("://")) continue;
123
+ const resolved = path.resolve(path.dirname(file), ref);
124
+ const relative = path.relative(skillRoot, resolved).split(path.sep);
125
+ if (relative.length > 1 && relative[0] !== name && !relative[0].startsWith(".") && !relative[0].includes("..") && fs.existsSync(resolved)) result.add(relative[0]);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ visitDir(current);
131
+ return [...result];
132
+ }
133
+
134
+ function requiredSkills(registryText, requested, triggers = [], templateRoot = null) {
135
+ const skillBlock = registryText.split(/^skills:\s*$/m)[1]?.split(/^platform_skills:\s*$/m)[0] || "";
136
+ const available = new Set([...skillBlock.matchAll(/^ - (?:\{ )?id: ([a-z][a-z0-9-]+)/gm)].map((match) => match[1]));
137
+ const platformBlock = registryText.split(/^platform_skills:\s*$/m)[1]?.split(/^external_skills:\s*$/m)[0] || "";
138
+ const platformSkills = new Set([...platformBlock.matchAll(/^ - id: ([a-z][a-z0-9-]+)/gm)].map((match) => match[1]));
139
+ const block = registryText.split(/^skill_dependencies:\s*$/m)[1]?.split(/^[^\s#][^\n]*:\s*$/m)[0] || "";
140
+ const dependencies = new Map();
141
+ let current = null;
142
+ for (const line of block.split("\n")) {
143
+ const header = line.match(/^ ([a-z][a-z0-9-]+):\s*$/);
144
+ if (header) { current = header[1]; continue; }
145
+ const item = line.match(/^ - \{ skill: ([a-z][a-z0-9-]+), type: ([a-z-]+)(?:, when: ([a-z0-9-]+))? \}/);
146
+ if (current && item) {
147
+ if (!dependencies.has(current)) dependencies.set(current, []);
148
+ dependencies.get(current).push({ name: item[1], type: item[2], when: item[3] });
149
+ }
150
+ }
151
+ const result = new Set();
152
+ function visit(name) {
153
+ if (!available.has(name)) throw new Error(platformSkills.has(name) ? `平台专属 Skill 不可作为共享 Skill 补装:${name}` : `Skill 未在注册表登记:${name}`);
154
+ if (result.has(name)) return;
155
+ result.add(name);
156
+ for (const dep of dependencies.get(name) || []) {
157
+ if (dep.type === "context-required" || (dep.type === "context-conditional" && triggers.includes(dep.when))) visit(dep.name);
158
+ }
159
+ if (templateRoot) for (const linked of linkedSkillDependencies(templateRoot, name)) visit(linked);
160
+ }
161
+ requested.forEach(visit);
162
+ return [...result].sort();
163
+ }
164
+
165
+ module.exports = { RUNTIMES, assertRuntime, readDistributionRegistry, distributionForVariables, isIncludedInstancePath, selectedSkillLock, renderInstanceSkillSupplyChain, renderInstanceDesignSkillFile, requiredSkills };