Sign In

arkgate

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

arkgate - npm Package Compare versions

Comparing version
4.5.5
to
4.5.6
+241
bin/lib/upgrade-package-decision.mjs
/**
* FX01–FX02 — package install decision for `ark upgrade`.
*
* Pure-ish helpers: registry version is injectable so unit tests need no network.
* Production may pass `getRegistryLatest` that runs `npm view arkgate version`.
*/
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
/**
* Compare numeric major.minor.patch cores (prerelease / build ignored).
* @returns {-1|0|1}
*/
export function compareSemverCore(a, b) {
const parse = (value) => {
const core = String(value ?? '')
.trim()
.replace(/^v/i, '')
.split(/[-+]/)[0];
const parts = core.split('.').map((part) => {
const n = Number.parseInt(part, 10);
return Number.isFinite(n) ? n : 0;
});
return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
};
const left = parse(a);
const right = parse(b);
for (let i = 0; i < 3; i += 1) {
if (left[i] < right[i]) return -1;
if (left[i] > right[i]) return 1;
}
return 0;
}
/**
* Best-effort registry latest (npm view). Returns null on failure.
* @param {{ timeoutMs?: number, run?: Function }} [opts]
*/
export function probeRegistryArkgateLatest(opts = {}) {
const timeout = Number(opts.timeoutMs) > 0 ? Number(opts.timeoutMs) : 8000;
const run =
typeof opts.run === 'function'
? opts.run
: () =>
spawnSync('npm', ['view', 'arkgate', 'version'], {
encoding: 'utf8',
timeout,
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
const result = run();
if (!result || result.status !== 0) return null;
const v = String(result.stdout || '')
.trim()
.split(/\s+/)[0];
return v || null;
} catch {
return null;
}
}
/**
* Whether install of arkgate can be skipped.
*
* FX01: when CLI == installed, still install if registryLatest > installed
* (unless registry probe skipped/unavailable — then fail open to install only if
* explicitly behind CLI; if equal and no registry, stay skip with honesty).
*
* @param {string} root
* @param {string} [cliVersion]
* @param {{
* registryLatest?: string|null,
* getRegistryLatest?: () => string|null|undefined,
* skipRegistryProbe?: boolean,
* }} [options]
* @returns {{
* skip: boolean,
* installedVersion: string|null,
* reason: string,
* reasonCode: string,
* registryLatest: string|null,
* cliVersion: string|null,
* }}
*/
export function shouldSkipArkgateInstall(root, cliVersion, options = {}) {
const cli = typeof cliVersion === 'string' && cliVersion.trim() ? cliVersion.trim() : null;
const pkgPath = path.join(root, 'node_modules', 'arkgate', 'package.json');
if (!fs.existsSync(pkgPath)) {
return {
skip: false,
installedVersion: null,
reason: 'not-installed',
reasonCode: 'NOT_INSTALLED',
registryLatest: null,
cliVersion: cli,
};
}
let installedVersion = null;
try {
installedVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
} catch {
return {
skip: false,
installedVersion: null,
reason: 'unreadable',
reasonCode: 'UNREADABLE',
registryLatest: null,
cliVersion: cli,
};
}
let registryLatest =
options.registryLatest !== undefined ? options.registryLatest : undefined;
if (registryLatest === undefined && options.skipRegistryProbe !== true) {
if (typeof options.getRegistryLatest === 'function') {
try {
registryLatest = options.getRegistryLatest() ?? null;
} catch {
registryLatest = null;
}
} else {
registryLatest = probeRegistryArkgateLatest();
}
}
if (registryLatest === undefined) registryLatest = null;
// Behind CLI version → always install
if (cli && installedVersion && installedVersion !== cli) {
return {
skip: false,
installedVersion,
reason: 'version-differs',
reasonCode: 'VERSION_DIFFERS',
registryLatest,
cliVersion: cli,
};
}
// Same as CLI (or no CLI): check registry
if (installedVersion && registryLatest && compareSemverCore(installedVersion, registryLatest) < 0) {
return {
skip: false,
installedVersion,
reason: 'behind-registry',
reasonCode: 'BEHIND_REGISTRY',
registryLatest,
cliVersion: cli,
};
}
if (cli && installedVersion && installedVersion === cli) {
if (registryLatest == null && options.skipRegistryProbe !== true) {
// Probe failed: do not false-skip forever — still report honesty; skip only when
// we cannot know registry (offline). Field preferred install when unsure is worse
// for offline CI; document REGISTRY_UNAVAILABLE and skip with reason.
return {
skip: true,
installedVersion,
reason: 'already-current-registry-unknown',
reasonCode: 'REGISTRY_UNAVAILABLE',
registryLatest: null,
cliVersion: cli,
};
}
return {
skip: true,
installedVersion,
reason: 'already-current',
reasonCode: 'ALREADY_CURRENT',
registryLatest: registryLatest ?? installedVersion,
cliVersion: cli,
};
}
return {
skip: false,
installedVersion,
reason: 'version-differs',
reasonCode: 'VERSION_DIFFERS',
registryLatest,
cliVersion: cli,
};
}
/**
* Machine-readable install decision + human recovery (FX02).
* @param {ReturnType<typeof shouldSkipArkgateInstall>} decision
* @param {string} root
* @param {(root: string, spec?: string) => [string, string[]]} packageInstallArgv
*/
export function buildPackageInstallSkipPayload(decision, root, packageInstallArgv) {
const targetSpec =
decision.registryLatest && compareSemverCore(decision.installedVersion || '0.0.0', decision.registryLatest) < 0
? decision.registryLatest
: 'latest';
const [command, commandArgs] = packageInstallArgv(root, targetSpec);
const suggestedInstallCmd = `${command} ${commandArgs.join(' ')}`.trim();
return {
schemaVersion: '1.0',
notAScore: true,
packageInstallSkipped: decision.skip === true,
reasonCode: decision.reasonCode || 'UNKNOWN',
reason: decision.reason || null,
installedVersion: decision.installedVersion,
cliVersion: decision.cliVersion,
registryLatest: decision.registryLatest,
suggestedInstallCmd,
};
}
/**
* Human lines for skip / behind-registry install decision.
* @param {ReturnType<typeof buildPackageInstallSkipPayload>} payload
*/
export function formatPackageInstallDecisionHuman(payload) {
if (!payload) return [];
if (payload.packageInstallSkipped && payload.reasonCode === 'ALREADY_CURRENT') {
return [
`Package already at arkgate@${payload.installedVersion}` +
(payload.registryLatest ? ` (registry ${payload.registryLatest})` : '') +
'; skipping install and recomputing managed preview.',
];
}
if (payload.packageInstallSkipped && payload.reasonCode === 'REGISTRY_UNAVAILABLE') {
return [
`Package at arkgate@${payload.installedVersion} matches this CLI; registry latest unknown (offline or npm view failed). Skipping install.`,
`If you know a newer release exists, run: ${payload.suggestedInstallCmd}`,
];
}
if (!payload.packageInstallSkipped && payload.reasonCode === 'BEHIND_REGISTRY') {
return [
`Installed arkgate@${payload.installedVersion} is behind registry ${payload.registryLatest}; installing update.`,
` ${payload.suggestedInstallCmd}`,
];
}
if (!payload.packageInstallSkipped) {
return [`Updating ArkGate (${payload.reasonCode}): ${payload.suggestedInstallCmd}`];
}
return [];
}
+7
-3

@@ -80,2 +80,3 @@ #!/usr/bin/env node

acceptConflicts: false,
refreshSkills: false,
planDigest: undefined,

@@ -122,2 +123,3 @@ json: false,

else if (arg === '--accept-conflicts') args.acceptConflicts = true;
else if (arg === '--refresh-skills') args.refreshSkills = true;
else if (arg === '--plan-digest') args.planDigest = requireValue(arg, i++);

@@ -158,3 +160,3 @@ else if (arg === '--json') args.json = true;

[--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--json] [--no-install] [--no-strict]
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--refresh-skills] [--json] [--no-install] [--no-strict]
ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--manifest <manifest.json>] [--tsconfig <tsconfig.json>] [--json]

