arkgate 3.9.1 → 4.0.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 (78) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +16 -4
  3. package/bin/ark-check-runtime.mjs +75 -3
  4. package/bin/ark-mcp-runtime.mjs +94 -0
  5. package/bin/lib/adapter-contract.mjs +14 -1
  6. package/bin/lib/ambient-state.mjs +64 -8
  7. package/bin/lib/analysis-engine.mjs +8 -8
  8. package/bin/lib/architecture-scan.mjs +35 -2
  9. package/bin/lib/arkrule-file-hints.mjs +71 -0
  10. package/bin/lib/arkrules-contract.mjs +382 -0
  11. package/bin/lib/arkrules-sensors.mjs +411 -0
  12. package/bin/lib/config-contract.mjs +85 -6
  13. package/bin/lib/doctor-advisories.mjs +22 -5
  14. package/bin/lib/doctor-plan.mjs +68 -13
  15. package/bin/lib/effective-contract-load.mjs +116 -0
  16. package/bin/lib/enforcement-honesty.mjs +225 -0
  17. package/bin/lib/field-install.mjs +104 -0
  18. package/bin/lib/graph-blind.mjs +254 -0
  19. package/bin/lib/html-report-advisories.mjs +29 -3
  20. package/bin/lib/install-migrate.mjs +20 -2
  21. package/bin/lib/invariant-coverage-io.mjs +157 -0
  22. package/bin/lib/invariant-coverage.mjs +127 -0
  23. package/bin/lib/pilot-loop.mjs +19 -0
  24. package/bin/lib/policy-delta-io.mjs +33 -0
  25. package/bin/lib/post-green-path.mjs +22 -1
  26. package/bin/lib/presets.mjs +241 -1
  27. package/bin/lib/remediation.mjs +28 -0
  28. package/bin/lib/resolved-candidate-facts.mjs +14 -1
  29. package/bin/lib/rules-inventory.mjs +144 -0
  30. package/bin/lib/rules-under-contract.mjs +66 -0
  31. package/bin/lib/start-preview.mjs +24 -7
  32. package/bin/lib/upgrade-command.mjs +48 -2
  33. package/dist/{configTypes-DAPvBqK6.d.ts → configTypes-CC0FEXoF.d.ts} +16 -3
  34. package/dist/eslint/index.cjs +2 -2
  35. package/dist/eslint/index.d.ts +1 -1
  36. package/dist/eslint/index.js +2 -2
  37. package/dist/index.cjs +14 -7
  38. package/dist/index.d.ts +615 -20
  39. package/dist/index.js +13 -6
  40. package/docs/README.md +4 -3
  41. package/docs/agent-guide.md +7 -3
  42. package/docs/ai-gates.md +6 -1
  43. package/docs/brownfield-adoption.md +20 -0
  44. package/docs/configuration.md +37 -4
  45. package/docs/develop.md +8 -2
  46. package/docs/enthusiast/README.md +11 -0
  47. package/docs/package-surface.md +13 -11
  48. package/docs/product-voice.md +9 -2
  49. package/docs/use.md +9 -0
  50. package/package.json +4 -17
  51. package/schemas/ark.analysis-result.schema.json +9 -1
  52. package/schemas/ark.arkrules.schema.json +141 -0
  53. package/schemas/ark.config.schema.json +10 -2
  54. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  55. package/server.json +3 -3
  56. package/templates/arkrules/ApplicationOrchestration.json +14 -0
  57. package/templates/arkrules/DomainModel.json +32 -0
  58. package/templates/arkrules/PersistenceAdapters.json +14 -0
  59. package/templates/arkrules/PresentationAdapters.json +14 -0
  60. package/templates/skills/ark-adopt.md +28 -1
  61. package/templates/skills/ark-architect.md +23 -0
  62. package/templates/skills/ark-autopilot.md +27 -1
  63. package/templates/skills/ark-contract.md +27 -1
  64. package/templates/skills/ark-coverage.md +30 -0
  65. package/templates/skills/ark-explain.md +23 -0
  66. package/templates/skills/ark-explore.md +30 -2
  67. package/templates/skills/ark-fix.md +23 -0
  68. package/templates/skills/ark-loop.md +23 -0
  69. package/templates/skills/ark-place.md +30 -0
  70. package/templates/skills/ark-runtime.md +4 -0
  71. package/templates/skills/ark-think.md +24 -1
  72. package/templates/skills/ark-upgrade.md +23 -0
  73. package/compat/nestjs.cjs +0 -2
  74. package/compat/nestjs.d.ts +0 -2
  75. package/compat/nestjs.js +0 -1
  76. package/compat/runtime.cjs +0 -2
  77. package/compat/runtime.d.ts +0 -2
  78. package/compat/runtime.js +0 -1
