@holdyourvoice/hyv 3.5.1 → 3.6.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 (34) hide show
  1. package/Readme.md +4 -2
  2. package/dist/cli.test.js +1 -1
  3. package/dist/hold-your-voice.mcpb +0 -0
  4. package/dist/mcp.js +7 -7
  5. package/dist/pipeline.js +19 -9
  6. package/dist/pipeline.test.js +5 -3
  7. package/dist/rebuild-task.test.js +1 -1
  8. package/dist/rewrite-task.js +1 -1
  9. package/dist/rewrite-task.test.js +10 -9
  10. package/dist/strict-quality.js +1 -2
  11. package/dist/strict-quality.test.js +3 -3
  12. package/dist/version.js +1 -1
  13. package/package.json +1 -1
  14. package/skills/hyv-analyze/SKILL.md +1 -1
  15. package/skills/hyv-analyze/agents/openai.yaml +1 -1
  16. package/skills/hyv-apply-rebuild/SKILL.md +1 -0
  17. package/skills/hyv-apply-rebuild/agents/openai.yaml +1 -1
  18. package/skills/hyv-apply-rewrite/SKILL.md +1 -0
  19. package/skills/hyv-apply-rewrite/agents/openai.yaml +1 -1
  20. package/skills/hyv-prepare-rebuild/SKILL.md +1 -0
  21. package/skills/hyv-prepare-rebuild/agents/openai.yaml +1 -1
  22. package/skills/hyv-prepare-rewrite/SKILL.md +1 -0
  23. package/skills/hyv-prepare-rewrite/agents/openai.yaml +1 -1
  24. package/skills/hyv-rebuild-writer-request/SKILL.md +1 -0
  25. package/skills/hyv-rebuild-writer-request/agents/openai.yaml +1 -1
  26. package/skills/hyv-rewrite-prompt/SKILL.md +4 -3
  27. package/skills/hyv-rewrite-prompt/agent.json +1 -1
  28. package/skills/hyv-rewrite-prompt/agents/openai.yaml +2 -2
  29. package/skills/hyv-strict-check/SKILL.md +3 -3
  30. package/skills/hyv-verify/SKILL.md +2 -1
  31. package/skills/hyv-verify/agents/openai.yaml +1 -1
  32. package/skills/hyv-verify-spec/SKILL.md +3 -2
  33. package/skills/hyv-verify-spec/agent.json +1 -1
  34. package/skills/hyv-verify-spec/agents/openai.yaml +2 -2
package/Readme.md CHANGED
@@ -131,7 +131,7 @@ producer | hyv final-check -
131
131
  | `hyv score <draft> <profile-v3.json> <heldout...>` | Measure the draft against a separate held-out local writing range; may abstain. |
132
132
  | `hyv ingest <source> <export> --owner=owner --output=/absolute/dir` | Create redacted, owner-authorized Gmail or Telegram samples and a text-free receipt. |
133
133
  | `hyv analyze <draft> <profile.json>` | Run VoiceDNA, AI Editor, and hygiene checks. |
134
- | `hyv strict-check <draft> <profile-v3.json> <sample...>` | Run the opt-in strict local quality gate. It requires a calibrated V3 profile built from five or more samples, five non-duplicate samples with a consistent visible format and 1,500 words total, and returns `strict-ready`, `needs-human-review`, or `blocked`. |
134
+ | `hyv strict-check <draft> <profile-v3.json> <sample...>` | Run the calibrated local strict-quality gate. It requires a V3 profile built from five or more samples, five non-duplicate validation samples with a consistent visible format and 1,500 words total, and returns `strict-ready`, `needs-human-review`, or `blocked`. |
135
135
  | `hyv hygiene <draft> [--fix]` | Inspect hidden Unicode or write a conservative cleaned copy. |
136
136
  | `hyv inspect-hidden-text <draft> [policy.json]` | Inspect hidden text with an optional policy. |
137
137
  | `hyv apply-hidden-text-policy <draft> <policy.json> <output>` | Apply approved hidden-text removals. |
@@ -143,7 +143,7 @@ producer | hyv final-check -
143
143
  | `hyv fact-lint <draft\|-> --source=id:path` | Check claims against local source files. |
144
144
  | `hyv logic-lint <draft\|-> [brief.json]` | Check deterministic document logic. |
145
145
  | `hyv batch-analyze <draft...>` | Find repeated openings and endings across drafts. |
146
- | `hyv rewrite-prompt <draft> <profile.json>` | Create a constrained editing brief. |
146
+ | `hyv rewrite-prompt <draft> <profile.json>` | Create a strict constrained editing brief. |
147
147
  | `hyv prepare-rewrite ...` | Create a fingerprint-bound edit task. |
148
148
  | `hyv apply-rewrite ...` | Apply and verify a response to an edit task. |
149
149
  | `hyv prepare-judgment ...` | Create a pre-edit or post-candidate judgment task. |
