arkgate 4.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +81 -3
  2. package/README.md +15 -4
  3. package/bin/ark-check-runtime.mjs +16 -5
  4. package/bin/ark-mcp-runtime.mjs +766 -64
  5. package/bin/lib/agent-gates.mjs +1 -0
  6. package/bin/lib/ci-and-commands.mjs +16 -7
  7. package/bin/lib/codex-home.mjs +90 -8
  8. package/bin/lib/design-smells.mjs +71 -9
  9. package/bin/lib/doctor-plan.mjs +36 -36
  10. package/bin/lib/effective-contract-load.mjs +73 -9
  11. package/bin/lib/enforcement-state.mjs +1 -1
  12. package/bin/lib/gate-files.mjs +441 -9
  13. package/bin/lib/github-enforcement.mjs +16 -3
  14. package/bin/lib/hook-templates.mjs +12 -11
  15. package/bin/lib/html-report-evolution.mjs +114 -0
  16. package/bin/lib/html-report.mjs +11 -89
  17. package/bin/lib/import-resolve.mjs +33 -11
  18. package/bin/lib/install-activation.mjs +87 -0
  19. package/bin/lib/install-migrate.mjs +66 -50
  20. package/bin/lib/managed-upgrade.mjs +10 -41
  21. package/bin/lib/mcp-adoption.mjs +15 -5
  22. package/bin/lib/pilot-loop.mjs +25 -8
  23. package/bin/lib/project-identity.mjs +103 -0
  24. package/bin/lib/report-snapshot-context.mjs +28 -0
  25. package/bin/lib/resident-hook.mjs +33 -9
  26. package/bin/lib/rules-inventory.mjs +100 -8
  27. package/bin/lib/skill-install.mjs +272 -22
  28. package/bin/lib/skill-write.mjs +899 -0
  29. package/bin/lib/start-preview.mjs +84 -1
  30. package/bin/lib/upgrade-command.mjs +2 -5
  31. package/dist/index.cjs +13 -13
  32. package/dist/index.d.ts +194 -2
  33. package/dist/index.js +13 -13
  34. package/docs/README.md +5 -3
  35. package/docs/agent-guide.md +110 -14
  36. package/docs/ai-gates.md +103 -18
  37. package/docs/assets/ark-write-gate.svg +2 -2
  38. package/docs/enthusiast/how-to-agent-gates.md +6 -0
  39. package/docs/package-surface.md +14 -9
  40. package/docs/product-voice.md +13 -1
  41. package/package.json +3 -1
  42. package/schemas/ark.project-identity.schema.json +116 -0
  43. package/server.json +2 -2
  44. package/templates/skills/ark-adopt.md +9 -0
  45. package/templates/skills/ark-architect.md +12 -2
  46. package/templates/skills/ark-autopilot.md +9 -0
  47. package/templates/skills/ark-contract.md +11 -1
  48. package/templates/skills/ark-coverage.md +9 -0
  49. package/templates/skills/ark-explain.md +13 -1
  50. package/templates/skills/ark-explore.md +9 -0
  51. package/templates/skills/ark-fix.md +10 -1
  52. package/templates/skills/ark-loop.md +11 -2
  53. package/templates/skills/ark-place.md +17 -6
  54. package/templates/skills/ark-runtime.md +8 -0
  55. package/templates/skills/ark-think.md +14 -2
  56. package/templates/skills/ark-upgrade.md +9 -0
@@ -1,2 +1,2 @@
1
1
  // Generated from enforcement-state.source.mjs — run npm run generate:packaged-tooling.
