@dzhechkov/harness-core 0.3.22 → 0.3.24

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/plugin.js CHANGED
@@ -12,11 +12,20 @@ import { join } from 'node:path';
12
12
  export function generatePlugin(projectRoot, registry, opts = {}) {
13
13
  const pluginDir = join(projectRoot, '.claude-plugin');
14
14
  mkdirSync(pluginDir, { recursive: true });
15
- // Build skill pack info from registry
15
+ // Build skill pack info from registry. Track the skill ids per pack so we can
16
+ // emit explicit `skills` path arrays — Claude Code only discovers a plugin's
17
+ // skills when they live under a `skills/` dir OR are listed by path in the
18
+ // manifest. Our pack layout keeps SKILL.md dirs at the package root
19
+ // (`skills-<pack>/<id>/SKILL.md`), so without explicit paths every installed
20
+ // plugin reported "Skills (0)".
16
21
  const packMap = new Map();
22
+ const packSkillIds = new Map();
17
23
  for (const entry of registry.entries) {
18
24
  const count = packMap.get(entry.pack) ?? 0;
19
25
  packMap.set(entry.pack, count + 1);
26
+ const ids = packSkillIds.get(entry.pack) ?? [];
27
+ ids.push(entry.id);
28
+ packSkillIds.set(entry.pack, ids);
20
29
  }
21
30
  const packDescriptions = {
22
31
  'skills-devops': 'Infrastructure, CI/CD, security, testing, APIs, databases',
@@ -30,6 +39,12 @@ export function generatePlugin(projectRoot, registry, opts = {}) {
30
39
  skills: count,
31
40
  description: packDescriptions[name] ?? `${count} skills`,
32
41
  }));
42
+ // Explicit skill paths for the full-suite plugin (source `./`): every skill
43
+ // dir across every pack, relative to the repo root. This is what makes
44
+ // `claude plugin details` report the real skill count instead of 0.
45
+ const allSkillPaths = [...packSkillIds.entries()]
46
+ .flatMap(([pack, ids]) => ids.map((id) => `packages/@dzhechkov/${pack}/${id}`))
47
+ .sort();
33
48
  const pluginJson = {
34
49
  name: 'dz-harness-hub',
35
50
  displayName: 'DZ Harness Hub',
@@ -40,6 +55,7 @@ export function generatePlugin(projectRoot, registry, opts = {}) {
40
55
  keywords: ['agent-skills', 'agentskills.io', 'cross-platform', 'claude-plugin', ...registry.categories],
41
56
  skillPacks,
42
57
  totalSkills: registry.totalSkills,
58
+ skills: allSkillPaths,
43
59
  };
44
60
  const marketplaceJson = {
45
61
  name: 'dz-harness-hub',
@@ -61,6 +77,9 @@ export function generatePlugin(projectRoot, registry, opts = {}) {
61
77
  source: `packages/@dzhechkov/skills-${pack.name}`,
62
78
  description: `${pack.skills} ${pack.name} skills — ${pack.description}`,
63
79
  keywords: [pack.name],
80
+ // Skill dirs sit at the pack source root, so they are listed relative
81
+ // to `source` here (`./<id>`) — required for Claude Code to discover them.
82
+ skills: (packSkillIds.get(`skills-${pack.name}`) ?? []).slice().sort().map((id) => `./${id}`),
64
83
  })),
65
84
  ],
66
85
  };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `dz mcp-scan --reconcile` — static capability reconciliation (Phase 3).
