arkgate 4.8.0 → 4.8.2
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 +20 -0
- package/README.md +5 -3
- package/bin/lib/analysis-engine.mjs +4 -4
- package/bin/lib/architecture-scan.mjs +7 -2
- package/bin/lib/invariant-coverage-io.mjs +61 -24
- package/bin/lib/invariant-coverage.mjs +4 -1
- package/bin/lib/policy-delta-io.mjs +8 -2
- package/bin/lib/rules-under-contract.mjs +8 -2
- package/dist/{diagnosticCatalog-RiKPUFRG.d.ts → diagnosticCatalog-CPzH-MLN.d.ts} +7 -1
- package/dist/index.cjs +6 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/runtime/index.cjs +15 -15
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +15 -15
- package/docs/README.md +2 -2
- package/docs/agent-guide.md +10 -9
- package/docs/package-surface.md +3 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +41 -13
- package/templates/agent-skills/ark-architect/SKILL.md +4 -4
- package/templates/agent-skills/ark-autopilot/SKILL.md +22 -4
- package/templates/agent-skills/ark-contract/SKILL.md +7 -7
- package/templates/agent-skills/ark-coverage/SKILL.md +11 -5
- package/templates/agent-skills/ark-explain/SKILL.md +10 -4
- package/templates/agent-skills/ark-explore/SKILL.md +12 -4
- package/templates/agent-skills/ark-fix/SKILL.md +3 -3
- package/templates/agent-skills/ark-loop/SKILL.md +3 -3
- package/templates/agent-skills/ark-place/SKILL.md +12 -3
- package/templates/agent-skills/ark-runtime/SKILL.md +13 -11
- package/templates/agent-skills/ark-think/SKILL.md +13 -7
- package/templates/agent-skills/ark-upgrade/SKILL.md +12 -4
- package/templates/skills/ark-adopt.md +41 -13
- package/templates/skills/ark-architect.md +4 -4
- package/templates/skills/ark-autopilot.md +22 -4
- package/templates/skills/ark-contract.md +7 -7
- package/templates/skills/ark-coverage.md +11 -5
- package/templates/skills/ark-explain.md +10 -4
- package/templates/skills/ark-explore.md +12 -4
- package/templates/skills/ark-fix.md +3 -3
- package/templates/skills/ark-loop.md +3 -3
- package/templates/skills/ark-place.md +12 -3
- package/templates/skills/ark-runtime.md +13 -11
- package/templates/skills/ark-think.md +13 -7
- package/templates/skills/ark-upgrade.md +12 -4
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
import { effectiveAnalysisConfig } from './analysis-policy.mjs';
|
|
12
12
|
import { resolveCandidateFacts } from './resolved-candidate-facts.mjs';
|
|
13
13
|
import { loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
invariantIdsFromCatalog,
|
|
16
|
+
loadInvariantCoverageInputs,
|
|
17
|
+
} from './invariant-coverage-io.mjs';
|
|
15
18
|
import { loadArkRuleFileHints } from './arkrule-file-hints.mjs';
|
|
16
19
|
|
|
17
20
|
/** Resolve canonical facts and optionally retain filesystem probes for resident invalidation. */
|
|
@@ -67,7 +70,9 @@ export function resolveArchitectureSnapshot({
|
|
|
67
70
|
});
|
|
68
71
|
const hasInvariants = (arkRulesLoad.arkRules?.invariants?.length ?? 0) > 0;
|
|
69
72
|
const coverageInputs = hasInvariants
|
|
70
|
-
? loadInvariantCoverageInputs(root, facts
|
|
73
|
+
? loadInvariantCoverageInputs(root, facts, {
|
|
74
|
+
invariantIds: invariantIdsFromCatalog(arkRulesLoad.arkRules),
|
|
75
|
+
})
|
|
71
76
|
: undefined;
|
|
72
77
|
// AR07: Tooling fileHints for orchestration-only / thin-adapter (reuse coverage contents when present).
|
|
73
78
|
const fileHints = loadArkRuleFileHints(
|
|
@@ -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).`,
|
|
@@ -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
|
}
|
|
@@ -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])
|
|
@@ -409,7 +409,7 @@ declare const ARK_ANALYSIS_RESULT_SCHEMA: {
|
|
|
409
409
|
};
|
|
410
410
|
|
|
411
411
|
/** ArkGate library version — single source of truth. */
|
|
412
|
-
declare const version = "4.8.
|
|
412
|
+
declare const version = "4.8.2";
|
|
413
413
|
|
|
414
414
|
/**
|
|
415
415
|
* AI Code Gate (basic).
|
|
@@ -1691,6 +1691,11 @@ type EvaluateInvariantCoverageInput = {
|
|
|
1691
1691
|
testFiles?: readonly string[];
|
|
1692
1692
|
/** When true, missing test files make coverage partial (never green covered). */
|
|
1693
1693
|
testGlobsMissing?: boolean;
|
|
1694
|
+
/**
|
|
1695
|
+
* Tooling hit MAX_COVERAGE_FILES. Partial must not claim the repo never had tests —
|
|
1696
|
+
* the suite may exist outside the scan budget.
|
|
1697
|
+
*/
|
|
1698
|
+
coverageBudgetExhausted?: boolean;
|
|
1694
1699
|
};
|
|
1695
1700
|
declare function evaluateInvariantCoverage(input: EvaluateInvariantCoverageInput): {
|
|
1696
1701
|
coverage: InvariantCoverageEvidence[];
|
|
@@ -1878,6 +1883,7 @@ type AnalyzeResolvedProjectInput = {
|
|
|
1878
1883
|
fileContents: Readonly<Record<string, string>>;
|
|
1879
1884
|
testFiles?: readonly string[];
|
|
1880
1885
|
testGlobsMissing?: boolean;
|
|
1886
|
+
coverageBudgetExhausted?: boolean;
|
|
1881
1887
|
};
|
|
1882
1888
|
/**
|
|
1883
1889
|
* AR07 — Tooling-supplied orchestration/thin-adapter heuristics per file.
|