buddy-agent-harness 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/bin/buddy-agent-harness.mjs +8 -3
  5. package/dist/cli-Bvnq8zOL.mjs +2122 -0
  6. package/dist/cli-Bvnq8zOL.mjs.map +1 -0
  7. package/dist/cli.d.mts +13 -2
  8. package/dist/cli.d.mts.map +1 -1
  9. package/dist/cli.mjs +2 -20
  10. package/dist/index.d.mts +491 -40
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +2 -2
  13. package/package.json +3 -2
  14. package/plugin.json +1 -1
  15. package/skills/doctor/SKILL.md +27 -23
  16. package/skills/doctor/references/bridges.md +23 -0
  17. package/skills/doctor/references/configuration.md +12 -0
  18. package/skills/doctor/references/harnesses/claude-code.md +39 -0
  19. package/skills/doctor/references/harnesses/codex.md +37 -0
  20. package/skills/doctor/references/harnesses/copilot-cli.md +39 -0
  21. package/skills/doctor/references/harnesses/cursor.md +40 -0
  22. package/skills/doctor/references/harnesses/devin-desktop.md +20 -0
  23. package/skills/doctor/references/harnesses/gemini-cli.md +38 -0
  24. package/skills/doctor/references/harnesses/windsurf.md +27 -0
  25. package/skills/doctor/references/instructions.md +14 -0
  26. package/skills/doctor/references/mcp.md +31 -0
  27. package/skills/doctor/references/nonstandard.md +27 -0
  28. package/skills/doctor/scripts/doctor.mjs +7 -3
  29. package/skills/enhance/SKILL.md +4 -0
  30. package/skills/init/README.md +1 -1
  31. package/skills/init/SKILL.md +24 -10
  32. package/skills/init/references/detection.md +5 -3
  33. package/skills/init/references/harnesses/claude-code.md +2 -0
  34. package/skills/init/references/harnesses/cursor.md +9 -2
  35. package/skills/init/references/harnesses/gemini-cli.md +6 -9
  36. package/skills/init/scripts/doctor.mjs +14 -0
  37. package/skills/init/scripts/init.mjs +7 -3
  38. package/skills/repair/README.md +21 -0
  39. package/skills/repair/SKILL.md +74 -0
  40. package/skills/repair/references/classes.md +49 -0
  41. package/skills/repair/scripts/doctor.mjs +14 -0
  42. package/src/cli.ts +40 -7
  43. package/src/command-output/command-output.ts +1 -4
  44. package/src/diagnose-bridges/agents-files.ts +34 -0
  45. package/src/diagnose-bridges/diagnose-bridges.ts +39 -21
  46. package/src/diagnose-bridges/diagnose-instructions.ts +145 -0
  47. package/src/diagnose-bridges/doctor-guidance.ts +683 -69
  48. package/src/diagnose-bridges/doctor.command.ts +63 -30
  49. package/src/diagnose-bridges/git-bridge-state.ts +30 -0
  50. package/src/diagnose-bridges/json-with-comments.ts +60 -0
  51. package/src/diagnose-bridges/locator.ts +36 -0
  52. package/src/diagnose-configuration/diagnose-configuration.ts +99 -0
  53. package/src/diagnose-mcp/diagnose-mcp.ts +135 -0
  54. package/src/diagnose-mcp/mcp-baseline.ts +122 -0
  55. package/src/diagnose-mcp/mcp-model.ts +88 -0
  56. package/src/diagnose-mcp/mcp-secrets.ts +101 -0
  57. package/src/diagnose-mcp/mcp-sources.ts +131 -0
  58. package/src/diagnose-nonstandard/diagnose-nonstandard.ts +109 -0
  59. package/src/harness-registry/harness-registry.ts +164 -14
  60. package/src/harness-registry/instruction-bridge.ts +31 -0
  61. package/src/harness-registry/mcp-config.ts +37 -0
  62. package/src/harness-registry/nonstandard-artifact.ts +39 -0
  63. package/src/index.ts +33 -4
  64. package/src/initialize-harnesses/init.command.ts +13 -18
  65. package/src/initialize-harnesses/initialize-harnesses.ts +7 -4
  66. package/src/is-record/is-record.ts +12 -0
  67. package/src/skill-projection/skill-projection.ts +104 -18
  68. package/dist/cli.mjs.map +0 -1
  69. package/dist/init.command-c0fBv15T.mjs +0 -714
  70. package/dist/init.command-c0fBv15T.mjs.map +0 -1
@@ -1,21 +1,23 @@
1
1
  ---
2
2
  name: init
3
3
  description: Use this skill when initializing, adopting, or migrating a repository's agent configuration to the open AGENTS.md and Agent Skills standards, so one canonical source works across Claude Code, Codex, Cursor, Copilot CLI, and other agent harnesses.
4
- argument-hint: '[--root <dir>] [--harness <names>] [--copy] [--force]'
4
+ argument-hint: '[--root <dir>] [--harness <names>] [--copy] [--force <targets>]'
5
5
  ---
6
6
 
7
7
  # Harness Init
8
8
 
9
9
  Give the repository one canonical agent configuration — a root `AGENTS.md` and an `.agents/` tree — then bridge the harnesses that cannot read it directly.
10
10
 
11
- Most of the field already reads `.agents/skills/` natively: Codex, Cursor, GitHub Copilot CLI, and Devin Desktop need no projection at all. Only Claude Code and Gemini CLI read solely their own directory and need a link. Keep that asymmetry in mind — this is a consolidation job, not a copy-everywhere job.
11
+ Most of the field already reads `.agents/skills/` natively: Codex, Cursor, GitHub Copilot CLI, Gemini CLI, and Devin Desktop need no projection at all. Only Claude Code reads solely its own directory and needs a link. Keep that asymmetry in mind — this is a consolidation job, not a copy-everywhere job.
12
+
13
+ Skills and instructions are separate questions. Gemini CLI reads the canonical skills directory and still does not read `AGENTS.md`, so it needs an instruction bridge and no skills projection.
12
14
 
