skill-family-engineering-kit 0.2.0 → 0.2.1
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/CHANGELOG.md +21 -0
- package/CHANGELOG.zh-CN.md +21 -0
- package/NOTICE +10 -0
- package/README.md +157 -55
- package/README.zh-CN.md +207 -0
- package/candidate/index.mjs +4 -0
- package/candidate/profile-bundle.mjs +268 -0
- package/candidate/projection-bundle-cli.mjs +22 -0
- package/docs/404.html +1434 -408
- package/docs/agents/architecture-routing/index.html +1944 -0
- package/docs/agents/capability-catalog.en.json +1561 -0
- package/docs/agents/capability-catalog.json +934 -0
- package/docs/agents/capability-catalog.schema.json +179 -0
- package/docs/agents/capability-catalog.zh-CN.json +1561 -0
- package/docs/agents/index.html +1914 -0
- package/docs/architecture/index.html +1706 -497
- package/docs/assets/javascripts/lunr/tinyseg.js +2 -2
- package/docs/assets/javascripts/lunr/wordcut.js +37 -37
- package/docs/en/agents/architecture-routing/index.html +1944 -0
- package/docs/en/agents/index.html +1925 -0
- package/docs/en/architecture/index.html +2187 -0
- package/docs/en/examples-and-fixtures/index.html +1888 -0
- package/docs/en/help/index.html +2035 -0
- package/docs/en/index.html +1895 -0
- package/docs/en/licensing/index.html +1917 -0
- package/docs/en/migration/index.html +2334 -0
- package/docs/en/quickstart/index.html +1919 -0
- package/docs/en/recipes/adapter-text-closure/index.html +2000 -0
- package/docs/en/recipes/adopt-existing-repository/index.html +1986 -0
- package/docs/en/recipes/deterministic-human-report/index.html +1998 -0
- package/docs/en/recipes/domain-schema-validation/index.html +2007 -0
- package/docs/en/recipes/durable-local-state/index.html +2009 -0
- package/docs/en/recipes/host-profile-integration/index.html +2001 -0
- package/docs/en/recipes/index.html +1876 -0
- package/docs/en/recipes/safe-filesystem-and-atomic-write/index.html +1994 -0
- package/docs/en/reference/api/index.html +1917 -0
- package/docs/en/reference/compatibility/index.html +1990 -0
- package/docs/en/reference/failure-and-side-effect-matrix/index.html +2216 -0
- package/docs/examples-and-fixtures/index.html +1871 -0
- package/docs/git-lifecycle/index.html +1508 -482
- package/docs/help/index.html +1655 -554
- package/docs/index.html +1479 -450
- package/docs/integration/audit/failure-evidence/index.html +1488 -462
- package/docs/integration/audit/independence/index.html +1513 -487
- package/docs/integration/audit/index.html +1514 -488
- package/docs/integration/audit/mutation-taxonomy/index.html +1548 -522
- package/docs/integration/audit/version-compatibility/index.html +1489 -463
- package/docs/licensing/index.html +1917 -0
- package/docs/migration/index.html +1678 -586
- package/docs/public/status/index.html +1542 -516
- package/docs/quickstart/index.html +1589 -539
- package/docs/recipes/adapter-text-closure/index.html +2000 -0
- package/docs/recipes/adopt-existing-repository/index.html +1986 -0
- package/docs/recipes/deterministic-human-report/index.html +1998 -0
- package/docs/recipes/domain-schema-validation/index.html +2007 -0
- package/docs/recipes/durable-local-state/index.html +2009 -0
- package/docs/recipes/host-profile-integration/index.html +2001 -0
- package/docs/recipes/index.html +1876 -0
- package/docs/recipes/safe-filesystem-and-atomic-write/index.html +1994 -0
- package/docs/reference/api/contracts/index.html +2532 -0
- package/docs/reference/api/engineering-kit/index.html +2551 -0
- package/docs/reference/api/harness/index.html +2608 -0
- package/docs/reference/api/index.html +1857 -0
- package/docs/reference/compatibility/index.html +1990 -0
- package/docs/reference/failure-and-side-effect-matrix/index.html +2216 -0
- package/docs/search/search_index.json +1 -1
- package/docs/setup/index.html +1494 -468
- package/docs/sitemap.xml +152 -0
- package/package.json +14 -4
- package/release-notes/0.2.1.yaml +23 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
CONTRACTS_VERSION,
|
|
7
|
+
canonicalJson,
|
|
8
|
+
} from "skill-family-contracts";
|
|
9
|
+
import {
|
|
10
|
+
QUICKSTART_PROFILE_ID,
|
|
11
|
+
QUICKSTART_PROFILE_VERSION,
|
|
12
|
+
} from "skill-family-contracts/candidate/quickstart-profile";
|
|
13
|
+
import { digestBytes } from "skill-family-harness-node";
|
|
14
|
+
|
|
15
|
+
const SOURCE_IDENTITY = "skill-family-foundation-workspace";
|
|
16
|
+
const DEFAULT_TARGET_PREFIX = "foundation/quickstart-profile";
|
|
17
|
+
const RUNTIME_EXTENSIONS = new Set([".js", ".json", ".cjs", ".mjs"]);
|
|
18
|
+
const requireFromKit = createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
function posix(relPath) {
|
|
21
|
+
return relPath.split(path.sep).join("/");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function packageRoot(entryPath, expectedName) {
|
|
25
|
+
let cursor = path.dirname(entryPath);
|
|
26
|
+
while (true) {
|
|
27
|
+
try {
|
|
28
|
+
const document = JSON.parse(await readFile(path.join(cursor, "package.json"), "utf8"));
|
|
29
|
+
if (document.name === expectedName) return { root: cursor, packageJson: document };
|
|
30
|
+
} catch {
|
|
31
|
+
// Keep walking to the package boundary.
|
|
32
|
+
}
|
|
33
|
+
const parent = path.dirname(cursor);
|
|
34
|
+
if (parent === cursor) throw new Error(`cannot resolve package root for ${expectedName}`);
|
|
35
|
+
cursor = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function walkFiles(root, relBase = "", accept = () => true) {
|
|
40
|
+
const files = [];
|
|
41
|
+
const entries = await readdir(path.join(root, relBase), { withFileTypes: true });
|
|
42
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
43
|
+
const rel = relBase === "" ? entry.name : path.join(relBase, entry.name);
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
46
|
+
files.push(...(await walkFiles(root, rel, accept)));
|
|
47
|
+
} else if (entry.isFile() && accept(rel)) {
|
|
48
|
+
files.push(posix(rel));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function runtimeFile(relPath) {
|
|
55
|
+
const base = path.basename(relPath);
|
|
56
|
+
const parts = posix(relPath).split("/");
|
|
57
|
+
if (
|
|
58
|
+
parts.some((part) => ["test", "tests", "spec", "benchmark", "benchmarks"].includes(part)) ||
|
|
59
|
+
base === ".runkit_example.js"
|
|
60
|
+
) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return (
|
|
64
|
+
relPath === "package.json" ||
|
|
65
|
+
RUNTIME_EXTENSIONS.has(path.extname(relPath)) ||
|
|
66
|
+
/^LICEN[CS]E(?:[.-].*)?$/.test(base) ||
|
|
67
|
+
/^NOTICE(?:[.-].*)?$/.test(base)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function addTree(files, sourceRoot, targetRoot, relPaths) {
|
|
72
|
+
for (const rel of relPaths) {
|
|
73
|
+
files.set(`${targetRoot}/${posix(rel)}`, await readFile(path.join(sourceRoot, rel)));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function resolveExternalClosure(contractsEntry) {
|
|
78
|
+
const queue = ["ajv"];
|
|
79
|
+
const packages = new Map();
|
|
80
|
+
let parentRequire = createRequire(pathToFileURL(contractsEntry));
|
|
81
|
+
while (queue.length > 0) {
|
|
82
|
+
const name = queue.shift();
|
|
83
|
+
if (packages.has(name)) continue;
|
|
84
|
+
const entry = parentRequire.resolve(name);
|
|
85
|
+
const resolved = await packageRoot(entry, name);
|
|
86
|
+
packages.set(name, resolved);
|
|
87
|
+
const requireFromPackage = createRequire(pathToFileURL(path.join(resolved.root, "package.json")));
|
|
88
|
+
for (const dependency of Object.keys(resolved.packageJson.dependencies ?? {}).sort()) {
|
|
89
|
+
if (!packages.has(dependency)) queue.push(dependency);
|
|
90
|
+
// Resolve each dependency from the package that declares it. pnpm's
|
|
91
|
+
// isolated layout requires this parent-specific resolver.
|
|
92
|
+
if (!packages.has(dependency)) {
|
|
93
|
+
const depEntry = requireFromPackage.resolve(dependency);
|
|
94
|
+
const depRoot = await packageRoot(depEntry, dependency);
|
|
95
|
+
packages.set(dependency, depRoot);
|
|
96
|
+
for (const nested of Object.keys(depRoot.packageJson.dependencies ?? {}).sort()) {
|
|
97
|
+
if (!packages.has(nested)) queue.push(nested);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
parentRequire = requireFromPackage;
|
|
102
|
+
}
|
|
103
|
+
return packages;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizePrefix(targetPrefix) {
|
|
107
|
+
if (
|
|
108
|
+
typeof targetPrefix !== "string" ||
|
|
109
|
+
!/^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/.test(targetPrefix)
|
|
110
|
+
) {
|
|
111
|
+
throw new TypeError("targetPrefix must be a contained relative POSIX path");
|
|
112
|
+
}
|
|
113
|
+
return targetPrefix;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function sourceClosure({ contractsRoot, harnessRoot, kitRoot }) {
|
|
117
|
+
const contractCandidate = [
|
|
118
|
+
"candidate/quickstart-profile/index.mjs",
|
|
119
|
+
"candidate/quickstart-profile/resource.schema.json",
|
|
120
|
+
"candidate/quickstart-profile/task.schema.json",
|
|
121
|
+
"candidate/quickstart-profile/result.schema.json",
|
|
122
|
+
];
|
|
123
|
+
const sources = [
|
|
124
|
+
...["package.json", ...(await walkFiles(contractsRoot, "src")), ...contractCandidate]
|
|
125
|
+
.map((rel) => [contractsRoot, rel, `packages/skill-family-contracts/${rel}`]),
|
|
126
|
+
...["package.json", ...(await walkFiles(harnessRoot, "src")), ...(await walkFiles(harnessRoot, "candidate"))]
|
|
127
|
+
.map((rel) => [harnessRoot, rel, `packages/skill-family-harness-node/${rel}`]),
|
|
128
|
+
[kitRoot, "candidate/profile-bundle.mjs", "packages/skill-family-engineering-kit/candidate/profile-bundle.mjs"],
|
|
129
|
+
];
|
|
130
|
+
const records = [];
|
|
131
|
+
for (const [root, rel, displayPath] of sources) {
|
|
132
|
+
records.push({ path: displayPath, sha256: digestBytes(await readFile(path.join(root, rel))) });
|
|
133
|
+
}
|
|
134
|
+
records.sort((a, b) => a.path.localeCompare(b.path));
|
|
135
|
+
return { files: records, digest: digestBytes(Buffer.from(canonicalJson(records), "utf8")) };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build the deterministic, self-contained Quickstart candidate bundle.
|
|
140
|
+
* The returned entries are ordinary `skill-family.projection.json` entries;
|
|
141
|
+
* runProjection remains the only writer and authorization boundary.
|
|
142
|
+
*/
|
|
143
|
+
export async function buildQuickstartProfileProjection({
|
|
144
|
+
targetPrefix = DEFAULT_TARGET_PREFIX,
|
|
145
|
+
} = {}) {
|
|
146
|
+
const prefix = normalizePrefix(targetPrefix);
|
|
147
|
+
const contractsEntry = requireFromKit.resolve("skill-family-contracts");
|
|
148
|
+
const harnessEntry = requireFromKit.resolve("skill-family-harness-node");
|
|
149
|
+
const contracts = await packageRoot(contractsEntry, "skill-family-contracts");
|
|
150
|
+
const harness = await packageRoot(harnessEntry, "skill-family-harness-node");
|
|
151
|
+
const kitRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
152
|
+
|
|
153
|
+
const files = new Map();
|
|
154
|
+
await addTree(
|
|
155
|
+
files,
|
|
156
|
+
contracts.root,
|
|
157
|
+
"node_modules/skill-family-contracts",
|
|
158
|
+
["package.json", ...(await walkFiles(contracts.root, "src"))],
|
|
159
|
+
);
|
|
160
|
+
await addTree(
|
|
161
|
+
files,
|
|
162
|
+
contracts.root,
|
|
163
|
+
"node_modules/skill-family-contracts",
|
|
164
|
+
[
|
|
165
|
+
"candidate/quickstart-profile/index.mjs",
|
|
166
|
+
"candidate/quickstart-profile/resource.schema.json",
|
|
167
|
+
"candidate/quickstart-profile/task.schema.json",
|
|
168
|
+
"candidate/quickstart-profile/result.schema.json",
|
|
169
|
+
],
|
|
170
|
+
);
|
|
171
|
+
await addTree(
|
|
172
|
+
files,
|
|
173
|
+
harness.root,
|
|
174
|
+
"node_modules/skill-family-harness-node",
|
|
175
|
+
["package.json", ...(await walkFiles(harness.root, "src"))],
|
|
176
|
+
);
|
|
177
|
+
await addTree(
|
|
178
|
+
files,
|
|
179
|
+
harness.root,
|
|
180
|
+
"node_modules/skill-family-harness-node",
|
|
181
|
+
await walkFiles(harness.root, "candidate"),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const external = await resolveExternalClosure(contractsEntry);
|
|
185
|
+
const dependencyVersions = [];
|
|
186
|
+
for (const [name, resolved] of [...external.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
187
|
+
const relPaths = await walkFiles(resolved.root, "", runtimeFile);
|
|
188
|
+
await addTree(files, resolved.root, `node_modules/${name}`, relPaths);
|
|
189
|
+
dependencyVersions.push({ name, version: resolved.packageJson.version });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const schemaRoot = "node_modules/skill-family-contracts/candidate/quickstart-profile";
|
|
193
|
+
for (const name of ["resource", "task", "result"]) {
|
|
194
|
+
files.set(`schemas/${name}.schema.json`, files.get(`${schemaRoot}/${name}.schema.json`));
|
|
195
|
+
}
|
|
196
|
+
files.set(
|
|
197
|
+
"runner.mjs",
|
|
198
|
+
Buffer.from(
|
|
199
|
+
[
|
|
200
|
+
'export * from "./node_modules/skill-family-harness-node/candidate/quickstart-profile.mjs";',
|
|
201
|
+
'export { validateQuickstartProfileDocument } from "./node_modules/skill-family-contracts/candidate/quickstart-profile/index.mjs";',
|
|
202
|
+
"",
|
|
203
|
+
].join("\n"),
|
|
204
|
+
"utf8",
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
files.set(
|
|
208
|
+
"mechanisms-cli.mjs",
|
|
209
|
+
Buffer.from(
|
|
210
|
+
[
|
|
211
|
+
'import { runMechanismCli } from "./node_modules/skill-family-harness-node/candidate/mechanisms-cli.mjs";',
|
|
212
|
+
"process.exitCode = await runMechanismCli();",
|
|
213
|
+
"",
|
|
214
|
+
].join("\n"),
|
|
215
|
+
"utf8",
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const source = await sourceClosure({
|
|
220
|
+
contractsRoot: contracts.root,
|
|
221
|
+
harnessRoot: harness.root,
|
|
222
|
+
kitRoot,
|
|
223
|
+
});
|
|
224
|
+
const bundleFiles = [...files.entries()]
|
|
225
|
+
.map(([filePath, bytes]) => ({ path: filePath, sha256: digestBytes(bytes) }))
|
|
226
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
227
|
+
const provenance = {
|
|
228
|
+
schemaVersion: 1,
|
|
229
|
+
kind: "skill-family.foundation-projection",
|
|
230
|
+
profile: {
|
|
231
|
+
id: QUICKSTART_PROFILE_ID,
|
|
232
|
+
version: QUICKSTART_PROFILE_VERSION,
|
|
233
|
+
contractsVersion: CONTRACTS_VERSION,
|
|
234
|
+
},
|
|
235
|
+
source: {
|
|
236
|
+
identity: SOURCE_IDENTITY,
|
|
237
|
+
closure: { digest: source.digest },
|
|
238
|
+
},
|
|
239
|
+
runtimeDependencies: dependencyVersions,
|
|
240
|
+
bundle: {
|
|
241
|
+
digestAlgorithm: "sha256",
|
|
242
|
+
files: bundleFiles,
|
|
243
|
+
digest: digestBytes(Buffer.from(canonicalJson(bundleFiles), "utf8")),
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
files.set(
|
|
247
|
+
"foundation-projection.json",
|
|
248
|
+
Buffer.from(`${JSON.stringify(provenance, null, 2)}\n`, "utf8"),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const entries = [...files.entries()]
|
|
252
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
253
|
+
.map(([filePath, bytes]) => ({
|
|
254
|
+
path: `${prefix}/${filePath}`,
|
|
255
|
+
content: { text: bytes.toString("utf8") },
|
|
256
|
+
expect: { state: "absent" },
|
|
257
|
+
}));
|
|
258
|
+
return {
|
|
259
|
+
manifest: {
|
|
260
|
+
schemaVersion: 1,
|
|
261
|
+
kind: "skill-family.projection-manifest",
|
|
262
|
+
entries,
|
|
263
|
+
},
|
|
264
|
+
provenance,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const QUICKSTART_PROFILE_TARGET_PREFIX = DEFAULT_TARGET_PREFIX;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { buildQuickstartProfileProjection } from "./profile-bundle.mjs";
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
let targetPrefix;
|
|
6
|
+
if (args.length > 0) {
|
|
7
|
+
if (args.length !== 2 || args[0] !== "--target-prefix") {
|
|
8
|
+
process.stderr.write("usage: projection-bundle-cli.mjs [--target-prefix <relative-path>]\n");
|
|
9
|
+
process.exitCode = 2;
|
|
10
|
+
} else {
|
|
11
|
+
targetPrefix = args[1];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (process.exitCode !== 2) {
|
|
15
|
+
try {
|
|
16
|
+
const { manifest } = await buildQuickstartProfileProjection({ targetPrefix });
|
|
17
|
+
process.stdout.write(`${JSON.stringify(manifest, null, 2)}\n`);
|
|
18
|
+
} catch (cause) {
|
|
19
|
+
process.stderr.write(`${cause?.message ?? String(cause)}\n`);
|
|
20
|
+
process.exitCode = 2;
|
|
21
|
+
}
|
|
22
|
+
}
|