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
@@ -211,32 +211,67 @@ function canonicalOverlayPath(
211
211
  return [...candidates].sort()[0] ?? requested;
212
212
  }
213
213
 
214
- function collectCandidateFiles(root, config, changes, observeInput) {
214
+ function discoverScopedFiles(root, config, scopeRelatives, observeInput) {
215
+ const discovered = [];
216
+ const seen = new Set();
217
+ for (const raw of scopeRelatives ?? []) {
218
+ let relative;
219
+ try {
220
+ relative = canonicalProjectPath(
221
+ String(raw || '')
222
+ .replace(/\\/g, '/')
223
+ .replace(/^\.\//, '')
224
+ );
225
+ } catch {
226
+ continue;
227
+ }
228
+ if (seen.has(relative) || !isGovernableSourceFile(path.basename(relative))) continue;
229
+ if (!isIncluded(relative, config.include) || isScanExcludedRelative(relative, config)) {
230
+ continue;
231
+ }
232
+ const absolute = path.join(root, ...relative.split('/'));
233
+ if (!fs.existsSync(absolute)) continue;
234
+ seen.add(relative);
235
+ observeResolvedInput(observeInput, absolute, 'realpath');
236
+ discovered.push({
237
+ absolute,
238
+ real: fs.realpathSync(absolute),
239
+ relative,
240
+ });
241
+ }
242
+ return discovered.sort((left, right) =>
243
+ left.relative < right.relative ? -1 : left.relative > right.relative ? 1 : 0
244
+ );
245
+ }
246
+
247
+ function collectCandidateFiles(root, config, changes, observeInput, scopeRelatives) {
215
248
  const files = new Map();
216
249
  const directoryAliases = new Map();
217
250
  const expandedAliasDirectories = new Set();
218
- const discovered = (config.include ?? [])
219
- .flatMap((entry) =>
220
- collectGovernedFiles(root, { ...config, include: [entry] }, {
221
- observeInput,
222
- onDirectory(absolute, real) {
223
- let relative = normalize(path.relative(root, absolute));
224
- if (relative === '.') relative = '';
225
- rememberDirectoryAlias(directoryAliases, real, relative, absolute);
226
- },
227
- })
228
- )
229
- .map((absolute) => {
230
- observeResolvedInput(observeInput, absolute, 'realpath');
231
- return {
232
- absolute,
233
- real: fs.realpathSync(absolute),
234
- relative: canonicalProjectPath(normalize(path.relative(root, absolute))),
235
- };
236
- })
237
- .sort((left, right) =>
238
- left.relative < right.relative ? -1 : left.relative > right.relative ? 1 : 0
239
- );
251
+ const discovered = Array.isArray(scopeRelatives)
252
+ ? discoverScopedFiles(root, config, scopeRelatives, observeInput)
253
+ : (config.include ?? [])
254
+ .flatMap((entry) =>
255
+ collectGovernedFiles(root, { ...config, include: [entry] }, {
256
+ observeInput,
257
+ onDirectory(absolute, real) {
258
+ let relative = normalize(path.relative(root, absolute));
259
+ if (relative === '.') relative = '';
260
+ rememberDirectoryAlias(directoryAliases, real, relative, absolute);
261
+ },
262
+ })
263
+ )
264
+ .map((absolute) => {
265
+ observeResolvedInput(observeInput, absolute, 'realpath');
266
+ return {
267
+ absolute,
268
+ real: fs.realpathSync(absolute),
269
+ relative: canonicalProjectPath(normalize(path.relative(root, absolute))),
270
+ };
271
+ })
272
+ .sort((left, right) =>
273
+ left.relative < right.relative ? -1 : left.relative > right.relative ? 1 : 0
274
+ );
240
275
  const canonicalByRealpath = new Map();
241
276
  for (const candidate of discovered) {
242
277
  if (!canonicalByRealpath.has(candidate.real)) canonicalByRealpath.set(candidate.real, candidate);
@@ -582,7 +617,8 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
582
617
  const optionsByPath = new Map();
583
618
  const configInputsByPath = new Map();
584
619
  const reasons = [];
585
- for (const configPath of configPaths) {
620
+ const loadConfigOptions = (configPath) => {
621
+ if (optionsByPath.has(configPath)) return optionsByPath.get(configPath);
586
622
  const configContents = new Map();
587
623
  const readConfig = (fileName) => {
588
624
  observeResolvedInput(observeInput, fileName, 'tsconfig');
@@ -605,15 +641,16 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
605
641
  });
606
642
  optionsByPath.set(configPath, {});
607
643
  configInputsByPath.set(configPath, configContents);
608
- continue;
644
+ return {};
609
645
  }
610
646
  const parsed = ts.parseJsonConfigFileContent(
611
647
  read.config,
612
648
  {
613
649
  useCaseSensitiveFileNames: ts.sys?.useCaseSensitiveFileNames ?? true,
614
- readDirectory(...args) {
615
- observeResolvedInput(observeInput, args[0], 'tsconfig-directory');
616
- return ts.sys?.readDirectory ? ts.sys.readDirectory(...args) : [];
650
+ readDirectory(dir) {
651
+ observeResolvedInput(observeInput, dir, 'tsconfig-directory');
652
+ // Options-only parse: `fileNames` are unused. Skip the include walk (#205).
653
+ return [];
617
654
  },
618
655
  fileExists(fileName) {
619
656
  observeResolvedInput(observeInput, fileName, 'tsconfig-exists');
@@ -637,11 +674,19 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
637
674
  }
638
675
  optionsByPath.set(configPath, parsed.options ?? {});
639
676
  configInputsByPath.set(configPath, configContents);
640
- }
677
+ return parsed.options ?? {};
678
+ };
679
+ for (const configPath of configPaths) loadConfigOptions(configPath);
641
680
 
642
681
  const optionsFor = (fileName) => {
643
- const configPath = configByFile.get(path.resolve(fileName));
644
- return configPath ? optionsByPath.get(configPath) ?? {} : {};
682
+ const resolved = path.resolve(fileName);
683
+ let configPath = configByFile.get(resolved);
684
+ if (!configPath) {
685
+ configPath = explicitPath
686
+ ?? nearestTsconfig(root, resolved, nearestConfigByDirectory, observeInput);
687
+ if (configPath) configByFile.set(resolved, configPath);
688
+ }
689
+ return configPath ? loadConfigOptions(configPath) ?? {} : {};
645
690
  };
646
691
  const configs = configPaths.map((configPath) => ({
647
692
  path: configLabel(root, configPath, externalAnchor),
@@ -1036,6 +1081,19 @@ function unavailableFacts(config, ts, reason) {
1036
1081
  });
1037
1082
  }
1038
1083
 
1084
+ function loadOneCandidateFile(root, config, relative, observeInput) {
1085
+ const discovered = discoverScopedFiles(root, config, [relative], observeInput);
1086
+ const found = discovered[0];
1087
+ if (!found) return null;
1088
+ observeResolvedInput(observeInput, found.absolute, 'source');
1089
+ return {
1090
+ path: found.relative,
1091
+ absolute: path.resolve(found.absolute),
1092
+ real: path.resolve(found.real),
1093
+ content: fs.readFileSync(found.absolute, 'utf8'),
1094
+ };
1095
+ }
1096
+
1039
1097
  /** Resolve one complete candidate tree (base or virtual overlay) into versioned neutral facts. */
1040
1098
  export function resolveCandidateFacts({
1041
1099
  root,
@@ -1044,6 +1102,7 @@ export function resolveCandidateFacts({
1044
1102
  tsconfig,
1045
1103
  changes = [],
1046
1104
  observeInput,
1105
+ scopeFiles,
1047
1106
  }) {
1048
1107
  if (!ts?.createSourceFile || !ts?.resolveModuleName) {
1049
1108
  return unavailableFacts(config, ts, 'No API-compatible TypeScript resolver is available.');
@@ -1053,10 +1112,17 @@ export function resolveCandidateFacts({
1053
1112
  let candidateFiles;
1054
1113
  let canonicalChanges;
1055
1114
  let compiler;
1115
+ const scoped = Array.isArray(scopeFiles);
1056
1116
  try {
1057
1117
  observeResolvedInput(observeInput, root, 'realpath');
1058
1118
  canonicalRoot = fs.realpathSync(root);
1059
- const candidate = collectCandidateFiles(canonicalRoot, config, changes, observeInput);
1119
+ const candidate = collectCandidateFiles(
1120
+ canonicalRoot,
1121
+ config,
1122
+ changes,
1123
+ observeInput,
1124
+ scoped ? scopeFiles : undefined
1125
+ );
1060
1126
  candidateFiles = candidate.files;
1061
1127
  canonicalChanges = candidate.changes;
1062
1128
  compiler = compilerContext(ts, canonicalRoot, tsconfig, candidateFiles, observeInput);
@@ -1106,7 +1172,8 @@ export function resolveCandidateFacts({
1106
1172
  const compositionRootPatterns = [...(config.arkRun?.compositionRoots ?? [])];
1107
1173
  const planeRootPatterns = [...(config.arkOrder?.planeRoots ?? [])];
1108
1174
 
1109
- for (const candidate of candidateFiles) {
1175
+ const seedPathSet = new Set(candidateFiles.map((file) => file.path));
1176
+ const ingest = (candidate, fullExtract) => {
1110
1177
  const sourceFile = ts.createSourceFile(
1111
1178
  candidate.absolute,
1112
1179
  candidate.content,
@@ -1158,6 +1225,7 @@ export function resolveCandidateFacts({
1158
1225
  message: `${candidate.path} has ${parseDiagnosticCount} TypeScript parse diagnostic(s).`,
1159
1226
  });
1160
1227
  }
1228
+ if (!fullExtract) return;
1161
1229
  const forbiddenUses = mayContainForbiddenCapability(ts, sourceFile, forbiddenGlobals)
1162
1230
  ? collectForbiddenCapabilityUses(ts, sourceFile, forbiddenGlobals)
1163
1231
  : [];
@@ -1244,10 +1312,50 @@ export function resolveCandidateFacts({
1244
1312
  // Never fail the resolver for ArkOrder release key-count extraction.
1245
1313
  }
1246
1314
  }
1315
+ };
1316
+
1317
+ for (const candidate of candidateFiles) ingest(candidate, true);
1318
+
1319
+ if (scoped) {
1320
+ const loaded = new Set(candidateFiles.map((file) => file.path));
1321
+ let pending = [...candidateFiles];
1322
+ while (pending.length > 0) {
1323
+ const discovered = [];
1324
+ for (const source of pending) {
1325
+ const parsedFile = parsed.get(source.path);
1326
+ if (!parsedFile) continue;
1327
+ for (const dependency of parsedFile.dependencies) {
1328
+ const resolved = resolveDependency(
1329
+ ts,
1330
+ dependency,
1331
+ parsedFile.candidate.absolute,
1332
+ compiler.optionsFor(parsedFile.candidate.absolute),
1333
+ host
1334
+ );
1335
+ if (resolved.resolution !== 'resolved-project' || !resolved.target) continue;
1336
+ if (loaded.has(resolved.target)) continue;
1337
+ const extra = loadOneCandidateFile(
1338
+ canonicalRoot,
1339
+ config,
1340
+ resolved.target,
1341
+ observeInput
1342
+ );
1343
+ if (!extra) continue;
1344
+ loaded.add(extra.path);
1345
+ candidateFiles.push(extra);
1346
+ ingest(extra, false);
1347
+ discovered.push(extra);
1348
+ }
1349
+ }
1350
+ pending = discovered;
1351
+ }
1247
1352
  }
1248
1353
 
1354
+ const extractCandidates = scoped
1355
+ ? candidateFiles.filter((file) => seedPathSet.has(file.path))
1356
+ : candidateFiles;
1249
1357
  const admittedTypeNames = new Set(classShapes.map((shape) => shape.className));
1250
- for (const candidate of candidateFiles) {
1358
+ for (const candidate of extractCandidates) {
1251
1359
  if (!/\.(tsx?|mts|cts)$/i.test(candidate.path)) continue;
1252
1360
  try {
1253
1361
  arkRunManagedNews.push(
@@ -1264,7 +1372,7 @@ export function resolveCandidateFacts({
1264
1372
 
1265
1373
  if (planeRootPatterns.length > 0) {
1266
1374
  const factoryFiles = new Set(arkOrderPlaneCalls.map((call) => call.file));
1267
- for (const candidate of candidateFiles) {
1375
+ for (const candidate of extractCandidates) {
1268
1376
  for (const pattern of planeRootPatterns) {
1269
1377
  try {
1270
1378
  if (!globToRegExp(pattern).test(candidate.path)) continue;
@@ -1284,7 +1392,7 @@ export function resolveCandidateFacts({
1284
1392
  const factoryFiles = new Set(
1285
1393
  arkRunKernelCalls.filter((call) => call.kind === 'factory').map((call) => call.file)
1286
1394
  );
1287
- for (const candidate of candidateFiles) {
1395
+ for (const candidate of extractCandidates) {
1288
1396
  for (const pattern of compositionRootPatterns) {
1289
1397
  try {
1290
1398
  if (!globToRegExp(pattern).test(candidate.path)) continue;
@@ -62,6 +62,18 @@ function arkRunMergeInput(config, residualCount = 0) {
62
62
  };
63
63
  }
64
64
 
65
+ function arkOrderMergeInput(config, residualCount = 0) {
66
+ const extra = config?.arkOrder;
67
+ if (!extra || typeof extra !== 'object') {
68
+ return { present: false, mode: null, residualCount: 0 };
69
+ }
70
+ return {
71
+ present: true,
72
+ mode: extra.mode === 'enforced' || extra.mode === 'advisory' ? extra.mode : null,
73
+ residualCount: Number(residualCount) || 0,
74
+ };
75
+ }
76
+
65
77
  export function summarizeRulesUnderContract(root, config, facts, classification) {
66
78
  if (!config?.arkRules || Object.keys(config.arkRules).length === 0) {
67
79
  return {
@@ -74,6 +86,7 @@ export function summarizeRulesUnderContract(root, config, facts, classification)
74
86
  classification,
75
87
  arkRules: { active: false },
76
88
  arkRun: arkRunMergeInput(config),
89
+ arkOrder: arkOrderMergeInput(config),
77
90
  }),
78
91
  notAScore: true,
79
92
  note: 'No arkRules map — intra-layer ArkRules are opt-in.',
@@ -187,6 +200,7 @@ export function summarizeRulesUnderContract(root, config, facts, classification)
187
200
  uncovered: uncoveredInvariants,
188
201
  },
189
202
  arkRun: arkRunMergeInput(config),
203
+ arkOrder: arkOrderMergeInput(config),
190
204
  });
191
205
 
192
206
  return {
@@ -115,6 +115,45 @@ export function walk(dir, files = [], options = {}) {
115
115
  }
116
116
 
117
117
  /** Walk include roots then drop codegen / config.exclude (universal scan filter). */
118
+ export function isIncludeMatch(relativePath, include) {
119
+ return (include ?? []).some((entry) => {
120
+ const includeRoot = String(entry)
121
+ .replace(/\\/g, '/')
122
+ .replace(/^\.\//, '')
123
+ .replace(/\/$/, '');
124
+ return (
125
+ includeRoot === '.' ||
126
+ relativePath === includeRoot ||
127
+ relativePath.startsWith(`${includeRoot}/`)
128
+ );
129
+ });
130
+ }
131
+
132
+ /** Path-only governed check — no tree walk. Matches `collectGovernedFiles` membership. */
133
+ export function isGovernedSourcePath(root, relativePath, config) {
134
+ const rel = normalize(String(relativePath || ''))
135
+ .replace(/^\.\//, '')
136
+ .replace(/\\/g, '/');
137
+ if (!rel || !isGovernableSourceFile(path.basename(rel))) return false;
138
+ if (!isIncludeMatch(rel, config?.include) || isScanExcludedRelative(rel, config)) return false;
139
+ return fs.existsSync(path.join(root, ...rel.split('/')));
140
+ }
141
+
142
+ /** Map diff paths to absolute governed files without walking the include tree. */
143
+ export function governedFilesFromRelativePaths(root, relativePaths, config) {
144
+ const out = [];
145
+ const seen = new Set();
146
+ for (const raw of relativePaths ?? []) {
147
+ const rel = normalize(String(raw || ''))
148
+ .replace(/^\.\//, '')
149
+ .replace(/\\/g, '/');
150
+ if (!rel || seen.has(rel) || !isGovernedSourcePath(root, rel, config)) continue;
151
+ seen.add(rel);
152
+ out.push(path.resolve(root, ...rel.split('/')));
153
+ }
154
+ return out;
155
+ }
156
+
118
157
  export function collectGovernedFiles(root, config, options = {}) {
119
158
  options.observeInput?.(path.resolve(root), 'realpath');
120
159
  const state = {
@@ -4,6 +4,7 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { arkCommand, buildArchitectureRecommendation } from '../ark-shared.mjs';
7
+ import { ARKORDER_FIRST_CONTACT_NEXT, ARKORDER_ONE_BREATH } from './ark-order-doctor.mjs';
7
8
  import { compactAgentInstructions, instructionRule, mcpJson } from './ci-and-commands.mjs';
8
9
  import {
9
10
  antigravityHooks,
@@ -162,6 +163,9 @@ export function renderStartPreview(preview, options = {}) {
162
163
  if (!applying) {
163
164
  console.log('Setup: install package + host gates (see --json).');
164
165
  console.log('Preview does not write. Apply installs CI.');
166
+ console.log('Optional extras stay off. This start is layers only — they stop bad imports.');
167
+ console.log(ARKORDER_ONE_BREATH);
168
+ console.log(ARKORDER_FIRST_CONTACT_NEXT);
165
169
  }
166
170
  if (preview.runtimeActivation) {
167
171
  console.log('Host: Codex is configured but not verified yet. Restart the host, then confirm this project.');
@@ -26,6 +26,7 @@ import { readBaseline } from './violations.mjs';
26
26
  import { reportsDir, readJsonSafe } from './html-report.mjs';
27
27
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
28
28
  import { projectStatusArkRun } from './ark-run-doctor.mjs';
29
+ import { projectStatusArkOrder } from './ark-order-doctor.mjs';
29
30
  import { collectVsBaseFacts, discoverTeamBaseRef } from './team-parliament-io.mjs';
30
31
  import { classifyAdopted, readAdoptionStance } from './adoption-stance.mjs';
31
32
 
@@ -354,6 +355,19 @@ export function collectStatusFacts(options = {}) {
354
355
  return projectStatusArkRun({ present: true, mode, extraMergeTeeth, residual });
355
356
  })();
356
357
 
358
+ const arkOrder = (() => {
359
+ const extra = config?.arkOrder;
360
+ if (!extra || typeof extra !== 'object') {
361
+ return projectStatusArkOrder({ present: false, residual: 0 });
362
+ }
363
+ const snap = latest?.arkOrder && typeof latest.arkOrder === 'object' ? latest.arkOrder : null;
364
+ const mode = extra.mode === 'enforced' || extra.mode === 'advisory' ? extra.mode : null;
365
+ const extraMergeTeeth =
366
+ snap && typeof snap.extraMergeTeeth === 'boolean' ? snap.extraMergeTeeth === true : false;
367
+ const residual = typeof snap?.residual === 'number' ? snap.residual : null;
368
+ return projectStatusArkOrder({ present: true, mode, extraMergeTeeth, residual });
369
+ })();
370
+
357
371
  // DF02 — always project compass with honesty mode (never invent green residual).
358
372
  // Prefer explicit override (tests/MCP inject doctor-facts); else report snapshot.
359
373
  let improvementCompass = null;
@@ -403,6 +417,7 @@ export function collectStatusFacts(options = {}) {
403
417
  latest?.designFitness?.designWeak === true ||
404
418
  latest?.doctor?.designFitness?.designWeak === true,
405
419
  arkRun: options.arkRun ?? arkRun,
420
+ arkOrder: options.arkOrder ?? arkOrder,
406
421
  adopted:
407
422
  options.adopted ??
408
423
  classifyAdopted({
@@ -534,6 +549,19 @@ export function runStatusCommand(args = {}) {
534
549
  ' · not a score'
535
550
  );
536
551
  }
552
+ const arkOrderLine = manifest.arkOrder;
553
+ if (arkOrderLine && arkOrderLine.notAScore === true) {
554
+ const residual =
555
+ arkOrderLine.residual == null ? 'unknown' : String(arkOrderLine.residual);
556
+ write(
557
+ ` arkOrder: ${arkOrderLine.present ? arkOrderLine.mode || 'on' : 'absent'}` +
558
+ ` · residual=${residual}` +
559
+ (arkOrderLine.present
560
+ ? ` · extraMergeTeeth=${arkOrderLine.extraMergeTeeth === true}`
561
+ : '') +
562
+ ' · not a score'
563
+ );
564
+ }
537
565
  write(` next: [${manifest.nextAction.id}] ${manifest.nextAction.summary}`);
538
566
  }
539
567
 
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { projectStatusArkRun } from './ark-run-doctor.mjs';
12
+ import { projectStatusArkOrder } from './ark-order-doctor.mjs';
12
13
  export const ARK_STATUS_MANIFEST_SCHEMA_VERSION = '1.0';
13
14
  export const ARK_STATUS_MANIFEST_SCHEMA_URL = 'https://unpkg.com/arkgate@4/schemas/ark.status-manifest.schema.json';
14
15
  /**
@@ -249,6 +250,12 @@ export function resolveStatusNextAction(facts, binding, activation, lastCheck, r
249
250
  summary: 'ArkRun residual remains — wire kernel usage or declarations through arkgate/runtime. Not a score.',
250
251
  };
251
252
  }
253
+ if (facts.arkOrder?.present === true && (facts.arkOrder.residual ?? 0) > 0) {
254
+ return {
255
+ id: 'review-arkorder-residual',
256
+ summary: 'ArkOrder leftover remains — change that product choice through the valve (proposeRelease then apply), not a generic update. Not a score.',
257
+ };
258
+ }
252
259
  if (facts.adopted === 'required-merge' || facts.adopted === 'advisory-only-acked') {
253
260
  return {
254
261
  id: 'stay-enforced',
@@ -336,6 +343,9 @@ export function buildStatusManifest(facts) {
336
343
  if (facts.arkRun && typeof facts.arkRun === 'object') {
337
344
  status.arkRun = projectStatusArkRun(facts.arkRun);
338
345
  }
346
+ if (facts.arkOrder && typeof facts.arkOrder === 'object') {
347
+ status.arkOrder = projectStatusArkOrder(facts.arkOrder);
348
+ }
339
349
  return status;
340
350
  }
341
351
  const STATUS_COMPASS_MODE_SET = new Set(STATUS_COMPASS_MODES);
@@ -628,5 +638,18 @@ export const ARK_STATUS_MANIFEST_SCHEMA = {
628
638
  residual: { anyOf: [{ type: 'integer', minimum: 0 }, { type: 'null' }] },
629
639
  },
630
640
  },
641
+ arkOrder: {
642
+ type: 'object',
643
+ description: 'ArkOrder extra residual (notAScore). present/mode from config; residual is a finding-id count (null = unknown, not green). extraMergeTeeth is honesty, never a score.',
644
+ additionalProperties: false,
645
+ required: ['notAScore', 'present', 'mode', 'extraMergeTeeth', 'residual'],
646
+ properties: {
647
+ notAScore: { const: true },
648
+ present: { type: 'boolean' },
649
+ mode: { anyOf: [{ enum: ['advisory', 'enforced'] }, { type: 'null' }] },
650
+ extraMergeTeeth: { type: 'boolean' },
651
+ residual: { anyOf: [{ type: 'integer', minimum: 0 }, { type: 'null' }] },
652
+ },
653
+ },
631
654
  },
632
655
  };
@@ -36,10 +36,10 @@ export function buildUpgradeWhatsNewSuggestions() {
36
36
  {
37
37
  id: 'five-door-autonomy',
38
38
  title: 'Five doors (invoke = write or map)',
39
- try: '/ark-adopt · /ark-place · /ark-autopilot · /ark-explore · /ark-upgrade',
40
- inspect: 'Skill bodies + doctor next action (other /ark-* names are shortcuts)',
39
+ try: '/ark-adopt · /ark-place · /ark-autopilot · /ark-explore · /ark-upgrade · /ark-runtime · /ark-order',
40
+ inspect: 'Skill bodies + doctor next action (other /ark-* names are one-release shortcuts)',
41
41
  why:
42
- 'Invoking a door is the approval. The CLI is sensor + gate — it does not apply the change. Explore maps only. Same 13 skill names.',
42
+ 'Invoking a door is the approval. The CLI is sensor + gate — it does not apply the change. Explore maps only. Closed catalog: first-class doors plus stubs. Contener · Guiar · Ordenar.',
43
43
  },
44
44
  {
45
45
  id: 'team-parliament',
@@ -59,9 +59,48 @@ export const FIX_HINTS = {
59
59
  'Break the cycle: extract the shared code into a module both sides import, invert one edge behind a port/interface, or merge the files if they are really one unit.',
60
60
  };
61
61
 
62
+ /** Extra-plane label so a slow-key deny reads as clearly as a bad import. */
63
+ export function violationPlaneLabel(ruleId) {
64
+ if (typeof ruleId !== 'string') return '';
65
+ if (ruleId.startsWith('ARKORDER_')) return '[ArkOrder] ';
66
+ if (ruleId.startsWith('ARKRUN_')) return '[ArkRun] ';
67
+ return '';
68
+ }
69
+
70
+ /**
71
+ * Where a warning came from, in the same `path:line` shape as a deny.
72
+ * Sensors already carry `file` / `line`; the human printer used to drop them.
73
+ * No file means the finding is about the project rules, not a line you edited.
74
+ */
75
+ export const WARNING_UNATTRIBUTED =
76
+ 'not a file — this is about the project rules, not a line you just edited';
77
+
78
+ export function formatWarningAttribution(warning) {
79
+ const file = typeof warning?.file === 'string' ? warning.file.trim() : '';
80
+ if (!file) return WARNING_UNATTRIBUTED;
81
+ const line =
82
+ Number.isInteger(warning.line) && warning.line > 0 ? warning.line : 1;
83
+ return `${file}:${line}`;
84
+ }
85
+
86
+ export function formatWarningLine(warning) {
87
+ const ruleId = typeof warning?.ruleId === 'string' ? warning.ruleId : 'WARNING';
88
+ const message = typeof warning?.message === 'string' ? warning.message : '';
89
+ return `warning ${ruleId} ${formatWarningAttribution(warning)} ${message}`.trimEnd();
90
+ }
91
+
92
+ export function printWarning(warning) {
93
+ const ruleId = typeof warning?.ruleId === 'string' ? warning.ruleId : 'WARNING';
94
+ const message = typeof warning?.message === 'string' ? warning.message : '';
95
+ console.error(
96
+ `${color.yellow('warning')} ${ruleId} ${formatWarningAttribution(warning)} ${message}`.trimEnd()
97
+ );
98
+ }
99
+
62
100
  export function printViolation(violation) {
63
101
  const location = `${violation.file}:${violation.line}`;
64
- console.error(`${color.red('✖')} ${color.bold(violation.ruleId)} ${location}`);
102
+ const plane = violationPlaneLabel(violation.ruleId);
103
+ console.error(`${color.red('✖')} ${color.bold(`${plane}${violation.ruleId}`)} ${location}`);
65
104
  if (violation.fromLayer && violation.toLayer) {
66
105
  const target = violation.target ? ` ${color.dim(`(${violation.target})`)}` : '';
67
106
  console.error(` ${violation.fromLayer} → ${violation.toLayer}${target}`);
@@ -392,7 +392,7 @@ declare function createAdapterResult(input: {
392
392
  }): CurrentAdapterResult;
393
393
 
394
394
  /** ArkGate library version — single source of truth. */
395
- declare const version = "4.8.11";
395
+ declare const version = "4.8.14";
396
396
 
397
397
  /**
398
398
  * AI Code Gate (basic).