arkgate 4.8.22 → 4.8.24
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 +91 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +27 -78
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +1117 -38
- package/bin/ark-mcp-runtime.mjs +412 -138
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +39 -0
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/agent-projection-command.mjs +5 -1
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +50 -6
- package/bin/lib/ark-order-doctor.mjs +1 -1
- package/bin/lib/ark-order-facts.mjs +410 -15
- package/bin/lib/ark-order-invariants.mjs +51 -9
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +2 -0
- package/bin/lib/ark-run-doctor.mjs +14 -0
- package/bin/lib/ark-run-facts.mjs +434 -11
- package/bin/lib/ark-run-sensors.mjs +5 -3
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrules-sensors.mjs +50 -249
- package/bin/lib/check-args.mjs +23 -1
- package/bin/lib/ci-and-commands.mjs +16 -6
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/codex-home.mjs +4 -2
- package/bin/lib/config-contract-slices.mjs +701 -0
- package/bin/lib/config-contract.mjs +5 -2
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/diagnostic-catalog.mjs +45 -6
- package/bin/lib/doctor-advisories.mjs +84 -1
- package/bin/lib/doctor-next-actions.mjs +17 -2
- package/bin/lib/doctor-plan.mjs +7 -9
- package/bin/lib/effective-contract-load.mjs +121 -59
- package/bin/lib/enforcement-honesty.mjs +22 -0
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/first-run-help.mjs +3 -2
- package/bin/lib/flat-parent-importers.mjs +137 -0
- package/bin/lib/flat-parent-pilot.mjs +192 -0
- package/bin/lib/gate-files.mjs +29 -2
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +1 -1
- package/bin/lib/hook-templates.mjs +18 -16
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +2 -0
- package/bin/lib/html-report-depth.mjs +3 -1
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage.mjs +59 -11
- package/bin/lib/mcp-adoption.mjs +9 -1
- package/bin/lib/mcp-hook-payload.mjs +401 -61
- package/bin/lib/package-manager.mjs +59 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +81 -0
- package/bin/lib/physical-cohesion.mjs +16 -18
- package/bin/lib/pilot-loop.mjs +21 -8
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +168 -10
- package/bin/lib/prepare-change.mjs +120 -6
- package/bin/lib/prepare-write.mjs +1 -0
- package/bin/lib/presets.mjs +3 -5
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +315 -55
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +179 -5
- package/bin/lib/scan-files.mjs +32 -0
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/stable-hash.mjs +90 -0
- package/bin/lib/start-install-recovery.mjs +71 -1
- package/bin/lib/start-preview.mjs +30 -0
- package/bin/lib/status-command.mjs +33 -4
- package/bin/lib/status-manifest.mjs +32 -4
- package/bin/lib/team-parliament-io.mjs +79 -75
- package/bin/lib/team-parliament.mjs +6 -1
- package/bin/lib/write-path-capabilities.mjs +4 -2
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-MBXCFLO4.js +16 -0
- package/dist/chunk-RNQHRDCQ.cjs +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/{configTypes-CTELo87-.d.ts → configTypes-BJbaeoj4.d.cts} +53 -4
- package/dist/configTypes-BJbaeoj4.d.ts +246 -0
- package/dist/eslint/index.cjs +17 -4
- package/dist/eslint/index.d.cts +359 -0
- package/dist/eslint/index.d.ts +178 -158
- package/dist/eslint/index.js +10 -4
- package/dist/index.cjs +38 -37
- package/dist/index.d.cts +5475 -0
- package/dist/index.d.ts +3408 -296
- package/dist/index.js +38 -37
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-WD2TPSwZ.d.ts → runtime/index.d.cts} +814 -290
- package/dist/runtime/index.d.ts +2678 -108
- package/dist/runtime/index.js +13 -30
- package/dist/{types-yDNEQ603.d.ts → types-DRinG-vT.d.cts} +576 -140
- package/dist/types-DRinG-vT.d.ts +1792 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +92 -15
- package/docs/ai-gates.md +160 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +273 -12
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +122 -13
- package/docs/package-surface.md +31 -8
- package/docs/product-voice.md +1 -1
- package/package.json +59 -18
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +163 -4
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +4 -1
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +4 -1
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-runtime.md +6 -1
- package/dist/types-fQ9-KrBj.d.ts +0 -183
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor suggestion: a universe-level flat file that one child slice imports.
|
|
3
|
+
* Advisory only. Not a finding, not a verdict, not a config field.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
findDeniedEdgeDecision,
|
|
7
|
+
layerForRelativePath,
|
|
8
|
+
resolveGovernedSlice,
|
|
9
|
+
sliceAliasDestination,
|
|
10
|
+
sliceIdForPath,
|
|
11
|
+
} from '../ark-layer-match.mjs';
|
|
12
|
+
import { destinationKeepsLayerAndSlice } from './physical-cohesion.mjs';
|
|
13
|
+
import { flatParentImporterGraph } from './flat-parent-importers.mjs';
|
|
14
|
+
|
|
15
|
+
function ruleKey(rule) {
|
|
16
|
+
const child = rule?.childSlices;
|
|
17
|
+
if (!child?.sliceFolders?.length) return '';
|
|
18
|
+
return [
|
|
19
|
+
(rule.sliceFolders ?? []).join('\0'),
|
|
20
|
+
rule.sliceIdentity ?? '',
|
|
21
|
+
child.sliceFolders.join('\0'),
|
|
22
|
+
child.sliceIdentity ?? '',
|
|
23
|
+
(child.commonFolders ?? []).join('\0'),
|
|
24
|
+
].join('\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function distinctChildRules(rules) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const rule of Array.isArray(rules) ? rules : []) {
|
|
31
|
+
const key = ruleKey(rule);
|
|
32
|
+
if (!key || seen.has(key)) continue;
|
|
33
|
+
seen.add(key);
|
|
34
|
+
out.push(rule);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isFlatParent(rel, rule) {
|
|
40
|
+
const universe = sliceIdForPath(rel, rule.sliceFolders, rule.sliceIdentity);
|
|
41
|
+
const raw = sliceIdForPath(rel, rule.childSlices.sliceFolders, rule.childSlices.sliceIdentity);
|
|
42
|
+
return Boolean(universe && raw && raw.toLowerCase() === universe.toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Importers of each flat file, split by child. A file any non-child imports
|
|
47
|
+
* (universe common, a shared root, another universe, another layer) is marked:
|
|
48
|
+
* moving it into one child would turn that edge into a wall deny.
|
|
49
|
+
*/
|
|
50
|
+
function childImporters(flatFiles, graph, rule) {
|
|
51
|
+
const out = new Map();
|
|
52
|
+
for (const target of flatFiles) {
|
|
53
|
+
const universe = resolveGovernedSlice(target, rule).universeId ?? '';
|
|
54
|
+
const ids = new Set();
|
|
55
|
+
let nonChild = false;
|
|
56
|
+
for (const importer of graph.get(target) ?? []) {
|
|
57
|
+
const place = resolveGovernedSlice(importer, rule);
|
|
58
|
+
if (!place.childId || (place.universeId ?? '') !== universe) nonChild = true;
|
|
59
|
+
else ids.add(place.childId);
|
|
60
|
+
}
|
|
61
|
+
out.set(target, { ids, nonChild, importers: [...(graph.get(target) ?? [])] });
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Would any recorded importer be denied by the rules after the move? */
|
|
67
|
+
function moveBreaksWall(importers, destFile, rules, layers) {
|
|
68
|
+
const toLayer = layerForRelativePath(destFile, layers);
|
|
69
|
+
for (const importer of importers) {
|
|
70
|
+
const fromLayer = layerForRelativePath(importer, layers);
|
|
71
|
+
if (!fromLayer || !toLayer) return true;
|
|
72
|
+
const denied = findDeniedEdgeDecision(rules, fromLayer, toLayer, {
|
|
73
|
+
fromPath: importer,
|
|
74
|
+
toPath: destFile,
|
|
75
|
+
layers,
|
|
76
|
+
});
|
|
77
|
+
if (denied) return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function basename(rel) {
|
|
83
|
+
const parts = rel.split('/');
|
|
84
|
+
return parts[parts.length - 1] ?? rel;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {{ root: string, files: string[], rules: object[] | undefined, layers: object[] | undefined, facts?: object, ts?: object }} input
|
|
89
|
+
* @returns {object | null}
|
|
90
|
+
*/
|
|
91
|
+
export function collectFlatParentPilot(input) {
|
|
92
|
+
const rules = distinctChildRules(input?.rules);
|
|
93
|
+
if (rules.length === 0) return null;
|
|
94
|
+
const files = (Array.isArray(input.files) ? input.files : [])
|
|
95
|
+
.map((file) => String(file).replace(/\\/g, '/'))
|
|
96
|
+
.filter((file) => file.length > 0);
|
|
97
|
+
const layers = input.layers;
|
|
98
|
+
const moves = [];
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
let graph = null;
|
|
101
|
+
for (const rule of rules) {
|
|
102
|
+
const flat = files.filter((file) => isFlatParent(file, rule));
|
|
103
|
+
if (flat.length === 0) continue;
|
|
104
|
+
graph ??= flatParentImporterGraph({ root: input.root, files, facts: input.facts, ts: input.ts });
|
|
105
|
+
const importers = childImporters(flat, graph, rule);
|
|
106
|
+
for (const file of flat) {
|
|
107
|
+
if (seen.has(file)) continue;
|
|
108
|
+
const entry = importers.get(file);
|
|
109
|
+
// Exactly one child and nobody else: two children, or any non-child importer, keeps it in place.
|
|
110
|
+
if (!entry || entry.nonChild || entry.ids.size !== 1) continue;
|
|
111
|
+
const importer = [...entry.ids][0];
|
|
112
|
+
const destination = sliceAliasDestination(file, importer, rule.childSlices);
|
|
113
|
+
if (!destination) continue;
|
|
114
|
+
const destFile = `${destination}/${basename(file)}`;
|
|
115
|
+
const layer = layerForRelativePath(file, layers);
|
|
116
|
+
if (!layer || layer !== layerForRelativePath(destFile, layers)) continue;
|
|
117
|
+
const toPlace = resolveGovernedSlice(destFile, rule);
|
|
118
|
+
if ((toPlace.childId ?? '') !== importer) continue;
|
|
119
|
+
if (moveBreaksWall(entry.importers, destFile, input.rules, layers)) continue;
|
|
120
|
+
const fromPlace = resolveGovernedSlice(file, rule);
|
|
121
|
+
const keepsSlice = destinationKeepsLayerAndSlice(file, destFile, layers, [rule]);
|
|
122
|
+
const wallSame =
|
|
123
|
+
(fromPlace.universeId ?? '') === (toPlace.universeId ?? '') &&
|
|
124
|
+
(fromPlace.childId ?? '') === (toPlace.childId ?? '');
|
|
125
|
+
seen.add(file);
|
|
126
|
+
moves.push({
|
|
127
|
+
file,
|
|
128
|
+
importer,
|
|
129
|
+
destination,
|
|
130
|
+
layer,
|
|
131
|
+
keepsLayer: true,
|
|
132
|
+
keepsSlice,
|
|
133
|
+
destinationChild: toPlace.childId,
|
|
134
|
+
agreesWithWall: keepsSlice === wallSame,
|
|
135
|
+
evidence: `One child imports ${file} (${importer}). Move it into ${destination}. The ${layer} layer stays. This is a suggestion with that importer as its evidence.`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (moves.length === 0) return null;
|
|
140
|
+
moves.sort((left, right) => (left.file < right.file ? -1 : left.file > right.file ? 1 : 0));
|
|
141
|
+
return {
|
|
142
|
+
advisory: true,
|
|
143
|
+
notAScore: true,
|
|
144
|
+
proposed: true,
|
|
145
|
+
applied: false,
|
|
146
|
+
neverMechanicalSafe: true,
|
|
147
|
+
moves,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** One pilot candidate per move. Appended after pattern bets and the reshape card. */
|
|
152
|
+
export function flatParentCandidates(section) {
|
|
153
|
+
if (!section || !Array.isArray(section.moves)) return [];
|
|
154
|
+
return section.moves
|
|
155
|
+
.filter((move) => move && typeof move.file === 'string' && move.file.length > 0)
|
|
156
|
+
.map((move) => ({
|
|
157
|
+
source: 'flat-parent',
|
|
158
|
+
target: move.file,
|
|
159
|
+
move: typeof move.evidence === 'string' ? move.evidence : `Move ${move.file} into its one importing child. This is a suggestion.`,
|
|
160
|
+
moveSample: [{ from: move.file, to: `${move.destination}/${basename(move.file)}` }],
|
|
161
|
+
successSignal: 're-run doctor: this file is no longer a single-importer flat parent',
|
|
162
|
+
killSwitch: 'leave the file where it is',
|
|
163
|
+
doNot: [
|
|
164
|
+
'a suggestion with one importer as its evidence',
|
|
165
|
+
'leave a file in place when two children import it',
|
|
166
|
+
'one pilot at a time; re-doctor before the next card',
|
|
167
|
+
'never weaken the contract to make a move pass',
|
|
168
|
+
],
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function printFlatParentPilot(section, io) {
|
|
173
|
+
const moves = Array.isArray(section?.moves) ? section.moves : [];
|
|
174
|
+
if (moves.length === 0) return;
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(io.color.bold('Flat parent files (suggestion)'));
|
|
177
|
+
for (const move of moves.slice(0, 5)) io.line(io.warn, move.evidence);
|
|
178
|
+
if (moves.length > 5) io.line(' ', io.color.dim(`…(+${moves.length - 5} more in doctor JSON)`));
|
|
179
|
+
io.line(' ', io.color.dim('suggestion only — one importer is the evidence; the gate verdict is unchanged'));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function flatParentPilotHtml(section, escape) {
|
|
183
|
+
const moves = Array.isArray(section?.moves) ? section.moves : [];
|
|
184
|
+
if (moves.length === 0) return '';
|
|
185
|
+
const esc = typeof escape === 'function' ? escape : (value) => String(value);
|
|
186
|
+
const items = moves
|
|
187
|
+
.slice(0, 5)
|
|
188
|
+
.map((move) => `<li>${esc(move.evidence ?? '')}</li>`)
|
|
189
|
+
.join('');
|
|
190
|
+
const more = moves.length > 5 ? `<p class="muted">…(+${moves.length - 5} more in doctor JSON)</p>` : '';
|
|
191
|
+
return `<section class="section card" data-advisory="flatParentPilot"><h2>Flat parent files <span class="muted">(suggestion — one importer is the evidence; not a score; the verdict is unchanged)</span></h2><ul>${items}</ul>${more}</section>`;
|
|
192
|
+
}
|
package/bin/lib/gate-files.mjs
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import { codexProjectMcpIsValid } from './codex-home.mjs';
|
|
8
9
|
import { enforcingArkRunText, runsArkCheck } from './github-enforcement.mjs';
|
|
10
|
+
import { npxArkgatePrefixLength } from './package-manager.mjs';
|
|
9
11
|
|
|
10
12
|
export const __packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
11
13
|
export const __arkCheckCli = path.join(__packageRoot, 'bin', 'ark-check.mjs');
|
|
@@ -86,10 +88,27 @@ export function treeHasTypecheckScript(root) {
|
|
|
86
88
|
* @param {{ write?: boolean }} [opts]
|
|
87
89
|
* @returns {{
|
|
88
90
|
* changed: boolean,
|
|
89
|
-
* reason: 'added' | 'already' | 'no-tsconfig' | 'no-package-json',
|
|
91
|
+
* reason: 'added' | 'already' | 'no-tsconfig' | 'no-package-json' | 'no-typescript',
|
|
90
92
|
* script?: string,
|
|
91
93
|
* }}
|
|
92
94
|
*/
|
|
95
|
+
/**
|
|
96
|
+
* True when `typescript` is declared by the project (deps/devDeps/peerDeps) or resolves
|
|
97
|
+
* from its root (e.g. hoisted in a workspace), so a generated `tsc --noEmit` can run.
|
|
98
|
+
*/
|
|
99
|
+
export function projectHasTypeScript(root, pkg = readPackageJson(root) || {}) {
|
|
100
|
+
for (const field of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
101
|
+
const deps = pkg?.[field];
|
|
102
|
+
if (deps && typeof deps === 'object' && Object.hasOwn(deps, 'typescript')) return true;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
createRequire(path.join(root, 'package.json')).resolve('typescript/package.json');
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
export function ensureTypecheckScript(root, opts = {}) {
|
|
94
113
|
const write = opts.write !== false;
|
|
95
114
|
const hasTsconfig =
|
|
@@ -105,6 +124,8 @@ export function ensureTypecheckScript(root, opts = {}) {
|
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
const pkg = readPackageJson(root) || {};
|
|
127
|
+
// `tsc` only exists when typescript resolves — otherwise CI fails with `tsc: not found`.
|
|
128
|
+
if (!projectHasTypeScript(root, pkg)) return { changed: false, reason: 'no-typescript' };
|
|
108
129
|
const scripts =
|
|
109
130
|
pkg.scripts && typeof pkg.scripts === 'object' ? { ...pkg.scripts } : {};
|
|
110
131
|
const script = 'tsc --noEmit';
|
|
@@ -194,7 +215,10 @@ function arkMcpArgs(server) {
|
|
|
194
215
|
const isArkBin = (value) => /^(?:arkgate-mcp|ark-mcp)(?:\.mjs)?$/.test(executableName(value));
|
|
195
216
|
if ([server.command, ...args].filter(isArkBin).length !== 1) return null;
|
|
196
217
|
if (isArkBin(server.command)) return args;
|
|
197
|
-
|
|
218
|
+
const npxPrefix = command === 'npx' ? npxArkgatePrefixLength(args) : 0;
|
|
219
|
+
if ((command === 'npx' || command === 'yarn') && isArkBin(args[npxPrefix])) {
|
|
220
|
+
return args.slice(npxPrefix + 1);
|
|
221
|
+
}
|
|
198
222
|
if (command === 'pnpm') {
|
|
199
223
|
const binIndex =
|
|
200
224
|
args[0] === 'exec'
|
|
@@ -285,6 +309,9 @@ export function hasArkMcpRegistration(root, relativePath = '.mcp.json') {
|
|
|
285
309
|
*/
|
|
286
310
|
export const MCP_JSON_GATE_FILES = ['.mcp.json', '.cursor/mcp.json', '.agents/mcp_config.json'];
|
|
287
311
|
|
|
312
|
+
/** Claude-style hook files: Ark SessionStart/PreToolUse groups are merged, never whole-file replaced. */
|
|
313
|
+
export const CLAUDE_STYLE_HOOK_FILES = ['.claude/settings.json', '.codex/hooks.json'];
|
|
314
|
+
|
|
288
315
|
/**
|
|
289
316
|
* Upsert mcpServers.ark into an existing MCP JSON document.
|
|
290
317
|
* Preserves sibling servers and unknown top-level keys. Returns null if unreadable.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git plumbing for diff-scoped checks (`--changed`, `--local`, `--against`, `--persona`).
|
|
3
|
+
*
|
|
4
|
+
* Every path this module returns or reads is relative to the Ark ROOT (the `-C` directory),
|
|
5
|
+
* never to the repository top level, so a package inside a monorepo sees its own files:
|
|
6
|
+
* - `git diff --relative` strips the package prefix and drops paths outside the root;
|
|
7
|
+
* - `git ls-files --others` is already cwd-relative;
|
|
8
|
+
* - `<ref>:./<path>` makes `git show` resolve from the root instead of the top level.
|
|
9
|
+
* Output is NUL-separated (`-z`) so core.quotePath never C-quotes a non-ASCII name.
|
|
10
|
+
* Every listing is checked: a failed or truncated call is an error, never an empty diff.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
|
|
15
|
+
/** Kill hung git instead of stalling CI. */
|
|
16
|
+
export const SPAWN_TIMEOUT_MS = 8000;
|
|
17
|
+
/** Same ceiling as design-delta: a large untracked tree must not overflow the default 1 MiB. */
|
|
18
|
+
export const GIT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
19
|
+
|
|
20
|
+
/** Refs tried, in order, when no `--base` / `--against` is given. */
|
|
21
|
+
export const TEAM_BASE_CANDIDATES = Object.freeze([
|
|
22
|
+
'origin/dev',
|
|
23
|
+
'origin/main',
|
|
24
|
+
'origin/master',
|
|
25
|
+
'dev',
|
|
26
|
+
'main',
|
|
27
|
+
'master',
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export function runGit(cwd, args) {
|
|
31
|
+
return spawnSync('git', ['-C', cwd, '-c', 'core.quotePath=false', ...args], {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
34
|
+
timeout: SPAWN_TIMEOUT_MS,
|
|
35
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function safeGitRef(value) {
|
|
40
|
+
return (
|
|
41
|
+
typeof value === 'string' &&
|
|
42
|
+
/^[A-Za-z0-9][A-Za-z0-9._/-]{0,200}$/.test(value) &&
|
|
43
|
+
!value.includes('..')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function discoverTeamBaseRef(root, preferred) {
|
|
48
|
+
if (safeGitRef(preferred)) return preferred;
|
|
49
|
+
for (const candidate of TEAM_BASE_CANDIDATES) {
|
|
50
|
+
const exists = runGit(root, ['rev-parse', '--verify', `${candidate}^{commit}`]);
|
|
51
|
+
if (exists.status === 0) return candidate;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function gitFailure(label, result) {
|
|
57
|
+
if (result.error) return `git ${label} failed: ${result.error.code || result.error.message}`;
|
|
58
|
+
if (result.status !== 0) {
|
|
59
|
+
const detail = String(result.stderr || '').trim();
|
|
60
|
+
return `git ${label} failed${detail ? `: ${detail}` : ` (status ${result.status})`}`;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Root-relative paths changed vs `baseRef` (committed since the merge base, staged,
|
|
67
|
+
* unstaged, untracked). Fails closed: any git failure returns `ok: false`.
|
|
68
|
+
*/
|
|
69
|
+
export function listChangedPaths(root, baseRef) {
|
|
70
|
+
if (!safeGitRef(baseRef)) {
|
|
71
|
+
return { ok: false, paths: [], error: 'Invalid or missing git base ref.' };
|
|
72
|
+
}
|
|
73
|
+
const verify = runGit(root, ['rev-parse', '--verify', `${baseRef}^{commit}`]);
|
|
74
|
+
if (verify.status !== 0) {
|
|
75
|
+
return { ok: false, paths: [], error: `Cannot resolve git ref ${baseRef}.` };
|
|
76
|
+
}
|
|
77
|
+
const calls = [
|
|
78
|
+
[`diff ${baseRef}...HEAD`, ['diff', '--relative', '--name-only', '-z', `${baseRef}...HEAD`]],
|
|
79
|
+
['diff (unstaged)', ['diff', '--relative', '--name-only', '-z']],
|
|
80
|
+
['diff --cached', ['diff', '--relative', '--name-only', '-z', '--cached']],
|
|
81
|
+
['ls-files --others', ['ls-files', '-z', '--others', '--exclude-standard']],
|
|
82
|
+
];
|
|
83
|
+
const paths = [];
|
|
84
|
+
for (const [label, gitArgs] of calls) {
|
|
85
|
+
const result = runGit(root, gitArgs);
|
|
86
|
+
const failure = gitFailure(label, result);
|
|
87
|
+
if (failure) return { ok: false, paths: [], error: failure };
|
|
88
|
+
paths.push(...String(result.stdout).split('\0'));
|
|
89
|
+
}
|
|
90
|
+
return { ok: true, paths: [...new Set(paths.filter(Boolean))].sort(), error: null };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Root-relative form of `relPath` for a `<ref>:./<path>` spec; null when outside the root. */
|
|
94
|
+
export function rootRelativeGitPath(root, relPath) {
|
|
95
|
+
if (typeof relPath !== 'string' || !relPath) return null;
|
|
96
|
+
const rel = path.isAbsolute(relPath) ? path.relative(path.resolve(root), relPath) : relPath;
|
|
97
|
+
const normalized = path.posix.normalize(rel.split(path.sep).join('/'));
|
|
98
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../')) return null;
|
|
99
|
+
return normalized.replace(/^\.\//, '');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** File text at `baseRef`, resolved relative to the Ark root (not the repo top level). */
|
|
103
|
+
export function gitShowText(root, baseRef, relPath) {
|
|
104
|
+
const rel = rootRelativeGitPath(root, relPath);
|
|
105
|
+
if (!safeGitRef(baseRef) || !rel) return null;
|
|
106
|
+
const shown = runGit(root, ['show', `${baseRef}:./${rel}`]);
|
|
107
|
+
if (shown.error || shown.status !== 0) return null;
|
|
108
|
+
return shown.stdout;
|
|
109
|
+
}
|
|
@@ -22,7 +22,7 @@ const SAFE_IF = /^(?:['"]?true['"]?|['"]?\$\{\{\s*(?:true|always\(\))\s*\}\}['"]
|
|
|
22
22
|
const SAFE_NEEDS_IF = /^['"]?\$\{\{\s*always\(\)\s*\}\}['"]?$/i;
|
|
23
23
|
const SAFE_CONTINUE = /^['"]?false['"]?$/i;
|
|
24
24
|
const ENV_PREFIX = /^(?:(?:env\s+)?(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)*)/;
|
|
25
|
-
const DIRECT_ARK = /^(?:(?:npx
|
|
25
|
+
const DIRECT_ARK = /^(?:(?:npx(?:\s+(?:-y|--yes))?(?:\s+(?:-p\s+|--package[\s=])arkgate(?:@\S+)?)?|yarn)\s+(?:arkgate-check|ark-check)(?=\s|$)|pnpm(?:\s+--\S+)*\s+exec\s+(?:arkgate-check|ark-check)(?=\s|$)|node\s+(?:\S+\/)?bin\/(?:arkgate-check|ark-check)\.mjs(?=\s|$)|(?:arkgate-check|ark-check)(?=\s|$))/;
|
|
26
26
|
const CHECK_SCRIPT = /^(?:(?:npm|pnpm)\s+run\s+check:architecture(?=\s|$)|yarn(?:\s+run)?\s+check:architecture(?=\s|$))/;
|
|
27
27
|
const FAIL_CLOSED = /(?:^|\s)--(?:strict|strict-merge|require-gates)(?=\s|$)/;
|
|
28
28
|
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
// Generated from hook-templates.source.mjs — run npm run generate:packaged-tooling.
|
|
2
|
-
import{execCommandParts as
|
|
3
|
-
`}function
|
|
4
|
-
`}function
|
|
5
|
-
`}function
|
|
2
|
+
import{execCommandParts as d,execRunner as g}from"../ark-shared.mjs";const p="arkgate-mcp";function S(r){const t=g(r);return`${JSON.stringify({version:1,hooks:{preToolUse:[{command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env CURSOR_PROJECT_DIR --config ark.config.json`,matcher:"Write|StrReplace",failClosed:!0,timeout:30}]}},null,2)}
|
|
3
|
+
`}function R(r){const t=g(r);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${t} ${p} --session-context --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
4
|
+
`}function _(r){const t=g(r);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${t} ${p} --session-context --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"ApplyPatch|apply_patch|Write|Edit|MultiEdit",hooks:[{type:"command",timeout:30,command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
5
|
+
`}function C(r){const{command:t,args:o}=d(r,p,["--root",".","--config","ark.config.json"]),e=n=>n.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),s=o.map(n=>`"${e(n)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
|
|
6
6
|
# CONFIGURED ON DISK \u2014 RUNTIME NOT VERIFIED.
|
|
7
7
|
# Restart Codex, then call ark_identity with expectedRoot before trusting MCP verdicts.
|
|
8
8
|
[mcp_servers.ark]
|
|
9
|
-
command = "${t
|
|
10
|
-
args = [${
|
|
11
|
-
`}function
|
|
9
|
+
command = "${e(t)}"
|
|
10
|
+
args = [${s}]
|
|
11
|
+
`}function E(r){const{command:t,args:o}=d(r,p,["--root",".","--config","ark.config.json"]),e=o.map(s=>`"${s.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
|
|
12
12
|
# Restart Grok (or /mcps \u2192 refresh) after changes. Also loads repo-root .mcp.json.
|
|
13
13
|
[mcp_servers.ark]
|
|
14
|
-
command = "${
|
|
15
|
-
args = [${
|
|
16
|
-
`}function
|
|
17
|
-
`}function
|
|
18
|
-
`}function
|
|
19
|
-
`}function
|
|
20
|
-
`}function
|
|
21
|
-
`}function
|
|
22
|
-
`}
|
|
14
|
+
command = "${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"
|
|
15
|
+
args = [${e}]
|
|
16
|
+
`}function v(r){const t=g(r);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${t} ${p} --session-context --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit|write|search_replace",hooks:[{type:"command",timeout:30,command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
17
|
+
`}function J(r){const t=g(r);return`${JSON.stringify({"ark-write-gate":{PreToolUse:[{matcher:"write_to_file|replace_file_content|multi_replace_file_content",hooks:[{type:"command",timeout:30,command:`${t} ${p} --hook --hook-repair --fail-on-new-smells --root . --config ark.config.json`}]}]}},null,2)}
|
|
18
|
+
`}function N(r){const{command:t,args:o}=d(r,p,["--root",".","--config","ark.config.json"]);return`${JSON.stringify({$schema:"https://opencode.ai/config.json",mcp:{ark:{type:"local",command:[t,...o],enabled:!0}}},null,2)}
|
|
19
|
+
`}function x(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e))return null;const s=e.hooks,k=(Array.isArray(s?.preToolUse)?s.preToolUse:[]).find(a=>a&&typeof a=="object"&&typeof a.command=="string"&&/arkgate-mcp|ark-mcp/.test(a.command)&&/\s--hook(?:\s|$)/.test(` ${a.command} `));if(!k)return null;const u=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?{...o.hooks}:{},l=Array.isArray(u.preToolUse)?[...u.preToolUse]:[],f=a=>a&&typeof a=="object"&&typeof a.command=="string"&&/arkgate-mcp|ark-mcp/.test(a.command)&&/\s--hook(?:\s|$)/.test(` ${a.command} `)&&(typeof a.matcher!="string"||/Write/.test(a.matcher)||/StrReplace/.test(a.matcher)),c=l.filter(a=>!f(a));u.preToolUse=[...c,k];const i={...o,version:o.version??e.version??1,hooks:u};return`${JSON.stringify(i,null,2)}
|
|
20
|
+
`}const O=/(?:^|[\s/])(?:arkgate-mcp|ark-mcp)(?:\.mjs)?(?:\s|$)/,j=/\s--(?:hook|session-context)(?:\s|$)/;function A(r){return!!r&&typeof r=="object"&&typeof r.command=="string"&&O.test(` ${r.command} `)&&j.test(` ${r.command} `)}function P(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e))return null;const s=e.hooks;if(!s||typeof s!="object"||o.hooks!==void 0&&(!o.hooks||typeof o.hooks!="object"||Array.isArray(o.hooks)))return null;const n={...o.hooks??{}};for(const[k,u]of Object.entries(s)){if(!Array.isArray(u))continue;const l=n[k];if(l!==void 0&&!Array.isArray(l))return null;const f=[];for(const c of l??[]){if(!c||typeof c!="object"||!Array.isArray(c.hooks)){f.push(c);continue}const i=c.hooks.filter(a=>!A(a));i.length===c.hooks.length?f.push(c):i.length>0&&f.push({...c,hooks:i})}n[k]=[...f,...u]}return`${JSON.stringify({...o,hooks:n},null,2)}
|
|
21
|
+
`}function m(r){const t=o=>typeof o=="string"?o.replace(/arkgate@[^\s"']+/g,"arkgate@*"):Array.isArray(o)?o.map(t):o&&typeof o=="object"?Object.fromEntries(Object.keys(o).sort().map(e=>[e,t(o[e])])):o;return JSON.stringify(t(r))}function h(r,t){try{const o=JSON.parse(r),e=JSON.parse(t);return!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e)?null:{existing:o,generated:e}}catch{return null}}function y(r){return{status:"removed",text:Object.keys(r).length===0?null:`${JSON.stringify(r,null,2)}
|
|
22
|
+
`}}function U(r,t){const o=h(r,t);if(!o)return{status:"unreadable"};const{existing:e,generated:s}=o;if(e.hooks===void 0)return{status:"absent"};if(!e.hooks||typeof e.hooks!="object"||Array.isArray(e.hooks))return{status:"unreadable"};const n={...e.hooks};let k=0;for(const[l,f]of Object.entries(s.hooks??{})){if(!Array.isArray(f)||n[l]===void 0)continue;if(!Array.isArray(n[l]))return{status:"unreadable"};const c=new Set(f.map(m)),i=[];for(const a of n[l]){if(!(a&&typeof a=="object"&&Array.isArray(a.hooks)&&a.hooks.some(A))){i.push(a);continue}if(!c.has(m(a)))return{status:"customized"};k+=1}i.length>0?n[l]=i:delete n[l]}if(k===0)return{status:"absent"};const u={...e,hooks:n};return Object.keys(n).length===0&&delete u.hooks,y(u)}function T(r,t){const o=h(r,t);if(!o)return{status:"unreadable"};const{existing:e,generated:s}=o,n=e.hooks?.preToolUse;if(n===void 0)return{status:"absent"};if(!Array.isArray(n))return{status:"unreadable"};const k=new Set((Array.isArray(s.hooks?.preToolUse)?s.hooks.preToolUse:[]).map(m)),u=[];let l=0;for(const i of n){if(!(i&&typeof i=="object"&&typeof i.command=="string"&&/arkgate-mcp|ark-mcp/.test(i.command)&&/\s--hook(?:\s|$)/.test(` ${i.command} `))){u.push(i);continue}if(!k.has(m(i)))return{status:"customized"};l+=1}if(l===0)return{status:"absent"};const f={...e.hooks};u.length>0?f.preToolUse=u:delete f.preToolUse;const c={...e,hooks:f};return Object.keys(f).length===0&&delete c.hooks,Object.keys(c).length===1&&c.version===(s.version??1)&&delete c.version,y(c)}function D(r,t){const o=h(r,t);if(!o)return{status:"unreadable"};const{existing:e,generated:s}=o;if(!Object.prototype.hasOwnProperty.call(e,"ark-write-gate"))return{status:"absent"};if(m(e["ark-write-gate"])!==m(s["ark-write-gate"]))return{status:"customized"};const n={...e};return delete n["ark-write-gate"],y(n)}function H(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!e||typeof e!="object"||Array.isArray(e))return null;const s=e["ark-write-gate"];if(!s||typeof s!="object")return null;const n={...o,"ark-write-gate":s};return`${JSON.stringify(n,null,2)}
|
|
23
|
+
`}function w(r,t){let o,e;try{o=r&&r.trim()?JSON.parse(r):{},e=JSON.parse(t)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o))return null;const s={...o};!s.$schema&&e.$schema&&(s.$schema=e.$schema);const n=o.mcp&&typeof o.mcp=="object"&&!Array.isArray(o.mcp)?{...o.mcp}:{};return n.ark=e.mcp.ark,s.mcp=n,`${JSON.stringify(s,null,2)}
|
|
24
|
+
`}export{p as PREFERRED_MCP_BIN,J as antigravityHooks,R as claudeSettings,_ as codexHooks,C as codexProjectConfig,S as cursorHooks,v as grokHooks,E as grokProjectConfig,H as mergeAntigravityArkHook,P as mergeClaudeStyleArkHooks,x as mergeCursorArkHook,w as mergeOpencodeArkMcp,N as opencodeProjectConfig,D as removeAntigravityArkHook,U as removeClaudeStyleArkHooks,T as removeCursorArkHook};
|
|
@@ -37,7 +37,9 @@ function hostProfile(label, hookPath, hookSurface, hookOperations, hardWrite, re
|
|
|
37
37
|
}),
|
|
38
38
|
// Host-native "deny when the checker cannot run" flag (FS-permission analog).
|
|
39
39
|
// required = schema field must be true for hard-write evidence (Cursor failClosed).
|
|
40
|
-
// unsupported = host has no documented flag
|
|
40
|
+
// unsupported = host has no documented flag. ark-mcp --hook fails closed itself
|
|
41
|
+
// (exit 2 / host deny) when its checker cannot run for a governed source write;
|
|
42
|
+
// only a crash of node itself before the launcher runs stays host-owned.
|
|
41
43
|
// none = advisory host; no local write block.
|
|
42
44
|
nativeFailClosed: extras.nativeFailClosed ?? (hardWrite ? 'unsupported' : 'none'),
|
|
43
45
|
// EH07 minimum ops matrix (hard=false for soft hosts on every listed op).
|
|
@@ -13,6 +13,7 @@ import { formatRulesUnderContractHtml } from './rules-under-contract.mjs';
|
|
|
13
13
|
import { formatArkRunHtml } from './ark-run-report.mjs';
|
|
14
14
|
import { formatArkOrderHtml } from './ark-order-report.mjs';
|
|
15
15
|
import { primaryImprovementCompassNextAction } from './improvement-compass.mjs';
|
|
16
|
+
import { flatParentPilotHtml } from './flat-parent-pilot.mjs';
|
|
16
17
|
|
|
17
18
|
// htmlEscape is injected by the caller (html-report.mjs) — importing it back
|
|
18
19
|
// would be a dependency cycle, and the repo's own gate blocks that. The
|
|
@@ -355,6 +356,7 @@ export function renderAdvisorySections(advisories, escape) {
|
|
|
355
356
|
deepModuleCoachHtml(advisories.deepModuleCoach),
|
|
356
357
|
stewardNudgeHtml(advisories.stewardNudge),
|
|
357
358
|
sliceIdentityHtml(advisories.sliceIdentity),
|
|
359
|
+
flatParentPilotHtml(advisories.flatParentPilot, esc),
|
|
358
360
|
contractHealthHtml(advisories.contractHealth),
|
|
359
361
|
ambientStateHtml(advisories.ambientState),
|
|
360
362
|
physicalCohesionHtml(advisories.physicalCohesion),
|
|
@@ -28,6 +28,7 @@ import { buildDeepModuleCoachAdvisory } from './deep-module-coach.mjs';
|
|
|
28
28
|
import { computePhysicalCohesion } from './physical-cohesion.mjs';
|
|
29
29
|
import { computeReshapeDecisionMemory } from './reshape-decisions.mjs';
|
|
30
30
|
import { POST_GREEN_LEDE, operatingModeTitle } from './product-copy.mjs';
|
|
31
|
+
import { anyChildWallAdvisory, anySliceAlias } from '../ark-layer-match.mjs';
|
|
31
32
|
|
|
32
33
|
function esc(value) {
|
|
33
34
|
return String(value)
|
|
@@ -174,7 +175,8 @@ export function buildReportDepthPayload(
|
|
|
174
175
|
: null,
|
|
175
176
|
primaryNextAction: postGreenPath?.action ?? dualTruthNext,
|
|
176
177
|
activeBlockingViolations: activeBlockingCount,
|
|
177
|
-
physicalCohesionResidual: physicalCohesionResidualRemains(physicalCohesion),
|
|
178
|
+
physicalCohesionResidual: physicalCohesionResidualRemains(physicalCohesion), childWallAdvisory: anyChildWallAdvisory(config?.rules),
|
|
179
|
+
sliceAliasDebt: anySliceAlias(config?.rules),
|
|
178
180
|
});
|
|
179
181
|
// Doctor parity: same physical-cohesion + baseline stale facts as runDoctor.
|
|
180
182
|
const baselineStale =
|
|
@@ -56,7 +56,19 @@ export function readTsconfigAliases(ts, root) {
|
|
|
56
56
|
if (!configPath) return { baseUrl: root, aliases: [] };
|
|
57
57
|
const read = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
58
58
|
if (read.error) return { baseUrl: root, aliases: [] };
|
|
59
|
-
|
|
59
|
+
// Options-only parse: `fileNames` are unused here. With the real `ts.sys` the
|
|
60
|
+
// parser walked the whole `include` tree on every write-hook call (#205 twin).
|
|
61
|
+
const optionsOnlyHost = {
|
|
62
|
+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
63
|
+
readDirectory: () => [],
|
|
64
|
+
fileExists: (fileName) => ts.sys.fileExists(fileName),
|
|
65
|
+
readFile: (fileName) => ts.sys.readFile(fileName),
|
|
66
|
+
};
|
|
67
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
68
|
+
read.config,
|
|
69
|
+
optionsOnlyHost,
|
|
70
|
+
path.dirname(configPath)
|
|
71
|
+
);
|
|
60
72
|
const opts = parsed.options || {};
|
|
61
73
|
const baseUrl = opts.baseUrl || path.dirname(configPath);
|
|
62
74
|
const aliases = [];
|