buddy-agent-harness 0.16.0 → 0.17.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "author": {
6
6
  "name": "repobuddy"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "author": {
6
6
  "name": "repobuddy"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "author": {
6
6
  "name": "repobuddy"
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`init`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`init`,...process.argv.slice(2)]);export{};
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`mcp`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`mcp`,...process.argv.slice(2)]);export{};
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`reference`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`reference`,...process.argv.slice(2)]);export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "keywords": [
6
6
  "repobuddy",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "buddy-agent-harness",
4
- "version": "0.16.0",
4
+ "version": "0.17.0",
5
5
  "description": "Initialize standards-based agent configuration across agent harnesses",
6
6
  "author": {
7
7
  "name": "repobuddy"
@@ -17,7 +17,7 @@ Diagnose it:
17
17
  node scripts/doctor.mjs
18
18
  ```
19
19
 
20
- That path is relative to this skill's own directory: `scripts/doctor.mjs` is a self-contained bundle built from this package's source and shipped inside the skill folder through the npm package, so it runs against the current working directory with nothing downloaded and no `node_modules` needed. Fall back to `npx -y buddy-agent-harness@^0.16.0 doctor` when `scripts/doctor.mjs` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle.
20
+ That path is relative to this skill's own directory: `scripts/doctor.mjs` is a self-contained bundle built from this package's source and shipped inside the skill folder through the npm package, so it runs against the current working directory with nothing downloaded and no `node_modules` needed. Fall back to `npx -y buddy-agent-harness@^0.17.0 doctor` when `scripts/doctor.mjs` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle.
21
21
 
22
22
  The command is read-only. It never repairs anything, so it is safe to run at any point, including from a session-start hook.
23
23
 
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
@@ -12,7 +12,7 @@ The split matters: initialization has to run everywhere and invent nothing, so i
12
12
 
13
13
  Every addition is **offered, never written on sight**. An addition asserts something about how the repository is worked in — it stays true whether or not this tool ever ran — so it is material under the rule in `../init/references/agents-md.md`, and material content needs approval. Nothing here goes inside the `buddy-agent-harness` managed region; that region is for the tool's own bookkeeping.
14
14
 
15
- Two additions ship today: `references/delegation.md` and `references/list-identifiers.md`. Classify, offer, and report each one on its own; the owner's answer to one decides nothing about the other. Each addition's reference file carries the text to offer, a `## Covered when` criterion for the subject already being present, a `## Stale when` section naming the history file its stale check reads and what else that check needs for this addition, and a `## Where it belongs` recommendation naming which instruction file the text should go in. The retired wordings themselves are kept beside it — `references/<addition>.history.md` — and are what a present section is compared against.
15
+ Three additions ship today: `references/delegation.md`, `references/list-identifiers.md`, and `references/session-scope.md`. Classify, offer, and report each one on its own; the owner's answer to one decides nothing about the others. Each addition's reference file carries the text to offer, a `## Covered when` criterion for the subject already being present, a `## Stale when` section naming the history file its stale check reads and what else that check needs for this addition, and a `## Where it belongs` recommendation naming which instruction file the text should go in. The retired wordings themselves are kept beside it — `references/<addition>.history.md` — and are what a present section is compared against.
16
16
 
17
17
  ## 1. Find the instruction file
18
18
 
@@ -97,7 +97,7 @@ A section from a retired wording in a file that is **not** the root `AGENTS.md`
97
97
 
98
98
  Where an addition is **absent**, show its text **verbatim** — the whole thing, not a summary — say where it would go, and ask.
99
99
 
100
- **Where it goes is part of the offer, and the addition decides it.** Read its `## Where it belongs` and lead with what that section recommends. An addition whose subject is the repository belongs in the repository's `AGENTS.md`; one whose subject is how the agent works belongs in the owner's own global instruction file, because it holds in every repository they open and a copy per repository is a copy per repository to keep in step. Delegation and list identifiers are both the second kind, and their references recommend the global file.
100
+ **Where it goes is part of the offer, and the addition decides it.** Read its `## Where it belongs` and lead with what that section recommends. An addition whose subject is the repository belongs in the repository's `AGENTS.md`; one whose subject is how the agent works belongs in the owner's own global instruction file, because it holds in every repository they open and a copy per repository is a copy per repository to keep in step. Delegation, list identifiers, and session scope are all the second kind, and their references recommend the global file.
101
101
 
102
102
  The global file is `~/.agents/AGENTS.md`, the user-scope counterpart of the root `AGENTS.md`. Say that the harness in use reads it only where a user-scope instruction file of its own loads it. Where the harness documents that file, name it: on Claude Code it is `~/.claude/CLAUDE.md`.
103
103
 
@@ -121,7 +121,7 @@ Where you **cannot tell**, say that first, in those words. Then show three texts
121
121
 
122
122
  Put the third every time the repository can actually do it, because the owner may not remember either, and the question they care about is not where the section came from but **which wording serves them better**. Provenance is a stand-in for that; when the stand-in fails, ask the real question.
123
123
 
124
- Measurement means running each wording against a set of real tasks and comparing how the agent behaves — whatever harness this repository has for that addition's wording. In this package's own repository `eval-delegation` scores Delegation's and `eval-list-identifiers` scores List identifiers'; a consumer repository will have its own or none. **Look before you offer it.** Where the repository has no harness for that addition, say the third answer would need one it does not have, and put the other two.
124
+ Measurement means running each wording against a set of real tasks and comparing how the agent behaves — whatever harness this repository has for that addition's wording. In this package's own repository `eval-delegation` scores Delegation's and `eval-list-identifiers` scores List identifiers'; Session scope has none, and a consumer repository will have its own or none. **Look before you offer it.** Where the repository has no harness for that addition, say the third answer would need one it does not have, and put the other two.
125
125
 
126
126
  **Ask before running it, and never run it unasked** — it is many model runs, and the owner is the one paying for them.
127
127
 
@@ -0,0 +1,9 @@
1
+ # Superseded wordings: Session scope
2
+
3
+ Every wording this addition has retired, newest first, each exactly as it was offered. Read this
4
+ file when a `## Session scope` section is already present and reads as covered; it is what the
5
+ section in front of you is compared against.
6
+
7
+ Nothing here is ever offered. The text to offer is in `session-scope.md`.
8
+
9
+ None yet. This addition still offers its first wording.
@@ -0,0 +1,75 @@
1
+ # Addition: Session scope
2
+
3
+ ## Covered when
4
+
5
+ The merged view already tells the agent **when the work in front of it belongs in a new session rather
6
+ than this one**. Any of these counts:
7
+
8
+ - guidance on what belongs in one session and what starts a new one, under any heading
9
+ - a rule about switching subjects mid-session, paired with any sense of what to carry over
10
+ - an explicit statement that this repository works in one long session
11
+
12
+ Naming a session command — compacting, clearing, resuming — is not coverage, and neither is a rule to
13
+ compact at some interval, which says nothing about when the subject has changed. Neither is a rule
14
+ that appears only in a skill body — this addition is for the always-loaded file.
15
+
16
+ ## Stale when
17
+
18
+ Compare the `## Session scope` section already in the file against `session-scope.history.md` by the
19
+ procedure in step 3 of the skill, with the text below as the current text.
20
+
21
+ `session-scope.history.md` holds no retired wording yet. Until it does, a section that is not already
22
+ current is compared against the text below, standing in for a retired wording: both answers yes is
23
+ this text edited, and gets the replacement offer; both no is the owner's own; a disagreement you
24
+ cannot tell. The paragraphs a replacement would remove are the ones the text below does not contain.
25
+
26
+ A section about planning — phases, a todo list, breaking work into subtasks — is not thereby this
27
+ wording. How to sequence work is a different subject from what one session holds, and anyone can
28
+ write about the first without having seen this text.
29
+
30
+ Where you cannot tell, the third answer needs a harness that scores this addition's wording, and none
31
+ does: `eval-delegation` scores Delegation's and `eval-list-identifiers` scores List identifiers', and
32
+ neither covers this one. Put two answers, not three, and say settling it by measurement would need a
33
+ harness that does not exist yet.
34
+
35
+ ## Offer this text verbatim
36
+
37
+ ```markdown
38
+ ## Session scope
39
+
40
+ Keep one session to one subject. Deciding how something should work and building it are two subjects: finish the first, write down what it settled, and start the second in a new session from what you wrote.
41
+
42
+ When the user turns to unrelated work, do not carry the old subject into it. Name what you were working on, offer to continue it in its own session, and start this one from what the user just asked.
43
+
44
+ A session that has run long on mixed work carries context you can no longer account for, and every later answer is drawn from it. Stop adding to it: record where the work stands and continue in a new session.
45
+ ```
46
+
47
+ ## Where it belongs
48
+
49
+ **Recommend the owner's own global instruction file, not this repository's `AGENTS.md`.**
50
+
51
+ The text says how to scope a session, which holds in every repository the owner opens, so the global
52
+ file, `~/.agents/AGENTS.md`, is its one home.
53
+
54
+ Offer this repository's `AGENTS.md` as the alternative, and name what each one buys:
55
+
56
+ | Destination | Reaches | Costs |
57
+ | --- | --- | --- |
58
+ | the owner's global file | every repository they open, themselves only | one copy, and nothing to keep in step |
59
+ | this repository's `AGENTS.md` | everyone who clones it, and any agent CI runs | a copy per repository, and a second copy in context for anyone who already has it globally |
60
+
61
+ A global placement is **handed over, not made**: give the text and the path, say it goes at the end
62
+ of that file, and stop there.
63
+
64
+ ## Do not edit it
65
+
66
+ The wording is fixed. In particular, do not name a harness's session commands, and do not name a file
67
+ or a directory the record should go in — the section has to hold in whatever harness loads it and
68
+ whatever repository it is read in, and naming either breaks that. Offer it as written or not at all.
69
+
70
+ If the text is ever revised, the same change must do two things:
71
+
72
+ - append the outgoing wording to `session-scope.history.md`, newest first, exactly as it was offered;
73
+ - add scenarios for the **shape of that revision** and pass the gate against the new pair.
74
+
75
+ Where the owner picks this repository over their global file, place it at the end of their prose, outside the `buddy-agent-harness` managed region.
@@ -84,7 +84,7 @@ A `CLAUDE.local.md` is its own case. It is personal, usually gitignored, and it
84
84
  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.
85
85
  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`.
86
86
  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 — except a `CLAUDE.md`, which is removed or left holding the import, because a copy under that name is read instead of `AGENTS.md`. Everything declined, and everything with no candidate, stays exactly where it is.
87
- 5. Run `node scripts/init.mjs` at the repository root, resolving that path against this skill's own directory: `scripts/init.mjs` is a self-contained bundle shipped inside this skill folder through the npm package, so it runs with nothing downloaded and no `node_modules` needed. Fall back to `npx -y buddy-agent-harness@^0.16.0 init` when `scripts/init.mjs` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle. 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`.
87
+ 5. Run `node scripts/init.mjs` at the repository root, resolving that path against this skill's own directory: `scripts/init.mjs` is a self-contained bundle shipped inside this skill folder through the npm package, so it runs with nothing downloaded and no `node_modules` needed. Fall back to `npx -y buddy-agent-harness@^0.17.0 init` when `scripts/init.mjs` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle. 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`.
88
88
  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.
89
89
  7. Apply the instruction work `init` does not do. Gemini CLI needs `AGENTS.md` added to `context.fileName` in `.gemini/settings.json` — the only bridge left, detailed in `references/harnesses/gemini-cli.md`, and only if you enabled it. Then remove each superseded `CLAUDE.md` the user approved, in every directory it was found in, and no others. Write no `CLAUDE.md` anywhere, at the root or beside a nested `AGENTS.md`: Claude Code reads both by itself, and a stub written beside one is the very file that would suppress it if anyone later put content in it.
90
90
  8. If a skills 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.
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`init`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`init`,...process.argv.slice(2)]);export{};
@@ -30,7 +30,7 @@ node <this skill's folder>/scripts/reference.mjs <subcommand> ... --root <reposi
30
30
 