@@ -0,0 +1,382 @@
1
+ /**
2
+ * GENERATED FILE — do not edit by hand.
3
+ *
4
+ * Canonical algorithm: src/domain/arkRulesContract.ts
5
+ * Regenerate: node scripts/generate-cli-pure.mjs
6
+ * Drift check: node scripts/generate-cli-pure.mjs --check
7
+ *
8
+ * Pure CLI helper (bin/lib/arkrules-contract.mjs). Zero Node I/O.
9
+ */
10
+
11
+ export const ARK_RULES_SCHEMA_VERSION = '1.0';
12
+ export const ARK_RULES_SCHEMA_URL = 'https://unpkg.com/arkgate/schemas/ark.arkrules.schema.json';
13
+ /** Closed sensor vocabulary — keep in lockstep with arkRulesTypes.ARK_RULE_SENSOR_IDS. */
14
+ export const ARK_RULE_SENSORS = [
15
+ 'aggregate-private-state',
16
+ 'always-valid-factory',
17
+ 'domain-event-on-mutation',
18
+ 'orchestration-only',
19
+ 'thin-adapter',
20
+ 'no-anemic-model',
21
+ 'invariant-coverage',
22
+ ];
23
+ export const ARK_RULE_TIER2_SENSORS = ['no-anemic-model'];
24
+ const stringArraySchema = {
25
+ type: 'array',
26
+ items: { type: 'string', minLength: 1 },
27
+ uniqueItems: true,
28
+ };
29
+ export const ARK_RULES_SCHEMA = {
30
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
31
+ $id: ARK_RULES_SCHEMA_URL,
32
+ title: 'ArkGate ArkRules (intra-layer contract)',
33
+ description: 'Per-layer structure sensors and invariant catalog consumed by the ArkGate Effective Contract.',
34
+ type: 'object',
35
+ additionalProperties: false,
36
+ required: ['schemaVersion', 'layer'],
37
+ properties: {
38
+ $schema: {
39
+ type: 'string',
40
+ minLength: 1,
41
+ default: ARK_RULES_SCHEMA_URL,
42
+ },
43
+ schemaVersion: {
44
+ type: 'string',
45
+ const: ARK_RULES_SCHEMA_VERSION,
46
+ default: ARK_RULES_SCHEMA_VERSION,
47
+ },
48
+ layer: { type: 'string', minLength: 1 },
49
+ structure: {
50
+ type: 'array',
51
+ default: [],
52
+ items: { $ref: '#/$defs/structureEntry' },
53
+ },
54
+ invariants: {
55
+ type: 'array',
56
+ default: [],
57
+ items: { $ref: '#/$defs/invariantEntry' },
58
+ },
59
+ },
60
+ $defs: {
61
+ structureEntry: {
62
+ type: 'object',
63
+ additionalProperties: false,
64
+ required: ['id', 'sensor'],
65
+ properties: {
66
+ id: { type: 'string', minLength: 1 },
67
+ sensor: { type: 'string', enum: [...ARK_RULE_SENSORS] },
68
+ mode: { type: 'string', enum: ['advisory', 'enforced'], default: 'advisory' },
69
+ appliesTo: stringArraySchema,
70
+ description: { type: 'string', minLength: 1 },
71
+ },
72
+ },
73
+ invariantEntry: {
74
+ type: 'object',
75
+ additionalProperties: false,
76
+ required: ['id', 'description'],
77
+ properties: {
78
+ id: { type: 'string', minLength: 1 },
79
+ description: { type: 'string', minLength: 1 },
80
+ aggregate: { type: 'string', minLength: 1 },
81
+ coverage: {
82
+ type: 'object',
83
+ additionalProperties: false,
84
+ properties: {
85
+ test: { type: 'boolean' },
86
+ symbol: { type: 'string', minLength: 1 },
87
+ },
88
+ },
89
+ mode: { type: 'string', enum: ['advisory', 'enforced'], default: 'advisory' },
90
+ appliesTo: stringArraySchema,
91
+ },
92
+ },
93
+ },
94
+ };
95
+ export class ArkRulesValidationError extends Error {
96
+ issues;
97
+ source;
98
+ constructor(source, issues) {
99
+ super(`Invalid ArkRules (${source}):\n${issues
100
+ .map((issue) => `- ${issue.path}: ${issue.message}`)
101
+ .join('\n')}`);
102
+ this.name = 'ArkRulesValidationError';
103
+ this.source = source;
104
+ this.issues = issues;
105
+ }
106
+ }
107
+ function isObject(value) {
108
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
109
+ }
110
+ function propertyPath(parent, key) {
111
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
112
+ ? `${parent}.${key}`
113
+ : `${parent}[${JSON.stringify(key)}]`;
114
+ }
115
+ function valueType(value) {
116
+ if (value === null)
117
+ return 'null';
118
+ if (Array.isArray(value))
119
+ return 'array';
120
+ return typeof value;
121
+ }
122
+ function resolveSchemaRef(ref, root) {
123
+ const prefix = '#/$defs/';
124
+ if (!ref.startsWith(prefix))
125
+ return undefined;
126
+ return root.$defs[ref.slice(prefix.length)];
127
+ }
128
+ function validateNode(value, schema, path, root, issues) {
129
+ if (schema.$ref) {
130
+ const referenced = resolveSchemaRef(schema.$ref, root);
131
+ if (!referenced) {
132
+ issues.push({ path, message: `schema reference ${schema.$ref} cannot be resolved` });
133
+ return;
134
+ }
135
+ validateNode(value, referenced, path, root, issues);
136
+ return;
137
+ }
138
+ if (schema.const !== undefined && !Object.is(value, schema.const)) {
139
+ issues.push({ path, message: `must equal ${JSON.stringify(schema.const)}` });
140
+ return;
141
+ }
142
+ if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
143
+ issues.push({ path, message: `must be one of ${schema.enum.map(String).join(', ')}` });
144
+ return;
145
+ }
146
+ if (schema.type === 'object') {
147
+ if (!isObject(value)) {
148
+ issues.push({ path, message: `must be an object; received ${valueType(value)}` });
149
+ return;
150
+ }
151
+ const properties = schema.properties ?? {};
152
+ for (const key of schema.required ?? []) {
153
+ if (value[key] === undefined) {
154
+ issues.push({ path: propertyPath(path, key), message: 'is required' });
155
+ }
156
+ }
157
+ if (schema.additionalProperties === false) {
158
+ for (const key of Object.keys(value)) {
159
+ if (!(key in properties)) {
160
+ issues.push({ path: propertyPath(path, key), message: 'unknown field' });
161
+ }
162
+ }
163
+ }
164
+ else if (isObject(schema.additionalProperties)) {
165
+ const additional = schema.additionalProperties;
166
+ for (const key of Object.keys(value)) {
167
+ if (!(key in properties)) {
168
+ validateNode(value[key], additional, propertyPath(path, key), root, issues);
169
+ }
170
+ }
171
+ }
172
+ for (const [key, childSchema] of Object.entries(properties)) {
173
+ if (value[key] !== undefined) {
174
+ validateNode(value[key], childSchema, propertyPath(path, key), root, issues);
175
+ }
176
+ }
177
+ return;
178
+ }
179
+ if (schema.type === 'array') {
180
+ if (!Array.isArray(value)) {
181
+ issues.push({ path, message: `must be an array; received ${valueType(value)}` });
182
+ return;
183
+ }
184
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
185
+ issues.push({ path, message: `must contain at least ${schema.minItems} item(s)` });
186
+ }
187
+ if (schema.uniqueItems) {
188
+ const serialized = value.map((entry) => JSON.stringify(entry));
189
+ if (new Set(serialized).size !== serialized.length) {
190
+ issues.push({ path, message: 'must not contain duplicate items' });
191
+ }
192
+ }
193
+ if (schema.items) {
194
+ value.forEach((entry, index) => validateNode(entry, schema.items, `${path}[${index}]`, root, issues));
195
+ }
196
+ return;
197
+ }
198
+ if (schema.type === 'string') {
199
+ if (typeof value !== 'string') {
200
+ issues.push({ path, message: `must be a string; received ${valueType(value)}` });
201
+ return;
202
+ }
203
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
204
+ issues.push({ path, message: `must contain at least ${schema.minLength} character(s)` });
205
+ }
206
+ return;
207
+ }
208
+ if (schema.type === 'boolean') {
209
+ if (typeof value !== 'boolean') {
210
+ issues.push({ path, message: `must be a boolean; received ${valueType(value)}` });
211
+ }
212
+ }
213
+ }
214
+ function defaultedArkRules(input) {
215
+ return {
216
+ ...input,
217
+ $schema: input.$schema === undefined ? ARK_RULES_SCHEMA_URL : input.$schema,
218
+ schemaVersion: input.schemaVersion === undefined ? ARK_RULES_SCHEMA_VERSION : input.schemaVersion,
219
+ structure: input.structure === undefined ? [] : input.structure,
220
+ invariants: input.invariants === undefined ? [] : input.invariants,
221
+ };
222
+ }
223
+ function normalizeMode(mode) {
224
+ return mode === 'enforced' ? 'enforced' : 'advisory';
225
+ }
226
+ function isTier2Sensor(sensor) {
227
+ return ARK_RULE_TIER2_SENSORS.includes(sensor);
228
+ }
229
+ /**
230
+ * Semantic checks that sit on top of the JSON Schema walk:
231
+ * unique ids, tier-2 cannot be enforced, empty appliesTo arrays rejected.
232
+ */
233
+ function validateSemantics(candidate, issues) {
234
+ const structure = Array.isArray(candidate.structure) ? candidate.structure : [];
235
+ const invariants = Array.isArray(candidate.invariants) ? candidate.invariants : [];
236
+ const seen = new Set();
237
+ structure.forEach((entry, index) => {
238
+ if (!isObject(entry))
239
+ return;
240
+ const id = typeof entry.id === 'string' ? entry.id : '';
241
+ if (id) {
242
+ if (seen.has(id)) {
243
+ issues.push({
244
+ path: `$.structure[${index}].id`,
245
+ message: `duplicate rule id ${JSON.stringify(id)}`,
246
+ });
247
+ }
248
+ seen.add(id);
249
+ }
250
+ if (typeof entry.sensor === 'string' &&
251
+ isTier2Sensor(entry.sensor) &&
252
+ entry.mode === 'enforced') {
253
+ issues.push({
254
+ path: `$.structure[${index}].mode`,
255
+ message: `sensor ${JSON.stringify(entry.sensor)} is Tier-2 advisory-only and cannot be enforced`,
256
+ });
257
+ }
258
+ if (Array.isArray(entry.appliesTo) && entry.appliesTo.length === 0) {
259
+ issues.push({
260
+ path: `$.structure[${index}].appliesTo`,
261
+ message: 'must not be an empty array (omit the field to apply to the whole layer)',
262
+ });
263
+ }
264
+ });
265
+ invariants.forEach((entry, index) => {
266
+ if (!isObject(entry))
267
+ return;
268
+ const id = typeof entry.id === 'string' ? entry.id : '';
269
+ if (id) {
270
+ if (seen.has(id)) {
271
+ issues.push({
272
+ path: `$.invariants[${index}].id`,
273
+ message: `duplicate rule id ${JSON.stringify(id)}`,
274
+ });
275
+ }
276
+ seen.add(id);
277
+ }
278
+ if (Array.isArray(entry.appliesTo) && entry.appliesTo.length === 0) {
279
+ issues.push({
280
+ path: `$.invariants[${index}].appliesTo`,
281
+ message: 'must not be an empty array (omit the field to apply to the whole layer)',
282
+ });
283
+ }
284
+ });
285
+ }
286
+ export function loadArkRulesContract(input, source = 'arkrules.json', expectedLayer) {
287
+ if (!isObject(input)) {
288
+ throw new ArkRulesValidationError(source, [
289
+ { path: '$', message: `must be an object; received ${valueType(input)}` },
290
+ ]);
291
+ }
292
+ const candidate = defaultedArkRules(input);
293
+ const issues = [];
294
+ validateNode(candidate, ARK_RULES_SCHEMA, '$', ARK_RULES_SCHEMA, issues);
295
+ validateSemantics(candidate, issues);
296
+ if (expectedLayer !== undefined &&
297
+ typeof candidate.layer === 'string' &&
298
+ candidate.layer !== expectedLayer) {
299
+ issues.push({
300
+ path: '$.layer',
301
+ message: `must match referencing key ${JSON.stringify(expectedLayer)}; received ${JSON.stringify(candidate.layer)}`,
302
+ });
303
+ }
304
+ if (issues.length > 0)
305
+ throw new ArkRulesValidationError(source, issues);
306
+ return { config: candidate };
307
+ }
308
+ export function parseArkRulesJson(json, source = 'arkrules.json', expectedLayer) {
309
+ let input;
310
+ try {
311
+ input = JSON.parse(json);
312
+ }
313
+ catch (error) {
314
+ throw new ArkRulesValidationError(source, [
315
+ {
316
+ path: '$',
317
+ message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
318
+ },
319
+ ]);
320
+ }
321
+ return loadArkRulesContract(input, source, expectedLayer);
322
+ }
323
+ /**
324
+ * Build the Effective Contract from already-validated ArkRules files.
325
+ * Callers supply `{ layer → { sourceFile, file } }` after resolving references.
326
+ */
327
+ export function buildEffectiveArkRules(parts) {
328
+ const byLayer = {};
329
+ const structure = [];
330
+ const invariants = [];
331
+ const ordered = [...parts].sort((a, b) => a.layer.localeCompare(b.layer));
332
+ for (const part of ordered) {
333
+ const structureRules = (part.file.structure ?? []).map((entry) => ({
334
+ ...entry,
335
+ mode: normalizeMode(entry.mode),
336
+ provenance: {
337
+ sourceFile: part.sourceFile,
338
+ ruleId: entry.id,
339
+ layer: part.layer,
340
+ },
341
+ }));
342
+ const invariantRules = (part.file.invariants ?? []).map((entry) => ({
343
+ ...entry,
344
+ mode: normalizeMode(entry.mode),
345
+ provenance: {
346
+ sourceFile: part.sourceFile,
347
+ ruleId: entry.id,
348
+ layer: part.layer,
349
+ },
350
+ }));
351
+ byLayer[part.layer] = {
352
+ sourceFile: part.sourceFile,
353
+ structure: structureRules,
354
+ invariants: invariantRules,
355
+ };
356
+ structure.push(...structureRules);
357
+ invariants.push(...invariantRules);
358
+ }
359
+ structure.sort((a, b) => {
360
+ const layer = a.provenance.layer.localeCompare(b.provenance.layer);
361
+ return layer !== 0 ? layer : a.id.localeCompare(b.id);
362
+ });
363
+ invariants.sort((a, b) => {
364
+ const layer = a.provenance.layer.localeCompare(b.provenance.layer);
365
+ return layer !== 0 ? layer : a.id.localeCompare(b.id);
366
+ });
367
+ return {
368
+ schemaVersion: ARK_RULES_SCHEMA_VERSION,
369
+ byLayer,
370
+ structure,
371
+ invariants,
372
+ };
373
+ }
374
+ /** Empty Effective Contract used when `arkRules` is absent (byte-for-byte verdict parity). */
375
+ export function emptyEffectiveArkRules() {
376
+ return {
377
+ schemaVersion: ARK_RULES_SCHEMA_VERSION,
378
+ byLayer: {},
379
+ structure: [],
380
+ invariants: [],
381
+ };
382
+ }