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
@@ -9,10 +9,13 @@ import {
9
9
  antigravityHooks,
10
10
  claudeSettings,
11
11
  codexHooks,
12
+ codexProjectConfig,
12
13
  grokHooks,
13
14
  grokProjectConfig,
14
15
  opencodeProjectConfig,
15
16
  } from './hook-templates.mjs';
17
+ import { codexPrimaryTable, codexProjectMcpIsValid } from './codex-home.mjs';
18
+ import { codexRuntimeActivation } from './enforcement-state.mjs';
16
19
 
17
20
  const COMPACT_HOST_TEMPLATES = {
18
21
  claude: (root) => [
@@ -28,7 +31,10 @@ const COMPACT_HOST_TEMPLATES = {
28
31
  ['.mcp.json', mcpJson(root)],
29
32
  ],
30
33
  cursor: (root) => [['.cursor/mcp.json', mcpJson(root)]],
31
- codex: (root) => [['.codex/hooks.json', codexHooks(root)]],
34
+ codex: (root) => [
35
+ ['.codex/hooks.json', codexHooks(root)],
36
+ ['.codex/config.toml', codexProjectConfig(root)],
37
+ ],
32
38
  opencode: (root) => [['opencode.json', opencodeProjectConfig(root)]],
33
39
  windsurf: (root) => [['.windsurf/rules/ark.md', instructionRule(root)]],
34
40
  cline: (root) => [['.clinerules/ark.md', instructionRule(root)]],
@@ -159,6 +165,12 @@ export function renderStartPreview(preview, options = {}) {
159
165
  }
160
166
  console.log('Host guarantees:');
161
167
  for (const guarantee of preview.hostGuarantees) console.log(` ${guarantee}`);
168
+ if (preview.runtimeActivation) {
169
+ console.log('Codex MCP CONFIGURED — RUNTIME NOT VERIFIED.');
170
+ console.log(` Runtime activation: ${JSON.stringify(preview.runtimeActivation)}`);
171
+ console.log(` Restart Codex, then call ark_identity with expectedRoot "${preview.root}".`);
172
+ console.log(' Do not trust MCP verdicts before the project identity matches.');
173
+ }
162
174
  if (preview.unresolvedDecisions.length > 0) {
163
175
  console.log('Unresolved decisions:');
164
176
  for (const decision of preview.unresolvedDecisions) console.log(` ${decision}`);
@@ -198,6 +210,31 @@ function planHostRemoval(args, helpers) {
198
210
  const target = path.join(args.root, relativePath);
199
211
  if (!fs.existsSync(target)) continue;
200
212
  const before = fs.readFileSync(target);
213
+ if (host === 'codex' && relativePath === '.codex/config.toml') {
214
+ const currentText = before.toString('utf8');
215
+ const currentTable = codexPrimaryTable(currentText);
216
+ const expectedTable = codexPrimaryTable(expected);
217
+ if (!currentTable) continue;
218
+ if (
219
+ !expectedTable ||
220
+ currentTable.block.trimEnd() !== expectedTable.block.trimEnd()
221
+ ) {
222
+ unresolvedDecisions.push(
223
+ '.codex/config.toml Ark MCP binding was customized and was left untouched.'
224
+ );
225
+ continue;
226
+ }
227
+ const expectedPrelude = expected.slice(0, expectedTable.start);
228
+ let prefix = currentText.slice(0, currentTable.start);
229
+ if (expectedPrelude && prefix.endsWith(expectedPrelude)) {
230
+ prefix = prefix.slice(0, -expectedPrelude.length);
231
+ }
232
+ let next = `${prefix}${currentText.slice(currentTable.end)}`;
233
+ if (prefix.length === 0) next = next.replace(/^\n+/, '');
234
+ const after = next.trim().length > 0 ? Buffer.from(next) : null;
235
+ changes.push(change(relativePath, before, after));
236
+ continue;
237
+ }
201
238
  if (before.toString('utf8') !== expected) {
202
239
  unresolvedDecisions.push(`${relativePath} was customized and was left untouched.`);
203
240
  continue;
@@ -220,6 +257,32 @@ function planHostRemoval(args, helpers) {
220
257
  changes.push(change('.mcp.json', null, Buffer.from(mcpJson(args.root))));
221
258
  }
222
259
 
260
+ const runtimeActivation =
261
+ host === 'codex'
262
+ ? codexRuntimeActivation({
263
+ configuredOnDisk: (() => {
264
+ const configPath = path.join(args.root, '.codex', 'config.toml');
265
+ const plannedConfig = changes.find(
266
+ (item) => item.path === '.codex/config.toml'
267
+ );
268
+ if (plannedConfig?.action === 'delete') return false;
269
+ if (plannedConfig?.afterBase64) {
270
+ return codexProjectMcpIsValid(
271
+ Buffer.from(plannedConfig.afterBase64, 'base64').toString('utf8'),
272
+ args.root
273
+ );
274
+ }
275
+ if (!fs.existsSync(configPath)) return false;
276
+ try {
277
+ return codexProjectMcpIsValid(fs.readFileSync(configPath, 'utf8'), args.root);
278
+ } catch {
279
+ return false;
280
+ }
281
+ })(),
282
+ restartRequired: true,
283
+ })
284
+ : null;
285
+
223
286
  return {
224
287
  version: 1,
225
288
  root: args.root,
@@ -233,6 +296,7 @@ function planHostRemoval(args, helpers) {
233
296
  commands: commands(args.root, args, helpers),
234
297
  hostGuarantees: ['host removal is limited to Ark-owned compact artifacts', 'restore with the displayed --tools command'],
235
298
  unresolvedDecisions,
299
+ ...(runtimeActivation ? { runtimeActivation } : {}),
236
300
  };
237
301
  }
238
302
 
@@ -312,6 +376,18 @@ export async function planStart(args, helpers) {
312
376
  changes.push(change(file, oldContent, newContent));
313
377
  }
314
378
  const percent = coverage.governed?.percent ?? null;
379
+ const codexSelected = args.tools === 'codex';
380
+ const runtimeActivation = codexSelected
381
+ ? codexRuntimeActivation({
382
+ configuredOnDisk: (() => {
383
+ const config = after.get('.codex/config.toml');
384
+ return Boolean(
385
+ config && codexProjectMcpIsValid(config.toString('utf8'), root)
386
+ );
387
+ })(),
388
+ restartRequired: true,
389
+ })
390
+ : null;
315
391
  return {
316
392
  version: 1,
317
393
  root,
@@ -329,8 +405,15 @@ export async function planStart(args, helpers) {
329
405
  args.requireWriteHook ? `Hard-write hook verified for ${args.requireWriteHook}` : 'shared CI merge gate will be installed',
330
406
  'preview phase performs no writes in the target project',
331
407
  'apply writes the exact bytes identified by each afterHash',
408
+ ...(codexSelected
409
+ ? [
410
+ 'Codex MCP is configured on disk, not runtime-verified',
411
+ 'restart Codex and match ark_identity expectedRoot before trusting MCP verdicts',
412
+ ]
413
+ : []),
332
414
  ],
333
415
  unresolvedDecisions: percent !== null && percent < 90 ? [`Projected governed coverage is ${percent}%; review unclassified files before treating the contract as complete.`] : [],
416
+ ...(runtimeActivation ? { runtimeActivation } : {}),
334
417
  };
335
418
  } finally {
336
419
  fs.rmSync(shadowRoot, { recursive: true, force: true });
@@ -427,7 +427,7 @@ export function runUpgradeCommand(args, dependencies) {
427
427
  const needsApply = wouldWrite > 0 || blocked > 0;
428
428
  const command = buildUpgradeNextCommand(args, plan.planDigest);
429
429
  if (args.json) {
430
- // Always expose nextCommand for digest-bound apply (metadata/manifest optional);
430
+ // Always expose nextCommand for digest-bound content/manifest apply;
431
431
  // nothingToApply flags when content writes are zero so UIs do not urge apply.
432
432
  console.log(
433
433
  managedUpgradeJson(plan, {
@@ -443,15 +443,12 @@ export function runUpgradeCommand(args, dependencies) {
443
443
  })
444
444
  );
445
445
  } else {
446
- const metadataRefresh = plan.summary?.metadataRefresh ?? 0;
447
446
  renderManagedUpgrade(plan, {
448
447
  next: needsApply
449
448
  ? args.install
450
449
  ? `Update the package and recompute this preview with: ${command}`
451
450
  : `Apply the exact preview with: ${command}`
452
451
  : undefined,
453
- // Human path: optional digest-bound stamp refresh without urging content apply.
454
- ...( !needsApply && metadataRefresh > 0 ? { optionalStampApply: command } : {}),
455
452
  });
456
453
  if (args.install === false) {
457
454
  console.log(
@@ -481,7 +478,7 @@ export function runUpgradeCommand(args, dependencies) {
481
478
  if (args.json) console.log(JSON.stringify(applied, null, 2));
482
479
  else {
483
480
  renderManagedUpgrade(applied);
484
- console.log('No managed content writes pending (optional stamp refresh needs --plan-digest).');
481
+ console.log('No managed content writes pending.');
485
482
  }
486
483
  return 0;
487
484
  }