arkgate 4.8.23 → 4.8.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +24 -77
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +596 -112
  6. package/bin/ark-mcp-runtime.mjs +412 -138
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +39 -0
  9. package/bin/ark.mjs +65 -97
  10. package/bin/lib/adapter-contract-types.mjs +5 -0
  11. package/bin/lib/adapter-contract.mjs +15 -0
  12. package/bin/lib/agent-projection-command.mjs +5 -1
  13. package/bin/lib/analysis-engine.mjs +15 -11
  14. package/bin/lib/architecture-scan.mjs +50 -6
  15. package/bin/lib/ark-order-doctor.mjs +1 -1
  16. package/bin/lib/ark-order-facts.mjs +410 -15
  17. package/bin/lib/ark-order-invariants.mjs +51 -9
  18. package/bin/lib/ark-order-sensors.mjs +28 -101
  19. package/bin/lib/ark-order-types.mjs +2 -0
  20. package/bin/lib/ark-run-doctor.mjs +14 -0
  21. package/bin/lib/ark-run-facts.mjs +434 -11
  22. package/bin/lib/ark-run-sensors.mjs +5 -3
  23. package/bin/lib/arkgate-pins.mjs +91 -0
  24. package/bin/lib/arkrules-sensors.mjs +50 -249
  25. package/bin/lib/check-args.mjs +23 -1
  26. package/bin/lib/ci-and-commands.mjs +16 -6
  27. package/bin/lib/class-source-scan.mjs +479 -0
  28. package/bin/lib/codex-home.mjs +4 -2
  29. package/bin/lib/config-contract-slices.mjs +701 -0
  30. package/bin/lib/config-contract.mjs +5 -493
  31. package/bin/lib/config-warnings.mjs +32 -0
  32. package/bin/lib/dashboard-cli.mjs +62 -0
  33. package/bin/lib/design-delta.mjs +1 -1
  34. package/bin/lib/diagnostic-catalog.mjs +16 -9
  35. package/bin/lib/doctor-advisories.mjs +25 -1
  36. package/bin/lib/doctor-next-actions.mjs +17 -2
  37. package/bin/lib/doctor-plan.mjs +6 -8
  38. package/bin/lib/effective-contract-load.mjs +121 -59
  39. package/bin/lib/enforcement-profiles.mjs +40 -4
  40. package/bin/lib/first-run-help.mjs +3 -2
  41. package/bin/lib/flat-parent-importers.mjs +137 -0
  42. package/bin/lib/flat-parent-pilot.mjs +40 -100
  43. package/bin/lib/gate-files.mjs +29 -2
  44. package/bin/lib/git-change-scope.mjs +109 -0
  45. package/bin/lib/github-enforcement.mjs +1 -1
  46. package/bin/lib/hook-templates.mjs +18 -16
  47. package/bin/lib/host-support-matrix.mjs +3 -1
  48. package/bin/lib/import-resolve.mjs +13 -1
  49. package/bin/lib/install-migrate.mjs +50 -73
  50. package/bin/lib/invariant-coverage.mjs +59 -11
  51. package/bin/lib/mcp-adoption.mjs +9 -1
  52. package/bin/lib/mcp-hook-payload.mjs +401 -61
  53. package/bin/lib/package-manager.mjs +59 -1
  54. package/bin/lib/passthrough-commands.mjs +83 -0
  55. package/bin/lib/persistence-write-hint.mjs +81 -0
  56. package/bin/lib/pin-dependent-gates.mjs +117 -0
  57. package/bin/lib/policy-delta-io.mjs +168 -10
  58. package/bin/lib/prepare-change.mjs +120 -6
  59. package/bin/lib/prepare-write.mjs +1 -0
  60. package/bin/lib/presets.mjs +3 -5
  61. package/bin/lib/remediation.mjs +23 -4
  62. package/bin/lib/resolved-candidate-facts.mjs +315 -55
  63. package/bin/lib/rules-inventory.mjs +22 -3
  64. package/bin/lib/rules-under-contract.mjs +179 -5
  65. package/bin/lib/scan-files.mjs +32 -0
  66. package/bin/lib/snippet-analysis.mjs +140 -6
  67. package/bin/lib/source-policy.mjs +34 -0
  68. package/bin/lib/stable-hash.mjs +90 -0
  69. package/bin/lib/start-install-recovery.mjs +71 -1
  70. package/bin/lib/start-preview.mjs +30 -0
  71. package/bin/lib/status-command.mjs +33 -4
  72. package/bin/lib/status-manifest.mjs +32 -4
  73. package/bin/lib/team-parliament-io.mjs +77 -75
  74. package/bin/lib/team-parliament.mjs +6 -1
  75. package/bin/lib/write-path-capabilities.mjs +4 -2
  76. package/dist/chunk-A5EIY3HI.cjs +5 -0
  77. package/dist/chunk-MBXCFLO4.js +16 -0
  78. package/dist/chunk-RNQHRDCQ.cjs +16 -0
  79. package/dist/chunk-SVSQVGFJ.js +5 -0
  80. package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-BJbaeoj4.d.cts} +18 -3
  81. package/dist/configTypes-BJbaeoj4.d.ts +246 -0
  82. package/dist/eslint/index.cjs +17 -4
  83. package/dist/eslint/index.d.cts +359 -0
  84. package/dist/eslint/index.d.ts +130 -156
  85. package/dist/eslint/index.js +10 -4
  86. package/dist/index.cjs +35 -34
  87. package/dist/index.d.cts +5475 -0
  88. package/dist/index.d.ts +3312 -299
  89. package/dist/index.js +35 -34
  90. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  91. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  92. package/dist/nestjs/index.cjs +1 -18
  93. package/dist/nestjs/index.d.cts +22 -0
  94. package/dist/nestjs/index.d.ts +1 -3
  95. package/dist/nestjs/index.js +1 -18
  96. package/dist/order/index.cjs +1 -1
  97. package/dist/order/index.d.cts +155 -0
  98. package/dist/order/index.d.ts +15 -3
  99. package/dist/order/index.js +1 -1
  100. package/dist/runtime/index.cjs +13 -30
  101. package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +804 -290
  102. package/dist/runtime/index.d.ts +2678 -108
  103. package/dist/runtime/index.js +13 -30
  104. package/dist/{types-Bdj32QwA.d.ts → types-DRinG-vT.d.cts} +576 -140
  105. package/dist/types-DRinG-vT.d.ts +1792 -0
  106. package/docs/README.md +2 -1
  107. package/docs/agent-guide.md +84 -15
  108. package/docs/ai-gates.md +160 -26
  109. package/docs/arkorder.md +32 -15
  110. package/docs/brownfield-adoption.md +5 -1
  111. package/docs/configuration.md +196 -26
  112. package/docs/develop.md +2 -1
  113. package/docs/diagnostics.md +96 -19
  114. package/docs/package-surface.md +31 -8
  115. package/docs/product-voice.md +1 -1
  116. package/package.json +59 -18
  117. package/schemas/ark.analysis-result.schema.json +15 -0
  118. package/schemas/ark.config.schema.json +47 -10
  119. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  120. package/schemas/ark.status-manifest.schema.json +22 -0
  121. package/server.json +3 -9
  122. package/templates/agent-skills/README.md +1 -1
  123. package/templates/agent-skills/ark-adopt/SKILL.md +4 -1
  124. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  125. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  126. package/templates/arkrules/PersistenceAdapters.json +1 -1
  127. package/templates/arkrules/PresentationAdapters.json +1 -1
  128. package/templates/skills/ark-adopt.md +4 -1
  129. package/templates/skills/ark-order.md +4 -2
  130. package/templates/skills/ark-runtime.md +6 -1
  131. package/dist/types-Ik_OakcI.d.ts +0 -183
