create-yss-spec 3.3.3 → 3.3.6

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 (81) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/api/_sync-service.js +6 -1
  4. package/src/commands/attach.js +6 -1
  5. package/src/commands/init.js +3 -0
  6. package/src/template/verification-runtime.js +5 -0
  7. package/template/.agents/skills/.strategic-design-skills-manifest.json +4 -4
  8. package/template/.agents/skills/code-review/SKILL.md +6 -0
  9. package/template/.agents/skills/i-have-adhd/LICENSE +21 -0
  10. package/template/.agents/skills/i-have-adhd/SKILL.md +26 -0
  11. package/template/.agents/skills/i-have-adhd/agents/openai.yaml +6 -0
  12. package/template/.agents/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  13. package/template/.agents/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  14. package/template/.agents/skills/prototype-review/SKILL.md +2 -0
  15. package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  16. package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  17. package/template/.agents/skills/yss-openapi-governance/SKILL.md +6 -0
  18. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +3 -1
  19. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  20. package/template/.agents/skills/yss-prototype-stage/SKILL.md +6 -0
  21. package/template/.agents/skills/yss-research/SKILL.md +6 -0
  22. package/template/.agents/skills/yss-stage-decision/SKILL.md +6 -0
  23. package/template/.agents/skills/yss-technical-design/SKILL.md +6 -0
  24. package/template/.codex/skills/code-review/SKILL.md +6 -0
  25. package/template/.codex/skills/i-have-adhd/LICENSE +21 -0
  26. package/template/.codex/skills/i-have-adhd/SKILL.md +26 -0
  27. package/template/.codex/skills/i-have-adhd/agents/openai.yaml +6 -0
  28. package/template/.codex/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  29. package/template/.codex/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  30. package/template/.codex/skills/prototype-review/SKILL.md +2 -0
  31. package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  32. package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  33. package/template/.codex/skills/yss-openapi-governance/SKILL.md +6 -0
  34. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +3 -1
  35. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  36. package/template/.codex/skills/yss-prototype-stage/SKILL.md +6 -0
  37. package/template/.codex/skills/yss-research/SKILL.md +6 -0
  38. package/template/.codex/skills/yss-stage-decision/SKILL.md +6 -0
  39. package/template/.codex/skills/yss-technical-design/SKILL.md +6 -0
  40. package/template/.cursor/skills/code-review/SKILL.md +6 -0
  41. package/template/.cursor/skills/i-have-adhd/LICENSE +21 -0
  42. package/template/.cursor/skills/i-have-adhd/SKILL.md +26 -0
  43. package/template/.cursor/skills/i-have-adhd/agents/openai.yaml +6 -0
  44. package/template/.cursor/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  45. package/template/.cursor/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  46. package/template/.cursor/skills/prototype-review/SKILL.md +2 -0
  47. package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  48. package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  49. package/template/.cursor/skills/yss-openapi-governance/SKILL.md +6 -0
  50. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +3 -1
  51. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  52. package/template/.cursor/skills/yss-prototype-stage/SKILL.md +6 -0
  53. package/template/.cursor/skills/yss-research/SKILL.md +6 -0
  54. package/template/.cursor/skills/yss-stage-decision/SKILL.md +6 -0
  55. package/template/.cursor/skills/yss-technical-design/SKILL.md +6 -0
  56. package/template/.pi/skills/code-review/SKILL.md +6 -0
  57. package/template/.pi/skills/i-have-adhd/LICENSE +21 -0
  58. package/template/.pi/skills/i-have-adhd/SKILL.md +26 -0
  59. package/template/.pi/skills/i-have-adhd/agents/openai.yaml +6 -0
  60. package/template/.pi/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  61. package/template/.pi/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  62. package/template/.pi/skills/prototype-review/SKILL.md +2 -0
  63. package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  64. package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  65. package/template/.pi/skills/yss-openapi-governance/SKILL.md +6 -0
  66. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +3 -1
  67. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  68. package/template/.pi/skills/yss-prototype-stage/SKILL.md +6 -0
  69. package/template/.pi/skills/yss-research/SKILL.md +6 -0
  70. package/template/.pi/skills/yss-stage-decision/SKILL.md +6 -0
  71. package/template/.pi/skills/yss-technical-design/SKILL.md +6 -0
  72. package/template/docs/agents/yss-skill-registry.yaml +22 -0
  73. package/template/docs/plan/templates/plan-template.md +8 -4
  74. package/template/docs/process/document-writing.md +77 -0
  75. package/template/docs/templates/examples/lifecycle-writing-examples.md +138 -0
  76. package/template/docs/templates/spec-template.md +16 -3
  77. package/template/docs/templates/vertical-slice-ticket-template.md +8 -4
  78. package/template/scripts/lib/profile-skill-sync.mjs +336 -0
  79. package/template/scripts/sync-profile-skills +43 -0
  80. package/template/skills-lock.json +37 -19
  81. package/template.snapshot.json +4 -4
@@ -7,17 +7,19 @@ owner: ai
7
7
 
8
8
  # 产品研发规格:<功能名称>
9
9
 
