@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.
Files changed (87) hide show
  1. package/Readme.md +66 -116
  2. package/dist/agents/catalog.js +118 -0
  3. package/dist/agents/catalog.test.js +60 -0
  4. package/dist/agents/emit.js +42 -0
  5. package/dist/agents/emit.test.js +64 -0
  6. package/dist/agents/index.js +4 -0
  7. package/dist/agents/load.js +184 -0
  8. package/dist/agents/load.test.js +149 -0
  9. package/dist/agents/types.js +18 -0
  10. package/dist/cli.js +464 -317
  11. package/dist/cli.test.js +76 -0
  12. package/dist/fact-linter.js +107 -98
  13. package/dist/mcp.js +43 -192
  14. package/dist/rebuild-task.test.js +1 -1
  15. package/dist/release-audit.test.js +5 -1
  16. package/dist/stage1-evaluation.js +74 -48
  17. package/dist/version.js +1 -1
  18. package/package.json +3 -3
  19. package/skills/hyv-analyze/SKILL.md +24 -0
  20. package/skills/hyv-analyze/agent.json +49 -0
  21. package/skills/hyv-analyze/agents/openai.yaml +4 -0
  22. package/skills/hyv-apply-hidden-text-policy/SKILL.md +24 -0
  23. package/skills/hyv-apply-hidden-text-policy/agent.json +49 -0
  24. package/skills/hyv-apply-hidden-text-policy/agents/openai.yaml +4 -0
  25. package/skills/hyv-apply-rebuild/SKILL.md +24 -0
  26. package/skills/hyv-apply-rebuild/agent.json +49 -0
  27. package/skills/hyv-apply-rebuild/agents/openai.yaml +4 -0
  28. package/skills/hyv-apply-rewrite/SKILL.md +24 -0
  29. package/skills/hyv-apply-rewrite/agent.json +48 -0
  30. package/skills/hyv-apply-rewrite/agents/openai.yaml +4 -0
  31. package/skills/hyv-batch-analyze/SKILL.md +24 -0
  32. package/skills/hyv-batch-analyze/agent.json +45 -0
  33. package/skills/hyv-batch-analyze/agents/openai.yaml +4 -0
  34. package/skills/hyv-fact-lint/SKILL.md +24 -0
  35. package/skills/hyv-fact-lint/agent.json +50 -0
  36. package/skills/hyv-fact-lint/agents/openai.yaml +4 -0
  37. package/skills/hyv-final-check/SKILL.md +24 -0
  38. package/skills/hyv-final-check/agent.json +43 -0
  39. package/skills/hyv-final-check/agents/openai.yaml +4 -0
  40. package/skills/hyv-hygiene/SKILL.md +24 -0
  41. package/skills/hyv-hygiene/agent.json +50 -0
  42. package/skills/hyv-hygiene/agents/openai.yaml +4 -0
  43. package/skills/hyv-inspect-hidden-text/SKILL.md +24 -0
  44. package/skills/hyv-inspect-hidden-text/agent.json +48 -0
  45. package/skills/hyv-inspect-hidden-text/agents/openai.yaml +4 -0
  46. package/skills/hyv-learning/SKILL.md +24 -0
  47. package/skills/hyv-learning/agent.json +48 -0
  48. package/skills/hyv-learning/agents/openai.yaml +4 -0
  49. package/skills/hyv-lifecycle/SKILL.md +24 -0
  50. package/skills/hyv-lifecycle/agent.json +48 -0
  51. package/skills/hyv-lifecycle/agents/openai.yaml +4 -0
  52. package/skills/hyv-logic-lint/SKILL.md +24 -0
  53. package/skills/hyv-logic-lint/agent.json +47 -0
  54. package/skills/hyv-logic-lint/agents/openai.yaml +4 -0
  55. package/skills/hyv-mcp/SKILL.md +24 -0
  56. package/skills/hyv-mcp/agent.json +43 -0
  57. package/skills/hyv-mcp/agents/openai.yaml +4 -0
  58. package/skills/hyv-patterns/SKILL.md +24 -0
  59. package/skills/hyv-patterns/agent.json +43 -0
  60. package/skills/hyv-patterns/agents/openai.yaml +4 -0
  61. package/skills/hyv-prepare-judgment/SKILL.md +24 -0
  62. package/skills/hyv-prepare-judgment/agent.json +52 -0
  63. package/skills/hyv-prepare-judgment/agents/openai.yaml +4 -0
  64. package/skills/hyv-prepare-rebuild/SKILL.md +24 -0
  65. package/skills/hyv-prepare-rebuild/agent.json +54 -0
  66. package/skills/hyv-prepare-rebuild/agents/openai.yaml +4 -0
  67. package/skills/hyv-prepare-rewrite/SKILL.md +24 -0
  68. package/skills/hyv-prepare-rewrite/agent.json +51 -0
  69. package/skills/hyv-prepare-rewrite/agents/openai.yaml +4 -0
  70. package/skills/hyv-profile/SKILL.md +24 -0
  71. package/skills/hyv-profile/agent.json +50 -0
  72. package/skills/hyv-profile/agents/openai.yaml +4 -0
  73. package/skills/hyv-rebuild-writer-request/SKILL.md +24 -0
  74. package/skills/hyv-rebuild-writer-request/agent.json +47 -0
  75. package/skills/hyv-rebuild-writer-request/agents/openai.yaml +4 -0
  76. package/skills/hyv-reduce-judgment/SKILL.md +24 -0
  77. package/skills/hyv-reduce-judgment/agent.json +45 -0
  78. package/skills/hyv-reduce-judgment/agents/openai.yaml +4 -0
  79. package/skills/hyv-rewrite-prompt/SKILL.md +24 -0
  80. package/skills/hyv-rewrite-prompt/agent.json +48 -0
  81. package/skills/hyv-rewrite-prompt/agents/openai.yaml +4 -0
  82. package/skills/hyv-verify/SKILL.md +24 -0
  83. package/skills/hyv-verify/agent.json +49 -0
  84. package/skills/hyv-verify/agents/openai.yaml +4 -0
  85. package/skills/hyv-verify-spec/SKILL.md +24 -0
  86. package/skills/hyv-verify-spec/agent.json +50 -0
  87. package/skills/hyv-verify-spec/agents/openai.yaml +4 -0