@@ -159,6 +159,8 @@ producer | hyv final-check -
159
159
  | `hyv agent list\|validate\|describe\|emit` | Inspect or emit portable agent contracts. |
160
160
  | `hyv mcp` | Start the local MCP server on standard input/output. |
161
161
 
162
+ `rewrite-prompt`, `prepare-rewrite`, `apply-rewrite`, and `verify` are strict by default: every active AI Editor finding must be repaired, and verification rejects a candidate that leaves one unresolved. A Profile v3 `disabled` policy is the only deliberate exception. `strict-check` adds the separate calibrated voice-evidence decision.
163
+
162
164
  Most commands return JSON. Exit code `0` means the command completed, `2` means a content or policy gate failed, and `1` means the command or input was invalid.
163
165
 
164
166
  Run `hyv <command>` without enough arguments to see its exact usage. Read the [CLI reference](docs/wiki/CLI-Reference.md) for every option.
package/dist/cli.test.js CHANGED
@@ -272,7 +272,7 @@ test('uses exit code 2 for a failed candidate gate and 1 for misuse', () => {
272
272
  assert.equal(run(['profile', profile, first, second, '--avoid=unlock']).status, 0);
273
273
  const verification = run(['verify', original, candidate, profile]);
274
274
  assert.equal(verification.status, 2);
275
- assert.deepEqual(Object.keys(JSON.parse(verification.stdout)).sort(), ['candidate', 'finalOutput', 'logicLint', 'original', 'passed', 'preservationScore', 'regressions', 'version']);
275
+ assert.deepEqual(Object.keys(JSON.parse(verification.stdout)).sort(), ['candidate', 'finalOutput', 'logicLint', 'original', 'passed', 'preservationScore', 'regressions', 'strictFindings', 'version']);
276
276
  assert.equal(run(['unknown-command']).status, 1);
277
277
  assert.equal(run(['mcp', 'unexpected']).status, 1);
278
278
  }
Binary file
package/dist/mcp.js CHANGED
@@ -74,7 +74,7 @@ server.registerTool('hyv_analyze', {
74
74
  annotations: { readOnlyHint: true },
75
75
  }, async ({ draft, profile_json, writing_brief_json }) => guardedJson(() => analyzeForMcp(draft, profile_json, writing_brief_json)));
76
76
  server.registerTool('hyv_strict_check', {
77
- description: 'Run the opt-in local strict quality gate. It requires a calibrated Profile v3 and local writing samples; it returns strict-ready, needs-human-review, or blocked without changing text or learning state.',
77
+ description: 'Run the calibrated local strict-quality gate. It requires a Profile v3 and local writing samples; it returns strict-ready, needs-human-review, or blocked without changing text or learning state.',
78
78
  inputSchema: { draft: writing, profile_json: profileJson, samples: strictSamples, writing_brief_json: writingBriefJson.optional() },
79
79
  annotations: { readOnlyHint: true },
80
80
  }, async ({ draft, profile_json, samples: localSamples, writing_brief_json }) => guardedJson(() => strictCheckForMcp(draft, profile_json, localSamples, writing_brief_json)));
@@ -128,7 +128,7 @@ server.registerTool('hyv_logic_lint', {
128
128
  annotations: { readOnlyHint: true },
129
129
  }, async ({ draft, writing_brief_json }) => guardedJson(() => logicLintForMcp(draft, writing_brief_json)));
130
130
  server.registerTool('hyv_rewrite_prompt', {
131
- description: 'Create a constrained editing brief. Explicit local examples are redacted in memory and injected only as advisory cadence evidence. It does not rewrite the draft or call a model.',
131
+ description: 'Create a strict constrained editing brief. Every active AI Editor finding is a required repair; explicit local examples remain advisory cadence evidence. It does not rewrite the draft or call a model.',
132
132
  inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional(), examples: writingExamples.optional() },
133
133
  annotations: { readOnlyHint: true },
134
134
  }, async ({ draft, profile_json, writing_brief_json, examples }) => guardedJson(() => rewritePromptForMcp(draft, profile_json, {}, writing_brief_json, examples)));
@@ -138,12 +138,12 @@ server.registerTool('hyv_find_writing_examples', {
138
138
  annotations: { readOnlyHint: true },
139
139
  }, async ({ query, examples }) => guardedJson(() => findWritingExamplesForMcp(query, examples)));
