arkgate 4.8.11 → 4.8.14

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 (93) hide show
  1. package/CHANGELOG.md +125 -2
  2. package/README.md +39 -46
  3. package/SECURITY.md +5 -3
  4. package/bin/ark-check-runtime.mjs +11 -9
  5. package/bin/lib/agent-projection-formatters.mjs +2 -0
  6. package/bin/lib/agent-skills-package.mjs +63 -8
  7. package/bin/lib/analysis-engine.mjs +4 -4
  8. package/bin/lib/architecture-scan.mjs +8 -2
  9. package/bin/lib/ark-order-doctor.mjs +160 -0
  10. package/bin/lib/ark-order-report.mjs +65 -0
  11. package/bin/lib/ark-order-sensors.mjs +1 -1
  12. package/bin/lib/ci-and-commands.mjs +7 -2
  13. package/bin/lib/design-smells.mjs +21 -1
  14. package/bin/lib/diagnostic-catalog.mjs +4 -4
  15. package/bin/lib/doctor-advisories.mjs +99 -18
  16. package/bin/lib/doctor-human.mjs +4 -11
  17. package/bin/lib/doctor-plan.mjs +4 -3
  18. package/bin/lib/extra-merge-teeth.mjs +32 -4
  19. package/bin/lib/first-run-help.mjs +11 -2
  20. package/bin/lib/gate-files.mjs +40 -3
  21. package/bin/lib/html-report-advisories.mjs +2 -0
  22. package/bin/lib/html-report-depth.mjs +8 -18
  23. package/bin/lib/html-report.mjs +16 -0
  24. package/bin/lib/install-migrate.mjs +23 -0
  25. package/bin/lib/mcp-hook-payload.mjs +1 -1
  26. package/bin/lib/product-copy.mjs +4 -0
  27. package/bin/lib/remediation.mjs +7 -7
  28. package/bin/lib/resolved-candidate-facts.mjs +144 -36
  29. package/bin/lib/rules-under-contract.mjs +14 -0
  30. package/bin/lib/scan-files.mjs +39 -0
  31. package/bin/lib/start-preview.mjs +4 -0
  32. package/bin/lib/status-command.mjs +28 -0
  33. package/bin/lib/status-manifest.mjs +23 -0
  34. package/bin/lib/upgrade-whats-new.mjs +3 -3
  35. package/bin/lib/violations.mjs +40 -1
  36. package/dist/{diagnosticCatalog-DiflIock.d.ts → diagnosticCatalog-DVx_2RmF.d.ts} +1 -1
  37. package/dist/eslint/index.cjs +4 -4
  38. package/dist/eslint/index.js +4 -4
  39. package/dist/index.cjs +31 -31
  40. package/dist/index.d.ts +129 -15
  41. package/dist/index.js +31 -31
  42. package/dist/nestjs/index.cjs +1 -1
  43. package/dist/nestjs/index.js +1 -1
  44. package/dist/runtime/index.cjs +15 -15
  45. package/dist/runtime/index.d.ts +1 -1
  46. package/dist/runtime/index.js +15 -15
  47. package/docs/README.md +11 -8
  48. package/docs/agent-guide.md +30 -13
  49. package/docs/ai-gates.md +3 -1
  50. package/docs/arkorder.md +35 -10
  51. package/docs/configuration.md +7 -6
  52. package/docs/develop.md +3 -1
  53. package/docs/diagnostics.md +7 -7
  54. package/docs/enthusiast/README.md +6 -1
  55. package/docs/enthusiast/how-to-gallery-starter.md +2 -1
  56. package/docs/package-surface.md +8 -5
  57. package/docs/product-voice.md +40 -13
  58. package/docs/threat-model.md +2 -2
  59. package/docs/typescript-support.md +3 -3
  60. package/docs/use.md +18 -11
  61. package/package.json +1 -1
  62. package/schemas/ark.status-manifest.schema.json +47 -0
  63. package/server.json +2 -2
  64. package/templates/agent-skills/README.md +7 -4
  65. package/templates/agent-skills/ark-adopt/SKILL.md +9 -5
  66. package/templates/agent-skills/ark-architect/SKILL.md +5 -18
  67. package/templates/agent-skills/ark-autopilot/SKILL.md +8 -4
  68. package/templates/agent-skills/ark-contract/SKILL.md +9 -20
  69. package/templates/agent-skills/ark-coverage/SKILL.md +12 -8
  70. package/templates/agent-skills/ark-explain/SKILL.md +7 -3
  71. package/templates/agent-skills/ark-explore/SKILL.md +25 -4
  72. package/templates/agent-skills/ark-fix/SKILL.md +15 -20
  73. package/templates/agent-skills/ark-loop/SKILL.md +14 -20
  74. package/templates/agent-skills/ark-order/SKILL.md +200 -0
  75. package/templates/agent-skills/ark-place/SKILL.md +11 -8
  76. package/templates/agent-skills/ark-runtime/SKILL.md +17 -4
  77. package/templates/agent-skills/ark-think/SKILL.md +24 -126
  78. package/templates/agent-skills/ark-upgrade/SKILL.md +13 -2
  79. package/templates/skills/ark-adopt.md +9 -5
  80. package/templates/skills/ark-architect.md +5 -18
  81. package/templates/skills/ark-autopilot.md +8 -4
  82. package/templates/skills/ark-contract.md +9 -20
  83. package/templates/skills/ark-coverage.md +12 -8
  84. package/templates/skills/ark-explain.md +7 -3
  85. package/templates/skills/ark-explore.md +25 -4
  86. package/templates/skills/ark-fix.md +15 -20
  87. package/templates/skills/ark-loop.md +14 -20
  88. package/templates/skills/ark-order.md +200 -0
  89. package/templates/skills/ark-place.md +11 -8
  90. package/templates/skills/ark-runtime.md +17 -4
  91. package/templates/skills/ark-think.md +24 -126
  92. package/templates/skills/ark-upgrade.md +13 -2
  93. package/templates/tests/ark-adoption-gaps.test.ts +5 -4
