arkgate 4.8.14 → 4.8.16

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 (103) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +17 -7
  3. package/bin/ark-check-runtime.mjs +17 -49
  4. package/bin/ark-mcp-runtime.mjs +111 -2
  5. package/bin/ark-shared.mjs +142 -11
  6. package/bin/ark.mjs +24 -33
  7. package/bin/lib/adr-presence.mjs +97 -0
  8. package/bin/lib/agent-skills-package.mjs +179 -1
  9. package/bin/lib/analysis-completeness.mjs +38 -2
  10. package/bin/lib/analysis-engine.mjs +6 -6
  11. package/bin/lib/architecture-scan.mjs +43 -4
  12. package/bin/lib/ark-run-doctor.mjs +6 -0
  13. package/bin/lib/ark-run-report.mjs +6 -2
  14. package/bin/lib/arkrules-sensors.mjs +81 -0
  15. package/bin/lib/baseline-key.mjs +6 -1
  16. package/bin/lib/check-args.mjs +52 -2
  17. package/bin/lib/config-contract.mjs +65 -2
  18. package/bin/lib/diagnostic-catalog.mjs +5 -1
  19. package/bin/lib/doctor-advisories.mjs +120 -10
  20. package/bin/lib/doctor-green-cite.mjs +139 -0
  21. package/bin/lib/doctor-human.mjs +115 -67
  22. package/bin/lib/doctor-next-actions.mjs +23 -0
  23. package/bin/lib/doctor-plan.mjs +35 -2
  24. package/bin/lib/enforcement-honesty.mjs +47 -2
  25. package/bin/lib/field-install.mjs +47 -7
  26. package/bin/lib/first-run-help.mjs +9 -1
  27. package/bin/lib/host-support-matrix.mjs +9 -1
  28. package/bin/lib/html-report-depth.mjs +2 -0
  29. package/bin/lib/html-report.mjs +13 -2
  30. package/bin/lib/import-resolve.mjs +74 -13
  31. package/bin/lib/improvement-compass-doctor.mjs +8 -1
  32. package/bin/lib/improvement-compass-map.mjs +5 -1
  33. package/bin/lib/invariant-coverage.mjs +121 -0
  34. package/bin/lib/invariant-tests-path.mjs +212 -0
  35. package/bin/lib/layer-description.mjs +90 -8
  36. package/bin/lib/mcp-hook-payload.mjs +56 -0
  37. package/bin/lib/no-domain-frontend.mjs +91 -0
  38. package/bin/lib/package-manager.mjs +8 -0
  39. package/bin/lib/presets.mjs +3 -4
  40. package/bin/lib/product-copy.mjs +15 -0
  41. package/bin/lib/projected-governed-coverage.mjs +114 -0
  42. package/bin/lib/prototype-shortcuts.mjs +224 -0
  43. package/bin/lib/recommend-cli.mjs +54 -0
  44. package/bin/lib/remediation.mjs +15 -0
  45. package/bin/lib/resolved-candidate-facts.mjs +83 -66
  46. package/bin/lib/rules-under-contract.mjs +117 -2
  47. package/bin/lib/snippet-analysis.mjs +14 -8
  48. package/bin/lib/start-preview.mjs +24 -2
  49. package/bin/lib/states-transitions-presence.mjs +212 -0
  50. package/bin/lib/status-command.mjs +2 -0
  51. package/bin/lib/status-transition-catalog.mjs +410 -0
  52. package/bin/lib/team-parliament-io.mjs +10 -0
  53. package/bin/lib/violations.mjs +13 -1
  54. package/bin/lib/write-path-capabilities.mjs +20 -5
  55. package/bin/lib/write-path-detect.mjs +27 -2
  56. package/dist/{configTypes-j7so8B4O.d.ts → configTypes-VD0qcubY.d.ts} +21 -1
  57. package/dist/{diagnosticCatalog-DVx_2RmF.d.ts → diagnosticCatalog-KWvGLI1U.d.ts} +24 -3
  58. package/dist/eslint/index.cjs +4 -4
  59. package/dist/eslint/index.d.ts +1 -1
  60. package/dist/eslint/index.js +4 -4
  61. package/dist/index.cjs +34 -34
  62. package/dist/index.d.ts +71 -10
  63. package/dist/index.js +34 -34
  64. package/dist/nestjs/index.cjs +5 -5
  65. package/dist/nestjs/index.d.ts +3 -3
  66. package/dist/nestjs/index.js +5 -5
  67. package/dist/runtime/index.cjs +15 -15
  68. package/dist/runtime/index.d.ts +6 -6
  69. package/dist/runtime/index.js +15 -15
  70. package/dist/{types-tGhZUiGX.d.ts → types-BSzRy2X1.d.ts} +1 -1
  71. package/dist/{types-Djbs3KjE.d.ts → types-D5GT5ZT8.d.ts} +3 -1
  72. package/docs/README.md +1 -1
  73. package/docs/agent-guide.md +8 -4
  74. package/docs/ai-gates.md +10 -3
  75. package/docs/brownfield-adoption.md +4 -1
  76. package/docs/configuration.md +66 -10
  77. package/docs/develop.md +4 -1
  78. package/docs/diagnostics.md +48 -4
  79. package/docs/enthusiast/how-to-agent-gates.md +5 -0
  80. package/docs/enthusiast/how-to-pick-shape.md +1 -1
  81. package/docs/package-surface.md +7 -5
  82. package/docs/use.md +10 -3
  83. package/package.json +1 -1
  84. package/schemas/ark.config.schema.json +24 -3
  85. package/server.json +2 -2
  86. package/templates/agent-skills/README.md +1 -1
  87. package/templates/agent-skills/ark-adopt/SKILL.md +97 -1
  88. package/templates/agent-skills/ark-autopilot/SKILL.md +12 -3
  89. package/templates/agent-skills/ark-coverage/SKILL.md +2 -2
  90. package/templates/agent-skills/ark-explain/SKILL.md +6 -3
  91. package/templates/agent-skills/ark-explore/SKILL.md +33 -1
  92. package/templates/agent-skills/ark-place/SKILL.md +42 -3
  93. package/templates/agent-skills/ark-runtime/SKILL.md +4 -2
  94. package/templates/agent-skills/ark-upgrade/SKILL.md +1 -1
  95. package/templates/arkrules/DomainModel.json +14 -1
  96. package/templates/skills/ark-adopt.md +97 -1
  97. package/templates/skills/ark-autopilot.md +12 -3
  98. package/templates/skills/ark-coverage.md +2 -2
  99. package/templates/skills/ark-explain.md +6 -3
  100. package/templates/skills/ark-explore.md +33 -1
  101. package/templates/skills/ark-place.md +42 -3
  102. package/templates/skills/ark-runtime.md +4 -2
  103. package/templates/skills/ark-upgrade.md +1 -1