3
+ *
4
+ * Joins two things `dz` can read deterministically:
5
+ * - the project's GRANT surface (`scanMcp` of `.claude/settings*.json` + `.mcp.json`), and
6
+ * - the aggregate DECLARED capabilities of the installed skills (`.claude/skills/<id>/SKILL.md`),
7
+ * then REPORTS the gaps (under-grant / over-grant) and, optionally, EMITS a
8
+ * least-privilege advisory policy artifact for a host to consume.
9
+ *
10
+ * HONESTY: `dz` is build-time / static. It does NOT run agents and CANNOT block,
11
+ * time out, or rate-limit a tool call. The HOST (Claude Code enforcing
12
+ * settings.json allow/deny; or an MCP server consuming a policy.json) is the only
13
+ * thing that enforces at call time. Verbs here are REPORT / RECONCILE / EMIT /
14
+ * CANDIDATE — never BLOCK / DENIED / ENFORCED.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ import type { McpScanReport } from './mcp-scan.js';
19
+ /** The honesty banner — repeated in --help, the report header, and the artifact $comment. */
20
+ export declare const RECONCILE_BANNER: string;
21
+ /** A reconcilable capability axis (the axes BOTH the grant surface and the manifest expose). */
22
+ export type ReconcileAxis = 'shell' | 'network' | 'file-write';
23
+ /** Per-axis reconciliation state. */
24
+ export interface AxisState {
25
+ readonly axis: ReconcileAxis;
26
+ /** Project permits this capability (from scanMcp). */
27
+ readonly grant: boolean;
28
+ /** At least one installed skill declares it needs this (declared === true). */
29
+ readonly need: boolean;
30
+ /** Skills that declared need (true) — for attribution. */
31
+ readonly needSkills: readonly string[];
32
+ /** Installed skills that are SILENT on this axis (declared === undefined). */
33
+ readonly silentCount: number;
34
+ }
35
+ /** A single reconciliation finding. */
36
+ export interface ReconcileFinding {
37
+ readonly id: string;
38
+ readonly kind: 'under-grant' | 'over-grant';
39
+ readonly severity: 'medium' | 'low' | 'info';
40
+ readonly axis: ReconcileAxis;
41
+ readonly detail: string;
42
+ readonly skills: readonly string[];
43
+ }
44
+ /** Aggregated declared limits across installed skills (tightest values; inert). */
45
+ export interface LimitsRollup {
46
+ readonly declaredBy: number;
47
+ readonly toolTimeoutMs?: number;
48
+ readonly maxToolCallsPerTurn?: number;
49
+ readonly requireApprovalForDangerous?: boolean;
50
+ }
51
+ /** The least-privilege advisory policy artifact (only written with --emit-policy). */
52
+ export interface PolicyArtifact {
53
+ readonly $comment: string;
54
+ readonly version: 1;
55
+ readonly defaultDeny: true;
56
+ /** axis → true ONLY where an installed skill declared need; absent otherwise. */
57
+ readonly allow: Partial<Record<ReconcileAxis, true>>;
58
+ readonly limits?: LimitsRollup;
59
+ readonly derivedFrom: {
60
+ readonly grants: Record<ReconcileAxis, boolean>;
61
+ readonly declaredNeed: Record<ReconcileAxis, boolean>;
62
+ readonly skillsByAxis: Partial<Record<ReconcileAxis, readonly string[]>>;
63
+ };
64
+ }
65
+ /** Result of {@link reconcileCapabilities}. */
66
+ export interface ReconcileReport {
67
+ readonly skillsDir: string;
68
+ readonly installedCount: number;
69
+ readonly axes: readonly AxisState[];
70
+ readonly findings: readonly ReconcileFinding[];
71
+ readonly limits: LimitsRollup | null;
72
+ readonly policy: PolicyArtifact;
73
+ }
74
+ /**
75
+ * Statically reconcile a project's GRANT surface against the DECLARED needs of
76
+ * its installed skills. Pure: same inputs → same report. No execution, no writes.
77
+ */
78
+ export declare function reconcileCapabilities(report: McpScanReport, skillsDir: string): ReconcileReport;
79
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,6FAA6F;AAC7F,eAAO,MAAM,gBAAgB,QAGkD,CAAC;AAEhF,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC;AAG/D,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CAChD;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAChD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACtD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;KAC1E,CAAC;CACH;AAED,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAqCD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CA6E/F"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * `dz mcp-scan --reconcile` — static capability reconciliation (Phase 3).
3
+ *
4
+ * Joins two things `dz` can read deterministically:
5
+ * - the project's GRANT surface (`scanMcp` of `.claude/settings*.json` + `.mcp.json`), and
6
+ * - the aggregate DECLARED capabilities of the installed skills (`.claude/skills/<id>/SKILL.md`),
7
+ * then REPORTS the gaps (under-grant / over-grant) and, optionally, EMITS a
8
+ * least-privilege advisory policy artifact for a host to consume.
9
+ *
10
+ * HONESTY: `dz` is build-time / static. It does NOT run agents and CANNOT block,
11
+ * time out, or rate-limit a tool call. The HOST (Claude Code enforcing
12
+ * settings.json allow/deny; or an MCP server consuming a policy.json) is the only
13
+ * thing that enforces at call time. Verbs here are REPORT / RECONCILE / EMIT /
14
+ * CANDIDATE — never BLOCK / DENIED / ENFORCED.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { parseDeclaredCapabilities, parseDeclaredLimits } from './capability-vocab.js';
21
+ /** The honesty banner — repeated in --help, the report header, and the artifact $comment. */
22
+ export const RECONCILE_BANNER = 'dz is build-time/static: it REPORTS the grant-vs-declaration gap and may EMIT an advisory policy, ' +
23
+ 'but does NOT block, time out, or rate-limit anything. The HOST (Claude Code settings.json; or an MCP ' +
24
+ 'server consuming policy.json) is the only thing that enforces at call time.';
25
+ const AXES = ['shell', 'network', 'file-write'];
26
+ function readInstalled(skillsDir) {
27
+ if (!existsSync(skillsDir))
28
+ return [];
29
+ let entries;
30
+ try {
31
+ entries = readdirSync(skillsDir, { withFileTypes: true });
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ const out = [];
37
+ for (const e of entries) {
38
+ if (!e.isDirectory())
39
+ continue;
40
+ const md = join(skillsDir, e.name, 'SKILL.md');
41
+ if (!existsSync(md))
42
+ continue;
43
+ let content;
44
+ try {
45
+ content = readFileSync(md, 'utf-8');
46
+ }
47
+ catch {
48
+ continue;
49
+ }
50
+ out.push({ id: e.name, caps: parseDeclaredCapabilities(content), limits: parseDeclaredLimits(content) });
51
+ }
52
+ return out.sort((a, b) => a.id.localeCompare(b.id));
53
+ }
54
+ function grantFor(report, axis) {
55
+ const c = report.capabilities;
56
+ return axis === 'shell' ? c.shell : axis === 'network' ? c.network : c.fileWrite;
57
+ }
58
+ /**
59
+ * Statically reconcile a project's GRANT surface against the DECLARED needs of
60
+ * its installed skills. Pure: same inputs → same report. No execution, no writes.
61
+ */
62
+ export function reconcileCapabilities(report, skillsDir) {
63
+ const installed = readInstalled(skillsDir);
64
+ const axes = [];
65
+ const findings = [];
66
+ const allow = {};
67
+ const grants = {};
68
+ const declaredNeed = {};
69
+ const skillsByAxis = {};
70
+ for (const axis of AXES) {
71
+ const grant = grantFor(report, axis);
72
+ const needSkills = installed.filter((s) => s.caps[axis] === true).map((s) => s.id);
73
+ const need = needSkills.length > 0;
74
+ const silentCount = installed.filter((s) => s.caps[axis] === undefined).length;
75
+ axes.push({ axis, grant, need, needSkills, silentCount });
76
+ grants[axis] = grant;
77
+ declaredNeed[axis] = need;
78
+ if (need) {
79
+ skillsByAxis[axis] = needSkills;
80
+ allow[axis] = true;
81
+ }
82
+ if (need && !grant) {
83
+ findings.push({
84
+ id: `MS-UNDERGRANT-${axis.toUpperCase()}`,
85
+ kind: 'under-grant',
86
+ severity: 'medium',
87
+ axis,
88
+ detail: `${needSkills.length} installed skill(s) declare needing ${axis}, but the grant surface does not permit it — the host will starve them at runtime. Add the grant or remove the skill(s).`,
89
+ skills: needSkills,
90
+ });
91
+ }
92
+ else if (grant && !need) {
93
+ // over-grant is ALWAYS advisory; downgrade to info when any skill is silent
94
+ // (it may genuinely need the grant but didn't declare), or when there are
95
+ // no installed skills at all.
96
+ const downgrade = silentCount > 0 || installed.length === 0;
97
+ findings.push({
98
+ id: `MS-OVERGRANT-${axis.toUpperCase()}`,
99
+ kind: 'over-grant',
100
+ severity: downgrade ? 'info' : 'low',
101
+ axis,
102
+ detail: `The project permits ${axis} but no installed skill declares needing it${silentCount > 0 ? ` (${silentCount} skill(s) silent on ${axis})` : ''} — least-privilege CANDIDATE to revoke; may be for the operator or a non-skill MCP server.`,
103
+ skills: [],
104
+ });
105
+ }
106
+ }
107
+ // limits roll-up (tightest values) — INERT, never a gate
108
+ const withLimits = installed.filter((s) => Object.keys(s.limits).length > 0);
109
+ let limits = null;
110
+ if (withLimits.length > 0) {
111
+ const tts = withLimits.map((s) => s.limits.toolTimeoutMs).filter((n) => typeof n === 'number');
112
+ const mcs = withLimits.map((s) => s.limits.maxToolCallsPerTurn).filter((n) => typeof n === 'number');
113
+ const ras = withLimits.some((s) => s.limits.requireApprovalForDangerous === true);
114
+ limits = {
115
+ declaredBy: withLimits.length,
116
+ ...(tts.length > 0 ? { toolTimeoutMs: Math.min(...tts) } : {}),
117
+ ...(mcs.length > 0 ? { maxToolCallsPerTurn: Math.min(...mcs) } : {}),
118
+ ...(ras ? { requireApprovalForDangerous: true } : {}),
119
+ };
120
+ }
121
+ const policy = {
122
+ $comment: `ADVISORY. ${RECONCILE_BANNER} A host MUST consume this; dz does not.`,
123
+ version: 1,
124
+ defaultDeny: true,
125
+ allow,
126
+ ...(limits ? { limits } : {}),
127
+ derivedFrom: { grants, declaredNeed, skillsByAxis },
128
+ };
129
+ // stable ordering: under-grant (medium) first, then over-grant
130
+ const order = { medium: 0, low: 1, info: 2 };
131
+ findings.sort((a, b) => order[a.severity] - order[b.severity] || a.axis.localeCompare(b.axis));
132
+ return { skillsDir, installedCount: installed.length, axes, findings, limits, policy };
133
+ }
134
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAElE,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,2BAA2B;AAC3B,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAaD,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAM5D;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAM5D;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAqBjF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,CAiB9E"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAElE,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,2BAA2B;AAC3B,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAsCD,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAM5D;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAM5D;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAqBjF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,CAoB9E"}
package/dist/skills.js CHANGED
@@ -7,6 +7,30 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
7
7
  import { join, relative } from 'node:path';
