create-yss-spec 3.3.4 → 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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/template/.agents/skills/.strategic-design-skills-manifest.json +4 -4
- package/template/.agents/skills/code-review/SKILL.md +6 -0
- package/template/.agents/skills/i-have-adhd/LICENSE +21 -0
- package/template/.agents/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.agents/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.agents/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.agents/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.agents/skills/prototype-review/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.agents/skills/yss-research/SKILL.md +6 -0
- package/template/.agents/skills/yss-stage-decision/SKILL.md +6 -0
- package/template/.agents/skills/yss-technical-design/SKILL.md +6 -0
- package/template/.codex/skills/code-review/SKILL.md +6 -0
- package/template/.codex/skills/i-have-adhd/LICENSE +21 -0
- package/template/.codex/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.codex/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.codex/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.codex/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.codex/skills/prototype-review/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.codex/skills/yss-research/SKILL.md +6 -0
- package/template/.codex/skills/yss-stage-decision/SKILL.md +6 -0
- package/template/.codex/skills/yss-technical-design/SKILL.md +6 -0
- package/template/.cursor/skills/code-review/SKILL.md +6 -0
- package/template/.cursor/skills/i-have-adhd/LICENSE +21 -0
- package/template/.cursor/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.cursor/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.cursor/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.cursor/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.cursor/skills/prototype-review/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.cursor/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.cursor/skills/yss-research/SKILL.md +6 -0
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +6 -0
- package/template/.cursor/skills/yss-technical-design/SKILL.md +6 -0
- package/template/.pi/skills/code-review/SKILL.md +6 -0
- package/template/.pi/skills/i-have-adhd/LICENSE +21 -0
- package/template/.pi/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.pi/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.pi/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.pi/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.pi/skills/prototype-review/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.pi/skills/yss-research/SKILL.md +6 -0
- package/template/.pi/skills/yss-stage-decision/SKILL.md +6 -0
- package/template/.pi/skills/yss-technical-design/SKILL.md +6 -0
- package/template/docs/agents/yss-skill-registry.yaml +22 -0
- package/template/docs/plan/templates/plan-template.md +8 -4
- package/template/docs/process/document-writing.md +77 -0
- package/template/docs/templates/examples/lifecycle-writing-examples.md +138 -0
- package/template/docs/templates/spec-template.md +16 -3
- package/template/docs/templates/vertical-slice-ticket-template.md +8 -4
- package/template/scripts/lib/profile-skill-sync.mjs +336 -0
- package/template/scripts/sync-profile-skills +43 -0
- package/template/skills-lock.json +37 -19
- package/template.snapshot.json +4 -4
|
@@ -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": "
|
|
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": "
|
|
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": "
|
|
466
|
-
"sourceRevision": "
|
|
467
|
-
"upstreamHash": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
1025
|
-
"sourceRevision": "
|
|
1026
|
-
"upstreamHash": "
|
|
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": "
|
|
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": "
|
|
1103
|
-
"sourceRevision": "
|
|
1104
|
-
"upstreamHash": "
|
|
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": "
|
|
1147
|
+
"effectiveHash": "06e7cdbf6441be90a19ec705388c1e164daefed5ace237c7e6595cf0c0f81e1f",
|
|
1130
1148
|
"targets": [
|
|
1131
1149
|
".agents/skills",
|
|
1132
1150
|
".codex/skills",
|
package/template.snapshot.json
CHANGED
|
@@ -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": "
|
|
7
|
-
"templateCommit": "
|
|
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": "
|
|
16
|
-
"generatedAt": "2026-09-
|
|
15
|
+
"snapshotHash": "7e8bec077d452f2270c479e622009536ae12495f21936b3f36a0298f0085d7aa",
|
|
16
|
+
"generatedAt": "2026-09-10T17:59:33.887Z"
|
|
17
17
|
}
|