@holdyourvoice/hyv 3.3.4 → 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 +85 -365
- 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/approval-capability.js +1 -4
- package/dist/approval-context.js +1 -1
- package/dist/cli.js +111 -2
- package/dist/cli.test.js +76 -0
- package/dist/copy-spec.js +1 -6
- package/dist/editorial-packs.js +1 -3
- package/dist/hidden-text.js +2 -12
- package/dist/internal.js +30 -0
- package/dist/judgment-task.js +1 -13
- package/dist/lifecycle-adapter.js +2 -1
- package/dist/mcp-tools.js +2 -1
- package/dist/pipeline.js +4 -12
- package/dist/profile.js +1 -3
- package/dist/rebuild-task.js +1 -16
- package/dist/rebuild-task.test.js +1 -1
- package/dist/release-audit.test.js +5 -1
- package/dist/rewrite-task.js +1 -5
- package/dist/semantic-review.js +1 -2
- 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
package/dist/approval-context.js
CHANGED
|
@@ -2,7 +2,7 @@ import { closeSync, constants, fstatSync, openSync, readSync } from 'node:fs';
|
|
|
2
2
|
import { userInfo } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { parseApprovalTrustStore } from './approval-capability.js';
|
|
5
|
-
|
|
5
|
+
import { MAX_JSON_BYTES as MAX_BYTES } from './internal.js';
|
|
6
6
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
7
7
|
function readBounded(descriptor) {
|
|
8
8
|
const chunks = [];
|
package/dist/cli.js
CHANGED
|
@@ -13,13 +13,14 @@ import { evaluateRewriteResponse, parseRewriteTask, prepareRewriteTask } from '.
|
|
|
13
13
|
import { parseJudgmentEnvelope, preparePostCandidateJudgment, preparePreEditJudgment, reducePostCandidate, reducePreEdit } from './judgment-task.js';
|
|
14
14
|
import { evaluateRebuildResponse, parseRebuildTask, prepareRebuildTask, writerRequestForRebuild } from './rebuild-task.js';
|
|
15
15
|
import { canonicalJson, parseCanonicalJson } from './canonical-json.js';
|
|
16
|
+
import { MAX_JSON_BYTES } from './internal.js';
|
|
16
17
|
import { finalizeLifecycle, inspectLifecycle, prepareLifecycle, recordApprovedLearning, submitSemanticVerdict, validateFinalApproval } from './lifecycle-adapter.js';
|
|
17
18
|
import { buildProfile } from './voice-dna.js';
|
|
18
19
|
import { loadApprovalContext } from './approval-context.js';
|
|
19
20
|
import { formatFactLintReport, lintFacts } from './fact-linter.js';
|
|
20
21
|
import { lintLogic } from './logic-linter.js';
|
|
21
|
-
|
|
22
|
-
const
|
|
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
|
+
});
|
package/dist/copy-spec.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { sentences } from './text.js';
|
|
2
|
+
import { escaped, isText } from './internal.js';
|
|
2
3
|
function normalized(value) {
|
|
3
4
|
return value.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
4
5
|
}
|
|
5
|
-
function escaped(value) {
|
|
6
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
-
}
|
|
8
6
|
function atomMatches(value, atom) {
|
|
9
7
|
return new RegExp(`(?<![\\p{L}\\p{N}\\p{M}])${escaped(atom)}(?![\\p{L}\\p{N}\\p{M}])`, 'u').test(value);
|
|
10
8
|
}
|
|
11
9
|
function isAtom(value) {
|
|
12
10
|
return isText(value, 500) && /[\p{L}\p{N}]/u.test(normalized(value));
|
|
13
11
|
}
|
|
14
|
-
function isText(value, limit) {
|
|
15
|
-
return typeof value === 'string' && value.trim().length > 0 && value.length <= limit;
|
|
16
|
-
}
|
|
17
12
|
function isClaim(value) {
|
|
18
13
|
if (!value || typeof value !== 'object')
|
|
19
14
|
return false;
|
package/dist/editorial-packs.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { paragraphs, sentences, words } from './text.js';
|
|
2
|
+
import { isText } from './internal.js';
|
|
2
3
|
const formats = ['general', 'social', 'deck', 'outreach', 'blog', 'audit', 'website'];
|
|
3
4
|
const evidenceStatuses = ['primary', 'attributed', 'internal', 'unverified'];
|
|
4
|
-
function isText(value, limit) {
|
|
5
|
-
return typeof value === 'string' && value.trim().length > 0 && value.length <= limit;
|
|
6
|
-
}
|
|
7
5
|
function isTerms(value) {
|
|
8
6
|
return Array.isArray(value) && value.length <= 100 && value.every((term) => isText(term, 200));
|
|
9
7
|
}
|
package/dist/hidden-text.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { canonicalJson } from './canonical-json.js';
|
|
1
|
+
import { fingerprint as hash } from './internal.js';
|
|
3
2
|
const ACKNOWLEDGEMENT = 'Removes only listed non-semantic controls; all other findings remain review-only.';
|
|
4
3
|
export const minimalHiddenTextPolicy = {
|
|
5
4
|
version: '1', name: 'minimal-text-control-cleanup', approvedRemovals: ['ascii_control', 'mid_document_bom'], acknowledgement: ACKNOWLEDGEMENT,
|
|
6
5
|
};
|
|
7
|
-
function hash(value) { return createHash('sha256').update(typeof value === 'string' ? value : canonicalJson(value)).digest('hex'); }
|
|
8
6
|
function codepoint(value) { return `U+${value.toString(16).toUpperCase().padStart(4, '0')}`; }
|
|
9
7
|
export function parseHiddenTextPolicy(value) {
|
|
10
8
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -50,15 +48,7 @@ export function inspectHiddenText(text, policy = minimalHiddenTextPolicy) {
|
|
|
50
48
|
}
|
|
51
49
|
export function applyHiddenTextPolicy(text, policy = minimalHiddenTextPolicy) {
|
|
52
50
|
const report = inspectHiddenText(text, policy);
|
|
53
|
-
const
|
|
54
|
-
let output = '';
|
|
55
|
-
for (let offset = 0; offset < text.length;) {
|
|
56
|
-
const value = text.codePointAt(offset);
|
|
57
|
-
const character = String.fromCodePoint(value);
|
|
58
|
-
if (!offsets.has(offset))
|
|
59
|
-
output += character;
|
|
60
|
-
offset += character.length;
|
|
61
|
-
}
|
|
51
|
+
const output = applyOnce(text, policy);
|
|
62
52
|
const remaining = inspectHiddenText(output, policy).findings;
|
|
63
53
|
const again = applyOnce(output, policy);
|
|
64
54
|
return { ...report, outputHash: hash(output), output, remaining, idempotent: again === output };
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from './canonical-json.js';
|
|
3
|
+
export const MAX_JSON_BYTES = 1024 * 1024;
|
|
4
|
+
export function sha256(value) {
|
|
5
|
+
return createHash('sha256').update(value).digest('hex');
|
|
6
|
+
}
|
|
7
|
+
export function digestCanonical(value) {
|
|
8
|
+
return sha256(canonicalJson(value));
|
|
9
|
+
}
|
|
10
|
+
export function fingerprint(value) {
|
|
11
|
+
return sha256(typeof value === 'string' ? value : canonicalJson(value));
|
|
12
|
+
}
|
|
13
|
+
export function profileIdentity(profile) {
|
|
14
|
+
if (profile.version === '3')
|
|
15
|
+
return { profileId: profile.id, profileRevisionDigest: profile.revisionDigest };
|
|
16
|
+
const legacy = `legacy-v2:${digestCanonical(profile)}`;
|
|
17
|
+
return { profileId: legacy, profileRevisionDigest: legacy };
|
|
18
|
+
}
|
|
19
|
+
export function isPlainObject(value) {
|
|
20
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
21
|
+
}
|
|
22
|
+
export function exactKeys(value, required, optional = []) {
|
|
23
|
+
return required.every((key) => key in value) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
|
|
24
|
+
}
|
|
25
|
+
export function isText(value, maximum = 256) {
|
|
26
|
+
return typeof value === 'string' && value.trim().length > 0 && value.length <= maximum;
|
|
27
|
+
}
|
|
28
|
+
export function escaped(value) {
|
|
29
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
30
|
+
}
|
package/dist/judgment-task.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { canonicalJson } from './canonical-json.js';
|
|
3
2
|
import { sentences } from './text.js';
|
|
4
3
|
import { HYV_VERSION } from './version.js';
|
|
4
|
+
import { profileIdentity, sha256 as digest } from './internal.js';
|
|
5
5
|
const PRE_EDIT_KINDS = ['triage', 'argument', 'form'];
|
|
6
6
|
const POST_CANDIDATE_KINDS = ['argument', 'polarity', 'form', 'flatness', 'semantic'];
|
|
7
|
-
function digest(value) {
|
|
8
|
-
return createHash('sha256').update(value).digest('hex');
|
|
9
|
-
}
|
|
10
|
-
function digestCanonical(value) {
|
|
11
|
-
return digest(canonicalJson(value));
|
|
12
|
-
}
|
|
13
|
-
function profileIdentity(profile) {
|
|
14
|
-
if (profile.version === '3')
|
|
15
|
-
return { profileId: profile.id, profileRevisionDigest: profile.revisionDigest };
|
|
16
|
-
const legacy = `legacy-v2:${digestCanonical(profile)}`;
|
|
17
|
-
return { profileId: legacy, profileRevisionDigest: legacy };
|
|
18
|
-
}
|
|
19
7
|
function fingerprintTask(task) {
|
|
20
8
|
return digest(`hyv:judgment-task:v1\0${canonicalJson(task)}`);
|
|
21
9
|
}
|
|
@@ -3,6 +3,7 @@ import { verifyApprovalCapability } from './approval-capability.js';
|
|
|
3
3
|
import { createInitialLifecycleArtifact, isValidLifecycleArtifact, parseSemanticVerdict, prepareSemanticReviewTask, reduceRewriteLifecycle } from './semantic-review.js';
|
|
4
4
|
import { verifyDeterministically } from './pipeline.js';
|
|
5
5
|
import { recordVerifiedCandidate } from './learning.js';
|
|
6
|
+
import { MAX_JSON_BYTES } from './internal.js';
|
|
6
7
|
export function prepareLifecycle(deterministic, binding, receipt, policy, allowedViolations) {
|
|
7
8
|
const task = prepareSemanticReviewTask(binding, policy, receipt, allowedViolations);
|
|
8
9
|
return { task, artifact: createInitialLifecycleArtifact(task, deterministic) };
|
|
@@ -58,7 +59,7 @@ export function finalizeLifecycle(artifact, decision, context, capability) {
|
|
|
58
59
|
}
|
|
59
60
|
export function recordApprovedLearning(request) {
|
|
60
61
|
const { ready, approved, decision, capability, source, candidate, profile, context, copySpec, writingBrief } = request;
|
|
61
|
-
if (Buffer.byteLength(source, 'utf8') >
|
|
62
|
+
if (Buffer.byteLength(source, 'utf8') > MAX_JSON_BYTES || Buffer.byteLength(candidate, 'utf8') > MAX_JSON_BYTES)
|
|
62
63
|
throw new Error('Approved learning text exceeds the byte limit.');
|
|
63
64
|
inspectLifecycle(ready);
|
|
64
65
|
inspectLifecycle(approved);
|
package/dist/mcp-tools.js
CHANGED
|
@@ -12,6 +12,7 @@ import { inspectHiddenText, applyHiddenTextPolicy, parseHiddenTextPolicy } from
|
|
|
12
12
|
import { buildProfile } from './voice-dna.js';
|
|
13
13
|
import { finalOutputCheck, inspectHygiene } from './hygiene.js';
|
|
14
14
|
import { finalizeLifecycle, inspectLifecycle, prepareLifecycle, recordApprovedLearning, submitSemanticVerdict, validateFinalApproval } from './lifecycle-adapter.js';
|
|
15
|
+
import { MAX_JSON_BYTES } from './internal.js';
|
|
15
16
|
function profileFromJson(profileJson) {
|
|
16
17
|
try {
|
|
17
18
|
return parseProfile(JSON.parse(profileJson));
|
|
@@ -103,7 +104,7 @@ export function logicLintForMcp(draft, writingBriefJson) {
|
|
|
103
104
|
return lintLogic(draft, writingBriefFromJson(writingBriefJson));
|
|
104
105
|
}
|
|
105
106
|
function parsed(json, label) {
|
|
106
|
-
if (Buffer.byteLength(json, 'utf8') >
|
|
107
|
+
if (Buffer.byteLength(json, 'utf8') > MAX_JSON_BYTES)
|
|
107
108
|
throw new Error(`${label} exceeds the byte limit.`);
|
|
108
109
|
try {
|
|
109
110
|
return JSON.parse(json);
|
package/dist/pipeline.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { canonicalJson } from './canonical-json.js';
|
|
3
2
|
import { HYV_VERSION } from './version.js';
|
|
4
3
|
import { analyzeAiEditor } from './ai-editor.js';
|
|
@@ -6,10 +5,11 @@ import { verifyClaims } from './copy-spec.js';
|
|
|
6
5
|
import { analyzeEditorial } from './editorial-packs.js';
|
|
7
6
|
import { finalOutputCheck, inspectHygiene } from './hygiene.js';
|
|
8
7
|
import { analyzeVoiceDna } from './voice-dna.js';
|
|
9
|
-
import { legacySetPreservation } from './preservation.js';
|
|
8
|
+
import { legacySetPreservation, LEGACY_SET_PRESERVATION_VERSION } from './preservation.js';
|
|
10
9
|
import { lintFacts } from './fact-linter.js';
|
|
11
10
|
import { lintLogic } from './logic-linter.js';
|
|
12
11
|
import { sentences } from './text.js';
|
|
12
|
+
import { digestCanonical, escaped as escapeRegex, profileIdentity, sha256 as digest } from './internal.js';
|
|
13
13
|
export function analyze(text, profile, brief) {
|
|
14
14
|
const voiceDna = analyzeVoiceDna(text, profile);
|
|
15
15
|
const aiEditor = analyzeAiEditor(text, profile);
|
|
@@ -101,7 +101,7 @@ function verifyRequiredFacts(candidate, brief) {
|
|
|
101
101
|
const reversed = brief.requiredFacts.filter((fact) => {
|
|
102
102
|
const terms = fact.atoms?.length ? fact.atoms : [fact.text];
|
|
103
103
|
return terms.some((term) => {
|
|
104
|
-
const escaped = term
|
|
104
|
+
const escaped = escapeRegex(term);
|
|
105
105
|
const quotedDenial = new RegExp(`${escaped}(?:["']|\\s)*(?:is|was|are|were)?\\s*(?:not|false|untrue|incorrect)`, 'i');
|
|
106
106
|
return quotedDenial.test(candidate) || draftSentences.some((sentence, index) => sentence.text.toLowerCase().includes(term.toLowerCase()) && (/\b(?:not|false|untrue|incorrect)\b/i.test(sentence.text) || /^(?:that|this) (?:statement|claim|fact|assertion|point) (?:is|was) (?:not|false|untrue|incorrect)\b/i.test(draftSentences[index + 1]?.text.trim() ?? '')));
|
|
107
107
|
});
|
|
@@ -153,19 +153,11 @@ export function verifyRebuildWithCopySpec(original, candidate, profile, spec, br
|
|
|
153
153
|
passed: checked.passed && !regressions.some(isBlockingFinding) && claims.passed && finalCheck.accepted && logicLint.passed && !factLint?.findings.some((finding) => finding.severity === 'error') && (requiredFacts?.passed ?? true),
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
-
function digest(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
157
|
-
function digestCanonical(value) { return digest(canonicalJson(value)); }
|
|
158
|
-
function profileIdentity(profile) {
|
|
159
|
-
if (profile.version === '3')
|
|
160
|
-
return { profileId: profile.id, profileRevisionDigest: profile.revisionDigest };
|
|
161
|
-
const legacy = `legacy-v2:${digestCanonical(profile)}`;
|
|
162
|
-
return { profileId: legacy, profileRevisionDigest: legacy };
|
|
163
|
-
}
|
|
164
156
|
function projectDeterministicVerificationArtifact(source, candidate, profile, verification, copySpec, writingBrief, verificationKind = 'claims' in verification ? 'copy_spec' : 'standard') {
|
|
165
157
|
const identity = profileIdentity(profile);
|
|
166
158
|
const base = {
|
|
167
159
|
version: '1', verificationKind, passed: verification.passed, analysisVersion: verification.candidate.version,
|
|
168
|
-
rulesetVersion: HYV_VERSION, preservationMetricVersion:
|
|
160
|
+
rulesetVersion: HYV_VERSION, preservationMetricVersion: LEGACY_SET_PRESERVATION_VERSION, preservationScore: verification.preservationScore,
|
|
169
161
|
sourceHash: digest(source), candidateHash: digest(candidate), ...identity,
|
|
170
162
|
...(copySpec ? { copySpecHash: digestCanonical(copySpec) } : {}), ...(writingBrief ? { writingBriefHash: digestCanonical(writingBrief) } : {}),
|
|
171
163
|
regressionKeys: verification.regressions.map((finding) => `${finding.engine}:${finding.id}:${finding.sentence}`).sort(),
|
package/dist/profile.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { canonicalJson } from './canonical-json.js';
|
|
3
|
+
import { isPlainObject } from './internal.js';
|
|
3
4
|
const METRICS_KEYS = ['sentenceLength', 'sentenceVariation', 'sentenceStructure', 'rhythm', 'paragraphLength', 'openingMoves', 'vocabulary', 'lexicalDensity', 'pointOfView', 'punctuation', 'caseStyle', 'questionRate', 'transitions'];
|
|
4
5
|
const PROFILE_V3_KEYS = ['version', 'id', 'revision', 'revisionDigest', 'sampleCount', 'metrics', 'avoid', 'provenance', 'rulePolicy', 'fingerprint', 'tolerances', 'metricFixtures'];
|
|
5
6
|
const FINGERPRINT_METRICS = ['contractionRate', 'sentenceLengthDistribution', 'bulletRate', 'enDashRate'];
|
|
6
7
|
const STABLE_ID = /^[a-z0-9](?:[a-z0-9._-]{0,127})$/;
|
|
7
|
-
function isPlainObject(value) {
|
|
8
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
9
|
-
}
|
|
10
8
|
function hasKnownKeys(value, keys) {
|
|
11
9
|
return Object.keys(value).every((key) => keys.includes(key)) && keys.every((key) => key in value);
|
|
12
10
|
}
|
package/dist/rebuild-task.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { canonicalJson } from './canonical-json.js';
|
|
3
2
|
import { parseCopySpec } from './copy-spec.js';
|
|
4
3
|
import { parseWritingBrief } from './editorial-packs.js';
|
|
@@ -10,29 +9,15 @@ import { sentences } from './text.js';
|
|
|
10
9
|
import { HYV_VERSION } from './version.js';
|
|
11
10
|
import { buildRecompositionBrief, measureLexicalResidual, parseRecompositionPolicy } from './recomposition.js';
|
|
12
11
|
import { provenanceStatusForRebuild, writerRequestForRebuild } from './provenance-status.js';
|
|
12
|
+
import { fingerprint, profileIdentity, sha256 as digest } from './internal.js';
|
|
13
13
|
const MAX_RESPONSE_BYTES = 100_000;
|
|
14
14
|
const MAX_CANDIDATE_CHARACTERS = 100_000;
|
|
15
|
-
function fingerprint(value) {
|
|
16
|
-
return createHash('sha256').update(typeof value === 'string' ? value : canonicalJson(value)).digest('hex');
|
|
17
|
-
}
|
|
18
|
-
function digest(value) {
|
|
19
|
-
return createHash('sha256').update(value).digest('hex');
|
|
20
|
-
}
|
|
21
|
-
function digestCanonical(value) {
|
|
22
|
-
return digest(canonicalJson(value));
|
|
23
|
-
}
|
|
24
15
|
function failure(code, message, path) {
|
|
25
16
|
return { code, message, ...(path ? { path } : {}) };
|
|
26
17
|
}
|
|
27
18
|
function isFailure(value) {
|
|
28
19
|
return typeof value === 'object' && value !== null && 'code' in value && 'message' in value;
|
|
29
20
|
}
|
|
30
|
-
function profileIdentity(profile) {
|
|
31
|
-
if (profile.version === '3')
|
|
32
|
-
return { profileId: profile.id, profileRevisionDigest: profile.revisionDigest };
|
|
33
|
-
const legacy = `legacy-v2:${digestCanonical(profile)}`;
|
|
34
|
-
return { profileId: legacy, profileRevisionDigest: legacy };
|
|
35
|
-
}
|
|
36
21
|
function parseJson(value) {
|
|
37
22
|
if (Buffer.byteLength(value) > MAX_RESPONSE_BYTES)
|
|
38
23
|
return failure('response_too_large', `Response exceeds ${MAX_RESPONSE_BYTES} bytes.`);
|
|
@@ -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/rewrite-task.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { canonicalJson } from './canonical-json.js';
|
|
3
1
|
import { parseWritingBrief } from './editorial-packs.js';
|
|
4
2
|
import { finalOutputCheck, hygieneSourceFindings } from './hygiene.js';
|
|
5
3
|
import { analyze, deriveEditScope, renderRewritePrompt, verifyDeterministically } from './pipeline.js';
|
|
6
4
|
import { sentences } from './text.js';
|
|
5
|
+
import { fingerprint } from './internal.js';
|
|
7
6
|
const MAX_RESPONSE_BYTES = 100_000;
|
|
8
7
|
const MAX_REPLACEMENTS = 100;
|
|
9
8
|
const MAX_REPLACEMENT_CHARACTERS = 10_000;
|
|
10
|
-
function fingerprint(value) {
|
|
11
|
-
return createHash('sha256').update(typeof value === 'string' ? value : canonicalJson(value)).digest('hex');
|
|
12
|
-
}
|
|
13
9
|
function failure(code, message, path) {
|
|
14
10
|
return { code, message, ...(path ? { path } : {}) };
|
|
15
11
|
}
|
package/dist/semantic-review.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { canonicalJson } from './canonical-json.js';
|
|
3
3
|
import { verifyApprovalCapability } from './approval-capability.js';
|
|
4
|
+
import { exactKeys as exact, isPlainObject as plain } from './internal.js';
|
|
4
5
|
const violations = new Set(['action_change', 'dropped_object', 'unsupported_claim', 'constraint_weakened', 'clarity_regression']);
|
|
5
6
|
const HEX = /^[a-f0-9]{64}$/;
|
|
6
7
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
7
8
|
function hash(domain, value) { return createHash('sha256').update(`${domain}\0`).update(canonicalJson(value)).digest('hex'); }
|
|
8
9
|
function same(left, right) { return canonicalJson(left) === canonicalJson(right); }
|
|
9
|
-
function exact(value, required, optional = []) { return required.every((key) => key in value) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key)); }
|
|
10
|
-
function plain(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype; }
|
|
11
10
|
function bindingValid(binding) {
|
|
12
11
|
return binding.schemaVersion === '1' && [binding.rewriteTaskFingerprint, binding.rewriteResponseFingerprint, binding.deterministicArtifactFingerprint, binding.sourceHash, binding.candidateHash].every((item) => HEX.test(item))
|
|
13
12
|
&& ID.test(binding.profileId) && ID.test(binding.profileRevisionDigest) && ID.test(binding.rulesetVersion);
|
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.
|