@holdyourvoice/hyv 3.6.0 → 4.0.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.
Files changed (92) hide show
  1. package/Readme.md +93 -154
  2. package/dist/ai-editor-rules.js +1 -0
  3. package/dist/ai-editor.js +23 -33
  4. package/dist/analysis.js +33 -0
  5. package/dist/cli/agents.js +88 -0
  6. package/dist/cli/checks.js +226 -0
  7. package/dist/cli/io.js +141 -0
  8. package/dist/cli/lifecycle.js +162 -0
  9. package/dist/cli/profiles.js +246 -0
  10. package/dist/cli/rewriting.js +111 -0
  11. package/dist/cli.js +5 -974
  12. package/dist/copy-spec.js +6 -6
  13. package/dist/editorial-packs.js +4 -3
  14. package/dist/fact-linter.js +45 -27
  15. package/dist/hidden-text.js +6 -6
  16. package/dist/hold-your-voice.mcpb +0 -0
  17. package/dist/internal.js +8 -0
  18. package/dist/judgment-task.js +10 -14
  19. package/dist/learning.js +77 -71
  20. package/dist/lifecycle-adapter.js +2 -5
  21. package/dist/local-eval.js +16 -31
  22. package/dist/mcp-server.js +272 -0
  23. package/dist/mcp-tools.js +10 -19
  24. package/dist/mcp.js +2 -267
  25. package/dist/pipeline.js +18 -105
  26. package/dist/profile-compose.js +18 -11
  27. package/dist/profile-score.js +6 -6
  28. package/dist/profile.js +17 -19
  29. package/dist/provenance-status.js +1 -3
  30. package/dist/rebuild-task.js +18 -52
  31. package/dist/recomposition.js +18 -13
  32. package/dist/rewrite-prompt.js +82 -0
  33. package/dist/rewrite-response.js +27 -0
  34. package/dist/rewrite-task.js +73 -98
  35. package/dist/rule-allowances.js +6 -10
  36. package/dist/semantic-review.js +71 -47
  37. package/dist/stage1-evaluation.js +29 -14
  38. package/dist/strict-quality.js +10 -18
  39. package/dist/version.js +1 -1
  40. package/dist/voice-dna.js +34 -21
  41. package/dist/writing-examples.js +2 -7
  42. package/package.json +6 -3
  43. package/skills/hyv-prepare-judgment/SKILL.md +4 -0
  44. package/dist/agents/catalog.test.js +0 -60
  45. package/dist/agents/emit.test.js +0 -64
  46. package/dist/agents/load.test.js +0 -149
  47. package/dist/ai-editor.test.js +0 -265
  48. package/dist/approval-capability.test.js +0 -52
  49. package/dist/approval-context.test.js +0 -38
  50. package/dist/backtest.test.js +0 -20
  51. package/dist/benchmark.test.js +0 -328
  52. package/dist/canonical-json.test.js +0 -24
  53. package/dist/cli.test.js +0 -731
  54. package/dist/editorial-packs.test.js +0 -94
  55. package/dist/fact-linter.test.js +0 -85
  56. package/dist/hidden-text.test.js +0 -26
  57. package/dist/hygiene.test.js +0 -83
  58. package/dist/judgment-task.test.js +0 -162
  59. package/dist/learning.test.js +0 -325
  60. package/dist/lifecycle-adapter.test.js +0 -56
  61. package/dist/local-eval.test.js +0 -20
  62. package/dist/logic-linter-corpus.test.js +0 -22
  63. package/dist/logic-linter.test.js +0 -39
  64. package/dist/mcp-tools.test.js +0 -286
  65. package/dist/mcp.test.js +0 -312
  66. package/dist/mirror-refs.test.js +0 -63
  67. package/dist/pipeline.test.js +0 -247
  68. package/dist/preservation.test.js +0 -22
  69. package/dist/production-gates.test.js +0 -34
  70. package/dist/profile-compose.test.js +0 -32
  71. package/dist/profile-score.test.js +0 -22
  72. package/dist/profile-watch.test.js +0 -23
  73. package/dist/profile.test.js +0 -141
  74. package/dist/provenance-status.test.js +0 -22
  75. package/dist/rebuild-task.test.js +0 -206
  76. package/dist/recomposition.test.js +0 -34
  77. package/dist/release-audit.test.js +0 -292
  78. package/dist/rewrite-task.test.js +0 -166
  79. package/dist/rule-allowances.test.js +0 -17
  80. package/dist/rule-reconciliation.test.js +0 -50
  81. package/dist/sample-ingest.test.js +0 -52
  82. package/dist/semantic-review.test.js +0 -101
  83. package/dist/stage1-dry-run.test.js +0 -39
  84. package/dist/stage1-evaluation.test.js +0 -184
  85. package/dist/stage1-human-packet.test.js +0 -102
  86. package/dist/stage1-schema-contract.test.js +0 -95
  87. package/dist/stage2-human-packet.test.js +0 -81
  88. package/dist/strict-quality.test.js +0 -62
  89. package/dist/text-provenance.feature.test.js +0 -45
  90. package/dist/text.test.js +0 -16
  91. package/dist/voice-dna.test.js +0 -121
  92. package/dist/writing-examples.test.js +0 -35