140
140
  server.registerTool('hyv_prepare_rewrite', {
141
- 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.',
141
+ description: 'Prepare a strict local, versioned rewrite task. Every active AI Editor finding is eligible for source-faithful repair; the caller may forward the task to a provider only by explicit choice.',
142
142
  inputSchema: { draft: writing, profile_json: profileJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
143
143
  annotations: { readOnlyHint: true },
144
144
  }, async ({ draft, profile_json, copy_spec_json, writing_brief_json }) => guardedJson(() => prepareRewriteForMcp(draft, profile_json, copy_spec_json, writing_brief_json)));
145
145
  server.registerTool('hyv_apply_rewrite', {
146
- 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.',
146
+ description: 'Validate a model response to a prepared task, then reject any candidate with an unresolved active AI Editor finding. It never calls a provider or stores source or candidate text.',
147
147
  inputSchema: { task_json: z.string().min(1).max(250_000), response_json: z.string().min(1).max(100_000), profile_json: profileJson },
148
148
  annotations: { readOnlyHint: true },
149
149
  }, async ({ task_json, response_json, profile_json }) => guardedJson(() => applyRewriteForMcp(task_json, response_json, profile_json)));
@@ -164,12 +164,12 @@ server.registerTool('hyv_reduce_judgment', {
164
164
  annotations: { readOnlyHint: true },
165
165
  }, async ({ envelopes_json }) => guardedJson(() => reduceJudgmentForMcp(envelopes_json)));
166
166
  server.registerTool('hyv_verify', {
167
- description: 'Verify a revised candidate against an original draft and portable profile without changing learning state.',
167
+ description: 'Verify a revised candidate against an original draft and portable profile. Every active AI Editor finding fails default verification; learning state remains unchanged.',
168
168
  inputSchema: { original: writing, candidate: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
169
169
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
170
170
  }, async ({ original, candidate, profile_json, writing_brief_json }) => guardedJson(() => verifyForMcp(original, candidate, profile_json, writing_brief_json)));
171
171
  server.registerTool('hyv_verify_copy_spec', {
172
- 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.',
172
+ description: 'Verify a candidate against strict default voice gates and a local CopySpec. Every active AI Editor finding fails verification; immutable claims remain verbatim unless atoms are supplied, and prohibited claims fail closed.',
173
173
  inputSchema: { original: writing, candidate: writing, profile_json: profileJson, copy_spec_json: copySpecJson, writing_brief_json: writingBriefJson.optional() },
174
174
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
175
175
  }, async ({ original, candidate, profile_json, copy_spec_json, writing_brief_json }) => guardedJson(() => verifyCopySpecForMcp(original, candidate, profile_json, copy_spec_json, writing_brief_json)));
@@ -250,7 +250,7 @@ if (redactsSensitiveInputs) {
250
250
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
251
251
  }, 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.'));
252
252
  server.registerTool('hyv_apply_rebuild', {
253
- 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.',
253
+ description: 'Validate a whole-document rebuild response against a prepared authorized rebuild task and reject unresolved active AI Editor findings. Capability input requires host-guaranteed sensitive-input redaction. It never calls a provider.',
254
254
  inputSchema: { task_json: lifecycleJson, response_json: z.string().min(1).max(100_000), profile_json: profileJson, capability_json: lifecycleJson },
255
255
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
256
256
  }, async (args) => guardedJson(() => applyRebuildForMcp(args.task_json, args.response_json, args.profile_json, args.capability_json, loadApprovalContext()), 'Rebuild application failed.'));
package/dist/pipeline.js CHANGED
@@ -29,10 +29,16 @@ function formatFindings(findings) {
29
29
  export function isBlockingFinding(finding) {
30
30
  return finding.engine === 'ai_editor' ? finding.appliedPolicy === 'blocking' : finding.severity === 'red';
31
31
  }
32
- export function deriveEditScope(result) {
32
+ export function isStrictFinding(finding) {
33
+ return finding.engine === 'ai_editor' || isBlockingFinding(finding);
34
+ }
35
+ export function strictFindings(result) {
36
+ return [...result.voiceDna.findings, ...result.aiEditor.findings, ...(result.editorial?.findings ?? [])].filter(isStrictFinding);
37
+ }
38
+ export function deriveEditScope(result, strict = false) {
33
39
  const findings = [...result.voiceDna.findings, ...result.aiEditor.findings, ...(result.editorial?.findings ?? [])];
34
- const blocking = findings.filter(isBlockingFinding);
35
- const pendingJudgment = findings.filter((finding) => finding.appliedPolicy === 'judgment-required');
40
+ const blocking = findings.filter(strict ? isStrictFinding : isBlockingFinding);
41
+ const pendingJudgment = strict ? [] : findings.filter((finding) => finding.appliedPolicy === 'judgment-required');
36
42
  return {
37
43
  eligibleSentenceIds: [...new Set(blocking.map((finding) => finding.sentence))].sort((left, right) => left - right),
38
44
  blocking,
@@ -41,16 +47,16 @@ export function deriveEditScope(result) {
41
47
  }
42
48
  export function renderRewritePrompt(draft, profile, result, learning = [], brief, examples = []) {
43
49
  const allFindings = [...result.voiceDna.findings, ...result.aiEditor.findings, ...(result.editorial?.findings ?? [])];
44
- const scope = deriveEditScope(result);
50
+ const scope = deriveEditScope(result, true);
45
51
  const redFindings = scope.blocking;
46
- const yellowFindings = allFindings.filter((finding) => !isBlockingFinding(finding) && finding.appliedPolicy !== 'judgment-required');
52
+ const yellowFindings = allFindings.filter((finding) => !isStrictFinding(finding) && finding.appliedPolicy !== 'judgment-required');
47
53
  const metrics = profile.metrics;
48
54
  return [
49
55
  '# Tier 0 — non-negotiable preservation',
50
56
  'Preserve facts, names, numbers, claims, and every unflagged sentence exactly. Do not add claims, examples, sections, hooks, or CTAs.',
51
57
  '',
52
- '# Tier 1 — release blockers',
53
- 'Treat each blocker as a required repair. Replace the flagged sentence with a stronger, source-faithful sentence; do not merely swap one stock phrase for another.',
58
+ '# Tier 1 — strict repair requirements',
59
+ 'Every active AI Editor finding is a required repair. Replace each flagged sentence with a stronger, source-faithful sentence; do not merely swap one stock phrase for another.',
54
60
  'Use only facts already present in the draft, CopySpec, WritingBrief, or supplied source context. Do not invent a source, metric, date, quotation, mechanism, example, CTA, or opinion.',
55
61
  `VoiceDNA: ${result.voiceDna.score}/100 (${result.voiceDna.passed ? 'pass' : 'fail'}).`,
56
62
  `AI Editor: ${result.aiEditor.score}/100 (${result.aiEditor.passed ? 'pass' : 'fail'}).`,
@@ -120,16 +126,18 @@ export function verify(original, candidate, profile, brief) {
120
126
  const logicLint = lintLogic(candidate, brief);
121
127
  const factLint = brief?.factSources?.length ? lintFacts({ sources: brief.factSources, draft: candidate, metadata: brief.factMetadata }) : undefined;
122
128
  const requiredFacts = verifyRequiredFacts(candidate, brief);
129
+ const unresolvedStrictFindings = strictFindings(checked);
123
130
  return {
124
131
  version: '2',
125
132
  original: baseline,
126
133
  candidate: checked,
127
134
  preservationScore: preservation,
128
135
  regressions,
136
+ strictFindings: unresolvedStrictFindings,
129
137
  finalOutput,
130
138
  logicLint,
131
139
  ...(factLint ? { factLint } : {}), ...(requiredFacts ? { requiredFacts } : {}),
132
- passed: checked.passed && !regressions.some(isBlockingFinding) && preservation >= 70 && finalOutput.accepted && logicLint.passed && !factLint?.findings.some((finding) => finding.severity === 'error') && (requiredFacts?.passed ?? true),
140
+ passed: checked.passed && unresolvedStrictFindings.length === 0 && !regressions.some(isBlockingFinding) && preservation >= 70 && finalOutput.accepted && logicLint.passed && !factLint?.findings.some((finding) => finding.severity === 'error') && (requiredFacts?.passed ?? true),
133
141
  };
134
142
  }
135
143
  export function verifyWithCopySpec(original, candidate, profile, spec, brief) {
@@ -144,17 +152,19 @@ export function verifyRebuildWithCopySpec(original, candidate, profile, spec, br
144
152
  const logicLint = lintLogic(candidate, brief);
145
153
  const factLint = brief?.factSources?.length ? lintFacts({ sources: brief.factSources, draft: candidate, metadata: brief.factMetadata }) : undefined;
146
154
  const requiredFacts = verifyRequiredFacts(candidate, brief);
155
+ const unresolvedStrictFindings = strictFindings(checked);
147
156
  return {
148
157
  version: '2',
149
158
  original: baseline,
150
159
  candidate: checked,
151
160
  preservationScore: preservation,
152
161
  regressions,
162
+ strictFindings: unresolvedStrictFindings,
153
163
  claims,
154
164
  finalOutput: finalCheck,
155
165
  logicLint,
156
166
  ...(factLint ? { factLint } : {}), ...(requiredFacts ? { requiredFacts } : {}),
157
- passed: checked.passed && !regressions.some(isBlockingFinding) && claims.passed && finalCheck.accepted && logicLint.passed && !factLint?.findings.some((finding) => finding.severity === 'error') && (requiredFacts?.passed ?? true),
167
+ passed: checked.passed && unresolvedStrictFindings.length === 0 && !regressions.some(isBlockingFinding) && claims.passed && finalCheck.accepted && logicLint.passed && !factLint?.findings.some((finding) => finding.severity === 'error') && (requiredFacts?.passed ?? true),
158
168
  };
159
169
  }
160
170
  function projectDeterministicVerificationArtifact(source, candidate, profile, verification, copySpec, writingBrief, verificationKind = 'claims' in verification ? 'copy_spec' : 'standard') {
@@ -83,7 +83,7 @@ test('orders rewrite instructions by importance tier', () => {
83
83
  });
84
84
  test('gives strong, source-faithful repair feedback without authorizing a broader rewrite', () => {
85
85
  const prompt = rewritePrompt('The scheduler failed — twice. Experts say this changes everything. The launch is on 14 August.', profile);
86
- assert.match(prompt, /Treat each blocker as a required repair/);
86
+ assert.match(prompt, /Every active AI Editor finding is a required repair/);
87
87
  assert.match(prompt, /Do not invent a source, metric, date, quotation, mechanism, example, CTA, or opinion/);
88
88
  assert.match(prompt, /otherwise remove the unsupported framing without widening the claim/);
89
89
  assert.match(prompt, /Before responding, check every Tier 1 finding against its replacement/);
@@ -119,9 +119,11 @@ test('advisory and pending-judgment findings pass while blocking findings fail',
119
119
  const blocking = analyze('The scheduler failed — twice.', profile);
120
120
  assert.equal(blocking.passed, false);
121
121
  });
122
- test('verify rejects only new policy-blocking regressions', () => {
122
+ test('verify rejects unresolved active AI Editor findings under the default strict policy', () => {
123
123
  const neutralProfile = buildProfile(['I write plainly.', 'I name the mechanism.']);
124
- assert.equal(verify('The scheduler failed twice.', 'The scheduler failed twice. We leverage logs.', neutralProfile).passed, true);
124
+ const unresolved = verify('The scheduler failed twice.', 'The scheduler failed twice. We leverage logs.', neutralProfile);
125
+ assert.equal(unresolved.passed, false);
126
+ assert.ok(unresolved.strictFindings.some((finding) => finding.id === 'ai.leverage'));
125
127
  assert.equal(verify('The scheduler failed twice.', 'The scheduler failed — twice.', profile).passed, false);
126
128
  });
127
129
  test('keeps verify pass/fail on the legacy metric while calibration reports both metrics', () => {
@@ -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.5.1');
177
+ assert.equal(HYV_VERSION, '3.6.0');
178
178
  });
179
179
  test('apply rejects forged tasks, missing capability, and substituted profiles', () => {
180
180
  const reduction = rebuildRecommendation();
@@ -106,7 +106,7 @@ export function prepareRewriteTask(draft, profile, copySpec, writingBrief, autho
106
106
  const result = analyze(draft, profile, writingBrief);
107
107
  const prompt = renderRewritePrompt(draft, profile, result, [], writingBrief);
108
108
  const mapped = sentences(draft);
109
- const eligibleSentenceIds = new Set([...deriveEditScope(result).eligibleSentenceIds, ...authorizedSentenceIds]);
109
+ const eligibleSentenceIds = new Set([...deriveEditScope(result, true).eligibleSentenceIds, ...authorizedSentenceIds]);
110
110
  const taskBase = {
111
111
  version: '1',
112
112
  draft,
@@ -21,18 +21,19 @@ test('applies only eligible numbered replacements and preserves all clean bytes'
21
21
  assert.equal(result.candidate, 'I use the answer. The launch is on 14 August.');
22
22
  assert.deepEqual(result.receipt.adapterIds, []);
23
23
  });
24
- test('does not make an advisory broad catalog match eligible', () => {
24
+ test('makes every active AI Editor finding eligible under the default strict rewrite policy', () => {
25
25
  const task = prepareRewriteTask('I write clear notes. This work is meaningful. I keep the mechanism visible.', profile);
26
- assert.deepEqual(task.eligibleSentenceIds, []);
27
- assert.deepEqual(task.sentences.map((sentence) => sentence.eligible), [false, false, false]);
26
+ assert.deepEqual(task.eligibleSentenceIds, [2]);
27
+ assert.deepEqual(task.sentences.map((sentence) => sentence.eligible), [false, true, false]);
28
+ assert.match(task.prompt, /Every active AI Editor finding is a required repair/);
28
29
  });
29
- test('grants edit scope only to blocking findings and keeps pending judgment separate', () => {
30
+ test('makes advisory and judgment-required findings required repairs', () => {
30
31
  const advisory = prepareRewriteTask('Firstly, check the invoice.', profile);
31
- assert.deepEqual(advisory.eligibleSentenceIds, []);
32
+ assert.deepEqual(advisory.eligibleSentenceIds, [1]);
32
33
  const neutralProfile = buildProfile(['I write plainly.', 'I name the mechanism.']);
33
34
  const pending = prepareRewriteTask('We leverage the scheduler.', neutralProfile);
34
- assert.deepEqual(pending.eligibleSentenceIds, []);
35
- assert.match(pending.prompt, /pending judgment/i);
35
+ assert.deepEqual(pending.eligibleSentenceIds, [1]);
36
+ assert.match(pending.prompt, /Every active AI Editor finding is a required repair/);
36
37
  const blocking = prepareRewriteTask('The scheduler failed — twice. The owner checked it.', profile);
37
38
  assert.deepEqual(blocking.eligibleSentenceIds, [1]);
38
39
  });
@@ -40,9 +41,9 @@ test('keeps blocking VoiceDNA avoid findings eligible', () => {
40
41
  const task = prepareRewriteTask('The plan uses leverage.', profile);
41
42
  assert.deepEqual(task.eligibleSentenceIds, [1]);
42
43
  });
43
- test('preserves founder reframes that reconciled policy makes advisory', () => {
44
+ test('makes reconciled active founder reframes eligible under the default strict policy', () => {
44
45
  const task = prepareRewriteTask("This isn't positioning. This is proof.", profile);
45
- assert.deepEqual(task.eligibleSentenceIds, []);
46
+ assert.deepEqual(task.eligibleSentenceIds, [1]);
46
47
  });
47
48
  test('preserves a clean draft byte-for-byte when the rewrite response is empty', () => {
48
49
  const draft = 'I write clear notes.\n\nI keep the mechanism visible.\n';
@@ -8,8 +8,7 @@ function finding(id, disposition, reason, suggestion, sentence) {
8
8
  }
9
9
  function strictFinding(source, profile) {
10
10
  if (source.engine === 'ai_editor') {
11
- const disposition = source.appliedPolicy === 'blocking' ? 'block' : 'review';
12
- return finding(`strict.${source.engine}.${source.id}`, disposition, source.reason, source.suggestion, source.sentence);
11
+ return finding(`strict.${source.engine}.${source.id}`, 'block', source.reason, source.suggestion, source.sentence);
13
12
  }
14
13
  if (source.engine === 'voice_dna') {
15
14
  if (source.severity === 'red')
@@ -45,10 +45,10 @@ test('blocks an underpowered or uncalibrated voice profile before trusting voice
45
45
  const mixedFormatReport = evaluateStrictQuality('The launch starts Tuesday.', profile(), mixedFormats);
46
46
  assert.ok(mixedFormatReport.findings.some((item) => item.id === 'strict.profile.format-spread'));
47
47
  });
48
- test('requires human review for a judgment-required AI pattern and blocks a configured one', () => {
48
+ test('blocks every active AI Editor pattern under strict quality', () => {
49
49
  const review = evaluateStrictQuality('We leverage the existing checklist.', profile(), samples());
50
- assert.equal(review.disposition, 'needs-human-review');
51
- assert.ok(review.findings.some((item) => item.id === 'strict.ai_editor.ai.leverage' && item.disposition === 'review'));
50
+ assert.equal(review.disposition, 'blocked');
51
+ assert.ok(review.findings.some((item) => item.id === 'strict.ai_editor.ai.leverage' && item.disposition === 'block'));
52
52
  const blockedProfile = profile();
53
53
  blockedProfile.rulePolicy['ai.leverage'] = 'blocking';
54
54
  const blocked = evaluateStrictQuality('We leverage the existing checklist.', blockedProfile, samples());
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const HYV_VERSION = '3.5.1';
1
+ export const HYV_VERSION = '3.6.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdyourvoice/hyv",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "description": "A local-first dual-engine writing gate that protects voice and catches generic AI patterns.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,4 +23,4 @@ hyv analyze draft.md profile.json [writing-brief.json]
23
23
 
24
24
  ## Handoff
25
25
 
26
- Run `hyv analyze` directly to execute the operation. For ordinary editing, follow on with hyv-rewrite-prompt or hyv-prepare-rewrite. When the caller asks for a strict AI-pattern and voice-match decision, hand off to hyv-strict-check with a calibrated Profile v3 and the required local samples; analysis alone is not a strict-ready result.
26
+ Run `hyv analyze` directly to execute the operation. Follow with hyv-rewrite-prompt or hyv-prepare-rewrite: both use strict-by-default repair scope, so every active AI Editor finding becomes a required repair. For calibrated voice evidence, hand off to hyv-strict-check with a Profile v3 and the required local samples; analysis alone is not a strict-ready result.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Analyze"
3
3
  short_description: "Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft."
4
- default_prompt: "Use $hyv-analyze to run separate voicedna and ai editor checks plus a non-scoring unicode hygiene inspection against a draft."
4
+ default_prompt: "Use $hyv-analyze to inspect this draft, then use the strict-by-default rewrite workflow so every active AI Editor finding is repaired."
@@ -16,6 +16,7 @@ hyv apply-rebuild task.json response.json profile.json (--capability-stdin|--cap
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - Strict by default: evaluation rejects a rebuild candidate with any unresolved active AI Editor finding.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Apply Rebuild"
3
3
  short_description: "Validate and evaluate a whole-document rebuild response against a prepared authorized rebuild task."
4
- default_prompt: "Use $hyv-apply-rebuild to validate and evaluate a whole-document rebuild response against a prepared authorized rebuild task."
4
+ default_prompt: "Use $hyv-apply-rebuild to reject a whole-document rebuild response with any unresolved active AI Editor finding."
@@ -16,6 +16,7 @@ hyv apply-rewrite task.json response.json profile.json
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - The task's default strict policy rejects the candidate at deterministic verification when an active AI Editor finding remains. A successful deterministic result is not calibrated `strict-ready` voice evidence.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Apply Rewrite"
3
3
  short_description: "Validate and apply a model response to a prepared rewrite task, then run local gates."
4
- default_prompt: "Use $hyv-apply-rewrite to validate and apply a model response to a prepared rewrite task, then run local gates."
4
+ default_prompt: "Use $hyv-apply-rewrite to validate a response through the strict default local gates."
@@ -16,6 +16,7 @@ hyv prepare-rebuild draft.md profile.json reduction.json copy-spec.json task.jso
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - Strict by default: the later rebuild evaluation rejects a candidate with any unresolved active AI Editor finding.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Prepare Rebuild"
3
3
  short_description: "Prepare a rebuild task only after an upstream REBUILD recommendation, a CopySpec, and a signed rebuild-authorization capability."
4
- default_prompt: "Use $hyv-prepare-rebuild to prepare a rebuild task only after an upstream rebuild recommendation, a copyspec, and a signed rebuild-authorization capability."
4
+ default_prompt: "Use $hyv-prepare-rebuild to prepare a strict rebuild task only after an upstream rebuild recommendation, a copyspec, and a signed rebuild-authorization capability."
@@ -16,6 +16,7 @@ hyv prepare-rewrite draft.md profile.json task.json [copy-spec.json] [writing-br
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - Strict by default: the task authorizes repairs for every active AI Editor finding and keeps unrelated sentences locked.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Prepare Rewrite"
3
3
  short_description: "Prepare a local, versioned, fingerprint-bound rewrite task."
4
- default_prompt: "Use $hyv-prepare-rewrite to prepare a local, versioned, fingerprint-bound rewrite task."
4
+ default_prompt: "Use $hyv-prepare-rewrite to prepare a strict local, versioned, fingerprint-bound rewrite task."
@@ -16,6 +16,7 @@ hyv rebuild-writer-request task.json writer-request.json
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - The resulting whole-document candidate remains subject to strict default verification: every active AI Editor finding must be repaired before it can proceed.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Rebuild Writer Request"
3
3
  short_description: "Create the writer-only payload for a prepared rebuild task."
4
- default_prompt: "Use $hyv-rebuild-writer-request to create the writer-only payload for a prepared rebuild task."
4
+ default_prompt: "Use $hyv-rebuild-writer-request to create the writer-only payload for a prepared rebuild task that will receive strict default verification."
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: hyv-rewrite-prompt
3
- description: Create a constrained editing brief without rewriting the draft.
3
+ description: Create a strict constrained editing brief without rewriting the draft.
4
4
  ---
5
5
 
6
6
  # hyv-rewrite-prompt
7
7
 
8
- Create a constrained editing brief. It does not rewrite the draft or call a model. The brief is printed to stdout for redirect into a markdown file.
8
+ Create a strict constrained editing brief. It does not rewrite the draft or call a model. The brief is printed to stdout for redirect into a markdown file.
9
9
 
10
10
  ## Usage
11
11
 
@@ -16,9 +16,10 @@ hyv rewrite-prompt draft.md profile.json [writing-brief.json] > rewrite-brief.md
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - Strict by default: every active AI Editor finding is a required, source-faithful repair. Do not return a candidate with an active finding unresolved; a separate calibrated `hyv-strict-check` is required only when the caller needs a `strict-ready` voice-evidence result.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
22
23
  ## Handoff
23
24
 
24
- Run `hyv rewrite-prompt` directly to execute the operation. Follow-on agents: hyv-prepare-rewrite.
25
+ Run `hyv rewrite-prompt` directly to execute the operation. Follow-on agents: hyv-prepare-rewrite, hyv-verify.
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1.0.0",
3
3
  "id": "hyv-rewrite-prompt",
4
4
  "title": "hyv-rewrite-prompt",
5
- "description": "Create a constrained editing brief without rewriting the draft.",
5
+ "description": "Create a strict constrained editing brief without rewriting the draft.",
6
6
  "instruction_file": "SKILL.md",
7
7
  "role": "Brief author",
8
8
  "workflow_phase": "plan",
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Rewrite Prompt"
3
- short_description: "Create a constrained editing brief without rewriting the draft."
4
- default_prompt: "Use $hyv-rewrite-prompt to create a constrained editing brief without rewriting the draft."
3
+ short_description: "Create a strict editing brief without rewriting the draft."
4
+ default_prompt: "Use $hyv-rewrite-prompt to create a strict constrained editing brief that requires repair of every active AI Editor finding."
@@ -5,7 +5,7 @@ description: Run the local strict AI-pattern and calibrated voice-match gate.
5
5
 
6
6
  # hyv-strict-check
7
7
 
8
- Run the opt-in strict local quality gate without changing the draft, profile, or learning state.
8
+ Run the calibrated strict local quality gate without changing the draft, profile, or learning state.
9
9
 
10
10
  ## Usage
11
11
 
@@ -17,8 +17,8 @@ hyv strict-check draft.md profile-v3.json sample-a.md sample-b.md sample-c.md sa
17
17
 
18
18
  - Requires a Profile v3 built from at least five samples, five or more local validation samples, 1,500 validation-sample words, no duplicate samples, a consistent visible format baseline, and calibrated fingerprint tolerances.
19
19
  - Returns `strict-ready`, `needs-human-review`, or `blocked` as JSON and exits `0` only for `strict-ready`.
20
- - `blocked` covers missing strict evidence, configured blocking AI patterns, hard calibrated fingerprint drift, red VoiceDNA findings, and unresolved hidden text.
21
- - `needs-human-review` keeps advisory and judgment-required AI patterns visible. It is not a clean result.
20
+ - `blocked` covers missing strict evidence, every active AI Editor pattern, hard calibrated fingerprint drift, red VoiceDNA findings, and unresolved hidden text.
21
+ - `needs-human-review` covers remaining non-blocking VoiceDNA or editorial review findings. It is not a clean result.
22
22
  - It remains local-first and never sends samples or drafts to a provider.
23
23
 
24
24
  ## Handoff
@@ -16,9 +16,10 @@ hyv verify original.md candidate.md profile.json [writing-brief.json]
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - Strict by default: verification fails when the candidate has any active AI Editor finding, including an advisory or judgment-required match. An explicit Profile v3 `disabled` policy remains a deliberate user exception.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
22
23
  ## Handoff
23
24
 
24
- Run `hyv verify` directly to execute the operation. If the candidate must satisfy the strict AI-pattern and voice-match policy, run hyv-strict-check before any lifecycle handoff. A passing verify report does not substitute for strict-ready. Follow-on agents: hyv-strict-check, hyv-lifecycle.
25
+ Run `hyv verify` directly to execute the operation. For calibrated voice evidence, run hyv-strict-check before any lifecycle handoff. A passing verify report does not substitute for strict-ready. Follow-on agents: hyv-strict-check, hyv-lifecycle.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Verify"
3
3
  short_description: "Verify a revised candidate against an original draft and portable profile."
4
- default_prompt: "Use $hyv-verify to verify a revised candidate against an original draft and portable profile."
4
+ default_prompt: "Use $hyv-verify to reject any revised candidate with an unresolved active AI Editor finding."
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: hyv-verify-spec
3
- description: Verify a candidate against the existing voice gates and a local CopySpec.
3
+ description: Verify a candidate against strict default voice gates and a local CopySpec.
4
4
  ---
5
5
 
6
6
  # hyv-verify-spec
7
7
 
8
- 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.
8
+ Verify a candidate against strict default voice gates and a local CopySpec. Immutable claims remain verbatim unless atoms are supplied; then each declared atom must remain. Prohibited claims fail closed.
9
9
 
10
10
  ## Usage
11
11
 
@@ -16,6 +16,7 @@ hyv verify-spec original.md candidate.md profile.json copy-spec.json [writing-br
16
16
  ## Behavior
17
17
 
18
18
  - Deterministic and local-first: this command never calls a provider and never sends drafts, samples, profiles, or telemetry to a service.
19
+ - Strict by default: verification fails when the candidate has any active AI Editor finding, in addition to CopySpec failures.
19
20
  - Commands that write use an explicit output path. Confirm that path before running the command.
20
21
  - This agent describes how to invoke the command and what it returns. It does not change command behavior or exit codes.
21
22
 
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1.0.0",
3
3
  "id": "hyv-verify-spec",
4
4
  "title": "hyv-verify-spec",
5
- "description": "Verify a candidate against the existing voice gates and a local CopySpec.",
5
+ "description": "Verify a candidate against strict default voice gates and a local CopySpec.",
6
6
  "instruction_file": "SKILL.md",
7
7
  "role": "CopySpec verifier",
8
8
  "workflow_phase": "verify",
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Hyv Verify Spec"
3
- short_description: "Verify a candidate against the existing voice gates and a local CopySpec."
4
- default_prompt: "Use $hyv-verify-spec to verify a candidate against the existing voice gates and a local copyspec."
3
+ short_description: "Verify a candidate against strict default voice gates and a local CopySpec."
4
+ default_prompt: "Use $hyv-verify-spec to reject a candidate with any active AI Editor finding or CopySpec failure."