31
31
  `scripts/reference.mjs` is the package's `reference` command bundled into one file; it needs no `node_modules`. Pass `--root` every time, so the repository's own `.agents/references/` is read.
32
32
 
33
- Outside Load, when it is missing or cannot run, use `npx -y buddy-agent-harness@^0.16.0 reference` with the same arguments. Load never falls back to a package runner.
33
+ Outside Load, when it is missing or cannot run, use `npx -y buddy-agent-harness@^0.17.0 reference` with the same arguments. Load never falls back to a package runner.
34
34
 
35
35
  ## Load
36
36
 
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`reference`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`reference`,...process.argv.slice(2)]);export{};
@@ -17,7 +17,7 @@ Each of the other three refuses this work deliberately. `init` has to run anywhe
17
17
  node scripts/doctor.mjs
18
18
  ```
19
19
 
20
- That path is relative to this skill's own directory: `scripts/doctor.mjs` is a self-contained bundle shipped inside this skill folder through the npm package, so it runs against the current working directory with nothing downloaded and no `node_modules` needed. Fall back to `npx -y buddy-agent-harness@^0.16.0 doctor` when `scripts/doctor.mjs` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle.
20
+ That path is relative to this skill's own directory: `scripts/doctor.mjs` is a self-contained bundle shipped inside this skill folder through the npm package, so it runs against the current working directory with nothing downloaded and no `node_modules` needed. Fall back to `npx -y buddy-agent-harness@^0.17.0 doctor` when `scripts/doctor.mjs` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle.
21
21
 
22
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
23
 
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`doctor`,...process.argv.slice(2)]);export{};
@@ -482,4 +482,4 @@ Write the guidance here. Give each topic its own top-level \`##\` section.
482
482
  `,i=Object.entries(n).map(([e,t])=>`${tomlKey(e)} = ${renderTomlInline(t)}\n`);return`${e}${r}\n[${t.map(tomlKey).join(`.`)}]\n${i.join(``)}`}function lineEndAt(e,t){let n=e.indexOf(`
483
483
  `,t);return n===-1?e.length:n}function setTomlKey(e,t,n,r){let i;try{i=parseTOML(e)}catch{return{kind:`refused`,reason:`unreadable`}}let a=i.body[0].body.filter(e=>e.type===`TOMLTable`),o=a.find(e=>e.kind===`standard`&&e.resolvedKey.length===t.length&&startsWith(e.resolvedKey,t));if(!o)return{kind:`refused`,reason:`absent`};let s=o.body;if(a.some(e=>startsWith(e.resolvedKey,[...t,n]))||s.some(e=>keyNames(e.key)[0]===n&&e.key.keys.length>1))return{kind:`refused`,reason:`shape`};let c=s.find(e=>e.key.keys.length===1&&keyNames(e.key)[0]===n);if(c&&r!==void 0)return{kind:`edited`,text:`${e.slice(0,c.value.range[0])}${renderTomlInline(r)}${e.slice(c.value.range[1])}`};if(c){let t=e.lastIndexOf(`
484
484
  `,c.range[0]-1)+1,n=lineEndAt(e,c.range[1]);return{kind:`edited`,text:`${e.slice(0,t)}${e.slice(Math.min(n+1,e.length))}`}}if(r===void 0)return{kind:`edited`,text:e};let l=lineEndAt(e,s.at(-1)?.range[1]??o.range[0]);return{kind:`edited`,text:`${e.slice(0,l)}\n${tomlKey(n)} = ${renderTomlInline(r)}${e.slice(l)}`}}function read$1(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}__name(read$1,`read`);const Oc={both:`both sides changed since the last projection — a three-way conflict for a person to settle, never imported`,unknown:`no baseline says which side changed — compare the two by hand; never imported`};function inferredTransport(e){return e.url===void 0?`stdio`:`http`}function mapValue(e,t){let n=Object.entries(e).flatMap(([e])=>{let n=isRecord$1(t)?t[e]:void 0;return typeof n==`string`?[[e,n]]:[]});return n.length?Object.fromEntries(n):void 0}function reconcileMcp({root:e,git:t,accept:n}){let r=read$1(e,uo),i=parseGoldenSet(r);if(i.kind!==`servers`)return i;let a=parseProjectionRecord(read$1(e,fo)),o=new McpBaseline({git:t,record:a}),s=[],c=[];for(let t of mcpTargets(e)){let n=parseTarget(t,read$1(e,t.path));if(n.kind===`unreadable`){s.push({path:t.path,action:`refuse`,detail:`the target does not parse`});continue}if(n.kind===`absent`)continue;let r=no[t.dialect];for(let[e,a]of n.servers){let n=i.servers.get(e),at=n=>locatorText({file:t.path,server:e,field:n}),offer=(i,o)=>{let l=literalsIn(i,o);for(let e of l)s.push({path:at(e),action:`refuse`,detail:`${e} holds a literal credential, which never enters the golden set — set it in the environment and reference it as \${VAR}`});let u=withoutLiterals(i,o,l,n);if(l.length&&(u===void 0||sameField(i,{[i]:u},n)))return;let d=r.defaults?.[i];if(d!==void 0&&sameField(i,{[i]:u},{[i]:d})){s.push({path:at(i),action:`skip`,detail:`${t.dialect} fills this in by default, so it may not be the user's — set it in the golden set by hand if it is`});return}let p={path:at(i),action:`import`,detail:n?`only the harness changed it — approve to take it into the golden set`:`the golden set does not declare this server — approve each field to add it`,value:u===void 0?`(unset)`:`${i} = ${renderTomlInline(u)}`};s.push(p),c.push({row:p,config:t,server:e,field:i,value:u,carried:a,adds:!n})};if(!n){for(let e of qa)r.fields.has(e)&&a[e]!==void 0&&(e!==`transport`||a.transport!==inferredTransport(a))&&offer(e,a[e]);continue}for(let i of divergingFields(n,a,r.fields)){let r=o.directionOf(t,e,i,n,a);r!==`golden`&&(r===`target`?offer(i,i===`env`||i===`headers`?mapValue(n[i],a[i]):a[i]):s.push({path:at(i),action:`skip`,detail:Oc[r]}))}}}if(!n.length)return{kind:`planned`,rows:s,written:!1};let l=approve(c,s,n);if(l.kind===`rejected`)return l;let u=r,d=[];for(let[e,t]of groupByServer(l.offers)){if(t[0]?.adds){let n=Object.fromEntries(qa.flatMap(e=>{let n=t.find(t=>t.field===e);return n?[[e,n.value]]:[]}));u=land(u,appendInlineTomlTable(u,[`servers`,e],n),e,n,t,d);continue}for(let n of t){let t={...parseGoldenSet(u).servers.get(e),[n.field]:n.value};n.value===void 0&&delete t[n.field];let r=setTomlKey(u,[`servers`,e],n.field,n.value);u=land(u,r.kind===`edited`?r.text:void 0,e,t,[n],d)}}return u!==r&&(m(v(e,uo),u),writeRecord$1(e,a,o,d,parseGoldenSet(u))),{kind:`planned`,rows:s,written:!0}}function literalsIn(e,t){return t===void 0?[]:e===`args`?nonSecretArgs(t).length===t.length?[]:[`args`]:e!==`env`&&e!==`headers`&&e!==`url`?[]:credentialFields({[e]:t})}function withoutLiterals(e,t,n,r){if(!n.length)return t;if(e!==`env`&&e!==`headers`)return;let i={...t};for(let t of n){let n=t.slice(e.length+1),a=r?.[e]?.[n];a===void 0?delete i[n]:i[n]=a}return Object.keys(i).length?i:void 0}function approve(e,t,n){let r=[],i=[];for(let a of new Set(n)){let n=e.find(e=>e.row.path===a);if(n)i.push(n);else{let e=t.find(e=>e.path===a);r.push(e?`${a} is ${e.action}, not importable: ${e.detail}`:`${a} names no importable field`)}}for(let[e,t]of groupByServer(i)){for(let n of new Set(t.map(e=>e.field))){let i=t.filter(e=>e.field===n);i.some(e=>!sameField(n,{[n]:e.value},{[n]:i[0]?.value}))&&r.push(`servers.${e}.${n} is approved from more than one target with different values — approve one`)}t[0]?.adds&&!t.some(e=>e.field===`command`||e.field===`url`)&&r.push(`servers.${e} would be added with nothing to run — approve its command or url too`)}return r.length?{kind:`rejected`,reasons:r}:{kind:`approved`,offers:i}}function groupByServer(e){let t=new Map;for(let n of e)t.set(n.server,[...t.get(n.server)??[],n]);return t}function land(e,t,n,r,i,a){let o=parseGoldenSet(e),s=t===void 0?void 0:parseGoldenSet(t),c=s?.kind===`servers`&&qa.every(e=>e===`transport`&&r.transport===void 0||sameField(e,s.servers.get(n),r))&&[...o.servers].every(([e,t])=>e===n||JSON.stringify(s.servers.get(e))===JSON.stringify(t));for(let e of i)e.row.action=c?`imported`:`edit`,e.row.detail=c?`written into the golden set`:`the golden set writes this server in a shape that cannot be edited byte-safely — apply the value by hand`;return c?(a.push(...i),t):e}function writeRecord$1(e,t,n,r,i){for(let e of r){let r=t.get(e.config.path)??new Map,a=r.get(e.server),o=a?{...a,[e.field]:e.value}:baseEntry(e,n,i.servers.get(e.server));o&&(e.value===void 0&&delete o[e.field],r.set(e.server,o),t.set(e.config.path,r))}let o=v(e,fo);a(g(o),{recursive:!0}),m(o,formatProjectionRecord(t))}__name(writeRecord$1,`writeRecord`);function baseEntry(e,t,n){if(e.adds)return{...n};let r={};for(let i of no[e.config.dialect].fields){if(n[i]===void 0)continue;if(sameField(i,n,e.carried)){r[i]=n[i];continue}let a=t.baseFor(e.config,e.server,i);if(!a)return;r[i]=a[i]}return r}const kc=command({name:`reconcile`,description:`Import a harness-side MCP change into the golden set, one approved field at a time. A dry run unless --accept names a field.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},accept:{description:`Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.`,type:q(In(K()))},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.accept??[],i=reconcileMcp({root:n,git:new GitBridgeState(n),accept:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`rejected`?(process.stderr.write(`error: nothing written.\n${i.reasons.map(e=>`- ${e}\n`).join(``)}`),O.error):i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to reconcile into`,fields:`0 fields`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:i.written?`written — only the fields named by --accept`:`dry run — nothing written; re-run with --accept <path> for each approved field`,fields:i.rows.length?i.rows:`0 fields — no harness holds a change the golden set lacks`,record:i.written?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP reconcile failed.`}\n`),O.error}}});function indentUnit(e){let t=/\n([ \t]+)\S/.exec(e);return t?t[1]:` `}function jsonAt(e,t,n){return JSON.stringify(e,null,t).replaceAll(`
485
- `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.16.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`mcp`,...process.argv.slice(2)]);export{};
485
+ `,`\n${t.repeat(n)}`)}function objectAt(e,t){let n=e&&(t.length?zi(e,t):e);return n?.type===`object`?n:void 0}function insertJsonProperty(e,t,n,r){let i=objectAt(Ri(e),t);if(!i||zi(i,[n]))return;let a=t.length+1,o=indentUnit(e),s=`${o.repeat(a)}${JSON.stringify(n)}: ${jsonAt(r,o,a)}`,c=i.children?.at(-1);if(c){let t=c.offset+c.length;return`${e.slice(0,t)},\n${s}${e.slice(t)}`}let l=i.offset+1,u=i.offset+i.length-1;return`${e.slice(0,l)}\n${s}\n${o.repeat(a-1)}${e.slice(u)}`}function replaceJsonValue(e,t,n){let r=Ri(e),i=r&&zi(r,t);if(!i)return{kind:`refused`,reason:`absent`};let a=i.offset+i.length,o=e.slice(i.offset,a);return stripJsonComments(o)===o?{kind:`edited`,text:`${e.slice(0,i.offset)}${jsonAt(n,indentUnit(e),t.length)}${e.slice(a)}`}:{kind:`refused`,reason:`comment`}}function createFile(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):`${JSON.stringify({[e.key]:{[t]:n}},null,2)}\n`}function appendServer(e,t,n,r){return e.format===`toml`?appendTomlTable(t,[e.key,n],r):insertJsonProperty(t,[e.key],n,r)??insertJsonProperty(t,[],e.key,{[n]:r})}function replaceServer(e,t,n,r){let i=e.format===`toml`?replaceTomlTable(t,[e.key,n],r):replaceJsonValue(t,[e.key,n],r);return i.kind===`edited`?i.text:void 0}function renderEntry(e,t,n){return e.format===`toml`?renderTomlTable([e.key,t],n):JSON.stringify({[t]:n},null,2)}function read(e,t){let n=v(e,t);return r(n)?o(n,`utf8`):void 0}const Ac={target:`the target changed since the last projection — reconcile it into the golden set first`,both:`both sides changed since the last projection — a three-way conflict for a person to settle`,unknown:`no baseline says which side changed — compare the two by hand`};function heldBack(e){return[`both`,`unknown`,`target`].find(t=>e.includes(t))}function projectMcp({root:e,git:t,write:n}){let r=parseGoldenSet(read(e,uo));if(r.kind!==`servers`)return r;let i=parseProjectionRecord(read(e,fo)),a=new McpBaseline({git:t,record:i}),o=[],s=[],c=[];for(let t of mcpTargets(e)){let n=read(e,t.path),i=parseTarget(t,n);if(i.kind===`unreadable`){o.push({target:t.path,server:``,action:`refuse`,detail:`the target does not parse`});continue}let l={config:t,source:n,created:n===void 0,servers:i.kind===`servers`?i.servers:new Map,agreed:new Map};c.push(l);let u=no[t.dialect];for(let[e,n]of r.servers){let row=(n,r)=>o.push({target:t.path,server:e,action:n,detail:r}),r=credentialFields(n)[0];if(r){row(`refuse`,`the golden set holds a literal credential at ${r} — reference it as \${VAR} first`);continue}let i=u.write(n);if(i.kind===`refused`){row(`refuse`,`${i.field}: ${i.reason}`);continue}let c=l.servers.get(e);if(!c){let r=l.source===void 0?createFile(t,e,i.entry):appendServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,r,l.created?`creates the file`:`appends the server`,row,s);continue}let d=divergingFields(n,c,u.fields);if(!d.length){l.agreed.set(e,n);continue}let p=heldBack(d.map(r=>a.directionOf(t,e,r,n,c)));if(p){row(`skip`,Ac[p]);continue}let m=replaceServer(t,l.source,e,i.entry);apply(l,e,n,i.entry,m,`replaces ${d.join(`, `)}`,row,s)}}if(n){for(let t of c)t.source!==void 0&&t.source!==read(e,t.config.path)&&writeTarget(e,t);writeRecord(e,i,c)}return{kind:`planned`,rows:o,entries:s,written:n}}function apply(e,t,n,r,i,a,o,s){let c=!e.servers.has(t),l=i!==void 0&&readsBack(e,t,n,i),u=l?c?`add`:`update`:`edit`;if(s.push({target:e.config.path,server:t,action:u,entry:renderEntry(e.config,t,r)}),!l){o(`edit`,c?`the file has no safe place to add it — apply the entry by hand`:`an in-place change here is applied by hand`);return}e.source=i,e.servers=parseTarget(e.config,i).servers,e.agreed.set(t,n),o(u,a)}function readsBack(e,t,n,r){let i=parseTarget(e.config,r);if(i.kind!==`servers`)return!1;let a=i.servers.get(t),o=no[e.config.dialect].fields;return!a||divergingFields(n,a,o).length?!1:[...e.servers].every(([e,n])=>{if(e===t)return!0;let r=i.servers.get(e);return r!==void 0&&JSON.stringify(r)===JSON.stringify(n)})}function writeTarget(e,t){let n=v(e,t.config.path);a(g(n),{recursive:!0}),m(n,t.source)}function writeRecord(e,t,n){for(let e of n){let n=t.get(e.config.path)??new Map;for(let[t,r]of e.agreed)n.set(t,r);n.size&&t.set(e.config.path,n)}let r=v(e,fo);a(g(r),{recursive:!0}),m(r,formatProjectionRecord(t))}const jc={name:`buddy-agent-harness`,description:`Initialize agent harness skill compatibility in consumer repositories.`,commands:[cc,Ys,command({name:`mcp`,description:`Work with the golden MCP server set at .agents/buddy-agent-harness/mcp.toml.`,commands:[command({name:`project`,description:`Project the golden MCP server set into each enabled harness. A dry run unless --write; never overwrites a change made on the harness side.`,options:{root:{description:`Repository or package directory. Defaults to the current directory.`,type:q(K())},write:{description:`Apply the plan and record what was projected. Without it nothing on disk changes.`,type:q(Fn())},format:{description:`Output format: toon (default), json, or text for a human-readable report.`,type:q(K()),default:`toon`}},run(e){try{let t=parseFormat(e.format),n=e.root??process.cwd(),r=e.write===!0,i=projectMcp({root:n,git:new GitBridgeState(n),write:r});if(i.kind===`unreadable`){let e=i.position?` at line ${i.position.line}, column ${i.position.column}`:``;return process.stderr.write(`error: ${uo} does not parse${e}.\n`),O.error}return i.kind===`absent`?(writeResult({golden:`0 servers — no golden set at ${uo}`,mode:`nothing to project`,actions:`0 changes`,record:`not written`},t),O.success):(writeResult({golden:uo,mode:r?`written`:`dry run — nothing written; re-run with --write to apply`,actions:i.rows.length?i.rows:`0 changes — every enabled harness already carries the golden set`,...i.entries.length?{entries:i.entries}:{},record:r?fo:`not written — dry run`},t),O.success)}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`MCP projection failed.`}\n`),O.error}}}),kc]}),ha,sc,$s]};function app(){let[e,...t]=jc.commands;return t.reduce((e,t)=>e.command(t),cli({name:jc.name,version:`0.17.0`,description:jc.description}).command(e))}async function run(e){try{let t=await app().parse(e);return typeof t==`number`?t:O.success}catch(e){return process.stderr.write(`error: ${e instanceof Error?e.message:`Invalid command.`}\n`),O.usage}}process.exitCode=await run([...process.argv.slice(0,2),`mcp`,...process.argv.slice(2)]);export{};