| /** | ||
| * GENERATED FILE — do not edit by hand. | ||
| * | ||
| * Canonical algorithm: src/domain/improvementCompassMap.ts | ||
| * Regenerate: node scripts/generate-cli-pure.mjs | ||
| * Drift check: node scripts/generate-cli-pure.mjs --check | ||
| * | ||
| * Pure CLI helper (bin/lib/improvement-compass-map.mjs). Zero Node I/O. | ||
| */ | ||
| import { IMPROVEMENT_COMPASS_LENS_LABELS, IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES, IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET, IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SUMMARIES, IMPROVEMENT_COMPASS_RESIDUAL_SORT_PRIORITY, IMPROVEMENT_COMPASS_TOP_RESIDUAL_CAP, IMPROVEMENT_LENS_IDS, } from './improvement-compass-types.mjs'; | ||
| function smellIdOf(smell) { | ||
| const raw = smell.id ?? smell.smellId ?? ''; | ||
| return typeof raw === 'string' ? raw.trim() : ''; | ||
| } | ||
| function violationRuleId(v) { | ||
| const raw = v.ruleId ?? v.code ?? ''; | ||
| return typeof raw === 'string' ? raw.trim() : ''; | ||
| } | ||
| function pushEvidence(lens, source, ref, detail) { | ||
| if (!ref) | ||
| return; | ||
| // Dedup by source+ref for deterministic stability. | ||
| if (lens.evidence.some((e) => e.source === source && e.ref === ref)) | ||
| return; | ||
| const entry = { source, ref }; | ||
| if (detail && detail.trim()) | ||
| entry.detail = detail.trim().slice(0, 240); | ||
| lens.evidence.push(entry); | ||
| } | ||
| function markResidual(lens, summary, nextAction) { | ||
| if (IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET.has(lens.id)) | ||
| return; | ||
| lens.status = 'residual'; | ||
| lens.summary = summary; | ||
| if (nextAction) | ||
| lens.nextAction = nextAction; | ||
| } | ||
| function defaultOkSummary(id) { | ||
| switch (id) { | ||
| case 'soc': | ||
| return 'No separation-of-concerns residual detected from current sensors.'; | ||
| case 'cohesion': | ||
| return 'No cohesion residual (god-module / physical cohesion) from current sensors.'; | ||
| case 'coupling': | ||
| return 'No coupling residual (import edges, cycles, peer isolation) from current sensors.'; | ||
| case 'srp': | ||
| return 'No single-responsibility residual from current sensors.'; | ||
| case 'dip': | ||
| return 'No dependency-inversion residual (pure / capability / forbidden walls) from current sensors.'; | ||
| case 'ocp': | ||
| return 'Open/closed is not strongly instrumented — no switch-chain sensor.'; | ||
| case 'encapsulation': | ||
| return 'No encapsulation residual from ArkRules structure sensors.'; | ||
| case 'modularity': | ||
| return 'No modularity / placement residual from current sensors.'; | ||
| case 'maintainability': | ||
| return 'No maintainability residual (design-weak / baseline honesty) from current sensors.'; | ||
| case 'testability': | ||
| return 'No testability residual (impure domain / capability walls) from current sensors.'; | ||
| case 'domain': | ||
| return 'No domain-alignment residual from current sensors.'; | ||
| case 'stack': | ||
| return 'Stack practices are only partially instrumented (TypeScript / host / Ark idioms).'; | ||
| default: | ||
| return `${IMPROVEMENT_COMPASS_LENS_LABELS[id]} — no residual from current sensors.`; | ||
| } | ||
| } | ||
| export function createInitialImprovementCompassLenses() { | ||
| return IMPROVEMENT_LENS_IDS.map((id) => { | ||
| if (IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET.has(id)) { | ||
| const key = id; | ||
| return { | ||
| id, | ||
| status: 'out-of-scope', | ||
| summary: IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SUMMARIES[key], | ||
| evidence: [], | ||
| nextAction: { | ||
| kind: 'docs', | ||
| ref: 'docs/use.md#improvement-compass', | ||
| summary: 'Out of scope for ArkGate — use dedicated tooling outside the gate.', | ||
| }, | ||
| }; | ||
| } | ||
| if (id === 'ocp') { | ||
| return { | ||
| id, | ||
| status: 'not-instrumented', | ||
| summary: defaultOkSummary(id), | ||
| evidence: [], | ||
| }; | ||
| } | ||
| return { | ||
| id, | ||
| status: 'ok', | ||
| summary: defaultOkSummary(id), | ||
| evidence: [], | ||
| }; | ||
| }); | ||
| } | ||
| function mapDesignSmells(byId, smells) { | ||
| for (const smell of smells) { | ||
| const id = smellIdOf(smell); | ||
| if (!id) | ||
| continue; | ||
| const detail = smell.outcome || smell.message || undefined; | ||
| const evidencePaths = Array.isArray(smell.evidence) ? smell.evidence : []; | ||
| const pathHint = evidencePaths[0]; | ||
| const attach = (lensId, summary, action) => { | ||
| const lens = byId.get(lensId); | ||
| if (!lens || IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET.has(lensId)) | ||
| return; | ||
| pushEvidence(lens, 'designSmells', id, detail); | ||
| if (pathHint) | ||
| pushEvidence(lens, 'designSmells', pathHint, id); | ||
| markResidual(lens, summary, action); | ||
| }; | ||
| const shapeAction = { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Map Shape residual (shape-focus), then one extraction pilot with user OK.', | ||
| }; | ||
| const dipAction = { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Inject a port/adapter for I/O; keep domain pure.', | ||
| }; | ||
| switch (id) { | ||
| case 'domain-logic-in-ui': | ||
| attach('soc', 'Business rules still mix with UI or presentation surfaces.', shapeAction); | ||
| attach('domain', 'Domain logic lives outside Domain — align rules with Domain ownership.', shapeAction); | ||
| break; | ||
| case 'facade-sql-in-routes': | ||
| attach('soc', 'Routes/controllers own SQL or ORM access — concerns are mixed.', shapeAction); | ||
| attach('dip', 'Transport depends on concrete persistence instead of a port.', dipAction); | ||
| break; | ||
| case 'io-under-application': | ||
| attach('soc', 'Application/business code reaches I/O directly — separation is weak.', shapeAction); | ||
| attach('dip', 'I/O is not inverted behind ports/adapters.', dipAction); | ||
| attach('testability', 'Direct I/O under application code hurts pure unit testing.', dipAction); | ||
| break; | ||
| case 'handler-in-persistence': | ||
| attach('soc', 'HTTP/transport handlers live under persistence folders.', shapeAction); | ||
| break; | ||
| case 'god-module': | ||
| attach('cohesion', 'Large multi-responsibility modules reduce cohesion.', shapeAction); | ||
| attach('srp', 'God modules own too many responsibilities — split by concern (one pilot).', { | ||
| kind: 'skill', | ||
| ref: '/ark-autopilot', | ||
| summary: 'One Shape pilot with user OK — never multi-pilot batch.', | ||
| }); | ||
| break; | ||
| case 'mixed-pattern-cluster': | ||
| attach('modularity', 'Multiple layout styles coexist — placement is unclear for the next AI turn.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Pick a golden pattern and migrate one pilot cluster on touch.', | ||
| }); | ||
| attach('cohesion', 'Mixed layout styles scatter the same concern across patterns.', shapeAction); | ||
| break; | ||
| case 'soft-contract': | ||
| attach('maintainability', 'Soft contract walls (layers without deny rules) hide maintainability debt.', { | ||
| kind: 'skill', | ||
| ref: '/ark-contract', | ||
| summary: 'Add real layer rules so the AI has hard walls.', | ||
| }); | ||
| attach('coupling', 'Layers with files but almost no deny rules allow free peer coupling.', { | ||
| kind: 'skill', | ||
| ref: '/ark-contract', | ||
| summary: 'Tighten inter-layer allows/denies without weakening enforcement.', | ||
| }); | ||
| break; | ||
| default: | ||
| // Unknown smell ids still feed maintainability residual (honest residual, | ||
| // not out-of-scope invention). | ||
| attach('maintainability', 'Design residual remains under an unrecognized smell id — review evidence.', shapeAction); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| function isTypeOnlyPlacementDebt(v) { | ||
| // Product voice: type-only edges are placement debt (failsStrict:false), not runtime coupling. | ||
| if (v.failsStrict === false) | ||
| return true; | ||
| if (v.typeOnly === true) | ||
| return true; | ||
| return false; | ||
| } | ||
| function mapViolations(byId, violations) { | ||
| for (const v of violations) { | ||
| const ruleId = violationRuleId(v); | ||
| if (!ruleId) | ||
| continue; | ||
| const detail = v.message; | ||
| const upper = ruleId.toUpperCase(); | ||
| const attach = (lensId, summary, action) => { | ||
| const lens = byId.get(lensId); | ||
| if (!lens || IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET.has(lensId)) | ||
| return; | ||
| pushEvidence(lens, 'violations', ruleId, detail); | ||
| if (v.file) | ||
| pushEvidence(lens, 'violations', v.file, ruleId); | ||
| markResidual(lens, summary, action); | ||
| }; | ||
| // Type-only / non-blocking placement debt → modularity only (never coupling / DIP residual). | ||
| if (isTypeOnlyPlacementDebt(v)) { | ||
| attach('modularity', 'Type-only placement debt remains — prefer SharedTypes / owning layer (not runtime coupling).', { | ||
| kind: 'skill', | ||
| ref: '/ark-place', | ||
| summary: 'Place shared types in a layer both sides may import; type-only debt is not a value edge.', | ||
| }); | ||
| continue; | ||
| } | ||
| const edgeAction = { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Clear the active edge residual, then re-doctor.', | ||
| }; | ||
| if (upper === 'LAYER_IMPORT_VIOLATION' || | ||
| upper.includes('LAYER_IMPORT') || | ||
| upper === 'DYNAMIC_IMPORT_VIOLATION') { | ||
| attach('coupling', 'Import graph edges violate the layer contract.', edgeAction); | ||
| continue; | ||
| } | ||
| if (upper.includes('CYCLE') || upper === 'CIRCULAR_DEPENDENCY') { | ||
| attach('coupling', 'Import cycles couple modules tightly.', edgeAction); | ||
| continue; | ||
| } | ||
| if (upper.includes('PEER_ISOLATION') || upper === 'PEER_ISOLATION_VIOLATION') { | ||
| attach('coupling', 'Peer isolation residual — slices import each other freely.', { | ||
| kind: 'skill', | ||
| ref: '/ark-loop', | ||
| summary: 'Peer isolation fixes are judgment-class — one cluster at a time.', | ||
| }); | ||
| continue; | ||
| } | ||
| if (upper === 'FORBIDDEN_GLOBAL' || upper.startsWith('FORBIDDEN_')) { | ||
| attach('dip', 'Forbidden globals / effect surfaces break dependency inversion.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Inject a port instead of the forbidden global.', | ||
| }); | ||
| attach('testability', 'Forbidden ambient effects reduce pure-domain testability.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Replace ambient effects with injectable ports.', | ||
| }); | ||
| continue; | ||
| } | ||
| if (upper === 'CAPABILITY_VIOLATION') { | ||
| attach('dip', 'Denied capability use — invert through an allowed adapter/port.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Capability walls require port injection (judgment, not mechanical-safe).', | ||
| }); | ||
| attach('testability', 'Capability violations couple domain code to I/O — harder to unit-test.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Keep pure layers free of denied capabilities.', | ||
| }); | ||
| continue; | ||
| } | ||
| if (upper.startsWith('ARKRULE_') || upper === 'INVARIANT_UNCOVERED') { | ||
| attach('encapsulation', 'ArkRules structure / invariant residual inside a layer.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Label [ArkRules]; structure fixes are judgment — never invent mechanical-safe.', | ||
| }); | ||
| attach('domain', 'Intra-layer domain structure or invariant coverage residual.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Inventory candidates → one ArkRules pilot with coverage evidence.', | ||
| }); | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
| function mapCountsAndFlags(byId, facts) { | ||
| const cycleCount = Number(facts.cycleCount) || 0; | ||
| if (cycleCount > 0) { | ||
| const lens = byId.get('coupling'); | ||
| pushEvidence(lens, 'cycles', `count:${cycleCount}`); | ||
| markResidual(lens, 'Import cycles couple modules tightly.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Break cycles with a judgment extraction — one pilot.', | ||
| }); | ||
| } | ||
| const peer = typeof facts.peerIsolationCount === 'boolean' | ||
| ? facts.peerIsolationCount | ||
| ? 1 | ||
| : 0 | ||
| : Number(facts.peerIsolationCount) || 0; | ||
| if (peer > 0) { | ||
| const lens = byId.get('coupling'); | ||
| pushEvidence(lens, 'peerIsolation', `count:${peer}`); | ||
| markResidual(lens, 'Peer isolation residual remains.', { | ||
| kind: 'skill', | ||
| ref: '/ark-loop', | ||
| summary: 'Peer isolation is judgment-class residual.', | ||
| }); | ||
| } | ||
| const pc = Number(facts.physicalCohesionFindingCount) || 0; | ||
| if (pc > 0) { | ||
| const cohesion = byId.get('cohesion'); | ||
| pushEvidence(cohesion, 'physicalCohesion', `findings:${pc}`); | ||
| markResidual(cohesion, 'Physical cohesion residual — mirrored concept clusters across anchors.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Review reshape pilot; one decision-aware pilot at a time.', | ||
| }); | ||
| const srp = byId.get('srp'); | ||
| pushEvidence(srp, 'physicalCohesion', `findings:${pc}`); | ||
| markResidual(srp, 'Mirrored clusters suggest split-by-concern residual (architecture SRP).', { | ||
| kind: 'skill', | ||
| ref: '/ark-autopilot', | ||
| summary: 'One reshape/extraction pilot with user OK.', | ||
| }); | ||
| } | ||
| const pureN = Number(facts.pureOrCapabilityResidual) || 0; | ||
| const fgN = Number(facts.forbiddenGlobalResidual) || 0; | ||
| if (pureN > 0 || fgN > 0) { | ||
| const dip = byId.get('dip'); | ||
| if (pureN > 0) | ||
| pushEvidence(dip, 'capability', `residual:${pureN}`); | ||
| if (fgN > 0) | ||
| pushEvidence(dip, 'forbiddenGlobals', `residual:${fgN}`); | ||
| markResidual(dip, 'Pure / capability / forbidden residual weakens dependency inversion.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Inject ports; keep pure layers free of effects.', | ||
| }); | ||
| const test = byId.get('testability'); | ||
| if (pureN > 0) | ||
| pushEvidence(test, 'capability', `residual:${pureN}`); | ||
| if (fgN > 0) | ||
| pushEvidence(test, 'forbiddenGlobals', `residual:${fgN}`); | ||
| markResidual(test, 'Impure domain or capability residual reduces testability.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Prefer ports over concrete I/O in pure/domain modules.', | ||
| }); | ||
| } | ||
| const arkN = Number(facts.arkRulesStructureResidual) || 0; | ||
| if (arkN > 0) { | ||
| const enc = byId.get('encapsulation'); | ||
| pushEvidence(enc, 'arkRules', `structureResidual:${arkN}`); | ||
| markResidual(enc, 'ArkRules structure residual — encapsulation inside the layer.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Fix structure sensors under [ArkRules] without inventing mechanical-safe.', | ||
| }); | ||
| const domain = byId.get('domain'); | ||
| pushEvidence(domain, 'arkRules', `structureResidual:${arkN}`); | ||
| markResidual(domain, 'ArkRules residual may mean domain shape is not yet under contract.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Map inventory candidates; one pilot rule at a time.', | ||
| }); | ||
| } | ||
| else if (facts.arkRulesLoaded === false || facts.arkRulesLoaded == null) { | ||
| // No ArkRules → encapsulation stays ok (absence is valid), not residual. | ||
| // Domain remains ok unless other evidence marked it. | ||
| } | ||
| if (facts.designWeak === true) { | ||
| const m = byId.get('maintainability'); | ||
| pushEvidence(m, 'designFitness', 'design-weak'); | ||
| markResidual(m, 'Design-weak: checked edges may be clean, but design residual remains — not finished.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Shape door: explore shape-focus → dual-plan B → one pilot with OK.', | ||
| }); | ||
| } | ||
| if (facts.dirtyBaselineRisk === true || (Number(facts.baselineStale) || 0) > 0) { | ||
| const m = byId.get('maintainability'); | ||
| if (facts.dirtyBaselineRisk === true) { | ||
| pushEvidence(m, 'baseline', 'dirty-freeze-risk'); | ||
| } | ||
| if ((Number(facts.baselineStale) || 0) > 0) { | ||
| pushEvidence(m, 'baseline', `stale:${facts.baselineStale}`); | ||
| } | ||
| markResidual(m, 'Baseline honesty residual — frozen debt or stale keys need review.', { | ||
| kind: 'command', | ||
| ref: 'ark-check --doctor', | ||
| summary: 'Review baseline freeze honesty; do not freeze new wrong debt.', | ||
| }); | ||
| } | ||
| // Large frozen residual (baseline exists with many freezes) is maintainability debt — | ||
| // only when there is already a residual signal or a substantial freeze surface. | ||
| const frozenN = Number(facts.frozenResidual) || 0; | ||
| if (facts.baselineExists === true && | ||
| frozenN >= 10 && | ||
| byId.get('maintainability').status !== 'residual') { | ||
| const m = byId.get('maintainability'); | ||
| pushEvidence(m, 'baseline', `frozen:${frozenN}`); | ||
| markResidual(m, 'Substantial frozen residual remains under the baseline — review debt honestly.', { | ||
| kind: 'command', | ||
| ref: 'ark-check --doctor', | ||
| summary: 'Review freezes; do not freeze new wrong debt to clear residual.', | ||
| }); | ||
| } | ||
| const ungov = Number(facts.ungovernedDirCount) || 0; | ||
| const emptyL = Number(facts.emptyLayerCount) || 0; | ||
| if (ungov > 0 || emptyL > 0) { | ||
| const mod = byId.get('modularity'); | ||
| if (ungov > 0) | ||
| pushEvidence(mod, 'coverage', `ungovernedDirs:${ungov}`); | ||
| if (emptyL > 0) | ||
| pushEvidence(mod, 'coverage', `emptyLayers:${emptyL}`); | ||
| markResidual(mod, 'Placement / modularity residual — ungoverned dirs or empty layer globs.', { | ||
| kind: 'skill', | ||
| ref: '/ark-contract', | ||
| summary: 'Classify ungoverned paths; fix empty layer patterns.', | ||
| }); | ||
| } | ||
| // Missing golden pattern under design-weak → modularity residual (AI placement cue). | ||
| if (facts.designWeak === true && facts.goldenPatternPresent === false) { | ||
| const mod = byId.get('modularity'); | ||
| pushEvidence(mod, 'goldenPattern', 'absent'); | ||
| markResidual(mod, 'Design-weak without a golden pattern — new code lacks a placement norm for the AI.', { | ||
| kind: 'skill', | ||
| ref: '/ark-place', | ||
| summary: 'Record an advisory golden pattern for new code (does not clear design-weak).', | ||
| }); | ||
| } | ||
| // Stack: TypeScript host partially instrumented; unknown → not-instrumented. | ||
| const stack = byId.get('stack'); | ||
| const kind = facts.stackKind ?? null; | ||
| if (kind === 'typescript') { | ||
| // Partial instrumentation is still honest `ok` when no residual evidence. | ||
| if (stack.status === 'ok') { | ||
| stack.summary = | ||
| 'Stack practices are partially instrumented for TypeScript / host / Ark idioms only — not a full framework checklist.'; | ||
| } | ||
| } | ||
| else { | ||
| stack.status = 'not-instrumented'; | ||
| stack.summary = | ||
| 'Stack-specific best practices outside TypeScript/host/Ark idioms are not instrumented.'; | ||
| stack.evidence = []; | ||
| stack.nextAction = { | ||
| kind: 'docs', | ||
| ref: 'docs/use.md#improvement-compass', | ||
| summary: 'Ark does not score non-TS stack idioms.', | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Project all supplied doctor-side facts onto the mutable lens map. | ||
| * Input-order independent for smells and violations (sorted inside). | ||
| */ | ||
| export function projectImprovementCompassFacts(byId, facts) { | ||
| if (Array.isArray(facts.designSmells) && facts.designSmells.length > 0) { | ||
| const smells = [...facts.designSmells].sort((a, b) => smellIdOf(a).localeCompare(smellIdOf(b))); | ||
| mapDesignSmells(byId, smells); | ||
| } | ||
| if (Array.isArray(facts.violations) && facts.violations.length > 0) { | ||
| const violations = [...facts.violations].sort((a, b) => { | ||
| const ra = violationRuleId(a).localeCompare(violationRuleId(b)); | ||
| if (ra !== 0) | ||
| return ra; | ||
| return String(a.file ?? '').localeCompare(String(b.file ?? '')); | ||
| }); | ||
| mapViolations(byId, violations); | ||
| } | ||
| mapCountsAndFlags(byId, facts); | ||
| } | ||
| /** Hard lock: out-of-scope can never be residual, even if bad facts arrive. */ | ||
| export function lockImprovementCompassOutOfScope(byId) { | ||
| for (const id of IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES) { | ||
| const lens = byId.get(id); | ||
| lens.status = 'out-of-scope'; | ||
| lens.summary = IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SUMMARIES[id]; | ||
| lens.evidence = []; | ||
| lens.nextAction = { | ||
| kind: 'docs', | ||
| ref: 'docs/use.md#improvement-compass', | ||
| summary: 'Out of scope for ArkGate — use dedicated tooling outside the gate.', | ||
| }; | ||
| } | ||
| } | ||
| /** Stable evidence order per lens (source then ref). */ | ||
| export function sortImprovementCompassEvidence(lenses) { | ||
| for (const lens of lenses) { | ||
| lens.evidence.sort((a, b) => { | ||
| const s = a.source.localeCompare(b.source); | ||
| if (s !== 0) | ||
| return s; | ||
| return a.ref.localeCompare(b.ref); | ||
| }); | ||
| } | ||
| } | ||
| export function finalizeImprovementCompassTopResidual(lenses) { | ||
| const residual = lenses | ||
| .filter((l) => l.status === 'residual' && !IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET.has(l.id)) | ||
| .slice() | ||
| .sort((a, b) => { | ||
| const pa = IMPROVEMENT_COMPASS_RESIDUAL_SORT_PRIORITY[a.id] ?? 150; | ||
| const pb = IMPROVEMENT_COMPASS_RESIDUAL_SORT_PRIORITY[b.id] ?? 150; | ||
| if (pa !== pb) | ||
| return pa - pb; | ||
| return a.id.localeCompare(b.id); | ||
| }); | ||
| return residual | ||
| .slice(0, IMPROVEMENT_COMPASS_TOP_RESIDUAL_CAP) | ||
| .map((l) => l.id); | ||
| } |
| /** | ||
| * GENERATED FILE — do not edit by hand. | ||
| * | ||
| * Canonical algorithm: src/domain/improvementCompassTypes.ts | ||
| * Regenerate: node scripts/generate-cli-pure.mjs | ||
| * Drift check: node scripts/generate-cli-pure.mjs --check | ||
| * | ||
| * Pure CLI helper (bin/lib/improvement-compass-types.mjs). Zero Node I/O. | ||
| */ | ||
| export const ARK_IMPROVEMENT_COMPASS_SCHEMA_VERSION = '1.0'; | ||
| /** Closed 15 lens ids (stable order for projection). */ | ||
| export const IMPROVEMENT_LENS_IDS = [ | ||
| 'soc', | ||
| 'cohesion', | ||
| 'coupling', | ||
| 'srp', | ||
| 'dip', | ||
| 'ocp', | ||
| 'encapsulation', | ||
| 'modularity', | ||
| 'scalability', | ||
| 'resilience', | ||
| 'security', | ||
| 'maintainability', | ||
| 'testability', | ||
| 'domain', | ||
| 'stack', | ||
| ]; | ||
| /** Cap for topResidual — short, agent-legible list (not a ranking score). */ | ||
| export const IMPROVEMENT_COMPASS_TOP_RESIDUAL_CAP = 5; | ||
| /** Locked out-of-scope — never become residual from missing sensors. */ | ||
| export const IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES = [ | ||
| 'scalability', | ||
| 'resilience', | ||
| 'security', | ||
| ]; | ||
| /** Shared out-of-scope set for mappers and build. */ | ||
| export const IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SET = new Set(IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES); | ||
| /** | ||
| * Residual sort priority (lower = earlier in topResidual). Product relevance, | ||
| * not a health score. Ties break by id. | ||
| */ | ||
| export const IMPROVEMENT_COMPASS_RESIDUAL_SORT_PRIORITY = { | ||
| soc: 10, | ||
| coupling: 20, | ||
| dip: 30, | ||
| domain: 40, | ||
| srp: 50, | ||
| cohesion: 60, | ||
| encapsulation: 70, | ||
| modularity: 80, | ||
| testability: 90, | ||
| maintainability: 100, | ||
| ocp: 110, | ||
| stack: 120, | ||
| scalability: 200, | ||
| resilience: 200, | ||
| security: 200, | ||
| }; | ||
| export const IMPROVEMENT_COMPASS_LENS_LABELS = { | ||
| soc: 'Separation of concerns', | ||
| cohesion: 'High cohesion', | ||
| coupling: 'Low coupling', | ||
| srp: 'Single responsibility (architecture)', | ||
| dip: 'Dependency inversion', | ||
| ocp: 'Open/closed', | ||
| encapsulation: 'Encapsulation', | ||
| modularity: 'Modularity', | ||
| scalability: 'Scalability / performance', | ||
| resilience: 'Resilience / fault tolerance', | ||
| security: 'Security by design', | ||
| maintainability: 'Maintainability', | ||
| testability: 'Testability', | ||
| domain: 'Domain alignment', | ||
| stack: 'Stack-specific practices', | ||
| }; | ||
| export const IMPROVEMENT_COMPASS_OUT_OF_SCOPE_SUMMARIES = { | ||
| scalability: 'ArkGate does not measure performance or horizontal scale. Use load tests and APM outside Ark.', | ||
| resilience: 'ArkGate does not measure app resilience or chaos readiness. Structural boundaries and optional experimental runtime are not a resilience score.', | ||
| security: 'ArkGate does not run SAST or app-security tooling. Structural least-privilege of effects is partial only — not a security rating.', | ||
| }; | ||
| export function improvementCompassHumanLabel(id) { | ||
| return IMPROVEMENT_COMPASS_LENS_LABELS[id] ?? id; | ||
| } |
| /** | ||
| * DF05 — managed-upgrade self-service honesty (one residual pilot). | ||
| * | ||
| * Self-service criterion (must stay answerable from package surfaces without a maintainer): | ||
| * After a managed upgrade (or equivalent), can a consumer learn from package surfaces whether | ||
| * the write-path is still honestly labeled active/advisory and whether customized install | ||
| * content was preserved — without asking a maintainer? | ||
| * | ||
| * This module projects that answer onto `ark upgrade` JSON/human output: | ||
| * - write-path activation labels per selected host (hard | advisory | unavailable) | ||
| * - customized (and conflicted) content-identity preserve proof | ||
| * | ||
| * Soft hosts never claim hard. Upgrade never invents hardWriteActive from disk alone — | ||
| * hard requires runtime evidence elsewhere (hooks/doctor/status); upgrade labels fail-closed. | ||
| * Always notAScore; never a gate input. | ||
| */ | ||
| import { getHostSupportProfile } from './host-support-matrix.mjs'; | ||
| import { classifyStatusWritePath, defaultHonestLabel } from './status-manifest.mjs'; | ||
| /** | ||
| * @param {string} host | ||
| * @param {{ hardWriteActive?: boolean }} [evidence] | ||
| * @returns {{ | ||
| * host: string, | ||
| * writePath: 'hard'|'advisory'|'unavailable', | ||
| * softWriteHost: boolean, | ||
| * hardWriteSupported: boolean, | ||
| * hardWriteActive: boolean, | ||
| * label: string, | ||
| * }} | ||
| */ | ||
| export function projectHostWritePathActivation(host, evidence = {}) { | ||
| const normalized = typeof host === 'string' ? host.trim().toLowerCase() : ''; | ||
| if (!normalized) { | ||
| return { | ||
| host: 'unknown', | ||
| writePath: 'unavailable', | ||
| softWriteHost: false, | ||
| hardWriteSupported: false, | ||
| hardWriteActive: false, | ||
| label: defaultHonestLabel('unavailable', null), | ||
| }; | ||
| } | ||
| const profile = getHostSupportProfile(normalized); | ||
| if (!profile) { | ||
| return { | ||
| host: normalized, | ||
| writePath: 'unavailable', | ||
| softWriteHost: false, | ||
| hardWriteSupported: false, | ||
| hardWriteActive: false, | ||
| label: defaultHonestLabel('unavailable', normalized), | ||
| }; | ||
| } | ||
| const hardWriteSupported = profile.capabilities?.['hard-write'] === true; | ||
| const softWriteHost = !hardWriteSupported; | ||
| // Fail-closed: soft never hard; hard only when caller supplies proven active evidence. | ||
| const hardWriteActive = | ||
| !softWriteHost && hardWriteSupported && evidence.hardWriteActive === true; | ||
| const writePath = classifyStatusWritePath({ | ||
| softWriteHost, | ||
| hardWriteActive, | ||
| activeHost: normalized, | ||
| }); | ||
| return { | ||
| host: normalized, | ||
| writePath, | ||
| softWriteHost, | ||
| hardWriteSupported, | ||
| hardWriteActive, | ||
| label: defaultHonestLabel(writePath, normalized), | ||
| }; | ||
| } | ||
| /** | ||
| * Project self-service honesty facts from a managed upgrade plan. | ||
| * | ||
| * @param {{ | ||
| * hosts?: string[], | ||
| * assets?: Array<{ path?: string, state?: string, willApply?: boolean, blocked?: boolean }>, | ||
| * summary?: { customizedPreserved?: number, blocked?: number, states?: Record<string, number> }, | ||
| * }} plan | ||
| * @param {{ | ||
| * hardWriteActiveByHost?: Record<string, boolean>, | ||
| * }} [options] | ||
| * @returns {{ | ||
| * schemaVersion: '1.0', | ||
| * notAScore: true, | ||
| * criterionId: 'df05-upgrade-activation-preserve', | ||
| * customizedPreserved: number, | ||
| * customizedPaths: string[], | ||
| * conflictedPaths: string[], | ||
| * customizedContentPreserved: boolean, | ||
| * writePathActivation: ReturnType<typeof projectHostWritePathActivation>[], | ||
| * writePathHonestlyLabeled: boolean, | ||
| * answers: { | ||
| * writePathActivationLabeled: boolean, | ||
| * customizedContentPreserved: boolean, | ||
| * }, | ||
| * }} | ||
| */ | ||
| export function projectManagedUpgradeSelfServiceHonesty(plan, options = {}) { | ||
| const assets = Array.isArray(plan?.assets) ? plan.assets : []; | ||
| const hosts = Array.isArray(plan?.hosts) ? plan.hosts : []; | ||
| const hardByHost = | ||
| options.hardWriteActiveByHost && typeof options.hardWriteActiveByHost === 'object' | ||
| ? options.hardWriteActiveByHost | ||
| : {}; | ||
| const customizedPaths = assets | ||
| .filter((asset) => asset?.state === 'customized' && typeof asset.path === 'string') | ||
| .map((asset) => asset.path) | ||
| .sort(); | ||
| const conflictedPaths = assets | ||
| .filter((asset) => asset?.state === 'conflicted' && typeof asset.path === 'string') | ||
| .map((asset) => asset.path) | ||
| .sort(); | ||
| // Preserve contract: customized assets must never be scheduled writes without consent. | ||
| const customizedContentPreserved = assets | ||
| .filter((asset) => asset?.state === 'customized' || asset?.state === 'conflicted') | ||
| .every((asset) => asset.willApply !== true); | ||
| const summaryCount = | ||
| typeof plan?.summary?.customizedPreserved === 'number' | ||
| ? plan.summary.customizedPreserved | ||
| : customizedPaths.length; | ||
| const writePathActivation = hosts.map((host) => { | ||
| const key = typeof host === 'string' ? host.trim().toLowerCase() : ''; | ||
| return projectHostWritePathActivation(host, { | ||
| hardWriteActive: hardByHost[key] === true, | ||
| }); | ||
| }); | ||
| // Soft hosts never labeled hard; hard only when evidence supplied. | ||
| const writePathHonestlyLabeled = writePathActivation.every((entry) => { | ||
| if (entry.softWriteHost && entry.writePath === 'hard') return false; | ||
| if (entry.softWriteHost && entry.hardWriteActive) return false; | ||
| if (!entry.hardWriteSupported && entry.writePath === 'hard') return false; | ||
| if (entry.writePath === 'hard' && !entry.hardWriteActive) return false; | ||
| return true; | ||
| }); | ||
| const answers = { | ||
| // Empty host list is not "labeled activation" — only claim labeled when hosts were projected. | ||
| writePathActivationLabeled: | ||
| writePathActivation.length > 0 && writePathHonestlyLabeled, | ||
| customizedContentPreserved, | ||
| }; | ||
| return { | ||
| schemaVersion: '1.0', | ||
| notAScore: true, | ||
| criterionId: 'df05-upgrade-activation-preserve', | ||
| customizedPreserved: summaryCount, | ||
| customizedPaths, | ||
| conflictedPaths, | ||
| customizedContentPreserved, | ||
| writePathActivation, | ||
| writePathHonestlyLabeled, | ||
| answers, | ||
| }; | ||
| } | ||
| /** | ||
| * Human one-liner block for upgrade preview/apply (stdout). | ||
| * @param {ReturnType<typeof projectManagedUpgradeSelfServiceHonesty>} honesty | ||
| */ | ||
| export function formatManagedUpgradeSelfServiceHonesty(honesty) { | ||
| if (!honesty) return []; | ||
| const lines = ['Self-service honesty (no maintainer required):']; | ||
| if (honesty.writePathActivation.length === 0) { | ||
| lines.push(' Write-path: shared/gates only (no host selected) — activation unavailable.'); | ||
| } else { | ||
| for (const entry of honesty.writePathActivation) { | ||
| const soft = entry.softWriteHost ? 'soft host' : 'hard-capable'; | ||
| const active = entry.hardWriteActive ? 'active' : 'not proven this invocation'; | ||
| lines.push( | ||
| ` Write-path ${entry.host}: ${entry.writePath} (${soft}; hard ${active}).` | ||
| ); | ||
| } | ||
| } | ||
| if (honesty.customizedPaths.length > 0) { | ||
| lines.push( | ||
| ` Customized preserved: ${honesty.customizedPreserved} (${honesty.customizedPaths.join(', ')}).` | ||
| ); | ||
| } else { | ||
| lines.push( | ||
| ` Customized preserved: ${honesty.customizedPreserved} (no customized managed assets).` | ||
| ); | ||
| } | ||
| if (honesty.conflictedPaths.length > 0) { | ||
| lines.push( | ||
| ` Conflicted (consent required): ${honesty.conflictedPaths.join(', ')}.` | ||
| ); | ||
| } | ||
| return lines; | ||
| } |
+25
-12
@@ -262,2 +262,17 @@ /** | ||
| /** | ||
| * PeerIsolation deny decision given resolved path/slice evidence (DF04 pure core). | ||
| * | ||
| * Fail-closed: missing path, no classifiable folders, or unclassifiable either | ||
| * side → deny. Same-slice → allow (return false). Cross-slice → deny. | ||
| */ | ||
| export function peerIsolationMustDeny(input) { | ||
| if (!input.fromPath || !input.toPath) | ||
| return true; | ||
| if (input.folderCount <= 0) | ||
| return true; | ||
| if (!input.fromSlice || !input.toSlice) | ||
| return true; | ||
| return input.fromSlice !== input.toSlice; | ||
| } | ||
| /** | ||
| * Find the first denying rule for a layer edge. | ||
@@ -283,16 +298,14 @@ * | ||
| const toPath = options?.toPath; | ||
| // Isolation is active: without both paths we cannot prove same-slice. | ||
| if (!fromPath || !toPath) | ||
| return rule; | ||
| const folders = resolveSliceFolders(rule, from, options?.layers); | ||
| // Configured isolation without classifiable folders cannot allow. | ||
| if (folders.length === 0) | ||
| const fromSlice = fromPath && toPath ? sliceIdForPath(fromPath, folders) : undefined; | ||
| const toSlice = fromPath && toPath ? sliceIdForPath(toPath, folders) : undefined; | ||
| if (peerIsolationMustDeny({ | ||
| fromPath, | ||
| toPath, | ||
| folderCount: folders.length, | ||
| fromSlice, | ||
| toSlice, | ||
| })) { | ||
| return rule; | ||
| const fromSlice = sliceIdForPath(fromPath, folders); | ||
| const toSlice = sliceIdForPath(toPath, folders); | ||
| // Unclassifiable either side: cannot prove same-slice → deny. | ||
| if (!fromSlice || !toSlice) | ||
| return rule; | ||
| if (fromSlice !== toSlice) | ||
| return rule; | ||
| } | ||
| continue; // same slice: this peerIsolation rule does not deny | ||
@@ -299,0 +312,0 @@ } |
@@ -11,524 +11,6 @@ /** | ||
| export const ARK_IMPROVEMENT_COMPASS_SCHEMA_VERSION = '1.0'; | ||
| /** Closed 15 lens ids (stable order for projection). */ | ||
| export const IMPROVEMENT_LENS_IDS = [ | ||
| 'soc', | ||
| 'cohesion', | ||
| 'coupling', | ||
| 'srp', | ||
| 'dip', | ||
| 'ocp', | ||
| 'encapsulation', | ||
| 'modularity', | ||
| 'scalability', | ||
| 'resilience', | ||
| 'security', | ||
| 'maintainability', | ||
| 'testability', | ||
| 'domain', | ||
| 'stack', | ||
| ]; | ||
| /** Cap for topResidual — short, agent-legible list (not a ranking score). */ | ||
| export const IMPROVEMENT_COMPASS_TOP_RESIDUAL_CAP = 5; | ||
| /** Locked out-of-scope — never become residual from missing sensors. */ | ||
| export const IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES = [ | ||
| 'scalability', | ||
| 'resilience', | ||
| 'security', | ||
| ]; | ||
| const OUT_OF_SCOPE_SET = new Set(IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES); | ||
| export { ARK_IMPROVEMENT_COMPASS_SCHEMA_VERSION, IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES, IMPROVEMENT_COMPASS_TOP_RESIDUAL_CAP, IMPROVEMENT_LENS_IDS, } from './improvement-compass-types.mjs'; | ||
| import { ARK_IMPROVEMENT_COMPASS_SCHEMA_VERSION, IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES, improvementCompassHumanLabel, } from './improvement-compass-types.mjs'; | ||
| import { createInitialImprovementCompassLenses, finalizeImprovementCompassTopResidual, lockImprovementCompassOutOfScope, projectImprovementCompassFacts, sortImprovementCompassEvidence, } from './improvement-compass-map.mjs'; | ||
| /** | ||
| * Residual sort priority (lower = earlier in topResidual). Product relevance, | ||
| * not a health score. Ties break by id. | ||
| */ | ||
| const RESIDUAL_SORT_PRIORITY = { | ||
| soc: 10, | ||
| coupling: 20, | ||
| dip: 30, | ||
| domain: 40, | ||
| srp: 50, | ||
| cohesion: 60, | ||
| encapsulation: 70, | ||
| modularity: 80, | ||
| testability: 90, | ||
| maintainability: 100, | ||
| ocp: 110, | ||
| stack: 120, | ||
| scalability: 200, | ||
| resilience: 200, | ||
| security: 200, | ||
| }; | ||
| const LENS_LABELS = { | ||
| soc: 'Separation of concerns', | ||
| cohesion: 'High cohesion', | ||
| coupling: 'Low coupling', | ||
| srp: 'Single responsibility (architecture)', | ||
| dip: 'Dependency inversion', | ||
| ocp: 'Open/closed', | ||
| encapsulation: 'Encapsulation', | ||
| modularity: 'Modularity', | ||
| scalability: 'Scalability / performance', | ||
| resilience: 'Resilience / fault tolerance', | ||
| security: 'Security by design', | ||
| maintainability: 'Maintainability', | ||
| testability: 'Testability', | ||
| domain: 'Domain alignment', | ||
| stack: 'Stack-specific practices', | ||
| }; | ||
| const OUT_OF_SCOPE_SUMMARIES = { | ||
| scalability: 'ArkGate does not measure performance or horizontal scale. Use load tests and APM outside Ark.', | ||
| resilience: 'ArkGate does not measure app resilience or chaos readiness. Structural boundaries and optional experimental runtime are not a resilience score.', | ||
| security: 'ArkGate does not run SAST or app-security tooling. Structural least-privilege of effects is partial only — not a security rating.', | ||
| }; | ||
| function smellIdOf(smell) { | ||
| const raw = smell.id ?? smell.smellId ?? ''; | ||
| return typeof raw === 'string' ? raw.trim() : ''; | ||
| } | ||
| function violationRuleId(v) { | ||
| const raw = v.ruleId ?? v.code ?? ''; | ||
| return typeof raw === 'string' ? raw.trim() : ''; | ||
| } | ||
| function pushEvidence(lens, source, ref, detail) { | ||
| if (!ref) | ||
| return; | ||
| // Dedup by source+ref for deterministic stability. | ||
| if (lens.evidence.some((e) => e.source === source && e.ref === ref)) | ||
| return; | ||
| const entry = { source, ref }; | ||
| if (detail && detail.trim()) | ||
| entry.detail = detail.trim().slice(0, 240); | ||
| lens.evidence.push(entry); | ||
| } | ||
| function markResidual(lens, summary, nextAction) { | ||
| if (OUT_OF_SCOPE_SET.has(lens.id)) | ||
| return; | ||
| lens.status = 'residual'; | ||
| lens.summary = summary; | ||
| if (nextAction) | ||
| lens.nextAction = nextAction; | ||
| } | ||
| function defaultOkSummary(id) { | ||
| switch (id) { | ||
| case 'soc': | ||
| return 'No separation-of-concerns residual detected from current sensors.'; | ||
| case 'cohesion': | ||
| return 'No cohesion residual (god-module / physical cohesion) from current sensors.'; | ||
| case 'coupling': | ||
| return 'No coupling residual (import edges, cycles, peer isolation) from current sensors.'; | ||
| case 'srp': | ||
| return 'No single-responsibility residual from current sensors.'; | ||
| case 'dip': | ||
| return 'No dependency-inversion residual (pure / capability / forbidden walls) from current sensors.'; | ||
| case 'ocp': | ||
| return 'Open/closed is not strongly instrumented — no switch-chain sensor.'; | ||
| case 'encapsulation': | ||
| return 'No encapsulation residual from ArkRules structure sensors.'; | ||
| case 'modularity': | ||
| return 'No modularity / placement residual from current sensors.'; | ||
| case 'maintainability': | ||
| return 'No maintainability residual (design-weak / baseline honesty) from current sensors.'; | ||
| case 'testability': | ||
| return 'No testability residual (impure domain / capability walls) from current sensors.'; | ||
| case 'domain': | ||
| return 'No domain-alignment residual from current sensors.'; | ||
| case 'stack': | ||
| return 'Stack practices are only partially instrumented (TypeScript / host / Ark idioms).'; | ||
| default: | ||
| return `${LENS_LABELS[id]} — no residual from current sensors.`; | ||
| } | ||
| } | ||
| function initLenses() { | ||
| return IMPROVEMENT_LENS_IDS.map((id) => { | ||
| if (OUT_OF_SCOPE_SET.has(id)) { | ||
| const key = id; | ||
| return { | ||
| id, | ||
| status: 'out-of-scope', | ||
| summary: OUT_OF_SCOPE_SUMMARIES[key], | ||
| evidence: [], | ||
| nextAction: { | ||
| kind: 'docs', | ||
| ref: 'docs/use.md#improvement-compass', | ||
| summary: 'Out of scope for ArkGate — use dedicated tooling outside the gate.', | ||
| }, | ||
| }; | ||
| } | ||
| if (id === 'ocp') { | ||
| return { | ||
| id, | ||
| status: 'not-instrumented', | ||
| summary: defaultOkSummary(id), | ||
| evidence: [], | ||
| }; | ||
| } | ||
| return { | ||
| id, | ||
| status: 'ok', | ||
| summary: defaultOkSummary(id), | ||
| evidence: [], | ||
| }; | ||
| }); | ||
| } | ||
| function mapDesignSmells(byId, smells) { | ||
| for (const smell of smells) { | ||
| const id = smellIdOf(smell); | ||
| if (!id) | ||
| continue; | ||
| const detail = smell.outcome || smell.message || undefined; | ||
| const evidencePaths = Array.isArray(smell.evidence) ? smell.evidence : []; | ||
| const pathHint = evidencePaths[0]; | ||
| const attach = (lensId, summary, action) => { | ||
| const lens = byId.get(lensId); | ||
| if (!lens || OUT_OF_SCOPE_SET.has(lensId)) | ||
| return; | ||
| pushEvidence(lens, 'designSmells', id, detail); | ||
| if (pathHint) | ||
| pushEvidence(lens, 'designSmells', pathHint, id); | ||
| markResidual(lens, summary, action); | ||
| }; | ||
| const shapeAction = { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Map Shape residual (shape-focus), then one extraction pilot with user OK.', | ||
| }; | ||
| const dipAction = { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Inject a port/adapter for I/O; keep domain pure.', | ||
| }; | ||
| switch (id) { | ||
| case 'domain-logic-in-ui': | ||
| attach('soc', 'Business rules still mix with UI or presentation surfaces.', shapeAction); | ||
| attach('domain', 'Domain logic lives outside Domain — align rules with Domain ownership.', shapeAction); | ||
| break; | ||
| case 'facade-sql-in-routes': | ||
| attach('soc', 'Routes/controllers own SQL or ORM access — concerns are mixed.', shapeAction); | ||
| attach('dip', 'Transport depends on concrete persistence instead of a port.', dipAction); | ||
| break; | ||
| case 'io-under-application': | ||
| attach('soc', 'Application/business code reaches I/O directly — separation is weak.', shapeAction); | ||
| attach('dip', 'I/O is not inverted behind ports/adapters.', dipAction); | ||
| attach('testability', 'Direct I/O under application code hurts pure unit testing.', dipAction); | ||
| break; | ||
| case 'handler-in-persistence': | ||
| attach('soc', 'HTTP/transport handlers live under persistence folders.', shapeAction); | ||
| break; | ||
| case 'god-module': | ||
| attach('cohesion', 'Large multi-responsibility modules reduce cohesion.', shapeAction); | ||
| attach('srp', 'God modules own too many responsibilities — split by concern (one pilot).', { | ||
| kind: 'skill', | ||
| ref: '/ark-autopilot', | ||
| summary: 'One Shape pilot with user OK — never multi-pilot batch.', | ||
| }); | ||
| break; | ||
| case 'mixed-pattern-cluster': | ||
| attach('modularity', 'Multiple layout styles coexist — placement is unclear for the next AI turn.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Pick a golden pattern and migrate one pilot cluster on touch.', | ||
| }); | ||
| attach('cohesion', 'Mixed layout styles scatter the same concern across patterns.', shapeAction); | ||
| break; | ||
| case 'soft-contract': | ||
| attach('maintainability', 'Soft contract walls (layers without deny rules) hide maintainability debt.', { | ||
| kind: 'skill', | ||
| ref: '/ark-contract', | ||
| summary: 'Add real layer rules so the AI has hard walls.', | ||
| }); | ||
| attach('coupling', 'Layers with files but almost no deny rules allow free peer coupling.', { | ||
| kind: 'skill', | ||
| ref: '/ark-contract', | ||
| summary: 'Tighten inter-layer allows/denies without weakening enforcement.', | ||
| }); | ||
| break; | ||
| default: | ||
| // Unknown smell ids still feed maintainability residual (honest residual, | ||
| // not out-of-scope invention). | ||
| attach('maintainability', 'Design residual remains under an unrecognized smell id — review evidence.', shapeAction); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| function isTypeOnlyPlacementDebt(v) { | ||
| // Product voice: type-only edges are placement debt (failsStrict:false), not runtime coupling. | ||
| if (v.failsStrict === false) | ||
| return true; | ||
| if (v.typeOnly === true) | ||
| return true; | ||
| return false; | ||
| } | ||
| function mapViolations(byId, violations) { | ||
| for (const v of violations) { | ||
| const ruleId = violationRuleId(v); | ||
| if (!ruleId) | ||
| continue; | ||
| const detail = v.message; | ||
| const upper = ruleId.toUpperCase(); | ||
| const attach = (lensId, summary, action) => { | ||
| const lens = byId.get(lensId); | ||
| if (!lens || OUT_OF_SCOPE_SET.has(lensId)) | ||
| return; | ||
| pushEvidence(lens, 'violations', ruleId, detail); | ||
| if (v.file) | ||
| pushEvidence(lens, 'violations', v.file, ruleId); | ||
| markResidual(lens, summary, action); | ||
| }; | ||
| // Type-only / non-blocking placement debt → modularity only (never coupling / DIP residual). | ||
| if (isTypeOnlyPlacementDebt(v)) { | ||
| attach('modularity', 'Type-only placement debt remains — prefer SharedTypes / owning layer (not runtime coupling).', { | ||
| kind: 'skill', | ||
| ref: '/ark-place', | ||
| summary: 'Place shared types in a layer both sides may import; type-only debt is not a value edge.', | ||
| }); | ||
| continue; | ||
| } | ||
| const edgeAction = { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Clear the active edge residual, then re-doctor.', | ||
| }; | ||
| if (upper === 'LAYER_IMPORT_VIOLATION' || | ||
| upper.includes('LAYER_IMPORT') || | ||
| upper === 'DYNAMIC_IMPORT_VIOLATION') { | ||
| attach('coupling', 'Import graph edges violate the layer contract.', edgeAction); | ||
| continue; | ||
| } | ||
| if (upper.includes('CYCLE') || upper === 'CIRCULAR_DEPENDENCY') { | ||
| attach('coupling', 'Import cycles couple modules tightly.', edgeAction); | ||
| continue; | ||
| } | ||
| if (upper.includes('PEER_ISOLATION') || upper === 'PEER_ISOLATION_VIOLATION') { | ||
| attach('coupling', 'Peer isolation residual — slices import each other freely.', { | ||
| kind: 'skill', | ||
| ref: '/ark-loop', | ||
| summary: 'Peer isolation fixes are judgment-class — one cluster at a time.', | ||
| }); | ||
| continue; | ||
| } | ||
| if (upper === 'FORBIDDEN_GLOBAL' || upper.startsWith('FORBIDDEN_')) { | ||
| attach('dip', 'Forbidden globals / effect surfaces break dependency inversion.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Inject a port instead of the forbidden global.', | ||
| }); | ||
| attach('testability', 'Forbidden ambient effects reduce pure-domain testability.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Replace ambient effects with injectable ports.', | ||
| }); | ||
| continue; | ||
| } | ||
| if (upper === 'CAPABILITY_VIOLATION') { | ||
| attach('dip', 'Denied capability use — invert through an allowed adapter/port.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Capability walls require port injection (judgment, not mechanical-safe).', | ||
| }); | ||
| attach('testability', 'Capability violations couple domain code to I/O — harder to unit-test.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Keep pure layers free of denied capabilities.', | ||
| }); | ||
| continue; | ||
| } | ||
| if (upper.startsWith('ARKRULE_') || upper === 'INVARIANT_UNCOVERED') { | ||
| attach('encapsulation', 'ArkRules structure / invariant residual inside a layer.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Label [ArkRules]; structure fixes are judgment — never invent mechanical-safe.', | ||
| }); | ||
| attach('domain', 'Intra-layer domain structure or invariant coverage residual.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Inventory candidates → one ArkRules pilot with coverage evidence.', | ||
| }); | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
| function mapCountsAndFlags(byId, facts) { | ||
| const cycleCount = Number(facts.cycleCount) || 0; | ||
| if (cycleCount > 0) { | ||
| const lens = byId.get('coupling'); | ||
| pushEvidence(lens, 'cycles', `count:${cycleCount}`); | ||
| markResidual(lens, 'Import cycles couple modules tightly.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Break cycles with a judgment extraction — one pilot.', | ||
| }); | ||
| } | ||
| const peer = typeof facts.peerIsolationCount === 'boolean' | ||
| ? facts.peerIsolationCount | ||
| ? 1 | ||
| : 0 | ||
| : Number(facts.peerIsolationCount) || 0; | ||
| if (peer > 0) { | ||
| const lens = byId.get('coupling'); | ||
| pushEvidence(lens, 'peerIsolation', `count:${peer}`); | ||
| markResidual(lens, 'Peer isolation residual remains.', { | ||
| kind: 'skill', | ||
| ref: '/ark-loop', | ||
| summary: 'Peer isolation is judgment-class residual.', | ||
| }); | ||
| } | ||
| const pc = Number(facts.physicalCohesionFindingCount) || 0; | ||
| if (pc > 0) { | ||
| const cohesion = byId.get('cohesion'); | ||
| pushEvidence(cohesion, 'physicalCohesion', `findings:${pc}`); | ||
| markResidual(cohesion, 'Physical cohesion residual — mirrored concept clusters across anchors.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Review reshape pilot; one decision-aware pilot at a time.', | ||
| }); | ||
| const srp = byId.get('srp'); | ||
| pushEvidence(srp, 'physicalCohesion', `findings:${pc}`); | ||
| markResidual(srp, 'Mirrored clusters suggest split-by-concern residual (architecture SRP).', { | ||
| kind: 'skill', | ||
| ref: '/ark-autopilot', | ||
| summary: 'One reshape/extraction pilot with user OK.', | ||
| }); | ||
| } | ||
| const pureN = Number(facts.pureOrCapabilityResidual) || 0; | ||
| const fgN = Number(facts.forbiddenGlobalResidual) || 0; | ||
| if (pureN > 0 || fgN > 0) { | ||
| const dip = byId.get('dip'); | ||
| if (pureN > 0) | ||
| pushEvidence(dip, 'capability', `residual:${pureN}`); | ||
| if (fgN > 0) | ||
| pushEvidence(dip, 'forbiddenGlobals', `residual:${fgN}`); | ||
| markResidual(dip, 'Pure / capability / forbidden residual weakens dependency inversion.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Inject ports; keep pure layers free of effects.', | ||
| }); | ||
| const test = byId.get('testability'); | ||
| if (pureN > 0) | ||
| pushEvidence(test, 'capability', `residual:${pureN}`); | ||
| if (fgN > 0) | ||
| pushEvidence(test, 'forbiddenGlobals', `residual:${fgN}`); | ||
| markResidual(test, 'Impure domain or capability residual reduces testability.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Prefer ports over concrete I/O in pure/domain modules.', | ||
| }); | ||
| } | ||
| const arkN = Number(facts.arkRulesStructureResidual) || 0; | ||
| if (arkN > 0) { | ||
| const enc = byId.get('encapsulation'); | ||
| pushEvidence(enc, 'arkRules', `structureResidual:${arkN}`); | ||
| markResidual(enc, 'ArkRules structure residual — encapsulation inside the layer.', { | ||
| kind: 'skill', | ||
| ref: '/ark-fix', | ||
| summary: 'Fix structure sensors under [ArkRules] without inventing mechanical-safe.', | ||
| }); | ||
| const domain = byId.get('domain'); | ||
| pushEvidence(domain, 'arkRules', `structureResidual:${arkN}`); | ||
| markResidual(domain, 'ArkRules residual may mean domain shape is not yet under contract.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Map inventory candidates; one pilot rule at a time.', | ||
| }); | ||
| } | ||
| else if (facts.arkRulesLoaded === false || facts.arkRulesLoaded == null) { | ||
| // No ArkRules → encapsulation stays ok (absence is valid), not residual. | ||
| // Domain remains ok unless other evidence marked it. | ||
| } | ||
| if (facts.designWeak === true) { | ||
| const m = byId.get('maintainability'); | ||
| pushEvidence(m, 'designFitness', 'design-weak'); | ||
| markResidual(m, 'Design-weak: checked edges may be clean, but design residual remains — not finished.', { | ||
| kind: 'skill', | ||
| ref: '/ark-explore', | ||
| summary: 'Shape door: explore shape-focus → dual-plan B → one pilot with OK.', | ||
| }); | ||
| } | ||
| if (facts.dirtyBaselineRisk === true || (Number(facts.baselineStale) || 0) > 0) { | ||
| const m = byId.get('maintainability'); | ||
| if (facts.dirtyBaselineRisk === true) { | ||
| pushEvidence(m, 'baseline', 'dirty-freeze-risk'); | ||
| } | ||
| if ((Number(facts.baselineStale) || 0) > 0) { | ||
| pushEvidence(m, 'baseline', `stale:${facts.baselineStale}`); | ||
| } | ||
| markResidual(m, 'Baseline honesty residual — frozen debt or stale keys need review.', { | ||
| kind: 'command', | ||
| ref: 'ark-check --doctor', | ||
| summary: 'Review baseline freeze honesty; do not freeze new wrong debt.', | ||
| }); | ||
| } | ||
| // Large frozen residual (baseline exists with many freezes) is maintainability debt — | ||
| // only when there is already a residual signal or a substantial freeze surface. | ||
| const frozenN = Number(facts.frozenResidual) || 0; | ||
| if (facts.baselineExists === true && frozenN >= 10 && byId.get('maintainability').status !== 'residual') { | ||
| const m = byId.get('maintainability'); | ||
| pushEvidence(m, 'baseline', `frozen:${frozenN}`); | ||
| markResidual(m, 'Substantial frozen residual remains under the baseline — review debt honestly.', { | ||
| kind: 'command', | ||
| ref: 'ark-check --doctor', | ||
| summary: 'Review freezes; do not freeze new wrong debt to clear residual.', | ||
| }); | ||
| } | ||
| const ungov = Number(facts.ungovernedDirCount) || 0; | ||
| const emptyL = Number(facts.emptyLayerCount) || 0; | ||
| if (ungov > 0 || emptyL > 0) { | ||
| const mod = byId.get('modularity'); | ||
| if (ungov > 0) | ||
| pushEvidence(mod, 'coverage', `ungovernedDirs:${ungov}`); | ||
| if (emptyL > 0) | ||
| pushEvidence(mod, 'coverage', `emptyLayers:${emptyL}`); | ||
| markResidual(mod, 'Placement / modularity residual — ungoverned dirs or empty layer globs.', { | ||
| kind: 'skill', | ||
| ref: '/ark-contract', | ||
| summary: 'Classify ungoverned paths; fix empty layer patterns.', | ||
| }); | ||
| } | ||
| // Missing golden pattern under design-weak → modularity residual (AI placement cue). | ||
| if (facts.designWeak === true && facts.goldenPatternPresent === false) { | ||
| const mod = byId.get('modularity'); | ||
| pushEvidence(mod, 'goldenPattern', 'absent'); | ||
| markResidual(mod, 'Design-weak without a golden pattern — new code lacks a placement norm for the AI.', { | ||
| kind: 'skill', | ||
| ref: '/ark-place', | ||
| summary: 'Record an advisory golden pattern for new code (does not clear design-weak).', | ||
| }); | ||
| } | ||
| // Stack: TypeScript host partially instrumented; unknown → not-instrumented. | ||
| const stack = byId.get('stack'); | ||
| const kind = facts.stackKind ?? null; | ||
| if (kind === 'typescript') { | ||
| // Partial instrumentation is still honest `ok` when no residual evidence. | ||
| if (stack.status === 'ok') { | ||
| stack.summary = | ||
| 'Stack practices are partially instrumented for TypeScript / host / Ark idioms only — not a full framework checklist.'; | ||
| } | ||
| } | ||
| else { | ||
| stack.status = 'not-instrumented'; | ||
| stack.summary = | ||
| 'Stack-specific best practices outside TypeScript/host/Ark idioms are not instrumented.'; | ||
| stack.evidence = []; | ||
| stack.nextAction = { | ||
| kind: 'docs', | ||
| ref: 'docs/use.md#improvement-compass', | ||
| summary: 'Ark does not score non-TS stack idioms.', | ||
| }; | ||
| } | ||
| } | ||
| function finalizeTopResidual(lenses) { | ||
| const residual = lenses | ||
| .filter((l) => l.status === 'residual' && !OUT_OF_SCOPE_SET.has(l.id)) | ||
| .slice() | ||
| .sort((a, b) => { | ||
| const pa = RESIDUAL_SORT_PRIORITY[a.id] ?? 150; | ||
| const pb = RESIDUAL_SORT_PRIORITY[b.id] ?? 150; | ||
| if (pa !== pb) | ||
| return pa - pb; | ||
| return a.id.localeCompare(b.id); | ||
| }); | ||
| return residual.slice(0, IMPROVEMENT_COMPASS_TOP_RESIDUAL_CAP).map((l) => l.id); | ||
| } | ||
| function humanLabel(id) { | ||
| return LENS_LABELS[id] ?? id; | ||
| } | ||
| /** | ||
| * Build a deterministic improvement compass from supplied doctor-side facts. | ||
@@ -538,41 +20,8 @@ * Always returns all 15 lenses; always `notAScore: true`. | ||
| export function buildImprovementCompass(facts = {}) { | ||
| const lenses = initLenses(); | ||
| const lenses = createInitialImprovementCompassLenses(); | ||
| const byId = new Map(lenses.map((l) => [l.id, l])); | ||
| if (Array.isArray(facts.designSmells) && facts.designSmells.length > 0) { | ||
| // Sort by smell id so projection is input-order independent. | ||
| const smells = [...facts.designSmells].sort((a, b) => smellIdOf(a).localeCompare(smellIdOf(b))); | ||
| mapDesignSmells(byId, smells); | ||
| } | ||
| if (Array.isArray(facts.violations) && facts.violations.length > 0) { | ||
| const violations = [...facts.violations].sort((a, b) => { | ||
| const ra = violationRuleId(a).localeCompare(violationRuleId(b)); | ||
| if (ra !== 0) | ||
| return ra; | ||
| return String(a.file ?? '').localeCompare(String(b.file ?? '')); | ||
| }); | ||
| mapViolations(byId, violations); | ||
| } | ||
| mapCountsAndFlags(byId, facts); | ||
| // Hard lock: out-of-scope can never be residual, even if bad facts arrive. | ||
| for (const id of IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES) { | ||
| const lens = byId.get(id); | ||
| lens.status = 'out-of-scope'; | ||
| lens.summary = OUT_OF_SCOPE_SUMMARIES[id]; | ||
| lens.evidence = []; | ||
| lens.nextAction = { | ||
| kind: 'docs', | ||
| ref: 'docs/use.md#improvement-compass', | ||
| summary: 'Out of scope for ArkGate — use dedicated tooling outside the gate.', | ||
| }; | ||
| } | ||
| // Stable evidence order per lens (source then ref). | ||
| for (const lens of lenses) { | ||
| lens.evidence.sort((a, b) => { | ||
| const s = a.source.localeCompare(b.source); | ||
| if (s !== 0) | ||
| return s; | ||
| return a.ref.localeCompare(b.ref); | ||
| }); | ||
| } | ||
| const topResidual = finalizeTopResidual(lenses); | ||
| projectImprovementCompassFacts(byId, facts); | ||
| lockImprovementCompassOutOfScope(byId); | ||
| sortImprovementCompassEvidence(lenses); | ||
| const topResidual = finalizeImprovementCompassTopResidual(lenses); | ||
| return { | ||
@@ -600,3 +49,3 @@ schemaVersion: ARK_IMPROVEMENT_COMPASS_SCHEMA_VERSION, | ||
| export function formatImprovementCompassResidualLabels(compass) { | ||
| return compass.topResidual.map((id) => humanLabel(id)); | ||
| return compass.topResidual.map((id) => improvementCompassHumanLabel(id)); | ||
| } | ||
@@ -619,3 +68,3 @@ /** | ||
| const residual = formatImprovementCompassResidualLabels(compass); | ||
| const outOfScope = IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES.map((id) => humanLabel(id)); | ||
| const outOfScope = IMPROVEMENT_COMPASS_OUT_OF_SCOPE_LENSES.map((id) => improvementCompassHumanLabel(id)); | ||
| const next = primaryImprovementCompassNextAction(compass); | ||
@@ -622,0 +71,0 @@ const lines = []; |
@@ -8,2 +8,6 @@ import { createHash } from 'node:crypto'; | ||
| import { | ||
| formatManagedUpgradeSelfServiceHonesty, | ||
| projectManagedUpgradeSelfServiceHonesty, | ||
| } from './managed-upgrade-honesty.mjs'; | ||
| import { | ||
| KNOWN_TOOLS, | ||
@@ -16,2 +20,8 @@ arkPackageVersion, | ||
| export { | ||
| formatManagedUpgradeSelfServiceHonesty, | ||
| projectHostWritePathActivation, | ||
| projectManagedUpgradeSelfServiceHonesty, | ||
| } from './managed-upgrade-honesty.mjs'; | ||
| export const MANAGED_MANIFEST_PATH = 'ark.managed.json'; | ||
@@ -539,3 +549,6 @@ const MANIFEST_VERSION = '1.0'; | ||
| function publicPlan(plan, overrides = {}) { | ||
| return { | ||
| const assets = plan.assets.map( | ||
| ({ containerBeforeHash: _container, [AFTER_CONTENT]: _content, ...asset }) => asset | ||
| ); | ||
| const base = { | ||
| schemaVersion: plan.schemaVersion, | ||
@@ -550,7 +563,17 @@ root: plan.root, | ||
| acceptConflicts: plan.acceptConflicts, | ||
| assets: plan.assets.map( | ||
| ({ containerBeforeHash: _container, [AFTER_CONTENT]: _content, ...asset }) => asset | ||
| ), | ||
| assets, | ||
| summary: plan.summary, | ||
| }; | ||
| // DF05: self-service honesty (write-path labels + customized preserve) on every public plan. | ||
| // Not part of planDigest — advisory projection only; never invents hard write on soft hosts. | ||
| const selfService = projectManagedUpgradeSelfServiceHonesty({ | ||
| hosts: base.hosts, | ||
| assets, | ||
| summary: base.summary, | ||
| }); | ||
| return { | ||
| ...base, | ||
| ...overrides, | ||
| // DF05 projection always present unless an override supplies a replacement. | ||
| selfService: overrides.selfService ?? selfService, | ||
| }; | ||
@@ -745,2 +768,12 @@ } | ||
| ); | ||
| const honesty = | ||
| plan.selfService ?? | ||
| projectManagedUpgradeSelfServiceHonesty({ | ||
| hosts: plan.hosts, | ||
| assets: plan.assets, | ||
| summary: plan.summary, | ||
| }); | ||
| for (const line of formatManagedUpgradeSelfServiceHonesty(honesty)) { | ||
| console.log(line); | ||
| } | ||
| if (plan.applied) { | ||
@@ -747,0 +780,0 @@ console.log( |
| /** | ||
| * ACS03 — gather session/project evidence for `ark status` / MCP `ark_status`. | ||
| * ACS03 + DF02 — gather session/project evidence for `ark status` / MCP `ark_status`. | ||
| * | ||
| * Fail-closed and CI-safe: never prompts (no readline), never invents hard write, | ||
| * never invents a numeric score. Pure assembly lives in Domain statusManifest. | ||
| * Improvement compass carries explicit honesty mode (full|subset|unavailable). | ||
| */ | ||
@@ -12,3 +13,8 @@ import { createHash } from 'node:crypto'; | ||
| import { buildStatusManifest } from './status-manifest.mjs'; | ||
| import { | ||
| buildStatusManifest, | ||
| normalizeStatusImprovementCompass, | ||
| projectStatusImprovementCompass, | ||
| unavailableStatusImprovementCompass, | ||
| } from './status-manifest.mjs'; | ||
| import { createProjectId } from './project-identity.mjs'; | ||
@@ -135,2 +141,90 @@ import { resolveEffectiveProjectRoot } from './project-root.mjs'; | ||
| /** | ||
| * Project status improvementCompass honesty from a report/session snapshot (DF02). | ||
| * Prefer stored thin slice; never invent green residual when facts are missing. | ||
| * | ||
| * @param {object|null|undefined} latest | ||
| * @param {{ contractHash?: string|null }} [opts] | ||
| * @returns {import('./status-manifest.mjs').StatusImprovementCompassSlice} | ||
| */ | ||
| export function statusCompassFromSnapshot(latest, opts = {}) { | ||
| const contractHash = | ||
| typeof opts.contractHash === 'string' && opts.contractHash.length > 0 | ||
| ? opts.contractHash | ||
| : null; | ||
| if (!latest || typeof latest !== 'object') { | ||
| return unavailableStatusImprovementCompass({ | ||
| reasonCode: 'NO_SESSION_SNAPSHOT', | ||
| reason: | ||
| 'No session report snapshot yet — run ark-check --doctor or --report for residual lenses. Status never invents green.', | ||
| contractHash, | ||
| }); | ||
| } | ||
| // Prefer explicit thin status slice on the snapshot (report path stores mode+residual). | ||
| if (latest.improvementCompass && typeof latest.improvementCompass === 'object') { | ||
| const normalized = normalizeStatusImprovementCompass({ | ||
| ...latest.improvementCompass, | ||
| ...(contractHash && !latest.improvementCompass.contractHash | ||
| ? { contractHash } | ||
| : {}), | ||
| factsSource: latest.improvementCompass.factsSource || 'report-snapshot', | ||
| }); | ||
| if (normalized) return normalized; | ||
| } | ||
| // Doctor-equivalent residual ids stored without honesty wrapper → full if complete flag set. | ||
| if ( | ||
| latest.doctorImprovementCompass && | ||
| typeof latest.doctorImprovementCompass === 'object' && | ||
| latest.doctorImprovementCompass.notAScore === true && | ||
| Array.isArray(latest.doctorImprovementCompass.topResidual) | ||
| ) { | ||
| const complete = latest.compassFactsComplete === true || latest.completeness === 'complete'; | ||
| return projectStatusImprovementCompass({ | ||
| mode: complete ? 'full' : 'subset', | ||
| topResidual: latest.doctorImprovementCompass.topResidual, | ||
| reasonCode: complete ? undefined : 'FACTS_PARTIAL', | ||
| reason: complete | ||
| ? undefined | ||
| : 'Session snapshot residual is partial — re-run doctor/report for full compass.', | ||
| factsSource: 'report-snapshot', | ||
| contractHash, | ||
| }); | ||
| } | ||
| return unavailableStatusImprovementCompass({ | ||
| reasonCode: 'NO_SESSION_SNAPSHOT', | ||
| reason: | ||
| 'Session snapshot has no improvement compass facts — run ark-check --doctor or --report. Status never invents green.', | ||
| contractHash, | ||
| }); | ||
| } | ||
| /** | ||
| * Build a storeable thin status compass from a full doctor ImprovementCompass (DF02). | ||
| * Used by report snapshot so status residual ⊆ doctor residual for the same tree. | ||
| * | ||
| * @param {{ notAScore?: boolean, topResidual?: string[] }|null|undefined} doctorCompass | ||
| * @param {{ mode?: 'full'|'subset', contractHash?: string|null, reasonCode?: string, reason?: string }} [opts] | ||
| */ | ||
| export function thinStatusCompassFromDoctor(doctorCompass, opts = {}) { | ||
| if (!doctorCompass || doctorCompass.notAScore !== true) { | ||
| return unavailableStatusImprovementCompass({ | ||
| reasonCode: 'FACTS_UNAVAILABLE', | ||
| contractHash: opts.contractHash, | ||
| }); | ||
| } | ||
| const mode = opts.mode === 'subset' ? 'subset' : 'full'; | ||
| return projectStatusImprovementCompass({ | ||
| mode, | ||
| topResidual: Array.isArray(doctorCompass.topResidual) ? doctorCompass.topResidual : [], | ||
| reasonCode: opts.reasonCode, | ||
| reason: opts.reason, | ||
| factsSource: 'report-snapshot', | ||
| contractHash: opts.contractHash, | ||
| }); | ||
| } | ||
| /** | ||
| * Collect status facts from disk (no prompts). | ||
@@ -145,2 +239,4 @@ * @param {{ | ||
| * env?: NodeJS.ProcessEnv, | ||
| * improvementCompass?: object | null, | ||
| * contractHash?: string | null, | ||
| * }} [options] | ||
@@ -248,2 +344,18 @@ */ | ||
| // DF02 — always project compass with honesty mode (never invent green residual). | ||
| // Prefer explicit override (tests/MCP inject doctor-facts); else report snapshot. | ||
| let improvementCompass = null; | ||
| if (options.improvementCompass != null) { | ||
| improvementCompass = normalizeStatusImprovementCompass(options.improvementCompass); | ||
| } | ||
| if (!improvementCompass) { | ||
| const contractHash = | ||
| typeof options.contractHash === 'string' && options.contractHash.length > 0 | ||
| ? options.contractHash | ||
| : configExists && config | ||
| ? sha256Hex(JSON.stringify(config)) | ||
| : null; | ||
| improvementCompass = statusCompassFromSnapshot(latest, { contractHash }); | ||
| } | ||
| return { | ||
@@ -276,2 +388,3 @@ arkgateVersion: options.arkgateVersion || packageVersion(), | ||
| : null, | ||
| improvementCompass, | ||
| }; | ||
@@ -354,2 +467,14 @@ } | ||
| ); | ||
| const ic = manifest.improvementCompass; | ||
| if (ic) { | ||
| const residual = | ||
| Array.isArray(ic.topResidual) && ic.topResidual.length > 0 | ||
| ? ic.topResidual.join(', ') | ||
| : '(none)'; | ||
| write( | ||
| ` compass: mode=${ic.mode}` + | ||
| (ic.mode === 'unavailable' ? '' : ` · residual=${residual}`) + | ||
| ' · not a score' | ||
| ); | ||
| } | ||
| write(` next: [${manifest.nextAction.id}] ${manifest.nextAction.summary}`); | ||
@@ -356,0 +481,0 @@ } |
+163
-14
@@ -13,2 +13,21 @@ /** | ||
| export const ARK_STATUS_MANIFEST_SCHEMA_URL = 'https://unpkg.com/arkgate@4/schemas/ark.status-manifest.schema.json'; | ||
| /** | ||
| * Honesty mode for status improvementCompass (DF02). | ||
| * - full: residual projected from doctor-equivalent facts (residual ⊆ doctor) | ||
| * - subset: incomplete facts; residual may omit doctor residual; never invent green | ||
| * - unavailable: no usable facts; empty residual + reason (never silent ok) | ||
| */ | ||
| export const STATUS_COMPASS_MODES = ['full', 'subset', 'unavailable']; | ||
| /** Provenance for status compass residual (same-tree intent). */ | ||
| export const STATUS_COMPASS_FACTS_SOURCES = [ | ||
| 'doctor-facts', | ||
| 'report-snapshot', | ||
| 'none', | ||
| ]; | ||
| /** Stable reason codes when mode is not full. */ | ||
| export const STATUS_COMPASS_REASON_CODES = { | ||
| FACTS_UNAVAILABLE: 'FACTS_UNAVAILABLE', | ||
| FACTS_PARTIAL: 'FACTS_PARTIAL', | ||
| NO_SESSION_SNAPSHOT: 'NO_SESSION_SNAPSHOT', | ||
| }; | ||
| const PROJECT_ID_PATTERN = /^sha256:[a-f0-9]{64}$/; | ||
@@ -298,20 +317,145 @@ /** | ||
| } | ||
| function normalizeStatusImprovementCompass(value) { | ||
| const STATUS_COMPASS_MODE_SET = new Set(STATUS_COMPASS_MODES); | ||
| const STATUS_COMPASS_SOURCE_SET = new Set(STATUS_COMPASS_FACTS_SOURCES); | ||
| /** | ||
| * Project a thin status improvementCompass with explicit honesty mode (DF02). | ||
| * | ||
| * Rules: | ||
| * - always notAScore: true | ||
| * - mode full | subset | unavailable (invalid mode → unavailable) | ||
| * - unavailable: topResidual forced empty (never invent residual or silent green) | ||
| * - full/subset: residual ids from input only (never fabricate ok lenses) | ||
| * - never carries valid / goal.met / score fields | ||
| */ | ||
| export function projectStatusImprovementCompass(input) { | ||
| const mode = STATUS_COMPASS_MODE_SET.has(input.mode) | ||
| ? input.mode | ||
| : 'unavailable'; | ||
| const factsSource = input.factsSource != null && STATUS_COMPASS_SOURCE_SET.has(input.factsSource) | ||
| ? input.factsSource | ||
| : mode === 'unavailable' | ||
| ? 'none' | ||
| : undefined; | ||
| const contractHash = typeof input.contractHash === 'string' && input.contractHash.length > 0 | ||
| ? input.contractHash | ||
| : undefined; | ||
| if (mode === 'unavailable') { | ||
| const out = { | ||
| schemaVersion: '1.0', | ||
| notAScore: true, | ||
| mode: 'unavailable', | ||
| topResidual: [], | ||
| reasonCode: typeof input.reasonCode === 'string' && input.reasonCode.length > 0 | ||
| ? input.reasonCode | ||
| : STATUS_COMPASS_REASON_CODES.FACTS_UNAVAILABLE, | ||
| reason: typeof input.reason === 'string' && input.reason.length > 0 | ||
| ? input.reason | ||
| : 'Improvement compass facts are unavailable — run ark-check --doctor for residual lenses. Status never invents green.', | ||
| factsSource: factsSource ?? 'none', | ||
| }; | ||
| if (contractHash) | ||
| out.contractHash = contractHash; | ||
| return out; | ||
| } | ||
| const topResidual = Array.isArray(input.topResidual) | ||
| ? input.topResidual | ||
| .filter((id) => typeof id === 'string' && id.length > 0) | ||
| .slice(0, 15) | ||
| : []; | ||
| const out = { | ||
| schemaVersion: '1.0', | ||
| notAScore: true, | ||
| mode, | ||
| topResidual, | ||
| }; | ||
| if (mode === 'subset') { | ||
| out.reasonCode = | ||
| typeof input.reasonCode === 'string' && input.reasonCode.length > 0 | ||
| ? input.reasonCode | ||
| : STATUS_COMPASS_REASON_CODES.FACTS_PARTIAL; | ||
| out.reason = | ||
| typeof input.reason === 'string' && input.reason.length > 0 | ||
| ? input.reason | ||
| : 'Status compass is a subset of doctor residual — incomplete session facts; run doctor for full.'; | ||
| } | ||
| else if (typeof input.reasonCode === 'string' && input.reasonCode.length > 0) { | ||
| out.reasonCode = input.reasonCode; | ||
| } | ||
| if (typeof input.reason === 'string' && input.reason.length > 0 && mode === 'full') { | ||
| out.reason = input.reason; | ||
| } | ||
| if (factsSource) | ||
| out.factsSource = factsSource; | ||
| if (contractHash) | ||
| out.contractHash = contractHash; | ||
| return out; | ||
| } | ||
| /** | ||
| * Unavailable compass when Tooling has no doctor/report residual facts. | ||
| * Empty residual + mode label — never a green / ok claim. | ||
| */ | ||
| export function unavailableStatusImprovementCompass(input = {}) { | ||
| return projectStatusImprovementCompass({ | ||
| mode: 'unavailable', | ||
| topResidual: [], | ||
| reasonCode: input.reasonCode ?? STATUS_COMPASS_REASON_CODES.NO_SESSION_SNAPSHOT, | ||
| reason: input.reason ?? | ||
| 'No session compass facts yet — run ark-check --doctor or --report for residual lenses. Status never invents green.', | ||
| factsSource: 'none', | ||
| contractHash: input.contractHash, | ||
| }); | ||
| } | ||
| /** | ||
| * Normalize an incoming status compass slice (Tooling pass-through / snapshot). | ||
| * Rejects score-like shapes; coerces missing mode to subset (never silent full). | ||
| * Unavailable always clears residual. | ||
| */ | ||
| export function normalizeStatusImprovementCompass(value) { | ||
| if (value == null || typeof value !== 'object') | ||
| return null; | ||
| if (value.notAScore !== true) | ||
| const record = value; | ||
| if (record.notAScore !== true) | ||
| return null; | ||
| if (value.schemaVersion !== '1.0') | ||
| if (record.schemaVersion !== '1.0') | ||
| return null; | ||
| if (!Array.isArray(value.topResidual)) | ||
| // Score-like fields never allowed on status compass. | ||
| if ('score' in record || 'valid' in record || 'goal' in record) | ||
| return null; | ||
| const topResidual = value.topResidual | ||
| .filter((id) => typeof id === 'string' && id.length > 0) | ||
| .slice(0, 15); | ||
| return { | ||
| schemaVersion: '1.0', | ||
| notAScore: true, | ||
| topResidual, | ||
| }; | ||
| let mode; | ||
| if (typeof record.mode === 'string' && STATUS_COMPASS_MODE_SET.has(record.mode)) { | ||
| mode = record.mode; | ||
| } | ||
| else if (Array.isArray(record.topResidual)) { | ||
| // Legacy thin slice without mode → subset honesty (never silent full). | ||
| mode = 'subset'; | ||
| } | ||
| else { | ||
| mode = 'unavailable'; | ||
| } | ||
| return projectStatusImprovementCompass({ | ||
| mode, | ||
| topResidual: Array.isArray(record.topResidual) | ||
| ? record.topResidual | ||
| : [], | ||
| reasonCode: typeof record.reasonCode === 'string' ? record.reasonCode : null, | ||
| reason: typeof record.reason === 'string' ? record.reason : null, | ||
| factsSource: typeof record.factsSource === 'string' ? record.factsSource : null, | ||
| contractHash: typeof record.contractHash === 'string' ? record.contractHash : null, | ||
| }); | ||
| } | ||
| /** | ||
| * Residual-id subset check for status ⊆ doctor parity fixtures (DF02). | ||
| * Returns true when every status residual id appears in doctor residual ids. | ||
| */ | ||
| export function statusCompassResidualIsSubsetOfDoctor(statusResidual, doctorResidual) { | ||
| const status = Array.isArray(statusResidual) ? statusResidual : []; | ||
| const doctor = new Set(Array.isArray(doctorResidual) ? doctorResidual : []); | ||
| for (const id of status) { | ||
| if (typeof id !== 'string' || id.length === 0) | ||
| continue; | ||
| if (!doctor.has(id)) | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| function numberOrNull(value) { | ||
@@ -419,8 +563,9 @@ if (value == null) | ||
| type: 'object', | ||
| description: 'Optional thin improvement-compass residual ids (notAScore). Never a gate input; full lenses on doctor JSON.', | ||
| description: 'Thin improvement-compass residual ids with honesty mode (notAScore). full | subset | unavailable. Never a gate input; full lenses on doctor JSON. When full, residual ids ⊆ doctor residual for the same facts. unavailable never invents green residual.', | ||
| additionalProperties: false, | ||
| required: ['schemaVersion', 'notAScore', 'topResidual'], | ||
| required: ['schemaVersion', 'notAScore', 'mode', 'topResidual'], | ||
| properties: { | ||
| schemaVersion: { const: '1.0' }, | ||
| notAScore: { const: true }, | ||
| mode: { enum: ['full', 'subset', 'unavailable'] }, | ||
| topResidual: { | ||
@@ -431,2 +576,6 @@ type: 'array', | ||
| }, | ||
| reasonCode: { type: 'string', minLength: 1 }, | ||
| reason: { type: 'string', minLength: 1 }, | ||
| factsSource: { enum: ['doctor-facts', 'report-snapshot', 'none'] }, | ||
| contractHash: { type: 'string', minLength: 1 }, | ||
| }, | ||
@@ -433,0 +582,0 @@ }, |
@@ -1,3 +0,3 @@ | ||
| "use strict";var Oe=Object.create;var N=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var $e=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var Te=(e,t)=>{for(var n in t)N(e,n,{get:t[n],enumerable:!0})},Q=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Pe(t))!ve.call(e,s)&&s!==n&&N(e,s,{get:()=>t[s],enumerable:!(r=_e(t,s))||r.enumerable});return e};var ee=(e,t,n)=>(n=e!=null?Oe($e(e)):{},Q(t||!e||!e.__esModule?N(n,"default",{value:e,enumerable:!0}):n,e)),je=e=>Q(N({},"__esModule",{value:!0}),e);var pt={};Te(pt,{default:()=>dt,findConfigPath:()=>T,globToRegExp:()=>E,isEdgeDenied:()=>F,layerForRelativePath:()=>R,loadArkConfig:()=>j,noDeniedCapabilities:()=>Ne,noDomainInfraImports:()=>we,noForbiddenGlobals:()=>Le,noRawEventPublish:()=>Ee,patternSpecificity:()=>M,plugin:()=>v,readTsconfigPathAliases:()=>he,requirePublishSource:()=>Ce,resolveImportSpecifier:()=>Se,resolveRelativeImport:()=>Ae});module.exports=je(pt);var k=ee(require("fs"),1),p=ee(require("path"),1);var te=new Map;function ne(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function O(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let s=e[n+1];if("*?{}[],".includes(s)||s==="\\"){t+="\\"+s,n+=1;continue}t+="/";continue}t+=r}return t}function De(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function E(e){let t=te.get(e);if(t)return t;let n=O(e),r=De(n),s="",i=0;for(let c=0;c<n.length;c+=1){let g=n[c];g==="\\"&&c+1<n.length?(s+=ne(n[c+1]),c+=1):g==="*"?n[c+1]==="*"?n[c+2]==="/"?(s+="(?:.*/)?",c+=2):(s+=".*",c+=1):s+="[^/]*":g==="?"?s+="[^/]":g==="{"&&r?(s+="(?:",i+=1):g==="}"&&r&&i>0?(s+=")",i-=1):g===","&&r&&i>0?s+="|":s+=ne(g)}let a=new RegExp(`^${s}$`);return te.set(e,a),a}function Me(e){return O(String(e)).split("/").filter(Boolean).filter(n=>n!=="**"&&n!=="*"&&!n.includes("*")&&!n.includes("?")&&!n.includes("{")&&!n.includes("["))}function M(e,t){let n=O(String(e)),r=Me(n),s=n.replace(/\*/g,"").length,i=r.length*1e4+s;if(t==null||t==="")return i;let a=String(t).split(/[/\\]/).filter(Boolean);if(r.length===0)return s;let c=0,g=-1;for(let f of r){let d=-1;for(let o=c;o<a.length;o+=1)if(a[o]===f){d=o;break}if(d<0)return i;g=d,c=d+1}return(g+1)*1e6+r.length*1e4+s}function R(e,t){let n=String(e).split(/[/\\]/).join("/"),r,s=-1;for(let i of t??[])if(!(i.exclude??[]).some(a=>E(a).test(n))){for(let a of i.patterns??[])if(E(a).test(n)){let c=M(a,n);c>s&&(s=c,r=i.name)}}return r}function re(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(s=>String(s).toLowerCase()));for(let s=0;s<n.length-1;s+=1)if(r.has(n[s].toLowerCase()))return`${n[s].toLowerCase()}/${n[s+1].toLowerCase()}`}function Ve(e){let t=new Set;for(let n of e??[]){let s=O(String(n)).split("/").filter(Boolean);for(let i=0;i<s.length;i+=1){let a=s[i];if((a==="**"||a==="*")&&i>0){let c=s[i-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function Fe(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(s=>typeof s=="string"&&s.length>0);let r=(n??[]).find(s=>s.name===t);return Ve(r?.patterns)}function V(e,t,n,r){for(let s of e??[])if(!(s.from!==t||s.to!==n)&&s.allowed===!1){if(s.peerIsolation){let i=r?.fromPath,a=r?.toPath;if(!i||!a)return s;let c=Fe(s,t,r?.layers);if(c.length===0)return s;let g=re(i,c),f=re(a,c);if(!g||!f||g!==f)return s;continue}if(t!==n)return s}}function F(e,t,n,r){return V(e,t,n,r)!==void 0}var Ke=["**/*.gen.ts","**/*.gen.tsx","**/*.generated.ts","**/*.generated.tsx"];function He(e){let t=Array.isArray(e?.exclude)?e.exclude.filter(r=>typeof r=="string"):[];return[...e?.excludeGenerated===!1?[]:Ke,...t]}function se(e,t){let n=String(e).split(/[/\\]/).join("/");return He(t).some(r=>E(r).test(n))}var oe=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),Ge=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),gt=Object.freeze(Object.keys(Ge).sort()),K=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"}),Be=Object.freeze({process:Object.freeze(["process","node:process"])});function ie(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=K[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),s=K[r];if(s)return s;let i=e.indexOf("/",n+1);return i<0?null:K[e.slice(0,i)]??null}function H(e,t){for(let n of t)if(Be[n]?.includes(e))return n;return null}function ae(e){if(e?.pure===!0)return[...oe].sort();let n=(e?.capabilities?.deny??[]).filter(r=>oe.includes(r));return[...new Set(n)].sort()}var G="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",le=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],Ue=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function qe(){let e=[];for(let t of le)for(let n of le)t===n||Ue.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var de=qe(),B=[{from:"unversioned",to:"1.0"},{from:"1.0",to:"1.1"}],I={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},ce={$schema:"https://json-schema.org/draft/2020-12/schema",$id:G,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:G,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.1",default:"1.1"},name:{type:"string",minLength:1},include:{...I,minItems:1,default:["src"]},exclude:{...I,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:de,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...I,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}},arkRules:{type:"object",additionalProperties:{type:"string",minLength:1},default:{}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...I,minItems:1},exclude:I,intentPrefixes:I,description:{type:"string",minLength:1},forbiddenGlobals:I,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...I,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},S=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}): | ||
| "use strict";var Oe=Object.create;var N=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var $e=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var Te=(e,t)=>{for(var n in t)N(e,n,{get:t[n],enumerable:!0})},Q=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Pe(t))!ve.call(e,s)&&s!==n&&N(e,s,{get:()=>t[s],enumerable:!(r=_e(t,s))||r.enumerable});return e};var ee=(e,t,n)=>(n=e!=null?Oe($e(e)):{},Q(t||!e||!e.__esModule?N(n,"default",{value:e,enumerable:!0}):n,e)),je=e=>Q(N({},"__esModule",{value:!0}),e);var ut={};Te(ut,{default:()=>pt,findConfigPath:()=>T,globToRegExp:()=>E,isEdgeDenied:()=>F,layerForRelativePath:()=>R,loadArkConfig:()=>j,noDeniedCapabilities:()=>Ne,noDomainInfraImports:()=>we,noForbiddenGlobals:()=>Le,noRawEventPublish:()=>Ee,patternSpecificity:()=>M,plugin:()=>v,readTsconfigPathAliases:()=>be,requirePublishSource:()=>Ce,resolveImportSpecifier:()=>Se,resolveRelativeImport:()=>Ae});module.exports=je(ut);var k=ee(require("fs"),1),p=ee(require("path"),1);var te=new Map;function ne(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function O(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let s=e[n+1];if("*?{}[],".includes(s)||s==="\\"){t+="\\"+s,n+=1;continue}t+="/";continue}t+=r}return t}function De(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function E(e){let t=te.get(e);if(t)return t;let n=O(e),r=De(n),s="",i=0;for(let c=0;c<n.length;c+=1){let g=n[c];g==="\\"&&c+1<n.length?(s+=ne(n[c+1]),c+=1):g==="*"?n[c+1]==="*"?n[c+2]==="/"?(s+="(?:.*/)?",c+=2):(s+=".*",c+=1):s+="[^/]*":g==="?"?s+="[^/]":g==="{"&&r?(s+="(?:",i+=1):g==="}"&&r&&i>0?(s+=")",i-=1):g===","&&r&&i>0?s+="|":s+=ne(g)}let a=new RegExp(`^${s}$`);return te.set(e,a),a}function Me(e){return O(String(e)).split("/").filter(Boolean).filter(n=>n!=="**"&&n!=="*"&&!n.includes("*")&&!n.includes("?")&&!n.includes("{")&&!n.includes("["))}function M(e,t){let n=O(String(e)),r=Me(n),s=n.replace(/\*/g,"").length,i=r.length*1e4+s;if(t==null||t==="")return i;let a=String(t).split(/[/\\]/).filter(Boolean);if(r.length===0)return s;let c=0,g=-1;for(let f of r){let d=-1;for(let o=c;o<a.length;o+=1)if(a[o]===f){d=o;break}if(d<0)return i;g=d,c=d+1}return(g+1)*1e6+r.length*1e4+s}function R(e,t){let n=String(e).split(/[/\\]/).join("/"),r,s=-1;for(let i of t??[])if(!(i.exclude??[]).some(a=>E(a).test(n))){for(let a of i.patterns??[])if(E(a).test(n)){let c=M(a,n);c>s&&(s=c,r=i.name)}}return r}function re(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(s=>String(s).toLowerCase()));for(let s=0;s<n.length-1;s+=1)if(r.has(n[s].toLowerCase()))return`${n[s].toLowerCase()}/${n[s+1].toLowerCase()}`}function Ve(e){let t=new Set;for(let n of e??[]){let s=O(String(n)).split("/").filter(Boolean);for(let i=0;i<s.length;i+=1){let a=s[i];if((a==="**"||a==="*")&&i>0){let c=s[i-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function Fe(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(s=>typeof s=="string"&&s.length>0);let r=(n??[]).find(s=>s.name===t);return Ve(r?.patterns)}function Ke(e){return!e.fromPath||!e.toPath||e.folderCount<=0||!e.fromSlice||!e.toSlice?!0:e.fromSlice!==e.toSlice}function V(e,t,n,r){for(let s of e??[])if(!(s.from!==t||s.to!==n)&&s.allowed===!1){if(s.peerIsolation){let i=r?.fromPath,a=r?.toPath,c=Fe(s,t,r?.layers),g=i&&a?re(i,c):void 0,f=i&&a?re(a,c):void 0;if(Ke({fromPath:i,toPath:a,folderCount:c.length,fromSlice:g,toSlice:f}))return s;continue}if(t!==n)return s}}function F(e,t,n,r){return V(e,t,n,r)!==void 0}var He=["**/*.gen.ts","**/*.gen.tsx","**/*.generated.ts","**/*.generated.tsx"];function Ge(e){let t=Array.isArray(e?.exclude)?e.exclude.filter(r=>typeof r=="string"):[];return[...e?.excludeGenerated===!1?[]:He,...t]}function se(e,t){let n=String(e).split(/[/\\]/).join("/");return Ge(t).some(r=>E(r).test(n))}var oe=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),Be=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),mt=Object.freeze(Object.keys(Be).sort()),K=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"}),Ue=Object.freeze({process:Object.freeze(["process","node:process"])});function ie(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=K[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),s=K[r];if(s)return s;let i=e.indexOf("/",n+1);return i<0?null:K[e.slice(0,i)]??null}function H(e,t){for(let n of t)if(Ue[n]?.includes(e))return n;return null}function ae(e){if(e?.pure===!0)return[...oe].sort();let n=(e?.capabilities?.deny??[]).filter(r=>oe.includes(r));return[...new Set(n)].sort()}var G="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",le=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],qe=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function We(){let e=[];for(let t of le)for(let n of le)t===n||qe.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var de=We(),B=[{from:"unversioned",to:"1.0"},{from:"1.0",to:"1.1"}],I={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},ce={$schema:"https://json-schema.org/draft/2020-12/schema",$id:G,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:G,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.1",default:"1.1"},name:{type:"string",minLength:1},include:{...I,minItems:1,default:["src"]},exclude:{...I,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:de,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...I,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}},arkRules:{type:"object",additionalProperties:{type:"string",minLength:1},default:{}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...I,minItems:1},exclude:I,intentPrefixes:I,description:{type:"string",minLength:1},forbiddenGlobals:I,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...I,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},S=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}): | ||
| ${n.map(r=>`- ${r.path}: ${r.message}`).join(` | ||
| `)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function pe(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function _(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function x(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function We(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function C(e,t,n,r,s){if(t.$ref){let i=We(t.$ref,r);if(!i){s.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}C(e,i,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(i=>Object.is(i,e))){s.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!pe(e)){s.push({path:n,message:`must be an object; received ${x(e)}`});return}let i=t.properties??{};for(let a of t.required??[])e[a]===void 0&&s.push({path:_(n,a),message:"is required"});if(t.additionalProperties===!1)for(let a of Object.keys(e))a in i||s.push({path:_(n,a),message:"unknown field"});else if(t.additionalProperties!==void 0&&t.additionalProperties!==!0&&typeof t.additionalProperties=="object"){let a=t.additionalProperties;for(let c of Object.keys(e))c in i||C(e[c],a,_(n,c),r,s)}for(let[a,c]of Object.entries(i))e[a]!==void 0&&C(e[a],c,_(n,a),r,s);return}if(t.type==="array"){if(!Array.isArray(e)){s.push({path:n,message:`must be an array; received ${x(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 i=e.map(a=>JSON.stringify(a));new Set(i).size!==i.length&&s.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((i,a)=>C(i,t.items,`${n}[${a}]`,r,s));return}if(t.type==="string"){if(typeof e!="string"){s.push({path:n,message:`must be a string; received ${x(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 ${x(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){s.push({path:n,message:`must be an integer; received ${x(e)}`});return}t.minimum!==void 0&&e<t.minimum&&s.push({path:n,message:`must be at least ${t.minimum}`})}}function Ye(e){return{...e,$schema:e.$schema===void 0?G: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?de.map(t=>({...t})):e.rules}}function Je(){let e=new Set(["1.1"]);for(let t of B)t.from!=="unversioned"&&e.add(t.from),e.add(t.to);return e}function ze(e,t="ark.config.json"){if(!pe(e))throw new S(t,[{path:"$",message:`must be an object; received ${x(e)}`}]);let n=Je(),r=e.schemaVersion===void 0?"unversioned":typeof e.schemaVersion=="string"?e.schemaVersion:null;if(r===null)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.1`}]);if(r!=="unversioned"&&!n.has(r))throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let s=r,i={...e},a=0;for(;s!=="1.1"&&a<B.length+1;){a+=1;let g=B.find(f=>f.from===s);if(!g)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(s)}; expected 1.1`}]);s=g.to,i.schemaVersion=s}if(s!=="1.1")throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let c=r==="unversioned"?"unversioned":r==="1.0"?"1.0":null;return{candidate:Ye(i),migratedFrom:c}}function Ze(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=ze(e,t),s=[];if(C(n,ce,"$",ce,s),s.length>0)throw new S(t,s);return{config:n,migratedFrom:r}}function ue(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new S(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return Ze(n,t)}var Xe="docs/diagnostics.md";function b(e){return typeof e=="string"&&e.length>0?e:void 0}function fe(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Qe(e){let t=typeof e.ruleId=="string"?e.ruleId:typeof e.code=="string"?e.code:void 0,n=typeof e.file=="string"?e.file:void 0,r=typeof e.fromLayer=="string"?e.fromLayer:void 0,s=typeof e.toLayer=="string"?e.toLayer:void 0,i=typeof e.target=="string"?e.target:void 0;return[t,n,r??"",s??"",i??""].join("|")}function et(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function tt(e){return`${Xe}#${e}`}function nt(e,t,n){if(e==="LAYER_IMPORT_VIOLATION")return t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`;if(e==="FORBIDDEN_GLOBAL")return`Inject ${t.target??"the capability"} through a port, then preflight again.`;if(e==="CAPABILITY_VIOLATION")return`Define a ${b(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`;if(e==="CIRCULAR_DEPENDENCY")return"Extract the shared dependency into a third module, then preflight again.";if(e==="RAW_EVENT_PUBLISH")return"Publish through a registered intent creator, then run Ark again.";if(e==="PUBLISH_MISSING_SOURCE")return"Add metadata.source to the publish call, then run Ark again.";if(e==="ARKRULE_STRUCTURE"||e==="ARKRULE_INVARIANT"||e==="INVARIANT_UNCOVERED"||e.startsWith("ARKRULE_")){let r=t.arkruleSource??"arkrules/<Layer>.json";return`Fix the structure or invariant for ${t.arkruleId??"the ArkRule"} (declared in ${r}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`}return`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function ge(e,t="error",n){let r=b(e.ruleId)??b(e.code)??"ARK_UNKNOWN",s=e.severity==="warning"||e.failsStrict===!1||e.typeOnly===!0&&e.peerIsolation!==!0?"warning":t,i={...b(e.target)?{target:b(e.target)}:{},...b(e.fromLayer)?{fromLayer:b(e.fromLayer)}:{},...b(e.toLayer)?{toLayer:b(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{},...typeof e.targetTypeOnlyExports=="boolean"?{targetTypeOnlyExports:e.targetTypeOnlyExports}:{},...typeof e.sourcePureTypeModule=="boolean"?{sourcePureTypeModule:e.sourcePureTypeModule}:{},...typeof e.namedBindingsTypeOnly=="boolean"?{namedBindingsTypeOnly:e.namedBindingsTypeOnly}:{},...typeof e.portProofEligible=="boolean"?{portProofEligible:e.portProofEligible}:{},...typeof e.peerIsolation=="boolean"?{peerIsolation:e.peerIsolation}:{},...b(e.capability)?{capability:b(e.capability)}:{},...b(e.edgeKind)?{edgeKind:b(e.edgeKind)}:{},...b(e.arkruleId)?{arkruleId:b(e.arkruleId)}:{},...b(e.arkruleSource)?{arkruleSource:b(e.arkruleSource)}:{}},a=n??Qe(e),c=et(a);return{ruleId:r,severity:s,message:b(e.message)??r,location:{file:b(e.file)??"<unknown>",line:fe(e.line,1),column:fe(e.column,1)},evidence:i,nextAction:b(e.nextAction)??nt(r,i,e),findingRef:c,targetKey:a,docsCodePath:tt(r)}}var me={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."},ht=Object.freeze([{layer:"DomainModel",prefixes:["Domain."]},{layer:"ApplicationOrchestration",prefixes:["Application."]},{layer:"PersistenceAdapters",prefixes:["Adapter.Persistence.","Adapter.Repository."]},{layer:"IntegrationAdapters",prefixes:["Adapter.Integration.","Adapter.External."]},{layer:"WorkflowSagaEngine",prefixes:["Workflow."]},{layer:"BackgroundJobsScheduling",prefixes:["Job."]},{layer:"PresentationAdapters",prefixes:["Presentation.","Adapter.Presentation.","Adapter.Api."]},{layer:"ReportingReadModels",prefixes:["Reporting."]},{layer:"ExtensibilityMetadata",prefixes:["Metadata."]},{layer:"SecurityAuditObservability",prefixes:["Security.","Audit.","Observability."]},{layer:"Kernel",prefixes:["Kernel."]}]);function rt(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function U(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&rt(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:me.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:me.PUBLISH_MISSING_SOURCE}),t}function L(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function w(e,t,n,r,s){let i=ge({...r,line:r.line??t.loc?.start?.line,column:r.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...s?{data:s}:{},diagnostic:i}),i}function T(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=p.default.dirname(p.default.resolve(e));for(;;){let n=p.default.join(t,"ark.config.json");if(k.default.existsSync(n))return n;let r=p.default.dirname(t);if(r===t)return null;t=r}}var ye=new Map;function j(e){if(!k.default.existsSync(e))return null;let t=k.default.readFileSync(e,"utf8"),n=ye.get(e);if(n?.source===t)return n.config;let r=ue(t,e).config;return ye.set(e,{source:t,config:r}),r}function W(e,t){return(e.include??[]).some(r=>{let s=String(r).replace(/\\/g,"/").replace(/^\.\//,"").replace(/\/$/,"");return s==="."||t===s||t.startsWith(`${s}/`)})&&!se(t,e)}function be(e){let t=[e,`${e}.ts`,`${e}.tsx`,`${e}.mts`,`${e}.cts`,`${e}.js`,`${e}.jsx`,p.default.join(e,"index.ts"),p.default.join(e,"index.tsx"),p.default.join(e,"index.js")];for(let n of t)try{if(k.default.existsSync(n)&&k.default.statSync(n).isFile())return n}catch{}return null}function he(e){let t=p.default.resolve(e),n=null;for(;;){let f=p.default.join(t,"tsconfig.json");if(k.default.existsSync(f)){n=f;break}let d=p.default.dirname(t);if(d===t)break;t=d}if(!n)return{baseUrl:e,aliases:[]};let r=f=>{try{let d=k.default.readFileSync(f,"utf8");return d=d.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1"),JSON.parse(d)}catch{return null}},s=(f,d)=>{if(d>4)return{};let o=r(f);if(!o)return{};let l=o.compilerOptions??{},u=l.baseUrl,m=l.paths,y=o.extends;if(typeof y=="string"&&!y.startsWith("@")){let h=p.default.resolve(p.default.dirname(f),y.endsWith(".json")?y:`${y}.json`);if(k.default.existsSync(h)){let A=s(h,d+1);u=u??A.baseUrl,m={...A.paths??{},...m??{}}}}return{baseUrl:u,paths:m}},i=s(n,0),a=p.default.dirname(n),c=p.default.resolve(a,i.baseUrl||"."),g=[];for(let[f,d]of Object.entries(i.paths||{})){if(!Array.isArray(d)||d.length===0)continue;let o=f.replace(/\*$/,"");o&&g.push({from:o,to:String(d[0]).replace(/\*$/,"")})}return g.sort((f,d)=>d.from.length-f.from.length),{baseUrl:c,aliases:g}}function Ae(e,t){if(!t.startsWith("."))return null;let n=p.default.resolve(p.default.dirname(e),t);return be(n)}function Se(e,t,n){if(!t)return null;if(t.startsWith("."))return Ae(e,t);let r=n||p.default.dirname(e),{baseUrl:s,aliases:i}=he(r),a=i.find(g=>t.startsWith(g.from));if(!a)return null;let c=p.default.resolve(s,`${a.to}${t.slice(a.from.length)}`);return be(c)}function D(e){return typeof e?.value=="string"?e.value:void 0}function Y(e){return e?.name??D(e)}function J(e){return e.sourceCode??e.getSourceCode?.()}function ke(e,t){let n=J(e)?.getScope?.(t);for(;n;){let r=n.references?.find(s=>s.identifier===t);if(r)return r;n=n.upper??void 0}}function P(e,t,n){let r=ke(e,t);if(r?.resolved)return(r.resolved.defs?.length??0)>0;let s=J(e)?.getScope?.(t);for(;s;){let i=s.set?.get(n);if(i)return(i.defs?.length??0)>0;s=s.upper??void 0}return!1}function st(e,t){let n=ke(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function Ie(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=Ie(e.object),r=Y(e.property);if(!(!n||!r))return{root:n.root,segments:[...n.segments,r]}}function ot(e){return Y(e.callee?.property)}function Re(e,t){return e?.properties?.find(n=>Y(n.key)===t)}function $(e,t){return Re(e,t)!==void 0}function it(e){let t=Re(e,"metadata")?.value;return $(t,"source")}function xe(e){return ot(e)==="publish"}function q(e){if(e.importKind==="type"||e.exportKind==="type")return!0;let t=e.specifiers??[];return t.length===0?!1:t.every(n=>n.type==="ImportSpecifier")?t.every(n=>n.importKind==="type"):t.every(n=>n.exportKind==="type")}function at(e){let t=e;for(;t?.parent;)t=t.parent;return t?.type==="Program"?t:void 0}function lt(e){let t=at(e)?.body;if(!t)return!1;let n=!1;for(let r of t){if(r.type==="ImportDeclaration"){if(!q(r))return!1;continue}if(!(r.type==="TSInterfaceDeclaration"||r.type==="TSTypeAliasDeclaration")){if(r.type==="ExportNamedDeclaration"){if(r.declaration){if(r.declaration.type!=="TSInterfaceDeclaration"&&r.declaration.type!=="TSTypeAliasDeclaration")return!1}else if(!q(r))return!1;n=!0;continue}return!1}}return n}var we={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=L(e),n=T(t),r=n?j(n):null,s=n?p.default.dirname(n):null,i=a=>{let c=D(a.source);if(c&&r&&s&&t){let g=p.default.isAbsolute(t)?t:p.default.resolve(t),f=p.default.relative(s,g).split(p.default.sep).join("/");if(!W(r,f))return;let d=R(f,r.layers);if(!d)return;let o=Se(g,c,s);if(!o)return;let l=p.default.relative(s,o).split(p.default.sep).join("/");if(l.startsWith(".."))return;let u=R(l,r.layers);if(!u)return;let m={fromPath:f,toPath:l,layers:r.layers},y=V(r.rules,d,u,m);if(y||F(r.rules,d,u,m)){let h=a.type?.startsWith("Export")?"export":"import",A=q(a),z=!!y?.peerIsolation,Z=A&&!z,X=y?.message??`${d} must not ${h} ${u}.`;w(e,a,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:f,fromLayer:d,toLayer:u,target:l,edgeKind:h,...z?{peerIsolation:!0}:{},...A?{typeOnly:!0}:{},...Z?{severity:"warning"}:{},...lt(a)?{sourcePureTypeModule:!0}:{},message:Z?`${X} (type-only \u2014 type placement debt; prefer SharedTypes / owning layer; not runtime coupling)`:X},{fromLayer:d,toLayer:u,specifier:c})}return}};return{ImportDeclaration:i,ExportNamedDeclaration:i,ExportAllDeclaration:i}}},Ee={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=D(n),s=U({publishCall:xe(t),rawIntentName:r,objectHasIntent:$(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(s.some(i=>i.ruleId==="RAW_EVENT_PUBLISH")){let i=s.find(a=>a.ruleId==="RAW_EVENT_PUBLISH");w(e,t,"rawPublish",{...i,file:L(e)})}}}}},Ce={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=t.arguments?.[2],i=U({publishCall:xe(t),rawIntentName:D(n),objectHasIntent:$(n,"intent"),arkPublishCandidate:!0,hasSource:it(n)||$(r,"source")}).find(a=>a.ruleId==="PUBLISH_MISSING_SOURCE");i&&w(e,t,"missingSource",{...i,file:L(e)})}}}},Le={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` is a standalone fallback when no project config applies."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.',forbiddenModule:'{{layer}} must not use module "{{specifier}}" because it is the import form of forbidden global "{{name}}".'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=L(e),n=e.options?.[0],r=T(t),s=r?j(r):null,i=r?p.default.dirname(r):null,a=null,c="this layer";if(s&&i&&t){let o=p.default.isAbsolute(t)?t:p.default.resolve(t),l=p.default.relative(i,o).split(p.default.sep).join("/");if(!W(s,l))return{};let u=s.layers?.find(m=>m.name===R(l,s.layers));u?.forbiddenGlobals?.length?(a=new Set(u.forbiddenGlobals),c=u.name):a=null}else n?.globals&&(a=new Set(n.globals));if(!a)return{};let g=typeof J(e)?.getScope=="function",f=(o,l)=>{let u=p.default.isAbsolute(t)?t:p.default.resolve(t),m=i?p.default.relative(i,u).split(p.default.sep).join("/"):t;w(e,o,s?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:m,fromLayer:c,target:l,message:`${c} must not use the ambient global "${l}".`},{name:l,layer:c})},d=(o,l,u,m)=>{if(u||typeof l!="string")return;let y=H(l,a);if(!y)return;let h=p.default.isAbsolute(t)?t:p.default.resolve(t),A=i?p.default.relative(i,h).split(p.default.sep).join("/"):t;w(e,o,"forbiddenModule",{ruleId:"FORBIDDEN_GLOBAL",file:A,fromLayer:c,target:l,edgeKind:m,message:`${c} must not use module "${l}" because it is the import form of forbidden global "${y}".`},{layer:c,name:y,specifier:l,importKind:m})};return{MemberExpression(o){if(o.parent?.type==="MemberExpression"&&o.parent.object===o)return;let l=Ie(o);if(!l||P(e,l.root,l.segments[0]))return;let u=l.segments[0]==="globalThis",m=u?l.segments.slice(1):l.segments,y;for(let h=m.length;h>=(u?1:2);h-=1){let A=m.slice(0,h).join(".");if(a.has(A)){y=A;break}}y?f(o,y):!g&&a.has(l.segments[0])&&f(o,l.segments[0])},CallExpression(o){let l=o;if(l.callee?.type==="Identifier"&&l.callee.name==="require"&&l.arguments?.[0]?.type==="Literal"&&!P(e,o,"require")&&d(o,l.arguments[0].value,!1,"require"),g)return;let u=l.callee?.type==="Identifier"?l.callee.name:void 0;u&&a.has(u)&&f(o,u)},ImportDeclaration(o){let l=o,u=(l.specifiers??[]).filter(y=>y.type==="ImportSpecifier"),m=u.length>0&&u.length===(l.specifiers??[]).length&&u.every(y=>y.importKind==="type");d(o,l.source?.value,l.importKind==="type"||m,"import")},ImportExpression(o){let l=o;l.source?.type==="Literal"&&d(o,l.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(o){let l=o;d(o,l.moduleReference?.expression?.value,l.importKind==="type"||l.isTypeOnly===!0,"require")},ExportNamedDeclaration(o){let l=o;if(!l.source)return;let u=l.specifiers??[],m=u.length>0&&u.every(y=>y.exportKind==="type");d(o,l.source.value,l.exportKind==="type"||m,"export")},ExportAllDeclaration(o){let l=o;d(o,l.source?.value,l.exportKind==="type","export")},NewExpression(o){if(g)return;let l=o.callee?.type==="Identifier"?o.callee.name:void 0;l&&a.has(l)&&f(o,l)},Identifier(o){!g||!o.name||!a.has(o.name)||!st(e,o)||P(e,o,o.name)||f(o,o.name)}}}},Ne={meta:{type:"problem",docs:{description:"Disallow importing modules whose effect capability the layer denies (ark.config.json capabilities.deny / pure \u2014 same wall surface as ark-check). Import dimension only: ambient globals stay with no-forbidden-globals and the CLI/hook symbol path."},messages:{deniedCapability:'{{layer}} denies the {{capability}} capability (ark.config.json); "{{specifier}}" imports it. Define a port and bind the implementation in an adapter layer.'},schema:[]},create(e){let t=L(e),n=T(t),r=n?j(n):null,s=n?p.default.dirname(n):null;if(!r||!s||!t)return{};let i=p.default.isAbsolute(t)?t:p.default.resolve(t),a=p.default.relative(s,i).split(p.default.sep).join("/");if(!W(r,a))return{};let c=r.layers?.find(d=>d.name===R(a,r.layers));if(!c)return{};let g=new Set(ae(c));if(g.size===0)return{};let f=(d,o,l,u)=>{if(l||typeof o!="string"||H(o,c.forbiddenGlobals??[]))return;let m=ie(o);!m||!g.has(m)||w(e,d,"deniedCapability",{ruleId:"CAPABILITY_VIOLATION",file:a,fromLayer:c.name,target:o,capability:m,edgeKind:u,message:`${c.name} denies the ${m} capability; found import of "${o}".`},{layer:c.name,capability:m,specifier:o})};return{ImportDeclaration(d){let o=d,l=(o.specifiers??[]).filter(m=>m.type==="ImportSpecifier"),u=l.length>0&&l.length===(o.specifiers??[]).length&&l.every(m=>m.importKind==="type");f(d,o.source?.value,o.importKind==="type"||u,"import")},ImportExpression(d){let o=d;o.source?.type==="Literal"&&f(d,o.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(d){let o=d;f(d,o.moduleReference?.expression?.value,o.importKind==="type"||o.isTypeOnly===!0,"require")},ExportNamedDeclaration(d){let o=d;if(!o.source)return;let l=o.specifiers??[],u=l.length>0&&l.every(m=>m.exportKind==="type");f(d,o.source.value,o.exportKind==="type"||u,"export")},ExportAllDeclaration(d){let o=d;f(d,o.source?.value,o.exportKind==="type","export")},CallExpression(d){let o=d;o.callee?.type==="Identifier"&&o.callee.name==="require"&&o.arguments?.[0]?.type==="Literal"&&!P(e,d,"require")&&f(d,o.arguments[0].value,!1,"require")}}}},ct={"no-domain-infra-imports":we,"no-raw-event-publish":Ee,"require-publish-source":Ce,"no-forbidden-globals":Le,"no-denied-capabilities":Ne},v={rules:ct};v.configs={recommended:{plugins:{ark:v},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error","ark/no-denied-capabilities":"error"}}};var dt=v;0&&(module.exports={findConfigPath,globToRegExp,isEdgeDenied,layerForRelativePath,loadArkConfig,noDeniedCapabilities,noDomainInfraImports,noForbiddenGlobals,noRawEventPublish,patternSpecificity,plugin,readTsconfigPathAliases,requirePublishSource,resolveImportSpecifier,resolveRelativeImport}); | ||
| `)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function pe(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function _(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function x(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Ye(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function C(e,t,n,r,s){if(t.$ref){let i=Ye(t.$ref,r);if(!i){s.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}C(e,i,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(i=>Object.is(i,e))){s.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!pe(e)){s.push({path:n,message:`must be an object; received ${x(e)}`});return}let i=t.properties??{};for(let a of t.required??[])e[a]===void 0&&s.push({path:_(n,a),message:"is required"});if(t.additionalProperties===!1)for(let a of Object.keys(e))a in i||s.push({path:_(n,a),message:"unknown field"});else if(t.additionalProperties!==void 0&&t.additionalProperties!==!0&&typeof t.additionalProperties=="object"){let a=t.additionalProperties;for(let c of Object.keys(e))c in i||C(e[c],a,_(n,c),r,s)}for(let[a,c]of Object.entries(i))e[a]!==void 0&&C(e[a],c,_(n,a),r,s);return}if(t.type==="array"){if(!Array.isArray(e)){s.push({path:n,message:`must be an array; received ${x(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 i=e.map(a=>JSON.stringify(a));new Set(i).size!==i.length&&s.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((i,a)=>C(i,t.items,`${n}[${a}]`,r,s));return}if(t.type==="string"){if(typeof e!="string"){s.push({path:n,message:`must be a string; received ${x(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 ${x(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){s.push({path:n,message:`must be an integer; received ${x(e)}`});return}t.minimum!==void 0&&e<t.minimum&&s.push({path:n,message:`must be at least ${t.minimum}`})}}function Je(e){return{...e,$schema:e.$schema===void 0?G: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?de.map(t=>({...t})):e.rules}}function ze(){let e=new Set(["1.1"]);for(let t of B)t.from!=="unversioned"&&e.add(t.from),e.add(t.to);return e}function Ze(e,t="ark.config.json"){if(!pe(e))throw new S(t,[{path:"$",message:`must be an object; received ${x(e)}`}]);let n=ze(),r=e.schemaVersion===void 0?"unversioned":typeof e.schemaVersion=="string"?e.schemaVersion:null;if(r===null)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.1`}]);if(r!=="unversioned"&&!n.has(r))throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let s=r,i={...e},a=0;for(;s!=="1.1"&&a<B.length+1;){a+=1;let g=B.find(f=>f.from===s);if(!g)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(s)}; expected 1.1`}]);s=g.to,i.schemaVersion=s}if(s!=="1.1")throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let c=r==="unversioned"?"unversioned":r==="1.0"?"1.0":null;return{candidate:Je(i),migratedFrom:c}}function Xe(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=Ze(e,t),s=[];if(C(n,ce,"$",ce,s),s.length>0)throw new S(t,s);return{config:n,migratedFrom:r}}function ue(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new S(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return Xe(n,t)}var Qe="docs/diagnostics.md";function h(e){return typeof e=="string"&&e.length>0?e:void 0}function fe(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function et(e){let t=typeof e.ruleId=="string"?e.ruleId:typeof e.code=="string"?e.code:void 0,n=typeof e.file=="string"?e.file:void 0,r=typeof e.fromLayer=="string"?e.fromLayer:void 0,s=typeof e.toLayer=="string"?e.toLayer:void 0,i=typeof e.target=="string"?e.target:void 0;return[t,n,r??"",s??"",i??""].join("|")}function tt(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function nt(e){return`${Qe}#${e}`}function rt(e,t,n){if(e==="LAYER_IMPORT_VIOLATION")return t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`;if(e==="FORBIDDEN_GLOBAL")return`Inject ${t.target??"the capability"} through a port, then preflight again.`;if(e==="CAPABILITY_VIOLATION")return`Define a ${h(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`;if(e==="CIRCULAR_DEPENDENCY")return"Extract the shared dependency into a third module, then preflight again.";if(e==="RAW_EVENT_PUBLISH")return"Publish through a registered intent creator, then run Ark again.";if(e==="PUBLISH_MISSING_SOURCE")return"Add metadata.source to the publish call, then run Ark again.";if(e==="ARKRULE_STRUCTURE"||e==="ARKRULE_INVARIANT"||e==="INVARIANT_UNCOVERED"||e.startsWith("ARKRULE_")){let r=t.arkruleSource??"arkrules/<Layer>.json";return`Fix the structure or invariant for ${t.arkruleId??"the ArkRule"} (declared in ${r}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`}return`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function ge(e,t="error",n){let r=h(e.ruleId)??h(e.code)??"ARK_UNKNOWN",s=e.severity==="warning"||e.failsStrict===!1||e.typeOnly===!0&&e.peerIsolation!==!0?"warning":t,i={...h(e.target)?{target:h(e.target)}:{},...h(e.fromLayer)?{fromLayer:h(e.fromLayer)}:{},...h(e.toLayer)?{toLayer:h(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{},...typeof e.targetTypeOnlyExports=="boolean"?{targetTypeOnlyExports:e.targetTypeOnlyExports}:{},...typeof e.sourcePureTypeModule=="boolean"?{sourcePureTypeModule:e.sourcePureTypeModule}:{},...typeof e.namedBindingsTypeOnly=="boolean"?{namedBindingsTypeOnly:e.namedBindingsTypeOnly}:{},...typeof e.portProofEligible=="boolean"?{portProofEligible:e.portProofEligible}:{},...typeof e.peerIsolation=="boolean"?{peerIsolation:e.peerIsolation}:{},...h(e.capability)?{capability:h(e.capability)}:{},...h(e.edgeKind)?{edgeKind:h(e.edgeKind)}:{},...h(e.arkruleId)?{arkruleId:h(e.arkruleId)}:{},...h(e.arkruleSource)?{arkruleSource:h(e.arkruleSource)}:{}},a=n??et(e),c=tt(a);return{ruleId:r,severity:s,message:h(e.message)??r,location:{file:h(e.file)??"<unknown>",line:fe(e.line,1),column:fe(e.column,1)},evidence:i,nextAction:h(e.nextAction)??rt(r,i,e),findingRef:c,targetKey:a,docsCodePath:nt(r)}}var me={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."},At=Object.freeze([{layer:"DomainModel",prefixes:["Domain."]},{layer:"ApplicationOrchestration",prefixes:["Application."]},{layer:"PersistenceAdapters",prefixes:["Adapter.Persistence.","Adapter.Repository."]},{layer:"IntegrationAdapters",prefixes:["Adapter.Integration.","Adapter.External."]},{layer:"WorkflowSagaEngine",prefixes:["Workflow."]},{layer:"BackgroundJobsScheduling",prefixes:["Job."]},{layer:"PresentationAdapters",prefixes:["Presentation.","Adapter.Presentation.","Adapter.Api."]},{layer:"ReportingReadModels",prefixes:["Reporting."]},{layer:"ExtensibilityMetadata",prefixes:["Metadata."]},{layer:"SecurityAuditObservability",prefixes:["Security.","Audit.","Observability."]},{layer:"Kernel",prefixes:["Kernel."]}]);function st(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function U(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&st(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:me.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:me.PUBLISH_MISSING_SOURCE}),t}function L(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function w(e,t,n,r,s){let i=ge({...r,line:r.line??t.loc?.start?.line,column:r.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...s?{data:s}:{},diagnostic:i}),i}function T(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=p.default.dirname(p.default.resolve(e));for(;;){let n=p.default.join(t,"ark.config.json");if(k.default.existsSync(n))return n;let r=p.default.dirname(t);if(r===t)return null;t=r}}var ye=new Map;function j(e){if(!k.default.existsSync(e))return null;let t=k.default.readFileSync(e,"utf8"),n=ye.get(e);if(n?.source===t)return n.config;let r=ue(t,e).config;return ye.set(e,{source:t,config:r}),r}function W(e,t){return(e.include??[]).some(r=>{let s=String(r).replace(/\\/g,"/").replace(/^\.\//,"").replace(/\/$/,"");return s==="."||t===s||t.startsWith(`${s}/`)})&&!se(t,e)}function he(e){let t=[e,`${e}.ts`,`${e}.tsx`,`${e}.mts`,`${e}.cts`,`${e}.js`,`${e}.jsx`,p.default.join(e,"index.ts"),p.default.join(e,"index.tsx"),p.default.join(e,"index.js")];for(let n of t)try{if(k.default.existsSync(n)&&k.default.statSync(n).isFile())return n}catch{}return null}function be(e){let t=p.default.resolve(e),n=null;for(;;){let f=p.default.join(t,"tsconfig.json");if(k.default.existsSync(f)){n=f;break}let d=p.default.dirname(t);if(d===t)break;t=d}if(!n)return{baseUrl:e,aliases:[]};let r=f=>{try{let d=k.default.readFileSync(f,"utf8");return d=d.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1"),JSON.parse(d)}catch{return null}},s=(f,d)=>{if(d>4)return{};let o=r(f);if(!o)return{};let l=o.compilerOptions??{},u=l.baseUrl,m=l.paths,y=o.extends;if(typeof y=="string"&&!y.startsWith("@")){let b=p.default.resolve(p.default.dirname(f),y.endsWith(".json")?y:`${y}.json`);if(k.default.existsSync(b)){let A=s(b,d+1);u=u??A.baseUrl,m={...A.paths??{},...m??{}}}}return{baseUrl:u,paths:m}},i=s(n,0),a=p.default.dirname(n),c=p.default.resolve(a,i.baseUrl||"."),g=[];for(let[f,d]of Object.entries(i.paths||{})){if(!Array.isArray(d)||d.length===0)continue;let o=f.replace(/\*$/,"");o&&g.push({from:o,to:String(d[0]).replace(/\*$/,"")})}return g.sort((f,d)=>d.from.length-f.from.length),{baseUrl:c,aliases:g}}function Ae(e,t){if(!t.startsWith("."))return null;let n=p.default.resolve(p.default.dirname(e),t);return he(n)}function Se(e,t,n){if(!t)return null;if(t.startsWith("."))return Ae(e,t);let r=n||p.default.dirname(e),{baseUrl:s,aliases:i}=be(r),a=i.find(g=>t.startsWith(g.from));if(!a)return null;let c=p.default.resolve(s,`${a.to}${t.slice(a.from.length)}`);return he(c)}function D(e){return typeof e?.value=="string"?e.value:void 0}function Y(e){return e?.name??D(e)}function J(e){return e.sourceCode??e.getSourceCode?.()}function ke(e,t){let n=J(e)?.getScope?.(t);for(;n;){let r=n.references?.find(s=>s.identifier===t);if(r)return r;n=n.upper??void 0}}function P(e,t,n){let r=ke(e,t);if(r?.resolved)return(r.resolved.defs?.length??0)>0;let s=J(e)?.getScope?.(t);for(;s;){let i=s.set?.get(n);if(i)return(i.defs?.length??0)>0;s=s.upper??void 0}return!1}function ot(e,t){let n=ke(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function Ie(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=Ie(e.object),r=Y(e.property);if(!(!n||!r))return{root:n.root,segments:[...n.segments,r]}}function it(e){return Y(e.callee?.property)}function Re(e,t){return e?.properties?.find(n=>Y(n.key)===t)}function $(e,t){return Re(e,t)!==void 0}function at(e){let t=Re(e,"metadata")?.value;return $(t,"source")}function xe(e){return it(e)==="publish"}function q(e){if(e.importKind==="type"||e.exportKind==="type")return!0;let t=e.specifiers??[];return t.length===0?!1:t.every(n=>n.type==="ImportSpecifier")?t.every(n=>n.importKind==="type"):t.every(n=>n.exportKind==="type")}function lt(e){let t=e;for(;t?.parent;)t=t.parent;return t?.type==="Program"?t:void 0}function ct(e){let t=lt(e)?.body;if(!t)return!1;let n=!1;for(let r of t){if(r.type==="ImportDeclaration"){if(!q(r))return!1;continue}if(!(r.type==="TSInterfaceDeclaration"||r.type==="TSTypeAliasDeclaration")){if(r.type==="ExportNamedDeclaration"){if(r.declaration){if(r.declaration.type!=="TSInterfaceDeclaration"&&r.declaration.type!=="TSTypeAliasDeclaration")return!1}else if(!q(r))return!1;n=!0;continue}return!1}}return n}var we={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=L(e),n=T(t),r=n?j(n):null,s=n?p.default.dirname(n):null,i=a=>{let c=D(a.source);if(c&&r&&s&&t){let g=p.default.isAbsolute(t)?t:p.default.resolve(t),f=p.default.relative(s,g).split(p.default.sep).join("/");if(!W(r,f))return;let d=R(f,r.layers);if(!d)return;let o=Se(g,c,s);if(!o)return;let l=p.default.relative(s,o).split(p.default.sep).join("/");if(l.startsWith(".."))return;let u=R(l,r.layers);if(!u)return;let m={fromPath:f,toPath:l,layers:r.layers},y=V(r.rules,d,u,m);if(y||F(r.rules,d,u,m)){let b=a.type?.startsWith("Export")?"export":"import",A=q(a),z=!!y?.peerIsolation,Z=A&&!z,X=y?.message??`${d} must not ${b} ${u}.`;w(e,a,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:f,fromLayer:d,toLayer:u,target:l,edgeKind:b,...z?{peerIsolation:!0}:{},...A?{typeOnly:!0}:{},...Z?{severity:"warning"}:{},...ct(a)?{sourcePureTypeModule:!0}:{},message:Z?`${X} (type-only \u2014 type placement debt; prefer SharedTypes / owning layer; not runtime coupling)`:X},{fromLayer:d,toLayer:u,specifier:c})}return}};return{ImportDeclaration:i,ExportNamedDeclaration:i,ExportAllDeclaration:i}}},Ee={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=D(n),s=U({publishCall:xe(t),rawIntentName:r,objectHasIntent:$(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(s.some(i=>i.ruleId==="RAW_EVENT_PUBLISH")){let i=s.find(a=>a.ruleId==="RAW_EVENT_PUBLISH");w(e,t,"rawPublish",{...i,file:L(e)})}}}}},Ce={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=t.arguments?.[2],i=U({publishCall:xe(t),rawIntentName:D(n),objectHasIntent:$(n,"intent"),arkPublishCandidate:!0,hasSource:at(n)||$(r,"source")}).find(a=>a.ruleId==="PUBLISH_MISSING_SOURCE");i&&w(e,t,"missingSource",{...i,file:L(e)})}}}},Le={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` is a standalone fallback when no project config applies."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.',forbiddenModule:'{{layer}} must not use module "{{specifier}}" because it is the import form of forbidden global "{{name}}".'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=L(e),n=e.options?.[0],r=T(t),s=r?j(r):null,i=r?p.default.dirname(r):null,a=null,c="this layer";if(s&&i&&t){let o=p.default.isAbsolute(t)?t:p.default.resolve(t),l=p.default.relative(i,o).split(p.default.sep).join("/");if(!W(s,l))return{};let u=s.layers?.find(m=>m.name===R(l,s.layers));u?.forbiddenGlobals?.length?(a=new Set(u.forbiddenGlobals),c=u.name):a=null}else n?.globals&&(a=new Set(n.globals));if(!a)return{};let g=typeof J(e)?.getScope=="function",f=(o,l)=>{let u=p.default.isAbsolute(t)?t:p.default.resolve(t),m=i?p.default.relative(i,u).split(p.default.sep).join("/"):t;w(e,o,s?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:m,fromLayer:c,target:l,message:`${c} must not use the ambient global "${l}".`},{name:l,layer:c})},d=(o,l,u,m)=>{if(u||typeof l!="string")return;let y=H(l,a);if(!y)return;let b=p.default.isAbsolute(t)?t:p.default.resolve(t),A=i?p.default.relative(i,b).split(p.default.sep).join("/"):t;w(e,o,"forbiddenModule",{ruleId:"FORBIDDEN_GLOBAL",file:A,fromLayer:c,target:l,edgeKind:m,message:`${c} must not use module "${l}" because it is the import form of forbidden global "${y}".`},{layer:c,name:y,specifier:l,importKind:m})};return{MemberExpression(o){if(o.parent?.type==="MemberExpression"&&o.parent.object===o)return;let l=Ie(o);if(!l||P(e,l.root,l.segments[0]))return;let u=l.segments[0]==="globalThis",m=u?l.segments.slice(1):l.segments,y;for(let b=m.length;b>=(u?1:2);b-=1){let A=m.slice(0,b).join(".");if(a.has(A)){y=A;break}}y?f(o,y):!g&&a.has(l.segments[0])&&f(o,l.segments[0])},CallExpression(o){let l=o;if(l.callee?.type==="Identifier"&&l.callee.name==="require"&&l.arguments?.[0]?.type==="Literal"&&!P(e,o,"require")&&d(o,l.arguments[0].value,!1,"require"),g)return;let u=l.callee?.type==="Identifier"?l.callee.name:void 0;u&&a.has(u)&&f(o,u)},ImportDeclaration(o){let l=o,u=(l.specifiers??[]).filter(y=>y.type==="ImportSpecifier"),m=u.length>0&&u.length===(l.specifiers??[]).length&&u.every(y=>y.importKind==="type");d(o,l.source?.value,l.importKind==="type"||m,"import")},ImportExpression(o){let l=o;l.source?.type==="Literal"&&d(o,l.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(o){let l=o;d(o,l.moduleReference?.expression?.value,l.importKind==="type"||l.isTypeOnly===!0,"require")},ExportNamedDeclaration(o){let l=o;if(!l.source)return;let u=l.specifiers??[],m=u.length>0&&u.every(y=>y.exportKind==="type");d(o,l.source.value,l.exportKind==="type"||m,"export")},ExportAllDeclaration(o){let l=o;d(o,l.source?.value,l.exportKind==="type","export")},NewExpression(o){if(g)return;let l=o.callee?.type==="Identifier"?o.callee.name:void 0;l&&a.has(l)&&f(o,l)},Identifier(o){!g||!o.name||!a.has(o.name)||!ot(e,o)||P(e,o,o.name)||f(o,o.name)}}}},Ne={meta:{type:"problem",docs:{description:"Disallow importing modules whose effect capability the layer denies (ark.config.json capabilities.deny / pure \u2014 same wall surface as ark-check). Import dimension only: ambient globals stay with no-forbidden-globals and the CLI/hook symbol path."},messages:{deniedCapability:'{{layer}} denies the {{capability}} capability (ark.config.json); "{{specifier}}" imports it. Define a port and bind the implementation in an adapter layer.'},schema:[]},create(e){let t=L(e),n=T(t),r=n?j(n):null,s=n?p.default.dirname(n):null;if(!r||!s||!t)return{};let i=p.default.isAbsolute(t)?t:p.default.resolve(t),a=p.default.relative(s,i).split(p.default.sep).join("/");if(!W(r,a))return{};let c=r.layers?.find(d=>d.name===R(a,r.layers));if(!c)return{};let g=new Set(ae(c));if(g.size===0)return{};let f=(d,o,l,u)=>{if(l||typeof o!="string"||H(o,c.forbiddenGlobals??[]))return;let m=ie(o);!m||!g.has(m)||w(e,d,"deniedCapability",{ruleId:"CAPABILITY_VIOLATION",file:a,fromLayer:c.name,target:o,capability:m,edgeKind:u,message:`${c.name} denies the ${m} capability; found import of "${o}".`},{layer:c.name,capability:m,specifier:o})};return{ImportDeclaration(d){let o=d,l=(o.specifiers??[]).filter(m=>m.type==="ImportSpecifier"),u=l.length>0&&l.length===(o.specifiers??[]).length&&l.every(m=>m.importKind==="type");f(d,o.source?.value,o.importKind==="type"||u,"import")},ImportExpression(d){let o=d;o.source?.type==="Literal"&&f(d,o.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(d){let o=d;f(d,o.moduleReference?.expression?.value,o.importKind==="type"||o.isTypeOnly===!0,"require")},ExportNamedDeclaration(d){let o=d;if(!o.source)return;let l=o.specifiers??[],u=l.length>0&&l.every(m=>m.exportKind==="type");f(d,o.source.value,o.exportKind==="type"||u,"export")},ExportAllDeclaration(d){let o=d;f(d,o.source?.value,o.exportKind==="type","export")},CallExpression(d){let o=d;o.callee?.type==="Identifier"&&o.callee.name==="require"&&o.arguments?.[0]?.type==="Literal"&&!P(e,d,"require")&&f(d,o.arguments[0].value,!1,"require")}}}},dt={"no-domain-infra-imports":we,"no-raw-event-publish":Ee,"require-publish-source":Ce,"no-forbidden-globals":Le,"no-denied-capabilities":Ne},v={rules:dt};v.configs={recommended:{plugins:{ark:v},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error","ark/no-denied-capabilities":"error"}}};var pt=v;0&&(module.exports={findConfigPath,globToRegExp,isEdgeDenied,layerForRelativePath,loadArkConfig,noDeniedCapabilities,noDomainInfraImports,noForbiddenGlobals,noRawEventPublish,patternSpecificity,plugin,readTsconfigPathAliases,requirePublishSource,resolveImportSpecifier,resolveRelativeImport}); |
@@ -1,3 +0,3 @@ | ||
| import I from"fs";import p from"path";var z=new Map;function Z(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function N(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let s=e[n+1];if("*?{}[],".includes(s)||s==="\\"){t+="\\"+s,n+=1;continue}t+="/";continue}t+=r}return t}function Ae(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function L(e){let t=z.get(e);if(t)return t;let n=N(e),r=Ae(n),s="",i=0;for(let c=0;c<n.length;c+=1){let g=n[c];g==="\\"&&c+1<n.length?(s+=Z(n[c+1]),c+=1):g==="*"?n[c+1]==="*"?n[c+2]==="/"?(s+="(?:.*/)?",c+=2):(s+=".*",c+=1):s+="[^/]*":g==="?"?s+="[^/]":g==="{"&&r?(s+="(?:",i+=1):g==="}"&&r&&i>0?(s+=")",i-=1):g===","&&r&&i>0?s+="|":s+=Z(g)}let a=new RegExp(`^${s}$`);return z.set(e,a),a}function Se(e){return N(String(e)).split("/").filter(Boolean).filter(n=>n!=="**"&&n!=="*"&&!n.includes("*")&&!n.includes("?")&&!n.includes("{")&&!n.includes("["))}function Q(e,t){let n=N(String(e)),r=Se(n),s=n.replace(/\*/g,"").length,i=r.length*1e4+s;if(t==null||t==="")return i;let a=String(t).split(/[/\\]/).filter(Boolean);if(r.length===0)return s;let c=0,g=-1;for(let f of r){let d=-1;for(let o=c;o<a.length;o+=1)if(a[o]===f){d=o;break}if(d<0)return i;g=d,c=d+1}return(g+1)*1e6+r.length*1e4+s}function w(e,t){let n=String(e).split(/[/\\]/).join("/"),r,s=-1;for(let i of t??[])if(!(i.exclude??[]).some(a=>L(a).test(n))){for(let a of i.patterns??[])if(L(a).test(n)){let c=Q(a,n);c>s&&(s=c,r=i.name)}}return r}function X(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(s=>String(s).toLowerCase()));for(let s=0;s<n.length-1;s+=1)if(r.has(n[s].toLowerCase()))return`${n[s].toLowerCase()}/${n[s+1].toLowerCase()}`}function ke(e){let t=new Set;for(let n of e??[]){let s=N(String(n)).split("/").filter(Boolean);for(let i=0;i<s.length;i+=1){let a=s[i];if((a==="**"||a==="*")&&i>0){let c=s[i-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function Ie(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(s=>typeof s=="string"&&s.length>0);let r=(n??[]).find(s=>s.name===t);return ke(r?.patterns)}function v(e,t,n,r){for(let s of e??[])if(!(s.from!==t||s.to!==n)&&s.allowed===!1){if(s.peerIsolation){let i=r?.fromPath,a=r?.toPath;if(!i||!a)return s;let c=Ie(s,t,r?.layers);if(c.length===0)return s;let g=X(i,c),f=X(a,c);if(!g||!f||g!==f)return s;continue}if(t!==n)return s}}function ee(e,t,n,r){return v(e,t,n,r)!==void 0}var Re=["**/*.gen.ts","**/*.gen.tsx","**/*.generated.ts","**/*.generated.tsx"];function xe(e){let t=Array.isArray(e?.exclude)?e.exclude.filter(r=>typeof r=="string"):[];return[...e?.excludeGenerated===!1?[]:Re,...t]}function te(e,t){let n=String(e).split(/[/\\]/).join("/");return xe(t).some(r=>L(r).test(n))}var ne=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),we=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),tt=Object.freeze(Object.keys(we).sort()),T=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"}),Ee=Object.freeze({process:Object.freeze(["process","node:process"])});function re(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=T[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),s=T[r];if(s)return s;let i=e.indexOf("/",n+1);return i<0?null:T[e.slice(0,i)]??null}function j(e,t){for(let n of t)if(Ee[n]?.includes(e))return n;return null}function se(e){if(e?.pure===!0)return[...ne].sort();let n=(e?.capabilities?.deny??[]).filter(r=>ne.includes(r));return[...new Set(n)].sort()}var D="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",oe=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],Ce=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function Le(){let e=[];for(let t of oe)for(let n of oe)t===n||Ce.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var ae=Le(),M=[{from:"unversioned",to:"1.0"},{from:"1.0",to:"1.1"}],k={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},ie={$schema:"https://json-schema.org/draft/2020-12/schema",$id:D,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:D,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.1",default:"1.1"},name:{type:"string",minLength:1},include:{...k,minItems:1,default:["src"]},exclude:{...k,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:ae,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...k,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}},arkRules:{type:"object",additionalProperties:{type:"string",minLength:1},default:{}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...k,minItems:1},exclude:k,intentPrefixes:k,description:{type:"string",minLength:1},forbiddenGlobals:k,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...k,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},S=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}): | ||
| import I from"fs";import p from"path";var z=new Map;function Z(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function N(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let s=e[n+1];if("*?{}[],".includes(s)||s==="\\"){t+="\\"+s,n+=1;continue}t+="/";continue}t+=r}return t}function Ae(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function L(e){let t=z.get(e);if(t)return t;let n=N(e),r=Ae(n),s="",i=0;for(let c=0;c<n.length;c+=1){let g=n[c];g==="\\"&&c+1<n.length?(s+=Z(n[c+1]),c+=1):g==="*"?n[c+1]==="*"?n[c+2]==="/"?(s+="(?:.*/)?",c+=2):(s+=".*",c+=1):s+="[^/]*":g==="?"?s+="[^/]":g==="{"&&r?(s+="(?:",i+=1):g==="}"&&r&&i>0?(s+=")",i-=1):g===","&&r&&i>0?s+="|":s+=Z(g)}let a=new RegExp(`^${s}$`);return z.set(e,a),a}function Se(e){return N(String(e)).split("/").filter(Boolean).filter(n=>n!=="**"&&n!=="*"&&!n.includes("*")&&!n.includes("?")&&!n.includes("{")&&!n.includes("["))}function Q(e,t){let n=N(String(e)),r=Se(n),s=n.replace(/\*/g,"").length,i=r.length*1e4+s;if(t==null||t==="")return i;let a=String(t).split(/[/\\]/).filter(Boolean);if(r.length===0)return s;let c=0,g=-1;for(let f of r){let d=-1;for(let o=c;o<a.length;o+=1)if(a[o]===f){d=o;break}if(d<0)return i;g=d,c=d+1}return(g+1)*1e6+r.length*1e4+s}function w(e,t){let n=String(e).split(/[/\\]/).join("/"),r,s=-1;for(let i of t??[])if(!(i.exclude??[]).some(a=>L(a).test(n))){for(let a of i.patterns??[])if(L(a).test(n)){let c=Q(a,n);c>s&&(s=c,r=i.name)}}return r}function X(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(s=>String(s).toLowerCase()));for(let s=0;s<n.length-1;s+=1)if(r.has(n[s].toLowerCase()))return`${n[s].toLowerCase()}/${n[s+1].toLowerCase()}`}function ke(e){let t=new Set;for(let n of e??[]){let s=N(String(n)).split("/").filter(Boolean);for(let i=0;i<s.length;i+=1){let a=s[i];if((a==="**"||a==="*")&&i>0){let c=s[i-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function Ie(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(s=>typeof s=="string"&&s.length>0);let r=(n??[]).find(s=>s.name===t);return ke(r?.patterns)}function Re(e){return!e.fromPath||!e.toPath||e.folderCount<=0||!e.fromSlice||!e.toSlice?!0:e.fromSlice!==e.toSlice}function v(e,t,n,r){for(let s of e??[])if(!(s.from!==t||s.to!==n)&&s.allowed===!1){if(s.peerIsolation){let i=r?.fromPath,a=r?.toPath,c=Ie(s,t,r?.layers),g=i&&a?X(i,c):void 0,f=i&&a?X(a,c):void 0;if(Re({fromPath:i,toPath:a,folderCount:c.length,fromSlice:g,toSlice:f}))return s;continue}if(t!==n)return s}}function ee(e,t,n,r){return v(e,t,n,r)!==void 0}var xe=["**/*.gen.ts","**/*.gen.tsx","**/*.generated.ts","**/*.generated.tsx"];function we(e){let t=Array.isArray(e?.exclude)?e.exclude.filter(r=>typeof r=="string"):[];return[...e?.excludeGenerated===!1?[]:xe,...t]}function te(e,t){let n=String(e).split(/[/\\]/).join("/");return we(t).some(r=>L(r).test(n))}var ne=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),Ee=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),nt=Object.freeze(Object.keys(Ee).sort()),T=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"}),Ce=Object.freeze({process:Object.freeze(["process","node:process"])});function re(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=T[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),s=T[r];if(s)return s;let i=e.indexOf("/",n+1);return i<0?null:T[e.slice(0,i)]??null}function j(e,t){for(let n of t)if(Ce[n]?.includes(e))return n;return null}function se(e){if(e?.pure===!0)return[...ne].sort();let n=(e?.capabilities?.deny??[]).filter(r=>ne.includes(r));return[...new Set(n)].sort()}var D="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",oe=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],Le=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function Ne(){let e=[];for(let t of oe)for(let n of oe)t===n||Le.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var ae=Ne(),M=[{from:"unversioned",to:"1.0"},{from:"1.0",to:"1.1"}],k={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},ie={$schema:"https://json-schema.org/draft/2020-12/schema",$id:D,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:D,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.1",default:"1.1"},name:{type:"string",minLength:1},include:{...k,minItems:1,default:["src"]},exclude:{...k,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:ae,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...k,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}},arkRules:{type:"object",additionalProperties:{type:"string",minLength:1},default:{}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...k,minItems:1},exclude:k,intentPrefixes:k,description:{type:"string",minLength:1},forbiddenGlobals:k,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...k,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},S=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}): | ||
| ${n.map(r=>`- ${r.path}: ${r.message}`).join(` | ||
| `)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function le(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function O(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function R(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Ne(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function E(e,t,n,r,s){if(t.$ref){let i=Ne(t.$ref,r);if(!i){s.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}E(e,i,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(i=>Object.is(i,e))){s.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!le(e)){s.push({path:n,message:`must be an object; received ${R(e)}`});return}let i=t.properties??{};for(let a of t.required??[])e[a]===void 0&&s.push({path:O(n,a),message:"is required"});if(t.additionalProperties===!1)for(let a of Object.keys(e))a in i||s.push({path:O(n,a),message:"unknown field"});else if(t.additionalProperties!==void 0&&t.additionalProperties!==!0&&typeof t.additionalProperties=="object"){let a=t.additionalProperties;for(let c of Object.keys(e))c in i||E(e[c],a,O(n,c),r,s)}for(let[a,c]of Object.entries(i))e[a]!==void 0&&E(e[a],c,O(n,a),r,s);return}if(t.type==="array"){if(!Array.isArray(e)){s.push({path:n,message:`must be an array; received ${R(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 i=e.map(a=>JSON.stringify(a));new Set(i).size!==i.length&&s.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((i,a)=>E(i,t.items,`${n}[${a}]`,r,s));return}if(t.type==="string"){if(typeof e!="string"){s.push({path:n,message:`must be a string; received ${R(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 ${R(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){s.push({path:n,message:`must be an integer; received ${R(e)}`});return}t.minimum!==void 0&&e<t.minimum&&s.push({path:n,message:`must be at least ${t.minimum}`})}}function Oe(e){return{...e,$schema:e.$schema===void 0?D: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?ae.map(t=>({...t})):e.rules}}function _e(){let e=new Set(["1.1"]);for(let t of M)t.from!=="unversioned"&&e.add(t.from),e.add(t.to);return e}function Pe(e,t="ark.config.json"){if(!le(e))throw new S(t,[{path:"$",message:`must be an object; received ${R(e)}`}]);let n=_e(),r=e.schemaVersion===void 0?"unversioned":typeof e.schemaVersion=="string"?e.schemaVersion:null;if(r===null)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.1`}]);if(r!=="unversioned"&&!n.has(r))throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let s=r,i={...e},a=0;for(;s!=="1.1"&&a<M.length+1;){a+=1;let g=M.find(f=>f.from===s);if(!g)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(s)}; expected 1.1`}]);s=g.to,i.schemaVersion=s}if(s!=="1.1")throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let c=r==="unversioned"?"unversioned":r==="1.0"?"1.0":null;return{candidate:Oe(i),migratedFrom:c}}function $e(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=Pe(e,t),s=[];if(E(n,ie,"$",ie,s),s.length>0)throw new S(t,s);return{config:n,migratedFrom:r}}function ce(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new S(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return $e(n,t)}var ve="docs/diagnostics.md";function b(e){return typeof e=="string"&&e.length>0?e:void 0}function de(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Te(e){let t=typeof e.ruleId=="string"?e.ruleId:typeof e.code=="string"?e.code:void 0,n=typeof e.file=="string"?e.file:void 0,r=typeof e.fromLayer=="string"?e.fromLayer:void 0,s=typeof e.toLayer=="string"?e.toLayer:void 0,i=typeof e.target=="string"?e.target:void 0;return[t,n,r??"",s??"",i??""].join("|")}function je(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function De(e){return`${ve}#${e}`}function Me(e,t,n){if(e==="LAYER_IMPORT_VIOLATION")return t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`;if(e==="FORBIDDEN_GLOBAL")return`Inject ${t.target??"the capability"} through a port, then preflight again.`;if(e==="CAPABILITY_VIOLATION")return`Define a ${b(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`;if(e==="CIRCULAR_DEPENDENCY")return"Extract the shared dependency into a third module, then preflight again.";if(e==="RAW_EVENT_PUBLISH")return"Publish through a registered intent creator, then run Ark again.";if(e==="PUBLISH_MISSING_SOURCE")return"Add metadata.source to the publish call, then run Ark again.";if(e==="ARKRULE_STRUCTURE"||e==="ARKRULE_INVARIANT"||e==="INVARIANT_UNCOVERED"||e.startsWith("ARKRULE_")){let r=t.arkruleSource??"arkrules/<Layer>.json";return`Fix the structure or invariant for ${t.arkruleId??"the ArkRule"} (declared in ${r}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`}return`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function pe(e,t="error",n){let r=b(e.ruleId)??b(e.code)??"ARK_UNKNOWN",s=e.severity==="warning"||e.failsStrict===!1||e.typeOnly===!0&&e.peerIsolation!==!0?"warning":t,i={...b(e.target)?{target:b(e.target)}:{},...b(e.fromLayer)?{fromLayer:b(e.fromLayer)}:{},...b(e.toLayer)?{toLayer:b(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{},...typeof e.targetTypeOnlyExports=="boolean"?{targetTypeOnlyExports:e.targetTypeOnlyExports}:{},...typeof e.sourcePureTypeModule=="boolean"?{sourcePureTypeModule:e.sourcePureTypeModule}:{},...typeof e.namedBindingsTypeOnly=="boolean"?{namedBindingsTypeOnly:e.namedBindingsTypeOnly}:{},...typeof e.portProofEligible=="boolean"?{portProofEligible:e.portProofEligible}:{},...typeof e.peerIsolation=="boolean"?{peerIsolation:e.peerIsolation}:{},...b(e.capability)?{capability:b(e.capability)}:{},...b(e.edgeKind)?{edgeKind:b(e.edgeKind)}:{},...b(e.arkruleId)?{arkruleId:b(e.arkruleId)}:{},...b(e.arkruleSource)?{arkruleSource:b(e.arkruleSource)}:{}},a=n??Te(e),c=je(a);return{ruleId:r,severity:s,message:b(e.message)??r,location:{file:b(e.file)??"<unknown>",line:de(e.line,1),column:de(e.column,1)},evidence:i,nextAction:b(e.nextAction)??Me(r,i,e),findingRef:c,targetKey:a,docsCodePath:De(r)}}var ue={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."},ot=Object.freeze([{layer:"DomainModel",prefixes:["Domain."]},{layer:"ApplicationOrchestration",prefixes:["Application."]},{layer:"PersistenceAdapters",prefixes:["Adapter.Persistence.","Adapter.Repository."]},{layer:"IntegrationAdapters",prefixes:["Adapter.Integration.","Adapter.External."]},{layer:"WorkflowSagaEngine",prefixes:["Workflow."]},{layer:"BackgroundJobsScheduling",prefixes:["Job."]},{layer:"PresentationAdapters",prefixes:["Presentation.","Adapter.Presentation.","Adapter.Api."]},{layer:"ReportingReadModels",prefixes:["Reporting."]},{layer:"ExtensibilityMetadata",prefixes:["Metadata."]},{layer:"SecurityAuditObservability",prefixes:["Security.","Audit.","Observability."]},{layer:"Kernel",prefixes:["Kernel."]}]);function Ve(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function V(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&Ve(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:ue.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:ue.PUBLISH_MISSING_SOURCE}),t}function C(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function x(e,t,n,r,s){let i=pe({...r,line:r.line??t.loc?.start?.line,column:r.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...s?{data:s}:{},diagnostic:i}),i}function H(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=p.dirname(p.resolve(e));for(;;){let n=p.join(t,"ark.config.json");if(I.existsSync(n))return n;let r=p.dirname(t);if(r===t)return null;t=r}}var fe=new Map;function G(e){if(!I.existsSync(e))return null;let t=I.readFileSync(e,"utf8"),n=fe.get(e);if(n?.source===t)return n.config;let r=ce(t,e).config;return fe.set(e,{source:t,config:r}),r}function B(e,t){return(e.include??[]).some(r=>{let s=String(r).replace(/\\/g,"/").replace(/^\.\//,"").replace(/\/$/,"");return s==="."||t===s||t.startsWith(`${s}/`)})&&!te(t,e)}function ge(e){let t=[e,`${e}.ts`,`${e}.tsx`,`${e}.mts`,`${e}.cts`,`${e}.js`,`${e}.jsx`,p.join(e,"index.ts"),p.join(e,"index.tsx"),p.join(e,"index.js")];for(let n of t)try{if(I.existsSync(n)&&I.statSync(n).isFile())return n}catch{}return null}function Fe(e){let t=p.resolve(e),n=null;for(;;){let f=p.join(t,"tsconfig.json");if(I.existsSync(f)){n=f;break}let d=p.dirname(t);if(d===t)break;t=d}if(!n)return{baseUrl:e,aliases:[]};let r=f=>{try{let d=I.readFileSync(f,"utf8");return d=d.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1"),JSON.parse(d)}catch{return null}},s=(f,d)=>{if(d>4)return{};let o=r(f);if(!o)return{};let l=o.compilerOptions??{},u=l.baseUrl,m=l.paths,y=o.extends;if(typeof y=="string"&&!y.startsWith("@")){let h=p.resolve(p.dirname(f),y.endsWith(".json")?y:`${y}.json`);if(I.existsSync(h)){let A=s(h,d+1);u=u??A.baseUrl,m={...A.paths??{},...m??{}}}}return{baseUrl:u,paths:m}},i=s(n,0),a=p.dirname(n),c=p.resolve(a,i.baseUrl||"."),g=[];for(let[f,d]of Object.entries(i.paths||{})){if(!Array.isArray(d)||d.length===0)continue;let o=f.replace(/\*$/,"");o&&g.push({from:o,to:String(d[0]).replace(/\*$/,"")})}return g.sort((f,d)=>d.from.length-f.from.length),{baseUrl:c,aliases:g}}function Ke(e,t){if(!t.startsWith("."))return null;let n=p.resolve(p.dirname(e),t);return ge(n)}function He(e,t,n){if(!t)return null;if(t.startsWith("."))return Ke(e,t);let r=n||p.dirname(e),{baseUrl:s,aliases:i}=Fe(r),a=i.find(g=>t.startsWith(g.from));if(!a)return null;let c=p.resolve(s,`${a.to}${t.slice(a.from.length)}`);return ge(c)}function $(e){return typeof e?.value=="string"?e.value:void 0}function U(e){return e?.name??$(e)}function q(e){return e.sourceCode??e.getSourceCode?.()}function me(e,t){let n=q(e)?.getScope?.(t);for(;n;){let r=n.references?.find(s=>s.identifier===t);if(r)return r;n=n.upper??void 0}}function _(e,t,n){let r=me(e,t);if(r?.resolved)return(r.resolved.defs?.length??0)>0;let s=q(e)?.getScope?.(t);for(;s;){let i=s.set?.get(n);if(i)return(i.defs?.length??0)>0;s=s.upper??void 0}return!1}function Ge(e,t){let n=me(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function ye(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=ye(e.object),r=U(e.property);if(!(!n||!r))return{root:n.root,segments:[...n.segments,r]}}function Be(e){return U(e.callee?.property)}function be(e,t){return e?.properties?.find(n=>U(n.key)===t)}function P(e,t){return be(e,t)!==void 0}function Ue(e){let t=be(e,"metadata")?.value;return P(t,"source")}function he(e){return Be(e)==="publish"}function F(e){if(e.importKind==="type"||e.exportKind==="type")return!0;let t=e.specifiers??[];return t.length===0?!1:t.every(n=>n.type==="ImportSpecifier")?t.every(n=>n.importKind==="type"):t.every(n=>n.exportKind==="type")}function qe(e){let t=e;for(;t?.parent;)t=t.parent;return t?.type==="Program"?t:void 0}function We(e){let t=qe(e)?.body;if(!t)return!1;let n=!1;for(let r of t){if(r.type==="ImportDeclaration"){if(!F(r))return!1;continue}if(!(r.type==="TSInterfaceDeclaration"||r.type==="TSTypeAliasDeclaration")){if(r.type==="ExportNamedDeclaration"){if(r.declaration){if(r.declaration.type!=="TSInterfaceDeclaration"&&r.declaration.type!=="TSTypeAliasDeclaration")return!1}else if(!F(r))return!1;n=!0;continue}return!1}}return n}var Ye={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=C(e),n=H(t),r=n?G(n):null,s=n?p.dirname(n):null,i=a=>{let c=$(a.source);if(c&&r&&s&&t){let g=p.isAbsolute(t)?t:p.resolve(t),f=p.relative(s,g).split(p.sep).join("/");if(!B(r,f))return;let d=w(f,r.layers);if(!d)return;let o=He(g,c,s);if(!o)return;let l=p.relative(s,o).split(p.sep).join("/");if(l.startsWith(".."))return;let u=w(l,r.layers);if(!u)return;let m={fromPath:f,toPath:l,layers:r.layers},y=v(r.rules,d,u,m);if(y||ee(r.rules,d,u,m)){let h=a.type?.startsWith("Export")?"export":"import",A=F(a),W=!!y?.peerIsolation,Y=A&&!W,J=y?.message??`${d} must not ${h} ${u}.`;x(e,a,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:f,fromLayer:d,toLayer:u,target:l,edgeKind:h,...W?{peerIsolation:!0}:{},...A?{typeOnly:!0}:{},...Y?{severity:"warning"}:{},...We(a)?{sourcePureTypeModule:!0}:{},message:Y?`${J} (type-only \u2014 type placement debt; prefer SharedTypes / owning layer; not runtime coupling)`:J},{fromLayer:d,toLayer:u,specifier:c})}return}};return{ImportDeclaration:i,ExportNamedDeclaration:i,ExportAllDeclaration:i}}},Je={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=$(n),s=V({publishCall:he(t),rawIntentName:r,objectHasIntent:P(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(s.some(i=>i.ruleId==="RAW_EVENT_PUBLISH")){let i=s.find(a=>a.ruleId==="RAW_EVENT_PUBLISH");x(e,t,"rawPublish",{...i,file:C(e)})}}}}},ze={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=t.arguments?.[2],i=V({publishCall:he(t),rawIntentName:$(n),objectHasIntent:P(n,"intent"),arkPublishCandidate:!0,hasSource:Ue(n)||P(r,"source")}).find(a=>a.ruleId==="PUBLISH_MISSING_SOURCE");i&&x(e,t,"missingSource",{...i,file:C(e)})}}}},Ze={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` is a standalone fallback when no project config applies."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.',forbiddenModule:'{{layer}} must not use module "{{specifier}}" because it is the import form of forbidden global "{{name}}".'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=C(e),n=e.options?.[0],r=H(t),s=r?G(r):null,i=r?p.dirname(r):null,a=null,c="this layer";if(s&&i&&t){let o=p.isAbsolute(t)?t:p.resolve(t),l=p.relative(i,o).split(p.sep).join("/");if(!B(s,l))return{};let u=s.layers?.find(m=>m.name===w(l,s.layers));u?.forbiddenGlobals?.length?(a=new Set(u.forbiddenGlobals),c=u.name):a=null}else n?.globals&&(a=new Set(n.globals));if(!a)return{};let g=typeof q(e)?.getScope=="function",f=(o,l)=>{let u=p.isAbsolute(t)?t:p.resolve(t),m=i?p.relative(i,u).split(p.sep).join("/"):t;x(e,o,s?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:m,fromLayer:c,target:l,message:`${c} must not use the ambient global "${l}".`},{name:l,layer:c})},d=(o,l,u,m)=>{if(u||typeof l!="string")return;let y=j(l,a);if(!y)return;let h=p.isAbsolute(t)?t:p.resolve(t),A=i?p.relative(i,h).split(p.sep).join("/"):t;x(e,o,"forbiddenModule",{ruleId:"FORBIDDEN_GLOBAL",file:A,fromLayer:c,target:l,edgeKind:m,message:`${c} must not use module "${l}" because it is the import form of forbidden global "${y}".`},{layer:c,name:y,specifier:l,importKind:m})};return{MemberExpression(o){if(o.parent?.type==="MemberExpression"&&o.parent.object===o)return;let l=ye(o);if(!l||_(e,l.root,l.segments[0]))return;let u=l.segments[0]==="globalThis",m=u?l.segments.slice(1):l.segments,y;for(let h=m.length;h>=(u?1:2);h-=1){let A=m.slice(0,h).join(".");if(a.has(A)){y=A;break}}y?f(o,y):!g&&a.has(l.segments[0])&&f(o,l.segments[0])},CallExpression(o){let l=o;if(l.callee?.type==="Identifier"&&l.callee.name==="require"&&l.arguments?.[0]?.type==="Literal"&&!_(e,o,"require")&&d(o,l.arguments[0].value,!1,"require"),g)return;let u=l.callee?.type==="Identifier"?l.callee.name:void 0;u&&a.has(u)&&f(o,u)},ImportDeclaration(o){let l=o,u=(l.specifiers??[]).filter(y=>y.type==="ImportSpecifier"),m=u.length>0&&u.length===(l.specifiers??[]).length&&u.every(y=>y.importKind==="type");d(o,l.source?.value,l.importKind==="type"||m,"import")},ImportExpression(o){let l=o;l.source?.type==="Literal"&&d(o,l.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(o){let l=o;d(o,l.moduleReference?.expression?.value,l.importKind==="type"||l.isTypeOnly===!0,"require")},ExportNamedDeclaration(o){let l=o;if(!l.source)return;let u=l.specifiers??[],m=u.length>0&&u.every(y=>y.exportKind==="type");d(o,l.source.value,l.exportKind==="type"||m,"export")},ExportAllDeclaration(o){let l=o;d(o,l.source?.value,l.exportKind==="type","export")},NewExpression(o){if(g)return;let l=o.callee?.type==="Identifier"?o.callee.name:void 0;l&&a.has(l)&&f(o,l)},Identifier(o){!g||!o.name||!a.has(o.name)||!Ge(e,o)||_(e,o,o.name)||f(o,o.name)}}}},Xe={meta:{type:"problem",docs:{description:"Disallow importing modules whose effect capability the layer denies (ark.config.json capabilities.deny / pure \u2014 same wall surface as ark-check). Import dimension only: ambient globals stay with no-forbidden-globals and the CLI/hook symbol path."},messages:{deniedCapability:'{{layer}} denies the {{capability}} capability (ark.config.json); "{{specifier}}" imports it. Define a port and bind the implementation in an adapter layer.'},schema:[]},create(e){let t=C(e),n=H(t),r=n?G(n):null,s=n?p.dirname(n):null;if(!r||!s||!t)return{};let i=p.isAbsolute(t)?t:p.resolve(t),a=p.relative(s,i).split(p.sep).join("/");if(!B(r,a))return{};let c=r.layers?.find(d=>d.name===w(a,r.layers));if(!c)return{};let g=new Set(se(c));if(g.size===0)return{};let f=(d,o,l,u)=>{if(l||typeof o!="string"||j(o,c.forbiddenGlobals??[]))return;let m=re(o);!m||!g.has(m)||x(e,d,"deniedCapability",{ruleId:"CAPABILITY_VIOLATION",file:a,fromLayer:c.name,target:o,capability:m,edgeKind:u,message:`${c.name} denies the ${m} capability; found import of "${o}".`},{layer:c.name,capability:m,specifier:o})};return{ImportDeclaration(d){let o=d,l=(o.specifiers??[]).filter(m=>m.type==="ImportSpecifier"),u=l.length>0&&l.length===(o.specifiers??[]).length&&l.every(m=>m.importKind==="type");f(d,o.source?.value,o.importKind==="type"||u,"import")},ImportExpression(d){let o=d;o.source?.type==="Literal"&&f(d,o.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(d){let o=d;f(d,o.moduleReference?.expression?.value,o.importKind==="type"||o.isTypeOnly===!0,"require")},ExportNamedDeclaration(d){let o=d;if(!o.source)return;let l=o.specifiers??[],u=l.length>0&&l.every(m=>m.exportKind==="type");f(d,o.source.value,o.exportKind==="type"||u,"export")},ExportAllDeclaration(d){let o=d;f(d,o.source?.value,o.exportKind==="type","export")},CallExpression(d){let o=d;o.callee?.type==="Identifier"&&o.callee.name==="require"&&o.arguments?.[0]?.type==="Literal"&&!_(e,d,"require")&&f(d,o.arguments[0].value,!1,"require")}}}},Qe={"no-domain-infra-imports":Ye,"no-raw-event-publish":Je,"require-publish-source":ze,"no-forbidden-globals":Ze,"no-denied-capabilities":Xe},K={rules:Qe};K.configs={recommended:{plugins:{ark:K},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error","ark/no-denied-capabilities":"error"}}};var gt=K;export{gt as default,H as findConfigPath,L as globToRegExp,ee as isEdgeDenied,w as layerForRelativePath,G as loadArkConfig,Xe as noDeniedCapabilities,Ye as noDomainInfraImports,Ze as noForbiddenGlobals,Je as noRawEventPublish,Q as patternSpecificity,K as plugin,Fe as readTsconfigPathAliases,ze as requirePublishSource,He as resolveImportSpecifier,Ke as resolveRelativeImport}; | ||
| `)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function le(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function O(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function R(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Oe(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function E(e,t,n,r,s){if(t.$ref){let i=Oe(t.$ref,r);if(!i){s.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}E(e,i,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(i=>Object.is(i,e))){s.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!le(e)){s.push({path:n,message:`must be an object; received ${R(e)}`});return}let i=t.properties??{};for(let a of t.required??[])e[a]===void 0&&s.push({path:O(n,a),message:"is required"});if(t.additionalProperties===!1)for(let a of Object.keys(e))a in i||s.push({path:O(n,a),message:"unknown field"});else if(t.additionalProperties!==void 0&&t.additionalProperties!==!0&&typeof t.additionalProperties=="object"){let a=t.additionalProperties;for(let c of Object.keys(e))c in i||E(e[c],a,O(n,c),r,s)}for(let[a,c]of Object.entries(i))e[a]!==void 0&&E(e[a],c,O(n,a),r,s);return}if(t.type==="array"){if(!Array.isArray(e)){s.push({path:n,message:`must be an array; received ${R(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 i=e.map(a=>JSON.stringify(a));new Set(i).size!==i.length&&s.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((i,a)=>E(i,t.items,`${n}[${a}]`,r,s));return}if(t.type==="string"){if(typeof e!="string"){s.push({path:n,message:`must be a string; received ${R(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 ${R(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){s.push({path:n,message:`must be an integer; received ${R(e)}`});return}t.minimum!==void 0&&e<t.minimum&&s.push({path:n,message:`must be at least ${t.minimum}`})}}function _e(e){return{...e,$schema:e.$schema===void 0?D: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?ae.map(t=>({...t})):e.rules}}function Pe(){let e=new Set(["1.1"]);for(let t of M)t.from!=="unversioned"&&e.add(t.from),e.add(t.to);return e}function $e(e,t="ark.config.json"){if(!le(e))throw new S(t,[{path:"$",message:`must be an object; received ${R(e)}`}]);let n=Pe(),r=e.schemaVersion===void 0?"unversioned":typeof e.schemaVersion=="string"?e.schemaVersion:null;if(r===null)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.1`}]);if(r!=="unversioned"&&!n.has(r))throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let s=r,i={...e},a=0;for(;s!=="1.1"&&a<M.length+1;){a+=1;let g=M.find(f=>f.from===s);if(!g)throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(s)}; expected 1.1`}]);s=g.to,i.schemaVersion=s}if(s!=="1.1")throw new S(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(r)}; expected 1.1`}]);let c=r==="unversioned"?"unversioned":r==="1.0"?"1.0":null;return{candidate:_e(i),migratedFrom:c}}function ve(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=$e(e,t),s=[];if(E(n,ie,"$",ie,s),s.length>0)throw new S(t,s);return{config:n,migratedFrom:r}}function ce(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new S(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return ve(n,t)}var Te="docs/diagnostics.md";function h(e){return typeof e=="string"&&e.length>0?e:void 0}function de(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function je(e){let t=typeof e.ruleId=="string"?e.ruleId:typeof e.code=="string"?e.code:void 0,n=typeof e.file=="string"?e.file:void 0,r=typeof e.fromLayer=="string"?e.fromLayer:void 0,s=typeof e.toLayer=="string"?e.toLayer:void 0,i=typeof e.target=="string"?e.target:void 0;return[t,n,r??"",s??"",i??""].join("|")}function De(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function Me(e){return`${Te}#${e}`}function Ve(e,t,n){if(e==="LAYER_IMPORT_VIOLATION")return t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`;if(e==="FORBIDDEN_GLOBAL")return`Inject ${t.target??"the capability"} through a port, then preflight again.`;if(e==="CAPABILITY_VIOLATION")return`Define a ${h(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`;if(e==="CIRCULAR_DEPENDENCY")return"Extract the shared dependency into a third module, then preflight again.";if(e==="RAW_EVENT_PUBLISH")return"Publish through a registered intent creator, then run Ark again.";if(e==="PUBLISH_MISSING_SOURCE")return"Add metadata.source to the publish call, then run Ark again.";if(e==="ARKRULE_STRUCTURE"||e==="ARKRULE_INVARIANT"||e==="INVARIANT_UNCOVERED"||e.startsWith("ARKRULE_")){let r=t.arkruleSource??"arkrules/<Layer>.json";return`Fix the structure or invariant for ${t.arkruleId??"the ArkRule"} (declared in ${r}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`}return`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function pe(e,t="error",n){let r=h(e.ruleId)??h(e.code)??"ARK_UNKNOWN",s=e.severity==="warning"||e.failsStrict===!1||e.typeOnly===!0&&e.peerIsolation!==!0?"warning":t,i={...h(e.target)?{target:h(e.target)}:{},...h(e.fromLayer)?{fromLayer:h(e.fromLayer)}:{},...h(e.toLayer)?{toLayer:h(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{},...typeof e.targetTypeOnlyExports=="boolean"?{targetTypeOnlyExports:e.targetTypeOnlyExports}:{},...typeof e.sourcePureTypeModule=="boolean"?{sourcePureTypeModule:e.sourcePureTypeModule}:{},...typeof e.namedBindingsTypeOnly=="boolean"?{namedBindingsTypeOnly:e.namedBindingsTypeOnly}:{},...typeof e.portProofEligible=="boolean"?{portProofEligible:e.portProofEligible}:{},...typeof e.peerIsolation=="boolean"?{peerIsolation:e.peerIsolation}:{},...h(e.capability)?{capability:h(e.capability)}:{},...h(e.edgeKind)?{edgeKind:h(e.edgeKind)}:{},...h(e.arkruleId)?{arkruleId:h(e.arkruleId)}:{},...h(e.arkruleSource)?{arkruleSource:h(e.arkruleSource)}:{}},a=n??je(e),c=De(a);return{ruleId:r,severity:s,message:h(e.message)??r,location:{file:h(e.file)??"<unknown>",line:de(e.line,1),column:de(e.column,1)},evidence:i,nextAction:h(e.nextAction)??Ve(r,i,e),findingRef:c,targetKey:a,docsCodePath:Me(r)}}var ue={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."},it=Object.freeze([{layer:"DomainModel",prefixes:["Domain."]},{layer:"ApplicationOrchestration",prefixes:["Application."]},{layer:"PersistenceAdapters",prefixes:["Adapter.Persistence.","Adapter.Repository."]},{layer:"IntegrationAdapters",prefixes:["Adapter.Integration.","Adapter.External."]},{layer:"WorkflowSagaEngine",prefixes:["Workflow."]},{layer:"BackgroundJobsScheduling",prefixes:["Job."]},{layer:"PresentationAdapters",prefixes:["Presentation.","Adapter.Presentation.","Adapter.Api."]},{layer:"ReportingReadModels",prefixes:["Reporting."]},{layer:"ExtensibilityMetadata",prefixes:["Metadata."]},{layer:"SecurityAuditObservability",prefixes:["Security.","Audit.","Observability."]},{layer:"Kernel",prefixes:["Kernel."]}]);function Fe(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function V(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&Fe(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:ue.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:ue.PUBLISH_MISSING_SOURCE}),t}function C(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function x(e,t,n,r,s){let i=pe({...r,line:r.line??t.loc?.start?.line,column:r.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...s?{data:s}:{},diagnostic:i}),i}function H(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=p.dirname(p.resolve(e));for(;;){let n=p.join(t,"ark.config.json");if(I.existsSync(n))return n;let r=p.dirname(t);if(r===t)return null;t=r}}var fe=new Map;function G(e){if(!I.existsSync(e))return null;let t=I.readFileSync(e,"utf8"),n=fe.get(e);if(n?.source===t)return n.config;let r=ce(t,e).config;return fe.set(e,{source:t,config:r}),r}function B(e,t){return(e.include??[]).some(r=>{let s=String(r).replace(/\\/g,"/").replace(/^\.\//,"").replace(/\/$/,"");return s==="."||t===s||t.startsWith(`${s}/`)})&&!te(t,e)}function ge(e){let t=[e,`${e}.ts`,`${e}.tsx`,`${e}.mts`,`${e}.cts`,`${e}.js`,`${e}.jsx`,p.join(e,"index.ts"),p.join(e,"index.tsx"),p.join(e,"index.js")];for(let n of t)try{if(I.existsSync(n)&&I.statSync(n).isFile())return n}catch{}return null}function Ke(e){let t=p.resolve(e),n=null;for(;;){let f=p.join(t,"tsconfig.json");if(I.existsSync(f)){n=f;break}let d=p.dirname(t);if(d===t)break;t=d}if(!n)return{baseUrl:e,aliases:[]};let r=f=>{try{let d=I.readFileSync(f,"utf8");return d=d.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1"),JSON.parse(d)}catch{return null}},s=(f,d)=>{if(d>4)return{};let o=r(f);if(!o)return{};let l=o.compilerOptions??{},u=l.baseUrl,m=l.paths,y=o.extends;if(typeof y=="string"&&!y.startsWith("@")){let b=p.resolve(p.dirname(f),y.endsWith(".json")?y:`${y}.json`);if(I.existsSync(b)){let A=s(b,d+1);u=u??A.baseUrl,m={...A.paths??{},...m??{}}}}return{baseUrl:u,paths:m}},i=s(n,0),a=p.dirname(n),c=p.resolve(a,i.baseUrl||"."),g=[];for(let[f,d]of Object.entries(i.paths||{})){if(!Array.isArray(d)||d.length===0)continue;let o=f.replace(/\*$/,"");o&&g.push({from:o,to:String(d[0]).replace(/\*$/,"")})}return g.sort((f,d)=>d.from.length-f.from.length),{baseUrl:c,aliases:g}}function He(e,t){if(!t.startsWith("."))return null;let n=p.resolve(p.dirname(e),t);return ge(n)}function Ge(e,t,n){if(!t)return null;if(t.startsWith("."))return He(e,t);let r=n||p.dirname(e),{baseUrl:s,aliases:i}=Ke(r),a=i.find(g=>t.startsWith(g.from));if(!a)return null;let c=p.resolve(s,`${a.to}${t.slice(a.from.length)}`);return ge(c)}function $(e){return typeof e?.value=="string"?e.value:void 0}function U(e){return e?.name??$(e)}function q(e){return e.sourceCode??e.getSourceCode?.()}function me(e,t){let n=q(e)?.getScope?.(t);for(;n;){let r=n.references?.find(s=>s.identifier===t);if(r)return r;n=n.upper??void 0}}function _(e,t,n){let r=me(e,t);if(r?.resolved)return(r.resolved.defs?.length??0)>0;let s=q(e)?.getScope?.(t);for(;s;){let i=s.set?.get(n);if(i)return(i.defs?.length??0)>0;s=s.upper??void 0}return!1}function Be(e,t){let n=me(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function ye(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=ye(e.object),r=U(e.property);if(!(!n||!r))return{root:n.root,segments:[...n.segments,r]}}function Ue(e){return U(e.callee?.property)}function he(e,t){return e?.properties?.find(n=>U(n.key)===t)}function P(e,t){return he(e,t)!==void 0}function qe(e){let t=he(e,"metadata")?.value;return P(t,"source")}function be(e){return Ue(e)==="publish"}function F(e){if(e.importKind==="type"||e.exportKind==="type")return!0;let t=e.specifiers??[];return t.length===0?!1:t.every(n=>n.type==="ImportSpecifier")?t.every(n=>n.importKind==="type"):t.every(n=>n.exportKind==="type")}function We(e){let t=e;for(;t?.parent;)t=t.parent;return t?.type==="Program"?t:void 0}function Ye(e){let t=We(e)?.body;if(!t)return!1;let n=!1;for(let r of t){if(r.type==="ImportDeclaration"){if(!F(r))return!1;continue}if(!(r.type==="TSInterfaceDeclaration"||r.type==="TSTypeAliasDeclaration")){if(r.type==="ExportNamedDeclaration"){if(r.declaration){if(r.declaration.type!=="TSInterfaceDeclaration"&&r.declaration.type!=="TSTypeAliasDeclaration")return!1}else if(!F(r))return!1;n=!0;continue}return!1}}return n}var Je={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=C(e),n=H(t),r=n?G(n):null,s=n?p.dirname(n):null,i=a=>{let c=$(a.source);if(c&&r&&s&&t){let g=p.isAbsolute(t)?t:p.resolve(t),f=p.relative(s,g).split(p.sep).join("/");if(!B(r,f))return;let d=w(f,r.layers);if(!d)return;let o=Ge(g,c,s);if(!o)return;let l=p.relative(s,o).split(p.sep).join("/");if(l.startsWith(".."))return;let u=w(l,r.layers);if(!u)return;let m={fromPath:f,toPath:l,layers:r.layers},y=v(r.rules,d,u,m);if(y||ee(r.rules,d,u,m)){let b=a.type?.startsWith("Export")?"export":"import",A=F(a),W=!!y?.peerIsolation,Y=A&&!W,J=y?.message??`${d} must not ${b} ${u}.`;x(e,a,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:f,fromLayer:d,toLayer:u,target:l,edgeKind:b,...W?{peerIsolation:!0}:{},...A?{typeOnly:!0}:{},...Y?{severity:"warning"}:{},...Ye(a)?{sourcePureTypeModule:!0}:{},message:Y?`${J} (type-only \u2014 type placement debt; prefer SharedTypes / owning layer; not runtime coupling)`:J},{fromLayer:d,toLayer:u,specifier:c})}return}};return{ImportDeclaration:i,ExportNamedDeclaration:i,ExportAllDeclaration:i}}},ze={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=$(n),s=V({publishCall:be(t),rawIntentName:r,objectHasIntent:P(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(s.some(i=>i.ruleId==="RAW_EVENT_PUBLISH")){let i=s.find(a=>a.ruleId==="RAW_EVENT_PUBLISH");x(e,t,"rawPublish",{...i,file:C(e)})}}}}},Ze={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],r=t.arguments?.[2],i=V({publishCall:be(t),rawIntentName:$(n),objectHasIntent:P(n,"intent"),arkPublishCandidate:!0,hasSource:qe(n)||P(r,"source")}).find(a=>a.ruleId==="PUBLISH_MISSING_SOURCE");i&&x(e,t,"missingSource",{...i,file:C(e)})}}}},Xe={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` is a standalone fallback when no project config applies."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.',forbiddenModule:'{{layer}} must not use module "{{specifier}}" because it is the import form of forbidden global "{{name}}".'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=C(e),n=e.options?.[0],r=H(t),s=r?G(r):null,i=r?p.dirname(r):null,a=null,c="this layer";if(s&&i&&t){let o=p.isAbsolute(t)?t:p.resolve(t),l=p.relative(i,o).split(p.sep).join("/");if(!B(s,l))return{};let u=s.layers?.find(m=>m.name===w(l,s.layers));u?.forbiddenGlobals?.length?(a=new Set(u.forbiddenGlobals),c=u.name):a=null}else n?.globals&&(a=new Set(n.globals));if(!a)return{};let g=typeof q(e)?.getScope=="function",f=(o,l)=>{let u=p.isAbsolute(t)?t:p.resolve(t),m=i?p.relative(i,u).split(p.sep).join("/"):t;x(e,o,s?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:m,fromLayer:c,target:l,message:`${c} must not use the ambient global "${l}".`},{name:l,layer:c})},d=(o,l,u,m)=>{if(u||typeof l!="string")return;let y=j(l,a);if(!y)return;let b=p.isAbsolute(t)?t:p.resolve(t),A=i?p.relative(i,b).split(p.sep).join("/"):t;x(e,o,"forbiddenModule",{ruleId:"FORBIDDEN_GLOBAL",file:A,fromLayer:c,target:l,edgeKind:m,message:`${c} must not use module "${l}" because it is the import form of forbidden global "${y}".`},{layer:c,name:y,specifier:l,importKind:m})};return{MemberExpression(o){if(o.parent?.type==="MemberExpression"&&o.parent.object===o)return;let l=ye(o);if(!l||_(e,l.root,l.segments[0]))return;let u=l.segments[0]==="globalThis",m=u?l.segments.slice(1):l.segments,y;for(let b=m.length;b>=(u?1:2);b-=1){let A=m.slice(0,b).join(".");if(a.has(A)){y=A;break}}y?f(o,y):!g&&a.has(l.segments[0])&&f(o,l.segments[0])},CallExpression(o){let l=o;if(l.callee?.type==="Identifier"&&l.callee.name==="require"&&l.arguments?.[0]?.type==="Literal"&&!_(e,o,"require")&&d(o,l.arguments[0].value,!1,"require"),g)return;let u=l.callee?.type==="Identifier"?l.callee.name:void 0;u&&a.has(u)&&f(o,u)},ImportDeclaration(o){let l=o,u=(l.specifiers??[]).filter(y=>y.type==="ImportSpecifier"),m=u.length>0&&u.length===(l.specifiers??[]).length&&u.every(y=>y.importKind==="type");d(o,l.source?.value,l.importKind==="type"||m,"import")},ImportExpression(o){let l=o;l.source?.type==="Literal"&&d(o,l.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(o){let l=o;d(o,l.moduleReference?.expression?.value,l.importKind==="type"||l.isTypeOnly===!0,"require")},ExportNamedDeclaration(o){let l=o;if(!l.source)return;let u=l.specifiers??[],m=u.length>0&&u.every(y=>y.exportKind==="type");d(o,l.source.value,l.exportKind==="type"||m,"export")},ExportAllDeclaration(o){let l=o;d(o,l.source?.value,l.exportKind==="type","export")},NewExpression(o){if(g)return;let l=o.callee?.type==="Identifier"?o.callee.name:void 0;l&&a.has(l)&&f(o,l)},Identifier(o){!g||!o.name||!a.has(o.name)||!Be(e,o)||_(e,o,o.name)||f(o,o.name)}}}},Qe={meta:{type:"problem",docs:{description:"Disallow importing modules whose effect capability the layer denies (ark.config.json capabilities.deny / pure \u2014 same wall surface as ark-check). Import dimension only: ambient globals stay with no-forbidden-globals and the CLI/hook symbol path."},messages:{deniedCapability:'{{layer}} denies the {{capability}} capability (ark.config.json); "{{specifier}}" imports it. Define a port and bind the implementation in an adapter layer.'},schema:[]},create(e){let t=C(e),n=H(t),r=n?G(n):null,s=n?p.dirname(n):null;if(!r||!s||!t)return{};let i=p.isAbsolute(t)?t:p.resolve(t),a=p.relative(s,i).split(p.sep).join("/");if(!B(r,a))return{};let c=r.layers?.find(d=>d.name===w(a,r.layers));if(!c)return{};let g=new Set(se(c));if(g.size===0)return{};let f=(d,o,l,u)=>{if(l||typeof o!="string"||j(o,c.forbiddenGlobals??[]))return;let m=re(o);!m||!g.has(m)||x(e,d,"deniedCapability",{ruleId:"CAPABILITY_VIOLATION",file:a,fromLayer:c.name,target:o,capability:m,edgeKind:u,message:`${c.name} denies the ${m} capability; found import of "${o}".`},{layer:c.name,capability:m,specifier:o})};return{ImportDeclaration(d){let o=d,l=(o.specifiers??[]).filter(m=>m.type==="ImportSpecifier"),u=l.length>0&&l.length===(o.specifiers??[]).length&&l.every(m=>m.importKind==="type");f(d,o.source?.value,o.importKind==="type"||u,"import")},ImportExpression(d){let o=d;o.source?.type==="Literal"&&f(d,o.source.value,!1,"dynamic-import")},TSImportEqualsDeclaration(d){let o=d;f(d,o.moduleReference?.expression?.value,o.importKind==="type"||o.isTypeOnly===!0,"require")},ExportNamedDeclaration(d){let o=d;if(!o.source)return;let l=o.specifiers??[],u=l.length>0&&l.every(m=>m.exportKind==="type");f(d,o.source.value,o.exportKind==="type"||u,"export")},ExportAllDeclaration(d){let o=d;f(d,o.source?.value,o.exportKind==="type","export")},CallExpression(d){let o=d;o.callee?.type==="Identifier"&&o.callee.name==="require"&&o.arguments?.[0]?.type==="Literal"&&!_(e,d,"require")&&f(d,o.arguments[0].value,!1,"require")}}}},et={"no-domain-infra-imports":Je,"no-raw-event-publish":ze,"require-publish-source":Ze,"no-forbidden-globals":Xe,"no-denied-capabilities":Qe},K={rules:et};K.configs={recommended:{plugins:{ark:K},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error","ark/no-denied-capabilities":"error"}}};var mt=K;export{mt as default,H as findConfigPath,L as globToRegExp,ee as isEdgeDenied,w as layerForRelativePath,G as loadArkConfig,Qe as noDeniedCapabilities,Je as noDomainInfraImports,Xe as noForbiddenGlobals,ze as noRawEventPublish,Q as patternSpecificity,K as plugin,Ke as readTsconfigPathAliases,Ze as requirePublishSource,Ge as resolveImportSpecifier,He as resolveRelativeImport}; |
+35
-8
@@ -62,4 +62,28 @@ # ArkGate — Agent Integration Guide | ||
| ### Improvement compass (doctor) | ||
| ### Session recipe (agent turn) | ||
| Default loop for each agent session (product language — no inventing residual): | ||
| 1. **Bind identity** — call `ark_identity` with `project.expectedRoot` = exact absolute project | ||
| root. Reuse root + returned `projectId` on later tools. Only `binding.status: "matched"` with | ||
| `authoritative: true` is authoritative. | ||
| 2. **Read status** — `ark status --json` / MCP `ark_status` for identity, write-path activation, | ||
| last-check summary, residual lens ids (`improvementCompass`), and primary next action. | ||
| 3. **Act** — address residual / next action / stable `findingRef` from diagnostics. Never invent | ||
| green residual lenses. Projection, skills, and AGENTS.md never enforce. | ||
| 4. **Doctor when compass mode is not full** — if `improvementCompass.mode` is `subset` or | ||
| `unavailable`, run `ark-check --doctor` (and `--json` for the full 15-lens map) before treating | ||
| residual as complete. When mode is `full`, status residual ids are a subset of doctor residual | ||
| for the same facts. | ||
| ```bash | ||
| npx ark status --json --expected-root /abs/project/root | ||
| # mode !== full → full residual map: | ||
| npx ark-check --doctor --json | ||
| ``` | ||
| Product path: [use.md — Session recipe](use.md#session-recipe-agent-turn). | ||
| ### Improvement compass (doctor + status) | ||
| `ark-check --doctor` (human + `--json`) projects residual architecture work as a closed set of | ||
@@ -71,5 +95,7 @@ **lenses** (`doctor.improvementCompass`). Always `notAScore: true`. Never feeds `valid`, | ||
| **Status snapshot:** the status schema may carry a thin optional residual-id slice when Tooling | ||
| passes it through, but **`ark status --json` does not compute the compass yet** — use doctor for | ||
| residual lenses. Residual never changes status `nextAction` by itself. | ||
| **Status snapshot:** `ark status --json` / MCP `ark_status` project a thin `improvementCompass` | ||
| with explicit honesty **`mode`**: `full` \| `subset` \| `unavailable` (always `notAScore: true`). | ||
| Incomplete facts → `subset` / `unavailable` + reason — **never invent green residual**. Residual | ||
| never flips gate verdicts and never alone rewrites status `nextAction` as a score. When mode is | ||
| not `full`, follow the [session recipe](#session-recipe-agent-turn) and run doctor. | ||
@@ -79,6 +105,7 @@ Compact router and skills read residual lenses in plain language; green edges alone are never | ||
| ### Unified status snapshot (4.3) | ||
| ### Unified status snapshot (4.3+) | ||
| For one machine-readable session/project manifest (identity binding, honest write-path activation, | ||
| last-check summary, rules residual counts, primary next action) use: | ||
| last-check summary, rules residual counts, primary next action, improvement-compass residual map) | ||
| use: | ||
@@ -93,4 +120,4 @@ ```bash | ||
| Schema: `arkgate/schema/status-manifest`. Never prompts; under `CI=1` JSON is forced. **Not a | ||
| score** — counts and verdicts only. Write-path interpretation of activation vs merge teeth is under | ||
| [Write-path honesty](#write-path-honesty). | ||
| score** — counts, honesty modes, and residual ids only. Write-path interpretation of activation vs | ||
| merge teeth is under [Write-path honesty](#write-path-honesty). | ||
@@ -97,0 +124,0 @@ **Stable finding refs (4.3):** every factory-emitted diagnostic on CLI JSON, MCP analysis |
+3
-1
@@ -72,3 +72,5 @@ # Develop with ArkGate | ||
| | Diagnostic codes (`ruleId` why/fix) | [diagnostics.md](diagnostics.md) · root `DIAGNOSTIC_CATALOG` | | ||
| | Session / project status snapshot | `ark status --json` · MCP `ark_status` · [agent-guide](agent-guide.md) · schema `arkgate/schema/status-manifest` | | ||
| | Session / project status snapshot | `ark status --json` · MCP `ark_status` · [session recipe](agent-guide.md#session-recipe-agent-turn) · schema `arkgate/schema/status-manifest` | | ||
| | Status compass honesty | `improvementCompass.mode`: `full` \| `subset` \| `unavailable` · residual ⊆ doctor when `full` · [package-surface](package-surface.md) | | ||
| | Managed upgrade self-service | `ark upgrade --json` → `selfService` (activation labels + customized preserve) · [package-surface](package-surface.md) | | ||
| | Version-matched AGENTS projection | `ark agents-md` · [agent-guide](agent-guide.md) · **non-authoritative** (never enforces) | | ||
@@ -75,0 +77,0 @@ | Stable finding refs (`findingRef` / `targetKey`) | analysis-result schema **1.5** · [agent-guide](agent-guide.md) · [package-surface](package-surface.md) | |
@@ -21,3 +21,3 @@ # ArkGate package surface policy | ||
| | **Programmatic gate API** | `import { analyzeProject, loadContract, createAICodeGate, ... } from 'arkgate'` | The root export is the static gate/config/analysis contract listed below. It intentionally contains no runtime-kernel implementation. | | ||
| | **Improvement compass (4.4)** | `ark-check --doctor --json` → `doctor.improvementCompass`; human doctor section **Improvement compass (not a score)**; HTML report `data-advisory="improvementCompass"`. Status schema accepts an optional thin `improvementCompass` residual-id slice for Tooling pass-through — **`ark status` does not compute lenses yet**; agents should read doctor for residual. | Additive schema `1.0`. Closed **15** lens ids (`soc`, `cohesion`, `coupling`, `srp`, `dip`, `ocp`, `encapsulation`, `modularity`, `scalability`, `resilience`, `security`, `maintainability`, `testability`, `domain`, `stack`) with status `ok` \| `residual` \| `not-instrumented` \| `out-of-scope`, evidence refs, optional `nextAction`, capped `topResidual`, always **`notAScore: true`**. Projection from existing smells / walls / cohesion / ArkRules / design-weak only — **never** a gate input (`valid` / strict-merge / `goal.met` unchanged). Out-of-scope locked for scalability, resilience, and app security (no residual invent from missing SAST/APM). Root API: `buildImprovementCompass` / `IMPROVEMENT_LENS_IDS`. | | ||
| | **Improvement compass (4.4; status honesty 4.5)** | `ark-check --doctor --json` → `doctor.improvementCompass`; human doctor section **Improvement compass (not a score)**; HTML report `data-advisory="improvementCompass"`. **`ark status --json` / MCP `ark_status`** project a thin `improvementCompass` residual map with explicit honesty **`mode`**: `full` \| `subset` \| `unavailable` (always `notAScore: true`). When `mode` is `full`, status residual lens **ids** are a **subset of** doctor residual for the same facts (report snapshot stores the thin slice after `--report`). Incomplete or missing session facts → `subset` / `unavailable` + `reasonCode` / `reason` — **never invent green residual**. Residual never flips `valid` / strict-merge / `goal.met`. When status mode ≠ full, run doctor for full 15-lens detail. | Additive schema `1.0`. Closed **15** lens ids (`soc`, `cohesion`, `coupling`, `srp`, `dip`, `ocp`, `encapsulation`, `modularity`, `scalability`, `resilience`, `security`, `maintainability`, `testability`, `domain`, `stack`) with status `ok` \| `residual` \| `not-instrumented` \| `out-of-scope`, evidence refs, optional `nextAction`, capped `topResidual`, always **`notAScore: true`**. Projection from existing smells / walls / cohesion / ArkRules / design-weak only — **never** a gate input. Out-of-scope locked for scalability, resilience, and app security (no residual invent from missing SAST/APM). Root API: `buildImprovementCompass` / `IMPROVEMENT_LENS_IDS`; status: `projectStatusImprovementCompass` / `STATUS_COMPASS_MODES`. | | ||
| | **Doctor design fitness** | `ark-check --doctor --json` → `doctor.designFitness`, `doctor.designSmells[]` | Additive. Stable smell `id`s: `io-under-application`, `handler-in-persistence`, `god-module`, `domain-logic-in-ui`, `facade-sql-in-routes`, `mixed-pattern-cluster`, `soft-contract`. `handler-in-persistence` covers static ES imports/re-exports of framework HTTP surfaces (`next/server`), `defineRoute` calls, and existing handler bodies inside Persistence-role layers or specific persistence paths; `require()` and dynamic `import()` are outside this narrow advisory, and a generic `Infrastructure` role alone is not Persistence. Persistence candidates are filtered and sorted before the bounded content scan so large application prefixes cannot hide the advisory. The detector inspects the first 800 sorted Persistence candidates; later candidates are uninspected, so **absence of a smell is not full-tree proof** above that envelope (incomplete/`partial` analysis also never proves “no smells”). **4.2 feedback hardening:** mode labels preserve the observed SUGGEST/ADAPT/ENFORCE state; a local permission/UI-state `canEdit` name alone is not a domain smell; real UI business rules route Domain → Application → UI; seed/fixture/demo/migration/generated files are not god-module pilots. Each smell has `evidence[]`, `fix`, technical `message`, and plain-language **`outcome`**. Does **not** fail the gate by itself. | | ||
@@ -44,2 +44,3 @@ | **Post-green Shape door** | `doctor.postGreenPath`, `doctor.primaryNextAction`, `doctor.healthyFinishedForbidden` | Additive when `designFitness.designWeak`. Single Shape door (`id: clarify-for-ai`): explore shape-focus → dual-plan B → autopilot only with OK. Never empty plan A = healthy finished. | | ||
| | **Package pin dual-truth (4.0)** | doctor JSON `packageVersionTruth`; upgrade JSON/human note when pin behind CLI | Additive, advisory. Surfaces after `upgrade --no-install` when managed CLI is ahead of package.json. | | ||
| | **Managed upgrade self-service honesty (4.5 / DF05)** | `ark upgrade [--json]` → `selfService` (+ human “Self-service honesty” lines) | Additive, advisory. Answers without a maintainer: write-path activation labels per selected host (`hard`\|`advisory`\|`unavailable`) and customized content-identity preserve (`customizedPaths` / `customizedContentPreserved`). Soft hosts never hard; upgrade never invents `hardWriteActive` from disk alone. Always `notAScore: true`. Not a gate input; not part of `planDigest`. | | ||
| | **Product honesty readiness split (4.1.1)** | doctor JSON `productHonesty` | Additive. `unfinished` / `headline` / `primaryNextAction` / `reasonIds` remain; EH adds `contractReadiness` (`ready`\|`partial`\|`not-ready`), `localWriteBoundary` (`advisory`\|`hard`\|`unverified`\|`unknown`), `architectureReasonIds`, `environmentResidualIds` / `environmentResiduals`. Soft-write hosts stay in evidence without alone forcing global **Not finished**. `notAScore: true` always. | | ||
@@ -56,3 +57,3 @@ | **Policy transition analysis (3.1.0)** | `analyzePolicyDelta(...)`; MCP `ark_policy_delta`; CLI `--policy-base` / `--policy-base-ref` / `--policy-ack`; check JSON `policyDelta` | Additive schema `1.0`. Classifications and finding ids are deterministic. Weakening/judgment requires an acknowledgement bound to both policy hashes and the exact blocking finding set. | | ||
| | **Diagnostic code catalog** | Root API `DIAGNOSTIC_CATALOG` / `getDiagnosticCatalogEntry` / `diagnosticDocsPath`; docs [diagnostics.md](diagnostics.md) (`#RULE_ID` anchors) | Closed vocabulary of public `ruleId`s with why/fix anchors. Cataloguing only — no new rule semantics. Remediation parity is test-guarded. Docs ship in the npm tarball. | | ||
| | **Status manifest** | CLI `ark status [--json]`; MCP `ark_status`; `arkgate/schema/status-manifest`; root API `buildStatusManifest` / `ARK_STATUS_MANIFEST_SCHEMA` | Schema `1.0`. One session/project snapshot: identity binding, honest write-path activation (`hard`\|`advisory`\|`unavailable`), last-check summary, rules residual counts, primary next action. Optional thin `improvementCompass` residual ids when Tooling supplies them. **Not a score.** Never prompts (`CI=1` forces JSON). Optional `--expected-root` / `--expected-project-id` (MCP `project`) for matched vs stale identity. | | ||
| | **Status manifest** | CLI `ark status [--json]`; MCP `ark_status`; `arkgate/schema/status-manifest`; root API `buildStatusManifest` / `ARK_STATUS_MANIFEST_SCHEMA` / `projectStatusImprovementCompass` | Schema `1.0`. One session/project snapshot: identity binding, honest write-path activation (`hard`\|`advisory`\|`unavailable`), last-check summary, rules residual counts, primary next action, and **`improvementCompass`** with honesty **`mode`** `full`\|`subset`\|`unavailable` (residual ids only; always `notAScore: true`; optional `reasonCode`/`reason`/`factsSource`/`contractHash`). **Not a score.** Residual never changes gate verdicts. Never prompts (`CI=1` forces JSON). Optional `--expected-root` / `--expected-project-id` (MCP `project`) for matched vs stale identity. | | ||
| | **Agent contract projection** | CLI `ark agents-md [--write] [--check] [--stdout] [--json]`; install/upgrade AGENTS templates; root API `buildAgentProjectionBlock` / `mergeAgentProjectionDocument` | Schema `1.0` (projection markers). Version-stamped managed block (`arkgateVersion` + contract summary + diagnostic short list). **Non-authoritative** — not a gate input; enforcement is ark-check / hooks / CI. Content-identity merge preserves customized regions outside markers. Drift: `--check` vs package version. | | ||
@@ -210,6 +211,6 @@ | **Agent Skills packaging** | `templates/agent-skills/<name>/SKILL.md` (+ package README); root API `ARK_SKILL_NAMES` / `validateAgentSkillsPackage`; `npm run check:agent-skills` | Schema `1.0` (package contract). Same **13** skill names as flat templates; Agent Skills–compatible layout for `npx skills add`. No new skill names. Layout is generated 1:1 from `templates/skills/*.md`. | | ||
| Ship notes for a version live under [releases/](https://github.com/pedroknigge/arkgate/tree/main/docs/releases) | ||
| (current published: [4.3.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.3.0.md); | ||
| next prepare: [4.4.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.4.0.md); | ||
| prior published: [4.2.1.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.2.1.md); | ||
| (current published: [4.4.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.4.0.md); | ||
| next prepare: [4.5.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.5.0.md); | ||
| prior published: [4.3.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.3.0.md), | ||
| [4.2.1.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.2.1.md); | ||
| previous: [4.2.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.2.0.md), | ||
@@ -216,0 +217,0 @@ [4.1.1.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.1.1.md), |
@@ -100,3 +100,5 @@ # ArkGate product voice | ||
| | **finding ref** | Stable id for a finding across turns (ruleId + target key), so agents re-address without fuzzy message match | | ||
| | **status snapshot** | One machine-readable project/session manifest (`ark status --json` shape): identity, activation honesty, last check, residual counts — not a numeric score | | ||
| | **status snapshot** | One machine-readable project/session manifest (`ark status --json` shape): identity, activation honesty, last check, residual counts, thin compass residual map — not a numeric score | | ||
| | **session recipe** | Agent loop: bind identity → read status → act on residual / findingRef; run doctor when status compass mode is not `full` | | ||
| | **compass mode** | Status honesty label for the projected residual map: `full` \| `subset` \| `unavailable` — never invent green residual | | ||
| | **improvement compass** | Closed projection of residual architecture work across fixed **lenses** (aligned to 15 common principles). Always `notAScore`. Never a gate input. | | ||
@@ -107,2 +109,3 @@ | **lens** | One named principle dimension (`soc`, `dip`, `domain`, …) with status `ok` / `residual` / `not-instrumented` / `out-of-scope` and evidence refs from existing sensors | | ||
| | **AI-easy architecture** | Small, pure, placeable modules and a golden pattern so the next agent turn stays ordered under the contract | | ||
| | **self-service upgrade honesty** | After managed upgrade, consumers can see write-path activation labels and customized-content preserve without asking a maintainer | | ||
@@ -109,0 +112,0 @@ ## Public docs are product-only (from 4.4.0) |
+5
-5
@@ -56,3 +56,3 @@ # ArkGate documentation | ||
| | Release notes (by version) | [releases/](releases/) · [CHANGELOG.md](../CHANGELOG.md) | | ||
| | Epic plans (seeded + shipped) | [plans/](plans/) — maintainer seeds (e.g. improvement compass for **4.4.0**, agent contract surface for **4.3.0**). Product how-to stays in use/develop/agent-guide; plans are not required reading to use the package. | | ||
| | Epic plans (seeded + shipped) | [plans/](plans/) — maintainer seeds (e.g. domain fitness & session truth for **4.5.0** with honesty modes / mandatory domain split / self-service residual, improvement compass for **4.4.0**, agent contract surface for **4.3.0**). Product how-to stays in use/develop/agent-guide; plans are not required reading to use the package. | | ||
| | Claims audit | [audit/claims-matrix.md](audit/claims-matrix.md) | | ||
@@ -62,6 +62,6 @@ | Field adoption kit (scaffolding, not closed) | [field/](field/) | | ||
| Current published: [releases/4.3.0.md](releases/4.3.0.md) (`arkgate@4.3.0` on npm `latest`). | ||
| Next prepare: [releases/4.4.0.md](releases/4.4.0.md) (Status: prepared — not on npm until publish verify). | ||
| Prior: [releases/4.2.1.md](releases/4.2.1.md) (`arkgate@4.2.1`). | ||
| Previous: [releases/4.2.0.md](releases/4.2.0.md) (`arkgate@4.2.0`) · [releases/4.1.1.md](releases/4.1.1.md) (`arkgate@4.1.1`). | ||
| Current published: [releases/4.4.0.md](releases/4.4.0.md) (`arkgate@4.4.0` on npm `latest`). | ||
| Next prepare: [releases/4.5.0.md](releases/4.5.0.md) (Status: prepared — not on npm until publish verify). | ||
| Prior: [releases/4.3.0.md](releases/4.3.0.md) (`arkgate@4.3.0`). | ||
| Previous: [releases/4.2.1.md](releases/4.2.1.md) · [releases/4.2.0.md](releases/4.2.0.md) · [releases/4.1.1.md](releases/4.1.1.md). | ||
| Previous major: [releases/4.0.0.md](releases/4.0.0.md) (`arkgate@4.0.0`). | ||
@@ -68,0 +68,0 @@ Config: [configuration.md](configuration.md) · Agent skills dual-plane: [agent-guide.md](agent-guide.md). |
+30
-0
@@ -108,2 +108,30 @@ # Use ArkGate | ||
| ## Session recipe (agent turn) | ||
| Short loop so agents do not invent residual or re-run doctor every message: | ||
| 1. **Bind identity** — MCP: call `ark_identity` with `project.expectedRoot` set to the project’s | ||
| exact absolute root; reuse that root plus the returned `projectId` on later Ark tools. CLI: | ||
| pass `--expected-root /abs/project/root` on `ark status` when you need matched vs stale binding. | ||
| 2. **Read status** — `npx ark status --json` (or MCP `ark_status`) for identity, write-path | ||
| activation honesty, last-check summary, residual lens ids, and primary next action. | ||
| 3. **Act** — work the residual / next action / stable `findingRef` from check diagnostics. Do not | ||
| invent green residual lenses. Green edges alone are never “architecture finished.” | ||
| 4. **Doctor when status is incomplete** — if status `improvementCompass.mode` is **`subset`** or | ||
| **`unavailable`** (or compass facts are missing), run `npx ark-check --doctor` (add `--json` for | ||
| the full 15-lens map) before treating residual as complete. When mode is **`full`**, status | ||
| residual ids are a safe subset of doctor residual for the same facts. | ||
| ```bash | ||
| npx ark status --json --expected-root /abs/project/root | ||
| # when mode is not full: | ||
| npx ark-check --doctor | ||
| npx ark-check --doctor --json # doctor.improvementCompass | ||
| ``` | ||
| Details: [agent-guide — Session recipe](agent-guide.md#session-recipe-agent-turn) · | ||
| [package surface — status / compass](package-surface.md). | ||
| --- | ||
| ## Improvement compass (not a score) | ||
@@ -129,2 +157,4 @@ | ||
| JSON: `ark-check --doctor --json` → `doctor.improvementCompass` (full lenses + `topResidual`). | ||
| Status also projects a thin residual map with honesty **`mode`**: `full` \| `subset` \| `unavailable` | ||
| (always `notAScore`) — see [Session recipe](#session-recipe-agent-turn). | ||
| Human doctor prints the short section above. | ||
@@ -131,0 +161,0 @@ |
+1
-1
| { | ||
| "name": "arkgate", | ||
| "version": "4.4.0", | ||
| "version": "4.5.0", | ||
| "description": "ArkGate — architecture co-pilot for AI TypeScript (write gate, CI gate, plan/loop; optional ArkRules)", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+5
-4
@@ -19,4 +19,4 @@ <div align="center"> | ||
| > **ArkGate 4.3.0** is on npm `latest`. Tree is preparing **4.4.0** (improvement compass) — see [4.4.0 notes](docs/releases/4.4.0.md) (Status: prepared until publish). | ||
| > [4.4.0 notes](docs/releases/4.4.0.md) · [4.3.0](docs/releases/4.3.0.md) · [4.2.1](docs/releases/4.2.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md) | ||
| > **ArkGate 4.4.0** is on npm `latest`. Tree is preparing **4.5.0** (session honesty + upgrade self-service) — see [4.5.0 notes](docs/releases/4.5.0.md) (Status: prepared until publish). | ||
| > [4.5.0 notes](docs/releases/4.5.0.md) · [4.4.0](docs/releases/4.4.0.md) · [4.3.0](docs/releases/4.3.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md) | ||
@@ -212,4 +212,5 @@ --- | ||
| | Security | [SECURITY.md](SECURITY.md) | | ||
| | Current release (4.3.0 on npm `latest`) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) · [CHANGELOG](CHANGELOG.md) | | ||
| | Next prepare (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) (prepared — not published until npm verify) | | ||
| | Current release (4.4.0 on npm `latest`) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) · [CHANGELOG](CHANGELOG.md) | | ||
| | Next prepare (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) (prepared — not published until npm verify) | | ||
| | Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) | | ||
| | Prior (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) | | ||
@@ -216,0 +217,0 @@ | Previous (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) | |
@@ -245,3 +245,3 @@ { | ||
| "type": "object", | ||
| "description": "Optional thin improvement-compass residual ids (notAScore). Never a gate input; full lenses on doctor JSON.", | ||
| "description": "Thin improvement-compass residual ids with honesty mode (notAScore). full | subset | unavailable. Never a gate input; full lenses on doctor JSON. When full, residual ids ⊆ doctor residual for the same facts. unavailable never invents green residual.", | ||
| "additionalProperties": false, | ||
@@ -251,2 +251,3 @@ "required": [ | ||
| "notAScore", | ||
| "mode", | ||
| "topResidual" | ||
@@ -261,2 +262,9 @@ ], | ||
| }, | ||
| "mode": { | ||
| "enum": [ | ||
| "full", | ||
| "subset", | ||
| "unavailable" | ||
| ] | ||
| }, | ||
| "topResidual": { | ||
@@ -269,2 +277,21 @@ "type": "array", | ||
| "maxItems": 15 | ||
| }, | ||
| "reasonCode": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "reason": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "factsSource": { | ||
| "enum": [ | ||
| "doctor-facts", | ||
| "report-snapshot", | ||
| "none" | ||
| ] | ||
| }, | ||
| "contractHash": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| } | ||
@@ -271,0 +298,0 @@ } |
+2
-2
@@ -9,3 +9,3 @@ { | ||
| }, | ||
| "version": "4.4.0", | ||
| "version": "4.5.0", | ||
| "packages": [ | ||
@@ -15,3 +15,3 @@ { | ||
| "identifier": "arkgate", | ||
| "version": "4.4.0", | ||
| "version": "4.5.0", | ||
| "runtimeHint": "npx", | ||
@@ -18,0 +18,0 @@ "transport": { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
3067577
1.49%195
1.56%45399
1.63%242
0.41%