@@ -0,0 +1,246 @@
1
+ import { lstatSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname, isAbsolute, join } from 'node:path';
3
+ import { buildProfile, buildProfileV3 } from '../voice-dna.js';
4
+ import { assessProfileReadiness } from '../profile-quality.js';
5
+ import { composeTeamProfile, parseTeamProfileBundle } from '../team-profile.js';
6
+ import { composeProfiles, parseProfileRatio } from '../profile-compose.js';
7
+ import { scoreHeldoutProfile } from '../profile-score.js';
8
+ import { ingestGmailSentMbox, ingestTelegramDesktopJson } from '../sample-ingest.js';
9
+ import { evaluateIsolatedBacktest } from '../backtest.js';
10
+ import { watchProfileSamples } from '../profile-watch.js';
11
+ import { evaluateLocalComposite } from '../local-eval.js';
12
+ import { input, readJson, readProfile, json, writeJson } from './io.js';
13
+ function profileArguments(args) {
14
+ const [output, ...rest] = args;
15
+ const samples = [];
16
+ const avoid = [];
17
+ for (const argument of rest) {
18
+ if (argument.startsWith('--avoid=')) {
19
+ const phrase = argument.slice('--avoid='.length).trim();
20
+ if (!phrase)
21
+ throw new Error('Avoid phrases must use --avoid=phrase.');
22
+ avoid.push(phrase);
23
+ }
24
+ else {
25
+ samples.push(argument);
26
+ }
27
+ }
28
+ if (!output || samples.length < 2)
29
+ throw new Error('Usage: hyv profile profile.json sample-a.md sample-b.md [sample-c.md] [--avoid=phrase]');
30
+ return { output, samples, avoid };
31
+ }
32
+ async function runProfileWatch(args) {
33
+ const [output, ...rest] = args;
34
+ const samples = [];
35
+ let id = '';
36
+ let channel;
37
+ let debounceMs = 500;
38
+ for (const value of rest) {
39
+ if (value.startsWith('--id='))
40
+ id = value.slice('--id='.length);
41
+ else if (value.startsWith('--channel='))
42
+ channel = value.slice('--channel='.length);
43
+ else if (value.startsWith('--debounce-ms='))
44
+ debounceMs = Number(value.slice('--debounce-ms='.length));
45
+ else
46
+ samples.push(value);
47
+ }
48
+ if (!output || !isAbsolute(output) || !id || !channel || samples.length < 2)
49
+ throw new Error('Usage: hyv profile watch /absolute/profile.json --id=writer.channel --channel=email sample-a.md sample-b.md [--debounce-ms=500]');
50
+ outputOutsideGitCheckout(dirname(output));
51
+ let initial = true;
52
+ const rebuild = () => {
53
+ const profile = buildProfileV3(samples.map(input), id, channel);
54
+ writeFileSync(output, JSON.stringify(profile, null, 2) + '\n', { encoding: 'utf8', flag: initial ? 'wx' : 'w', mode: 0o600 });
55
+ initial = false;
56
+ json({ version: '1', status: 'rebuilt', sampleCount: samples.length, profileId: profile.id, revisionDigest: profile.revisionDigest });
57
+ };
58
+ rebuild();
59
+ const handle = watchProfileSamples({ samples, debounceMs, rebuild });
60
+ await new Promise((resolve) => process.once('SIGINT', resolve));
61
+ handle.close();
62
+ return 0;
63
+ }
64
+ export function runProfile(args) {
65
+ if (args[0] === 'watch')
66
+ return runProfileWatch(args.slice(1));
67
+ if (args[0] === 'assess') {
68
+ if (args.length < 3)
69
+ throw new Error('Usage: hyv profile assess sample-a.md sample-b.md [sample-c.md]');
70
+ json(assessProfileReadiness(args.slice(1).map(input)));
71
+ return 0;
72
+ }
73
+ if (args[0] === 'compose') {
74
+ const rest = args.slice(1);
75
+ const ratioIndex = rest.findIndex((value) => value === '--ratio' || value.startsWith('--ratio='));
76
+ if (ratioIndex < 0)
77
+ throw new Error('Usage: hyv profile compose --ratio 70:30 profile-a.json profile-b.json [profile-c.json]');
78
+ const ratio = rest[ratioIndex] === '--ratio' ? rest[ratioIndex + 1] : rest[ratioIndex].slice('--ratio='.length);
79
+ const profilePaths = rest.filter((_, index) => index !== ratioIndex && index !== ratioIndex + Number(rest[ratioIndex] === '--ratio'));
80
+ if (!ratio || profilePaths.length < 2)
81
+ throw new Error('Usage: hyv profile compose --ratio 70:30 profile-a.json profile-b.json [profile-c.json]');
82
+ const profiles = profilePaths.map(readProfile);
83
+ if (profiles.some((profile) => profile.version !== '3'))
84
+ throw new Error('Profile composition requires Profile v3 inputs.');
85
+ json(composeProfiles(profiles, parseProfileRatio(ratio, profiles.length)));
86
+ return 0;
87
+ }
88
+ if (args[0] === 'v3') {
89
+ const [output, ...rest] = args.slice(1);
90
+ const samples = [];
91
+ const avoid = [];
92
+ let id = '';
93
+ let channel;
94
+ let tone;
95
+ for (const argument of rest) {
96
+ if (argument.startsWith('--id='))
97
+ id = argument.slice('--id='.length);
98
+ else if (argument.startsWith('--channel='))
99
+ channel = argument.slice('--channel='.length);
100
+ else if (argument.startsWith('--avoid='))
101
+ avoid.push(argument.slice('--avoid='.length));
102
+ else if (argument.startsWith('--tone=')) {
103
+ const values = argument.slice('--tone='.length).split(',').map(Number);
104
+ if (values.length !== 5 || values.some((value) => !Number.isFinite(value) || value < 0 || value > 1))
105
+ throw new Error('Tone must use five 0–1 comma-separated values: formality,confidence,warmth,energy,complexity.');
106
+ tone = { formality: values[0], confidence: values[1], warmth: values[2], energy: values[3], complexity: values[4] };
107
+ }
108
+ else
109
+ samples.push(argument);
110
+ }
111
+ if (!output || !id || !channel || samples.length < 2)
112
+ 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]');
113
+ writeJson(output, buildProfileV3(samples.map(input), id, channel, avoid, tone));
114
+ return 0;
115
+ }
116
+ const { output, samples, avoid } = profileArguments(args);
117
+ writeJson(output, buildProfile(samples.map(input), avoid));
118
+ return 0;
119
+ }
120
+ export function runScore(args) {
121
+ const [draftPath, profilePath, ...rest] = args;
122
+ if (!draftPath || !profilePath)
123
+ throw new Error('Usage: hyv score draft.md profile.json heldout-a.md heldout-b.md heldout-c.md [--channel=channel]');
124
+ const samplePaths = [];
125
+ let channel;
126
+ for (const value of rest) {
127
+ if (value.startsWith('--channel='))
128
+ channel = value.slice('--channel='.length);
129
+ else
130
+ samplePaths.push(value);
131
+ }
132
+ if (samplePaths.length < 3)
133
+ throw new Error('Usage: hyv score draft.md profile.json heldout-a.md heldout-b.md heldout-c.md [--channel=channel]');
134
+ json(scoreHeldoutProfile(input(draftPath), readProfile(profilePath), samplePaths.map(input), channel));
135
+ return 0;
136
+ }
137
+ export function runBacktest(args) {
138
+ const [contextPath, targetPath, candidatePath, profilePath, ...heldoutPaths] = args;
139
+ if (!contextPath || !targetPath || !candidatePath || !profilePath || heldoutPaths.length < 3)
140
+ throw new Error('Usage: hyv backtest context.md heldout-target.md candidate.md profile.json heldout-a.md heldout-b.md heldout-c.md');
141
+ json(evaluateIsolatedBacktest(input(contextPath), input(targetPath), input(candidatePath), readProfile(profilePath), heldoutPaths.map(input)));
142
+ return 0;
143
+ }
144
+ function readEvalParagraphs(path, label) {
145
+ const value = readJson(path);
146
+ if (!Array.isArray(value) || !value.every((item) => item && typeof item === 'object' && typeof item.paragraph_id === 'string' && typeof item.text === 'string'))
147
+ throw new Error(`${label} must be a JSON array of { paragraph_id, text } values.`);
148
+ return value.map((item) => ({ paragraphId: item.paragraph_id, text: item.text }));
149
+ }
150
+ export function runEvaluateLocal(args) {
151
+ const [inputPath, candidatePath, userPath, aiShadowPath] = args;
152
+ if (!inputPath || !candidatePath || !userPath || !aiShadowPath || args.length !== 4)
153
+ throw new Error('Usage: hyv evaluate-local input.md candidate.md user-paragraphs.json ai-shadow-paragraphs.json');
154
+ json(evaluateLocalComposite(input(inputPath), input(candidatePath), readEvalParagraphs(userPath, 'User paragraphs'), readEvalParagraphs(aiShadowPath, 'AI-shadow paragraphs')));
155
+ return 0;
156
+ }
157
+ function outputOutsideGitCheckout(path) {
158
+ if (!isAbsolute(path))
159
+ throw new Error('Sample ingest output must be an absolute path outside a Git checkout.');
160
+ let current;
161
+ try {
162
+ const stats = lstatSync(path);
163
+ if (!stats.isDirectory() || stats.isSymbolicLink())
164
+ throw new Error('Sample ingest output directory must be an existing non-symlink directory outside a Git checkout.');
165
+ current = realpathSync(path);
166
+ }
167
+ catch (error) {
168
+ if (error instanceof Error && error.message.includes('Sample ingest output directory'))
169
+ throw error;
170
+ throw new Error('Sample ingest output directory must already exist and remain outside a Git checkout.');
171
+ }
172
+ for (;;) {
173
+ try {
174
+ lstatSync(join(current, '.git'));
175
+ throw new Error('Sample ingest output must be outside a Git checkout.');
176
+ }
177
+ catch (error) {
178
+ if (error instanceof Error && error.message === 'Sample ingest output must be outside a Git checkout.')
179
+ throw error;
180
+ }
181
+ const parent = dirname(current);
182
+ if (parent === current)
183
+ return;
184
+ current = parent;
185
+ }
186
+ }
187
+ export function runIngest(args) {
188
+ const [sourceType, sourcePath, ...options] = args;
189
+ let owner = '';
190
+ let output = '';
191
+ const blockedWords = [];
192
+ for (const option of options) {
193
+ if (option.startsWith('--owner='))
194
+ owner = option.slice('--owner='.length);
195
+ else if (option.startsWith('--output='))
196
+ output = option.slice('--output='.length);
197
+ else if (option.startsWith('--blocked='))
198
+ blockedWords.push(option.slice('--blocked='.length));
199
+ else
200
+ throw new Error('Usage: hyv ingest <gmail-sent-mbox|telegram-desktop-json> export --owner=owner --output=/absolute/safe-directory [--blocked=word]');
201
+ }
202
+ if (!sourcePath || !owner || !output || !['gmail-sent-mbox', 'telegram-desktop-json'].includes(sourceType ?? ''))
203
+ throw new Error('Usage: hyv ingest <gmail-sent-mbox|telegram-desktop-json> export --owner=owner --output=/absolute/safe-directory [--blocked=word]');
204
+ outputOutsideGitCheckout(output);
205
+ const result = sourceType === 'gmail-sent-mbox'
206
+ ? ingestGmailSentMbox(input(sourcePath), owner, blockedWords)
207
+ : ingestTelegramDesktopJson(input(sourcePath), owner, blockedWords);
208
+ const outputDirectory = realpathSync(output);
209
+ const samplesPath = join(outputDirectory, 'samples.jsonl');
210
+ const receiptPath = join(outputDirectory, 'receipt.json');
211
+ try {
212
+ writeFileSync(samplesPath, result.samples.map((sample) => JSON.stringify({ text: sample })).join('\n') + (result.samples.length ? '\n' : ''), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
213
+ }
214
+ catch (error) {
215
+ if (error.code === 'ENOENT')
216
+ throw new Error('Sample ingest output directory must already exist and remain outside a Git checkout.');
217
+ throw error;
218
+ }
219
+ try {
220
+ writeFileSync(receiptPath, JSON.stringify(result.receipt, null, 2) + '\n', { encoding: 'utf8', flag: 'wx', mode: 0o600 });
221
+ }
222
+ catch (error) {
223
+ try {
224
+ rmSync(samplesPath, { force: true });
225
+ }
226
+ catch { }
227
+ throw error;
228
+ }
229
+ json(result.receipt);
230
+ return 0;
231
+ }
232
+ export function runTeamProfile(args) {
233
+ const [action, bundlePath, authorPath, ...brandPaths] = args;
234
+ if (action === 'validate' && bundlePath && !authorPath) {
235
+ json(parseTeamProfileBundle(readJson(bundlePath)));
236
+ return 0;
237
+ }
238
+ if (action === 'compose' && bundlePath && authorPath) {
239
+ const brands = brandPaths.map(readProfile).filter((profile) => profile.version === '3');
240
+ if (brands.length !== brandPaths.length)
241
+ throw new Error('Team brand profiles must use Profile v3.');
242
+ json(composeTeamProfile(readProfile(authorPath), brands, parseTeamProfileBundle(readJson(bundlePath))));
243
+ return 0;
244
+ }
245
+ throw new Error('Usage: hyv team-profile <validate bundle.json|compose bundle.json author-profile.json [brand-profile.json...]>');
246
+ }
@@ -0,0 +1,111 @@
1
+ import { parseCopySpec } from '../copy-spec.js';
2
+ import { composeLearning } from '../learning.js';
3
+ import { rewritePrompt } from '../pipeline.js';
4
+ import { evaluateRewriteResponse, parseRewriteTask, prepareRewriteTask } from '../rewrite-task.js';
5
+ import { parseJudgmentEnvelope, preparePostCandidateJudgment, preparePreEditJudgment, reducePostCandidate, reducePreEdit } from '../judgment-task.js';
6
+ import { evaluateRebuildResponse, parseRebuildTask, prepareRebuildTask, writerRequestForRebuild } from '../rebuild-task.js';
7
+ import { loadApprovalContext } from '../approval-context.js';
8
+ import { findWritingExamples } from '../writing-examples.js';
9
+ import { input, readJson, capabilityArguments, readProfile, readBrief, prepareContext, json, writeJson } from './io.js';
10
+ function rebuildArguments(args) {
11
+ const values = [];
12
+ let policyPath;
13
+ for (let index = 0; index < args.length; index += 1) {
14
+ if (args[index] !== '--recomposition-policy') {
15
+ values.push(args[index]);
16
+ continue;
17
+ }
18
+ const path = args[index + 1];
19
+ if (policyPath || !path || path === '-' || path.startsWith('--'))
20
+ throw new Error('Choose one recomposition policy file.');
21
+ policyPath = path;
22
+ index += 1;
23
+ }
24
+ const capability = capabilityArguments(values);
25
+ return { ...capability, ...(policyPath ? { recompositionPolicy: readJson(policyPath) } : {}) };
26
+ }
27
+ export function runRewritePrompt(args) {
28
+ const [draft, profilePath, ...rest] = args;
29
+ const exampleOption = rest.find((value) => value.startsWith('--examples-json='));
30
+ const briefPaths = rest.filter((value) => !value.startsWith('--'));
31
+ const briefPath = briefPaths[0];
32
+ if (!draft || !profilePath || briefPaths.length > 1 || rest.some((value) => value.startsWith('--') && !value.startsWith('--examples-json=')))
33
+ throw new Error('Usage: hyv rewrite-prompt draft.md profile.json [writing-brief.json] [--examples-json=local-examples.json]');
34
+ const profile = readProfile(profilePath);
35
+ const examplesValue = exampleOption ? readJson(exampleOption.slice('--examples-json='.length)) : undefined;
36
+ if (examplesValue !== undefined && (!Array.isArray(examplesValue) || !examplesValue.every((item) => item && typeof item === 'object' && typeof item.basename === 'string' && typeof item.text === 'string')))
37
+ throw new Error('Local examples must be a JSON array of { basename, text } values.');
38
+ const draftText = input(draft);
39
+ console.log(rewritePrompt(draftText, profile, composeLearning(profile), readBrief(briefPath), examplesValue ? findWritingExamples(draftText, examplesValue) : []));
40
+ return 0;
41
+ }
42
+ export function runPrepareRewrite(args) {
43
+ const [draft, profilePath, output, ...contextPaths] = args;
44
+ if (!draft || !profilePath || !output)
45
+ throw new Error('Usage: hyv prepare-rewrite draft.md profile.json task.json [copy-spec.json] [writing-brief.json]');
46
+ const context = prepareContext(contextPaths);
47
+ const task = prepareRewriteTask(input(draft), readProfile(profilePath), context.copySpec, context.writingBrief);
48
+ writeJson(output, task);
49
+ json({ version: task.version, fingerprint: task.fingerprint, eligibleSentenceIds: task.eligibleSentenceIds });
50
+ return 0;
51
+ }
52
+ export function runApplyRewrite(args) {
53
+ const [taskPath, responsePath, profilePath] = args;
54
+ if (!taskPath || !responsePath || !profilePath)
55
+ throw new Error('Usage: hyv apply-rewrite task.json response.json profile.json');
56
+ const result = evaluateRewriteResponse(parseRewriteTask(JSON.parse(input(taskPath))), input(responsePath), readProfile(profilePath));
57
+ json(result);
58
+ return result.status === 'accepted' ? 0 : 2;
59
+ }
60
+ export function runPrepareJudgment(args) {
61
+ const [stage, kind, draft, profilePath, output, candidatePath] = args;
62
+ if (!stage || !kind || !draft || !profilePath || !output)
63
+ throw new Error('Usage: hyv prepare-judgment pre-edit|post-candidate kind draft.md profile.json task.json [candidate.md]');
64
+ if (stage === 'post-candidate' && !candidatePath)
65
+ throw new Error('Usage: hyv prepare-judgment post-candidate kind draft.md profile.json task.json candidate.md');
66
+ const profile = readProfile(profilePath);
67
+ const task = stage === 'pre-edit'
68
+ ? preparePreEditJudgment(input(draft), profile, kind)
69
+ : preparePostCandidateJudgment(input(draft), input(candidatePath ?? ''), profile, kind);
70
+ writeJson(output, task);
71
+ json({ version: task.version, stage: task.stage, judgmentType: task.judgmentType, taskFingerprint: task.taskFingerprint });
72
+ return 0;
73
+ }
74
+ export function runReduceJudgment(args) {
75
+ if (args.length < 3)
76
+ throw new Error('Usage: hyv reduce-judgment envelope.json envelope.json [envelope.json...]');
77
+ const envelopes = args.map((path) => parseJudgmentEnvelope(JSON.parse(input(path))));
78
+ json(envelopes[0]?.stage === 'pre-edit' ? reducePreEdit(envelopes) : reducePostCandidate(envelopes));
79
+ return 0;
80
+ }
81
+ export function runPrepareRebuild(args) {
82
+ const { values, capability, recompositionPolicy } = rebuildArguments(args);
83
+ const [draft, profilePath, reductionPath, specPath, output, briefPath] = values;
84
+ if (!draft || !profilePath || !reductionPath || !specPath || !output || !capability) {
85
+ 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)');
86
+ }
87
+ const context = loadApprovalContext();
88
+ const task = prepareRebuildTask(input(draft), readProfile(profilePath), readJson(reductionPath), parseCopySpec(JSON.parse(input(specPath))), capability, context.trustStore, context.now, readBrief(briefPath), recompositionPolicy);
89
+ writeJson(output, task);
90
+ json({ version: task.version, fingerprint: task.fingerprint, recommendationFingerprint: task.recommendationFingerprint, authorizationFingerprint: task.authorizationFingerprint, ...(task.recompositionPolicy ? { recompositionPolicy: task.recompositionPolicy } : {}) });
91
+ return 0;
92
+ }
93
+ export function runApplyRebuild(args) {
94
+ const { values, capability } = capabilityArguments(args);
95
+ const [taskPath, responsePath, profilePath, ...extra] = values;
96
+ if (!taskPath || !responsePath || !profilePath || extra.length || !capability)
97
+ throw new Error('Usage: hyv apply-rebuild task.json response.json profile.json (--capability-stdin|--capability-file path)');
98
+ const context = loadApprovalContext();
99
+ const result = evaluateRebuildResponse(parseRebuildTask(JSON.parse(input(taskPath))), input(responsePath), readProfile(profilePath), capability, context.trustStore, context.now);
100
+ json(result);
101
+ return result.status === 'accepted' ? 0 : 2;
102
+ }
103
+ export function runRebuildWriterRequest(args) {
104
+ const [taskPath, output, ...extra] = args;
105
+ if (!taskPath || !output || extra.length)
106
+ throw new Error('Usage: hyv rebuild-writer-request task.json writer-request.json');
107
+ const request = writerRequestForRebuild(parseRebuildTask(JSON.parse(input(taskPath))));
108
+ writeJson(output, request);
109
+ json({ version: request.version, taskFingerprint: request.taskFingerprint, copySpecFingerprint: request.copySpecFingerprint, ...(request.recompositionPolicyFingerprint ? { recompositionPolicyFingerprint: request.recompositionPolicyFingerprint } : {}) });
110
+ return 0;
111
+ }