@dzhechkov/harness-core 0.3.19 → 0.3.21
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/README.md +1 -1
- package/dist/benchmark.d.ts +3 -0
- package/dist/benchmark.d.ts.map +1 -1
- package/dist/benchmark.js +3 -2
- package/dist/benchmark.js.map +1 -1
- package/dist/cost-scoring.d.ts +119 -0
- package/dist/cost-scoring.d.ts.map +1 -0
- package/dist/cost-scoring.js +191 -0
- package/dist/cost-scoring.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +6 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +6 -2
- package/dist/operations.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -0
- package/dist/registry.js.map +1 -1
- package/dist/setup.d.ts +2 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +122 -0
- package/dist/setup.js.map +1 -1
- package/dist/sync-upstream.d.ts.map +1 -1
- package/dist/sync-upstream.js +7 -1
- package/dist/sync-upstream.js.map +1 -1
- package/package.json +1 -1
- package/src/benchmark.ts +6 -2
- package/src/cost-scoring.ts +273 -0
- package/src/index.ts +22 -2
- package/src/operations.ts +12 -2
- package/src/registry.ts +2 -0
- package/src/setup.ts +127 -0
- package/src/sync-upstream.ts +5 -1
package/src/operations.ts
CHANGED
|
@@ -51,6 +51,12 @@ export interface InitReport {
|
|
|
51
51
|
readonly skillsDir: string;
|
|
52
52
|
readonly projectRoot: string;
|
|
53
53
|
readonly skills: InitSkillResult[];
|
|
54
|
+
/**
|
|
55
|
+
* Selected skill ids that were NOT found in `skillsDir`. Empty when no
|
|
56
|
+
* `select` was given. Lets callers surface a warning instead of silently
|
|
57
|
+
* installing fewer skills than the selection asked for.
|
|
58
|
+
*/
|
|
59
|
+
readonly missing: string[];
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
// ---------------------------------------------------------------------------
|
|
@@ -142,9 +148,13 @@ export async function runInit(options: InitOptions): Promise<InitReport> {
|
|
|
142
148
|
const adapter = TARGETS[options.target];
|
|
143
149
|
const skills: InitSkillResult[] = [];
|
|
144
150
|
const selection = options.select;
|
|
145
|
-
const
|
|
151
|
+
const discovered = discoverSkillIds(options.skillsDir);
|
|
152
|
+
const ids = discovered.filter(
|
|
146
153
|
(id) => selection === undefined || selection.includes(id),
|
|
147
154
|
);
|
|
155
|
+
const missing = selection === undefined
|
|
156
|
+
? []
|
|
157
|
+
: selection.filter((id) => !discovered.includes(id));
|
|
148
158
|
for (const id of ids) {
|
|
149
159
|
const skill = loadSkillFromDir(options.skillsDir, id);
|
|
150
160
|
const emit = await adapter.compile(skill, { targetRoot: options.projectRoot });
|
|
@@ -157,7 +167,7 @@ export async function runInit(options: InitOptions): Promise<InitReport> {
|
|
|
157
167
|
});
|
|
158
168
|
skills.push({ id, written: applied.written, skipped: applied.skipped, warnings: [...emit.warnings] });
|
|
159
169
|
}
|
|
160
|
-
return { target: options.target, skillsDir: options.skillsDir, projectRoot: options.projectRoot, skills };
|
|
170
|
+
return { target: options.target, skillsDir: options.skillsDir, projectRoot: options.projectRoot, skills, missing };
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
// ---------------------------------------------------------------------------
|
package/src/registry.ts
CHANGED
|
@@ -46,6 +46,8 @@ function categoryFromPack(pack: string): string {
|
|
|
46
46
|
if (pack.includes('web3')) return 'web3';
|
|
47
47
|
if (pack.includes('mcp')) return 'mcp';
|
|
48
48
|
if (pack.includes('qe')) return 'qe';
|
|
49
|
+
if (pack.includes('academic')) return 'academic';
|
|
50
|
+
if (pack.includes('ecc')) return 'ecc';
|
|
49
51
|
if (pack.includes('meta')) return 'meta';
|
|
50
52
|
if (pack.includes('bto')) return 'bto';
|
|
51
53
|
if (pack.includes('health')) return 'health';
|
package/src/setup.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface SetupOptions {
|
|
|
29
29
|
readonly noMemory?: boolean | undefined;
|
|
30
30
|
readonly noPretrain?: boolean | undefined;
|
|
31
31
|
readonly force?: boolean | undefined;
|
|
32
|
+
/** Also deploy the operating-instructions "driver" skill + agent docs. */
|
|
33
|
+
readonly installDriver?: boolean | undefined;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/** Setup result. */
|
|
@@ -122,6 +124,125 @@ function isAgentdbAvailable(): boolean {
|
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
/** Run full environment setup. */
|
|
127
|
+
/** Marker that brackets the dz-harness section in a shared CLAUDE.md/AGENTS.md. */
|
|
128
|
+
const DRIVER_MARKER_START = '<!-- dz-harness-driver:start -->';
|
|
129
|
+
const DRIVER_MARKER_END = '<!-- dz-harness-driver:end -->';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Operating instructions for a coding agent that should *drive* the dz CLI
|
|
133
|
+
* correctly. Inspired by Visa VVAH `--install-agents`: the toolkit ships its own
|
|
134
|
+
* "how to operate me" doc so agents use the CLI as intended rather than guessing.
|
|
135
|
+
*/
|
|
136
|
+
function generateDriverInstructions(): string {
|
|
137
|
+
return `# Operating dz-harness-hub (CLI driver)
|
|
138
|
+
|
|
139
|
+
You have the \`dz\` CLI (\`@dzhechkov/harness-cli\`) available. It manages cross-platform
|
|
140
|
+
AI skills (the agentskills.io \`SKILL.md\` format) across Claude Code, Codex, OpenCode,
|
|
141
|
+
Hermes, and OpenClaude.
|
|
142
|
+
|
|
143
|
+
## Core commands
|
|
144
|
+
|
|
145
|
+
| Command | Use it when |
|
|
146
|
+
|---------|-------------|
|
|
147
|
+
| \`dz recommend "<task>"\` | The user describes a task — suggests the right skills/preset/npx package. Start here. |
|
|
148
|
+
| \`dz setup --target <t> [--preset <p>] [--memory agentdb]\` | Bootstrap a project: config, hooks, learning memory. |
|
|
149
|
+
| \`dz init --target <t> [--preset <p>] [--select id,id]\` | Install skills into a project for a platform. |
|
|
150
|
+
| \`dz benchmark <skill-dir>\` | Score a skill (L0 structural checks, grade A–F, cost band). |
|
|
151
|
+
| \`dz pretrain\` | Detect the project's tech stack and pre-load relevant skills. |
|
|
152
|
+
| \`dz scout [--deep]\` | Discover new skill sources across the ecosystem. |
|
|
153
|
+
| \`dz import-ecc\` | Import skills from an ECC repo. |
|
|
154
|
+
|
|
155
|
+
## Rules for driving this CLI
|
|
156
|
+
|
|
157
|
+
1. **Do NOT hand-edit \`SKILL.md\` files to make a benchmark pass** — fix the underlying
|
|
158
|
+
structure (missing frontmatter, sections, schema) instead.
|
|
159
|
+
2. **Do NOT hand-write \`.dz/config.json\`** — run \`dz setup\` and let it generate config.
|
|
160
|
+
3. **Skills are agentskills.io format** — YAML frontmatter (name, description, trust_tier,
|
|
161
|
+
validation) + a Markdown body with a Protocol and Anti-Patterns section.
|
|
162
|
+
4. **Presets bundle skills**; prefer \`--preset\` over selecting individual skills unless the
|
|
163
|
+
user wants a minimal install.
|
|
164
|
+
5. **Trust tiers**: tier 1 (Structured) → run \`/bto-test\` to promote to tier 2 (Validated).
|
|
165
|
+
6. When unsure which skills fit, run \`dz recommend\` first and follow its output.
|
|
166
|
+
|
|
167
|
+
## Targets (platform install dirs)
|
|
168
|
+
|
|
169
|
+
claude-code → \`.claude/skills/\` · codex → \`.agents/skills/\` · opencode → \`.opencode/\` ·
|
|
170
|
+
hermes → \`.hermes/\` · openclaude → \`.claude/skills/\`. The SKILL.md format is identical
|
|
171
|
+
across all five — only the directory differs.
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The same instructions packaged as a loadable Claude Code skill. */
|
|
176
|
+
function generateDriverSkill(): string {
|
|
177
|
+
return `---
|
|
178
|
+
name: dz-harness-driver
|
|
179
|
+
description: >
|
|
180
|
+
Operating instructions for the dz-harness-hub CLI (@dzhechkov/harness-cli). Load this when
|
|
181
|
+
asked to install/manage AI skills, set up a project with dz, benchmark a skill, or pick
|
|
182
|
+
the right preset. Tells you which dz command to run and the rules for driving the toolkit.
|
|
183
|
+
Triggers on: "dz setup", "install skills", "benchmark skill", "which preset", "dz recommend".
|
|
184
|
+
trust_tier: 0
|
|
185
|
+
trust_tier_label: "Reference"
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
${generateDriverInstructions()}
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Write the driver docs non-destructively. New files are created; an existing
|
|
194
|
+
* CLAUDE.md/AGENTS.md gets a marked section appended only if not already present.
|
|
195
|
+
* Returns a short detail string for the setup step.
|
|
196
|
+
*/
|
|
197
|
+
function installDriverDocs(projectRoot: string, force: boolean): string {
|
|
198
|
+
const written: string[] = [];
|
|
199
|
+
const skipped: string[] = [];
|
|
200
|
+
|
|
201
|
+
// 1. The loadable skill (own directory — always safe to write/refresh)
|
|
202
|
+
const skillDir = join(projectRoot, '.claude', 'skills', 'dz-harness-driver');
|
|
203
|
+
const skillPath = join(skillDir, 'SKILL.md');
|
|
204
|
+
if (!existsSync(skillPath) || force) {
|
|
205
|
+
mkdirSync(skillDir, { recursive: true });
|
|
206
|
+
writeFileSync(skillPath, generateDriverSkill());
|
|
207
|
+
written.push('skill');
|
|
208
|
+
} else {
|
|
209
|
+
skipped.push('skill');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 2. Standalone agent docs — create only if absent (never clobber the user's).
|
|
213
|
+
const instructions = generateDriverInstructions();
|
|
214
|
+
for (const name of ['AGENTS.md', 'GEMINI.md']) {
|
|
215
|
+
const p = join(projectRoot, name);
|
|
216
|
+
if (!existsSync(p)) {
|
|
217
|
+
writeFileSync(p, instructions);
|
|
218
|
+
written.push(name);
|
|
219
|
+
} else {
|
|
220
|
+
skipped.push(name);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 3. CLAUDE.md — append a marked block if the file lacks one (additive, idempotent).
|
|
225
|
+
const claudePath = join(projectRoot, 'CLAUDE.md');
|
|
226
|
+
const block = `\n${DRIVER_MARKER_START}\n\n${instructions}\n${DRIVER_MARKER_END}\n`;
|
|
227
|
+
if (!existsSync(claudePath)) {
|
|
228
|
+
writeFileSync(claudePath, block.trimStart());
|
|
229
|
+
written.push('CLAUDE.md');
|
|
230
|
+
} else {
|
|
231
|
+
const existing = readFileSync(claudePath, 'utf-8');
|
|
232
|
+
if (!existing.includes(DRIVER_MARKER_START)) {
|
|
233
|
+
writeFileSync(claudePath, existing + block);
|
|
234
|
+
written.push('CLAUDE.md(appended)');
|
|
235
|
+
} else {
|
|
236
|
+
skipped.push('CLAUDE.md');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const parts: string[] = [];
|
|
241
|
+
if (written.length) parts.push(`wrote ${written.join(', ')}`);
|
|
242
|
+
if (skipped.length) parts.push(`skipped ${skipped.join(', ')}`);
|
|
243
|
+
return parts.join('; ') || 'no changes';
|
|
244
|
+
}
|
|
245
|
+
|
|
125
246
|
export function runSetup(opts: SetupOptions): SetupResult {
|
|
126
247
|
const steps: SetupStep[] = [];
|
|
127
248
|
const dzDir = join(opts.projectRoot, '.dz');
|
|
@@ -268,6 +389,12 @@ export function runSetup(opts: SetupOptions): SetupResult {
|
|
|
268
389
|
steps.push({ name: 'Create .gitignore', status: 'done', detail: 'with .dz entries' });
|
|
269
390
|
}
|
|
270
391
|
|
|
392
|
+
// Step 7: Install the CLI-driver skill + agent docs (--install-driver)
|
|
393
|
+
if (opts.installDriver) {
|
|
394
|
+
const detail = installDriverDocs(opts.projectRoot, opts.force ?? false);
|
|
395
|
+
steps.push({ name: 'Install driver skill', status: 'done', detail });
|
|
396
|
+
}
|
|
397
|
+
|
|
271
398
|
return {
|
|
272
399
|
steps,
|
|
273
400
|
totalSteps: steps.length,
|
package/src/sync-upstream.ts
CHANGED
|
@@ -57,7 +57,11 @@ export function loadSourcesManifest(packageDir: string): SourcesManifest | undef
|
|
|
57
57
|
const sourcesPath = join(packageDir, 'sources.json');
|
|
58
58
|
if (!existsSync(sourcesPath)) return undefined;
|
|
59
59
|
const raw = readFileSync(sourcesPath, 'utf-8');
|
|
60
|
-
|
|
60
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
61
|
+
// Only return if it has the expected origin + skills structure
|
|
62
|
+
if (!parsed.origin || typeof parsed.origin !== 'object') return undefined;
|
|
63
|
+
if (!parsed.skills || typeof parsed.skills !== 'object') return undefined;
|
|
64
|
+
return parsed as unknown as SourcesManifest;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/** Info about a package with external sources. */
|