arkgate 4.8.11 → 4.8.14
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 +125 -2
- package/README.md +39 -46
- package/SECURITY.md +5 -3
- package/bin/ark-check-runtime.mjs +11 -9
- package/bin/lib/agent-projection-formatters.mjs +2 -0
- package/bin/lib/agent-skills-package.mjs +63 -8
- package/bin/lib/analysis-engine.mjs +4 -4
- package/bin/lib/architecture-scan.mjs +8 -2
- package/bin/lib/ark-order-doctor.mjs +160 -0
- package/bin/lib/ark-order-report.mjs +65 -0
- package/bin/lib/ark-order-sensors.mjs +1 -1
- package/bin/lib/ci-and-commands.mjs +7 -2
- package/bin/lib/design-smells.mjs +21 -1
- package/bin/lib/diagnostic-catalog.mjs +4 -4
- package/bin/lib/doctor-advisories.mjs +99 -18
- package/bin/lib/doctor-human.mjs +4 -11
- package/bin/lib/doctor-plan.mjs +4 -3
- package/bin/lib/extra-merge-teeth.mjs +32 -4
- package/bin/lib/first-run-help.mjs +11 -2
- package/bin/lib/gate-files.mjs +40 -3
- package/bin/lib/html-report-advisories.mjs +2 -0
- package/bin/lib/html-report-depth.mjs +8 -18
- package/bin/lib/html-report.mjs +16 -0
- package/bin/lib/install-migrate.mjs +23 -0
- package/bin/lib/mcp-hook-payload.mjs +1 -1
- package/bin/lib/product-copy.mjs +4 -0
- package/bin/lib/remediation.mjs +7 -7
- package/bin/lib/resolved-candidate-facts.mjs +144 -36
- package/bin/lib/rules-under-contract.mjs +14 -0
- package/bin/lib/scan-files.mjs +39 -0
- package/bin/lib/start-preview.mjs +4 -0
- package/bin/lib/status-command.mjs +28 -0
- package/bin/lib/status-manifest.mjs +23 -0
- package/bin/lib/upgrade-whats-new.mjs +3 -3
- package/bin/lib/violations.mjs +40 -1
- package/dist/{diagnosticCatalog-DiflIock.d.ts → diagnosticCatalog-DVx_2RmF.d.ts} +1 -1
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +31 -31
- package/dist/index.d.ts +129 -15
- package/dist/index.js +31 -31
- 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 +11 -8
- package/docs/agent-guide.md +30 -13
- package/docs/ai-gates.md +3 -1
- package/docs/arkorder.md +35 -10
- package/docs/configuration.md +7 -6
- package/docs/develop.md +3 -1
- package/docs/diagnostics.md +7 -7
- package/docs/enthusiast/README.md +6 -1
- package/docs/enthusiast/how-to-gallery-starter.md +2 -1
- package/docs/package-surface.md +8 -5
- package/docs/product-voice.md +40 -13
- package/docs/threat-model.md +2 -2
- package/docs/typescript-support.md +3 -3
- package/docs/use.md +18 -11
- package/package.json +1 -1
- package/schemas/ark.status-manifest.schema.json +47 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +7 -4
- package/templates/agent-skills/ark-adopt/SKILL.md +9 -5
- package/templates/agent-skills/ark-architect/SKILL.md +5 -18
- package/templates/agent-skills/ark-autopilot/SKILL.md +8 -4
- package/templates/agent-skills/ark-contract/SKILL.md +9 -20
- package/templates/agent-skills/ark-coverage/SKILL.md +12 -8
- package/templates/agent-skills/ark-explain/SKILL.md +7 -3
- package/templates/agent-skills/ark-explore/SKILL.md +25 -4
- package/templates/agent-skills/ark-fix/SKILL.md +15 -20
- package/templates/agent-skills/ark-loop/SKILL.md +14 -20
- package/templates/agent-skills/ark-order/SKILL.md +200 -0
- package/templates/agent-skills/ark-place/SKILL.md +11 -8
- package/templates/agent-skills/ark-runtime/SKILL.md +17 -4
- package/templates/agent-skills/ark-think/SKILL.md +24 -126
- package/templates/agent-skills/ark-upgrade/SKILL.md +13 -2
- package/templates/skills/ark-adopt.md +9 -5
- package/templates/skills/ark-architect.md +5 -18
- package/templates/skills/ark-autopilot.md +8 -4
- package/templates/skills/ark-contract.md +9 -20
- package/templates/skills/ark-coverage.md +12 -8
- package/templates/skills/ark-explain.md +7 -3
- package/templates/skills/ark-explore.md +25 -4
- package/templates/skills/ark-fix.md +15 -20
- package/templates/skills/ark-loop.md +14 -20
- package/templates/skills/ark-order.md +200 -0
- package/templates/skills/ark-place.md +11 -8
- package/templates/skills/ark-runtime.md +17 -4
- package/templates/skills/ark-think.md +24 -126
- package/templates/skills/ark-upgrade.md +13 -2
- package/templates/tests/ark-adoption-gaps.test.ts +5 -4
|
@@ -126,12 +126,18 @@ export function resolveArchitectureSnapshot({
|
|
|
126
126
|
{ ...config, rules: rules ?? config.rules },
|
|
127
127
|
manifest
|
|
128
128
|
);
|
|
129
|
+
const scoped = fileLocalScope(root, files);
|
|
129
130
|
const facts = resolveCandidateFacts({
|
|
130
131
|
root,
|
|
131
132
|
config: effectiveConfig,
|
|
132
133
|
ts,
|
|
133
134
|
...(args?.tsconfig ? { tsconfig: args.tsconfig } : {}),
|
|
134
135
|
observeInput,
|
|
136
|
+
...(scoped
|
|
137
|
+
? { scopeFiles: [...scoped] }
|
|
138
|
+
: args?.changed
|
|
139
|
+
? { scopeFiles: [] }
|
|
140
|
+
: {}),
|
|
135
141
|
});
|
|
136
142
|
const arkRulesLoad = loadEffectiveArkRulesFromDisk(root, effectiveConfig, {
|
|
137
143
|
observeInput,
|
|
@@ -145,7 +151,6 @@ export function resolveArchitectureSnapshot({
|
|
|
145
151
|
err.issues = arkRulesLoad.errors;
|
|
146
152
|
throw err;
|
|
147
153
|
}
|
|
148
|
-
const scoped = fileLocalScope(root, files);
|
|
149
154
|
const loadedContract = loadContract(effectiveConfig, configPath, {
|
|
150
155
|
arkRules: arkRulesLoad.arkRules,
|
|
151
156
|
});
|
|
@@ -162,7 +167,8 @@ export function resolveArchitectureSnapshot({
|
|
|
162
167
|
...coverageOptionsFromConfig(effectiveConfig),
|
|
163
168
|
})
|
|
164
169
|
: undefined;
|
|
165
|
-
// File-local
|
|
170
|
+
// File-local sensors honor the touched set. Facts (and graph) use that set plus
|
|
171
|
+
// its import closure — not the whole include tree. Unbounded `files` stays full-tree.
|
|
166
172
|
const fileHints = loadHintsForScope(
|
|
167
173
|
root,
|
|
168
174
|
facts,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/arkOrderDoctor.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/ark-order-doctor.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { composeMergePlanesHonesty, extraMergeTeethAllowed, isArkOrderRuleId, } from './extra-merge-teeth.mjs';
|
|
12
|
+
export const ARK_ORDER_DOCTOR_SCHEMA_VERSION = '1.0';
|
|
13
|
+
const RESIDUAL_RULE_CAP = 12;
|
|
14
|
+
export const ARKORDER_ONE_BREATH = 'Layers stop a bad import. ArkOrder stops rewriting a big product choice — like the billing plan — as if it were a seat count. Change those choices through a valve, not a generic update.';
|
|
15
|
+
/** First-contact next step. Reuses the billing gallery + existing doors. */
|
|
16
|
+
export const ARKORDER_FIRST_CONTACT_NEXT = 'Next: examples/arkorder-billing, then /ark-adopt to turn the extra on and /ark-order to wire one candidate.';
|
|
17
|
+
function closedMode(value) {
|
|
18
|
+
return value === 'enforced' || value === 'advisory' ? value : null;
|
|
19
|
+
}
|
|
20
|
+
function uniqueArkOrderRuleIds(findings) {
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
if (!Array.isArray(findings))
|
|
23
|
+
return [];
|
|
24
|
+
for (const finding of findings) {
|
|
25
|
+
const id = finding?.ruleId;
|
|
26
|
+
if (typeof id !== 'string' || !isArkOrderRuleId(id) || seen.has(id))
|
|
27
|
+
continue;
|
|
28
|
+
seen.add(id);
|
|
29
|
+
}
|
|
30
|
+
return [...seen].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
31
|
+
}
|
|
32
|
+
function extraFromConfig(arkOrder) {
|
|
33
|
+
if (!arkOrder || typeof arkOrder !== 'object') {
|
|
34
|
+
return { present: false, mode: null, roots: 0, layers: 0, xiKeys: [] };
|
|
35
|
+
}
|
|
36
|
+
const xiKeys = Array.isArray(arkOrder.xiKeys)
|
|
37
|
+
? arkOrder.xiKeys.filter((key) => typeof key === 'string' && key.length > 0)
|
|
38
|
+
: [];
|
|
39
|
+
return {
|
|
40
|
+
present: true,
|
|
41
|
+
mode: closedMode(arkOrder.mode),
|
|
42
|
+
roots: Array.isArray(arkOrder.planeRoots) ? arkOrder.planeRoots.length : 0,
|
|
43
|
+
layers: Array.isArray(arkOrder.managedLayers) ? arkOrder.managedLayers.length : 0,
|
|
44
|
+
xiKeys,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Doctor / HTML ArkOrder advisory. Always emitted; absence is an honest silent row.
|
|
49
|
+
*/
|
|
50
|
+
export function summarizeArkOrderSection(input = {}) {
|
|
51
|
+
const extra = extraFromConfig(input.arkOrder);
|
|
52
|
+
const uniqueIds = extra.present ? uniqueArkOrderRuleIds(input.findings) : [];
|
|
53
|
+
const ruleIds = uniqueIds.slice(0, RESIDUAL_RULE_CAP);
|
|
54
|
+
const residualCount = uniqueIds.length;
|
|
55
|
+
const mergePlanes = composeMergePlanesHonesty({
|
|
56
|
+
classification: input.classification,
|
|
57
|
+
arkRules: {
|
|
58
|
+
active: input.arkRules?.active === true,
|
|
59
|
+
structureEnforced: input.arkRules?.structureEnforced,
|
|
60
|
+
structureTotal: input.arkRules?.structureTotal,
|
|
61
|
+
structureAdvisory: input.arkRules?.structureAdvisory,
|
|
62
|
+
invariantEnforced: input.arkRules?.invariantEnforced,
|
|
63
|
+
invariantTotal: input.arkRules?.invariantTotal,
|
|
64
|
+
invariantAdvisory: input.arkRules?.invariantAdvisory,
|
|
65
|
+
covered: input.arkRules?.covered,
|
|
66
|
+
uncovered: input.arkRules?.uncovered,
|
|
67
|
+
},
|
|
68
|
+
arkRun: {
|
|
69
|
+
present: input.arkRun?.present === true,
|
|
70
|
+
mode: input.arkRun?.mode ?? null,
|
|
71
|
+
residualCount: input.arkRun?.residualCount,
|
|
72
|
+
},
|
|
73
|
+
arkOrder: {
|
|
74
|
+
present: extra.present,
|
|
75
|
+
mode: extra.mode,
|
|
76
|
+
residualCount,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const extraMergeTeeth = extra.present && extra.mode === 'enforced' && extraMergeTeethAllowed(input.classification);
|
|
80
|
+
let note;
|
|
81
|
+
if (!extra.present) {
|
|
82
|
+
note = 'Absence of arkOrder is silent — Layers verdicts unchanged. Not a score.';
|
|
83
|
+
}
|
|
84
|
+
else if (extra.mode === 'advisory') {
|
|
85
|
+
note =
|
|
86
|
+
'Advisory ArkOrder residual only — never flips valid or --strict-merge. Residual is a finding-id count, never a score.';
|
|
87
|
+
}
|
|
88
|
+
else if (extraMergeTeeth) {
|
|
89
|
+
note =
|
|
90
|
+
'Enforced ArkOrder is on the extra merge plane. Residual is a finding-id count, never a score.';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
note =
|
|
94
|
+
'Enforced ArkOrder extra teeth stay demoted until the layer plane is honestly classified. Residual is a finding-id count, never a score.';
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
schemaVersion: ARK_ORDER_DOCTOR_SCHEMA_VERSION,
|
|
98
|
+
notAScore: true,
|
|
99
|
+
active: extra.present,
|
|
100
|
+
mode: extra.mode,
|
|
101
|
+
planeRoots: extra.roots,
|
|
102
|
+
managedLayers: extra.layers,
|
|
103
|
+
xiKeys: extra.xiKeys,
|
|
104
|
+
residual: { count: residualCount, ruleIds },
|
|
105
|
+
extraMergeTeeth,
|
|
106
|
+
failMergeWhen: mergePlanes.failMergeWhen,
|
|
107
|
+
note,
|
|
108
|
+
mergePlanes,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Thin status slice — counts only; residual null means unknown, not green. */
|
|
112
|
+
export function projectStatusArkOrder(input = {}) {
|
|
113
|
+
const present = input.present === true;
|
|
114
|
+
const mode = closedMode(input.mode);
|
|
115
|
+
const residualRaw = input.residual;
|
|
116
|
+
let residual = null;
|
|
117
|
+
if (typeof residualRaw === 'number' && Number.isFinite(residualRaw) && residualRaw >= 0) {
|
|
118
|
+
residual = Math.floor(residualRaw);
|
|
119
|
+
}
|
|
120
|
+
if (!present)
|
|
121
|
+
residual = residual == null ? 0 : residual;
|
|
122
|
+
return {
|
|
123
|
+
notAScore: true,
|
|
124
|
+
present,
|
|
125
|
+
mode: present ? mode : null,
|
|
126
|
+
extraMergeTeeth: present && mode === 'enforced' && input.extraMergeTeeth === true,
|
|
127
|
+
residual,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function formatArkOrderDoctorLines(section) {
|
|
131
|
+
if (!section || section.notAScore !== true)
|
|
132
|
+
return [];
|
|
133
|
+
if (section.active !== true) {
|
|
134
|
+
return [
|
|
135
|
+
ARKORDER_ONE_BREATH,
|
|
136
|
+
'Off — Layers stay the same (not a score).',
|
|
137
|
+
ARKORDER_FIRST_CONTACT_NEXT,
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
const mode = section.mode ?? 'unknown';
|
|
141
|
+
const teeth = section.extraMergeTeeth === true ? 'armed' : 'not armed';
|
|
142
|
+
const keys = section.xiKeys.length > 0 ? section.xiKeys.join(', ') : '(none named — field-write sensor silent)';
|
|
143
|
+
const lines = [
|
|
144
|
+
ARKORDER_ONE_BREATH,
|
|
145
|
+
`mode: ${mode} · xiKeys: ${keys} · extra merge teeth ${teeth} · not a score`,
|
|
146
|
+
];
|
|
147
|
+
if (section.residual.count > 0) {
|
|
148
|
+
const shown = section.residual.ruleIds.join(', ');
|
|
149
|
+
const more = section.residual.count > section.residual.ruleIds.length
|
|
150
|
+
? ` (+${section.residual.count - section.residual.ruleIds.length} more)`
|
|
151
|
+
: '';
|
|
152
|
+
lines.push(`Residual: ${shown}${more}`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
lines.push('Residual: none on this scan (not a score — green extras ≠ a frozen billing plan).');
|
|
156
|
+
}
|
|
157
|
+
if (section.failMergeWhen)
|
|
158
|
+
lines.push(section.failMergeWhen);
|
|
159
|
+
return lines;
|
|
160
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML for the doctor ArkOrder advisory (report parity: data-advisory="arkOrder").
|
|
3
|
+
*/
|
|
4
|
+
import { ARKORDER_FIRST_CONTACT_NEXT, ARKORDER_ONE_BREATH } from './ark-order-doctor.mjs';
|
|
5
|
+
|
|
6
|
+
export function formatArkOrderHtml(section, esc) {
|
|
7
|
+
if (!section || typeof section !== 'object' || section.notAScore !== true) return '';
|
|
8
|
+
const escape = typeof esc === 'function' ? esc : (v) => String(v);
|
|
9
|
+
const note = section.note ? `<p class="muted">${escape(section.note)}</p>` : '';
|
|
10
|
+
if (section.active !== true) {
|
|
11
|
+
return `
|
|
12
|
+
<section class="section card" data-advisory="arkOrder">
|
|
13
|
+
<h2>ArkOrder <span class="muted">(opt-in extra — not a score)</span></h2>
|
|
14
|
+
<p class="dim" style="margin:.15rem 0 .55rem;font-size:.88rem">
|
|
15
|
+
${ARKORDER_ONE_BREATH}
|
|
16
|
+
Off until you turn it on — Layers stay the same.
|
|
17
|
+
${ARKORDER_FIRST_CONTACT_NEXT}
|
|
18
|
+
</p>
|
|
19
|
+
${note}
|
|
20
|
+
</section>`;
|
|
21
|
+
}
|
|
22
|
+
const residual = section.residual && typeof section.residual === 'object' ? section.residual : { count: 0, ruleIds: [] };
|
|
23
|
+
const ids = Array.isArray(residual.ruleIds) ? residual.ruleIds : [];
|
|
24
|
+
const residualHtml =
|
|
25
|
+
residual.count > 0
|
|
26
|
+
? `<p><span class="tag warn">residual</span> ${ids
|
|
27
|
+
.map((id) => `<code>${escape(id)}</code>`)
|
|
28
|
+
.join(' · ')}${
|
|
29
|
+
residual.count > ids.length ? ` <span class="muted">(+${residual.count - ids.length} more)</span>` : ''
|
|
30
|
+
}</p>`
|
|
31
|
+
: '<p class="muted">Residual: none on this scan (not a score — green extras ≠ a frozen billing plan).</p>';
|
|
32
|
+
const teeth = section.extraMergeTeeth === true
|
|
33
|
+
? '<span class="tag">extra merge teeth armed</span>'
|
|
34
|
+
: '<span class="tag warn">extra merge teeth not armed</span>';
|
|
35
|
+
const mergeSentence =
|
|
36
|
+
section.mergePlanes && typeof section.mergePlanes.failMergeWhen === 'string'
|
|
37
|
+
? section.mergePlanes.failMergeWhen
|
|
38
|
+
: section.failMergeWhen;
|
|
39
|
+
const merge =
|
|
40
|
+
mergeSentence
|
|
41
|
+
? `<p class="muted" style="margin:.35rem 0 .55rem;font-size:.86rem"><b>Merge planes:</b> ${escape(mergeSentence)}</p>`
|
|
42
|
+
: '';
|
|
43
|
+
const keys = Array.isArray(section.xiKeys) && section.xiKeys.length > 0
|
|
44
|
+
? section.xiKeys.map((key) => `<code>${escape(key)}</code>`).join(' · ')
|
|
45
|
+
: '<span class="muted">(none named — field-write sensor silent)</span>';
|
|
46
|
+
return `
|
|
47
|
+
<section class="section card" data-advisory="arkOrder">
|
|
48
|
+
<h2>ArkOrder <span class="muted">(not a score)</span></h2>
|
|
49
|
+
<p class="dim" style="margin:.15rem 0 .55rem;font-size:.88rem">
|
|
50
|
+
<b>[ArkOrder]</b> ${ARKORDER_ONE_BREATH}
|
|
51
|
+
Separate from <b>[Layer]</b> imports, <b>[ArkRules]</b> shape, and <b>[ArkRun]</b> travel.
|
|
52
|
+
</p>
|
|
53
|
+
${merge}
|
|
54
|
+
<div class="kpis" style="margin-bottom:.55rem">
|
|
55
|
+
<div class="kpi"><b>${escape(section.mode || '—')}</b><span>Mode</span></div>
|
|
56
|
+
<div class="kpi"><b>${Number(residual.count) || 0}</b><span>Residual ids</span></div>
|
|
57
|
+
<div class="kpi"><b>${Number(section.planeRoots) || 0}</b><span>Plane roots</span></div>
|
|
58
|
+
<div class="kpi"><b>${Number(section.managedLayers) || 0}</b><span>Managed layers</span></div>
|
|
59
|
+
</div>
|
|
60
|
+
<p>${teeth} · <code>notAScore</code></p>
|
|
61
|
+
<p class="dim" style="margin:.35rem 0 .55rem;font-size:.86rem">xiKeys: ${keys}</p>
|
|
62
|
+
${residualHtml}
|
|
63
|
+
${note}
|
|
64
|
+
</section>`;
|
|
65
|
+
}
|
|
@@ -219,7 +219,7 @@ export function evaluateArkOrderSensors(input) {
|
|
|
219
219
|
continue;
|
|
220
220
|
if (!matchesArkOrderAppliesTo(write.file, extra.appliesTo))
|
|
221
221
|
continue;
|
|
222
|
-
findings.push(finding(extra, 'arkorder-xi-field-write', write.file, write.line, `
|
|
222
|
+
findings.push(finding(extra, 'arkorder-xi-field-write', write.file, write.line, `This file writes ${JSON.stringify(write.key)} the same way it would write a seat count. Take the event in, or change that choice through the valve (propose, then apply).`, { fromLayer, target: write.key }, teethAllowed));
|
|
223
223
|
}
|
|
224
224
|
findings.sort((left, right) => left.file.localeCompare(right.file) ||
|
|
225
225
|
left.ruleId.localeCompare(right.ruleId) ||
|
|
@@ -338,6 +338,8 @@ files; never weaken the gate via subagents.
|
|
|
338
338
|
|
|
339
339
|
**Escapes, not a second curriculum.** Do **not** run overlapping skills for the same job.
|
|
340
340
|
Pick **one** primary skill. Prefer doctor top action #1 when unsure.
|
|
341
|
+
Filter: **Contener · Guiar · Ordenar** — contain the write, guide the next step, order leftover mess. Skills never enforce. First-class extras:
|
|
342
|
+
\`/ark-runtime\` (ArkRun) · \`/ark-order\` (ArkOrder).
|
|
341
343
|
|
|
342
344
|
| When | Invoke | Not this |
|
|
343
345
|
|------|--------|----------|
|
|
@@ -352,10 +354,11 @@ Pick **one** primary skill. Prefer doctor top action #1 when unsure.
|
|
|
352
354
|
| Gate violation on a change (small cluster) | \`/ark-autopilot\` | leftover \`/ark-fix\` |
|
|
353
355
|
| Drive plan **A** to goal.met | \`/ark-autopilot\` | leftover \`/ark-loop\` |
|
|
354
356
|
| Ark **fitness** only (governed%, gates, baseline, install gaps) | \`/ark-coverage\` | leftover design work (use single path above) |
|
|
355
|
-
| One design decision, 2–3 options | \`/ark-
|
|
357
|
+
| One design decision, 2–3 options | \`/ark-explore\` (one decision) | leftover \`/ark-think\`; apply → autopilot |
|
|
356
358
|
| Explain / HTML report tour | \`/ark-explain\` | explore |
|
|
357
359
|
| Bump arkgate + refresh hosts | \`/ark-upgrade\` | — |
|
|
358
|
-
| Optional runtime kernel evaluate | \`/ark-runtime\` |
|
|
360
|
+
| Optional runtime kernel evaluate | \`/ark-runtime\` | leftover \`/ark-run\` (not a skill); order plane → \`/ark-order\` |
|
|
361
|
+
| Optional ArkOrder evaluate / wire | \`/ark-order\` | extra off → \`/ark-adopt\`; skip grind → \`/ark-autopilot\` |
|
|
359
362
|
|
|
360
363
|
**Post-green door:** when doctor reports ENFORCE · leftover design work, the **primary** next action is the single Shape path above — not a choice among explore / coverage / think. Doctor JSON: \`postGreenPath\` / \`primaryNextAction\`.
|
|
361
364
|
|
|
@@ -405,6 +408,8 @@ ${projectionBlock}
|
|
|
405
408
|
|
|
406
409
|
**Primary path (do this):**
|
|
407
410
|
|
|
411
|
+
Contener · Guiar · Ordenar — contain the write, guide the next step, order leftover mess.
|
|
412
|
+
|
|
408
413
|
1. Status anytime: \`${doctorCmd}\` — what's wrong and what to do first.
|
|
409
414
|
2. Read the **Improvement compass** section (not a score). Name residual lenses in plain language when present (SoC, DIP, domain, …). Out-of-scope lenses (performance, app security tooling, full resilience) stay honest — do not invent Ark enforcement for them.
|
|
410
415
|
3. CLI-first: if the local CLI already resolved this project root, do not wait on MCP “still connecting”. Identity handshake is optional in that case. When you do trust MCP evidence: call \`ark_identity\` with \`project.expectedRoot\` set to this project's exact absolute root, then reuse that root plus the returned \`projectIdentity.projectId\` on every Ark MCP call. A descendant path is authoritative only with that matching id. Missing tool, non-\`matched\` binding, or wrong root means this is not proven to be the right project: restart the host and use the local CLI meanwhile.
|
|
@@ -493,6 +493,25 @@ export function summarizeDesignFitness(smells, ctx = {}) {
|
|
|
493
493
|
/** How many smell ids the green-run pointer names before it counts the rest. */
|
|
494
494
|
export const GREEN_PLAN_POINTER_MAX_IDS = 4;
|
|
495
495
|
|
|
496
|
+
/** When a smell has no evidence path, say so — do not leave the reader hunting. */
|
|
497
|
+
export const SMELL_UNATTRIBUTED =
|
|
498
|
+
'no file — leftover design, not a specific line';
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* One named smell with its first evidence path (or an explicit "no file").
|
|
502
|
+
* The pointer used to print only the id, so "1 design smell (domain-logic-in-ui)"
|
|
503
|
+
* could not answer "did my change cause this?"
|
|
504
|
+
*/
|
|
505
|
+
export function formatSmellPointerName(id, smells) {
|
|
506
|
+
const match = (smells || []).find((smell) => smell?.id === id);
|
|
507
|
+
const evidence = Array.isArray(match?.evidence)
|
|
508
|
+
? match.evidence.filter((entry) => typeof entry === 'string' && entry.trim())
|
|
509
|
+
: [];
|
|
510
|
+
if (evidence.length === 0) return `${id} (${SMELL_UNATTRIBUTED})`;
|
|
511
|
+
const extra = evidence.length - 1;
|
|
512
|
+
return extra > 0 ? `${id} at ${evidence[0]} (+${extra} more)` : `${id} at ${evidence[0]}`;
|
|
513
|
+
}
|
|
514
|
+
|
|
496
515
|
/**
|
|
497
516
|
* Name `--plan` on a run that passed.
|
|
498
517
|
*
|
|
@@ -528,7 +547,8 @@ export function formatGreenPlanPointer(smells, ctx = {}, planCommand = 'ark-chec
|
|
|
528
547
|
if (ids.length === 0) return null;
|
|
529
548
|
const shown = ids.slice(0, GREEN_PLAN_POINTER_MAX_IDS);
|
|
530
549
|
const hidden = ids.length - shown.length;
|
|
531
|
-
const
|
|
550
|
+
const namedIds = shown.map((id) => formatSmellPointerName(id, smells));
|
|
551
|
+
const named = hidden > 0 ? `${namedIds.join(', ')}, +${hidden} more` : namedIds.join(', ');
|
|
532
552
|
const plural = ids.length === 1 ? '' : 's';
|
|
533
553
|
const suppressed = ctx.suppressedCount ?? 0;
|
|
534
554
|
const opening =
|
|
@@ -32,7 +32,7 @@ function entry(ruleId, category, title, why, fix, extras) {
|
|
|
32
32
|
*/
|
|
33
33
|
export const DIAGNOSTIC_CATALOG = Object.freeze([
|
|
34
34
|
// ── layer / graph ────────────────────────────────────────────────────────
|
|
35
|
-
entry('LAYER_IMPORT_VIOLATION', 'layer', '
|
|
35
|
+
entry('LAYER_IMPORT_VIOLATION', 'layer', 'This import is not allowed', 'This file imported a folder it may not reach. The write doesn’t land. The same check fails the pull request.', 'Branch by import kind: constants/types/pure → adopt into DomainModel or SharedKernel (do not invent a port); kernel/events/bootstrap from Persistence → inject a port or move the map to SharedTypes (Persistence must not emit); define a port only when the target is a real use-case. Type-only edges use `import type`. Then preflight again. Do not weaken the layer rule without a hash-bound policy acknowledgement.'),
|
|
36
36
|
entry('LAYER_INTENT_REFERENCE_VIOLATION', 'layer', 'Intent referenced across a blocked layer edge', 'A string intent (or intent-like reference) names a layer that the file’s layer may not reach under the contract rules — the same plane as import edges, for event/intent coupling.', 'Reference that intent from a layer allowed to know about it (usually an adapter or application layer), or relocate the reference — then preflight again.'),
|
|
37
37
|
entry('LAYER_REFERENCE_VIOLATION', 'layer', 'Layer reference blocked (snippet / AI gate)', 'Snippet analysis found an intent or string reference that would couple layers in a direction the architecture profile forbids.', 'Move the reference to an allowed layer or introduce a port/event boundary, then re-run the snippet gate.'),
|
|
38
38
|
entry('CIRCULAR_DEPENDENCY', 'layer', 'Dependency cycle', 'Two or more modules import each other in a loop. Cycles make ownership unclear and break stable layer direction.', 'Extract the shared dependency into a third module, invert one edge behind a port, or merge units that are truly one — then preflight again.'),
|
|
@@ -68,14 +68,14 @@ export const DIAGNOSTIC_CATALOG = Object.freeze([
|
|
|
68
68
|
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.'),
|
|
69
69
|
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.'),
|
|
70
70
|
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.'),
|
|
71
|
-
entry('ARKORDER_GENERIC_UPDATE', 'arkorder', 'Generic update of
|
|
71
|
+
entry('ARKORDER_GENERIC_UPDATE', 'arkorder', 'Generic update of a big product choice', 'A call to update/patch/set rewrites a named product choice (like billing plan) as if it were a seat count.', 'Don\'t use a generic update. First freeze with release(). Later, propose the change, then apply it.'),
|
|
72
72
|
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.'),
|
|
73
73
|
entry('ARKORDER_INGEST_WRITES_XI', 'arkorder', 'ingest assigned into ξ', 'An ingest() result is written into a Release or ξ store. ingest may absorb, escalate_up, or hold; it never mints a pattern.', 'Keep ingest results as absorb/escalate_up/hold only. Change ξ with proposeRelease then apply(ProposeResult). Never mechanical-safe.'),
|
|
74
|
-
entry('ARKORDER_XI_FIELD_WRITE', 'arkorder', '
|
|
74
|
+
entry('ARKORDER_XI_FIELD_WRITE', 'arkorder', 'Big product choice written like a seat count', 'A use-case writes a named product choice (like billing plan) through a database update. Invoices and seats can flow. That choice cannot.', 'Keep invoices and seats as events. Change the choice through the valve (proposeRelease then apply), not a generic update.'),
|
|
75
75
|
entry('ARKORDER_INFORMATION_BUDGET', 'arkorder', 'Projection observes a forbidden kind', 'h(ξ) allowedKinds includes a kind listed in informationBudget.cannotObserve. A scale may not look at what it was told not to see.', 'Cut that kind from the projector or from cannotObserve, then preflight again. Never mechanical-safe.'),
|
|
76
76
|
entry('ARKORDER_XI_TTL', 'arkorder', 'Slow key carries a freshness field', 'ξ named ttl/freshUntil/maxAge. Freshness belongs on σ. A slow parameter that expires per transaction is not slow.', 'Move freshness onto σ (freshUntil) and keep ξ stable, then preflight again. Never mechanical-safe.'),
|
|
77
77
|
entry('ARKORDER_STALE_SIGMA', 'arkorder', 'σ is stale', 'ingest ran after σ.freshUntil (or sigmaMaxAgeMs). ξ does not TTL.', 'Call refreshSigma and ingest again, or proposeRelease then apply(ProposeResult) if the pattern changed. Never mechanical-safe.'),
|
|
78
|
-
entry('ARKORDER_UNVALVED_RELEASE', 'arkorder', '
|
|
78
|
+
entry('ARKORDER_UNVALVED_RELEASE', 'arkorder', 'Second freeze without the valve', 'release() already froze the big choice. A later release() with a different value does not land. First freeze is release(); later change is proposeRelease then apply.', 'Change the choice with proposeRelease then apply. release() is only the first freeze. Never mechanical-safe.'),
|
|
79
79
|
// ── atomic preflight / change set ────────────────────────────────────────
|
|
80
80
|
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.'),
|
|
81
81
|
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.'),
|
|
@@ -21,6 +21,90 @@ import { detectGraphBlindSpots, printGraphBlindSection } from './graph-blind.mjs
|
|
|
21
21
|
import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
|
|
22
22
|
import { collectStewardNudge } from './team-parliament-io.mjs';
|
|
23
23
|
import { formatArkRunDoctorLines, summarizeArkRunSection } from './ark-run-doctor.mjs';
|
|
24
|
+
import {
|
|
25
|
+
ARKORDER_FIRST_CONTACT_NEXT,
|
|
26
|
+
ARKORDER_ONE_BREATH,
|
|
27
|
+
formatArkOrderDoctorLines,
|
|
28
|
+
summarizeArkOrderSection,
|
|
29
|
+
} from './ark-order-doctor.mjs';
|
|
30
|
+
import { composeMergePlanesHonesty } from './extra-merge-teeth.mjs';
|
|
31
|
+
|
|
32
|
+
export function attachExtraDoctorSections(rulesUnderContract, config, classification, findings) {
|
|
33
|
+
const arkRulesMerge = {
|
|
34
|
+
active: rulesUnderContract?.active === true,
|
|
35
|
+
structureEnforced: rulesUnderContract?.mergePlanes?.structureSensors?.enforced,
|
|
36
|
+
structureTotal: rulesUnderContract?.mergePlanes?.structureSensors?.total,
|
|
37
|
+
structureAdvisory: rulesUnderContract?.mergePlanes?.structureSensors?.advisory,
|
|
38
|
+
invariantEnforced: rulesUnderContract?.mergePlanes?.invariants?.enforced,
|
|
39
|
+
invariantTotal: rulesUnderContract?.mergePlanes?.invariants?.total,
|
|
40
|
+
invariantAdvisory: rulesUnderContract?.mergePlanes?.invariants?.advisory,
|
|
41
|
+
covered: rulesUnderContract?.mergePlanes?.invariants?.covered,
|
|
42
|
+
uncovered: rulesUnderContract?.mergePlanes?.invariants?.uncovered,
|
|
43
|
+
};
|
|
44
|
+
const arkRun = summarizeArkRunSection({
|
|
45
|
+
arkRun: config?.arkRun,
|
|
46
|
+
findings,
|
|
47
|
+
classification,
|
|
48
|
+
arkRules: arkRulesMerge,
|
|
49
|
+
});
|
|
50
|
+
const arkOrder = summarizeArkOrderSection({
|
|
51
|
+
arkOrder: config?.arkOrder,
|
|
52
|
+
findings,
|
|
53
|
+
classification,
|
|
54
|
+
arkRules: arkRulesMerge,
|
|
55
|
+
arkRun: {
|
|
56
|
+
present: arkRun.active === true,
|
|
57
|
+
mode: arkRun.mode,
|
|
58
|
+
residualCount: arkRun.residual?.count,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const mergePlanes = composeMergePlanesHonesty({
|
|
62
|
+
classification,
|
|
63
|
+
arkRules: arkRulesMerge,
|
|
64
|
+
arkRun: {
|
|
65
|
+
present: arkRun.active === true,
|
|
66
|
+
mode: arkRun.mode,
|
|
67
|
+
residualCount: arkRun.residual?.count,
|
|
68
|
+
},
|
|
69
|
+
arkOrder: {
|
|
70
|
+
present: arkOrder.active === true,
|
|
71
|
+
mode: arkOrder.mode,
|
|
72
|
+
residualCount: arkOrder.residual?.count,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
if (rulesUnderContract?.mergePlanes) rulesUnderContract.mergePlanes = mergePlanes;
|
|
76
|
+
arkRun.mergePlanes = mergePlanes;
|
|
77
|
+
arkRun.failMergeWhen = mergePlanes.failMergeWhen;
|
|
78
|
+
arkOrder.mergePlanes = mergePlanes;
|
|
79
|
+
arkOrder.failMergeWhen = mergePlanes.failMergeWhen;
|
|
80
|
+
return { arkRun, arkOrder, mergePlanes };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function printCompactExtraDoctorLines(advisories, io) {
|
|
84
|
+
const arkRun = advisories?.arkRun;
|
|
85
|
+
if (arkRun?.active === true && arkRun.notAScore === true) {
|
|
86
|
+
console.log('');
|
|
87
|
+
const residual = Number(arkRun.residual?.count) || 0;
|
|
88
|
+
io.line(residual > 0 ? io.warn : ' ', `ArkRun: ${arkRun.mode || 'on'} · residual=${residual} · not a score`);
|
|
89
|
+
}
|
|
90
|
+
const arkOrder = advisories?.arkOrder;
|
|
91
|
+
if (arkOrder && arkOrder.notAScore === true) {
|
|
92
|
+
console.log('');
|
|
93
|
+
if (arkOrder.active === true) {
|
|
94
|
+
const residual = Number(arkOrder.residual?.count) || 0;
|
|
95
|
+
const keys =
|
|
96
|
+
Array.isArray(arkOrder.xiKeys) && arkOrder.xiKeys.length > 0
|
|
97
|
+
? arkOrder.xiKeys.join(', ')
|
|
98
|
+
: 'unnamed';
|
|
99
|
+
const mark = residual > 0 ? io.warn : ' ';
|
|
100
|
+
io.line(mark, ARKORDER_ONE_BREATH);
|
|
101
|
+
io.line(mark, `ArkOrder: ${arkOrder.mode || 'on'} · xiKeys=${keys} · residual=${residual} · not a score`);
|
|
102
|
+
} else {
|
|
103
|
+
io.line(' ', ARKORDER_ONE_BREATH);
|
|
104
|
+
io.line(' ', ARKORDER_FIRST_CONTACT_NEXT);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
24
108
|
|
|
25
109
|
function classificationFromCoverage(cov) {
|
|
26
110
|
return {
|
|
@@ -55,25 +139,12 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
|
|
|
55
139
|
: undefined);
|
|
56
140
|
const classification = classificationFromCoverage(cov);
|
|
57
141
|
const rulesUnderContract = summarizeRulesUnderContract(root, config, factPaths, classification);
|
|
58
|
-
const arkRun =
|
|
59
|
-
|
|
60
|
-
|
|
142
|
+
const { arkRun, arkOrder } = attachExtraDoctorSections(
|
|
143
|
+
rulesUnderContract,
|
|
144
|
+
config,
|
|
61
145
|
classification,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
structureEnforced: rulesUnderContract?.mergePlanes?.structureSensors?.enforced,
|
|
65
|
-
structureTotal: rulesUnderContract?.mergePlanes?.structureSensors?.total,
|
|
66
|
-
structureAdvisory: rulesUnderContract?.mergePlanes?.structureSensors?.advisory,
|
|
67
|
-
invariantEnforced: rulesUnderContract?.mergePlanes?.invariants?.enforced,
|
|
68
|
-
invariantTotal: rulesUnderContract?.mergePlanes?.invariants?.total,
|
|
69
|
-
invariantAdvisory: rulesUnderContract?.mergePlanes?.invariants?.advisory,
|
|
70
|
-
covered: rulesUnderContract?.mergePlanes?.invariants?.covered,
|
|
71
|
-
uncovered: rulesUnderContract?.mergePlanes?.invariants?.uncovered,
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
if (rulesUnderContract?.mergePlanes) {
|
|
75
|
-
rulesUnderContract.mergePlanes = arkRun.mergePlanes;
|
|
76
|
-
}
|
|
146
|
+
activeViolations
|
|
147
|
+
);
|
|
77
148
|
return {
|
|
78
149
|
contractHealth: computeContractHealth(root, config, cov, rules),
|
|
79
150
|
ambientState: computeAmbientState(ts, root, config, files),
|
|
@@ -86,6 +157,7 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
|
|
|
86
157
|
stewardNudge: collectStewardNudge(root, config),
|
|
87
158
|
rulesUnderContract,
|
|
88
159
|
arkRun,
|
|
160
|
+
arkOrder,
|
|
89
161
|
};
|
|
90
162
|
}
|
|
91
163
|
|
|
@@ -116,4 +188,13 @@ export function printDoctorAdvisories(advisories, io) {
|
|
|
116
188
|
io.line(mark, text);
|
|
117
189
|
}
|
|
118
190
|
}
|
|
191
|
+
const arkOrder = advisories.arkOrder;
|
|
192
|
+
if (arkOrder && arkOrder.notAScore === true) {
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log(io.color.bold('ArkOrder (not a score)'));
|
|
195
|
+
const mark = arkOrder.active && arkOrder.residual?.count > 0 ? io.warn : ' ';
|
|
196
|
+
for (const text of formatArkOrderDoctorLines(arkOrder)) {
|
|
197
|
+
io.line(mark, text);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
119
200
|
}
|
package/bin/lib/doctor-human.mjs
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { arkCommand } from '../ark-shared.mjs';
|
|
7
|
-
import { operatingModeTitle } from './product-copy.mjs';
|
|
7
|
+
import { NORTH_STAR_ONE_LINE, operatingModeTitle } from './product-copy.mjs';
|
|
8
8
|
import { isDoctorHealthyNothingToDo } from './post-green-path.mjs';
|
|
9
9
|
import { printParseHealthSection } from './parse-health.mjs';
|
|
10
|
-
import { printDoctorAdvisories } from './doctor-advisories.mjs';
|
|
10
|
+
import { printDoctorAdvisories, printCompactExtraDoctorLines } from './doctor-advisories.mjs';
|
|
11
11
|
import { designDeltaDoctorLines } from './design-delta.mjs';
|
|
12
12
|
import { enforcementDoctorLines } from './enforcement-state.mjs';
|
|
13
13
|
import { analysisIncompleteStatement } from './analysis-completeness.mjs';
|
|
@@ -73,6 +73,7 @@ export function printDoctorCompactHuman(view) {
|
|
|
73
73
|
const skippableCi = ciNotFailClosedNotice(view);
|
|
74
74
|
|
|
75
75
|
console.log(color.bold(`Ark doctor — ${path.basename(path.resolve(root)) || '.'}`));
|
|
76
|
+
console.log(color.dim(NORTH_STAR_ONE_LINE));
|
|
76
77
|
if (!analysisComplete) line(warn, analysisIncompleteStatement(completeness));
|
|
77
78
|
printParseHealthSection(doctorAdvisories.parseHealth, { color, warn, line });
|
|
78
79
|
|
|
@@ -164,15 +165,7 @@ export function printDoctorCompactHuman(view) {
|
|
|
164
165
|
line(warn, nudge.ask);
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
if (arkRun?.active === true && arkRun.notAScore === true) {
|
|
169
|
-
console.log('');
|
|
170
|
-
const residual = Number(arkRun.residual?.count) || 0;
|
|
171
|
-
line(
|
|
172
|
-
residual > 0 ? warn : ' ',
|
|
173
|
-
`ArkRun: ${arkRun.mode || 'on'} · residual=${residual} · not a score`
|
|
174
|
-
);
|
|
175
|
-
}
|
|
168
|
+
printCompactExtraDoctorLines(doctorAdvisories, { line, warn });
|
|
176
169
|
|
|
177
170
|
if (violations.length === 0) {
|
|
178
171
|
if (!analysisComplete) {
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -658,11 +658,11 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
658
658
|
pilotTarget: residualPilot?.pilotTarget ?? residualPilot?.pilot ?? null,
|
|
659
659
|
arkRulesMergeHonesty: rulesUnderContract?.mergePlanes
|
|
660
660
|
? {
|
|
661
|
-
active: rulesUnderContract.active === true || arkRun?.active === true,
|
|
661
|
+
active: rulesUnderContract.active === true || arkRun?.active === true || doctorAdvisories.arkOrder?.active === true,
|
|
662
662
|
...rulesUnderContract.mergePlanes,
|
|
663
663
|
}
|
|
664
|
-
: rulesUnderContract?.active === true || arkRun?.active === true
|
|
665
|
-
? { active: true, extraMergeTeeth: arkRun?.extraMergeTeeth === true }
|
|
664
|
+
: rulesUnderContract?.active === true || arkRun?.active === true || doctorAdvisories.arkOrder?.active === true
|
|
665
|
+
? { active: true, extraMergeTeeth: arkRun?.extraMergeTeeth === true || doctorAdvisories.arkOrder?.extraMergeTeeth === true }
|
|
666
666
|
: null,
|
|
667
667
|
primaryNextAction:
|
|
668
668
|
adopted === 'not-adopted' ? NOT_ADOPTED_NEXT_ACTION : postGreenPath?.action ?? dualTruthNext,
|
|
@@ -757,6 +757,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
757
757
|
...doctorAdvisories,
|
|
758
758
|
rulesUnderContract,
|
|
759
759
|
arkRun,
|
|
760
|
+
arkOrder: doctorAdvisories.arkOrder,
|
|
760
761
|
// P0-B — single anti-false-green honesty surface (never a score).
|
|
761
762
|
productHonesty,
|
|
762
763
|
governed: cov.governed,
|