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
package/dist/index.d.mts CHANGED
@@ -1,20 +1,161 @@
1
+ import { run } from "./cli.mjs";
1
2
  import { cli as cli$1 } from "clibuilder";
3
+ //#region src/harness-registry/instruction-bridge.d.ts
4
+ /**
5
+ * What a harness needs in order to read the repository's `AGENTS.md`.
6
+ *
7
+ * A skills projection is one shape — a directory pointing at `.agents/skills` — so the registry
8
+ * models it as a path. Instruction bridges are not one shape: Claude Code needs a Markdown file
9
+ * whose body imports `AGENTS.md`, and Gemini CLI needs `AGENTS.md` added to an array inside a JSON
10
+ * settings file. A second bare path field would describe the first and lie about the second, so the
11
+ * variant carries what its own check needs.
12
+ *
13
+ * The `init` command writes none of these. They are the `init` skill's work, which is why every
14
+ * finding against one names the skill rather than a flag.
15
+ */
16
+ type InstructionBridge = {
17
+ /**
18
+ * A Markdown file whose body is an `@AGENTS.md` import, or a symlink to `AGENTS.md`. One is
19
+ * needed beside every `AGENTS.md` in the tree, not only the root file: an import bridges the
20
+ * one file next to it, so a nested `AGENTS.md` is invisible until it has its own.
21
+ */
22
+ kind: "import";
23
+ /** Repository-relative name, the same in every directory that holds an `AGENTS.md`. */
24
+ path: string;
25
+ } | {
26
+ /** An `AGENTS.md` entry in an array inside a JSON settings file. */
27
+ kind: "settings-entry";
28
+ /** Repository-relative path of the settings file. */
29
+ path: string;
30
+ /** Dotted path to the array within it, as the harness documents it. */
31
+ key: string;
32
+ };
33
+ //#endregion
34
+ //#region src/harness-registry/mcp-config.d.ts
35
+ /**
36
+ * Where one harness keeps its MCP server configuration at one scope, as that harness's own vendor
37
+ * documents it.
38
+ *
39
+ * Recorded per scope like everything else in a `HarnessScope`, and populated at **project** scope
40
+ * only. Much of the world's MCP configuration lives at user scope — `~/.codex/config.toml`,
41
+ * `~/.claude.json`, `claude_desktop_config.json` — and the registry records user scope as
42
+ * described and diagnosable, never written. Reading a user's home directory into output that
43
+ * lands in every session's transcript is a wider blast radius than diagnosis needs, so those paths
44
+ * stay out until something needs them.
45
+ *
46
+ * A harness with no documented project-scope MCP file gets no entry. Copilot CLI is the case that
47
+ * matters: its own documentation states that `.vscode/mcp.json` "is not read by Copilot CLI", and
48
+ * names `~/.copilot/mcp-config.json` instead. That is a documented absence, not a gap — see
49
+ * `.research/mcp-canonical-location/` (E-MCP-11).
50
+ */
51
+ type McpConfig = {
52
+ /** Repository-relative path of the file, as the vendor documents it. */
53
+ path: string;
54
+ /**
55
+ * The key the servers sit under. Not one key across hosts: `mcpServers` for Claude Code, Cursor,
56
+ * and Gemini CLI, `mcp_servers` for Codex, `servers` for VS Code. Reading the wrong one finds
57
+ * nothing and reports a repository as unconfigured.
58
+ */
59
+ key: string;
60
+ /**
61
+ * How the file is serialized. It decides the parser, and it is also why comparison is semantic:
62
+ * the same server in TOML and in JSON shares no bytes.
63
+ */
64
+ format: "json" | "toml";
65
+ /**
66
+ * Set when the file holds far more than MCP configuration. `.gemini/settings.json` also carries
67
+ * the instruction bridge, so anything reading it must take its key and leave the rest alone —
68
+ * and anything writing it must never rewrite the file wholesale.
69
+ */
70
+ shared?: true;
71
+ };
72
+ //#endregion
73
+ //#region src/harness-registry/nonstandard-artifact.d.ts
74
+ /**
75
+ * Configuration a harness reads that no other harness can, declared per harness beside the paths
76
+ * `doctor` already knows about it.
77
+ *
78
+ * These are not faults. Every one of them works — for exactly one harness — which is the whole
79
+ * problem: the guidance in a Cursor rule reaches Cursor and nothing else, and nobody finds out
80
+ * except by noticing an agent behaving differently in another tool. `doctor` surfaces them so they
81
+ * can be converted, one at a time, toward a repository whose agent configuration is canonical and
82
+ * whose harness files are generated from it. Zero of these is the north star, not a release gate.
83
+ *
84
+ * `kind` carries the conversion, because the canonical form differs per kind and nothing else about
85
+ * the artifact does. It is what a finding routes on.
86
+ */
87
+ type NonstandardKind = "instructions" | "rule" | "command" | "skill" | "subagent";
88
+ type NonstandardArtifact = {
89
+ /**
90
+ * Repository-relative path. A file is reported as itself; a directory is reported per file
91
+ * below it, so each one can be converted on its own rather than as an all-or-nothing batch.
92
+ */
93
+ path: string;
94
+ /** Whether `path` names one file or a directory to walk. */
95
+ shape: "file" | "directory";
96
+ kind: NonstandardKind;
97
+ };
98
+ //#endregion
2
99
  //#region src/harness-registry/harness-registry.d.ts
3
100
  type HarnessName = "claude-code" | "cursor" | "codex" | "copilot-cli" | "gemini-cli" | "devin-desktop" | "windsurf";