10
+ 填写前读取[中文写作规范](../process/document-writing.md)和 [Spec 对照](examples/lifecycle-writing-examples.md#2-spec让规则可以转成验收)。保留原规则、例外和不确定性;输入缺失时记录待确认项。
11
+
10
12
  ## 功能父 Ticket
11
13
 
12
14
  <Local:`docs/.scratch/<feature>/parent-ticket.md`;GitHub / GitLab:Issue URL / 编号>
13
15
 
14
16
  ## 问题陈述
15
17
 
16
- (从用户视角描述要解决的问题。)
18
+ (说明哪个角色在什么场景遇到什么问题,给出来源及其限制。)
17
19
 
18
20
  ## 解决方案
19
21
 
20
- (从用户视角描述解决方案。)
22
+ (说明本次改变的用户行为和范围,区分已确认规则与候选方案。)
21
23
 
22
24
  ## 用户故事
23
25
 
@@ -25,12 +27,16 @@ owner: ai
25
27
 
26
28
  ## 功能需求
27
29
 
30
+ 需求写明主体、触发条件和可观察结果;关联验收标准。不要用“完善、友好、高效”代替规则。
31
+
28
32
  | ID | 需求 | 优先级 | 备注 |
29
33
  |---|---|---|---|
30
34
  | FR-001 | | P0/P1/P2 | |
31
35
 
32
36
  ## 非功能需求
33
37
 
38
+ 命中时填写工作负载或适用条件、目标值及单位、验证方式与依据;尚未确认的目标保留为未决项,不补造性能承诺。
39
+
34
40
  | 类别 | 需求 | 目标 |
35
41
  |---|---|---|
36
42
  | 性能 | | |
@@ -38,6 +44,8 @@ owner: ai
38
44
 
39
45
  ## 验收标准
40
46
 
47
+ 关联需求 ID,按实际影响覆盖成功、拒绝、边界和恢复。保留“全部”“仅当”等限定;测试数据不能反向扩展业务规则。
48
+
41
49
  ```gherkin
42
50
  假如 <上下文>
43
51
  当 <操作>
@@ -47,7 +55,8 @@ owner: ai
47
55
  ## 产品总体设计 / 功能架构
48
56
 
49
57
  - 功能域与模块边界:
50
- - MVP / 非目标:
58
+ - MVP:
59
+ - 非目标:见本文“非目标范围”,此处不重复维护。
51
60
  - UI 影响:是 / 否
52
61
  - 产品总体设计:`docs/.scratch/<feature>/design/<feature>-product-overview-design.md`
53
62
 
@@ -99,4 +108,8 @@ owner: ai
99
108
 
100
109
  ## 非目标范围
101
110
 
111
+ 明确本次不交付什么及依据;与上游范围保持一致。
112
+
102
113
  ## 风险
114
+
115
+ 记录已知风险和未决项的影响、责任人、解决时点及接收方;尚未明确原因时保留现象和验证方法。
@@ -4,7 +4,7 @@ status: ready-for-human
4
4
 
5
5
  # 垂直切片 Ticket:<标题>
6
6
 
7
- Status: ready-for-human
7
+ 填写前读取[中文写作规范](../process/document-writing.md)和 [Ticket 对照](examples/lifecycle-writing-examples.md#3-ticket把交付行为与实现就绪区分清楚)。状态只在 frontmatter 维护,正文记录依据。
8
8
 
9
9
  ## 父级
10
10
 
@@ -14,6 +14,8 @@ Status: ready-for-human
14
14
 
15
15
  描述本 Ticket 要交付的窄而完整的端到端行为。它必须贯穿所有受影响层,是可独立验证的垂直切片,不能只是某一层的横向任务。
16
16
 
17
+ 先用一句话写出用户操作及交付结果,再引用对应 Spec 规则、版本和本切片边界;不要重写冻结需求或填入未经确认的范围。
18
+
17
19
  ## 覆盖的用户故事
18
20
 
19
21
  - <用户故事 ID 或文本>
@@ -31,6 +33,8 @@ Status: ready-for-human
31
33
 
32
34
  ## 验收标准
33
35
 
36
+ 每项写条件、操作和可观察结果,关联上游规则;保留必要例外。测试未执行前保持未勾选。
37
+
34
38
  - [ ] 标准 1
35
39
  - [ ] 标准 2
36
40
  - [ ] 标准 3
@@ -92,7 +96,7 @@ Status: ready-for-human
92
96
 
93
97
  ## 阻塞关系
94
98
 
95
- - 无,可立即开始
99
+ 列出实际依赖、未决项、责任人和解除条件;尚未核验时写“待核验”。只有确认没有阻塞时才写“无”,无依赖不等于已通过实现就绪核验。
96
100
 
97
101
  ## 重路由状态
98
102
 
@@ -116,9 +120,9 @@ Status: ready-for-human
116
120
 
117
121
  ## 状态
118
122
 
119
- `ready-for-agent`
123
+ 状态依据:<当前版本合同、批准和就绪核验记录引用,以及尚未关闭的阻塞>。
120
124
 
121
- > 仅当阻塞关系为“无”且必要门禁已通过时填写 `ready-for-agent`;否则写明被哪个 Ticket 阻塞。
125
+ > 默认 frontmatter 为 `ready-for-human`。仅在生命周期确认合同已批准且当前、阻塞已清除、全部适用门禁及就绪条件通过后更新为 `ready-for-agent`;本节不另维护第二个状态值。
122
126
 
123
127
  ## AI / 人工审查点
124
128
 
@@ -0,0 +1,336 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ mkdtempSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { spawnSync } from "node:child_process";
13
+ import { tmpdir } from "node:os";
14
+ import path from "node:path";
15
+
16
+ function fail(message) {
17
+ throw new TypeError(message);
18
+ }
19
+
20
+ function safe(root, relative, label = "path") {
21
+ if (typeof relative !== "string" || !relative || path.isAbsolute(relative)) fail(`${label} 必须是仓库相对路径`);
22
+ const resolvedRoot = path.resolve(root);
23
+ const resolved = path.resolve(resolvedRoot, relative);
24
+ if (resolved === resolvedRoot || !resolved.startsWith(`${resolvedRoot}${path.sep}`)) fail(`${label} 越界: ${relative}`);
25
+ return resolved;
26
+ }
27
+
28
+ function files(directory, prefix = "") {
29
+ if (!existsSync(directory)) return new Map();
30
+ const info = lstatSync(directory);
31
+ if (!info.isDirectory()) fail(`技能源必须是目录: ${directory}`);
32
+ const result = new Map();
33
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
34
+ if (entry.isSymbolicLink()) fail(`跨仓同步不接受符号链接: ${path.join(directory, entry.name)}`);
35
+ const absolute = path.join(directory, entry.name);
36
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
37
+ if (entry.isDirectory()) {
38
+ for (const item of files(absolute, relative)) result.set(...item);
39
+ } else if (entry.isFile()) result.set(relative, readFileSync(absolute));
40
+ }
41
+ return result;
42
+ }
43
+
44
+ function digest(value) {
45
+ return createHash("sha256").update(value).digest("hex");
46
+ }
47
+
48
+ function sameTree(left, right) {
49
+ if (left.size !== right.size) return false;
50
+ for (const [name, content] of left) if (!right.get(name)?.equals(content)) return false;
51
+ return true;
52
+ }
53
+
54
+ function treeDigest(tree) {
55
+ const hash = createHash("sha256");
56
+ for (const [name, content] of [...tree.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
57
+ hash.update(name);
58
+ hash.update("\0");
59
+ hash.update(content);
60
+ hash.update("\0");
61
+ }
62
+ return hash.digest("hex");
63
+ }
64
+
65
+ function patchedSource(root, profile, item) {
66
+ const source = safe(root, item.source ?? `.agents/skills/${item.id}`, "source");
67
+ const patchFile = safe(root, item.patch, "patch");
68
+ const sourceFiles = files(source);
69
+ const actualBaseline = treeDigest(sourceFiles);
70
+ if (actualBaseline !== item.source_tree_sha256) {
71
+ fail(`${profile}/${item.id} 适配基线冲突: 上游树预期 ${item.source_tree_sha256},实际 ${actualBaseline}`);
72
+ }
73
+ const temporary = mkdtempSync(path.join(tmpdir(), "profile-skill-patch-"));
74
+ try {
75
+ for (const [name, content] of sourceFiles) {
76
+ const target = safe(temporary, name, "patch source file");
77
+ mkdirSync(path.dirname(target), { recursive: true });
78
+ writeFileSync(target, content);
79
+ }
80
+ const applied = spawnSync("git", ["apply", "--binary", "--whitespace=nowarn", patchFile], {
81
+ cwd: temporary,
82
+ encoding: "utf8",
83
+ stdio: ["ignore", "pipe", "pipe"],
84
+ });
85
+ if (applied.status !== 0) fail(`${profile}/${item.id} 补丁无法重放: ${applied.stderr.trim()}`);
86
+ return files(temporary);
87
+ } finally {
88
+ rmSync(temporary, { recursive: true, force: true });
89
+ }
90
+ }
91
+
92
+ function gitDirty(profileRoot, relativePaths) {
93
+ if (!relativePaths.length) return new Set();
94
+ const result = spawnSync("git", ["-C", profileRoot, "status", "--porcelain=v1", "--untracked-files=all", "--", ...relativePaths], {
95
+ encoding: "utf8",
96
+ stdio: ["ignore", "pipe", "pipe"],
97
+ });
98
+ if (result.status !== 0) fail(`无法读取目标工作树状态: ${result.stderr.trim()}`);
99
+ return new Set(result.stdout.split("\n").filter(Boolean).map((line) => line.slice(3).replace(/^"|"$/g, "")));
100
+ }
101
+
102
+ function dirtyMatch(dirty, relative) {
103
+ return [...dirty].some((entry) => entry === relative || entry.startsWith(`${relative}/`) || relative.startsWith(`${entry}/`));
104
+ }
105
+
106
+ function transformedSource(root, profile, item) {
107
+ const source = safe(root, item.source ?? `.agents/skills/${item.id}`, "source");
108
+ const sourceFiles = files(source);
109
+ const desired = item.files?.length
110
+ ? new Map(item.files.map((name) => {
111
+ if (!sourceFiles.has(name)) fail(`${profile}/${item.id} 适配源文件不存在: ${name}`);
112
+ return [name, sourceFiles.get(name)];
113
+ }))
114
+ : sourceFiles;
115
+ for (const replacement of item.replacements ?? []) {
116
+ const current = desired.get(replacement.file);
117
+ if (!current) fail(`${profile}/${item.id} 适配文件不存在: ${replacement.file}`);
118
+ const before = current.toString("utf8");
119
+ const count = before.split(replacement.from).length - 1;
120
+ const expected = replacement.count ?? 1;
121
+ if (count !== expected) fail(`${profile}/${item.id} 适配基线冲突: ${replacement.file} 中预期 ${expected} 处片段,实际 ${count} 处`);
122
+ desired.set(replacement.file, Buffer.from(before.split(replacement.from).join(replacement.to)));
123
+ }
124
+ return desired;
125
+ }
126
+
127
+ function compareTree(profile, id, targetRelative, current, desired, classification, changes) {
128
+ const names = new Set([...current.keys(), ...desired.keys()]);
129
+ for (const name of [...names].sort()) {
130
+ const before = current.get(name);
131
+ const after = desired.get(name);
132
+ if (before && after && before.equals(after)) continue;
133
+ changes.push({
134
+ profile,
135
+ skill: id,
136
+ classification,
137
+ status: before ? (after ? "updated" : "retired") : "added",
138
+ path: `${targetRelative}/${name}`,
139
+ before_sha256: before ? digest(before) : null,
140
+ after_sha256: after ? digest(after) : null,
141
+ content: after ?? null,
142
+ });
143
+ }
144
+ }
145
+
146
+ export function loadProfileSyncConfig(root, configPath = ".template-source/profile-skill-sync.json") {
147
+ const absolute = safe(root, configPath, "config");
148
+ const config = JSON.parse(readFileSync(absolute, "utf8"));
149
+ if (config.schema_version !== 1 || !config.profiles || typeof config.profiles !== "object") fail("profile skill sync 配置必须是 schema v1");
150
+ return config;
151
+ }
152
+
153
+ export function planProfileSkillSync({ root, config, selectedProfiles, dirtyProvider = gitDirty }) {
154
+ const names = selectedProfiles?.length ? selectedProfiles : Object.keys(config.profiles);
155
+ const canonicalRoot = safe(root, config.canonical_root ?? ".agents/skills", "canonical_root");
156
+ const changes = [];
157
+ const classifications = [];
158
+ const issues = [];
159
+ for (const profile of names) {
160
+ const definition = config.profiles[profile];
161
+ if (!definition) fail(`未知 profile: ${profile}`);
162
+ const profileRoot = safe(root, definition.target, `${profile}.target`);
163
+ if (!existsSync(path.join(profileRoot, "yss-project.yaml"))) issues.push({ profile, status: "missing_dependency", path: `${definition.target}/yss-project.yaml` });
164
+ const classified = new Map();
165
+ for (const [classification, items] of [
166
+ ["exact", (definition.exact ?? []).map(({ id }) => id)],
167
+ ["adapted", (definition.adapted ?? []).map(({ id }) => id)],
168
+ ["retired", definition.retired ?? []],
169
+ ["local-only", definition.local_only ?? []],
170
+ ["excluded", definition.excluded ?? []],
171
+ ["upstream", definition.upstream ?? []],
172
+ ]) {
173
+ for (const id of items) {
174
+ if (classified.has(id)) fail(`${profile}/${id} 同时声明为 ${classified.get(id)} 和 ${classification},违反单向来源规则`);
175
+ classified.set(id, classification);
176
+ }
177
+ }
178
+ if (definition.upstream_manifest) {
179
+ const manifestPath = safe(root, definition.upstream_manifest, "upstream manifest");
180
+ if (!existsSync(manifestPath)) issues.push({ profile, status: "missing_dependency", path: definition.upstream_manifest });
181
+ else {
182
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
183
+ const declared = [...(definition.upstream ?? [])].sort();
184
+ const manifestIds = (manifest.skills ?? []).map(({ canonical }) => canonical).sort();
185
+ if (JSON.stringify(declared) !== JSON.stringify(manifestIds) || manifest.source_root !== ".agents/skills") {
186
+ issues.push({ profile, status: "adaptation_conflict", path: definition.upstream_manifest, message: "上游清单与 profile 单向来源声明不一致" });
187
+ }
188
+ }
189
+ }
190
+
191
+ for (const item of definition.exact ?? []) {
192
+ const sourceRelative = item.source ?? `.agents/skills/${item.id}`;
193
+ const targetRelative = item.target ?? `.agents/skills/${item.id}`;
194
+ const source = safe(root, sourceRelative, "source");
195
+ if (!existsSync(source)) {
196
+ issues.push({ profile, skill: item.id, status: "missing_dependency", path: sourceRelative });
197
+ continue;
198
+ }
199
+ compareTree(profile, item.id, targetRelative, files(safe(profileRoot, targetRelative, "target")), files(source), "exact", changes);
200
+ classifications.push({ profile, skill: item.id, classification: "exact" });
201
+ }
202
+
203
+ for (const item of definition.adapted ?? []) {
204
+ classifications.push({ profile, skill: item.id, classification: "adapted" });
205
+ if (item.strategy === "profile-owned") {
206
+ const targetRelative = item.target ?? `.agents/skills/${item.id}`;
207
+ const skillFile = safe(profileRoot, `${targetRelative}/SKILL.md`, "adapted target");
208
+ if (!existsSync(skillFile)) issues.push({ profile, skill: item.id, status: "missing_dependency", path: `${definition.target}/${targetRelative}/SKILL.md` });
209
+ else {
210
+ const content = readFileSync(skillFile, "utf8");
211
+ for (const marker of item.required_markers ?? []) if (!content.includes(marker)) issues.push({ profile, skill: item.id, status: "adaptation_conflict", path: `${definition.target}/${targetRelative}/SKILL.md`, marker });
212
+ }
213
+ continue;
214
+ }
215
+ const targetRelative = item.target ?? `.agents/skills/${item.id}`;
216
+ let desired;
217
+ try {
218
+ const sourceRelative = item.source ?? `.agents/skills/${item.id}`;
219
+ if (!existsSync(safe(root, sourceRelative, "source"))) {
220
+ issues.push({ profile, skill: item.id, status: "missing_dependency", path: sourceRelative });
221
+ continue;
222
+ }
223
+ if (item.strategy === "replace") desired = transformedSource(root, profile, item);
224
+ else if (item.strategy === "patch") {
225
+ if (!item.patch || !existsSync(safe(root, item.patch, "patch"))) {
226
+ issues.push({ profile, skill: item.id, status: "missing_dependency", path: item.patch ?? "patch" });
227
+ continue;
228
+ }
229
+ desired = patchedSource(root, profile, item);
230
+ } else fail(`${profile}/${item.id} 未知适配策略`);
231
+ } catch (error) {
232
+ issues.push({ profile, skill: item.id, status: "adaptation_conflict", message: error.message });
233
+ continue;
234
+ }
235
+ const targetFiles = files(safe(profileRoot, targetRelative, "target"));
236
+ const current = item.files?.length ? new Map(item.files.filter((name) => targetFiles.has(name)).map((name) => [name, targetFiles.get(name)])) : targetFiles;
237
+ compareTree(profile, item.id, targetRelative, current, desired, "adapted", changes);
238
+ }
239
+
240
+ for (const item of definition.assets ?? []) {
241
+ const source = safe(root, item.source, "asset source");
242
+ const target = safe(profileRoot, item.target ?? item.source, "asset target");
243
+ if (!existsSync(source)) {
244
+ issues.push({ profile, status: "missing_dependency", path: item.source });
245
+ continue;
246
+ }
247
+ const before = existsSync(target) ? readFileSync(target) : null;
248
+ const after = readFileSync(source);
249
+ if (!before?.equals(after)) changes.push({ profile, classification: "exact", status: before ? "updated" : "added", path: item.target ?? item.source, before_sha256: before ? digest(before) : null, after_sha256: digest(after), content: after });
250
+ }
251
+
252
+ for (const id of definition.retired ?? []) {
253
+ classifications.push({ profile, skill: id, classification: "retired" });
254
+ const targetRelative = `.agents/skills/${id}`;
255
+ const current = files(safe(profileRoot, targetRelative, "retired target"));
256
+ for (const [name, content] of current) changes.push({ profile, skill: id, classification: "retired", status: "retired", path: `${targetRelative}/${name}`, before_sha256: digest(content), after_sha256: null, content: null });
257
+ }
258
+ for (const id of definition.local_only ?? []) classifications.push({ profile, skill: id, classification: "local-only" });
259
+ for (const id of definition.excluded ?? []) classifications.push({ profile, skill: id, classification: "excluded" });
260
+ for (const id of definition.upstream ?? []) classifications.push({ profile, skill: id, classification: "upstream" });
261
+
262
+ const targetSkillRoot = safe(profileRoot, ".agents/skills", "profile skill root");
263
+ if (existsSync(targetSkillRoot)) {
264
+ for (const entry of readdirSync(targetSkillRoot, { withFileTypes: true })) {
265
+ if (!entry.isDirectory() || classified.has(entry.name)) continue;
266
+ const sourceSkill = path.join(canonicalRoot, entry.name);
267
+ if (!existsSync(sourceSkill) || !lstatSync(sourceSkill).isDirectory()) continue;
268
+ if (!sameTree(files(sourceSkill), files(path.join(targetSkillRoot, entry.name)))) {
269
+ issues.push({
270
+ profile,
271
+ skill: entry.name,
272
+ status: "unregistered_difference",
273
+ path: `${definition.target}/.agents/skills/${entry.name}`,
274
+ message: "同名 Skill 内容不同但未登记同步分类",
275
+ });
276
+ }
277
+ }
278
+ }
279
+
280
+ const changedPaths = [...new Set(changes.filter((item) => item.profile === profile).map((item) => item.path))];
281
+ const dirty = dirtyProvider(profileRoot, changedPaths);
282
+ for (const change of changes.filter((item) => item.profile === profile && dirtyMatch(dirty, item.path))) {
283
+ issues.push({ profile, skill: change.skill, status: "adaptation_conflict", path: `${definition.target}/${change.path}`, message: "目标含未提交改动" });
284
+ }
285
+ }
286
+ return { schema_version: 1, profiles: names, changes, classifications, issues };
287
+ }
288
+
289
+ export function applyProfileSkillSync({ root, config, plan, write = writeFileSync, remove = rmSync }) {
290
+ if (plan.issues.length) fail(`同步预检失败,共 ${plan.issues.length} 项问题`);
291
+ const snapshots = new Map();
292
+ const touched = [];
293
+ try {
294
+ for (const change of plan.changes) {
295
+ const profileRoot = safe(root, config.profiles[change.profile].target, "profile target");
296
+ const target = safe(profileRoot, change.path, "change target");
297
+ const current = existsSync(target) ? readFileSync(target) : null;
298
+ const currentHash = current ? digest(current) : null;
299
+ if (currentHash !== change.before_sha256) fail(`目标在预览后发生变化: ${change.profile}/${change.path}`);
300
+ snapshots.set(target, current);
301
+ touched.push(target);
302
+ if (change.content === null) remove(target, { force: true });
303
+ else {
304
+ mkdirSync(path.dirname(target), { recursive: true });
305
+ write(target, change.content);
306
+ }
307
+ }
308
+ for (const profile of plan.profiles) {
309
+ const profileRoot = safe(root, config.profiles[profile].target, "profile target");
310
+ for (const id of config.profiles[profile].retired ?? []) {
311
+ const retiredRoot = safe(profileRoot, `.agents/skills/${id}`, "retired skill");
312
+ if (!existsSync(retiredRoot)) continue;
313
+ if (!lstatSync(retiredRoot).isDirectory()) fail(`退役 Skill 目标不是目录: ${retiredRoot}`);
314
+ if (files(retiredRoot).size) fail(`退役 Skill 在预览后出现未纳管文件: ${retiredRoot}`);
315
+ remove(retiredRoot, { recursive: true, force: true });
316
+ }
317
+ }
318
+ } catch (error) {
319
+ for (const target of touched.reverse()) {
320
+ const before = snapshots.get(target);
321
+ if (before === null) remove(target, { force: true });
322
+ else {
323
+ mkdirSync(path.dirname(target), { recursive: true });
324
+ writeFileSync(target, before);
325
+ }
326
+ }
327
+ throw error;
328
+ }
329
+ return { ...plan, applied: plan.changes.length };
330
+ }
331
+
332
+ export function reportProfileSkillSync(plan) {
333
+ const counts = {};
334
+ for (const item of [...plan.changes, ...plan.issues]) counts[item.status] = (counts[item.status] ?? 0) + 1;
335
+ return { ...plan, changes: plan.changes.map(({ content, ...item }) => item), counts };
336
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { applyProfileSkillSync, loadProfileSyncConfig, planProfileSkillSync, reportProfileSkillSync } from "./lib/profile-skill-sync.mjs";
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
+ try {
8
+ const args = process.argv.slice(2);
9
+ const modes = ["--check", "--dry-run", "--apply"].filter((item) => args.includes(item));
10
+ if (modes.length > 1) throw new TypeError("--check、--dry-run、--apply 只能选择一个");
11
+ const profileArgs = [];
12
+ for (let index = 0; index < args.length; index += 1) {
13
+ const arg = args[index];
14
+ if (["--check", "--dry-run", "--apply", "--json"].includes(arg)) continue;
15
+ if (arg === "--profile") {
16
+ const value = args[index + 1];
17
+ if (!value || value.startsWith("--")) throw new TypeError("--profile 缺少值");
18
+ profileArgs.push(value);
19
+ index += 1;
20
+ continue;
21
+ }
22
+ if (arg.startsWith("--profile=")) profileArgs.push(arg.slice(10));
23
+ else throw new TypeError(`未知参数: ${arg}`);
24
+ }
25
+ const json = args.includes("--json");
26
+ const selectedProfiles = profileArgs.some((item) => item === "all") ? [] : profileArgs.flatMap((item) => item.split(",").filter(Boolean));
27
+ const config = loadProfileSyncConfig(root);
28
+ const plan = planProfileSkillSync({ root, config, selectedProfiles });
29
+ const result = args.includes("--apply") ? applyProfileSkillSync({ root, config, plan }) : plan;
30
+ const report = reportProfileSkillSync(result);
31
+ if (json) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
32
+ else {
33
+ process.stdout.write(`profile skills: ${report.profiles.join(", ")}\n`);
34
+ for (const [status, count] of Object.entries(report.counts)) process.stdout.write(`${status}: ${count}\n`);
35
+ for (const item of report.changes) process.stdout.write(`${item.profile}: ${item.status}: ${item.path}\n`);
36
+ for (const item of report.issues) process.stderr.write(`${item.profile}: ${item.status}: ${item.path ?? item.skill ?? item.message}\n`);
37
+ }
38
+ if (args.includes("--check") && (plan.changes.length || plan.issues.length)) process.exitCode = 1;
39
+ if (args.includes("--apply") && plan.issues.length) process.exitCode = 1;
40
+ } catch (error) {
41
+ process.stderr.write(`${error.message}\n`);
42
+ process.exitCode = 1;
43
+ }
@@ -18,7 +18,10 @@
18
18
  "revision": "c4d958f18cc947cb91cbe1bd48783fa4601cc18f"
19
19
  },
20
20
  "iloveZzz/yss-harness-design-agent": {
21
- "revision": "3a358684c5684ada433dca4fd97ee187050ca6d8"
21
+ "revision": "41fb25425928153791f7d92a85eb717f4eb2d546"
22
+ },
23
+ "ayghri/i-have-adhd": {
24
+ "revision": "7b9069b39972e269e61bd95c2f66ebb90cac6a02"
22
25
  }
23
26
  },
24
27
  "skills": {
@@ -54,7 +57,7 @@
54
57
  "source": "mattpocock/skills",
55
58
  "sourceType": "github",
56
59
  "skillPath": "skills/engineering/code-review/SKILL.md",
57
- "effectiveHash": "660ca39ab37b65099940b2e6a197089291e2fb74494ec93bbeb8e2fde6559465",
60
+ "effectiveHash": "9113c8cad19ee2b44120b7f324cac7246e63272cf50a28c4cae1a4d61a9b5290",
58
61
  "sourceRevision": "0ab1b63a410a03d3627979a109c8695de27af954",
59
62
  "upstreamHash": "12daafb9c4f77deb3c3303dc2e6f8a3c2a0ff7928fc004af959ba18b8bd38068",
60
63
  "adaptationRef": "docs/process/MATT-POCOCK-ENGINEERING-SKILLS.md",
@@ -294,6 +297,21 @@
294
297
  ".pi/skills"
295
298
  ]
296
299
  },
300
+ "i-have-adhd": {
301
+ "source": "ayghri/i-have-adhd",
302
+ "sourceType": "git",
303
+ "skillPath": "skills/i-have-adhd/SKILL.md",
304
+ "effectiveHash": "efabaa2a67780462997fdebadee3a9731a16368d158c79c9df5ae50e0983debf",
305
+ "sourceRevision": "7b9069b39972e269e61bd95c2f66ebb90cac6a02",
306
+ "upstreamHash": "4c88635cbcb4765b63528d608d27589596840708e89607fda9c9fa5726c11f7e",
307
+ "adaptationRef": ".agents/skills/i-have-adhd/references/yss-adaptation.md",
308
+ "targets": [
309
+ ".agents/skills",
310
+ ".codex/skills",
311
+ ".cursor/skills",
312
+ ".pi/skills"
313
+ ]
314
+ },
297
315
  "implement": {
298
316
  "source": "mattpocock/skills",
299
317
  "sourceType": "github",
@@ -462,9 +480,9 @@
462
480
  "source": "iloveZzz/yss-harness-design-agent",
463
481
  "sourceType": "git-submodule",
464
482
  "skillPath": ".agents/skills/prototype-review/SKILL.md",
465
- "effectiveHash": "d795e8a04a3473f606d98f70faafb8334673f085c9bf0816aa8c433f4be579ff",
466
- "sourceRevision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
467
- "upstreamHash": "2e77b3093a758bb692fb3bda453c5c020d0523f875975e17e2731365f67432e1",
483
+ "effectiveHash": "df6f131bef0f52a96d307d98ab42d74888a9b72c5508911b5622913b334e8eab",
484
+ "sourceRevision": "41fb25425928153791f7d92a85eb717f4eb2d546",
485
+ "upstreamHash": "9206df8b6c555c1af1351e425c5ec99ec2a40263c6861607f1418b9c520873ab",
468
486
  "adaptationRef": "docs/agents/strategic-design-skills-integration.md",
469
487
  "targets": [
470
488
  ".agents/skills",
@@ -683,7 +701,7 @@
683
701
  "sourceType": "git-submodule",
684
702
  "skillPath": ".agents/skills/yss-antd-design/SKILL.md",
685
703
  "effectiveHash": "92739eb34352fd70fbb52f4d9d6c6ac09aa02bc0155a4bd812a98cd5f933ff70",
686
- "sourceRevision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
704
+ "sourceRevision": "41fb25425928153791f7d92a85eb717f4eb2d546",
687
705
  "upstreamHash": "08ce99d45586d5ea19e25fdd9e9a6ede16e66f18992b70b87e0cd23f92343b16",
688
706
  "adaptationRef": "docs/agents/strategic-design-skills-integration.md",
689
707
  "targets": [
@@ -698,7 +716,7 @@
698
716
  "sourceType": "git-submodule",
699
717
  "skillPath": ".agents/skills/yss-antdv-next-design/SKILL.md",
700
718
  "effectiveHash": "c7c07c5dc3716f578904559473d43d92878aa25a6a45c8827e288f0242d42b75",
701
- "sourceRevision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
719
+ "sourceRevision": "41fb25425928153791f7d92a85eb717f4eb2d546",
702
720
  "upstreamHash": "2942ed52dd58a38e51f09eb11254809334aeaed08f21cd081f09f35e6647a68c",
703
721
  "adaptationRef": "docs/agents/strategic-design-skills-integration.md",
704
722
  "targets": [
@@ -788,7 +806,7 @@
788
806
  "sourceType": "git-submodule",
789
807
  "skillPath": ".agents/skills/yss-design-system/SKILL.md",
790
808
  "effectiveHash": "e69597b8b6a5ce4de5bd40f67630443b8176202f6b028b2ce146a2a1d062816f",
791
- "sourceRevision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
809
+ "sourceRevision": "41fb25425928153791f7d92a85eb717f4eb2d546",
792
810
  "upstreamHash": "90aaf06b2514353152464a4d032076fa9d5ef56272d8ad526f5544f3c22a2b00",
793
811
  "adaptationRef": "docs/agents/strategic-design-skills-integration.md",
794
812
  "targets": [
@@ -913,7 +931,7 @@
913
931
  "source": "project",
914
932
  "sourceType": "local",
915
933
  "skillPath": ".agents/skills/yss-implementation-contract-compiler/SKILL.md",
916
- "effectiveHash": "e88d2bf7138c48e4ac4e01555a4f0a3fc2d2d1faef3bd78bd098d9fa2bf418f3",
934
+ "effectiveHash": "cfa69127e633d485a6691d9e70737fd050401bee7e598971c42bf5d6825c0391",
917
935
  "targets": [
918
936
  ".agents/skills",
919
937
  ".codex/skills",
@@ -997,7 +1015,7 @@
997
1015
  "source": "project",
998
1016
  "sourceType": "local",
999
1017
  "skillPath": ".agents/skills/yss-openapi-governance/SKILL.md",
1000
- "effectiveHash": "b6938f1eede04f21cce32a870e365684e83c64775b07293af9dbd64a17e9cee4",
1018
+ "effectiveHash": "e33980c3964c4749ef900c7c27ee683ad4e5ff285e9adf234bc82687304dee30",
1001
1019
  "targets": [
1002
1020
  ".agents/skills",
1003
1021
  ".codex/skills",
@@ -1009,7 +1027,7 @@
1009
1027
  "source": "project",
1010
1028
  "sourceType": "local",
1011
1029
  "skillPath": ".agents/skills/yss-product-lifecycle/SKILL.md",
1012
- "effectiveHash": "5285de45bf23391837d891eb8513a2cb5cadac3a99fedffcda9dfa2fbbffff00",
1030
+ "effectiveHash": "9bebb957fdd80cfe812605cec25cdb2d2c2ba229db2547f870efb00e2175b2a9",
1013
1031
  "targets": [
1014
1032
  ".agents/skills",
1015
1033
  ".codex/skills",
@@ -1021,9 +1039,9 @@
1021
1039
  "source": "iloveZzz/yss-harness-design-agent",
1022
1040
  "sourceType": "git-submodule",
1023
1041
  "skillPath": ".agents/skills/yss-prototype-stage/SKILL.md",
1024
- "effectiveHash": "a30d795c53d6f42ae5654e812fcdd9781949333f9c2e1572b65c058761b44417",
1025
- "sourceRevision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
1026
- "upstreamHash": "e8f2f3a507da6bf7be0582e4a4dd9659381da8e291755070698a1f51a4e33808",
1042
+ "effectiveHash": "c87c7c38c9598c04f28cfa759fd6c1c12b2937b49e92f8171992d8346cb179e0",
1043
+ "sourceRevision": "41fb25425928153791f7d92a85eb717f4eb2d546",
1044
+ "upstreamHash": "aa78ef758ad3c515e94fb6b87d4e519f39a38e53bb3c82413d707f7d0f96f58b",
1027
1045
  "adaptationRef": "docs/agents/strategic-design-skills-integration.md",
1028
1046
  "targets": [
1029
1047
  ".agents/skills",
@@ -1048,7 +1066,7 @@
1048
1066
  "source": "mattpocock/skills",
1049
1067
  "sourceType": "github",
1050
1068
  "skillPath": "skills/engineering/research/SKILL.md",
1051
- "effectiveHash": "0d8f94e6379ad56897a7821b4803a3ba72910e617aab730e47db719e6eb02807",
1069
+ "effectiveHash": "bf209209dd5bd458a5a61a2468a3cecc9b2f41ca655503b21d67613584c2bdb1",
1052
1070
  "sourceRevision": "0ab1b63a410a03d3627979a109c8695de27af954",
1053
1071
  "upstreamHash": "75b7f9bbd7ccff6755a006f8b25d319a46ecbf6bd4b4c3323965ed9cb65e78ff",
1054
1072
  "adaptationRef": "docs/process/MATT-POCOCK-ENGINEERING-SKILLS.md",
@@ -1099,9 +1117,9 @@
1099
1117
  "source": "iloveZzz/yss-harness-design-agent",
1100
1118
  "sourceType": "git-submodule",
1101
1119
  "skillPath": ".agents/skills/yss-stage-decision/SKILL.md",
1102
- "effectiveHash": "159ec9269b8680826986009345f9067566e727a15380b887f16f6e1fd9672d00",
1103
- "sourceRevision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
1104
- "upstreamHash": "2ec63f66aaf799d693cec0f0b5d71b8c4917e4e2822e0ddd519bc9b711fa3c95",
1120
+ "effectiveHash": "a9d209ca8cf16bb8d0f9f6ea05cfedf6fb25928216f3872bc5a1ca5ac698d34a",
1121
+ "sourceRevision": "41fb25425928153791f7d92a85eb717f4eb2d546",
1122
+ "upstreamHash": "b9fe9d8f5009db0289123155f1662d8d21b34e26928d6728545da7f0c0bb0de1",
1105
1123
  "adaptationRef": "docs/agents/strategic-design-skills-integration.md",
1106
1124
  "targets": [
1107
1125
  ".agents/skills",
@@ -1126,7 +1144,7 @@
1126
1144
  "source": "project",
1127
1145
  "sourceType": "local",
1128
1146
  "skillPath": ".agents/skills/yss-technical-design/SKILL.md",
1129
- "effectiveHash": "a4226303571483b115ef7134f9615440ce8b7d39f209be39e1bc16152e080a9f",
1147
+ "effectiveHash": "06e7cdbf6441be90a19ec705388c1e164daefed5ace237c7e6595cf0c0f81e1f",
1130
1148
  "targets": [
1131
1149
  ".agents/skills",
1132
1150
  ".codex/skills",
@@ -3,8 +3,8 @@
3
3
  "templateName": "yss-spec-project-template",
4
4
  "templateSource": "github:iloveZzz/yss-spec-project-template",
5
5
  "templateRepository": "https://github.com/iloveZzz/yss-spec-project-template.git",
6
- "requestedRef": "46b29114de53cf4d8d92123006b211d7b3a5a142",
7
- "templateCommit": "46b29114de53cf4d8d92123006b211d7b3a5a142",
6
+ "requestedRef": "99061076f951d9fdf2f54eaa24d1edf745aefa65",
7
+ "templateCommit": "99061076f951d9fdf2f54eaa24d1edf745aefa65",
8
8
  "manifestHash": "d194bb318a47c9c53b04292e97805c563ef7b296c2ca9b507cca498be7f52afe",
9
9
  "encodedPaths": {
10
10
  ".codex/skills/data-analytics/.gitignore": ".codex/skills/data-analytics/__yss_dotfile__.gitignore",
@@ -12,6 +12,6 @@
12
12
  ".codex/skills/product-design/templates/prototype/.npmrc": ".codex/skills/product-design/templates/prototype/__yss_dotfile__.npmrc",
13
13
  ".gitignore": "__yss_dotfile__.gitignore"
14
14
  },
15
- "snapshotHash": "6fe2b0f7444fdf19d3d23eca1c334195c551de01349d79ff0a1e468a3bb973f0",
16
- "generatedAt": "2026-09-10T10:12:58.943Z"
15
+ "snapshotHash": "7e8bec077d452f2270c479e622009536ae12495f21936b3f36a0298f0085d7aa",
16
+ "generatedAt": "2026-09-10T17:59:33.887Z"
17
17
  }