@holdyourvoice/hyv 3.3.5 → 3.4.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 +12 -1
- package/dist/agents/catalog.js +118 -0
- package/dist/agents/catalog.test.js +60 -0
- package/dist/agents/emit.js +42 -0
- package/dist/agents/emit.test.js +64 -0
- package/dist/agents/index.js +4 -0
- package/dist/agents/load.js +184 -0
- package/dist/agents/load.test.js +149 -0
- package/dist/agents/types.js +18 -0
- package/dist/cli.js +110 -1
- package/dist/cli.test.js +76 -0
- package/dist/rebuild-task.test.js +1 -1
- package/dist/release-audit.test.js +5 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/skills/hyv-analyze/SKILL.md +24 -0
- package/skills/hyv-analyze/agent.json +49 -0
- package/skills/hyv-analyze/agents/openai.yaml +4 -0
- package/skills/hyv-apply-hidden-text-policy/SKILL.md +24 -0
- package/skills/hyv-apply-hidden-text-policy/agent.json +49 -0
- package/skills/hyv-apply-hidden-text-policy/agents/openai.yaml +4 -0
- package/skills/hyv-apply-rebuild/SKILL.md +24 -0
- package/skills/hyv-apply-rebuild/agent.json +49 -0
- package/skills/hyv-apply-rebuild/agents/openai.yaml +4 -0
- package/skills/hyv-apply-rewrite/SKILL.md +24 -0
- package/skills/hyv-apply-rewrite/agent.json +48 -0
- package/skills/hyv-apply-rewrite/agents/openai.yaml +4 -0
- package/skills/hyv-batch-analyze/SKILL.md +24 -0
- package/skills/hyv-batch-analyze/agent.json +45 -0
- package/skills/hyv-batch-analyze/agents/openai.yaml +4 -0
- package/skills/hyv-fact-lint/SKILL.md +24 -0
- package/skills/hyv-fact-lint/agent.json +50 -0
- package/skills/hyv-fact-lint/agents/openai.yaml +4 -0
- package/skills/hyv-final-check/SKILL.md +24 -0
- package/skills/hyv-final-check/agent.json +43 -0
- package/skills/hyv-final-check/agents/openai.yaml +4 -0
- package/skills/hyv-hygiene/SKILL.md +24 -0
- package/skills/hyv-hygiene/agent.json +50 -0
- package/skills/hyv-hygiene/agents/openai.yaml +4 -0
- package/skills/hyv-inspect-hidden-text/SKILL.md +24 -0
- package/skills/hyv-inspect-hidden-text/agent.json +48 -0
- package/skills/hyv-inspect-hidden-text/agents/openai.yaml +4 -0
- package/skills/hyv-learning/SKILL.md +24 -0
- package/skills/hyv-learning/agent.json +48 -0
- package/skills/hyv-learning/agents/openai.yaml +4 -0
- package/skills/hyv-lifecycle/SKILL.md +24 -0
- package/skills/hyv-lifecycle/agent.json +48 -0
- package/skills/hyv-lifecycle/agents/openai.yaml +4 -0
- package/skills/hyv-logic-lint/SKILL.md +24 -0
- package/skills/hyv-logic-lint/agent.json +47 -0
- package/skills/hyv-logic-lint/agents/openai.yaml +4 -0
- package/skills/hyv-mcp/SKILL.md +24 -0
- package/skills/hyv-mcp/agent.json +43 -0
- package/skills/hyv-mcp/agents/openai.yaml +4 -0
- package/skills/hyv-patterns/SKILL.md +24 -0
- package/skills/hyv-patterns/agent.json +43 -0
- package/skills/hyv-patterns/agents/openai.yaml +4 -0
- package/skills/hyv-prepare-judgment/SKILL.md +24 -0
- package/skills/hyv-prepare-judgment/agent.json +52 -0
- package/skills/hyv-prepare-judgment/agents/openai.yaml +4 -0
- package/skills/hyv-prepare-rebuild/SKILL.md +24 -0
- package/skills/hyv-prepare-rebuild/agent.json +54 -0
- package/skills/hyv-prepare-rebuild/agents/openai.yaml +4 -0
- package/skills/hyv-prepare-rewrite/SKILL.md +24 -0
- package/skills/hyv-prepare-rewrite/agent.json +51 -0
- package/skills/hyv-prepare-rewrite/agents/openai.yaml +4 -0
- package/skills/hyv-profile/SKILL.md +24 -0
- package/skills/hyv-profile/agent.json +50 -0
- package/skills/hyv-profile/agents/openai.yaml +4 -0
- package/skills/hyv-rebuild-writer-request/SKILL.md +24 -0
- package/skills/hyv-rebuild-writer-request/agent.json +47 -0
- package/skills/hyv-rebuild-writer-request/agents/openai.yaml +4 -0
- package/skills/hyv-reduce-judgment/SKILL.md +24 -0
- package/skills/hyv-reduce-judgment/agent.json +45 -0
- package/skills/hyv-reduce-judgment/agents/openai.yaml +4 -0
- package/skills/hyv-rewrite-prompt/SKILL.md +24 -0
- package/skills/hyv-rewrite-prompt/agent.json +48 -0
- package/skills/hyv-rewrite-prompt/agents/openai.yaml +4 -0
- package/skills/hyv-verify/SKILL.md +24 -0
- package/skills/hyv-verify/agent.json +49 -0
- package/skills/hyv-verify/agents/openai.yaml +4 -0
- package/skills/hyv-verify-spec/SKILL.md +24 -0
- package/skills/hyv-verify-spec/agent.json +50 -0
- package/skills/hyv-verify-spec/agents/openai.yaml +4 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { loadAll, loadAllFrom, validateAll, validateId, sortedIds } from './load.js';
|
|
7
|
+
import { AGENT_SCHEMA_VERSION } from './types.js';
|
|
8
|
+
function makePackage(dir, id, overrides = {}) {
|
|
9
|
+
const packageDir = join(dir, 'skills', id);
|
|
10
|
+
mkdirSync(join(packageDir, 'agents'), { recursive: true });
|
|
11
|
+
const descriptor = {
|
|
12
|
+
schema_version: AGENT_SCHEMA_VERSION,
|
|
13
|
+
id,
|
|
14
|
+
title: id,
|
|
15
|
+
description: 'test agent',
|
|
16
|
+
instruction_file: 'SKILL.md',
|
|
17
|
+
role: 'Tester',
|
|
18
|
+
workflow_phase: 'test',
|
|
19
|
+
input: { required: ['draft'], optional: [] },
|
|
20
|
+
output: { required: ['report'], optional: [] },
|
|
21
|
+
evidence_requirements: ['preserve evidence'],
|
|
22
|
+
permissions: ['read_repository'],
|
|
23
|
+
stop_conditions: ['stop when evidence missing'],
|
|
24
|
+
tool_free_mode: {
|
|
25
|
+
available: true,
|
|
26
|
+
behavior: 'report NOT_AVAILABLE',
|
|
27
|
+
unavailable_statuses: ['NOT_AVAILABLE', 'NOT_RUN'],
|
|
28
|
+
},
|
|
29
|
+
handoff_to: [],
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
writeFileSync(join(packageDir, 'agent.json'), JSON.stringify(descriptor));
|
|
33
|
+
writeFileSync(join(packageDir, 'SKILL.md'), `# ${id}\n\nUsage: hyv ${id} draft.md\n`);
|
|
34
|
+
writeFileSync(join(packageDir, 'agents', 'openai.yaml'), `interface:\n display_name: "${id}"\n short_description: "test agent"\n default_prompt: "Use ${id}"\n`);
|
|
35
|
+
return packageDir;
|
|
36
|
+
}
|
|
37
|
+
function makeTempRoot() {
|
|
38
|
+
return mkdtempSync(join(tmpdir(), 'hyv-agents-'));
|
|
39
|
+
}
|
|
40
|
+
test('loads all valid packages and rejects unknown schema version', () => {
|
|
41
|
+
const root = makeTempRoot();
|
|
42
|
+
try {
|
|
43
|
+
const first = makePackage(root, 'hyv-alpha');
|
|
44
|
+
makePackage(root, 'hyv-beta', { handoff_to: ['hyv-alpha'] });
|
|
45
|
+
const packages = loadAllFrom(root);
|
|
46
|
+
assert.deepEqual(sortedIds(packages), ['hyv-alpha', 'hyv-beta']);
|
|
47
|
+
assert.equal(packages.get('hyv-alpha').directory, first);
|
|
48
|
+
assert.match(packages.get('hyv-alpha').instructions, /^# hyv-alpha/);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
rmSync(root, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
test('rejects a package whose id does not match its directory name', () => {
|
|
55
|
+
const root = makeTempRoot();
|
|
56
|
+
try {
|
|
57
|
+
makePackage(root, 'hyv-alpha', { id: 'hyv-mismatch' });
|
|
58
|
+
assert.throws(() => loadAllFrom(root), /id must match skill directory/);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
rmSync(root, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
test('rejects a handoff target that does not exist', () => {
|
|
65
|
+
const root = makeTempRoot();
|
|
66
|
+
try {
|
|
67
|
+
makePackage(root, 'hyv-alpha', { handoff_to: ['hyv-ghost'] });
|
|
68
|
+
assert.throws(() => loadAllFrom(root), /hands off to unknown agent/);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
rmSync(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
test('rejects an unsupported permission and an unsupported status', () => {
|
|
75
|
+
const root = makeTempRoot();
|
|
76
|
+
try {
|
|
77
|
+
makePackage(root, 'hyv-alpha', { permissions: ['definitely-not-real'] });
|
|
78
|
+
assert.throws(() => loadAllFrom(root), /unsupported permission/);
|
|
79
|
+
rmSync(root, { recursive: true, force: true });
|
|
80
|
+
const root2 = makeTempRoot();
|
|
81
|
+
makePackage(root2, 'hyv-beta', {
|
|
82
|
+
tool_free_mode: { available: true, behavior: 'x', unavailable_statuses: ['NOPE'] },
|
|
83
|
+
});
|
|
84
|
+
assert.throws(() => loadAllFrom(root2), /tool_free_mode requires/);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
rmSync(root, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
test('validateAll and validateId report PASS or unknown id', () => {
|
|
91
|
+
const root = makeTempRoot();
|
|
92
|
+
try {
|
|
93
|
+
makePackage(root, 'hyv-alpha');
|
|
94
|
+
const packages = loadAllFrom(root);
|
|
95
|
+
validateAll(packages);
|
|
96
|
+
validateId(packages, 'hyv-alpha');
|
|
97
|
+
assert.throws(() => validateId(packages, 'hyv-missing'), /unknown agent/);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
rmSync(root, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
test('rejects a malformed agent.json', () => {
|
|
104
|
+
const root = makeTempRoot();
|
|
105
|
+
try {
|
|
106
|
+
const packageDir = join(root, 'skills', 'hyv-alpha');
|
|
107
|
+
mkdirSync(join(packageDir, 'agents'), { recursive: true });
|
|
108
|
+
writeFileSync(join(packageDir, 'agent.json'), '{ not json');
|
|
109
|
+
writeFileSync(join(packageDir, 'SKILL.md'), '# x');
|
|
110
|
+
assert.throws(() => loadAllFrom(root), /not valid JSON/);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
rmSync(root, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
test('rejects unknown contract fields and invalid OpenAI interface metadata', () => {
|
|
117
|
+
const root = makeTempRoot();
|
|
118
|
+
try {
|
|
119
|
+
makePackage(root, 'hyv-alpha', { unsupported: true });
|
|
120
|
+
assert.throws(() => loadAllFrom(root), /supported contract fields/);
|
|
121
|
+
rmSync(root, { recursive: true, force: true });
|
|
122
|
+
const root2 = makeTempRoot();
|
|
123
|
+
const packageDir = makePackage(root2, 'hyv-beta');
|
|
124
|
+
rmSync(join(packageDir, 'agents', 'openai.yaml'));
|
|
125
|
+
assert.throws(() => loadAllFrom(root2), /openai\.yaml/);
|
|
126
|
+
rmSync(root2, { recursive: true, force: true });
|
|
127
|
+
const root3 = makeTempRoot();
|
|
128
|
+
const invalidPackageDir = makePackage(root3, 'hyv-gamma');
|
|
129
|
+
writeFileSync(join(invalidPackageDir, 'agents', 'openai.yaml'), 'interface:\n display_name: "unterminated\n short_description: "test"\n default_prompt: "Use hyv-gamma"\n');
|
|
130
|
+
assert.throws(() => loadAllFrom(root3), /supported interface metadata/);
|
|
131
|
+
rmSync(root3, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
rmSync(root, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
test('published packages declare command execution and required writes', () => {
|
|
138
|
+
const packages = loadAll();
|
|
139
|
+
const writers = new Set(['hyv-profile', 'hyv-hygiene', 'hyv-apply-hidden-text-policy', 'hyv-prepare-rewrite', 'hyv-prepare-judgment', 'hyv-prepare-rebuild', 'hyv-rebuild-writer-request', 'hyv-lifecycle', 'hyv-learning']);
|
|
140
|
+
for (const [id, pkg] of packages) {
|
|
141
|
+
assert.ok(pkg.descriptor.permissions.includes('execute_commands'), `${id} must require command execution`);
|
|
142
|
+
assert.equal(pkg.descriptor.permissions.includes('write_repository'), writers.has(id), `${id} write permission mismatch`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
test('published instructions document command-specific argument requirements', () => {
|
|
146
|
+
const packages = loadAll();
|
|
147
|
+
assert.match(packages.get('hyv-reduce-judgment').instructions, /hyv reduce-judgment envelope\.json envelope\.json envelope\.json \[envelope\.json\.\.\.\]/);
|
|
148
|
+
assert.match(packages.get('hyv-learning').instructions, /record-approved/);
|
|
149
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const AGENT_SCHEMA_VERSION = '1.0.0';
|
|
2
|
+
export const VALID_PERMISSIONS = new Set([
|
|
3
|
+
'read_repository',
|
|
4
|
+
'write_repository',
|
|
5
|
+
'execute_commands',
|
|
6
|
+
'network',
|
|
7
|
+
'browser_automation',
|
|
8
|
+
'git_write',
|
|
9
|
+
'pull_request_write',
|
|
10
|
+
'subagents',
|
|
11
|
+
]);
|
|
12
|
+
export const VALID_UNAVAILABLE_STATUSES = new Set([
|
|
13
|
+
'NOT_AVAILABLE',
|
|
14
|
+
'NOT_CONFIGURED',
|
|
15
|
+
'NOT_RUN',
|
|
16
|
+
'STALE',
|
|
17
|
+
'ERROR',
|
|
18
|
+
]);
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,8 @@ import { buildProfile } from './voice-dna.js';
|
|
|
19
19
|
import { loadApprovalContext } from './approval-context.js';
|
|
20
20
|
import { formatFactLintReport, lintFacts } from './fact-linter.js';
|
|
21
21
|
import { lintLogic } from './logic-linter.js';
|
|
22
|
-
|
|
22
|
+
import { loadAll, validateAll, validateId, sortedIds, describe, emitJson, emitPrompt } from './agents/index.js';
|
|
23
|
+
const usage = 'Commands: agent, profile, analyze, hygiene, inspect-hidden-text, apply-hidden-text-policy, final-check, fact-lint, logic-lint, batch-analyze, rewrite-prompt, prepare-rewrite, apply-rewrite, prepare-judgment, reduce-judgment, prepare-rebuild, rebuild-writer-request, apply-rebuild, verify, verify-spec, lifecycle, learning, patterns, mcp';
|
|
23
24
|
function input(path) {
|
|
24
25
|
return path === '-' ? readFileSync(0, 'utf8') : readFileSync(path, 'utf8');
|
|
25
26
|
}
|
|
@@ -269,8 +270,116 @@ function writeNewFileAtomically(path, text) {
|
|
|
269
270
|
if (primaryError)
|
|
270
271
|
throw primaryError;
|
|
271
272
|
}
|
|
273
|
+
function agentFlags(args) {
|
|
274
|
+
const values = [];
|
|
275
|
+
let host = 'generic';
|
|
276
|
+
let mode;
|
|
277
|
+
let output;
|
|
278
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
279
|
+
const argument = args[index];
|
|
280
|
+
if (argument === '--host') {
|
|
281
|
+
const value = args[index + 1];
|
|
282
|
+
if (!value || value.startsWith('--'))
|
|
283
|
+
throw new Error('Usage: hyv agent --host HOST requires a value.');
|
|
284
|
+
host = value;
|
|
285
|
+
index += 1;
|
|
286
|
+
}
|
|
287
|
+
else if (argument.startsWith('--host=')) {
|
|
288
|
+
const value = argument.slice('--host='.length);
|
|
289
|
+
if (!value)
|
|
290
|
+
throw new Error('Usage: hyv agent --host HOST requires a value.');
|
|
291
|
+
host = value;
|
|
292
|
+
}
|
|
293
|
+
else if (argument === '--mode') {
|
|
294
|
+
const value = args[index + 1];
|
|
295
|
+
if (value !== 'prompt' && value !== 'json')
|
|
296
|
+
throw new Error('Usage: hyv agent --mode prompt|json requires a mode.');
|
|
297
|
+
mode = value;
|
|
298
|
+
index += 1;
|
|
299
|
+
}
|
|
300
|
+
else if (argument.startsWith('--mode=')) {
|
|
301
|
+
const value = argument.slice('--mode='.length);
|
|
302
|
+
if (value !== 'prompt' && value !== 'json')
|
|
303
|
+
throw new Error('Usage: hyv agent --mode prompt|json requires a mode.');
|
|
304
|
+
mode = value;
|
|
305
|
+
}
|
|
306
|
+
else if (argument === '--output') {
|
|
307
|
+
const value = args[index + 1];
|
|
308
|
+
if (!value || value.startsWith('--'))
|
|
309
|
+
throw new Error('Usage: hyv agent --output FILE requires a value.');
|
|
310
|
+
output = value;
|
|
311
|
+
index += 1;
|
|
312
|
+
}
|
|
313
|
+
else if (argument.startsWith('--output=')) {
|
|
314
|
+
const value = argument.slice('--output='.length);
|
|
315
|
+
if (!value)
|
|
316
|
+
throw new Error('Usage: hyv agent --output FILE requires a value.');
|
|
317
|
+
output = value;
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
values.push(argument);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { values, host, mode, output };
|
|
324
|
+
}
|
|
325
|
+
function runAgent(args) {
|
|
326
|
+
const [subcommand, ...subargs] = args;
|
|
327
|
+
if (subcommand === 'list') {
|
|
328
|
+
if (subargs.length)
|
|
329
|
+
throw new Error('Usage: hyv agent list');
|
|
330
|
+
const packages = loadAll();
|
|
331
|
+
const ids = sortedIds(packages);
|
|
332
|
+
json(ids.map((id) => {
|
|
333
|
+
const descriptor = packages.get(id).descriptor;
|
|
334
|
+
return { id, role: descriptor.role, workflow_phase: descriptor.workflow_phase, description: descriptor.description };
|
|
335
|
+
}));
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
if (subcommand === 'validate') {
|
|
339
|
+
if (subargs.length > 1)
|
|
340
|
+
throw new Error('Usage: hyv agent validate [id]');
|
|
341
|
+
const packages = loadAll();
|
|
342
|
+
const id = subargs[0];
|
|
343
|
+
if (id !== undefined)
|
|
344
|
+
validateId(packages, id);
|
|
345
|
+
validateAll(packages);
|
|
346
|
+
json({ schema_version: '1.0.0', status: 'PASS', agent: id ?? 'all' });
|
|
347
|
+
return 0;
|
|
348
|
+
}
|
|
349
|
+
if (subcommand === 'describe') {
|
|
350
|
+
const { values, host, mode, output } = agentFlags(subargs);
|
|
351
|
+
if (values.length !== 1 || mode !== undefined || output !== undefined)
|
|
352
|
+
throw new Error('Usage: hyv agent describe <id> [--host HOST]');
|
|
353
|
+
const packages = loadAll();
|
|
354
|
+
validateId(packages, values[0]);
|
|
355
|
+
json(describe(packages.get(values[0]), host));
|
|
356
|
+
return 0;
|
|
357
|
+
}
|
|
358
|
+
if (subcommand === 'emit') {
|
|
359
|
+
const { values, host, mode, output } = agentFlags(subargs);
|
|
360
|
+
if (values.length !== 1)
|
|
361
|
+
throw new Error('Usage: hyv agent emit <id> --mode prompt|json [--host HOST] [--output FILE]');
|
|
362
|
+
if (!mode)
|
|
363
|
+
throw new Error('Usage: hyv agent emit <id> --mode prompt|json [--host HOST] [--output FILE]');
|
|
364
|
+
const packages = loadAll();
|
|
365
|
+
validateId(packages, values[0]);
|
|
366
|
+
const pkg = packages.get(values[0]);
|
|
367
|
+
const body = mode === 'prompt' ? emitPrompt(pkg, host) : `${emitJson(pkg, host)}\n`;
|
|
368
|
+
if (output !== undefined) {
|
|
369
|
+
writeFileSync(output, body, { encoding: 'utf8', flag: 'wx' });
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
process.stdout.write(body);
|
|
373
|
+
}
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
throw new Error('Usage: hyv agent <list|validate|describe|emit> ...');
|
|
377
|
+
}
|
|
272
378
|
export async function runCli(args) {
|
|
273
379
|
const [command, ...rest] = args;
|
|
380
|
+
if (command === 'agent') {
|
|
381
|
+
return runAgent(rest);
|
|
382
|
+
}
|
|
274
383
|
if (command === 'profile') {
|
|
275
384
|
const { output, samples, avoid } = profileArguments(rest);
|
|
276
385
|
writeFileSync(output, `${JSON.stringify(buildProfile(samples.map(input), avoid), null, 2)}\n`);
|
package/dist/cli.test.js
CHANGED
|
@@ -556,3 +556,79 @@ test('prepares and applies an authorized rebuild through CLI', () => {
|
|
|
556
556
|
rmSync(directory, { recursive: true, force: true });
|
|
557
557
|
}
|
|
558
558
|
});
|
|
559
|
+
test('agent list enumerates every command as one portable package', () => {
|
|
560
|
+
const result = run(['agent', 'list']);
|
|
561
|
+
assert.equal(result.status, 0, result.stderr);
|
|
562
|
+
const entries = JSON.parse(result.stdout);
|
|
563
|
+
assert.equal(entries.length, 23);
|
|
564
|
+
const ids = entries.map((entry) => entry.id);
|
|
565
|
+
assert.equal(new Set(ids).size, ids.length);
|
|
566
|
+
for (const id of ['hyv-profile', 'hyv-analyze', 'hyv-verify', 'hyv-mcp', 'hyv-patterns']) {
|
|
567
|
+
assert.ok(ids.includes(id), `missing ${id}`);
|
|
568
|
+
}
|
|
569
|
+
for (const entry of entries) {
|
|
570
|
+
assert.ok(entry.role);
|
|
571
|
+
assert.ok(entry.workflow_phase);
|
|
572
|
+
assert.ok(entry.description);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
test('agent validate passes over the full package tree and rejects unknown ids', () => {
|
|
576
|
+
const result = run(['agent', 'validate']);
|
|
577
|
+
assert.equal(result.status, 0, result.stderr);
|
|
578
|
+
assert.equal(JSON.parse(result.stdout).status, 'PASS');
|
|
579
|
+
assert.equal(run(['agent', 'validate', 'hyv-verify']).status, 0);
|
|
580
|
+
const unknown = run(['agent', 'validate', 'hyv-does-not-exist']);
|
|
581
|
+
assert.equal(unknown.status, 1);
|
|
582
|
+
assert.match(unknown.stderr, /unknown agent/);
|
|
583
|
+
});
|
|
584
|
+
test('agent describe resolves a host and reports the runtime descriptor', () => {
|
|
585
|
+
const result = run(['agent', 'describe', 'hyv-verify', '--host', 'codex']);
|
|
586
|
+
assert.equal(result.status, 0, result.stderr);
|
|
587
|
+
const descriptor = JSON.parse(result.stdout);
|
|
588
|
+
assert.equal(descriptor.agent.id, 'hyv-verify');
|
|
589
|
+
assert.equal(descriptor.host.id, 'codex');
|
|
590
|
+
assert.ok(descriptor.available_capabilities.includes('read_repository'));
|
|
591
|
+
assert.ok(Array.isArray(descriptor.unavailable_capabilities));
|
|
592
|
+
});
|
|
593
|
+
test('agent emit writes a prompt or json contract to stdout or a named file', () => {
|
|
594
|
+
const prompt = run(['agent', 'emit', 'hyv-verify', '--mode=prompt', '--host=generic']);
|
|
595
|
+
assert.equal(prompt.status, 0, prompt.stderr);
|
|
596
|
+
assert.match(prompt.stdout, /# hyv-verify/);
|
|
597
|
+
assert.match(prompt.stdout, /Required input: original path, candidate path, profile path/);
|
|
598
|
+
const jsonOut = run(['agent', 'emit', 'hyv-verify', '--mode', 'json', '--host', 'generic']);
|
|
599
|
+
assert.equal(jsonOut.status, 0, jsonOut.stderr);
|
|
600
|
+
assert.equal(JSON.parse(jsonOut.stdout).agent.id, 'hyv-verify');
|
|
601
|
+
const directory = mkdtempSync(join(tmpdir(), 'holdyourvoice-agent-'));
|
|
602
|
+
try {
|
|
603
|
+
const target = join(directory, 'verify.json');
|
|
604
|
+
const emitted = run(['agent', 'emit', 'hyv-verify', '--mode', 'json', '--output', target]);
|
|
605
|
+
assert.equal(emitted.status, 0, emitted.stderr);
|
|
606
|
+
assert.equal(JSON.parse(readFileSync(target, 'utf8')).agent.id, 'hyv-verify');
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
rmSync(directory, { recursive: true, force: true });
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
test('agent commands locate the installed package tree outside a repository', () => {
|
|
613
|
+
const directory = mkdtempSync(join(tmpdir(), 'holdyourvoice-agent-cwd-'));
|
|
614
|
+
try {
|
|
615
|
+
mkdirSync(join(directory, 'skills'));
|
|
616
|
+
const result = spawnSync(process.execPath, [cli, 'agent', 'validate'], { cwd: directory, encoding: 'utf8' });
|
|
617
|
+
assert.equal(result.status, 0, result.stderr);
|
|
618
|
+
assert.equal(JSON.parse(result.stdout).status, 'PASS');
|
|
619
|
+
}
|
|
620
|
+
finally {
|
|
621
|
+
rmSync(directory, { recursive: true, force: true });
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
test('agent usage errors return exit 1 and never mutate', () => {
|
|
625
|
+
assert.equal(run(['agent']).status, 1);
|
|
626
|
+
assert.equal(run(['agent', 'emit', 'hyv-verify']).status, 1);
|
|
627
|
+
assert.equal(run(['agent', 'emit', 'hyv-verify', '--mode', 'bad']).status, 1);
|
|
628
|
+
assert.equal(run(['agent', 'describe']).status, 1);
|
|
629
|
+
assert.equal(run(['agent', 'describe', 'hyv-verify', '--mode', 'json']).status, 1);
|
|
630
|
+
assert.equal(run(['agent', 'describe', 'hyv-verify', '--output', 'ignored.json']).status, 1);
|
|
631
|
+
assert.equal(run(['agent', 'list', '--host', 'codex']).status, 1);
|
|
632
|
+
assert.equal(run(['agent', 'list', 'extra']).status, 1);
|
|
633
|
+
assert.equal(run(['agent', 'nope']).status, 1);
|
|
634
|
+
});
|
|
@@ -174,7 +174,7 @@ test('CLI and MCP rebuild helpers share fingerprints', () => {
|
|
|
174
174
|
version: '1', audience: 'operators', intent: 'explain', format: 'outreach',
|
|
175
175
|
});
|
|
176
176
|
assert.match(briefTask.prompt, /# WritingBrief/);
|
|
177
|
-
assert.equal(HYV_VERSION, '3.
|
|
177
|
+
assert.equal(HYV_VERSION, '3.4.0');
|
|
178
178
|
});
|
|
179
179
|
test('apply rejects forged tasks, missing capability, and substituted profiles', () => {
|
|
180
180
|
const reduction = rebuildRecommendation();
|
|
@@ -23,7 +23,7 @@ function fixture(files) {
|
|
|
23
23
|
const directory = mkdtempSync(join(tmpdir(), 'holdyourvoice-audit-'));
|
|
24
24
|
execFileSync('git', ['init', '--quiet'], { cwd: directory });
|
|
25
25
|
const defaults = {
|
|
26
|
-
'package.json': JSON.stringify({ name: 'audit-fixture', license: 'MIT', files: ['dist', 'Readme.md', 'LICENSE'], scripts: { 'stage1:evaluate': 'node scripts/evaluate-rewrite-benchmark.mjs', 'stage1:dry-run': 'npm run build && node scripts/run-stage1-dry-run.mjs', 'stage1:human-packet': 'npm run build && node scripts/run-stage1-human-packet.mjs', 'stage2:human-packet': 'npm run build && node scripts/run-stage2-human-packet.mjs' }, version: '1.0.0', type: 'module', bin: { hyv: 'dist/cli.js' }, engines: { node: '>=20' } }),
|
|
26
|
+
'package.json': JSON.stringify({ name: 'audit-fixture', license: 'MIT', files: ['dist', 'skills', 'Readme.md', 'LICENSE'], scripts: { 'stage1:evaluate': 'node scripts/evaluate-rewrite-benchmark.mjs', 'stage1:dry-run': 'npm run build && node scripts/run-stage1-dry-run.mjs', 'stage1:human-packet': 'npm run build && node scripts/run-stage1-human-packet.mjs', 'stage2:human-packet': 'npm run build && node scripts/run-stage2-human-packet.mjs' }, version: '1.0.0', type: 'module', bin: { hyv: 'dist/cli.js' }, engines: { node: '>=20' } }),
|
|
27
27
|
'mcpb/manifest.json': JSON.stringify({ version: '1.0.0' }),
|
|
28
28
|
'claude-plugin/.claude-plugin/plugin.json': JSON.stringify({ version: '1.0.0' }),
|
|
29
29
|
'.claude-plugin/marketplace.json': JSON.stringify({ plugins: [{ name: 'hold-your-voice', version: '1.0.0' }] }),
|
|
@@ -36,6 +36,9 @@ function fixture(files) {
|
|
|
36
36
|
].join('\n'),
|
|
37
37
|
'src/version.ts': "export const HYV_VERSION = '1.0.0';",
|
|
38
38
|
'src/stage1-evaluation.ts': "const baseline = '4e6269121d551c008a34db73077e1e4fea41b3f9'; const stage1 = '550ea24f652291dca13757fdbd2f0fa0b5e3f621';",
|
|
39
|
+
'skills/hyv-test/agent.json': '{}',
|
|
40
|
+
'skills/hyv-test/SKILL.md': '# test',
|
|
41
|
+
'skills/hyv-test/agents/openai.yaml': 'name: test',
|
|
39
42
|
...Object.fromEntries(stage1Files.map((file) => [file, '{}'])),
|
|
40
43
|
};
|
|
41
44
|
for (const [file, text] of Object.entries({ ...defaults, ...files })) {
|
|
@@ -146,6 +149,7 @@ test('requires the npm package runtime contract', () => {
|
|
|
146
149
|
const result = spawnSync(process.execPath, [audit], { cwd: directory, encoding: 'utf8' });
|
|
147
150
|
assert.notEqual(result.status, 0);
|
|
148
151
|
assert.match(result.stderr, /npm package must include dist/);
|
|
152
|
+
assert.match(result.stderr, /npm package must include skills/);
|
|
149
153
|
assert.match(result.stderr, /npm package must include Readme\.md/);
|
|
150
154
|
assert.match(result.stderr, /npm package must use the ESM runtime contract/);
|
|
151
155
|
assert.match(result.stderr, /npm hyv binary must point to dist\/cli\.js/);
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const HYV_VERSION = '3.
|
|
1
|
+
export const HYV_VERSION = '3.4.0';
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holdyourvoice/hyv",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "A local-first dual-engine writing gate that protects voice and catches generic AI patterns.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "hyv": "dist/cli.js" },
|
|
7
|
-
"files": ["dist", "Readme.md", "LICENSE"],
|
|
8
|
-
"scripts": { "build": "tsc -p tsconfig.json", "bundle:claude": "esbuild src/mcp.ts --bundle --platform=node --format=esm --target=node20 --outfile=mcpb/server/index.js", "build:claude": "npm run build && npm run bundle:claude", "pack:claude": "npm run build:claude && node scripts/pack-claude.mjs", "stage1:evaluate": "node scripts/evaluate-rewrite-benchmark.mjs", "stage1:dry-run": "npm run build && node scripts/run-stage1-dry-run.mjs", "stage1:human-packet": "npm run build && node scripts/run-stage1-human-packet.mjs", "stage2:human-packet": "npm run build && node scripts/run-stage2-human-packet.mjs", "test": "npm run build && node --test dist/**/*.test.js", "check:release": "node scripts/release-audit.mjs", "prepack": "npm run check:release && npm test" },
|
|
7
|
+
"files": ["dist", "skills", "Readme.md", "LICENSE"],
|
|
8
|
+
"scripts": { "build": "tsc -p tsconfig.json", "bundle:claude": "esbuild src/mcp.ts --bundle --platform=node --format=esm --target=node20 --outfile=mcpb/server/index.js", "build:claude": "npm run build && npm run bundle:claude", "pack:claude": "npm run build:claude && node scripts/pack-claude.mjs", "stage1:evaluate": "node scripts/evaluate-rewrite-benchmark.mjs", "stage1:dry-run": "npm run build && node scripts/run-stage1-dry-run.mjs", "stage1:human-packet": "npm run build && node scripts/run-stage1-human-packet.mjs", "stage2:human-packet": "npm run build && node scripts/run-stage2-human-packet.mjs", "test": "npm run build && node --test \"dist/**/*.test.js\"", "check:release": "node scripts/release-audit.mjs", "prepack": "npm run check:release && npm test" },
|
|
9
9
|
"engines": { "node": ">=20" },
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"keywords": ["ai-writing", "cli", "editing", "voice", "writing"],
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hyv-analyze
|
|
3
|
+
description: Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hyv-analyze
|
|
7
|
+
|
|
8
|
+
Run independent VoiceDNA and AI Editor reports plus a separate non-scoring Unicode hygiene report against a draft using a portable profile. An optional WritingBrief adds local audience, intent, and format context without changing the profile. Analysis is read-only; it never rewrites the draft or calls a model.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
hyv analyze draft.md profile.json [writing-brief.json]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
- Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
|
|
19
|
+
- Commands that write use an explicit output path. Confirm that path before running the command.
|
|
20
|
+
- This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
|
|
21
|
+
|
|
22
|
+
## Handoff
|
|
23
|
+
|
|
24
|
+
Run `hyv analyze` directly to execute the operation. Follow-on agents: hyv-rewrite-prompt, hyv-prepare-rewrite.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0.0",
|
|
3
|
+
"id": "hyv-analyze",
|
|
4
|
+
"title": "hyv-analyze",
|
|
5
|
+
"description": "Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft.",
|
|
6
|
+
"instruction_file": "SKILL.md",
|
|
7
|
+
"role": "Analyzer",
|
|
8
|
+
"workflow_phase": "analyze",
|
|
9
|
+
"input": {
|
|
10
|
+
"required": [
|
|
11
|
+
"draft path",
|
|
12
|
+
"profile path"
|
|
13
|
+
],
|
|
14
|
+
"optional": [
|
|
15
|
+
"writing-brief.json"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"output": {
|
|
19
|
+
"required": [
|
|
20
|
+
"combined analysis report"
|
|
21
|
+
],
|
|
22
|
+
"optional": []
|
|
23
|
+
},
|
|
24
|
+
"evidence_requirements": [
|
|
25
|
+
"Report only the deterministic result the command returns; never invent a model or provider call that did not occur."
|
|
26
|
+
],
|
|
27
|
+
"permissions": [
|
|
28
|
+
"execute_commands",
|
|
29
|
+
"read_repository"
|
|
30
|
+
],
|
|
31
|
+
"stop_conditions": [
|
|
32
|
+
"Stop when required input is missing, when the command cannot run without changing existing behavior, or when a claimed capability is unavailable."
|
|
33
|
+
],
|
|
34
|
+
"tool_free_mode": {
|
|
35
|
+
"available": true,
|
|
36
|
+
"behavior": "Provide the decision, artifact template, and exact unavailable capability; never claim a tool action occurred.",
|
|
37
|
+
"unavailable_statuses": [
|
|
38
|
+
"NOT_AVAILABLE",
|
|
39
|
+
"NOT_CONFIGURED",
|
|
40
|
+
"NOT_RUN",
|
|
41
|
+
"STALE",
|
|
42
|
+
"ERROR"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"handoff_to": [
|
|
46
|
+
"hyv-rewrite-prompt",
|
|
47
|
+
"hyv-prepare-rewrite"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Hyv Analyze"
|
|
3
|
+
short_description: "Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft."
|
|
4
|
+
default_prompt: "Use $hyv-analyze to run separate voicedna and ai editor checks plus a non-scoring unicode hygiene inspection against a draft."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hyv-apply-hidden-text-policy
|
|
3
|
+
description: Apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining findings.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hyv-apply-hidden-text-policy
|
|
7
|
+
|
|
8
|
+
Apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining review findings. The output must differ from the input path. This never invents removals beyond the approved policy.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
hyv apply-hidden-text-policy draft.md policy.json output.md
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
- Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
|
|
19
|
+
- Commands that write use an explicit output path. Confirm that path before running the command.
|
|
20
|
+
- This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
|
|
21
|
+
|
|
22
|
+
## Handoff
|
|
23
|
+
|
|
24
|
+
Run `hyv apply-hidden-text-policy` directly to execute the operation. Follow-on agents: hyv-final-check.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0.0",
|
|
3
|
+
"id": "hyv-apply-hidden-text-policy",
|
|
4
|
+
"title": "hyv-apply-hidden-text-policy",
|
|
5
|
+
"description": "Apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining findings.",
|
|
6
|
+
"instruction_file": "SKILL.md",
|
|
7
|
+
"role": "Hidden-text editor",
|
|
8
|
+
"workflow_phase": "edit",
|
|
9
|
+
"input": {
|
|
10
|
+
"required": [
|
|
11
|
+
"draft path",
|
|
12
|
+
"policy path",
|
|
13
|
+
"output path"
|
|
14
|
+
],
|
|
15
|
+
"optional": []
|
|
16
|
+
},
|
|
17
|
+
"output": {
|
|
18
|
+
"required": [
|
|
19
|
+
"written output.md",
|
|
20
|
+
"change report"
|
|
21
|
+
],
|
|
22
|
+
"optional": []
|
|
23
|
+
},
|
|
24
|
+
"evidence_requirements": [
|
|
25
|
+
"Report only the deterministic result the command returns; never invent a model or provider call that did not occur."
|
|
26
|
+
],
|
|
27
|
+
"permissions": [
|
|
28
|
+
"execute_commands",
|
|
29
|
+
"read_repository",
|
|
30
|
+
"write_repository"
|
|
31
|
+
],
|
|
32
|
+
"stop_conditions": [
|
|
33
|
+
"Stop when required input is missing, when the command cannot run without changing existing behavior, or when a claimed capability is unavailable."
|
|
34
|
+
],
|
|
35
|
+
"tool_free_mode": {
|
|
36
|
+
"available": true,
|
|
37
|
+
"behavior": "Provide the decision, artifact template, and exact unavailable capability; never claim a tool action occurred.",
|
|
38
|
+
"unavailable_statuses": [
|
|
39
|
+
"NOT_AVAILABLE",
|
|
40
|
+
"NOT_CONFIGURED",
|
|
41
|
+
"NOT_RUN",
|
|
42
|
+
"STALE",
|
|
43
|
+
"ERROR"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"handoff_to": [
|
|
47
|
+
"hyv-final-check"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Hyv Apply Hidden Text Policy"
|
|
3
|
+
short_description: "Apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining findings."
|
|
4
|
+
default_prompt: "Use $hyv-apply-hidden-text-policy to apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining findings."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hyv-apply-rebuild
|
|
3
|
+
description: Validate and evaluate a whole-document rebuild response against a prepared authorized rebuild task.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hyv-apply-rebuild
|
|
7
|
+
|
|
8
|
+
Validate and evaluate a whole-document rebuild response against a prepared authorized rebuild task. Capability input requires host-guaranteed sensitive-input redaction. It never calls a provider.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
hyv apply-rebuild task.json response.json profile.json (--capability-stdin|--capability-file path)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
- Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
|
|
19
|
+
- Commands that write use an explicit output path. Confirm that path before running the command.
|
|
20
|
+
- This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
|
|
21
|
+
|
|
22
|
+
## Handoff
|
|
23
|
+
|
|
24
|
+
Run `hyv apply-rebuild` directly to execute the operation. Follow-on agents: hyv-verify, hyv-lifecycle.
|