101
+ /** The two scopes a harness reads configuration at. Their roots differ; their shape does not. */
102
+ type HarnessScopeName = "project" | "user";
4
103
  /**
5
- * `skillsDirectory` is the projection target a harness needs to see `.agents/skills`.
6
- * A harness that reads `.agents/skills` natively has none, and is never projected into.
104
+ * What one harness does at one scope. Paths are relative to that scope's root: the repository root
105
+ * at project scope, the user's home directory at user scope.
106
+ *
107
+ * `detect` is the directory whose presence means this harness is configured at this scope — a
108
+ * different question per scope, which is why it is recorded per scope rather than once.
109
+ *
110
+ * `skillsDirectory` is the projection target the harness needs to see `.agents/skills` at this
111
+ * scope. A harness that reads `.agents/skills` natively there has none, and is never projected into.
112
+ *
113
+ * `mcpConfig` is where this harness keeps its MCP servers at this scope. Unlike the other two it
114
+ * is not a bridge into a canonical file the harness cannot read — it is the harness's own
115
+ * configuration, which `doctor` compares against the golden set and never writes.
116
+ *
117
+ * `nonstandard` is what this harness reads that no other harness can. Unlike the three above it is
118
+ * not something the tool maintains — it is what a repository accumulated before it was canonical,
119
+ * and `doctor` reports it so it can be converted. Declared here because the paths are per harness
120
+ * and belong beside the harness's other paths, not in a second list that would drift from this one.
121
+ *
122
+ * `instructionBridge` is the same question for `AGENTS.md`: what the harness needs at this scope in
123
+ * order to read it. It belongs per scope for the same reason `skillsDirectory` does — the file
124
+ * differs. Unlike `skillsDirectory`, the `init` command does not write it; the `init` skill does. It
125
+ * is recorded here to be diagnosed and gated per harness, not to be projected.
7
126
  */
