buddy-agent-harness 1.1.0 → 1.2.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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/cli.mjs +86 -9
- package/dist/index.d.mts +10 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +92 -8
- package/dist/index.mjs.map +1 -1
- package/dist/skill-scripts/doctor.mjs +27 -27
- package/dist/skill-scripts/init.mjs +27 -27
- package/dist/skill-scripts/mcp.mjs +27 -27
- package/llms.txt +6 -5
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/skills/doctor-buddy-agent-harness/README.md +1 -1
- package/skills/doctor-buddy-agent-harness/SKILL.md +9 -2
- package/skills/doctor-buddy-agent-harness/references/worktrees.md +17 -0
- package/skills/doctor-buddy-agent-harness/scripts/doctor.mjs +27 -27
- package/skills/enhance/SKILL.md +18 -10
- package/skills/enhance/references/agent-worktrees.md +59 -0
- package/skills/enhance/references/references-as-links.history.md +9 -0
- package/skills/enhance/references/references-as-links.md +76 -0
- package/skills/enhance/scripts/doctor.mjs +27 -27
- package/skills/init-buddy-agent-harness/SKILL.md +8 -5
- package/skills/init-buddy-agent-harness/references/agent-worktrees.md +59 -0
- package/skills/init-buddy-agent-harness/scripts/doctor.mjs +27 -27
- package/skills/init-buddy-agent-harness/scripts/init.mjs +27 -27
- package/skills/repair/README.md +2 -2
- package/skills/repair/SKILL.md +3 -3
- package/skills/repair/scripts/doctor.mjs +27 -27
- package/skills/repair/scripts/mcp.mjs +27 -27
- package/src/diagnose-bridges/doctor-guidance.ts +66 -1
- package/src/diagnose-bridges/doctor.command.ts +2 -0
- package/src/diagnose-bridges/git-bridge-state.ts +5 -0
- package/src/diagnose-worktrees/diagnose-worktrees.ts +69 -0
- package/src/index.ts +1 -0
|
@@ -72,6 +72,12 @@ export type GlobalInstructionProblem =
|
|
|
72
72
|
| 'global-instructions-overridden'
|
|
73
73
|
| 'global-instructions-emptied'
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* What a repository lacks for the git worktrees Claude Code creates under `.claude/worktrees/`;
|
|
77
|
+
* each is an offer `init` and `enhance` make, never a fault in a file the owner wrote.
|
|
78
|
+
*/
|
|
79
|
+
export type WorktreeProblem = 'worktrees-untracked' | 'worktrees-scanned' | 'worktree-base-unset'
|
|
80
|
+
|
|
75
81
|
/** Everything `doctor` can report against, across every section. */
|
|
76
82
|
export type DoctorProblem =
|
|
77
83
|
| BridgeProblem
|
|
@@ -80,6 +86,7 @@ export type DoctorProblem =
|
|
|
80
86
|
| ConfigurationFault
|
|
81
87
|
| McpProblem
|
|
82
88
|
| NonstandardProblem
|
|
89
|
+
| WorktreeProblem
|
|
83
90
|
|
|
84
91
|
/** Alias kept for callers that name the whole set rather than one section. */
|
|
85
92
|
export type ConfigurationProblem = DoctorProblem
|
|
@@ -192,6 +199,7 @@ export const pnpmSkillRef = (invocation: string): string =>
|
|
|
192
199
|
`\`${invocation}\` (pnpm install: \`pnpm-${commandInvocation}-${invocation.slice(invocation.indexOf(':') + 1)}\`)`
|
|
193
200
|
|
|
194
201
|
export const initSkillRef: string = pnpmSkillRef(initSkillInvocation)
|
|
202
|
+
export const enhanceSkillRef: string = pnpmSkillRef('/buddy-agent-harness:enhance')
|
|
195
203
|
export const repairSkillRef: string = pnpmSkillRef(repairSkillInvocation)
|
|
196
204
|
|
|
197
205
|
/** Where the user authors the golden MCP server set, named in every MCP repair that points at it. */
|
|
@@ -589,6 +597,38 @@ const nonstandardTable: Record<NonstandardProblem, RepairRow> = {
|
|
|
589
597
|
|
|
590
598
|
export const nonstandardRepairs: readonly Repair[] = repairsOf(nonstandardTable)
|
|
591
599
|
|
|
600
|
+
/** Who offers every worktree repair, named the same way in each row. */
|
|
601
|
+
const worktreeOffer = `${initSkillRef} offers it, or ${enhanceSkillRef} where the repository already has an AGENTS.md`
|
|
602
|
+
|
|
603
|
+
const worktreeTable: Record<WorktreeProblem, RepairRow> = {
|
|
604
|
+
'worktrees-untracked': {
|
|
605
|
+
detail:
|
|
606
|
+
'.claude/worktrees/ is not git-ignored, so every agent worktree Claude Code creates there shows up as untracked in git status',
|
|
607
|
+
repair: ({ file }) => ({ command: '', instruction: `add \`.claude/worktrees/\` to ${file} — ${worktreeOffer}` }),
|
|
608
|
+
skillRepair: () => `run ${initSkillRef} or ${enhanceSkillRef}, which offers the .gitignore line`,
|
|
609
|
+
},
|
|
610
|
+
'worktrees-scanned': {
|
|
611
|
+
detail:
|
|
612
|
+
'this config does not exclude .claude/worktrees, so the tool walks into each agent worktree, a whole checkout with its own config, and Biome stops on the nested root configuration',
|
|
613
|
+
repair: ({ file }) => ({
|
|
614
|
+
command: '',
|
|
615
|
+
instruction: `add \`"!**/.claude/worktrees"\` to \`files.includes\` in ${file} — ${worktreeOffer}`,
|
|
616
|
+
}),
|
|
617
|
+
skillRepair: () => `run ${initSkillRef} or ${enhanceSkillRef}, which offers the exclusion`,
|
|
618
|
+
},
|
|
619
|
+
'worktree-base-unset': {
|
|
620
|
+
detail:
|
|
621
|
+
'worktree.baseRef is unset, so agent worktrees branch from the default branch rather than the current HEAD — work on a feature branch starts without its commits',
|
|
622
|
+
repair: ({ file }) => ({
|
|
623
|
+
command: '',
|
|
624
|
+
instruction: `decide whether agent worktrees should branch from HEAD, and if so set \`"worktree": { "baseRef": "head" }\` in ${file} — ${worktreeOffer}`,
|
|
625
|
+
}),
|
|
626
|
+
skillRepair: () => `run ${initSkillRef} or ${enhanceSkillRef}, which explains the trade-off and offers the setting`,
|
|
627
|
+
},
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export const worktreeRepairs: readonly Repair[] = repairsOf(worktreeTable)
|
|
631
|
+
|
|
592
632
|
/**
|
|
593
633
|
* Annotated over the whole union rather than inferred, so an added problem fails to compile until
|
|
594
634
|
* its section carries it.
|
|
@@ -600,6 +640,7 @@ const doctorTable: Record<DoctorProblem, RepairRow> = {
|
|
|
600
640
|
...configurationTable,
|
|
601
641
|
...mcpTable,
|
|
602
642
|
...nonstandardTable,
|
|
643
|
+
...worktreeTable,
|
|
603
644
|
}
|
|
604
645
|
|
|
605
646
|
export const doctorRepairs: readonly Repair[] = repairsOf(doctorTable)
|
|
@@ -612,7 +653,7 @@ export function repairFor(problem: DoctorProblem): Repair {
|
|
|
612
653
|
export const doctorSkill = {
|
|
613
654
|
name: 'doctor-buddy-agent-harness',
|
|
614
655
|
description:
|
|
615
|
-
'
|
|
656
|
+
"Checks whether a repository's agent configuration bridges into .claude/skills and the other harness files still resolve — when a repository loads no project skills, when skills are missing after a clone, or when a harness appears to be ignoring AGENTS.md.",
|
|
616
657
|
} as const
|
|
617
658
|
|
|
618
659
|
/** Written into the generated skill so a reader knows not to edit it in place. */
|
|
@@ -699,6 +740,7 @@ Every \`problem\` name routes to exactly one page. Load the page for the finding
|
|
|
699
740
|
| \`references/configuration.md\` | a finding about the configuration around the bridges rather than a bridge |
|
|
700
741
|
| \`references/mcp.md\` | any finding whose path is an MCP locator — **always** before acting on a credential finding |
|
|
701
742
|
| \`references/nonstandard.md\` | a finding about configuration only one harness can read |
|
|
743
|
+
| \`references/worktrees.md\` | a finding about the agent worktrees Claude Code creates under \`.claude/worktrees/\` |
|
|
702
744
|
| \`references/harnesses/<name>.md\` | the paths and files one named harness uses |
|
|
703
745
|
|
|
704
746
|
## Rules
|
|
@@ -709,6 +751,12 @@ Every \`problem\` name routes to exactly one page. Load the page for the finding
|
|
|
709
751
|
- Never repeat a value from a file an \`mcp-literal-secret\` or \`mcp-committed-secret\` finding points at. The report withheld it on purpose, and quoting it back puts it in the transcript anyway.
|
|
710
752
|
- Never write outside the repository. A \`globalInstructions\` repair is a step for the user to take in their own home directory; hand it over exactly as reported.
|
|
711
753
|
- Write instructions in \`AGENTS.md\`, never in \`CLAUDE.md\`. Content written there reaches one harness, drifts from the canonical file, and — because Claude Code prefers it — takes \`AGENTS.md\` out of that harness's context entirely.
|
|
754
|
+
|
|
755
|
+
## Done when
|
|
756
|
+
|
|
757
|
+
- **C1.** The reply names each problem the report found and its cause, or, when the report found none, says the configuration is healthy and calls for no change.
|
|
758
|
+
- **C2.** No bridge, skill, or instruction file changed: no \`init\` ran, and nothing was written to \`CLAUDE.md\`.
|
|
759
|
+
- **C3.** The reply repeats no value that a secret finding withheld.
|
|
712
760
|
`
|
|
713
761
|
}
|
|
714
762
|
|
|
@@ -988,6 +1036,23 @@ ${repairTable(nonstandardRepairs)}
|
|
|
988
1036
|
A harness directory that is a **symlink** is a projection someone already made, not configuration authored here. An artifact is reported whether or not its harness is enabled: a \`.cursorrules\` in a repository nobody opens in Cursor is still instruction content \`AGENTS.md\` does not carry, and filtering by the enabled set would hide exactly the drift worth converting.
|
|
989
1037
|
|
|
990
1038
|
Hooks, LSP settings, and output styles are not covered yet. Their event names and shapes differ by harness with no safe projection, and half-reporting them would be worse than the silence.
|
|
1039
|
+
`,
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
path: 'references/worktrees.md',
|
|
1043
|
+
content: `${generatedSkillWarning}
|
|
1044
|
+
|
|
1045
|
+
# Agent worktree findings
|
|
1046
|
+
|
|
1047
|
+
Claude Code runs a session started with \`--worktree\`, and a subagent isolated in a worktree, in a git worktree under \`.claude/worktrees/<name>/\` inside the repository. That place is by design, and no setting moves it. These findings are what a repository needs so the worktrees there do no harm. \`doctor\` checks them only where a \`.claude\` directory exists.
|
|
1048
|
+
|
|
1049
|
+
${repairTable(worktreeRepairs)}
|
|
1050
|
+
|
|
1051
|
+
None of these is a fault in a file the owner wrote, and none is a \`repair\` correction. Each is an offer the \`init-buddy-agent-harness\` and \`enhance\` skills make, one at a time, and write only on approval.
|
|
1052
|
+
|
|
1053
|
+
\`worktree-base-unset\` is a choice, not an error. The default branch suits agents given unrelated tasks; \`"head"\` suits agents asked to build on the branch in front of them. Put the trade-off to the owner, and report an explicit \`"fresh"\` as their answer rather than as a finding — \`doctor\` already does.
|
|
1054
|
+
|
|
1055
|
+
\`worktrees-scanned\` is checked for Biome only, the one tool with a confirmed failure. A repository whose ESLint, Prettier, knip, or TypeScript configuration walks the tree can hit the same thing; the skills look for those when they make the offer.
|
|
991
1056
|
`,
|
|
992
1057
|
},
|
|
993
1058
|
...harnessRegistry.map((harness) => harnessPage(harness, initReferences.has(harness.name))),
|
|
@@ -7,6 +7,7 @@ import { collapseHome, displayBinPath, fail, parseHarnessOption } from '../comma
|
|
|
7
7
|
import { type ConfigurationFinding, diagnoseConfiguration } from '../diagnose-configuration/diagnose-configuration.ts'
|
|
8
8
|
import { diagnoseMcp } from '../diagnose-mcp/diagnose-mcp.ts'
|
|
9
9
|
import { diagnoseNonstandard } from '../diagnose-nonstandard/diagnose-nonstandard.ts'
|
|
10
|
+
import { diagnoseWorktrees } from '../diagnose-worktrees/diagnose-worktrees.ts'
|
|
10
11
|
import { referenceLayers } from '../references/references.ts'
|
|
11
12
|
import { type DiagnoseResult, diagnoseBridges } from './diagnose-bridges.ts'
|
|
12
13
|
import { commandInvocation, type DoctorProblem, type RepairAction } from './doctor-guidance.ts'
|
|
@@ -116,6 +117,7 @@ export async function diagnoseReport(args: {
|
|
|
116
117
|
...diagnoseConfiguration({ root, git, cli: commandInvocation }),
|
|
117
118
|
...diagnoseMcp({ root, git, cli: commandInvocation }),
|
|
118
119
|
...diagnoseNonstandard({ root, cli: commandInvocation }),
|
|
120
|
+
...diagnoseWorktrees({ root, git, cli: commandInvocation }),
|
|
119
121
|
]
|
|
120
122
|
const { layers: rows } = listReferences(
|
|
121
123
|
await referenceLayers({
|
|
@@ -37,6 +37,11 @@ export class GitBridgeState {
|
|
|
37
37
|
this.repoRootPrefix = git(root, ['rev-parse', '--show-prefix'])?.trim()
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** Whether `root` is inside a git work tree at all; a check that needs git's answer skips when not. */
|
|
41
|
+
isRepository(): boolean {
|
|
42
|
+
return this.repoRootPrefix !== undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
/** The skip-worktree bit is a hint some checkouts clear, so it is verified rather than assumed. */
|
|
41
46
|
trackingOf(path: string): TrackingState {
|
|
42
47
|
if (this.repoRootPrefix === undefined) return 'untracked'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { type RepairAction, repairFor, type WorktreeProblem } from '../diagnose-bridges/doctor-guidance.ts'
|
|
4
|
+
import type { GitBridgeState } from '../diagnose-bridges/git-bridge-state.ts'
|
|
5
|
+
import { isRecord } from '../is-record/is-record.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The agent-worktree half of `doctor`: what a repository lacks for the git worktrees Claude Code
|
|
9
|
+
* creates inside it under `.claude/worktrees/`. Read-only.
|
|
10
|
+
*/
|
|
11
|
+
export type WorktreeFinding = {
|
|
12
|
+
/** Repository-relative path the finding is about. */
|
|
13
|
+
path: string
|
|
14
|
+
problem: WorktreeProblem
|
|
15
|
+
detail: string
|
|
16
|
+
/** Always an empty `command`: each repair is an offer the owner approves. */
|
|
17
|
+
repair: RepairAction
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DiagnoseWorktreesOptions = {
|
|
21
|
+
root: string
|
|
22
|
+
git: GitBridgeState
|
|
23
|
+
/** How to name this tool in the repair commands. */
|
|
24
|
+
cli: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The trailing slash matters: `git check-ignore` matches a directory rule only against a directory path. */
|
|
28
|
+
export const worktreeDirectory = '.claude/worktrees/'
|
|
29
|
+
|
|
30
|
+
const biomeConfigs = ['biome.json', 'biome.jsonc']
|
|
31
|
+
const claudeSettings = '.claude/settings.json'
|
|
32
|
+
|
|
33
|
+
/** `undefined` when the file is missing or does not parse; either way Claude Code reads no base from it. */
|
|
34
|
+
function readJson(path: string): unknown {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checked only where `.claude/` exists, the sign the repository is worked in with Claude Code.
|
|
44
|
+
* Biome is the one tool checked: it is the confirmed failure, and the others are offered by the
|
|
45
|
+
* skills without a finding.
|
|
46
|
+
*/
|
|
47
|
+
export function diagnoseWorktrees({ root, git, cli }: DiagnoseWorktreesOptions): WorktreeFinding[] {
|
|
48
|
+
if (!existsSync(join(root, '.claude'))) return []
|
|
49
|
+
|
|
50
|
+
const findings: WorktreeFinding[] = []
|
|
51
|
+
const add = (path: string, problem: WorktreeProblem) => {
|
|
52
|
+
const { detail, repair } = repairFor(problem)
|
|
53
|
+
findings.push({ path, problem, detail, repair: repair({ file: path }, cli) })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (git.isRepository() && !git.isIgnored(worktreeDirectory)) add('.gitignore', 'worktrees-untracked')
|
|
57
|
+
|
|
58
|
+
for (const config of biomeConfigs) {
|
|
59
|
+
const path = join(root, config)
|
|
60
|
+
if (existsSync(path) && !readFileSync(path, 'utf8').includes('.claude/worktrees')) add(config, 'worktrees-scanned')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const settings = readJson(join(root, claudeSettings))
|
|
64
|
+
const worktree = isRecord(settings) ? settings['worktree'] : undefined
|
|
65
|
+
// An explicit `"fresh"` is the owner's choice; only an unset key is reported.
|
|
66
|
+
if (!isRecord(worktree) || worktree['baseRef'] === undefined) add(claudeSettings, 'worktree-base-unset')
|
|
67
|
+
|
|
68
|
+
return findings
|
|
69
|
+
}
|