@@ -2,6 +2,19 @@
2
2
  import { layerForRelativePath } from '../ark-layer-match.mjs';
3
3
  import { ANALYSIS_COMPLETENESS } from './analysis-completeness.mjs';
4
4
  import { evaluateArkRunEditorSensorsFromSource } from './ark-run-sensors.mjs';
5
+ import { getDiagnosticCatalogEntry } from './diagnostic-catalog.mjs';
6
+
7
+ function lexicalEvidenceIncompleteMessage(file) {
8
+ const entry = getDiagnosticCatalogEntry('LEXICAL_EVIDENCE_INCOMPLETE');
9
+ const text = [entry?.why, entry?.fix].filter(Boolean).join(' ');
10
+ return {
11
+ code: 'LEXICAL_EVIDENCE_INCOMPLETE',
12
+ message:
13
+ text ||
14
+ 'This check only saw one file, so it cannot fully prove how the import resolves. The result is provisional — `ark-check` on the project is the authority. Run `npx arkgate-check --root . --config ark.config.json` to confirm. Do not call ark_prepare_change from a hook deny.',
15
+ ...(file ? { file } : {}),
16
+ };
17
+ }
5
18
 
6
19
  export function flattenTsParseDiagnostics(ts, diagnostics, sourceFile) {
7
20
  if (!Array.isArray(diagnostics) || !ts) return [];
@@ -156,14 +169,7 @@ export function validateSnippetAnalysis({ gate, ts, source, context = {} }) {
156
169
  valid: false,
157
170
  lexicalValid: base.valid,
158
171
  completeness: ANALYSIS_COMPLETENESS.partial,
159
- completenessReasons: [
160
- {
161
- code: 'LEXICAL_EVIDENCE_INCOMPLETE',
162
- message:
163
- 'Single-file validation cannot prove project module resolution. The write hook is already the verdict, or re-run `npx arkgate-check --root . --config ark.config.json`. Do not call ark_prepare_change from a hook deny.',
164
- ...(file ? { file } : {}),
165
- },
166
- ],
172
+ completenessReasons: [lexicalEvidenceIncompleteMessage(file)],
167
173
  };
168
174
  } catch {
169
175
  return {
@@ -3,7 +3,13 @@ import { spawnSync } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
- import { arkCommand, buildArchitectureRecommendation } from '../ark-shared.mjs';
6
+ import {
7
+ arkCommand,
8
+ arkPackageRecoveryCommand,
9
+ buildArchitectureRecommendation,
10
+ START_APPLY_REFUSE_FOOTER,
11
+ } from '../ark-shared.mjs';
12
+ import { withProjectedGovernedCoverage } from './projected-governed-coverage.mjs';
7
13
  import { ARKORDER_FIRST_CONTACT_NEXT, ARKORDER_ONE_BREATH } from './ark-order-doctor.mjs';
8
14
  import { compactAgentInstructions, instructionRule, mcpJson } from './ci-and-commands.mjs';
9
15
  import {
@@ -134,6 +140,20 @@ function commands(root, args, helpers) {
134
140
  return result;
135
141
  }
136
142
 
143
+ /**
144
+ * Red next-step when post-apply package install fails.
145
+ * Host files may already be written; local bins may be missing.
146
+ */
147
+ export function formatStartPackageInstallFailure({ exitStatus, installCommand }) {
148
+ const doctor = arkPackageRecoveryCommand('arkgate-check', '--doctor');
149
+ return [
150
+ `Package install failed (exit ${exitStatus}). Setup files are written; the local command is not installed yet.`,
151
+ ` ${installCommand}`,
152
+ ` ${doctor}`,
153
+ '`arkgate-check` is a command in the arkgate package — not its own npm package.',
154
+ ].join('\n');
155
+ }
156
+
137
157
  /**
138
158
  * @param {object} preview
139
159
  * @param {{ applying?: boolean }} [options] when applying=true, do not claim “no files were changed”
@@ -166,6 +186,7 @@ export function renderStartPreview(preview, options = {}) {
166
186
  console.log('Optional extras stay off. This start is layers only — they stop bad imports.');
167
187
  console.log(ARKORDER_ONE_BREATH);
168
188
  console.log(ARKORDER_FIRST_CONTACT_NEXT);
189
+ console.log(START_APPLY_REFUSE_FOOTER);
169
190
  }
170
191
  if (preview.runtimeActivation) {
171
192
  console.log('Host: Codex is configured but not verified yet. Restart the host, then confirm this project.');
@@ -325,12 +346,13 @@ export async function planStart(args, helpers) {
325
346
  };
326
347
  } else {
327
348
  try {
328
- const rec = buildArchitectureRecommendation(root);
349
+ const rec = withProjectedGovernedCoverage(buildArchitectureRecommendation(root), root);
329
350
  recommendation = {
330
351
  archetype: rec.archetype,
331
352
  label: rec.label,
332
353
  confidence: rec.confidence,
333
354
  mature: rec.mature,
355
+ projectedGovernedCoverage: rec.signals?.projectedGovernedCoverage,
334
356
  };
335
357
  } catch {
336
358
  recommendation = null;
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Soft states/transitions artifact guidance (P2 §2 / Guiar).
3
+ * Tooling I/O. Never a gate fail. Silent unless a conventional domain
4
+ * (or dedicated states) markdown home is already in play.
5
+ * Does not walk the tree. Does not scan code for status fields.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ /** Product-domain / domain-model homes. Closed list — no tree walk. */
12
+ export const STATES_TRANSITIONS_DOMAIN_HOMES = Object.freeze([
13
+ 'docs/domain.md',
14
+ 'docs/data-model.md',
15
+ 'docs/product-domain.md',
16
+ 'docs/modelo-de-dominio.md',
17
+ 'docs/producto-y-dominio.md',
18
+ 'docs/architecture.md',
19
+ ]);
20
+
21
+ /** Evolved dedicated homes. Adopt if present; do not force a filename. */
22
+ export const STATES_TRANSITIONS_DEDICATED_HOMES = Object.freeze([
23
+ 'docs/states.md',
24
+ 'docs/transitions.md',
25
+ 'docs/states-transitions.md',
26
+ 'docs/lifecycle.md',
27
+ ]);
28
+
29
+ export const STATES_TRANSITIONS_ASK_MISSING =
30
+ 'A domain doc is in play, but there is no states → transitions map yet.';
31
+
32
+ export const STATES_TRANSITIONS_ASK_INCOMPLETE =
33
+ 'The states heading is there, but it is not a map yet — no table or link.';
34
+
35
+ export const STATES_TRANSITIONS_NEXT =
36
+ 'Add a short table (entity · states · allowed from → to) — or one link — on the domain doc. Names from the code. No flag soup.';
37
+
38
+ function readMarkdown(abs) {
39
+ try {
40
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return null;
41
+ const text = fs.readFileSync(abs, 'utf8');
42
+ return text.trim().length > 0 ? text : '';
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function cellIsStateVocab(cell) {
49
+ return (
50
+ cell === 'state' ||
51
+ cell === 'states' ||
52
+ cell.startsWith('state ') ||
53
+ cell.endsWith(' state') ||
54
+ cell.endsWith(' states')
55
+ );
56
+ }
57
+
58
+ function cellIsTransitionVocab(cell) {
59
+ return (
60
+ cell.includes('transition') ||
61
+ cell === 'from' ||
62
+ cell === 'to' ||
63
+ cell === 'from → to' ||
64
+ cell === 'from -> to'
65
+ );
66
+ }
67
+
68
+ function cellIsEntityVocab(cell) {
69
+ return cell === 'entity' || cell === 'entities';
70
+ }
71
+
72
+ /** Closed table: entity | states | allowed transitions (or from → to). */
73
+ export function markdownHasStatesTable(text) {
74
+ if (typeof text !== 'string' || text.length === 0) return false;
75
+ for (const line of text.split('\n')) {
76
+ const trimmed = line.trim();
77
+ if (!trimmed.startsWith('|')) continue;
78
+ const cells = trimmed
79
+ .split('|')
80
+ .map((cell) => cell.trim().toLowerCase())
81
+ .filter((cell) => cell.length > 0 && !/^[-:]+$/.test(cell));
82
+ if (cells.length < 2) continue;
83
+ const hasEntity = cells.some((cell) => cellIsEntityVocab(cell));
84
+ const hasState = cells.some((cell) => cellIsStateVocab(cell));
85
+ const hasTransition = cells.some((cell) => cellIsTransitionVocab(cell));
86
+ if ((hasState && hasTransition) || (hasEntity && (hasState || hasTransition))) return true;
87
+ }
88
+ return false;
89
+ }
90
+
91
+ function mentionsStatesTopic(value) {
92
+ return (
93
+ value.includes('transition') ||
94
+ value.includes('lifecycle') ||
95
+ value.includes('states/transitions') ||
96
+ value.includes('states-transitions') ||
97
+ value.includes('state machine')
98
+ );
99
+ }
100
+
101
+ /** One link to the captain’s existing authority. */
102
+ export function markdownHasStatesLink(text) {
103
+ if (typeof text !== 'string' || text.length === 0) return false;
104
+ let from = 0;
105
+ while (from < text.length) {
106
+ const open = text.indexOf('[', from);
107
+ if (open < 0) break;
108
+ const mid = text.indexOf('](', open + 1);
109
+ if (mid < 0) break;
110
+ const close = text.indexOf(')', mid + 2);
111
+ if (close < 0) break;
112
+ const label = text.slice(open + 1, mid).toLowerCase();
113
+ const href = text.slice(mid + 2, close).toLowerCase();
114
+ if (mentionsStatesTopic(label) || mentionsStatesTopic(href)) return true;
115
+ from = close + 1;
116
+ }
117
+ return false;
118
+ }
119
+
120
+ export function markdownHasStatesHeading(text) {
121
+ if (typeof text !== 'string' || text.length === 0) return false;
122
+ for (const line of text.split('\n')) {
123
+ const trimmed = line.trim();
124
+ if (!trimmed.startsWith('#')) continue;
125
+ const title = trimmed.replace(/^#+\s*/, '').toLowerCase();
126
+ if (
127
+ title === 'state' ||
128
+ title === 'states' ||
129
+ title === 'transition' ||
130
+ title === 'transitions' ||
131
+ title === 'lifecycle' ||
132
+ title.includes('states/transitions') ||
133
+ title.includes('states and transitions')
134
+ ) {
135
+ return true;
136
+ }
137
+ }
138
+ return false;
139
+ }
140
+
141
+ function scoreMarkdown(text) {
142
+ if (text == null) return 'absent';
143
+ if (text === '') return 'incomplete';
144
+ if (markdownHasStatesTable(text) || markdownHasStatesLink(text)) return 'present';
145
+ if (markdownHasStatesHeading(text)) return 'incomplete';
146
+ return 'missing';
147
+ }
148
+
149
+ function existingHomes(root, relatives) {
150
+ const found = [];
151
+ for (const rel of relatives) {
152
+ const text = readMarkdown(path.join(root, rel));
153
+ if (text == null) continue;
154
+ found.push({ rel, score: scoreMarkdown(text) });
155
+ }
156
+ return found;
157
+ }
158
+
159
+ /**
160
+ * First conventional home already on disk (domain doc or dedicated file).
161
+ * Empty / missing files do not count as a home.
162
+ *
163
+ * @param {string} root
164
+ * @returns {string | null}
165
+ */
166
+ export function findStatesTransitionsHome(root) {
167
+ if (typeof root !== 'string' || root.length === 0) return null;
168
+ const homes = [
169
+ ...existingHomes(root, STATES_TRANSITIONS_DOMAIN_HOMES),
170
+ ...existingHomes(root, STATES_TRANSITIONS_DEDICATED_HOMES),
171
+ ];
172
+ return homes[0]?.rel ?? null;
173
+ }
174
+
175
+ function nextActionFor(home) {
176
+ if (!home) return STATES_TRANSITIONS_NEXT;
177
+ return `Add a short table (entity · states · allowed from → to) — or one link — to ${home}. Names from the code. No flag soup.`;
178
+ }
179
+
180
+ /**
181
+ * Soft residual when a domain (or dedicated states) doc is in play and the
182
+ * map is missing or only a heading. Off / no such doc → null (silent).
183
+ *
184
+ * @param {{ root?: string }} [input]
185
+ * @returns {{ kind: 'missing' | 'incomplete', home: string, ask: string, nextAction: string } | null}
186
+ */
187
+ export function collectStatesTransitionsResidual(input = {}) {
188
+ const root = input.root ?? '';
189
+ if (typeof root !== 'string' || root.length === 0) return null;
190
+ const homes = [
191
+ ...existingHomes(root, STATES_TRANSITIONS_DOMAIN_HOMES),
192
+ ...existingHomes(root, STATES_TRANSITIONS_DEDICATED_HOMES),
193
+ ];
194
+ if (homes.length === 0) return null;
195
+ if (homes.some((row) => row.score === 'present')) return null;
196
+ const incomplete = homes.find((row) => row.score === 'incomplete');
197
+ const home = (incomplete ?? homes[0]).rel;
198
+ if (incomplete) {
199
+ return {
200
+ kind: 'incomplete',
201
+ home,
202
+ ask: STATES_TRANSITIONS_ASK_INCOMPLETE,
203
+ nextAction: nextActionFor(home),
204
+ };
205
+ }
206
+ return {
207
+ kind: 'missing',
208
+ home,
209
+ ask: STATES_TRANSITIONS_ASK_MISSING,
210
+ nextAction: nextActionFor(home),
211
+ };
212
+ }
@@ -306,6 +306,8 @@ export function collectStatusFacts(options = {}) {
306
306
  const softWriteHost = Boolean(matrix && matrix.capabilities?.['hard-write'] !== true);
307
307
  const writePathHonesty = buildWritePathHonesty(hostKey, hardWriteActive, {
308
308
  packageInstalled: writePath?.enforcementState?.localWrite?.installed !== false,
309
+ nativeFailClosed: writePath?.nativeFailClosed,
310
+ nativeFailClosedPolicy: writePath?.nativeFailClosedPolicy,
309
311
  });
310
312
 
311
313
  const latestPath = path.join(reportsDir(resolvedRoot), 'latest.json');