arkgate 4.5.5 → 4.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -2
- package/README.md +4 -5
- package/bin/ark-mcp-runtime.mjs +49 -0
- package/bin/ark-shared.mjs +8 -29
- package/bin/ark.mjs +7 -3
- package/bin/lib/managed-upgrade.mjs +198 -1
- package/bin/lib/rules-inventory.mjs +23 -0
- package/bin/lib/upgrade-command.mjs +132 -11
- package/bin/lib/upgrade-package-decision.mjs +241 -0
- package/bin/lib/upgrade-whats-new.mjs +25 -0
- package/dist/index.cjs +21 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -19
- package/docs/README.md +2 -3
- package/docs/agent-guide.md +21 -0
- package/docs/package-surface.md +5 -3
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-upgrade/SKILL.md +48 -16
- package/templates/skills/ark-upgrade.md +48 -16
package/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,57 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.5.6 — 2026-08-11
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.5.5**. Field upgrade & multi-project MCP truth (Phase FX): registry-aware
|
|
11
|
+
package install, structured skip recovery, skill drift + opt-in refresh, post-upgrade checks,
|
|
12
|
+
MCP process package honesty, host selection notes, early whatsNew, inventory UX-copy quieting.
|
|
13
|
+
**No required config migration.** No new skill names, sensors, or scores. Freezes held.
|
|
14
|
+
|
|
15
|
+
**Status: prepared** (not on npm `latest` until publish train completes; see
|
|
16
|
+
`docs/releases/4.5.6.md`).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Registry-aware upgrade (FX01–FX02):** `ark upgrade --apply` no longer skips package install
|
|
21
|
+
solely because CLI version equals `node_modules/arkgate`. When the registry (injectable for
|
|
22
|
+
tests) is ahead, install runs. Skip paths emit structured JSON (`reasonCode`,
|
|
23
|
+
`installedVersion`, `cliVersion`, `registryLatest`, `suggestedInstallCmd`) plus human
|
|
24
|
+
recovery. Offline / registry-unknown stays honest (`REGISTRY_UNAVAILABLE`) without inventing
|
|
25
|
+
a bump.
|
|
26
|
+
- **Skill content drift honesty (FX03):** upgrade preview/JSON `skillDrift` counts
|
|
27
|
+
current/stale/customized/missing skills with sample paths and preserve notes.
|
|
28
|
+
- **Opt-in skill refresh (FX04):** `--refresh-skills` rewrites customized managed *skills* to
|
|
29
|
+
package templates with explicit consent. Conflicted assets still need `--accept-conflicts`.
|
|
30
|
+
Never silent overwrite of true user edits by default.
|
|
31
|
+
- **Post-upgrade verification block (FX05):** after apply, advisory `postUpgradeChecks` (pin↔CLI,
|
|
32
|
+
architecture verification, dual-truth, doctor/status/MCP notes). Always `notAScore`.
|
|
33
|
+
- **MCP multi-project process honesty (FX06):** every MCP tool context includes
|
|
34
|
+
`processPackage` (`processArkgateVersion`, `projectInstalledVersion`,
|
|
35
|
+
`processPackageMismatch` / `processStale`, `nextAction`). Prefer project-local CLI until
|
|
36
|
+
identity matched and versions align; multi-checkout recipe in agent-guide + `/ark-upgrade`.
|
|
37
|
+
- **Host selection honesty (FX07):** upgrade notes when detected active host ∉ managed
|
|
38
|
+
`--tools`/manifest hosts and suggests `--tools` expansion.
|
|
39
|
+
- **whatsNew early path (FX08):** suggested improvements on preview even when nothing to apply;
|
|
40
|
+
includes FX field-truth try/inspect items.
|
|
41
|
+
- **Inventory UX message quieting (FX09):** rules inventory downranks pure UX/error-message
|
|
42
|
+
string constants so they do not crowd business-rule pilots (no new sensors).
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `/ark-upgrade` skill + upgrade help document registry-aware install, `--refresh-skills`,
|
|
47
|
+
skill drift, post-upgrade checks, and multi-project MCP restart guidance.
|
|
48
|
+
|
|
49
|
+
**Freezes held:** no principle scores/ranks; no new skill names; no silent customized overwrite;
|
|
50
|
+
no silent multi-project MCP retarget; soft hosts stay advisory; no fake published claims.
|
|
51
|
+
|
|
8
52
|
## 4.5.5 — 2026-08-11
|
|
9
53
|
|
|
10
54
|
**Patch** over **4.5.0**. Deep-module coach train plus upgrade **suggested improvements /
|
|
11
55
|
what’s new** so consumers know what to try after install. **No required config migration.**
|
|
12
56
|
No new skill names, sensors, or scores. Freezes held.
|
|
13
57
|
|
|
14
|
-
**Status:
|
|
15
|
-
the publication checklist completes).
|
|
58
|
+
**Status: published** (`arkgate@4.5.5` on npm `latest`; see `docs/releases/4.5.5.md`).
|
|
16
59
|
|
|
17
60
|
### Added
|
|
18
61
|
|
package/README.md
CHANGED
|
@@ -16,9 +16,8 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.5.5** is
|
|
20
|
-
>
|
|
21
|
-
> [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
19
|
+
> **ArkGate 4.5.5** is on npm `latest` — deep-module coach, upgrade what’s new, session honesty.
|
|
20
|
+
> [4.5.5 notes](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) · [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
22
21
|
|
|
23
22
|
---
|
|
24
23
|
|
|
@@ -209,8 +208,8 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
209
208
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
210
209
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
211
210
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
212
|
-
|
|
|
213
|
-
|
|
|
211
|
+
| Current release (4.5.5 on npm `latest`) | [docs/releases/4.5.5.md](docs/releases/4.5.5.md) · [CHANGELOG](CHANGELOG.md) |
|
|
212
|
+
| Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
|
|
214
213
|
| Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |
|
|
215
214
|
| Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |
|
|
216
215
|
| Prior (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) |
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import readline from 'node:readline';
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import {
|
|
10
11
|
DEFAULT_INTENT_PREFIXES,
|
|
@@ -1847,11 +1848,59 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
1847
1848
|
return currentBinding;
|
|
1848
1849
|
}
|
|
1849
1850
|
|
|
1851
|
+
/**
|
|
1852
|
+
* FX06 — process package vs project install honesty (multi-project field truth).
|
|
1853
|
+
* Process arkgateVersion is startup-loaded; after consumer `npm i arkgate@newer`,
|
|
1854
|
+
* long-lived MCP can report a stale process version until restart.
|
|
1855
|
+
*/
|
|
1856
|
+
function readProjectInstalledArkgateVersion() {
|
|
1857
|
+
try {
|
|
1858
|
+
const shallow = path.join(resolvedRoot, 'node_modules', 'arkgate', 'package.json');
|
|
1859
|
+
if (fs.existsSync(shallow)) {
|
|
1860
|
+
const v = JSON.parse(fs.readFileSync(shallow, 'utf8')).version;
|
|
1861
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
1862
|
+
}
|
|
1863
|
+
} catch {
|
|
1864
|
+
/* fall through */
|
|
1865
|
+
}
|
|
1866
|
+
try {
|
|
1867
|
+
const requireFromProject = createRequire(path.join(resolvedRoot, 'package.json'));
|
|
1868
|
+
const pkgJson = requireFromProject.resolve('arkgate/package.json');
|
|
1869
|
+
const v = JSON.parse(fs.readFileSync(pkgJson, 'utf8')).version;
|
|
1870
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
1871
|
+
} catch {
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function processPackageHonesty() {
|
|
1877
|
+
const processVersion = typeof ark.version === 'string' ? ark.version : null;
|
|
1878
|
+
const projectInstalledVersion = readProjectInstalledArkgateVersion();
|
|
1879
|
+
const mismatch =
|
|
1880
|
+
processVersion != null &&
|
|
1881
|
+
projectInstalledVersion != null &&
|
|
1882
|
+
processVersion !== projectInstalledVersion;
|
|
1883
|
+
return {
|
|
1884
|
+
schemaVersion: '1.0',
|
|
1885
|
+
notAScore: true,
|
|
1886
|
+
processArkgateVersion: processVersion,
|
|
1887
|
+
projectInstalledVersion,
|
|
1888
|
+
processPackageMismatch: mismatch,
|
|
1889
|
+
processStale: mismatch,
|
|
1890
|
+
nextAction: mismatch
|
|
1891
|
+
? 'Restart or retarget the Ark MCP server so process arkgateVersion matches the project install. Prefer project-local CLI (`npx arkgate` / `npx arkgate-check`) until identity is matched and versions align. Multi-checkout users: one expectedRoot per project; never reuse another checkout’s projectId.'
|
|
1892
|
+
: projectInstalledVersion == null
|
|
1893
|
+
? 'Project has no resolvable node_modules/arkgate; install the package or use CLI from a project that pins arkgate.'
|
|
1894
|
+
: 'Process package version matches project install for this MCP root.',
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1850
1898
|
function contextFor(binding) {
|
|
1851
1899
|
return {
|
|
1852
1900
|
projectIdentity,
|
|
1853
1901
|
binding,
|
|
1854
1902
|
authoritative: binding.authoritative,
|
|
1903
|
+
processPackage: processPackageHonesty(),
|
|
1855
1904
|
};
|
|
1856
1905
|
}
|
|
1857
1906
|
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -722,35 +722,14 @@ export function packageInstallArgv(root, versionSpec = 'latest') {
|
|
|
722
722
|
return ['npm', ['install', '-D', pkgSpec]];
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
export function shouldSkipArkgateInstall(root, cliVersion) {
|
|
734
|
-
const pkgPath = path.join(root, 'node_modules', 'arkgate', 'package.json');
|
|
735
|
-
if (!fs.existsSync(pkgPath)) {
|
|
736
|
-
return { skip: false, installedVersion: null, reason: 'not-installed' };
|
|
737
|
-
}
|
|
738
|
-
let installedVersion = null;
|
|
739
|
-
try {
|
|
740
|
-
installedVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
|
|
741
|
-
} catch {
|
|
742
|
-
return { skip: false, installedVersion: null, reason: 'unreadable' };
|
|
743
|
-
}
|
|
744
|
-
if (
|
|
745
|
-
typeof cliVersion === 'string' &&
|
|
746
|
-
cliVersion &&
|
|
747
|
-
installedVersion &&
|
|
748
|
-
installedVersion === cliVersion
|
|
749
|
-
) {
|
|
750
|
-
return { skip: true, installedVersion, reason: 'already-current' };
|
|
751
|
-
}
|
|
752
|
-
return { skip: false, installedVersion, reason: 'version-differs' };
|
|
753
|
-
}
|
|
725
|
+
// FX01–FX02: registry-aware skip lives in upgrade-package-decision (injectable probe).
|
|
726
|
+
export {
|
|
727
|
+
shouldSkipArkgateInstall,
|
|
728
|
+
buildPackageInstallSkipPayload,
|
|
729
|
+
formatPackageInstallDecisionHuman,
|
|
730
|
+
compareSemverCore,
|
|
731
|
+
probeRegistryArkgateLatest,
|
|
732
|
+
} from './lib/upgrade-package-decision.mjs';
|
|
754
733
|
|
|
755
734
|
/** Package-manager aware "install a dev dependency" hint (e.g. for a missing typescript). */
|
|
756
735
|
export function installDevHint(root, pkg) {
|
package/bin/ark.mjs
CHANGED
|
@@ -77,6 +77,7 @@ function parseArgs(argv) {
|
|
|
77
77
|
installExplicit: false,
|
|
78
78
|
apply: false,
|
|
79
79
|
acceptConflicts: false,
|
|
80
|
+
refreshSkills: false,
|
|
80
81
|
planDigest: undefined,
|
|
81
82
|
json: false,
|
|
82
83
|
internalApply: false,
|
|
@@ -118,6 +119,7 @@ function parseArgs(argv) {
|
|
|
118
119
|
}
|
|
119
120
|
else if (arg === '--apply') args.apply = true;
|
|
120
121
|
else if (arg === '--accept-conflicts') args.acceptConflicts = true;
|
|
122
|
+
else if (arg === '--refresh-skills') args.refreshSkills = true;
|
|
121
123
|
else if (arg === '--plan-digest') args.planDigest = requireValue(arg, i++);
|
|
122
124
|
else if (arg === '--json') args.json = true;
|
|
123
125
|
else if (arg === '--internal-apply') args.internalApply = true;
|
|
@@ -153,7 +155,7 @@ function usage() {
|
|
|
153
155
|
ark start [--root <project>] [--tools <host>] [--require-write-hook <host>] [--install] [--apply] [--json]
|
|
154
156
|
ark init [--root <project>] [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture]
|
|
155
157
|
[--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
|
|
156
|
-
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--json] [--no-install] [--no-strict]
|
|
158
|
+
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--refresh-skills] [--json] [--no-install] [--no-strict]
|
|
157
159
|
ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--manifest <manifest.json>] [--tsconfig <tsconfig.json>] [--json]
|
|
158
160
|
ark status [--root <project>] [--config ark.config.json] [--json]
|
|
159
161
|
[--expected-root <abs>] [--expected-project-id sha256:…] [--tools <host>]
|
|
@@ -164,8 +166,10 @@ Commands:
|
|
|
164
166
|
start New here? Analyze and preview the complete setup. Read-only unless --apply.
|
|
165
167
|
init Configure Ark project enforcement with explicit prompts.
|
|
166
168
|
upgrade Preview identity-proven Ark-managed asset updates. With package install,
|
|
167
|
-
--apply bumps
|
|
168
|
-
--apply --no-install applies
|
|
169
|
+
--apply bumps toward registry latest when behind (not only when CLI ≠ pin)
|
|
170
|
+
and recomputes the preview; a second explicit --apply --no-install applies
|
|
171
|
+
those exact bytes and verifies them. --refresh-skills opts in to rewrite
|
|
172
|
+
customized managed skills to package templates (never silent default).
|
|
169
173
|
(alias: ark update)
|
|
170
174
|
preflight Validate one atomic create/update/delete set without writing project files.
|
|
171
175
|
status Unified session/project manifest (identity, activation, last check, rules).
|
|
@@ -465,11 +465,19 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
465
465
|
kind: catalogAsset.kind,
|
|
466
466
|
});
|
|
467
467
|
const accepted = options.acceptConflicts === true;
|
|
468
|
+
// FX04: --refresh-skills opt-in rewrites customized *skill* assets to package
|
|
469
|
+
// templates. Conflicted still needs --accept-conflicts. Never silent default.
|
|
470
|
+
const refreshSkills = options.refreshSkills === true;
|
|
471
|
+
const skillRefresh =
|
|
472
|
+
refreshSkills &&
|
|
473
|
+
catalogAsset.kind === 'skill' &&
|
|
474
|
+
classified.state === 'customized';
|
|
468
475
|
const canApply =
|
|
469
476
|
classified.state === 'stale' ||
|
|
477
|
+
skillRefresh ||
|
|
470
478
|
(classified.state === 'missing' && (!recorded || accepted)) ||
|
|
471
479
|
(classified.state === 'conflicted' && accepted);
|
|
472
|
-
const blocked = classified.requiresConsent && !accepted;
|
|
480
|
+
const blocked = classified.requiresConsent && !accepted && !skillRefresh;
|
|
473
481
|
const desiredFile = afterFileContent(catalogAsset, currentFile, desiredScoped);
|
|
474
482
|
const asset = {
|
|
475
483
|
path: catalogAsset.relativePath,
|
|
@@ -542,6 +550,7 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
542
550
|
profile: selection.profile,
|
|
543
551
|
hosts: selection.hosts,
|
|
544
552
|
acceptConflicts: options.acceptConflicts === true,
|
|
553
|
+
refreshSkills: options.refreshSkills === true,
|
|
545
554
|
assets,
|
|
546
555
|
summary,
|
|
547
556
|
nextManifest,
|
|
@@ -552,6 +561,183 @@ export function planManagedUpgrade(root, options = {}) {
|
|
|
552
561
|
return plan;
|
|
553
562
|
}
|
|
554
563
|
|
|
564
|
+
/**
|
|
565
|
+
* FX03 — skill content drift honesty (counts by state + sample paths).
|
|
566
|
+
* Skills only; never claims "skills upgraded" when only package pin moved.
|
|
567
|
+
*/
|
|
568
|
+
export function buildSkillDriftSummary(plan) {
|
|
569
|
+
const assets = Array.isArray(plan?.assets) ? plan.assets : [];
|
|
570
|
+
const skills = assets.filter((a) => a?.kind === 'skill');
|
|
571
|
+
const byState = {};
|
|
572
|
+
for (const skill of skills) {
|
|
573
|
+
const state = typeof skill.state === 'string' ? skill.state : 'unknown';
|
|
574
|
+
byState[state] = (byState[state] ?? 0) + 1;
|
|
575
|
+
}
|
|
576
|
+
const sample = (state, limit = 5) =>
|
|
577
|
+
skills
|
|
578
|
+
.filter((s) => s.state === state)
|
|
579
|
+
.map((s) => s.path)
|
|
580
|
+
.sort()
|
|
581
|
+
.slice(0, limit);
|
|
582
|
+
const customized = byState.customized ?? 0;
|
|
583
|
+
const stale = byState.stale ?? 0;
|
|
584
|
+
const missing = byState.missing ?? 0;
|
|
585
|
+
const current = byState.current ?? 0;
|
|
586
|
+
const wouldRefresh = skills.filter((s) => s.willApply === true).length;
|
|
587
|
+
return {
|
|
588
|
+
schemaVersion: '1.0',
|
|
589
|
+
notAScore: true,
|
|
590
|
+
skillCount: skills.length,
|
|
591
|
+
byState,
|
|
592
|
+
stale,
|
|
593
|
+
customized,
|
|
594
|
+
missing,
|
|
595
|
+
current,
|
|
596
|
+
wouldRefresh,
|
|
597
|
+
samplePaths: {
|
|
598
|
+
stale: sample('stale'),
|
|
599
|
+
customized: sample('customized'),
|
|
600
|
+
missing: sample('missing'),
|
|
601
|
+
},
|
|
602
|
+
note:
|
|
603
|
+
customized > 0 && wouldRefresh === 0
|
|
604
|
+
? 'Skills on disk differ from package templates (customized preserved). Use --refresh-skills to opt in to rewrite customized skills; never silent overwrite.'
|
|
605
|
+
: stale > 0
|
|
606
|
+
? 'Some skills are stale vs package templates and will refresh on apply.'
|
|
607
|
+
: skills.length === 0
|
|
608
|
+
? 'No managed skill assets in this upgrade selection.'
|
|
609
|
+
: 'Skill content matches package templates or is scheduled for write.',
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* FX07 — active host vs managed --tools / manifest hosts.
|
|
615
|
+
*/
|
|
616
|
+
export function buildHostSelectionHonesty(plan) {
|
|
617
|
+
const hosts = Array.isArray(plan?.hosts) ? plan.hosts.map((h) => String(h).toLowerCase()) : [];
|
|
618
|
+
let active = null;
|
|
619
|
+
try {
|
|
620
|
+
active = detectActiveAgentHost();
|
|
621
|
+
} catch {
|
|
622
|
+
active = null;
|
|
623
|
+
}
|
|
624
|
+
const activeNorm =
|
|
625
|
+
typeof active === 'string' && active.trim() ? active.trim().toLowerCase() : null;
|
|
626
|
+
const known = activeNorm && KNOWN_TOOLS.includes(activeNorm);
|
|
627
|
+
const inSelection = Boolean(activeNorm && hosts.includes(activeNorm));
|
|
628
|
+
const note =
|
|
629
|
+
known && !inSelection
|
|
630
|
+
? `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.`
|
|
631
|
+
: known && inSelection
|
|
632
|
+
? `Detected host "${activeNorm}" is in the managed selection.`
|
|
633
|
+
: activeNorm
|
|
634
|
+
? `Detected host "${activeNorm}" is outside the known managed tool set.`
|
|
635
|
+
: 'No active agent host detected for this process.';
|
|
636
|
+
return {
|
|
637
|
+
schemaVersion: '1.0',
|
|
638
|
+
notAScore: true,
|
|
639
|
+
activeHost: activeNorm,
|
|
640
|
+
managedHosts: hosts,
|
|
641
|
+
activeInSelection: inSelection,
|
|
642
|
+
suggestTools:
|
|
643
|
+
known && !inSelection
|
|
644
|
+
? [...new Set([...hosts, activeNorm])].sort().join(',')
|
|
645
|
+
: null,
|
|
646
|
+
note,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* FX05 — post-upgrade verification block (advisory sensors only).
|
|
652
|
+
*/
|
|
653
|
+
export function buildPostUpgradeChecks(root, options = {}) {
|
|
654
|
+
const resolvedRoot = path.resolve(root);
|
|
655
|
+
const checks = [];
|
|
656
|
+
let projectVersion = null;
|
|
657
|
+
try {
|
|
658
|
+
const pkgPath = path.join(resolvedRoot, 'node_modules', 'arkgate', 'package.json');
|
|
659
|
+
if (fs.existsSync(pkgPath)) {
|
|
660
|
+
projectVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
|
|
661
|
+
}
|
|
662
|
+
} catch {
|
|
663
|
+
projectVersion = null;
|
|
664
|
+
}
|
|
665
|
+
const cli = typeof options.cliVersion === 'string' ? options.cliVersion : arkPackageVersion();
|
|
666
|
+
const pinOk =
|
|
667
|
+
projectVersion != null && cli != null ? projectVersion === cli : null;
|
|
668
|
+
checks.push({
|
|
669
|
+
id: 'package-pin-cli',
|
|
670
|
+
ok: pinOk,
|
|
671
|
+
detail:
|
|
672
|
+
pinOk === true
|
|
673
|
+
? `Installed arkgate@${projectVersion} matches CLI ${cli}.`
|
|
674
|
+
: pinOk === false
|
|
675
|
+
? `Installed arkgate@${projectVersion} ≠ CLI ${cli}; re-run install or restart using project-local CLI.`
|
|
676
|
+
: `Could not compare pin (installed=${projectVersion ?? 'missing'}, cli=${cli ?? 'unknown'}).`,
|
|
677
|
+
});
|
|
678
|
+
checks.push({
|
|
679
|
+
id: 'architecture-verification',
|
|
680
|
+
ok:
|
|
681
|
+
options.verification?.mode === 'skipped'
|
|
682
|
+
? null
|
|
683
|
+
: options.verification?.exitCode === 0,
|
|
684
|
+
detail:
|
|
685
|
+
options.verification?.mode === 'skipped'
|
|
686
|
+
? 'Strict architecture verification was skipped (--no-strict).'
|
|
687
|
+
: options.verification?.exitCode === 0
|
|
688
|
+
? 'Strict-merge architecture verification passed.'
|
|
689
|
+
: `Architecture verification exit ${options.verification?.exitCode ?? 'unknown'}.`,
|
|
690
|
+
});
|
|
691
|
+
checks.push({
|
|
692
|
+
id: 'package-version-truth',
|
|
693
|
+
ok: options.dualTruth?.dualTruth === true ? false : options.dualTruth ? true : null,
|
|
694
|
+
detail:
|
|
695
|
+
options.dualTruth?.dualTruth === true
|
|
696
|
+
? options.dualTruth.note || 'Package pin dual-truth detected.'
|
|
697
|
+
: options.dualTruth
|
|
698
|
+
? 'Package pin truth is consistent for this apply.'
|
|
699
|
+
: 'Package version truth not evaluated.',
|
|
700
|
+
});
|
|
701
|
+
checks.push({
|
|
702
|
+
id: 'doctor-compass-coach',
|
|
703
|
+
ok: null,
|
|
704
|
+
detail:
|
|
705
|
+
'Run `npx arkgate-check --doctor --json` and confirm doctor.improvementCompass + doctor.deepModuleCoach (notAScore).',
|
|
706
|
+
});
|
|
707
|
+
checks.push({
|
|
708
|
+
id: 'agents-md-projection',
|
|
709
|
+
ok: null,
|
|
710
|
+
detail: 'Run `npx arkgate agents-md --check` (or --write) so AGENTS.md matches the package projection.',
|
|
711
|
+
});
|
|
712
|
+
checks.push({
|
|
713
|
+
id: 'status-mode',
|
|
714
|
+
ok: null,
|
|
715
|
+
detail: 'Run `npx arkgate status --json` and read honesty mode; incomplete facts never invent green residual.',
|
|
716
|
+
});
|
|
717
|
+
return {
|
|
718
|
+
schemaVersion: '1.0',
|
|
719
|
+
notAScore: true,
|
|
720
|
+
neverGateInput: true,
|
|
721
|
+
checks,
|
|
722
|
+
mcpNote:
|
|
723
|
+
'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.',
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function formatSkillDriftHuman(skillDrift) {
|
|
728
|
+
if (!skillDrift) return [];
|
|
729
|
+
const lines = [
|
|
730
|
+
`Skill drift: ${skillDrift.skillCount} skill(s) — current ${skillDrift.current}, stale ${skillDrift.stale}, customized ${skillDrift.customized}, missing ${skillDrift.missing}, would refresh ${skillDrift.wouldRefresh}.`,
|
|
731
|
+
];
|
|
732
|
+
if (skillDrift.note) lines.push(` ${skillDrift.note}`);
|
|
733
|
+
return lines;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export function formatHostSelectionHuman(hostSelection) {
|
|
737
|
+
if (!hostSelection?.note) return [];
|
|
738
|
+
return [`Host selection: ${hostSelection.note}`];
|
|
739
|
+
}
|
|
740
|
+
|
|
555
741
|
function publicPlan(plan, overrides = {}) {
|
|
556
742
|
const assets = plan.assets.map(
|
|
557
743
|
({ containerBeforeHash: _container, [AFTER_CONTENT]: _content, ...asset }) => asset
|
|
@@ -784,6 +970,17 @@ export function renderManagedUpgrade(plan, options = {}) {
|
|
|
784
970
|
for (const line of formatManagedUpgradeSelfServiceHonesty(honesty)) {
|
|
785
971
|
console.log(line);
|
|
786
972
|
}
|
|
973
|
+
const skillDrift =
|
|
974
|
+
options.skillDrift ?? plan.skillDrift ?? buildSkillDriftSummary(plan);
|
|
975
|
+
for (const line of formatSkillDriftHuman(skillDrift)) {
|
|
976
|
+
console.log(line);
|
|
977
|
+
}
|
|
978
|
+
const hostSelection =
|
|
979
|
+
options.hostSelection ?? plan.hostSelection ?? buildHostSelectionHonesty(plan);
|
|
980
|
+
for (const line of formatHostSelectionHuman(hostSelection)) {
|
|
981
|
+
console.log(line);
|
|
982
|
+
}
|
|
983
|
+
// FX08: whatsNew always on preview/apply human path (including nothing-to-apply).
|
|
787
984
|
const whatsNew = plan.whatsNew ?? buildUpgradeWhatsNewSuggestions();
|
|
788
985
|
for (const line of formatUpgradeWhatsNewSuggestions(whatsNew)) {
|
|
789
986
|
console.log(line);
|
|
@@ -136,8 +136,29 @@ export function buildRulesInventory(input) {
|
|
|
136
136
|
/_(?:OID|OIDS)$/i.test(name) ||
|
|
137
137
|
/^(?:INT2|INT4|INT8|FLOAT4|FLOAT8|NUMERIC|DATE|TIME|TIMESTAMP|TIMESTAMPTZ|JSON|JSONB|UUID)OID$/i.test(name) ||
|
|
138
138
|
/(?:^|_)(?:SCHEMA|PROTOCOL|RESOLVER|FORMAT)_(?:URL|URI|VERSION|ID|IDENTITY)$/i.test(name);
|
|
139
|
+
/**
|
|
140
|
+
* FX09 — pure UX copy / error-message string constants crowd inventory pilots.
|
|
141
|
+
* Downrank (skip) sentence-like strings and message-named identifiers; keep
|
|
142
|
+
* numeric thresholds and domain status tokens for adopt/contract pilots.
|
|
143
|
+
*/
|
|
144
|
+
const isUxMessageConstant = (name, rawValue) => {
|
|
145
|
+
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) ||
|
|
146
|
+
/_(?:MSG|MESSAGE|TEXT|COPY|TOAST|SNACK|ALERT|BANNER|CAPTION|HINT|HELP_TEXT|ERROR_TEXT|EMPTY_TEXT|PLACEHOLDER_TEXT|USER_MESSAGE|FEEDBACK)$/i.test(name)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
const unquoted = rawValue.replace(/^['"]|['"]$/g, '');
|
|
150
|
+
// Sentence-like string values (spaces or terminal punctuation) are UX copy,
|
|
151
|
+
// not behavioral business limits — unless the name is a clear domain status seed.
|
|
152
|
+
if (/^['"]/.test(rawValue) &&
|
|
153
|
+
(/\s/.test(unquoted) || /[.!?…]$/.test(unquoted)) &&
|
|
154
|
+
!/^(?:STATUS|STATE|PHASE|ROLE|TYPE|KIND|ORDER|PAYMENT|CART|INVOICE|POLICY)_[A-Z0-9_]+$/i.test(name)) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
};
|
|
139
159
|
while ((magic = magicRe.exec(content)) !== null) {
|
|
140
160
|
const name = magic[2];
|
|
161
|
+
const rawValue = magic[3] ?? '';
|
|
141
162
|
// With governed layer evidence, generic Tooling/Kernel constants are not
|
|
142
163
|
// business-rule candidates. Controller-shaped boundaries stay eligible
|
|
143
164
|
// because business policy can leak into them.
|
|
@@ -145,6 +166,8 @@ export function buildRulesInventory(input) {
|
|
|
145
166
|
continue;
|
|
146
167
|
if (isInfraMagicName(name))
|
|
147
168
|
continue;
|
|
169
|
+
if (isUxMessageConstant(name, rawValue))
|
|
170
|
+
continue;
|
|
148
171
|
// P2-N: skip remaining ALL_CAPS noise only on clear UI chrome (not all of app/).
|
|
149
172
|
if (isUiChrome && !isDomain)
|
|
150
173
|
continue;
|