@dzhechkov/harness-core 0.2.0
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 +24 -0
- package/dist/apply.d.ts +31 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/apply.js +36 -0
- package/dist/apply.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/operations.d.ts +126 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +247 -0
- package/dist/operations.js.map +1 -0
- package/dist/skills.d.ts +36 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +87 -0
- package/dist/skills.js.map +1 -0
- package/dist/targets.d.ts +23 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +26 -0
- package/dist/targets.js.map +1 -0
- package/dist/workflows.d.ts +28 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +103 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +58 -0
- package/src/apply.ts +57 -0
- package/src/index.ts +14 -0
- package/src/operations.ts +388 -0
- package/src/security/boundaries.json +55 -0
- package/src/skills.ts +109 -0
- package/src/targets.ts +33 -0
- package/src/workflows.ts +123 -0
package/dist/skills.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill discovery + loading — the consolidated filesystem loader.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join, relative } from 'node:path';
|
|
8
|
+
import { parse as parseYaml } from 'yaml';
|
|
9
|
+
import { ClaudeSkillFrontmatterSchema, parseSkillDocument } from '@dzhechkov/core';
|
|
10
|
+
/** Recursively list every file under `dir`. */
|
|
11
|
+
function walkFiles(dir) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
14
|
+
const full = join(dir, entry.name);
|
|
15
|
+
if (entry.isDirectory())
|
|
16
|
+
out.push(...walkFiles(full));
|
|
17
|
+
else if (entry.isFile())
|
|
18
|
+
out.push(full);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
/** Return the ids of every `<skillsDir>/<id>/SKILL.md`, sorted. */
|
|
23
|
+
export function discoverSkillIds(skillsDir) {
|
|
24
|
+
if (!existsSync(skillsDir))
|
|
25
|
+
return [];
|
|
26
|
+
return readdirSync(skillsDir, { withFileTypes: true })
|
|
27
|
+
.filter((entry) => entry.isDirectory() && existsSync(join(skillsDir, entry.name, 'SKILL.md')))
|
|
28
|
+
.map((entry) => entry.name)
|
|
29
|
+
.sort();
|
|
30
|
+
}
|
|
31
|
+
/** Discover every skill in `skillsDir`, returning id + description. */
|
|
32
|
+
export function listSkills(skillsDir) {
|
|
33
|
+
return discoverSkillIds(skillsDir).map((id) => {
|
|
34
|
+
const document = parseSkillDocument(readFileSync(join(skillsDir, id, 'SKILL.md'), 'utf-8'));
|
|
35
|
+
const frontmatter = ClaudeSkillFrontmatterSchema.parse(parseYaml(document.frontmatterYaml));
|
|
36
|
+
return { id, description: frontmatter.description };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Get detailed info about a single skill without loading all assets. */
|
|
40
|
+
export function getSkillInfo(skillsDir, id) {
|
|
41
|
+
const skillDir = join(skillsDir, id);
|
|
42
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
43
|
+
if (!existsSync(skillMdPath))
|
|
44
|
+
return undefined;
|
|
45
|
+
const document = parseSkillDocument(readFileSync(skillMdPath, 'utf-8'));
|
|
46
|
+
const fm = parseYaml(document.frontmatterYaml);
|
|
47
|
+
const parsed = ClaudeSkillFrontmatterSchema.parse(fm);
|
|
48
|
+
const assetPaths = walkFiles(skillDir)
|
|
49
|
+
.filter((p) => p !== skillMdPath)
|
|
50
|
+
.map((p) => relative(skillDir, p).split('\\').join('/'))
|
|
51
|
+
.sort();
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
description: parsed.description,
|
|
55
|
+
name: parsed.name ?? id,
|
|
56
|
+
trustTier: fm['trust_tier'],
|
|
57
|
+
version: parsed.version,
|
|
58
|
+
assetCount: assetPaths.length,
|
|
59
|
+
assetPaths,
|
|
60
|
+
frontmatter: fm,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load one `<skillsDir>/<id>/` directory into a {@link CanonicalSkill}: its
|
|
65
|
+
* `SKILL.md` document plus every other file as a bundled asset.
|
|
66
|
+
*
|
|
67
|
+
* @throws if the skill directory has no `SKILL.md`.
|
|
68
|
+
*/
|
|
69
|
+
export function loadSkillFromDir(skillsDir, id) {
|
|
70
|
+
const skillDir = join(skillsDir, id);
|
|
71
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
72
|
+
if (!existsSync(skillMdPath)) {
|
|
73
|
+
throw new Error(`skill not found: ${JSON.stringify(id)} (looked in ${skillsDir})`);
|
|
74
|
+
}
|
|
75
|
+
const document = parseSkillDocument(readFileSync(skillMdPath, 'utf-8'));
|
|
76
|
+
const frontmatter = ClaudeSkillFrontmatterSchema.parse(parseYaml(document.frontmatterYaml));
|
|
77
|
+
const assets = walkFiles(skillDir)
|
|
78
|
+
.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
|
+
}))
|
|
84
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
85
|
+
return { id, frontmatter, document, assets };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--target` names → platform adapters.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import type { Adapter } from '@dzhechkov/core';
|
|
7
|
+
/**
|
|
8
|
+
* The targets the harness can initialise. The key is the CLI `--target` name
|
|
9
|
+
* (`claude-code`, not `claude`); the value is the adapter that emits for it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TARGETS: {
|
|
12
|
+
readonly 'claude-code': Adapter;
|
|
13
|
+
readonly codex: Adapter;
|
|
14
|
+
readonly opencode: Adapter;
|
|
15
|
+
readonly hermes: Adapter;
|
|
16
|
+
};
|
|
17
|
+
/** A valid `--target` name. */
|
|
18
|
+
export type TargetName = keyof typeof TARGETS;
|
|
19
|
+
/** Every supported `--target` name. */
|
|
20
|
+
export declare const TARGET_NAMES: TargetName[];
|
|
21
|
+
/** Type guard: is `value` a supported `--target` name? */
|
|
22
|
+
export declare function isTargetName(value: string): value is TargetName;
|
|
23
|
+
//# sourceMappingURL=targets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.d.ts","sourceRoot":"","sources":["../src/targets.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,OAAO;;;;;CAKwB,CAAC;AAE7C,+BAA+B;AAC/B,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,OAAO,CAAC;AAE9C,uCAAuC;AACvC,eAAO,MAAM,YAAY,EAA2B,UAAU,EAAE,CAAC;AAEjE,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}
|
package/dist/targets.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--target` names → platform adapters.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { claudeAdapter } from '@dzhechkov/adapter-claude';
|
|
7
|
+
import { codexAdapter } from '@dzhechkov/adapter-codex';
|
|
8
|
+
import { hermesAdapter } from '@dzhechkov/adapter-hermes';
|
|
9
|
+
import { opencodeAdapter } from '@dzhechkov/adapter-opencode';
|
|
10
|
+
/**
|
|
11
|
+
* The targets the harness can initialise. The key is the CLI `--target` name
|
|
12
|
+
* (`claude-code`, not `claude`); the value is the adapter that emits for it.
|
|
13
|
+
*/
|
|
14
|
+
export const TARGETS = {
|
|
15
|
+
'claude-code': claudeAdapter,
|
|
16
|
+
codex: codexAdapter,
|
|
17
|
+
opencode: opencodeAdapter,
|
|
18
|
+
hermes: hermesAdapter,
|
|
19
|
+
};
|
|
20
|
+
/** Every supported `--target` name. */
|
|
21
|
+
export const TARGET_NAMES = Object.keys(TARGETS);
|
|
22
|
+
/** Type guard: is `value` a supported `--target` name? */
|
|
23
|
+
export function isTargetName(value) {
|
|
24
|
+
return Object.prototype.hasOwnProperty.call(TARGETS, value);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.js","sourceRoot":"","sources":["../src/targets.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,aAAa,EAAE,aAAa;IAC5B,KAAK,EAAE,YAAY;IACnB,QAAQ,EAAE,eAAe;IACzB,MAAM,EAAE,aAAa;CACqB,CAAC;AAK7C,uCAAuC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAiB,CAAC;AAEjE,0DAA0D;AAC1D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic workflow templates for Opus 4.8+ orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR-005: workflows live in the orchestration layer only.
|
|
5
|
+
* They generate JS scripts that Claude Code's dynamic workflow engine executes.
|
|
6
|
+
* Core schema, adapters, and skill content remain model-agnostic.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/** A workflow template — generates a JS orchestration script from parameters. */
|
|
11
|
+
export interface WorkflowTemplate {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
readonly generate: (options: WorkflowOptions) => string;
|
|
15
|
+
}
|
|
16
|
+
/** Options passed to workflow generation. */
|
|
17
|
+
export interface WorkflowOptions {
|
|
18
|
+
readonly projectRoot: string;
|
|
19
|
+
readonly dryRun?: boolean;
|
|
20
|
+
readonly packages?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/** All registered workflow templates. */
|
|
23
|
+
export declare const WORKFLOWS: Record<string, WorkflowTemplate>;
|
|
24
|
+
/** Valid workflow names. */
|
|
25
|
+
export declare const WORKFLOW_NAMES: string[];
|
|
26
|
+
/** Look up a workflow by name. */
|
|
27
|
+
export declare function getWorkflow(name: string): WorkflowTemplate | undefined;
|
|
28
|
+
//# sourceMappingURL=workflows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,MAAM,CAAC;CACzD;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAsFD,yCAAyC;AACzC,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAKtD,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,cAAc,UAAyB,CAAC;AAErD,kCAAkC;AAClC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEtE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic workflow templates for Opus 4.8+ orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR-005: workflows live in the orchestration layer only.
|
|
5
|
+
* They generate JS scripts that Claude Code's dynamic workflow engine executes.
|
|
6
|
+
* Core schema, adapters, and skill content remain model-agnostic.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
const coverageLift = {
|
|
11
|
+
name: 'coverage-lift',
|
|
12
|
+
description: 'Lift Tier-A package coverage to ≥95% using parallel agents per package.',
|
|
13
|
+
generate: (opts) => `
|
|
14
|
+
// Dynamic Workflow: Coverage Lift
|
|
15
|
+
// Generated for: ${opts.projectRoot}
|
|
16
|
+
// Per ADR-005: orchestration only — no adapter/core changes
|
|
17
|
+
|
|
18
|
+
const tierA = ${JSON.stringify(opts.packages ?? [
|
|
19
|
+
'core', 'memory', 'harness-core', 'harness-cli',
|
|
20
|
+
'harness-presets', 'mcp-server-tools',
|
|
21
|
+
'adapter-claude', 'adapter-codex', 'adapter-opencode', 'adapter-hermes',
|
|
22
|
+
'skills-qe',
|
|
23
|
+
])};
|
|
24
|
+
|
|
25
|
+
const tasks = tierA.map(pkg => ({
|
|
26
|
+
name: \`coverage-\${pkg}\`,
|
|
27
|
+
description: \`Lift @dzhechkov/\${pkg} to ≥95% line coverage. Run: pnpm --filter @dzhechkov/\${pkg} test -- --coverage. Add tests for uncovered lines.\`,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Claude Code dynamic workflow engine will:
|
|
31
|
+
// 1. Spawn one agent per task (up to 16 concurrent)
|
|
32
|
+
// 2. Each agent reads coverage, writes tests, re-runs coverage
|
|
33
|
+
// 3. Results aggregated when all agents complete
|
|
34
|
+
export default { tasks, maxConcurrency: 4${opts.dryRun ? ', dryRun: true' : ''} };
|
|
35
|
+
`.trim(),
|
|
36
|
+
};
|
|
37
|
+
const mutationKill = {
|
|
38
|
+
name: 'mutation-kill',
|
|
39
|
+
description: 'Kill surviving mutants across core packages using parallel Stryker runs.',
|
|
40
|
+
generate: (opts) => `
|
|
41
|
+
// Dynamic Workflow: Mutation Kill
|
|
42
|
+
// Generated for: ${opts.projectRoot}
|
|
43
|
+
|
|
44
|
+
const packages = ${JSON.stringify(opts.packages ?? ['core', 'memory', 'harness-core'])};
|
|
45
|
+
|
|
46
|
+
const tasks = packages.map(pkg => ({
|
|
47
|
+
name: \`mutate-\${pkg}\`,
|
|
48
|
+
description: \`Run Stryker on @dzhechkov/\${pkg}, analyze survivors, write tests to kill them. Target: ≥80% mutation score.\`,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
export default { tasks, maxConcurrency: 3${opts.dryRun ? ', dryRun: true' : ''} };
|
|
52
|
+
`.trim(),
|
|
53
|
+
};
|
|
54
|
+
const canonicalize = {
|
|
55
|
+
name: 'canonicalize',
|
|
56
|
+
description: 'Canonicalize packages from vendored directories into @dzhechkov/* namespace.',
|
|
57
|
+
generate: (opts) => `
|
|
58
|
+
// Dynamic Workflow: Canonicalize
|
|
59
|
+
// Generated for: ${opts.projectRoot}
|
|
60
|
+
|
|
61
|
+
const tasks = [
|
|
62
|
+
{ name: 'discover', description: 'Scan vendored directories for un-canonicalized packages.' },
|
|
63
|
+
{ name: 'copy', description: 'Copy discovered packages to packages/@dzhechkov/. Exclude node_modules, .git, runtime state.' },
|
|
64
|
+
{ name: 'metadata', description: 'Add publishConfig, repository, homepage to each package.json.' },
|
|
65
|
+
{ name: 'verify', description: 'Run byte-level diff between source and canonical. 0 missing, 0 changed.' },
|
|
66
|
+
{ name: 'test', description: 'Run canonical-packages structural tests. All must pass.' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export default { tasks, maxConcurrency: 2${opts.dryRun ? ', dryRun: true' : ''} };
|
|
70
|
+
`.trim(),
|
|
71
|
+
};
|
|
72
|
+
const securityAudit = {
|
|
73
|
+
name: 'security-audit',
|
|
74
|
+
description: 'Run adversarial security audit with parallel boundary scanning.',
|
|
75
|
+
generate: (opts) => `
|
|
76
|
+
// Dynamic Workflow: Security Audit
|
|
77
|
+
// Generated for: ${opts.projectRoot}
|
|
78
|
+
|
|
79
|
+
const tasks = [
|
|
80
|
+
{ name: 'npm-audit', description: 'Run pnpm audit, capture baseline.' },
|
|
81
|
+
{ name: 'gitleaks', description: 'Run gitleaks detect, document findings.' },
|
|
82
|
+
{ name: 'boundaries', description: 'Verify all 8 input boundaries have runtime guards.' },
|
|
83
|
+
{ name: 'payloads', description: 'Test aidefence payloads against boundaries.' },
|
|
84
|
+
{ name: 'report', description: 'Generate security-audit.md with 6 H2 sections.' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
export default { tasks, maxConcurrency: 3${opts.dryRun ? ', dryRun: true' : ''} };
|
|
88
|
+
`.trim(),
|
|
89
|
+
};
|
|
90
|
+
/** All registered workflow templates. */
|
|
91
|
+
export const WORKFLOWS = {
|
|
92
|
+
'coverage-lift': coverageLift,
|
|
93
|
+
'mutation-kill': mutationKill,
|
|
94
|
+
'canonicalize': canonicalize,
|
|
95
|
+
'security-audit': securityAudit,
|
|
96
|
+
};
|
|
97
|
+
/** Valid workflow names. */
|
|
98
|
+
export const WORKFLOW_NAMES = Object.keys(WORKFLOWS);
|
|
99
|
+
/** Look up a workflow by name. */
|
|
100
|
+
export function getWorkflow(name) {
|
|
101
|
+
return WORKFLOWS[name];
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=workflows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.js","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,YAAY,GAAqB;IACrC,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,yEAAyE;IACtF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;;oBAEF,IAAI,CAAC,WAAW;;;gBAGpB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,IAAI;QAC5C,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa;QAC/C,iBAAiB,EAAE,kBAAkB;QACrC,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB;QACvE,WAAW;KACZ,CAAC;;;;;;;;;;;2CAWuC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;CAC7E,CAAC,IAAI,EAAE;CACP,CAAC;AAEF,MAAM,YAAY,GAAqB;IACrC,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,0EAA0E;IACvF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;;oBAEF,IAAI,CAAC,WAAW;;mBAEjB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;;;;;;;2CAO3C,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;CAC7E,CAAC,IAAI,EAAE;CACP,CAAC;AAEF,MAAM,YAAY,GAAqB;IACrC,IAAI,EAAE,cAAc;IACpB,WAAW,EAAE,8EAA8E;IAC3F,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;;oBAEF,IAAI,CAAC,WAAW;;;;;;;;;;2CAUO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;CAC7E,CAAC,IAAI,EAAE;CACP,CAAC;AAEF,MAAM,aAAa,GAAqB;IACtC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,iEAAiE;IAC9E,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;;oBAEF,IAAI,CAAC,WAAW;;;;;;;;;;2CAUO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;CAC7E,CAAC,IAAI,EAAE;CACP,CAAC;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,SAAS,GAAqC;IACzD,eAAe,EAAE,YAAY;IAC7B,eAAe,EAAE,YAAY;IAC7B,cAAc,EAAE,YAAY;IAC5B,gBAAgB,EAAE,aAAa;CAChC,CAAC;AAEF,4BAA4B;AAC5B,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAErD,kCAAkC;AAClC,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dzhechkov/harness-core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared harness logic - skill loading, additive apply, and the init/sync/verify/doctor operations.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "dzhechko",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"agent-skills",
|
|
10
|
+
"harness",
|
|
11
|
+
"claude-code"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"yaml": "^2.0.0",
|
|
28
|
+
"@dzhechkov/adapter-claude": "0.2.0",
|
|
29
|
+
"@dzhechkov/adapter-codex": "0.2.0",
|
|
30
|
+
"@dzhechkov/adapter-hermes": "0.2.0",
|
|
31
|
+
"@dzhechkov/adapter-opencode": "0.2.0",
|
|
32
|
+
"@dzhechkov/core": "0.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.6.0",
|
|
36
|
+
"typescript": "^5.7.0",
|
|
37
|
+
"vitest": "^3.0.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/djd1m/dz-harness-hub.git",
|
|
48
|
+
"directory": "packages/@dzhechkov/harness-core"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-core#readme",
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc -p tsconfig.json",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
56
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/apply.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The additive disk writer — the only part of the harness that writes files.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import type { EmitResult } from '@dzhechkov/core';
|
|
11
|
+
|
|
12
|
+
/** Options for {@link applyEmitResult}. */
|
|
13
|
+
export interface ApplyOptions {
|
|
14
|
+
/** Root directory the emit's relative paths are written under. */
|
|
15
|
+
readonly targetRoot: string;
|
|
16
|
+
/** Overwrite files that already exist. Default `false` — purely additive. */
|
|
17
|
+
readonly force?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The outcome of {@link applyEmitResult}. */
|
|
21
|
+
export interface ApplyReport {
|
|
22
|
+
/** Files written (created, or overwritten under `force`). */
|
|
23
|
+
readonly written: string[];
|
|
24
|
+
/** Files left untouched because they already existed and `force` was off. */
|
|
25
|
+
readonly skipped: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Write an adapter {@link EmitResult} to disk under `targetRoot`.
|
|
30
|
+
*
|
|
31
|
+
* **Additive (ADR-001):** creates files and parent directories, never deletes,
|
|
32
|
+
* and never overwrites an existing file unless `force` is `true`. A file that
|
|
33
|
+
* already exists (without `force`) is reported in `skipped`, not `written`.
|
|
34
|
+
*
|
|
35
|
+
* @throws if an emit path is absolute or contains a `..` segment.
|
|
36
|
+
*/
|
|
37
|
+
export function applyEmitResult(emit: EmitResult, options: ApplyOptions): ApplyReport {
|
|
38
|
+
const written: string[] = [];
|
|
39
|
+
const skipped: string[] = [];
|
|
40
|
+
|
|
41
|
+
for (const file of emit.files) {
|
|
42
|
+
if (file.path.startsWith('/') || file.path.split('/').includes('..')) {
|
|
43
|
+
throw new Error(`apply: refusing unsafe emit path ${JSON.stringify(file.path)}`);
|
|
44
|
+
}
|
|
45
|
+
const absolutePath = join(options.targetRoot, file.path);
|
|
46
|
+
if (existsSync(absolutePath) && options.force !== true) {
|
|
47
|
+
skipped.push(file.path);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
51
|
+
const data = file.encoding === 'base64' ? Buffer.from(file.content, 'base64') : file.content;
|
|
52
|
+
writeFileSync(absolutePath, data);
|
|
53
|
+
written.push(file.path);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { written, skipped };
|
|
57
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dzhechkov/harness-core` — shared harness logic behind `@dzhechkov/harness-cli`.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Package version. Kept in sync with `package.json`. */
|
|
8
|
+
export const HARNESS_CORE_VERSION = '0.1.0';
|
|
9
|
+
|
|
10
|
+
export * from './skills.js';
|
|
11
|
+
export * from './apply.js';
|
|
12
|
+
export * from './targets.js';
|
|
13
|
+
export * from './operations.js';
|
|
14
|
+
export * from './workflows.js';
|