buddy-agent-harness 0.2.0 → 0.3.1
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 +2 -2
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +2 -2
- package/README.md +13 -9
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +81 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/init.command-BN_w5DRZ.mjs +686 -0
- package/dist/init.command-BN_w5DRZ.mjs.map +1 -0
- package/package.json +4 -2
- package/plugin.json +2 -2
- package/skills/doctor/README.md +33 -0
- package/skills/doctor/SKILL.md +56 -0
- package/skills/init/README.md +1 -5
- package/skills/init/SKILL.md +13 -1
- package/skills/init/references/frontmatter.md +7 -0
- package/skills/init/references/harnesses/claude-code.md +1 -0
- package/skills/init/references/harnesses/codex.md +6 -0
- package/src/cli.ts +2 -1
- package/src/command-output/command-output.ts +68 -0
- package/src/diagnose-bridges/diagnose-bridges.ts +147 -0
- package/src/diagnose-bridges/directory-files.ts +17 -0
- package/src/diagnose-bridges/doctor-guidance.ts +176 -0
- package/src/diagnose-bridges/doctor.command.ts +87 -0
- package/src/diagnose-bridges/git-bridge-state.ts +102 -0
- package/src/index.ts +14 -0
- package/src/initialize-harnesses/init.command.ts +10 -12
- package/dist/init.command-wxC4cgUc.mjs +0 -203
- package/dist/init.command-wxC4cgUc.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.command-wxC4cgUc.mjs","names":[],"sources":["../src/harness-registry/harness-registry.ts","../src/skill-projection/skill-projection.ts","../src/initialize-harnesses/initialize-harnesses.ts","../src/initialize-harnesses/init.command.ts"],"sourcesContent":["import { lstatSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport type HarnessName =\n\t| 'claude-code'\n\t| 'cursor'\n\t| 'codex'\n\t| 'copilot-cli'\n\t| 'gemini-cli'\n\t| 'devin-desktop'\n\t| 'windsurf'\n\n/**\n * `skillsDirectory` is the projection target a harness needs to see `.agents/skills`.\n * A harness that reads `.agents/skills` natively has none, and is never projected into.\n */\nexport type Harness = {\n\tname: HarnessName\n\tdetect: string\n\tskillsDirectory?: string\n\t/** Set when this name has been superseded; the value is the name that replaces it. */\n\tdeprecated?: HarnessName\n}\n\n/**\n * Codex, Cursor, Copilot CLI, and Devin Desktop read `.agents/skills` directly, so they need no\n * projection. Only Claude Code and Gemini CLI read solely their own directory.\n *\n * `windsurf` is the former name of Devin Desktop, rebranded 2026-06-02. It is retained as a\n * deprecated alias: Devin still scans the legacy `.windsurf/skills` path, so its projection keeps\n * working, but new repositories should enable `devin-desktop` and have nothing written for them.\n *\n * See `.research/agentic-configuration-standards/` for the per-harness sources.\n */\nexport const harnessRegistry: readonly Harness[] = [\n\t{ name: 'claude-code', detect: '.claude', skillsDirectory: '.claude/skills' },\n\t{ name: 'cursor', detect: '.cursor' },\n\t{ name: 'codex', detect: '.codex' },\n\t{ name: 'copilot-cli', detect: '.github/skills' },\n\t{ name: 'gemini-cli', detect: '.gemini', skillsDirectory: '.gemini/skills' },\n\t{ name: 'devin-desktop', detect: '.devin' },\n\t{ name: 'windsurf', detect: '.windsurf', skillsDirectory: '.windsurf/skills', deprecated: 'devin-desktop' },\n]\n\n/** Enabled whether or not the repository already contains their directories. */\nconst defaultHarnesses: readonly HarnessName[] = ['claude-code', 'cursor']\n\nfunction directoryExists(path: string): boolean {\n\ttry {\n\t\treturn lstatSync(path).isDirectory()\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/** The default harnesses, plus the preferred ones, plus every harness whose directory is present. */\nexport function selectHarnesses(root: string, preferred: readonly HarnessName[]): Harness[] {\n\tconst enabled = new Set<HarnessName>([...defaultHarnesses, ...preferred])\n\treturn harnessRegistry.filter((harness) => enabled.has(harness.name) || directoryExists(join(root, harness.detect)))\n}\n","import { cpSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from 'node:fs'\nimport { dirname, join, relative } from 'node:path'\nimport type { Harness, HarnessName } from '../harness-registry/harness-registry.ts'\n\nfunction skillNames(skillsDirectory: string): string[] {\n\treturn readdirSync(skillsDirectory, { withFileTypes: true })\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name)\n\t\t.sort()\n}\n\nfunction pathOccupied(path: string): boolean {\n\ttry {\n\t\tlstatSync(path)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction isCorrectLink(target: string, source: string): boolean {\n\treturn lstatSync(target).isSymbolicLink() && readlinkSync(target) === relative(dirname(target), source)\n}\n\n/** Creates the canonical directory when absent, so a fresh repository reports zero skills. */\nexport function countSkills(canonicalSkills: string): number {\n\tmkdirSync(canonicalSkills, { recursive: true })\n\treturn skillNames(canonicalSkills).length\n}\n\nexport type ProjectSkillsOptions = {\n\troot: string\n\tcanonicalSkills: string\n\tharnesses: readonly Harness[]\n\tcopy: boolean\n\tforce: boolean\n}\n\n/**\n * Points every harness that cannot read `.agents/skills` at the canonical directory. Symlinking is\n * preferred; a platform that refuses the link falls back to a copy. Returns the projected harnesses.\n */\nexport function projectSkills({ root, canonicalSkills, harnesses, copy, force }: ProjectSkillsOptions): HarnessName[] {\n\tconst projections = harnesses\n\t\t.filter((harness) => harness.skillsDirectory)\n\t\t.map((harness) => ({\n\t\t\tharness,\n\t\t\ttarget: join(root, harness.skillsDirectory as string),\n\t\t}))\n\n\tconst conflicts = projections\n\t\t.filter(({ target }) => pathOccupied(target) && !isCorrectLink(target, canonicalSkills))\n\t\t.map(({ target }) => target)\n\n\tif (conflicts.length && !force) {\n\t\tthrow new Error(\n\t\t\t`Refusing to replace existing skill targets:\\n${conflicts.map((target) => `- ${target}`).join('\\n')}`,\n\t\t)\n\t}\n\n\tfor (const { target } of projections) {\n\t\tmkdirSync(dirname(target), { recursive: true })\n\t\tif (pathOccupied(target) && isCorrectLink(target, canonicalSkills)) continue\n\t\tif (pathOccupied(target)) rmSync(target, { recursive: true, force: true })\n\t\tif (copy) {\n\t\t\tcpSync(canonicalSkills, target, { recursive: true })\n\t\t\tcontinue\n\t\t}\n\t\ttry {\n\t\t\tsymlinkSync(relative(dirname(target), canonicalSkills), target, 'junction')\n\t\t} catch (error) {\n\t\t\tif (pathOccupied(target)) throw error\n\t\t\tcpSync(canonicalSkills, target, { recursive: true })\n\t\t}\n\t}\n\n\treturn projections.map(({ harness }) => harness.name)\n}\n","import { join } from 'node:path'\nimport { type HarnessName, selectHarnesses } from '../harness-registry/harness-registry.ts'\nimport { countSkills, projectSkills } from '../skill-projection/skill-projection.ts'\n\nexport type InitializeOptions = {\n\troot: string\n\tharnesses?: HarnessName[]\n\tcopy?: boolean\n\tforce?: boolean\n}\nexport type InitializeResult = {\n\troot: string\n\tharnesses: HarnessName[]\n\tnative: HarnessName[]\n\tlinked: HarnessName[]\n\t/** Enabled harnesses whose name has been superseded, as `{ name, replacedBy }`. */\n\tdeprecated: { name: HarnessName; replacedBy: HarnessName }[]\n\tskills: number\n\tcopied: boolean\n}\n\nexport function initializeHarnesses({\n\troot,\n\tharnesses: preferred = [],\n\tcopy = false,\n\tforce = false,\n}: InitializeOptions): InitializeResult {\n\tconst canonicalSkills = join(root, '.agents', 'skills')\n\tconst skills = countSkills(canonicalSkills)\n\tconst harnesses = selectHarnesses(root, preferred)\n\tconst linked = projectSkills({ root, canonicalSkills, harnesses, copy, force })\n\n\treturn {\n\t\troot,\n\t\tharnesses: harnesses.map((harness) => harness.name),\n\t\tnative: harnesses.filter((harness) => !harness.skillsDirectory).map((harness) => harness.name),\n\t\tlinked,\n\t\tdeprecated: harnesses\n\t\t\t.filter((harness) => harness.deprecated)\n\t\t\t.map((harness) => ({ name: harness.name, replacedBy: harness.deprecated as HarnessName })),\n\t\tskills,\n\t\tcopied: copy,\n\t}\n}\n","import { encode } from '@toon-format/toon'\nimport type { cli } from 'clibuilder'\nimport { command, z } from 'clibuilder'\nimport { type HarnessName, harnessRegistry } from '../harness-registry/harness-registry.ts'\nimport { initializeHarnesses } from './initialize-harnesses.ts'\n\nfunction write(value: object, format: 'json' | 'toon' | undefined): void {\n\tprocess.stdout.write(`${format === 'json' ? JSON.stringify(value) : encode(value)}\\n`)\n}\n\nexport const initCommand: cli.Command = command({\n\tname: 'init',\n\tdescription: 'Set up canonical agent skills for the harnesses already enabled in a repository.',\n\toptions: {\n\t\troot: {\n\t\t\tdescription: 'Repository or package directory. Defaults to the current directory.',\n\t\t\ttype: z.optional(z.string()),\n\t\t},\n\t\tharness: {\n\t\t\tdescription: 'Comma-separated harnesses to enable in addition to Claude Code and Cursor, e.g. codex,windsurf.',\n\t\t\ttype: z.optional(z.string()),\n\t\t},\n\t\tcopy: {\n\t\t\tdescription: 'Copy skills instead of linking them.',\n\t\t\ttype: z.optional(z.boolean()),\n\t\t},\n\t\tforce: {\n\t\t\tdescription: 'Replace conflicting target skill directories.',\n\t\t\ttype: z.optional(z.boolean()),\n\t\t},\n\t\tformat: {\n\t\t\tdescription: 'Output format: toon (default) or json.',\n\t\t\ttype: z.optional(z.string()),\n\t\t\tdefault: 'toon',\n\t\t},\n\t},\n\trun(args) {\n\t\ttry {\n\t\t\tconst format = args.format\n\t\t\tif (format !== 'toon' && format !== 'json') throw new Error('--format must be toon or json.')\n\t\t\tconst requested = args.harness\n\t\t\t\t?.split(',')\n\t\t\t\t.map((name) => name.trim())\n\t\t\t\t.filter(Boolean)\n\t\t\tconst unsupported = requested?.filter((name) => !harnessRegistry.some((harness) => harness.name === name))\n\t\t\tif (unsupported?.length)\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Unsupported harness: ${unsupported.join(', ')}. Supported: ${harnessRegistry\n\t\t\t\t\t\t.map((harness) => harness.name)\n\t\t\t\t\t\t.join(', ')}.`,\n\t\t\t\t)\n\t\t\twrite(\n\t\t\t\tinitializeHarnesses({\n\t\t\t\t\troot: args.root ?? process.cwd(),\n\t\t\t\t\t...(requested?.length ? { harnesses: requested as HarnessName[] } : {}),\n\t\t\t\t\t...(args.copy === undefined ? {} : { copy: args.copy }),\n\t\t\t\t\t...(args.force === undefined ? {} : { force: args.force }),\n\t\t\t\t}),\n\t\t\t\tformat,\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tprocess.stderr.write(`error: ${error instanceof Error ? error.message : 'Harness initialization failed.'}\\n`)\n\t\t\tprocess.exitCode = 1\n\t\t}\n\t},\n})\n\nexport const harnessCommand: cli.Command = command({\n\tname: 'harness',\n\tdescription: 'Commands for configuring agent harness compatibility.',\n\tcommands: [initCommand],\n})\n\nexport function activate({ addCommand }: { addCommand(command: typeof harnessCommand): void }): void {\n\taddCommand(harnessCommand)\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,MAAa,kBAAsC;CAClD;EAAE,MAAM;EAAe,QAAQ;EAAW,iBAAiB;CAAiB;CAC5E;EAAE,MAAM;EAAU,QAAQ;CAAU;CACpC;EAAE,MAAM;EAAS,QAAQ;CAAS;CAClC;EAAE,MAAM;EAAe,QAAQ;CAAiB;CAChD;EAAE,MAAM;EAAc,QAAQ;EAAW,iBAAiB;CAAiB;CAC3E;EAAE,MAAM;EAAiB,QAAQ;CAAS;CAC1C;EAAE,MAAM;EAAY,QAAQ;EAAa,iBAAiB;EAAoB,YAAY;CAAgB;AAC3G;;AAGA,MAAM,mBAA2C,CAAC,eAAe,QAAQ;AAEzE,SAAS,gBAAgB,MAAuB;CAC/C,IAAI;EACH,OAAO,UAAU,IAAI,CAAC,CAAC,YAAY;CACpC,QAAQ;EACP,OAAO;CACR;AACD;;AAGA,SAAgB,gBAAgB,MAAc,WAA8C;CAC3F,MAAM,0BAAU,IAAI,IAAiB,CAAC,GAAG,kBAAkB,GAAG,SAAS,CAAC;CACxE,OAAO,gBAAgB,QAAQ,YAAY,QAAQ,IAAI,QAAQ,IAAI,KAAK,gBAAgB,KAAK,MAAM,QAAQ,MAAM,CAAC,CAAC;AACpH;;;ACvDA,SAAS,WAAW,iBAAmC;CACtD,OAAO,YAAY,iBAAiB,EAAE,eAAe,KAAK,CAAC,CAAC,CAC1D,QAAQ,UAAU,MAAM,YAAY,CAAC,CAAC,CACtC,KAAK,UAAU,MAAM,IAAI,CAAC,CAC1B,KAAK;AACR;AAEA,SAAS,aAAa,MAAuB;CAC5C,IAAI;EACH,UAAU,IAAI;EACd,OAAO;CACR,QAAQ;EACP,OAAO;CACR;AACD;AAEA,SAAS,cAAc,QAAgB,QAAyB;CAC/D,OAAO,UAAU,MAAM,CAAC,CAAC,eAAe,KAAK,aAAa,MAAM,MAAM,SAAS,QAAQ,MAAM,GAAG,MAAM;AACvG;;AAGA,SAAgB,YAAY,iBAAiC;CAC5D,UAAU,iBAAiB,EAAE,WAAW,KAAK,CAAC;CAC9C,OAAO,WAAW,eAAe,CAAC,CAAC;AACpC;;;;;AAcA,SAAgB,cAAc,EAAE,MAAM,iBAAiB,WAAW,MAAM,SAA8C;CACrH,MAAM,cAAc,UAClB,QAAQ,YAAY,QAAQ,eAAe,CAAC,CAC5C,KAAK,aAAa;EAClB;EACA,QAAQ,KAAK,MAAM,QAAQ,eAAyB;CACrD,EAAE;CAEH,MAAM,YAAY,YAChB,QAAQ,EAAE,aAAa,aAAa,MAAM,KAAK,CAAC,cAAc,QAAQ,eAAe,CAAC,CAAC,CACvF,KAAK,EAAE,aAAa,MAAM;CAE5B,IAAI,UAAU,UAAU,CAAC,OACxB,MAAM,IAAI,MACT,gDAAgD,UAAU,KAAK,WAAW,KAAK,QAAQ,CAAC,CAAC,KAAK,IAAI,GACnG;CAGD,KAAK,MAAM,EAAE,YAAY,aAAa;EACrC,UAAU,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,IAAI,aAAa,MAAM,KAAK,cAAc,QAAQ,eAAe,GAAG;EACpE,IAAI,aAAa,MAAM,GAAG,OAAO,QAAQ;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EACzE,IAAI,MAAM;GACT,OAAO,iBAAiB,QAAQ,EAAE,WAAW,KAAK,CAAC;GACnD;EACD;EACA,IAAI;GACH,YAAY,SAAS,QAAQ,MAAM,GAAG,eAAe,GAAG,QAAQ,UAAU;EAC3E,SAAS,OAAO;GACf,IAAI,aAAa,MAAM,GAAG,MAAM;GAChC,OAAO,iBAAiB,QAAQ,EAAE,WAAW,KAAK,CAAC;EACpD;CACD;CAEA,OAAO,YAAY,KAAK,EAAE,cAAc,QAAQ,IAAI;AACrD;;;ACxDA,SAAgB,oBAAoB,EACnC,MACA,WAAW,YAAY,CAAC,GACxB,OAAO,OACP,QAAQ,SAC+B;CACvC,MAAM,kBAAkB,KAAK,MAAM,WAAW,QAAQ;CACtD,MAAM,SAAS,YAAY,eAAe;CAC1C,MAAM,YAAY,gBAAgB,MAAM,SAAS;CACjD,MAAM,SAAS,cAAc;EAAE;EAAM;EAAiB;EAAW;EAAM;CAAM,CAAC;CAE9E,OAAO;EACN;EACA,WAAW,UAAU,KAAK,YAAY,QAAQ,IAAI;EAClD,QAAQ,UAAU,QAAQ,YAAY,CAAC,QAAQ,eAAe,CAAC,CAAC,KAAK,YAAY,QAAQ,IAAI;EAC7F;EACA,YAAY,UACV,QAAQ,YAAY,QAAQ,UAAU,CAAC,CACvC,KAAK,aAAa;GAAE,MAAM,QAAQ;GAAM,YAAY,QAAQ;EAA0B,EAAE;EAC1F;EACA,QAAQ;CACT;AACD;;;ACrCA,SAAS,MAAM,OAAe,QAA2C;CACxE,QAAQ,OAAO,MAAM,GAAG,WAAW,SAAS,KAAK,UAAU,KAAK,IAAI,OAAO,KAAK,EAAE,GAAG;AACtF;AAEA,MAAa,cAA2B,QAAQ;CAC/C,MAAM;CACN,aAAa;CACb,SAAS;EACR,MAAM;GACL,aAAa;GACb,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;EAC5B;EACA,SAAS;GACR,aAAa;GACb,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;EAC5B;EACA,MAAM;GACL,aAAa;GACb,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC7B;EACA,OAAO;GACN,aAAa;GACb,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC7B;EACA,QAAQ;GACP,aAAa;GACb,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC;GAC3B,SAAS;EACV;CACD;CACA,IAAI,MAAM;EACT,IAAI;GACH,MAAM,SAAS,KAAK;GACpB,IAAI,WAAW,UAAU,WAAW,QAAQ,MAAM,IAAI,MAAM,gCAAgC;GAC5F,MAAM,YAAY,KAAK,SACpB,MAAM,GAAG,CAAC,CACX,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO;GAChB,MAAM,cAAc,WAAW,QAAQ,SAAS,CAAC,gBAAgB,MAAM,YAAY,QAAQ,SAAS,IAAI,CAAC;GACzG,IAAI,aAAa,QAChB,MAAM,IAAI,MACT,wBAAwB,YAAY,KAAK,IAAI,EAAE,eAAe,gBAC5D,KAAK,YAAY,QAAQ,IAAI,CAAC,CAC9B,KAAK,IAAI,EAAE,EACd;GACD,MACC,oBAAoB;IACnB,MAAM,KAAK,QAAQ,QAAQ,IAAI;IAC/B,GAAI,WAAW,SAAS,EAAE,WAAW,UAA2B,IAAI,CAAC;IACrE,GAAI,KAAK,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK;IACrD,GAAI,KAAK,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM;GACzD,CAAC,GACD,MACD;EACD,SAAS,OAAO;GACf,QAAQ,OAAO,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,iCAAiC,GAAG;GAC5G,QAAQ,WAAW;EACpB;CACD;AACD,CAAC;AAED,MAAa,iBAA8B,QAAQ;CAClD,MAAM;CACN,aAAa;CACb,UAAU,CAAC,WAAW;AACvB,CAAC;AAED,SAAgB,SAAS,EAAE,cAA0E;CACpG,WAAW,cAAc;AAC1B"}
|