arkgate 4.8.24 → 4.8.25
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 +85 -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 +55 -8
- 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 +31 -62
- 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-AHACEKSV.js +16 -0
- package/dist/chunk-UN5EH4QV.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 +2 -1
- package/docs/agent-guide.md +134 -2
- package/docs/ai-gates.md +15 -1
- package/docs/configuration.md +40 -11
- package/docs/diagnostics.md +118 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,78 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.8.25 — 2026-10-05
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.8.24**. Weekly accumulate (#342, #344, #345, #346, #347, #348, #349, #350). **Write. Check. Ship.** **No required config migration.** `childSlices.arkRulesFile` and pinned `sliceAliases` need 4.8.25. No `schemaVersion` bump. Does not close `K01` / `Z09`. This mother `ark.config.json` still does **not** turn `arkOrder` on.
|
|
11
|
+
|
|
12
|
+
**Status: released** (tag `v4.8.25`). Run `npm view arkgate@4.8.25 version` to confirm (#314).
|
|
13
|
+
|
|
8
14
|
### Added
|
|
15
|
+
- Per-slice ArkRules file `childSlices.arkRulesFile` and pinned `sliceAliases` (#342, #341).
|
|
16
|
+
- `arkgate-check --probe-invariants[=<id>]` checks that the tests covering an
|
|
17
|
+
invariant actually pin it. A test title that names the rule proves the test
|
|
18
|
+
exists, not that it would fail if the rule broke. For each invariant with a
|
|
19
|
+
`coverage.symbol`, the probe copies the project to a temporary folder and
|
|
20
|
+
runs only the covering tests (titles that name the id, and tests that import
|
|
21
|
+
the symbol's file) with your own runner — vitest, jest or `node --test`:
|
|
22
|
+
unchanged, then with the file throwing on load, then with the symbol
|
|
23
|
+
throwing when called, then with at most three small changes inside it
|
|
24
|
+
(negate a guard, drop a `throw`, flip or shift a comparison, shift a
|
|
25
|
+
constant). Each invariant reads `killed`, `survived`, `not-reached`,
|
|
26
|
+
`inconclusive` or `unprobeable`, with the change that went unnoticed and the
|
|
27
|
+
line. Your files are never changed. Report by default; `--write` saves
|
|
28
|
+
`.ark/invariant-probe.json` (new schema export `arkgate/schema/invariant-probe`).
|
|
29
|
+
Exit 0 when nothing survived, 1 when something did, 2 when it could not run.
|
|
30
|
+
Not a score.
|
|
31
|
+
- Promotion reads the committed probe result. When `.ark/invariant-probe.json`
|
|
32
|
+
has a fresh `survived` or `not-reached` row — the symbol file, its covering
|
|
33
|
+
tests and the invariant unchanged since the run — `--promote` and the policy
|
|
34
|
+
delta refuse to promote that invariant to `enforced` (blocker
|
|
35
|
+
`probe-survived`). A stale, `killed`, `inconclusive` or missing result
|
|
36
|
+
changes nothing, so every promotion without the file behaves as before.
|
|
37
|
+
Status and `--rules-inventory` show the probe state per invariant when the
|
|
38
|
+
file exists; new catalog id `INVARIANT_PROBE_SURVIVED` (advisory, never in
|
|
39
|
+
the check). The probe runs only when you start it: never from the write
|
|
40
|
+
hook, MCP, ESLint, the GitHub Action or `--strict-merge`, and it refuses to
|
|
41
|
+
combine with them. The runner gets a short environment allowlist (no
|
|
42
|
+
tokens); that is best effort, not a sandbox. No config key, no new skill;
|
|
43
|
+
`/ark-contract` and `/ark-coverage` explain the blocker and the next test to
|
|
44
|
+
write.
|
|
45
|
+
- Status lists code copied across a wall. When a slice wall denies an import,
|
|
46
|
+
the cheapest way around it is a copy, and a copy is not an import.
|
|
47
|
+
`--doctor --all`, the HTML report and `--doctor --all --json` (under
|
|
48
|
+
`doctor.crossWallDuplication`) now list near-identical code whose copies sit
|
|
49
|
+
on two sides of a slice wall, in two child slices of one universe, or in two
|
|
50
|
+
layers, with a place the shared code could live (the declared shared root,
|
|
51
|
+
the universe common folder, or the lower layer both sides may import).
|
|
52
|
+
Copies inside one slice are counted, never listed. It reads token
|
|
53
|
+
fingerprints taken while status runs, with the TypeScript ArkGate already
|
|
54
|
+
loaded; they never enter the check, so the verdict and `factsHash` do not
|
|
55
|
+
change. Tests, `.d.ts`, generated files and files over 256 KB are skipped.
|
|
56
|
+
Fixed thresholds (at least 50 tokens, 5 lines, most names matching), no
|
|
57
|
+
config key. Compact status never runs it; its JSON says `not-run` and names
|
|
58
|
+
the command. When a cap stops the pass the section says `partial` and how
|
|
59
|
+
much was left. New catalog ids `CROSS_WALL_DUPLICATE` and
|
|
60
|
+
`CROSS_LAYER_DUPLICATE` (advisory). Never a score, never in the write hook,
|
|
61
|
+
MCP, ESLint, `--changed`, or `--strict-merge`. No new skill; `/ark-place`
|
|
62
|
+
names the destination and `/ark-fix` moves one copy at a time.
|
|
63
|
+
- Status lists the files nothing imports. A governed file with no importer —
|
|
64
|
+
no governed file, test, project script, or known entry point — shows in
|
|
65
|
+
`--doctor --all`, the HTML report, and `--doctor --json` under
|
|
66
|
+
`doctor.orphanModules`; compact status prints one count line at most. It
|
|
67
|
+
reads the import edges the check already resolved, so the verdict and
|
|
68
|
+
`factsHash` do not change. Entry points come from `package.json` (built
|
|
69
|
+
`dist/` paths map back to source), CI workflow run steps, Next / Vite / Nest /
|
|
70
|
+
Vercel / Storybook conventions, the arkRun / arkOrder roots, config and setup
|
|
71
|
+
files, ambient declaration files, and an optional `.ark/entry-points.json`
|
|
72
|
+
(glob + reason + optional `reviewBy`; not a config key). Files only tests
|
|
73
|
+
import are counted, not listed. When a dynamic import, `import.meta.glob`, a
|
|
74
|
+
quoted path, or an unresolved import may still reach a file, the item says
|
|
75
|
+
so and the list is `partial`. `--doctor --all` and `--report` also list
|
|
76
|
+
exports nothing imports by name. New catalog ids `ORPHAN_MODULE` and
|
|
77
|
+
`UNUSED_EXPORT` (advisory). Never a score, never in the write hook, MCP
|
|
78
|
+
write tools, ESLint, or `--strict-merge`. No new skill; `/ark-explore` maps
|
|
79
|
+
the list and `/ark-fix` deletes one file at a time through the write gate.
|
|
9
80
|
- Soft doctor residual when Domain is declared but empty and the UI holds the
|
|
10
81
|
rules (`noDomainFrontend`). Projects empty Domain + presentation share, or
|
|
11
82
|
the existing `domain-logic-in-ui` smell. Friendly next step: one Domain file
|
|
@@ -48,6 +119,10 @@ in the immutable pre-2.0 archive linked below.
|
|
|
48
119
|
short phrase templates so the shape is visible. No new skill, schema, or flag.
|
|
49
120
|
|
|
50
121
|
### Changed
|
|
122
|
+
- `brace-expansion` is overridden to 5.0.12 (Dependabot medium, quadratic expansion) (#350).
|
|
123
|
+
- Eval fixtures pin Next.js 16.3.6 (#346).
|
|
124
|
+
- The package no longer ships `bin/lib/ark-order-invariants.mjs`, `bin/lib/ark-order-error.mjs`, or `bin/lib/stable-hash.mjs`; no CLI loaded them. ArkOrder invariants still ship in `arkgate/order`. One new generated CLI module, `bin/lib/schema-validation.mjs`, holds the JSON-Schema walker shared by the config and ArkRules contracts.
|
|
125
|
+
- Internal cleanup with no public export or behavior change: about 390 unused internal exports removed (knip) and hand-written copy/paste duplication cut from 1.01% to 0% (jscpd). Maintainers: `npm run check:knip` and `npm run check:duplication` run in the CI build job.
|
|
51
126
|
- Compact `--doctor` names ArkRules only when the `arkRules` map is on
|
|
52
127
|
(one breath + counts, not a score). Absence stays silent. Reuses
|
|
53
128
|
`rulesUnderContract` — no new schema, flag, or skill. `--doctor --all`
|
|
@@ -66,6 +141,16 @@ in the immutable pre-2.0 archive linked below.
|
|
|
66
141
|
Required CI is still the shared merge line. No new skill, schema, or host.
|
|
67
142
|
|
|
68
143
|
### Fixed
|
|
144
|
+
- `writes-via-aggregate` flags tagged-template SQL writes (`UPDATE`, `MERGE`, `TRUNCATE`, including `ONLY`, quoted names, and interpolated tables). Comments, `FOR UPDATE`, and `DO UPDATE SET` stay silent (#344, #343).
|
|
145
|
+
- `--policy-base` and MCP policy-delta now judge an advisory → enforced
|
|
146
|
+
invariant promotion with the declared `coverage.coverageRoots`, the same way
|
|
147
|
+
`--promote` does. Before, a covered promotion with roots declared came out
|
|
148
|
+
`judgment-required` ("Declare coverage.coverageRoots…") while `--promote`
|
|
149
|
+
allowed it.
|
|
150
|
+
- The flat-parent suggestion in status reads the resolved importer index
|
|
151
|
+
instead of re-reading every file for import text.
|
|
152
|
+
- Doctor sees CI jobs gated by `needs:`: a job that runs the fail-closed Ark check behind unconditional jobs (such as a CI-profile job) now counts as a merge gate; `ciMergeBoundary.ci.workflowPresent` / `merge-gate` no longer report it absent. Jobs a profile output can skip, jobs with `continue-on-error`, and chains with a conditional job still do not count.
|
|
153
|
+
- With `ARK_DOCTOR_GITHUB=1`, the Ark check counts as required only when every job that can skip it is required too (a skipped job satisfies branch protection); otherwise the doctor reports `unverified`, `arkCheckUpstreamNotRequired`, and the gap `enforcement-ark-check-upstream-not-required`. Required checks bound to the GitHub Actions app now correlate (`arkCheckSourceBound: true`).
|
|
69
154
|
- Write hook and `ark-check` now agree on overlapping layer globs: the hook
|
|
70
155
|
probes the same specifier extensions as `ark-check` and classifies with
|
|
71
156
|
`layerForRelativePath` (explicit `money.ts` beats `src/lib/**`). Field
|
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ Design the boundary before you implement. Put a small interface in front of the
|
|
|
25
25
|
|
|
26
26
|
## Try it
|
|
27
27
|
|
|
28
|
-
One command. Preview only — no files change. Current release is **4.8.
|
|
28
|
+
One command. Preview only — no files change. Current release is **4.8.25**
|
|
29
29
|
(check npm `latest` with `npm view arkgate version`):
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npx arkgate@4.8.
|
|
32
|
+
npx arkgate@4.8.25 start
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## What a red result looks like
|
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
printViolationBreakdown,
|
|
78
78
|
printHumanWarnings,
|
|
79
79
|
CONCENTRATION_MIN_VIOLATIONS,
|
|
80
|
+
color,
|
|
80
81
|
} from './lib/violations.mjs';
|
|
81
82
|
import {
|
|
82
83
|
detectBestFitModel,
|
|
@@ -1033,15 +1034,6 @@ function readManifest(root, manifestPath) {
|
|
|
1033
1034
|
return readJson(fullPath);
|
|
1034
1035
|
}
|
|
1035
1036
|
|
|
1036
|
-
const useColor = process.stderr.isTTY && !process.env.NO_COLOR;
|
|
1037
|
-
const color = {
|
|
1038
|
-
red: (s) => (useColor ? `\x1b[31m${s}\x1b[0m` : s),
|
|
1039
|
-
yellow: (s) => (useColor ? `\x1b[33m${s}\x1b[0m` : s),
|
|
1040
|
-
green: (s) => (useColor ? `\x1b[32m${s}\x1b[0m` : s),
|
|
1041
|
-
dim: (s) => (useColor ? `\x1b[2m${s}\x1b[0m` : s),
|
|
1042
|
-
bold: (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s),
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
1037
|
/**
|
|
1046
1038
|
* Monorepo honesty: when cwd/--root has no ark.config.json, walk parents (bounded).
|
|
1047
1039
|
* Stamps configRoot / configWalkedUp. Mutates args.root only for read paths, or
|
|
@@ -1147,6 +1139,14 @@ async function main() {
|
|
|
1147
1139
|
return;
|
|
1148
1140
|
}
|
|
1149
1141
|
|
|
1142
|
+
if (args.probeInvariants) {
|
|
1143
|
+
// ADR 0039: owner-invoked, outside the gate path. Loaded only here; parseArgs
|
|
1144
|
+
// refuses every pairing with --strict-merge, --changed, --local or --doctor.
|
|
1145
|
+
const { runProbeInvariants } = await import('./lib/invariant-probe-cli.mjs');
|
|
1146
|
+
await runProbeInvariants(args, readConfig);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
1150
|
if (args.sensors) {
|
|
1151
1151
|
const { runSensors } = await import('./lib/sensor-promote-cli.mjs');
|
|
1152
1152
|
await withSensorsPartialModeHonesty(args, () => runSensors(args, readConfig));
|
|
@@ -1437,7 +1437,7 @@ async function main() {
|
|
|
1437
1437
|
|
|
1438
1438
|
const {
|
|
1439
1439
|
violations: rawViolations, warnings, safety, parseHealth, completeness, completenessReasons,
|
|
1440
|
-
mode, policyHash, resolverIdentity, factsHash, candidateTreeHash,
|
|
1440
|
+
mode, policyHash, resolverIdentity, factsHash, candidateTreeHash, importGraph,
|
|
1441
1441
|
} = runArchitectureScan({
|
|
1442
1442
|
root,
|
|
1443
1443
|
config,
|
|
@@ -1449,6 +1449,8 @@ async function main() {
|
|
|
1449
1449
|
files: args.changed ? files : [],
|
|
1450
1450
|
ts,
|
|
1451
1451
|
args,
|
|
1452
|
+
// ADR 0037: doctor / report read a compact importer index, never the verdict path.
|
|
1453
|
+
graphProjection: Boolean(args.doctor || args.report) && !args.changed,
|
|
1452
1454
|
});
|
|
1453
1455
|
|
|
1454
1456
|
if (args.changed) pruneScopedPatternWarnings(warnings, root, loadGovernedFiles);
|
|
@@ -1508,8 +1510,8 @@ async function main() {
|
|
|
1508
1510
|
configWalkedUp: args.configWalkedUp === true,
|
|
1509
1511
|
safety, designDelta,
|
|
1510
1512
|
ts, parseHealth, completeness,
|
|
1511
|
-
all: args.all === true, requireGates: args.requireGates === true,
|
|
1512
|
-
warnings,
|
|
1513
|
+
all: args.all === true, changed: args.changed === true, requireGates: args.requireGates === true,
|
|
1514
|
+
warnings, architectureFacts: importGraph ? { importGraph } : undefined,
|
|
1513
1515
|
});
|
|
1514
1516
|
if (designDelta) process.exitCode = !designDelta.complete ? 2 : designDelta.valid ? 0 : 1; return;
|
|
1515
1517
|
}
|
|
@@ -1790,8 +1792,9 @@ async function main() {
|
|
|
1790
1792
|
files,
|
|
1791
1793
|
ts,
|
|
1792
1794
|
parseHealth,
|
|
1793
|
-
undefined,
|
|
1794
|
-
activeViolations
|
|
1795
|
+
importGraph ? { importGraph } : undefined,
|
|
1796
|
+
activeViolations,
|
|
1797
|
+
{ details: true, changed: args.changed === true }
|
|
1795
1798
|
),
|
|
1796
1799
|
// Doctor parity: always emit improvement compass when doctor would (reportParity).
|
|
1797
1800
|
...(designDepth?.improvementCompass
|
package/bin/ark-layer-match.mjs
CHANGED
|
@@ -132,7 +132,7 @@ export function globToRegExp(pattern) {
|
|
|
132
132
|
* Used for path-anchored ranking so a domain folder glob can beat a broad
|
|
133
133
|
* Application bag like src/lib when the file actually sits under domain/.
|
|
134
134
|
*/
|
|
135
|
-
|
|
135
|
+
function concreteGlobSegments(pattern) {
|
|
136
136
|
const glob = normalizeGlobSeparators(String(pattern));
|
|
137
137
|
return glob
|
|
138
138
|
.split('/')
|
|
@@ -393,7 +393,7 @@ function bindAnchoredSlice(parts, pattern, offset, identity = 'path') {
|
|
|
393
393
|
* literal, and everything after it. Literals before the last literal drop.
|
|
394
394
|
* Returns null when the pattern has no literal.
|
|
395
395
|
*/
|
|
396
|
-
|
|
396
|
+
function starsKeptSegmentIndexes(segments) {
|
|
397
397
|
let lastLiteral = -1;
|
|
398
398
|
for (let at = 0; at < segments.length; at += 1) {
|
|
399
399
|
if (segments[at] !== '*')
|
|
@@ -681,6 +681,7 @@ export function peerIsolationMustDeny(input) {
|
|
|
681
681
|
return peerIsolationDecision(input).denied;
|
|
682
682
|
}
|
|
683
683
|
export const SLICE_ALIAS_DEBT = 'Aliases are an owed move, not a destination. These files are not finished.';
|
|
684
|
+
const PINNED_ALIAS_NOTE = 'Pinned aliases stay where they are. They are not an owed move.';
|
|
684
685
|
function aliasGlobPattern(glob) {
|
|
685
686
|
return trimTrailingSlashes(glob.trim().replace(/\\/g, '/')).toLowerCase();
|
|
686
687
|
}
|
|
@@ -781,7 +782,7 @@ function literalsMatch(shape, parts) {
|
|
|
781
782
|
* New-id shapes (`*` for a star) that a 4.8.23 stars id maps to. Empty when the id is
|
|
782
783
|
* already a new id or no shape takes it. Config load rejects more than one.
|
|
783
784
|
*/
|
|
784
|
-
|
|
785
|
+
function legacyStarsIdTargets(folders, id) {
|
|
785
786
|
const parts = id.split('/').filter(Boolean).map((part) => part.toLowerCase());
|
|
786
787
|
const shapes = starsUniverseShapes(folders);
|
|
787
788
|
if (parts.length === 0 || shapes.some((shape) => literalsMatch(shape.literals, parts)))
|
|
@@ -937,11 +938,93 @@ export function sliceAliasDestination(file, childId, child) {
|
|
|
937
938
|
export function anySliceAlias(rules) {
|
|
938
939
|
return (rules ?? []).some((rule) => (rule?.childSlices?.sliceAliases?.length ?? 0) > 0);
|
|
939
940
|
}
|
|
941
|
+
/** True when an alias is still an owed move. `pinned: true` does not count. A reason alone does. */
|
|
942
|
+
export function sliceAliasOwesMove(rules) {
|
|
943
|
+
return (rules ?? []).some((rule) => (rule?.childSlices?.sliceAliases ?? []).some((alias) => alias?.pinned !== true));
|
|
944
|
+
}
|
|
945
|
+
const SLICE_RULE_LAYER_TOKEN = '<Layer>';
|
|
946
|
+
function sliceRuleFileName(pattern, layer) {
|
|
947
|
+
const token = SLICE_RULE_LAYER_TOKEN;
|
|
948
|
+
if (!pattern.includes(token) || pattern.split(token).length !== 2)
|
|
949
|
+
return null;
|
|
950
|
+
if (/[*\\/]/.test(pattern))
|
|
951
|
+
return null;
|
|
952
|
+
const name = pattern.replace(token, layer);
|
|
953
|
+
if (!name || name.includes('..'))
|
|
954
|
+
return null;
|
|
955
|
+
return name;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Child-slice ArkRules files this wall already named.
|
|
959
|
+
* Roots come from the governed file index. The pattern is a filename, not a search.
|
|
960
|
+
*/
|
|
961
|
+
export function resolveSliceRulePlan(input) {
|
|
962
|
+
const layers = [...new Set(input.layers.filter((name) => typeof name === 'string' && name.length > 0))].sort();
|
|
963
|
+
const entries = [];
|
|
964
|
+
const seen = new Set();
|
|
965
|
+
for (const rule of input.rules ?? []) {
|
|
966
|
+
const child = rule?.childSlices;
|
|
967
|
+
const pattern = child?.arkRulesFile;
|
|
968
|
+
if (!child || typeof pattern !== 'string')
|
|
969
|
+
continue;
|
|
970
|
+
const roots = new Map();
|
|
971
|
+
for (const file of input.files) {
|
|
972
|
+
if (typeof file !== 'string' || file.length === 0)
|
|
973
|
+
continue;
|
|
974
|
+
const universeId = rule.sliceFolders?.length
|
|
975
|
+
? sliceIdForPath(file, rule.sliceFolders, rule.sliceIdentity)
|
|
976
|
+
: undefined;
|
|
977
|
+
const resolved = resolveChildSliceId(file, universeId, child);
|
|
978
|
+
if (!resolved.childId || roots.has(resolved.childId))
|
|
979
|
+
continue;
|
|
980
|
+
const root = sliceAliasDestination(file, resolved.childId, child);
|
|
981
|
+
if (root)
|
|
982
|
+
roots.set(resolved.childId, root);
|
|
983
|
+
}
|
|
984
|
+
for (const [childId, sliceRoot] of [...roots.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
985
|
+
for (const layer of layers) {
|
|
986
|
+
const name = sliceRuleFileName(pattern, layer);
|
|
987
|
+
if (!name)
|
|
988
|
+
continue;
|
|
989
|
+
const rel = `${sliceRoot}/${name}`.replace(/\\/g, '/');
|
|
990
|
+
if (seen.has(`${layer}\0${rel}`))
|
|
991
|
+
continue;
|
|
992
|
+
seen.add(`${layer}\0${rel}`);
|
|
993
|
+
entries.push({
|
|
994
|
+
path: rel,
|
|
995
|
+
childId,
|
|
996
|
+
layer,
|
|
997
|
+
defaultAppliesTo: [`${sliceRoot}/**`],
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return entries;
|
|
1003
|
+
}
|
|
1004
|
+
/** Ids from the effective catalog that belong to the child slice of this file. */
|
|
1005
|
+
export function sliceRuleIdsForFile(filePath, rules, catalog) {
|
|
1006
|
+
const childIds = new Set();
|
|
1007
|
+
for (const rule of rules ?? []) {
|
|
1008
|
+
if (!rule?.childSlices)
|
|
1009
|
+
continue;
|
|
1010
|
+
const universeId = rule.sliceFolders?.length
|
|
1011
|
+
? sliceIdForPath(filePath, rule.sliceFolders, rule.sliceIdentity)
|
|
1012
|
+
: undefined;
|
|
1013
|
+
const resolved = resolveChildSliceId(filePath, universeId, rule.childSlices);
|
|
1014
|
+
if (resolved.childId)
|
|
1015
|
+
childIds.add(resolved.childId);
|
|
1016
|
+
}
|
|
1017
|
+
return catalog
|
|
1018
|
+
.filter((row) => typeof row.childId === 'string' && childIds.has(row.childId))
|
|
1019
|
+
.map((row) => row.id)
|
|
1020
|
+
.sort();
|
|
1021
|
+
}
|
|
940
1022
|
/** Doctor list. Absent when no rule sets sliceAliases, so the key stays off. */
|
|
941
1023
|
export function sliceAliasReport(rules, files) {
|
|
942
1024
|
if (!anySliceAlias(rules))
|
|
943
1025
|
return null;
|
|
944
1026
|
const moves = new Map();
|
|
1027
|
+
const pinned = new Map();
|
|
945
1028
|
for (const rule of rules ?? []) {
|
|
946
1029
|
const child = rule?.childSlices;
|
|
947
1030
|
if (!child?.sliceAliases)
|
|
@@ -950,10 +1033,19 @@ export function sliceAliasReport(rules, files) {
|
|
|
950
1033
|
if (!alias || typeof alias.from !== 'string' || typeof alias.to !== 'string')
|
|
951
1034
|
continue;
|
|
952
1035
|
const key = `${aliasGlobPattern(alias.from)}\0${aliasGlobPattern(alias.to)}`;
|
|
953
|
-
|
|
1036
|
+
const bucket = alias.pinned === true ? pinned : moves;
|
|
1037
|
+
let move = bucket.get(key);
|
|
954
1038
|
if (!move) {
|
|
955
|
-
move = {
|
|
956
|
-
|
|
1039
|
+
move = {
|
|
1040
|
+
from: alias.from,
|
|
1041
|
+
to: alias.to,
|
|
1042
|
+
destination: '',
|
|
1043
|
+
files: [],
|
|
1044
|
+
...(alias.pinned === true && typeof alias.reason === 'string' && alias.reason.length > 0
|
|
1045
|
+
? { reason: alias.reason }
|
|
1046
|
+
: {}),
|
|
1047
|
+
};
|
|
1048
|
+
bucket.set(key, move);
|
|
957
1049
|
}
|
|
958
1050
|
const seen = new Set(move.files);
|
|
959
1051
|
for (const file of files) {
|
|
@@ -967,7 +1059,7 @@ export function sliceAliasReport(rules, files) {
|
|
|
967
1059
|
}
|
|
968
1060
|
}
|
|
969
1061
|
const universes = knownUniverseIds(rules, files);
|
|
970
|
-
const
|
|
1062
|
+
const decorate = (move) => {
|
|
971
1063
|
const target = splitAliasTarget(move.to);
|
|
972
1064
|
const unknown = universes !== null && target !== null && !universes.has(target.universeId);
|
|
973
1065
|
return {
|
|
@@ -980,9 +1072,19 @@ export function sliceAliasReport(rules, files) {
|
|
|
980
1072
|
}
|
|
981
1073
|
: {}),
|
|
982
1074
|
};
|
|
983
|
-
}
|
|
984
|
-
listed.
|
|
985
|
-
|
|
1075
|
+
};
|
|
1076
|
+
const listed = [...moves.values()].map((move) => decorate(move));
|
|
1077
|
+
const pinnedListed = [...pinned.values()].map((move) => decorate(move));
|
|
1078
|
+
const byAlias = (left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to);
|
|
1079
|
+
listed.sort(byAlias);
|
|
1080
|
+
pinnedListed.sort(byAlias);
|
|
1081
|
+
return {
|
|
1082
|
+
notAScore: true,
|
|
1083
|
+
finished: false,
|
|
1084
|
+
debt: listed.length > 0 ? SLICE_ALIAS_DEBT : PINNED_ALIAS_NOTE,
|
|
1085
|
+
moves: listed,
|
|
1086
|
+
...(pinnedListed.length > 0 ? { pinned: pinnedListed } : {}),
|
|
1087
|
+
};
|
|
986
1088
|
}
|
|
987
1089
|
/**
|
|
988
1090
|
* Universe ids that real (non-aliased) files resolve to, across rules that set
|
|
@@ -1062,7 +1164,7 @@ function isCommonFolderName(segment, commonFolders) {
|
|
|
1062
1164
|
* (`features/projects/rfi/domain/**`) belongs to that child. A child id that
|
|
1063
1165
|
* does not extend the universe id is not used; the caller warns.
|
|
1064
1166
|
*/
|
|
1065
|
-
|
|
1167
|
+
function resolveChildSliceId(relPath, universeId, child) {
|
|
1066
1168
|
if (!relPath || !child?.sliceFolders?.length)
|
|
1067
1169
|
return {};
|
|
1068
1170
|
const raw = sliceIdForPath(relPath, child.sliceFolders, child.sliceIdentity);
|
|
@@ -1123,7 +1225,7 @@ export function importerInEnforcedSubtree(fromPath, fromChild, entries) {
|
|
|
1123
1225
|
* value deny every one. An object denies the importer only when it is listed,
|
|
1124
1226
|
* unless `default` is already `deny`.
|
|
1125
1227
|
*/
|
|
1126
|
-
|
|
1228
|
+
function siblingCrossingAdvisory(siblings, fromPath, fromChild) {
|
|
1127
1229
|
if (siblings == null || siblings === 'deny')
|
|
1128
1230
|
return false;
|
|
1129
1231
|
if (siblings === 'advisory')
|
|
@@ -1206,7 +1308,7 @@ function childAllowanceSpansUniverses(from, to) {
|
|
|
1206
1308
|
* Universe wall, then the child wall. An allow from the child wall never
|
|
1207
1309
|
* overturns a universe deny: that deny returns before the child wall runs.
|
|
1208
1310
|
*/
|
|
1209
|
-
|
|
1311
|
+
function evaluateNestedSliceWall(input) {
|
|
1210
1312
|
const universe = peerIsolationDecision({
|
|
1211
1313
|
fromPath: input.fromPath,
|
|
1212
1314
|
toPath: input.toPath,
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import { createImportTargetResolver } from './lib/import-resolve.mjs';
|
|
|
28
28
|
import { validateWithAutoPatch, resolveImportFileAbs } from './lib/auto-patch.mjs';
|
|
29
29
|
import { composePrepareWrite } from './lib/prepare-write.mjs';
|
|
30
30
|
import { placementDescriptionFields } from './lib/layer-description.mjs';
|
|
31
|
+
import { sliceRuleIdsForFile } from './ark-layer-match.mjs';
|
|
31
32
|
import { loadArkConfigContract } from './lib/config-contract.mjs';
|
|
32
33
|
import { loadEffectiveArkRulesFromDisk } from './lib/effective-contract-load.mjs';
|
|
33
34
|
import { loadArkRulesContract } from './lib/arkrules-contract.mjs';
|
|
@@ -112,6 +113,7 @@ import {
|
|
|
112
113
|
startResidentHookServer,
|
|
113
114
|
} from './lib/resident-hook.mjs';
|
|
114
115
|
import { resolveArchitectureSnapshot } from './lib/architecture-scan.mjs';
|
|
116
|
+
import { projectImporterIndex } from './lib/import-graph-projection.mjs';
|
|
115
117
|
import { runDoctor } from './lib/doctor-plan.mjs';
|
|
116
118
|
import {
|
|
117
119
|
evaluateWriteDesignDelta,
|
|
@@ -126,6 +128,23 @@ import {
|
|
|
126
128
|
const arkCheckBin = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
|
|
127
129
|
const arkMcpLauncher = fileURLToPath(new URL('./ark-mcp.mjs', import.meta.url));
|
|
128
130
|
|
|
131
|
+
/** MCP tool result for a thrown error: its message as text, flagged isError. */
|
|
132
|
+
function toolErrorResult(error) {
|
|
133
|
+
return {
|
|
134
|
+
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** MCP tool result for a `{ valid }` verdict: pretty JSON text + structuredContent. */
|
|
140
|
+
function validityToolResult(result) {
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
143
|
+
structuredContent: result,
|
|
144
|
+
isError: !result.valid,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
/**
|
|
130
149
|
* W4 — opt-in hook repair payload.
|
|
131
150
|
* True when CLI `--hook-repair` or env ARK_HOOK_REPAIR is 1/true/yes.
|
|
@@ -1266,7 +1285,9 @@ function createResidentDoctorSession(args, config, ts) {
|
|
|
1266
1285
|
const resolutionInputs = snapshotInputPaths(snapshot.inputs);
|
|
1267
1286
|
const ledger = createResidentInputLedger([...after.paths, ...resolutionInputs]);
|
|
1268
1287
|
if (!ledger.matches([...after.paths, ...resolutionInputs])) return null;
|
|
1269
|
-
|
|
1288
|
+
// Same importer index the cold doctor projects (ADR 0037), built once per snapshot.
|
|
1289
|
+
const importGraph = projectImporterIndex(snapshot.facts);
|
|
1290
|
+
return { root: args.root, files: after.files, ledger, resolutionInputs, rules, snapshot, importGraph };
|
|
1270
1291
|
}
|
|
1271
1292
|
|
|
1272
1293
|
function verifyResidentDoctorSession(session) {
|
|
@@ -1287,6 +1308,7 @@ function renderResidentDoctor(session, args, config, ts) {
|
|
|
1287
1308
|
ts,
|
|
1288
1309
|
parseHealth: session.snapshot.result.parseHealth,
|
|
1289
1310
|
completeness: session.snapshot.result.completeness,
|
|
1311
|
+
architectureFacts: { importGraph: session.importGraph },
|
|
1290
1312
|
writeJson: (value) => {
|
|
1291
1313
|
stdout += `${value}\n`;
|
|
1292
1314
|
},
|
|
@@ -2788,16 +2810,9 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
2788
2810
|
}),
|
|
2789
2811
|
{ root: args.root, acknowledgement, failClosed: true }
|
|
2790
2812
|
);
|
|
2791
|
-
return
|
|
2792
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
2793
|
-
structuredContent: result,
|
|
2794
|
-
isError: !result.valid,
|
|
2795
|
-
};
|
|
2813
|
+
return validityToolResult(result);
|
|
2796
2814
|
} catch (error) {
|
|
2797
|
-
return
|
|
2798
|
-
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
2799
|
-
isError: true,
|
|
2800
|
-
};
|
|
2815
|
+
return toolErrorResult(error);
|
|
2801
2816
|
}
|
|
2802
2817
|
}
|
|
2803
2818
|
|
|
@@ -2859,6 +2874,11 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
2859
2874
|
rules.some((rule) => !rule.allowed && rule.from === layerName && rule.to === to)
|
|
2860
2875
|
);
|
|
2861
2876
|
const mayImport = otherNames.filter((name) => !mustNotImport.includes(name));
|
|
2877
|
+
const sliceCatalog = [
|
|
2878
|
+
...(args.arkRulesSnapshot?.arkRules?.structure ?? []),
|
|
2879
|
+
...(args.arkRulesSnapshot?.arkRules?.invariants ?? []),
|
|
2880
|
+
].map((row) => ({ id: row.id, childId: row.provenance?.childId }));
|
|
2881
|
+
const sliceRules = sliceRuleIdsForFile(filePath, rules, sliceCatalog);
|
|
2862
2882
|
return withGolden({
|
|
2863
2883
|
filePath,
|
|
2864
2884
|
layer: layerName,
|
|
@@ -2866,6 +2886,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
2866
2886
|
...placementDescriptionFields(layerMeta),
|
|
2867
2887
|
forbiddenGlobals: layerMeta?.forbiddenGlobals ?? [],
|
|
2868
2888
|
...(layerMeta?.mayImportInfrastructure ? { mayImportInfrastructure: true } : {}),
|
|
2889
|
+
...(sliceRules.length > 0 ? { sliceRules } : {}),
|
|
2869
2890
|
mayImport,
|
|
2870
2891
|
mustNotImport,
|
|
2871
2892
|
note:
|
|
@@ -2978,16 +2999,9 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
2978
2999
|
tsconfig: args.tsconfig,
|
|
2979
3000
|
manifest: projectManifest,
|
|
2980
3001
|
});
|
|
2981
|
-
return
|
|
2982
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
2983
|
-
structuredContent: result,
|
|
2984
|
-
isError: !result.valid,
|
|
2985
|
-
};
|
|
3002
|
+
return validityToolResult(result);
|
|
2986
3003
|
} catch (error) {
|
|
2987
|
-
return
|
|
2988
|
-
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
2989
|
-
isError: true,
|
|
2990
|
-
};
|
|
3004
|
+
return toolErrorResult(error);
|
|
2991
3005
|
}
|
|
2992
3006
|
}
|
|
2993
3007
|
|
|
@@ -3028,10 +3042,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
3028
3042
|
isError: binding?.status === 'mismatch',
|
|
3029
3043
|
};
|
|
3030
3044
|
} catch (error) {
|
|
3031
|
-
return
|
|
3032
|
-
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
3033
|
-
isError: true,
|
|
3034
|
-
};
|
|
3045
|
+
return toolErrorResult(error);
|
|
3035
3046
|
}
|
|
3036
3047
|
}
|
|
3037
3048
|
|
|
@@ -3049,10 +3060,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
3049
3060
|
structuredContent: payload,
|
|
3050
3061
|
};
|
|
3051
3062
|
} catch (error) {
|
|
3052
|
-
return
|
|
3053
|
-
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
3054
|
-
isError: true,
|
|
3055
|
-
};
|
|
3063
|
+
return toolErrorResult(error);
|
|
3056
3064
|
}
|
|
3057
3065
|
}
|
|
3058
3066
|
|
|
@@ -3086,10 +3094,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
|
|
|
3086
3094
|
isError: false,
|
|
3087
3095
|
};
|
|
3088
3096
|
} catch (error) {
|
|
3089
|
-
return
|
|
3090
|
-
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
3091
|
-
isError: true,
|
|
3092
|
-
};
|
|
3097
|
+
return toolErrorResult(error);
|
|
3093
3098
|
}
|
|
3094
3099
|
}
|
|
3095
3100
|
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -19,10 +19,8 @@ import {
|
|
|
19
19
|
execCommandParts,
|
|
20
20
|
isPnpmWorkspaceRoot,
|
|
21
21
|
isNpmYarnWorkspaceRoot,
|
|
22
|
-
normalizeArkgateInstallSpec,
|
|
23
22
|
packageInstallArgv,
|
|
24
23
|
installDevHint,
|
|
25
|
-
arkPackageRecoveryCommand,
|
|
26
24
|
} from './lib/package-manager.mjs';
|
|
27
25
|
|
|
28
26
|
/**
|
|
@@ -566,16 +564,8 @@ export function collectForbiddenGlobalUses(ts, sourceFile, forbidden) {
|
|
|
566
564
|
export {
|
|
567
565
|
globToRegExp,
|
|
568
566
|
patternSpecificity,
|
|
569
|
-
concreteGlobSegments,
|
|
570
567
|
matchingLayersForRelativePath,
|
|
571
568
|
layerForFile,
|
|
572
|
-
layerForRelativePath,
|
|
573
|
-
isEdgeDenied,
|
|
574
|
-
findDeniedEdgeRule,
|
|
575
|
-
sliceIdForPath,
|
|
576
|
-
inferSliceFoldersFromPatterns,
|
|
577
|
-
DEFAULT_GENERATED_FILE_GLOBS,
|
|
578
|
-
scanExcludePatterns,
|
|
579
569
|
isScanExcludedRelative,
|
|
580
570
|
} from './ark-layer-match.mjs';
|
|
581
571
|
|
|
@@ -601,7 +591,6 @@ export function looksLikeIntent(value) {
|
|
|
601
591
|
* Generated CLI load path: bin/lib/remediation.mjs (`npm run generate:cli-pure`).
|
|
602
592
|
*/
|
|
603
593
|
export {
|
|
604
|
-
REMEDIATION_CLASSES,
|
|
605
594
|
classifyRemediation,
|
|
606
595
|
enrichViolationWithFixClass,
|
|
607
596
|
} from './lib/remediation.mjs';
|
|
@@ -671,11 +660,8 @@ export {
|
|
|
671
660
|
arkCommand,
|
|
672
661
|
execCommandParts,
|
|
673
662
|
isPnpmWorkspaceRoot,
|
|
674
|
-
isNpmYarnWorkspaceRoot,
|
|
675
|
-
normalizeArkgateInstallSpec,
|
|
676
663
|
packageInstallArgv,
|
|
677
664
|
installDevHint,
|
|
678
|
-
arkPackageRecoveryCommand,
|
|
679
665
|
};
|
|
680
666
|
|
|
681
667
|
// FX01–FX02: registry-aware skip lives in upgrade-package-decision (injectable probe).
|
|
@@ -684,7 +670,6 @@ export {
|
|
|
684
670
|
buildPackageInstallSkipPayload,
|
|
685
671
|
formatPackageInstallDecisionHuman,
|
|
686
672
|
compareSemverCore,
|
|
687
|
-
probeRegistryArkgateLatest,
|
|
688
673
|
} from './lib/upgrade-package-decision.mjs';
|
|
689
674
|
|
|
690
675
|
export const ARCHETYPE_IDS = [
|
|
@@ -880,7 +865,7 @@ const SKIP_DIR_NAMES = new Set([
|
|
|
880
865
|
* (`isSkippedSourceDir` in scan-files). Start/recommend/discovery must not
|
|
881
866
|
* treat playground, fixtures, scaffolds, or tests as production architecture.
|
|
882
867
|
*/
|
|
883
|
-
|
|
868
|
+
const NON_PRODUCT_SOURCE_DIR_NAMES = new Set([
|
|
884
869
|
'bench',
|
|
885
870
|
'benches',
|
|
886
871
|
'benchmark',
|
|
@@ -902,10 +887,6 @@ export const NON_PRODUCT_SOURCE_DIR_NAMES = new Set([
|
|
|
902
887
|
'tests',
|
|
903
888
|
]);
|
|
904
889
|
|
|
905
|
-
export function isNonProductSourceDirName(name) {
|
|
906
|
-
return NON_PRODUCT_SOURCE_DIR_NAMES.has(name);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
890
|
export function isNonProductRelativePath(relPath) {
|
|
910
891
|
return String(relPath)
|
|
911
892
|
.split(/[/\\]/)
|
|
@@ -1067,7 +1048,7 @@ function packageRole(rel, pkg) {
|
|
|
1067
1048
|
}
|
|
1068
1049
|
|
|
1069
1050
|
/** Path tokens already treated as UI app folders in discoverRepoUnits (root scan). */
|
|
1070
|
-
|
|
1051
|
+
const UI_PACKAGE_PATH_TOKENS = new Set(['web', 'frontend', 'client', 'ui']);
|
|
1071
1052
|
|
|
1072
1053
|
const UI_PACKAGE_DEP_NAMES =
|
|
1073
1054
|
/^(react|react-dom|vue|svelte|preact|solid-js|next|@angular\/core|@sveltejs\/kit)$/i;
|
|
@@ -2218,7 +2199,7 @@ export function writeAdoptionPlan(root, recommendation, filename = ADOPTION_PLAN
|
|
|
2218
2199
|
|
|
2219
2200
|
const __arkSharedDir = path.dirname(fileURLToPath(import.meta.url));
|
|
2220
2201
|
|
|
2221
|
-
|
|
2202
|
+
function defaultPolicyPacksPath() {
|
|
2222
2203
|
return path.resolve(__arkSharedDir, '../templates/policy-packs');
|
|
2223
2204
|
}
|
|
2224
2205
|
|
|
@@ -2271,7 +2252,7 @@ export function loadPolicyPackMeta(packId, packsPath = defaultPolicyPacksPath())
|
|
|
2271
2252
|
* (`INV-*` invariants keep their prefix: `INV-<LAYER>-…`).
|
|
2272
2253
|
*/
|
|
2273
2254
|
|
|
2274
|
-
|
|
2255
|
+
function kebabLayerName(layerName) {
|
|
2275
2256
|
return String(layerName)
|
|
2276
2257
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
2277
2258
|
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const ADOPTION_STANCE_REL = '.ark/adoption-stance.json';
|
|
9
|
+
const ADOPTION_STANCE_VALUE = 'advisory-only';
|
|
10
10
|
export const ADOPTED_REQUIRED_MERGE = 'required-merge';
|
|
11
11
|
export const ADOPTED_ADVISORY_ACKED = 'advisory-only-acked';
|
|
12
12
|
export const ADOPTED_NOT = 'not-adopted';
|