13
15
  `references/standard.md` defines the baseline every repository gets. Read the file below for each harness you are enabling, and only those.
14
16
 
15
17
  | Harness | Skills projection | Instruction bridge | Read |
16
18
  | --- | --- | --- | --- |
17
19
  | Claude Code | `.claude/skills` | `CLAUDE.md` with `@AGENTS.md` | `references/harnesses/claude-code.md` |
18
- | Gemini CLI | `.gemini/skills` | `.gemini/settings.json` edit | `references/harnesses/gemini-cli.md` |
20
+ | Gemini CLI | none | `.gemini/settings.json` edit | `references/harnesses/gemini-cli.md` |
19
21
  | Codex | none | none | `references/harnesses/codex.md` |
20
22
  | Cursor | none | none written; one gap to report | `references/harnesses/cursor.md` |
21
23
  | GitHub Copilot CLI | none | none | `references/harnesses/copilot-cli.md` |
@@ -25,12 +27,12 @@ Most of the field already reads `.agents/skills/` natively: Codex, Cursor, GitHu
25
27
 
26
28
  ## Arguments
27
29
 
28
- An invocation may carry the command's own flags, most often when `doctor` hands back a repair: `/buddy-agent-harness:init --copy --force`.
30
+ An invocation may carry the command's own flags, most often when `doctor` hands back a repair: `/buddy-agent-harness:init --copy --force .claude/skills`.
29
31
 
30
32
  Read them from the invocation itself rather than from a placeholder. Claude Code appends what the caller typed as `ARGUMENTS: <value>`, and Codex substitutes nothing at all, so on every harness the flags arrive as text you can read. Writing `$ARGUMENTS` into this body would resolve on Claude Code and stay literal everywhere else.
31
33
 
32
34
  - `--root`, `--harness`, `--copy`, `--force`, and `--format` pass through to the command in Phase 4.
33
- - Prose carries the same weight: "links are unavailable here" means `--copy`, and "replace the bridge" means `--force`.
35
+ - Prose carries the same weight: "links are unavailable here" means `--copy`, and "replace the bridge" means `--force` naming that bridge.
34
36
  - An argument never skips a phase. `--force` still needs the Phase 3 approval, and the survey still runs first.
35
37
  - Say what you did not recognize and carry on. Never guess at a flag.
36
38
 
@@ -42,6 +44,8 @@ Locate the Git repository root; the canonical configuration always lives there,
42
44
 
43
45
  Inventory both the canonical surface (`AGENTS.md`, `.agents/AGENTS.md`, `.agents/skills/`, and any nested `AGENTS.md` below the root) and every pre-existing harness artifact listed in `references/detection.md`. Read what you find. Write nothing yet.
44
46
 
47
+ Run `node scripts/doctor.mjs` for the artifacts only one harness can read. It reports each with the canonical form it is a candidate for, and that report is the list — detection lives in the command, so deriving a second one here would drift from it.
48
+
45
49
  ## 2. Classify
46
50
 
47
51
  Sort each finding into exactly one bucket:
@@ -50,7 +54,8 @@ Sort each finding into exactly one bucket:
50
54
  - **already linked** — a symlink resolving into `.agents/`; skip it. This is what makes re-runs idempotent.
51
55
  - **consolidatable** — harness instruction files whose content belongs in `AGENTS.md`.
52
56
  - **portable** — skill and command directories that move into `.agents/skills/`.
53
- - **canonical-only** — MCP servers, rules, subagents, hooks, output styles. No safe cross-harness mapping exists, so report them and leave them alone.
57
+ - **convertible with judgment** — a rule only one harness reads. Where its guidance is generally true and the paths it names are incidental, a skill carries it to every harness; where the path scoping is the point, nothing reproduces it and the rule stays. Which of the two it is cannot be read off the file, so it is offered and never assumed.
58
+ - **canonical-only** — subagents, hooks, output styles, MCP servers. Report them and leave them alone. For the first three there is no cross-harness format to convert into. For MCP there is a mapping, but it is not lossless — some servers cannot be expressed for some hosts, and writing one into another host's format means supplying fields the source never carried. Never convert one.
54
59
 
55
60
  ## 3. Confirm
56
61
 
@@ -58,6 +63,10 @@ Present the plan before touching anything the user wrote: what will be created,
58
63
 
59
64
  Get explicit approval before any step that deletes, replaces, or rewrites a user-authored file. Creating a missing directory, a missing `AGENTS.md`, or a `CLAUDE.md` bridge stub needs no approval, and neither does the non-material region in `references/agents-md.md` — report these rather than asking.
60
65
 
66
+ **List every artifact only one harness can read, and ask about each one separately.** Give its path, what reads it today, and the canonical form it is a candidate for. Ask per artifact rather than for the set: they convert for different reasons and a single yes would carry files the owner never looked at. Say plainly which ones have no candidate at all — a subagent has none — so the list is not read as a queue of pending work.
67
+
68
+ **A conversion that would narrow what reads the content is not offered without the bridge that prevents it.** Cursor reads `AGENTS.md` in Agent mode only, so moving `.cursorrules` there and deleting it takes that guidance out of Chat and Composer. The offer is therefore always *consolidate into `AGENTS.md` and leave a generated copy behind*, never *consolidate and delete* — `references/harnesses/cursor.md` has the detail. Where no such bridge exists, say so and leave the artifact alone.
69
+
61
70
  One case does need asking even though it only creates a file: a nested `AGENTS.md` that reverses a root rule, where bridging it hands Claude Code a contradiction it has no rule for resolving. Raise that file, bridge the others, and keep going.
