@holdyourvoice/hyv 3.3.5 → 3.4.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/Readme.md +66 -116
- package/dist/agents/catalog.js +118 -0
- package/dist/agents/catalog.test.js +60 -0
- package/dist/agents/emit.js +42 -0
- package/dist/agents/emit.test.js +64 -0
- package/dist/agents/index.js +4 -0
- package/dist/agents/load.js +184 -0
- package/dist/agents/load.test.js +149 -0
- package/dist/agents/types.js +18 -0
- package/dist/cli.js +464 -317
- package/dist/cli.test.js +76 -0
- package/dist/fact-linter.js +107 -98
- package/dist/mcp.js +43 -192
- package/dist/rebuild-task.test.js +1 -1
- package/dist/release-audit.test.js +5 -1
- package/dist/stage1-evaluation.js +74 -48
- 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/mcp.js
CHANGED
|
@@ -35,31 +35,33 @@ function failure(error) {
|
|
|
35
35
|
function lifecycleResult(result) {
|
|
36
36
|
return json(result.ok ? result.artifact : { error: result.error });
|
|
37
37
|
}
|
|
38
|
+
function guardedJson(run, publicError) {
|
|
39
|
+
try {
|
|
40
|
+
return json(run());
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return failure(publicError ? new Error(publicError) : error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function guardedLifecycle(run, publicError) {
|
|
47
|
+
try {
|
|
48
|
+
return lifecycleResult(run());
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return failure(publicError ? new Error(publicError) : error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
38
54
|
const server = new McpServer({ name: 'hold-your-voice', version: HYV_VERSION });
|
|
39
55
|
server.registerTool('hyv_build_profile', {
|
|
40
56
|
description: 'Build a portable VoiceDNA profile from at least two writing samples. The samples stay in memory and are not saved.',
|
|
41
57
|
inputSchema: { samples, avoid },
|
|
42
58
|
annotations: { readOnlyHint: true },
|
|
43
|
-
}, async ({ samples: writingSamples, avoid: phrases }) =>
|
|
44
|
-
try {
|
|
45
|
-
return json(buildProfileForMcp(writingSamples, phrases));
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
return failure(error);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
59
|
+
}, async ({ samples: writingSamples, avoid: phrases }) => guardedJson(() => buildProfileForMcp(writingSamples, phrases)));
|
|
51
60
|
server.registerTool('hyv_analyze', {
|
|
52
61
|
description: 'Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft using a portable profile JSON string.',
|
|
53
62
|
inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
|
|
54
63
|
annotations: { readOnlyHint: true },
|
|
55
|
-
}, async ({ draft, profile_json, writing_brief_json }) =>
|
|
56
|
-
try {
|
|
57
|
-
return json(analyzeForMcp(draft, profile_json, writing_brief_json));
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
return failure(error);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
64
|
+
}, async ({ draft, profile_json, writing_brief_json }) => guardedJson(() => analyzeForMcp(draft, profile_json, writing_brief_json)));
|
|
63
65
|
server.registerTool('hyv_hygiene', {
|
|
64
66
|
description: 'Inspect text for zero-width characters, bidirectional controls, Unicode tag characters, and unusual spaces without changing it or requiring a voice profile.',
|
|
65
67
|
inputSchema: { draft: hygieneText },
|
|
@@ -82,50 +84,22 @@ server.registerTool('hyv_logic_lint', {
|
|
|
82
84
|
description: 'Run the deterministic document-coherence gate. It detects configured topic drift, unanchored inference, and direct internal contradictions; it does not verify facts or approve publication.',
|
|
83
85
|
inputSchema: { draft: writing, writing_brief_json: writingBriefJson.optional() },
|
|
84
86
|
annotations: { readOnlyHint: true },
|
|
85
|
-
}, async ({ draft, writing_brief_json }) =>
|
|
86
|
-
try {
|
|
87
|
-
return json(logicLintForMcp(draft, writing_brief_json));
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
return failure(error);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
87
|
+
}, async ({ draft, writing_brief_json }) => guardedJson(() => logicLintForMcp(draft, writing_brief_json)));
|
|
93
88
|
server.registerTool('hyv_rewrite_prompt', {
|
|
94
89
|
description: 'Create a constrained editing brief. It does not rewrite the draft or call a model.',
|
|
95
90
|
inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
|
|
96
91
|
annotations: { readOnlyHint: true },
|
|
97
|
-
}, async ({ draft, profile_json, writing_brief_json }) => {
|
|
98
|
-
try {
|
|
99
|
-
return json(rewritePromptForMcp(draft, profile_json, {}, writing_brief_json));
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
return failure(error);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
92
|
+
}, async ({ draft, profile_json, writing_brief_json }) => guardedJson(() => rewritePromptForMcp(draft, profile_json, {}, writing_brief_json)));
|
|
105
93
|
server.registerTool('hyv_prepare_rewrite', {
|
|
106
94
|
description: 'Prepare a local, versioned rewrite task. The caller may forward it to a provider; doing so shares the draft and must be an explicit choice.',
|
|
107
95
|
inputSchema: { draft: writing, profile_json: profileJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
|
|
108
96
|
annotations: { readOnlyHint: true },
|
|
109
|
-
}, async ({ draft, profile_json, copy_spec_json, writing_brief_json }) =>
|
|
110
|
-
try {
|
|
111
|
-
return json(prepareRewriteForMcp(draft, profile_json, copy_spec_json, writing_brief_json));
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
return failure(error);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
97
|
+
}, async ({ draft, profile_json, copy_spec_json, writing_brief_json }) => guardedJson(() => prepareRewriteForMcp(draft, profile_json, copy_spec_json, writing_brief_json)));
|
|
117
98
|
server.registerTool('hyv_apply_rewrite', {
|
|
118
99
|
description: 'Validate and apply a model response to a prepared task, then run the local gates. It never calls a provider or stores source or candidate text.',
|
|
119
100
|
inputSchema: { task_json: z.string().min(1).max(250_000), response_json: z.string().min(1).max(100_000), profile_json: profileJson },
|
|
120
101
|
annotations: { readOnlyHint: true },
|
|
121
|
-
}, async ({ task_json, response_json, profile_json }) =>
|
|
122
|
-
try {
|
|
123
|
-
return json(applyRewriteForMcp(task_json, response_json, profile_json));
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
return failure(error);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
102
|
+
}, async ({ task_json, response_json, profile_json }) => guardedJson(() => applyRewriteForMcp(task_json, response_json, profile_json)));
|
|
129
103
|
server.registerTool('hyv_prepare_judgment', {
|
|
130
104
|
description: 'Prepare a versioned pre-edit or post-candidate judgment task. It does not call a model.',
|
|
131
105
|
inputSchema: {
|
|
@@ -136,62 +110,27 @@ server.registerTool('hyv_prepare_judgment', {
|
|
|
136
110
|
candidate: writing.optional(),
|
|
137
111
|
},
|
|
138
112
|
annotations: { readOnlyHint: true },
|
|
139
|
-
}, async ({ stage, kind, draft, profile_json, candidate }) =>
|
|
140
|
-
try {
|
|
141
|
-
return json(prepareJudgmentForMcp(stage, kind, draft, profile_json, candidate));
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
return failure(error);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
113
|
+
}, async ({ stage, kind, draft, profile_json, candidate }) => guardedJson(() => prepareJudgmentForMcp(stage, kind, draft, profile_json, candidate)));
|
|
147
114
|
server.registerTool('hyv_reduce_judgment', {
|
|
148
115
|
description: 'Reduce bound judgment envelopes into SHIP, EDIT, REBUILD, CLEAR, or ESCALATE. It does not call a model.',
|
|
149
116
|
inputSchema: { envelopes_json: z.string().min(1).max(250_000) },
|
|
150
117
|
annotations: { readOnlyHint: true },
|
|
151
|
-
}, async ({ envelopes_json }) =>
|
|
152
|
-
try {
|
|
153
|
-
return json(reduceJudgmentForMcp(envelopes_json));
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
return failure(error);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
118
|
+
}, async ({ envelopes_json }) => guardedJson(() => reduceJudgmentForMcp(envelopes_json)));
|
|
159
119
|
server.registerTool('hyv_verify', {
|
|
160
120
|
description: 'Verify a revised candidate against an original draft and portable profile without changing learning state.',
|
|
161
121
|
inputSchema: { original: writing, candidate: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
|
|
162
122
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
163
|
-
}, async ({ original, candidate, profile_json, writing_brief_json }) =>
|
|
164
|
-
try {
|
|
165
|
-
return json(verifyForMcp(original, candidate, profile_json, writing_brief_json));
|
|
166
|
-
}
|
|
167
|
-
catch (error) {
|
|
168
|
-
return failure(error);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
123
|
+
}, async ({ original, candidate, profile_json, writing_brief_json }) => guardedJson(() => verifyForMcp(original, candidate, profile_json, writing_brief_json)));
|
|
171
124
|
server.registerTool('hyv_verify_copy_spec', {
|
|
172
125
|
description: 'Verify a candidate against the existing voice gates and a local CopySpec. Immutable claims remain verbatim unless atoms are supplied; then each declared atom must remain. Prohibited claims fail closed.',
|
|
173
126
|
inputSchema: { original: writing, candidate: writing, profile_json: profileJson, copy_spec_json: copySpecJson, writing_brief_json: writingBriefJson.optional() },
|
|
174
127
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
175
|
-
}, async ({ original, candidate, profile_json, copy_spec_json, writing_brief_json }) =>
|
|
176
|
-
try {
|
|
177
|
-
return json(verifyCopySpecForMcp(original, candidate, profile_json, copy_spec_json, writing_brief_json));
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
return failure(error);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
128
|
+
}, async ({ original, candidate, profile_json, copy_spec_json, writing_brief_json }) => guardedJson(() => verifyCopySpecForMcp(original, candidate, profile_json, copy_spec_json, writing_brief_json)));
|
|
183
129
|
server.registerTool('hyv_batch_analyze', {
|
|
184
130
|
description: 'Inspect two to one hundred drafts for repeated opening and closing sentences. It returns advisory batch findings and does not store the drafts.',
|
|
185
131
|
inputSchema: { drafts: z.array(writing).min(2).max(100) },
|
|
186
132
|
annotations: { readOnlyHint: true },
|
|
187
|
-
}, async ({ drafts }) =>
|
|
188
|
-
try {
|
|
189
|
-
return json(analyzeBatchForMcp(drafts));
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
return failure(error);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
133
|
+
}, async ({ drafts }) => guardedJson(() => analyzeBatchForMcp(drafts)));
|
|
195
134
|
server.registerTool('hyv_patterns', {
|
|
196
135
|
description: 'List the exact AI Editor rules that run in this extension.',
|
|
197
136
|
inputSchema: {},
|
|
@@ -200,116 +139,56 @@ server.registerTool('hyv_patterns', {
|
|
|
200
139
|
server.registerTool('hyv_learning_inspect', {
|
|
201
140
|
description: 'Inspect profile-scoped learning receipts without returning stored instruction or draft text.',
|
|
202
141
|
inputSchema: { profile_json: profileJson }, annotations: { readOnlyHint: true },
|
|
203
|
-
}, async ({ profile_json }) =>
|
|
204
|
-
return json(inspectLearningForMcp(profile_json));
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
return failure(error);
|
|
208
|
-
} });
|
|
142
|
+
}, async ({ profile_json }) => guardedJson(() => inspectLearningForMcp(profile_json)));
|
|
209
143
|
server.registerTool('hyv_learning_record', {
|
|
210
144
|
description: 'Record an explicit profile-scoped learning instruction with authority and provenance metadata.',
|
|
211
145
|
inputSchema: { profile_json: profileJson, instruction: z.string().min(1).max(240), ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
212
|
-
}, async (args) =>
|
|
213
|
-
return json(recordLearningForMcp(args.profile_json, args.instruction, learningArgs(args)));
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
return failure(error);
|
|
217
|
-
} });
|
|
146
|
+
}, async (args) => guardedJson(() => recordLearningForMcp(args.profile_json, args.instruction, learningArgs(args))));
|
|
218
147
|
server.registerTool('hyv_learning_ratify', {
|
|
219
148
|
description: 'Ratify a learning event for a Profile v3 revision.',
|
|
220
149
|
inputSchema: { profile_json: profileJson, event_id: z.string().min(1).max(200), ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
221
|
-
}, async (args) =>
|
|
222
|
-
return json(ratifyLearningForMcp(args.profile_json, args.event_id, learningArgs(args)));
|
|
223
|
-
}
|
|
224
|
-
catch (error) {
|
|
225
|
-
return failure(error);
|
|
226
|
-
} });
|
|
150
|
+
}, async (args) => guardedJson(() => ratifyLearningForMcp(args.profile_json, args.event_id, learningArgs(args))));
|
|
227
151
|
server.registerTool('hyv_learning_supersede', {
|
|
228
152
|
description: 'Supersede a learning event for a Profile v3 revision.',
|
|
229
153
|
inputSchema: { profile_json: profileJson, event_id: z.string().min(1).max(200), ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
230
|
-
}, async (args) =>
|
|
231
|
-
return json(supersedeLearningForMcp(args.profile_json, args.event_id, learningArgs(args)));
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
return failure(error);
|
|
235
|
-
} });
|
|
154
|
+
}, async (args) => guardedJson(() => supersedeLearningForMcp(args.profile_json, args.event_id, learningArgs(args))));
|
|
236
155
|
server.registerTool('hyv_learning_migrate', {
|
|
237
156
|
description: 'Migrate Profile v2 learning into a Profile v3 identity.',
|
|
238
157
|
inputSchema: { source_profile_json: profileJson, target_profile_json: profileJson, ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
239
|
-
}, async (args) =>
|
|
240
|
-
return json(migrateLearningForMcp(args.source_profile_json, args.target_profile_json, learningArgs(args)));
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
return failure(error);
|
|
244
|
-
} });
|
|
158
|
+
}, async (args) => guardedJson(() => migrateLearningForMcp(args.source_profile_json, args.target_profile_json, learningArgs(args))));
|
|
245
159
|
server.registerTool('hyv_learning_clear', {
|
|
246
160
|
description: 'Delete all local learning state for a profile.',
|
|
247
161
|
inputSchema: { profile_json: profileJson }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
248
|
-
}, async ({ profile_json }) =>
|
|
249
|
-
return json(clearLearningForMcp(profile_json));
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
return failure(error);
|
|
253
|
-
} });
|
|
162
|
+
}, async ({ profile_json }) => guardedJson(() => clearLearningForMcp(profile_json)));
|
|
254
163
|
server.registerTool('hyv_lifecycle_prepare_semantic', {
|
|
255
164
|
description: 'Prepare a normal semantic-review task and its initial immutable lifecycle artifact.',
|
|
256
165
|
inputSchema: { deterministic_json: lifecycleJson, binding_json: lifecycleJson, receipt_json: lifecycleJson, policy: z.literal('normal'), allowed_violations: z.array(semanticViolation).max(5) },
|
|
257
166
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
258
|
-
}, async (args) =>
|
|
259
|
-
return json(prepareLifecycleForMcp(args.deterministic_json, args.binding_json, args.receipt_json, args.policy, args.allowed_violations));
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
return failure(error);
|
|
263
|
-
} });
|
|
167
|
+
}, async (args) => guardedJson(() => prepareLifecycleForMcp(args.deterministic_json, args.binding_json, args.receipt_json, args.policy, args.allowed_violations)));
|
|
264
168
|
server.registerTool('hyv_lifecycle_submit_verdict', {
|
|
265
169
|
description: 'Submit one normal-policy semantic verdict using the server-installed evaluator authorization context.',
|
|
266
170
|
inputSchema: { artifact_json: lifecycleJson, task_json: lifecycleJson, evaluator_id: evaluatorId, verdict_json: lifecycleJson },
|
|
267
171
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
268
|
-
}, async (args) =>
|
|
269
|
-
return lifecycleResult(submitSemanticVerdictForMcp(args.artifact_json, args.task_json, args.evaluator_id, args.verdict_json, loadApprovalContext()));
|
|
270
|
-
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
return failure(error);
|
|
273
|
-
} });
|
|
172
|
+
}, async (args) => guardedLifecycle(() => submitSemanticVerdictForMcp(args.artifact_json, args.task_json, args.evaluator_id, args.verdict_json, loadApprovalContext())));
|
|
274
173
|
server.registerTool('hyv_lifecycle_inspect', {
|
|
275
174
|
description: 'Validate and inspect an immutable lifecycle artifact without exposing bound source or candidate hashes.',
|
|
276
175
|
inputSchema: { artifact_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
277
|
-
}, async ({ artifact_json }) =>
|
|
278
|
-
return json(inspectLifecycleForMcp(artifact_json));
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
return failure(error);
|
|
282
|
-
} });
|
|
176
|
+
}, async ({ artifact_json }) => guardedJson(() => inspectLifecycleForMcp(artifact_json)));
|
|
283
177
|
if (redactsSensitiveInputs) {
|
|
284
178
|
server.registerTool('hyv_lifecycle_finalize', {
|
|
285
179
|
description: 'Finalize an authorized human approval or rejection. Capability input requires host-guaranteed sensitive-input redaction.',
|
|
286
180
|
inputSchema: { artifact_json: lifecycleJson, decision_json: lifecycleJson, capability_json: lifecycleJson.optional() },
|
|
287
181
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
288
|
-
}, async (args) =>
|
|
289
|
-
return lifecycleResult(finalizeLifecycleForMcp(args.artifact_json, args.decision_json, loadApprovalContext(), args.capability_json));
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
return failure(new Error('Lifecycle finalization failed.'));
|
|
293
|
-
} });
|
|
182
|
+
}, async (args) => guardedLifecycle(() => finalizeLifecycleForMcp(args.artifact_json, args.decision_json, loadApprovalContext(), args.capability_json), 'Lifecycle finalization failed.'));
|
|
294
183
|
server.registerTool('hyv_lifecycle_validate_final_approval', {
|
|
295
184
|
description: 'Validate a final-approval capability against the server-installed trust context.',
|
|
296
185
|
inputSchema: { artifact_json: lifecycleJson, capability_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
297
|
-
}, async (args) =>
|
|
298
|
-
return json(validateFinalApprovalForMcp(args.artifact_json, args.capability_json, loadApprovalContext()));
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
return failure(new Error('Capability validation failed.'));
|
|
302
|
-
} });
|
|
186
|
+
}, async (args) => guardedJson(() => validateFinalApprovalForMcp(args.artifact_json, args.capability_json, loadApprovalContext()), 'Capability validation failed.'));
|
|
303
187
|
server.registerTool('hyv_learning_record_approved', {
|
|
304
188
|
description: 'Record one approval-revalidated, deterministic, text-free learning event.',
|
|
305
189
|
inputSchema: { ready_json: lifecycleJson, approved_json: lifecycleJson, original: approvedLearningText, candidate: approvedLearningText, profile_json: profileJson, decision_json: lifecycleJson, capability_json: lifecycleJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
|
|
306
190
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
307
|
-
}, async (args) => {
|
|
308
|
-
return json({ status: recordApprovedLearningForMcp({ readyJson: args.ready_json, approvedJson: args.approved_json, source: args.original, candidate: args.candidate, profileJson: args.profile_json, decisionJson: args.decision_json, capabilityJson: args.capability_json, context: loadApprovalContext(), copySpecJson: args.copy_spec_json, writingBriefJson: args.writing_brief_json }) });
|
|
309
|
-
}
|
|
310
|
-
catch {
|
|
311
|
-
return failure(new Error('Approved learning was not authorized.'));
|
|
312
|
-
} });
|
|
191
|
+
}, async (args) => guardedJson(() => ({ status: recordApprovedLearningForMcp({ readyJson: args.ready_json, approvedJson: args.approved_json, source: args.original, candidate: args.candidate, profileJson: args.profile_json, decisionJson: args.decision_json, capabilityJson: args.capability_json, context: loadApprovalContext(), copySpecJson: args.copy_spec_json, writingBriefJson: args.writing_brief_json }) }), 'Approved learning was not authorized.'));
|
|
313
192
|
server.registerTool('hyv_prepare_rebuild', {
|
|
314
193
|
description: 'Prepare a rebuild task only after an upstream REBUILD recommendation, CopySpec, and signed rebuild-authorization capability. Capability input requires host-guaranteed sensitive-input redaction.',
|
|
315
194
|
inputSchema: {
|
|
@@ -322,49 +201,21 @@ if (redactsSensitiveInputs) {
|
|
|
322
201
|
recomposition_policy_json: lifecycleJson.optional(),
|
|
323
202
|
},
|
|
324
203
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
325
|
-
}, async (args) =>
|
|
326
|
-
try {
|
|
327
|
-
return json(prepareRebuildForMcp(args.draft, args.profile_json, args.reduction_json, args.copy_spec_json, args.capability_json, loadApprovalContext(), args.writing_brief_json, args.recomposition_policy_json));
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
return failure(new Error('Rebuild preparation failed.'));
|
|
331
|
-
}
|
|
332
|
-
});
|
|
204
|
+
}, async (args) => guardedJson(() => prepareRebuildForMcp(args.draft, args.profile_json, args.reduction_json, args.copy_spec_json, args.capability_json, loadApprovalContext(), args.writing_brief_json, args.recomposition_policy_json), 'Rebuild preparation failed.'));
|
|
333
205
|
server.registerTool('hyv_apply_rebuild', {
|
|
334
206
|
description: 'Validate and evaluate a whole-document rebuild response against a prepared authorized rebuild task. Capability input requires host-guaranteed sensitive-input redaction. It never calls a provider.',
|
|
335
207
|
inputSchema: { task_json: lifecycleJson, response_json: z.string().min(1).max(100_000), profile_json: profileJson, capability_json: lifecycleJson },
|
|
336
208
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
337
|
-
}, async (args) =>
|
|
338
|
-
try {
|
|
339
|
-
return json(applyRebuildForMcp(args.task_json, args.response_json, args.profile_json, args.capability_json, loadApprovalContext()));
|
|
340
|
-
}
|
|
341
|
-
catch {
|
|
342
|
-
return failure(new Error('Rebuild application failed.'));
|
|
343
|
-
}
|
|
344
|
-
});
|
|
209
|
+
}, async (args) => guardedJson(() => applyRebuildForMcp(args.task_json, args.response_json, args.profile_json, args.capability_json, loadApprovalContext()), 'Rebuild application failed.'));
|
|
345
210
|
server.registerTool('hyv_rebuild_writer_request', {
|
|
346
211
|
description: 'Create the writer-only payload for a prepared rebuild. It excludes source draft, capability, profile body, and validation evidence.',
|
|
347
212
|
inputSchema: { task_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
348
|
-
}, async ({ task_json }) =>
|
|
349
|
-
try {
|
|
350
|
-
return json(rebuildWriterRequestForMcp(task_json));
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
return failure(new Error('Writer request could not be prepared.'));
|
|
354
|
-
}
|
|
355
|
-
});
|
|
213
|
+
}, async ({ task_json }) => guardedJson(() => rebuildWriterRequestForMcp(task_json), 'Writer request could not be prepared.'));
|
|
356
214
|
}
|
|
357
215
|
else {
|
|
358
216
|
server.registerTool('hyv_lifecycle_finalize', {
|
|
359
217
|
description: 'Record an authorized human rejection. Approval is unavailable because this host does not guarantee sensitive-input redaction.',
|
|
360
218
|
inputSchema: { artifact_json: lifecycleJson, decision_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
361
|
-
}, async (args) =>
|
|
362
|
-
try {
|
|
363
|
-
return lifecycleResult(finalizeRejectionForMcp(args.artifact_json, args.decision_json, loadApprovalContext()));
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
return failure(new Error('Only rejection is available without sensitive-input redaction.'));
|
|
367
|
-
}
|
|
368
|
-
});
|
|
219
|
+
}, async (args) => guardedLifecycle(() => finalizeRejectionForMcp(args.artifact_json, args.decision_json, loadApprovalContext()), 'Only rejection is available without sensitive-input redaction.'));
|
|
369
220
|
}
|
|
370
221
|
await server.connect(new StdioServerTransport());
|
|
@@ -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.1');
|
|
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/);
|
|
@@ -432,77 +432,103 @@ export function sealRatings(packetInput, mappingInput, ratingsInput) {
|
|
|
432
432
|
const base = { kind: 'hyv-stage1-ratings-seal', version: '1', protocolDigest: packet.protocolDigest, candidateDigest: packet.candidateDigest, packetDigest: packet.packetDigest, mappingDigest: packet.mappingDigest, ratingsDigest: sha256Canonical(ratings), recordCount: ratings.length };
|
|
433
433
|
return { ...base, sealDigest: sha256Canonical(base) };
|
|
434
434
|
}
|
|
435
|
-
|
|
436
|
-
const protocol = assertCommittedProtocol(protocolInput);
|
|
437
|
-
const summary = validateRuns(protocol, runsInput);
|
|
438
|
-
const packet = parsePacket(packetInput);
|
|
435
|
+
function validateEvaluationPacket(protocol, runs, summary, packet) {
|
|
439
436
|
if (packet.protocolDigest !== protocol.protocolDigest || packet.candidateDigest !== candidateDigest(protocol) || packet.runsDigest !== summary.runsDigest)
|
|
440
437
|
fail('packet_digest_chain_mismatch');
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
if (packet.reviewableCount !== expectedReviewableCount || packet.nonReviewableCount !== protocol.cases.length - expectedReviewableCount)
|
|
438
|
+
const reviewableCount = protocol.cases.filter((entry) => runs.filter((run) => run.caseId === entry.caseId && effectiveRunOutcome(run) === 'completed').length === 2).length;
|
|
439
|
+
if (packet.reviewableCount !== reviewableCount || packet.nonReviewableCount !== protocol.cases.length - reviewableCount)
|
|
444
440
|
fail('blind_packet_count_mismatch');
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
const seal = sealRatings(packet, mapping, ratings);
|
|
449
|
-
if (sha256Canonical(seal) !== sha256Canonical(sealInput))
|
|
450
|
-
fail('ratings_seal_mismatch');
|
|
451
|
-
const audit = record(releaseAuditInput);
|
|
441
|
+
}
|
|
442
|
+
function parseReleaseAudit(protocol, input) {
|
|
443
|
+
const audit = record(input);
|
|
452
444
|
exactKeys(audit, ['kind', 'version', 'candidateCommit', 'protocolDigest', 'contractDigest', 'passed', 'digest']);
|
|
453
445
|
const { digest: auditDigest, ...auditCore } = audit;
|
|
454
446
|
if (auditDigest !== sha256Canonical(auditCore))
|
|
455
447
|
fail('release_audit_digest_mismatch');
|
|
456
448
|
if (audit.kind !== 'hyv-release-audit' || audit.version !== '1' || audit.candidateCommit !== protocol.stage1.sourceCommit || audit.protocolDigest !== protocol.protocolDigest || audit.contractDigest !== protocol.releaseAuditContractDigest || audit.passed !== true)
|
|
457
449
|
fail('release_audit_binding_mismatch');
|
|
450
|
+
return audit;
|
|
451
|
+
}
|
|
452
|
+
function correctionCount(ratings, mapping, arm, result) {
|
|
453
|
+
const label = mapping.labels.A === arm ? 'A' : 'B';
|
|
454
|
+
return ratings.filter((rating) => rating.correctionVersusConfirm[label] === result).length;
|
|
455
|
+
}
|
|
456
|
+
function workflowForArm(runs, arm) {
|
|
457
|
+
const armRuns = runs.filter((run) => run.arm === arm);
|
|
458
|
+
return { completed: armRuns.filter((run) => effectiveRunOutcome(run) === 'completed').length, denominator: armRuns.length };
|
|
459
|
+
}
|
|
460
|
+
function calculateEvaluation(protocol, runs, ratings, mapping, summary) {
|
|
461
|
+
const completedRatings = ratings.filter((rating) => rating.workflow === 'completed');
|
|
462
|
+
const preferenceDenominator = protocol.intentToTreat.expectedReviewers * protocol.cases.length;
|
|
463
|
+
const preferred = completedRatings.reduce((total, rating) => total + (rating.preferredLabel === 'tie' ? 0.5 : mapping.labels[rating.preferredLabel] === 'stage1' ? 1 : 0), 0);
|
|
464
|
+
const preferenceRate = preferenceDenominator ? preferred / preferenceDenominator : 0;
|
|
465
|
+
const stage1Confirm = correctionCount(completedRatings, mapping, 'stage1', 'confirm');
|
|
466
|
+
const baselineConfirm = correctionCount(completedRatings, mapping, 'baseline', 'confirm');
|
|
467
|
+
const stage1Correction = correctionCount(completedRatings, mapping, 'stage1', 'correction');
|
|
468
|
+
const baselineCorrection = correctionCount(completedRatings, mapping, 'baseline', 'correction');
|
|
469
|
+
const baselineWorkflow = workflowForArm(runs, 'baseline');
|
|
470
|
+
const stage1Workflow = workflowForArm(runs, 'stage1');
|
|
471
|
+
const correctionMarginMet = (stage1Confirm - baselineConfirm) / preferenceDenominator >= protocol.analysis.margin;
|
|
472
|
+
const workflowRegressed = stage1Workflow.denominator === 0 || baselineWorkflow.denominator === 0 || stage1Workflow.completed / stage1Workflow.denominator < baselineWorkflow.completed / baselineWorkflow.denominator;
|
|
473
|
+
const metrics = {
|
|
474
|
+
preference: { numerator: preferred, denominator: preferenceDenominator, rate: preferenceRate, uncertainty95: wilson95(preferred, preferenceDenominator) },
|
|
475
|
+
correctionVersusConfirm: { stage1: { corrections: stage1Correction, confirms: stage1Confirm, denominator: preferenceDenominator, correctionRate: stage1Correction / preferenceDenominator, uncertainty95: wilson95(stage1Correction, preferenceDenominator) }, baseline: { corrections: baselineCorrection, confirms: baselineConfirm, denominator: preferenceDenominator, correctionRate: baselineCorrection / preferenceDenominator, uncertainty95: wilson95(baselineCorrection, preferenceDenominator) } },
|
|
476
|
+
completion: { numerator: completedRatings.length, denominator: preferenceDenominator, rate: completedRatings.length / preferenceDenominator, uncertainty95: wilson95(completedRatings.length, preferenceDenominator) },
|
|
477
|
+
abandonment: { numerator: ratings.length - completedRatings.length, denominator: preferenceDenominator, rate: (ratings.length - completedRatings.length) / preferenceDenominator, uncertainty95: wilson95(ratings.length - completedRatings.length, preferenceDenominator) },
|
|
478
|
+
providerRuns: { completed: summary.completed, abandoned: summary.abandonments, hardFailures: summary.hardFailures, timeouts: summary.timeouts, denominator: summary.denominator },
|
|
479
|
+
};
|
|
480
|
+
return { correctionMarginMet, metrics, preferenceDenominator, preferenceRate, workflowRegressed };
|
|
481
|
+
}
|
|
482
|
+
function mappingCustodyIsValid(protocol, packet, mapping) {
|
|
483
|
+
const { attestation, ...openingCore } = mapping;
|
|
484
|
+
try {
|
|
485
|
+
validateAttestation(attestation, sha256Canonical(openingCore), protocol.protocolDigest, packet.packetDigest);
|
|
486
|
+
return attestation.purpose === 'stage1-mapping-custody';
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function evaluationBlockers(protocol, runs, packet, ratings, mapping, calculation) {
|
|
458
493
|
const blockers = ['human_writer_evidence_deferred'];
|
|
459
|
-
if (protocol.benchmark.synthetic || ratings.some((
|
|
494
|
+
if (protocol.benchmark.synthetic || ratings.some((rating) => rating.evidenceClass === 'synthetic-dry-run'))
|
|
460
495
|
blockers.push('synthetic_fixture_evidence');
|
|
461
496
|
if (protocol.mode !== 'locked-human')
|
|
462
497
|
blockers.push('locked_human_evidence_required');
|
|
463
498
|
if (protocol.mode === 'locked-human') {
|
|
464
499
|
blockers.push('reviewer_roster_verification_deferred');
|
|
465
|
-
|
|
466
|
-
try {
|
|
467
|
-
validateAttestation(attestation, sha256Canonical(openingCore), protocol.protocolDigest, packet.packetDigest);
|
|
468
|
-
if (attestation.purpose !== 'stage1-mapping-custody')
|
|
469
|
-
throw new Error('purpose');
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
500
|
+
if (!mappingCustodyIsValid(protocol, packet, mapping))
|
|
472
501
|
blockers.push('mapping_custody_attestation_required');
|
|
473
|
-
}
|
|
474
502
|
}
|
|
475
|
-
if (ratings.length !==
|
|
503
|
+
if (ratings.length !== calculation.preferenceDenominator)
|
|
476
504
|
blockers.push('reviewer_denominator_mismatch');
|
|
477
505
|
if (protocol.cases.length < protocol.analysis.minimumCases || ratings.length < protocol.analysis.minimumRatings)
|
|
478
506
|
blockers.push('preregistered_minimum_not_met');
|
|
479
|
-
if (
|
|
507
|
+
if (runs.some((run) => run.hardGates && Object.values(run.hardGates).some((passed) => !passed)))
|
|
480
508
|
blockers.push('hard_gate_regression');
|
|
481
|
-
|
|
482
|
-
const preferred = completedRatings.reduce((total, rating) => total + (rating.preferredLabel === 'tie' ? 0.5 : mapping.labels[rating.preferredLabel] === 'stage1' ? 1 : 0), 0);
|
|
483
|
-
const preferenceDenominator = protocol.intentToTreat.expectedReviewers * protocol.cases.length;
|
|
484
|
-
const preferenceRate = preferenceDenominator ? preferred / preferenceDenominator : 0;
|
|
485
|
-
const stage1Confirm = completedRatings.filter((rating) => rating.correctionVersusConfirm[mapping.labels.A === 'stage1' ? 'A' : 'B'] === 'confirm').length;
|
|
486
|
-
const baselineConfirm = completedRatings.filter((rating) => rating.correctionVersusConfirm[mapping.labels.A === 'baseline' ? 'A' : 'B'] === 'confirm').length;
|
|
487
|
-
const correctionMarginMet = (stage1Confirm - baselineConfirm) / preferenceDenominator >= protocol.analysis.margin;
|
|
488
|
-
if (preferenceRate < 0.5 + protocol.analysis.margin && !correctionMarginMet)
|
|
509
|
+
if (calculation.preferenceRate < 0.5 + protocol.analysis.margin && !calculation.correctionMarginMet)
|
|
489
510
|
blockers.push('checkpoint_threshold_not_met');
|
|
490
|
-
|
|
491
|
-
const baselineWorkflow = workflowByArm('baseline');
|
|
492
|
-
const stage1Workflow = workflowByArm('stage1');
|
|
493
|
-
if (stage1Workflow.denominator === 0 || baselineWorkflow.denominator === 0 || stage1Workflow.completed / stage1Workflow.denominator < baselineWorkflow.completed / baselineWorkflow.denominator)
|
|
511
|
+
if (calculation.workflowRegressed)
|
|
494
512
|
blockers.push('workflow_regression');
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
513
|
+
return blockers;
|
|
514
|
+
}
|
|
515
|
+
export function reduceEvaluation(protocolInput, runsInput, packetInput, mappingInput, ratingsInput, sealInput, releaseAuditInput) {
|
|
516
|
+
const protocol = assertCommittedProtocol(protocolInput);
|
|
517
|
+
const summary = validateRuns(protocol, runsInput);
|
|
518
|
+
const runs = runsInput;
|
|
519
|
+
const packet = parsePacket(packetInput);
|
|
520
|
+
validateEvaluationPacket(protocol, runs, summary, packet);
|
|
521
|
+
const mapping = parseMapping(mappingInput);
|
|
522
|
+
validateMappingOpening(protocol, mapping);
|
|
523
|
+
const ratings = validateReviewerLog(protocol, packet, ratingsInput, true);
|
|
524
|
+
const seal = sealRatings(packet, mapping, ratings);
|
|
525
|
+
if (sha256Canonical(seal) !== sha256Canonical(sealInput))
|
|
526
|
+
fail('ratings_seal_mismatch');
|
|
527
|
+
const audit = parseReleaseAudit(protocol, releaseAuditInput);
|
|
528
|
+
const calculation = calculateEvaluation(protocol, runs, ratings, mapping, summary);
|
|
529
|
+
const blockers = evaluationBlockers(protocol, runs, packet, ratings, mapping, calculation);
|
|
530
|
+
const intentToTreat = { ...summary, expectedRatings: calculation.preferenceDenominator, observedRatings: ratings.length, missingRatings: Math.max(0, calculation.preferenceDenominator - ratings.length), reconciled: summary.denominator === protocol.intentToTreat.expectedAssignments && ratings.length === calculation.preferenceDenominator };
|
|
531
|
+
const base = { kind: 'hyv-stage1-evaluation-report', version: '1', protocolDigest: protocol.protocolDigest, candidateDigest: candidateDigest(protocol), runsDigest: summary.runsDigest, packetDigest: packet.packetDigest, mappingDigest: packet.mappingDigest, sealDigest: seal.sealDigest, releaseAuditDigest: audit.digest, intentToTreat, metrics: calculation.metrics, decision: blockers.length ? 'BLOCKED' : 'PASS', promotable: blockers.length === 0, blockers };
|
|
506
532
|
return { ...base, reportDigest: sha256Canonical(base) };
|
|
507
533
|
}
|
|
508
534
|
export function preflight(protocolInput) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const HYV_VERSION = '3.
|
|
1
|
+
export const HYV_VERSION = '3.4.1';
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holdyourvoice/hyv",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
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.
|