8
- type Harness = {
9
- name: HarnessName;
127
+ type HarnessScope = {
10
128
  detect: string;
11
129
  skillsDirectory?: string;
130
+ instructionBridge?: InstructionBridge;
131
+ mcpConfig?: McpConfig;
132
+ nonstandard?: readonly NonstandardArtifact[];
133
+ };
134
+ type Harness = {
135
+ name: HarnessName;
136
+ /** What this harness does inside a repository. `init` and `doctor` act only here. */
137
+ project: HarnessScope;
138
+ /**
139
+ * What this harness does for the user, outside any repository. Absent when no user-scope path is
140
+ * primary-sourced — Devin documents none. Described and diagnosable; never written to.
141
+ */
142
+ user?: HarnessScope;
12
143
  /** Set when this name has been superseded; the value is the name that replaces it. */
13
144
  deprecated?: HarnessName;
14
145
  };
15
146
  /**
16
- * Codex, Cursor, Copilot CLI, and Devin Desktop read `.agents/skills` directly, so they need no
17
- * projection. Only Claude Code and Gemini CLI read solely their own directory.
147
+ * The two scopes disagree in general, and a single answer per harness could only record one of them:
148
+ * Copilot CLI is `.github/skills` in a repository and `~/.copilot` for the user. Claude Code is the
149
+ * only harness that needs a skills projection, and it needs one at both scopes. Codex, Cursor,
150
+ * Copilot CLI, Gemini CLI, and Devin Desktop read `.agents/skills` themselves and are never
151
+ * projected into.
152
+ *
153
+ * Gemini CLI carried a `.gemini/skills` projection until E-GEM-02: it reads the `.agents/skills`
154
+ * alias at project scope too, where that alias takes precedence over `.gemini/skills`. It still
155
+ * needs an instruction bridge, which is a separate axis and unaffected.
156
+ *
157
+ * Instruction bridges are recorded at project scope only. The user-scope equivalents exist, but
158
+ * nothing writes or reads them yet: `init` works inside a repository, and so does `doctor`.
18
159
  *
19
160
  * `windsurf` is the former name of Devin Desktop, rebranded 2026-06-02. It is retained as a
20
161
  * deprecated alias: Devin still scans the legacy `.windsurf/skills` path, so its projection keeps
@@ -24,9 +165,219 @@ type Harness = {
24
165
  */
25
166
  declare const harnessRegistry: readonly Harness[];
26
167
  //#endregion
168
+ //#region src/diagnose-bridges/diagnose-instructions.d.ts
169
+ /** What is on disk where an instruction bridge belongs. */
170
+ type InstructionKind = "import" | "symlink" | "settings-entry" | "file" | "none";
171
+ /**
172
+ * `unbridged` is the case a skills bridge has no equivalent of: the file is there, so nothing looks
173
+ * wrong, and it names `AGENTS.md` nowhere — a `CLAUDE.md` overwritten with real content, or a
174
+ * settings file another tool rewrote.
175
+ */
176
+ type InstructionStatus = "ok" | "missing" | "unbridged" | "unreadable";
177
+ type InstructionReport = {
178
+ harness: HarnessName;
179
+ /** Repository-relative path of the file that carries the bridge. */
180
+ path: string;
181
+ kind: InstructionKind;
182
+ status: InstructionStatus;
183
+ };
184
+ /**
185
+ * Reports whether every enabled harness can still read `AGENTS.md`.
186
+ *
187
+ * Losing one of these is quieter than losing a skills bridge and costs more: a harness with no
188
+ * instruction bridge reads none of the repository's instructions, and says nothing about it. The
189
+ * checks are read-only, and no repair is a command — rewriting an instruction file is the `init`
190
+ * skill's judgment.
191
+ */
192
+ declare function diagnoseInstructions(root: string, harnesses: readonly Harness[], cli: string): {
193
+ instructions: InstructionReport[];
194
+ findings: BridgeFinding[];
195
+ };
196
+ //#endregion
197
+ //#region src/diagnose-bridges/locator.d.ts
198
+ /**
199
+ * Where a finding is, in parts.
200
+ *
201
+ * A locator addresses a place inside a file — `.cursor/mcp.json#servers.linear.command` — because a
202
+ * file holding twenty servers is not an address. The parts are known wherever a finding is raised,
203
+ * and only the report and the repairs need the string, so the parts are what travel and this module
204
+ * is the one place they become one. Nothing ever recovers a part by splitting a locator back apart:
205
+ * a server may be named `io.github.foo`, and no split of the rendered string tells where the server
206
+ * name ends and the field begins.
207
+ */
208
+ /**
209
+ * Where a malformed file failed. **Position only.** A parser's own error message quotes the
210
+ * offending line back, and in a file of MCP configuration that line is exactly the one holding a
211
+ * credential — so neither the message nor the code block it carries is ever read. Line and column
212
+ * are enough to fix the file and carry nothing out of it.
213
+ */
214
+ type Position = {
215
+ line: number;
216
+ column: number;
217
+ };
218
+ type Locator = {
219
+ /** Repository-relative path of the file the finding is about. */
220
+ file: string;
221
+ /** The MCP server inside that file, when the finding is about one. */
222
+ server?: string;
223
+ /** The field inside that server, dotted for the per-name fields — `command`, `env.LINEAR_TOKEN`. */
224
+ field?: string;
225
+ /** Where a file that does not parse failed, when the parser gave a position. */
226
+ position?: Position | undefined;
227
+ };
228
+ //#endregion
229
+ //#region src/diagnose-bridges/doctor-guidance.d.ts
230
+ /**
231
+ * The one place `doctor`'s guidance is written. The command reads `detail` and `repair` to fill its
232
+ * `findings` and `help` sections; `skills/doctor/SKILL.md` is generated from the same table by
233
+ * `scripts/generate-skills.ts`, so the shipped skill cannot drift from what the command says.
234
+ */
235
+ /** Every way a skills bridge can fail, in the order `doctor` reports them. */
236
+ type BridgeProblem = "no-canonical" | "missing" | "degraded" | "stale" | "diverged-bridge" | "diverged-canonical" | "diverged-both" | "diverged-unknown" | "unpinned-copy";
237
+ /**
238
+ * Every way an instruction bridge can fail. Separate from `BridgeProblem` because the two share no
239
+ * repair: a skills bridge is rebuilt with `init` flags, and an instruction bridge is a file whose
240
+ * content is the user's, so every repair here goes back to the `init` skill.
241
+ */
242
+ /**
243
+ * Configuration that is present and **wrong**, as against a bridge that does not resolve. These are
244
+ * the faults the `repair` skill owns: none is expressible as an `init` flag, because `init`
245
+ * consolidates and creates but never corrects a file the user already wrote.
246
+ */
247
+ type ConfigurationFault = "deprecated-harness" | "ignored-bridge" | "unread-local-override" | "unloadable-skill";
248
+ /**
249
+ * Every way the golden MCP server set and a harness's copy of it can disagree, plus the two
250
+ * credential findings.
251
+ *
252
+ * A family of its own because nothing it says is shared with the others. A bridge either resolves
253
+ * or does not; two MCP files never share a byte, so every finding here is the result of a semantic
254
+ * comparison, and each one names a **server** and usually a **field** rather than a file.
255
+ */
256
+ type McpProblem = "mcp-golden-unreadable" | "mcp-target-unreadable" | "mcp-unprojected" | "mcp-undeclared" | "mcp-diverged-target" | "mcp-diverged-golden" | "mcp-diverged-both" | "mcp-diverged-unknown" | "mcp-literal-secret" | "mcp-committed-secret";
257
+ /**
258
+ * Configuration that works, for exactly one harness. Not wrong and not missing — the two states the
259
+ * other families cover — which is why it is a family of its own: every other finding says something
260
+ * is broken, and these say something reaches less far than the repository meant.
261
+ *
262
+ * Each one names the canonical form it converts to, because surfacing without a destination is just
263
+ * a list of files somebody already knows about.
264
+ */
265
+ type NonstandardProblem = "nonstandard-instructions" | "nonstandard-rule" | "nonstandard-command" | "nonstandard-skill" | "nonstandard-subagent";
266
+ type InstructionProblem = "no-instructions" | "instructions-missing" | "instructions-unbridged" | "instructions-unreadable";
267
+ /** Everything `doctor` can report against, across every section. */
268
+ type DoctorProblem = BridgeProblem | InstructionProblem | ConfigurationFault | McpProblem | NonstandardProblem;
269
+ /** Alias kept for callers that name the whole set rather than one section. */
270
+ type ConfigurationProblem = DoctorProblem;
271
+ /**
272
+ * What resolves one finding, for `doctor`'s own output. Two fields rather than one string, because
273
+ * the caller `doctor` is written for is an agent parsing TOON, and the question it has to answer is
274
+ * "can I execute this, or is this judgment I hand to a skill?" A single string leaves that
275
+ * answerable only by parsing English.
276
+ *
277
+ * The contract is exact, and it is what makes the field safe to act on blindly:
278
+ *
279
+ * - `command` non-empty — a shell invocation that, run verbatim, **completes** the repair.
280
+ * - `command` empty — no single invocation does; act on `instruction` and do not synthesize one.
281
+ *
282
+ * So a diagnostic worth running is not a `command`. `diverged-both` carries none even though
283
+ * `git diff --no-index …` is perfectly runnable: the diff shows what differs, it does not reconcile
284
+ * anything. That is what stops an agent executing every `command` it is handed from rebuilding a
285
+ * diverged bridge over whichever side holds the newer edit. A skill invocation is not a `command`
286
+ * either — nothing in a shell runs `/buddy-agent-harness:init`.
287
+ */
288
+ type RepairAction = {
289
+ /** A shell invocation that completes the repair, or empty when none does. */
290
+ command: string;
291
+ /** The imperative, in prose, always present and complete on its own. */
292
+ instruction: string;
293
+ };
294
+ type Repair = {
295
+ problem: DoctorProblem;
296
+ /** What `doctor` prints in the `findings` row for this problem. */
297
+ detail: string;
298
+ /**
299
+ * What fixes it, for `doctor`'s own output. `at` is where the finding is, in parts — the file
300
+ * alone for a bridge, the file, server, and field for an MCP finding; `cli` is how to invoke this
301
+ * tool. A repair that names only part of a locator reads it off `at` rather than splitting the
302
+ * rendered string, which no separator survives: a server may be named `io.github.foo`.
303
+ */
304
+ repair(at: Locator, cli: string): RepairAction;
305
+ /**
306
+ * What the shipped skill tells an agent to do instead. A repair that rebuilds a bridge delegates
307
+ * to the `init` skill rather than calling the `init` command, because rebuilding can move
308
+ * user-authored skills and that judgment is the `init` skill's, not `doctor`'s.
309
+ */
310
+ skillRepair(at: Locator): string;
311
+ };
312
+ declare const bridgeRepairs: readonly Repair[];
313
+ declare const instructionRepairs: readonly Repair[];
314
+ declare const doctorRepairs: readonly Repair[];
315
+ declare const doctorSkill: {
316
+ readonly name: "doctor";
317
+ readonly description: "Use this skill when a repository loads no project skills, when skills are missing after a clone, when a harness appears to be ignoring AGENTS.md, or when checking whether the agent configuration bridges into .claude/skills, CLAUDE.md, and the other harness files still resolve.";
318
+ };
319
+ /**
320
+ * The shipped `doctor` skill, rendered from the same table the command prints. `version` is the
321
+ * package version the skill ships with; it pins the `npx` invocation so the table and the CLI that
322
+ * produced it stay on the same breaking line.
323
+ *
324
+ * The finding tables are NOT here. They live in the reference pages below, and this file carries
325
+ * only what every reader needs: how to run it, how to read the report, and where to go next.
326
+ */
327
+ declare function renderDoctorSkill(version: string): string;
328
+ //#endregion
27
329
  //#region src/diagnose-bridges/git-bridge-state.d.ts
28
330
  /** Which side of a diverged bridge moved since the last commit where the two agreed. */
29
331
  type DivergenceDirection = "bridge" | "canonical" | "both" | "unknown";
332
+ /** Whether a path is tracked, and if so whether its skip-worktree bit is still set. */
333
+ type TrackingState = "untracked" | "tracked" | "skip-worktree";
334
+ /**
335
+ * Reads git state for the two sides of a bridge. Every method degrades to "cannot tell" rather than
336
+ * throwing, so `doctor` still reports on a tarball, a worktree without git, or a repository with no
337
+ * commits.
338
+ */
339
+ declare class GitBridgeState {
340
+ private readonly root;
341
+ /** Path of `root` relative to the repository root, `''` at the top, `undefined` outside a repository. */
342
+ private readonly prefix;
343
+ constructor(root: string);
344
+ /**
345
+ * `init --copy` marks a bridge skip-worktree so a machine-local copy over a tracked symlink stops
346
+ * showing as a pending change. The bit is a hint that some checkouts clear, so it is verified
347
+ * rather than assumed.
348
+ */
349
+ trackingOf(path: string): TrackingState;
350
+ /**
351
+ * Whether a `.gitignore` rule matches this path. Asked of git rather than matched against the
352
+ * file, so a rule on a parent directory — the common way a bridge gets ignored — is caught.
353
+ * Outside a repository nothing is ignored, which is the same "cannot tell" degradation the rest
354
+ * of this class uses.
355
+ */
356
+ isIgnored(path: string): boolean;
357
+ /**
358
+ * The newest commits that touched any of these paths, newest first. Empty outside a repository,
359
+ * which is the same "cannot tell" degradation as everything else here.
360
+ *
361
+ * Bounded like `lastAgreedTree` is: a baseline that is not in the recent history of the two files
362
+ * is not a baseline anyone can act on, and walking a whole repository to prove its absence costs
363
+ * more than the answer is worth.
364
+ */
365
+ commitsTouching(paths: readonly string[]): string[];
366
+ /** One file's content at one commit, or `undefined` when it did not exist there. */
367
+ contentAt(commit: string, path: string): string | undefined;
368
+ /**
369
+ * Names the side that moved by finding the newest commit whose two trees agreed, then asking which
370
+ * of the working directories still matches it.
371
+ */
372
+ directionOf(bridge: string, canonical: string): DivergenceDirection;
373
+ /** The tree of the newest commit where both paths held identical content. */
374
+ private lastAgreedTree;
375
+ /**
376
+ * Compares a working directory against a git tree by object id. `hash-object` is used rather than
377
+ * hashing in-process so the comparison follows whatever object format the repository uses.
378
+ */
379
+ private matchesTree;
380
+ }
30
381
  //#endregion
31
382
  //#region src/diagnose-bridges/diagnose-bridges.d.ts
32
383
  type BridgeKind = "symlink" | "copy" | "file" | "none";
@@ -40,9 +391,14 @@ type BridgeReport = {
40
391
  };
41
392
  type BridgeFinding = {
42
393
  path: string;
394
+ /**
395
+ * Which problem this is. Emitted so a caller routes on a name rather than by matching `detail`
396
+ * prose, which would break the moment the wording is improved.
397
+ */
398
+ problem: DoctorProblem;
43
399
  detail: string;
44
- /** The command that repairs this finding, already carrying the bridge path. */
45
- repair: string;
400
+ /** What repairs this finding, already carrying the bridge path. Empty `command` means judgment. */
401
+ repair: RepairAction;
46
402
  };
47
403
  type DivergenceReport = {
48
404
  path: string;
@@ -56,54 +412,139 @@ type DiagnoseOptions = {
56
412
  };
57
413
  type DiagnoseResult = {
58
414
  bridges: BridgeReport[];
415
+ /**
416
+ * The instruction bridges into `AGENTS.md`, kept out of `bridges` on purpose: a different `kind`
417
+ * and `status` vocabulary, and a repair that is the `init` skill rather than a command.
418
+ */
419
+ instructions: InstructionReport[];
59
420
  divergence: DivergenceReport[];
60
421
  findings: BridgeFinding[];
61
422
  };
62
423
  /**
63
- * Reports whether every bridge `init` would create for this repository still resolves into
64
- * `.agents/skills`. Read-only: nothing is created, moved, or repaired, so the caller decides what
65
- * to run from the repair each finding carries.
424
+ * Reports whether every bridge this repository needs still resolves: the skills bridges into
425
+ * `.agents/skills`, and the instruction bridges into `AGENTS.md`. Read-only: nothing is created,
426
+ * moved, or repaired, so the caller decides what to run from the repair each finding carries.
66
427
  */
67
428
  declare function diagnoseBridges({ root, harnesses: preferred, cli }: DiagnoseOptions): DiagnoseResult;
68
429
  //#endregion
69
- //#region src/diagnose-bridges/doctor.command.d.ts
70
- declare const doctorCommand: cli$1.Command;
71
- //#endregion
72
- //#region src/diagnose-bridges/doctor-guidance.d.ts
430
+ //#region src/diagnose-configuration/diagnose-configuration.d.ts
73
431
  /**
74
- * The one place `doctor`'s guidance is written. The command reads `detail` and `repair` to fill its
75
- * `findings` and `help` sections; `skills/doctor/SKILL.md` is generated from the same table by
76
- * `scripts/generate-skills.ts`, so the shipped skill cannot drift from what the command says.
432
+ * The configuration half of `doctor`. Where `diagnose-bridges` asks whether a bridge still
433
+ * resolves, this asks whether the configuration around it is still *right* — a harness name that
434
+ * has been superseded, an instruction file that never reaches `AGENTS.md`, a bridge git-ignores.
435
+ *
436
+ * Everything here is present-and-wrong rather than missing. Configuration that is absent is
437
+ * `init`'s to create; configuration that is wrong is what the `repair` skill acts on, and this
438
+ * module is where it reads its work from. Detection has one home so the two cannot drift.
439
+ *
440
+ * Instruction bridges are deliberately not here: `diagnose-instructions` owns them, and every one
441
+ * of its repairs goes back to the `init` skill rather than to `repair`.
442
+ *
443
+ * Read-only, like every other part of `doctor`.
77
444
  */
78
- /** Every way a bridge can fail, in the order `doctor` reports them. */
79
- type BridgeProblem = "no-canonical" | "missing" | "degraded" | "stale" | "diverged-bridge" | "diverged-canonical" | "diverged-both" | "diverged-unknown" | "unpinned-copy";
80
- type Repair = {
81
- problem: BridgeProblem;
82
- /** What `doctor` prints in the `findings` row for this problem. */
445
+ type ConfigurationFinding = {
446
+ /** Repository-relative path the finding is about. */
447
+ path: string;
448
+ problem: ConfigurationProblem;
83
449
  detail: string;
450
+ /** What repairs it, already carrying the path. Every fault here is judgment, so `command` is empty. */
451
+ repair: RepairAction;
452
+ };
453
+ //#endregion
454
+ //#region src/diagnose-bridges/doctor.command.d.ts
455
+ type DoctorReport = {
456
+ bin: string;
457
+ bridges: DiagnoseResult["bridges"];
458
+ instructions: DiagnoseResult["instructions"];
459
+ divergence?: DiagnoseResult["divergence"];
84
460
  /**
85
- * The command that fixes it, for `doctor`'s own output. `path` is the repository-relative bridge
86
- * path; `cli` is how to invoke this tool.
461
+ * The repair is lifted out into `help`, so a finding row stays to the diagnosis itself. `problem`
462
+ * stays on the row: it is how a caller routes without parsing `detail` prose.
87
463
  */
88
- repair(path: string, cli: string): string;
464
+ findings: {
465
+ path: string;
466
+ problem: DoctorProblem;
467
+ detail: string;
468
+ }[] | string;
89
469
  /**
90
- * What the shipped skill tells an agent to do instead. A repair that rebuilds a bridge delegates
91
- * to the `init` skill rather than calling the `init` command, because rebuilding can move
92
- * user-authored skills and that judgment is the `init` skill's, not `doctor`'s.
470
+ * One entry per distinct repair. Two fields rather than a sentence, because the caller has to
471
+ * tell an executable repair from an instruction, and `RepairAction` is where that lives.
472
+ *
473
+ * Both keys are always emitted, `command` as an empty string when there is none. An optional key
474
+ * would drop the whole array out of TOON's tabular form into the nested list form — worse for
475
+ * exactly the consumer the default format exists for.
93
476
  */
94
- skillRepair(path: string): string;
477
+ help?: RepairAction[];
95
478
  };
96
- declare const doctorRepairs: readonly Repair[];
97
- declare const doctorSkill: {
98
- readonly name: "doctor";
99
- readonly description: "Use this skill when a repository loads no project skills, when skills are missing after a clone, or when checking whether the agent configuration bridges into .claude/skills and the other harness directories still resolve.";
479
+ /**
480
+ * AXI §5: the healthy answer states the zero with context, so an agent does not re-run with other
481
+ * flags to confirm that an empty section really meant "nothing wrong".
482
+ */
483
+ declare function buildDoctorReport(bin: string, result: DiagnoseResult, configuration?: ConfigurationFinding[]): DoctorReport;
484
+ declare const doctorCommand: cli$1.Command;
485
+ //#endregion
486
+ //#region src/diagnose-mcp/diagnose-mcp.d.ts
487
+ /**
488
+ * The MCP half of `doctor`: how a repository's **golden MCP server set** and the harness copies of
489
+ * it have drifted apart, and where a literal credential is sitting in either.
490
+ *
491
+ * The golden set is a file the user authors. That is what makes this capability possible at all:
492
+ * `init` reports MCP configuration rather than converting it because converting a config someone
493
+ * wrote for one harness into another's format has to invent values they never wrote, and `init`
494
+ * invents nothing. A set the user authored inverts the premise — a field they filled in is
495
+ * transcription. **No golden set means no drift diagnosis**, and nothing here invents anything
496
+ * either.
497
+ *
498
+ * Read-only, like the rest of `doctor`. Projecting the golden set forward and reconciling a
499
+ * target-side change back into it are writes, and they need an approval-gated home; this module is
500
+ * where that home reads its work from.
501
+ */
502
+ type DiagnoseMcpOptions = {
503
+ root: string;
504
+ git: GitBridgeState;
505
+ /** How to name this tool in the repair commands. */
506
+ cli: string;
100
507
  };
508
+ declare function diagnoseMcp({ root, git, cli }: DiagnoseMcpOptions): ConfigurationFinding[];
509
+ //#endregion
510
+ //#region src/diagnose-mcp/mcp-model.d.ts
101
511
  /**
102
- * The shipped `doctor` skill, rendered from the same table the command prints. `version` is the
103
- * package version the skill ships with; it pins the `npx` invocation so the table and the CLI that
104
- * produced it stay on the same breaking line.
512
+ * The shape both sides of an MCP comparison are normalized into.
513
+ *
514
+ * No two MCP configuration files are ever byte-equal — the supported harnesses spread the same
515
+ * servers across six config keys and three serialization formats — so comparison cannot be a
516
+ * diff. Each side is parsed into this model and the models are compared.
517
+ *
518
+ * The field list is a **superset** of what the hosts accept, which is the whole premise of a
519
+ * golden set: `description`, `enabled`, and `timeout` are Goose's, `source` is Zed's, and a
520
+ * converter writing into either has to supply them. Supplying a value the user wrote is
521
+ * transcription; supplying one they did not is invention, and `init` invents nothing. See
522
+ * `.research/agentic-configuration-standards/` (E-MCP-05) for what each host makes up today.
105
523
  */
106
- declare function renderDoctorSkill(version: string): string;
524
+ type McpTransport = "stdio" | "http" | "sse";
525
+ type McpServer = {
526
+ transport?: McpTransport;
527
+ command?: string;
528
+ args?: readonly string[];
529
+ env?: Readonly<Record<string, string>>;
530
+ url?: string;
531
+ headers?: Readonly<Record<string, string>>;
532
+ description?: string;
533
+ enabled?: boolean;
534
+ timeout?: number;
535
+ source?: string;
536
+ };
537
+ /** Every field of the model, in the order a finding reports them. */
538
+ declare const mcpFields: readonly ["transport", "command", "args", "env", "url", "headers", "description", "enabled", "timeout", "source"];
539
+ type McpField = (typeof mcpFields)[number];
540
+ //#endregion
541
+ //#region src/diagnose-mcp/mcp-baseline.d.ts
542
+ /** Which side of a diverged server moved since the two last agreed. */
543
+ type McpDirection = "target" | "golden" | "both" | "unknown";
544
+ //#endregion
545
+ //#region src/diagnose-mcp/mcp-sources.d.ts
546
+ /** Where the golden set lives, and why it is namespaced rather than sitting at `.agents/mcp.json`. */
547
+ declare const goldenSetPath = ".agents/buddy-agent-harness/mcp.toml";
107
548
  //#endregion
108
549
  //#region src/initialize-harnesses/init.command.d.ts
109
550
  declare const initCommand: cli$1.Command;
@@ -112,18 +553,28 @@ declare function activate({ addCommand }: {
112
553
  addCommand(command: typeof harnessCommand): void;
113
554
  }): void;
114
555
  //#endregion
556
+ //#region src/skill-projection/skill-projection.d.ts
557
+ /**
558
+ * Which conflicting targets a run may replace. `true` is every one of them; a list names them
559
+ * individually, so a run invoked to fix one bridge cannot reach past it to another. `false` and an
560
+ * empty list both mean none, which is what makes a conflict stop the run.
561
+ */
562
+ type ForceSelection = boolean | readonly string[];
563
+ //#endregion
115
564
  //#region src/initialize-harnesses/initialize-harnesses.d.ts
116
565
  type InitializeOptions = {
117
566
  root: string;
118
567
  harnesses?: HarnessName[];
119
568
  copy?: boolean;
120
- force?: boolean;
569
+ force?: ForceSelection;
121
570
  };
122
571
  type InitializeResult = {
123
572
  root: string;
124
573
  harnesses: HarnessName[];
125
574
  native: HarnessName[];
126
575
  linked: HarnessName[];
576
+ /** Enabled harnesses left untouched because their target conflicts and `--force` did not name it. */
577
+ skipped: HarnessName[];
127
578
  /** Enabled harnesses whose name has been superseded, as `{ name, replacedBy }`. */
128
579
  deprecated: {
129
580
  name: HarnessName;
@@ -134,5 +585,5 @@ type InitializeResult = {
134
585
  };
135
586
  declare function initializeHarnesses({ root, harnesses: preferred, copy, force }: InitializeOptions): InitializeResult;
136
587
  //#endregion
137
- export { type BridgeFinding, type BridgeKind, type BridgeProblem, type BridgeReport, type BridgeStatus, type DiagnoseOptions, type DiagnoseResult, type DivergenceDirection, type DivergenceReport, type HarnessName, type InitializeOptions, type InitializeResult, type Repair, activate, diagnoseBridges, doctorCommand, doctorRepairs, doctorSkill, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, renderDoctorSkill };
588
+ export { type BridgeFinding, type BridgeKind, type BridgeProblem, type BridgeReport, type BridgeStatus, type ConfigurationFault, type ConfigurationProblem, type DiagnoseMcpOptions, type DiagnoseOptions, type DiagnoseResult, type DivergenceDirection, type DivergenceReport, type DoctorProblem, type DoctorReport, type Harness, type HarnessName, type HarnessScope, type HarnessScopeName, type InitializeOptions, type InitializeResult, type InstructionBridge, type InstructionKind, type InstructionProblem, type InstructionReport, type InstructionStatus, type McpConfig, type McpDirection, type McpField, type McpProblem, type McpServer, type McpTransport, type Repair, activate, bridgeRepairs, buildDoctorReport, diagnoseBridges, diagnoseInstructions, diagnoseMcp, doctorCommand, doctorRepairs, doctorSkill, goldenSetPath, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, instructionRepairs, renderDoctorSkill, run };
138
589
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/harness-registry/harness-registry.ts","../src/diagnose-bridges/git-bridge-state.ts","../src/diagnose-bridges/diagnose-bridges.ts","../src/diagnose-bridges/doctor.command.ts","../src/diagnose-bridges/doctor-guidance.ts","../src/initialize-harnesses/init.command.ts","../src/initialize-harnesses/initialize-harnesses.ts"],"mappings":";;KAGY;;;;;KAaA;EACX,MAAM;EACN;EACA;;EAEA,aAAa;;;;;;;;;;;;cAaD,0BAA0B;;;;KC7B3B;;;KCEA;KACA;KAEA;EACX,SAAS;;EAET;EACA,MAAM;EACN,QAAQ;;KAGG;EACX;EACA;;EAEA;;KAGW;EACX;EACA,WAAW;;KAGA;EACX;EACA,YAAY;;EAEZ;;KAGW;EACX,SAAS;EACT,YAAY;EACZ,UAAU;;;;;;;iBA8EK,kBAAkB,MAAM,WAAW,WAAgB,OAAO,kBAAkB;;;cC7E/E,eAAe,MAAI;;;;;;;;;KClCpB;KAWA;EACX,SAAS;;EAET;;;;;EAKA,OAAO,cAAc;;;;;;EAMrB,YAAY;;cAqDA,wBAAwB;cAmExB;WACZ;WACA;;;;;;;iBAae,kBAAkB;;;cChKrB,aAAa,MAAI;cA0DjB,gBAAgB,MAAI;iBAMjB,WAAW;EAAgB,WAAW,gBAAgB;;;;KCnE1D;EACX;EACA,YAAY;EACZ;EACA;;KAEW;EACX;EACA,WAAW;EACX,QAAQ;EACR,QAAQ;;EAER;IAAc,MAAM;IAAa,YAAY;;EAC7C;EACA;;iBAGe,sBACf,MACA,WAAW,WACX,MACA,SACE,oBAAoB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/harness-registry/instruction-bridge.ts","../src/harness-registry/mcp-config.ts","../src/harness-registry/nonstandard-artifact.ts","../src/harness-registry/harness-registry.ts","../src/diagnose-bridges/diagnose-instructions.ts","../src/diagnose-bridges/locator.ts","../src/diagnose-bridges/doctor-guidance.ts","../src/diagnose-bridges/git-bridge-state.ts","../src/diagnose-bridges/diagnose-bridges.ts","../src/diagnose-configuration/diagnose-configuration.ts","../src/diagnose-bridges/doctor.command.ts","../src/diagnose-mcp/diagnose-mcp.ts","../src/diagnose-mcp/mcp-model.ts","../src/diagnose-mcp/mcp-baseline.ts","../src/diagnose-mcp/mcp-sources.ts","../src/initialize-harnesses/init.command.ts","../src/skill-projection/skill-projection.ts","../src/initialize-harnesses/initialize-harnesses.ts"],"mappings":";;;;;;;;;;;;;;;KAYY;;;;;;EAOT;;EAEA;;;EAIA;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;;;KCbS;;EAEX;;;;;;EAMA;;;;;EAKA;;;;;;EAMA;;;;;;;;;;;;;;;;;KCtBW;KAgBA;;;;;EAKX;;EAEA;EACA,MAAM;;;;KC/BK;;KAUA;;;;;;;;;;;;;;;;;;;;;;;;;KA0BA;EACX;EACA;EACA,oBAAoB;EACpB,YAAY;EACZ,uBAAuB;;KAGZ;EACX,MAAM;;EAEN,SAAS;;;;;EAKT,OAAO;;EAEP,aAAa;;;;;;;;;;;;;;;;;;;;;;cAuBD,0BAA0B;;;;KCzE3B;;;;;;KAMA;KAEA;EACX,SAAS;;EAET;EACA,MAAM;EACN,QAAQ;;;;;;;;;;iBAkFO,qBACf,cACA,oBAAoB,WACpB;EACI,cAAc;EAAqB,UAAU;;;;;;;;;;;;;;;;;;;;KC5FtC;EAAa;EAAc;;KAE3B;;EAEX;;EAEA;;EAEA;;EAEA,WAAW;;;;;;;;;;KCfA;;;;;;;;;;;KAqBA;;;;;;;;;KAUA;;;;;;;;;KAoBA;KAOA;;KAOA,gBAAgB,gBAAgB,qBAAqB,qBAAqB,aAAa;;KAGvF,uBAAuB;;;;;;;;;;;;;;;;;;KAmBvB;;EAEX;;EAEA;;KAGW;EACX,SAAS;;EAET;;;;;;;EAOA,OAAO,IAAI,SAAS,cAAc;;;;;;EAMlC,YAAY,IAAI;;cAwKJ,wBAAwB;cA4CxB,6BAA6B;cAuM7B,wBAAwB;cAOxB;WACZ;WACA;;;;;;;;;;iBAmCe,kBAAkB;;;;KC5jBtB;;KAGA;;;;;;cAwBC;mBAIA;;mBAFK;EAEjB,YAAY;;;;;;EASZ,WAAW,eAAe;;;;;;;EAa1B,UAAU;;;;;;;;;EAaV,gBAAgB;;EAMhB,UAAU,gBAAgB;;;;;EAS1B,YAAY,gBAAgB,oBAAoB;;UAYxC;;;;;UAmBA;;;;KC5GG;KACA;KAEA;EACX,SAAS;;EAET;EACA,MAAM;EACN,QAAQ;;KAGG;EACX;;;;;EAKA,SAAS;EACT;;EAEA,QAAQ;;KAGG;EACX;EACA,WAAW;;KAGA;EACX;EACA,YAAY;;EAEZ;;KAGW;EACX,SAAS;;;;;EAKT,cAAc;EACd,YAAY;EACZ,UAAU;;;;;;;iBA2EK,kBAAkB,MAAM,WAAW,WAAgB,OAAO,kBAAkB;;;;;;;;;;;;;;;;;KC1GhF;;EAEX;EACA,SAAS;EACT;;EAEA,QAAQ;;;;KCfG;EACX;EACA,SAAS;EACT,cAAc;EACd,aAAa;;;;;EAKb;IAAY;IAAc,SAAS;IAAe;;;;;;;;;;EASlD,OAAO;;;;;;iBAOQ,kBACf,aACA,QAAQ,gBACR,gBAAe,yBACb;cAgCU,eAAe,MAAI;;;;;;;;;;;;;;;;;;KC7CpB;EACX;EACA,KAAK;;EAEL;;iBAgCe,cAAc,MAAM,KAAK,OAAO,qBAAqB;;;;;;;;;;;;;;;;KClDzD;KAEA;EACX,YAAY;EACZ;EACA;EACA,MAAM,SAAS;EACf;EACA,UAAU,SAAS;EACnB;EACA;EACA;EACA;;;cAIY;KAaD,mBAAmB;;;;KCnCnB;;;;cCAC;;;cCAA,aAAa,MAAI;cAoDjB,gBAAgB,MAAI;iBAMjB,WAAW;EAAgB,WAAW,gBAAgB;;;;;;;;;KCV1D;;;KCpDA;EACX;EACA,YAAY;EACZ;EACA,QAAQ;;KAEG;EACX;EACA,WAAW;EACX,QAAQ;EACR,QAAQ;;EAER,SAAS;;EAET;IAAc,MAAM;IAAa,YAAY;;EAC7C;EACA;;iBAGe,sBACf,MACA,WAAW,WACX,MACA,SACE,oBAAoB"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as doctorCommand, c as doctorSkill, i as initializeHarnesses, l as renderDoctorSkill, n as harnessCommand, o as diagnoseBridges, r as initCommand, s as doctorRepairs, t as activate, u as harnessRegistry } from "./init.command-c0fBv15T.mjs";
2
- export { activate, diagnoseBridges, doctorCommand, doctorRepairs, doctorSkill, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, renderDoctorSkill };
1
+ import { _ as harnessRegistry, a as initializeHarnesses, c as diagnoseBridges, d as goldenSetPath, f as bridgeRepairs, g as renderDoctorSkill, h as instructionRepairs, i as initCommand, l as diagnoseInstructions, m as doctorSkill, n as activate, o as buildDoctorReport, p as doctorRepairs, r as harnessCommand, s as doctorCommand, t as run, u as diagnoseMcp } from "./cli-Bvnq8zOL.mjs";
2
+ export { activate, bridgeRepairs, buildDoctorReport, diagnoseBridges, diagnoseInstructions, diagnoseMcp, doctorCommand, doctorRepairs, doctorSkill, goldenSetPath, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, instructionRepairs, renderDoctorSkill, run };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "keywords": [
6
6
  "repobuddy",
@@ -46,7 +46,8 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@toon-format/toon": "^4.1.1",
49
- "clibuilder": "^10.0.0"
49
+ "clibuilder": "^10.0.0",
50
+ "smol-toml": "^1.8.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@types/node": "^24.12.4",