arkgate 4.5.0 → 4.5.6
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 +69 -2
- package/README.md +5 -4
- package/bin/ark-check-runtime.mjs +4 -0
- package/bin/ark-mcp-runtime.mjs +49 -0
- package/bin/ark-shared.mjs +8 -29
- package/bin/ark.mjs +7 -3
- package/bin/lib/adapter-contract.mjs +5 -5
- package/bin/lib/analysis-engine.mjs +1 -1
- package/bin/lib/ci-and-commands.mjs +5 -0
- package/bin/lib/deep-module-coach.mjs +177 -0
- package/bin/lib/deepening-coach.mjs +177 -0
- package/bin/lib/doctor-plan.mjs +14 -0
- package/bin/lib/html-report-advisories.mjs +33 -0
- package/bin/lib/html-report-depth.mjs +9 -0
- package/bin/lib/managed-upgrade.mjs +215 -1
- package/bin/lib/remediation.mjs +5 -5
- package/bin/lib/rules-inventory.mjs +23 -0
- package/bin/lib/upgrade-command.mjs +132 -11
- package/bin/lib/upgrade-package-decision.mjs +241 -0
- package/bin/lib/upgrade-whats-new.mjs +135 -0
- package/dist/eslint/index.cjs +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/index.cjs +27 -27
- package/dist/index.d.ts +1 -1
- package/dist/index.js +29 -29
- package/docs/README.md +4 -5
- package/docs/agent-guide.md +36 -0
- package/docs/brownfield-adoption.md +12 -0
- package/docs/package-surface.md +6 -3
- package/docs/product-voice.md +21 -0
- package/docs/use.md +3 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +2 -2
- package/templates/agent-skills/ark-adopt/SKILL.md +13 -0
- package/templates/agent-skills/ark-explore/SKILL.md +21 -0
- package/templates/agent-skills/ark-fix/SKILL.md +7 -0
- package/templates/agent-skills/ark-loop/SKILL.md +7 -0
- package/templates/agent-skills/ark-place/SKILL.md +7 -0
- package/templates/agent-skills/ark-think/SKILL.md +7 -0
- package/templates/agent-skills/ark-upgrade/SKILL.md +61 -15
- package/templates/skills/ark-adopt.md +13 -0
- package/templates/skills/ark-explore.md +21 -0
- package/templates/skills/ark-fix.md +7 -0
- package/templates/skills/ark-loop.md +7 -0
- package/templates/skills/ark-place.md +7 -0
- package/templates/skills/ark-think.md +7 -0
- package/templates/skills/ark-upgrade.md +61 -15
package/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,81 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.5.6 — 2026-08-11
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.5.5**. Field upgrade & multi-project MCP truth (Phase FX): registry-aware
|
|
11
|
+
package install, structured skip recovery, skill drift + opt-in refresh, post-upgrade checks,
|
|
12
|
+
MCP process package honesty, host selection notes, early whatsNew, inventory UX-copy quieting.
|
|
13
|
+
**No required config migration.** No new skill names, sensors, or scores. Freezes held.
|
|
14
|
+
|
|
15
|
+
**Status: prepared** (not on npm `latest` until publish train completes; see
|
|
16
|
+
`docs/releases/4.5.6.md`).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Registry-aware upgrade (FX01–FX02):** `ark upgrade --apply` no longer skips package install
|
|
21
|
+
solely because CLI version equals `node_modules/arkgate`. When the registry (injectable for
|
|
22
|
+
tests) is ahead, install runs. Skip paths emit structured JSON (`reasonCode`,
|
|
23
|
+
`installedVersion`, `cliVersion`, `registryLatest`, `suggestedInstallCmd`) plus human
|
|
24
|
+
recovery. Offline / registry-unknown stays honest (`REGISTRY_UNAVAILABLE`) without inventing
|
|
25
|
+
a bump.
|
|
26
|
+
- **Skill content drift honesty (FX03):** upgrade preview/JSON `skillDrift` counts
|
|
27
|
+
current/stale/customized/missing skills with sample paths and preserve notes.
|
|
28
|
+
- **Opt-in skill refresh (FX04):** `--refresh-skills` rewrites customized managed *skills* to
|
|
29
|
+
package templates with explicit consent. Conflicted assets still need `--accept-conflicts`.
|
|
30
|
+
Never silent overwrite of true user edits by default.
|
|
31
|
+
- **Post-upgrade verification block (FX05):** after apply, advisory `postUpgradeChecks` (pin↔CLI,
|
|
32
|
+
architecture verification, dual-truth, doctor/status/MCP notes). Always `notAScore`.
|
|
33
|
+
- **MCP multi-project process honesty (FX06):** every MCP tool context includes
|
|
34
|
+
`processPackage` (`processArkgateVersion`, `projectInstalledVersion`,
|
|
35
|
+
`processPackageMismatch` / `processStale`, `nextAction`). Prefer project-local CLI until
|
|
36
|
+
identity matched and versions align; multi-checkout recipe in agent-guide + `/ark-upgrade`.
|
|
37
|
+
- **Host selection honesty (FX07):** upgrade notes when detected active host ∉ managed
|
|
38
|
+
`--tools`/manifest hosts and suggests `--tools` expansion.
|
|
39
|
+
- **whatsNew early path (FX08):** suggested improvements on preview even when nothing to apply;
|
|
40
|
+
includes FX field-truth try/inspect items.
|
|
41
|
+
- **Inventory UX message quieting (FX09):** rules inventory downranks pure UX/error-message
|
|
42
|
+
string constants so they do not crowd business-rule pilots (no new sensors).
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `/ark-upgrade` skill + upgrade help document registry-aware install, `--refresh-skills`,
|
|
47
|
+
skill drift, post-upgrade checks, and multi-project MCP restart guidance.
|
|
48
|
+
|
|
49
|
+
**Freezes held:** no principle scores/ranks; no new skill names; no silent customized overwrite;
|
|
50
|
+
no silent multi-project MCP retarget; soft hosts stay advisory; no fake published claims.
|
|
51
|
+
|
|
52
|
+
## 4.5.5 — 2026-08-11
|
|
53
|
+
|
|
54
|
+
**Patch** over **4.5.0**. Deep-module coach train plus upgrade **suggested improvements /
|
|
55
|
+
what’s new** so consumers know what to try after install. **No required config migration.**
|
|
56
|
+
No new skill names, sensors, or scores. Freezes held.
|
|
57
|
+
|
|
58
|
+
**Status: published** (`arkgate@4.5.5` on npm `latest`; see `docs/releases/4.5.5.md`).
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- **Deep-module coach (advisory):** `ark-check --doctor` JSON `doctor.deepModuleCoach` + human
|
|
63
|
+
section + HTML `data-advisory="deepModuleCoach"`. Hot paths from bounded git history
|
|
64
|
+
(`unavailable` + empty when history missing — never invent). Deepening candidates projected
|
|
65
|
+
only from existing design smells / physical cohesion / pilot / residual compass evidence —
|
|
66
|
+
empty when no evidence. Always `notAScore: true`; never flips gate verdicts. Pure helper
|
|
67
|
+
`buildDeepeningCandidates` (+ CLI gen mirror).
|
|
68
|
+
- **Product voice + skills:** lexicon for depth / seam / deletion test / test-at-public-interface
|
|
69
|
+
/ domain glossary / two-axis done. Existing skills (explore, think, place, fix, loop, adopt)
|
|
70
|
+
deepened; agent-skills 1:1. Compact router + agent-guide **two-axis done** (Enforce green ≠
|
|
71
|
+
feature done). Remediation port/inject `nextAction` prose includes test at the public interface.
|
|
72
|
+
- **Upgrade suggested improvements / what’s new:** `ark upgrade` JSON `whatsNew` + human block
|
|
73
|
+
lists concrete try/inspect actions (deep-module coach, improvement compass, session/status
|
|
74
|
+
honesty, two-axis done, self-service honesty). Always `notAScore`; never a gate input.
|
|
75
|
+
|
|
8
76
|
## 4.5.0 — 2026-08-10
|
|
9
77
|
|
|
10
78
|
**Minor** over 4.4.0. Session control-plane honesty (status/MCP improvement compass with explicit
|
|
11
79
|
modes), domain module fitness (split oversized pure modules), selective pure verification ratchet,
|
|
12
80
|
self-service upgrade activation honesty, and a short session recipe in product docs. **No required
|
|
13
81
|
config migration.** Codex remains advisory at write time. Skills and AGENTS projection never
|
|
14
|
-
enforce. **Status:
|
|
15
|
-
(see `docs/releases/4.5.0.md`).
|
|
82
|
+
enforce. **Status: published** (`arkgate@4.5.0` on npm `latest`; see `docs/releases/4.5.0.md`).
|
|
16
83
|
|
|
17
84
|
### Added
|
|
18
85
|
|
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.
|
|
20
|
-
> [4.5.
|
|
19
|
+
> **ArkGate 4.5.5** is on npm `latest` — deep-module coach, upgrade what’s new, session honesty.
|
|
20
|
+
> [4.5.5 notes](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) · [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -208,8 +208,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
208
208
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
209
209
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
210
210
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
211
|
-
| Current release (4.
|
|
212
|
-
|
|
|
211
|
+
| Current release (4.5.5 on npm `latest`) | [docs/releases/4.5.5.md](docs/releases/4.5.5.md) · [CHANGELOG](CHANGELOG.md) |
|
|
212
|
+
| Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
|
|
213
|
+
| Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |
|
|
213
214
|
| Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |
|
|
214
215
|
| Prior (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) |
|
|
215
216
|
| Previous (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) |
|
|
@@ -1644,6 +1644,10 @@ async function main() {
|
|
|
1644
1644
|
...(designDepth?.improvementCompass
|
|
1645
1645
|
? { improvementCompass: designDepth.improvementCompass }
|
|
1646
1646
|
: {}),
|
|
1647
|
+
// Doctor parity: deep-module coach advisory (hot paths + deepening; notAScore).
|
|
1648
|
+
...(designDepth?.deepModuleCoach
|
|
1649
|
+
? { deepModuleCoach: designDepth.deepModuleCoach }
|
|
1650
|
+
: {}),
|
|
1647
1651
|
},
|
|
1648
1652
|
};
|
|
1649
1653
|
const html = args.beginner
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import readline from 'node:readline';
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import {
|
|
10
11
|
DEFAULT_INTENT_PREFIXES,
|
|
@@ -1847,11 +1848,59 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
1847
1848
|
return currentBinding;
|
|
1848
1849
|
}
|
|
1849
1850
|
|
|
1851
|
+
/**
|
|
1852
|
+
* FX06 — process package vs project install honesty (multi-project field truth).
|
|
1853
|
+
* Process arkgateVersion is startup-loaded; after consumer `npm i arkgate@newer`,
|
|
1854
|
+
* long-lived MCP can report a stale process version until restart.
|
|
1855
|
+
*/
|
|
1856
|
+
function readProjectInstalledArkgateVersion() {
|
|
1857
|
+
try {
|
|
1858
|
+
const shallow = path.join(resolvedRoot, 'node_modules', 'arkgate', 'package.json');
|
|
1859
|
+
if (fs.existsSync(shallow)) {
|
|
1860
|
+
const v = JSON.parse(fs.readFileSync(shallow, 'utf8')).version;
|
|
1861
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
1862
|
+
}
|
|
1863
|
+
} catch {
|
|
1864
|
+
/* fall through */
|
|
1865
|
+
}
|
|
1866
|
+
try {
|
|
1867
|
+
const requireFromProject = createRequire(path.join(resolvedRoot, 'package.json'));
|
|
1868
|
+
const pkgJson = requireFromProject.resolve('arkgate/package.json');
|
|
1869
|
+
const v = JSON.parse(fs.readFileSync(pkgJson, 'utf8')).version;
|
|
1870
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
1871
|
+
} catch {
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function processPackageHonesty() {
|
|
1877
|
+
const processVersion = typeof ark.version === 'string' ? ark.version : null;
|
|
1878
|
+
const projectInstalledVersion = readProjectInstalledArkgateVersion();
|
|
1879
|
+
const mismatch =
|
|
1880
|
+
processVersion != null &&
|
|
1881
|
+
projectInstalledVersion != null &&
|
|
1882
|
+
processVersion !== projectInstalledVersion;
|
|
1883
|
+
return {
|
|
1884
|
+
schemaVersion: '1.0',
|
|
1885
|
+
notAScore: true,
|
|
1886
|
+
processArkgateVersion: processVersion,
|
|
1887
|
+
projectInstalledVersion,
|
|
1888
|
+
processPackageMismatch: mismatch,
|
|
1889
|
+
processStale: mismatch,
|
|
1890
|
+
nextAction: mismatch
|
|
1891
|
+
? 'Restart or retarget the Ark MCP server so process arkgateVersion matches the project install. Prefer project-local CLI (`npx arkgate` / `npx arkgate-check`) until identity is matched and versions align. Multi-checkout users: one expectedRoot per project; never reuse another checkout’s projectId.'
|
|
1892
|
+
: projectInstalledVersion == null
|
|
1893
|
+
? 'Project has no resolvable node_modules/arkgate; install the package or use CLI from a project that pins arkgate.'
|
|
1894
|
+
: 'Process package version matches project install for this MCP root.',
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1850
1898
|
function contextFor(binding) {
|
|
1851
1899
|
return {
|
|
1852
1900
|
projectIdentity,
|
|
1853
1901
|
binding,
|
|
1854
1902
|
authoritative: binding.authoritative,
|
|
1903
|
+
processPackage: processPackageHonesty(),
|
|
1855
1904
|
};
|
|
1856
1905
|
}
|
|
1857
1906
|
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -722,35 +722,14 @@ export function packageInstallArgv(root, versionSpec = 'latest') {
|
|
|
722
722
|
return ['npm', ['install', '-D', pkgSpec]];
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
export function shouldSkipArkgateInstall(root, cliVersion) {
|
|
734
|
-
const pkgPath = path.join(root, 'node_modules', 'arkgate', 'package.json');
|
|
735
|
-
if (!fs.existsSync(pkgPath)) {
|
|
736
|
-
return { skip: false, installedVersion: null, reason: 'not-installed' };
|
|
737
|
-
}
|
|
738
|
-
let installedVersion = null;
|
|
739
|
-
try {
|
|
740
|
-
installedVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
|
|
741
|
-
} catch {
|
|
742
|
-
return { skip: false, installedVersion: null, reason: 'unreadable' };
|
|
743
|
-
}
|
|
744
|
-
if (
|
|
745
|
-
typeof cliVersion === 'string' &&
|
|
746
|
-
cliVersion &&
|
|
747
|
-
installedVersion &&
|
|
748
|
-
installedVersion === cliVersion
|
|
749
|
-
) {
|
|
750
|
-
return { skip: true, installedVersion, reason: 'already-current' };
|
|
751
|
-
}
|
|
752
|
-
return { skip: false, installedVersion, reason: 'version-differs' };
|
|
753
|
-
}
|
|
725
|
+
// FX01–FX02: registry-aware skip lives in upgrade-package-decision (injectable probe).
|
|
726
|
+
export {
|
|
727
|
+
shouldSkipArkgateInstall,
|
|
728
|
+
buildPackageInstallSkipPayload,
|
|
729
|
+
formatPackageInstallDecisionHuman,
|
|
730
|
+
compareSemverCore,
|
|
731
|
+
probeRegistryArkgateLatest,
|
|
732
|
+
} from './lib/upgrade-package-decision.mjs';
|
|
754
733
|
|
|
755
734
|
/** Package-manager aware "install a dev dependency" hint (e.g. for a missing typescript). */
|
|
756
735
|
export function installDevHint(root, pkg) {
|
package/bin/ark.mjs
CHANGED
|
@@ -77,6 +77,7 @@ function parseArgs(argv) {
|
|
|
77
77
|
installExplicit: false,
|
|
78
78
|
apply: false,
|
|
79
79
|
acceptConflicts: false,
|
|
80
|
+
refreshSkills: false,
|
|
80
81
|
planDigest: undefined,
|
|
81
82
|
json: false,
|
|
82
83
|
internalApply: false,
|
|
@@ -118,6 +119,7 @@ function parseArgs(argv) {
|
|
|
118
119
|
}
|
|
119
120
|
else if (arg === '--apply') args.apply = true;
|
|
120
121
|
else if (arg === '--accept-conflicts') args.acceptConflicts = true;
|
|
122
|
+
else if (arg === '--refresh-skills') args.refreshSkills = true;
|
|
121
123
|
else if (arg === '--plan-digest') args.planDigest = requireValue(arg, i++);
|
|
122
124
|
else if (arg === '--json') args.json = true;
|
|
123
125
|
else if (arg === '--internal-apply') args.internalApply = true;
|
|
@@ -153,7 +155,7 @@ function usage() {
|
|
|
153
155
|
ark start [--root <project>] [--tools <host>] [--require-write-hook <host>] [--install] [--apply] [--json]
|
|
154
156
|
ark init [--root <project>] [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture]
|
|
155
157
|
[--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
|
|
156
|
-
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--json] [--no-install] [--no-strict]
|
|
158
|
+
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--refresh-skills] [--json] [--no-install] [--no-strict]
|
|
157
159
|
ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--manifest <manifest.json>] [--tsconfig <tsconfig.json>] [--json]
|
|
158
160
|
ark status [--root <project>] [--config ark.config.json] [--json]
|
|
159
161
|
[--expected-root <abs>] [--expected-project-id sha256:…] [--tools <host>]
|
|
@@ -164,8 +166,10 @@ Commands:
|
|
|
164
166
|
start New here? Analyze and preview the complete setup. Read-only unless --apply.
|
|
165
167
|
init Configure Ark project enforcement with explicit prompts.
|
|
166
168
|
upgrade Preview identity-proven Ark-managed asset updates. With package install,
|
|
167
|
-
--apply bumps
|
|
168
|
-
--apply --no-install applies
|
|
169
|
+
--apply bumps toward registry latest when behind (not only when CLI ≠ pin)
|
|
170
|
+
and recomputes the preview; a second explicit --apply --no-install applies
|
|
171
|
+
those exact bytes and verifies them. --refresh-skills opts in to rewrite
|
|
172
|
+
customized managed skills to package templates (never silent default).
|
|
169
173
|
(alias: ark update)
|
|
170
174
|
preflight Validate one atomic create/update/delete set without writing project files.
|
|
171
175
|
status Unified session/project manifest (identity, activation, last check, rules).
|
|
@@ -82,18 +82,18 @@ function nextActionForDiagnostic(ruleId, evidence, violation) {
|
|
|
82
82
|
return 'Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.';
|
|
83
83
|
}
|
|
84
84
|
if (violation.peerIsolation === true) {
|
|
85
|
-
return 'Extract the shared dependency to a shared layer, then preflight again.';
|
|
85
|
+
return 'Extract the shared dependency to a shared layer, test at the public interface, then preflight again.';
|
|
86
86
|
}
|
|
87
|
-
return `Define a port in ${evidence.fromLayer ?? 'the source layer'}, inject the ${evidence.toLayer ?? 'outer-layer'} implementation, then preflight again.`;
|
|
87
|
+
return `Define a port in ${evidence.fromLayer ?? 'the source layer'}, inject the ${evidence.toLayer ?? 'outer-layer'} implementation, test at the public interface, then preflight again.`;
|
|
88
88
|
}
|
|
89
89
|
if (ruleId === 'FORBIDDEN_GLOBAL') {
|
|
90
|
-
return `Inject ${evidence.target ?? 'the capability'} through a port, then preflight again.`;
|
|
90
|
+
return `Inject ${evidence.target ?? 'the capability'} through a port, test at the public interface, then preflight again.`;
|
|
91
91
|
}
|
|
92
92
|
if (ruleId === 'CAPABILITY_VIOLATION') {
|
|
93
|
-
return `Define a ${text(violation.capability) ?? 'capability'} port in ${evidence.fromLayer ?? 'the walled layer'}, bind the implementation outside it, then preflight again.`;
|
|
93
|
+
return `Define a ${text(violation.capability) ?? 'capability'} port in ${evidence.fromLayer ?? 'the walled layer'}, bind the implementation outside it, test at the public interface, then preflight again.`;
|
|
94
94
|
}
|
|
95
95
|
if (ruleId === 'CIRCULAR_DEPENDENCY') {
|
|
96
|
-
return 'Extract the shared dependency into a third module, then preflight again.';
|
|
96
|
+
return 'Extract the shared dependency into a third module, test at the public interface, then preflight again.';
|
|
97
97
|
}
|
|
98
98
|
if (ruleId === 'RAW_EVENT_PUBLISH')
|
|
99
99
|
return 'Publish through a registered intent creator, then run Ark again.';
|
|
@@ -7,4 +7,4 @@ ${n.map(r=>`- ${r.path}: ${r.message}`).join(`
|
|
|
7
7
|
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function Mt(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function xe(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function le(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Vn(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function Ae(e,t,n,r,s){if(t.$ref){let a=Vn(t.$ref,r);if(!a){s.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}Ae(e,a,n,r,s);return}if(t.const!==void 0&&!Object.is(e,t.const)){s.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(a=>Object.is(a,e))){s.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!Mt(e)){s.push({path:n,message:`must be an object; received ${le(e)}`});return}let a=t.properties??{};for(let i of t.required??[])e[i]===void 0&&s.push({path:xe(n,i),message:"is required"});if(t.additionalProperties===!1)for(let i of Object.keys(e))i in a||s.push({path:xe(n,i),message:"unknown field"});else if(t.additionalProperties!==void 0&&t.additionalProperties!==!0&&typeof t.additionalProperties=="object"){let i=t.additionalProperties;for(let d of Object.keys(e))d in a||Ae(e[d],i,xe(n,d),r,s)}for(let[i,d]of Object.entries(a))e[i]!==void 0&&Ae(e[i],d,xe(n,i),r,s);return}if(t.type==="array"){if(!Array.isArray(e)){s.push({path:n,message:`must be an array; received ${le(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&s.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let a=e.map(i=>JSON.stringify(i));new Set(a).size!==a.length&&s.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((a,i)=>Ae(a,t.items,`${n}[${i}]`,r,s));return}if(t.type==="string"){if(typeof e!="string"){s.push({path:n,message:`must be a string; received ${le(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&s.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&s.push({path:n,message:`must be a boolean; received ${le(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){s.push({path:n,message:`must be an integer; received ${le(e)}`});return}t.minimum!==void 0&&e<t.minimum&&s.push({path:n,message:`must be at least ${t.minimum}`})}}function Un(e){return{...e,$schema:e.$schema===void 0?nt:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.1":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?Nt.map(t=>({...t})):e.rules}}function Gn(){let e=new Set(["1.1"]);for(let t of rt)t.from!=="unversioned"&&e.add(t.from),e.add(t.to);return e}function qn(e,t="ark.config.json"){if(!Mt(e))throw new G(t,[{path:"$",message:`must be an object; received ${le(e)}`}]);let n=Gn(),r=e.schemaVersion===void 0?"unversioned":typeof e.schemaVersion=="string"?e.schemaVersion:null;if(r===null)throw new G(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.1`}]);if(r!=="unversioned"&&!n.has(r))throw new G(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let s=r,a={...e},i=0;for(;s!=="1.1"&&i<rt.length+1;){i+=1;let p=rt.find(b=>b.from===s);if(!p)throw new G(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(s)}; expected 1.1`}]);s=p.to,a.schemaVersion=s}if(s!=="1.1")throw new G(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let d=r==="unversioned"?"unversioned":r==="1.0"?"1.0":null;return{candidate:Un(a),migratedFrom:d}}function st(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=qn(e,t),s=[];if(Ae(n,Lt,"$",Lt,s),s.length>0)throw new G(t,s);return{config:n,migratedFrom:r}}function Ht(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new G(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return st(n,t)}function Dt(e){return{config:e.config,arkRules:{schemaVersion:e.arkRules.schemaVersion,structure:e.arkRules.structure.map(t=>({id:t.id,sensor:t.sensor,mode:t.mode,appliesTo:t.appliesTo??null,description:t.description??null,provenance:t.provenance})),invariants:e.arkRules.invariants.map(t=>({id:t.id,description:t.description,aggregate:t.aggregate??null,coverage:t.coverage??null,mode:t.mode,appliesTo:t.appliesTo??null,provenance:t.provenance}))}}}var Ut="1.0";function I(e,t){e.push({id:`${t.classification}:${t.path}:${t.kind}`,path:t.path,classification:t.classification,message:t.message,...t.classification==="weakening"||t.classification==="judgment-required"?{nextAction:`Restore the previous protection at ${t.path}, then run ArkGate again.`}:{},...t.before===void 0?{}:{before:t.before},...t.after===void 0?{}:{after:t.after}})}function T(e){return[...new Set(e??[])].sort()}function X(e,t,n,r,s){let a=T(n),i=T(r),d=new Set(a),p=new Set(i),b=i.filter(h=>!d.has(h)),A=a.filter(h=>!p.has(h));b.length===0&&A.length===0||(b.length>0&&I(e,{kind:"added",path:t,classification:s.added,message:s.addedMessage,before:a,after:i}),A.length>0&&I(e,{kind:"removed",path:t,classification:s.removed,message:s.removedMessage,before:a,after:i}))}function Oe(e,t,n,r,s,a,i){if(n===r)return;I(e,{kind:r?"enabled":"disabled",path:t,classification:r?s:s==="strengthening"?"weakening":"strengthening",message:r?a:i,before:n,after:r})}function Pe(e,t){let n=new Map,r=new Set;for(let s of e){let a=t(s);n.has(a)?r.add(a):n.set(a,s)}return{values:n,duplicates:[...r].sort()}}function zn(e,t,n){let r=Pe(t,a=>a.name),s=Pe(n,a=>a.name);(r.duplicates.length>0||s.duplicates.length>0)&&I(e,{kind:"duplicate-layer",path:"$.layers",classification:"judgment-required",message:"Duplicate layer names make policy ownership ambiguous.",before:r.duplicates,after:s.duplicates});for(let a of[...new Set([...r.values.keys(),...s.values.keys()])].sort()){let i=r.values.get(a),d=s.values.get(a),p=`$.layers[${a}]`;if(!i&&d){I(e,{kind:"layer-added",path:p,classification:"judgment-required",message:"A layer was added; verify overlap, ownership, and rule coverage.",after:d});continue}if(i&&!d){I(e,{kind:"layer-removed",path:p,classification:"weakening",message:"Removing a layer can leave its source paths ungoverned.",before:i});continue}if(!i||!d)continue;X(e,`${p}.patterns`,i.patterns,d.patterns,{added:"strengthening",removed:"weakening",addedMessage:"Additional paths are governed by this layer.",removedMessage:"Paths were removed from this layer and may become ungoverned."}),X(e,`${p}.exclude`,i.exclude,d.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional paths are excluded from this layer.",removedMessage:"Fewer paths are excluded from this layer."});let b=Se(i),A=Se(d);X(e,`${p}.forbiddenGlobals`,b.rawGlobals,A.rawGlobals,{added:"strengthening",removed:"weakening",addedMessage:"Additional forbidden globals are enforced in this layer.",removedMessage:"A forbidden-global protection was removed from this layer."}),X(e,`${p}.capabilities`,b.atoms,A.atoms,{added:"strengthening",removed:"weakening",addedMessage:"Additional ambient/import protection is enforced in this layer (coverage atoms).",removedMessage:"An ambient/import protection was lost from this layer (coverage atoms)."}),T(i.intentPrefixes).join("\0")!==T(d.intentPrefixes).join("\0")&&I(e,{kind:"intent-prefixes-changed",path:`${p}.intentPrefixes`,classification:"judgment-required",message:"Intent ownership changed and must be reviewed against publishers and consumers.",before:T(i.intentPrefixes),after:T(d.intentPrefixes)}),Oe(e,`${p}.mayImportInfrastructure`,i.mayImportInfrastructure===!0,d.mayImportInfrastructure===!0,"weakening","The layer may now import infrastructure directly.","Direct infrastructure imports are no longer allowed for this layer."),Oe(e,`${p}.optional`,i.optional===!0,d.optional===!0,"weakening","The layer is now optional and can be absent without a strict warning.","The layer is now required when its contract is active.")}}function Bn(e,t,n){let r=i=>`${i.from}->${i.to}`,s=Pe(t,r),a=Pe(n,r);(s.duplicates.length>0||a.duplicates.length>0)&&I(e,{kind:"duplicate-rule",path:"$.rules",classification:"judgment-required",message:"Duplicate rule edges make the effective verdict order-dependent.",before:s.duplicates,after:a.duplicates});for(let i of[...new Set([...s.values.keys(),...a.values.keys()])].sort()){let d=s.values.get(i),p=a.values.get(i),b=`$.rules[${i}]`;if(!d&&p){p.allowed===!1&&I(e,{kind:"deny-added",path:b,classification:"strengthening",message:"A denied dependency edge was added.",after:p});continue}if(d&&!p){d.allowed===!1&&I(e,{kind:"deny-removed",path:b,classification:"weakening",message:"A denied dependency edge was removed.",before:d});continue}if(!d||!p)continue;d.allowed!==p.allowed&&I(e,{kind:p.allowed?"deny-disabled":"deny-enabled",path:`${b}.allowed`,classification:p.allowed?"weakening":"strengthening",message:p.allowed?"A previously denied dependency edge is now allowed.":"A dependency edge is now denied.",before:d.allowed,after:p.allowed});let A=d.peerIsolation===!0,h=p.peerIsolation===!0;if(A!==h){let o=d.from===d.to&&p.from===p.to;I(e,{kind:h?"peer-isolation-enabled":"peer-isolation-disabled",path:`${b}.peerIsolation`,classification:o?h?"strengthening":"weakening":"judgment-required",message:o?h?"Cross-slice dependencies inside this layer are now denied.":"Cross-slice dependencies inside this layer are no longer denied.":"Changing peer isolation on a cross-layer edge changes the denial scope.",before:A,after:h})}T(d.sliceFolders).join("\0")!==T(p.sliceFolders).join("\0")&&I(e,{kind:"slice-folders-changed",path:`${b}.sliceFolders`,classification:"judgment-required",message:"Slice ownership folders changed and can reclassify existing dependencies.",before:T(d.sliceFolders),after:T(p.sliceFolders)})}}function Kn(e,t,n){let r=t.safety??{},s=n.safety??{};for(let a of["maxTsSuppressions","maxAnyCasts"]){let i=r[a]??0,d=s[a]??0;i!==d&&I(e,{kind:d>i?"threshold-raised":"threshold-lowered",path:`$.safety.${a}`,classification:d>i?"weakening":"strengthening",message:d>i?"The safety threshold allows more violations.":"The safety threshold allows fewer violations.",before:i,after:d})}for(let a of["allowInMemory","allowDisabledPeerIsolation"])Oe(e,`$.safety.${a}`,r[a]===!0,s[a]===!0,"weakening","A safety exception was enabled.","A safety exception was disabled.")}function Yn(e){return e.some(t=>t.classification==="weakening")?"weakening":e.some(t=>t.classification==="judgment-required")?"judgment-required":e.some(t=>t.classification==="strengthening")?"strengthening":"neutral"}function jt(e,t){return`${e}::${t}`}function Vt(e){let t=new Map,n=new Map;if(!e)return{structure:t,invariants:n};for(let r of e.structure)t.set(jt(r.provenance.layer,r.id),r);for(let r of e.invariants)n.set(jt(r.provenance.layer,r.id),r);return{structure:t,invariants:n}}function Wn(e,t,n,r,s,a){let i=new Map((a??[]).map(o=>[o.invariantId,o])),d=t.arkRules??{},p=n.arkRules??{},b=[...new Set([...Object.keys(d),...Object.keys(p)])].sort();for(let o of b){let l=d[o],c=p[o],f=`$.arkRules[${o}]`;if(l===void 0&&c!==void 0){I(e,{kind:"arkrules-ref-added",path:f,classification:"strengthening",message:`ArkRules reference for layer ${o} was added.`,after:c});continue}if(l!==void 0&&c===void 0){I(e,{kind:"arkrules-ref-removed",path:f,classification:"weakening",message:`ArkRules reference for layer ${o} was removed.`,before:l});continue}l!==c&&I(e,{kind:"arkrules-ref-path-changed",path:f,classification:"judgment-required",message:`ArkRules file path for layer ${o} changed; verify the effective rules still match intent.`,before:l,after:c})}let A=Vt(r),h=Vt(s);for(let o of[...new Set([...A.structure.keys(),...h.structure.keys()])].sort()){let l=A.structure.get(o),c=h.structure.get(o),f=`$.arkRules.structure[${o}]`;if(!l&&c){I(e,{kind:"arkrule-structure-added",path:f,classification:"strengthening",message:`Structure ArkRule ${c.id} was added (${c.mode}).`,after:c});continue}if(l&&!c){I(e,{kind:"arkrule-structure-removed",path:f,classification:"weakening",message:`Structure ArkRule ${l.id} was removed.`,before:l});continue}if(!(!l||!c)){if(l.mode!==c.mode){let y=l.mode==="advisory"&&c.mode==="enforced";I(e,{kind:y?"arkrule-promoted":"arkrule-demoted",path:`${f}.mode`,classification:y?"strengthening":"weakening",message:y?`Structure ArkRule ${c.id} was promoted to enforced.`:`Structure ArkRule ${c.id} was demoted to advisory.`,before:l.mode,after:c.mode})}l.sensor!==c.sensor&&I(e,{kind:"arkrule-sensor-changed",path:`${f}.sensor`,classification:"judgment-required",message:`Structure ArkRule ${c.id} changed sensor identity.`,before:l.sensor,after:c.sensor})}}for(let o of[...new Set([...A.invariants.keys(),...h.invariants.keys()])].sort()){let l=A.invariants.get(o),c=h.invariants.get(o),f=`$.arkRules.invariants[${o}]`;if(!l&&c){I(e,{kind:"arkrule-invariant-added",path:f,classification:"strengthening",message:`Invariant ${c.id} was added (${c.mode}).`,after:c});continue}if(l&&!c){I(e,{kind:"arkrule-invariant-removed",path:f,classification:"weakening",message:`Invariant ${l.id} was removed.`,before:l});continue}if(!(!l||!c)&&l.mode!==c.mode)if(l.mode==="advisory"&&c.mode==="enforced"){let R=i?.get(c.id),u=$t(R);u.ok?I(e,{kind:"arkrule-invariant-promoted",path:`${f}.mode`,classification:"strengthening",message:`Invariant ${c.id} was promoted to enforced (coverage evidence present).`,before:l.mode,after:c.mode}):I(e,{kind:"arkrule-invariant-promote-refused",path:`${f}.mode`,classification:"judgment-required",message:`Invariant ${c.id} cannot be promoted to enforced: ${u.reason}`,before:l.mode,after:c.mode})}else I(e,{kind:"arkrule-invariant-demoted",path:`${f}.mode`,classification:"weakening",message:`Invariant ${c.id} was demoted to advisory.`,before:l.mode,after:c.mode})}}function Gt(e,t,n){let r=[];X(r,"$.include",e.include,t.include,{added:"strengthening",removed:"weakening",addedMessage:"Additional project roots are governed.",removedMessage:"Project roots were removed from governance."}),X(r,"$.exclude",e.exclude,t.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional project paths are excluded from governance.",removedMessage:"Fewer project paths are excluded from governance."}),X(r,"$.dynamicImportAllowlist",e.dynamicImportAllowlist,t.dynamicImportAllowlist,{added:"weakening",removed:"strengthening",addedMessage:"Additional files may use non-literal dynamic imports.",removedMessage:"Fewer files may use non-literal dynamic imports."}),Oe(r,"$.excludeGenerated",e.excludeGenerated!==!1,t.excludeGenerated!==!1,"weakening","Generated source is now excluded from governance.","Generated source is now governed.");let s={off:0,soft:1,"framework-soft":1,strict:2},a=e.cyclePolicy??"strict",i=t.cyclePolicy??"strict";if(a!==i){let d=s[i]===s[a]?"judgment-required":s[i]>s[a]?"strengthening":"weakening";I(r,{kind:"cycle-policy-changed",path:"$.cyclePolicy",classification:d,message:"The cycle enforcement level changed.",before:a,after:i})}return(e.frameworkOverlay??null)!==(t.frameworkOverlay??null)&&I(r,{kind:"framework-overlay-changed",path:"$.frameworkOverlay",classification:"judgment-required",message:"The framework overlay changed and may alter effective layer matching.",before:e.frameworkOverlay??null,after:t.frameworkOverlay??null}),zn(r,e.layers,t.layers),Bn(r,e.rules,t.rules),Kn(r,e,t),Wn(r,e,t,n?.baseArkRules,n?.candidateArkRules,n?.candidateInvariantCoverage),r.sort((d,p)=>d.path.localeCompare(p.path)||d.id.localeCompare(p.id)),{schemaVersion:Ut,classification:Yn(r),findings:r}}function qt(e,t){if(!e||e.schemaVersion!==Ut||typeof e.basePolicyHash!="string"||typeof e.candidatePolicyHash!="string"||typeof e.reason!="string"||!Array.isArray(e.findingIds)||e.findingIds.some(s=>typeof s!="string")||e.reason.trim().length===0||e.basePolicyHash!==t.basePolicyHash||e.candidatePolicyHash!==t.candidatePolicyHash)return!1;let n=T(e.findingIds),r=T(t.findingIds);return n.length===r.length&&n.every((s,a)=>s===r[a])}function Q(e){let t=[];for(let n of e.replace(/\\/g,"/").split("/"))!n||n==="."||(n===".."&&t.length>0&&t.at(-1)!==".."?t.pop():t.push(n));return t.join("/")}function zt(e){return e!==void 0&&/[A-Za-z_$]/.test(e)}function Fe(e){return e!==void 0&&/[A-Za-z0-9_$]/.test(e)}function P(e,t){for(;t<e.length&&/\s/.test(e[t]);)t+=1;return t}function Re(e,t){let n=e[t];if(n!=="'"&&n!=='"')return;let r=t,s="";for(t+=1;t<e.length;t+=1){let a=e[t];if(a===n)return{value:s,offset:r,excerpt:e.slice(r,t+1)};a==="\\"&&t+1<e.length?(s+=e[t+1],t+=1):s+=a}}function _(e,t,n){return e.startsWith(t,n)&&!Fe(e[n-1])&&!Fe(e[n+t.length])}function Bt(e,t){let n=P(e,t);if(e[n]!=="{")return!1;n+=1;let r=!1;for(;n<e.length;){if(n=P(e,n),e[n]==="}")return r;if(e[n]===","){n+=1;continue}if(!_(e,"type",n))return!1;let s=P(e,n+4);if(s>=e.length||e[s]===","||e[s]==="}"||_(e,"as",s)||!zt(e[s]))return!1;for(n=s;n<e.length&&Fe(e[n]);)n+=1;if(n=P(e,n),_(e,"as",n)){if(n=P(e,n+2),!zt(e[n]))return!1;for(;n<e.length&&Fe(e[n]);)n+=1}r=!0}return!1}function Jn(e,t){if(t=P(e,t+6),e[t]==="(")return Re(e,P(e,t+1));let n=!1;if(_(e,"type",t)){let s=P(e,t+4);e[s]!==","&&!_(e,"from",s)&&(n=!0)}else Bt(e,t)&&(n=!0);let r=Kt(e,t,!0);return r&&n?{...r,typeOnly:!0}:r}function Zn(e,t){t=t+6;let n=P(e,t),r=!1;if(_(e,"type",n)){let a=P(e,n+4);(e[a]==="{"||e[a]==="*")&&(r=!0)}else Bt(e,n)&&(r=!0);let s=Kt(e,t,!1);return s&&r?{...s,typeOnly:!0}:s}function Kt(e,t,n){for(;t<e.length;t+=1){if(e[t]===";")return;if(_(e,"from",t))return Re(e,P(e,t+4));if(n&&(e[t]==="'"||e[t]==='"'))return Re(e,t);if(t>0&&(_(e,"import",t)||_(e,"export",t)))return}}function Xn(e,t){for(t+=1;t<e.length;t+=1){let n=e[t];if(n==="\\")t+=1;else if(n==="`")return t}return e.length}function Qn(e,t){let n=t-1;for(;n>=0&&/\s/.test(e[n]);)n-=1;if(e[n]===".")return;let r=P(e,t+7);if(e[r]!=="(")return;r=P(e,r+1);let s=Re(e,r);return s?{...s,requireCall:!0}:void 0}function er(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="/"&&e[n+1]==="/"){if(n=e.indexOf(`
|
|
8
8
|
`,n+2),n<0)break;continue}if(r==="/"&&e[n+1]==="*"){let a=e.indexOf("*/",n+2);if(a<0)break;n=a+1;continue}if(r==="`"){n=Xn(e,n);continue}if(r==="'"||r==='"'){let a=Re(e,n);a&&(n=a.offset+a.excerpt.length-1);continue}let s=r==="i"&&_(e,"import",n)?Jn(e,n):r==="e"&&_(e,"export",n)?Zn(e,n):r==="r"&&_(e,"require",n)?Qn(e,n):void 0;s&&(t.push(s),n=s.offset+s.excerpt.length-1)}return t}function Yt(e,t){let n=[],r=[];for(let s of er(e.content)){let a=s.value;if(!a.startsWith(".")){if(s.typeOnly)continue;let b=pe(a);if(!b)continue;let A=e.content.slice(0,s.offset).split(`
|
|
9
9
|
`).length;r.push({file:e.path,symbol:a,capability:b,evidence:{kind:"import",file:e.path,line:A,excerpt:s.excerpt}});continue}let i=e.content.slice(0,s.offset).split(`
|
|
10
|
-
`).length,d={kind:"import",file:e.path,line:i,excerpt:s.excerpt},p=tr(e.path,a,t);n.push({from:e.path,specifier:a,to:p?.path??null,resolution:p?"resolved":"unresolved",fromLayer:e.layer,toLayer:p?.layer??null,evidence:d})}return{edges:n,capabilityUses:r}}function tr(e,t,n){let r=e.split("/");r.pop();for(let a of t.split("/"))a==="."||a===""||(a===".."?r.pop():r.push(a));let s=r.join("/");for(let a of[s,`${s}.ts`,`${s}.tsx`,`${s}.mts`,`${s}.cts`,`${s}/index.ts`,`${s}/index.tsx`]){let i=n.get(a);if(i)return i}}function Wt(e,t){let n=[];for(let r of e){if(!r.to||!r.fromLayer||!r.toLayer)continue;let s=ae(t.rules,r.fromLayer,r.toLayer,{fromPath:r.from,toPath:r.to,layers:t.layers});s&&n.push({ruleId:`layer-dependency:${s.from}->${s.to}`,message:s.message??`${s.from} must not depend on ${s.to}.`,edge:r,evidence:r.evidence})}return n}var nr={code:"LEXICAL_EVIDENCE_INCOMPLETE",message:"Lexical compatibility mode cannot prove parse status, TypeScript/package/symlink resolution, or symbol-aware source-policy and safety evidence. Use the resolved candidate facts APIs for an authoritative verdict."};function rr(e,t){return e.arkRules!==void 0&&Object.keys(e.arkRules).length>0||t.structure.length>0||t.invariants.length>0}function sr(e,t){return rr(e,t)?E(S(Dt({config:e,arkRules:t,warnings:[]}))):E(S(e))}function at(e,t,n){let r=typeof e=="string"?Ht(e,t):st(e,t),s=n?.arkRules??me();return{...r,arkRules:s,policyHash:sr(r.config,s)}}function ar(e){let t=at(e.baseConfig,e.baseSource??"base ark.config.json",{arkRules:e.baseArkRules}),n=at(e.candidateConfig,e.candidateSource??"candidate ark.config.json",{arkRules:e.candidateArkRules}),r=Gt(t.config,n.config,{baseArkRules:t.arkRules,candidateArkRules:n.arkRules,candidateInvariantCoverage:e.candidateInvariantCoverage}),s=r.findings.filter(d=>d.classification==="weakening"||d.classification==="judgment-required").map(d=>d.id).sort(),a=s.length>0,i=a&&qt(e.acknowledgement,{basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,findingIds:s});return{schemaVersion:r.schemaVersion,basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,classification:r.classification,findings:r.findings,blockingFindingIds:s,requiresAcknowledgement:a,acknowledged:i,valid:!a||i}}function Te(e){let t=e.files.map(h=>{let o=Q(h.path);return{path:o,content:h.content,contentHash:E(h.content),layer:V(o,e.contract.config.layers)??null}}).sort((h,o)=>h.path.localeCompare(o.path)),n=new Map(t.map(h=>[h.path,h])),r=[],s=[];for(let h of t){let o=Yt(h,n);r.push(...o.edges),s.push(...o.capabilityUses)}let a=Wt(r,e.contract.config),i=new Map(e.contract.config.layers.map(h=>[h.name,h])),d=new Map(e.contract.config.layers.map(h=>[h.name,new Set(se(h))]));for(let h of s){let o=n.get(h.file)?.layer;if(!o)continue;let l=i.get(o),c=fe(h.symbol,l?.forbiddenGlobals??[]);if(c){a.push({ruleId:"FORBIDDEN_GLOBAL",message:`${o} must not use module "${h.symbol}" because it is the import form of forbidden global "${c}".`,symbol:h.symbol,evidence:h.evidence});continue}d.get(o)?.has(h.capability)&&a.push({ruleId:"CAPABILITY_VIOLATION",message:`${o} denies the ${h.capability} capability; found import of "${h.symbol}".`,capability:h.capability,symbol:h.symbol,evidence:h.evidence})}let p=t.length===0?"complete":"partial",b=p==="complete"?[]:[{...nr}],A={schemaVersion:mt,policyHash:e.contract.policyHash,compilerOptionsHash:E(S(e.compilerOptions??{})),files:t,layers:e.contract.config.layers.map(h=>h.name),edges:r,capabilityUses:s,violations:a};return{mode:"lexical-compatibility",completeness:p,completenessReasons:b,valid:p==="complete"&&a.length===0,ir:A}}function it(e){let t=new Map(e.files.map(n=>[Q(n.path),n]));for(let n of e.changes){let r=Q(n.path);"delete"in n&&n.delete?t.delete(r):"content"in n&&t.set(r,{path:r,content:n.content})}return Te({contract:e.contract,files:[...t.values()],compilerOptions:e.compilerOptions})}function ir(e){let t=`${e.evidence.file}:${e.evidence.line}`;if(!e.edge)return`${e.ruleId} at ${t}: ${e.message}`;let n=e.edge.to??e.edge.specifier;return`${e.ruleId} at ${t}: ${e.edge.from} imports ${n}. ${e.message}`}function _e(e){switch(e.ruleId){case"LAYER_IMPORT_VIOLATION":return e.typeOnly||e.targetTypeOnlyExports||e.namedBindingsTypeOnly?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":e.peerIsolation?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${e.fromLayer??"the source layer"}, inject the ${e.toLayer??"outer-layer"} implementation, then preflight again.`;case"FORBIDDEN_GLOBAL":return`Inject ${e.target??"the capability"} through a port, then preflight again.`;case"CAPABILITY_VIOLATION":return`Define a ${String(e.capability??"capability")} port in ${e.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`;case"CIRCULAR_DEPENDENCY":return"Extract the shared dependency into a third module, then preflight again.";case"RAW_EVENT_PUBLISH":return"Publish through a registered intent creator, then run Ark again.";case"PUBLISH_MISSING_SOURCE":return"Add metadata.source to the publish call, then run Ark again.";case"ARKRULE_STRUCTURE":case"ARKRULE_INVARIANT":case"INVARIANT_UNCOVERED":return`Fix the structure or invariant for ${typeof e.arkruleId=="string"&&e.arkruleId.length>0?e.arkruleId:"the ArkRule"} (declared in ${typeof e.arkruleSource=="string"&&e.arkruleSource.length>0?e.arkruleSource:"arkrules/<Layer>.json"}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`;default:return typeof e.ruleId=="string"&&e.ruleId.startsWith("ARKRULE_")?`Fix the ArkRule ${typeof e.arkruleId=="string"?e.arkruleId:e.ruleId}, then preflight again.`:`Resolve ${typeof e.ruleId=="string"&&e.ruleId.length>0?e.ruleId:"ARK_UNKNOWN"} without weakening ark.config.json, then run Ark again.`}}function Jt(e){return E(S(e.map(({path:t,contentHash:n})=>({path:t,contentHash:n}))))}function or(e){let t=Te(e),n=new Map(t.ir.files.map(c=>[c.path,c])),r=[],s=new Set,a=[];for(let c of e.changes){let f=c.path.replace(/\\/g,"/"),y=Q(c.path);if(!y||y===".."||y.startsWith("../")||f.startsWith("/")||/^[A-Za-z]:\//.test(f)||f.includes("\0")){a.push({ruleId:"INVALID_CHANGE_PATH",file:"<change-set>",line:1,message:"Every change requires a safe, non-empty project-relative path."});continue}if(s.has(y)){a.push({ruleId:"DUPLICATE_CHANGE_PATH",file:y,line:1,message:`The atomic change set contains more than one operation for ${y}.`});continue}s.add(y),"delete"in c&&c.delete&&!n.has(y)&&a.push({ruleId:"DELETE_TARGET_MISSING",file:y,line:1,message:`Cannot delete ${y} because it is not present in the supplied base tree.`}),r.push("delete"in c&&c.delete?{path:y,delete:!0}:{path:y,content:"content"in c?c.content:""})}e.changes.length===0&&a.push({ruleId:"CHANGE_SET_EMPTY",file:"<change-set>",line:1,message:"Atomic preflight requires at least one create, update, or delete."});let i=it({...e,changes:r}),d=new Map(i.ir.files.map(c=>[c.path,c])),p=i.ir.violations.filter(c=>c.ruleId==="CAPABILITY_VIOLATION"||c.ruleId==="FORBIDDEN_GLOBAL").map(c=>{let f=d.get(c.evidence.file)?.layer;return{ruleId:c.ruleId,file:c.evidence.file,line:c.evidence.line,target:c.symbol,...c.capability?{capability:c.capability}:{},...f?{fromLayer:f}:{},edgeKind:"import",message:c.message}}),b=ge({config:e.contract.config,rules:e.contract.config.rules,files:i.ir.files.map(c=>c.path),contentViolations:p,edges:i.ir.edges.filter(c=>!!c.fromLayer).map(c=>({from:c.from,fromLayer:c.fromLayer,...c.to?{to:c.to}:{},...c.toLayer?{toLayer:c.toLayer}:{},line:c.evidence.line,kind:"import",...c.typeOnly?{typeOnly:!0}:{},...c.namedBindingsTypeOnly?{namedBindingsTypeOnly:!0}:{}}))}),A=r.map(c=>{let f=Q(c.path),y=n.get(f),R=d.get(f);return{path:f,operation:"delete"in c&&c.delete?"delete":y?"update":"create",...y?{beforeContentHash:y.contentHash}:{},...R?{candidateContentHash:R.contentHash}:{}}}).sort((c,f)=>c.path.localeCompare(f.path)),h=[...a,...b.violations].map(c=>({...c,nextAction:_e(c)})),o=h.filter(c=>c.failsStrict!==!1),l=e.changeMap?be({changeMap:e.changeMap,changes:A,baseDependencies:t.ir.edges.flatMap(c=>c.to?[{from:c.from,to:c.to}]:[]),candidateDependencies:i.ir.edges.flatMap(c=>c.to?[{from:c.from,to:c.to}]:[])}):void 0;return{schemaVersion:"1.0",mode:"lexical-compatibility",valid:t.completeness==="complete"&&i.valid&&o.length===0&&(l?.structurallyConverged??!0),readOnly:!0,policyHash:e.contract.policyHash,compilerOptionsHash:i.ir.compilerOptionsHash,baseTreeHash:Jt(t.ir.files),candidateTreeHash:Jt(i.ir.files),baseCompleteness:t.completeness,candidateCompleteness:i.completeness,baseCompletenessReasons:t.completenessReasons,candidateCompletenessReasons:i.completenessReasons,...e.changeMap?{changeMapHash:e.changeMap.hash}:{},...l?{convergence:l}:{},changes:A,violations:h,warnings:b.warnings}}function lr(e){let t=e.replace(/\\/g,"/");if(!t||t.startsWith("/")||/^[A-Za-z]:\//.test(t)||t.includes("\0"))return;let n=[];for(let s of t.split("/"))if(!(!s||s==="."))if(s===".."){if(n.length===0)return;n.pop()}else n.push(s);let r=n.join("/");return r&&r===t?r:void 0}function cr(e,t){return[["resolverIdentity",e.resolverIdentity,t.resolverIdentity],["compilerIdentity",e.compilerIdentity,t.compilerIdentity],["evidenceRequirementsHash",e.evidenceRequirementsHash,t.evidenceRequirementsHash],["projectPackageName",e.projectPackageName??"",t.projectPackageName??""]].filter(([,r,s])=>r!==s).map(([r,s,a])=>({ruleId:"FACTS_IDENTITY_MISMATCH",file:"<change-set>",line:1,field:r,before:s,candidate:a,message:`Base and candidate facts must use the same ${r}.`}))}function dr(e,t,n,r){let s=n.get(t),a=r.get(t);return{path:t,operation:"delete"in e&&e.delete?"delete":s?"update":"create",...s?{beforeContentHash:s.contentHash}:{},...a?{candidateContentHash:a.contentHash}:{}}}function pr(e){let t=W(e.baseFacts),n=W(e.candidateFacts),r=$e({contract:e.contract,facts:t}),s=$e({contract:e.contract,facts:n}),a=new Map(t.files.map(l=>[l.path,l])),i=new Map(n.files.map(l=>[l.path,l])),d=cr(t,n),p=new Map,b=[];for(let l of e.changes){let c=lr(l.path);if(!c){d.push({ruleId:"INVALID_CHANGE_PATH",file:"<change-set>",line:1,message:"Every change requires a canonical project-relative path."});continue}if(p.has(c)){d.push({ruleId:"DUPLICATE_CHANGE_PATH",file:c,line:1,message:`The atomic change set contains more than one operation for ${c}.`});continue}p.set(c,l);let f=a.get(c),y=i.get(c);if("delete"in l&&l.delete)f||d.push({ruleId:"DELETE_TARGET_MISSING",file:c,line:1,message:`Cannot delete ${c} because it is not present in the supplied base facts.`}),y&&d.push({ruleId:"CANDIDATE_DELETE_NOT_APPLIED",file:c,line:1,message:`Candidate facts still contain deleted file ${c}.`});else{let R="content"in l?l.content:"",u=E(R);y?y.contentHash!==u&&d.push({ruleId:"CANDIDATE_CONTENT_HASH_MISMATCH",file:c,line:1,expectedContentHash:u,candidateContentHash:y.contentHash,message:`Candidate facts for ${c} do not match the declared content.`}):d.push({ruleId:"CANDIDATE_CHANGE_MISSING",file:c,line:1,message:`Candidate facts do not contain changed file ${c}.`})}b.push(dr(l,c,a,i))}e.changes.length===0&&d.push({ruleId:"CHANGE_SET_EMPTY",file:"<change-set>",line:1,message:"Atomic preflight requires at least one create, update, or delete."});for(let l of new Set([...a.keys(),...i.keys()])){if(p.has(l))continue;let c=a.get(l),f=i.get(l);c&&f&&S(c)===S(f)||d.push({ruleId:"UNDECLARED_CANDIDATE_CHANGE",file:l,line:1,message:`Candidate facts change ${l}, but the atomic change set does not declare it.`})}let A=e.changeMap?be({changeMap:e.changeMap,changes:b,baseDependencies:r.ir.edges.flatMap(l=>l.to?[{from:l.from,to:l.to}]:[]),candidateDependencies:s.ir.edges.flatMap(l=>l.to?[{from:l.from,to:l.to}]:[])}):void 0,h=[...d,...s.ir.violations].map(l=>({...l,nextAction:_e(l)})),o=h.filter(l=>l.failsStrict!==!1);return{schemaVersion:"1.0",mode:"resolved-candidate-facts",valid:r.completeness==="complete"&&s.strictValid&&o.length===0&&(A?.structurallyConverged??!0),readOnly:!0,policyHash:e.contract.policyHash,resolverIdentity:n.resolverIdentity,compilerIdentity:n.compilerIdentity,compilerOptionsHash:n.compilerOptionsHash,tsconfigHash:n.tsconfigHash,baseCompilerOptionsHash:t.compilerOptionsHash,candidateCompilerOptionsHash:n.compilerOptionsHash,baseTsconfigHash:t.tsconfigHash,candidateTsconfigHash:n.tsconfigHash,evidenceRequirementsHash:n.evidenceRequirementsHash,baseFactsHash:t.factsHash,candidateFactsHash:n.factsHash,baseTreeHash:t.candidateTreeHash,candidateTreeHash:n.candidateTreeHash,baseCompleteness:r.completeness,candidateCompleteness:s.completeness,baseCompletenessReasons:r.completenessReasons,candidateCompletenessReasons:s.completenessReasons,...e.changeMap?{changeMapHash:e.changeMap.hash}:{},...A?{convergence:A}:{},changes:b.sort((l,c)=>l.path<c.path?-1:l.path>c.path?1:0),violations:h,warnings:s.ir.warnings}}var Zt=new WeakSet;function Xt(e){if(!e||typeof e!="object"||Object.isFrozen(e))return e;for(let t of Object.values(e))Xt(t);return Object.freeze(e)}function Ys(e){let t=Xt(Ie(e));return Zt.add(t),t}function Ws(e){if(!Zt.has(e.facts))throw new Error("Trusted resolved analysis requires immutable in-process canonical facts.");return Ye(e)}export{De as AMBIENT_CAPABILITY_ENTRIES,Me as CAPABILITY_IDS,Ge as DEFAULT_INTENT_PREFIXES,ut as RESOLVED_CANDIDATE_FACTS_SCHEMA,ee as RESOLVED_CANDIDATE_FACTS_SCHEMA_VERSION,Ue as SOURCE_POLICY_MESSAGES,je as ambientCoveredByForbiddenGlobals,be as analyzeArchitectureConvergence,it as analyzeChange,ar as analyzePolicyDelta,Te as analyzeProject,$e as analyzeResolvedProject,Ws as analyzeTrustedResolvedProject,ue as capabilityForAmbientName,pe as capabilityForModuleSpecifier,ze as classifyPublishFacts,Ke as collectAnalysisConfigWarnings,Hn as collectCapabilityUses,tt as collectForbiddenCapabilityUses,Ie as createResolvedCandidateFacts,Ys as createTrustedResolvedCandidateFacts,wt as detectArchitectureCycles,E as deterministicHash,se as effectiveCapabilityDeny,ge as evaluateArchitectureGraph,ir as explainViolation,et as extractSemanticDependencies,fe as forbiddenGlobalForModuleSpecifier,Ln as loadArchitectureChangeMap,at as loadContract,W as loadResolvedCandidateFacts,bt as looksLikeArkIntent,an as lowerForbiddenGlobal,Se as loweredLayerCoverage,or as preflightChange,pr as preflightResolvedChange,qe as resolveIntentLayer,ve as resolvedFactsEvidenceRequirementsHash,S as stableSerialize};
|
|
10
|
+
`).length,d={kind:"import",file:e.path,line:i,excerpt:s.excerpt},p=tr(e.path,a,t);n.push({from:e.path,specifier:a,to:p?.path??null,resolution:p?"resolved":"unresolved",fromLayer:e.layer,toLayer:p?.layer??null,evidence:d})}return{edges:n,capabilityUses:r}}function tr(e,t,n){let r=e.split("/");r.pop();for(let a of t.split("/"))a==="."||a===""||(a===".."?r.pop():r.push(a));let s=r.join("/");for(let a of[s,`${s}.ts`,`${s}.tsx`,`${s}.mts`,`${s}.cts`,`${s}/index.ts`,`${s}/index.tsx`]){let i=n.get(a);if(i)return i}}function Wt(e,t){let n=[];for(let r of e){if(!r.to||!r.fromLayer||!r.toLayer)continue;let s=ae(t.rules,r.fromLayer,r.toLayer,{fromPath:r.from,toPath:r.to,layers:t.layers});s&&n.push({ruleId:`layer-dependency:${s.from}->${s.to}`,message:s.message??`${s.from} must not depend on ${s.to}.`,edge:r,evidence:r.evidence})}return n}var nr={code:"LEXICAL_EVIDENCE_INCOMPLETE",message:"Lexical compatibility mode cannot prove parse status, TypeScript/package/symlink resolution, or symbol-aware source-policy and safety evidence. Use the resolved candidate facts APIs for an authoritative verdict."};function rr(e,t){return e.arkRules!==void 0&&Object.keys(e.arkRules).length>0||t.structure.length>0||t.invariants.length>0}function sr(e,t){return rr(e,t)?E(S(Dt({config:e,arkRules:t,warnings:[]}))):E(S(e))}function at(e,t,n){let r=typeof e=="string"?Ht(e,t):st(e,t),s=n?.arkRules??me();return{...r,arkRules:s,policyHash:sr(r.config,s)}}function ar(e){let t=at(e.baseConfig,e.baseSource??"base ark.config.json",{arkRules:e.baseArkRules}),n=at(e.candidateConfig,e.candidateSource??"candidate ark.config.json",{arkRules:e.candidateArkRules}),r=Gt(t.config,n.config,{baseArkRules:t.arkRules,candidateArkRules:n.arkRules,candidateInvariantCoverage:e.candidateInvariantCoverage}),s=r.findings.filter(d=>d.classification==="weakening"||d.classification==="judgment-required").map(d=>d.id).sort(),a=s.length>0,i=a&&qt(e.acknowledgement,{basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,findingIds:s});return{schemaVersion:r.schemaVersion,basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,classification:r.classification,findings:r.findings,blockingFindingIds:s,requiresAcknowledgement:a,acknowledged:i,valid:!a||i}}function Te(e){let t=e.files.map(h=>{let o=Q(h.path);return{path:o,content:h.content,contentHash:E(h.content),layer:V(o,e.contract.config.layers)??null}}).sort((h,o)=>h.path.localeCompare(o.path)),n=new Map(t.map(h=>[h.path,h])),r=[],s=[];for(let h of t){let o=Yt(h,n);r.push(...o.edges),s.push(...o.capabilityUses)}let a=Wt(r,e.contract.config),i=new Map(e.contract.config.layers.map(h=>[h.name,h])),d=new Map(e.contract.config.layers.map(h=>[h.name,new Set(se(h))]));for(let h of s){let o=n.get(h.file)?.layer;if(!o)continue;let l=i.get(o),c=fe(h.symbol,l?.forbiddenGlobals??[]);if(c){a.push({ruleId:"FORBIDDEN_GLOBAL",message:`${o} must not use module "${h.symbol}" because it is the import form of forbidden global "${c}".`,symbol:h.symbol,evidence:h.evidence});continue}d.get(o)?.has(h.capability)&&a.push({ruleId:"CAPABILITY_VIOLATION",message:`${o} denies the ${h.capability} capability; found import of "${h.symbol}".`,capability:h.capability,symbol:h.symbol,evidence:h.evidence})}let p=t.length===0?"complete":"partial",b=p==="complete"?[]:[{...nr}],A={schemaVersion:mt,policyHash:e.contract.policyHash,compilerOptionsHash:E(S(e.compilerOptions??{})),files:t,layers:e.contract.config.layers.map(h=>h.name),edges:r,capabilityUses:s,violations:a};return{mode:"lexical-compatibility",completeness:p,completenessReasons:b,valid:p==="complete"&&a.length===0,ir:A}}function it(e){let t=new Map(e.files.map(n=>[Q(n.path),n]));for(let n of e.changes){let r=Q(n.path);"delete"in n&&n.delete?t.delete(r):"content"in n&&t.set(r,{path:r,content:n.content})}return Te({contract:e.contract,files:[...t.values()],compilerOptions:e.compilerOptions})}function ir(e){let t=`${e.evidence.file}:${e.evidence.line}`;if(!e.edge)return`${e.ruleId} at ${t}: ${e.message}`;let n=e.edge.to??e.edge.specifier;return`${e.ruleId} at ${t}: ${e.edge.from} imports ${n}. ${e.message}`}function _e(e){switch(e.ruleId){case"LAYER_IMPORT_VIOLATION":return e.typeOnly||e.targetTypeOnlyExports||e.namedBindingsTypeOnly?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":e.peerIsolation?"Extract the shared dependency to a shared layer, test at the public interface, then preflight again.":`Define a port in ${e.fromLayer??"the source layer"}, inject the ${e.toLayer??"outer-layer"} implementation, test at the public interface, then preflight again.`;case"FORBIDDEN_GLOBAL":return`Inject ${e.target??"the capability"} through a port, test at the public interface, then preflight again.`;case"CAPABILITY_VIOLATION":return`Define a ${String(e.capability??"capability")} port in ${e.fromLayer??"the walled layer"}, bind the implementation outside it, test at the public interface, then preflight again.`;case"CIRCULAR_DEPENDENCY":return"Extract the shared dependency into a third module, test at the public interface, then preflight again.";case"RAW_EVENT_PUBLISH":return"Publish through a registered intent creator, then run Ark again.";case"PUBLISH_MISSING_SOURCE":return"Add metadata.source to the publish call, then run Ark again.";case"ARKRULE_STRUCTURE":case"ARKRULE_INVARIANT":case"INVARIANT_UNCOVERED":return`Fix the structure or invariant for ${typeof e.arkruleId=="string"&&e.arkruleId.length>0?e.arkruleId:"the ArkRule"} (declared in ${typeof e.arkruleSource=="string"&&e.arkruleSource.length>0?e.arkruleSource:"arkrules/<Layer>.json"}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`;default:return typeof e.ruleId=="string"&&e.ruleId.startsWith("ARKRULE_")?`Fix the ArkRule ${typeof e.arkruleId=="string"?e.arkruleId:e.ruleId}, then preflight again.`:`Resolve ${typeof e.ruleId=="string"&&e.ruleId.length>0?e.ruleId:"ARK_UNKNOWN"} without weakening ark.config.json, then run Ark again.`}}function Jt(e){return E(S(e.map(({path:t,contentHash:n})=>({path:t,contentHash:n}))))}function or(e){let t=Te(e),n=new Map(t.ir.files.map(c=>[c.path,c])),r=[],s=new Set,a=[];for(let c of e.changes){let f=c.path.replace(/\\/g,"/"),y=Q(c.path);if(!y||y===".."||y.startsWith("../")||f.startsWith("/")||/^[A-Za-z]:\//.test(f)||f.includes("\0")){a.push({ruleId:"INVALID_CHANGE_PATH",file:"<change-set>",line:1,message:"Every change requires a safe, non-empty project-relative path."});continue}if(s.has(y)){a.push({ruleId:"DUPLICATE_CHANGE_PATH",file:y,line:1,message:`The atomic change set contains more than one operation for ${y}.`});continue}s.add(y),"delete"in c&&c.delete&&!n.has(y)&&a.push({ruleId:"DELETE_TARGET_MISSING",file:y,line:1,message:`Cannot delete ${y} because it is not present in the supplied base tree.`}),r.push("delete"in c&&c.delete?{path:y,delete:!0}:{path:y,content:"content"in c?c.content:""})}e.changes.length===0&&a.push({ruleId:"CHANGE_SET_EMPTY",file:"<change-set>",line:1,message:"Atomic preflight requires at least one create, update, or delete."});let i=it({...e,changes:r}),d=new Map(i.ir.files.map(c=>[c.path,c])),p=i.ir.violations.filter(c=>c.ruleId==="CAPABILITY_VIOLATION"||c.ruleId==="FORBIDDEN_GLOBAL").map(c=>{let f=d.get(c.evidence.file)?.layer;return{ruleId:c.ruleId,file:c.evidence.file,line:c.evidence.line,target:c.symbol,...c.capability?{capability:c.capability}:{},...f?{fromLayer:f}:{},edgeKind:"import",message:c.message}}),b=ge({config:e.contract.config,rules:e.contract.config.rules,files:i.ir.files.map(c=>c.path),contentViolations:p,edges:i.ir.edges.filter(c=>!!c.fromLayer).map(c=>({from:c.from,fromLayer:c.fromLayer,...c.to?{to:c.to}:{},...c.toLayer?{toLayer:c.toLayer}:{},line:c.evidence.line,kind:"import",...c.typeOnly?{typeOnly:!0}:{},...c.namedBindingsTypeOnly?{namedBindingsTypeOnly:!0}:{}}))}),A=r.map(c=>{let f=Q(c.path),y=n.get(f),R=d.get(f);return{path:f,operation:"delete"in c&&c.delete?"delete":y?"update":"create",...y?{beforeContentHash:y.contentHash}:{},...R?{candidateContentHash:R.contentHash}:{}}}).sort((c,f)=>c.path.localeCompare(f.path)),h=[...a,...b.violations].map(c=>({...c,nextAction:_e(c)})),o=h.filter(c=>c.failsStrict!==!1),l=e.changeMap?be({changeMap:e.changeMap,changes:A,baseDependencies:t.ir.edges.flatMap(c=>c.to?[{from:c.from,to:c.to}]:[]),candidateDependencies:i.ir.edges.flatMap(c=>c.to?[{from:c.from,to:c.to}]:[])}):void 0;return{schemaVersion:"1.0",mode:"lexical-compatibility",valid:t.completeness==="complete"&&i.valid&&o.length===0&&(l?.structurallyConverged??!0),readOnly:!0,policyHash:e.contract.policyHash,compilerOptionsHash:i.ir.compilerOptionsHash,baseTreeHash:Jt(t.ir.files),candidateTreeHash:Jt(i.ir.files),baseCompleteness:t.completeness,candidateCompleteness:i.completeness,baseCompletenessReasons:t.completenessReasons,candidateCompletenessReasons:i.completenessReasons,...e.changeMap?{changeMapHash:e.changeMap.hash}:{},...l?{convergence:l}:{},changes:A,violations:h,warnings:b.warnings}}function lr(e){let t=e.replace(/\\/g,"/");if(!t||t.startsWith("/")||/^[A-Za-z]:\//.test(t)||t.includes("\0"))return;let n=[];for(let s of t.split("/"))if(!(!s||s==="."))if(s===".."){if(n.length===0)return;n.pop()}else n.push(s);let r=n.join("/");return r&&r===t?r:void 0}function cr(e,t){return[["resolverIdentity",e.resolverIdentity,t.resolverIdentity],["compilerIdentity",e.compilerIdentity,t.compilerIdentity],["evidenceRequirementsHash",e.evidenceRequirementsHash,t.evidenceRequirementsHash],["projectPackageName",e.projectPackageName??"",t.projectPackageName??""]].filter(([,r,s])=>r!==s).map(([r,s,a])=>({ruleId:"FACTS_IDENTITY_MISMATCH",file:"<change-set>",line:1,field:r,before:s,candidate:a,message:`Base and candidate facts must use the same ${r}.`}))}function dr(e,t,n,r){let s=n.get(t),a=r.get(t);return{path:t,operation:"delete"in e&&e.delete?"delete":s?"update":"create",...s?{beforeContentHash:s.contentHash}:{},...a?{candidateContentHash:a.contentHash}:{}}}function pr(e){let t=W(e.baseFacts),n=W(e.candidateFacts),r=$e({contract:e.contract,facts:t}),s=$e({contract:e.contract,facts:n}),a=new Map(t.files.map(l=>[l.path,l])),i=new Map(n.files.map(l=>[l.path,l])),d=cr(t,n),p=new Map,b=[];for(let l of e.changes){let c=lr(l.path);if(!c){d.push({ruleId:"INVALID_CHANGE_PATH",file:"<change-set>",line:1,message:"Every change requires a canonical project-relative path."});continue}if(p.has(c)){d.push({ruleId:"DUPLICATE_CHANGE_PATH",file:c,line:1,message:`The atomic change set contains more than one operation for ${c}.`});continue}p.set(c,l);let f=a.get(c),y=i.get(c);if("delete"in l&&l.delete)f||d.push({ruleId:"DELETE_TARGET_MISSING",file:c,line:1,message:`Cannot delete ${c} because it is not present in the supplied base facts.`}),y&&d.push({ruleId:"CANDIDATE_DELETE_NOT_APPLIED",file:c,line:1,message:`Candidate facts still contain deleted file ${c}.`});else{let R="content"in l?l.content:"",u=E(R);y?y.contentHash!==u&&d.push({ruleId:"CANDIDATE_CONTENT_HASH_MISMATCH",file:c,line:1,expectedContentHash:u,candidateContentHash:y.contentHash,message:`Candidate facts for ${c} do not match the declared content.`}):d.push({ruleId:"CANDIDATE_CHANGE_MISSING",file:c,line:1,message:`Candidate facts do not contain changed file ${c}.`})}b.push(dr(l,c,a,i))}e.changes.length===0&&d.push({ruleId:"CHANGE_SET_EMPTY",file:"<change-set>",line:1,message:"Atomic preflight requires at least one create, update, or delete."});for(let l of new Set([...a.keys(),...i.keys()])){if(p.has(l))continue;let c=a.get(l),f=i.get(l);c&&f&&S(c)===S(f)||d.push({ruleId:"UNDECLARED_CANDIDATE_CHANGE",file:l,line:1,message:`Candidate facts change ${l}, but the atomic change set does not declare it.`})}let A=e.changeMap?be({changeMap:e.changeMap,changes:b,baseDependencies:r.ir.edges.flatMap(l=>l.to?[{from:l.from,to:l.to}]:[]),candidateDependencies:s.ir.edges.flatMap(l=>l.to?[{from:l.from,to:l.to}]:[])}):void 0,h=[...d,...s.ir.violations].map(l=>({...l,nextAction:_e(l)})),o=h.filter(l=>l.failsStrict!==!1);return{schemaVersion:"1.0",mode:"resolved-candidate-facts",valid:r.completeness==="complete"&&s.strictValid&&o.length===0&&(A?.structurallyConverged??!0),readOnly:!0,policyHash:e.contract.policyHash,resolverIdentity:n.resolverIdentity,compilerIdentity:n.compilerIdentity,compilerOptionsHash:n.compilerOptionsHash,tsconfigHash:n.tsconfigHash,baseCompilerOptionsHash:t.compilerOptionsHash,candidateCompilerOptionsHash:n.compilerOptionsHash,baseTsconfigHash:t.tsconfigHash,candidateTsconfigHash:n.tsconfigHash,evidenceRequirementsHash:n.evidenceRequirementsHash,baseFactsHash:t.factsHash,candidateFactsHash:n.factsHash,baseTreeHash:t.candidateTreeHash,candidateTreeHash:n.candidateTreeHash,baseCompleteness:r.completeness,candidateCompleteness:s.completeness,baseCompletenessReasons:r.completenessReasons,candidateCompletenessReasons:s.completenessReasons,...e.changeMap?{changeMapHash:e.changeMap.hash}:{},...A?{convergence:A}:{},changes:b.sort((l,c)=>l.path<c.path?-1:l.path>c.path?1:0),violations:h,warnings:s.ir.warnings}}var Zt=new WeakSet;function Xt(e){if(!e||typeof e!="object"||Object.isFrozen(e))return e;for(let t of Object.values(e))Xt(t);return Object.freeze(e)}function Ys(e){let t=Xt(Ie(e));return Zt.add(t),t}function Ws(e){if(!Zt.has(e.facts))throw new Error("Trusted resolved analysis requires immutable in-process canonical facts.");return Ye(e)}export{De as AMBIENT_CAPABILITY_ENTRIES,Me as CAPABILITY_IDS,Ge as DEFAULT_INTENT_PREFIXES,ut as RESOLVED_CANDIDATE_FACTS_SCHEMA,ee as RESOLVED_CANDIDATE_FACTS_SCHEMA_VERSION,Ue as SOURCE_POLICY_MESSAGES,je as ambientCoveredByForbiddenGlobals,be as analyzeArchitectureConvergence,it as analyzeChange,ar as analyzePolicyDelta,Te as analyzeProject,$e as analyzeResolvedProject,Ws as analyzeTrustedResolvedProject,ue as capabilityForAmbientName,pe as capabilityForModuleSpecifier,ze as classifyPublishFacts,Ke as collectAnalysisConfigWarnings,Hn as collectCapabilityUses,tt as collectForbiddenCapabilityUses,Ie as createResolvedCandidateFacts,Ys as createTrustedResolvedCandidateFacts,wt as detectArchitectureCycles,E as deterministicHash,se as effectiveCapabilityDeny,ge as evaluateArchitectureGraph,ir as explainViolation,et as extractSemanticDependencies,fe as forbiddenGlobalForModuleSpecifier,Ln as loadArchitectureChangeMap,at as loadContract,W as loadResolvedCandidateFacts,bt as looksLikeArkIntent,an as lowerForbiddenGlobal,Se as loweredLayerCoverage,or as preflightChange,pr as preflightResolvedChange,qe as resolveIntentLayer,ve as resolvedFactsEvidenceRequirementsHash,S as stableSerialize};
|
|
@@ -414,6 +414,11 @@ ${projectionBlock}
|
|
|
414
414
|
- **Design-weak / residual shape lenses** (compass residual while edges may look green) → map first, then guided apply with user OK — never “you’re done” on green edges alone.
|
|
415
415
|
- Empty plan A + residual lenses / design-weak → **not finished**.
|
|
416
416
|
|
|
417
|
+
**Two-axis done (never collapse):**
|
|
418
|
+
1. **Architecture residual** — status / doctor / compass (scan). Prefer deep modules; name seams; test at the public interface.
|
|
419
|
+
2. **Feature / ticket residual** — does the change match the requested behavior? (process outside the package).
|
|
420
|
+
**Enforce green ≠ feature done.** No package LLM verdict for either axis.
|
|
421
|
+
|
|
417
422
|
The selected host is \`${selectedHost}\`. Host registration and CI are installed with this file.
|
|
418
423
|
This compact router is enough for normal feature work.
|
|
419
424
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-module coach advisory (Tooling) — hot paths + deepening candidates.
|
|
3
|
+
*
|
|
4
|
+
* Advisory only (`notAScore`). Never feeds valid / strict-merge / goal.met /
|
|
5
|
+
* completeness green. Hot paths use a bounded git log heuristic; incomplete or
|
|
6
|
+
* missing history → status `unavailable`, empty paths (never invent).
|
|
7
|
+
* Deepening candidates come only from existing smells / cohesion / compass /
|
|
8
|
+
* pilot evidence via Domain pure projection.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import {
|
|
12
|
+
buildDeepeningCandidates,
|
|
13
|
+
ARK_DEEPENING_COACH_SCHEMA_VERSION,
|
|
14
|
+
DEEPENING_CANDIDATE_CAP,
|
|
15
|
+
} from './deepening-coach.mjs';
|
|
16
|
+
|
|
17
|
+
export { buildDeepeningCandidates, ARK_DEEPENING_COACH_SCHEMA_VERSION, DEEPENING_CANDIDATE_CAP };
|
|
18
|
+
|
|
19
|
+
/** Recent-window commit cap for hot-path heuristic (budget). */
|
|
20
|
+
export const HOT_PATH_COMMIT_LIMIT = 200;
|
|
21
|
+
/** Max listed hot paths. */
|
|
22
|
+
export const HOT_PATH_LIST_CAP = 8;
|
|
23
|
+
/** Minimum change hits before a path is “elevated”. */
|
|
24
|
+
export const HOT_PATH_MIN_HITS = 3;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Best-effort recent-churn paths from git history.
|
|
28
|
+
* @param {string} root
|
|
29
|
+
* @param {{ runGit?: Function, commitLimit?: number, minHits?: number, listCap?: number }} [opts]
|
|
30
|
+
*/
|
|
31
|
+
export function computeHotPathAdvisory(root, opts = {}) {
|
|
32
|
+
const commitLimit = Number(opts.commitLimit) > 0 ? Number(opts.commitLimit) : HOT_PATH_COMMIT_LIMIT;
|
|
33
|
+
const minHits = Number(opts.minHits) > 0 ? Number(opts.minHits) : HOT_PATH_MIN_HITS;
|
|
34
|
+
const listCap = Number(opts.listCap) > 0 ? Number(opts.listCap) : HOT_PATH_LIST_CAP;
|
|
35
|
+
const runGit =
|
|
36
|
+
typeof opts.runGit === 'function'
|
|
37
|
+
? opts.runGit
|
|
38
|
+
: (args) =>
|
|
39
|
+
spawnSync('git', ['-C', root, ...args], {
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
42
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const emptyUnavailable = (reason) => ({
|
|
46
|
+
available: false,
|
|
47
|
+
status: 'unavailable',
|
|
48
|
+
reason,
|
|
49
|
+
paths: [],
|
|
50
|
+
notAScore: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const head = runGit(['rev-parse', '--verify', 'HEAD']);
|
|
55
|
+
if (!head || head.status !== 0) {
|
|
56
|
+
return emptyUnavailable('git history incomplete or missing (no HEAD)');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const log = runGit([
|
|
60
|
+
'log',
|
|
61
|
+
'-n',
|
|
62
|
+
String(commitLimit),
|
|
63
|
+
'--name-only',
|
|
64
|
+
'--pretty=format:',
|
|
65
|
+
'--diff-filter=AMR',
|
|
66
|
+
]);
|
|
67
|
+
if (!log || log.status !== 0) {
|
|
68
|
+
return emptyUnavailable('git log unavailable for hot-path heuristic');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const counts = new Map();
|
|
72
|
+
const text = typeof log.stdout === 'string' ? log.stdout : '';
|
|
73
|
+
for (const line of text.split('\n')) {
|
|
74
|
+
const raw = line.trim().replace(/\\/g, '/');
|
|
75
|
+
if (!raw || raw.startsWith('.git/')) continue;
|
|
76
|
+
// Prefer product source; still allow other paths if they dominate.
|
|
77
|
+
counts.set(raw, (counts.get(raw) || 0) + 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (counts.size === 0) {
|
|
81
|
+
return {
|
|
82
|
+
available: true,
|
|
83
|
+
status: 'ok',
|
|
84
|
+
reason: null,
|
|
85
|
+
paths: [],
|
|
86
|
+
notAScore: true,
|
|
87
|
+
window: { commitLimit, minHits },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const ranked = [...counts.entries()]
|
|
92
|
+
.filter(([, n]) => n >= minHits)
|
|
93
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
94
|
+
.slice(0, listCap)
|
|
95
|
+
.map(([path, changeCount]) => ({ path, changeCount }));
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
available: true,
|
|
99
|
+
status: 'ok',
|
|
100
|
+
reason: null,
|
|
101
|
+
paths: ranked,
|
|
102
|
+
notAScore: true,
|
|
103
|
+
window: { commitLimit, minHits },
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return emptyUnavailable('git history incomplete or missing');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Full deep-module coach advisory for doctor / report.
|
|
112
|
+
* @param {string} root
|
|
113
|
+
* @param {{
|
|
114
|
+
* designSmells?: object[],
|
|
115
|
+
* physicalCohesion?: object | null,
|
|
116
|
+
* improvementCompass?: object | null,
|
|
117
|
+
* pilotLoop?: object | null,
|
|
118
|
+
* runGit?: Function,
|
|
119
|
+
* }} [input]
|
|
120
|
+
*/
|
|
121
|
+
export function buildDeepModuleCoachAdvisory(root, input = {}) {
|
|
122
|
+
const deepening = buildDeepeningCandidates({
|
|
123
|
+
designSmells: input.designSmells,
|
|
124
|
+
physicalCohesion: input.physicalCohesion,
|
|
125
|
+
improvementCompass: input.improvementCompass,
|
|
126
|
+
pilotLoop: input.pilotLoop,
|
|
127
|
+
});
|
|
128
|
+
const hotPaths = computeHotPathAdvisory(root, {
|
|
129
|
+
runGit: input.runGit,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
schemaVersion: ARK_DEEPENING_COACH_SCHEMA_VERSION,
|
|
134
|
+
notAScore: true,
|
|
135
|
+
hotPaths,
|
|
136
|
+
deepeningCandidates: deepening.candidates,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Human doctor section (never a score bar). Always one short block when coach is present.
|
|
142
|
+
* @param {ReturnType<typeof buildDeepModuleCoachAdvisory>} coach
|
|
143
|
+
* @param {{ line: Function, warn: string, ok: string, color: { bold: Function, dim: Function } }} io
|
|
144
|
+
*/
|
|
145
|
+
export function printDeepModuleCoachSection(coach, io) {
|
|
146
|
+
if (!coach || coach.notAScore !== true) return;
|
|
147
|
+
const { line, warn, ok, color } = io;
|
|
148
|
+
const hot = coach.hotPaths || { status: 'unavailable', paths: [], reason: 'missing' };
|
|
149
|
+
const candidates = Array.isArray(coach.deepeningCandidates) ? coach.deepeningCandidates : [];
|
|
150
|
+
const paths = Array.isArray(hot.paths) ? hot.paths : [];
|
|
151
|
+
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log(color.bold('Deep-module coach (advisory — not a score)'));
|
|
154
|
+
|
|
155
|
+
if (hot.status === 'unavailable') {
|
|
156
|
+
line(ok, color.dim(`Hot paths: unavailable — ${hot.reason || 'no git history'}; never invented.`));
|
|
157
|
+
} else if (paths.length > 0) {
|
|
158
|
+
line(warn, `Hot paths (recent churn heuristic, top ${paths.length}):`);
|
|
159
|
+
for (const row of paths.slice(0, HOT_PATH_LIST_CAP)) {
|
|
160
|
+
line(' ', color.dim(`${row.path} · ${row.changeCount} recent change(s)`));
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
line(ok, color.dim('Hot paths: none above churn threshold (advisory only).'));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (candidates.length === 0) {
|
|
167
|
+
line(ok, color.dim('Deepening candidates: none from existing evidence.'));
|
|
168
|
+
} else {
|
|
169
|
+
line(warn, `Deepening candidates (${candidates.length}, from existing residual only):`);
|
|
170
|
+
for (const c of candidates.slice(0, DEEPENING_CANDIDATE_CAP)) {
|
|
171
|
+
line(' ', `${c.target} — ${c.friction}`);
|
|
172
|
+
if (c.intent) line(' ', color.dim(`intent: ${c.intent}`));
|
|
173
|
+
if (c.benefit) line(' ', color.dim(`benefit: ${c.benefit}`));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
line(ok, color.dim('Never changes gate verdicts. Prefer deep modules; test at the public interface.'));
|
|
177
|
+
}
|