@tenphi/harnesspm 0.0.0-snapshot.7984e7b

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 (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +88 -0
  4. package/dist/added.js +189 -0
  5. package/dist/check.js +160 -0
  6. package/dist/cli.js +139 -0
  7. package/dist/commands.js +414 -0
  8. package/dist/config.js +43 -0
  9. package/dist/exec-outputs.js +360 -0
  10. package/dist/exec-units.js +144 -0
  11. package/dist/fetch.js +93 -0
  12. package/dist/frontmatter.js +35 -0
  13. package/dist/homes.js +17 -0
  14. package/dist/lock.js +102 -0
  15. package/dist/manifest.js +154 -0
  16. package/dist/new.js +223 -0
  17. package/dist/outputs.js +453 -0
  18. package/dist/render.js +119 -0
  19. package/dist/resolve.js +396 -0
  20. package/dist/run.js +83 -0
  21. package/dist/sections.js +121 -0
  22. package/dist/user-outputs.js +195 -0
  23. package/dist/util.js +140 -0
  24. package/dist/workspaces.js +61 -0
  25. package/docs/spec/01-overview.md +130 -0
  26. package/docs/spec/02-package-format.md +238 -0
  27. package/docs/spec/03-resolution.md +104 -0
  28. package/docs/spec/04-outputs.md +109 -0
  29. package/docs/spec/05-executables.md +158 -0
  30. package/docs/spec/06-trust.md +72 -0
  31. package/docs/spec/07-added.md +104 -0
  32. package/docs/spec/08-improving.md +77 -0
  33. package/docs/spec/09-cloud.md +47 -0
  34. package/docs/spec/10-cli.md +60 -0
  35. package/docs/spec/11-standards.md +49 -0
  36. package/docs/spec/12-dogfooding.md +51 -0
  37. package/docs/spec/13-open-questions.md +85 -0
  38. package/docs/spec/README.md +33 -0
  39. package/harness/dev.harnesspm/rules/generated-files.md +4 -0
  40. package/harness/dev.harnesspm/rules/harnesspm-config.md +11 -0
  41. package/harness/plugin.json +13 -0
  42. package/harness/skills/harnesspm-export/SKILL.md +39 -0
  43. package/harness/skills/harnesspm-import/SKILL.md +35 -0
  44. package/harness/skills/harnesspm-improve/SKILL.md +38 -0
  45. package/package.json +60 -0
  46. package/schema/harnesspm.schema.json +31 -0
@@ -0,0 +1,238 @@
1
+ # 2. Package format
2
+
3
+ **In short**
4
+
5
+ - A harness is a `harness/` folder at the package root. It is an [Agent Plugins 1.0](https://agent-plugins.org/) plugin: `plugin.json`, `skills/`, `mcp.json`.
6
+ - Everything harnesspm adds lives in the standard's extension namespace: the `dev.harnesspm` field in `plugin.json` and the folder of the same name.
7
+ - Every unit has an ID, `<plugin name>:<kind>/<name>`, used to exclude, replace or patch it.
8
+
9
+ The smallest harness is two files:
10
+
11
+ ```text
12
+ harness/
13
+ ├── plugin.json {"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "mylib"}
14
+ └── dev.harnesspm/rules/usage.md one rule, always on
15
+ ```
16
+
17
+ The portable parts (skills and MCP servers) work in any Agent Plugins client, even without harnesspm.
18
+
19
+ ## 2.1 Location
20
+
21
+ 1. A package MUST put its harness in a folder named `harness/` at the package root.
22
+ 2. If `package.json` has a `files` field, it MUST include `harness`.
23
+ 3. harnesspm MUST NOT run any code from a package to discover its harness. Discovery reads files only.
24
+ > pnpm 12.11 and later link package-root `skills/` folders from direct dependencies by themselves. harnesspm reads only `harness/skills/`, so the two never link the same skills twice.
25
+
26
+ ## 2.2 Layout
27
+
28
+ A package that uses every unit kind:
29
+
30
+ ```text
31
+ harness/
32
+ ├── plugin.json ← Agent Plugins manifest (required)
33
+ ├── skills/ ← Agent Skills, one folder each
34
+ │ └── uikit-review/
35
+ │ └── SKILL.md
36
+ ├── mcp.json ← Agent Plugins MCP configuration
37
+ └── dev.harnesspm/ ← harnesspm extension folder
38
+ ├── rules/
39
+ │ ├── forms.md
40
+ │ └── state-maps.md
41
+ ├── agents/
42
+ │ └── uikit-reviewer.md
43
+ ├── scripts/
44
+ │ ├── probe.mjs
45
+ │ └── check-styles.mjs
46
+ ├── hooks.json
47
+ └── permissions.json
48
+ ```
49
+
50
+ Every file is optional except `plugin.json`. A package that has only one rule is valid.
51
+
52
+ The extension folder name is the harnesspm namespace, `dev.harnesspm`, based on the domain `harnesspm.dev`. See [13-open-questions.md](13-open-questions.md#133-extension-namespace).
53
+
54
+ ## 2.3 Manifest
55
+
56
+ `plugin.json` follows Agent Plugins 1.0. Its schema is closed, so all harnesspm fields go under `extensions["dev.harnesspm"]`.
57
+
58
+ ```json
59
+ {
60
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
61
+ "name": "uikit",
62
+ "description": "Rules, docs and tools for building UIs with @cube-dev/ui-kit",
63
+ "extensions": {
64
+ "dev.harnesspm": {
65
+ "extends": ["@tenphi/tasty", "@tenphi/glaze"],
66
+ "docs": "../docs/components",
67
+ "scripts": {
68
+ "probe": {
69
+ "file": "./dev.harnesspm/scripts/probe.mjs",
70
+ "description": "Capture a story's DOM and computed CSS",
71
+ "allow": true
72
+ },
73
+ "check-styles": { "file": "./dev.harnesspm/scripts/check-styles.mjs" }
74
+ },
75
+ "exclude": ["tasty:rules/legacy-tokens"],
76
+ "replace": { "tasty:rules/lint-comments": "./dev.harnesspm/rules/lint-comments.md" }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Core fields (Agent Plugins)
83
+
84
+ | Field | Required | Notes |
85
+ | --- | --- | --- |
86
+ | `$schema` | yes | The Agent Plugins schema identifier. |
87
+ | `name` | yes | The **plugin name**. Lowercase letters, digits, `-` and `.`. harnesspm uses it as the prefix in unit IDs and script names. |
88
+ | `version` | no | If missing, harnesspm uses `version` from `package.json`. |
89
+ | `description`, `author`, `homepage`, `repository`, `license`, `keywords` | no | Metadata, as defined by Agent Plugins. |
90
+
91
+ ### harnesspm extension fields
92
+
93
+ | Field | Type | Meaning |
94
+ | --- | --- | --- |
95
+ | `extends` | string[] | Packages whose harness this harness passes on. Resolved from this package's own folder. See [03-resolution.md](03-resolution.md#33-extends). |
96
+ | `exclude` | string[] | Units or whole packages to drop from what this harness passes on. |
97
+ | `replace` | object | Unit ID → path of a file that takes that unit's place. |
98
+ | `patch` | object | Rule ID → path of a patch that changes some of its sections. See [2.5](#25-rules). |
99
+ | `docs` | string | The folder with the package's docs. See [2.7](#27-docs). |
100
+ | `scripts` | object | Script name → script definition. See [05-executables.md](05-executables.md#51-scripts). |
101
+ | `projects` | object | Git remote → folder with units for that repository. Read only in added harnesses. See [07-added.md](07-added.md#72-example). |
102
+
103
+ Unknown fields inside the extension MUST make `harnesspm check` fail, so typos do not ship.
104
+
105
+ ## 2.4 Unit kinds
106
+
107
+ | Kind | Location | Format | Activation |
108
+ | --- | --- | --- | --- |
109
+ | Rule | `<ns>/rules/*.md` | Markdown with optional frontmatter | Always on, file-scoped, or on demand. See [2.5](#25-rules). |
110
+ | Skill | `skills/<name>/SKILL.md` | [Agent Skills](https://agentskills.io/specification) | The agent loads it when relevant or when asked. |
111
+ | Sub-agent | `<ns>/agents/*.md` | Markdown: `name` and `description` frontmatter, instructions in the body. See [2.6](#26-sub-agents). | The agent starts it. |
112
+ | Doc | The folder named in `docs` | Markdown | Listed in an index. The agent reads a page when needed. |
113
+ | Script | Named in `scripts` | Any executable file | Run through `harnesspm run`. |
114
+ | Hook | `<ns>/hooks.json` | See [05-executables.md](05-executables.md#52-hooks) | The agent runs it on an event. |
115
+ | MCP server | `mcp.json` | Agent Plugins MCP configuration | The agent starts it. |
116
+ | Permission | `<ns>/permissions.json`, and `allow` on scripts | See [05-executables.md](05-executables.md#54-permissions) | The agent applies it. |
117
+
118
+ `<ns>` is the extension folder, `dev.harnesspm`. A file that `replace` or `patch` names is not a unit of its own, even inside `rules/`.
119
+
120
+ An agent picks a skill by its `description`. Say what the skill does, then, after "Use when", the situations and the words people use when they need it. Being picked is likely, not certain. When a skill must run in a situation, also add an always-on rule that names it: "To trace UI text, use the `uikit-text-origin` skill."
121
+
122
+ ## 2.5 Rules
123
+
124
+ A rule is one Markdown file about one topic. Small files make overrides simple: a consumer replaces or drops a whole file, or patches a few of its sections.
125
+
126
+ ```markdown
127
+ ---
128
+ description: How to write state maps in Tasty styles
129
+ paths: ["**/*.tsx", "**/*.ts"]
130
+ ---
131
+
132
+ # State maps
133
+
134
+ - Put every state of one property in one map.
135
+ - …
136
+ ```
137
+
138
+ | Frontmatter | Activation |
139
+ | --- | --- |
140
+ | none | **Always on.** The text goes into the agent's main instructions. |
141
+ | `paths`, with or without `description` | **File-scoped.** Active when the agent works on matching files. The description, if any, is the text of the pointer line. |
142
+ | `description` only | **On demand.** Listed with its description. The agent reads it when relevant. |
143
+
144
+ Always-on rules cost context in every session. A package SHOULD keep them short and move details into on-demand rules, docs or skills. `harnesspm check` reports the size of a package's always-on text.
145
+
146
+ A package SHOULD split a rule into small sections with `##` headings, so that others can change one section with a patch.
147
+
148
+ ### Patches
149
+
150
+ A patch changes some sections of a rule and keeps the rest. It is a Markdown file:
151
+
152
+ ```markdown
153
+ ---
154
+ remove: ["Legacy tokens"]
155
+ ---
156
+
157
+ ## Disable comments
158
+
159
+ Use `oxlint-disable-next-line`. `eslint-disable` comments are ignored in this project.
160
+ ```
161
+
162
+ 1. A **section** is a heading and everything up to the next heading of the same or a higher level.
163
+ 2. Each section in the patch **replaces** the section with the same heading text and level in the rule.
164
+ 3. A section whose heading the rule does not have is **added** at the end.
165
+ 4. Text before the first heading of the patch is **added** at the end of the rule.
166
+ 5. `remove` in the frontmatter lists headings whose sections are **removed**.
167
+
168
+ A patch keeps the rule's ID. `harnesspm why` lists the patches applied to a unit. Patches work on rules only. Where patches come from and in which order they apply is defined in [03-resolution.md](03-resolution.md#34-priority).
169
+
170
+ ## 2.6 Sub-agents
171
+
172
+ ```markdown
173
+ ---
174
+ name: uikit-reviewer
175
+ description: Reviews UI changes against UI Kit rules. Use for UI pull requests.
176
+ claude: { tools: "Read, Grep, Glob", model: "sonnet" }
177
+ codex: { model_reasoning_effort: "medium" }
178
+ ---
179
+
180
+ Review the change like an owner. Do not edit files.
181
+ ```
182
+
183
+ | Part | Claude Code | Codex |
184
+ | --- | --- | --- |
185
+ | `name`, `description` | frontmatter | `name`, `description` |
186
+ | Body | the prompt | `developer_instructions` |
187
+ | `claude:` | copied into the frontmatter | ignored |
188
+ | `codex:` | ignored | copied into the TOML file |
189
+
190
+ Only `name`, `description` and the body are portable. Agent-specific settings, such as models and tool limits, go under the agent's own key. Keys that widen what the agent may do (`permissionMode` for Claude Code, `sandbox_mode` and `approval_policy` for Codex) are written only for the project, added harnesses and trusted packages.
191
+
192
+ ## 2.7 Docs
193
+
194
+ ```json
195
+ "docs": "../docs"
196
+ ```
197
+
198
+ 1. The path is relative to `harness/`. It MAY point outside `harness/` but MUST stay inside the package.
199
+ 2. If `<dir>/llms.txt` exists, harnesspm uses it as the index. Its format is the [llms.txt](https://llmstxt.org/) convention: a Markdown list of links with short descriptions.
200
+ 3. Otherwise harnesspm builds the index from every `.md` and `.mdx` page. A page's title is its frontmatter `title`, else its first heading, else its file name. Its frontmatter `description` follows the link. To leave pages out, write an `llms.txt`.
201
+ 4. The index points at the files of the **installed** version. See [04-outputs.md](04-outputs.md#42-stable-package-paths).
202
+
203
+ ## 2.8 Unit IDs
204
+
205
+ Every unit has an ID: `<plugin name>:<kind>/<name>`.
206
+
207
+ | Unit | ID |
208
+ | --- | --- |
209
+ | `harness/dev.harnesspm/rules/state-maps.md` in plugin `tasty` | `tasty:rules/state-maps` |
210
+ | `harness/skills/uikit-review/` in plugin `uikit` | `uikit:skills/uikit-review` |
211
+ | script `probe` in plugin `uikit` | `uikit:scripts/probe` |
212
+ | server `docs` in `mcp.json` of plugin `uikit` | `uikit:mcp/docs` |
213
+ | hook entry with `"id": "check-styles"` | `uikit:hooks/check-styles` |
214
+ | `harness/dev.harnesspm/agents/uikit-reviewer.md` | `uikit:agents/uikit-reviewer` |
215
+ | the docs of plugin `uikit` | `uikit:docs` |
216
+ | `permissions.json` of plugin `uikit` | `uikit:permissions` |
217
+
218
+ `exclude` also accepts a package name, such as `@tenphi/glaze`, to drop a whole package.
219
+
220
+ Commands name scripts and MCP servers in a short form, `<plugin name>:<name>`: `harnesspm run uikit:probe`, `harnesspm mcp uikit:docs`. Agents see an MCP server as `<plugin name>-<server>`, such as `uikit-docs`.
221
+
222
+ ## 2.9 Naming rules
223
+
224
+ 1. Plugin names MUST be unique within one resolved graph. On a clash, harnesspm stops and names both packages. The consumer can then exclude one of them.
225
+ 2. **harnesspm never renames a skill.** The Agent Skills format requires a skill's `name` to match its folder name, and agents look skills up by that name.
226
+ 3. Skill names SHOULD start with the plugin name, such as `uikit-review`, so names from different packages do not collide. `harnesspm check` warns when they do not.
227
+ 4. When two layers ship a skill with the same name, the higher layer wins and harnesspm prints a warning.
228
+ 5. Script names MUST be unique within a plugin. Agents call them as `<plugin name>:<script>`, such as `uikit:probe`.
229
+
230
+ ## 2.10 Validation
231
+
232
+ `harnesspm check` runs at `prepack` and fails when:
233
+
234
+ - `plugin.json` or `mcp.json` breaks the Agent Plugins schema;
235
+ - a skill breaks the Agent Skills format;
236
+ - a harnesspm extension field is unknown or has the wrong type;
237
+ - a path points outside the package, or at a file the npm tarball would not include;
238
+ - a link in a rule, skill or doc points at a file that will not be published.
@@ -0,0 +1,104 @@
1
+ # 3. Resolution
2
+
3
+ **In short**
4
+
5
+ - Three layers, lowest to highest: **dependencies**, **project**, **added** harnesses.
6
+ - A dependency passes on the harness of other packages only through `extends`.
7
+ - A higher layer changes units below it only through `exclude`, `replace` and `patch`. Unit IDs never clash between packages, because plugin names are unique.
8
+
9
+ ## 3.1 Layers
10
+
11
+ | Layer | Source | Committed outputs? |
12
+ | --- | --- | --- |
13
+ | 📦 **Dependencies** | Harness packages in the project's dependencies, and what they `extends` | yes |
14
+ | 🏢 **Project** | `harnesspm.json` and the project's own `harness/` | yes |
15
+ | ➕ **Added** | Harnesses a person adds on top (see [07-added.md](07-added.md)). User scope first, then local scope, each in the order added | no |
16
+
17
+ Committed outputs MUST be computed from the dependency and project layers only. Two teammates with different added harnesses produce identical committed files.
18
+
19
+ ## 3.2 Discovery
20
+
21
+ For each workspace, harnesspm starts from its `dependencies`, `devDependencies` and `optionalDependencies`:
22
+
23
+ 1. Resolve each one from the workspace folder, the way Node.js does.
24
+ 2. Keep it if it has a `harness/` folder.
25
+ 3. Follow `extends` of every kept package (see [3.3](#33-extends)).
26
+
27
+ harnesspm reads files only. It never runs code from a dependency during discovery. Every dependency with a harness is included unless the project excludes it.
28
+
29
+ ## 3.3 Extends
30
+
31
+ `extends` lets a package pass on the harness of packages it builds on.
32
+
33
+ ```json
34
+ "extends": ["@tenphi/tasty", "@tenphi/glaze"]
35
+ ```
36
+
37
+ 1. Each name MUST be resolved **from the folder of the package that declares it**, so the version is the one that package uses. When copies with different versions remain, [3.5](#35-one-package-several-versions) picks one.
38
+ 2. harnesspm follows `extends` recursively and stops with an error on a cycle.
39
+ 3. Only `extends` passes harness on. A library's other dependencies, including its development tools, never reach its users.
40
+ 4. A package's `exclude`, `replace` and `patch` change what it passes on. A middle package can fix an upstream rule for all its users.
41
+
42
+ Example: an app depends only on `@cube-dev/ui-kit`, which extends Tasty and Glaze. The app gets all three, in the versions UI Kit installed.
43
+
44
+ ## 3.4 Priority
45
+
46
+ Packages in the dependency layer are ordered **upstream first**: a package comes after the packages it extends. Unrelated packages are ordered by name.
47
+
48
+ | Situation | Result |
49
+ | --- | --- |
50
+ | A project entry repeats a unit ID of its own harness | The entry's unit wins. |
51
+ | `exclude` names a unit or a package | It is dropped, with everything only it extends. |
52
+ | `replace` names a unit | The new file takes the unit's ID and place. |
53
+ | `patch` names a rule | Its sections change as defined in [02-package-format.md](02-package-format.md#25-rules). |
54
+ | Two skills have the same name | The later one wins, with a warning. |
55
+ | An override names a unit that does not exist | Warning. It usually means an upstream unit was renamed. |
56
+
57
+ For one rule, harnesspm takes the original or the highest `replace`, then applies every patch from the layers above it, lowest layer first. A patch from a layer below the winning `replace` is dropped with a warning.
58
+
59
+ When an upgrade changes a section that a patch replaces or removes, `harnesspm sync` warns and names the section, because the patch may now be out of date. The lock stores a hash of each such section to detect this.
60
+
61
+ ## 3.5 One package, several versions
62
+
63
+ Outputs point at one stable path per package ([04-outputs.md](04-outputs.md#42-stable-package-paths)), so harnesspm uses one version of each package in a repository:
64
+
65
+ 1. A direct dependency of a workspace wins.
66
+ 2. Otherwise the copy closest to a workspace in the `extends` chain wins.
67
+ 3. Among equals, the highest version wins.
68
+
69
+ harnesspm warns and names the versions.
70
+
71
+ ## 3.6 Monorepos
72
+
73
+ Workspaces come from the `workspaces` field of the root `package.json` or from `pnpm-workspace.yaml`.
74
+
75
+ 1. Each workspace is resolved on its own dependencies, from its own folder. Backend workspaces do not get frontend rules.
76
+ 2. Units that the root reaches, or that every workspace shares, are written once, at the root.
77
+ 3. A workspace's own `harness/` belongs to that workspace. A workspace that another workspace depends on is still the project's own code, so it is in the project layer and trusted.
78
+ 4. Executable units are written at the root, where both agents read them.
79
+ 5. One version per package applies to the whole repository ([3.5](#35-one-package-several-versions)).
80
+
81
+ ## 3.7 Project configuration
82
+
83
+ `harnesspm.json` is committed. Every field is optional.
84
+
85
+ ```json
86
+ {
87
+ "$schema": "https://unpkg.com/@tenphi/harnesspm/schema/harnesspm.schema.json",
88
+ "exclude": ["@tenphi/glaze"],
89
+ "patch": { "tasty:rules/lint-comments": "./harnesspm/patches/tasty-lint-comments.patch.md" },
90
+ "trust": { "@cube-dev/ui-kit": true }
91
+ }
92
+ ```
93
+
94
+ | Field | Default | Meaning |
95
+ | --- | --- | --- |
96
+ | `$schema` | — | The JSON schema, for editors. |
97
+ | `exclude` | `[]` | Packages or unit IDs to drop. |
98
+ | `replace` | `{}` | Unit ID → path of a project file that takes its place. |
99
+ | `patch` | `{}` | Rule ID → path of a project patch that changes some of its sections. |
100
+ | `trust` | `{}` | Which packages may run executable units. See [06-trust.md](06-trust.md). |
101
+
102
+ The project's own `harness/`, if it has one, resolves like any harness package: its `extends` bring packages into the dependency layer, and its `exclude`, `replace` and `patch` apply here as they do for its users. `harnesspm.json` applies after it, for this repository only. So contributors see what users see, plus the repository's own changes.
103
+
104
+ harnesspm writes outputs for both Claude Code and Codex. Outputs for an agent a person does not use cost nothing: they are per machine and excluded from git.
@@ -0,0 +1,109 @@
1
+ # 4. Outputs
2
+
3
+ **In short**
4
+
5
+ - harnesspm writes each agent's **native** files, where the agent already looks.
6
+ - Of the outputs, only the managed blocks in `AGENTS.md`, the `@AGENTS.md` lines in `CLAUDE.md` and `harnesspm.lock.json` are committed. Everything else is written on each machine and kept out of git.
7
+ - People never edit outputs. Every output names its source.
8
+
9
+ Package authors do not need this chapter. It defines what implementations write.
10
+
11
+ ## 4.1 Principles
12
+
13
+ 1. **Native files only.** No agent has to understand a harnesspm format.
14
+ 2. **Commit only what reviewers need.** The managed block in `AGENTS.md` shows rule changes in pull requests. The lock shows every other unit change. All other outputs are per machine.
15
+ 3. **Per-machine outputs are excluded through `.git/info/exclude`.** That file belongs to the clone, is never committed, and is shared by worktrees. harnesspm never edits `.gitignore`.
16
+ 4. **Units of added harnesses never enter a tracked file.** harnesspm MUST refuse to write one there.
17
+ 5. **Outputs are marked.** Each generated file or block says it is generated and names the sources to edit.
18
+ 6. **harnesspm edits only its own entries** in files that people or agents also write, and keeps the rest unchanged.
19
+
20
+ ## 4.2 Stable package paths
21
+
22
+ Package managers place packages at paths that differ between machines. harnesspm creates one stable link per harness package and uses only these paths in outputs:
23
+
24
+ ```
25
+ node_modules/.harnesspm/pkgs/@tenphi/tasty → <real location of the package>
26
+ ```
27
+
28
+ A job that does not install dependencies, such as a review bot, runs `npx -y @tenphi/harnesspm fetch`. It downloads only the harness packages in the lock with `npm pack`, checks each tarball against the lock's integrity, and fills these paths and the generated files, so pointers in the committed block work there too. It reuses installed copies, never runs package code and writes no agent settings.
29
+
30
+ ## 4.3 Project outputs
31
+
32
+ These hold the dependency and project layers, plus added harnesses with local scope and the project entries of all added harnesses.
33
+
34
+ | Unit | Claude Code | Codex |
35
+ | --- | --- | --- |
36
+ | Always-on rules, docs index, script list (team) | Managed block in `AGENTS.md` (committed), reached through `@AGENTS.md` in `CLAUDE.md` | Managed block in `AGENTS.md` (committed) |
37
+ | Always-on rules (added harnesses) | Managed block in `CLAUDE.local.md` | `developer_instructions` in `.codex/config.toml` |
38
+ | File-scoped rules | Link in `.claude/rules/harnesspm/<plugin>/` with `paths`, plus a pointer line in the block | Pointer line in the block |
39
+ | On-demand rules | Pointer line with its description | Pointer line with its description |
40
+ | Skills | Link in `.claude/skills/<name>`, plus `Skill(<name>)` in `permissions.allow` of `.claude/settings.local.json` for skills from the project, an added harness or a trusted package | Link in `.agents/skills/<name>` |
41
+ | Sub-agents | Generated `.claude/agents/<name>.md` | Generated `.codex/agents/<name>.toml` |
42
+ | Hooks, permissions | Entries in `.claude/settings.local.json` | `.codex/hooks.json`, `.codex/rules/harnesspm.rules` |
43
+ | MCP servers | Local scope: `projects.<project>.mcpServers` in `~/.claude.json`, where `claude mcp add-json --scope local` writes them | `[mcp_servers.<id>]` in `.codex/config.toml` |
44
+
45
+ Notes:
46
+
47
+ 1. `CLAUDE.local.md` counts as a `CLAUDE.md` for Claude Code. In a repository with only `AGENTS.md`, harnesspm MUST start `CLAUDE.local.md` with `@AGENTS.md`, or Claude would stop reading `AGENTS.md`.
48
+ 2. harnesspm owns `.codex/` and writes it on each machine. If a team already commits `.codex/config.toml`, harnesspm does not touch it and `harnesspm status` lists the units Codex is missing. The team moves those settings into `harness/`, which feeds both agents.
49
+ 3. A file-scoped rule has two outputs for Claude Code on purpose: the link activates it natively on matching files, and the pointer line in the shared block serves Codex.
50
+ 4. File-scoped and on-demand rules of added harnesses are pointer lines with absolute paths, in `CLAUDE.local.md` and the user-level blocks. They get no `.claude/rules/` link, because Claude Code treats a link to a file outside the working folder as an external import that needs approval. Patched rules and docs indexes of added harnesses are generated into `~/.harnesspm/generated/`, because a repository may have no `node_modules`.
51
+ 5. An added harness's `exclude` or `patch` of a team always-on rule cannot change committed text. harnesspm writes the change into the local block instead ("ignore rule X", or "in rule X, section Y now says: …"), which both agents read after the team block.
52
+
53
+ ## 4.4 Outputs of added harnesses, user scope
54
+
55
+ These go to the agents' user-level folders, so they work in every repository, including ones without harnesspm.
56
+
57
+ | Unit | Claude Code | Codex |
58
+ | --- | --- | --- |
59
+ | Rules | Managed block in `~/.claude/CLAUDE.md` | Managed block in `~/.codex/AGENTS.md` |
60
+ | Skills | Link in `~/.claude/skills/`, plus `Skill(<name>)` in `permissions.allow` of `~/.claude/settings.json` | Link in `~/.agents/skills/` |
61
+ | Sub-agents | Generated in `~/.claude/agents/` | Generated in `~/.codex/agents/` |
62
+ | Hooks, permissions | Entries in `~/.claude/settings.json` | `~/.codex/hooks.json`, `~/.codex/rules/harnesspm.rules` |
63
+ | MCP servers | User scope: `mcpServers` in `~/.claude.json` | `[mcp_servers.<id>]` in a managed block at the end of `~/.codex/config.toml` |
64
+ | Write access to the working copies of added harnesses | `permissions.additionalDirectories` | `sandbox_workspace_write.writable_roots` |
65
+ | The hook that applies added harnesses in each repository ([07-added.md](07-added.md#75-applying-in-every-repository)) | `SessionStart` entry in `~/.claude/settings.json` | `SessionStart` entry in `~/.codex/hooks.json` |
66
+
67
+ ## 4.5 Monorepos
68
+
69
+ 1. A workspace with its own units gets a managed block in `<workspace>/AGENTS.md`. Codex joins `AGENTS.md` files from the root down to the current folder. Paths in a workspace's block are relative to its folder. A block that becomes empty is removed, and so is a file that held only the block.
70
+ 2. Claude Code does not read a nested `AGENTS.md` when the root has a `CLAUDE.md`. harnesspm writes `<workspace>/CLAUDE.md` with `@AGENTS.md` when it is missing. Both files are committed.
71
+ 3. Skills and executable units are written at the root.
72
+
73
+ ## 4.6 Managed blocks
74
+
75
+ A managed block is the only part of a Markdown file harnesspm owns. Text outside it never changes. Without markers, harnesspm appends a block at the end.
76
+
77
+ ```markdown
78
+ <!-- BEGIN:harnesspm — generated by `harnesspm sync`. Do not edit. To change a rule, run `npx harnesspm why <ID>`. -->
79
+ ## @tenphi/tasty 3.17.1 (via @cube-dev/ui-kit)
80
+
81
+ - Never put dynamic values into styles. Use tokens. (tasty:rules/style-values)
82
+ - When editing `**/*.tsx`, read node_modules/.harnesspm/pkgs/@tenphi/tasty/harness/dev.harnesspm/rules/state-maps.md (tasty:rules/state-maps)
83
+ - Docs: node_modules/.harnesspm/pkgs/@tenphi/tasty/docs/llms.txt
84
+
85
+ ## @cube-dev/ui-kit 0.206.1
86
+
87
+ - …
88
+ - Scripts: `npx harnesspm run uikit:probe <story>` — capture a story's DOM and computed CSS
89
+ <!-- END:harnesspm -->
90
+ ```
91
+
92
+ Every line carries its unit ID, so an agent can find the source with `harnesspm why`.
93
+
94
+ ## 4.7 Files shared with people
95
+
96
+ `CLAUDE.md` files, `~/.codex/AGENTS.md`, `.claude/settings.local.json` and `~/.claude/settings.json` are also written by people, or by the agent itself (Claude Code saves "don't ask again" approvals into `.claude/settings.local.json`). In these files, and in `~/.claude.json`, `.codex/hooks.json` and `~/.codex/config.toml`, harnesspm changes only its managed block or the entries it created. It records its entries in a per-machine state file, `harnesspm/state.json` in the clone's git folder or `~/.harnesspm/state.json`, so it can remove them later. A settings file that held only harnesspm's entries is deleted with them.
97
+
98
+ ## 4.8 Why native files and not plugins
99
+
100
+ Both agents have plugin systems. harnesspm does not generate plugins in v1, because Claude Code cloud sessions do not install plugins that a repository declares, and plugins hide changes from pull request review. See [13-open-questions.md](13-open-questions.md#135-plugin-output-mode).
101
+
102
+ ## 4.9 Size limits
103
+
104
+ | Agent | Limit |
105
+ | --- | --- |
106
+ | Codex | `AGENTS.md` files are joined up to `project_doc_max_bytes`, 32 KiB by default. The rest is cut off. |
107
+ | Claude Code | No hard limit. The vendor advises under about 200 lines per `CLAUDE.md`. |
108
+
109
+ `harnesspm status` reports the always-on size per agent and per package, and warns near a limit.
@@ -0,0 +1,158 @@
1
+ # 5. Executables
2
+
3
+ **In short**
4
+
5
+ - A package declares **scripts**. Agents and hooks run them through one command: `npx harnesspm run <plugin>:<script>`. MCP servers start through `npx harnesspm mcp <plugin>:<server>`.
6
+ - Hooks and MCP servers are written once and translated for each agent.
7
+ - A package can allow only its own scripts. It can always restrict (deny, ask).
8
+ - Nothing executable runs unless the project trusts the package ([06-trust.md](06-trust.md)).
9
+
10
+ ## 5.1 Scripts
11
+
12
+ A script is a file a package ships for agents, hooks or MCP servers to run.
13
+
14
+ ```json
15
+ "scripts": {
16
+ "probe": {
17
+ "file": "./dev.harnesspm/scripts/probe.mjs",
18
+ "description": "Capture a story's DOM and computed CSS",
19
+ "allow": true
20
+ }
21
+ }
22
+ ```
23
+
24
+ | Field | Required | Meaning |
25
+ | --- | --- | --- |
26
+ | `file` | yes | Path inside `harness/`, starting with `./`. |
27
+ | `description` | no | Shown to the agent in the script list. Without it, the script is hidden from the agent and only hooks and MCP servers use it. |
28
+ | `allow` | no | The producer suggests that agents may run it without asking. Takes effect only when the consumer trusts the package. See [5.4](#54-permissions). |
29
+
30
+ ### The runner
31
+
32
+ Every script runs through one command:
33
+
34
+ ```bash
35
+ npx harnesspm run uikit:probe -- --story Button
36
+ ```
37
+
38
+ The runner:
39
+
40
+ 1. finds the package through the stable path in `node_modules/.harnesspm/pkgs/`, so the installed version always runs;
41
+ 2. refuses the script when its package is not trusted, when the installed version differs from the lock (run `harnesspm sync`), or when a `"pin"` package has a version nobody approved;
42
+ 3. picks the interpreter from the file: `node` for `.js`, `.mjs` and `.cjs`, `sh` for `.sh`, otherwise the file itself;
43
+ 4. sets these environment variables:
44
+
45
+ | Variable | Value |
46
+ | --- | --- |
47
+ | `PLUGIN_ROOT` | The package's `harness/` folder (same meaning as in Agent Plugins) |
48
+ | `PLUGIN_DATA` | `~/.harnesspm/data/<plugin name>/`, kept across updates (same meaning as in Agent Plugins) |
49
+ | `HARNESSPM_PROJECT_DIR` | The project root |
50
+ | `HARNESSPM_AGENT` | `claude`, `codex`, or empty when a person runs it. harnesspm puts it into the hook and MCP commands it writes, and reads the agent's marker for a script an agent runs ([06-trust.md](06-trust.md#62-trusting-a-package), rule 4). |
51
+
52
+ The runner must start fast, because hooks call it on every matching tool call.
53
+
54
+ Why a runner instead of file paths:
55
+
56
+ - one command works with every package manager and folder layout;
57
+ - permission rules match one stable prefix in both agents;
58
+ - hooks, MCP servers and skills use the same command, so a `SKILL.md` works on every machine.
59
+
60
+ ## 5.2 Hooks
61
+
62
+ Claude Code and Codex use nearly the same hook model: event → matcher → command, JSON on stdin, exit code `0` to continue and `2` to block with the reason on stderr. harnesspm's format keeps that shape.
63
+
64
+ ```json
65
+ {
66
+ "hooks": {
67
+ "PostToolUse": [
68
+ {
69
+ "id": "check-styles",
70
+ "matcher": { "claude": "Edit|Write" },
71
+ "run": "check-styles",
72
+ "timeout": 30
73
+ }
74
+ ]
75
+ }
76
+ }
77
+ ```
78
+
79
+ | Field | Required | Meaning |
80
+ | --- | --- | --- |
81
+ | `id` | yes | Unit name. The ID becomes `<plugin>:hooks/<id>`. |
82
+ | `run` | yes | A script name from `scripts`. harnesspm writes the full command for each agent. |
83
+ | `matcher` | no | A string for both agents, or an object with a `claude` key, a `codex` key or both. With an object, the hook is written only for the agents it names. Tool names differ between agents (for example, file edits), so an object is common. Without a matcher, the hook matches every tool in both agents. |
84
+ | `timeout` | no | Seconds. |
85
+
86
+ ### Events
87
+
88
+ Portable events, supported by both agents: `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`, `SubagentStart`, `SubagentStop`, `Stop`.
89
+
90
+ harnesspm writes a hook only for the agents that support its event.
91
+
92
+ ### Contract for hook scripts
93
+
94
+ 1. The script reads the agent's own JSON on stdin. `HARNESSPM_AGENT` says which agent sent it.
95
+ 2. Exit `0` continues. Exit `2` blocks, for events that support blocking, with the reason on stderr.
96
+ 3. JSON on stdout, such as `hookSpecificOutput.permissionDecision`, follows the agent's own format.
97
+
98
+ Agent Plugins has an open proposal for portable hooks ([PR #89](https://github.com/agentplugins/agent-plugins-spec/pull/89)). If it becomes part of the standard, harnesspm reads the standard file as well and keeps its own format working during a migration period.
99
+
100
+ ## 5.3 MCP servers
101
+
102
+ MCP servers use the Agent Plugins `mcp.json` format.
103
+
104
+ ```json
105
+ {
106
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
107
+ "mcpServers": {
108
+ "docs": {
109
+ "type": "stdio",
110
+ "command": "node",
111
+ "args": ["${PLUGIN_ROOT}/dev.harnesspm/scripts/docs-mcp.mjs"]
112
+ },
113
+ "figma": { "type": "streamable-http", "url": "https://example.com/mcp" }
114
+ }
115
+ }
116
+ ```
117
+
118
+ harnesspm writes each server under the name `<plugin name>-<server>`, such as `uikit-docs`. For a server from the project, an added harness or a trusted package, it also writes an allow rule for its tools in Claude Code (`mcp__uikit-docs`), because MCP tool calls ask for approval even in cloud sessions. A trusted server already runs the package's code, so its tools need no opt-in of their own. Codex gets no rule.
119
+
120
+ - **stdio servers** are written as `npx harnesspm mcp uikit:docs`. The runner expands `${PLUGIN_ROOT}` and `${PLUGIN_DATA}`, sets the environment from [5.1](#51-scripts) and starts the server. Both agents get the same command.
121
+ - **Remote servers** are written as they are. Secrets never go into `mcp.json`. Authentication stays with the agent.
122
+
123
+ ## 5.4 Permissions
124
+
125
+ A dependency MUST NOT be able to grant itself broad rights. harnesspm writes allow rules only for units from the project, an added harness or a trusted package, and only for three things: skills, scripts with `"allow": true`, and MCP servers ([5.3](#53-mcp-servers)). Deny and ask rules pass through freely.
126
+
127
+ **Skills get an allow rule to run unattended.** Outside cloud sessions, Claude Code asks before it invokes a skill, and a headless run denies the call. For each skill from the project's own harness, an added harness or a trusted package, harnesspm writes `Skill(<name>)` into `permissions.allow`. The `allowed-tools` field inside a `SKILL.md` does not replace this: it only pre-approves tools while the skill runs. In cloud sessions the rule changes nothing ([5.5](#55-what-the-agents-still-decide)).
128
+
129
+ **Scripts get an allow rule when they ask for one.** When a script has `"allow": true` and the consumer trusts the package, harnesspm writes:
130
+
131
+ | Agent | Rule |
132
+ | --- | --- |
133
+ | Claude Code | `Bash(npx harnesspm run uikit:probe *)` in `permissions.allow` |
134
+ | Codex | `prefix_rule(pattern=["npx", "harnesspm", "run", "uikit:probe"], decision="allow")` |
135
+
136
+ **Deny and ask rules** only restrict. They are written as command prefixes:
137
+
138
+ ```json
139
+ {
140
+ "deny": [["git", "push", "--force"]],
141
+ "ask": [["npm", "publish"]]
142
+ }
143
+ ```
144
+
145
+ | Neutral | Claude Code | Codex |
146
+ | --- | --- | --- |
147
+ | `deny` | `Bash(git push --force *)` in `permissions.deny` | `prefix_rule(…, decision="forbidden")` |
148
+ | `ask` | `Bash(npm publish *)` in `permissions.ask` | `prefix_rule(…, decision="prompt")` |
149
+
150
+ ## 5.5 What the agents still decide
151
+
152
+ harnesspm adds a gate. It does not remove the agents' own gates:
153
+
154
+ | Agent | Own gate |
155
+ | --- | --- |
156
+ | Claude Code | Allow rules in the untracked `.claude/settings.local.json` apply without the workspace trust step, because the file belongs to the person. harnesspm's trust is the main gate here. |
157
+ | Claude Code cloud sessions | A platform rule approves built-in tools, even in default mode, but not MCP tools. Deny rules from settings still apply and win. The gates are harnesspm's trust, which decides which hooks, MCP servers and scripts exist, the lock check in `harnesspm run`, and deny rules. |
158
+ | Codex | Project `.codex/` files load only when the project is trusted. Each hook is reviewed by its hash in `/hooks`. |