@salaros/ai-harness 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -10
- package/README.md +65 -149
- package/package.json +1 -1
- package/scripts/update-harness.js +67 -3
package/LICENSE
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
1
3
|
Copyright (c) 2019 CODECAVE
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from the copyright holder.
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,186 +1,102 @@
|
|
|
1
1
|
# AI harness
|
|
2
2
|
|
|
3
|
-
A starting point
|
|
3
|
+
A starting point for projects built with AI coding agents. It gives every agent the same instructions, skills, safety hooks and documentation workflow, whether you use Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI or OpenCode.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It assumes no language or framework. What you put in `src/` decides the stack.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## What you get
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **`AGENTS.md`**: one instruction file that every agent reads.
|
|
10
|
+
- **Skills**: 60 vendored [agent skills](https://skills.sh) for requirements, design, testing, code review and more.
|
|
11
|
+
- **Agents**: `engineer`, `business-analyst`, `devops` and `assistant`, each routing work to the right skills.
|
|
12
|
+
- **Hooks**: agent hooks block dangerous shell commands and check every edit. Git hooks check commit messages, the documentation chain and formatting.
|
|
13
|
+
- **A documentation chain**: BRD → PRD → EARS → BDD → ADR → SPEC → TDD → plan → code, with a checker that keeps every document traceable to the one before it.
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
npx @salaros/ai-harness
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
It installs what the harness needs and nothing else: `AGENTS.md`, the agents and their routing, the skills, the Git hooks, and the checks those hooks call, the documentation-chain validator among them. What maintains the upstream stays behind — the installer itself, the harness test suite and its fixtures, the CI workflows, `package.json`, and the .NET and OpenCode config a project may have no use for.
|
|
16
|
-
|
|
17
|
-
The published package is the installer, not the harness. npm carries four files -- `scripts/update-harness.js`, `scripts/lib.js`, `README.md` and `LICENSE` -- and everything they install comes from a clone of this repository, made at run time from the `master` branch. So the version on npm numbers the installer, not what it installs, and two runs of the same version a month apart give you different skills. That is the trade the harness makes: `master` is the released line, reached only through a reviewed pull request, and tracking it lets a skill fix reach every repo the week it lands instead of waiting for a release. What it costs is reproducibility across time. Pass `--ref <tag>` when you need a fixed harness, and read `harness-lock.json` to see the exact commit a past run took.
|
|
18
|
-
|
|
19
|
-
It never writes `README.md` or `LICENSE`. Under `docs/`, `src/`, `tests/`, `tools/`, `workflows/` and `.scratch/` it adds the folder README where that file is absent and touches nothing else, so a `src/` with code in it gains a README and keeps the code. A first run leaves three skeletons behind when the repo has none: `MEMORY.md` with its six facts still `<placeholder>`, so the initialisation gate says what to run next; an empty `CONTEXT.md` glossary; and a `TODO.md` holding its header and nothing else. Later runs leave all three alone.
|
|
20
|
-
|
|
21
|
-
Pass `--astro-docs` to also install `tools/docs-site/`, the Astro site that renders the chain in a browser. Nothing in the harness reaches it, so it stays out unless asked for.
|
|
22
|
-
|
|
23
|
-
Every run checks its result before it finishes. Merging is not checking: the installer knows it wrote a file, not whether the result still works. So it runs the harness invariants in `scripts/check-harness.js`, from the upstream checkout against the repo it just wrote, writes nothing to run them, and reports what it found. A failure exits non-zero, the same way a conflict does. That is what catches an `AGENTS.md` whose chain table no longer parses, routing sections naming an agent this repo does not have, a skill nothing links to, or a vendored upstream with no licence row. Pass `--no-check` to skip it.
|
|
15
|
+
## Requirements
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
- [Node.js](https://nodejs.org) 22 or newer
|
|
18
|
+
- Git
|
|
19
|
+
- On Windows: Developer Mode on, and `git config --global core.symlinks true` set **before** you clone
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
## Installation
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Skills are added and updated, never removed: one you vendored yourself survives every update, and `skills-lock.json` is merged as a union.
|
|
32
|
-
|
|
33
|
-
A repo whose harness predates `harness-lock.json` is the one case where "never touch what is already there" works against you: with no receipt there is no merge base, so the old harness stays and its checks then run against the new skills and fail, naming rules this version dropped. The run says so and names `--adopt`, which replaces every harness file with the upstream's — hooks, agents, scripts, `AGENTS.md`, and a `.claude/agents` that checked out as a text file rather than a symlink. It stays opt-in because it discards your edits to those files. `--dry-run --quiet` lists them first.
|
|
34
|
-
|
|
35
|
-
That first install writes a receipt even though it kept the old files, so `--adopt` has to keep working afterwards — it is normally the self check that tells you the harness is stale, and by then the receipt exists. A run at the recorded commit says so and takes every harness file again, rather than answering the one command that fixes it with "nothing to update".
|
|
36
|
-
|
|
37
|
-
The run reports each path as it works on it: the policy, the mode Git records, what happened, and the path, with the skills tree collapsed to one line per skill. `--quiet` gives the summary alone.
|
|
38
|
-
|
|
39
|
-
## Getting started
|
|
40
|
-
|
|
41
|
-
1. **Windows only, before cloning:** enable Developer Mode (Settings → System → For developers) and run `git config --global core.symlinks true`. The `.claude/` folder is tracked as symlinks; without this, Git checks them out as text files and Claude Code sees no skills.
|
|
42
|
-
2. Clone, then install the Git hooks once: `node scripts/githooks-init.js`. Node is the only runtime the harness needs; on Windows nothing else (no `sh`) is required.
|
|
43
|
-
3. Run the `project-init` skill (`/project-init` in Claude Code). It asks, through the tool's own question prompt, what the project is, where the requirements live, the stack and, if the project has one, the issue tracker, and writes the answers to `MEMORY.md`, this README and `docs/agents/issue-tracker.md`. It hands you the scaffold commands for the stack; running them is up to you.
|
|
44
|
-
4. Skills are vendored in `.agents/skills`, so there is nothing to install. To add one: `npx skills add <owner/repo> -s <skill> -a claude-code codex -y`, then `node scripts/skills.js relink`, then commit.
|
|
45
|
-
5. Open the repo in your AI tool and check the section for your tool under [Files per AI tool](#files-per-ai-tool). Authorise the Atlassian MCP server if the project tracks work in Jira, and the Figma one if it has designs; both are optional, and a project that plans in `docs/` and has no designs needs neither. See [MCP servers](#files-per-ai-tool).
|
|
46
|
-
|
|
47
|
-
## Layout
|
|
48
|
-
|
|
49
|
-
| Path | What it is |
|
|
50
|
-
| --- | --- |
|
|
51
|
-
| `src/`, `tests/`, `scripts/`, `tools/`, `docs/` | Product code, tests, repo automation, dev utilities, documents. Each has a README describing what belongs there. |
|
|
52
|
-
| `AGENTS.md` | The one file every agent reads: layout, where skills are, domain-language pointers. Kept short on purpose. |
|
|
53
|
-
| `CLAUDE.md` | One line, `@AGENTS.md`, because Claude Code reads `CLAUDE.md` rather than `AGENTS.md`. |
|
|
54
|
-
| `MEMORY.md` | The project facts (name, purpose, the language its prose is written in, requirements location, stack, the frontend framework when the project has browser code, and the issue tracker when there is one), one per line. Written by the `project-init` skill; absent until it runs. |
|
|
55
|
-
| `TODO.md` | The loose-ends ledger: questions nobody has answered, assumptions taken on trust, and work knowingly left undone. Follows the [todo-md standard](https://github.com/todo-md/todo-md) -- a `# TODO` header, one entry per line as `- [ ] <text> #<tag> (<source>)`, `@user` and indented subtasks optional -- so its tooling reads this file. Written by the `loose-ends` skill and absent until the first entry, since an empty ledger says less than no ledger. It departs from the standard in one place: a settled entry is deleted in the commit that settles it rather than kept as `[x]` or `[-]`, because entries nobody will act on are context every agent re-reads. Needs no issue tracker. |
|
|
56
|
-
| `THIRD-PARTY-NOTICES.md`, `scripts/skill-licences.tsv` | Vendoring a skill copies someone else’s work here, and MIT and Apache-2.0 both ask that the copyright and permission notice travel with the copy; `npx skills` carries only what sits inside the skill folder, so an upstream keeping its licence at the repo root sends none. The TSV holds one row per upstream (SPDX id, copyright line, licence URL, and any restriction); `node scripts/skills.js notices` regenerates the notice from it and `skills-lock.json`. A new upstream with no row fails `node scripts/check-harness.js`. |
|
|
57
|
-
| `skills-lock.json` | Written by `npx skills`: source, path and hash of every installed skill. The single record of what is installed; `node scripts/skills.js install` restores from it. |
|
|
58
|
-
| `.agents/skills/<name>/` | The canonical, vendored copy of each skill (`SKILL.md` plus its reference files). |
|
|
59
|
-
| `.agents/hooks/` | Harness-neutral hook scripts (see [Hooks](#hooks)). |
|
|
60
|
-
| `.agents/agents/` | Agent definitions (see [Agents](#agents)). |
|
|
61
|
-
| `scripts/update-harness.js`, `scripts/harness-files.tsv`, `harness-lock.json` | The installer, the table of what travels and what does not, and the receipt naming the upstream commit a repo last took and the released installer that wrote it. The first two are the upstream’s own and are never installed; the receipt is written into the repo being updated. |
|
|
62
|
-
| `.agents/routing.md` | Route rows shared by some agents but not all, in a section per audience, plus the rule for what earns a row. What every agent needs is in `AGENTS.md` ("Working here") instead, since every session loads that. Above `agents/` because `.claude/agents` is a symlink to that folder and a harness reads everything in it as an agent. |
|
|
63
|
-
| `.claude/` | Claude Code wiring: `skills/*` and `agents` are symlinks into `.agents/`, `settings.json` wires the hooks. |
|
|
64
|
-
| `.githooks/`, `scripts/githooks-init.js` | Git hooks, run through `core.hooksPath` after `githooks-init.js` is run once per clone. They are the only shell scripts left, because Git runs them through its own `sh` on every OS; each is a two-line wrapper that finds the repo root and hands over to `scripts/githook.js`, which holds what every hook decides, so nothing is chosen in a file the suite cannot drive. `post-merge` works out what the merge brought in and feeds `scripts/on-manifest-change.js`, which restores what `scripts/stacks.tsv` says (skills, npm, pnpm, yarn, NuGet, uv). `pre-commit` and `pre-push` both start with `scripts/check-initialised.js`, which refuses an unconfigured clone: `MEMORY.md` must record the six facts that describe the project itself (name, purpose, requirements, unit type, language, runtime), none of them left as a `<placeholder>`. The `Jira` line is not among them: a tracker is a choice, not a property of the code. `pre-commit` then reads `TODO.md` and checks the shape of the loose-ends ledger against the todo-md standard — the staged copy, whose problems block the commit, or an unstaged one, whose problems are only reported, since the commit does not carry it: the `# TODO` header, and an entry carrying a known tag (`#question`, `#assumption`, `#deferred`) and a source the chain would accept. Only checkbox lines are entries, every other line is prose, and an indented subtask inherits its parent's tag and source. It never asks for entries to exist, and it rejects `[x]` and `[-]`, because a settled entry is deleted rather than kept. `pre-commit` then feeds `scripts/check-staged-docs.js`, which blocks a commit that would break the documentation chain. `commit-msg` feeds `scripts/check-commit-msg.js`, which blocks a commit whose message is not a conventional commit (`<type>(<scope>)?!?: <description>`, subject at most 72 characters, a description of at least four words, and a body of real prose after a blank line — trailers such as `Refs:` are metadata and do not count as one); messages Git writes itself for a merge, revert, fixup or squash are left alone. A message citing no Jira issue key is warned about rather than rejected, to encourage the habit without blocking a change nobody has raised a ticket for; the key comes from `docs/agents/issue-tracker.md`, and any `PROJ-123` shape counts until `project-init` records the real one. A project whose `MEMORY.md` records `Issue tracker: none` is not warned: it has no key to cite. A tracker whose references are not `KEY-123` sets `Key format:` in `docs/agents/issue-tracker.md`, so a GitHub-Issues project is warned about `#42` instead. `pre-push` feeds `scripts/format-changed.js`, which checks that the files a push publishes are formatted, using the stack's own formatter from `scripts/stacks.tsv` (`prettier`, `dotnet format` or a project's Husky.NET task runner, `ruff`). It never rewrites files: it reports and blocks, and skips silently when the stack's formatter is not installed. Formatting is checked on push rather than on commit because some formatters load the whole project and cost seconds. The restore, chain and format scripts take `--dry-run`; `check-commit-msg.js` takes the message file, or reads stdin. `git commit --no-verify` and `git push --no-verify` skip them. |
|
|
65
|
-
| `docs/agents/` | Per-repo configuration the skills read: issue tracker, triage labels, domain-doc rules. |
|
|
66
|
-
| `CODING_STANDARDS.md` | Rules the `code-review` skill applies to a diff. A stub until the stack lands; anything a tool enforces stays out of it. |
|
|
67
|
-
| `.editorconfig`, `.gitattributes`, `.gitignore`, `stylecop.json` | Encoding, indentation, line endings, ignored output and analyzer settings. Stack-specific entries are kept when they are inert on other stacks, so no stack is forced. |
|
|
68
|
-
| `.skip-project-init` | Untracked, ignored, created by hand: it tells the initialisation gate that this clone has no project to configure, such as the upstream's own repo. Never committed, so it stays with whoever made it. |
|
|
69
|
-
| `workflows/` | Workflow specs written by `loop-me`. |
|
|
70
|
-
| `.scratch/` | Committed working files: feature specs, ticket drafts, prototypes not yet on a branch. |
|
|
71
|
-
| `.mcp.json`, `opencode.json` | MCP server registrations for Claude Code and OpenCode: Atlassian for Jira, Figma for designs. Both are optional; a project with no tracker and no designs authorises neither. |
|
|
72
|
-
|
|
73
|
-
## Skills
|
|
74
|
-
|
|
75
|
-
Skills follow the Agent Skills format: a folder with a `SKILL.md` whose frontmatter carries a `name` and a `description`, plus optional reference files. They are managed with the [`skills` CLI](https://skills.sh); `skills-lock.json` records what is installed and `.agents/skills` holds the files. Commit both, plus the `.claude/skills` link.
|
|
23
|
+
### Start a new project
|
|
76
24
|
|
|
77
25
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
node scripts/skills.js relink # after any of the above, and after writing a local skill
|
|
26
|
+
git clone https://github.com/salaros/ai-harness.git my-project
|
|
27
|
+
cd my-project
|
|
28
|
+
node scripts/githooks-init.js
|
|
82
29
|
```
|
|
83
30
|
|
|
84
|
-
|
|
85
|
-
- `node scripts/skills.js install` restores `.agents/skills` from the lock file. A normal clone never needs it; the post-merge Git hook runs it when the lock changes.
|
|
86
|
-
- Do not edit a vendored skill in place; the next update overwrites it. Fork it under another name outside `.agents/skills`, or change it upstream.
|
|
87
|
-
- Two kinds of skill: **model-invoked** ones carry a description the agent matches on its own; **user-invoked** ones (`disable-model-invocation: true`) only fire when you type `/name`.
|
|
88
|
-
- The lock file records only a hash per skill, so an upstream repo going private or rewriting history is otherwise invisible. Run `npx skills update` every so often (monthly, or before a stretch of work that leans on skills), then `node scripts/check-harness.js` to catch a skill an update broke, and review the diff before committing. The current upstreams are `addyosmani/agent-skills`, `alirezarezvani/claude-skills`, `anthropics/skills`, `codewithmukesh/dotnet-claude-kit`, `dietrichgebert/ponytail`, `diskd-ai/design-doc`, `github/awesome-copilot`, `jeffallan/claude-skills`, `mattpocock/skills`, `mindrally/skills`, `openai/skills`, `softaworks/agent-toolkit`, `sprngr/rubber-duck`, `thebushidocollective/han`, `vercel-labs/agent-browser`, `vercel-labs/agent-skills`, `vercel-labs/skills`, `vuejs-ai/skills`, `wondelai/skills` and `wshobson/agents`.
|
|
89
|
-
|
|
90
|
-
`node scripts/skills.js list` prints what is installed: name, how it is invoked, which agents route it, and where it came from. It is a report, not a rule; nothing requires a skill to be routed or listed anywhere.
|
|
91
|
-
|
|
92
|
-
## Hooks
|
|
93
|
-
|
|
94
|
-
Three Node scripts in `.agents/hooks/`, run with the `node` on your PATH (Windows has no `sh` on its PATH by default, and only Claude Code and Git bring their own). Each reads the harness's JSON payload from stdin, prints a message, and uses the exit code every harness understands the same way: `0` = fine, `2` = block or send the message back to the agent.
|
|
31
|
+
Then open the folder in your AI tool and run the `project-init` skill (`/project-init` in Claude Code). It asks what the project is, where its requirements live, its stack and its issue tracker, and records the answers in `MEMORY.md`. The Git hooks refuse commits until it has run.
|
|
95
32
|
|
|
96
|
-
|
|
97
|
-
| --- | --- | --- |
|
|
98
|
-
| `session-start.js` | session start | Prints a brief into the agent's context: branch, whether Git hooks are installed, skills recorded in `skills-lock.json` but missing from disk, whether `CONTEXT.md`, `docs/adr/` and the issue-tracker config exist. |
|
|
99
|
-
| `guard-command.js` | before a shell command | Blocks force pushes, `git reset --hard`, `git clean -f`, `git branch -D`, and recursive deletes of `/`, `~`, `.git` or `*`. Checks the harness's own command field (falling back to the raw payload for an unrecognised shape) and only where the phrase could be a real invocation, so a dangerous phrase quoted as data in another command's arguments does not trip it. The agent is told to ask you instead. |
|
|
100
|
-
| `check-edit.js` | after a file write/edit | Syntax-checks `*.js`, validates `*.json`, runs `scripts/docs-check.js` for Markdown under `docs/` and for `AGENTS.md`, runs `scripts/check-harness.js` when a path its invariants read changed (the skills and their links, the routing, the Git hooks, the licence rows), runs the hook suite in the upstream when harness code or a table changed, and refuses in-place edits of vendored skills. |
|
|
33
|
+
### Add the harness to an existing repository
|
|
101
34
|
|
|
102
|
-
|
|
35
|
+
From the repository's root:
|
|
103
36
|
|
|
104
|
-
|
|
37
|
+
```bash
|
|
38
|
+
npx @salaros/ai-harness
|
|
39
|
+
```
|
|
105
40
|
|
|
106
|
-
|
|
41
|
+
The installer adds the harness files and leaves your own work alone: it never writes `README.md` or `LICENSE`, and it adds a folder README only where one is missing. It records the upstream commit it installed in `harness-lock.json`.
|
|
107
42
|
|
|
108
|
-
|
|
43
|
+
Run the same command again to update. Files you haven't edited take the new version, files you have edited keep your changes and gain the new ones, and a real conflict is written with conflict markers and reported.
|
|
109
44
|
|
|
110
|
-
|
|
|
45
|
+
| Option | Effect |
|
|
111
46
|
| --- | --- |
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
|
|
|
115
|
-
| `
|
|
116
|
-
|
|
117
|
-
|
|
47
|
+
| `--dry-run` | Show what would change, write nothing |
|
|
48
|
+
| `--quiet` | Print only the summary |
|
|
49
|
+
| `--ref <tag>` | Install a fixed version instead of the latest `master` |
|
|
50
|
+
| `--adopt` | Replace every harness file with the upstream's, for repos installed before `harness-lock.json` existed. Discards your edits to those files. |
|
|
51
|
+
| `--astro-docs` | Also install `tools/docs-site/`, a website that renders your documentation |
|
|
52
|
+
| `--no-check` | Skip the check the installer runs after writing |
|
|
53
|
+
| `--help` | Print the options and exit. Any argument not listed here stops the installer before it writes. |
|
|
118
54
|
|
|
119
|
-
|
|
55
|
+
The npm package holds only the installer. It fetches the harness from `master` at run time, so two runs a month apart may install different skills. Use `--ref` when you need the same result every time.
|
|
120
56
|
|
|
121
|
-
|
|
57
|
+
## Usage
|
|
122
58
|
|
|
123
|
-
|
|
59
|
+
Talk to your agent as usual. `AGENTS.md` tells it which skill fits the task. You can also call a skill or an agent by name:
|
|
124
60
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| Per-developer overrides | `.claude/settings.local.json` (git-ignored) | no |
|
|
132
|
-
|
|
133
|
-
Invoke an agent with "use the engineer agent to …", or a skill with `/tdd`, `/grilling`, and so on. On Windows the symlinks need Developer Mode and `core.symlinks=true` (see Getting started); if you cannot use symlinks, run `npx skills add … --copy` and copy `.agents/agents/*.md` into `.claude/agents/`.
|
|
134
|
-
|
|
135
|
-
### Other tools
|
|
61
|
+
```text
|
|
62
|
+
/grilling stress-test an idea before writing requirements
|
|
63
|
+
/brd write a business requirements document
|
|
64
|
+
/tdd build a feature test-first
|
|
65
|
+
use the engineer agent to implement docs/spec/0001-billing.md
|
|
66
|
+
```
|
|
136
67
|
|
|
137
|
-
|
|
138
|
-
| --- | --- | --- | --- | --- | --- | --- |
|
|
139
|
-
| OpenAI Codex | `AGENTS.md`, read natively | `.agents/skills/`, read natively | create `.codex/hooks.json`, same shape as `.claude/settings.json` | create `.codex/agents/<name>.toml`, one per agent | create `.codex/config.toml` with `[mcp_servers.atlassian]`, then `codex mcp login atlassian` | Edits arrive as `apply_patch` commands with the paths inside the patch; `check-edit.js` skips them until `lib.js` learns that shape. [Docs](https://developers.openai.com/codex/hooks) |
|
|
140
|
-
| Cursor | `AGENTS.md`, read natively | `.agents/skills/`, read natively | create `.cursor/hooks.json` (`sessionStart`, `beforeShellExecution`, `afterFileEdit`) | create `.cursor/agents/<name>.md`: copy or symlink | create `.cursor/mcp.json` (`mcpServers`, no `type`) | Exit 2 blocks; `file_path` and `command` are top-level payload fields, which `lib.js` reads. [Docs](https://cursor.com/docs/agent/hooks) |
|
|
141
|
-
| GitHub Copilot | `AGENTS.md`, read natively | `.agents/skills/`, read natively | create `.github/hooks/*.json` (`sessionStart`, `preToolUse`, `postToolUse`) | create `.github/agents/<name>.agent.md`: copy with the suffix | create `.vscode/mcp.json` (`servers`) | `toolArgs` is a JSON string inside the payload, which `lib.js` parses; any non-zero exit denies. [Docs](https://docs.github.com/en/copilot/reference/hooks-reference) |
|
|
142
|
-
| Gemini CLI | `GEMINI.md` by default; point it at `AGENTS.md` through `.gemini/settings.json` | `.agents/skills/`, read natively | create `.gemini/settings.json` with `hooks` (`SessionStart`, `BeforeTool`, `AfterTool`) | create `.gemini/agents/<name>.md`: copy or symlink | create `.gemini/settings.json` with `mcpServers` (`httpUrl`) | Same payload shape as Claude Code; timeouts in milliseconds and a `name` per hook. [Docs](https://geminicli.com/docs/hooks/) |
|
|
143
|
-
| OpenCode | `AGENTS.md`, read natively | `.agents/skills/`, read natively | a JS plugin under `.opencode/plugins/` that shells out to the three scripts; there are no command hooks | none | `opencode.json`, in repo | [Docs](https://opencode.ai/docs/plugins/) |
|
|
144
|
-
| Anything else | `AGENTS.md` | read each `SKILL.md` description and load what matches, as `AGENTS.md` says | wire the three scripts to the tool's events: payload on stdin, exit 2 blocks | an agent file pasted as the system prompt | its own file | |
|
|
68
|
+
Everyday commands:
|
|
145
69
|
|
|
146
|
-
|
|
70
|
+
```bash
|
|
71
|
+
node scripts/docs-check.js # check the documentation chain
|
|
72
|
+
node scripts/check-harness.js # check the harness itself
|
|
73
|
+
npx skills add <owner/repo> -s <skill> -a claude-code codex -y
|
|
74
|
+
node scripts/skills.js relink # run after adding or updating skills
|
|
75
|
+
```
|
|
147
76
|
|
|
148
|
-
|
|
77
|
+
Claude Code works as soon as you clone. Other tools need a small config file for hooks and agents; [.agents/README.md](.agents/README.md#files-per-ai-tool) lists the files for each tool.
|
|
149
78
|
|
|
150
|
-
|
|
79
|
+
## Documentation
|
|
151
80
|
|
|
152
|
-
|
|
81
|
+
- [AGENTS.md](AGENTS.md): the rules every agent follows, including the documentation chain.
|
|
82
|
+
- [docs/README.md](docs/README.md): what each document in the chain must contain.
|
|
83
|
+
- [.agents/README.md](.agents/README.md): reference for the layout, skills, hooks, agents, per-tool setup and what each skill expects.
|
|
84
|
+
- `src/`, `tests/`, `scripts/`, `tools/` and `docs/` each have a README saying what belongs in them.
|
|
153
85
|
|
|
154
|
-
|
|
86
|
+
## Releasing
|
|
155
87
|
|
|
156
|
-
|
|
88
|
+
Maintainers publish the installer to npm by pushing an annotated version tag on `master`:
|
|
157
89
|
|
|
158
|
-
|
|
90
|
+
```bash
|
|
91
|
+
git tag -a 0.2.8 -m "0.2.8" && git push origin 0.2.8
|
|
92
|
+
```
|
|
159
93
|
|
|
160
|
-
|
|
161
|
-
- `docs/agents/triage-labels.md`: maps the five triage roles (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`) to plain Jira labels of the same names.
|
|
162
|
-
- `docs/agents/domain.md`: tells skills to read `CONTEXT.md` and `docs/adr/` before exploring, and to stay silent when they are absent.
|
|
163
|
-
- `docs/agents/questions.md`: the question tool each harness offers (Claude Code, OpenCode, Copilot and so on) and the plain-text fallback, so interviews and confirmations always go through the tool. `AGENTS.md` ("Working here") makes this a rule for every skill.
|
|
164
|
-
- The `## Agent skills` section in `AGENTS.md` points at the three files.
|
|
94
|
+
The [release workflow](.github/workflows/release.yml) runs the tests, writes the tag's version into `package.json` and publishes. Leave the version in `package.json` at `0.0.0`.
|
|
165
95
|
|
|
166
|
-
|
|
96
|
+
## License
|
|
167
97
|
|
|
168
|
-
|
|
98
|
+
MIT; see [LICENSE](LICENSE). Vendored skills keep their own licences, listed in [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|
|
169
99
|
|
|
170
|
-
|
|
171
|
-
| --- | --- | --- |
|
|
172
|
-
| `code-review` | `docs/agents/issue-tracker.md`; `CODING_STANDARDS.md`; the spec, from an issue in the commit messages, a path you pass, or a file under `docs/`, `specs/` or `.scratch/` matching the branch | nothing |
|
|
173
|
-
| `to-tickets` | `docs/agents/issue-tracker.md` | drafts under `.scratch/<feature-slug>/`, then Jira issues in dependency order |
|
|
174
|
-
| `triage` | `docs/agents/issue-tracker.md`, `docs/agents/triage-labels.md`, `.out-of-scope/` | `.out-of-scope/<concept>.md` per rejected request; Jira labels and comments |
|
|
175
|
-
| `domain-modeling`, also through `grill-with-docs`, `improve-codebase-architecture` and `triage` | `CONTEXT.md`, `docs/adr/` | `CONTEXT.md`, `docs/adr/NNNN-<slug>.md`; `CONTEXT-MAP.md` only in a multi-context repo |
|
|
176
|
-
| `brd`, `prd`, `feature-forge`, `bdd-scenarios`, `design-doc`, `create-implementation-plan` | the document one stage upstream; `CONTEXT.md` when present | `docs/<stage>/NNNN-<slug>.md` per the `AGENTS.md` table (`.scratch/<feature>/` for the plan), whatever path the skill's own instructions name |
|
|
177
|
-
| `docs-check` | the `AGENTS.md` table, `docs/` | repairs in place |
|
|
178
|
-
| `loose-ends` | `TODO.md` | `TODO.md` at the repo root, created on the first entry |
|
|
179
|
-
| `project-init` | answers from the harness's question tool | `MEMORY.md`, the Project section of this README, and the key and site in `docs/agents/issue-tracker.md` when the project has a tracker |
|
|
180
|
-
| `teach` | the working directory as a workspace | `MISSION.md`, `RESOURCES.md`, `NOTES.md`, `reference/`, `lessons/`, `learning-records/`, `assets/` |
|
|
181
|
-
| `loop-me` | `NOTES.md`, shared with `teach` | `workflows/<name>.md`, `NOTES.md` |
|
|
182
|
-
| `implement`, `tdd`, `prototype` | a spec or tickets; the stack's test runner, type checker and task runner | code under `src/` and `tests/`; prototypes on a throwaway branch |
|
|
183
|
-
| `retro` | the session logs of the agent that ran | proposed edits to `AGENTS.md`, `CODING_STANDARDS.md`, the docs and the skills |
|
|
184
|
-
| `agent-browser` | the `agent-browser` CLI (`npx agent-browser` fetches it) | nothing in the repo |
|
|
100
|
+
## Contact
|
|
185
101
|
|
|
186
|
-
|
|
102
|
+
Maintained by CODECAVE. Report problems and suggestions in [GitHub issues](https://github.com/salaros/ai-harness/issues).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salaros/ai-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Installs and updates the agent harness from its upstream repository: hooks, skills, agents and the documentation chain, merged into an existing repository without touching its own work.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-harness": "scripts/update-harness.js"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// node scripts/update-harness.js --no-check install without proving it afterwards
|
|
25
25
|
// node scripts/update-harness.js --quiet the summary alone, no line per path
|
|
26
26
|
// node scripts/update-harness.js --adopt take every harness file from the upstream, losing local edits
|
|
27
|
+
// node scripts/update-harness.js --help print this usage and exit
|
|
27
28
|
// Installing into a repo that has no harness yet, from anywhere:
|
|
28
29
|
// git clone https://github.com/salaros/ai-harness .harness && \
|
|
29
30
|
// node .harness/scripts/update-harness.js --from .harness --target . && rm -rf .harness
|
|
@@ -41,6 +42,50 @@ const DEFAULT_REF = "master";
|
|
|
41
42
|
const argv = process.argv.slice(2);
|
|
42
43
|
const flag = name => argv.includes(name);
|
|
43
44
|
const value = (name, fallback) => { const i = argv.indexOf(name); return i >= 0 && argv[i + 1] ? argv[i + 1] : fallback; };
|
|
45
|
+
// Every argument the installer knows. An optional part of the harness adds its own flag through the
|
|
46
|
+
// manifest (optional:<flag>), so those are checked once the upstream checkout is read. Anything else
|
|
47
|
+
// stops the run before a file is written: this script installs when it is run, so a mistyped
|
|
48
|
+
// --dry-rn, or a --help it did not understand, used to install for real.
|
|
49
|
+
const FLAGS = ["--dry-run", "--adopt", "--quiet", "--no-check", "--help", "-h"];
|
|
50
|
+
const VALUES = ["--ref", "--target", "--from"];
|
|
51
|
+
function unknownArgs(args, optional) {
|
|
52
|
+
const known = [...FLAGS, ...optional.map(name => `--${name}`)];
|
|
53
|
+
const unknown = [];
|
|
54
|
+
for (let i = 0; i < args.length; i++) {
|
|
55
|
+
if (VALUES.includes(args[i])) i++;
|
|
56
|
+
else if (!known.includes(args[i])) unknown.push(args[i]);
|
|
57
|
+
}
|
|
58
|
+
return unknown;
|
|
59
|
+
}
|
|
60
|
+
// The part of that check that needs no manifest, so a typo fails before the clone rather than after
|
|
61
|
+
// it: anything not shaped like a flag, and anything a letter or two from a flag this script already
|
|
62
|
+
// knows, which no optional part would be named.
|
|
63
|
+
function mistypedArgs(args) {
|
|
64
|
+
const named = [...FLAGS, ...VALUES];
|
|
65
|
+
const near = (a, b) => {
|
|
66
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
67
|
+
for (let j = 1; j <= b.length; j++) d[0][j] = j;
|
|
68
|
+
for (let i = 1; i <= a.length; i++)
|
|
69
|
+
for (let j = 1; j <= b.length; j++)
|
|
70
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
71
|
+
return d[a.length][b.length] <= 2;
|
|
72
|
+
};
|
|
73
|
+
const mistyped = [];
|
|
74
|
+
for (let i = 0; i < args.length; i++) {
|
|
75
|
+
if (VALUES.includes(args[i])) { i++; continue; }
|
|
76
|
+
if (named.includes(args[i])) continue;
|
|
77
|
+
if (!/^--[a-z0-9]+(-[a-z0-9]+)*$/.test(args[i]) || named.some(n => near(args[i], n))) mistyped.push(args[i]);
|
|
78
|
+
}
|
|
79
|
+
return mistyped;
|
|
80
|
+
}
|
|
81
|
+
// Read from the comment block at the top of this file, so the usage has one copy.
|
|
82
|
+
function usage() {
|
|
83
|
+
const lines = fs.readFileSync(__filename, "utf8").split(/\r?\n/);
|
|
84
|
+
const start = lines.findIndex(l => l.startsWith("// Usage:"));
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let i = start + 1; i < lines.length && lines[i].startsWith("// "); i++) out.push(lines[i].slice(3).replace("node scripts/update-harness.js", "npx @salaros/ai-harness"));
|
|
87
|
+
return ["Installs or updates the agent harness in the current git repository.", "", "Usage:", ...out].join("\n");
|
|
88
|
+
}
|
|
44
89
|
const dryRun = flag("--dry-run");
|
|
45
90
|
const adopt = flag("--adopt");
|
|
46
91
|
|
|
@@ -293,10 +338,19 @@ const SKELETONS = {
|
|
|
293
338
|
],
|
|
294
339
|
};
|
|
295
340
|
|
|
341
|
+
// A repo that already has an INTENT.md names the product and its purpose there, so the MEMORY.md
|
|
342
|
+
// laid down beside it leaves those two out rather than asking for them a second time.
|
|
343
|
+
function skeletonLines(target, file, lines) {
|
|
344
|
+
if (file !== "MEMORY.md" || !fs.existsSync(path.join(target, "INTENT.md"))) return lines;
|
|
345
|
+
const facts = lines.filter(l => !/^- \*\*(?:Name|Purpose):\*\*/.test(l));
|
|
346
|
+
const first = facts.findIndex(l => l.startsWith("- **"));
|
|
347
|
+
return [...facts.slice(0, first), "The name and purpose are in `INTENT.md`, under `## Product`.", "", ...facts.slice(first)];
|
|
348
|
+
}
|
|
349
|
+
|
|
296
350
|
function skeletons(target) {
|
|
297
351
|
for (const [file, lines] of Object.entries(SKELETONS)) {
|
|
298
352
|
if (fs.existsSync(path.join(target, file))) { step("seed", "100644", "yours", file); continue; }
|
|
299
|
-
write(target, file, lines.join("\n"));
|
|
353
|
+
write(target, file, skeletonLines(target, file, lines).join("\n"));
|
|
300
354
|
step("seed", "100644", "created", file, "seeded");
|
|
301
355
|
}
|
|
302
356
|
}
|
|
@@ -445,6 +499,9 @@ function selfCheck(target, templateDir) {
|
|
|
445
499
|
// ---------------------------------------------------------------- the run
|
|
446
500
|
|
|
447
501
|
function main() {
|
|
502
|
+
if (flag("--help") || flag("-h")) { console.log(usage()); return; }
|
|
503
|
+
const mistyped = mistypedArgs(argv);
|
|
504
|
+
if (mistyped.length) fail(`unknown argument(s): ${mistyped.join(" ")}. Nothing was written; run with --help for the options.`);
|
|
448
505
|
const target = targetRoot();
|
|
449
506
|
if (!fs.existsSync(path.join(target, ".git"))) fail(`${target} is not a git checkout`);
|
|
450
507
|
|
|
@@ -454,6 +511,14 @@ function main() {
|
|
|
454
511
|
const { dir: templateDir, temporary } = templateCheckout(ref);
|
|
455
512
|
|
|
456
513
|
try {
|
|
514
|
+
// Checked before anything is said about the target, so a bad argument is the only message.
|
|
515
|
+
const rows = policies(templateDir);
|
|
516
|
+
const optional = rows.filter(r => r.policy.startsWith("optional:")).map(r => r.policy.slice("optional:".length));
|
|
517
|
+
const unknown = unknownArgs(argv, optional);
|
|
518
|
+
if (unknown.length) {
|
|
519
|
+
if (temporary) fs.rmSync(templateDir, { recursive: true, force: true });
|
|
520
|
+
fail(`unknown argument(s): ${unknown.join(" ")}. Nothing was written; run with --help for the options.`);
|
|
521
|
+
}
|
|
457
522
|
const head = at(templateDir, ["rev-parse", "HEAD"]).output.trim();
|
|
458
523
|
// A base is what makes this an update rather than an overwrite. Without one -- a first
|
|
459
524
|
// install, or an upstream whose history was rewritten -- an existing file is left alone
|
|
@@ -482,7 +547,6 @@ function main() {
|
|
|
482
547
|
else say(`this repo has a harness (${stale.join(", ")}) but no ${LOCK}, so it predates the receipt and there is no merge base.\nEvery harness file already here is kept, which leaves old checks running against new skills. Re-run with --adopt to replace them, or --dry-run --quiet to list them first.`);
|
|
483
548
|
}
|
|
484
549
|
|
|
485
|
-
const rows = policies(templateDir);
|
|
486
550
|
const wants = name => flag(`--${name}`);
|
|
487
551
|
const files = templateFiles(templateDir);
|
|
488
552
|
const skills = [];
|
|
@@ -680,6 +744,6 @@ function report(target, head, ref, base) {
|
|
|
680
744
|
// The decision, and the two pure helpers under it, so the suite can put a case in and read the
|
|
681
745
|
// answer out rather than building a git checkout to reach one branch. Everything else here writes to
|
|
682
746
|
// somebody's repository and stays behind main().
|
|
683
|
-
module.exports = { policyFor, decideText, decideBinary, lineCounts, overlap, NEAREST };
|
|
747
|
+
module.exports = { unknownArgs, mistypedArgs, usage, policyFor, decideText, decideBinary, lineCounts, overlap, NEAREST, skeletonLines };
|
|
684
748
|
|
|
685
749
|
if (require.main === module) main();
|