62
71
 
63
72
  ## 4. Apply
@@ -65,15 +74,18 @@ One case does need asking even though it only creates a file: a nested `AGENTS.m
65
74
  1. Scaffold the baseline in `references/standard.md`: `.agents/`, `.agents/skills/`, and a root `AGENTS.md` if absent. Never clobber an existing one. `references/agents-md.md` decides what belongs in that file — a repository with no instruction content still needs one derived and confirmed, not an empty heading.
66
75
  2. Move approved skills and commands to `.agents/skills/<name>/SKILL.md`, preferring `git mv` so history follows. Fix frontmatter per `references/frontmatter.md` — this is where cross-harness portability is won or lost.
67
76
  3. Merge approved instruction content into `AGENTS.md`, preserving the author's wording. Append; do not restructure. Replace a harness file with a pointer only where approved. Content only some tasks need belongs in a skill, not in `AGENTS.md`.
68
- 4. Run `node scripts/init.mjs` at the repository root, resolving that path against this skill's own directory. The launcher runs the CLI that shipped beside it, so nothing is downloaded; fall back to `npx -y buddy-agent-harness@^0.5.0 init` when the path cannot be resolved. It links `.agents/skills` into the harnesses that need it and skips those that read it natively. Add harnesses the user names with `--harness codex,gemini-cli`.
69
- 5. If the command reports a conflict, resolve the named target and retry. Use `--force` only to replace that exact projection; use `--copy` only where links are unavailable — a copy is a snapshot, not a live projection, so say so when you fall back.
70
- 6. Apply the instruction bridges `init` does not write. Claude Code reads `CLAUDE.md`, not `AGENTS.md` — create a `CLAUDE.md` containing `@AGENTS.md`. Gemini CLI needs `AGENTS.md` added to `context.fileName` in `.gemini/settings.json`. Both are detailed in the matching `references/harnesses/<harness>.md`; apply only the ones you enabled. Where nested `AGENTS.md` files exist, write the same stub in each directory holding one — `references/agents-md.md` covers the one case that stops for approval first.
71
- 7. If any bridge now exists, write or restore the non-material region in `AGENTS.md` per `references/agents-md.md`, unless the markers are present and empty. Without it, an agent asked to change a skill edits `.claude/skills/<name>/SKILL.md` — which a link resolves back to canonical, but a copy silently forks. Skip this when every enabled harness reads `.agents/skills/` natively; with no bridge there is nothing to warn about.
77
+ 4. Apply the conversions that were approved, one at a time, and nothing else from the list. A rule approved as a skill moves to `.agents/skills/<name>/SKILL.md` with frontmatter per `references/frontmatter.md`; instruction content approved for consolidation is appended as in the step above and its harness file left as a generated copy. Everything declined, and everything with no candidate, stays exactly where it is.
78
+ 5. Run `node scripts/init.mjs` at the repository root, resolving that path against this skill's own directory. The launcher runs the CLI that shipped beside it, so nothing is downloaded; fall back to `npx -y buddy-agent-harness@^0.7.0 init` when the path cannot be resolved. It links `.agents/skills` into the harnesses that need it and skips those that read it natively. Add harnesses the user names with `--harness codex,gemini-cli`.
79
+ 6. If the command reports a conflict, resolve the named target and retry. Pass the target to `--force` — `--force .claude/skills` replaces that projection and reports any other conflict untouched, where a bare `--force` replaces every conflicting target at once. Reach for the bare form only when replacing all of them is what you came for. Use `--copy` only where links are unavailable — a copy is a snapshot, not a live projection, so say so when you fall back.
80
+ 7. Apply the instruction bridges `init` does not write. Claude Code reads `CLAUDE.md`, not `AGENTS.md` — create a `CLAUDE.md` containing `@AGENTS.md`. Gemini CLI needs `AGENTS.md` added to `context.fileName` in `.gemini/settings.json`. Both are detailed in the matching `references/harnesses/<harness>.md`; apply only the ones you enabled. Where nested `AGENTS.md` files exist, write the same stub in each directory holding one — `references/agents-md.md` covers the one case that stops for approval first.
81
+ 8. If any bridge now exists, write or restore the non-material region in `AGENTS.md` per `references/agents-md.md`, unless the markers are present and empty. Without it, an agent asked to change a skill edits `.claude/skills/<name>/SKILL.md` — which a link resolves back to canonical, but a copy silently forks. Skip this when every enabled harness reads `.agents/skills/` natively; with no bridge there is nothing to warn about.
72
82
 
73
83
  ## 5. Verify and report
74
84
 
75
85
  Confirm each projection resolves into `.agents/skills` and that every migrated `SKILL.md` parses and has valid frontmatter. The command reports the enabled set itself; it records nothing on disk, so there is no file to reconcile against. Report what was created, consolidated, linked, and left canonical-only.
76
86
 
87
+ Report the artifacts only one harness can read that are still there, split by why: declined, or no canonical form to convert into. Re-running `doctor` gives the same list, and a repository works toward having none of them — that is a direction, not a state anything here blocks on.
88
+
77
89
  `init` is not a formatter. If the repository has one, run it over the written files and say so.
78
90
 
79
91
  Then offer to continue with the `enhance` skill, which proposes guidance the repository is missing rather than consolidating what it has. Ask once and take the answer; `init` writes no instruction content of its own either way, and `enhance` decides for itself whether anything is worth offering.
@@ -82,6 +94,8 @@ Then offer to continue with the `enhance` skill, which proposes guidance the rep
82
94
 
83
95
  - Never invent project policy, and never make a material change to a user's `AGENTS.md`. `references/agents-md.md` draws the line: a statement that would stop being true if `init`'s output were removed describes the tool's own artifact and is non-material.
84
96
  - Never convert tool settings between formats without a documented mapping. Unmapped settings stay canonical.
97
+ - Never convert an artifact only one harness reads without asking, and never in a way that leaves fewer readers than before. The content is the user's, the conversion is a judgment about what their guidance is *for*, and a consolidation with no bridge behind it silently narrows who sees it.
98
+ - Edit a user-authored settings file in place: change the one key or array element you came for and leave the rest byte-identical, including key order, indentation, and comments. Harnesses disagree about whether a JSON settings file may hold comments, so reading one with `JSON.parse` and writing the object back either deletes what the author wrote or produces a file the harness rejects. The matching `references/harnesses/<harness>.md` says which case a file is.
85
99
  - Symlinks belong in version control. Leave them tracked; do not add them to `.gitignore`.
86
100
  - Only `.agents/skills/` is an established convention. Do not invent `.agents/rules/`, `.agents/commands/`, or `.agents/agents/` and present them as standard.
87
101
  - Never merge a nested `AGENTS.md` into the root. Merging changes which files it governs.
@@ -8,11 +8,13 @@ What to look for during the survey, and where each finding belongs.
8
8
  | Nested instructions | `**/AGENTS.md` below the root, and any `**/CLAUDE.md` beside one | canonical and scoped — report, never merge upward. Bridging is per directory and per approval; see `agents-md.md` |
9
9
  | Skills | `.claude/skills/`, `.cursor/skills/`, `.codex/skills/`, `.github/skills/`, `.windsurf/skills/`, `.gemini/skills/` | move to `.agents/skills/`, fix frontmatter, then link back |
10
10
  | Commands | `.claude/commands/*.md`, `.cursor/commands/*.md` | move to `.agents/skills/<name>/SKILL.md`. Claude Code has merged commands into skills, so this follows the harness rather than fighting it |
11
- | Subagents | `.claude/agents/` | canonical-only — no cross-harness format exists. Report; leave in place |
12
- | Rules | `.cursor/rules/**.mdc`, `.claude/rules/`, `.windsurf/rules/` | canonical-only — `.mdc` and `.md` are not interchangeable, and path-scoping syntax differs. Report |
13
- | MCP servers | `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `.claude/settings.json` | canonical-only. Report; never convert between formats |
11
+ | Subagents | `.claude/agents/` | canonical-only — no cross-harness format exists. Report as having no candidate at all, so the list is not read as pending work; leave in place |
12
+ | Rules | `.cursor/rules/**.mdc`, `.claude/rules/`, `.windsurf/rules/` | offer a skill where the paths a rule names are incidental to what it says; leave it where the path scoping is the point. `.mdc` and `.md` are not interchangeable and path-scoping has no `AGENTS.md` equivalent, so the choice is the owner's — ask per rule |
13
+ | MCP servers | `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `.claude/settings.json` | canonical-only. Report; never convert between formats. A mapping between them exists but is not lossless, and applying it means inventing fields the source did not carry |
14
14
  | Hooks, LSP, output styles | hook blocks in settings files, `.claude/output-styles/` | canonical-only. Hook event names differ by case across harnesses, so no safe projection exists. Report |
15
15
 
16
+ `doctor` reports every artifact only one harness can read, each with the canonical form it is a candidate for. That report is the list — the table above says what each class *is*, and the command says what this repository actually has. Do not derive a second list by walking these paths yourself.
17
+
16
18
  For every entry, record which of three states it is in:
17
19
 
18
20
  - a real file or directory the user authored,
@@ -47,6 +47,8 @@ Report these and do not convert them:
47
47
  - hook blocks in `.claude/settings.json` — event names differ by case across harnesses
48
48
  - MCP server definitions
49
49
 
50
+ `.claude/settings.json` is strict JSON, the opposite of Gemini CLI's settings file: a comment is a parse error, and a settings file that fails to parse is rejected whole rather than in part. Never add one, and never offer one as a way to annotate a permission or hook entry.
51
+
50
52
  ## Commands
51
53
 
52
54
  `.claude/commands/*.md` are **portable** — migrate to `.agents/skills/<name>/SKILL.md`. Claude Code has merged commands into skills, so this follows the harness rather than fighting it.
@@ -14,10 +14,17 @@ Tell the user: a repository that consolidates into `AGENTS.md` keeps its instruc
14
14
 
15
15
  ## Do not
16
16
 
17
- - **Do not generate `.cursor/rules/*.mdc` from `AGENTS.md`.** Rules are canonical-only — `.mdc` and `.md` are not interchangeable and path-scoping has no `AGENTS.md` equivalent.
18
- - **Do not delete or rewrite `.cursorrules` or `.cursor/rules/**` on the assumption that `AGENTS.md` covers them.** In Chat and Composer it does not.
17
+ - **Do not generate `.cursor/rules/*.mdc` from `AGENTS.md`.** `.mdc` and `.md` are not interchangeable and path-scoping has no `AGENTS.md` equivalent, so a generated rule would be inventing scope the source never carried.
18
+ - **Do not delete or rewrite `.cursorrules` or `.cursor/rules/**` on the assumption that `AGENTS.md` covers them.** In Chat and Composer it does not. This is the reason a migration is *offered* rather than applied: the owner is agreeing to a trade, and they cannot agree to one nobody described.
19
19
  - Do not assert whether Cursor's `.agents/skills` discovery recurses into nested subdirectories. It is untested.
20
20
 
21
+ ## Migrating a rule, when the owner asks for it
22
+
23
+ Offer, never assume, and offer the whole thing:
24
+
25
+ - **A rule whose paths are incidental** — guidance that happens to name files but says something generally true — becomes a skill under `.agents/skills/`, which every harness reads. A rule whose scoping *is* the point has no equivalent; say so and leave it.
26
+ - **Consolidating `.cursorrules` into `AGENTS.md` narrows who reads it** unless a copy stays behind for Chat and Composer. Offer the consolidation and the copy together. Never offer the consolidation alone, and never delete the file because `AGENTS.md` now carries the words.
27
+
21
28
  ## Frontmatter
22
29
 
23
30
  `paths`, `disable-model-invocation`, and legacy `globs` are Cursor-recognized. Other harnesses drop them — keep load-bearing behavior in the body, per `frontmatter.md`.
@@ -1,18 +1,14 @@
1
1
  # Gemini CLI
2
2
 
3
- Reads `.agents/skills` at **user scope only**. At project scope it reads `.gemini/skills/` and nothing else, so **both bridges are required**.
3
+ Reads `.agents/skills` at both scopes, so **skills need no bridge**. It does not read `AGENTS.md`, so **the instruction bridge is required**.
4
4
 
5
- Do not enable Gemini CLI speculatively — unlike Cursor, enabling it writes a real directory and symlink. Enable it only via `--harness gemini-cli` or a pre-existing `.gemini/` directory.
5
+ ## Skills — nothing to write
6
6
 
7
- ## Bridge 1 — skills
7
+ `.agents/skills` is read directly at project scope, where the alias takes precedence over `.gemini/skills/`. `init` writes no skills bridge for Gemini CLI.
8
8
 
9
- `buddy-agent-harness init` creates this when `gemini-cli` is enabled:
9
+ An existing `.gemini/skills` symlink still resolves — that path is still scanned. Leave it; removing it is not part of enabling this harness.
10
10
 
11
- ```
12
- .gemini/skills → ../.agents/skills
13
- ```
14
-
15
- ## Bridge 2 — instructions
11
+ ## Instructions — required
16
12
 
17
13
  `init` does **not** do this, and it cannot be a link. Add `AGENTS.md` to `context.fileName` in `.gemini/settings.json`:
18
14
 
@@ -30,4 +26,5 @@ When the file already exists:
30
26
 
31
27
  - Get approval first — it is user-authored.
32
28
  - Add to the array; do not replace it. Preserve surrounding settings.
29
+ - **Comments are legal in this file.** Gemini CLI strips them before parsing, so a user's settings may carry them, and `JSON.parse` on the whole file deletes every one. Make a targeted edit to the `context.fileName` array instead of a parse-and-rewrite, and leave key order and indentation as the author left them. `.claude/settings.json` is the opposite case; see `claude-code.md`.
33
30
  - Keep `GEMINI.md` in the list if the repository still has one.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ // Generated from src/diagnose-bridges/doctor-guidance.ts by scripts/generate-skills.ts. Do not edit by hand.
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+
6
+ // <package>/skills/<skill>/scripts/doctor.mjs: four levels up is the package root.
7
+ const packageRoot = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))
8
+ const { run } = await import(pathToFileURL(join(packageRoot, 'dist', 'cli.mjs')).href)
9
+
10
+ // The subcommand is composed into a fresh argv rather than spliced into the global one, so nothing
11
+ // outside this file observes the rewrite. Applied only when non-zero, so a usage code clibuilder
12
+ // recorded itself is not overwritten by the zero `run` returns on that path.
13
+ const code = await run([...process.argv.slice(0, 2), 'doctor', ...process.argv.slice(2)])
14
+ if (code !== 0) process.exitCode = code
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  // Generated from src/diagnose-bridges/doctor-guidance.ts by scripts/generate-skills.ts. Do not edit by hand.
3
3
  import { dirname, join } from 'node:path'
4
- import { fileURLToPath } from 'node:url'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
5
 
6
6
  // <package>/skills/<skill>/scripts/init.mjs: four levels up is the package root.
7
7
  const packageRoot = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))
8
+ const { run } = await import(pathToFileURL(join(packageRoot, 'dist', 'cli.mjs')).href)
8
9
 
9
- process.argv.splice(2, 0, 'init')
10
- await import(join(packageRoot, 'bin', 'buddy-agent-harness.mjs'))
10
+ // The subcommand is composed into a fresh argv rather than spliced into the global one, so nothing
11
+ // outside this file observes the rewrite. Applied only when non-zero, so a usage code clibuilder
12
+ // recorded itself is not overwritten by the zero `run` returns on that path.
13
+ const code = await run([...process.argv.slice(0, 2), 'init', ...process.argv.slice(2)])
14
+ if (code !== 0) process.exitCode = code
@@ -0,0 +1,21 @@
1
+ # Harness Repair
2
+
3
+ Correct agent configuration a repository already has that is wrong or outdated.
4
+
5
+ ## What it does
6
+
7
+ `init` consolidates what a repository has, `enhance` offers what it is missing, and `doctor` reports what is wrong. Configuration that is present but **wrong** falls between all three, and that is what this skill takes: a harness name the registry retired, a git-ignored bridge, an `AGENTS.local.md` nothing reads, a skill whose frontmatter makes a harness skip it.
8
+
9
+ Each correction is offered with the file as it stands and as it would read, and written only on approval.
10
+
11
+ ## Why a fourth skill rather than a `--fix` flag
12
+
13
+ `doctor` stays read-only. Its whole safety property is that it never writes, which is what makes it safe to run from a session-start hook — a repair flag would forfeit that for every caller. Its **bridge** findings are all repairable with existing `init` flags, so a flag there would duplicate `init` rather than add anything; its **configuration** findings need judgment about files you wrote, which is a skill's job and not a flag's.
14
+
15
+ That split also defines this skill's remit. `repair` owns what `init` deliberately refuses. Every bridge finding — including an instruction bridge that was never completed — is reported and handed to `init`, which is what writes bridges in the first place.
16
+
17
+ ## The line it does not cross
18
+
19
+ `repair` corrects what the **tooling** decides is wrong, never what the project means. The discriminator is the one `init` already applies: content that would stop being true if this tool's output were removed is non-material, and non-material content is all this skill may correct. A statement about how the repository is worked in stays the user's, even when it is out of date — `repair` reports it and offers no write.
20
+
21
+ `references/classes.md` carries the detection, the correction, and the stopping point for each class.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: repair
3
+ description: Use this skill when a repository's agent configuration is present but wrong or outdated — a retired harness name, a git-ignored bridge, an AGENTS.local.md nothing reads, or a skill a harness refuses to load. Runs doctor to find them, then offers each correction with its before and after and writes only what is approved.
4
+ ---
5
+
6
+ # Harness Repair
7
+
8
+ `init` consolidates what a repository has. `enhance` proposes what it does not. `doctor` reports what is wrong. `repair` corrects it.
9
+
10
+ Each of the other three refuses this work deliberately. `init` has to run anywhere and invent nothing, so it never clobbers a file the user wrote. `enhance` only offers what is **missing**. `doctor` never writes, which is what makes it safe in a session-start hook. Configuration that is present but wrong falls between them.
11
+
12
+ **`doctor` detects; this skill repairs.** Detection has one home, so the two cannot drift and the finding set can grow without touching this file. Do not detect anything yourself — run the command and repair what it reports.
13
+
14
+ ## 1. Diagnose
15
+
16
+ ```sh
17
+ node scripts/doctor.mjs
18
+ ```
19
+
20
+ That path is relative to this skill's own directory. The launcher runs the CLI that shipped beside it against the current working directory, so nothing is downloaded. Fall back to `npx -y buddy-agent-harness@^0.5.0 doctor` when the launcher cannot be resolved or run.
21
+
22
+ The default output is TOON, which is what you parse. Each entry in `findings` carries three fields: `problem` (the finding's name), `path` (what it is about), and `detail` (what is wrong, in prose). The repairs are not on the row — they are lifted into `help`, which follows `findings` in order and collapses two entries only where the repair is word-for-word identical. Read a finding's repair off the matching position and confirm it by the `path` it names; where two findings sit at one path, what each repair says to do is what tells them apart.
23
+
24
+ **Route on `problem`, never on `detail`.** The prose exists to be read by a person, and rewording it must not change what you do.
25
+
26
+ If `findings` reports zero problems, say so and stop. There is nothing to repair.
27
+
28
+ ## 2. Sort what it found
29
+
30
+ **Route on `problem`.** `references/classes.md` carries one section per fault this skill corrects, keyed by the `problem` name. A `problem` with a section there is yours; a `problem` without one is not. Nothing else decides it.
31
+
32
+ That rule holds for every finding `doctor` reports today and for the ones it reports next year. A fault this skill has no correction for is not one it can repair, so what `classes.md` covers and what this skill owns are the same fact — and a finding added tomorrow gets reported rather than improvised on.
33
+
34
+ **Do not read ownership off a skill name in `help`.** Most repairs name no skill — some are a shell invocation, some a `git` command, some a reconciliation by hand — and only the instruction and configuration families name one at all. An owner is not something the report can always be asked for.
35
+
36
+ - **Findings that are yours.** Look the `problem` up in `references/classes.md`, which carries the correction and the stopping point for each.
37
+ - **Findings that are not yours.** Report each one and pass on the repair `doctor` states for it, unchanged. **Who to hand it to is read off that repair, and the question is whether it names `init`.** Some name the `/buddy-agent-harness:init` skill and some name a `buddy-agent-harness init` command line; both mean the same thing — the finding is `init`'s, because `init` writes both kinds of bridge in the first place, and it writes the `CLAUDE.md` stub *without* asking, where everything here needs approval, and one write cannot have two homes and two contradictory approval rules. Hand it to the **skill** and never run the command yourself: rebuilding a projection can relocate skills a user wrote, and on the Windows case the naive repair is to recreate the link, the operation that already failed on that machine. A repair naming `init` in **neither** form — a `git` invocation, a reconciliation by hand, every MCP finding — is work for a person; say that rather than inferring an owner, and never rebuild over a two-sided divergence, which discards whichever side holds the newer edit.
38
+
39
+ ## 3. Draw the line at material content
40
+
41
+ You correct what the **tooling** decides is wrong, never what the repository means.
42
+
43
+ `../init/references/agents-md.md` draws the line: content that would stop being true if this tool's output were removed is **non-material**, and non-material content is all you may correct. A statement about how the repository is worked in is the user's, even when it is out of date — report it and offer no write.
44
+
45
+ The clearest case is `unloadable-skill`. A `description` broken by an unquoted colon is a quoting fault — correct it. A `description` that is **missing** cannot be written without asserting what a skill you did not author does, and that claim holds whether or not this tool ever ran. Report it and ask.
46
+
47
+ ## 4. Offer
48
+
49
+ Show the file **as it stands and as it would read**. Not a summary — the actual before and after, so the user is reading the change rather than your description of it.
50
+
51
+ Where more than one correction is valid, present the options and ask which. Never pick for the user. `unread-local-override` is always this case, and its options come from `references/classes.md` — `doctor` names one repair per finding and has not read the file's contents.
52
+
53
+ Do not argue past one sentence. The diff is the argument.
54
+
55
+ ## 5. Apply and re-run
56
+
57
+ Write only what was approved, preserving the rest of the file exactly.
58
+
59
+ Then **run `doctor` again**. A correction can fail to hold — a second `.gitignore` rule matching the same path, a frontmatter fix that leaves another fault behind. Re-running is how you know, and it costs nothing. When a finding survives, say it is still open rather than reporting it landed.
60
+
61
+ ## 6. Report
62
+
63
+ Report every run, whichever way it went. One row per finding, naming its **path**, what was wrong, and its **outcome** — corrected, declined, handed to `init`, reported as material, or still open.
64
+
65
+ A run that finds nothing still reports. "Nothing found" without saying `doctor` ran clean is indistinguishable from not having looked.
66
+
67
+ ## Rules
68
+
69
+ - **Never write without approval.** There is no flag that skips it and none should be added.
70
+ - **Never detect.** `doctor` owns detection. A check written here is a second home for it, and two homes drift.
71
+ - **Never rebuild a bridge.** That is `doctor`'s diagnosis and `init`'s repair.
72
+ - **Never consolidate.** Moving content into `AGENTS.md` is `init`'s and has one home.
73
+ - **Correct only non-material content.** Project policy is the user's, even when it is wrong.
74
+ - Local agent configuration only. Do not change workflows, GitHub Actions, repository settings, security scanning, branch rules, or unrelated project files — a retired harness name in a workflow file is not yours to rename.
@@ -0,0 +1,49 @@
1
+ # Correction classes
2
+
3
+ One row per fault `doctor` reports here, with the correction and where it stops.
4
+
5
+ **This file carries no detection.** `doctor` decides what is wrong; you decide how to correct it. If you find yourself checking whether a fault is present, you are doing the command's job — run it instead.
6
+
7
+ Every correction below is non-material: it would stop being true if this tool's output were removed. That is what makes it correctable at all. Where a finding's correction would change what the repository *means*, report it and stop; `../../init/references/agents-md.md` draws the line.
8
+
9
+ ## `deprecated-harness`
10
+
11
+ **Correct.** The replacement harness reads `.agents/skills/` natively and needs no projection at all, so the correction is to **delete** the reported projection — not to rename it, and not to create a new one under the new name. This is the one correction that removes a path rather than editing a file, so show what will be deleted and what will remain.
12
+
13
+ **Stops at.** Everything under that harness's directory that is not the skills projection. `.windsurfrules` and any settings file are instruction artifacts; consolidating them is `init`'s. A harness name in a workflow, a README, or a comment is prose, not configuration — leave it.
14
+
15
+ ## `ignored-bridge`
16
+
17
+ **Correct.** Narrow or remove the reported `.gitignore` rule so the bridge is tracked. Where the rule exists for something else under that directory, the correction is a negation (`!.claude/skills`) rather than a deletion — that keeps the rest of the rule doing its job.
18
+
19
+ **Stops at.** Every rule that does not match a bridge path. `.gitignore` is a project file, and only the part swallowing a bridge is this tool's business.
20
+
21
+ ## `unread-local-override`
22
+
23
+ **More than one correction is valid, so this fault always presents options and never picks.** The options come from this file, not from the report: `doctor` names one repair per finding, and choosing among these is a judgment about content it has not read.
24
+
25
+ Read the file to describe the options, then offer all three:
26
+
27
+ - move it to `CLAUDE.local.md` and add that to `.gitignore`, where the content is personal and Claude Code is the reader that matters;
28
+ - hand it to `init` to consolidate into `AGENTS.md`, where the content is project guidance — that move is material, and `init`'s;
29
+ - delete it, where it is dead.
30
+
31
+ **Stops at.** The content. Read it to describe the options; never rewrite it.
32
+
33
+ ## `unloadable-skill`
34
+
35
+ Two faults arrive under this name, and only one of them is correctable.
36
+
37
+ **An unquoted colon in `description`.** Correctable: quote the value. The YAML then parses and the skill loads.
38
+
39
+ **A missing or empty `description`.** **Not correctable — report it and ask.** Writing one would mean inventing a claim about what a skill you did not author does, and that claim is material: it stays true whether or not this tool ever ran. Name the skill, say why it will not load, and stop.
40
+
41
+ **Stops at.** Everything below the frontmatter. The skill body is the author's. A `name` that mismatches its directory is not reported by `doctor` at all — it is a warning and the skill still loads.
42
+
43
+ ## Not yours: every `problem` with no section above
44
+
45
+ `doctor` reports far more than this file covers: skills bridges that no longer resolve, instruction bridges that were never completed, and drift between the golden MCP server set and a harness's copy of it. None of them has a section here, and that is what says they are not yours.
46
+
47
+ **Who to hand one to is read off the repair `doctor` gave it, and the question is whether it names `init`.** It names it one of two ways — the `/buddy-agent-harness:init` skill, or a `buddy-agent-harness init` command line — and both mean the finding is `init`'s, which writes both kinds of bridge in the first place and writes a `CLAUDE.md` stub *without* asking, where every correction here needs approval. Hand it to the skill; never run the command. A repair naming `init` in **neither** form is work for a person: `doctor` states it in full, and passing it on is the whole of what you do with it. Every MCP finding is that second case, as is every bridge finding a rebuild would not fix.
48
+
49
+ Never infer an owner for a finding that names none. The wrong guess is always `init`, and on a two-sided divergence rebuilding is precisely what discards whichever side holds the newer edit.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ // Generated from src/diagnose-bridges/doctor-guidance.ts by scripts/generate-skills.ts. Do not edit by hand.
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+
6
+ // <package>/skills/<skill>/scripts/doctor.mjs: four levels up is the package root.
7
+ const packageRoot = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))
8
+ const { run } = await import(pathToFileURL(join(packageRoot, 'dist', 'cli.mjs')).href)
9
+
10
+ // The subcommand is composed into a fresh argv rather than spliced into the global one, so nothing
11
+ // outside this file observes the rewrite. Applied only when non-zero, so a usage code clibuilder
12
+ // recorded itself is not overwritten by the zero `run` returns on that path.
13
+ const code = await run([...process.argv.slice(0, 2), 'doctor', ...process.argv.slice(2)])
14
+ if (code !== 0) process.exitCode = code
package/src/cli.ts CHANGED
@@ -1,17 +1,50 @@
1
- import { cli } from 'clibuilder'
1
+ import { readFileSync } from 'node:fs'
2
+ import { cli, exitCodes } from 'clibuilder'
2
3
  import { doctorCommand } from './diagnose-bridges/doctor.command.ts'
3
4
  import { initCommand } from './initialize-harnesses/init.command.ts'
4
5
 
5
- export async function main(): Promise<void> {
6
- const app = cli({
6
+ /**
7
+ * Read from the manifest rather than restated here: a version written into this call is a second
8
+ * home for a number that already has one, and it stayed at `0.1.0` for five minor releases.
9
+ *
10
+ * `../package.json` resolves from `src/cli.ts` and from the bundled `dist/cli.mjs` alike — both sit
11
+ * one directory under the package root.
12
+ */
13
+ const version = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version as string
14
+
15
+ /**
16
+ * A factory rather than a module-level constant: `cli()` builds state, and state built at import
17
+ * time is shared by every later call in the process.
18
+ */
19
+ function app() {
20
+ return cli({
7
21
  name: 'buddy-agent-harness',
8
- version: '0.1.0',
22
+ version,
9
23
  description: 'Initialize agent harness skill compatibility in consumer repositories.',
10
24
  })
25
+ .command(initCommand)
26
+ .command(doctorCommand)
27
+ }
28
+
29
+ /**
30
+ * The application boundary: argv in, exit code out, nothing read from or written to the process.
31
+ *
32
+ * The exit code is returned rather than written so that a caller which is not the process — a skill
33
+ * launcher, a test — can run a command and learn how it went. `bin/buddy-agent-harness.mjs` is the
34
+ * only place that turns the returned code back into a process outcome.
35
+ *
36
+ * One exit path this return cannot carry: `clibuilder` answers an unknown option or an unknown
37
+ * command by printing help and writing `process.exitCode` itself, returning nothing. `run` reports
38
+ * `0` there, which is why `bin` and the launchers apply the returned code only when it is non-zero.
39
+ */
40
+ export async function run(argv: string[]): Promise<number> {
11
41
  try {
12
- await app.command(initCommand).command(doctorCommand).parse(process.argv)
42
+ const code = await app().parse<number | undefined>(argv)
43
+ return typeof code === 'number' ? code : exitCodes.success
13
44
  } catch (error) {
14
- process.stdout.write(`error: ${error instanceof Error ? error.message : 'Invalid command.'}\n`)
15
- process.exitCode = 2
45
+ // stderr, not stdout: `doctor`'s default output is TOON that an agent parses, and an error
46
+ // line on that stream lands in the middle of what it is parsing.
47
+ process.stderr.write(`error: ${error instanceof Error ? error.message : 'Invalid command.'}\n`)
48
+ return exitCodes.usage
16
49
  }
17
50
  }
