craftar 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/dist/cli.js +1 -1
- package/dist/core/fingerprint.js +41 -3
- package/dist/core/fingerprint.js.map +1 -1
- package/dist/core/unify.js +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { UnifyPlanSchema } from "./schema/index.js";
|
|
|
15
15
|
process.stdout.on("error", (e) => { if (e.code === "EPIPE")
|
|
16
16
|
process.exit(0); });
|
|
17
17
|
const program = new Command();
|
|
18
|
-
program.name("craftar").description("Craft, sync and convert AI-coding workspace harnesses.").version("0.2.
|
|
18
|
+
program.name("craftar").description("Craft, sync and convert AI-coding workspace harnesses.").version("0.2.1");
|
|
19
19
|
/* ---------------------------------------------------------------- import */
|
|
20
20
|
program
|
|
21
21
|
.command("import")
|
package/dist/core/fingerprint.js
CHANGED
|
@@ -13,17 +13,55 @@ export function fingerprintOf(meta, files) {
|
|
|
13
13
|
delete m.origin;
|
|
14
14
|
delete m.name;
|
|
15
15
|
delete m.as;
|
|
16
|
-
const parts = [JSON.stringify(
|
|
16
|
+
const parts = [JSON.stringify(sortedDeep(m))];
|
|
17
17
|
for (const k of Object.keys(files).sort())
|
|
18
18
|
parts.push(k, hashNormalized(files[k]));
|
|
19
19
|
return hashNormalized(parts.join("\0"));
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A copy of `v` with object keys sorted at every depth, so `JSON.stringify` of it is canonical.
|
|
23
|
+
* Not `JSON.stringify(v, sortedKeys)`: a replacer array filters keys at every depth, which hashed
|
|
24
|
+
* an MCP `server: { command, args, env }` as `{}` and made two different servers look identical.
|
|
25
|
+
* For metadata with no nested object the output is byte-identical to that form, so those
|
|
26
|
+
* fingerprints — and the unify plans that record them — do not move. (One exception: integer-like
|
|
27
|
+
* keys, which only a hand-added unknown field can introduce, now follow the engine's numeric-first
|
|
28
|
+
* key order instead of the replacer's lexicographic one. The result is still canonical.)
|
|
29
|
+
* A YAML alias can make the parsed metadata cyclic; that is refused rather than overflowing.
|
|
30
|
+
*/
|
|
31
|
+
function sortedDeep(v, ancestors = new Set()) {
|
|
32
|
+
if (v === null || typeof v !== "object")
|
|
33
|
+
return v;
|
|
34
|
+
if (ancestors.has(v))
|
|
35
|
+
throw new Error("ingredient metadata is cyclic (a YAML alias refers back to itself)");
|
|
36
|
+
if (Array.isArray(v)) {
|
|
37
|
+
ancestors.add(v);
|
|
38
|
+
const out = v.map((x) => sortedDeep(x, ancestors));
|
|
39
|
+
ancestors.delete(v);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
const proto = Object.getPrototypeOf(v);
|
|
43
|
+
if (proto !== Object.prototype && proto !== null)
|
|
44
|
+
return v; // Date and friends keep their toJSON
|
|
45
|
+
ancestors.add(v);
|
|
46
|
+
// A null prototype, so a `__proto__` key is an ordinary key instead of setting the prototype.
|
|
47
|
+
const out = Object.create(null);
|
|
48
|
+
for (const k of Object.keys(v).sort())
|
|
49
|
+
out[k] = sortedDeep(v[k], ancestors);
|
|
50
|
+
ancestors.delete(v);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
21
53
|
export async function fingerprintDir(dir) {
|
|
22
|
-
const
|
|
54
|
+
const metaFile = path.join(dir, "ingredient.yaml");
|
|
55
|
+
const meta = YAML.parse(await fs.readFile(metaFile, "utf8"));
|
|
23
56
|
const files = {};
|
|
24
57
|
for (const rel of await listFiles(dir))
|
|
25
58
|
if (rel !== "ingredient.yaml")
|
|
26
59
|
files[rel] = await fs.readFile(path.join(dir, rel));
|
|
27
|
-
|
|
60
|
+
try {
|
|
61
|
+
return fingerprintOf(meta, files);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
throw new Error(`${metaFile}: ${e.message}`);
|
|
65
|
+
}
|
|
28
66
|
}
|
|
29
67
|
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/core/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa,EAAE,KAAsC;IACjF,MAAM,CAAC,GAA4B,EAAE,GAAI,IAAgC,EAAE,CAAC;IAC5E,OAAO,CAAC,CAAC,MAAM,CAAC;IAChB,OAAO,CAAC,CAAC,IAAI,CAAC;IACd,OAAO,CAAC,CAAC,EAAE,CAAC;IACZ,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/core/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/core/unify.js
CHANGED
|
@@ -89,9 +89,9 @@ function comparableMeta(meta) {
|
|
|
89
89
|
* Ruling 28: the top-level `ingredient.yaml` fields on which base and variant differ, sorted.
|
|
90
90
|
* `diffIngredients` leaves `ingredient.yaml` out, so without this a variant that differs only in
|
|
91
91
|
* its metadata (an MCP `server`, an agent's `model` or `tools`) would resolve with zero decisions
|
|
92
|
-
* and be deleted. Compared on the zod-validated `meta` with a real deep equality
|
|
93
|
-
*
|
|
94
|
-
* and
|
|
92
|
+
* and be deleted. Compared on the zod-validated `meta` with a real deep equality, field by field so
|
|
93
|
+
* the differing fields can be named — never through `JSON.stringify(x, keys)`, whose key-array
|
|
94
|
+
* replacer filters at every depth and sees two different nested `server` objects as the same `{}`.
|
|
95
95
|
*/
|
|
96
96
|
export function metaDifferences(base, variant) {
|
|
97
97
|
const a = comparableMeta(base.meta);
|