arkgate 4.3.0 → 4.5.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.
- package/CHANGELOG.md +81 -10
- package/README.md +11 -7
- package/bin/ark-check-runtime.mjs +50 -14
- package/bin/ark-layer-match.mjs +25 -12
- package/bin/lib/agent-projection.mjs +1 -1
- package/bin/lib/analysis-engine.mjs +5 -5
- package/bin/lib/ci-and-commands.mjs +11 -5
- package/bin/lib/doctor-plan.mjs +25 -0
- package/bin/lib/html-report-advisories.mjs +33 -0
- package/bin/lib/html-report-depth.mjs +24 -0
- package/bin/lib/html-report.mjs +8 -1
- package/bin/lib/improvement-compass-doctor.mjs +106 -0
- package/bin/lib/improvement-compass-map.mjs +507 -0
- package/bin/lib/improvement-compass-types.mjs +85 -0
- package/bin/lib/improvement-compass.mjs +79 -0
- package/bin/lib/managed-upgrade-honesty.mjs +201 -0
- package/bin/lib/managed-upgrade.mjs +37 -4
- package/bin/lib/status-command.mjs +127 -2
- package/bin/lib/status-manifest.mjs +187 -1
- package/dist/eslint/index.cjs +2 -2
- package/dist/eslint/index.js +2 -2
- package/dist/index.cjs +35 -35
- package/dist/index.d.ts +267 -5
- package/dist/index.js +35 -35
- package/docs/README.md +7 -6
- package/docs/agent-guide.md +50 -7
- package/docs/develop.md +9 -4
- package/docs/diagnostics.md +1 -1
- package/docs/package-surface.md +38 -34
- package/docs/product-voice.md +30 -1
- package/docs/use.md +67 -1
- package/package.json +1 -1
- package/schemas/ark.status-manifest.schema.json +53 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +2 -2
- package/templates/agent-skills/ark-adopt/SKILL.md +20 -0
- package/templates/agent-skills/ark-architect/SKILL.md +21 -1
- package/templates/agent-skills/ark-autopilot/SKILL.md +25 -5
- package/templates/agent-skills/ark-contract/SKILL.md +20 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +20 -0
- package/templates/agent-skills/ark-explain/SKILL.md +20 -0
- package/templates/agent-skills/ark-explore/SKILL.md +23 -3
- package/templates/agent-skills/ark-fix/SKILL.md +22 -2
- package/templates/agent-skills/ark-loop/SKILL.md +22 -2
- package/templates/agent-skills/ark-place/SKILL.md +20 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +7 -0
- package/templates/agent-skills/ark-think/SKILL.md +20 -0
- package/templates/agent-skills/ark-upgrade/SKILL.md +20 -0
- package/templates/skills/ark-adopt.md +20 -0
- package/templates/skills/ark-architect.md +21 -1
- package/templates/skills/ark-autopilot.md +25 -5
- package/templates/skills/ark-contract.md +20 -0
- package/templates/skills/ark-coverage.md +20 -0
- package/templates/skills/ark-explain.md +20 -0
- package/templates/skills/ark-explore.md +23 -3
- package/templates/skills/ark-fix.md +22 -2
- package/templates/skills/ark-loop.md +22 -2
- package/templates/skills/ark-place.md +20 -0
- package/templates/skills/ark-runtime.md +7 -0
- package/templates/skills/ark-think.md +20 -0
- package/templates/skills/ark-upgrade.md +20 -0
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -62,6 +62,10 @@ import { computeDoctorAdvisories, printDoctorAdvisories } from './doctor-advisor
|
|
|
62
62
|
import { ANALYSIS_COMPLETENESS, analysisIncompleteStatement, normalizeAnalysisCompleteness } from './analysis-completeness.mjs';
|
|
63
63
|
import { designDeltaDoctorLines } from './design-delta.mjs';
|
|
64
64
|
import { enforcementDoctorLines } from './enforcement-state.mjs';
|
|
65
|
+
import {
|
|
66
|
+
buildDoctorImprovementCompass,
|
|
67
|
+
printImprovementCompassSection,
|
|
68
|
+
} from './improvement-compass-doctor.mjs';
|
|
65
69
|
|
|
66
70
|
const color = {
|
|
67
71
|
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
@@ -640,6 +644,23 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
640
644
|
packageVersionTruth?.code === 'PACKAGE_PIN_SELF_HOST',
|
|
641
645
|
});
|
|
642
646
|
|
|
647
|
+
// Improvement compass: projection only — never feeds ok/valid/goal.met.
|
|
648
|
+
const improvementCompass = buildDoctorImprovementCompass({
|
|
649
|
+
designSmells,
|
|
650
|
+
violations,
|
|
651
|
+
designWeak: designFitness.designWeak === true,
|
|
652
|
+
physicalCohesion: doctorAdvisories.physicalCohesion,
|
|
653
|
+
rulesUnderContract,
|
|
654
|
+
baselineExists: baseline.exists,
|
|
655
|
+
baselineStale: analysisComplete ? staleBaseline : null,
|
|
656
|
+
frozenResidual: baseline.exists ? baseline.keys.size : null,
|
|
657
|
+
dirtyBaselineRisk: productHonesty?.reasonIds?.includes?.('dirty-baseline') === true,
|
|
658
|
+
ungovernedDirCount: cov.suggestions?.length ?? 0,
|
|
659
|
+
emptyLayerCount: cov.emptyLayers?.length ?? 0,
|
|
660
|
+
goldenPatternPresent: goldenPattern.present === true,
|
|
661
|
+
arkRulesLoaded: rulesUnderContract?.active === true,
|
|
662
|
+
});
|
|
663
|
+
|
|
643
664
|
if (asJson) {
|
|
644
665
|
(options.writeJson ?? console.log)(
|
|
645
666
|
JSON.stringify(
|
|
@@ -654,6 +675,8 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
654
675
|
// Path-correct ENFORCE can still be design-weak (P02).
|
|
655
676
|
designFitness,
|
|
656
677
|
designSmells,
|
|
678
|
+
// Improvement compass (lenses; notAScore; never a gate input).
|
|
679
|
+
improvementCompass,
|
|
657
680
|
...(options.designDelta ? { designDelta: options.designDelta } : {}),
|
|
658
681
|
// Q01: primary next action when Shape residual dominates (null if not design-weak).
|
|
659
682
|
postGreenPath,
|
|
@@ -846,6 +869,8 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
846
869
|
}
|
|
847
870
|
}
|
|
848
871
|
|
|
872
|
+
printImprovementCompassSection(improvementCompass, { line, warn, ok, color });
|
|
873
|
+
|
|
849
874
|
console.log('');
|
|
850
875
|
console.log(color.bold('Design fitness'));
|
|
851
876
|
if (designSmells.length === 0) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { effectiveCapabilityDeny } from './analysis-engine.mjs';
|
|
11
11
|
import { graphBlindSpotsHtml } from './graph-blind.mjs';
|
|
12
12
|
import { formatRulesUnderContractHtml } from './rules-under-contract.mjs';
|
|
13
|
+
import { primaryImprovementCompassNextAction } from './improvement-compass.mjs';
|
|
13
14
|
|
|
14
15
|
// htmlEscape is injected by the caller (html-report.mjs) — importing it back
|
|
15
16
|
// would be a dependency cycle, and the repo's own gate blocks that. The
|
|
@@ -247,10 +248,42 @@ function rulesUnderContractHtml(section) {
|
|
|
247
248
|
return formatRulesUnderContractHtml(section, esc);
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
/** Improvement compass — advisory lenses only; never a score bar or gate input. */
|
|
252
|
+
function improvementCompassHtml(compass) {
|
|
253
|
+
if (!compass || compass.notAScore !== true || !Array.isArray(compass.lenses)) return '';
|
|
254
|
+
const residual = Array.isArray(compass.topResidual) ? compass.topResidual : [];
|
|
255
|
+
const residualLine =
|
|
256
|
+
residual.length === 0
|
|
257
|
+
? '<p class="muted">Residual: none on instrumented lenses (not a score — green edges ≠ finished design).</p>'
|
|
258
|
+
: `<p><span class="tag warn">residual</span> ${residual
|
|
259
|
+
.map((id) => {
|
|
260
|
+
const lens = compass.lenses.find((l) => l.id === id);
|
|
261
|
+
return `<code>${esc(id)}</code>${lens?.summary ? ` — ${esc(lens.summary)}` : ''}`;
|
|
262
|
+
})
|
|
263
|
+
.join('<br/>')}</p>`;
|
|
264
|
+
const oos = compass.lenses
|
|
265
|
+
.filter((l) => l && l.status === 'out-of-scope')
|
|
266
|
+
.map((l) => `<code>${esc(l.id)}</code>`)
|
|
267
|
+
.join(' · ');
|
|
268
|
+
// Same primary next as doctor human: severity-ordered topResidual, not lens-id order.
|
|
269
|
+
const next = primaryImprovementCompassNextAction(compass);
|
|
270
|
+
const nextLine = next
|
|
271
|
+
? `<p class="muted">Next: <code>${esc(next.ref)}</code> — ${esc(next.summary)}</p>`
|
|
272
|
+
: '';
|
|
273
|
+
return `
|
|
274
|
+
<section class="section card" data-advisory="improvementCompass">
|
|
275
|
+
<h2>Improvement compass <span class="muted">(not a score — projection only; never changes the verdict)</span></h2>
|
|
276
|
+
${residualLine}
|
|
277
|
+
<p class="muted">Out of scope (honest): ${oos || 'scalability · resilience · security'}</p>
|
|
278
|
+
${nextLine}
|
|
279
|
+
</section>`;
|
|
280
|
+
}
|
|
281
|
+
|
|
250
282
|
export function renderAdvisorySections(advisories, escape) {
|
|
251
283
|
if (!advisories || typeof advisories !== 'object') return '';
|
|
252
284
|
if (typeof escape === 'function') esc = escape;
|
|
253
285
|
return [
|
|
286
|
+
improvementCompassHtml(advisories.improvementCompass),
|
|
254
287
|
contractHealthHtml(advisories.contractHealth),
|
|
255
288
|
ambientStateHtml(advisories.ambientState),
|
|
256
289
|
physicalCohesionHtml(advisories.physicalCohesion),
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
|
|
22
22
|
import { readBaseline, baselineOccurrenceKeys } from './violations.mjs';
|
|
23
23
|
import { describePackageVersionDualTruth } from './field-install.mjs';
|
|
24
|
+
import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
|
|
25
|
+
import { computePhysicalCohesion } from './physical-cohesion.mjs';
|
|
24
26
|
|
|
25
27
|
function esc(value) {
|
|
26
28
|
return String(value)
|
|
@@ -43,6 +45,7 @@ function esc(value) {
|
|
|
43
45
|
* frozenKeys?: number,
|
|
44
46
|
* activeCount?: number,
|
|
45
47
|
* activeBlockingCount?: number,
|
|
48
|
+
* baselineStale?: number | null,
|
|
46
49
|
* }} [baselineSplit] same numbers doctor uses (do not recompute from active-only list)
|
|
47
50
|
*/
|
|
48
51
|
export function buildReportDepthPayload(
|
|
@@ -155,6 +158,26 @@ export function buildReportDepthPayload(
|
|
|
155
158
|
primaryNextAction: postGreenPath?.action ?? dualTruthNext,
|
|
156
159
|
activeBlockingViolations: activeBlockingCount,
|
|
157
160
|
});
|
|
161
|
+
// Doctor parity: same physical-cohesion + baseline stale facts as runDoctor.
|
|
162
|
+
const physicalCohesion = computePhysicalCohesion(root, files);
|
|
163
|
+
const baselineStale =
|
|
164
|
+
typeof baselineSplit.baselineStale === 'number' ? baselineSplit.baselineStale : null;
|
|
165
|
+
// Improvement compass — same projection as doctor; notAScore; never a gate input.
|
|
166
|
+
const improvementCompass = buildDoctorImprovementCompass({
|
|
167
|
+
designSmells,
|
|
168
|
+
violations: activeViolations,
|
|
169
|
+
designWeak: designFitness.designWeak === true,
|
|
170
|
+
physicalCohesion,
|
|
171
|
+
rulesUnderContract,
|
|
172
|
+
baselineExists: baseline.exists || frozenKeys > 0,
|
|
173
|
+
baselineStale,
|
|
174
|
+
frozenResidual: frozenKeys,
|
|
175
|
+
dirtyBaselineRisk: productHonesty?.reasonIds?.includes?.('dirty-baseline') === true,
|
|
176
|
+
ungovernedDirCount: coverage?.suggestions?.length ?? 0,
|
|
177
|
+
emptyLayerCount: coverage?.emptyLayers?.length ?? 0,
|
|
178
|
+
goldenPatternPresent: goldenPattern.present === true,
|
|
179
|
+
arkRulesLoaded: rulesUnderContract?.active === true,
|
|
180
|
+
});
|
|
158
181
|
return {
|
|
159
182
|
adoption,
|
|
160
183
|
designDepth: {
|
|
@@ -166,6 +189,7 @@ export function buildReportDepthPayload(
|
|
|
166
189
|
// P0-B / P1-M — folded into designDepth so --report stays a single payload.
|
|
167
190
|
productHonesty,
|
|
168
191
|
mergePlanes: rulesUnderContract?.mergePlanes ?? null,
|
|
192
|
+
improvementCompass,
|
|
169
193
|
},
|
|
170
194
|
};
|
|
171
195
|
}
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -143,6 +143,8 @@ export function buildReportSnapshot({
|
|
|
143
143
|
enforcement,
|
|
144
144
|
score,
|
|
145
145
|
mode,
|
|
146
|
+
/** DF02 — thin status compass slice (mode + residual ids, notAScore). */
|
|
147
|
+
improvementCompass = null,
|
|
146
148
|
}) {
|
|
147
149
|
const layers = Array.isArray(config?.layers) ? config.layers : [];
|
|
148
150
|
const rules = Array.isArray(config?.rules) ? config.rules : [];
|
|
@@ -151,7 +153,7 @@ export function buildReportSnapshot({
|
|
|
151
153
|
for (const [name, n] of fileCountByLayer) counts[name] = n;
|
|
152
154
|
}
|
|
153
155
|
const gatesOn = (enforcement || []).filter((e) => e.on).length;
|
|
154
|
-
|
|
156
|
+
const snapshot = {
|
|
155
157
|
version: 1,
|
|
156
158
|
kind: 'ark-architecture-snapshot',
|
|
157
159
|
generatedAt: new Date().toISOString(),
|
|
@@ -186,6 +188,11 @@ export function buildReportSnapshot({
|
|
|
186
188
|
gatesTotal: (enforcement || []).length,
|
|
187
189
|
layerFiles: counts,
|
|
188
190
|
};
|
|
191
|
+
// DF02 — store thin status compass so `ark status` can project residual honestly.
|
|
192
|
+
if (improvementCompass && typeof improvementCompass === 'object') {
|
|
193
|
+
snapshot.improvementCompass = improvementCompass;
|
|
194
|
+
}
|
|
195
|
+
return snapshot;
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
export function readJsonSafe(file) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor adapter for the Domain improvement compass (notAScore projection).
|
|
3
|
+
* Keeps doctor-plan.mjs inside its module budget; pure assembly only.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
buildImprovementCompass,
|
|
7
|
+
formatImprovementCompassDoctorLines,
|
|
8
|
+
primaryImprovementCompassNextAction,
|
|
9
|
+
} from './improvement-compass.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {{
|
|
13
|
+
* designSmells?: object[],
|
|
14
|
+
* violations?: object[],
|
|
15
|
+
* designWeak?: boolean,
|
|
16
|
+
* physicalCohesion?: { findings?: object[] } | null,
|
|
17
|
+
* rulesUnderContract?: object | null,
|
|
18
|
+
* baselineExists?: boolean,
|
|
19
|
+
* baselineStale?: number | null,
|
|
20
|
+
* frozenResidual?: number | null,
|
|
21
|
+
* dirtyBaselineRisk?: boolean,
|
|
22
|
+
* ungovernedDirCount?: number,
|
|
23
|
+
* emptyLayerCount?: number,
|
|
24
|
+
* goldenPatternPresent?: boolean,
|
|
25
|
+
* arkRulesLoaded?: boolean,
|
|
26
|
+
* }} input
|
|
27
|
+
*/
|
|
28
|
+
export function buildDoctorImprovementCompass(input = {}) {
|
|
29
|
+
const violations = Array.isArray(input.violations) ? input.violations : [];
|
|
30
|
+
const ruleId = (v) => String(v?.ruleId ?? v?.code ?? '');
|
|
31
|
+
|
|
32
|
+
let cycleCount = 0;
|
|
33
|
+
let peerIsolationCount = 0;
|
|
34
|
+
let pureOrCapabilityResidual = 0;
|
|
35
|
+
let forbiddenGlobalResidual = 0;
|
|
36
|
+
let arkRulesStructureResidual = 0;
|
|
37
|
+
|
|
38
|
+
for (const v of violations) {
|
|
39
|
+
const id = ruleId(v).toUpperCase();
|
|
40
|
+
if (!id) continue;
|
|
41
|
+
if (id.includes('CYCLE') || id === 'CIRCULAR_DEPENDENCY') cycleCount += 1;
|
|
42
|
+
if (id.includes('PEER_ISOLATION')) peerIsolationCount += 1;
|
|
43
|
+
if (id === 'CAPABILITY_VIOLATION') pureOrCapabilityResidual += 1;
|
|
44
|
+
if (id === 'FORBIDDEN_GLOBAL' || id.startsWith('FORBIDDEN_')) forbiddenGlobalResidual += 1;
|
|
45
|
+
if (id.startsWith('ARKRULE_') || id === 'INVARIANT_UNCOVERED') arkRulesStructureResidual += 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const pcFindings = input.physicalCohesion?.findings;
|
|
49
|
+
const physicalCohesionFindingCount = Array.isArray(pcFindings) ? pcFindings.length : 0;
|
|
50
|
+
|
|
51
|
+
const arkRulesLoaded =
|
|
52
|
+
input.arkRulesLoaded === true ||
|
|
53
|
+
input.rulesUnderContract?.active === true ||
|
|
54
|
+
(typeof input.rulesUnderContract?.structureRules === 'number' &&
|
|
55
|
+
input.rulesUnderContract.structureRules > 0);
|
|
56
|
+
|
|
57
|
+
return buildImprovementCompass({
|
|
58
|
+
designSmells: Array.isArray(input.designSmells) ? input.designSmells : [],
|
|
59
|
+
violations: violations.map((v) => ({
|
|
60
|
+
ruleId: ruleId(v) || undefined,
|
|
61
|
+
message: typeof v?.message === 'string' ? v.message : undefined,
|
|
62
|
+
file: typeof v?.file === 'string' ? v.file : typeof v?.path === 'string' ? v.path : undefined,
|
|
63
|
+
fromLayer: v?.fromLayer,
|
|
64
|
+
toLayer: v?.toLayer,
|
|
65
|
+
failsStrict: v?.failsStrict,
|
|
66
|
+
typeOnly: v?.typeOnly === true || v?.namedBindingsTypeOnly === true || undefined,
|
|
67
|
+
})),
|
|
68
|
+
cycleCount,
|
|
69
|
+
peerIsolationCount,
|
|
70
|
+
physicalCohesionFindingCount,
|
|
71
|
+
arkRulesLoaded,
|
|
72
|
+
arkRulesStructureResidual,
|
|
73
|
+
designWeak: input.designWeak === true,
|
|
74
|
+
baselineExists: input.baselineExists === true,
|
|
75
|
+
baselineStale: input.baselineStale ?? null,
|
|
76
|
+
frozenResidual: input.frozenResidual ?? null,
|
|
77
|
+
dirtyBaselineRisk: input.dirtyBaselineRisk === true,
|
|
78
|
+
pureOrCapabilityResidual,
|
|
79
|
+
forbiddenGlobalResidual,
|
|
80
|
+
ungovernedDirCount: Number(input.ungovernedDirCount) || 0,
|
|
81
|
+
emptyLayerCount: Number(input.emptyLayerCount) || 0,
|
|
82
|
+
goldenPatternPresent: input.goldenPatternPresent === true,
|
|
83
|
+
// Doctor path is TypeScript-oriented (ArkGate product surface).
|
|
84
|
+
stackKind: 'typescript',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
formatImprovementCompassDoctorLines,
|
|
90
|
+
primaryImprovementCompassNextAction,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Human doctor section (never a score bar).
|
|
95
|
+
* @param {import('./improvement-compass.mjs').ImprovementCompass} compass
|
|
96
|
+
* @param {{ line: Function, warn: string, ok: string, color: { bold: Function } }} io
|
|
97
|
+
*/
|
|
98
|
+
export function printImprovementCompassSection(compass, io) {
|
|
99
|
+
const { line, warn, ok, color } = io;
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(color.bold('Improvement compass (not a score)'));
|
|
102
|
+
const mark = compass.topResidual.length > 0 ? warn : ok;
|
|
103
|
+
for (const text of formatImprovementCompassDoctorLines(compass)) {
|
|
104
|
+
line(mark, text);
|
|
105
|
+
}
|
|
106
|
+
}
|