@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 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';
@@ -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":"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"}
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.80",
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/memory": "0.2.9",
39
- "@dzhechkov/core": "0.2.14"
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
+ }