@holdyourvoice/hyv 3.6.0 → 3.6.1

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/dist/cli.js CHANGED
@@ -1,979 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { closeSync, constants, fstatSync, linkSync, lstatSync, mkdtempSync, openSync, readFileSync, readSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
3
- import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
4
- import { RULESET_VERSION, serializedRules } from './ai-editor.js';
5
- import { parseCopySpec } from './copy-spec.js';
6
- import { analyzeBatch, parseWritingBrief } from './editorial-packs.js';
7
- import { clearLearning, composeLearning, inspectLearning, migrateLearningV2ToV3, profileFingerprint, ratifyLearningEvent, recordLearningInstruction, supersedeLearningEvent } from './learning.js';
8
- import { cleanHygiene, finalOutputCheck, inspectHygiene } from './hygiene.js';
9
- import { applyHiddenTextPolicy, inspectHiddenText, parseHiddenTextPolicy } from './hidden-text.js';
10
- import { analyze, rewritePrompt, verify, verifyWithCopySpec } from './pipeline.js';
11
- import { parseProfile } from './profile.js';
12
- import { evaluateRewriteResponse, parseRewriteTask, prepareRewriteTask } from './rewrite-task.js';
13
- import { parseJudgmentEnvelope, preparePostCandidateJudgment, preparePreEditJudgment, reducePostCandidate, reducePreEdit } from './judgment-task.js';
14
- import { evaluateRebuildResponse, parseRebuildTask, prepareRebuildTask, writerRequestForRebuild } from './rebuild-task.js';
15
- import { canonicalJson, parseCanonicalJson } from './canonical-json.js';
16
- import { MAX_JSON_BYTES } from './internal.js';
17
- import { finalizeLifecycle, inspectLifecycle, prepareLifecycle, recordApprovedLearning, submitSemanticVerdict, validateFinalApproval } from './lifecycle-adapter.js';
18
- import { buildProfile, buildProfileV3 } from './voice-dna.js';
19
- import { loadApprovalContext } from './approval-context.js';
20
- import { formatFactLintReport, lintFacts } from './fact-linter.js';
21
- import { lintLogic } from './logic-linter.js';
22
- import { loadAll, validateAll, validateId, sortedIds, describe, emitJson, emitPrompt } from './agents/index.js';
23
- import { inspectDeliveryIntegrity, parseDeliveryIntegrityPolicy } from './delivery-integrity.js';
24
- import { assessProfileReadiness } from './profile-quality.js';
25
- import { evaluateStrictQuality } from './strict-quality.js';
26
- import { normalizeFinding, parseSurfacePolicy } from './disposition.js';
27
- import { composeTeamProfile, parseTeamProfileBundle } from './team-profile.js';
28
- import { composeProfiles, parseProfileRatio } from './profile-compose.js';
29
- import { scoreHeldoutProfile } from './profile-score.js';
30
- import { ingestGmailSentMbox, ingestTelegramDesktopJson } from './sample-ingest.js';
31
- import { evaluateIsolatedBacktest } from './backtest.js';
32
- import { watchProfileSamples } from './profile-watch.js';
33
- import { evaluateLocalComposite } from './local-eval.js';
34
- import { findWritingExamples } from './writing-examples.js';
2
+ import { runAgent } from './cli/agents.js';
3
+ import { runProfile, runScore, runBacktest, runEvaluateLocal, runIngest, runTeamProfile } from './cli/profiles.js';
4
+ import { runDeliveryCheck, runAnalyze, runStrictCheck, runHygiene, runInspectHiddenText, runApplyHiddenTextPolicy, runFinalCheck, runFactLint, runLogicLint, runBatchAnalyze, runVerify, runVerifySpec, runPatterns, runDispositions } from './cli/checks.js';
5
+ import { runRewritePrompt, runPrepareRewrite, runApplyRewrite, runPrepareJudgment, runReduceJudgment, runPrepareRebuild, runApplyRebuild, runRebuildWriterRequest } from './cli/rewriting.js';
6
+ import { runLifecycle, runLearning } from './cli/lifecycle.js';
35
7
  const usage = 'Commands: agent, profile, team-profile, analyze, score, backtest, evaluate-local, ingest, strict-check, hygiene, inspect-hidden-text, apply-hidden-text-policy, final-check, delivery-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, dispositions, mcp';
36
- function input(path) {
37
- return path === '-' ? readFileSync(0, 'utf8') : readFileSync(path, 'utf8');
38
- }
39
- function parseBoundedJson(text) {
40
- if (Buffer.byteLength(text, 'utf8') > MAX_JSON_BYTES)
41
- throw new Error('JSON input exceeds the byte limit.');
42
- const value = JSON.parse(text);
43
- canonicalJson(value);
44
- return value;
45
- }
46
- function readBoundedDescriptor(descriptor) {
47
- const chunks = [];
48
- let size = 0;
49
- while (size <= MAX_JSON_BYTES) {
50
- const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, MAX_JSON_BYTES + 1 - size));
51
- const count = readSync(descriptor, chunk, 0, chunk.length, null);
52
- if (!count)
53
- break;
54
- chunks.push(chunk.subarray(0, count));
55
- size += count;
56
- }
57
- if (size > MAX_JSON_BYTES)
58
- throw new Error('JSON input exceeds the byte limit.');
59
- return Buffer.concat(chunks, size).toString('utf8');
60
- }
61
- function readJson(path) {
62
- if (path === '-')
63
- return parseBoundedJson(readBoundedDescriptor(0));
64
- let descriptor;
65
- try {
66
- descriptor = openSync(path, constants.O_RDONLY);
67
- return parseBoundedJson(readBoundedDescriptor(descriptor));
68
- }
69
- finally {
70
- if (descriptor !== undefined)
71
- closeSync(descriptor);
72
- }
73
- }
74
- function capabilityArguments(args) {
75
- const values = [];
76
- let source;
77
- for (let index = 0; index < args.length; index += 1) {
78
- if (args[index] === '--capability-stdin') {
79
- if (source)
80
- throw new Error('Choose one capability source.');
81
- source = { kind: 'stdin' };
82
- continue;
83
- }
84
- if (args[index] === '--capability-file') {
85
- const path = args[index + 1];
86
- if (source || !path || path.startsWith('--capability-'))
87
- throw new Error('Choose one capability source.');
88
- source = { kind: 'file', path };
89
- index += 1;
90
- continue;
91
- }
92
- values.push(args[index]);
93
- }
94
- if (!source)
95
- return { values };
96
- if (source.kind === 'stdin' && values.includes('-'))
97
- throw new Error('Capability stdin cannot be combined with another stdin input.');
98
- let raw;
99
- if (source.kind === 'stdin')
100
- raw = readBoundedDescriptor(0);
101
- else {
102
- let descriptor;
103
- try {
104
- descriptor = openSync(source.path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
105
- const before = fstatSync(descriptor);
106
- if (!before.isFile() || before.uid !== process.geteuid?.() || (before.mode & 0o077) !== 0 || before.nlink !== 1 || before.size > MAX_JSON_BYTES)
107
- throw new Error('Capability file is unavailable or unsafe.');
108
- raw = readBoundedDescriptor(descriptor);
109
- const after = fstatSync(descriptor);
110
- if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs)
111
- throw new Error('Capability file is unavailable or unsafe.');
112
- }
113
- catch {
114
- throw new Error('Capability file is unavailable or unsafe.');
115
- }
116
- finally {
117
- if (descriptor !== undefined)
118
- closeSync(descriptor);
119
- }
120
- }
121
- if (Buffer.byteLength(raw, 'utf8') > MAX_JSON_BYTES)
122
- throw new Error('JSON input exceeds the byte limit.');
123
- return { values, capability: parseCanonicalJson(Buffer.from(raw, 'utf8')) };
124
- }
125
- function rebuildArguments(args) {
126
- const values = [];
127
- let policyPath;
128
- for (let index = 0; index < args.length; index += 1) {
129
- if (args[index] !== '--recomposition-policy') {
130
- values.push(args[index]);
131
- continue;
132
- }
133
- const path = args[index + 1];
134
- if (policyPath || !path || path === '-' || path.startsWith('--'))
135
- throw new Error('Choose one recomposition policy file.');
136
- policyPath = path;
137
- index += 1;
138
- }
139
- const capability = capabilityArguments(values);
140
- return { ...capability, ...(policyPath ? { recompositionPolicy: readJson(policyPath) } : {}) };
141
- }
142
- function readProfile(path) {
143
- return parseProfile(JSON.parse(input(path)));
144
- }
145
- function requireProfileV3(profile) {
146
- if (profile.version !== '3')
147
- throw new Error('This learning operation requires a Profile v3.');
148
- return profile;
149
- }
150
- function learningArguments(args) {
151
- const values = [];
152
- const options = {};
153
- for (const argument of args) {
154
- if (!argument.startsWith('--')) {
155
- values.push(argument);
156
- continue;
157
- }
158
- const [name, ...parts] = argument.slice(2).split('=');
159
- const value = parts.join('=').trim();
160
- if (!value)
161
- throw new Error(`Learning option --${name} requires a value.`);
162
- if (name === 'mutation-id' && value.length <= 200)
163
- options.mutationId = value;
164
- else if (name === 'authority' && ['founder', 'team', 'system'].includes(value))
165
- options.authority = value;
166
- else if (name === 'provenance' && value.length <= 500)
167
- options.provenance = value;
168
- else if (name === 'weight' && Number.isFinite(Number(value)) && Number(value) > 0)
169
- options.weight = Number(value);
170
- else if (name === 'compatibility' && ['same-or-newer', 'exact'].includes(value))
171
- options.compatibility = value;
172
- else
173
- throw new Error(`Invalid learning option: --${name}=${value}`);
174
- }
175
- return { values, options };
176
- }
177
- function readBrief(path) {
178
- return path ? parseWritingBrief(JSON.parse(input(path))) : undefined;
179
- }
180
- function prepareContext(paths) {
181
- let copySpec;
182
- let writingBrief;
183
- for (const path of paths) {
184
- const value = JSON.parse(input(path));
185
- try {
186
- const parsed = parseCopySpec(value);
187
- if (copySpec)
188
- throw new Error('Prepare-rewrite accepts at most one CopySpec.');
189
- copySpec = parsed;
190
- continue;
191
- }
192
- catch (error) {
193
- if (error instanceof Error && error.message === 'Prepare-rewrite accepts at most one CopySpec.')
194
- throw error;
195
- }
196
- try {
197
- const parsed = parseWritingBrief(value);
198
- if (writingBrief)
199
- throw new Error('Prepare-rewrite accepts at most one WritingBrief.');
200
- writingBrief = parsed;
201
- }
202
- catch (error) {
203
- if (error instanceof Error && error.message === 'Prepare-rewrite accepts at most one WritingBrief.')
204
- throw error;
205
- throw new Error(`Expected a valid CopySpec or WritingBrief at ${path}.`);
206
- }
207
- }
208
- return { copySpec, writingBrief };
209
- }
210
- function json(value) {
211
- console.log(JSON.stringify(value, null, 2));
212
- }
213
- function canonical(value) { process.stdout.write(`${canonicalJson(value)}\n`); }
214
- function writeJson(path, value) {
215
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
216
- }
217
- function profileArguments(args) {
218
- const [output, ...rest] = args;
219
- const samples = [];
220
- const avoid = [];
221
- for (const argument of rest) {
222
- if (argument.startsWith('--avoid=')) {
223
- const phrase = argument.slice('--avoid='.length).trim();
224
- if (!phrase)
225
- throw new Error('Avoid phrases must use --avoid=phrase.');
226
- avoid.push(phrase);
227
- }
228
- else {
229
- samples.push(argument);
230
- }
231
- }
232
- if (!output || samples.length < 2)
233
- throw new Error('Usage: hyv profile profile.json sample-a.md sample-b.md [sample-c.md] [--avoid=phrase]');
234
- return { output, samples, avoid };
235
- }
236
- function cleanedPath(path) {
237
- const extension = extname(path);
238
- const stem = extension ? path.slice(0, -extension.length) : path;
239
- return `${stem}.cleaned${extension}`;
240
- }
241
- function hygieneArguments(args) {
242
- const [path, ...options] = args;
243
- if (!path)
244
- throw new Error('Usage: hyv hygiene draft.md [--fix] [--output=cleaned.md]');
245
- let fix = false;
246
- let output;
247
- for (const option of options) {
248
- if (option === '--fix')
249
- fix = true;
250
- else if (option.startsWith('--output='))
251
- output = option.slice('--output='.length).trim();
252
- else
253
- throw new Error('Usage: hyv hygiene draft.md [--fix] [--output=cleaned.md]');
254
- }
255
- if (output !== undefined && (!output || !fix))
256
- throw new Error('--output requires --fix and a non-empty path.');
257
- return { path, fix, ...(output ? { output } : {}) };
258
- }
259
- function writeNewFileAtomically(path, text) {
260
- const temporaryDirectory = mkdtempSync(join(dirname(resolve(path)), '.hyv-hygiene-'));
261
- const temporaryPath = join(temporaryDirectory, 'cleaned');
262
- let primaryError;
263
- try {
264
- writeFileSync(temporaryPath, text, 'utf8');
265
- try {
266
- linkSync(temporaryPath, path);
267
- }
268
- catch (error) {
269
- const code = error.code;
270
- if (!['EPERM', 'ENOTSUP', 'EOPNOTSUPP', 'EXDEV'].includes(code ?? ''))
271
- throw error;
272
- throw new Error(`Atomic hygiene output is not supported by this filesystem: ${path}`);
273
- }
274
- }
275
- catch (error) {
276
- primaryError = error.code === 'EEXIST' ? new Error(`Hygiene output already exists: ${path}`) : error;
277
- }
278
- try {
279
- rmSync(temporaryDirectory, { recursive: true, force: true });
280
- }
281
- catch (error) {
282
- if (!primaryError)
283
- console.error(`Warning: output was published, but temporary-file cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
284
- }
285
- if (primaryError)
286
- throw primaryError;
287
- }
288
- function agentFlags(args) {
289
- const values = [];
290
- let host = 'generic';
291
- let mode;
292
- let output;
293
- for (let index = 0; index < args.length; index += 1) {
294
- const argument = args[index];
295
- if (argument === '--host') {
296
- const value = args[index + 1];
297
- if (!value || value.startsWith('--'))
298
- throw new Error('Usage: hyv agent --host HOST requires a value.');
299
- host = value;
300
- index += 1;
301
- }
302
- else if (argument.startsWith('--host=')) {
303
- const value = argument.slice('--host='.length);
304
- if (!value)
305
- throw new Error('Usage: hyv agent --host HOST requires a value.');
306
- host = value;
307
- }
308
- else if (argument === '--mode') {
309
- const value = args[index + 1];
310
- if (value !== 'prompt' && value !== 'json')
311
- throw new Error('Usage: hyv agent --mode prompt|json requires a mode.');
312
- mode = value;
313
- index += 1;
314
- }
315
- else if (argument.startsWith('--mode=')) {
316
- const value = argument.slice('--mode='.length);
317
- if (value !== 'prompt' && value !== 'json')
318
- throw new Error('Usage: hyv agent --mode prompt|json requires a mode.');
319
- mode = value;
320
- }
321
- else if (argument === '--output') {
322
- const value = args[index + 1];
323
- if (!value || value.startsWith('--'))
324
- throw new Error('Usage: hyv agent --output FILE requires a value.');
325
- output = value;
326
- index += 1;
327
- }
328
- else if (argument.startsWith('--output=')) {
329
- const value = argument.slice('--output='.length);
330
- if (!value)
331
- throw new Error('Usage: hyv agent --output FILE requires a value.');
332
- output = value;
333
- }
334
- else {
335
- values.push(argument);
336
- }
337
- }
338
- return { values, host, mode, output };
339
- }
340
- function runAgent(args) {
341
- const [subcommand, ...subargs] = args;
342
- if (subcommand === 'list') {
343
- if (subargs.length)
344
- throw new Error('Usage: hyv agent list');
345
- const packages = loadAll();
346
- const ids = sortedIds(packages);
347
- json(ids.map((id) => {
348
- const descriptor = packages.get(id).descriptor;
349
- return { id, role: descriptor.role, workflow_phase: descriptor.workflow_phase, description: descriptor.description };
350
- }));
351
- return 0;
352
- }
353
- if (subcommand === 'validate') {
354
- if (subargs.length > 1)
355
- throw new Error('Usage: hyv agent validate [id]');
356
- const packages = loadAll();
357
- const id = subargs[0];
358
- if (id !== undefined)
359
- validateId(packages, id);
360
- validateAll(packages);
361
- json({ schema_version: '1.0.0', status: 'PASS', agent: id ?? 'all' });
362
- return 0;
363
- }
364
- if (subcommand === 'describe') {
365
- const { values, host, mode, output } = agentFlags(subargs);
366
- if (values.length !== 1 || mode !== undefined || output !== undefined)
367
- throw new Error('Usage: hyv agent describe <id> [--host HOST]');
368
- const packages = loadAll();
369
- validateId(packages, values[0]);
370
- json(describe(packages.get(values[0]), host));
371
- return 0;
372
- }
373
- if (subcommand === 'emit') {
374
- const { values, host, mode, output } = agentFlags(subargs);
375
- if (values.length !== 1)
376
- throw new Error('Usage: hyv agent emit <id> --mode prompt|json [--host HOST] [--output FILE]');
377
- if (!mode)
378
- throw new Error('Usage: hyv agent emit <id> --mode prompt|json [--host HOST] [--output FILE]');
379
- const packages = loadAll();
380
- validateId(packages, values[0]);
381
- const pkg = packages.get(values[0]);
382
- const body = mode === 'prompt' ? emitPrompt(pkg, host) : `${emitJson(pkg, host)}\n`;
383
- if (output !== undefined) {
384
- writeFileSync(output, body, { encoding: 'utf8', flag: 'wx' });
385
- }
386
- else {
387
- process.stdout.write(body);
388
- }
389
- return 0;
390
- }
391
- throw new Error('Usage: hyv agent <list|validate|describe|emit> ...');
392
- }
393
- async function runProfileWatch(args) {
394
- const [output, ...rest] = args;
395
- const samples = [];
396
- let id = '';
397
- let channel;
398
- let debounceMs = 500;
399
- for (const value of rest) {
400
- if (value.startsWith('--id='))
401
- id = value.slice('--id='.length);
402
- else if (value.startsWith('--channel='))
403
- channel = value.slice('--channel='.length);
404
- else if (value.startsWith('--debounce-ms='))
405
- debounceMs = Number(value.slice('--debounce-ms='.length));
406
- else
407
- samples.push(value);
408
- }
409
- if (!output || !isAbsolute(output) || !id || !channel || samples.length < 2)
410
- throw new Error('Usage: hyv profile watch /absolute/profile.json --id=writer.channel --channel=email sample-a.md sample-b.md [--debounce-ms=500]');
411
- outputOutsideGitCheckout(dirname(output));
412
- let initial = true;
413
- const rebuild = () => {
414
- const profile = buildProfileV3(samples.map(input), id, channel);
415
- writeFileSync(output, JSON.stringify(profile, null, 2) + '\n', { encoding: 'utf8', flag: initial ? 'wx' : 'w', mode: 0o600 });
416
- initial = false;
417
- json({ version: '1', status: 'rebuilt', sampleCount: samples.length, profileId: profile.id, revisionDigest: profile.revisionDigest });
418
- };
419
- rebuild();
420
- const handle = watchProfileSamples({ samples, debounceMs, rebuild });
421
- await new Promise((resolve) => process.once('SIGINT', resolve));
422
- handle.close();
423
- return 0;
424
- }
425
- function runProfile(args) {
426
- if (args[0] === 'watch')
427
- return runProfileWatch(args.slice(1));
428
- if (args[0] === 'assess') {
429
- if (args.length < 3)
430
- throw new Error('Usage: hyv profile assess sample-a.md sample-b.md [sample-c.md]');
431
- json(assessProfileReadiness(args.slice(1).map(input)));
432
- return 0;
433
- }
434
- if (args[0] === 'compose') {
435
- const rest = args.slice(1);
436
- const ratioIndex = rest.findIndex((value) => value === '--ratio' || value.startsWith('--ratio='));
437
- if (ratioIndex < 0)
438
- throw new Error('Usage: hyv profile compose --ratio 70:30 profile-a.json profile-b.json [profile-c.json]');
439
- const ratio = rest[ratioIndex] === '--ratio' ? rest[ratioIndex + 1] : rest[ratioIndex].slice('--ratio='.length);
440
- const profilePaths = rest.filter((_, index) => index !== ratioIndex && index !== ratioIndex + Number(rest[ratioIndex] === '--ratio'));
441
- if (!ratio || profilePaths.length < 2)
442
- throw new Error('Usage: hyv profile compose --ratio 70:30 profile-a.json profile-b.json [profile-c.json]');
443
- const profiles = profilePaths.map(readProfile);
444
- if (profiles.some((profile) => profile.version !== '3'))
445
- throw new Error('Profile composition requires Profile v3 inputs.');
446
- json(composeProfiles(profiles, parseProfileRatio(ratio, profiles.length)));
447
- return 0;
448
- }
449
- if (args[0] === 'v3') {
450
- const [output, ...rest] = args.slice(1);
451
- const samples = [];
452
- const avoid = [];
453
- let id = '';
454
- let channel;
455
- let tone;
456
- for (const argument of rest) {
457
- if (argument.startsWith('--id='))
458
- id = argument.slice('--id='.length);
459
- else if (argument.startsWith('--channel='))
460
- channel = argument.slice('--channel='.length);
461
- else if (argument.startsWith('--avoid='))
462
- avoid.push(argument.slice('--avoid='.length));
463
- else if (argument.startsWith('--tone=')) {
464
- const values = argument.slice('--tone='.length).split(',').map(Number);
465
- if (values.length !== 5 || values.some((value) => !Number.isFinite(value) || value < 0 || value > 1))
466
- throw new Error('Tone must use five 0–1 comma-separated values: formality,confidence,warmth,energy,complexity.');
467
- tone = { formality: values[0], confidence: values[1], warmth: values[2], energy: values[3], complexity: values[4] };
468
- }
469
- else
470
- samples.push(argument);
471
- }
472
- if (!output || !id || !channel || samples.length < 2)
473
- throw new Error('Usage: hyv profile v3 profile.json --id=writer.channel --channel=email sample-a.md sample-b.md [--tone=0,0,0,0,0] [--avoid=phrase]');
474
- writeJson(output, buildProfileV3(samples.map(input), id, channel, avoid, tone));
475
- return 0;
476
- }
477
- const { output, samples, avoid } = profileArguments(args);
478
- writeJson(output, buildProfile(samples.map(input), avoid));
479
- return 0;
480
- }
481
- function runScore(args) {
482
- const [draftPath, profilePath, ...rest] = args;
483
- if (!draftPath || !profilePath)
484
- throw new Error('Usage: hyv score draft.md profile.json heldout-a.md heldout-b.md heldout-c.md [--channel=channel]');
485
- const samplePaths = [];
486
- let channel;
487
- for (const value of rest) {
488
- if (value.startsWith('--channel='))
489
- channel = value.slice('--channel='.length);
490
- else
491
- samplePaths.push(value);
492
- }
493
- if (samplePaths.length < 3)
494
- throw new Error('Usage: hyv score draft.md profile.json heldout-a.md heldout-b.md heldout-c.md [--channel=channel]');
495
- json(scoreHeldoutProfile(input(draftPath), readProfile(profilePath), samplePaths.map(input), channel));
496
- return 0;
497
- }
498
- function runBacktest(args) {
499
- const [contextPath, targetPath, candidatePath, profilePath, ...heldoutPaths] = args;
500
- if (!contextPath || !targetPath || !candidatePath || !profilePath || heldoutPaths.length < 3)
501
- throw new Error('Usage: hyv backtest context.md heldout-target.md candidate.md profile.json heldout-a.md heldout-b.md heldout-c.md');
502
- json(evaluateIsolatedBacktest(input(contextPath), input(targetPath), input(candidatePath), readProfile(profilePath), heldoutPaths.map(input)));
503
- return 0;
504
- }
505
- function readEvalParagraphs(path, label) {
506
- const value = readJson(path);
507
- if (!Array.isArray(value) || !value.every((item) => item && typeof item === 'object' && typeof item.paragraph_id === 'string' && typeof item.text === 'string'))
508
- throw new Error(`${label} must be a JSON array of { paragraph_id, text } values.`);
509
- return value.map((item) => ({ paragraphId: item.paragraph_id, text: item.text }));
510
- }
511
- function runEvaluateLocal(args) {
512
- const [inputPath, candidatePath, userPath, aiShadowPath] = args;
513
- if (!inputPath || !candidatePath || !userPath || !aiShadowPath || args.length !== 4)
514
- throw new Error('Usage: hyv evaluate-local input.md candidate.md user-paragraphs.json ai-shadow-paragraphs.json');
515
- json(evaluateLocalComposite(input(inputPath), input(candidatePath), readEvalParagraphs(userPath, 'User paragraphs'), readEvalParagraphs(aiShadowPath, 'AI-shadow paragraphs')));
516
- return 0;
517
- }
518
- function outputOutsideGitCheckout(path) {
519
- if (!isAbsolute(path))
520
- throw new Error('Sample ingest output must be an absolute path outside a Git checkout.');
521
- let current;
522
- try {
523
- const stats = lstatSync(path);
524
- if (!stats.isDirectory() || stats.isSymbolicLink())
525
- throw new Error('Sample ingest output directory must be an existing non-symlink directory outside a Git checkout.');
526
- current = realpathSync(path);
527
- }
528
- catch (error) {
529
- if (error instanceof Error && error.message.includes('Sample ingest output directory'))
530
- throw error;
531
- throw new Error('Sample ingest output directory must already exist and remain outside a Git checkout.');
532
- }
533
- for (;;) {
534
- try {
535
- lstatSync(join(current, '.git'));
536
- throw new Error('Sample ingest output must be outside a Git checkout.');
537
- }
538
- catch (error) {
539
- if (error instanceof Error && error.message === 'Sample ingest output must be outside a Git checkout.')
540
- throw error;
541
- }
542
- const parent = dirname(current);
543
- if (parent === current)
544
- return;
545
- current = parent;
546
- }
547
- }
548
- function runIngest(args) {
549
- const [sourceType, sourcePath, ...options] = args;
550
- let owner = '';
551
- let output = '';
552
- const blockedWords = [];
553
- for (const option of options) {
554
- if (option.startsWith('--owner='))
555
- owner = option.slice('--owner='.length);
556
- else if (option.startsWith('--output='))
557
- output = option.slice('--output='.length);
558
- else if (option.startsWith('--blocked='))
559
- blockedWords.push(option.slice('--blocked='.length));
560
- else
561
- throw new Error('Usage: hyv ingest <gmail-sent-mbox|telegram-desktop-json> export --owner=owner --output=/absolute/safe-directory [--blocked=word]');
562
- }
563
- if (!sourcePath || !owner || !output || !['gmail-sent-mbox', 'telegram-desktop-json'].includes(sourceType ?? ''))
564
- throw new Error('Usage: hyv ingest <gmail-sent-mbox|telegram-desktop-json> export --owner=owner --output=/absolute/safe-directory [--blocked=word]');
565
- outputOutsideGitCheckout(output);
566
- const result = sourceType === 'gmail-sent-mbox'
567
- ? ingestGmailSentMbox(input(sourcePath), owner, blockedWords)
568
- : ingestTelegramDesktopJson(input(sourcePath), owner, blockedWords);
569
- const outputDirectory = realpathSync(output);
570
- const samplesPath = join(outputDirectory, 'samples.jsonl');
571
- const receiptPath = join(outputDirectory, 'receipt.json');
572
- try {
573
- writeFileSync(samplesPath, result.samples.map((sample) => JSON.stringify({ text: sample })).join('\n') + (result.samples.length ? '\n' : ''), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
574
- }
575
- catch (error) {
576
- if (error.code === 'ENOENT')
577
- throw new Error('Sample ingest output directory must already exist and remain outside a Git checkout.');
578
- throw error;
579
- }
580
- try {
581
- writeFileSync(receiptPath, JSON.stringify(result.receipt, null, 2) + '\n', { encoding: 'utf8', flag: 'wx', mode: 0o600 });
582
- }
583
- catch (error) {
584
- try {
585
- rmSync(samplesPath, { force: true });
586
- }
587
- catch { }
588
- throw error;
589
- }
590
- json(result.receipt);
591
- return 0;
592
- }
593
- function runTeamProfile(args) {
594
- const [action, bundlePath, authorPath, ...brandPaths] = args;
595
- if (action === 'validate' && bundlePath && !authorPath) {
596
- json(parseTeamProfileBundle(readJson(bundlePath)));
597
- return 0;
598
- }
599
- if (action === 'compose' && bundlePath && authorPath) {
600
- const brands = brandPaths.map(readProfile).filter((profile) => profile.version === '3');
601
- if (brands.length !== brandPaths.length)
602
- throw new Error('Team brand profiles must use Profile v3.');
603
- json(composeTeamProfile(readProfile(authorPath), brands, parseTeamProfileBundle(readJson(bundlePath))));
604
- return 0;
605
- }
606
- throw new Error('Usage: hyv team-profile <validate bundle.json|compose bundle.json author-profile.json [brand-profile.json...]>');
607
- }
608
- function runDeliveryCheck(args) {
609
- const [path, policyPath, ...extra] = args;
610
- if (!path || extra.length)
611
- throw new Error('Usage: hyv delivery-check <path|-> [policy.json]');
612
- const report = inspectDeliveryIntegrity(input(path), policyPath ? parseDeliveryIntegrityPolicy(readJson(policyPath)) : undefined, process.cwd());
613
- json(report);
614
- return report.passed ? 0 : 2;
615
- }
616
- function runAnalyze(args) {
617
- const [draft, profilePath, briefPath] = args;
618
- if (!draft || !profilePath)
619
- throw new Error('Usage: hyv analyze draft.md profile.json [writing-brief.json]');
620
- json(analyze(input(draft), readProfile(profilePath), readBrief(briefPath)));
621
- return 0;
622
- }
623
- function runStrictCheck(args) {
624
- const [draft, profilePath, ...samplePaths] = args;
625
- if (!draft || !profilePath || samplePaths.length < 2)
626
- throw new Error('Usage: hyv strict-check draft.md profile-v3.json sample-a.md sample-b.md [sample-c.md ...]');
627
- const report = evaluateStrictQuality(input(draft), readProfile(profilePath), samplePaths.map(input));
628
- json(report);
629
- return report.disposition === 'strict-ready' ? 0 : 2;
630
- }
631
- function runHygiene(args) {
632
- const { path, fix, output } = hygieneArguments(args);
633
- if (fix && path === '-')
634
- throw new Error('hyv hygiene --fix requires a file path so the original can be preserved.');
635
- const text = input(path);
636
- if (!fix) {
637
- json(inspectHygiene(text));
638
- return 0;
639
- }
640
- const outputPath = output ?? cleanedPath(path);
641
- if (resolve(outputPath) === resolve(path))
642
- throw new Error('Hygiene output must differ from the input path.');
643
- const result = cleanHygiene(text);
644
- writeNewFileAtomically(outputPath, result.cleaned);
645
- json({ ...result.report, changed: result.changed, changes: result.changes, outputPath });
646
- return 0;
647
- }
648
- function runInspectHiddenText(args) {
649
- const [path, policyPath, ...extra] = args;
650
- if (!path || extra.length)
651
- throw new Error('Usage: hyv inspect-hidden-text draft.md [policy.json]');
652
- json(inspectHiddenText(input(path), policyPath ? parseHiddenTextPolicy(readJson(policyPath)) : undefined));
653
- return 0;
654
- }
655
- function runApplyHiddenTextPolicy(args) {
656
- const [path, policyPath, output, ...extra] = args;
657
- if (!path || !policyPath || !output || extra.length)
658
- throw new Error('Usage: hyv apply-hidden-text-policy draft.md policy.json output.md');
659
- if (path === '-' || resolve(path) === resolve(output))
660
- throw new Error('Hidden-text output must differ from the input path.');
661
- const result = applyHiddenTextPolicy(input(path), parseHiddenTextPolicy(readJson(policyPath)));
662
- writeNewFileAtomically(output, result.output);
663
- json({ ...result, outputPath: output });
664
- return 0;
665
- }
666
- function runFinalCheck(args) {
667
- const [path, ...options] = args;
668
- if (!path || options.length)
669
- throw new Error('Usage: hyv final-check <path|->');
670
- const result = finalOutputCheck(input(path));
671
- if (!result.accepted) {
672
- console.error(JSON.stringify(result, null, 2));
673
- return 2;
674
- }
675
- if (result.changed)
676
- console.error(JSON.stringify({ changed: true, changes: result.changes }, null, 2));
677
- process.stdout.write(result.output);
678
- return 0;
679
- }
680
- function runFactLint(args) {
681
- const [draftPath, ...options] = args;
682
- const sources = [];
683
- let metadata;
684
- let strict = false;
685
- let human = false;
686
- if (!draftPath)
687
- throw new Error('Usage: hyv fact-lint <draft|-> --source=id:path [--source=id:path] [--metadata=metadata.json] [--strict] [--human]');
688
- for (const option of options) {
689
- if (option === '--strict') {
690
- strict = true;
691
- continue;
692
- }
693
- if (option === '--human') {
694
- human = true;
695
- continue;
696
- }
697
- if (option.startsWith('--source=')) {
698
- const value = option.slice('--source='.length);
699
- const separator = value.indexOf(':');
700
- const id = value.slice(0, separator).trim();
701
- const path = value.slice(separator + 1);
702
- if (separator < 1 || !id || !path)
703
- throw new Error('Sources must use --source=id:path.');
704
- sources.push({ id, text: input(path) });
705
- continue;
706
- }
707
- if (option.startsWith('--metadata=')) {
708
- metadata = JSON.parse(input(option.slice('--metadata='.length)));
709
- continue;
710
- }
711
- throw new Error('Usage: hyv fact-lint <draft|-> --source=id:path [--source=id:path] [--metadata=metadata.json] [--strict] [--human]');
712
- }
713
- const report = lintFacts({ sources, draft: input(draftPath), metadata });
714
- if (human)
715
- console.log(formatFactLintReport(report));
716
- else
717
- json(report);
718
- return strict && report.findings.some((item) => item.severity === 'error') ? 2 : 0;
719
- }
720
- function runLogicLint(args) {
721
- const [draftPath, briefPath, ...extra] = args;
722
- if (!draftPath || extra.length)
723
- throw new Error('Usage: hyv logic-lint <draft|-> [writing-brief.json]');
724
- const report = lintLogic(input(draftPath), readBrief(briefPath));
725
- json(report);
726
- return report.passed ? 0 : 2;
727
- }
728
- function runBatchAnalyze(args) {
729
- if (args.length < 2)
730
- throw new Error('Usage: hyv batch-analyze draft-a.md draft-b.md [draft-c.md]');
731
- json(analyzeBatch(args.map(input)));
732
- return 0;
733
- }
734
- function runRewritePrompt(args) {
735
- const [draft, profilePath, ...rest] = args;
736
- const exampleOption = rest.find((value) => value.startsWith('--examples-json='));
737
- const briefPaths = rest.filter((value) => !value.startsWith('--'));
738
- const briefPath = briefPaths[0];
739
- if (!draft || !profilePath || briefPaths.length > 1 || rest.some((value) => value.startsWith('--') && !value.startsWith('--examples-json=')))
740
- throw new Error('Usage: hyv rewrite-prompt draft.md profile.json [writing-brief.json] [--examples-json=local-examples.json]');
741
- const profile = readProfile(profilePath);
742
- const examplesValue = exampleOption ? readJson(exampleOption.slice('--examples-json='.length)) : undefined;
743
- if (examplesValue !== undefined && (!Array.isArray(examplesValue) || !examplesValue.every((item) => item && typeof item === 'object' && typeof item.basename === 'string' && typeof item.text === 'string')))
744
- throw new Error('Local examples must be a JSON array of { basename, text } values.');
745
- const draftText = input(draft);
746
- console.log(rewritePrompt(draftText, profile, composeLearning(profile), readBrief(briefPath), examplesValue ? findWritingExamples(draftText, examplesValue) : []));
747
- return 0;
748
- }
749
- function runPrepareRewrite(args) {
750
- const [draft, profilePath, output, ...contextPaths] = args;
751
- if (!draft || !profilePath || !output)
752
- throw new Error('Usage: hyv prepare-rewrite draft.md profile.json task.json [copy-spec.json] [writing-brief.json]');
753
- const context = prepareContext(contextPaths);
754
- const task = prepareRewriteTask(input(draft), readProfile(profilePath), context.copySpec, context.writingBrief);
755
- writeJson(output, task);
756
- json({ version: task.version, fingerprint: task.fingerprint, eligibleSentenceIds: task.eligibleSentenceIds });
757
- return 0;
758
- }
759
- function runApplyRewrite(args) {
760
- const [taskPath, responsePath, profilePath] = args;
761
- if (!taskPath || !responsePath || !profilePath)
762
- throw new Error('Usage: hyv apply-rewrite task.json response.json profile.json');
763
- const result = evaluateRewriteResponse(parseRewriteTask(JSON.parse(input(taskPath))), input(responsePath), readProfile(profilePath));
764
- json(result);
765
- return result.status === 'accepted' ? 0 : 2;
766
- }
767
- function runPrepareJudgment(args) {
768
- const [stage, kind, draft, profilePath, output, candidatePath] = args;
769
- if (!stage || !kind || !draft || !profilePath || !output)
770
- throw new Error('Usage: hyv prepare-judgment pre-edit|post-candidate kind draft.md profile.json task.json [candidate.md]');
771
- if (stage === 'post-candidate' && !candidatePath)
772
- throw new Error('Usage: hyv prepare-judgment post-candidate kind draft.md profile.json task.json candidate.md');
773
- const profile = readProfile(profilePath);
774
- const task = stage === 'pre-edit'
775
- ? preparePreEditJudgment(input(draft), profile, kind)
776
- : preparePostCandidateJudgment(input(draft), input(candidatePath ?? ''), profile, kind);
777
- writeJson(output, task);
778
- json({ version: task.version, stage: task.stage, judgmentType: task.judgmentType, taskFingerprint: task.taskFingerprint });
779
- return 0;
780
- }
781
- function runReduceJudgment(args) {
782
- if (args.length < 3)
783
- throw new Error('Usage: hyv reduce-judgment envelope.json envelope.json [envelope.json...]');
784
- const envelopes = args.map((path) => parseJudgmentEnvelope(JSON.parse(input(path))));
785
- json(envelopes[0]?.stage === 'pre-edit' ? reducePreEdit(envelopes) : reducePostCandidate(envelopes));
786
- return 0;
787
- }
788
- function runPrepareRebuild(args) {
789
- const { values, capability, recompositionPolicy } = rebuildArguments(args);
790
- const [draft, profilePath, reductionPath, specPath, output, briefPath] = values;
791
- if (!draft || !profilePath || !reductionPath || !specPath || !output || !capability) {
792
- throw new Error('Usage: hyv prepare-rebuild draft.md profile.json reduction.json copy-spec.json task.json [writing-brief.json] [--recomposition-policy policy.json] (--capability-stdin|--capability-file path)');
793
- }
794
- const context = loadApprovalContext();
795
- const task = prepareRebuildTask(input(draft), readProfile(profilePath), readJson(reductionPath), parseCopySpec(JSON.parse(input(specPath))), capability, context.trustStore, context.now, briefPath ? parseWritingBrief(JSON.parse(input(briefPath))) : undefined, recompositionPolicy);
796
- writeJson(output, task);
797
- json({ version: task.version, fingerprint: task.fingerprint, recommendationFingerprint: task.recommendationFingerprint, authorizationFingerprint: task.authorizationFingerprint, ...(task.recompositionPolicy ? { recompositionPolicy: task.recompositionPolicy } : {}) });
798
- return 0;
799
- }
800
- function runApplyRebuild(args) {
801
- const { values, capability } = capabilityArguments(args);
802
- const [taskPath, responsePath, profilePath, ...extra] = values;
803
- if (!taskPath || !responsePath || !profilePath || extra.length || !capability)
804
- throw new Error('Usage: hyv apply-rebuild task.json response.json profile.json (--capability-stdin|--capability-file path)');
805
- const context = loadApprovalContext();
806
- const result = evaluateRebuildResponse(parseRebuildTask(JSON.parse(input(taskPath))), input(responsePath), readProfile(profilePath), capability, context.trustStore, context.now);
807
- json(result);
808
- return result.status === 'accepted' ? 0 : 2;
809
- }
810
- function runRebuildWriterRequest(args) {
811
- const [taskPath, output, ...extra] = args;
812
- if (!taskPath || !output || extra.length)
813
- throw new Error('Usage: hyv rebuild-writer-request task.json writer-request.json');
814
- const request = writerRequestForRebuild(parseRebuildTask(JSON.parse(input(taskPath))));
815
- writeJson(output, request);
816
- json({ version: request.version, taskFingerprint: request.taskFingerprint, copySpecFingerprint: request.copySpecFingerprint, ...(request.recompositionPolicyFingerprint ? { recompositionPolicyFingerprint: request.recompositionPolicyFingerprint } : {}) });
817
- return 0;
818
- }
819
- function runVerify(args) {
820
- const [original, candidate, profilePath, briefPath] = args;
821
- if (!original || !candidate || !profilePath)
822
- throw new Error('Usage: hyv verify original.md candidate.md profile.json [writing-brief.json]');
823
- const profile = readProfile(profilePath);
824
- const originalText = input(original);
825
- const candidateText = input(candidate);
826
- const result = verify(originalText, candidateText, profile, readBrief(briefPath));
827
- json(result);
828
- return result.passed ? 0 : 2;
829
- }
830
- function runVerifySpec(args) {
831
- const [original, candidate, profilePath, specPath, briefPath] = args;
832
- if (!original || !candidate || !profilePath || !specPath)
833
- throw new Error('Usage: hyv verify-spec original.md candidate.md profile.json copy-spec.json [writing-brief.json]');
834
- const profile = readProfile(profilePath);
835
- const candidateText = input(candidate);
836
- const result = verifyWithCopySpec(input(original), candidateText, profile, parseCopySpec(JSON.parse(input(specPath))), readBrief(briefPath));
837
- json(result);
838
- return result.passed ? 0 : 2;
839
- }
840
- function runLifecycle(args) {
841
- const [action, ...raw] = args;
842
- if (action === 'prepare-semantic')
843
- return prepareSemanticLifecycle(raw);
844
- if (action === 'submit-verdict')
845
- return submitLifecycleVerdict(raw);
846
- if (action === 'inspect')
847
- return inspectLifecycleArtifact(raw);
848
- if (action === 'validate-final-approval' || action === 'finalize')
849
- return finishLifecycle(action, raw);
850
- throw new Error('Usage: hyv lifecycle <prepare-semantic|submit-verdict|inspect|validate-final-approval|finalize> ...');
851
- }
852
- function prepareSemanticLifecycle(args) {
853
- const [deterministicPath, bindingPath, receiptPath, policy, violationsPath, output, ...extra] = args;
854
- if (!deterministicPath || !bindingPath || !receiptPath || !policy || !violationsPath || !output || extra.length || !['normal', 'high_assurance'].includes(policy))
855
- throw new Error('Usage: hyv lifecycle prepare-semantic deterministic.json binding.json receipt.json <normal|high_assurance> violations.json output.json');
856
- if (policy === 'high_assurance')
857
- throw new Error('High-assurance semantic review requires a trusted embedding.');
858
- const result = prepareLifecycle(readJson(deterministicPath), readJson(bindingPath), readJson(receiptPath), policy, readJson(violationsPath));
859
- const serialized = canonicalJson(result);
860
- writeFileSync(output, `${serialized}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
861
- process.stdout.write(`${serialized}\n`);
862
- return 0;
863
- }
864
- function submitLifecycleVerdict(args) {
865
- const [artifactPath, taskPath, evaluatorId, verdictPath, ...extra] = args;
866
- if (!artifactPath || !taskPath || !evaluatorId || !verdictPath || extra.length)
867
- throw new Error('Usage: hyv lifecycle submit-verdict artifact.json task.json evaluator-id verdict.json');
868
- const artifact = readJson(artifactPath);
869
- const task = readJson(taskPath);
870
- if (task.policy !== 'normal')
871
- throw new Error('High-assurance semantic review requires a trusted embedding.');
872
- const result = submitSemanticVerdict(artifact, task, evaluatorId, readJson(verdictPath), loadApprovalContext());
873
- canonical(result.ok ? result.artifact : { error: result.error });
874
- return result.ok && result.artifact.status === 'ready_for_human_review' ? 0 : 2;
875
- }
876
- function inspectLifecycleArtifact(args) {
877
- const [artifactPath, ...extra] = args;
878
- if (!artifactPath || extra.length)
879
- throw new Error('Usage: hyv lifecycle inspect artifact.json');
880
- canonical(inspectLifecycle(readJson(artifactPath)));
881
- return 0;
882
- }
883
- function finishLifecycle(action, args) {
884
- const { values, capability } = capabilityArguments(args);
885
- if (action === 'validate-final-approval') {
886
- const [artifactPath, ...extra] = values;
887
- if (!artifactPath || extra.length || !capability)
888
- throw new Error('Usage: hyv lifecycle validate-final-approval artifact.json (--capability-stdin|--capability-file path)');
889
- const result = validateFinalApproval(readJson(artifactPath), capability, loadApprovalContext());
890
- canonical(result);
891
- return result.ok ? 0 : 2;
892
- }
893
- const [artifactPath, decisionPath, ...extra] = values;
894
- if (!artifactPath || !decisionPath || extra.length)
895
- throw new Error('Usage: hyv lifecycle finalize artifact.json decision.json [--capability-stdin|--capability-file path]');
896
- const decision = readJson(decisionPath);
897
- if (decision.decision === 'approve' && !capability)
898
- throw new Error('Approval requires a capability.');
899
- if (decision.decision === 'reject' && capability)
900
- throw new Error('Rejection does not accept a capability.');
901
- const result = finalizeLifecycle(readJson(artifactPath), decision, loadApprovalContext(), capability);
902
- canonical(result.ok ? result.artifact : { error: result.error });
903
- return result.ok && result.artifact.status === 'approved' ? 0 : 2;
904
- }
905
- function runLearning(args) {
906
- const [action, ...raw] = args;
907
- if (action === 'record-approved')
908
- return runRecordApprovedLearning(raw);
909
- const { values, options } = learningArguments(raw);
910
- const [profilePath, ...operands] = values;
911
- if (!action || !profilePath)
912
- throw new Error('Usage: hyv learning <show|inspect|add|record|ratify|supersede|migrate|clear> profile.json [value] [options]');
913
- const profile = readProfile(profilePath);
914
- if (action === 'show') {
915
- json({ profile: profileFingerprint(profile), preferences: composeLearning(profile, options) });
916
- return 0;
917
- }
918
- if (action === 'inspect') {
919
- if (Object.keys(options).length)
920
- throw new Error('Usage: hyv learning inspect profile.json');
921
- json(inspectLearning(profile));
922
- return 0;
923
- }
924
- if (action === 'add' || action === 'record') {
925
- const text = operands.join(' ').trim();
926
- if (!text)
927
- throw new Error('Usage: hyv learning record profile.json "instruction" [options]');
928
- const result = recordLearningInstruction(profile, text, options);
929
- json(action === 'add' ? { added: result.status === 'recorded' } : result);
930
- return 0;
931
- }
932
- if (action === 'ratify' || action === 'supersede') {
933
- const [eventId, ...extra] = operands;
934
- if (!eventId || extra.length)
935
- throw new Error(`Usage: hyv learning ${action} profile.json event-id [options]`);
936
- json(action === 'ratify' ? ratifyLearningEvent(requireProfileV3(profile), eventId, options) : supersedeLearningEvent(requireProfileV3(profile), eventId, options));
937
- return 0;
938
- }
939
- if (action === 'migrate') {
940
- const [targetPath, ...extra] = operands;
941
- if (!targetPath || extra.length || profile.version !== '2')
942
- throw new Error('Usage: hyv learning migrate source-v2.json target-v3.json [options]');
943
- json(migrateLearningV2ToV3(profile, requireProfileV3(readProfile(targetPath)), options));
944
- return 0;
945
- }
946
- if (action === 'clear') {
947
- if (operands.length || Object.keys(options).length)
948
- throw new Error('Usage: hyv learning clear profile.json');
949
- json({ cleared: clearLearning(profile) });
950
- return 0;
951
- }
952
- throw new Error('Usage: hyv learning <show|inspect|add|record|ratify|supersede|migrate|clear> profile.json [value] [options]');
953
- }
954
- function runRecordApprovedLearning(args) {
955
- const { values, capability } = capabilityArguments(args);
956
- const [readyPath, approvedPath, originalPath, candidatePath, profilePath, decisionPath, ...contextPaths] = values;
957
- if (!readyPath || !approvedPath || !originalPath || !candidatePath || !profilePath || !decisionPath || !capability)
958
- throw new Error('Usage: hyv learning record-approved ready.json approved.json original.md candidate.md profile.json decision.json [copy-spec.json] [writing-brief.json] (--capability-stdin|--capability-file path)');
959
- const context = prepareContext(contextPaths);
960
- const status = recordApprovedLearning({ ready: readJson(readyPath), approved: readJson(approvedPath), decision: readJson(decisionPath), capability, source: input(originalPath), candidate: input(candidatePath), profile: readProfile(profilePath), context: loadApprovalContext(), copySpec: context.copySpec, writingBrief: context.writingBrief });
961
- canonical({ status });
962
- return status === 'write_failed' ? 2 : 0;
963
- }
964
- function runPatterns() {
965
- json({ version: RULESET_VERSION, rules: serializedRules() });
966
- return 0;
967
- }
968
- function runDispositions(args) {
969
- const [draft, profilePath, briefPath, surfacePolicyPath] = args;
970
- if (!draft || !profilePath)
971
- throw new Error('Usage: hyv dispositions draft.md profile.json [writing-brief.json]');
972
- const report = analyze(input(draft), readProfile(profilePath), readBrief(briefPath));
973
- const policy = surfacePolicyPath ? parseSurfacePolicy(readJson(surfacePolicyPath)) : undefined;
974
- json({ version: '1', findings: [report.voiceDna, report.aiEditor, report.editorial].flatMap((engine) => engine?.findings.map((finding) => normalizeFinding(finding, policy)) ?? []) });
975
- return 0;
976
- }
977
8
  async function runMcp(args) {
978
9
  if (args.length > 0)
979
10
  throw new Error('Usage: hyv mcp');