8
8
  import { parse as parseYaml } from 'yaml';
9
9
  import { ClaudeSkillFrontmatterSchema, parseSkillDocument } from '@dzhechkov/core';
10
+ /**
11
+ * Read one asset file, choosing the encoding that round-trips losslessly.
12
+ *
13
+ * Text files are read as `utf-8`. Binary files (detected by a NUL byte or a
14
+ * byte sequence that does not survive a `utf-8` decode/encode round-trip) are
15
+ * read as `base64` so {@link applyEmitResult} writes them back byte-for-byte.
16
+ * Without this, binary assets (PNGs, fonts, archives) were silently mangled by
17
+ * a hardcoded `utf-8` read while `verify` still reported `ok`.
18
+ */
19
+ function readAssetContent(path) {
20
+ const buf = readFileSync(path);
21
+ // NUL byte is a strong, cheap signal of binary content.
22
+ if (buf.includes(0)) {
23
+ return { encoding: 'base64', content: buf.toString('base64') };
24
+ }
25
+ // Round-trip through utf-8: if decoding then re-encoding changes the bytes,
26
+ // the file is not valid utf-8 (e.g. latin-1 / arbitrary binary) and must be
27
+ // preserved as base64.
28
+ const decoded = buf.toString('utf-8');
29
+ if (!Buffer.from(decoded, 'utf-8').equals(buf)) {
30
+ return { encoding: 'base64', content: buf.toString('base64') };
31
+ }
32
+ return { encoding: 'utf-8', content: decoded };
33
+ }
10
34
  /** Recursively list every file under `dir`. */
