arkgate 4.8.24 → 4.8.26
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 +95 -0
- package/README.md +2 -2
- package/bin/ark-check-runtime.mjs +17 -14
- package/bin/ark-layer-match.mjs +115 -13
- package/bin/ark-mcp-runtime.mjs +36 -31
- package/bin/ark-shared.mjs +4 -23
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +4 -4
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +14 -14
- package/bin/lib/architecture-scan.mjs +11 -9
- package/bin/lib/ark-order-doctor.mjs +3 -28
- package/bin/lib/ark-order-facts.mjs +6 -99
- package/bin/lib/ark-order-types.mjs +1 -2
- package/bin/lib/ark-run-doctor.mjs +3 -28
- package/bin/lib/ark-run-facts.mjs +30 -40
- package/bin/lib/ark-run-sensors.mjs +1 -1
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +7 -5
- package/bin/lib/check-args.mjs +63 -1
- package/bin/lib/ci-and-commands.mjs +8 -72
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +5 -5
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +1 -1
- package/bin/lib/config-contract-slices.mjs +44 -6
- package/bin/lib/config-contract.mjs +40 -117
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +2 -2
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +8 -1
- package/bin/lib/doctor-advisories.mjs +55 -9
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +2 -2
- package/bin/lib/doctor-plan.mjs +4 -6
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +181 -44
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +7 -3
- package/bin/lib/flat-parent-importers.mjs +5 -3
- package/bin/lib/gate-files.mjs +30 -89
- package/bin/lib/github-enforcement.mjs +165 -28
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +16 -16
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +26 -0
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +4 -5
- package/bin/lib/mcp-hook-payload.mjs +4 -4
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +3 -3
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/persistence-write-hint.mjs +33 -8
- package/bin/lib/pin-dependent-gates.mjs +2 -2
- package/bin/lib/policy-delta-io.mjs +60 -12
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +3 -9
- package/bin/lib/prepare-write.mjs +1 -2
- package/bin/lib/presets.mjs +56 -68
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +43 -85
- package/bin/lib/resolved-candidate-facts.mjs +90 -62
- package/bin/lib/rules-under-contract.mjs +72 -8
- package/bin/lib/scan-files.mjs +4 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +1 -1
- package/bin/lib/start-install-recovery.mjs +1 -1
- package/bin/lib/start-preview.mjs +1 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +2 -18
- package/bin/lib/status-manifest.mjs +18 -26
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +3 -17
- package/bin/lib/team-parliament.mjs +15 -9
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +2 -2
- package/dist/chunk-G3JRBJGH.js +16 -0
- package/dist/chunk-R3AJ5RSZ.cjs +16 -0
- package/dist/{configTypes-BJbaeoj4.d.cts → configTypes-GPvvdleM.d.cts} +12 -1
- package/dist/{configTypes-BJbaeoj4.d.ts → configTypes-GPvvdleM.d.ts} +12 -1
- package/dist/eslint/index.cjs +11 -11
- package/dist/eslint/index.d.cts +10 -1
- package/dist/eslint/index.d.ts +10 -1
- package/dist/eslint/index.js +11 -11
- package/dist/index.cjs +34 -34
- package/dist/index.d.cts +587 -515
- package/dist/index.d.ts +587 -515
- package/dist/index.js +34 -34
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +12 -12
- package/dist/runtime/index.d.cts +174 -136
- package/dist/runtime/index.d.ts +174 -136
- package/dist/runtime/index.js +12 -12
- package/dist/{types-DRinG-vT.d.cts → types-CnauMQQL.d.cts} +33 -25
- package/dist/{types-DRinG-vT.d.ts → types-CnauMQQL.d.ts} +33 -25
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +140 -6
- package/docs/ai-gates.md +15 -1
- package/docs/configuration.md +40 -11
- package/docs/diagnostics.md +119 -3
- package/docs/package-surface.md +4 -1
- package/docs/product-voice.md +7 -1
- package/package.json +11 -2
- package/schemas/ark.config.schema.json +31 -3
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +1 -0
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/skills/ark-adopt.md +1 -0
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-place.md +8 -0
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -388
- package/bin/lib/stable-hash.mjs +0 -90
- package/dist/chunk-MBXCFLO4.js +0 -16
- package/dist/chunk-RNQHRDCQ.cjs +0 -16
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
directoryArkRulesReader,
|
|
8
8
|
loadEffectiveArkRules,
|
|
9
9
|
loadEffectiveArkRulesFromDisk,
|
|
10
|
+
normalizeProjectRelativePath,
|
|
10
11
|
objectArkRulesReader,
|
|
11
12
|
} from './effective-contract-load.mjs';
|
|
12
13
|
import {
|
|
@@ -15,7 +16,9 @@ import {
|
|
|
15
16
|
loadInvariantCoverageInputs,
|
|
16
17
|
} from './invariant-coverage-io.mjs';
|
|
17
18
|
import { evaluateInvariantCoverage } from './invariant-coverage.mjs';
|
|
19
|
+
import { attachProbeEvidence } from './invariant-probe-io.mjs';
|
|
18
20
|
import { attachPolicyAdrNote } from './adr-path.mjs';
|
|
21
|
+
import { collectGovernedFiles } from './scan-files.mjs';
|
|
19
22
|
|
|
20
23
|
function readJsonFile(filePath, label) {
|
|
21
24
|
if (!fs.existsSync(filePath)) throw new Error(`${label} not found: ${filePath}`);
|
|
@@ -95,6 +98,20 @@ function configPathInRepository(root, configPath, top) {
|
|
|
95
98
|
* ArkRules files at the base ref, relative to the project root inside the repository.
|
|
96
99
|
* A path absent at the ref (added by the candidate) reads as `missing`.
|
|
97
100
|
*/
|
|
101
|
+
function baseRefSourceFiles(top, ref, prefix) {
|
|
102
|
+
const shown = runGit(top, ['ls-tree', '-r', '--name-only', ref]);
|
|
103
|
+
if (shown.status !== 0) return [];
|
|
104
|
+
const rootPrefix = prefix ? `${prefix}/` : '';
|
|
105
|
+
const files = [];
|
|
106
|
+
for (const line of shown.stdout.split('\n')) {
|
|
107
|
+
if (!line) continue;
|
|
108
|
+
if (rootPrefix && !line.startsWith(rootPrefix)) continue;
|
|
109
|
+
const rel = rootPrefix ? line.slice(rootPrefix.length) : line;
|
|
110
|
+
if (/\.[cm]?[tj]sx?$/.test(rel) && !rel.endsWith('.d.ts')) files.push(rel);
|
|
111
|
+
}
|
|
112
|
+
return files;
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
function baseRefArkRulesReader(root, top, ref) {
|
|
99
116
|
const prefix = path
|
|
100
117
|
.relative(fs.realpathSync(top), fs.realpathSync(root))
|
|
@@ -102,6 +119,7 @@ function baseRefArkRulesReader(root, top, ref) {
|
|
|
102
119
|
.join('/');
|
|
103
120
|
const spec = (rel) => `${ref}:${prefix ? `${prefix}/` : ''}${rel}`;
|
|
104
121
|
return {
|
|
122
|
+
files: baseRefSourceFiles(top, ref, prefix),
|
|
105
123
|
readArkRulesFile: (rel) => {
|
|
106
124
|
const shown = runGit(top, ['show', spec(rel)]);
|
|
107
125
|
if (shown.status === 0) return { ok: true, content: shown.stdout };
|
|
@@ -131,11 +149,16 @@ export function resolvePolicyBaseConfig({
|
|
|
131
149
|
// A base config FILE carries its ArkRules next to it (same relative paths as the
|
|
132
150
|
// project), never from the candidate working tree: that would compare the
|
|
133
151
|
// candidate catalog with itself and hide every demotion/deletion.
|
|
152
|
+
const baseRoot = fs.realpathSync(path.dirname(absolute));
|
|
153
|
+
const include = Array.isArray(config.include) && config.include.length > 0 ? config.include : ['src'];
|
|
134
154
|
return {
|
|
135
155
|
config,
|
|
136
156
|
source: absolute,
|
|
137
157
|
ref: null,
|
|
138
|
-
|
|
158
|
+
files: collectGovernedFiles(baseRoot, { ...config, include })
|
|
159
|
+
.map((file) => path.relative(baseRoot, file).split(path.sep).join('/'))
|
|
160
|
+
.filter((rel) => rel && !rel.startsWith('..')),
|
|
161
|
+
readArkRulesFile: directoryArkRulesReader(baseRoot),
|
|
139
162
|
};
|
|
140
163
|
}
|
|
141
164
|
|
|
@@ -198,9 +221,10 @@ export function readPolicyAcknowledgement(root, acknowledgementPath) {
|
|
|
198
221
|
* failure is fail-closed: silently dropping the base catalog would make every
|
|
199
222
|
* ArkRule demotion or deletion classify as neutral.
|
|
200
223
|
*/
|
|
201
|
-
|
|
224
|
+
function loadBaseArkRules(base) {
|
|
202
225
|
const loaded = loadEffectiveArkRules(base.config, base.readArkRulesFile, {
|
|
203
226
|
missingAsEmpty: base.ref != null,
|
|
227
|
+
...(Array.isArray(base.files) ? { files: base.files } : {}),
|
|
204
228
|
});
|
|
205
229
|
if (loaded.errors.length > 0) {
|
|
206
230
|
const message = loaded.errors.map((issue) => `- ${issue.path}: ${issue.message}`).join('\n');
|
|
@@ -220,6 +244,17 @@ function hasArkRulesMap(config) {
|
|
|
220
244
|
return Boolean(refs && typeof refs === 'object' && Object.keys(refs).length > 0);
|
|
221
245
|
}
|
|
222
246
|
|
|
247
|
+
/** Paths in a supplied catalog. A slice rule file is enough for the child wall to name it. */
|
|
248
|
+
function catalogPaths(files) {
|
|
249
|
+
if (!files || typeof files !== 'object') return [];
|
|
250
|
+
const out = [];
|
|
251
|
+
for (const key of Object.keys(files)) {
|
|
252
|
+
const rel = normalizeProjectRelativePath(String(key));
|
|
253
|
+
if (rel) out.push(rel);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
223
258
|
function loadedOrThrow(loaded, label) {
|
|
224
259
|
if (loaded.errors.length === 0) return loaded.arkRules;
|
|
225
260
|
const message = loaded.errors.map((issue) => `- ${issue.path}: ${issue.message}`).join('\n');
|
|
@@ -275,7 +310,9 @@ export function resolvePolicyDeltaArkRules({
|
|
|
275
310
|
);
|
|
276
311
|
}
|
|
277
312
|
baseArkRules = loadedOrThrow(
|
|
278
|
-
loadEffectiveArkRules(baseConfig, objectArkRulesReader(baseArkRuleFiles)
|
|
313
|
+
loadEffectiveArkRules(baseConfig, objectArkRulesReader(baseArkRuleFiles), {
|
|
314
|
+
files: catalogPaths(baseArkRuleFiles),
|
|
315
|
+
}),
|
|
279
316
|
'Base'
|
|
280
317
|
);
|
|
281
318
|
} else {
|
|
@@ -285,7 +322,9 @@ export function resolvePolicyDeltaArkRules({
|
|
|
285
322
|
let candidateArkRules;
|
|
286
323
|
if (candidateArkRuleFiles && typeof candidateArkRuleFiles === 'object') {
|
|
287
324
|
candidateArkRules = loadedOrThrow(
|
|
288
|
-
loadEffectiveArkRules(candidateConfig, objectArkRulesReader(candidateArkRuleFiles)
|
|
325
|
+
loadEffectiveArkRules(candidateConfig, objectArkRulesReader(candidateArkRuleFiles), {
|
|
326
|
+
files: catalogPaths(candidateArkRuleFiles),
|
|
327
|
+
}),
|
|
289
328
|
'Candidate'
|
|
290
329
|
);
|
|
291
330
|
} else if (candidateIsProjectConfig) {
|
|
@@ -316,16 +355,17 @@ export function analyzePolicyTransition({
|
|
|
316
355
|
acknowledgementPath,
|
|
317
356
|
}) {
|
|
318
357
|
if (!strictMerge && !basePath && !baseRef && !acknowledgementPath) return undefined;
|
|
358
|
+
if (strictMerge && !basePath) {
|
|
359
|
+
const top = repositoryRoot(root);
|
|
360
|
+
if (top) configPathInRepository(root, configPath, top);
|
|
361
|
+
}
|
|
319
362
|
const base = resolvePolicyBaseConfig({ root, configPath, basePath, baseRef, adoptMissingBase });
|
|
320
363
|
if (!base && (basePath || (baseRef && !adoptMissingBase) || acknowledgementPath)) {
|
|
321
364
|
throw new Error('Policy delta was requested but no policy base could be resolved.');
|
|
322
365
|
}
|
|
323
366
|
if (!base) return undefined;
|
|
324
367
|
|
|
325
|
-
//
|
|
326
|
-
// and attach candidate coverage so covered advisory→enforced can auto-allow.
|
|
327
|
-
// Base ArkRules come from the SAME source as the base config (git ref or the
|
|
328
|
-
// base file's directory), never from the candidate working tree.
|
|
368
|
+
// Base ArkRules come from the base ref or base file, never the candidate tree.
|
|
329
369
|
const baseArkRules = loadBaseArkRules(base);
|
|
330
370
|
const candidateLoad = loadEffectiveArkRulesFromDisk(root, candidateConfig);
|
|
331
371
|
if (candidateLoad.errors.length > 0) {
|
|
@@ -347,12 +387,20 @@ export function analyzePolicyTransition({
|
|
|
347
387
|
fileContents: coverageInputs.fileContents,
|
|
348
388
|
testFiles: coverageInputs.testFiles,
|
|
349
389
|
testGlobsMissing: coverageInputs.testGlobsMissing,
|
|
350
|
-
// No coverageStats / coverageRoots: this caller reads coverage ROWS and
|
|
351
|
-
// drops the violations, and both only shape violation messages. Passing
|
|
352
|
-
// them would look like wiring while changing nothing observable here.
|
|
353
390
|
coverageBudgetExhausted: coverageInputs.coverageBudgetExhausted === true,
|
|
391
|
+
...(coverageInputs.stats ? { coverageStats: coverageInputs.stats } : {}),
|
|
392
|
+
// The declared roots set `coverageRootsDeclared` / `outsideDeclaredRoots`
|
|
393
|
+
// on each row, and canPromoteInvariant refuses without them. Same inputs
|
|
394
|
+
// as `--promote` (sensor-promote-io), so both surfaces judge alike.
|
|
395
|
+
...(coverageInputs.coverageRoots ? { coverageRoots: coverageInputs.coverageRoots } : {}),
|
|
354
396
|
});
|
|
355
|
-
|
|
397
|
+
// ADR 0039: the same committed probe evidence --promote reads, so both
|
|
398
|
+
// surfaces refuse alike. Read-only; nothing here runs a test.
|
|
399
|
+
candidateInvariantCoverage = attachProbeEvidence(
|
|
400
|
+
root,
|
|
401
|
+
evaluated.coverage,
|
|
402
|
+
candidateArkRules.invariants
|
|
403
|
+
).rows;
|
|
356
404
|
}
|
|
357
405
|
|
|
358
406
|
const acknowledgement = readPolicyAcknowledgement(root, acknowledgementPath);
|
|
@@ -21,7 +21,7 @@ export const POST_GREEN_PRIMARY_SKILL = '/ark-explore';
|
|
|
21
21
|
export const POST_GREEN_PRIMARY_ACTION = POST_GREEN_HUMAN;
|
|
22
22
|
|
|
23
23
|
/** Short label for tables / metrics. */
|
|
24
|
-
|
|
24
|
+
const POST_GREEN_PRIMARY_SHORT =
|
|
25
25
|
'/ark-explore shape-focus → /ark-autopilot (apply one B pilot) # Shape residual';
|
|
26
26
|
|
|
27
27
|
/** Placement coaching while design residual remains (new code only). */
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { effectiveAnalysisConfig } from './analysis-policy.mjs';
|
|
16
16
|
import { isScanExcludedRelative } from '../ark-shared.mjs';
|
|
17
17
|
import { classifyChangeSet, evaluateTeamGate } from './team-parliament.mjs';
|
|
18
|
-
import { loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
|
|
18
|
+
import { arkRulesLoadFailed, loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
|
|
19
19
|
import {
|
|
20
20
|
coverageOptionsFromConfig,
|
|
21
21
|
invariantIdsFromCatalog,
|
|
@@ -83,7 +83,7 @@ function assertInsideProject(root, relativePath) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
function normalizeChangeSet(input) {
|
|
87
87
|
if (!Array.isArray(input)) throw new Error('changes must be an array.');
|
|
88
88
|
return input.map((change, index) => {
|
|
89
89
|
if (!change || typeof change !== 'object' || Array.isArray(change)) {
|
|
@@ -201,13 +201,7 @@ export function prepareChangeFromRoot({
|
|
|
201
201
|
// and a broken reference fails closed instead of silently dropping the catalog.
|
|
202
202
|
const arkRulesLoad = loadEffectiveArkRulesFromDisk(root, effectiveConfig);
|
|
203
203
|
if (arkRulesLoad.errors.length > 0) {
|
|
204
|
-
|
|
205
|
-
.map((issue) => (typeof issue === 'string' ? issue : `- ${issue.path}: ${issue.message}`))
|
|
206
|
-
.join('\n');
|
|
207
|
-
const error = new Error(`Invalid Effective Contract (${contractSource}):\n${message}`);
|
|
208
|
-
error.code = 'ARKRULES_LOAD_FAILED';
|
|
209
|
-
error.issues = arkRulesLoad.errors;
|
|
210
|
-
throw error;
|
|
204
|
+
throw arkRulesLoadFailed(contractSource, arkRulesLoad.errors);
|
|
211
205
|
}
|
|
212
206
|
const contract = loadContract(effectiveConfig, contractSource, {
|
|
213
207
|
arkRules: arkRulesLoad.arkRules,
|
|
@@ -8,11 +8,10 @@ import crypto from 'node:crypto';
|
|
|
8
8
|
import { validateWithAutoPatch } from './auto-patch.mjs';
|
|
9
9
|
import { classifyRemediation, enrichViolationWithFixClass } from './remediation.mjs';
|
|
10
10
|
import {
|
|
11
|
-
layerDescriptionCaption,
|
|
12
11
|
placementDescriptionFields,
|
|
13
12
|
} from './layer-description.mjs';
|
|
14
13
|
|
|
15
|
-
export {
|
|
14
|
+
export { placementDescriptionFields };
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Stable content identity for host commit / cache keys.
|
package/bin/lib/presets.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { withArkConfigMetadata } from './config-contract.mjs';
|
|
|
19
19
|
const PRESETS_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
20
20
|
const ARKRULES_TEMPLATES_DIR = path.join(PRESETS_DIR, '../../templates/arkrules');
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
function denyUpward(names) {
|
|
23
23
|
const rules = [];
|
|
24
24
|
for (let i = 0; i < names.length; i += 1) {
|
|
25
25
|
for (let j = i + 1; j < names.length; j += 1) {
|
|
@@ -33,7 +33,7 @@ export function denyUpward(names) {
|
|
|
33
33
|
* peerIsolation matrix: deny only when importer/importee sit under different slices.
|
|
34
34
|
* Covers same-layer and cross-layer pairs (honest DDD / vertical-slice isolation).
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
function peerIsolationEdges(layerNames, sliceFolders, message) {
|
|
37
37
|
const rules = [];
|
|
38
38
|
for (const from of layerNames) {
|
|
39
39
|
for (const to of layerNames) {
|
|
@@ -57,7 +57,7 @@ export function peerIsolationEdges(layerNames, sliceFolders, message) {
|
|
|
57
57
|
// Framework internals under `src/kernel/**` are NOT application architecture — a broad
|
|
58
58
|
// `src/**/domain/**` would otherwise swallow `src/kernel/domain`. Do NOT use `**/kernel/**`
|
|
59
59
|
// (that carves out legitimate `src/shared/kernel/**` SharedKernel paths).
|
|
60
|
-
|
|
60
|
+
const FRAMEWORK_INTERNAL_EXCLUDE = ['src/kernel/**', '**/src/kernel/**'];
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Shared high-spec domain globs. Prefer these over Application/Presentation scatter
|
|
@@ -217,18 +217,6 @@ export const APPLICATION_LIB_ORCHESTRATION_PATTERNS = Object.freeze([
|
|
|
217
217
|
'**/lib/api-handlers/**',
|
|
218
218
|
]);
|
|
219
219
|
|
|
220
|
-
/**
|
|
221
|
-
* AR08 — attach lean arkRules map. Keys are always exact project layer names.
|
|
222
|
-
* Sensor roles (domain-structure / orchestration / adapter-thin / generic) are
|
|
223
|
-
* independent of display names so renamed layers still get the right starter.
|
|
224
|
-
*/
|
|
225
|
-
export const DEFAULT_ARKRULES_REFS = {
|
|
226
|
-
DomainModel: 'arkrules/DomainModel.json',
|
|
227
|
-
ApplicationOrchestration: 'arkrules/ApplicationOrchestration.json',
|
|
228
|
-
PresentationAdapters: 'arkrules/PresentationAdapters.json',
|
|
229
|
-
PersistenceAdapters: 'arkrules/PersistenceAdapters.json',
|
|
230
|
-
};
|
|
231
|
-
|
|
232
220
|
/** Sensor roles used when selecting or synthesizing per-layer templates. */
|
|
233
221
|
export const ARKRULES_SENSOR_ROLES = Object.freeze({
|
|
234
222
|
DOMAIN_STRUCTURE: 'domain-structure',
|
|
@@ -241,7 +229,7 @@ export const ARKRULES_SENSOR_ROLES = Object.freeze({
|
|
|
241
229
|
* Exact-name aliases → sensor role. Prefer this table over heuristics when the
|
|
242
230
|
* project uses a known vocabulary (hexagonal, monorepo field renames, etc.).
|
|
243
231
|
*/
|
|
244
|
-
|
|
232
|
+
const LAYER_SENSOR_ROLE_ALIASES = Object.freeze({
|
|
245
233
|
// Domain / pure model
|
|
246
234
|
DomainModel: ARKRULES_SENSOR_ROLES.DOMAIN_STRUCTURE,
|
|
247
235
|
Domain: ARKRULES_SENSOR_ROLES.DOMAIN_STRUCTURE,
|
|
@@ -448,7 +436,46 @@ export function writeArkRulesTemplates(root, config, { force = false } = {}) {
|
|
|
448
436
|
return written;
|
|
449
437
|
}
|
|
450
438
|
|
|
451
|
-
|
|
439
|
+
/**
|
|
440
|
+
* Deny edges shared by the ports-and-adapters presets after their DomainModel
|
|
441
|
+
* rules: orchestration never reaches adapters, adapters never reach each other,
|
|
442
|
+
* and presentation never reaches the domain directly. Fresh objects per call.
|
|
443
|
+
*/
|
|
444
|
+
function portsAndAdaptersRules() {
|
|
445
|
+
return [
|
|
446
|
+
{ from: 'ApplicationOrchestration', to: 'PersistenceAdapters', allowed: false },
|
|
447
|
+
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
448
|
+
{ from: 'PresentationAdapters', to: 'PersistenceAdapters', allowed: false },
|
|
449
|
+
{ from: 'PresentationAdapters', to: 'DomainModel', allowed: false },
|
|
450
|
+
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
451
|
+
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
452
|
+
];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* UI-surface preset rules: the domain and orchestration stay out of the UI, while
|
|
457
|
+
* presentation may reach data clients on day one (soft, `message` explains why).
|
|
458
|
+
* Persistence → Presentation stays denied. Fresh objects per call.
|
|
459
|
+
* @param {string} presentationToPersistenceMessage
|
|
460
|
+
*/
|
|
461
|
+
function uiSurfaceRules(presentationToPersistenceMessage) {
|
|
462
|
+
return [
|
|
463
|
+
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
464
|
+
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
465
|
+
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
466
|
+
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
467
|
+
{
|
|
468
|
+
from: 'PresentationAdapters',
|
|
469
|
+
to: 'PersistenceAdapters',
|
|
470
|
+
allowed: true,
|
|
471
|
+
message: presentationToPersistenceMessage,
|
|
472
|
+
},
|
|
473
|
+
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
474
|
+
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function presetWithOverlays(baseConfig, root) {
|
|
452
479
|
const config = root ? applyFrameworkLayoutOverlays(baseConfig, root) : baseConfig;
|
|
453
480
|
return withArkConfigMetadata(withDefaultArkRules(config));
|
|
454
481
|
}
|
|
@@ -511,12 +538,7 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
511
538
|
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
512
539
|
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
513
540
|
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
514
|
-
|
|
515
|
-
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
516
|
-
{ from: 'PresentationAdapters', to: 'PersistenceAdapters', allowed: false },
|
|
517
|
-
{ from: 'PresentationAdapters', to: 'DomainModel', allowed: false },
|
|
518
|
-
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
519
|
-
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
541
|
+
...portsAndAdaptersRules(),
|
|
520
542
|
],
|
|
521
543
|
},
|
|
522
544
|
root
|
|
@@ -705,12 +727,7 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
705
727
|
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
706
728
|
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
707
729
|
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
708
|
-
|
|
709
|
-
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
710
|
-
{ from: 'PresentationAdapters', to: 'PersistenceAdapters', allowed: false },
|
|
711
|
-
{ from: 'PresentationAdapters', to: 'DomainModel', allowed: false },
|
|
712
|
-
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
713
|
-
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
730
|
+
...portsAndAdaptersRules(),
|
|
714
731
|
],
|
|
715
732
|
},
|
|
716
733
|
root
|
|
@@ -810,24 +827,12 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
810
827
|
optional: true,
|
|
811
828
|
},
|
|
812
829
|
],
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
// ui-surface keeps this as a soft guidance edge (allowed) until ports exist —
|
|
820
|
-
// Persistence → Presentation stays denied when that edge appears.
|
|
821
|
-
{
|
|
822
|
-
from: 'PresentationAdapters',
|
|
823
|
-
to: 'PersistenceAdapters',
|
|
824
|
-
allowed: true,
|
|
825
|
-
message:
|
|
826
|
-
'UI/routes may reach data clients on day one (RSC); prefer application ports as the product grows.',
|
|
827
|
-
},
|
|
828
|
-
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
829
|
-
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
830
|
-
],
|
|
830
|
+
// Next RSC often imports data clients from routes; deny is ideal but day-one
|
|
831
|
+
// ui-surface keeps this as a soft guidance edge (allowed) until ports exist —
|
|
832
|
+
// Persistence → Presentation stays denied when that edge appears.
|
|
833
|
+
rules: uiSurfaceRules(
|
|
834
|
+
'UI/routes may reach data clients on day one (RSC); prefer application ports as the product grows.'
|
|
835
|
+
),
|
|
831
836
|
},
|
|
832
837
|
root
|
|
833
838
|
),
|
|
@@ -978,12 +983,7 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
978
983
|
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
979
984
|
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
980
985
|
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
981
|
-
|
|
982
|
-
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
983
|
-
{ from: 'PresentationAdapters', to: 'PersistenceAdapters', allowed: false },
|
|
984
|
-
{ from: 'PresentationAdapters', to: 'DomainModel', allowed: false },
|
|
985
|
-
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
986
|
-
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
986
|
+
...portsAndAdaptersRules(),
|
|
987
987
|
{ from: 'SharedKernel', to: 'DomainModel', allowed: false },
|
|
988
988
|
{ from: 'SharedKernel', to: 'ApplicationOrchestration', allowed: false },
|
|
989
989
|
{ from: 'SharedKernel', to: 'PresentationAdapters', allowed: false },
|
|
@@ -1078,21 +1078,9 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
1078
1078
|
optional: true,
|
|
1079
1079
|
},
|
|
1080
1080
|
],
|
|
1081
|
-
rules:
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
1085
|
-
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
1086
|
-
{
|
|
1087
|
-
from: 'PresentationAdapters',
|
|
1088
|
-
to: 'PersistenceAdapters',
|
|
1089
|
-
allowed: true,
|
|
1090
|
-
message:
|
|
1091
|
-
'SPA day-one UI may reach data clients; prefer ports as the product grows.',
|
|
1092
|
-
},
|
|
1093
|
-
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
1094
|
-
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
1095
|
-
],
|
|
1081
|
+
rules: uiSurfaceRules(
|
|
1082
|
+
'SPA day-one UI may reach data clients; prefer ports as the product grows.'
|
|
1083
|
+
),
|
|
1096
1084
|
},
|
|
1097
1085
|
root
|
|
1098
1086
|
),
|
package/bin/lib/product-copy.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export const ARKRULES_FIRST_CONTACT_NEXT =
|
|
|
25
25
|
export const ARKRULES_EMPTY_CATALOG_NEXT =
|
|
26
26
|
'Next: add 1–2 short phrases to invariants[] in the Domain rules file, then ark-check --doctor.';
|
|
27
27
|
|
|
28
|
-
/** Status-light leftover-design qualifier (was “design-weak”). */
|
|
28
|
+
/** Status-light leftover-design qualifier (was “design-weak”); also the HTML/doctor badge text. */
|
|
29
29
|
export const LEFTOVER_DESIGN_LABEL = 'leftover design work';
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -41,14 +41,9 @@ export function operatingModeTitle(mode, leftoverDesign, stewardsUnset) {
|
|
|
41
41
|
return light;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Short HTML/doctor badge text. */
|
|
45
|
-
export const LEFTOVER_DESIGN_BADGE = LEFTOVER_DESIGN_LABEL;
|
|
46
|
-
|
|
47
44
|
export const POST_GREEN_HUMAN =
|
|
48
45
|
'Imports check out, but the design is still messy. Next: /ark-explore, then one small refactor with /ark-autopilot and your OK.';
|
|
49
46
|
|
|
50
47
|
export const POST_GREEN_LEDE =
|
|
51
48
|
'Import rules are clean, but leftover design work remains. That does not fail the check — it only means “done” is still wrong until you tidy shape.';
|
|
52
49
|
|
|
53
|
-
export const HARD_WRITE_HUMAN = 'pre-write block';
|
|
54
|
-
export const ADVISORY_WRITE_HUMAN = 'warning only (not blocked)';
|
package/bin/lib/project-root.mjs
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* Monorepo honesty (NEW-MONOREPO-CWD-WALKUP): when cwd (or --root) has no
|
|
5
5
|
* ark.config.json, walk parent directories until one is found. Never invent a
|
|
6
6
|
* silent 11-layer / empty ADAPT world while a parent monorepo contract exists.
|
|
7
|
+
* That ancestor contains the directory the caller named, so the requested tree
|
|
8
|
+
* stays inside the analysis.
|
|
9
|
+
*
|
|
10
|
+
* An explicit --config path (absolute, or any name with a separator) is a file.
|
|
11
|
+
* Layer patterns resolve against the caller's --root. The file's directory is
|
|
12
|
+
* never a fallback analysis root (#352).
|
|
7
13
|
*
|
|
8
14
|
* Security (S0 walk-up review):
|
|
9
15
|
* - Split **config discovery root** from **write root**.
|
|
@@ -16,14 +22,14 @@ import fs from 'node:fs';
|
|
|
16
22
|
import path from 'node:path';
|
|
17
23
|
|
|
18
24
|
/** Safety cap so a pathological tree cannot walk forever. */
|
|
19
|
-
|
|
25
|
+
const MAX_CONFIG_WALK_DEPTH = 32;
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* True when dir looks like a package-manager workspaces / monorepo root.
|
|
23
29
|
* Used as an upper bound for walk-up (config at this root is accepted; parents are not).
|
|
24
30
|
* @param {string} dir
|
|
25
31
|
*/
|
|
26
|
-
|
|
32
|
+
function isWorkspacesPackageRoot(dir) {
|
|
27
33
|
const pkgPath = path.join(dir, 'package.json');
|
|
28
34
|
try {
|
|
29
35
|
if (!fs.statSync(pkgPath, { throwIfNoEntry: false })?.isFile()) {
|
|
@@ -55,7 +61,7 @@ export function isWorkspacesPackageRoot(dir) {
|
|
|
55
61
|
* True when dir is a git worktree root (.git file or directory).
|
|
56
62
|
* @param {string} dir
|
|
57
63
|
*/
|
|
58
|
-
|
|
64
|
+
function isGitRoot(dir) {
|
|
59
65
|
try {
|
|
60
66
|
const git = path.join(dir, '.git');
|
|
61
67
|
const st = fs.statSync(git, { throwIfNoEntry: false });
|
|
@@ -97,18 +103,13 @@ function isFile(absPath) {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
/** True when --config is a path (nested or absolute), not a basename to walk. */
|
|
100
|
-
|
|
106
|
+
function configNameIsPath(configName) {
|
|
101
107
|
return (
|
|
102
108
|
typeof configName === 'string' &&
|
|
103
109
|
(path.isAbsolute(configName) || /[\\/]/.test(configName))
|
|
104
110
|
);
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
function isInsideRoot(root, target) {
|
|
108
|
-
const rel = path.relative(root, target);
|
|
109
|
-
return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
113
|
/**
|
|
113
114
|
* Resolve a nested/absolute --config path without walking parents.
|
|
114
115
|
* `--root examples/app --config examples/app/ark.config.json` must load the nested
|
|
@@ -118,7 +119,7 @@ function isInsideRoot(root, target) {
|
|
|
118
119
|
* @param {string} configName
|
|
119
120
|
* @returns {string | null} absolute config file path
|
|
120
121
|
*/
|
|
121
|
-
|
|
122
|
+
function resolveConfigPathCandidate(startDir, configName) {
|
|
122
123
|
if (typeof configName !== 'string' || configName.trim() === '') return null;
|
|
123
124
|
const start = path.resolve(startDir || process.cwd());
|
|
124
125
|
if (path.isAbsolute(configName)) return isFile(configName) ? path.resolve(configName) : null;
|
|
@@ -145,7 +146,8 @@ export function resolveConfigPathCandidate(startDir, configName) {
|
|
|
145
146
|
* Walk parents from startDir looking for configName (default ark.config.json).
|
|
146
147
|
* Bounds: filesystem root, max depth, git root, workspaces package root.
|
|
147
148
|
* Config found at a bound root is accepted; walking above a bound is refused.
|
|
148
|
-
*
|
|
149
|
+
* An explicit --config path is a file. The returned root stays `startDir`.
|
|
150
|
+
* Basename walk-up (`ark.config.json`) may still adopt an ancestor.
|
|
149
151
|
*
|
|
150
152
|
* @param {string} startDir
|
|
151
153
|
* @param {string} [configName='ark.config.json']
|
|
@@ -157,26 +159,12 @@ export function findNearestArkConfig(startDir, configName = 'ark.config.json', o
|
|
|
157
159
|
const boundAtGit = opts.boundAtGitRoot !== false;
|
|
158
160
|
const boundAtWorkspaces = opts.boundAtWorkspacesRoot !== false;
|
|
159
161
|
|
|
160
|
-
if (typeof configName === 'string' && path.isAbsolute(configName)) {
|
|
161
|
-
if (isFile(configName)) {
|
|
162
|
-
const root = path.dirname(configName);
|
|
163
|
-
const start = path.resolve(startDir || process.cwd());
|
|
164
|
-
return { root, configPath: path.resolve(configName), walkedUp: path.resolve(root) !== start };
|
|
165
|
-
}
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
162
|
const name = configName || 'ark.config.json';
|
|
170
163
|
if (configNameIsPath(name)) {
|
|
171
164
|
const resolved = resolveConfigPathCandidate(startDir, name);
|
|
172
165
|
if (!resolved) return null;
|
|
173
166
|
const start = path.resolve(startDir || process.cwd());
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
root: inside ? start : path.dirname(resolved),
|
|
177
|
-
configPath: resolved,
|
|
178
|
-
walkedUp: !inside,
|
|
179
|
-
};
|
|
167
|
+
return { root: start, configPath: resolved, walkedUp: false };
|
|
180
168
|
}
|
|
181
169
|
|
|
182
170
|
let dir = path.resolve(startDir || process.cwd());
|
|
@@ -213,9 +201,10 @@ export function findNearestArkConfig(startDir, configName = 'ark.config.json', o
|
|
|
213
201
|
* Resolve config discovery vs write roots for CLI invocation.
|
|
214
202
|
*
|
|
215
203
|
* - If config exists at startRoot → use startRoot for both.
|
|
216
|
-
* -
|
|
204
|
+
* - An explicit --config path keeps `root` at startRoot. The file's directory is not a fallback.
|
|
205
|
+
* - Else walk parents for a basename config (bounded) → configRoot may differ from writeRoot.
|
|
217
206
|
* - writeMode without followConfigRoot: keep writeRoot/start as `root` (do not rewrite parent).
|
|
218
|
-
* - writeMode + followConfigRoot (or read mode): adopt
|
|
207
|
+
* - writeMode + followConfigRoot (or read mode): adopt a basename walk-up root as `root`.
|
|
219
208
|
*
|
|
220
209
|
* @param {string} startRoot
|
|
221
210
|
* @param {{
|
|
@@ -243,76 +232,45 @@ export function resolveEffectiveProjectRoot(startRoot, opts = {}) {
|
|
|
243
232
|
// Writes adopt walked config root only with explicit opt-in.
|
|
244
233
|
const adoptWalkedRoot = !writeMode || followConfigRoot;
|
|
245
234
|
|
|
235
|
+
/** Config found by walk-up (or at start); root follows it unless a plain write. */
|
|
236
|
+
const walkedResult = (found) => ({
|
|
237
|
+
root: adoptWalkedRoot ? found.root : start,
|
|
238
|
+
writeRoot: start,
|
|
239
|
+
config: configName,
|
|
240
|
+
configPath: found.configPath,
|
|
241
|
+
configRoot: found.root,
|
|
242
|
+
walkedUp: found.walkedUp,
|
|
243
|
+
configFound: true,
|
|
244
|
+
writeRootFollowedConfig: adoptWalkedRoot && found.walkedUp,
|
|
245
|
+
});
|
|
246
|
+
/** Everything stays at the start directory. */
|
|
247
|
+
const startResult = (configPath, configFound) => ({
|
|
248
|
+
root: start,
|
|
249
|
+
writeRoot: start,
|
|
250
|
+
config: configName,
|
|
251
|
+
configPath,
|
|
252
|
+
configRoot: start,
|
|
253
|
+
walkedUp: false,
|
|
254
|
+
configFound,
|
|
255
|
+
writeRootFollowedConfig: false,
|
|
256
|
+
});
|
|
257
|
+
|
|
246
258
|
if (typeof configName === 'string' && path.isAbsolute(configName)) {
|
|
247
259
|
const found = findNearestArkConfig(start, configName, opts);
|
|
248
|
-
|
|
249
|
-
const root = adoptWalkedRoot ? found.root : start;
|
|
250
|
-
return {
|
|
251
|
-
root,
|
|
252
|
-
writeRoot: start,
|
|
253
|
-
config: configName,
|
|
254
|
-
configPath: found.configPath,
|
|
255
|
-
configRoot: found.root,
|
|
256
|
-
walkedUp: found.walkedUp,
|
|
257
|
-
configFound: true,
|
|
258
|
-
writeRootFollowedConfig: adoptWalkedRoot && found.walkedUp,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
return {
|
|
262
|
-
root: start,
|
|
263
|
-
writeRoot: start,
|
|
264
|
-
config: configName,
|
|
265
|
-
configPath: configName,
|
|
266
|
-
configRoot: start,
|
|
267
|
-
walkedUp: false,
|
|
268
|
-
configFound: false,
|
|
269
|
-
writeRootFollowedConfig: false,
|
|
270
|
-
};
|
|
260
|
+
return found ? walkedResult(found) : startResult(configName, false);
|
|
271
261
|
}
|
|
272
262
|
|
|
273
263
|
const localPath = path.join(start, configName);
|
|
274
264
|
try {
|
|
275
265
|
if (fs.statSync(localPath, { throwIfNoEntry: false })?.isFile()) {
|
|
276
|
-
return
|
|
277
|
-
root: start,
|
|
278
|
-
writeRoot: start,
|
|
279
|
-
config: configName,
|
|
280
|
-
configPath: localPath,
|
|
281
|
-
configRoot: start,
|
|
282
|
-
walkedUp: false,
|
|
283
|
-
configFound: true,
|
|
284
|
-
writeRootFollowedConfig: false,
|
|
285
|
-
};
|
|
266
|
+
return startResult(localPath, true);
|
|
286
267
|
}
|
|
287
268
|
} catch {
|
|
288
269
|
// fall through to walk-up
|
|
289
270
|
}
|
|
290
271
|
|
|
291
272
|
const found = findNearestArkConfig(start, configName, opts);
|
|
292
|
-
|
|
293
|
-
const root = adoptWalkedRoot ? found.root : start;
|
|
294
|
-
return {
|
|
295
|
-
root,
|
|
296
|
-
writeRoot: start,
|
|
297
|
-
config: configName,
|
|
298
|
-
configPath: found.configPath,
|
|
299
|
-
configRoot: found.root,
|
|
300
|
-
walkedUp: found.walkedUp,
|
|
301
|
-
configFound: true,
|
|
302
|
-
writeRootFollowedConfig: adoptWalkedRoot && found.walkedUp,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
root: start,
|
|
308
|
-
writeRoot: start,
|
|
309
|
-
config: configName,
|
|
310
|
-
configPath: localPath,
|
|
311
|
-
configRoot: start,
|
|
312
|
-
walkedUp: false,
|
|
313
|
-
configFound: false,
|
|
314
|
-
writeRootFollowedConfig: false,
|
|
315
|
-
};
|
|
273
|
+
return found ? walkedResult(found) : startResult(localPath, false);
|
|
316
274
|
}
|
|
317
275
|
|
|
318
276
|
/**
|