@@ -1,22 +1,24 @@
1
1
  // Generated from hook-templates.source.mjs — run npm run generate:packaged-tooling.
2
- import{execCommandParts as l,execRunner as i}from"../ark-shared.mjs";const s="arkgate-mcp";function h(e){const r=i(e);return`${JSON.stringify({version:1,hooks:{preToolUse:[{command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env CURSOR_PROJECT_DIR --config ark.config.json`,matcher:"Write|StrReplace",failClosed:!0,timeout:30}]}},null,2)}
3
- `}function y(e){const r=i(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${r} ${s} --session-context --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
4
- `}function $(e){const r=i(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${s} --session-context --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"ApplyPatch|apply_patch|Write|Edit|MultiEdit",hooks:[{type:"command",timeout:30,command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
5
- `}function O(e){const{command:r,args:o}=l(e,s,["--root",".","--config","ark.config.json"]),t=a=>a.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),c=o.map(a=>`"${t(a)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
2
+ import{execCommandParts as d,execRunner as g}from"../ark-shared.mjs";const p="arkgate-mcp";function S(r){const t=g(r);return`${JSON.stringify({version:1,hooks:{preToolUse:[{command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env CURSOR_PROJECT_DIR --config ark.config.json`,matcher:"Write|StrReplace",failClosed:!0,timeout:30}]}},null,2)}
3
+ `}function R(r){const t=g(r);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${t} ${p} --session-context --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
4
+ `}function _(r){const t=g(r);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${t} ${p} --session-context --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"ApplyPatch|apply_patch|Write|Edit|MultiEdit",hooks:[{type:"command",timeout:30,command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
5
+ `}function C(r){const{command:t,args:o}=d(r,p,["--root",".","--config","ark.config.json"]),e=n=>n.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),s=o.map(n=>`"${e(n)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
6
6
  # CONFIGURED ON DISK \u2014 RUNTIME NOT VERIFIED.
7
7
  # Restart Codex, then call ark_identity with expectedRoot before trusting MCP verdicts.
8
8
  [mcp_servers.ark]
9
- command = "${t(r)}"
10
- args = [${c}]
11
- `}function R(e){const{command:r,args:o}=l(e,s,["--root",".","--config","ark.config.json"]),t=o.map(c=>`"${c.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
9
+ command = "${e(t)}"
10
+ args = [${s}]
11
+ `}function E(r){const{command:t,args:o}=d(r,p,["--root",".","--config","ark.config.json"]),e=o.map(s=>`"${s.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
12
12
  # Restart Grok (or /mcps \u2192 refresh) after changes. Also loads repo-root .mcp.json.
13
13
  [mcp_servers.ark]
14
- command = "${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"
15
- args = [${t}]
16
- `}function _(e){const r=i(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${s} --session-context --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit|write|search_replace",hooks:[{type:"command",timeout:30,command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
17
- `}function A(e){const r=i(e);return`${JSON.stringify({"ark-write-gate":{PreToolUse:[{matcher:"write_to_file|replace_file_content|multi_replace_file_content",hooks:[{type:"command",timeout:30,command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --config ark.config.json`}]}]}},null,2)}
18
- `}function j(e){const{command:r,args:o}=l(e,s,["--root",".","--config","ark.config.json"]);return`${JSON.stringify({$schema:"https://opencode.ai/config.json",mcp:{ark:{type:"local",command:[r,...o],enabled:!0}}},null,2)}
19
- `}function E(e,r){let o,t;try{o=e&&e.trim()?JSON.parse(e):{},t=JSON.parse(r)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!t||typeof t!="object"||Array.isArray(t))return null;const c=t.hooks,p=(Array.isArray(c?.preToolUse)?c.preToolUse:[]).find(n=>n&&typeof n=="object"&&typeof n.command=="string"&&/arkgate-mcp|ark-mcp/.test(n.command)&&/\s--hook(?:\s|$)/.test(` ${n.command} `));if(!p)return null;const m=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?{...o.hooks}:{},f=Array.isArray(m.preToolUse)?[...m.preToolUse]:[],k=n=>n&&typeof n=="object"&&typeof n.command=="string"&&/arkgate-mcp|ark-mcp/.test(n.command)&&/\s--hook(?:\s|$)/.test(` ${n.command} `)&&(typeof n.matcher!="string"||/Write/.test(n.matcher)||/StrReplace/.test(n.matcher)),g=f.filter(n=>!k(n));m.preToolUse=[...g,p];const u={...o,version:o.version??t.version??1,hooks:m};return`${JSON.stringify(u,null,2)}
20
- `}function S(e,r){let o,t;try{o=e&&e.trim()?JSON.parse(e):{},t=JSON.parse(r)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!t||typeof t!="object"||Array.isArray(t))return null;const c=t["ark-write-gate"];if(!c||typeof c!="object")return null;const a={...o,"ark-write-gate":c};return`${JSON.stringify(a,null,2)}
21
- `}function C(e,r){let o,t;try{o=e&&e.trim()?JSON.parse(e):{},t=JSON.parse(r)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o))return null;const c={...o};!c.$schema&&t.$schema&&(c.$schema=t.$schema);const a=o.mcp&&typeof o.mcp=="object"&&!Array.isArray(o.mcp)?{...o.mcp}:{};return a.ark=t.mcp.ark,c.mcp=a,`${JSON.stringify(c,null,2)}
22
- `}export{s as PREFERRED_MCP_BIN,A as antigravityHooks,y as claudeSettings,$ as codexHooks,O as codexProjectConfig,h as cursorHooks,_ as grokHooks,R as grokProjectConfig,S as mergeAntigravityArkHook,E as mergeCursorArkHook,C as mergeOpencodeArkMcp,j as opencodeProjectConfig};
14
+ command = "${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"
15
+ args = [${e}]
16
+ `}function v(r){const t=g(r);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${t} ${p} --session-context --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit|write|search_replace",hooks:[{type:"command",timeout:30,command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
17
+ `}function J(r){const t=g(r);return`${JSON.stringify({"ark-write-gate":{PreToolUse:[{matcher:"write_to_file|replace_file_content|multi_replace_file_content",hooks:[{type:"command",timeout:30,command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --config ark.config.json`}]}]}},null,2)}
18
+ `}function N(r){const{command:t,args:o}=d(r,p,["--root",".","--config","ark.config.json"]);return`${JSON.stringify({$schema:"https://opencode.ai/config.json",mcp:{ark:{type:"local",command:[t,...o],enabled:!0}}},null,2)}
19
+ `}function x(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e))return null;const s=e.hooks,k=(Array.isArray(s?.preToolUse)?s.preToolUse:[]).find(a=>a&&typeof a=="object"&&typeof a.command=="string"&&/arkgate-mcp|ark-mcp/.test(a.command)&&/\s--hook(?:\s|$)/.test(` ${a.command} `));if(!k)return null;const u=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?{...o.hooks}:{},l=Array.isArray(u.preToolUse)?[...u.preToolUse]:[],f=a=>a&&typeof a=="object"&&typeof a.command=="string"&&/arkgate-mcp|ark-mcp/.test(a.command)&&/\s--hook(?:\s|$)/.test(` ${a.command} `)&&(typeof a.matcher!="string"||/Write/.test(a.matcher)||/StrReplace/.test(a.matcher)),c=l.filter(a=>!f(a));u.preToolUse=[...c,k];const i={...o,version:o.version??e.version??1,hooks:u};return`${JSON.stringify(i,null,2)}
20
+ `}const O=/(?:^|[\s/])(?:arkgate-mcp|ark-mcp)(?:\.mjs)?(?:\s|$)/,j=/\s--(?:hook|session-context)(?:\s|$)/;function A(r){return!!r&&typeof r=="object"&&typeof r.command=="string"&&O.test(` ${r.command} `)&&j.test(` ${r.command} `)}function P(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e))return null;const s=e.hooks;if(!s||typeof s!="object"||o.hooks!==void 0&&(!o.hooks||typeof o.hooks!="object"||Array.isArray(o.hooks)))return null;const n={...o.hooks??{}};for(const[k,u]of Object.entries(s)){if(!Array.isArray(u))continue;const l=n[k];if(l!==void 0&&!Array.isArray(l))return null;const f=[];for(const c of l??[]){if(!c||typeof c!="object"||!Array.isArray(c.hooks)){f.push(c);continue}const i=c.hooks.filter(a=>!A(a));i.length===c.hooks.length?f.push(c):i.length>0&&f.push({...c,hooks:i})}n[k]=[...f,...u]}return`${JSON.stringify({...o,hooks:n},null,2)}
21
+ `}function m(r){const t=o=>typeof o=="string"?o.replace(/arkgate@[^\s"']+/g,"arkgate@*"):Array.isArray(o)?o.map(t):o&&typeof o=="object"?Object.fromEntries(Object.keys(o).sort().map(e=>[e,t(o[e])])):o;return JSON.stringify(t(r))}function h(r,t){try{const o=JSON.parse(r),e=JSON.parse(t);return!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e)?null:{existing:o,generated:e}}catch{return null}}function y(r){return{status:"removed",text:Object.keys(r).length===0?null:`${JSON.stringify(r,null,2)}
22
+ `}}function U(r,t){const o=h(r,t);if(!o)return{status:"unreadable"};const{existing:e,generated:s}=o;if(e.hooks===void 0)return{status:"absent"};if(!e.hooks||typeof e.hooks!="object"||Array.isArray(e.hooks))return{status:"unreadable"};const n={...e.hooks};let k=0;for(const[l,f]of Object.entries(s.hooks??{})){if(!Array.isArray(f)||n[l]===void 0)continue;if(!Array.isArray(n[l]))return{status:"unreadable"};const c=new Set(f.map(m)),i=[];for(const a of n[l]){if(!(a&&typeof a=="object"&&Array.isArray(a.hooks)&&a.hooks.some(A))){i.push(a);continue}if(!c.has(m(a)))return{status:"customized"};k+=1}i.length>0?n[l]=i:delete n[l]}if(k===0)return{status:"absent"};const u={...e,hooks:n};return Object.keys(n).length===0&&delete u.hooks,y(u)}function T(r,t){const o=h(r,t);if(!o)return{status:"unreadable"};const{existing:e,generated:s}=o,n=e.hooks?.preToolUse;if(n===void 0)return{status:"absent"};if(!Array.isArray(n))return{status:"unreadable"};const k=new Set((Array.isArray(s.hooks?.preToolUse)?s.hooks.preToolUse:[]).map(m)),u=[];let l=0;for(const i of n){if(!(i&&typeof i=="object"&&typeof i.command=="string"&&/arkgate-mcp|ark-mcp/.test(i.command)&&/\s--hook(?:\s|$)/.test(` ${i.command} `))){u.push(i);continue}if(!k.has(m(i)))return{status:"customized"};l+=1}if(l===0)return{status:"absent"};const f={...e.hooks};u.length>0?f.preToolUse=u:delete f.preToolUse;const c={...e,hooks:f};return Object.keys(f).length===0&&delete c.hooks,Object.keys(c).length===1&&c.version===(s.version??1)&&delete c.version,y(c)}function D(r,t){const o=h(r,t);if(!o)return{status:"unreadable"};const{existing:e,generated:s}=o;if(!Object.prototype.hasOwnProperty.call(e,"ark-write-gate"))return{status:"absent"};if(m(e["ark-write-gate"])!==m(s["ark-write-gate"]))return{status:"customized"};const n={...e};return delete n["ark-write-gate"],y(n)}function H(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e))return null;const s=e["ark-write-gate"];if(!s||typeof s!="object")return null;const n={...o,"ark-write-gate":s};return`${JSON.stringify(n,null,2)}
23
+ `}function w(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o))return null;const s={...o};!s.$schema&&e.$schema&&(s.$schema=e.$schema);const n=o.mcp&&typeof o.mcp=="object"&&!Array.isArray(o.mcp)?{...o.mcp}:{};return n.ark=e.mcp.ark,s.mcp=n,`${JSON.stringify(s,null,2)}
24
+ `}export{p as PREFERRED_MCP_BIN,J as antigravityHooks,R as claudeSettings,_ as codexHooks,C as codexProjectConfig,S as cursorHooks,v as grokHooks,E as grokProjectConfig,H as mergeAntigravityArkHook,P as mergeClaudeStyleArkHooks,x as mergeCursorArkHook,w as mergeOpencodeArkMcp,N as opencodeProjectConfig,D as removeAntigravityArkHook,U as removeClaudeStyleArkHooks,T as removeCursorArkHook};
@@ -37,7 +37,9 @@ function hostProfile(label, hookPath, hookSurface, hookOperations, hardWrite, re
37
37
  }),
38
38
  // Host-native "deny when the checker cannot run" flag (FS-permission analog).
39
39
  // required = schema field must be true for hard-write evidence (Cursor failClosed).
40
- // unsupported = host has no documented flag; hook-crash behavior is host-owned.
40
+ // unsupported = host has no documented flag. ark-mcp --hook fails closed itself
41
+ // (exit 2 / host deny) when its checker cannot run for a governed source write;
42
+ // only a crash of node itself before the launcher runs stays host-owned.
41
43
  // none = advisory host; no local write block.
42
44
  nativeFailClosed: extras.nativeFailClosed ?? (hardWrite ? 'unsupported' : 'none'),
43
45
  // EH07 minimum ops matrix (hard=false for soft hosts on every listed op).
@@ -56,7 +56,19 @@ export function readTsconfigAliases(ts, root) {
56
56
  if (!configPath) return { baseUrl: root, aliases: [] };
57
57
  const read = ts.readConfigFile(configPath, ts.sys.readFile);
58
58
  if (read.error) return { baseUrl: root, aliases: [] };
59
- const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(configPath));
59
+ // Options-only parse: `fileNames` are unused here. With the real `ts.sys` the
60
+ // parser walked the whole `include` tree on every write-hook call (#205 twin).
61
+ const optionsOnlyHost = {
62
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
63
+ readDirectory: () => [],
64
+ fileExists: (fileName) => ts.sys.fileExists(fileName),
65
+ readFile: (fileName) => ts.sys.readFile(fileName),
66
+ };
67
+ const parsed = ts.parseJsonConfigFileContent(
68
+ read.config,
69
+ optionsOnlyHost,
70
+ path.dirname(configPath)
71
+ );
60
72
  const opts = parsed.options || {};
61
73
  const baseUrl = opts.baseUrl || path.dirname(configPath);
62
74
  const aliases = [];
@@ -10,6 +10,7 @@ import {
10
10
  execRunner,
11
11
  presentLockfiles,
12
12
  } from '../ark-shared.mjs';
13
+ import { npxArkgatePrefixLength } from './package-manager.mjs';
13
14
  import {
14
15
  codexPromptsDir,
15
16
  codexSkillsDir,
@@ -29,6 +30,7 @@ import {
29
30
  grokHooks,
30
31
  grokProjectConfig,
31
32
  mergeAntigravityArkHook,
33
+ mergeClaudeStyleArkHooks,
32
34
  mergeCursorArkHook,
33
35
  mergeOpencodeArkMcp,
34
36
  opencodeProjectConfig,
@@ -39,6 +41,7 @@ import {
39
41
  compactRouterHost,
40
42
  mergeArkMcpJson,
41
43
  MCP_JSON_GATE_FILES,
44
+ CLAUDE_STYLE_HOOK_FILES,
42
45
  writeTemplate,
43
46
  } from './gate-files.mjs';
44
47
  import {
@@ -119,7 +122,10 @@ export function staleRunnerGateFiles(root) {
119
122
  continue;
120
123
  }
121
124
  const ark = json?.mcpServers?.ark;
122
- if (ark && ark.command && ark.command !== want.split(' ')[0]) stale.push(rel);
125
+ if (!ark || !ark.command) continue;
126
+ // Unpinned project: a bare `npx <bin>` MCP entry 404s the same way a text command does.
127
+ const barePinnedNpx = want.startsWith('npx ') && npxArkgatePrefixLength(ark.args) === 0;
128
+ if (ark.command !== want.split(' ')[0] || barePinnedNpx) stale.push(rel);
123
129
  }
124
130
  return stale;
125
131
  }
@@ -341,6 +347,18 @@ export function runMigrateCommands(root) {
341
347
  warnLockfileConflict(root);
342
348
  }
343
349
 
350
+ /** Shared host files Ark merges into (keeps sibling servers / hooks / keys). */
351
+ function mergeableGateFile(relativePath) {
352
+ if (relativePath === 'opencode.json') return { fn: mergeOpencodeArkMcp, forceReplaces: false };
353
+ if (relativePath === '.cursor/hooks.json') return { fn: mergeCursorArkHook, forceReplaces: false };
354
+ if (relativePath === '.agents/hooks.json') return { fn: mergeAntigravityArkHook, forceReplaces: false };
355
+ if (CLAUDE_STYLE_HOOK_FILES.includes(relativePath)) {
356
+ return { fn: mergeClaudeStyleArkHooks, forceReplaces: true };
357
+ }
358
+ if (MCP_JSON_GATE_FILES.includes(relativePath)) return { fn: mergeArkMcpJson, forceReplaces: true };
359
+ return null;
360
+ }
361
+
344
362
  export function runInstallAgentGates(args) {
345
363
  const root = args.root;
346
364
  if (args.migrateCommands) {
@@ -454,6 +472,11 @@ export function runInstallAgentGates(args) {
454
472
  `Added package.json script "typecheck": "${typecheckBootstrap.script}" (tsconfig present; local/CI parity).`
455
473
  );
456
474
  }
475
+ if (typecheckBootstrap.reason === 'no-typescript' && !args.compact && !args.json) {
476
+ console.log(
477
+ 'tsconfig/jsconfig found but typescript is not a dependency — skipped the "typecheck" script and CI step (add typescript as a devDependency, then "typecheck": "tsc --noEmit").'
478
+ );
479
+ }
457
480
  }
458
481
  const catalog = buildManagedAssetCatalog({
459
482
  root,
@@ -518,81 +541,24 @@ export function runInstallAgentGates(args) {
518
541
  if (merged === existing) return { relativePath, status: 'skipped' };
519
542
  return writeTemplate(root, relativePath, merged, true);
520
543
  }
521
- if (relativePath === 'opencode.json') {
522
- const fullPath = path.join(root, relativePath);
523
- let existing = '';
524
- try {
525
- existing = fs.readFileSync(fullPath, 'utf8');
526
- } catch {
527
- // Missing project config → write the generated Ark MCP block.
528
- }
529
- if (!existing) {
530
- return writeTemplate(root, relativePath, content, true);
531
- }
532
- const merged = mergeOpencodeArkMcp(existing, content);
533
- if (merged == null) {
534
- return { relativePath, status: 'skipped-non-ark' };
535
- }
536
- if (merged === existing) return { relativePath, status: 'skipped' };
537
- return writeTemplate(root, relativePath, merged, true);
538
- }
539
- if (relativePath === '.cursor/hooks.json') {
540
- const fullPath = path.join(root, relativePath);
541
- let existing = '';
542
- try {
543
- existing = fs.readFileSync(fullPath, 'utf8');
544
- } catch {
545
- // Missing hooks file → write generated Cursor preToolUse gate.
546
- }
547
- if (!existing) {
548
- return writeTemplate(root, relativePath, content, true);
549
- }
550
- const merged = mergeCursorArkHook(existing, content);
551
- if (merged == null) {
552
- return { relativePath, status: 'skipped-non-ark' };
553
- }
554
- if (merged === existing) return { relativePath, status: 'skipped' };
555
- // Upsert Ark preToolUse without requiring --force; never wipe sibling hooks.
556
- return writeTemplate(root, relativePath, merged, true);
557
- }
558
- if (relativePath === '.agents/hooks.json') {
559
- const fullPath = path.join(root, relativePath);
560
- let existing = '';
561
- try {
562
- existing = fs.readFileSync(fullPath, 'utf8');
563
- } catch {
564
- // Missing hooks file → write generated ark-write-gate map.
565
- }
566
- if (!existing) {
567
- return writeTemplate(root, relativePath, content, true);
568
- }
569
- const merged = mergeAntigravityArkHook(existing, content);
570
- if (merged == null) {
571
- return { relativePath, status: 'skipped-non-ark' };
572
- }
573
- if (merged === existing) return { relativePath, status: 'skipped' };
574
- // Upsert ark-write-gate without requiring --force; never wipe sibling named hooks.
575
- return writeTemplate(root, relativePath, merged, true);
576
- }
577
- if (MCP_JSON_GATE_FILES.includes(relativePath)) {
578
- const fullPath = path.join(root, relativePath);
544
+ const merge = mergeableGateFile(relativePath);
545
+ if (merge) {
546
+ // Upsert Ark's entry into a shared host file without --force; never wipe sibling keys.
579
547
  let existing = '';
580
548
  try {
581
- existing = fs.readFileSync(fullPath, 'utf8');
549
+ existing = fs.readFileSync(path.join(root, relativePath), 'utf8');
582
550
  } catch {
583
- // Missing MCP JSON → write generated ark server.
584
- }
585
- if (!existing) {
586
- return writeTemplate(root, relativePath, content, true);
551
+ // Missing file → write the generated Ark template.
587
552
  }
588
- const merged = mergeArkMcpJson(existing, content);
553
+ if (!existing) return writeTemplate(root, relativePath, content, true);
554
+ const merged = merge.fn(existing, content);
589
555
  if (merged == null) {
590
- return args.force
556
+ // Unmergeable file: never guess. --force replaces it only where that is explicit consent.
557
+ return merge.forceReplaces && args.force
591
558
  ? writeTemplate(root, relativePath, content, true)
592
559
  : { relativePath, status: 'skipped-non-ark' };
593
560
  }
594
561
  if (merged === existing) return { relativePath, status: 'skipped' };
595
- // Upsert mcpServers.ark without requiring --force; never wipe sibling servers.
596
562
  return writeTemplate(root, relativePath, merged, true);
597
563
  }
598
564
  return writeTemplate(
@@ -794,14 +760,25 @@ export function runInstallAgentGates(args) {
794
760
  }
795
761
  if (tools.has('codex') && !args.compact) {
796
762
  console.log(' Codex write path (honest):');
797
- console.log(' - Local: advisory MCP + best-effort .codex/hooks.json (not a hard boundary).');
798
- console.log(' - Hard merge backstop: CI --strict-merge + required status check.');
799
- console.log(' - Not equivalent to Claude/Grok PreToolUse hard-write + repair.');
763
+ console.log(' - Local: .codex/hooks.json PreToolUse hard-blocks a complete local apply_patch (Codex CLI / local Desktop) once the project is trusted and a hook run is runtime-observed; on disk it is configured, not verified.');
764
+ console.log(' - Outside that claim (hosted tools, hook opt-out, shell writes, incomplete patch reconstruction): CI --strict-merge + required status check.');
765
+ console.log(' - Repair envelopes may emit, but Codex reinjection is not guaranteed (unlike Claude/Grok hard-write + repair).');
800
766
  }
801
767
  if (args.codexHome) {
802
- console.log(
803
- ` Codex: refreshed home skills under ${codexSkillsDir()}/<name>/SKILL.md (Codex skill catalog).`
804
- );
768
+ // Report what the home write actually did (it is skipped when the project catalog exists).
769
+ if (homeResults.some((r) => r.status === 'written')) {
770
+ console.log(
771
+ ` Codex: refreshed home skills under ${codexSkillsDir()}/<name>/SKILL.md (Codex skill catalog).`
772
+ );
773
+ } else if (homeResults.some((r) => r.status === 'failed')) {
774
+ console.log(` Codex: home skills NOT written under ${codexSkillsDir()} (see failures above).`);
775
+ } else if (homeResults.length === 0) {
776
+ console.log(
777
+ ' Codex: home skills not written — project .agents/skills is the catalog (Codex reads repo scope).'
778
+ );
779
+ } else {
780
+ console.log(` Codex: home skills under ${codexSkillsDir()} already current (no changes).`);
781
+ }
805
782
  } else if (tools.has('codex') && skills.length > 0 && !args.compact) {
806
783
  console.log(
807
784
  ' Codex: project skills at `.agents/skills/<name>/SKILL.md` (Agent Skills REPO scope).'
@@ -8,6 +8,7 @@
8
8
  * Pure CLI helper (bin/lib/invariant-coverage.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
+ import { findClassDeclarations, findMemberContainers, objectLiteralMemberNames, scanClassMembers, } from './class-source-scan.mjs';
11
12
  /** Adopted + catalogued invariants, but no declared tests path (P2 §10). */
12
13
  export const INVARIANT_TESTS_PATH_RULE_ID = 'INVARIANT_TESTS_PATH_MISSING';
13
14
  export const INVARIANT_TESTS_PATH_MESSAGE = 'This project is adopted and has domain invariants, but ark.config.json does not name a real tests path. Add coverage.testGlobs or coverage.coverageRoots pointing at the folder where those tests live, then re-run. Without that path, coverage is an empty checkbox.';
@@ -185,6 +186,38 @@ function declarationShape(content, name) {
185
186
  }
186
187
  return undefined;
187
188
  }
189
+ /**
190
+ * `Name.member` coverage: the member must be declared inside the body of
191
+ * `class Name` (exported, default-exported, abstract, or local), a
192
+ * `const Name = class { … }` expression, a `namespace Name { … }` (function or
193
+ * const member), or a `const Name = { … }` object literal, in this file. A
194
+ * member of another container, a free function, or a file that merely mentions
195
+ * the name does not count.
196
+ */
197
+ function classMemberShape(content, className, member) {
198
+ const hasClassMethod = (body) => scanClassMembers(body).members.some((m) => m.kind === 'method' && m.name === member);
199
+ for (const decl of findClassDeclarations(content)) {
200
+ if (decl.name !== className || decl.bodyStart == null)
201
+ continue;
202
+ if (hasClassMethod(content.slice(decl.bodyStart, decl.bodyEnd ?? content.length))) {
203
+ return 'method';
204
+ }
205
+ }
206
+ for (const container of findMemberContainers(content, className)) {
207
+ const body = content.slice(container.bodyStart, container.bodyEnd);
208
+ if (container.kind === 'class-expression' && hasClassMethod(body))
209
+ return 'method';
210
+ if (container.kind === 'object' && objectLiteralMemberNames(body).includes(member)) {
211
+ return 'method';
212
+ }
213
+ if (container.kind === 'namespace') {
214
+ const shape = declarationShape(body, member);
215
+ if (shape === 'function' || shape === 'const')
216
+ return shape;
217
+ }
218
+ }
219
+ return undefined;
220
+ }
188
221
  function lineAt(content, index) {
189
222
  const start = content.lastIndexOf('\n', index - 1) + 1;
190
223
  const end = content.indexOf('\n', index);
@@ -298,7 +331,8 @@ function symbolNeedle(symbol) {
298
331
  const name = parts[parts.length - 1] ?? '';
299
332
  if (!name)
300
333
  return undefined;
301
- const className = parts.length > 1 ? (parts[0] ?? null) : null;
334
+ // `Aggregate.method` (or `ns.Aggregate.method`): the segment before the member names the class.
335
+ const className = parts.length > 1 ? (parts[parts.length - 2] || null) : null;
302
336
  return { className, name };
303
337
  }
304
338
  function declaredLabel(invariant) {
@@ -344,9 +378,9 @@ function rankCoverage(invariant, files, scanTests = true) {
344
378
  const content = files.fileContents[file];
345
379
  if (!content)
346
380
  continue;
347
- if (needle.className && !content.includes(needle.className))
348
- continue;
349
- const shape = declarationShape(content, needle.name);
381
+ const shape = needle.className
382
+ ? classMemberShape(content, needle.className, needle.name)
383
+ : declarationShape(content, needle.name);
350
384
  if (!shape)
351
385
  continue;
352
386
  declaration = { file, shape };
@@ -513,10 +547,14 @@ export function evaluateInvariantCoverage(input) {
513
547
  // Covered if the policy says this verdict counts.
514
548
  // When coverage declares neither test nor symbol, require at least description-only advisory presence = not covered.
515
549
  const requiresEvidence = inv.coverage?.test === true || Boolean(symbol) || inv.coverage === undefined;
550
+ // `coverage.test: false` without a symbol declares zero evidence. That opt-out is
551
+ // advisory-only: on an enforced invariant it is reported uncovered (never a false
552
+ // green), not refused at load, so the rest of the contract still evaluates.
553
+ const zeroEvidenceOptOut = inv.coverage?.test === false && !symbol;
516
554
  const covered = requiresEvidence && counts
517
555
  ? true
518
- : inv.coverage?.test === false && !symbol
519
- ? true // explicitly no coverage requirements
556
+ : zeroEvidenceOptOut && inv.mode !== 'enforced'
557
+ ? true // explicitly no coverage requirements (advisory-only opt-out)
520
558
  : counts;
521
559
  // Partial only when tests are missing *and* no other evidence (e.g. symbol) completed coverage.
522
560
  const partial = testGlobsMissing && wantsTest && !counts;
@@ -562,11 +600,13 @@ export function evaluateInvariantCoverage(input) {
562
600
  ? coverageBudgetExhausted
563
601
  ? `Invariant ${inv.id} coverage cannot be proven (${budgetDetail}); reporting partial, not covered.`
564
602
  : `Invariant ${inv.id} coverage cannot be proven (test globs missing or empty); reporting partial, not covered (never-had-tests).`
565
- : // The sentence is the verdict. It names a title, a declaration
566
- // shape, a bare mention, or silence — it does not re-scan.
567
- `Invariant ${inv.id}: ${describeCoverage(inv, ev)} (${kind === 'tests-disappeared'
568
- ? 'tests-disappeared — a suite exists'
569
- : 'never-had-tests — the scan found no tests at all'}). ArkGate matches declared text; it never executes tests.`) +
603
+ : zeroEvidenceOptOut
604
+ ? `Invariant ${inv.id} is enforced but declares no evidence (coverage.test is false and no coverage.symbol); set coverage.symbol, drop test:false, or keep mode "advisory".`
605
+ : // The sentence is the verdict. It names a title, a declaration
606
+ // shape, a bare mention, or silence — it does not re-scan.
607
+ `Invariant ${inv.id}: ${describeCoverage(inv, ev)} (${kind === 'tests-disappeared'
608
+ ? 'tests-disappeared — a suite exists'
609
+ : 'never-had-tests — the scan found no tests at all'}). ArkGate matches declared text; it never executes tests.`) +
570
610
  (partial && coverageBudgetExhausted ? budgetExhaustedTail : discardTail),
571
611
  file: inv.provenance.sourceFile,
572
612
  line: 1,
@@ -608,6 +648,14 @@ export function canPromoteInvariant(coverage) {
608
648
  reason: `Invariant ${coverage.invariantId} is uncovered; add a test title or symbol before promoting to enforced.`,
609
649
  };
610
650
  }
651
+ // A `coverage.test: false` opt-out without a symbol reports covered with no
652
+ // evidence at all. That is fine while advisory; it is not a basis for enforcing.
653
+ if (Array.isArray(coverage.evidence) && coverage.evidence.length === 0) {
654
+ return {
655
+ ok: false,
656
+ reason: `Invariant ${coverage.invariantId} declares no evidence (coverage.test:false and no symbol); add coverage.symbol or a test title before promoting to enforced.`,
657
+ };
658
+ }
611
659
  // Promotion is the moment coverage stops being advice, so an evidence file
612
660
  // the project itself says its runner does not walk cannot carry it.
613
661
  if (coverage.outsideDeclaredRoots === true) {
@@ -53,8 +53,14 @@ export const MCP_RUNNER_ARGV = new Set(['exec', '--config.verify-deps-before-run
53
53
  // The runner token immediately before an ark command in a text command string.
54
54
  // Matches npm/yarn runners and both pnpm forms (legacy `pnpm exec` + verify-deps-safe form).
55
55
  // Longer bin names first so `arkgate-check` is not partially matched as `ark`.
56
+ // The pinned npx form (`npx -y -p arkgate@x`) comes first so it is replaced whole.
56
57
  export const RUNNER_BEFORE_ARK =
57
- /\b(?:npx|pnpm --config\.verify-deps-before-run=false exec|pnpm exec|yarn)(?= (?:arkgate-check|arkgate-mcp|arkgate|ark-check|ark-mcp|ark)\b)/g;
58
+ /\b(?:npx -y -p arkgate@[^\s"'`]+|npx|pnpm --config\.verify-deps-before-run=false exec|pnpm exec|yarn)(?= (?:arkgate-check|arkgate-mcp|arkgate|ark-check|ark-mcp|ark)\b)/g;
59
+
60
+ /** npx pinned-package argv (`-y`, `-p`, `arkgate@x`) — runner noise, not a server flag. */
61
+ function isPinnedNpxArgv(entry) {
62
+ return /^(?:-y|--yes|-p|--package|--package=arkgate(?:@\S+)?|arkgate@\S+)$/.test(entry);
63
+ }
58
64
 
59
65
  /** Keep only MCP server flags from existing args (drop runner tokens + any ark* bin names). */
60
66
 
@@ -66,6 +72,7 @@ export function stripMcpServerArgs(args) {
66
72
  (entry) =>
67
73
  typeof entry === 'string' &&
68
74
  !MCP_RUNNER_ARGV.has(entry) &&
75
+ !isPinnedNpxArgv(entry) &&
69
76
  !ARK_MCP_BINS.has(entry) &&
70
77
  !ARK_CHECK_BINS.has(entry) &&
71
78
  !ARK_CLI_BINS.has(entry)
@@ -89,6 +96,7 @@ export function stripOpencodeMcpCommand(command) {
89
96
  if (
90
97
  runners.has(base) ||
91
98
  MCP_RUNNER_ARGV.has(entry) ||
99
+ isPinnedNpxArgv(entry) ||
92
100
  ARK_MCP_BINS.has(base) ||
93
101
  ARK_MCP_BINS.has(entry) ||
94
102
  ARK_CHECK_BINS.has(base) ||