@@ -1,5 +1,6 @@
1
1
  import { sep } from 'node:path'
2
2
  import { encode } from '@toon-format/toon'
3
+ import { isRecord } from '../is-record/is-record.ts'
3
4
 
4
5
  export type OutputFormat = 'json' | 'toon' | 'text'
5
6
 
@@ -21,10 +22,6 @@ function encodeResult(value: object, format: OutputFormat): string {
21
22
  return format === 'text' ? renderText(value) : encode(value)
22
23
  }
23
24
 
24
- function isRecord(value: unknown): value is Record<string, unknown> {
25
- return typeof value === 'object' && value !== null && !Array.isArray(value)
26
- }
27
-
28
25
  function cell(value: unknown): string {
29
26
  return value === undefined ? '' : String(value)
30
27
  }
@@ -0,0 +1,34 @@
1
+ import { type Dirent, existsSync, readdirSync } from 'node:fs'
2
+ import { join, posix } from 'node:path'
3
+
4
+ /** Directories that never hold instructions this repository is responsible for bridging. */
5
+ const pruned = new Set(['node_modules'])
6
+
7
+ /**
8
+ * Every directory below `root` holding an `AGENTS.md`, repository-relative and POSIX-separated,
9
+ * with the root itself as `''`. Sorted, root first.
10
+ *
11
+ * Dot-directories are pruned along with `node_modules`. That is not only a speed measure:
12
+ * `.agents/AGENTS.md` is canonical shared instructions rather than a subtree-scoped file, and
13
+ * bridging it would claim a scope it does not have.
14
+ */
15
+ export function agentsFileDirectories(root: string): string[] {
16
+ const found: string[] = []
17
+
18
+ const walk = (relative: string): void => {
19
+ if (existsSync(join(root, relative, 'AGENTS.md'))) found.push(relative)
20
+ let entries: Dirent[]
21
+ try {
22
+ entries = readdirSync(join(root, relative), { withFileTypes: true })
23
+ } catch {
24
+ return
25
+ }
26
+ for (const entry of entries) {
27
+ if (!entry.isDirectory() || entry.name.startsWith('.') || pruned.has(entry.name)) continue
28
+ walk(relative ? posix.join(relative, entry.name) : entry.name)
29
+ }
30
+ }
31
+
32
+ walk('')
33
+ return found.sort()
34
+ }