package/Readme.md CHANGED
@@ -2,168 +2,118 @@
2
2
 
3
3
  [![npm downloads](https://img.shields.io/npm/dt/%40holdyourvoice%2Fhyv?label=npm%20downloads&color=2f81f7)](https://www.npmjs.com/package/@holdyourvoice/hyv)
4
4
 
5
- Hold Your Voice is a local-first writing gate. It helps you use AI without losing the parts of your writing that make it yours.
5
+ Hold Your Voice (`hyv`) is a local writing checker. It helps you edit AI-assisted writing without losing your own writing patterns.
6
6
 
7
- It runs two checks on a draft, with separate scores and pass states. A strong result in one engine leaves the other engine's finding unchanged.
7
+ It runs two independent checks:
8
8
 
9
- - **VoiceDNA** compares your draft with observable elements from your own writing samples.
10
- - **AI Editor** flags a versioned set of editorial patterns that make writing sound generic or formulaic.
9
+ - VoiceDNA compares a draft with a profile built from your writing samples.
10
+ - AI Editor finds deterministic patterns that often make writing generic or formulaic.
11
11
 
12
- Everything runs from local files. There are no accounts, no telemetry, and no runtime network requests. The optional Claude extension is a local adapter around the same engine.
12
+ The package also checks hidden Unicode, source-backed facts, document logic, and protected claims. All checks run locally, without model calls, automatic draft changes, or runtime network requests.
13
13
 
14
- > **Status:** [`@holdyourvoice/hyv`](https://www.npmjs.com/package/@holdyourvoice/hyv) runs locally and makes no runtime network requests.
14
+ ## install
15
15
 
16
- ## Start here
17
-
18
- **Requirements:** Node.js 20+, npm, and at least two writing samples you have the right to use.
19
-
20
- ### Install
16
+ You need Node.js 20 or newer and at least two writing samples you have the right to use.
21
17
 
22
18
  ```bash
23
19
  npm install --global @holdyourvoice/hyv
24
- hyv patterns
25
20
  ```
26
21
 
27
- No global install? Run any command with `npx @holdyourvoice/hyv`.
22
+ For a one-off command, replace `hyv` with `npx @holdyourvoice/hyv`.
23
+
24
+ ## basic workflow
28
25
 
29
- ### Build a profile
26
+ 1. Build a local profile from your samples.
30
27
 
31
28
  ```bash
32
- hyv profile profile.json samples/one.md samples/two.md --avoid=overused-phrase
29
+ hyv profile profile.json samples/one.md samples/two.md
33
30
  ```
34
31
 
35
- Use writing by one person, with a similar audience and format. The command needs at least two samples. It writes a portable JSON profile and keeps your samples on your machine. Repeat `--avoid=phrase` for each phrase that must block a candidate.
32
+ Add `--avoid=phrase` for wording that must block a draft. Repeat the option for more phrases.
36
33
 
37
- ### Analyze a draft
34
+ 2. Check a draft against that profile.
38
35
 
39
36
  ```bash
40
37
  hyv analyze draft.md profile.json
41
38
  ```
42
39
 
43
- The result is JSON with independent reports. The outer `passed` field is true only when each engine passes.
44
-
45
- ```json
46
- {
47
- "voiceDna": { "score": 93, "passed": true, "findings": [] },
48
- "aiEditor": { "score": 88, "passed": true, "findings": [] },
49
- "hygiene": { "suspiciousCount": 0, "fixableCount": 0, "hits": [] },
50
- "passed": true
51
- }
52
- ```
40
+ The result contains separate VoiceDNA and AI Editor reports. The top-level `passed` value is true only when every required check passes.
53
41
 
54
- ### Edit and verify
42
+ 3. Create an editing brief, edit the draft, and verify the candidate.
55
43
 
56
44
  ```bash
57
45
  hyv rewrite-prompt draft.md profile.json > rewrite-brief.md
58
46
  hyv verify draft.md candidate.md profile.json
59
47
  ```
60
48
 
61
- Give the brief and draft to a human editor or any model you trust. Ask for replacement sentences keyed by sentence number, then save them as a separate candidate file. `verify` compares the original and candidate, reports new findings, and exits `2` when the candidate fails the gate.
49
+ Send the brief to a human editor or a model you choose. Delivery stays under your control.
62
50
 
63
- ### Gate final output
51
+ 4. Check the exact text before delivery.
64
52
 
65
53
  ```bash
54
+ hyv final-check candidate.md
66
55
  producer | hyv final-check -
67
- hyv final-check final-response.md
68
56
  ```
69
57
 
70
- `final-check` is the last step before text reaches a user. It needs no profile. Clean text goes to stdout byte-for-byte. If hidden Unicode remains, stdout stays empty and the command exits `2`. Run it after the last edit, formatter, or template expansion.
58
+ `final-check` writes accepted text to stdout. It withholds output and exits with code `2` when unresolved hidden Unicode remains.
71
59
 
72
- ### Clean hidden Unicode
60
+ ## commands
73
61
 
74
- ```bash
75
- hyv hygiene draft.md
76
- hyv hygiene draft.md --fix
77
- ```
62
+ | Command | Purpose |
63
+ | --- | --- |
64
+ | `hyv profile <profile.json> <sample...>` | Build a local profile from two or more samples. |
65
+ | `hyv analyze <draft> <profile.json>` | Run VoiceDNA, AI Editor, and hygiene checks. |
66
+ | `hyv hygiene <draft> [--fix]` | Inspect hidden Unicode or write a conservative cleaned copy. |
67
+ | `hyv inspect-hidden-text <draft> [policy.json]` | Inspect hidden text with an optional policy. |
68
+ | `hyv apply-hidden-text-policy <draft> <policy.json> <output>` | Apply approved hidden-text removals. |
69
+ | `hyv final-check <path\|->` | Gate the exact text before delivery. |
70
+ | `hyv fact-lint <draft\|-> --source=id:path` | Check claims against local source files. |
71
+ | `hyv logic-lint <draft\|-> [brief.json]` | Check deterministic document logic. |
72
+ | `hyv batch-analyze <draft...>` | Find repeated openings and endings across drafts. |
73
+ | `hyv rewrite-prompt <draft> <profile.json>` | Create a constrained editing brief. |
74
+ | `hyv prepare-rewrite ...` | Create a fingerprint-bound edit task. |
75
+ | `hyv apply-rewrite ...` | Apply and verify a response to an edit task. |
76
+ | `hyv prepare-judgment ...` | Create a pre-edit or post-candidate judgment task. |
77
+ | `hyv reduce-judgment <envelope...>` | Reduce judgments to SHIP, EDIT, REBUILD, CLEAR, or ESCALATE. |
78
+ | `hyv prepare-rebuild ...` | Create an authorized whole-document rebuild task. |
79
+ | `hyv rebuild-writer-request ...` | Create the writer-only part of a rebuild task. |
80
+ | `hyv apply-rebuild ...` | Apply and verify an authorized rebuild response. |
81
+ | `hyv verify <original> <candidate> <profile.json>` | Verify a candidate without changing learning state. |
82
+ | `hyv verify-spec ...` | Verify a candidate and a CopySpec. |
83
+ | `hyv lifecycle ...` | Run semantic review and final approval steps. |
84
+ | `hyv learning ...` | Inspect or change local profile learning. |
85
+ | `hyv patterns` | Print the active AI Editor rule catalog. |
86
+ | `hyv agent list\|validate\|describe\|emit` | Inspect or emit portable agent contracts. |
87
+ | `hyv mcp` | Start the local MCP server on standard input/output. |
78
88
 
79
- Inspect zero-width characters, bidirectional controls, tag characters, and unusual spaces. Add `--fix` to write a cleaned copy while leaving the original untouched. The cleaner only removes non-semantic ASCII controls and byte-order marks; everything else is reported for review because it can carry real meaning.
80
-
81
- ## The editing loop
82
-
83
- ```mermaid
84
- flowchart LR
85
- S[Your samples] --> P[Build profile]
86
- P --> D[VoiceDNA profile]
87
- T[Draft] --> A[Analyze]
88
- D --> A
89
- A --> V[VoiceDNA report]
90
- A --> E[AI Editor report]
91
- V --> B[Tiered brief]
92
- E --> B
93
- B --> C[Human or chosen model]
94
- T --> G[Verify candidate]
95
- C --> G
96
- D --> G
97
- G --> R[Errors block; findings stay visible]
98
- R --> H[final-check before output]
99
- ```
89
+ 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.
100
90
 
101
- The tool never edits your draft. You decide which findings are valid, apply the changes yourself, and run the final check.
91
+ Run `hyv <command>` without enough arguments to see its exact usage. Read the [CLI reference](docs/wiki/CLI-Reference.md) for every option.
102
92
 
103
- ## A few more commands
93
+ ## portable agents and MCP
104
94
 
105
- | Command | Use it when |
106
- | --- | --- |
107
- | `hyv verify-spec` | A draft has facts that must stay verbatim (CopySpec). |
108
- | `hyv fact-lint` | Check a draft against local evidence sources. |
109
- | `hyv batch-analyze` | Catch exact repeated sentences across drafts. |
110
- | `hyv prepare-rewrite` / `apply-rewrite` | Fingerprint-bound sentence or range edits. |
111
- | `hyv prepare-rebuild` / `apply-rebuild` | Whole-document rebuild after an authorized recommendation. |
112
- | `hyv prepare-judgment` / `reduce-judgment` | Reduce findings to SHIP, EDIT, or REBUILD. |
113
- | `hyv lifecycle` | Semantic and human-review lifecycle steps. |
114
- | `hyv learning show` | Inspect local voice-memory preferences. |
115
-
116
- ## Commands
117
-
118
- | Command | Input | Output |
119
- | --- | --- | --- |
120
- | `hyv profile <profile.json> <sample...>` | Two or more text files | Profile JSON |
121
- | `hyv analyze <draft> <profile.json>` | Draft and profile | Analysis JSON |
122
- | `hyv hygiene <draft> [--fix] [--output=path]` | Draft | Hygiene report or cleaned copy plus receipt |
123
- | `hyv final-check <path\|->` | Any final text | Accepted text on stdout or a withheld-output report |
124
- | `hyv rewrite-prompt <draft> <profile.json>` | Draft and profile | Markdown editing brief |
125
- | `hyv prepare-rewrite <draft> <profile.json> <task.json>` | Draft and profile | Versioned task file |
126
- | `hyv apply-rewrite <task.json> <response.json> <profile.json>` | Task, response, profile | Candidate evaluation JSON |
127
- | `hyv prepare-judgment <pre-edit\|post-candidate> <kind> <draft> <profile.json> <task.json> [candidate.md]` | Draft, profile, optional candidate | Versioned judgment task |
128
- | `hyv reduce-judgment <envelope.json> ...` | Signed judgment envelopes | Recommendation JSON |
129
- | `hyv prepare-rebuild <draft> <profile.json> <reduction.json> <copy-spec.json> <task.json> [--recomposition-policy policy.json]` | Draft, recommendation, CopySpec, capability, optional policy | Versioned rebuild task |
130
- | `hyv apply-rebuild <task.json> <response.json> <profile.json>` | Task, response, profile, capability | Candidate evaluation JSON |
131
- | `hyv verify <original> <candidate> <profile.json>` | Original, candidate, profile | Verification JSON and exit code |
132
- | `hyv verify-spec <original> <candidate> <profile.json> <copy-spec.json>` | Original, candidate, profile, CopySpec | Verification JSON with hard claim gate |
133
- | `hyv learning <show\|inspect\|add\|record\|ratify\|supersede\|migrate\|clear> ...` | Profile, operation, bounded metadata | Preferences or a text-free receipt |
134
- | `hyv lifecycle <prepare-semantic\|submit-verdict\|inspect\|validate-final-approval\|finalize> ...` | Versioned lifecycle artifacts | Lifecycle artifact or metadata |
135
- | `hyv patterns` | None | Ruleset JSON |
136
-
137
- Every file argument can be `-` when the command accepts input on standard input. Use `npx @holdyourvoice/hyv <command>` if you have not installed the CLI globally.
138
-
139
- ## Privacy
140
-
141
- Your samples, drafts, profiles, and candidates stay on your machine. Verification is read-only. Learning commands can write text-free local events under `~/.hyv/learning/` — profile fingerprint, finding IDs, counts, and an opaque digest. No writing text is uploaded, and the package makes no runtime network requests.
142
-
143
- Keep writing samples, edit histories, and client text out of public commits unless you hold the rights and a provenance record. A profile is aggregated JSON and can still reveal vocabulary, so store private profiles outside public repositories.
144
-
145
- ## Documentation
146
-
147
- | Read this | When you need |
148
- | --- | --- |
149
- | [The Wiki](https://github.com/shashank-sn/holdyourvoice/wiki) | Product and contributor docs. |
150
- | [Architecture](docs/ARCHITECTURE.md) | Source boundaries and extension rules. |
151
- | [Prompt contract](docs/PROMPT-CONTRACT.md) | The tier order and editing constraints. |
152
- | [VoiceDNA](docs/VOICE-DNA.md) | The 13 profile elements. |
153
- | [Fact linter](docs/wiki/Fact-Linter.md) | The source-consistency checker. |
154
- | [Support](SUPPORT.md) | Funding without a feature gate. |
95
+ The `skills/hyv-*` directories package the CLI workflows as portable agent contracts. Use `hyv agent validate` to check them and `hyv agent emit` to create a host-specific prompt or JSON contract.
96
+
97
+ The MCP server exposes the same local engine for compatible hosts. Read [Portable Agents](docs/wiki/Portable-Agents.md), [Claude Desktop setup](docs/CLAUDE-DESKTOP.md), or [Claude Code setup](docs/CLAUDE-CODE.md).
98
+
99
+ ## privacy and safety
155
100
 
156
- ## Contribute
101
+ Drafts, samples, profiles, candidates, and source files stay on your machine. The package has no accounts, telemetry, hosted analysis, or runtime network requests.
102
+
103
+ Learning commands can write text-free events under `~/.hyv/learning/`. A manually added learning instruction is stored as entered. Keep private writing, profiles, and client data out of public repositories.
104
+
105
+ VoiceDNA fit, AI-pattern findings, fact consistency, and human approval are separate results. A clean report only states that its configured checks passed. Authorship, factual truth, and publication quality still need separate evidence or review.
106
+
107
+ ## development
157
108
 
158
109
  ```bash
110
+ npm ci
159
111
  npm test
160
112
  npm run check:release
161
113
  ```
162
114
 
163
- Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. Keep changes narrow, add tests when behavior changes, and keep private or unlicensed material out of the repo.
164
-
165
- `@holdyourvoice/hyv` publishes automatically when a change reaches `main`. Bump the version in `package.json` in the same pull request as a release-worthy change; the workflow publishes only if that version is not already on npm.
115
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. The main design boundaries are in [Architecture](docs/ARCHITECTURE.md) and the full user guides are in the [wiki](https://github.com/shashank-sn/holdyourvoice/wiki).
166
116
 
167
- ## License
117
+ ## license
168
118
 
169
- [MIT](LICENSE). Third-party writing and data retain their own rights.
119
+ [MIT](LICENSE). Third-party writing and data keep their own rights.
@@ -0,0 +1,118 @@
1
+ const CATALOG = {
2
+ generic: {
3
+ id: 'generic', display_name: 'Generic coding environment', native_skills: false, subagents: false,
4
+ hooks: false, blocking_approvals: false, file_edits: false, command_execution: false, browser_automation: false,
5
+ background_tasks: false, cli: true, repository_read: false,
6
+ context_capacity: 'host-defined', filesystem_mode: 'host-defined', network_policy: 'host-defined',
7
+ subagent_isolation: false, session_reset: false, structured_output: false,
8
+ integration: 'portable Markdown instructions and standalone CLI',
9
+ },
10
+ codex: {
11
+ id: 'codex', display_name: 'Codex', native_skills: true, subagents: true,
12
+ hooks: false, blocking_approvals: true, file_edits: true, command_execution: true, browser_automation: false,
13
+ background_tasks: false, cli: true, repository_read: true,
14
+ context_capacity: 'host-defined', filesystem_mode: 'sandboxed', network_policy: 'approval-gated',
15
+ subagent_isolation: true, session_reset: true, structured_output: true,
16
+ integration: 'native skills with AGENTS.md fallback',
17
+ },
18
+ 'claude-code': {
19
+ id: 'claude-code', display_name: 'Claude Code', native_skills: true, subagents: true,
20
+ hooks: true, blocking_approvals: true, file_edits: true, command_execution: true, browser_automation: false,
21
+ background_tasks: false, cli: true, repository_read: true,
22
+ context_capacity: 'host-defined', filesystem_mode: 'host-defined', network_policy: 'approval-gated',
23
+ subagent_isolation: true, session_reset: false, structured_output: true,
24
+ integration: 'native skills and agent instructions',
25
+ },
26
+ cursor: {
27
+ id: 'cursor', display_name: 'Cursor', native_skills: false, subagents: false,
28
+ hooks: false, blocking_approvals: false, file_edits: true, command_execution: true, browser_automation: false,
29
+ background_tasks: false, cli: true, repository_read: true,
30
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'host-defined',
31
+ subagent_isolation: false, session_reset: false, structured_output: false,
32
+ integration: 'generated rules and portable instructions',
33
+ },
34
+ copilot: {
35
+ id: 'copilot', display_name: 'GitHub Copilot', native_skills: false, subagents: false,
36
+ hooks: false, blocking_approvals: false, file_edits: true, command_execution: false, browser_automation: false,
37
+ background_tasks: false, cli: true, repository_read: true,
38
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'host-defined',
39
+ subagent_isolation: false, session_reset: false, structured_output: false,
40
+ integration: 'repository instructions and custom agent definitions',
41
+ },
42
+ 'gemini-cli': {
43
+ id: 'gemini-cli', display_name: 'Gemini CLI', native_skills: false, subagents: false,
44
+ hooks: false, blocking_approvals: true, file_edits: true, command_execution: true, browser_automation: false,
45
+ background_tasks: true, cli: true, repository_read: true,
46
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'approval-gated',
47
+ subagent_isolation: false, session_reset: false, structured_output: true,
48
+ integration: 'terminal-agent instructions and standalone CLI',
49
+ },
50
+ 'ide-agent': {
51
+ id: 'ide-agent', display_name: 'IDE coding agent', native_skills: false, subagents: false,
52
+ hooks: false, blocking_approvals: false, file_edits: true, command_execution: false, browser_automation: false,
53
+ background_tasks: false, cli: true, repository_read: true,
54
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'host-defined',
55
+ subagent_isolation: false, session_reset: false, structured_output: false,
56
+ integration: 'generated rules or portable instructions',
57
+ },
58
+ windsurf: {
59
+ id: 'windsurf', display_name: 'Windsurf', native_skills: false, subagents: false,
60
+ hooks: false, blocking_approvals: false, file_edits: true, command_execution: true, browser_automation: false,
61
+ background_tasks: false, cli: true, repository_read: true,
62
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'host-defined',
63
+ subagent_isolation: false, session_reset: false, structured_output: false,
64
+ integration: 'generated workspace rules and standalone CLI',
65
+ },
66
+ cline: {
67
+ id: 'cline', display_name: 'Cline', native_skills: false, subagents: false,
68
+ hooks: false, blocking_approvals: false, file_edits: true, command_execution: true, browser_automation: false,
69
+ background_tasks: false, cli: true, repository_read: true,
70
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'host-defined',
71
+ subagent_isolation: false, session_reset: false, structured_output: false,
72
+ integration: 'generated workspace rules and standalone CLI',
73
+ },
74
+ 'roo-code': {
75
+ id: 'roo-code', display_name: 'Roo Code', native_skills: false, subagents: false,
76
+ hooks: false, blocking_approvals: false, file_edits: true, command_execution: true, browser_automation: false,
77
+ background_tasks: false, cli: true, repository_read: true,
78
+ context_capacity: 'host-defined', filesystem_mode: 'workspace', network_policy: 'host-defined',
79
+ subagent_isolation: false, session_reset: false, structured_output: false,
80
+ integration: 'generated workspace rules and standalone CLI',
81
+ },
82
+ };
83
+ export function resolveHost(id) {
84
+ return CATALOG[id] ?? CATALOG.generic;
85
+ }
86
+ export function listHosts() {
87
+ return Object.values(CATALOG).sort((a, b) => a.id.localeCompare(b.id));
88
+ }
89
+ function hostSupports(host, permission) {
90
+ switch (permission) {
91
+ case 'read_repository': return host.repository_read;
92
+ case 'write_repository': return host.file_edits;
93
+ case 'execute_commands': return host.command_execution;
94
+ case 'network': return false;
95
+ case 'browser_automation': return host.browser_automation;
96
+ case 'git_write':
97
+ case 'pull_request_write': return host.command_execution;
98
+ case 'subagents': return host.subagents;
99
+ default: return false;
100
+ }
101
+ }
102
+ export function runtime(agent, hostID) {
103
+ const host = resolveHost(hostID);
104
+ const available = [];
105
+ const unavailable = [];
106
+ for (const permission of agent.permissions) {
107
+ if (hostSupports(host, permission))
108
+ available.push(permission);
109
+ else
110
+ unavailable.push({ capability: permission, status: 'NOT_AVAILABLE' });
111
+ }
112
+ let mode = 'procedural';
113
+ if (unavailable.length > 0)
114
+ mode = 'prompt-only';
115
+ if (host.native_skills && unavailable.length === 0)
116
+ mode = 'native';
117
+ return { agent, host, available_capabilities: available, unavailable_capabilities: unavailable, execution_mode: mode };
118
+ }
@@ -0,0 +1,60 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { resolveHost, listHosts, runtime } from './catalog.js';
4
+ import { AGENT_SCHEMA_VERSION } from './types.js';
5
+ function minimalDescriptor(id, permissions) {
6
+ return {
7
+ schema_version: AGENT_SCHEMA_VERSION,
8
+ id,
9
+ title: id,
10
+ description: 'test',
11
+ instruction_file: 'SKILL.md',
12
+ role: 'Tester',
13
+ workflow_phase: 'test',
14
+ input: { required: ['draft'], optional: [] },
15
+ output: { required: ['report'], optional: [] },
16
+ evidence_requirements: ['evidence'],
17
+ permissions,
18
+ stop_conditions: ['stop'],
19
+ tool_free_mode: { available: true, behavior: 'report', unavailable_statuses: ['NOT_AVAILABLE'] },
20
+ handoff_to: [],
21
+ };
22
+ }
23
+ test('resolves known hosts and falls back to generic for unknown ids', () => {
24
+ assert.equal(resolveHost('codex').id, 'codex');
25
+ assert.equal(resolveHost('future-ide').id, 'generic');
26
+ assert.equal(resolveHost('future-ide').native_skills, false);
27
+ assert.equal(resolveHost('future-ide').cli, true);
28
+ });
29
+ test('catalog has unique host ids and includes generic', () => {
30
+ const hosts = listHosts();
31
+ const ids = hosts.map((host) => host.id);
32
+ assert.ok(ids.includes('generic'));
33
+ assert.equal(new Set(ids).size, ids.length);
34
+ });
35
+ test('runtime marks unsupported permissions unavailable and switches to prompt-only', () => {
36
+ const descriptor = minimalDescriptor('hyv-verify', ['read_repository', 'network', 'subagents']);
37
+ const resolved = runtime(descriptor, 'generic');
38
+ assert.deepEqual(resolved.available_capabilities, []);
39
+ assert.deepEqual(resolved.unavailable_capabilities, [
40
+ { capability: 'read_repository', status: 'NOT_AVAILABLE' },
41
+ { capability: 'network', status: 'NOT_AVAILABLE' },
42
+ { capability: 'subagents', status: 'NOT_AVAILABLE' },
43
+ ]);
44
+ assert.equal(resolved.execution_mode, 'prompt-only');
45
+ });
46
+ test('runtime uses native mode when the host supports every permission', () => {
47
+ const descriptor = minimalDescriptor('hyv-verify', ['read_repository', 'execute_commands']);
48
+ const resolved = runtime(descriptor, 'codex');
49
+ assert.deepEqual(resolved.available_capabilities, ['read_repository', 'execute_commands']);
50
+ assert.deepEqual(resolved.unavailable_capabilities, []);
51
+ assert.equal(resolved.execution_mode, 'native');
52
+ });
53
+ test('network is never available on any host', () => {
54
+ for (const host of listHosts()) {
55
+ const resolved = runtime(minimalDescriptor('hyv-verify', ['network']), host.id);
56
+ assert.deepEqual(resolved.available_capabilities, []);
57
+ assert.deepEqual(resolved.unavailable_capabilities, [{ capability: 'network', status: 'NOT_AVAILABLE' }]);
58
+ assert.equal(resolved.execution_mode, 'prompt-only');
59
+ }
60
+ });
@@ -0,0 +1,42 @@
1
+ import { runtime } from './catalog.js';
2
+ export function describe(pkg, hostID) {
3
+ return runtime(pkg.descriptor, hostID);
4
+ }
5
+ export function emitJson(pkg, hostID) {
6
+ return JSON.stringify(describe(pkg, hostID), null, 2);
7
+ }
8
+ function printable(values) {
9
+ return values.length === 0 ? 'none' : values.join(', ');
10
+ }
11
+ function printableUnavailable(values) {
12
+ return values.length === 0 ? 'none' : values.map(({ capability, status }) => `${capability} (${status})`).join(', ');
13
+ }
14
+ function runtimeSummary(descriptor) {
15
+ const host = descriptor.host;
16
+ return `Context capacity: ${host.context_capacity}; filesystem mode: ${host.filesystem_mode}; network policy: ${host.network_policy}; browser/UI: ${host.browser_automation}; subagent isolation: ${host.subagent_isolation}; session reset: ${host.session_reset}; structured output: ${host.structured_output}.`;
17
+ }
18
+ export function emitPrompt(pkg, hostID) {
19
+ const descriptor = pkg.descriptor;
20
+ const resolved = describe(pkg, hostID);
21
+ const lines = [];
22
+ lines.push(`# ${descriptor.title}`, '');
23
+ lines.push(`Role: ${descriptor.role}`);
24
+ lines.push(`Phase: ${descriptor.workflow_phase}`);
25
+ lines.push(`Execution mode: ${resolved.execution_mode}`, '');
26
+ lines.push('## Contract', '');
27
+ lines.push(`Required input: ${printable(descriptor.input.required)}`);
28
+ lines.push(`Required output: ${printable(descriptor.output.required)}`);
29
+ lines.push(`Evidence: ${printable(descriptor.evidence_requirements)}`);
30
+ lines.push(`Stop conditions: ${printable(descriptor.stop_conditions)}`, '');
31
+ lines.push('## Runtime descriptor', '');
32
+ lines.push(runtimeSummary(resolved), '');
33
+ lines.push('## Capability boundary', '');
34
+ lines.push(`Available: ${printable(resolved.available_capabilities)}`);
35
+ lines.push(`Unavailable: ${printableUnavailable(resolved.unavailable_capabilities)}`, '');
36
+ lines.push(`When a capability is unavailable, ${descriptor.tool_free_mode.behavior} Status must be one of: ${printable(descriptor.tool_free_mode.unavailable_statuses)}.`, '');
37
+ lines.push('## Handoff', '');
38
+ lines.push(`Next agents: ${printable(descriptor.handoff_to)}`, '');
39
+ lines.push('## Instructions', '');
40
+ lines.push(pkg.instructions.trim());
41
+ return `${lines.join('\n')}\n`;
42
+ }
@@ -0,0 +1,64 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { describe, emitJson, emitPrompt } from './emit.js';
4
+ import { AGENT_SCHEMA_VERSION } from './types.js';
5
+ function makePackage(id) {
6
+ return {
7
+ directory: '/pkg',
8
+ instructions: 'Usage: hyv verify original.md candidate.md profile.json',
9
+ descriptor: {
10
+ schema_version: AGENT_SCHEMA_VERSION,
11
+ id,
12
+ title: 'hyv-verify',
13
+ description: 'verify a candidate',
14
+ instruction_file: 'SKILL.md',
15
+ role: 'Verifier',
16
+ workflow_phase: 'verify',
17
+ input: { required: ['original', 'candidate'], optional: ['writing brief'] },
18
+ output: { required: ['verification report'], optional: [] },
19
+ evidence_requirements: ['preserve revision evidence'],
20
+ permissions: ['read_repository', 'network'],
21
+ stop_conditions: ['stop when evidence is stale'],
22
+ tool_free_mode: {
23
+ available: true,
24
+ behavior: 'report the decision and exact unavailable capability',
25
+ unavailable_statuses: ['NOT_AVAILABLE', 'NOT_CONFIGURED', 'NOT_RUN', 'STALE', 'ERROR'],
26
+ },
27
+ handoff_to: ['hyv-lifecycle'],
28
+ },
29
+ };
30
+ }
31
+ test('describe resolves a host and returns a runtime descriptor', () => {
32
+ const resolved = describe(makePackage('hyv-verify'), 'codex');
33
+ assert.equal(resolved.agent.id, 'hyv-verify');
34
+ assert.equal(resolved.host.id, 'codex');
35
+ assert.ok(resolved.available_capabilities.includes('read_repository'));
36
+ assert.deepEqual(resolved.unavailable_capabilities, [{ capability: 'network', status: 'NOT_AVAILABLE' }]);
37
+ assert.equal(resolved.execution_mode, 'prompt-only');
38
+ });
39
+ test('emitJson renders a valid JSON runtime descriptor', () => {
40
+ const body = emitJson(makePackage('hyv-verify'), 'generic');
41
+ const parsed = JSON.parse(body);
42
+ assert.equal(parsed.agent.id, 'hyv-verify');
43
+ assert.equal(parsed.host.id, 'generic');
44
+ assert.ok(Array.isArray(parsed.unavailable_capabilities));
45
+ assert.deepEqual(parsed.unavailable_capabilities, [
46
+ { capability: 'read_repository', status: 'NOT_AVAILABLE' },
47
+ { capability: 'network', status: 'NOT_AVAILABLE' },
48
+ ]);
49
+ });
50
+ test('emitPrompt includes instructions, capability boundary, and required statuses', () => {
51
+ const prompt = emitPrompt(makePackage('hyv-verify'), 'codex');
52
+ assert.match(prompt, /# hyv-verify/);
53
+ assert.match(prompt, /Role: Verifier/);
54
+ assert.match(prompt, /Usage: hyv verify original\.md candidate\.md profile\.json/);
55
+ assert.match(prompt, /Available: read_repository/);
56
+ assert.match(prompt, /Unavailable: network \(NOT_AVAILABLE\)/);
57
+ assert.match(prompt, /NOT_AVAILABLE, NOT_CONFIGURED, NOT_RUN, STALE, ERROR/);
58
+ assert.match(prompt, /Next agents: hyv-lifecycle/);
59
+ });
60
+ test('emitPrompt reports no available capabilities for a fully unavailable host', () => {
61
+ const prompt = emitPrompt(makePackage('hyv-verify'), 'generic');
62
+ assert.match(prompt, /Available: none/);
63
+ assert.match(prompt, /Execution mode: prompt-only/);
64
+ });
@@ -0,0 +1,4 @@
1
+ export { AGENT_SCHEMA_VERSION, VALID_PERMISSIONS, VALID_UNAVAILABLE_STATUSES, } from './types.js';
2
+ export { resolveHost, listHosts, runtime } from './catalog.js';
3
+ export { loadAll, loadAllFrom, sortedIds, validateAll, validateId } from './load.js';
4
+ export { describe, emitJson, emitPrompt } from './emit.js';