@@ -170,4 +172,6 @@ ark status [--root <project>] [--config ark.config.json] [--json]

upgrade Preview identity-proven Ark-managed asset updates. With package install,
--apply bumps to @latest and recomputes the preview; a second explicit
--apply --no-install applies those exact bytes and verifies them.
--apply bumps toward registry latest when behind (not only when CLI ≠ pin)
and recomputes the preview; a second explicit --apply --no-install applies
those exact bytes and verifies them. --refresh-skills opts in to rewrite
customized managed skills to package templates (never silent default).
(alias: ark update)

@@ -174,0 +178,0 @@ preflight Validate one atomic create/update/delete set without writing project files.

@@ -468,7 +468,15 @@ import { createHash } from 'node:crypto';

const accepted = options.acceptConflicts === true;
// FX04: --refresh-skills opt-in rewrites customized *skill* assets to package
// templates. Conflicted still needs --accept-conflicts. Never silent default.
const refreshSkills = options.refreshSkills === true;
const skillRefresh =
refreshSkills &&
catalogAsset.kind === 'skill' &&
classified.state === 'customized';
const canApply =
classified.state === 'stale' ||
skillRefresh ||
(classified.state === 'missing' && (!recorded || accepted)) ||
(classified.state === 'conflicted' && accepted);
const blocked = classified.requiresConsent && !accepted;
const blocked = classified.requiresConsent && !accepted && !skillRefresh;
const desiredFile = afterFileContent(catalogAsset, currentFile, desiredScoped);