@@ -80,7 +80,7 @@ export function demoteExtraPlaneTeethUnderClassificationFloor(violations, classi
80
80
  return violations;
81
81
  }
82
82
  /** Stamp for extra-plane honesty: never one architecture score. */
83
- export const MERGE_PLANES_DUAL_STAMP = 'Structure = heuristics; invariants = catalog+coverage evidence (not business runtime); ArkRun = kernel usage + declarations (not a score); ArkOrder = pattern slaving (not a score). Extra planes never merge into one architecture score. Advisory ArkRules ≠ merge teeth. Advisory ArkRun ≠ merge teeth. Advisory ArkOrder ≠ merge teeth.';
83
+ export const MERGE_PLANES_DUAL_STAMP = 'Structure = heuristics; invariants = catalog+coverage evidence (not business runtime); ArkRun = kernel usage + declarations (not a score); ArkOrder = the few big product choices (not a score). Extra planes never merge into one architecture score. Advisory ArkRules ≠ merge teeth. Advisory ArkRun ≠ merge teeth. Advisory ArkOrder ≠ merge teeth.';
84
84
  function countOrZero(value) {
85
85
  return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
86
86
  }
@@ -112,7 +112,14 @@ export function composeMergePlanesHonesty(input = {}) {
112
112
  const arkRunResidual = countOrZero(input.arkRun?.residualCount);
113
113
  const arkRunHasEnforced = arkRunPresent && arkRunMode === 'enforced';
114
114
  const arkRunTeeth = arkRunHasEnforced && classificationAllowsTeeth;
115
- const hasEnforcedTeeth = arkRulesHasEnforced || arkRunHasEnforced;
115
+ const arkOrderPresent = input.arkOrder?.present === true;
116
+ const arkOrderMode = input.arkOrder?.mode === 'enforced' || input.arkOrder?.mode === 'advisory'
117
+ ? input.arkOrder.mode
118
+ : null;
119
+ const arkOrderResidual = countOrZero(input.arkOrder?.residualCount);
120
+ const arkOrderHasEnforced = arkOrderPresent && arkOrderMode === 'enforced';
121
+ const arkOrderTeeth = arkOrderHasEnforced && classificationAllowsTeeth;
122
+ const hasEnforcedTeeth = arkRulesHasEnforced || arkRunHasEnforced || arkOrderHasEnforced;
116
123
  const extraMergeTeeth = hasEnforcedTeeth && classificationAllowsTeeth;
117
124
  const teethDeferredForClassification = hasEnforcedTeeth && classificationKnown && !classificationAllowsTeeth;
118
125
  let failMergeWhen;
@@ -122,12 +129,15 @@ export function composeMergePlanesHonesty(input = {}) {
122
129
  extras.push('enforced structure/invariant findings');
123
130
  if (arkRunHasEnforced)
124
131
  extras.push('enforced ArkRun skip findings');
132
+ if (arkOrderHasEnforced)
133
+ extras.push('enforced ArkOrder skip findings');
125
134
  failMergeWhen = `Layer graph failures plus ${extras.join(' and ')} (advisory extras never fail merge alone).`;
126
135
  }
127
136
  else if (teethDeferredForClassification) {
128
137
  const which = [
129
138
  arkRulesHasEnforced ? 'ArkRules structure/invariant' : null,
130
139
  arkRunHasEnforced ? 'ArkRun' : null,
140
+ arkOrderHasEnforced ? 'ArkOrder' : null,
131
141
  ]
132
142
  .filter((part) => Boolean(part))
133
143
  .join(' and ');
@@ -139,15 +149,21 @@ export function composeMergePlanesHonesty(input = {}) {
139
149
  : arkRunMode === 'advisory'
140
150
  ? ' Advisory ArkRun never merge-blocks.'
141
151
  : ' ArkRun extra is present but does not arm merge teeth.';
152
+ const arkOrderBit = !arkOrderPresent
153
+ ? ' Absence of arkOrder is silent.'
154
+ : arkOrderMode === 'advisory'
155
+ ? ' Advisory ArkOrder never merge-blocks.'
156
+ : ' ArkOrder extra is present but does not arm merge teeth.';
142
157
  failMergeWhen =
143
158
  'Layer graph only — no enforced ArkRules structure/invariant teeth on this tree. Advisory packs do not arm merge teeth.' +
144
- arkRunBit;
159
+ arkRunBit +
160
+ arkOrderBit;
145
161
  }
146
162
  const out = {
147
163
  layers: {
148
164
  role: 'inter-layer-edges',
149
165
  alwaysOnGate: true,
150
- note: 'Import/export layer graph — the default merge plane. Absent arkRules or arkRun changes nothing here.',
166
+ note: 'Import/export layer graph — the default merge plane. Absent extras change nothing here.',
151
167
  },
152
168
  structureSensors: {
153
169
  role: 'intra-layer-heuristics',
@@ -177,6 +193,18 @@ export function composeMergePlanesHonesty(input = {}) {
177
193
  : 'Advisory ArkRun never adds merge teeth and never flips valid. Residual is a count, never a score.'
178
194
  : 'Absence of arkRun is silent — Layers and ArkRules verdicts unchanged. The extra never becomes a score.',
179
195
  },
196
+ arkOrder: {
197
+ role: 'pattern-slaving',
198
+ present: arkOrderPresent,
199
+ mode: arkOrderMode,
200
+ residualCount: arkOrderResidual,
201
+ extraMergeTeeth: arkOrderTeeth,
202
+ note: arkOrderPresent
203
+ ? arkOrderMode === 'enforced'
204
+ ? 'Enforced ArkOrder arms extra merge teeth only when the layer plane is classified. Residual is a count, never a score.'
205
+ : 'Advisory ArkOrder never adds merge teeth and never flips valid. Residual is a count, never a score.'
206
+ : 'Absence of arkOrder is silent — Layers verdicts unchanged. The extra never becomes a score.',
207
+ },
180
208
  extraMergeTeeth,
181
209
  dualPlaneStamp: MERGE_PLANES_DUAL_STAMP,
182
210
  failMergeWhen,
@@ -2,9 +2,12 @@
2
2
  * First-run CLI help (setup + check). Encyclopedia text stays behind --help --all.
3
3
  */
4
4
 
5
+ import { NORTH_STAR_ONE_LINE } from './product-copy.mjs';
6
+
5
7
  export function setupUsage() {
6
8
  return `arkgate (alias ark) — Write. Check. Ship.
7
9
  When the agent writes a bad import, the write doesn’t land. Same check fails the PR.
10
+ ${NORTH_STAR_ONE_LINE}
8
11
 
9
12
  arkgate start preview (no writes)
10
13
  arkgate start --apply write host + CI setup
@@ -30,6 +33,7 @@ Every flag: arkgate --help --all
30
33
  export function setupUsageAll() {
31
34
  return `arkgate (alias ark) — Write. Check. Ship.
32
35
  When the agent writes a bad import, the write doesn’t land. Same check fails the PR.
36
+ ${NORTH_STAR_ONE_LINE}
33
37
 
34
38
  Usage:
35
39
  arkgate start [--root <project>] [--tools <host>] [--require-write-hook <host>] [--install] [--apply] [--json]
@@ -158,9 +162,12 @@ export async function withSensorsPartialModeHonesty(args, run) {
158
162
 
159
163
  export function checkUsage() {
160
164
  return [
161
- 'arkgate-check (alias ark-check) — the architecture check.',
165
+ 'arkgate-check (alias ark-check) — Write. Check. Ship.',
166
+ 'When the agent writes a bad import, the write doesn’t land. Same check fails the PR.',
167
+ NORTH_STAR_ONE_LINE,
162
168
  '',
163
169
  ' arkgate-check --doctor where you are: one status light, one next action',
170
+ ' arkgate-check --changed --base <ref> local / pre-push: checks touched files only',
164
171
  ' arkgate-check --strict-merge CI / merge gate (required GitHub status)',
165
172
  ' arkgate-check --sensors which sensors can ever be enforced (does not run analysis)',
166
173
  '',
@@ -170,7 +177,9 @@ export function checkUsage() {
170
177
 
171
178
  export function checkUsageAll() {
172
179
  return [
173
- 'arkgate-check (alias ark-check) — the architecture check.',
180
+ 'arkgate-check (alias ark-check) — Write. Check. Ship.',
181
+ 'When the agent writes a bad import, the write doesn’t land. Same check fails the PR.',
182
+ NORTH_STAR_ONE_LINE,
174
183
  '',
175
184
  'Usage: arkgate-check | ark-check (identical bins; product name ArkGate)',
176
185
  ' arkgate-check --version',
@@ -257,6 +257,42 @@ export function hasArkMcpRegistration(root, relativePath = '.mcp.json') {
257
257
  }
258
258
  }
259
259
 
260
+ /**
261
+ * Shared MCP JSON destinations written by install-agent-gates (catalog + host copies).
262
+ * Upsert `mcpServers.ark` without clobbering sibling servers — same contract as
263
+ * mergeOpencodeArkMcp / mergeCursorArkHook.
264
+ */
265
+ export const MCP_JSON_GATE_FILES = ['.mcp.json', '.cursor/mcp.json', '.agents/mcp_config.json'];
266
+
267
+ /**
268
+ * Upsert mcpServers.ark into an existing MCP JSON document.
269
+ * Preserves sibling servers and unknown top-level keys. Returns null if unreadable.
270
+ */
271
+ export function mergeArkMcpJson(existingText, generatedText) {
272
+ let existing;
273
+ let generated;
274
+ try {
275
+ existing = existingText && existingText.trim() ? JSON.parse(existingText) : {};
276
+ generated = JSON.parse(generatedText);
277
+ } catch {
278
+ return null;
279
+ }
280
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) return null;
281
+ if (!generated || typeof generated !== 'object' || Array.isArray(generated)) return null;
282
+ const generatedArk = generated.mcpServers?.ark;
283
+ if (!generatedArk || typeof generatedArk !== 'object' || Array.isArray(generatedArk)) {
284
+ return null;
285
+ }
286
+ const servers =
287
+ existing.mcpServers &&
288
+ typeof existing.mcpServers === 'object' &&
289
+ !Array.isArray(existing.mcpServers)
290
+ ? { ...existing.mcpServers }
291
+ : {};
292
+ const next = { ...existing, mcpServers: { ...servers, ark: generatedArk } };
293
+ return `${JSON.stringify(next, null, 2)}\n`;
294
+ }
295
+
260
296
  function commandArkMcpArgs(command) {
261
297
  if (typeof command !== 'string') return null;
262
298
  const words = [];
@@ -747,12 +783,13 @@ export function writeTemplate(root, relativePath, content, force) {
747
783
  return { relativePath, status: 'skipped-self-hosted' };
748
784
  }
749
785
  if (existing && !isArkAgentsContent(existing)) {
750
- // Never clobber a project-owned AGENTS.md — even with --force.
751
- // If Ark section not present yet, merge once; subsequent runs leave it alone.
786
+ // Never replace a project-owned AGENTS.md — even with --force.
787
+ // Merge the Ark section once when missing so start / install-agent-gates
788
+ // leave a contract doctor accepts (issue #210). Subsequent runs leave it.
752
789
  const hasArkSection =
753
790
  /#\s*Ark(Gate)?\s+Enforcement\b/.test(existing) ||
754
791
  /ark\.config\.json is authoritative/i.test(existing);
755
- if (force && isArkAgentsContent(content) && !hasArkSection) {
792
+ if (isArkAgentsContent(content) && !hasArkSection) {
756
793
  try {
757
794
  const merged = `${existing.replace(/\s*$/, '')}\n\n---\n\n${content}`;
758
795
  ensureDirForFile(fullPath);
@@ -11,6 +11,7 @@ import { effectiveCapabilityDeny } from './analysis-engine.mjs';
11
11
  import { graphBlindSpotsHtml } from './graph-blind.mjs';
12
12
  import { formatRulesUnderContractHtml } from './rules-under-contract.mjs';
13
13
  import { formatArkRunHtml } from './ark-run-report.mjs';
14
+ import { formatArkOrderHtml } from './ark-order-report.mjs';
14
15
  import { primaryImprovementCompassNextAction } from './improvement-compass.mjs';
15
16
 
16
17
  // htmlEscape is injected by the caller (html-report.mjs) — importing it back
@@ -353,6 +354,7 @@ export function renderAdvisorySections(advisories, escape) {
353
354
  graphBlindSpotsHtml(advisories.graphBlindSpots, esc),
354
355
  rulesUnderContractHtml(advisories.rulesUnderContract),
355
356
  formatArkRunHtml(advisories.arkRun, esc),
357
+ formatArkOrderHtml(advisories.arkOrder, esc),
356
358
  ]
357
359
  .filter(Boolean)
358
360
  .join('\n');
@@ -19,7 +19,7 @@ import {
19
19
  buildProductHonesty,
20
20
  } from './enforcement-honesty.mjs';
21
21
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
22
- import { summarizeArkRunSection } from './ark-run-doctor.mjs';
22
+ import { attachExtraDoctorSections } from './doctor-advisories.mjs';
23
23
  import { readBaseline, baselineOccurrenceKeys } from './violations.mjs';
24
24
  import { describePackageVersionDualTruth } from './field-install.mjs';
25
25
  import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
@@ -136,23 +136,12 @@ export function buildReportDepthPayload(
136
136
  classifiedFiles: coverage?.governed?.classifiedFiles ?? null,
137
137
  };
138
138
  const rulesUnderContract = summarizeRulesUnderContract(root, config, undefined, classification);
139
- const arkRun = summarizeArkRunSection({
140
- arkRun: config?.arkRun,
141
- findings: activeViolations,
139
+ const { arkRun, arkOrder } = attachExtraDoctorSections(
140
+ rulesUnderContract,
141
+ config,
142
142
  classification,
143
- arkRules: {
144
- active: rulesUnderContract?.active === true,
145
- structureEnforced: rulesUnderContract?.mergePlanes?.structureSensors?.enforced,
146
- structureTotal: rulesUnderContract?.mergePlanes?.structureSensors?.total,
147
- structureAdvisory: rulesUnderContract?.mergePlanes?.structureSensors?.advisory,
148
- invariantEnforced: rulesUnderContract?.mergePlanes?.invariants?.enforced,
149
- invariantTotal: rulesUnderContract?.mergePlanes?.invariants?.total,
150
- invariantAdvisory: rulesUnderContract?.mergePlanes?.invariants?.advisory,
151
- covered: rulesUnderContract?.mergePlanes?.invariants?.covered,
152
- uncovered: rulesUnderContract?.mergePlanes?.invariants?.uncovered,
153
- },
154
- });
155
- if (rulesUnderContract?.mergePlanes) rulesUnderContract.mergePlanes = arkRun.mergePlanes;
143
+ activeViolations
144
+ );
156
145
  // Single residual expression (parity with doctor): nextPilot || extractionCard.
157
146
  const residualPilot =
158
147
  pilotLoop?.nextPilot || pilotLoop?.extractionCard || null;
@@ -174,7 +163,7 @@ export function buildReportDepthPayload(
174
163
  residualPilots: Boolean(residualPilot) && designFitness.designWeak === true,
175
164
  pilotTarget: residualPilot?.pilotTarget ?? residualPilot?.pilot ?? null,
176
165
  arkRulesMergeHonesty: rulesUnderContract?.mergePlanes
177
- ? { active: rulesUnderContract.active === true || arkRun?.active === true, ...rulesUnderContract.mergePlanes }
166
+ ? { active: rulesUnderContract.active === true || arkRun?.active === true || arkOrder?.active === true, ...rulesUnderContract.mergePlanes }
178
167
  : null,
179
168
  primaryNextAction: postGreenPath?.action ?? dualTruthNext,
180
169
  activeBlockingViolations: activeBlockingCount,
@@ -218,6 +207,7 @@ export function buildReportDepthPayload(
218
207
  productHonesty,
219
208
  mergePlanes: rulesUnderContract?.mergePlanes ?? null,
220
209
  arkRun,
210
+ arkOrder,
221
211
  improvementCompass,
222
212
  deepModuleCoach,
223
213
  },
@@ -152,6 +152,8 @@ export function buildReportSnapshot({
152
152
  improvementCompass = null,
153
153
  /** RN08 — thin status ArkRun slice (notAScore; residual count, never a score). */
154
154
  arkRun = null,
155
+ /** Thin status ArkOrder slice (notAScore; residual count, never a score). */
156
+ arkOrder = null,
155
157
  }) {
156
158
  const layers = Array.isArray(config?.layers) ? config.layers : [];
157
159
  const rules = Array.isArray(config?.rules) ? config.rules : [];
@@ -213,6 +215,20 @@ export function buildReportSnapshot({
213
215
  : null,
214
216
  };
215
217
  }
218
+ if (arkOrder && typeof arkOrder === 'object' && arkOrder.notAScore === true) {
219
+ snapshot.arkOrder = {
220
+ notAScore: true,
221
+ present: arkOrder.active === true || arkOrder.present === true,
222
+ mode: arkOrder.mode === 'enforced' || arkOrder.mode === 'advisory' ? arkOrder.mode : null,
223
+ extraMergeTeeth: arkOrder.extraMergeTeeth === true,
224
+ residual:
225
+ typeof arkOrder.residual === 'number'
226
+ ? arkOrder.residual
227
+ : typeof arkOrder.residual?.count === 'number'
228
+ ? arkOrder.residual.count
229
+ : null,
230
+ };
231
+ }
216
232
  return snapshot;
217
233
  }
218
234
 
@@ -37,6 +37,8 @@ import {
37
37
  hasCheckArchitectureScript,
38
38
  ensureTypecheckScript,
39
39
  compactRouterHost,
40
+ mergeArkMcpJson,
41
+ MCP_JSON_GATE_FILES,
40
42
  writeTemplate,
41
43
  } from './gate-files.mjs';
42
44
  import {
@@ -573,6 +575,27 @@ export function runInstallAgentGates(args) {
573
575
  // Upsert ark-write-gate without requiring --force; never wipe sibling named hooks.
574
576
  return writeTemplate(root, relativePath, merged, true);
575
577
  }
578
+ if (MCP_JSON_GATE_FILES.includes(relativePath)) {
579
+ const fullPath = path.join(root, relativePath);
580
+ let existing = '';
581
+ try {
582
+ existing = fs.readFileSync(fullPath, 'utf8');
583
+ } catch {
584
+ // Missing MCP JSON → write generated ark server.
585
+ }
586
+ if (!existing) {
587
+ return writeTemplate(root, relativePath, content, true);
588
+ }
589
+ const merged = mergeArkMcpJson(existing, content);
590
+ if (merged == null) {
591
+ return args.force
592
+ ? writeTemplate(root, relativePath, content, true)
593
+ : { relativePath, status: 'skipped-non-ark' };
594
+ }
595
+ if (merged === existing) return { relativePath, status: 'skipped' };
596
+ // Upsert mcpServers.ark without requiring --force; never wipe sibling servers.
597
+ return writeTemplate(root, relativePath, merged, true);
598
+ }
576
599
  return writeTemplate(
577
600
  root,
578
601
  relativePath,
@@ -314,7 +314,7 @@ export function emitHostAllow(output, { antigravityStyle, cursorStyle }) {
314
314
  */
315
315
  export function formatWriteGateDeny({ file, reason, ruleId, nextAction, extraLines = [] }) {
316
316
  const target = file || 'this write';
317
- const why = String(reason || 'this change breaks the architecture layers').replace(/\s+/g, ' ').trim();
317
+ const why = String(reason || 'a bad import the write doesn’t land').replace(/\s+/g, ' ').trim();
318
318
  const next =
319
319
  nextAction && /place|move|import|port/i.test(nextAction)
320
320
  ? nextAction
@@ -6,6 +6,10 @@
6
6
  * common software words. See docs/product-voice.md.
7
7
  */
8
8
 
9
+ /** First-contact north star. Ordinary English. Spanish names once. */
10
+ export const NORTH_STAR_ONE_LINE =
11
+ 'Contener · Guiar · Ordenar — contain the write, guide the next step, order leftover mess.';
12
+
9
13
  /** Status-light leftover-design qualifier (was “design-weak”). */
10
14
  export const LEFTOVER_DESIGN_LABEL = 'leftover design work';
11
15
 
@@ -239,17 +239,17 @@ export function deterministicNextAction(violation) {
239
239
  case 'ARKORDER_KERNEL_IN_DOMAIN':
240
240
  return 'Move the arkgate/order import out of the Domain-role layer into a plane root or adapter, then preflight again. Never mechanical-safe.';
241
241
  case 'ARKORDER_GENERIC_UPDATE':
242
- return 'Use release() for the first freeze of ξ. Later pattern change is proposeRelease then apply(ProposeResult). Never update/patch/set. Never mechanical-safe.';
242
+ return 'Don\'t use a generic update. First freeze with release(). Later, propose the change, then apply it.';
243
243
  case 'ARKORDER_TOO_MANY_PARAMS':
244
244
  return 'Cut ξ to the slow keys that actually slave the rest, then preflight again. Never mechanical-safe.';
245
245
  case 'ARKORDER_INGEST_WRITES_XI':
246
246
  return 'Keep ingest results as absorb/escalate_up/hold only. Change ξ with proposeRelease then apply(ProposeResult). Never mechanical-safe.';
247
247
  case 'ARKORDER_XI_FIELD_WRITE':
248
248
  return typeof violation.target === 'string' && violation.target.length > 0
249
- ? `Do not persist slow key ${violation.target} from a use-case. Absorb the field with ingest() or change the pattern with proposeRelease then apply, then preflight again.`
250
- : 'Do not persist a declared slow key from a use-case. Absorb the field with ingest() or change the pattern with proposeRelease then apply, then preflight again. Never mechanical-safe.';
249
+ ? `Don't write ${violation.target} from a use-case. Take the event in, or change that choice through the valve (proposeRelease then apply), not a generic update.`
250
+ : "Don't write a named product choice from a use-case. Take the event in, or change that choice through the valve (proposeRelease then apply), not a generic update.";
251
251
  case 'ARKORDER_UNVALVED_RELEASE':
252
- return 'Change ξ with proposeRelease then apply(ProposeResult). release() is only the first freeze. Never mechanical-safe.';
252
+ return 'Change the choice with proposeRelease then apply. release() is only the first freeze. Never mechanical-safe.';
253
253
  default:
254
254
  if (typeof violation.ruleId === 'string' && violation.ruleId.startsWith('ARKRULE_')) {
255
255
  return `Fix the ArkRule ${typeof violation.arkruleId === 'string' ? violation.arkruleId : violation.ruleId}, then preflight again.`;
@@ -528,7 +528,7 @@ export function enrichViolationWithFixClass(violation) {
528
528
  enriched.effort = 'medium';
529
529
  enriched.enthusiastHint =
530
530
  violation.ruleId === 'ARKORDER_GENERIC_UPDATE'
531
- ? 'Do not PATCH the slow pattern. Freeze ξ with release() or propose a pattern change that lists what dies.'
531
+ ? 'Don\'t PATCH the billing plan. First freeze with release(), or propose the change then apply it.'
532
532
  : violation.ruleId === 'ARKORDER_KERNEL_IN_DOMAIN'
533
533
  ? 'Domain stays plane-free. Import arkgate/order only from a listed plane root.'
534
534
  : violation.ruleId === 'ARKORDER_TOO_MANY_PARAMS'
@@ -536,7 +536,7 @@ export function enrichViolationWithFixClass(violation) {
536
536
  : violation.ruleId === 'ARKORDER_INGEST_WRITES_XI'
537
537
  ? 'ingest can absorb or escalate. It never writes a new house.'
538
538
  : violation.ruleId === 'ARKORDER_XI_FIELD_WRITE'
539
- ? 'Name the slow keys in arkOrder.xiKeys. Invoices and seats still flow; changing the plan is a new release, not prisma.update.'
539
+ ? 'Name the few big choices in arkOrder.xiKeys. Invoices and seats still flow; changing the plan goes through the valve, not a generic update.'
540
540
  : violation.ruleId === 'ARKORDER_INFORMATION_BUDGET'
541
541
  ? 'A scale may not observe a denied kind. Cut it from the projector or from cannotObserve.'
542
542
  : violation.ruleId === 'ARKORDER_XI_TTL'
@@ -544,7 +544,7 @@ export function enrichViolationWithFixClass(violation) {
544
544
  : violation.ruleId === 'ARKORDER_STALE_SIGMA'
545
545
  ? 'Refresh σ. ξ does not expire.'
546
546
  : violation.ruleId === 'ARKORDER_UNVALVED_RELEASE'
547
- ? 'The pattern is frozen. proposeRelease then apply — do not call release() again with a different ξ.'
547
+ ? 'The choice is already frozen. proposeRelease then apply — do not call release() again with a different value.'
548
548
  : 'Call createOrderPlane from arkgate/order in a listed plane root so the app actually freezes a pattern.';
549
549
  break;
550
550
  default: