arkgate 4.7.6 → 4.8.1
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.
- package/CHANGELOG.md +37 -3
- package/README.md +8 -9
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/architecture-scan.mjs +7 -2
- package/bin/lib/ark-order-error.mjs +18 -0
- package/bin/lib/ark-order-facts.mjs +53 -0
- package/bin/lib/ark-order-invariants.mjs +160 -0
- package/bin/lib/ark-order-sensors.mjs +118 -0
- package/bin/lib/ark-order-types.mjs +11 -0
- package/bin/lib/ark-run-sensors.mjs +13 -5
- package/bin/lib/config-contract.mjs +16 -72
- package/bin/lib/config-extras.mjs +151 -0
- package/bin/lib/diagnostic-catalog.mjs +7 -2
- package/bin/lib/extra-merge-teeth.mjs +8 -2
- package/bin/lib/install-migrate.mjs +4 -2
- package/bin/lib/invariant-coverage-io.mjs +61 -24
- package/bin/lib/invariant-coverage.mjs +4 -1
- package/bin/lib/managed-upgrade.mjs +1 -1
- package/bin/lib/mcp-adoption.mjs +9 -3
- package/bin/lib/policy-delta-io.mjs +8 -2
- package/bin/lib/remediation.mjs +48 -9
- package/bin/lib/resolved-candidate-facts.mjs +43 -0
- package/bin/lib/rules-under-contract.mjs +8 -2
- package/bin/lib/skill-install.mjs +17 -2
- package/bin/lib/start-preview.mjs +1 -0
- package/bin/lib/status-manifest.mjs +1 -1
- package/bin/lib/write-path-capabilities.mjs +2 -2
- package/dist/{configTypes-CgJimx9o.d.ts → configTypes-BdCe_gvv.d.ts} +22 -6
- package/dist/diagnosticCatalog-CSF4N3w8.d.ts +2313 -0
- package/dist/eslint/index.cjs +7 -6
- package/dist/eslint/index.d.ts +33 -2
- package/dist/eslint/index.js +7 -6
- package/dist/index.cjs +35 -35
- package/dist/index.d.ts +271 -2554
- package/dist/index.js +35 -35
- package/dist/nestjs/index.cjs +18 -0
- package/dist/nestjs/index.d.ts +24 -0
- package/dist/nestjs/index.js +18 -0
- package/dist/order/index.cjs +1 -0
- package/dist/order/index.d.ts +79 -0
- package/dist/order/index.js +1 -0
- package/dist/runtime/index.cjs +25 -0
- package/dist/runtime/index.d.ts +497 -0
- package/dist/runtime/index.js +25 -0
- package/dist/types-C9KApBzX.d.ts +1237 -0
- package/dist/types-DCSlrRnV.d.ts +181 -0
- package/docs/README.md +4 -4
- package/docs/agent-guide.md +1 -1
- package/docs/ai-gates.md +9 -2
- package/docs/configuration.md +13 -6
- package/docs/develop.md +4 -3
- package/docs/diagnostics.md +57 -3
- package/docs/package-surface.md +20 -16
- package/docs/product-voice.md +2 -1
- package/package.json +21 -2
- package/schemas/ark.config.schema.json +54 -2
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +2 -2
- package/templates/agent-skills/ark-contract/SKILL.md +1 -1
- package/templates/agent-skills/ark-place/SKILL.md +15 -6
- package/templates/agent-skills/ark-runtime/SKILL.md +10 -15
- package/templates/skills/ark-adopt.md +2 -2
- package/templates/skills/ark-contract.md +1 -1
- package/templates/skills/ark-place.md +15 -6
- package/templates/skills/ark-runtime.md +10 -15
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/configExtras.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/config-extras.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const stringArraySchema = {
|
|
12
|
+
type: 'array',
|
|
13
|
+
items: { type: 'string', minLength: 1 },
|
|
14
|
+
uniqueItems: true,
|
|
15
|
+
};
|
|
16
|
+
export const ARK_RUN_SCHEMA_DEF = {
|
|
17
|
+
type: 'object',
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
properties: {
|
|
20
|
+
mode: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
enum: ['advisory', 'enforced'],
|
|
23
|
+
default: 'advisory',
|
|
24
|
+
},
|
|
25
|
+
compositionRoots: { ...stringArraySchema, default: [] },
|
|
26
|
+
kernelRoots: { ...stringArraySchema },
|
|
27
|
+
managedLayers: { ...stringArraySchema, default: [] },
|
|
28
|
+
requireDeclarations: { type: 'boolean', default: true },
|
|
29
|
+
ignoreDirectNewForErrors: { type: 'boolean', default: true },
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export const ARK_ORDER_SCHEMA_DEF = {
|
|
33
|
+
type: 'object',
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
properties: {
|
|
36
|
+
mode: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['advisory', 'enforced'],
|
|
39
|
+
default: 'advisory',
|
|
40
|
+
},
|
|
41
|
+
planeRoots: { ...stringArraySchema, default: [] },
|
|
42
|
+
managedLayers: { ...stringArraySchema, default: [] },
|
|
43
|
+
maxXiKeys: { type: 'integer', minimum: 1, default: 7 },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
function isObject(value) {
|
|
47
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
48
|
+
}
|
|
49
|
+
function declaredLayerNames(config) {
|
|
50
|
+
const layerNames = new Set();
|
|
51
|
+
if (!Array.isArray(config.layers))
|
|
52
|
+
return layerNames;
|
|
53
|
+
for (const layer of config.layers) {
|
|
54
|
+
if (isObject(layer) && typeof layer.name === 'string' && layer.name.length > 0) {
|
|
55
|
+
layerNames.add(layer.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return layerNames;
|
|
59
|
+
}
|
|
60
|
+
export function defaultedArkRun(value) {
|
|
61
|
+
if (!isObject(value))
|
|
62
|
+
return value;
|
|
63
|
+
const out = {
|
|
64
|
+
mode: value.mode === undefined ? 'advisory' : value.mode,
|
|
65
|
+
compositionRoots: value.compositionRoots === undefined ? [] : value.compositionRoots,
|
|
66
|
+
managedLayers: value.managedLayers === undefined ? [] : value.managedLayers,
|
|
67
|
+
requireDeclarations: value.requireDeclarations === undefined ? true : value.requireDeclarations,
|
|
68
|
+
};
|
|
69
|
+
if (value.kernelRoots !== undefined)
|
|
70
|
+
out.kernelRoots = value.kernelRoots;
|
|
71
|
+
if (value.ignoreDirectNewForErrors !== undefined) {
|
|
72
|
+
out.ignoreDirectNewForErrors = value.ignoreDirectNewForErrors;
|
|
73
|
+
}
|
|
74
|
+
return { ...value, ...out };
|
|
75
|
+
}
|
|
76
|
+
export function defaultedArkOrder(value) {
|
|
77
|
+
if (!isObject(value))
|
|
78
|
+
return value;
|
|
79
|
+
const max = typeof value.maxXiKeys === 'number' && value.maxXiKeys > 0 ? value.maxXiKeys : 7;
|
|
80
|
+
return {
|
|
81
|
+
...value,
|
|
82
|
+
mode: value.mode === undefined ? 'advisory' : value.mode,
|
|
83
|
+
planeRoots: value.planeRoots === undefined ? [] : value.planeRoots,
|
|
84
|
+
managedLayers: value.managedLayers === undefined ? [] : value.managedLayers,
|
|
85
|
+
maxXiKeys: max,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function validateArkRunExtra(config, issues) {
|
|
89
|
+
const extra = config.arkRun;
|
|
90
|
+
if (extra === undefined || !isObject(extra))
|
|
91
|
+
return;
|
|
92
|
+
const layerNames = declaredLayerNames(config);
|
|
93
|
+
const managed = extra.managedLayers;
|
|
94
|
+
if (Array.isArray(managed)) {
|
|
95
|
+
managed.forEach((name, index) => {
|
|
96
|
+
if (typeof name === 'string' && name.length > 0 && !layerNames.has(name)) {
|
|
97
|
+
issues.push({
|
|
98
|
+
path: `$.arkRun.managedLayers[${index}]`,
|
|
99
|
+
message: `layer ${JSON.stringify(name)} is not declared in layers[]`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (extra.mode === 'enforced') {
|
|
105
|
+
const roots = extra.kernelRoots ?? extra.compositionRoots;
|
|
106
|
+
if (!Array.isArray(roots) || roots.length === 0) {
|
|
107
|
+
issues.push({
|
|
108
|
+
path: extra.kernelRoots !== undefined ? '$.arkRun.kernelRoots' : '$.arkRun.compositionRoots',
|
|
109
|
+
message: 'ARKRUN_MISSING_ROOT: enforced mode requires at least one kernel root',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (!Array.isArray(managed) || managed.length === 0) {
|
|
113
|
+
issues.push({
|
|
114
|
+
path: '$.arkRun.managedLayers',
|
|
115
|
+
message: 'enforced mode requires at least one managed layer',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function validateArkOrderExtra(config, issues) {
|
|
121
|
+
const extra = config.arkOrder;
|
|
122
|
+
if (extra === undefined || !isObject(extra))
|
|
123
|
+
return;
|
|
124
|
+
const layerNames = declaredLayerNames(config);
|
|
125
|
+
const managed = extra.managedLayers;
|
|
126
|
+
if (Array.isArray(managed)) {
|
|
127
|
+
managed.forEach((name, index) => {
|
|
128
|
+
if (typeof name === 'string' && name.length > 0 && !layerNames.has(name)) {
|
|
129
|
+
issues.push({
|
|
130
|
+
path: `$.arkOrder.managedLayers[${index}]`,
|
|
131
|
+
message: `layer ${JSON.stringify(name)} is not declared in layers[]`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (extra.mode === 'enforced') {
|
|
137
|
+
const roots = extra.planeRoots;
|
|
138
|
+
if (!Array.isArray(roots) || roots.length === 0) {
|
|
139
|
+
issues.push({
|
|
140
|
+
path: '$.arkOrder.planeRoots',
|
|
141
|
+
message: 'ARKORDER_MISSING_PLANE: enforced mode requires at least one plane root',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (!Array.isArray(managed) || managed.length === 0) {
|
|
145
|
+
issues.push({
|
|
146
|
+
path: '$.arkOrder.managedLayers',
|
|
147
|
+
message: 'enforced mode requires at least one managed layer',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -57,13 +57,18 @@ export const DIAGNOSTIC_CATALOG = Object.freeze([
|
|
|
57
57
|
entry('ARKRULE_SCOPE_EMPTY', 'arkrules', 'ArkRule appliesTo matched zero files', 'An ArkRule’s appliesTo globs matched no governed files — the rule cannot observe what it claims to protect.', 'Fix appliesTo globs so they match governed files, or remove the rule. Enforced empty scope fails; advisory empty scope warns.', { oftenAdvisory: true }),
|
|
58
58
|
entry('INVARIANT_UNCOVERED', 'arkrules', 'Invariant without coverage evidence', 'An ArkRules invariant is under contract but no covering test title or declared symbol evidence was found (or coverage is partial). Kind is never-had-tests (adopt residual) vs tests-disappeared (suite exists).', 'Add a test title or declared symbol covering the arkruleId, then preflight again. Treat never-had-tests as adopt residual; treat tests-disappeared as a regression. Missing test globs report partial — never fake green.'),
|
|
59
59
|
// ── ArkRun (opt-in extra; RN05 dual-depth nextAction) ────────────────────
|
|
60
|
-
entry('ARKRUN_MISSING_ROOT', 'arkrun', 'No kernel factory in composition roots', 'The ArkRun extra is on but no createArkKernel / createStrictArkKernel / createArkKernelFromConfig / createStrictArkKernelFromConfig factory was found in arkRun.compositionRoots, so agents can skip the kernel while the write gate stays green.', 'Import createStrictArkKernel from
|
|
61
|
-
entry('ARKRUN_KERNEL_IN_DOMAIN', 'arkrun', 'Domain-role layer imports the kernel', 'A Domain-role layer imports @arkgate/runtime or kernel types. Domain stays kernel-free; composition roots and adapters own the factory.', 'Move the kernel import out of the Domain-role layer into a composition root or adapter. Import from
|
|
60
|
+
entry('ARKRUN_MISSING_ROOT', 'arkrun', 'No kernel factory in composition roots', 'The ArkRun extra is on but no createArkKernel / createStrictArkKernel / createArkKernelFromConfig / createStrictArkKernelFromConfig factory was found in arkRun.compositionRoots, so agents can skip the kernel while the write gate stays green.', 'Import createStrictArkKernel from arkgate/runtime (same npm package; @arkgate/runtime is deprecated) and call it in a composition root listed in arkRun.compositionRoots, then preflight again. Never mechanical-safe — factory placement is a design decision.'),
|
|
61
|
+
entry('ARKRUN_KERNEL_IN_DOMAIN', 'arkrun', 'Domain-role layer imports the kernel', 'A Domain-role layer imports arkgate/runtime, @arkgate/runtime, or kernel types. Domain stays kernel-free; composition roots and adapters own the factory.', 'Move the kernel import out of the Domain-role layer into a composition root or adapter. Import from arkgate/runtime (same npm package; @arkgate/runtime is deprecated), then preflight again. Never mechanical-safe.'),
|
|
62
62
|
entry('ARKRUN_DIRECT_NEW', 'arkrun', 'Managed type constructed with new', 'A managed non-Domain file constructs an admitted type with new outside an ArkRun composition-root factory, skipping kernel resolve/registration.', 'Resolve the type from the kernel instead of constructing it with new, then preflight again. Never mechanical-safe — rewiring construction is a design decision.'),
|
|
63
63
|
entry('ARKRUN_UNDECLARED_EMIT', 'arkrun', 'Emit name not in raises/sends', 'A publisher / publish / raise / send call-site literal is not listed in the file’s raises or sends declaration.', 'Add the existing call-site name to raises or sends on the managed component, then preflight again. Mechanical-safe only when that literal already exists and the edit is the declaration list; inventing a new emit stays judgment.'),
|
|
64
64
|
entry('ARKRUN_UNDECLARED_HANDLE', 'arkrun', 'Handle name not in reactsTo', 'A subscribe / registerHandler call-site literal is not listed in the file’s reactsTo declaration.', 'Add the existing call-site name to reactsTo on the managed component, then preflight again. Mechanical-safe only when that literal already exists and the edit is the declaration list; inventing a new handle stays judgment.'),
|
|
65
65
|
entry('ARKRUN_UNDECLARED_DEPEND', 'arkrun', 'Depend name not in uses', 'A resolve / resolveSingleton call-site literal is not listed in the file’s uses declaration.', 'Add the existing call-site name to uses on the managed component, then preflight again. Mechanical-safe only when that literal already exists and the edit is the declaration list; inventing a new depend stays judgment.'),
|
|
66
66
|
entry('ARKRUN_TRANSPORT_BYPASS', 'arkrun', 'Homemade broker or emitter import', 'A managed layer imports a closed broker/queue/emitter specifier (EventEmitter, queue clients, …) instead of the ArkRun kernel transport.', 'Send through the ArkRun kernel transport instead of importing that broker or emitter, then preflight again. Never mechanical-safe — homemade buses stay judgment.'),
|
|
67
|
+
entry('ARKORDER_MISSING_PLANE', 'arkorder', 'No createOrderPlane in plane roots', 'The ArkOrder extra is on but no createOrderPlane factory was found in arkOrder.planeRoots, so agents can skip the pattern plane while the write gate stays green.', 'Import createOrderPlane from arkgate/order and call it in a plane root listed in arkOrder.planeRoots, then preflight again. Never mechanical-safe — factory placement is a design decision.'),
|
|
68
|
+
entry('ARKORDER_KERNEL_IN_DOMAIN', 'arkorder', 'Domain-role layer imports the order plane', 'A Domain-role layer imports arkgate/order. Domain stays plane-free; planeRoots own the factory.', 'Move the arkgate/order import out of the Domain-role layer into a plane root or adapter, then preflight again. Never mechanical-safe.'),
|
|
69
|
+
entry('ARKORDER_GENERIC_UPDATE', 'arkorder', 'Generic update of ξ', 'A call to update/patch/set on the order plane rewrites the slow pattern. Haken slaving forbids generic ξ mutation.', 'Use release() to freeze ξ or proposeRelease() for a pattern change with blast radius, then preflight again. Never mechanical-safe.'),
|
|
70
|
+
entry('ARKORDER_TOO_MANY_PARAMS', 'arkorder', 'Too many slow keys', 'ξ has more keys than arkOrder.maxXiKeys. Haken requires a few slow modes, not a dump of microstate.', 'Cut ξ to the slow keys that actually slave the rest, then preflight again. Never mechanical-safe.'),
|
|
71
|
+
entry('ARKORDER_INGEST_WRITES_XI', 'arkorder', 'ingest assigned into ξ', 'An ingest() result is written into a Release or ξ store. ingest may absorb or escalate; it never mints a pattern.', 'Keep ingest results as absorb/escalate only. Change ξ with proposeRelease + release. Never mechanical-safe.'),
|
|
67
72
|
// ── atomic preflight / change set ────────────────────────────────────────
|
|
68
73
|
entry('INVALID_CHANGE_PATH', 'preflight', 'Unsafe change path', 'A change set entry is not a safe, non-empty project-relative path (absolute, escape, empty, or NUL).', 'Use canonical project-relative paths only in the atomic change set, then preflight again.'),
|
|
69
74
|
entry('DUPLICATE_CHANGE_PATH', 'preflight', 'Duplicate path in change set', 'The atomic change set lists more than one operation for the same path.', 'Collapse to one create/update/delete per path, then preflight again.'),
|
|
@@ -49,11 +49,17 @@ export function classifyResolvedLayerCoverage(files) {
|
|
|
49
49
|
export function isArkRunRuleId(ruleId) {
|
|
50
50
|
return typeof ruleId === 'string' && ruleId.startsWith('ARKRUN_');
|
|
51
51
|
}
|
|
52
|
+
export function isArkOrderRuleId(ruleId) {
|
|
53
|
+
return typeof ruleId === 'string' && ruleId.startsWith('ARKORDER_');
|
|
54
|
+
}
|
|
52
55
|
export function isExtraPlaneFinding(violation) {
|
|
53
56
|
if (violation?.arkruleId != null)
|
|
54
57
|
return true;
|
|
55
58
|
const id = typeof violation?.ruleId === 'string' ? violation.ruleId : '';
|
|
56
|
-
return id.startsWith('ARKRULE') ||
|
|
59
|
+
return (id.startsWith('ARKRULE') ||
|
|
60
|
+
id.startsWith('arkrule') ||
|
|
61
|
+
id.startsWith('ARKRUN_') ||
|
|
62
|
+
id.startsWith('ARKORDER_'));
|
|
57
63
|
}
|
|
58
64
|
/**
|
|
59
65
|
* Under the classification floor, demote enforced extra-plane findings in place
|
|
@@ -74,7 +80,7 @@ export function demoteExtraPlaneTeethUnderClassificationFloor(violations, classi
|
|
|
74
80
|
return violations;
|
|
75
81
|
}
|
|
76
82
|
/** Stamp for extra-plane honesty: never one architecture score. */
|
|
77
|
-
export const MERGE_PLANES_DUAL_STAMP = 'Structure = heuristics; invariants = catalog+coverage evidence (not business runtime); ArkRun = kernel usage + declarations (not a score). Extra planes never merge into one architecture score. Advisory ArkRules ≠ merge teeth. Advisory ArkRun ≠ 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 = 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.';
|
|
78
84
|
function countOrZero(value) {
|
|
79
85
|
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
80
86
|
}
|
|
@@ -216,6 +216,8 @@ export function buildManagedAssetCatalog({ root, tools, compact = false, skillsO
|
|
|
216
216
|
}
|
|
217
217
|
if (selectedTools.has('antigravity')) {
|
|
218
218
|
add('.agents/hooks.json', antigravityHooks(root));
|
|
219
|
+
// Official workspace MCP. Antigravity does not load repo-root `.mcp.json`.
|
|
220
|
+
add('.agents/mcp_config.json', mcpJson(root));
|
|
219
221
|
// Still useful for Gemini CLI / legacy Gemini consumers sharing the tree.
|
|
220
222
|
add('GEMINI.md', instructionRule(root));
|
|
221
223
|
}
|
|
@@ -747,8 +749,8 @@ export function runInstallAgentGates(args) {
|
|
|
747
749
|
if (tools.has('antigravity') && !args.compact) {
|
|
748
750
|
console.log('');
|
|
749
751
|
console.log(' Antigravity: PreToolUse deny is hard for listed write tools when hooks are trusted.');
|
|
750
|
-
console.log(' - Install path: `.agents/hooks.json` (+ GEMINI.md for legacy Gemini consumers).');
|
|
751
|
-
console.log(' - Trust project hooks
|
|
752
|
+
console.log(' - Install path: `.agents/hooks.json` + `.agents/mcp_config.json` (+ GEMINI.md for legacy Gemini consumers).');
|
|
753
|
+
console.log(' - Trust project hooks; refresh MCP (`/mcp` or restart); pair with required CI --strict-merge.');
|
|
752
754
|
}
|
|
753
755
|
if (tools.has('opencode') && !args.compact) {
|
|
754
756
|
console.log('');
|
|
@@ -62,16 +62,42 @@ function matchSimpleGlob(glob, file) {
|
|
|
62
62
|
return new RegExp(`^${out}$`).test(target);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Declared invariant ids from an Effective catalog. Empty when the extra is off.
|
|
67
|
+
* @param {{ invariants?: Array<{ id?: unknown }> } | null | undefined} arkRules
|
|
68
|
+
* @returns {string[]}
|
|
69
|
+
*/
|
|
70
|
+
export function invariantIdsFromCatalog(arkRules) {
|
|
71
|
+
return (arkRules?.invariants ?? [])
|
|
72
|
+
.map((inv) => inv?.id)
|
|
73
|
+
.filter((id) => typeof id === 'string' && id.length > 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
/**
|
|
66
77
|
* @param {string} root
|
|
67
78
|
* @param {{ files?: Array<{ path: string }> }} facts
|
|
68
|
-
* @param {{ testGlobs?: string[] }} [opts]
|
|
69
|
-
* @returns {{
|
|
79
|
+
* @param {{ testGlobs?: string[], invariantIds?: string[] }} [opts]
|
|
80
|
+
* @returns {{
|
|
81
|
+
* fileContents: Record<string, string>,
|
|
82
|
+
* testFiles: string[],
|
|
83
|
+
* testGlobsMissing: boolean,
|
|
84
|
+
* coverageBudgetExhausted: boolean,
|
|
85
|
+
* }}
|
|
70
86
|
*/
|
|
71
87
|
export function loadInvariantCoverageInputs(root, facts, opts = {}) {
|
|
72
88
|
const fileContents = {};
|
|
73
89
|
const testFiles = [];
|
|
74
90
|
const seen = new Set();
|
|
91
|
+
// Declared invariant ids. When present, a test file is RETAINED only if it
|
|
92
|
+
// mentions one: scanning is cheap (hundreds of small files), retaining is
|
|
93
|
+
// what costs memory. Without this the budget goes to whichever N tests the
|
|
94
|
+
// walk reaches first — an arbitrary order — so coverage is wrong on any repo
|
|
95
|
+
// with more test files than budget. Measured: 707 tests against a cap of 400.
|
|
96
|
+
const invariantIds = Array.isArray(opts.invariantIds)
|
|
97
|
+
? opts.invariantIds.filter((id) => typeof id === 'string' && id.length > 0)
|
|
98
|
+
: [];
|
|
99
|
+
const mentionsInvariant = (content) =>
|
|
100
|
+
invariantIds.length === 0 || invariantIds.some((id) => content.includes(id));
|
|
75
101
|
const testGlobs = Array.isArray(opts.testGlobs)
|
|
76
102
|
? opts.testGlobs.filter((g) => typeof g === 'string' && g.length > 0)
|
|
77
103
|
: [];
|
|
@@ -93,40 +119,51 @@ export function loadInvariantCoverageInputs(root, facts, opts = {}) {
|
|
|
93
119
|
const stat = fs.statSync(absolute);
|
|
94
120
|
if (!stat.isFile() || stat.size > MAX_FILE_BYTES) return;
|
|
95
121
|
const content = fs.readFileSync(absolute, 'utf8');
|
|
122
|
+
const asTest = forceAsTest || isTestPath(rel);
|
|
123
|
+
// A test that names no invariant is evidence of nothing: scan it, drop
|
|
124
|
+
// it, and let it cost no budget.
|
|
125
|
+
if (asTest && !mentionsInvariant(content)) return;
|
|
96
126
|
seen.add(rel);
|
|
97
127
|
fileContents[rel] = content;
|
|
98
|
-
if (
|
|
128
|
+
if (asTest) testFiles.push(rel);
|
|
99
129
|
} catch {
|
|
100
130
|
// skip unreadable
|
|
101
131
|
}
|
|
102
132
|
};
|
|
103
133
|
|
|
104
|
-
|
|
105
|
-
|
|
134
|
+
// Tests FIRST, then production files.
|
|
135
|
+
//
|
|
136
|
+
// The order is load-bearing, not stylistic. `pushFile` stops at
|
|
137
|
+
// MAX_COVERAGE_FILES, and a real repo has far more production files than the
|
|
138
|
+
// budget — so walking facts first consumed the whole budget and the test walk
|
|
139
|
+
// pushed nothing. Coverage then reported `testGlobsMissing: true`, which the
|
|
140
|
+
// caller renders as "never-had-tests": a claim about the USER's repo that was
|
|
141
|
+
// actually about our own budget. Measured on a 4511-file project: every
|
|
142
|
+
// invariant reported uncovered while its test sat on disk with the invariant
|
|
143
|
+
// id in the describe title. Tests are tens of files, not thousands, so giving
|
|
144
|
+
// them the head of the budget costs the production scan nothing in practice.
|
|
145
|
+
const testWalkRoots = useCustomGlobs
|
|
146
|
+
? ['.', 'tests', 'test', 'src', '__tests__', 'spec']
|
|
147
|
+
: ['tests', 'test', 'src', '__tests__'];
|
|
148
|
+
for (const dir of testWalkRoots) {
|
|
149
|
+
const absDir = path.join(root, dir === '.' ? '' : dir);
|
|
150
|
+
if (!fs.existsSync(absDir)) continue;
|
|
151
|
+
walkTestFiles(absDir, root, (rel) => {
|
|
152
|
+
if (isTestPath(rel)) pushFile(rel, true);
|
|
153
|
+
});
|
|
106
154
|
}
|
|
107
155
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
for (const dir of ['.', 'tests', 'test', 'src', '__tests__', 'spec']) {
|
|
111
|
-
const absDir = path.join(root, dir === '.' ? '' : dir);
|
|
112
|
-
if (!fs.existsSync(absDir)) continue;
|
|
113
|
-
walkTestFiles(absDir, root, (rel) => {
|
|
114
|
-
if (isTestPath(rel)) pushFile(rel, true);
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
// Walk common test roots when facts only cover production include globs.
|
|
119
|
-
for (const dir of ['tests', 'test', 'src', '__tests__']) {
|
|
120
|
-
const absDir = path.join(root, dir);
|
|
121
|
-
if (!fs.existsSync(absDir)) continue;
|
|
122
|
-
walkTestFiles(absDir, root, (rel) => {
|
|
123
|
-
if (isTestPath(rel)) pushFile(rel, true);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
156
|
+
for (const file of facts?.files ?? []) {
|
|
157
|
+
if (file?.path) pushFile(file.path);
|
|
126
158
|
}
|
|
127
159
|
|
|
128
160
|
const testGlobsMissing = testFiles.length === 0;
|
|
129
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
fileContents,
|
|
163
|
+
testFiles,
|
|
164
|
+
testGlobsMissing,
|
|
165
|
+
coverageBudgetExhausted: seen.size >= MAX_COVERAGE_FILES,
|
|
166
|
+
};
|
|
130
167
|
}
|
|
131
168
|
|
|
132
169
|
/**
|
|
@@ -38,6 +38,7 @@ export function evaluateInvariantCoverage(input) {
|
|
|
38
38
|
}
|
|
39
39
|
const testFiles = input.testFiles ?? [];
|
|
40
40
|
const testGlobsMissing = input.testGlobsMissing === true || testFiles.length === 0;
|
|
41
|
+
const coverageBudgetExhausted = input.coverageBudgetExhausted === true;
|
|
41
42
|
const coverage = [];
|
|
42
43
|
const violations = [];
|
|
43
44
|
for (const inv of invariants) {
|
|
@@ -83,7 +84,9 @@ export function evaluateInvariantCoverage(input) {
|
|
|
83
84
|
violations.push({
|
|
84
85
|
ruleId: 'INVARIANT_UNCOVERED',
|
|
85
86
|
message: partial
|
|
86
|
-
?
|
|
87
|
+
? coverageBudgetExhausted
|
|
88
|
+
? `Invariant ${inv.id} coverage cannot be proven (coverage file budget exhausted); reporting partial, not covered.`
|
|
89
|
+
: `Invariant ${inv.id} coverage cannot be proven (test globs missing or empty); reporting partial, not covered (never-had-tests).`
|
|
87
90
|
: kind === 'tests-disappeared'
|
|
88
91
|
? `Invariant ${inv.id} is not covered by a test title or declared symbol (tests-disappeared — suite exists).`
|
|
89
92
|
: `Invariant ${inv.id} is not covered by a test title or declared symbol (never-had-tests).`,
|
|
@@ -129,7 +129,7 @@ const HOST_SIGNALS = {
|
|
|
129
129
|
cursor: ['.cursor/mcp.json', '.cursor/hooks.json', '.cursor/rules/ark.mdc'],
|
|
130
130
|
codex: ['.codex/hooks.json', '.codex/config.toml'],
|
|
131
131
|
grok: ['.grok/config.toml', '.grok/hooks/ark-write-gate.json', '.grok/skills/ark-upgrade/SKILL.md'],
|
|
132
|
-
antigravity: ['.agents/hooks.json'],
|
|
132
|
+
antigravity: ['.agents/hooks.json', '.agents/mcp_config.json'],
|
|
133
133
|
opencode: ['opencode.json', '.opencode/skills/ark-upgrade/SKILL.md'],
|
|
134
134
|
windsurf: ['.windsurf/rules/ark.md', '.windsurf/workflows/ark-upgrade.md'],
|
|
135
135
|
cline: ['.clinerules/ark.md', '.clinerules/workflows/ark-upgrade.md'],
|
package/bin/lib/mcp-adoption.mjs
CHANGED
|
@@ -36,6 +36,7 @@ export const COMMAND_GATE_JSON_FILES = [
|
|
|
36
36
|
'.mcp.json',
|
|
37
37
|
'.cursor/mcp.json',
|
|
38
38
|
'.cursor/hooks.json',
|
|
39
|
+
'.agents/mcp_config.json',
|
|
39
40
|
'opencode.json',
|
|
40
41
|
];
|
|
41
42
|
// Primary CLI names (product) + one-major aliases. migrate-commands must strip ALL of these
|
|
@@ -208,10 +209,15 @@ export function collectAdoptionGaps(root, config, coverage) {
|
|
|
208
209
|
host: 'antigravity',
|
|
209
210
|
dir: '.agents',
|
|
210
211
|
skill: (n) => path.join(root, '.agents', 'skills', n, 'SKILL.md'),
|
|
211
|
-
extras: [
|
|
212
|
+
extras: [
|
|
213
|
+
['.agents/hooks.json', 'write-gate hook'],
|
|
214
|
+
['.agents/mcp_config.json', 'workspace MCP'],
|
|
215
|
+
],
|
|
212
216
|
toolsFlag: 'antigravity',
|
|
213
|
-
//
|
|
214
|
-
presentIf: () =>
|
|
217
|
+
// Hooks or official workspace MCP — `.agents/skills` alone is Codex scope.
|
|
218
|
+
presentIf: () =>
|
|
219
|
+
fs.existsSync(path.join(root, '.agents', 'hooks.json')) ||
|
|
220
|
+
fs.existsSync(path.join(root, '.agents', 'mcp_config.json')),
|
|
215
221
|
},
|
|
216
222
|
{
|
|
217
223
|
host: 'opencode',
|
|
@@ -3,7 +3,10 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { analyzePolicyDelta } from './analysis-engine.mjs';
|
|
5
5
|
import { loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
invariantIdsFromCatalog,
|
|
8
|
+
loadInvariantCoverageInputs,
|
|
9
|
+
} from './invariant-coverage-io.mjs';
|
|
7
10
|
import { evaluateInvariantCoverage } from './invariant-coverage.mjs';
|
|
8
11
|
|
|
9
12
|
function readJsonFile(filePath, label) {
|
|
@@ -175,12 +178,15 @@ export function analyzePolicyTransition({
|
|
|
175
178
|
|
|
176
179
|
let candidateInvariantCoverage;
|
|
177
180
|
if ((candidateArkRules?.invariants?.length ?? 0) > 0) {
|
|
178
|
-
const coverageInputs = loadInvariantCoverageInputs(root, { files: [] }
|
|
181
|
+
const coverageInputs = loadInvariantCoverageInputs(root, { files: [] }, {
|
|
182
|
+
invariantIds: invariantIdsFromCatalog(candidateArkRules),
|
|
183
|
+
});
|
|
179
184
|
const evaluated = evaluateInvariantCoverage({
|
|
180
185
|
arkRules: candidateArkRules,
|
|
181
186
|
fileContents: coverageInputs.fileContents,
|
|
182
187
|
testFiles: coverageInputs.testFiles,
|
|
183
188
|
testGlobsMissing: coverageInputs.testGlobsMissing,
|
|
189
|
+
coverageBudgetExhausted: coverageInputs.coverageBudgetExhausted === true,
|
|
184
190
|
});
|
|
185
191
|
candidateInvariantCoverage = evaluated.coverage;
|
|
186
192
|
}
|
package/bin/lib/remediation.mjs
CHANGED
|
@@ -100,6 +100,13 @@ const ARKRUN_JUDGMENT_RULE_IDS = new Set([
|
|
|
100
100
|
'ARKRUN_DIRECT_NEW',
|
|
101
101
|
'ARKRUN_TRANSPORT_BYPASS',
|
|
102
102
|
]);
|
|
103
|
+
const ARKORDER_JUDGMENT_RULE_IDS = new Set([
|
|
104
|
+
'ARKORDER_MISSING_PLANE',
|
|
105
|
+
'ARKORDER_KERNEL_IN_DOMAIN',
|
|
106
|
+
'ARKORDER_GENERIC_UPDATE',
|
|
107
|
+
'ARKORDER_TOO_MANY_PARAMS',
|
|
108
|
+
'ARKORDER_INGEST_WRITES_XI',
|
|
109
|
+
]);
|
|
103
110
|
function arkRunCallSiteName(violation) {
|
|
104
111
|
return typeof violation.target === 'string' && violation.target.trim().length > 0
|
|
105
112
|
? violation.target.trim()
|
|
@@ -119,12 +126,12 @@ function arkRunNextAction(violation) {
|
|
|
119
126
|
switch (violation.ruleId) {
|
|
120
127
|
case 'ARKRUN_MISSING_ROOT':
|
|
121
128
|
return target
|
|
122
|
-
? `Import createStrictArkKernel from
|
|
123
|
-
: 'Import createStrictArkKernel from
|
|
129
|
+
? `Import createStrictArkKernel from arkgate/runtime and call it in composition root ${target} listed in arkRun.compositionRoots, then preflight again.`
|
|
130
|
+
: 'Import createStrictArkKernel from arkgate/runtime (same npm package; @arkgate/runtime is deprecated) and call it in a composition root listed in arkRun.compositionRoots, then preflight again. Never mechanical-safe — factory placement is a design decision.';
|
|
124
131
|
case 'ARKRUN_KERNEL_IN_DOMAIN':
|
|
125
132
|
return target
|
|
126
|
-
? `Move the kernel import of ${target} out of ${fromLayer ?? 'the Domain-role layer'} into a composition root or adapter. Import from
|
|
127
|
-
: 'Move the kernel import out of the Domain-role layer into a composition root or adapter. Import from
|
|
133
|
+
? `Move the kernel import of ${target} out of ${fromLayer ?? 'the Domain-role layer'} into a composition root or adapter. Import from arkgate/runtime, then preflight again.`
|
|
134
|
+
: 'Move the kernel import out of the Domain-role layer into a composition root or adapter. Import from arkgate/runtime (same npm package; @arkgate/runtime is deprecated), then preflight again. Never mechanical-safe.';
|
|
128
135
|
case 'ARKRUN_DIRECT_NEW':
|
|
129
136
|
return target
|
|
130
137
|
? `Resolve ${target} from the kernel instead of constructing it with new, then preflight again.`
|
|
@@ -154,12 +161,12 @@ function arkRunEnthusiastHint(violation) {
|
|
|
154
161
|
switch (violation.ruleId) {
|
|
155
162
|
case 'ARKRUN_MISSING_ROOT':
|
|
156
163
|
return target
|
|
157
|
-
? `Call createStrictArkKernel from
|
|
158
|
-
: 'Call createStrictArkKernel from
|
|
164
|
+
? `Call createStrictArkKernel from arkgate/runtime in ${target} so the app actually uses the kernel.`
|
|
165
|
+
: 'Call createStrictArkKernel from arkgate/runtime in a listed composition root so the app actually uses the kernel.';
|
|
159
166
|
case 'ARKRUN_KERNEL_IN_DOMAIN':
|
|
160
167
|
return target
|
|
161
|
-
? `Domain stays kernel-free. Move the ${target} import to a composition root or adapter
|
|
162
|
-
: 'Domain stays kernel-free. Move that
|
|
168
|
+
? `Domain stays kernel-free. Move the ${target} import to a composition root or adapter. Prefer arkgate/runtime.`
|
|
169
|
+
: 'Domain stays kernel-free. Move that kernel import to a composition root or adapter. Prefer arkgate/runtime; @arkgate/runtime is deprecated.';
|
|
163
170
|
case 'ARKRUN_DIRECT_NEW':
|
|
164
171
|
return target
|
|
165
172
|
? `Do not construct ${target} with new. Resolve it from the kernel instead.`
|
|
@@ -215,6 +222,18 @@ export function deterministicNextAction(violation) {
|
|
|
215
222
|
case 'ARKRUN_UNDECLARED_DEPEND':
|
|
216
223
|
case 'ARKRUN_TRANSPORT_BYPASS':
|
|
217
224
|
return arkRunNextAction(violation);
|
|
225
|
+
case 'ARKORDER_MISSING_PLANE':
|
|
226
|
+
return typeof violation.target === 'string' && violation.target.length > 0
|
|
227
|
+
? `Import createOrderPlane from arkgate/order and call it in plane root ${violation.target} listed in arkOrder.planeRoots, then preflight again.`
|
|
228
|
+
: 'Import createOrderPlane from arkgate/order and call it in a plane root listed in arkOrder.planeRoots, then preflight again. Never mechanical-safe — factory placement is a design decision.';
|
|
229
|
+
case 'ARKORDER_KERNEL_IN_DOMAIN':
|
|
230
|
+
return 'Move the arkgate/order import out of the Domain-role layer into a plane root or adapter, then preflight again. Never mechanical-safe.';
|
|
231
|
+
case 'ARKORDER_GENERIC_UPDATE':
|
|
232
|
+
return 'Use release() to freeze ξ or proposeRelease() for a pattern change with blast radius, then preflight again. Never mechanical-safe.';
|
|
233
|
+
case 'ARKORDER_TOO_MANY_PARAMS':
|
|
234
|
+
return 'Cut ξ to the slow keys that actually slave the rest, then preflight again. Never mechanical-safe.';
|
|
235
|
+
case 'ARKORDER_INGEST_WRITES_XI':
|
|
236
|
+
return 'Keep ingest results as absorb/escalate only. Change ξ with proposeRelease + release. Never mechanical-safe.';
|
|
218
237
|
default:
|
|
219
238
|
if (typeof violation.ruleId === 'string' && violation.ruleId.startsWith('ARKRULE_')) {
|
|
220
239
|
return `Fix the ArkRule ${typeof violation.arkruleId === 'string' ? violation.arkruleId : violation.ruleId}, then preflight again.`;
|
|
@@ -340,7 +359,9 @@ export function classifyRemediation(violation) {
|
|
|
340
359
|
};
|
|
341
360
|
}
|
|
342
361
|
if (typeof ruleId === 'string' &&
|
|
343
|
-
(ARKRUN_JUDGMENT_RULE_IDS.has(ruleId) ||
|
|
362
|
+
(ARKRUN_JUDGMENT_RULE_IDS.has(ruleId) ||
|
|
363
|
+
ARKRUN_UNDECLARED_RULE_IDS.has(ruleId) ||
|
|
364
|
+
ARKORDER_JUDGMENT_RULE_IDS.has(ruleId))) {
|
|
344
365
|
return {
|
|
345
366
|
class: 'judgment',
|
|
346
367
|
confidence: 0.85,
|
|
@@ -470,6 +491,24 @@ export function enrichViolationWithFixClass(violation) {
|
|
|
470
491
|
enriched.effort = ARKRUN_UNDECLARED_RULE_IDS.has(violation.ruleId ?? '') ? 'small' : 'medium';
|
|
471
492
|
enriched.enthusiastHint = arkRunEnthusiastHint(violation);
|
|
472
493
|
break;
|
|
494
|
+
case 'ARKORDER_MISSING_PLANE':
|
|
495
|
+
case 'ARKORDER_KERNEL_IN_DOMAIN':
|
|
496
|
+
case 'ARKORDER_GENERIC_UPDATE':
|
|
497
|
+
case 'ARKORDER_TOO_MANY_PARAMS':
|
|
498
|
+
case 'ARKORDER_INGEST_WRITES_XI':
|
|
499
|
+
enriched.fixClass = 'arkorder-usage';
|
|
500
|
+
enriched.effort = 'medium';
|
|
501
|
+
enriched.enthusiastHint =
|
|
502
|
+
violation.ruleId === 'ARKORDER_GENERIC_UPDATE'
|
|
503
|
+
? 'Do not PATCH the slow pattern. Freeze ξ with release() or propose a pattern change that lists what dies.'
|
|
504
|
+
: violation.ruleId === 'ARKORDER_KERNEL_IN_DOMAIN'
|
|
505
|
+
? 'Domain stays plane-free. Import arkgate/order only from a listed plane root.'
|
|
506
|
+
: violation.ruleId === 'ARKORDER_TOO_MANY_PARAMS'
|
|
507
|
+
? 'Too many slow keys. Keep ξ small — the rest is derived noise.'
|
|
508
|
+
: violation.ruleId === 'ARKORDER_INGEST_WRITES_XI'
|
|
509
|
+
? 'ingest can absorb or escalate. It never writes a new house.'
|
|
510
|
+
: 'Call createOrderPlane from arkgate/order in a listed plane root so the app actually freezes a pattern.';
|
|
511
|
+
break;
|
|
473
512
|
default:
|
|
474
513
|
enriched.fixClass = 'review-contract';
|
|
475
514
|
enriched.effort = 'small';
|
|
@@ -40,6 +40,10 @@ import {
|
|
|
40
40
|
extractArkRunKernelCallsFromSource,
|
|
41
41
|
extractArkRunManagedNewsFromSource,
|
|
42
42
|
} from './ark-run-facts.mjs';
|
|
43
|
+
import {
|
|
44
|
+
extractArkOrderGenericUpdatesFromSource,
|
|
45
|
+
extractArkOrderPlaneCallsFromSource,
|
|
46
|
+
} from './ark-order-facts.mjs';
|
|
43
47
|
import {
|
|
44
48
|
collectGovernedFiles,
|
|
45
49
|
isGovernableSourceFile,
|
|
@@ -1008,7 +1012,11 @@ export function resolveCandidateFacts({
|
|
|
1008
1012
|
const arkRunManagedNews = [];
|
|
1009
1013
|
const arkRunCompositionRootHits = [];
|
|
1010
1014
|
const arkRunDeclarations = [];
|
|
1015
|
+
const arkOrderPlaneCalls = [];
|
|
1016
|
+
const arkOrderGenericUpdates = [];
|
|
1017
|
+
const arkOrderRootHits = [];
|
|
1011
1018
|
const compositionRootPatterns = [...(config.arkRun?.compositionRoots ?? [])];
|
|
1019
|
+
const planeRootPatterns = [...(config.arkOrder?.planeRoots ?? [])];
|
|
1012
1020
|
|
|
1013
1021
|
for (const candidate of candidateFiles) {
|
|
1014
1022
|
const sourceFile = ts.createSourceFile(
|
|
@@ -1112,6 +1120,20 @@ export function resolveCandidateFacts({
|
|
|
1112
1120
|
} catch {
|
|
1113
1121
|
// Never fail the resolver for ArkRun declaration extraction.
|
|
1114
1122
|
}
|
|
1123
|
+
try {
|
|
1124
|
+
arkOrderPlaneCalls.push(
|
|
1125
|
+
...extractArkOrderPlaneCallsFromSource(candidate.path, candidate.content)
|
|
1126
|
+
);
|
|
1127
|
+
} catch {
|
|
1128
|
+
// Never fail the resolver for ArkOrder factory extraction.
|
|
1129
|
+
}
|
|
1130
|
+
try {
|
|
1131
|
+
arkOrderGenericUpdates.push(
|
|
1132
|
+
...extractArkOrderGenericUpdatesFromSource(candidate.path, candidate.content)
|
|
1133
|
+
);
|
|
1134
|
+
} catch {
|
|
1135
|
+
// Never fail the resolver for ArkOrder generic-update extraction.
|
|
1136
|
+
}
|
|
1115
1137
|
}
|
|
1116
1138
|
}
|
|
1117
1139
|
|
|
@@ -1131,6 +1153,24 @@ export function resolveCandidateFacts({
|
|
|
1131
1153
|
}
|
|
1132
1154
|
}
|
|
1133
1155
|
|
|
1156
|
+
if (planeRootPatterns.length > 0) {
|
|
1157
|
+
const factoryFiles = new Set(arkOrderPlaneCalls.map((call) => call.file));
|
|
1158
|
+
for (const candidate of candidateFiles) {
|
|
1159
|
+
for (const pattern of planeRootPatterns) {
|
|
1160
|
+
try {
|
|
1161
|
+
if (!globToRegExp(pattern).test(candidate.path)) continue;
|
|
1162
|
+
} catch {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
arkOrderRootHits.push({
|
|
1166
|
+
file: candidate.path,
|
|
1167
|
+
matchedRoot: pattern,
|
|
1168
|
+
hasPlaneFactory: factoryFiles.has(candidate.path),
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1134
1174
|
if (compositionRootPatterns.length > 0) {
|
|
1135
1175
|
const factoryFiles = new Set(
|
|
1136
1176
|
arkRunKernelCalls.filter((call) => call.kind === 'factory').map((call) => call.file)
|
|
@@ -1234,5 +1274,8 @@ export function resolveCandidateFacts({
|
|
|
1234
1274
|
arkRunManagedNews,
|
|
1235
1275
|
arkRunCompositionRootHits,
|
|
1236
1276
|
arkRunDeclarations,
|
|
1277
|
+
arkOrderPlaneCalls,
|
|
1278
|
+
arkOrderGenericUpdates,
|
|
1279
|
+
arkOrderRootHits,
|
|
1237
1280
|
});
|
|
1238
1281
|
}
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
|
|
8
8
|
import { evaluateInvariantCoverage } from './invariant-coverage.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
invariantIdsFromCatalog,
|
|
11
|
+
loadInvariantCoverageInputs,
|
|
12
|
+
} from './invariant-coverage-io.mjs';
|
|
10
13
|
import {
|
|
11
14
|
EXTRA_MERGE_TEETH_GOVERNED_FLOOR,
|
|
12
15
|
composeMergePlanesHonesty,
|
|
@@ -89,13 +92,16 @@ export function summarizeRulesUnderContract(root, config, facts, classification)
|
|
|
89
92
|
const invariants = loaded.arkRules.invariants?.length ?? 0;
|
|
90
93
|
const coverageInputs =
|
|
91
94
|
invariants > 0
|
|
92
|
-
? loadInvariantCoverageInputs(root, facts ?? { files: [] }
|
|
95
|
+
? loadInvariantCoverageInputs(root, facts ?? { files: [] }, {
|
|
96
|
+
invariantIds: invariantIdsFromCatalog(loaded.arkRules),
|
|
97
|
+
})
|
|
93
98
|
: { fileContents: {}, testFiles: [], testGlobsMissing: false };
|
|
94
99
|
const coverage = evaluateInvariantCoverage({
|
|
95
100
|
arkRules: loaded.arkRules,
|
|
96
101
|
fileContents: coverageInputs.fileContents,
|
|
97
102
|
testFiles: coverageInputs.testFiles,
|
|
98
103
|
testGlobsMissing: coverageInputs.testGlobsMissing,
|
|
104
|
+
coverageBudgetExhausted: coverageInputs.coverageBudgetExhausted === true,
|
|
99
105
|
});
|
|
100
106
|
const covById = new Map(
|
|
101
107
|
(coverage.coverage ?? []).map((row) => [row.invariantId, row])
|