@@ -546,2 +554,3 @@ const asset = {

acceptConflicts: options.acceptConflicts === true,
refreshSkills: options.refreshSkills === true,
assets,

@@ -557,2 +566,179 @@ summary,

/**
* FX03 — skill content drift honesty (counts by state + sample paths).
* Skills only; never claims "skills upgraded" when only package pin moved.
*/
export function buildSkillDriftSummary(plan) {
const assets = Array.isArray(plan?.assets) ? plan.assets : [];
const skills = assets.filter((a) => a?.kind === 'skill');
const byState = {};
for (const skill of skills) {
const state = typeof skill.state === 'string' ? skill.state : 'unknown';
byState[state] = (byState[state] ?? 0) + 1;
}
const sample = (state, limit = 5) =>
skills
.filter((s) => s.state === state)
.map((s) => s.path)
.sort()
.slice(0, limit);
const customized = byState.customized ?? 0;
const stale = byState.stale ?? 0;
const missing = byState.missing ?? 0;
const current = byState.current ?? 0;
const wouldRefresh = skills.filter((s) => s.willApply === true).length;
return {
schemaVersion: '1.0',
notAScore: true,
skillCount: skills.length,
byState,
stale,
customized,
missing,
current,
wouldRefresh,
samplePaths: {
stale: sample('stale'),
customized: sample('customized'),
missing: sample('missing'),
},
note:
customized > 0 && wouldRefresh === 0
? 'Skills on disk differ from package templates (customized preserved). Use --refresh-skills to opt in to rewrite customized skills; never silent overwrite.'
: stale > 0
? 'Some skills are stale vs package templates and will refresh on apply.'
: skills.length === 0
? 'No managed skill assets in this upgrade selection.'
: 'Skill content matches package templates or is scheduled for write.',
};
}
/**
* FX07 — active host vs managed --tools / manifest hosts.
*/
export function buildHostSelectionHonesty(plan) {
const hosts = Array.isArray(plan?.hosts) ? plan.hosts.map((h) => String(h).toLowerCase()) : [];
let active = null;
try {
active = detectActiveAgentHost();
} catch {
active = null;
}
const activeNorm =
typeof active === 'string' && active.trim() ? active.trim().toLowerCase() : null;
const known = activeNorm && KNOWN_TOOLS.includes(activeNorm);
const inSelection = Boolean(activeNorm && hosts.includes(activeNorm));
const note =
known && !inSelection
? `Detected host "${activeNorm}" is not in managed tools [${hosts.join(', ') || 'none'}]. Re-run with --tools ${[...new Set([...hosts, activeNorm])].sort().join(',')} so that host's skills/hooks are in the plan.`
: known && inSelection
? `Detected host "${activeNorm}" is in the managed selection.`
: activeNorm
? `Detected host "${activeNorm}" is outside the known managed tool set.`
: 'No active agent host detected for this process.';
return {
schemaVersion: '1.0',
notAScore: true,
activeHost: activeNorm,
managedHosts: hosts,
activeInSelection: inSelection,
suggestTools:
known && !inSelection
? [...new Set([...hosts, activeNorm])].sort().join(',')
: null,
note,
};
}
/**
* FX05 — post-upgrade verification block (advisory sensors only).
*/
export function buildPostUpgradeChecks(root, options = {}) {
const resolvedRoot = path.resolve(root);
const checks = [];
let projectVersion = null;
try {
const pkgPath = path.join(resolvedRoot, 'node_modules', 'arkgate', 'package.json');
if (fs.existsSync(pkgPath)) {
projectVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
}
} catch {
projectVersion = null;
}
const cli = typeof options.cliVersion === 'string' ? options.cliVersion : arkPackageVersion();
const pinOk =
projectVersion != null && cli != null ? projectVersion === cli : null;
checks.push({
id: 'package-pin-cli',
ok: pinOk,
detail:
pinOk === true
? `Installed arkgate@${projectVersion} matches CLI ${cli}.`
: pinOk === false
? `Installed arkgate@${projectVersion} ≠ CLI ${cli}; re-run install or restart using project-local CLI.`
: `Could not compare pin (installed=${projectVersion ?? 'missing'}, cli=${cli ?? 'unknown'}).`,
});
checks.push({
id: 'architecture-verification',
ok:
options.verification?.mode === 'skipped'
? null
: options.verification?.exitCode === 0,
detail:
options.verification?.mode === 'skipped'
? 'Strict architecture verification was skipped (--no-strict).'
: options.verification?.exitCode === 0
? 'Strict-merge architecture verification passed.'
: `Architecture verification exit ${options.verification?.exitCode ?? 'unknown'}.`,
});
checks.push({
id: 'package-version-truth',
ok: options.dualTruth?.dualTruth === true ? false : options.dualTruth ? true : null,
detail:
options.dualTruth?.dualTruth === true
? options.dualTruth.note || 'Package pin dual-truth detected.'
: options.dualTruth
? 'Package pin truth is consistent for this apply.'
: 'Package version truth not evaluated.',
});
checks.push({
id: 'doctor-compass-coach',
ok: null,
detail:
'Run `npx arkgate-check --doctor --json` and confirm doctor.improvementCompass + doctor.deepModuleCoach (notAScore).',
});
checks.push({
id: 'agents-md-projection',
ok: null,
detail: 'Run `npx arkgate agents-md --check` (or --write) so AGENTS.md matches the package projection.',
});
checks.push({
id: 'status-mode',
ok: null,
detail: 'Run `npx arkgate status --json` and read honesty mode; incomplete facts never invent green residual.',
});
return {
schemaVersion: '1.0',
notAScore: true,
neverGateInput: true,
checks,
mcpNote:
'If you used Ark MCP this session: restart/retarget MCP after package bump so process arkgateVersion matches project install; always pass project.expectedRoot + expectedProjectId (WI01). Prefer project-local CLI until identity matched and versions align.',
};
}
export function formatSkillDriftHuman(skillDrift) {
if (!skillDrift) return [];
const lines = [
`Skill drift: ${skillDrift.skillCount} skill(s) — current ${skillDrift.current}, stale ${skillDrift.stale}, customized ${skillDrift.customized}, missing ${skillDrift.missing}, would refresh ${skillDrift.wouldRefresh}.`,
];
if (skillDrift.note) lines.push(` ${skillDrift.note}`);
return lines;
}
export function formatHostSelectionHuman(hostSelection) {
if (!hostSelection?.note) return [];
return [`Host selection: ${hostSelection.note}`];
}
function publicPlan(plan, overrides = {}) {

@@ -790,2 +976,13 @@ const assets = plan.assets.map(

}
const skillDrift =
options.skillDrift ?? plan.skillDrift ?? buildSkillDriftSummary(plan);
for (const line of formatSkillDriftHuman(skillDrift)) {
console.log(line);
}
const hostSelection =
options.hostSelection ?? plan.hostSelection ?? buildHostSelectionHonesty(plan);
for (const line of formatHostSelectionHuman(hostSelection)) {
console.log(line);
}
// FX08: whatsNew always on preview/apply human path (including nothing-to-apply).
const whatsNew = plan.whatsNew ?? buildUpgradeWhatsNewSuggestions();

@@ -792,0 +989,0 @@ for (const line of formatUpgradeWhatsNewSuggestions(whatsNew)) {

@@ -139,4 +139,25 @@ /**

/(?:^|_)(?:SCHEMA|PROTOCOL|RESOLVER|FORMAT)_(?:URL|URI|VERSION|ID|IDENTITY)$/i.test(name);
/**
* FX09 — pure UX copy / error-message string constants crowd inventory pilots.
* Downrank (skip) sentence-like strings and message-named identifiers; keep
* numeric thresholds and domain status tokens for adopt/contract pilots.
*/
const isUxMessageConstant = (name, rawValue) => {
if (/^(?:ERROR|SUCCESS|WARNING|INFO|HINT|HELP|EMPTY|TOAST|SNACK|ALERT|BANNER|DIALOG|MODAL|TOOLTIP|CAPTION|SUBTITLE|HEADLINE|USER|UI|DISPLAY|FEEDBACK)_(?:MSG|MESSAGE|TEXT|COPY|LABEL|TITLE|BODY|DESC|DESCRIPTION|HINT|HELP)?/i.test(name) ||
/_(?:MSG|MESSAGE|TEXT|COPY|TOAST|SNACK|ALERT|BANNER|CAPTION|HINT|HELP_TEXT|ERROR_TEXT|EMPTY_TEXT|PLACEHOLDER_TEXT|USER_MESSAGE|FEEDBACK)$/i.test(name)) {
return true;
}
const unquoted = rawValue.replace(/^['"]|['"]$/g, '');
// Sentence-like string values (spaces or terminal punctuation) are UX copy,
// not behavioral business limits — unless the name is a clear domain status seed.
if (/^['"]/.test(rawValue) &&
(/\s/.test(unquoted) || /[.!?…]$/.test(unquoted)) &&
!/^(?:STATUS|STATE|PHASE|ROLE|TYPE|KIND|ORDER|PAYMENT|CART|INVOICE|POLICY)_[A-Z0-9_]+$/i.test(name)) {
return true;
}
return false;
};
while ((magic = magicRe.exec(content)) !== null) {
const name = magic[2];
const rawValue = magic[3] ?? '';
// With governed layer evidence, generic Tooling/Kernel constants are not

@@ -149,2 +170,4 @@ // business-rule candidates. Controller-shaped boundaries stay eligible

continue;
if (isUxMessageConstant(name, rawValue))
continue;
// P2-N: skip remaining ALL_CAPS noise only on clear UI chrome (not all of app/).

@@ -151,0 +174,0 @@ if (isUiChrome && !isDomain)

@@ -7,3 +7,7 @@ /** Read-only-first `ark upgrade` orchestration. Managed identity logic lives separately. */

import { arkCommand } from '../ark-shared.mjs';
import {
arkCommand,
buildPackageInstallSkipPayload,
formatPackageInstallDecisionHuman,
} from '../ark-shared.mjs';
import { describePackageVersionDualTruth } from './field-install.mjs';

@@ -16,2 +20,5 @@ import { __packageRoot } from './gate-files.mjs';

renderManagedUpgrade,
buildSkillDriftSummary,
buildPostUpgradeChecks,
buildHostSelectionHonesty,
} from './managed-upgrade.mjs';

@@ -282,2 +289,3 @@

if (args.acceptConflicts) next.push('--accept-conflicts');
if (args.refreshSkills) next.push('--refresh-skills');
if (!args.strict) next.push('--no-strict');

@@ -305,2 +313,3 @@ if (args.json) next.push('--json');

if (args.acceptConflicts) flagParts.push('--accept-conflicts');
if (args.refreshSkills) flagParts.push('--refresh-skills');
if (!args.strict) flagParts.push('--no-strict');

@@ -364,15 +373,87 @@ if (args.json) flagParts.push('--json');

if (args.apply && args.install) {
const skip =
// FX01–FX02: registry-aware skip + structured skip truth (injectable probe for tests).
const skipOptions = {
...(dependencies.registryLatest !== undefined
? { registryLatest: dependencies.registryLatest }
: {}),
...(typeof dependencies.getRegistryLatest === 'function'
? { getRegistryLatest: dependencies.getRegistryLatest }
: {}),
...(dependencies.skipRegistryProbe === true ? { skipRegistryProbe: true } : {}),
};
const decisionRaw =
typeof dependencies.shouldSkipArkgateInstall === 'function'
? dependencies.shouldSkipArkgateInstall(root, dependencies.cliVersion)
: { skip: false };
if (skip.skip) {
? dependencies.shouldSkipArkgateInstall(root, dependencies.cliVersion, skipOptions)
: {
skip: false,
reasonCode: 'NOT_INSTALLED',
installedVersion: null,
registryLatest: null,
cliVersion: dependencies.cliVersion ?? null,
reason: 'not-installed',
};
// Normalize injectable mocks that only return { skip, installedVersion }.
const decision = {
...decisionRaw,
reasonCode:
decisionRaw.reasonCode ||
(decisionRaw.skip
? 'ALREADY_CURRENT'
: decisionRaw.installedVersion
? 'VERSION_DIFFERS'
: 'NOT_INSTALLED'),
reason:
decisionRaw.reason ||
(decisionRaw.skip ? 'already-current' : 'version-differs'),
cliVersion: decisionRaw.cliVersion ?? dependencies.cliVersion ?? null,
registryLatest: decisionRaw.registryLatest ?? null,
};
const installArgv =
typeof dependencies.packageInstallArgv === 'function'
? dependencies.packageInstallArgv
: null;
const targetSpec =
decision.registryLatest && decision.reasonCode === 'BEHIND_REGISTRY'
? decision.registryLatest
: 'latest';
function fallbackPayload(spec = targetSpec, skipped = decision.skip === true) {
return {
schemaVersion: '1.0',
notAScore: true,
packageInstallSkipped: skipped,
reasonCode: decision.reasonCode || 'UNKNOWN',
reason: decision.reason || null,
installedVersion: decision.installedVersion,
cliVersion: decision.cliVersion,
registryLatest: decision.registryLatest,
suggestedInstallCmd: `npm install -D arkgate@${spec}`,
};
}
if (decision.skip) {
// Skip path must not call packageInstallArgv or spawn install (legacy contract).
// Recovery command uses a portable default; agents can still read reasonCode.
if (!args.json) {
console.log(
`Package already at arkgate@${skip.installedVersion}; skipping install and recomputing managed preview.`
);
for (const line of formatPackageInstallDecisionHuman(fallbackPayload())) {
console.log(line);
}
}
} else {
const [command, commandArgs] = dependencies.packageInstallArgv(root);
if (!args.json) console.log(`Updating ArkGate: ${command} ${commandArgs.join(' ')}`);
const [command, commandArgs] = installArgv
? installArgv(root, targetSpec)
: ['npm', ['install', '-D', `arkgate@${targetSpec}`]];
const payload = installArgv
? {
...buildPackageInstallSkipPayload(decision, root, installArgv),
packageInstallSkipped: false,
suggestedInstallCmd: `${command} ${commandArgs.join(' ')}`,
}
: {
...fallbackPayload(targetSpec, false),
suggestedInstallCmd: `${command} ${commandArgs.join(' ')}`,
};
if (!args.json) {
for (const line of formatPackageInstallDecisionHuman(payload)) {
console.log(line);
}
}
const install = spawnSync(command, commandArgs, {

@@ -386,2 +467,15 @@ cwd: root,

if (args.json && install.stderr) console.error(install.stderr.trim());
if (args.json) {
console.log(
JSON.stringify(
{
packageInstallFailed: true,
exitCode,
...payload,
},
null,
2
)
);
}
const recovery = `${command} ${commandArgs.join(' ')}`;

@@ -429,2 +523,3 @@ const rePreview = arkCommand(

acceptConflicts: args.acceptConflicts,
refreshSkills: args.refreshSkills === true,
});

@@ -436,8 +531,13 @@ if (!args.apply) {

const command = buildUpgradeNextCommand(args, plan.planDigest);
const skillDrift = buildSkillDriftSummary(plan);
const hostHonesty = buildHostSelectionHonesty(plan);
if (args.json) {
// Always expose nextCommand for digest-bound content/manifest apply;
// nothingToApply flags when content writes are zero so UIs do not urge apply.
// FX03 skillDrift + FX07 hostSelection + FX08 whatsNew always on preview.
console.log(
managedUpgradeJson(plan, {
nextCommand: command,
skillDrift,
hostSelection: hostHonesty,
...(needsApply ? {} : { nothingToApply: true }),

@@ -460,2 +560,4 @@ // Surface dual-truth when managed assets refresh without a package pin bump.

: undefined,
skillDrift,
hostSelection: hostHonesty,
});

@@ -498,2 +600,10 @@ if (args.install === false) {

const dualTruth = describePackageVersionDualTruth(root);
const skillDrift = buildSkillDriftSummary(applied);
const hostHonesty = buildHostSelectionHonesty(applied);
// FX05: post-upgrade verification block (advisory, notAScore).
const postUpgradeChecks = buildPostUpgradeChecks(root, {
cliVersion: dependencies.cliVersion,
verification,
dualTruth,
});
if (args.json) {

@@ -505,2 +615,5 @@ console.log(

verification,
skillDrift,
hostSelection: hostHonesty,
postUpgradeChecks,
...(args.install === false || dualTruth.dualTruth

@@ -527,3 +640,3 @@ ? {

} else {
renderManagedUpgrade(applied);
renderManagedUpgrade(applied, { skillDrift, hostSelection: hostHonesty });
if (!args.strict) console.log('Architecture verification skipped (--no-strict).');

@@ -537,4 +650,12 @@ if (args.install === false || dualTruth.dualTruth) {

}
console.log('Post-upgrade checks (advisory — not a score; never flips the gate):');
for (const check of postUpgradeChecks.checks ?? []) {
const mark = check.ok === true ? 'ok' : check.ok === false ? 'attention' : 'note';
console.log(` [${mark}] ${check.id}: ${check.detail}`);
}
if (postUpgradeChecks.mcpNote) {
console.log(` [note] mcp: ${postUpgradeChecks.mcpNote}`);
}
}
return verification.exitCode;
}

@@ -77,2 +77,27 @@ /**

},
{
id: 'registry-aware-upgrade',
title: 'Registry-aware package upgrade (FX field truth)',
try: 'npx arkgate upgrade --apply',
inspect:
'packageInstallSkipped / reasonCode / registryLatest / suggestedInstallCmd (JSON)',
why:
'When CLI version equals node_modules but npm registry is ahead, upgrade no longer false-skips. Offline/registry-unknown stays honest with a copy-paste install command.',
},
{
id: 'skill-drift-refresh',
title: 'Skill content drift + opt-in refresh',
try: 'npx arkgate upgrade --json',
inspect: 'skillDrift (+ --refresh-skills for customized skill rewrite consent)',
why:
'See stale/customized/missing skill counts. Customized skills stay preserved unless you pass --refresh-skills; never silent overwrite of true edits.',
},
{
id: 'mcp-multi-project',
title: 'Multi-project MCP process honesty',
try: 'ark_identity with project.expectedRoot; read processPackage on every tool',
inspect: 'processPackage.processPackageMismatch / processStale + nextAction',
why:
'One user, many checkouts: after package bump, restart MCP so process arkgateVersion matches install. Prefer project-local CLI until identity matched and versions align.',
},
],

@@ -79,0 +104,0 @@ };

@@ -86,2 +86,23 @@ # ArkGate — Agent Integration Guide

### Multi-project MCP and upgrade honesty (4.5.6)
One human often has **N checkouts** and **N package pins**. Product rules:
| Rule | Why |
|------|-----|
| One checkout = one `project.expectedRoot` | Identity (WI01) fail-closes cross-project evidence when used correctly |
| After `npm install arkgate@…`, restart/retarget Ark MCP | Process `arkgateVersion` is startup-loaded; long-lived MCP can lag install |
| Read `processPackage` on every tool response | `processPackageMismatch` / `processStale` + `nextAction` when process ≠ project install |
| Prefer project-local CLI until versions align | CLI always available; MCP analysis is not “fully current” while process is stale |
| Upgrade each package that pins arkgate | Monorepo “done” is not one package’s pin |
| Registry-aware `ark upgrade --apply` | Does not false-skip when CLI == pin but registry is ahead; skip JSON has `reasonCode` + `suggestedInstallCmd` |
| Skills: `skillDrift` + optional `--refresh-skills` | Customized skill bodies stay preserved unless you opt in; never silent overwrite of true edits |
```bash
npx arkgate upgrade --json # skillDrift, whatsNew, hostSelection
npx arkgate upgrade --apply # registry-aware package step, then re-preview
# digest-bound apply + optional skill refresh:
npx arkgate upgrade --apply --no-install --plan-digest sha256:… --refresh-skills
```
### Two-axis done recipe

@@ -88,0 +109,0 @@

@@ -23,3 +23,5 @@ # ArkGate package surface policy

| **Deep-module coach (post-4.5 advisory)** | `ark-check --doctor --json` → `doctor.deepModuleCoach`; human doctor section **Deep-module coach (advisory — not a score)** always when doctor runs (empty candidates / hot-path `unavailable` are honesty, not omission); HTML `data-advisory="deepModuleCoach"`. | Additive schema `1.0`. Always **`notAScore: true`**. **`hotPaths`**: recent-churn heuristic from bounded git log; `available` + `status` `ok` \| `unavailable`; empty `paths` when history missing/incomplete — **never invent**. **`deepeningCandidates`**: cards projected only from existing design smells / physical cohesion / reshape pilot / pilotLoop / residual compass lenses — **empty when no evidence** (no fake candidates). Never flips `valid`, strict-merge, completeness green, or `goal.met`. Prefer deep modules / named seams / test-at-public-interface process language in skills. Domain pure + CLI gen mirror (`deepeningCoach.ts` / `bin/lib/deepening-coach.mjs`); **not** a root package export — consume via `doctor.deepModuleCoach` (or the gen mirror in Tooling). |
| **Upgrade what’s new (4.5.5)** | `ark upgrade --json` → `whatsNew` (+ human **Suggested improvements** block). | Always **`notAScore: true`**, **`neverGateInput: true`**. Closed try/inspect list: deep-module coach, improvement compass, session/status honesty, two-axis done, self-service honesty. Never invents residual or flips gates. |
| **Upgrade what’s new (4.5.5+)** | `ark upgrade --json` → `whatsNew` (+ human **Suggested improvements** block; also on preview). | Always **`notAScore: true`**, **`neverGateInput: true`**. Closed try/inspect list: deep-module coach, improvement compass, session/status honesty, two-axis done, self-service honesty, registry-aware upgrade, skill drift/refresh, multi-project MCP. Never invents residual or flips gates. |
| **Field upgrade truth (4.5.6)** | `ark upgrade` registry-aware install; JSON `reasonCode` / `suggestedInstallCmd`; `skillDrift`; `--refresh-skills`; `postUpgradeChecks`; `hostSelection`. | No false-skip when registry ahead; offline honesty; customized skills preserved unless opt-in refresh; checks are advisory only. |
| **MCP process package honesty (4.5.6)** | Every MCP tool context → `processPackage` (`processPackageMismatch` / `processStale`, versions, `nextAction`). | Multi-checkout users: restart MCP after pin bump; prefer project-local CLI until identity matched and versions align. Fail-closed identity (WI01) unchanged. |
| **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. |

@@ -211,4 +213,4 @@ | **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. |

Ship notes for a version live under [releases/](https://github.com/pedroknigge/arkgate/tree/main/docs/releases)
(current published: [4.5.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.5.0.md); prepared: [4.5.5.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.5.5.md);
prior published: [4.4.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.4.0.md), [4.3.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.3.0.md),
(current published: [4.5.5.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.5.5.md);
prior published: [4.5.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.5.0.md), [4.4.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.4.0.md), [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);

@@ -215,0 +217,0 @@ previous: [4.2.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.2.0.md),

@@ -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. deep-module coach post-4.5 **implemented, not published**; domain fitness & session truth for **4.5.0**; 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. [field-upgrade-mcp-truth](plans/field-upgrade-mcp-truth/README.md) **in progress → 4.5.6 prepared**; deep-module coach **shipped in 4.5.5**; domain fitness & session truth for **4.5.0**; 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,4 +62,3 @@ | Field adoption kit (scaffolding, not closed) | [field/](field/) |

Current published: [releases/4.5.0.md](releases/4.5.0.md) (`arkgate@4.5.0` on npm `latest`).
Prepared next: [releases/4.5.5.md](releases/4.5.5.md) (`arkgate@4.5.5` — not on `latest` until publish).
Current published: [releases/4.5.5.md](releases/4.5.5.md) (`arkgate@4.5.5` on npm `latest`).
Prior: [releases/4.4.0.md](releases/4.4.0.md) (`arkgate@4.4.0`).

@@ -66,0 +65,0 @@ Previous: [releases/4.3.0.md](releases/4.3.0.md) · [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).

{
"name": "arkgate",
"version": "4.5.5",
"version": "4.5.6",
"description": "ArkGate — architecture co-pilot for AI TypeScript (write gate, CI gate, plan/loop; optional ArkRules)",

@@ -5,0 +5,0 @@ "type": "module",

@@ -19,5 +19,4 @@ <div align="center">

> **ArkGate 4.5.5** is **prepared** (deep-module coach + upgrade what’s new). npm `latest` remains
> **4.5.0** until publish. [4.5.5 notes](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) ·
> [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
> **ArkGate 4.5.5** is on npm `latest` — deep-module coach, upgrade what’s new, session honesty.
> [4.5.5 notes](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) · [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)

@@ -213,4 +212,4 @@ ---

| Security | [SECURITY.md](SECURITY.md) |
| Prepared (4.5.5) | [docs/releases/4.5.5.md](docs/releases/4.5.5.md) · [CHANGELOG](CHANGELOG.md) |
| Current published (4.5.0 on npm `latest`) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
| Current release (4.5.5 on npm `latest`) | [docs/releases/4.5.5.md](docs/releases/4.5.5.md) · [CHANGELOG](CHANGELOG.md) |
| Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
| Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |

@@ -217,0 +216,0 @@ | Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |

@@ -9,3 +9,3 @@ {

},
"version": "4.5.5",
"version": "4.5.6",
"packages": [

@@ -15,3 +15,3 @@ {

"identifier": "arkgate",
"version": "4.5.5",
"version": "4.5.6",
"runtimeHint": "npx",

@@ -18,0 +18,0 @@ "transport": {

@@ -38,3 +38,4 @@ ---

After `ark upgrade` (preview or apply), read JSON **`whatsNew`** or the human **Suggested improvements**
block. It lists concrete try/inspect actions for this package line (advisory only — not a score):
block (also on preview when nothing to apply). It lists concrete try/inspect actions for this package
line (advisory only — not a score):

@@ -46,6 +47,23 @@ 1. **Deep-module coach** — `ark-check --doctor` → `doctor.deepModuleCoach` (hot paths + deepening)

5. **Self-service honesty** — upgrade `selfService` write-path labels + customized preserve
6. **Registry-aware upgrade** — `reasonCode` / `suggestedInstallCmd` when package install is skipped or needed
7. **Skill drift + refresh** — `skillDrift`; opt-in `--refresh-skills` for customized skill rewrite
8. **Multi-project MCP** — `processPackage` mismatch/stale on every MCP tool; restart after package bump
Never invent gate verdicts from these suggestions. Missing residual is honest empty, not green.
## Field truth (package install + skills + multi-project MCP)
| Situation | Honest product behavior |
|-----------|-------------------------|
| CLI version == `node_modules` but npm registry is ahead | `--apply` **installs** (does not false-skip). Inspect `reasonCode: BEHIND_REGISTRY`. |
| Offline / `npm view` failed | May skip with `REGISTRY_UNAVAILABLE` + `suggestedInstallCmd` — do not invent a version. |
| Skills customized after install | Preserved by default. Preview `skillDrift` shows counts. **`--refresh-skills`** rewrites customized *skills* only with consent. |
| Conflicted managed assets | Still need `--accept-conflicts`. Never silent overwrite of true edits. |
| Multiple checkouts / monorepo packages | One `expectedRoot` per project; upgrade **each** pin; restart MCP after bump; prefer project-local CLI until identity matched **and** process version aligns. |
| Active host not in `--tools` / manifest | Preview `hostSelection` notes it and suggests `--tools` expansion. |
**Post-apply:** read `postUpgradeChecks` (advisory). Confirm pin↔CLI, run doctor (compass + deepModuleCoach),
`agents-md --check`, `ark status --json`, and MCP version note if MCP was used.
## Dual engine (mandatory)

@@ -67,2 +85,7 @@

**Process package honesty:** every tool response includes `processPackage` (`processArkgateVersion`,
`projectInstalledVersion`, `processPackageMismatch` / `processStale`, `nextAction`). After
`npm install arkgate@…`, **restart/retarget MCP** so process version matches install. Until then,
prefer project-local CLI and do not treat MCP analysis as fully current.
## Dual plane — layers + ArkRules (mandatory, except /ark-runtime)

@@ -117,4 +140,4 @@

| `missing` | Candidate is absent. | Create if new; require consent if a recorded asset was deleted. |
| `customized` | User content diverged without a competing managed base. | Preserve it. |
| `conflicted` | Both managed base and user content diverged. | Preserve and require explicit consent. |
| `customized` | User content diverged without a competing managed base. | Preserve it. Opt-in rewrite for **skills only**: `--refresh-skills`. |
| `conflicted` | Both managed base and user content diverged. | Preserve and require explicit consent (`--accept-conflicts`). |
| `retired` | A recorded asset is no longer selected by the candidate. | Preserve its file and manifest identity; take no action. |

@@ -179,3 +202,4 @@

3. **Update and re-preview.** If the registry is newer, run (project-local CLI):
3. **Update and re-preview.** If the registry is newer **or** CLI == pin but registry is ahead
(field false-skip is fixed), run (project-local CLI):

@@ -186,6 +210,7 @@ ```bash

This updates through the detected package manager and hands control to the new
package for a fresh preview. Review that new preview; do not assume the old
candidate and new candidate are identical. If already on the latest package,
retain the current read-only preview.
This updates through the detected package manager (registry-aware) and hands control to the new
package for a fresh preview. On skip, read `reasonCode` / `suggestedInstallCmd` — agents must not
invent recovery. Review the new preview; do not assume old and new candidates are identical.
If already current (`ALREADY_CURRENT`), retain the read-only preview and still read `whatsNew`
+ `skillDrift`.

@@ -208,14 +233,21 @@ For pnpm repositories with `minimumReleaseAge`, use the repository's existing

If recorded deletion/conflict recovery is desired, ask first and then add
`--accept-conflicts`. Never add it merely to make the run green. Run a second
preview and require `summary.changed: 0`.
`--accept-conflicts`. Never add it merely to make the run green.
5. **Verify enforcement and architecture.** Run
If customized **skills** should match package templates after pin bump, ask first and add
`--refresh-skills` on the digest-bound apply (or a new preview that includes the flag). Never
add it merely to make the run green. Run a second preview and require `summary.changed: 0`
(unless more deliberate refreshes remain).
5. **Verify enforcement and architecture (post-upgrade checks).** Read apply JSON
`postUpgradeChecks` when present. Also run:
`npx arkgate-check --doctor --json` (or the project-local `ark-check`) and
the same fail-closed architecture command used by managed apply (normally
`npx arkgate-check --root . --config ark.config.json --strict-merge --json`).
Require `completeness: "complete"` and `ok: true`. Treat provider-unavailable CI
required-check evidence as `unverified`, never as proof that merges are
blocked. If new violations appear, hand off to `/ark-fix` for a small set or
`/ark-loop` / `/ark-autopilot` for residual debt; do not regenerate a baseline
without explicit approval.
Require `completeness: "complete"` and `ok: true`. Confirm `doctor.improvementCompass` and
`doctor.deepModuleCoach` honesty. Run `npx arkgate agents-md --check` and
`npx arkgate status --json`. If MCP was used, restart MCP after package bump and re-bind
identity. Treat provider-unavailable CI required-check evidence as `unverified`, never as proof
that merges are blocked. If new violations appear, hand off to `/ark-fix` for a small set or
`/ark-loop` / `/ark-autopilot` for residual debt; do not regenerate a baseline without explicit
approval.

@@ -222,0 +254,0 @@ ## Active host vs deferred hosts

@@ -10,3 +10,3 @@ # ArkGate Agent Skills package

Package version when last generated context: **arkgate@4.5.0**
Package version when last generated context: **arkgate@4.5.6**
Schema: agent-skills package contract `1.0`

@@ -13,0 +13,0 @@

@@ -38,3 +38,4 @@ ---

After `ark upgrade` (preview or apply), read JSON **`whatsNew`** or the human **Suggested improvements**
block. It lists concrete try/inspect actions for this package line (advisory only — not a score):
block (also on preview when nothing to apply). It lists concrete try/inspect actions for this package
line (advisory only — not a score):

@@ -46,6 +47,23 @@ 1. **Deep-module coach** — `ark-check --doctor` → `doctor.deepModuleCoach` (hot paths + deepening)

5. **Self-service honesty** — upgrade `selfService` write-path labels + customized preserve
6. **Registry-aware upgrade** — `reasonCode` / `suggestedInstallCmd` when package install is skipped or needed
7. **Skill drift + refresh** — `skillDrift`; opt-in `--refresh-skills` for customized skill rewrite
8. **Multi-project MCP** — `processPackage` mismatch/stale on every MCP tool; restart after package bump
Never invent gate verdicts from these suggestions. Missing residual is honest empty, not green.
## Field truth (package install + skills + multi-project MCP)
| Situation | Honest product behavior |
|-----------|-------------------------|
| CLI version == `node_modules` but npm registry is ahead | `--apply` **installs** (does not false-skip). Inspect `reasonCode: BEHIND_REGISTRY`. |
| Offline / `npm view` failed | May skip with `REGISTRY_UNAVAILABLE` + `suggestedInstallCmd` — do not invent a version. |
| Skills customized after install | Preserved by default. Preview `skillDrift` shows counts. **`--refresh-skills`** rewrites customized *skills* only with consent. |
| Conflicted managed assets | Still need `--accept-conflicts`. Never silent overwrite of true edits. |
| Multiple checkouts / monorepo packages | One `expectedRoot` per project; upgrade **each** pin; restart MCP after bump; prefer project-local CLI until identity matched **and** process version aligns. |
| Active host not in `--tools` / manifest | Preview `hostSelection` notes it and suggests `--tools` expansion. |
**Post-apply:** read `postUpgradeChecks` (advisory). Confirm pin↔CLI, run doctor (compass + deepModuleCoach),
`agents-md --check`, `ark status --json`, and MCP version note if MCP was used.
## Dual engine (mandatory)

@@ -67,2 +85,7 @@

**Process package honesty:** every tool response includes `processPackage` (`processArkgateVersion`,
`projectInstalledVersion`, `processPackageMismatch` / `processStale`, `nextAction`). After
`npm install arkgate@…`, **restart/retarget MCP** so process version matches install. Until then,
prefer project-local CLI and do not treat MCP analysis as fully current.
## Dual plane — layers + ArkRules (mandatory, except /ark-runtime)

@@ -117,4 +140,4 @@

| `missing` | Candidate is absent. | Create if new; require consent if a recorded asset was deleted. |
| `customized` | User content diverged without a competing managed base. | Preserve it. |
| `conflicted` | Both managed base and user content diverged. | Preserve and require explicit consent. |
| `customized` | User content diverged without a competing managed base. | Preserve it. Opt-in rewrite for **skills only**: `--refresh-skills`. |
| `conflicted` | Both managed base and user content diverged. | Preserve and require explicit consent (`--accept-conflicts`). |
| `retired` | A recorded asset is no longer selected by the candidate. | Preserve its file and manifest identity; take no action. |

@@ -179,3 +202,4 @@

3. **Update and re-preview.** If the registry is newer, run (project-local CLI):
3. **Update and re-preview.** If the registry is newer **or** CLI == pin but registry is ahead
(field false-skip is fixed), run (project-local CLI):

@@ -186,6 +210,7 @@ ```bash

This updates through the detected package manager and hands control to the new
package for a fresh preview. Review that new preview; do not assume the old
candidate and new candidate are identical. If already on the latest package,
retain the current read-only preview.
This updates through the detected package manager (registry-aware) and hands control to the new
package for a fresh preview. On skip, read `reasonCode` / `suggestedInstallCmd` — agents must not
invent recovery. Review the new preview; do not assume old and new candidates are identical.
If already current (`ALREADY_CURRENT`), retain the read-only preview and still read `whatsNew`
+ `skillDrift`.

@@ -208,14 +233,21 @@ For pnpm repositories with `minimumReleaseAge`, use the repository's existing

If recorded deletion/conflict recovery is desired, ask first and then add
`--accept-conflicts`. Never add it merely to make the run green. Run a second
preview and require `summary.changed: 0`.
`--accept-conflicts`. Never add it merely to make the run green.
5. **Verify enforcement and architecture.** Run
If customized **skills** should match package templates after pin bump, ask first and add
`--refresh-skills` on the digest-bound apply (or a new preview that includes the flag). Never
add it merely to make the run green. Run a second preview and require `summary.changed: 0`
(unless more deliberate refreshes remain).
5. **Verify enforcement and architecture (post-upgrade checks).** Read apply JSON
`postUpgradeChecks` when present. Also run:
`npx arkgate-check --doctor --json` (or the project-local `ark-check`) and
the same fail-closed architecture command used by managed apply (normally
`npx arkgate-check --root . --config ark.config.json --strict-merge --json`).
Require `completeness: "complete"` and `ok: true`. Treat provider-unavailable CI
required-check evidence as `unverified`, never as proof that merges are
blocked. If new violations appear, hand off to `/ark-fix` for a small set or
`/ark-loop` / `/ark-autopilot` for residual debt; do not regenerate a baseline
without explicit approval.
Require `completeness: "complete"` and `ok: true`. Confirm `doctor.improvementCompass` and
`doctor.deepModuleCoach` honesty. Run `npx arkgate agents-md --check` and
`npx arkgate status --json`. If MCP was used, restart MCP after package bump and re-bind
identity. Treat provider-unavailable CI required-check evidence as `unverified`, never as proof
that merges are blocked. If new violations appear, hand off to `/ark-fix` for a small set or
`/ark-loop` / `/ark-autopilot` for residual debt; do not regenerate a baseline without explicit
approval.

@@ -222,0 +254,0 @@ ## Active host vs deferred hosts

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