@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/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 ids = discoverSkillIds(options.skillsDir).filter(
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,
@@ -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
- return JSON.parse(raw) as SourcesManifest;
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. */