11
35
  function walkFiles(dir) {
12
36
  const out = [];
@@ -76,11 +100,14 @@ export function loadSkillFromDir(skillsDir, id) {
76
100
  const frontmatter = ClaudeSkillFrontmatterSchema.parse(parseYaml(document.frontmatterYaml));
77
101
  const assets = walkFiles(skillDir)
78
102
  .filter((path) => path !== skillMdPath)
79
- .map((path) => ({
80
- path: relative(skillDir, path).split('\\').join('/'),
81
- encoding: 'utf-8',
82
- content: readFileSync(path, 'utf-8'),
83
- }))
103
+ .map((path) => {
104
+ const { encoding, content } = readAssetContent(path);
105
+ return {
106
+ path: relative(skillDir, path).split('\\').join('/'),
107
+ encoding,
108
+ content,
109
+ };
110
+ })
84
111
  .sort((a, b) => a.path.localeCompare(b.path));
85
112
  return { id, frontmatter, document, assets };
86
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.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.22",
3
+ "version": "0.3.24",
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",
@@ -23,13 +23,6 @@
23
23
  "src",
24
24
  "README.md"
25
25
  ],
26
- "scripts": {
27
- "build": "tsc -p tsconfig.json",
28
- "test": "vitest run",
29
- "test:watch": "vitest",
30
- "typecheck": "tsc -p tsconfig.json --noEmit",
31
- "lint": "tsc -p tsconfig.json --noEmit"
32
- },
33
26
  "dependencies": {
34
27
  "@dzhechkov/adapter-claude": "^0.2.0",
35
28
  "@dzhechkov/adapter-codex": "^0.2.0",
@@ -55,5 +48,12 @@
55
48
  "url": "https://github.com/djd1m/dz-harness-hub.git",
56
49
  "directory": "packages/@dzhechkov/harness-core"
57
50
  },
58
- "homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-core#readme"
59
- }
51
+ "homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-core#readme",
52
+ "scripts": {
53
+ "build": "tsc -p tsconfig.json",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "typecheck": "tsc -p tsconfig.json --noEmit",
57
+ "lint": "tsc -p tsconfig.json --noEmit"
58
+ }
59
+ }
package/src/benchmark.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  import { existsSync, readFileSync, statSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
13
 
14
+ import { detectScriptCapabilities, parseDeclaredCapabilities } from './capability-vocab.js';
14
15
  import { estimateSkillCost, type CostEstimate } from './cost-scoring.js';
15
16
 
16
17
  /** A single benchmark check result. */
@@ -19,6 +20,12 @@ export interface BenchmarkCheck {
19
20
  readonly name: string;
20
21
  readonly passed: boolean;
21
22
  readonly detail?: string | undefined;
23
+ /**
24
+ * When true this check is informational ONLY — it is excluded from the
25
+ * pass-rate / grade math (so it can ship without regressing existing grades).
26
+ * Surfaced as a warning in output. Used by S16 (capability-declaration nudge).
27
+ */
28
+ readonly advisory?: boolean;
22
29
  }
23
30
 
24
31
  /** Score for a single skill benchmark. */
@@ -160,11 +167,52 @@ export function benchmarkSkill(skillDir: string, skillId: string): BenchmarkScor
160
167
  || /\*\*\s*PROHIBITED/i.test(content);
161
168
  checks.push({ id: 'S14', name: 'anti-patterns or self-check', passed: hasAntiPatterns });
162
169
 
163
- const passed = checks.filter((c) => c.passed).length;
164
- const passRate = Math.round((passed / checks.length) * 100);
170
+ // S15: capability declaration matches usage (advisory, contradiction-only).
171
+ // Phase 1 auto-detects network + shell from scripts/ only (never SKILL.md prose).
172
+ // PASS unless a declared `capabilities.<cap>: false` is contradicted by detected
173
+ // usage. Absent block, or under-declaration (uses it, declares nothing), PASS —
174
+ // so the ~all skills that declare nothing today stay green.
175
+ const declaredCaps = parseDeclaredCapabilities(content);
176
+ const detectedCaps = detectScriptCapabilities(skillDir);
177
+ const contradictions: string[] = [];
178
+ if (detectedCaps.network && declaredCaps.network === false) contradictions.push('network');
179
+ if (detectedCaps.shell && declaredCaps.shell === false) contradictions.push('shell');
180
+ checks.push({
181
+ id: 'S15',
182
+ name: 'capability declaration matches usage',
183
+ passed: contradictions.length === 0,
184
+ detail:
185
+ contradictions.length > 0
186
+ ? `scripts use ${contradictions.join(' + ')} but capabilities declare it false`
187
+ : undefined,
188
+ });
189
+
190
+ // S16: side-effecting skills should declare their capabilities (ADVISORY — not
191
+ // graded). Warns when scripts use network/shell but the capability is not
192
+ // declared at all (neither true nor false). The adoption nudge for the manifest;
193
+ // excluded from the grade so it never regresses an existing skill.
194
+ const undeclaredUsed: string[] = [];
195
+ if (detectedCaps.network && declaredCaps.network === undefined) undeclaredUsed.push('network');
196
+ if (detectedCaps.shell && declaredCaps.shell === undefined) undeclaredUsed.push('shell');
197
+ checks.push({
198
+ id: 'S16',
199
+ name: 'side-effects declared (advisory)',
200
+ passed: undeclaredUsed.length === 0,
201
+ advisory: true,
202
+ detail:
203
+ undeclaredUsed.length > 0
204
+ ? `scripts use ${undeclaredUsed.join(' + ')} — add a capabilities: block declaring it`
205
+ : undefined,
206
+ });
207
+
208
+ // grade math is over GRADED checks only — advisory checks (S16) are excluded
209
+ // so they ship without changing any existing grade.
210
+ const graded = checks.filter((c) => !c.advisory);
211
+ const passed = graded.filter((c) => c.passed).length;
212
+ const passRate = graded.length > 0 ? Math.round((passed / graded.length) * 100) : 0;
165
213
 
166
214
  return {
167
- skillId, skillDir, checks, passed, total: checks.length,
215
+ skillId, skillDir, checks, passed, total: graded.length,
168
216
  passRate, grade: gradeFromRate(passRate), cost: estimateSkillCost(content),
169
217
  };
170
218
  }
@@ -190,8 +238,12 @@ export function compareSkills(
190
238
  const skillA = benchmarkSkill(skillADir, skillAId);
191
239
  const skillB = benchmarkSkill(skillBDir, skillBId);
192
240
 
241
+ // deltaChecks is GRADED-ONLY — advisory checks (e.g. S16) never touch the grade,
242
+ // so surfacing them in the A/B diff would misrepresent an adoption nudge as a
243
+ // quality difference. Mirrors the graded filter in the grade math above.
193
244
  const deltaChecks: { id: string; aPass: boolean; bPass: boolean }[] = [];
194
245
  for (const checkA of skillA.checks) {
246
+ if (checkA.advisory) continue;
195
247
  const checkB = skillB.checks.find((c) => c.id === checkA.id);
196
248
  if (checkB && checkA.passed !== checkB.passed) {
197
249
  deltaChecks.push({ id: checkA.id, aPass: checkA.passed, bPass: checkB.passed });