2
- import m from"node:fs";import{createRequire as q}from"node:module";import h from"node:path";const n="unverified";function S(a){const e=h.join(a,"package.json");try{if(JSON.parse(m.readFileSync(e,"utf8"))?.name==="arkgate"&&m.statSync(h.join(a,"bin","ark-check.mjs"),{throwIfNoEntry:!1})?.isFile())return{installed:!0,source:"package.json + bin/ark-check.mjs (self-host)",selfHost:!0}}catch{}try{const r=q(e).resolve("arkgate/package.json"),o=h.dirname(r),i=JSON.parse(m.readFileSync(r,"utf8")),s=h.join(o,"bin","ark-check.mjs");if(i?.name==="arkgate"&&m.statSync(s,{throwIfNoEntry:!1})?.isFile())return{installed:!0,source:"arkgate/package.json via project resolver",selfHost:!1}}catch{}return{installed:!1,source:"arkgate/package.json unresolved from project",selfHost:!1}}function M(a){return a.length>0?a:["filesystem scan (no matching configuration)"]}function g({supported:a,configuredPaths:e,installed:r,active:o,runtimeObserved:i,operation:s,operationCoverage:v,bypassable:u,required:c,hard:t,sources:l}){const p=e.length>0,d=!!r.installed;return{supported:a,analyzed:!0,configured:p,installed:d,active:o,runtimeObserved:i,operation:s,operationCoverage:v,bypassable:u,required:c,hard:t,evidence:[...M(e).map(f=>({field:"configured",source:f,value:p})),{field:"installed",source:r.source,value:d},{field:"active",source:l.active,value:o},{field:"runtimeObserved",source:l.runtimeObserved,value:i},{field:"operationCoverage",source:l.operationCoverage,value:v},{field:"bypassable",source:l.bypassable,value:u},{field:"required",source:l.required,value:c},{field:"hard",source:l.hard,value:t}]}}function O(a,e){const r=S(a),o=!!e.support?.capabilities?.["hard-write"],i=!!e.support?.capabilities?.["advisory-write"],s=e.capabilityEvidence["hard-write"],v=e.capabilityEvidence["advisory-write"],u=e.capabilityEvidence["merge-gate"],c=e.enforcementLadder.localWrite,t=typeof c.operationCovered=="boolean",l=t?c.operationCovered:n,p=t&&l===!0,d=!!(o&&r.installed&&p&&c.hard===!0),f=t?p&&r.installed:o&&s.length>0&&r.installed?n:!1,b=i&&v.length>0&&r.installed?n:!1,y=!!(e.ci?.failClosed&&u.length>0),C=y&&r.installed?n:!1;return{schemaVersion:"1.1",activeHost:e.activeHost,localWrite:g({supported:o,configuredPaths:s,installed:r,active:f,runtimeObserved:t,operation:t?c.operation??null:null,operationCoverage:l,bypassable:d?!1:o&&!t?n:!0,required:n,hard:d,sources:{active:t?"observed PreToolUse attempt":"runtime observation unavailable",runtimeObserved:t?"fresh PreToolUse invocation":"runtime observation unavailable",operationCoverage:t?"active-host operation matcher":"operation not observed",bypassable:d?"observed hard write boundary":"host runtime bypass evidence unavailable",required:"local host policy unavailable",hard:d?"fresh covered active-host invocation":"hardness not proven for this invocation"}}),advisoryMcp:g({supported:i,configuredPaths:v,installed:r,active:b,runtimeObserved:!1,operation:null,operationCoverage:n,bypassable:!0,required:n,hard:!1,sources:{active:"MCP runtime observation unavailable",runtimeObserved:"doctor did not observe an MCP tool invocation",operationCoverage:"advisory MCP is caller-invoked",bypassable:"advisory MCP does not intercept every write",required:"local host policy unavailable",hard:"MCP presence is advisory and never proves a hard boundary"}}),ciMerge:g({supported:!0,configuredPaths:y?u:[],installed:r,active:C,runtimeObserved:!1,operation:"merge",operationCoverage:y?n:!1,bypassable:y?n:!0,required:n,hard:!1,sources:{active:"CI run and provider enforcement not observed",runtimeObserved:"provider evidence unavailable",operationCoverage:"required-status operation coverage unavailable",bypassable:"branch-protection evidence unavailable",required:"branch-protection evidence unavailable",hard:"merge hardness requires fresh provider evidence"}})}}function $(a,e,r,o){return{...a,...o,evidence:[...a.evidence.filter(i=>!e.includes(i.field)),...e.map(i=>({field:i,source:r,value:o[i]}))]}}function R(a,e){if(!e)return a;const r=e.reason==="provider-policy-unavailable-plan"||e.policyReason==="unavailable-plan",o=e.available===!0,i=!o&&!r&&(e.reason==="provider-enforcement-unverified"||e.reason==="gh-cli-unavailable"||e.reason==="gh-repo-unavailable"||!!e.reason);if(!o&&e.runtimeObserved!==!0&&!r&&!i)return a;const s=o?typeof e.arkCheckRequired=="boolean"?e.arkCheckRequired:n:r?!1:n,v=!!(a.enforcementState.ciMerge.configured&&a.enforcementState.ciMerge.installed),u=s===!0?v:s===!1?!1:v?n:!1,c=u===!0?e.arkCheckSourceBound===!1?!0:n:s===!1?!0:v?n:!0,t=e.runtimeObserved===!0,l=o?`GitHub branch protection (${e.repo??"repository"}:${e.branch??"default"})`:r?`GitHub provider policy unavailable (plan) (${e.repo??"repository"}:${e.branch??"default"})`:`GitHub CI runtime (${e.repo??"repository"})`,p=s,d=u===!0&&c===!1&&p===!0,f=$(a.enforcementState.ciMerge,["active","runtimeObserved","operationCoverage","bypassable","required","hard"],l,{active:u,runtimeObserved:t,operationCoverage:p,bypassable:c,required:s,hard:d}),b={...a,enforcementState:{...a.enforcementState,ciMerge:f},enforcementLadder:{...a.enforcementLadder,ciMerge:{...a.enforcementLadder.ciMerge,requiredStatus:s,...e.latestCiRun?{latestCiRun:e.latestCiRun}:{},...r?{providerPolicy:"unavailable-plan"}:{}}}};return(r||e.reason)&&(b.providerEnforcement={available:o,reason:e.reason||(r?"provider-policy-unavailable-plan":"provider-enforcement-unverified"),policyReason:e.policyReason||(r?"unavailable-plan":null),runtimeObserved:t,latestCiRun:e.latestCiRun??null,hard:d===!0}),b}function k(a,e){const r=o=>o===!0?"yes":o===!1?"no":String(o);return`${a} \u2014 supported: ${r(e.supported)} \xB7 analyzed: ${r(e.analyzed)} \xB7 configured: ${r(e.configured)} \xB7 installed: ${r(e.installed)} \xB7 runtime observed: ${r(e.runtimeObserved)} \xB7 operation: ${e.operation??"none"} \xB7 operation covered: ${r(e.operationCoverage)} \xB7 active: ${r(e.active)} \xB7 bypassable: ${r(e.bypassable)} \xB7 required: ${r(e.required)} \xB7 hard: ${r(e.hard)}`}function x(a){const e=[{level:a.localWrite.active===!0?"ok":"warn",text:k("Local write",a.localWrite)},{level:"warn",text:k("Advisory MCP",a.advisoryMcp)},{level:a.ciMerge.required===!0?"ok":"warn",text:k("CI merge",a.ciMerge)}];return a.localWrite.active===n&&a.localWrite.hard===!1&&e.push({level:"bad",text:"RED FLAG: local hook assets exist, but this active-host operation was not observed at runtime; hard blocking is unverified."}),a.activeHost==="unknown"&&e.push({level:"warn",text:"Active host unknown for this invocation \u2014 enforcementState is session projection only. See writePath.inventory for on-disk host hooks; hard write is never claimed without runtime proof."}),e}export{O as buildEnforcementState,x as enforcementDoctorLines,S as packageInstallation,R as withCiProviderEvidence};
2
+ import m from"node:fs";import{createRequire as q}from"node:module";import h from"node:path";const n="unverified";function O({configuredOnDisk:a=!1,restartRequired:e=a}={}){return{configuredOnDisk:!!a,restartRequired:!!e,runtimeObserved:!1,identityMatch:n,active:!1}}function M(a){const e=h.join(a,"package.json");try{if(JSON.parse(m.readFileSync(e,"utf8"))?.name==="arkgate"&&m.statSync(h.join(a,"bin","ark-check.mjs"),{throwIfNoEntry:!1})?.isFile())return{installed:!0,source:"package.json + bin/ark-check.mjs (self-host)",selfHost:!0}}catch{}try{const r=q(e).resolve("arkgate/package.json"),o=h.dirname(r),i=JSON.parse(m.readFileSync(r,"utf8")),s=h.join(o,"bin","ark-check.mjs");if(i?.name==="arkgate"&&m.statSync(s,{throwIfNoEntry:!1})?.isFile())return{installed:!0,source:"arkgate/package.json via project resolver",selfHost:!1}}catch{}return{installed:!1,source:"arkgate/package.json unresolved from project",selfHost:!1}}function S(a){return a.length>0?a:["filesystem scan (no matching configuration)"]}function g({supported:a,configuredPaths:e,installed:r,active:o,runtimeObserved:i,operation:s,operationCoverage:u,bypassable:v,required:c,hard:t,sources:l}){const p=e.length>0,d=!!r.installed;return{supported:a,analyzed:!0,configured:p,installed:d,active:o,runtimeObserved:i,operation:s,operationCoverage:u,bypassable:v,required:c,hard:t,evidence:[...S(e).map(f=>({field:"configured",source:f,value:p})),{field:"installed",source:r.source,value:d},{field:"active",source:l.active,value:o},{field:"runtimeObserved",source:l.runtimeObserved,value:i},{field:"operationCoverage",source:l.operationCoverage,value:u},{field:"bypassable",source:l.bypassable,value:v},{field:"required",source:l.required,value:c},{field:"hard",source:l.hard,value:t}]}}function E(a,e){const r=M(a),o=!!e.support?.capabilities?.["hard-write"],i=!!e.support?.capabilities?.["advisory-write"],s=e.capabilityEvidence["hard-write"],u=e.capabilityEvidence["advisory-write"],v=e.capabilityEvidence["merge-gate"],c=e.enforcementLadder.localWrite,t=typeof c.operationCovered=="boolean",l=t?c.operationCovered:n,p=t&&l===!0,d=!!(o&&r.installed&&p&&c.hard===!0),f=t?p&&r.installed:o&&s.length>0&&r.installed?n:!1,b=i&&u.length>0&&r.installed?n:!1,y=!!(e.ci?.failClosed&&v.length>0),C=y&&r.installed?n:!1;return{schemaVersion:"1.1",activeHost:e.activeHost,localWrite:g({supported:o,configuredPaths:s,installed:r,active:f,runtimeObserved:t,operation:t?c.operation??null:null,operationCoverage:l,bypassable:d?!1:o&&!t?n:!0,required:n,hard:d,sources:{active:t?"observed PreToolUse attempt":"runtime observation unavailable",runtimeObserved:t?"fresh PreToolUse invocation":"runtime observation unavailable",operationCoverage:t?"active-host operation matcher":"operation not observed",bypassable:d?"observed hard write boundary":"host runtime bypass evidence unavailable",required:"local host policy unavailable",hard:d?"fresh covered active-host invocation":"hardness not proven for this invocation"}}),advisoryMcp:g({supported:i,configuredPaths:u,installed:r,active:b,runtimeObserved:!1,operation:null,operationCoverage:n,bypassable:!0,required:n,hard:!1,sources:{active:"MCP runtime observation unavailable",runtimeObserved:"doctor did not observe an MCP tool invocation",operationCoverage:"advisory MCP is caller-invoked",bypassable:"advisory MCP does not intercept every write",required:"local host policy unavailable",hard:"MCP presence is advisory and never proves a hard boundary"}}),ciMerge:g({supported:!0,configuredPaths:y?v:[],installed:r,active:C,runtimeObserved:!1,operation:"merge",operationCoverage:y?n:!1,bypassable:y?n:!0,required:n,hard:!1,sources:{active:"CI run and provider enforcement not observed",runtimeObserved:"provider evidence unavailable",operationCoverage:"required-status operation coverage unavailable",bypassable:"branch-protection evidence unavailable",required:"branch-protection evidence unavailable",hard:"merge hardness requires fresh provider evidence"}})}}function $(a,e,r,o){return{...a,...o,evidence:[...a.evidence.filter(i=>!e.includes(i.field)),...e.map(i=>({field:i,source:r,value:o[i]}))]}}function x(a,e){if(!e)return a;const r=e.reason==="provider-policy-unavailable-plan"||e.policyReason==="unavailable-plan",o=e.available===!0,i=!o&&!r&&(e.reason==="provider-enforcement-unverified"||e.reason==="gh-cli-unavailable"||e.reason==="gh-repo-unavailable"||!!e.reason);if(!o&&e.runtimeObserved!==!0&&!r&&!i)return a;const s=o?typeof e.arkCheckRequired=="boolean"?e.arkCheckRequired:n:r?!1:n,u=!!(a.enforcementState.ciMerge.configured&&a.enforcementState.ciMerge.installed),v=s===!0?u:s===!1?!1:u?n:!1,c=v===!0?e.arkCheckSourceBound===!1?!0:n:s===!1?!0:u?n:!0,t=e.runtimeObserved===!0,l=o?`GitHub branch protection (${e.repo??"repository"}:${e.branch??"default"})`:r?`GitHub provider policy unavailable (plan) (${e.repo??"repository"}:${e.branch??"default"})`:`GitHub CI runtime (${e.repo??"repository"})`,p=s,d=v===!0&&c===!1&&p===!0,f=$(a.enforcementState.ciMerge,["active","runtimeObserved","operationCoverage","bypassable","required","hard"],l,{active:v,runtimeObserved:t,operationCoverage:p,bypassable:c,required:s,hard:d}),b={...a,enforcementState:{...a.enforcementState,ciMerge:f},enforcementLadder:{...a.enforcementLadder,ciMerge:{...a.enforcementLadder.ciMerge,requiredStatus:s,...e.latestCiRun?{latestCiRun:e.latestCiRun}:{},...r?{providerPolicy:"unavailable-plan"}:{}}}};return(r||e.reason)&&(b.providerEnforcement={available:o,reason:e.reason||(r?"provider-policy-unavailable-plan":"provider-enforcement-unverified"),policyReason:e.policyReason||(r?"unavailable-plan":null),runtimeObserved:t,latestCiRun:e.latestCiRun??null,hard:d===!0}),b}function k(a,e){const r=o=>o===!0?"yes":o===!1?"no":String(o);return`${a} \u2014 supported: ${r(e.supported)} \xB7 analyzed: ${r(e.analyzed)} \xB7 configured: ${r(e.configured)} \xB7 installed: ${r(e.installed)} \xB7 runtime observed: ${r(e.runtimeObserved)} \xB7 operation: ${e.operation??"none"} \xB7 operation covered: ${r(e.operationCoverage)} \xB7 active: ${r(e.active)} \xB7 bypassable: ${r(e.bypassable)} \xB7 required: ${r(e.required)} \xB7 hard: ${r(e.hard)}`}function B(a){const e=[{level:a.localWrite.active===!0?"ok":"warn",text:k("Local write",a.localWrite)},{level:"warn",text:k("Advisory MCP",a.advisoryMcp)},{level:a.ciMerge.required===!0?"ok":"warn",text:k("CI merge",a.ciMerge)}];return a.localWrite.active===n&&a.localWrite.hard===!1&&e.push({level:"bad",text:"RED FLAG: local hook assets exist, but this active-host operation was not observed at runtime; hard blocking is unverified."}),a.activeHost==="unknown"&&e.push({level:"warn",text:"Active host unknown for this invocation \u2014 enforcementState is session projection only. See writePath.inventory for on-disk host hooks; hard write is never claimed without runtime proof."}),e}export{E as buildEnforcementState,O as codexRuntimeActivation,B as enforcementDoctorLines,M as packageInstallation,x as withCiProviderEvidence};
@@ -4,6 +4,8 @@
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import { codexProjectMcpIsValid } from './codex-home.mjs';
8
+ import { enforcingArkRunText } from './github-enforcement.mjs';
7
9
 
8
10
  export const __packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
9
11
  export const __arkCheckCli = path.join(__packageRoot, 'bin', 'ark-check.mjs');
@@ -120,6 +122,7 @@ export const REQUIRED_GATE_FILES = [
120
122
  ];
121
123
  const REQUIRED_GATE_WORKFLOW = '.github/workflows/*.yml running ark-check';
122
124
  const COMPACT_ROUTER = /<!--\s*arkgate:compact-router host=([a-z]+)\s*-->/;
125
+ const FAIL_CLOSED_ARK_FLAG = /(?:^|\s)--(?:strict|strict-merge|require-gates)(?=\s|$)/;
123
126
 
124
127
  const COMPACT_HOST_FILES = {
125
128
  claude: ['.claude/settings.json'],
@@ -149,24 +152,454 @@ export function isCompactRouterAgentsContent(text) {
149
152
  }
150
153
 
151
154
  function hasCompactHostRegistration(root, host) {
152
- if (host === 'none') return fs.existsSync(path.join(root, '.mcp.json'));
155
+ if (host === 'none') return hasArkMcpRegistration(root);
156
+ if (host === 'cursor') return hasArkMcpRegistration(root, '.cursor/mcp.json');
157
+ if (host === 'codex') return hasCodexCompactRegistration(root);
153
158
  const files = COMPACT_HOST_FILES[host];
154
159
  return Boolean(files) && files.every((relativePath) => fs.existsSync(path.join(root, relativePath)));
155
160
  }
156
161
 
162
+ function executableName(value) {
163
+ return path.basename(String(value).trim().replace(/\\/g, '/')).replace(/\.(?:cmd|exe)$/i, '');
164
+ }
165
+
166
+ function arkMcpArgs(server) {
167
+ if (!server || typeof server !== 'object' || typeof server.command !== 'string') return null;
168
+ if (server.args !== undefined && !Array.isArray(server.args)) return null;
169
+ const args = server.args ?? [];
170
+ if (!args.every((value) => typeof value === 'string')) return null;
171
+ const command = executableName(server.command);
172
+ const isArkBin = (value) => /^(?:arkgate-mcp|ark-mcp)(?:\.mjs)?$/.test(executableName(value));
173
+ if ([server.command, ...args].filter(isArkBin).length !== 1) return null;
174
+ if (isArkBin(server.command)) return args;
175
+ if ((command === 'npx' || command === 'yarn') && isArkBin(args[0])) return args.slice(1);
176
+ if (command === 'pnpm') {
177
+ const binIndex =
178
+ args[0] === 'exec'
179
+ ? 1
180
+ : args[0] === '--config.verify-deps-before-run=false' && args[1] === 'exec'
181
+ ? 2
182
+ : -1;
183
+ return binIndex >= 0 && isArkBin(args[binIndex]) ? args.slice(binIndex + 1) : null;
184
+ }
185
+ if (command === 'node') {
186
+ const script = String(args[0] ?? '').replace(/\\/g, '/');
187
+ return /(?:^|\/)bin\/ark-mcp\.mjs$/.test(script) ? args.slice(1) : null;
188
+ }
189
+ return null;
190
+ }
191
+
192
+ function projectBindingArguments(args) {
193
+ if (args.length !== 4) return null;
194
+ const values = {};
195
+ for (let index = 0; index < args.length; index += 2) {
196
+ const name = args[index];
197
+ if ((name !== '--root' && name !== '--config') || values[name] !== undefined) return null;
198
+ const value = args[index + 1];
199
+ if (typeof value !== 'string' || !value.trim() || value.startsWith('-')) return null;
200
+ values[name] = value;
201
+ }
202
+ return values['--root'] && values['--config']
203
+ ? { root: values['--root'], config: values['--config'] }
204
+ : null;
205
+ }
206
+
207
+ function nativePathInput(value) {
208
+ const text = String(value).trim();
209
+ return path.sep === '/' ? text.replace(/\\/g, '/') : text.replace(/\//g, '\\');
210
+ }
211
+
212
+ function canonicalNativePath(value) {
213
+ const absolute = path.resolve(value);
214
+ let canonical = absolute;
215
+ try {
216
+ canonical = fs.realpathSync.native(absolute);
217
+ } catch {
218
+ /* A missing candidate still compares by its normalized absolute path. */
219
+ }
220
+ const normalized = path.normalize(canonical);
221
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
222
+ }
223
+
224
+ function bindingTargetsProject(binding, root, invocationRoot = root) {
225
+ const resolvedRoot = path.resolve(invocationRoot, nativePathInput(binding.root));
226
+ if (canonicalNativePath(resolvedRoot) !== canonicalNativePath(root)) return false;
227
+ const nativeConfig = nativePathInput(binding.config);
228
+ const resolvedConfig = path.isAbsolute(nativeConfig)
229
+ ? nativeConfig
230
+ : path.resolve(resolvedRoot, nativeConfig);
231
+ return (
232
+ canonicalNativePath(resolvedConfig) ===
233
+ canonicalNativePath(path.join(root, 'ark.config.json'))
234
+ );
235
+ }
236
+
237
+ function registrationTargetsProject(server, args, root) {
238
+ const binding = projectBindingArguments(args);
239
+ if (!binding) return false;
240
+ if (server.cwd !== undefined && (typeof server.cwd !== 'string' || !server.cwd.trim())) {
241
+ return false;
242
+ }
243
+ const invocationRoot = server.cwd
244
+ ? path.resolve(root, nativePathInput(server.cwd))
245
+ : root;
246
+ return bindingTargetsProject(binding, root, invocationRoot);
247
+ }
248
+
249
+ export function hasArkMcpRegistration(root, relativePath = '.mcp.json') {
250
+ try {
251
+ const server = readJson(path.join(root, relativePath))?.mcpServers?.ark;
252
+ const args = arkMcpArgs(server);
253
+ return Boolean(args && registrationTargetsProject(server, args, root));
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+
259
+ function commandArkMcpArgs(command) {
260
+ if (typeof command !== 'string') return null;
261
+ const words = [];
262
+ let consumed = 0;
263
+ for (const match of command.matchAll(/"([^"]*)"|'([^']*)'|(&&|\|\||[;|#])|([^\s;&|#]+)/g)) {
264
+ if (command.slice(consumed, match.index).trim() || match[3]) return null;
265
+ words.push(match[1] ?? match[2] ?? match[4]);
266
+ consumed = Number(match.index) + match[0].length;
267
+ }
268
+ if (command.slice(consumed).trim()) return null;
269
+ return arkMcpArgs({ command: words[0], args: words.slice(1) });
270
+ }
271
+
272
+ function codexHookArguments(args, expectedModes) {
273
+ const allowedModes = new Set(expectedModes);
274
+ const seenModes = new Set();
275
+ const values = {};
276
+ for (let index = 0; index < args.length; index += 1) {
277
+ const name = args[index];
278
+ if (allowedModes.has(name)) {
279
+ if (seenModes.has(name)) return null;
280
+ seenModes.add(name);
281
+ continue;
282
+ }
283
+ if (!['--root', '--root-env', '--config'].includes(name) || values[name] !== undefined) {
284
+ return null;
285
+ }
286
+ const value = args[++index];
287
+ if (typeof value !== 'string' || !value.trim() || value.startsWith('-')) return null;
288
+ values[name] = value;
289
+ }
290
+ if (
291
+ seenModes.size !== allowedModes.size ||
292
+ !values['--root'] ||
293
+ !values['--root-env'] ||
294
+ !values['--config']
295
+ ) {
296
+ return null;
297
+ }
298
+ return {
299
+ root: values['--root'],
300
+ rootEnv: values['--root-env'],
301
+ config: values['--config'],
302
+ };
303
+ }
304
+
305
+ function codexHookCommandIsValid(command, root, expectedModes) {
306
+ const args = commandArkMcpArgs(command);
307
+ if (!args) return false;
308
+ const binding = codexHookArguments(args, expectedModes);
309
+ return Boolean(
310
+ binding &&
311
+ binding.rootEnv === 'CODEX_PROJECT_DIR' &&
312
+ bindingTargetsProject(binding, root)
313
+ );
314
+ }
315
+
316
+ function matcherHasExactTools(matcher, expectedTools) {
317
+ if (typeof matcher !== 'string') return false;
318
+ const tools = matcher.split('|').map((tool) => tool.trim()).filter(Boolean);
319
+ const unique = new Set(tools);
320
+ return (
321
+ tools.length === expectedTools.length &&
322
+ unique.size === expectedTools.length &&
323
+ expectedTools.every((tool) => unique.has(tool))
324
+ );
325
+ }
326
+
327
+ function hookGroupHasValidCodexContract(group, root, expectedModes, expectedTools = null) {
328
+ if (!Array.isArray(group)) return false;
329
+ return group.some(
330
+ (entry) =>
331
+ entry &&
332
+ typeof entry === 'object' &&
333
+ (!expectedTools || matcherHasExactTools(entry.matcher, expectedTools)) &&
334
+ Array.isArray(entry.hooks) &&
335
+ entry.hooks.some(
336
+ (hook) =>
337
+ hook &&
338
+ typeof hook === 'object' &&
339
+ hook.type === 'command' &&
340
+ codexHookCommandIsValid(hook.command, root, expectedModes)
341
+ )
342
+ );
343
+ }
344
+
345
+ function hasCodexCompactRegistration(root) {
346
+ try {
347
+ const config = fs.readFileSync(path.join(root, '.codex', 'config.toml'), 'utf8');
348
+ const hooks = readJson(path.join(root, '.codex', 'hooks.json'))?.hooks;
349
+ return (
350
+ codexProjectMcpIsValid(config, root) &&
351
+ hookGroupHasValidCodexContract(hooks?.SessionStart, root, ['--session-context']) &&
352
+ hookGroupHasValidCodexContract(hooks?.PreToolUse, root, [
353
+ '--hook',
354
+ '--hook-repair',
355
+ '--fail-on-new-smells',
356
+ ], ['ApplyPatch', 'apply_patch', 'Write', 'Edit', 'MultiEdit'])
357
+ );
358
+ } catch {
359
+ return false;
360
+ }
361
+ }
362
+
363
+ function architectureScript(root) {
364
+ try {
365
+ const script = readPackageJson(root)?.scripts?.['check:architecture'];
366
+ return typeof script === 'string' ? script : '';
367
+ } catch {
368
+ return '';
369
+ }
370
+ }
371
+
372
+ function isFailClosedArchitectureScript(script) {
373
+ if (!script) return false;
374
+ if (
375
+ /(?:^|;|\n|(?<!&)&(?!&))\s*exit(?:\s+\/b)?\s+0(?=\s*(?:;|&&|\|\||#|$))/im.test(
376
+ script
377
+ )
378
+ ) {
379
+ return false;
380
+ }
381
+ const body = script
382
+ .split('\n')
383
+ .map((line) => ` ${line}`)
384
+ .join('\n');
385
+ const workflow = `jobs:
386
+ ark:
387
+ runs-on: ubuntu-latest
388
+ steps:
389
+ - run: |
390
+ ${body}
391
+ `;
392
+ return FAIL_CLOSED_ARK_FLAG.test(enforcingArkRunText(workflow));
393
+ }
394
+
395
+ export function hasArkAgentsContract(root) {
396
+ try {
397
+ const content = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8');
398
+ const directCheck =
399
+ /\b(?:arkgate-check|ark-check)\b[\s\S]{0,240}--(?:strict-config|strict-merge|strict)\b/.test(
400
+ content
401
+ );
402
+ const scriptCheck =
403
+ /\b(?:npm|pnpm)\s+run\s+check:architecture\b|\byarn(?:\s+run)?\s+check:architecture\b/.test(
404
+ content
405
+ ) && isFailClosedArchitectureScript(architectureScript(root));
406
+ return (
407
+ /^#{1,6}\s+Ark(?:Gate)?\s+Enforcement\b/im.test(content) &&
408
+ /\bark\.config\.json\b/i.test(content) &&
409
+ /\bauthoritative\b/i.test(content) &&
410
+ (directCheck || scriptCheck)
411
+ );
412
+ } catch {
413
+ return false;
414
+ }
415
+ }
416
+
417
+ function withFailClosedArkActions(content) {
418
+ const lines = String(content).split('\n');
419
+ for (let index = 0; index < lines.length; index += 1) {
420
+ const match = lines[index].match(
421
+ /^(\s*)(-\s+)?uses:\s*['"]?pedroknigge\/arkgate@[^'"\s#]+['"]?\s*(?:#.*)?$/i
422
+ );
423
+ if (!match) continue;
424
+ const propertyIndent = match[1].length + (match[2] ? 2 : 0);
425
+ let start = index;
426
+ let stepIndent = match[2] ? match[1].length : null;
427
+ if (stepIndent === null) {
428
+ for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
429
+ const indent = lines[cursor].match(/^\s*/)?.[0].length ?? 0;
430
+ if (/^\s*-\s+/.test(lines[cursor]) && indent < propertyIndent) {
431
+ start = cursor;
432
+ stepIndent = indent;
433
+ break;
434
+ }
435
+ }
436
+ }
437
+ if (stepIndent === null) continue;
438
+ let end = lines.length;
439
+ for (let cursor = start + 1; cursor < lines.length; cursor += 1) {
440
+ if (!lines[cursor].trim()) continue;
441
+ const indent = lines[cursor].match(/^\s*/)?.[0].length ?? 0;
442
+ if (indent < stepIndent || (indent === stepIndent && /^\s*-\s+/.test(lines[cursor]))) {
443
+ end = cursor;
444
+ break;
445
+ }
446
+ }
447
+ const block = lines.slice(start, end).join('\n');
448
+ const strictInput = block.match(/^\s*strict-config:\s*(.*?)\s*(?:#.*)?$/im)?.[1];
449
+ if (strictInput !== undefined && !/^['"]?true['"]?$/i.test(strictInput)) {
450
+ continue;
451
+ }
452
+ lines[index] = lines[index].replace(/\buses:/, 'run:').replace(
453
+ /['"]?pedroknigge\/arkgate@[^'"\s#]+['"]?/i,
454
+ 'ark-check --strict-merge'
455
+ );
456
+ }
457
+ return lines.join('\n');
458
+ }
459
+
460
+ function workflowJobSections(content) {
461
+ const lines = String(content).split('\n');
462
+ const jobsIndex = lines.findIndex((line) =>
463
+ /^\s*(?:"jobs"|'jobs'|jobs):\s*(?:#.*)?$/.test(line)
464
+ );
465
+ if (jobsIndex < 0) return { lines, jobs: [] };
466
+ const jobsIndent = lines[jobsIndex].match(/^\s*/)?.[0].length ?? 0;
467
+ let jobIndent = null;
468
+ let jobsEnd = lines.length;
469
+ const headers = [];
470
+ for (let index = jobsIndex + 1; index < lines.length; index += 1) {
471
+ if (!lines[index].trim() || /^\s*#/.test(lines[index])) continue;
472
+ const indent = lines[index].match(/^\s*/)?.[0].length ?? 0;
473
+ if (indent <= jobsIndent) {
474
+ jobsEnd = index;
475
+ break;
476
+ }
477
+ const header = lines[index].match(
478
+ /^\s*(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+)):\s*(?:#.*)?$/
479
+ );
480
+ if (!header) continue;
481
+ jobIndent ??= indent;
482
+ if (indent === jobIndent) {
483
+ headers.push({ id: header[1] ?? header[2] ?? header[3], start: index });
484
+ }
485
+ }
486
+ const jobs = headers.map((header, index) => {
487
+ const end = headers[index + 1]?.start ?? jobsEnd;
488
+ const propertyIndents = lines
489
+ .slice(header.start + 1, end)
490
+ .filter((line) => line.trim() && !/^\s*#/.test(line))
491
+ .map((line) => line.match(/^\s*/)?.[0].length ?? 0)
492
+ .filter((indent) => indent > Number(jobIndent));
493
+ return {
494
+ ...header,
495
+ end,
496
+ propertyIndent:
497
+ propertyIndents.length > 0 ? Math.min(...propertyIndents) : Number(jobIndent) + 2,
498
+ };
499
+ });
500
+ return { lines, jobs };
501
+ }
502
+
503
+ function jobProperty(lines, job, name) {
504
+ const matcher = new RegExp(
505
+ `^\\s*(?:"${name}"|'${name}'|${name}):\\s*(.*?)\\s*(?:#.*)?$`,
506
+ 'i'
507
+ );
508
+ for (let index = job.start + 1; index < job.end; index += 1) {
509
+ if ((lines[index].match(/^\s*/)?.[0].length ?? 0) !== job.propertyIndent) continue;
510
+ const match = lines[index].match(matcher);
511
+ if (match) return { index, value: match[1].trim() };
512
+ }
513
+ return null;
514
+ }
515
+
516
+ function unquoteYamlScalar(value) {
517
+ const text = String(value).trim();
518
+ const match = text.match(/^(['"])(.*)\1$/);
519
+ return match ? match[2].trim() : text;
520
+ }
521
+
522
+ function jobCondition(lines, job) {
523
+ const condition = jobProperty(lines, job, 'if');
524
+ if (!condition) return 'default';
525
+ const value = unquoteYamlScalar(condition.value);
526
+ if (/^(?:\$\{\{\s*)?always\(\)(?:\s*\}\})?$/i.test(value)) return 'always';
527
+ if (/^(?:true|\$\{\{\s*true\s*\}\})$/i.test(value)) return 'true';
528
+ return 'conditional';
529
+ }
530
+
531
+ function jobNeeds(lines, job) {
532
+ const property = jobProperty(lines, job, 'needs');
533
+ if (!property) return { ids: [], indexes: [], valid: true };
534
+ const indexes = [property.index];
535
+ if (property.value) {
536
+ const value = unquoteYamlScalar(property.value);
537
+ const raw = value.startsWith('[') && value.endsWith(']')
538
+ ? value.slice(1, -1).split(',')
539
+ : [value];
540
+ const ids = raw.map(unquoteYamlScalar).filter((id) => /^[A-Za-z0-9_-]+$/.test(id));
541
+ return { ids, indexes, valid: ids.length === raw.length && ids.length > 0 };
542
+ }
543
+ const ids = [];
544
+ for (let index = property.index + 1; index < job.end; index += 1) {
545
+ if (!lines[index].trim() || /^\s*#/.test(lines[index])) {
546
+ indexes.push(index);
547
+ continue;
548
+ }
549
+ const indent = lines[index].match(/^\s*/)?.[0].length ?? 0;
550
+ if (indent <= job.propertyIndent) break;
551
+ indexes.push(index);
552
+ const item = lines[index].match(/^\s*-\s*(['"]?)([A-Za-z0-9_-]+)\1\s*(?:#.*)?$/);
553
+ if (!item) return { ids: [], indexes, valid: false };
554
+ ids.push(item[2]);
555
+ }
556
+ return { ids, indexes, valid: ids.length > 0 };
557
+ }
558
+
559
+ function withVerifiedDependencyJobs(content) {
560
+ const { lines, jobs } = workflowJobSections(content);
561
+ const byId = new Map(jobs.map((job) => [job.id, job]));
562
+ const guaranteed = (job, seen = new Set()) => {
563
+ if (!job || seen.has(job.id)) return false;
564
+ const condition = jobCondition(lines, job);
565
+ if (condition === 'conditional') return false;
566
+ if (condition === 'always') return true;
567
+ const needs = jobNeeds(lines, job);
568
+ if (!needs.valid) return false;
569
+ const nextSeen = new Set(seen).add(job.id);
570
+ return needs.ids.every((id) => guaranteed(byId.get(id), nextSeen));
571
+ };
572
+ for (const job of jobs) {
573
+ const needs = jobNeeds(lines, job);
574
+ if (
575
+ needs.valid &&
576
+ needs.ids.length > 0 &&
577
+ needs.ids.every((id) => guaranteed(byId.get(id)))
578
+ ) {
579
+ // The shared analyzer treats every `needs` as skippable. Hide it only after
580
+ // this dependency chain is proven unconditional; keep uncertain/skipped needs visible.
581
+ for (const index of needs.indexes) lines[index] = '';
582
+ }
583
+ }
584
+ return lines.join('\n');
585
+ }
586
+
157
587
  export function hasArkWorkflow(root) {
158
588
  const workflowsDir = path.join(root, '.github', 'workflows');
159
589
  if (!fs.existsSync(workflowsDir)) return false;
590
+ const declaredScript = architectureScript(root);
591
+ const script = isFailClosedArchitectureScript(declaredScript) ? declaredScript : '';
160
592
  return fs
161
593
  .readdirSync(workflowsDir)
162
594
  .filter((file) => /\.ya?ml$/i.test(file))
163
595
  .some((file) => {
164
596
  try {
165
597
  const content = fs.readFileSync(path.join(workflowsDir, file), 'utf8');
166
- return (
167
- /\bark-check\b/.test(content) ||
168
- /\bcheck:architecture\b/.test(content) ||
169
- /\buses\s*:\s*['"]?[^'"\s#]+\/arkgate@/i.test(content)
598
+ return FAIL_CLOSED_ARK_FLAG.test(
599
+ enforcingArkRunText(
600
+ withVerifiedDependencyJobs(withFailClosedArkActions(content)),
601
+ script
602
+ )
170
603
  );
171
604
  } catch {
172
605
  return false;
@@ -176,10 +609,9 @@ export function hasArkWorkflow(root) {
176
609
 
177
610
  export function missingGates(root) {
178
611
  const compactHost = compactRouterHost(root);
179
- const required = compactHost
180
- ? REQUIRED_GATE_FILES.filter((relativePath) => relativePath !== '.mcp.json')
181
- : REQUIRED_GATE_FILES;
182
- const missing = required.filter((relativePath) => !fs.existsSync(path.join(root, relativePath)));
612
+ const missing = [];
613
+ if (!hasArkAgentsContract(root)) missing.push('AGENTS.md');
614
+ if (!compactHost && !hasArkMcpRegistration(root)) missing.push('.mcp.json');
183
615
  if (compactHost && !hasCompactHostRegistration(root, compactHost)) {
184
616
  missing.push(`compact host registration (${compactHost})`);
185
617
  }
@@ -51,7 +51,14 @@ function shellSegments(text) {
51
51
  index++;
52
52
  continue;
53
53
  }
54
- if (char === ';' || char === '|' || char === '\n') {
54
+ if (
55
+ char === '&' &&
56
+ (input[index - 1] === '>' || input[index - 1] === '<' || input[index + 1] === '>')
57
+ ) {
58
+ current += char;
59
+ continue;
60
+ }
61
+ if (char === ';' || char === '|' || char === '&' || char === '\n') {
55
62
  push(char);
56
63
  continue;
57
64
  }
@@ -70,12 +77,18 @@ function analyzeCommands(commands, script = '') {
70
77
  .filter((segment) => DIRECT_ARK.test(executableText(segment.text)))
71
78
  .map((segment) => ({
72
79
  text: segment.text,
73
- enforcing: segment.terminator !== '||' && segment.terminator !== '|',
80
+ enforcing:
81
+ segment.terminator !== '||' &&
82
+ segment.terminator !== '|' &&
83
+ segment.terminator !== '&',
74
84
  }));
75
85
  const found = [];
76
86
  for (const segment of shellSegments(commands)) {
77
87
  const executable = executableText(segment.text);
78
- const outerEnforcing = segment.terminator !== '||' && segment.terminator !== '|';
88
+ const outerEnforcing =
89
+ segment.terminator !== '||' &&
90
+ segment.terminator !== '|' &&
91
+ segment.terminator !== '&';
79
92
  if (DIRECT_ARK.test(executable)) {
80
93
  found.push({ text: segment.text, enforcing: outerEnforcing });
81
94
  } else if (CHECK_SCRIPT.test(executable)) {
@@ -1,19 +1,20 @@
1
1
  // Generated from hook-templates.source.mjs — run npm run generate:packaged-tooling.
2
- import{execCommandParts as i,execRunner as s}from"../ark-shared.mjs";const c="arkgate-mcp";function l(e){const r=s(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${r} ${c} --session-context --root "$CLAUDE_PROJECT_DIR" --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root "$CLAUDE_PROJECT_DIR" --config ark.config.json`}]}]}},null,2)}
3
- `}function p(e){const r=s(e),o="${CODEX_PROJECT_DIR:-${PWD:-.}}";return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${c} --session-context --root "${o}" --config ark.config.json`}]}],PreToolUse:[{matcher:"ApplyPatch|apply_patch|Write|Edit|MultiEdit",hooks:[{type:"command",timeout:30,command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root "${o}" --config ark.config.json`}]}]}},null,2)}
4
- `}function g(e){const{command:r,args:o}=i(e,c,["--root",".","--config","ark.config.json"]),t=a=>a.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),n=o.map(a=>`"${t(a)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
5
- # Restart Codex after changes; MCP servers are loaded when the project session starts.
2
+ import{execCommandParts as i,execRunner as s}from"../ark-shared.mjs";const c="arkgate-mcp";function l(o){const r=s(o);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${r} ${c} --session-context --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
3
+ `}function p(o){const r=s(o);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${c} --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} ${c} --hook --hook-repair --fail-on-new-smells --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
4
+ `}function f(o){const{command:r,args:e}=i(o,c,["--root",".","--config","ark.config.json"]),t=a=>a.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),n=e.map(a=>`"${t(a)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
5
+ # CONFIGURED ON DISK \u2014 RUNTIME NOT VERIFIED.
6
+ # Restart Codex, then call ark_identity with expectedRoot before trusting MCP verdicts.
6
7
  [mcp_servers.ark]
7
8
  command = "${t(r)}"
8
9
  args = [${n}]
9
- `}function f(e){const{command:r,args:o}=i(e,c,["--root",".","--config","ark.config.json"]),t=o.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
10
+ `}function g(o){const{command:r,args:e}=i(o,c,["--root",".","--config","ark.config.json"]),t=e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
10
11
  # Restart Grok (or /mcps \u2192 refresh) after changes. Also loads repo-root .mcp.json.
11
12
  [mcp_servers.ark]
12
13
  command = "${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"
13
14
  args = [${t}]
14
- `}function u(e){const r=s(e),o="${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}";return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${c} --session-context --root "${o}" --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit|write|search_replace",hooks:[{type:"command",timeout:30,command:`${r} ${c} --hook --hook-repair --fail-on-new-smells --root "${o}" --config ark.config.json`}]}]}},null,2)}
15
- `}function k(e){const r=s(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} ${c} --hook --hook-repair --fail-on-new-smells --root "\${PWD:-.}" --config ark.config.json`}]}]}},null,2)}
16
- `}function d(e){const{command:r,args:o}=i(e,c,["--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)}
17
- `}function $(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 n=t["ark-write-gate"];if(!n||typeof n!="object")return null;const a={...o,"ark-write-gate":n};return`${JSON.stringify(a,null,2)}
18
- `}function h(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 n={...o};!n.$schema&&t.$schema&&(n.$schema=t.$schema);const a=o.mcp&&typeof o.mcp=="object"&&!Array.isArray(o.mcp)?{...o.mcp}:{};return a.ark=t.mcp.ark,n.mcp=a,`${JSON.stringify(n,null,2)}
19
- `}export{c as PREFERRED_MCP_BIN,k as antigravityHooks,l as claudeSettings,p as codexHooks,g as codexProjectConfig,u as grokHooks,f as grokProjectConfig,$ as mergeAntigravityArkHook,h as mergeOpencodeArkMcp,d as opencodeProjectConfig};
15
+ `}function u(o){const r=s(o);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${c} --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} ${c} --hook --hook-repair --fail-on-new-smells --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
16
+ `}function k(o){const r=s(o);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} ${c} --hook --hook-repair --fail-on-new-smells --root . --config ark.config.json`}]}]}},null,2)}
17
+ `}function d(o){const{command:r,args:e}=i(o,c,["--root",".","--config","ark.config.json"]);return`${JSON.stringify({$schema:"https://opencode.ai/config.json",mcp:{ark:{type:"local",command:[r,...e],enabled:!0}}},null,2)}
18
+ `}function h(o,r){let e,t;try{e=o&&o.trim()?JSON.parse(o):{},t=JSON.parse(r)}catch{return null}if(!e||typeof e!="object"||Array.isArray(e)||!t||typeof t!="object"||Array.isArray(t))return null;const n=t["ark-write-gate"];if(!n||typeof n!="object")return null;const a={...e,"ark-write-gate":n};return`${JSON.stringify(a,null,2)}
19
+ `}function y(o,r){let e,t;try{e=o&&o.trim()?JSON.parse(o):{},t=JSON.parse(r)}catch{return null}if(!e||typeof e!="object"||Array.isArray(e))return null;const n={...e};!n.$schema&&t.$schema&&(n.$schema=t.$schema);const a=e.mcp&&typeof e.mcp=="object"&&!Array.isArray(e.mcp)?{...e.mcp}:{};return a.ark=t.mcp.ark,n.mcp=a,`${JSON.stringify(n,null,2)}
20
+ `}export{c as PREFERRED_MCP_BIN,k as antigravityHooks,l as claudeSettings,p as codexHooks,f as codexProjectConfig,u as grokHooks,g as grokProjectConfig,h as mergeAntigravityArkHook,y as mergeOpencodeArkMcp,d as opencodeProjectConfig};