arkgate 4.8.14 → 4.8.16
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 +172 -0
- package/README.md +17 -7
- package/bin/ark-check-runtime.mjs +17 -49
- package/bin/ark-mcp-runtime.mjs +111 -2
- package/bin/ark-shared.mjs +142 -11
- package/bin/ark.mjs +24 -33
- package/bin/lib/adr-presence.mjs +97 -0
- package/bin/lib/agent-skills-package.mjs +179 -1
- package/bin/lib/analysis-completeness.mjs +38 -2
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/architecture-scan.mjs +43 -4
- package/bin/lib/ark-run-doctor.mjs +6 -0
- package/bin/lib/ark-run-report.mjs +6 -2
- package/bin/lib/arkrules-sensors.mjs +81 -0
- package/bin/lib/baseline-key.mjs +6 -1
- package/bin/lib/check-args.mjs +52 -2
- package/bin/lib/config-contract.mjs +65 -2
- package/bin/lib/diagnostic-catalog.mjs +5 -1
- package/bin/lib/doctor-advisories.mjs +120 -10
- package/bin/lib/doctor-green-cite.mjs +139 -0
- package/bin/lib/doctor-human.mjs +115 -67
- package/bin/lib/doctor-next-actions.mjs +23 -0
- package/bin/lib/doctor-plan.mjs +35 -2
- package/bin/lib/enforcement-honesty.mjs +47 -2
- package/bin/lib/field-install.mjs +47 -7
- package/bin/lib/first-run-help.mjs +9 -1
- package/bin/lib/host-support-matrix.mjs +9 -1
- package/bin/lib/html-report-depth.mjs +2 -0
- package/bin/lib/html-report.mjs +13 -2
- package/bin/lib/import-resolve.mjs +74 -13
- package/bin/lib/improvement-compass-doctor.mjs +8 -1
- package/bin/lib/improvement-compass-map.mjs +5 -1
- package/bin/lib/invariant-coverage.mjs +121 -0
- package/bin/lib/invariant-tests-path.mjs +212 -0
- package/bin/lib/layer-description.mjs +90 -8
- package/bin/lib/mcp-hook-payload.mjs +56 -0
- package/bin/lib/no-domain-frontend.mjs +91 -0
- package/bin/lib/package-manager.mjs +8 -0
- package/bin/lib/presets.mjs +3 -4
- package/bin/lib/product-copy.mjs +15 -0
- package/bin/lib/projected-governed-coverage.mjs +114 -0
- package/bin/lib/prototype-shortcuts.mjs +224 -0
- package/bin/lib/recommend-cli.mjs +54 -0
- package/bin/lib/remediation.mjs +15 -0
- package/bin/lib/resolved-candidate-facts.mjs +83 -66
- package/bin/lib/rules-under-contract.mjs +117 -2
- package/bin/lib/snippet-analysis.mjs +14 -8
- package/bin/lib/start-preview.mjs +24 -2
- package/bin/lib/states-transitions-presence.mjs +212 -0
- package/bin/lib/status-command.mjs +2 -0
- package/bin/lib/status-transition-catalog.mjs +410 -0
- package/bin/lib/team-parliament-io.mjs +10 -0
- package/bin/lib/violations.mjs +13 -1
- package/bin/lib/write-path-capabilities.mjs +20 -5
- package/bin/lib/write-path-detect.mjs +27 -2
- package/dist/{configTypes-j7so8B4O.d.ts → configTypes-VD0qcubY.d.ts} +21 -1
- package/dist/{diagnosticCatalog-DVx_2RmF.d.ts → diagnosticCatalog-KWvGLI1U.d.ts} +24 -3
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +1 -1
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +34 -34
- package/dist/index.d.ts +71 -10
- package/dist/index.js +34 -34
- package/dist/nestjs/index.cjs +5 -5
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +5 -5
- package/dist/runtime/index.cjs +15 -15
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +15 -15
- package/dist/{types-tGhZUiGX.d.ts → types-BSzRy2X1.d.ts} +1 -1
- package/dist/{types-Djbs3KjE.d.ts → types-D5GT5ZT8.d.ts} +3 -1
- package/docs/README.md +1 -1
- package/docs/agent-guide.md +8 -4
- package/docs/ai-gates.md +10 -3
- package/docs/brownfield-adoption.md +4 -1
- package/docs/configuration.md +66 -10
- package/docs/develop.md +4 -1
- package/docs/diagnostics.md +48 -4
- package/docs/enthusiast/how-to-agent-gates.md +5 -0
- package/docs/enthusiast/how-to-pick-shape.md +1 -1
- package/docs/package-surface.md +7 -5
- package/docs/use.md +10 -3
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +24 -3
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +97 -1
- package/templates/agent-skills/ark-autopilot/SKILL.md +12 -3
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -2
- package/templates/agent-skills/ark-explain/SKILL.md +6 -3
- package/templates/agent-skills/ark-explore/SKILL.md +33 -1
- package/templates/agent-skills/ark-place/SKILL.md +42 -3
- package/templates/agent-skills/ark-runtime/SKILL.md +4 -2
- package/templates/agent-skills/ark-upgrade/SKILL.md +1 -1
- package/templates/arkrules/DomainModel.json +14 -1
- package/templates/skills/ark-adopt.md +97 -1
- package/templates/skills/ark-autopilot.md +12 -3
- package/templates/skills/ark-coverage.md +2 -2
- package/templates/skills/ark-explain.md +6 -3
- package/templates/skills/ark-explore.md +33 -1
- package/templates/skills/ark-place.md +42 -3
- package/templates/skills/ark-runtime.md +4 -2
- package/templates/skills/ark-upgrade.md +1 -1
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
import { layerForRelativePath } from '../ark-layer-match.mjs';
|
|
3
3
|
import { ANALYSIS_COMPLETENESS } from './analysis-completeness.mjs';
|
|
4
4
|
import { evaluateArkRunEditorSensorsFromSource } from './ark-run-sensors.mjs';
|
|
5
|
+
import { getDiagnosticCatalogEntry } from './diagnostic-catalog.mjs';
|
|
6
|
+
|
|
7
|
+
function lexicalEvidenceIncompleteMessage(file) {
|
|
8
|
+
const entry = getDiagnosticCatalogEntry('LEXICAL_EVIDENCE_INCOMPLETE');
|
|
9
|
+
const text = [entry?.why, entry?.fix].filter(Boolean).join(' ');
|
|
10
|
+
return {
|
|
11
|
+
code: 'LEXICAL_EVIDENCE_INCOMPLETE',
|
|
12
|
+
message:
|
|
13
|
+
text ||
|
|
14
|
+
'This check only saw one file, so it cannot fully prove how the import resolves. The result is provisional — `ark-check` on the project is the authority. Run `npx arkgate-check --root . --config ark.config.json` to confirm. Do not call ark_prepare_change from a hook deny.',
|
|
15
|
+
...(file ? { file } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
5
18
|
|
|
6
19
|
export function flattenTsParseDiagnostics(ts, diagnostics, sourceFile) {
|
|
7
20
|
if (!Array.isArray(diagnostics) || !ts) return [];
|
|
@@ -156,14 +169,7 @@ export function validateSnippetAnalysis({ gate, ts, source, context = {} }) {
|
|
|
156
169
|
valid: false,
|
|
157
170
|
lexicalValid: base.valid,
|
|
158
171
|
completeness: ANALYSIS_COMPLETENESS.partial,
|
|
159
|
-
completenessReasons: [
|
|
160
|
-
{
|
|
161
|
-
code: 'LEXICAL_EVIDENCE_INCOMPLETE',
|
|
162
|
-
message:
|
|
163
|
-
'Single-file validation cannot prove project module resolution. The write hook is already the verdict, or re-run `npx arkgate-check --root . --config ark.config.json`. Do not call ark_prepare_change from a hook deny.',
|
|
164
|
-
...(file ? { file } : {}),
|
|
165
|
-
},
|
|
166
|
-
],
|
|
172
|
+
completenessReasons: [lexicalEvidenceIncompleteMessage(file)],
|
|
167
173
|
};
|
|
168
174
|
} catch {
|
|
169
175
|
return {
|
|
@@ -3,7 +3,13 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
arkCommand,
|
|
8
|
+
arkPackageRecoveryCommand,
|
|
9
|
+
buildArchitectureRecommendation,
|
|
10
|
+
START_APPLY_REFUSE_FOOTER,
|
|
11
|
+
} from '../ark-shared.mjs';
|
|
12
|
+
import { withProjectedGovernedCoverage } from './projected-governed-coverage.mjs';
|
|
7
13
|
import { ARKORDER_FIRST_CONTACT_NEXT, ARKORDER_ONE_BREATH } from './ark-order-doctor.mjs';
|
|
8
14
|
import { compactAgentInstructions, instructionRule, mcpJson } from './ci-and-commands.mjs';
|
|
9
15
|
import {
|
|
@@ -134,6 +140,20 @@ function commands(root, args, helpers) {
|
|
|
134
140
|
return result;
|
|
135
141
|
}
|
|
136
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Red next-step when post-apply package install fails.
|
|
145
|
+
* Host files may already be written; local bins may be missing.
|
|
146
|
+
*/
|
|
147
|
+
export function formatStartPackageInstallFailure({ exitStatus, installCommand }) {
|
|
148
|
+
const doctor = arkPackageRecoveryCommand('arkgate-check', '--doctor');
|
|
149
|
+
return [
|
|
150
|
+
`Package install failed (exit ${exitStatus}). Setup files are written; the local command is not installed yet.`,
|
|
151
|
+
` ${installCommand}`,
|
|
152
|
+
` ${doctor}`,
|
|
153
|
+
'`arkgate-check` is a command in the arkgate package — not its own npm package.',
|
|
154
|
+
].join('\n');
|
|
155
|
+
}
|
|
156
|
+
|
|
137
157
|
/**
|
|
138
158
|
* @param {object} preview
|
|
139
159
|
* @param {{ applying?: boolean }} [options] when applying=true, do not claim “no files were changed”
|
|
@@ -166,6 +186,7 @@ export function renderStartPreview(preview, options = {}) {
|
|
|
166
186
|
console.log('Optional extras stay off. This start is layers only — they stop bad imports.');
|
|
167
187
|
console.log(ARKORDER_ONE_BREATH);
|
|
168
188
|
console.log(ARKORDER_FIRST_CONTACT_NEXT);
|
|
189
|
+
console.log(START_APPLY_REFUSE_FOOTER);
|
|
169
190
|
}
|
|
170
191
|
if (preview.runtimeActivation) {
|
|
171
192
|
console.log('Host: Codex is configured but not verified yet. Restart the host, then confirm this project.');
|
|
@@ -325,12 +346,13 @@ export async function planStart(args, helpers) {
|
|
|
325
346
|
};
|
|
326
347
|
} else {
|
|
327
348
|
try {
|
|
328
|
-
const rec = buildArchitectureRecommendation(root);
|
|
349
|
+
const rec = withProjectedGovernedCoverage(buildArchitectureRecommendation(root), root);
|
|
329
350
|
recommendation = {
|
|
330
351
|
archetype: rec.archetype,
|
|
331
352
|
label: rec.label,
|
|
332
353
|
confidence: rec.confidence,
|
|
333
354
|
mature: rec.mature,
|
|
355
|
+
projectedGovernedCoverage: rec.signals?.projectedGovernedCoverage,
|
|
334
356
|
};
|
|
335
357
|
} catch {
|
|
336
358
|
recommendation = null;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft states/transitions artifact guidance (P2 §2 / Guiar).
|
|
3
|
+
* Tooling I/O. Never a gate fail. Silent unless a conventional domain
|
|
4
|
+
* (or dedicated states) markdown home is already in play.
|
|
5
|
+
* Does not walk the tree. Does not scan code for status fields.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
/** Product-domain / domain-model homes. Closed list — no tree walk. */
|
|
12
|
+
export const STATES_TRANSITIONS_DOMAIN_HOMES = Object.freeze([
|
|
13
|
+
'docs/domain.md',
|
|
14
|
+
'docs/data-model.md',
|
|
15
|
+
'docs/product-domain.md',
|
|
16
|
+
'docs/modelo-de-dominio.md',
|
|
17
|
+
'docs/producto-y-dominio.md',
|
|
18
|
+
'docs/architecture.md',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/** Evolved dedicated homes. Adopt if present; do not force a filename. */
|
|
22
|
+
export const STATES_TRANSITIONS_DEDICATED_HOMES = Object.freeze([
|
|
23
|
+
'docs/states.md',
|
|
24
|
+
'docs/transitions.md',
|
|
25
|
+
'docs/states-transitions.md',
|
|
26
|
+
'docs/lifecycle.md',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const STATES_TRANSITIONS_ASK_MISSING =
|
|
30
|
+
'A domain doc is in play, but there is no states → transitions map yet.';
|
|
31
|
+
|
|
32
|
+
export const STATES_TRANSITIONS_ASK_INCOMPLETE =
|
|
33
|
+
'The states heading is there, but it is not a map yet — no table or link.';
|
|
34
|
+
|
|
35
|
+
export const STATES_TRANSITIONS_NEXT =
|
|
36
|
+
'Add a short table (entity · states · allowed from → to) — or one link — on the domain doc. Names from the code. No flag soup.';
|
|
37
|
+
|
|
38
|
+
function readMarkdown(abs) {
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return null;
|
|
41
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
42
|
+
return text.trim().length > 0 ? text : '';
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cellIsStateVocab(cell) {
|
|
49
|
+
return (
|
|
50
|
+
cell === 'state' ||
|
|
51
|
+
cell === 'states' ||
|
|
52
|
+
cell.startsWith('state ') ||
|
|
53
|
+
cell.endsWith(' state') ||
|
|
54
|
+
cell.endsWith(' states')
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function cellIsTransitionVocab(cell) {
|
|
59
|
+
return (
|
|
60
|
+
cell.includes('transition') ||
|
|
61
|
+
cell === 'from' ||
|
|
62
|
+
cell === 'to' ||
|
|
63
|
+
cell === 'from → to' ||
|
|
64
|
+
cell === 'from -> to'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cellIsEntityVocab(cell) {
|
|
69
|
+
return cell === 'entity' || cell === 'entities';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Closed table: entity | states | allowed transitions (or from → to). */
|
|
73
|
+
export function markdownHasStatesTable(text) {
|
|
74
|
+
if (typeof text !== 'string' || text.length === 0) return false;
|
|
75
|
+
for (const line of text.split('\n')) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (!trimmed.startsWith('|')) continue;
|
|
78
|
+
const cells = trimmed
|
|
79
|
+
.split('|')
|
|
80
|
+
.map((cell) => cell.trim().toLowerCase())
|
|
81
|
+
.filter((cell) => cell.length > 0 && !/^[-:]+$/.test(cell));
|
|
82
|
+
if (cells.length < 2) continue;
|
|
83
|
+
const hasEntity = cells.some((cell) => cellIsEntityVocab(cell));
|
|
84
|
+
const hasState = cells.some((cell) => cellIsStateVocab(cell));
|
|
85
|
+
const hasTransition = cells.some((cell) => cellIsTransitionVocab(cell));
|
|
86
|
+
if ((hasState && hasTransition) || (hasEntity && (hasState || hasTransition))) return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function mentionsStatesTopic(value) {
|
|
92
|
+
return (
|
|
93
|
+
value.includes('transition') ||
|
|
94
|
+
value.includes('lifecycle') ||
|
|
95
|
+
value.includes('states/transitions') ||
|
|
96
|
+
value.includes('states-transitions') ||
|
|
97
|
+
value.includes('state machine')
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** One link to the captain’s existing authority. */
|
|
102
|
+
export function markdownHasStatesLink(text) {
|
|
103
|
+
if (typeof text !== 'string' || text.length === 0) return false;
|
|
104
|
+
let from = 0;
|
|
105
|
+
while (from < text.length) {
|
|
106
|
+
const open = text.indexOf('[', from);
|
|
107
|
+
if (open < 0) break;
|
|
108
|
+
const mid = text.indexOf('](', open + 1);
|
|
109
|
+
if (mid < 0) break;
|
|
110
|
+
const close = text.indexOf(')', mid + 2);
|
|
111
|
+
if (close < 0) break;
|
|
112
|
+
const label = text.slice(open + 1, mid).toLowerCase();
|
|
113
|
+
const href = text.slice(mid + 2, close).toLowerCase();
|
|
114
|
+
if (mentionsStatesTopic(label) || mentionsStatesTopic(href)) return true;
|
|
115
|
+
from = close + 1;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function markdownHasStatesHeading(text) {
|
|
121
|
+
if (typeof text !== 'string' || text.length === 0) return false;
|
|
122
|
+
for (const line of text.split('\n')) {
|
|
123
|
+
const trimmed = line.trim();
|
|
124
|
+
if (!trimmed.startsWith('#')) continue;
|
|
125
|
+
const title = trimmed.replace(/^#+\s*/, '').toLowerCase();
|
|
126
|
+
if (
|
|
127
|
+
title === 'state' ||
|
|
128
|
+
title === 'states' ||
|
|
129
|
+
title === 'transition' ||
|
|
130
|
+
title === 'transitions' ||
|
|
131
|
+
title === 'lifecycle' ||
|
|
132
|
+
title.includes('states/transitions') ||
|
|
133
|
+
title.includes('states and transitions')
|
|
134
|
+
) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function scoreMarkdown(text) {
|
|
142
|
+
if (text == null) return 'absent';
|
|
143
|
+
if (text === '') return 'incomplete';
|
|
144
|
+
if (markdownHasStatesTable(text) || markdownHasStatesLink(text)) return 'present';
|
|
145
|
+
if (markdownHasStatesHeading(text)) return 'incomplete';
|
|
146
|
+
return 'missing';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function existingHomes(root, relatives) {
|
|
150
|
+
const found = [];
|
|
151
|
+
for (const rel of relatives) {
|
|
152
|
+
const text = readMarkdown(path.join(root, rel));
|
|
153
|
+
if (text == null) continue;
|
|
154
|
+
found.push({ rel, score: scoreMarkdown(text) });
|
|
155
|
+
}
|
|
156
|
+
return found;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* First conventional home already on disk (domain doc or dedicated file).
|
|
161
|
+
* Empty / missing files do not count as a home.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} root
|
|
164
|
+
* @returns {string | null}
|
|
165
|
+
*/
|
|
166
|
+
export function findStatesTransitionsHome(root) {
|
|
167
|
+
if (typeof root !== 'string' || root.length === 0) return null;
|
|
168
|
+
const homes = [
|
|
169
|
+
...existingHomes(root, STATES_TRANSITIONS_DOMAIN_HOMES),
|
|
170
|
+
...existingHomes(root, STATES_TRANSITIONS_DEDICATED_HOMES),
|
|
171
|
+
];
|
|
172
|
+
return homes[0]?.rel ?? null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function nextActionFor(home) {
|
|
176
|
+
if (!home) return STATES_TRANSITIONS_NEXT;
|
|
177
|
+
return `Add a short table (entity · states · allowed from → to) — or one link — to ${home}. Names from the code. No flag soup.`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Soft residual when a domain (or dedicated states) doc is in play and the
|
|
182
|
+
* map is missing or only a heading. Off / no such doc → null (silent).
|
|
183
|
+
*
|
|
184
|
+
* @param {{ root?: string }} [input]
|
|
185
|
+
* @returns {{ kind: 'missing' | 'incomplete', home: string, ask: string, nextAction: string } | null}
|
|
186
|
+
*/
|
|
187
|
+
export function collectStatesTransitionsResidual(input = {}) {
|
|
188
|
+
const root = input.root ?? '';
|
|
189
|
+
if (typeof root !== 'string' || root.length === 0) return null;
|
|
190
|
+
const homes = [
|
|
191
|
+
...existingHomes(root, STATES_TRANSITIONS_DOMAIN_HOMES),
|
|
192
|
+
...existingHomes(root, STATES_TRANSITIONS_DEDICATED_HOMES),
|
|
193
|
+
];
|
|
194
|
+
if (homes.length === 0) return null;
|
|
195
|
+
if (homes.some((row) => row.score === 'present')) return null;
|
|
196
|
+
const incomplete = homes.find((row) => row.score === 'incomplete');
|
|
197
|
+
const home = (incomplete ?? homes[0]).rel;
|
|
198
|
+
if (incomplete) {
|
|
199
|
+
return {
|
|
200
|
+
kind: 'incomplete',
|
|
201
|
+
home,
|
|
202
|
+
ask: STATES_TRANSITIONS_ASK_INCOMPLETE,
|
|
203
|
+
nextAction: nextActionFor(home),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
kind: 'missing',
|
|
208
|
+
home,
|
|
209
|
+
ask: STATES_TRANSITIONS_ASK_MISSING,
|
|
210
|
+
nextAction: nextActionFor(home),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -306,6 +306,8 @@ export function collectStatusFacts(options = {}) {
|
|
|
306
306
|
const softWriteHost = Boolean(matrix && matrix.capabilities?.['hard-write'] !== true);
|
|
307
307
|
const writePathHonesty = buildWritePathHonesty(hostKey, hardWriteActive, {
|
|
308
308
|
packageInstalled: writePath?.enforcementState?.localWrite?.installed !== false,
|
|
309
|
+
nativeFailClosed: writePath?.nativeFailClosed,
|
|
310
|
+
nativeFailClosedPolicy: writePath?.nativeFailClosedPolicy,
|
|
309
311
|
});
|
|
310
312
|
|
|
311
313
|
const latestPath = path.join(reportsDir(resolvedRoot), 'latest.json');
|