@dzhechkov/harness-core 0.3.80 → 0.3.82
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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/skill-drift.d.ts +99 -0
- package/dist/skill-drift.d.ts.map +1 -0
- package/dist/skill-drift.js +210 -0
- package/dist/skill-drift.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +2 -0
- package/src/skill-drift.ts +273 -0
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export { createSkill } from './create-skill.js';
|
|
|
16
16
|
export type { CreateSkillOptions, CreateSkillResult } from './create-skill.js';
|
|
17
17
|
export { checkUpstream, checkAllUpstream, discoverSourcePackages, loadSourcesManifest } from './sync-upstream.js';
|
|
18
18
|
export type { SyncUpstreamReport, UpstreamCheckResult, SourcesManifest, SourcePackageInfo } from './sync-upstream.js';
|
|
19
|
+
export { sweepSkillDrift, syncCanonicalSkill } from './skill-drift.js';
|
|
20
|
+
export type { SweepResult, DriftedSkill, SyncResult, SyncCanonicalOptions } from './skill-drift.js';
|
|
19
21
|
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
20
22
|
export { buildRegistry, searchRegistry, filterByCategory, skillPackBaseDirs, discoverSkillPackDirs } from './registry.js';
|
|
21
23
|
export { recommend } from './recommend.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export * from './operations.js';
|
|
|
14
14
|
export * from './workflows.js';
|
|
15
15
|
export { createSkill } from './create-skill.js';
|
|
16
16
|
export { checkUpstream, checkAllUpstream, discoverSourcePackages, loadSourcesManifest } from './sync-upstream.js';
|
|
17
|
+
export { sweepSkillDrift, syncCanonicalSkill } from './skill-drift.js';
|
|
17
18
|
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
18
19
|
export { buildRegistry, searchRegistry, filterByCategory, skillPackBaseDirs, discoverSkillPackDirs } from './registry.js';
|
|
19
20
|
export { recommend } from './recommend.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,mFAAmF;AACnF,MAAM,CAAC,MAAM,oBAAoB,GAC9B,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAyB,CAAC,OAAO,CAAC;AAErF,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAElH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC1H,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,aAAa,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE/W,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAuB1B,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC3H,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEjH,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE3L,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEhF,OAAO,EACL,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,SAAS,EACT,qBAAqB,EACrB,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAK7C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC3J,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EACL,YAAY,EACZ,UAAU,EACV,cAAc,EACd,SAAS,EACT,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,mBAAmB,EACnB,cAAc,EACd,QAAQ,GACT,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intra-monorepo skill-drift guard.
|
|
3
|
+
*
|
|
4
|
+
* The same skill is physically duplicated across many monorepo packages
|
|
5
|
+
* (`packages/@dzhechkov/*/<skill>/` + `.claude/skills/<skill>/`). A fix applied to ONE copy
|
|
6
|
+
* silently leaves the others broken — this is exactly how a CRITICAL `goap-research-ed25519`
|
|
7
|
+
* self-signed-forgery exploit shipped in 10 of 12 copies, and how a `brutal-honesty-review`
|
|
8
|
+
* `set -e` crash reached the PUBLISHED `@dzhechkov/skills-qe`. Both were found only by accident.
|
|
9
|
+
*
|
|
10
|
+
* `dz sync-upstream` only checks against EXTERNAL repos and is structurally blind to this class of
|
|
11
|
+
* drift. This module is the intra-monorepo complement:
|
|
12
|
+
*
|
|
13
|
+
* • `sweepSkillDrift(root)` — detector: which shared skills byte-differ between copies.
|
|
14
|
+
* • `syncCanonicalSkill(root, s)` — healer: overwrite every copy from `skills-meta/<skill>`.
|
|
15
|
+
*
|
|
16
|
+
* Both are PURE functions that return plain data — no printing, no `process.exit`, no throwing on
|
|
17
|
+
* the "canonical missing" / "drift found" business cases. The CLI layer owns exit codes and I/O.
|
|
18
|
+
* Dependency-free: `node:fs` / `node:path` / `node:crypto` only.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
/** One shared skill whose copies byte-differ (`driftFiles > 0`). */
|
|
23
|
+
export interface DriftedSkill {
|
|
24
|
+
/** Skill dir basename (e.g. `goap-research-ed25519`). */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** How many locations hold this skill. */
|
|
27
|
+
readonly copies: number;
|
|
28
|
+
/** # relative paths where the copies disagree (≥1 ⇒ drift). */
|
|
29
|
+
readonly driftFiles: number;
|
|
30
|
+
/** Size of the union of relative file paths across all copies. */
|
|
31
|
+
readonly totalFiles: number;
|
|
32
|
+
/** # (copy × relative-path) pairs where the file is absent from a copy. */
|
|
33
|
+
readonly missingFiles: number;
|
|
34
|
+
/** Absolute paths of every copy — lets a human / `--json` consumer jump to the drifted dirs. */
|
|
35
|
+
readonly locations: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/** Options for {@link sweepSkillDrift}. */
|
|
38
|
+
export interface SweepOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Which copies to compare.
|
|
41
|
+
* - `'packages'` (the CI-gate default): PUBLISHED package copies only (`packages/`). The dogfood
|
|
42
|
+
* `.claude/skills/<skill>` dev copies are excluded — the repo's own `dz sync` test treats them as
|
|
43
|
+
* "legitimately lagging" the published version, so counting them makes the gate red-on-arrival.
|
|
44
|
+
* The dangerous drift (goap, brutal-honesty) was always between PUBLISHED packages.
|
|
45
|
+
* - `'all'`: packages + `.claude/skills` (the raw sweep the audit script does).
|
|
46
|
+
*/
|
|
47
|
+
readonly scope?: 'packages' | 'all';
|
|
48
|
+
/** Skill basenames whose drift is ACCEPTED (documented intentional forks) — reported separately, never counted as gate drift. */
|
|
49
|
+
readonly allowlist?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/** Result of a read-only intra-monorepo drift sweep. */
|
|
52
|
+
export interface SweepResult {
|
|
53
|
+
/** # skills present in ≥2 locations (within scope). */
|
|
54
|
+
readonly duplicated: number;
|
|
55
|
+
/** Skills that byte-differ AND are not allowlisted, sorted by `driftFiles` desc — the gate keys on this. */
|
|
56
|
+
readonly drifted: readonly DriftedSkill[];
|
|
57
|
+
/** Skills that byte-differ but are allowlisted (intentional) — surfaced for transparency, not gated. */
|
|
58
|
+
readonly allowlisted: readonly DriftedSkill[];
|
|
59
|
+
}
|
|
60
|
+
/** Options for {@link syncCanonicalSkill}. */
|
|
61
|
+
export interface SyncCanonicalOptions {
|
|
62
|
+
/** Report drift only, write NOTHING (CI mode). */
|
|
63
|
+
readonly check?: boolean;
|
|
64
|
+
/** Override the canonical source dir (default: `skills-meta/<skill>`). */
|
|
65
|
+
readonly from?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Result of a canonical-wins sync (or a `check:true` dry-run). */
|
|
68
|
+
export interface SyncResult {
|
|
69
|
+
/** Resolved canonical dir (abs). */
|
|
70
|
+
readonly canonical: string;
|
|
71
|
+
/** Whether the canonical source dir exists. `false` ⇒ nothing was done (CLI → non-zero exit). */
|
|
72
|
+
readonly canonicalExists: boolean;
|
|
73
|
+
/** # non-canonical copies found. */
|
|
74
|
+
readonly copies: number;
|
|
75
|
+
/** # copies overwritten (0 when `check:true`). */
|
|
76
|
+
readonly synced: number;
|
|
77
|
+
/** # copies that differ from canonical. */
|
|
78
|
+
readonly drifted: number;
|
|
79
|
+
/** Abs paths of copies written (empty when `check:true` — proves no writes). */
|
|
80
|
+
readonly wrote: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Detect intra-monorepo skill drift: find every skill duplicated across ≥2 locations and report
|
|
84
|
+
* which copies byte-differ. Pure port of `scripts/drift-sweep-skills.mjs`.
|
|
85
|
+
*
|
|
86
|
+
* `result.drifted.length === 0` is the exact condition the CI gate keys on.
|
|
87
|
+
*/
|
|
88
|
+
export declare function sweepSkillDrift(root: string, opts?: SweepOptions): SweepResult;
|
|
89
|
+
/**
|
|
90
|
+
* Heal one skill: treat `from ?? skills-meta/<skill>` as canonical and overwrite every other copy in
|
|
91
|
+
* the monorepo, proving byte-identity. Pure port of `scripts/sync-canonical-skill.mjs`.
|
|
92
|
+
*
|
|
93
|
+
* `check:true` writes NOTHING (`wrote` stays empty) and only reports the drift count.
|
|
94
|
+
* Default overwrites drifting copies; a subsequent {@link sweepSkillDrift} then reports 0 drift.
|
|
95
|
+
* When the canonical dir does not exist, returns `canonicalExists:false` and does nothing (the CLI
|
|
96
|
+
* turns that into a non-zero exit) — this function never throws / never `process.exit`s.
|
|
97
|
+
*/
|
|
98
|
+
export declare function syncCanonicalSkill(root: string, skill: string, opts?: SyncCanonicalOptions): SyncResult;
|
|
99
|
+
//# sourceMappingURL=skill-drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-drift.d.ts","sourceRoot":"","sources":["../src/skill-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gGAAgG;IAChG,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IACpC,iIAAiI;IACjI,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,4GAA4G;IAC5G,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,wGAAwG;IACxG,QAAQ,CAAC,WAAW,EAAE,SAAS,YAAY,EAAE,CAAC;CAC/C;AAED,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,oCAAoC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iGAAiG;IACjG,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AA0DD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,WAAW,CA0DlF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,UAAU,CAkD3G"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intra-monorepo skill-drift guard.
|
|
3
|
+
*
|
|
4
|
+
* The same skill is physically duplicated across many monorepo packages
|
|
5
|
+
* (`packages/@dzhechkov/*/<skill>/` + `.claude/skills/<skill>/`). A fix applied to ONE copy
|
|
6
|
+
* silently leaves the others broken — this is exactly how a CRITICAL `goap-research-ed25519`
|
|
7
|
+
* self-signed-forgery exploit shipped in 10 of 12 copies, and how a `brutal-honesty-review`
|
|
8
|
+
* `set -e` crash reached the PUBLISHED `@dzhechkov/skills-qe`. Both were found only by accident.
|
|
9
|
+
*
|
|
10
|
+
* `dz sync-upstream` only checks against EXTERNAL repos and is structurally blind to this class of
|
|
11
|
+
* drift. This module is the intra-monorepo complement:
|
|
12
|
+
*
|
|
13
|
+
* • `sweepSkillDrift(root)` — detector: which shared skills byte-differ between copies.
|
|
14
|
+
* • `syncCanonicalSkill(root, s)` — healer: overwrite every copy from `skills-meta/<skill>`.
|
|
15
|
+
*
|
|
16
|
+
* Both are PURE functions that return plain data — no printing, no `process.exit`, no throwing on
|
|
17
|
+
* the "canonical missing" / "drift found" business cases. The CLI layer owns exit codes and I/O.
|
|
18
|
+
* Dependency-free: `node:fs` / `node:path` / `node:crypto` only.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
import { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
23
|
+
import { join, relative, dirname, basename } from 'node:path';
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
const SKILL_MANIFEST = 'SKILL.md';
|
|
26
|
+
const IGNORED_ENTRIES = new Set(['node_modules', '__pycache__', '.DS_Store']);
|
|
27
|
+
/** md5 of a file's bytes (identical to both prototype scripts ⇒ identical drift verdicts). */
|
|
28
|
+
function md5(path) {
|
|
29
|
+
return createHash('md5').update(readFileSync(path)).digest('hex');
|
|
30
|
+
}
|
|
31
|
+
/** Recursive file list under `dir`; skips `node_modules` / `__pycache__` / `.DS_Store`. */
|
|
32
|
+
function walk(dir) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const entry of readdirSync(dir)) {
|
|
35
|
+
if (IGNORED_ENTRIES.has(entry))
|
|
36
|
+
continue;
|
|
37
|
+
const p = join(dir, entry);
|
|
38
|
+
let st;
|
|
39
|
+
try {
|
|
40
|
+
st = statSync(p);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (st.isDirectory())
|
|
46
|
+
out.push(...walk(p));
|
|
47
|
+
else
|
|
48
|
+
out.push(p);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Every skill dir (a dir containing `SKILL.md`) under `packages/` + `.claude/skills`, excluding
|
|
54
|
+
* `node_modules` / `__pycache__`. Ported verbatim from `scripts/drift-sweep-skills.mjs`.
|
|
55
|
+
*/
|
|
56
|
+
function findSkillDirs(root, scope = 'all') {
|
|
57
|
+
const dirs = [];
|
|
58
|
+
const roots = scope === 'packages' ? [join(root, 'packages')] : [join(root, 'packages'), join(root, '.claude', 'skills')];
|
|
59
|
+
const stack = roots.filter((p) => existsSync(p));
|
|
60
|
+
while (stack.length) {
|
|
61
|
+
const d = stack.pop();
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = readdirSync(d);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (entries.includes(SKILL_MANIFEST))
|
|
70
|
+
dirs.push(d);
|
|
71
|
+
for (const e of entries) {
|
|
72
|
+
if (IGNORED_ENTRIES.has(e))
|
|
73
|
+
continue;
|
|
74
|
+
const p = join(d, e);
|
|
75
|
+
try {
|
|
76
|
+
if (statSync(p).isDirectory())
|
|
77
|
+
stack.push(p);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* skip unreadable entries */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return dirs;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Detect intra-monorepo skill drift: find every skill duplicated across ≥2 locations and report
|
|
88
|
+
* which copies byte-differ. Pure port of `scripts/drift-sweep-skills.mjs`.
|
|
89
|
+
*
|
|
90
|
+
* `result.drifted.length === 0` is the exact condition the CI gate keys on.
|
|
91
|
+
*/
|
|
92
|
+
export function sweepSkillDrift(root, opts = {}) {
|
|
93
|
+
const scope = opts.scope ?? 'all';
|
|
94
|
+
const allow = new Set(opts.allowlist ?? []);
|
|
95
|
+
// Group skill dirs by basename → Map<name, locations[]>.
|
|
96
|
+
const byName = new Map();
|
|
97
|
+
for (const d of findSkillDirs(root, scope)) {
|
|
98
|
+
const name = basename(d);
|
|
99
|
+
const list = byName.get(name);
|
|
100
|
+
if (list)
|
|
101
|
+
list.push(d);
|
|
102
|
+
else
|
|
103
|
+
byName.set(name, [d]);
|
|
104
|
+
}
|
|
105
|
+
let duplicated = 0;
|
|
106
|
+
const drifted = [];
|
|
107
|
+
const allowlisted = [];
|
|
108
|
+
for (const [name, unsorted] of [...byName.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
109
|
+
if (unsorted.length < 2)
|
|
110
|
+
continue;
|
|
111
|
+
duplicated++;
|
|
112
|
+
const copies = [...unsorted].sort();
|
|
113
|
+
// Union of relative file paths across every copy.
|
|
114
|
+
const relFiles = new Set();
|
|
115
|
+
for (const c of copies)
|
|
116
|
+
for (const f of walk(c))
|
|
117
|
+
relFiles.add(relative(c, f));
|
|
118
|
+
let driftFiles = 0;
|
|
119
|
+
let missingFiles = 0;
|
|
120
|
+
for (const rel of relFiles) {
|
|
121
|
+
const hashes = new Set();
|
|
122
|
+
for (const c of copies) {
|
|
123
|
+
const p = join(c, rel);
|
|
124
|
+
if (existsSync(p))
|
|
125
|
+
hashes.add(md5(p));
|
|
126
|
+
else {
|
|
127
|
+
missingFiles++;
|
|
128
|
+
hashes.add('__MISSING__');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (hashes.size > 1)
|
|
132
|
+
driftFiles++;
|
|
133
|
+
}
|
|
134
|
+
if (driftFiles > 0) {
|
|
135
|
+
const entry = {
|
|
136
|
+
name,
|
|
137
|
+
copies: copies.length,
|
|
138
|
+
driftFiles,
|
|
139
|
+
totalFiles: relFiles.size,
|
|
140
|
+
missingFiles,
|
|
141
|
+
locations: copies,
|
|
142
|
+
};
|
|
143
|
+
(allow.has(name) ? allowlisted : drifted).push(entry);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const byDrift = (a, b) => b.driftFiles - a.driftFiles;
|
|
147
|
+
drifted.sort(byDrift);
|
|
148
|
+
allowlisted.sort(byDrift);
|
|
149
|
+
return { duplicated, drifted, allowlisted };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Heal one skill: treat `from ?? skills-meta/<skill>` as canonical and overwrite every other copy in
|
|
153
|
+
* the monorepo, proving byte-identity. Pure port of `scripts/sync-canonical-skill.mjs`.
|
|
154
|
+
*
|
|
155
|
+
* `check:true` writes NOTHING (`wrote` stays empty) and only reports the drift count.
|
|
156
|
+
* Default overwrites drifting copies; a subsequent {@link sweepSkillDrift} then reports 0 drift.
|
|
157
|
+
* When the canonical dir does not exist, returns `canonicalExists:false` and does nothing (the CLI
|
|
158
|
+
* turns that into a non-zero exit) — this function never throws / never `process.exit`s.
|
|
159
|
+
*/
|
|
160
|
+
export function syncCanonicalSkill(root, skill, opts = {}) {
|
|
161
|
+
const check = opts.check === true;
|
|
162
|
+
const canonical = opts.from ?? join(root, 'packages/@dzhechkov/skills-meta', skill);
|
|
163
|
+
if (!existsSync(canonical)) {
|
|
164
|
+
return { canonical, canonicalExists: false, copies: 0, synced: 0, drifted: 0, wrote: [] };
|
|
165
|
+
}
|
|
166
|
+
const canonFiles = walk(canonical)
|
|
167
|
+
.map((p) => relative(canonical, p))
|
|
168
|
+
.sort();
|
|
169
|
+
// The healer heals EXACTLY what the detector sees (same roots via findSkillDirs) — otherwise a copy
|
|
170
|
+
// could be silently healed but never gated, or vice-versa. Every <skill>/ dir except the canonical.
|
|
171
|
+
const copies = findSkillDirs(root, 'all')
|
|
172
|
+
.filter((d) => basename(d) === skill && relative(canonical, d) !== '')
|
|
173
|
+
.sort();
|
|
174
|
+
let drifted = 0;
|
|
175
|
+
let synced = 0;
|
|
176
|
+
const wrote = [];
|
|
177
|
+
for (const copy of copies) {
|
|
178
|
+
const copyFiles = new Set(walk(copy).map((p) => relative(copy, p)));
|
|
179
|
+
let differs = false;
|
|
180
|
+
// Extra files in the copy not present in canonical ⇒ drift.
|
|
181
|
+
for (const f of copyFiles)
|
|
182
|
+
if (!canonFiles.includes(f))
|
|
183
|
+
differs = true;
|
|
184
|
+
for (const f of canonFiles) {
|
|
185
|
+
const src = join(canonical, f);
|
|
186
|
+
const dst = join(copy, f);
|
|
187
|
+
if (!existsSync(dst) || md5(src) !== md5(dst))
|
|
188
|
+
differs = true;
|
|
189
|
+
}
|
|
190
|
+
if (!differs)
|
|
191
|
+
continue;
|
|
192
|
+
drifted++;
|
|
193
|
+
if (check)
|
|
194
|
+
continue; // report only — write NOTHING
|
|
195
|
+
// Overwrite: remove extra files, then copy every canonical file byte-for-byte.
|
|
196
|
+
for (const f of copyFiles)
|
|
197
|
+
if (!canonFiles.includes(f))
|
|
198
|
+
rmSync(join(copy, f));
|
|
199
|
+
for (const f of canonFiles) {
|
|
200
|
+
const src = join(canonical, f);
|
|
201
|
+
const dst = join(copy, f);
|
|
202
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
203
|
+
writeFileSync(dst, readFileSync(src));
|
|
204
|
+
}
|
|
205
|
+
synced++;
|
|
206
|
+
wrote.push(copy);
|
|
207
|
+
}
|
|
208
|
+
return { canonical, canonicalExists: true, copies: copies.length, synced, drifted, wrote };
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=skill-drift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-drift.js","sourceRoot":"","sources":["../src/skill-drift.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.82",
|
|
4
4
|
"description": "Shared harness logic - skill loading, additive apply, and the init/sync/verify/doctor operations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"@dzhechkov/adapter-opencode": "^0.2.0",
|
|
32
32
|
"yaml": "^2.0.0",
|
|
33
33
|
"@dzhechkov/adapter-agents-md": "0.1.1",
|
|
34
|
-
"@dzhechkov/adapter-cursor": "0.1.1",
|
|
35
34
|
"@dzhechkov/adapter-copilot": "0.1.1",
|
|
36
35
|
"@dzhechkov/adapter-gemini": "0.1.1",
|
|
36
|
+
"@dzhechkov/adapter-cursor": "0.1.1",
|
|
37
37
|
"@dzhechkov/adapter-windsurf": "0.1.1",
|
|
38
|
-
"@dzhechkov/
|
|
39
|
-
"@dzhechkov/
|
|
38
|
+
"@dzhechkov/core": "0.2.14",
|
|
39
|
+
"@dzhechkov/memory": "0.2.9"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
42
42
|
"@ruvector/rvf": {
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ export { createSkill } from './create-skill.js';
|
|
|
21
21
|
export type { CreateSkillOptions, CreateSkillResult } from './create-skill.js';
|
|
22
22
|
export { checkUpstream, checkAllUpstream, discoverSourcePackages, loadSourcesManifest } from './sync-upstream.js';
|
|
23
23
|
export type { SyncUpstreamReport, UpstreamCheckResult, SourcesManifest, SourcePackageInfo } from './sync-upstream.js';
|
|
24
|
+
export { sweepSkillDrift, syncCanonicalSkill } from './skill-drift.js';
|
|
25
|
+
export type { SweepResult, DriftedSkill, SyncResult, SyncCanonicalOptions } from './skill-drift.js';
|
|
24
26
|
export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
|
|
25
27
|
export { buildRegistry, searchRegistry, filterByCategory, skillPackBaseDirs, discoverSkillPackDirs } from './registry.js';
|
|
26
28
|
export { recommend } from './recommend.js';
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intra-monorepo skill-drift guard.
|
|
3
|
+
*
|
|
4
|
+
* The same skill is physically duplicated across many monorepo packages
|
|
5
|
+
* (`packages/@dzhechkov/*/<skill>/` + `.claude/skills/<skill>/`). A fix applied to ONE copy
|
|
6
|
+
* silently leaves the others broken — this is exactly how a CRITICAL `goap-research-ed25519`
|
|
7
|
+
* self-signed-forgery exploit shipped in 10 of 12 copies, and how a `brutal-honesty-review`
|
|
8
|
+
* `set -e` crash reached the PUBLISHED `@dzhechkov/skills-qe`. Both were found only by accident.
|
|
9
|
+
*
|
|
10
|
+
* `dz sync-upstream` only checks against EXTERNAL repos and is structurally blind to this class of
|
|
11
|
+
* drift. This module is the intra-monorepo complement:
|
|
12
|
+
*
|
|
13
|
+
* • `sweepSkillDrift(root)` — detector: which shared skills byte-differ between copies.
|
|
14
|
+
* • `syncCanonicalSkill(root, s)` — healer: overwrite every copy from `skills-meta/<skill>`.
|
|
15
|
+
*
|
|
16
|
+
* Both are PURE functions that return plain data — no printing, no `process.exit`, no throwing on
|
|
17
|
+
* the "canonical missing" / "drift found" business cases. The CLI layer owns exit codes and I/O.
|
|
18
|
+
* Dependency-free: `node:fs` / `node:path` / `node:crypto` only.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
24
|
+
import { join, relative, dirname, basename } from 'node:path';
|
|
25
|
+
import { createHash } from 'node:crypto';
|
|
26
|
+
|
|
27
|
+
/** One shared skill whose copies byte-differ (`driftFiles > 0`). */
|
|
28
|
+
export interface DriftedSkill {
|
|
29
|
+
/** Skill dir basename (e.g. `goap-research-ed25519`). */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/** How many locations hold this skill. */
|
|
32
|
+
readonly copies: number;
|
|
33
|
+
/** # relative paths where the copies disagree (≥1 ⇒ drift). */
|
|
34
|
+
readonly driftFiles: number;
|
|
35
|
+
/** Size of the union of relative file paths across all copies. */
|
|
36
|
+
readonly totalFiles: number;
|
|
37
|
+
/** # (copy × relative-path) pairs where the file is absent from a copy. */
|
|
38
|
+
readonly missingFiles: number;
|
|
39
|
+
/** Absolute paths of every copy — lets a human / `--json` consumer jump to the drifted dirs. */
|
|
40
|
+
readonly locations: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Options for {@link sweepSkillDrift}. */
|
|
44
|
+
export interface SweepOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Which copies to compare.
|
|
47
|
+
* - `'packages'` (the CI-gate default): PUBLISHED package copies only (`packages/`). The dogfood
|
|
48
|
+
* `.claude/skills/<skill>` dev copies are excluded — the repo's own `dz sync` test treats them as
|
|
49
|
+
* "legitimately lagging" the published version, so counting them makes the gate red-on-arrival.
|
|
50
|
+
* The dangerous drift (goap, brutal-honesty) was always between PUBLISHED packages.
|
|
51
|
+
* - `'all'`: packages + `.claude/skills` (the raw sweep the audit script does).
|
|
52
|
+
*/
|
|
53
|
+
readonly scope?: 'packages' | 'all';
|
|
54
|
+
/** Skill basenames whose drift is ACCEPTED (documented intentional forks) — reported separately, never counted as gate drift. */
|
|
55
|
+
readonly allowlist?: readonly string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Result of a read-only intra-monorepo drift sweep. */
|
|
59
|
+
export interface SweepResult {
|
|
60
|
+
/** # skills present in ≥2 locations (within scope). */
|
|
61
|
+
readonly duplicated: number;
|
|
62
|
+
/** Skills that byte-differ AND are not allowlisted, sorted by `driftFiles` desc — the gate keys on this. */
|
|
63
|
+
readonly drifted: readonly DriftedSkill[];
|
|
64
|
+
/** Skills that byte-differ but are allowlisted (intentional) — surfaced for transparency, not gated. */
|
|
65
|
+
readonly allowlisted: readonly DriftedSkill[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Options for {@link syncCanonicalSkill}. */
|
|
69
|
+
export interface SyncCanonicalOptions {
|
|
70
|
+
/** Report drift only, write NOTHING (CI mode). */
|
|
71
|
+
readonly check?: boolean;
|
|
72
|
+
/** Override the canonical source dir (default: `skills-meta/<skill>`). */
|
|
73
|
+
readonly from?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Result of a canonical-wins sync (or a `check:true` dry-run). */
|
|
77
|
+
export interface SyncResult {
|
|
78
|
+
/** Resolved canonical dir (abs). */
|
|
79
|
+
readonly canonical: string;
|
|
80
|
+
/** Whether the canonical source dir exists. `false` ⇒ nothing was done (CLI → non-zero exit). */
|
|
81
|
+
readonly canonicalExists: boolean;
|
|
82
|
+
/** # non-canonical copies found. */
|
|
83
|
+
readonly copies: number;
|
|
84
|
+
/** # copies overwritten (0 when `check:true`). */
|
|
85
|
+
readonly synced: number;
|
|
86
|
+
/** # copies that differ from canonical. */
|
|
87
|
+
readonly drifted: number;
|
|
88
|
+
/** Abs paths of copies written (empty when `check:true` — proves no writes). */
|
|
89
|
+
readonly wrote: readonly string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const SKILL_MANIFEST = 'SKILL.md';
|
|
93
|
+
const IGNORED_ENTRIES = new Set(['node_modules', '__pycache__', '.DS_Store']);
|
|
94
|
+
|
|
95
|
+
/** md5 of a file's bytes (identical to both prototype scripts ⇒ identical drift verdicts). */
|
|
96
|
+
function md5(path: string): string {
|
|
97
|
+
return createHash('md5').update(readFileSync(path)).digest('hex');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Recursive file list under `dir`; skips `node_modules` / `__pycache__` / `.DS_Store`. */
|
|
101
|
+
function walk(dir: string): string[] {
|
|
102
|
+
const out: string[] = [];
|
|
103
|
+
for (const entry of readdirSync(dir)) {
|
|
104
|
+
if (IGNORED_ENTRIES.has(entry)) continue;
|
|
105
|
+
const p = join(dir, entry);
|
|
106
|
+
let st;
|
|
107
|
+
try {
|
|
108
|
+
st = statSync(p);
|
|
109
|
+
} catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (st.isDirectory()) out.push(...walk(p));
|
|
113
|
+
else out.push(p);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Every skill dir (a dir containing `SKILL.md`) under `packages/` + `.claude/skills`, excluding
|
|
120
|
+
* `node_modules` / `__pycache__`. Ported verbatim from `scripts/drift-sweep-skills.mjs`.
|
|
121
|
+
*/
|
|
122
|
+
function findSkillDirs(root: string, scope: 'packages' | 'all' = 'all'): string[] {
|
|
123
|
+
const dirs: string[] = [];
|
|
124
|
+
const roots = scope === 'packages' ? [join(root, 'packages')] : [join(root, 'packages'), join(root, '.claude', 'skills')];
|
|
125
|
+
const stack = roots.filter((p) => existsSync(p));
|
|
126
|
+
while (stack.length) {
|
|
127
|
+
const d = stack.pop() as string;
|
|
128
|
+
let entries: string[];
|
|
129
|
+
try {
|
|
130
|
+
entries = readdirSync(d);
|
|
131
|
+
} catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (entries.includes(SKILL_MANIFEST)) dirs.push(d);
|
|
135
|
+
for (const e of entries) {
|
|
136
|
+
if (IGNORED_ENTRIES.has(e)) continue;
|
|
137
|
+
const p = join(d, e);
|
|
138
|
+
try {
|
|
139
|
+
if (statSync(p).isDirectory()) stack.push(p);
|
|
140
|
+
} catch {
|
|
141
|
+
/* skip unreadable entries */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return dirs;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Detect intra-monorepo skill drift: find every skill duplicated across ≥2 locations and report
|
|
150
|
+
* which copies byte-differ. Pure port of `scripts/drift-sweep-skills.mjs`.
|
|
151
|
+
*
|
|
152
|
+
* `result.drifted.length === 0` is the exact condition the CI gate keys on.
|
|
153
|
+
*/
|
|
154
|
+
export function sweepSkillDrift(root: string, opts: SweepOptions = {}): SweepResult {
|
|
155
|
+
const scope = opts.scope ?? 'all';
|
|
156
|
+
const allow = new Set(opts.allowlist ?? []);
|
|
157
|
+
|
|
158
|
+
// Group skill dirs by basename → Map<name, locations[]>.
|
|
159
|
+
const byName = new Map<string, string[]>();
|
|
160
|
+
for (const d of findSkillDirs(root, scope)) {
|
|
161
|
+
const name = basename(d);
|
|
162
|
+
const list = byName.get(name);
|
|
163
|
+
if (list) list.push(d);
|
|
164
|
+
else byName.set(name, [d]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let duplicated = 0;
|
|
168
|
+
const drifted: DriftedSkill[] = [];
|
|
169
|
+
const allowlisted: DriftedSkill[] = [];
|
|
170
|
+
|
|
171
|
+
for (const [name, unsorted] of [...byName.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
172
|
+
if (unsorted.length < 2) continue;
|
|
173
|
+
duplicated++;
|
|
174
|
+
const copies = [...unsorted].sort();
|
|
175
|
+
|
|
176
|
+
// Union of relative file paths across every copy.
|
|
177
|
+
const relFiles = new Set<string>();
|
|
178
|
+
for (const c of copies) for (const f of walk(c)) relFiles.add(relative(c, f));
|
|
179
|
+
|
|
180
|
+
let driftFiles = 0;
|
|
181
|
+
let missingFiles = 0;
|
|
182
|
+
for (const rel of relFiles) {
|
|
183
|
+
const hashes = new Set<string>();
|
|
184
|
+
for (const c of copies) {
|
|
185
|
+
const p = join(c, rel);
|
|
186
|
+
if (existsSync(p)) hashes.add(md5(p));
|
|
187
|
+
else {
|
|
188
|
+
missingFiles++;
|
|
189
|
+
hashes.add('__MISSING__');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (hashes.size > 1) driftFiles++;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (driftFiles > 0) {
|
|
196
|
+
const entry: DriftedSkill = {
|
|
197
|
+
name,
|
|
198
|
+
copies: copies.length,
|
|
199
|
+
driftFiles,
|
|
200
|
+
totalFiles: relFiles.size,
|
|
201
|
+
missingFiles,
|
|
202
|
+
locations: copies,
|
|
203
|
+
};
|
|
204
|
+
(allow.has(name) ? allowlisted : drifted).push(entry);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const byDrift = (a: DriftedSkill, b: DriftedSkill): number => b.driftFiles - a.driftFiles;
|
|
209
|
+
drifted.sort(byDrift);
|
|
210
|
+
allowlisted.sort(byDrift);
|
|
211
|
+
return { duplicated, drifted, allowlisted };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Heal one skill: treat `from ?? skills-meta/<skill>` as canonical and overwrite every other copy in
|
|
216
|
+
* the monorepo, proving byte-identity. Pure port of `scripts/sync-canonical-skill.mjs`.
|
|
217
|
+
*
|
|
218
|
+
* `check:true` writes NOTHING (`wrote` stays empty) and only reports the drift count.
|
|
219
|
+
* Default overwrites drifting copies; a subsequent {@link sweepSkillDrift} then reports 0 drift.
|
|
220
|
+
* When the canonical dir does not exist, returns `canonicalExists:false` and does nothing (the CLI
|
|
221
|
+
* turns that into a non-zero exit) — this function never throws / never `process.exit`s.
|
|
222
|
+
*/
|
|
223
|
+
export function syncCanonicalSkill(root: string, skill: string, opts: SyncCanonicalOptions = {}): SyncResult {
|
|
224
|
+
const check = opts.check === true;
|
|
225
|
+
const canonical = opts.from ?? join(root, 'packages/@dzhechkov/skills-meta', skill);
|
|
226
|
+
|
|
227
|
+
if (!existsSync(canonical)) {
|
|
228
|
+
return { canonical, canonicalExists: false, copies: 0, synced: 0, drifted: 0, wrote: [] };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const canonFiles = walk(canonical)
|
|
232
|
+
.map((p) => relative(canonical, p))
|
|
233
|
+
.sort();
|
|
234
|
+
|
|
235
|
+
// The healer heals EXACTLY what the detector sees (same roots via findSkillDirs) — otherwise a copy
|
|
236
|
+
// could be silently healed but never gated, or vice-versa. Every <skill>/ dir except the canonical.
|
|
237
|
+
const copies = findSkillDirs(root, 'all')
|
|
238
|
+
.filter((d) => basename(d) === skill && relative(canonical, d) !== '')
|
|
239
|
+
.sort();
|
|
240
|
+
|
|
241
|
+
let drifted = 0;
|
|
242
|
+
let synced = 0;
|
|
243
|
+
const wrote: string[] = [];
|
|
244
|
+
|
|
245
|
+
for (const copy of copies) {
|
|
246
|
+
const copyFiles = new Set(walk(copy).map((p) => relative(copy, p)));
|
|
247
|
+
let differs = false;
|
|
248
|
+
// Extra files in the copy not present in canonical ⇒ drift.
|
|
249
|
+
for (const f of copyFiles) if (!canonFiles.includes(f)) differs = true;
|
|
250
|
+
for (const f of canonFiles) {
|
|
251
|
+
const src = join(canonical, f);
|
|
252
|
+
const dst = join(copy, f);
|
|
253
|
+
if (!existsSync(dst) || md5(src) !== md5(dst)) differs = true;
|
|
254
|
+
}
|
|
255
|
+
if (!differs) continue;
|
|
256
|
+
drifted++;
|
|
257
|
+
|
|
258
|
+
if (check) continue; // report only — write NOTHING
|
|
259
|
+
|
|
260
|
+
// Overwrite: remove extra files, then copy every canonical file byte-for-byte.
|
|
261
|
+
for (const f of copyFiles) if (!canonFiles.includes(f)) rmSync(join(copy, f));
|
|
262
|
+
for (const f of canonFiles) {
|
|
263
|
+
const src = join(canonical, f);
|
|
264
|
+
const dst = join(copy, f);
|
|
265
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
266
|
+
writeFileSync(dst, readFileSync(src));
|
|
267
|
+
}
|
|
268
|
+
synced++;
|
|
269
|
+
wrote.push(copy);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { canonical, canonicalExists: true, copies: copies.length, synced, drifted, wrote };
|
|
273
|
+
}
|