arkgate 4.5.7 → 4.6.0

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 CHANGED
@@ -5,6 +5,29 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.6.0 — 2026-08-12
9
+
10
+ **Minor** over **4.5.7**. Understandable Ark: doctor, HTML, skills, and public docs use common
11
+ software words (import rules, leftover design work, pre-write block) while **ArkGate** and
12
+ **ArkRules** stay as product names. Shared Claude/Grok agent homes get the same monotonic
13
+ “always latest” floor Codex already had. **No required config migration.** JSON field names
14
+ and `ruleId`s stay stable. No new skill names, sensors, or scores.
15
+
16
+ **Status: prepared** (not yet on npm `latest`; see `docs/releases/4.6.0.md`).
17
+
18
+ ### Added
19
+
20
+ - **Shared Claude/Grok home skills:** `--claude-home`, `--grok-home`, and `--agent-homes`
21
+ write monotonic home catalogs (Codex-parity lock + floor). Doctor reports stale
22
+ `~/.claude/skills` / `~/.grok/skills` when those catalogs exist. Temp/upgrade `--root`
23
+ never mutates default user homes.
24
+
25
+ ### Changed
26
+
27
+ - **Human language:** doctor, HTML report, compact router, skills, and public lanes prefer
28
+ common terms. Leftover design work replaces “design-weak” in human copy; JSON `designWeak`
29
+ is unchanged.
30
+
8
31
  ## 4.5.7 — 2026-08-12
9
32
 
10
33
  **Patch** over **4.5.6**. Cursor hard write path: project `.cursor/hooks.json` `preToolUse`
@@ -13,7 +36,7 @@ gate for `Write` / `StrReplace` (failClosed), host matrix + doctor honesty, payl
13
36
  emit; Write `updated_input` reinjection is **not** claimed. Codex/OpenCode stay advisory at
14
37
  write. Shell/Tab/human edits still rely on required CI. **No required config migration.**
15
38
 
16
- **Status: prepared** (not published).
39
+ **Status: published** (on npm `latest`; see `docs/releases/4.5.7.md`).
17
40
 
18
41
  ### Added
19
42
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # ArkGate — Architecture Co-pilot for AI TypeScript
4
4
 
5
- **One contract. One gate. One co-pilot.**
5
+ **One architecture config. One check. One coach.**
6
6
 
7
7
  Your AI writes most of the code. ArkGate keeps that work inside an architecture you can trust —
8
8
  and makes sure a “green” check means something real.
@@ -16,8 +16,8 @@ and makes sure a “green” check means something real.
16
16
 
17
17
  </div>
18
18
 
19
- > **ArkGate 4.5.6** is on npm `latest` field upgrade truth, multi-project MCP honesty, skill drift.
20
- > [4.5.6 notes](docs/releases/4.5.6.md) · [4.5.5](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
19
+ > **ArkGate 4.6.0** is preparedclearer language + shared agent home skills.
20
+ > [4.6.0 notes](docs/releases/4.6.0.md) · [4.5.7](docs/releases/4.5.7.md) (npm `latest`) · [4.5.6](docs/releases/4.5.6.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
21
21
 
22
22
  ---
23
23
 
@@ -43,9 +43,9 @@ npx arkgate-check --doctor # control plane: status light + primary next a
43
43
  ```
44
44
 
45
45
  That is the product. Doctor is the control plane — when stuck, do **primary next action #1**.
46
- Doctor also shows an **improvement compass** (architecture lenses such as separation of concerns and
47
- dependency inversion — **not a score**). Residual lenses mean Shape work may remain even when edges
48
- are green. Details: [use.md — Improvement compass](docs/use.md#improvement-compass-not-a-score).
46
+ Doctor also shows **what to improve next** (architecture lenses such as separation of concerns and
47
+ dependency inversion — **not a score**). Leftover lenses mean design work may remain even when
48
+ imports are green. Details: [use.md — Improvement compass](docs/use.md#improvement-compass-not-a-score).
49
49
 
50
50
  ```text
51
51
  start → doctor (+ compass) → day-to-day (place + gate)
@@ -64,15 +64,15 @@ A machine-readable architecture file (`ark.config.json`) plus enforcement:
64
64
 
65
65
  | When | Tool |
66
66
  |------|------|
67
- | **While the AI writes** | Hard PreToolUse on supported hosts; advisory MCP elsewhere |
67
+ | **While the AI writes** | Pre-write block on supported hosts; warning only elsewhere |
68
68
  | **Before merge** | `arkgate-check` as a **required** CI status |
69
69
 
70
70
  ### Two planes (4.0)
71
71
 
72
72
  | Plane | What it guards | Config |
73
73
  |-------|----------------|--------|
74
- | **Layers** (always) | Who may talk to whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
75
- | **ArkRules** (opt-in) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
74
+ | **Layers** (always) | Who may import whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
75
+ | **ArkRules** (opt-in; structure rules inside a layer) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
76
76
 
77
77
  Absence of ArkRules changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
78
78
  Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [use path](docs/use.md).
@@ -175,6 +175,8 @@ npx arkgate-check --plan
175
175
  npx arkgate-check --coverage
176
176
  npx arkgate-check --strict-merge # CI / required status
177
177
  npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
178
+ # optional: refresh shared home skills (Claude/Grok/Codex; never downgrades)
179
+ # npx arkgate-check --install-agent-gates --skills-only --agent-homes --force
178
180
  # optional: same 13 skills via Agent Skills ecosystem (no new names)
179
181
  # npx skills add ./node_modules/arkgate/templates/agent-skills
180
182
  ```
@@ -208,7 +210,7 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
208
210
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
209
211
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
210
212
  | Security | [SECURITY.md](SECURITY.md) |
211
- | Current release (4.5.6 on npm `latest`) | [docs/releases/4.5.6.md](docs/releases/4.5.6.md) · [CHANGELOG](CHANGELOG.md) |
213
+ | Current release (4.5.7 on npm `latest`) | [docs/releases/4.5.7.md](docs/releases/4.5.7.md) · [CHANGELOG](CHANGELOG.md) |
212
214
  | Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
213
215
  | Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |
214
216
  | Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |
@@ -213,6 +213,14 @@ function parseArgs(argv) {
213
213
  else if (arg === '--watch') args.watch = true;
214
214
  else if (arg === '--beginner') args.beginner = true;
215
215
  else if (arg === '--codex-home') args.codexHome = true;
216
+ else if (arg === '--claude-home') args.claudeHome = true;
217
+ else if (arg === '--grok-home') args.grokHome = true;
218
+ else if (arg === '--agent-homes') {
219
+ args.agentHomes = true;
220
+ args.codexHome = true;
221
+ args.claudeHome = true;
222
+ args.grokHome = true;
223
+ }
216
224
  else if (arg === '--migrate-commands') args.migrateCommands = true;
217
225
  else if (arg === '--no-cache') args.noCache = true;
218
226
  else if (arg === '--resident') args.resident = true;
@@ -275,7 +283,7 @@ function usage() {
275
283
  ' Best-effort open in browser (local TTY). No-op if open fails. --no-open / ARK_NO_OPEN_REPORT=1 to skip; --open forces open.',
276
284
  ' ark-check --init [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|vite-vercel-spa|clean-architecture|onion-architecture] [--force] [--follow-config-root]',
277
285
  ' --follow-config-root On writes (init/install-agent-gates/migrate --write/…), adopt walked-up monorepo config root (default: keep explicit --root)',
278
- ' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--force]',
286
+ ' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--claude-home] [--grok-home] [--agent-homes] [--force]',
279
287
  ' ark-check --update-baseline [file] freeze current violations (default .ark-baseline.json)',
280
288
  ' ark-check --print-config eleven-layer',
281
289
  '',
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Shared agent home skill catalogs (Claude / Grok), Codex-parity monotonic install.
3
+ * Repo catalogs stay per-project; these homes are the machine floor (never downgrade).
4
+ */
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { arkCommand } from '../ark-shared.mjs';
9
+ import { isTempOrUpgradeRoot } from './codex-home.mjs';
10
+ import {
11
+ arkPackageVersion,
12
+ assessSkillCatalogParity,
13
+ detectActiveAgentHost,
14
+ isValidSemver,
15
+ isVersionOlder,
16
+ skillTemplateNames,
17
+ skillTemplates,
18
+ } from './skill-install.mjs';
19
+ import {
20
+ HOME_SKILL_CATALOG,
21
+ HOME_SKILL_PENDING_CATALOG,
22
+ installSkillCatalog,
23
+ skillInstallLine,
24
+ } from './skill-write.mjs';
25
+
26
+ /** @typedef {'claude'|'grok'} AgentHomeHost */
27
+
28
+ const HOSTS = {
29
+ claude: {
30
+ id: 'claude',
31
+ label: 'Claude',
32
+ envKey: 'CLAUDE_HOME',
33
+ defaultDirName: '.claude',
34
+ flag: '--claude-home',
35
+ },
36
+ grok: {
37
+ id: 'grok',
38
+ label: 'Grok',
39
+ envKey: 'GROK_HOME',
40
+ defaultDirName: '.grok',
41
+ flag: '--grok-home',
42
+ },
43
+ };
44
+
45
+ export function agentHomeHostIds() {
46
+ return Object.keys(HOSTS);
47
+ }
48
+
49
+ export function claudeHomeDir(env = process.env, homeDir = os.homedir()) {
50
+ return resolveHomeDir(HOSTS.claude, env, homeDir);
51
+ }
52
+
53
+ export function grokHomeDir(env = process.env, homeDir = os.homedir()) {
54
+ return resolveHomeDir(HOSTS.grok, env, homeDir);
55
+ }
56
+
57
+ export function claudeSkillsDir(env = process.env, homeDir = os.homedir()) {
58
+ return path.join(claudeHomeDir(env, homeDir), 'skills');
59
+ }
60
+
61
+ export function grokSkillsDir(env = process.env, homeDir = os.homedir()) {
62
+ return path.join(grokHomeDir(env, homeDir), 'skills');
63
+ }
64
+
65
+ export function usesDefaultClaudeHome(env = process.env, homeDir = os.homedir()) {
66
+ return usesDefaultHome(HOSTS.claude, env, homeDir);
67
+ }
68
+
69
+ export function usesDefaultGrokHome(env = process.env, homeDir = os.homedir()) {
70
+ return usesDefaultHome(HOSTS.grok, env, homeDir);
71
+ }
72
+
73
+ function resolveHomeDir(spec, env, homeDir) {
74
+ const configured = env?.[spec.envKey];
75
+ if (typeof configured === 'string' && configured.trim() !== '') {
76
+ return path.resolve(configured);
77
+ }
78
+ return path.resolve(homeDir, spec.defaultDirName);
79
+ }
80
+
81
+ function usesDefaultHome(spec, env, homeDir) {
82
+ const configured = env?.[spec.envKey];
83
+ if (typeof configured !== 'string' || configured.trim() === '') return true;
84
+ return path.resolve(configured) === path.resolve(homeDir, spec.defaultDirName);
85
+ }
86
+
87
+ function skillsDirFor(host, env = process.env) {
88
+ return host === 'grok' ? grokSkillsDir(env) : claudeSkillsDir(env);
89
+ }
90
+
91
+ function readHomeCatalogFloor(skillsDir) {
92
+ const readOne = (file) => {
93
+ try {
94
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'));
95
+ const version = typeof value?.packageVersion === 'string' ? value.packageVersion : null;
96
+ const valid =
97
+ value &&
98
+ value.schemaVersion === '1.0' &&
99
+ isValidSemver(version) &&
100
+ Array.isArray(value.skills);
101
+ return { exists: true, valid, version: valid ? version : null };
102
+ } catch (error) {
103
+ if (error && error.code === 'ENOENT') return { exists: false, valid: true, version: null };
104
+ return { exists: true, valid: false, version: null };
105
+ }
106
+ };
107
+ const catalog = readOne(path.join(skillsDir, HOME_SKILL_CATALOG));
108
+ const pending = readOne(path.join(skillsDir, HOME_SKILL_PENDING_CATALOG));
109
+ let floorVersion = catalog.version;
110
+ if (pending.version && (!floorVersion || isVersionOlder(floorVersion, pending.version))) {
111
+ floorVersion = pending.version;
112
+ }
113
+ return {
114
+ floorVersion,
115
+ pendingVersion: pending.version,
116
+ hasMetadata: catalog.exists || pending.exists,
117
+ metadataInvalid:
118
+ (catalog.exists && !catalog.valid) || (pending.exists && !pending.valid),
119
+ };
120
+ }
121
+
122
+ function homeInPlay(parity, catalogState) {
123
+ return parity.presentCount > 0 || catalogState.hasMetadata;
124
+ }
125
+
126
+ /**
127
+ * Detect Claude/Grok user-home ark-* catalogs that lag this package.
128
+ * Absent homes are not debt. Stamp-only body-match is not content-behind
129
+ * (assessSkillCatalogParity already treats identity match as current).
130
+ *
131
+ * @param {string} root
132
+ * @param {NodeJS.ProcessEnv} [env]
133
+ * @returns {Array<{
134
+ * host: AgentHomeHost,
135
+ * label: string,
136
+ * skillsDir: string,
137
+ * missing: number,
138
+ * stale: number,
139
+ * presentCount: number,
140
+ * expectedCount: number,
141
+ * packageVersion: string|null,
142
+ * catalogVersion: string|null,
143
+ * pendingRecoveryRequired: boolean,
144
+ * catalogMetadataInvalid: boolean,
145
+ * catalogStateReason: string|null,
146
+ * flag: string,
147
+ * }>}
148
+ */
149
+ export function detectAgentHomeGaps(root, env = process.env) {
150
+ if (!fs.existsSync(path.join(root, 'AGENTS.md'))) return [];
151
+ if (fs.existsSync(path.join(root, 'templates', 'skills'))) return [];
152
+ const skillNames = skillTemplateNames();
153
+ if (skillNames.length === 0) return [];
154
+ const packageVersion = arkPackageVersion();
155
+ const gaps = [];
156
+ for (const host of agentHomeHostIds()) {
157
+ const spec = HOSTS[host];
158
+ const dir = skillsDirFor(host, env);
159
+ const skillFile = (name) => path.join(dir, name, 'SKILL.md');
160
+ const parity = assessSkillCatalogParity(skillNames, skillFile, packageVersion);
161
+ const catalogState = readHomeCatalogFloor(dir);
162
+ if (!homeInPlay(parity, catalogState)) continue;
163
+ const newerFloor =
164
+ catalogState.floorVersion &&
165
+ isValidSemver(packageVersion) &&
166
+ isVersionOlder(packageVersion, catalogState.floorVersion);
167
+ const pendingRecoveryRequired =
168
+ catalogState.pendingVersion !== null && !newerFloor;
169
+ const needsAttention =
170
+ !newerFloor &&
171
+ (parity.missing > 0 ||
172
+ parity.stale > 0 ||
173
+ pendingRecoveryRequired ||
174
+ catalogState.metadataInvalid);
175
+ if (!needsAttention) continue;
176
+ gaps.push({
177
+ host,
178
+ label: spec.label,
179
+ skillsDir: dir,
180
+ missing: parity.missing,
181
+ stale: parity.stale,
182
+ presentCount: parity.presentCount,
183
+ expectedCount: skillNames.length,
184
+ packageVersion,
185
+ catalogVersion: catalogState.floorVersion,
186
+ pendingRecoveryRequired,
187
+ catalogMetadataInvalid: catalogState.metadataInvalid,
188
+ catalogStateReason: catalogState.metadataInvalid
189
+ ? 'invalid catalog metadata'
190
+ : pendingRecoveryRequired
191
+ ? 'interrupted catalog commit'
192
+ : null,
193
+ flag: spec.flag,
194
+ });
195
+ }
196
+ return gaps;
197
+ }
198
+
199
+ /**
200
+ * Claude home is loaded by Claude Code and often by Cursor. Treat both as in-session.
201
+ * Grok home is urgent only on a Grok session (or when ARK_ACTIVE_HOST=grok).
202
+ */
203
+ export function agentHomeConcernIsActive(host, env = process.env) {
204
+ const active = detectActiveAgentHost(env);
205
+ if (host === 'claude') return active === 'claude' || active === 'cursor' || !active;
206
+ if (host === 'grok') return active === 'grok' || !active;
207
+ return true;
208
+ }
209
+
210
+ export function agentHomeRefreshCommand(root, gap) {
211
+ return arkCommand(
212
+ root,
213
+ 'ark-check',
214
+ `--install-agent-gates --skills-only ${gap.flag} --force`
215
+ );
216
+ }
217
+
218
+ /**
219
+ * @param {{
220
+ * root: string,
221
+ * skills?: Array<[string, string]>,
222
+ * version: string|null,
223
+ * force?: boolean,
224
+ * claudeHome?: boolean,
225
+ * grokHome?: boolean,
226
+ * agentHomes?: boolean,
227
+ * json?: boolean,
228
+ * env?: NodeJS.ProcessEnv,
229
+ * }} args
230
+ * @returns {Array<{ host: string, results: object[] }>}
231
+ */
232
+ export function installRequestedAgentHomes(args) {
233
+ const env = args.env ?? process.env;
234
+ const wantClaude = Boolean(args.claudeHome || args.agentHomes);
235
+ const wantGrok = Boolean(args.grokHome || args.agentHomes);
236
+ if (!wantClaude && !wantGrok) return [];
237
+ const skills = args.skills ?? skillTemplates();
238
+ const version = args.version ?? arkPackageVersion();
239
+ const installed = [];
240
+ const targets = [
241
+ wantClaude ? { host: 'claude', spec: HOSTS.claude, dir: claudeSkillsDir(env), usesDefault: usesDefaultClaudeHome(env) } : null,
242
+ wantGrok ? { host: 'grok', spec: HOSTS.grok, dir: grokSkillsDir(env), usesDefault: usesDefaultGrokHome(env) } : null,
243
+ ].filter(Boolean);
244
+
245
+ for (const target of targets) {
246
+ if (isTempOrUpgradeRoot(args.root) && target.usesDefault) {
247
+ if (!args.json) {
248
+ console.log('');
249
+ console.log(
250
+ `${target.spec.label} home skills: skipped (temp/upgrade --root must not mutate default ~/${target.spec.defaultDirName}).`
251
+ );
252
+ }
253
+ installed.push({
254
+ host: target.host,
255
+ skipped: true,
256
+ reason: 'temp-root-default-home',
257
+ results: [],
258
+ });
259
+ continue;
260
+ }
261
+ if (!args.json) {
262
+ console.log('');
263
+ console.log(
264
+ `${target.spec.label} home skills (scope=home-shared; source=${version ? `arkgate@${version}` : 'arkgate@unknown'}; target=${target.dir}/<name>/SKILL.md):`
265
+ );
266
+ console.log(
267
+ ' Compatibility: monotonic downgrade protection requires ArkGate 4.2.0+ writers; older packages cannot lower this catalog.'
268
+ );
269
+ }
270
+ try {
271
+ fs.mkdirSync(target.dir, { recursive: true });
272
+ } catch (error) {
273
+ const message = error instanceof Error ? error.message : String(error);
274
+ if (!args.json) console.error(` FAILED to create ${target.dir} (${message})`);
275
+ installed.push({
276
+ host: target.host,
277
+ skipped: false,
278
+ results: [{ relativePath: target.dir, status: 'failed', message }],
279
+ });
280
+ continue;
281
+ }
282
+ const results = [];
283
+ for (const result of installSkillCatalog({
284
+ directory: target.dir,
285
+ skills,
286
+ packageVersion: version,
287
+ force: args.force,
288
+ scope: 'home',
289
+ })) {
290
+ if (!args.json) console.log(skillInstallLine(result));
291
+ results.push(result);
292
+ }
293
+ installed.push({ host: target.host, skipped: false, results });
294
+ }
295
+ return installed;
296
+ }
@@ -148,7 +148,7 @@ export function buildAgentProjectionBody(facts) {
148
148
  '',
149
149
  ];
150
150
  if (profile === 'compact') {
151
- lines.push('### Primary path', '', '1. Run doctor (`ark-check --doctor`) — status light + primary next action.', '2. Read the improvement compass (not a score). Name residual lenses in plain language; never “done” on green edges alone while residual remains.', '3. Call `ark_identity` with `project.expectedRoot` at the exact project root; reuse root + `projectId` on Ark MCP calls.', '4. Read the contract with `ark_manifest` (same expectation). `ark://manifest` is compatibility-only / unverified.', '5. Place files inside configured layers; validate; run the check command above on violations — fix architecture, do not weaken the gate.', '6. Single door: edges debt → fix; design-weak / residual shape lenses → map then guided apply with user OK.', '', '### Contract layers (summary)', '', formatAgentProjectionLayers(layers), '');
151
+ lines.push('### Primary path', '', '1. Run doctor (`ark-check --doctor`) — status light + primary next action.', '2. Read the improvement compass (not a score). Name leftover work in plain language; never “done” on green imports alone while leftover design work remains.', '3. Call `ark_identity` with `project.expectedRoot` at the exact project root; reuse root + `projectId` on Ark MCP calls.', '4. Read architecture config with `ark_manifest` (same expectation). `ark://manifest` is compatibility-only / unverified.', '5. Place files inside configured layers; validate; run the check command above on violations — fix architecture, do not weaken the gate.', '6. Single door: import-rule debt → fix; leftover design work / residual shape lenses → map then guided apply with user OK.', '', '### Contract layers (summary)', '', formatAgentProjectionLayers(layers), '');
152
152
  }
153
153
  else {
154
154
  lines.push('### Contract layers', '', formatAgentProjectionLayers(layers), '', 'When creating a **new** kind of code that no layer covers, update `ark.config.json` first (`/ark-contract`), then place the file.', '', '### Diagnostic codes (short list)', '', formatAgentProjectionCatalogShortList(catalog, docsPath), '', '### Session truth', '', '- Machine snapshot: `ark status --json` (or MCP `ark_status`) — identity, activation honesty, last check, residual counts. **Not a score.**', '- Authoritative contract: `ark_manifest` after a matched `ark_identity` handshake.', '');
@@ -343,7 +343,7 @@ Pick **one** primary skill. Prefer doctor top action #1 when unsure.
343
343
  |------|--------|----------|
344
344
  | Unsure what to do next | **Doctor top action #1** (\`${doctorCmd}\`), then re-run doctor | skill-shopping, defaulting to autopilot |
345
345
  | Make architecture sound (guided apply path) | **/ark-autopilot** | explore-only, coverage-only |
346
- | **Messy / spaghetti / design-weak after green / Shape residual** | **Single path:** \`/ark-explore\` shape-focus → dual-plan B, then \`/ark-autopilot\` only to apply B with OK | coverage, think, loop-as-done, skill-shopping |
346
+ | **Messy / leftover design work after green** | **Single path:** \`/ark-explore\` shape-focus → plan B, then \`/ark-autopilot\` only to apply B with OK | coverage, think, loop-as-done, skill-shopping |
347
347
  | Map / residual / dual-plan seed only (no apply, already know you want recon) | \`/ark-explore\` | coverage (fitness only) |
348
348
  | Greenfield shape / empty tree | \`/ark-architect\` | adopt |
349
349
  | Brownfield / wrong contract / false-green | \`/ark-adopt\` then \`/ark-contract\` if globs wrong | architect |
@@ -351,13 +351,13 @@ Pick **one** primary skill. Prefer doctor top action #1 when unsure.
351
351
  | New file “where does this go?” | \`/ark-place\` | architect (unless greenfield shape missing) |
352
352
  | Gate violation on a change (small cluster) | \`/ark-fix\` | loop/autopilot unless bulk |
353
353
  | Drive plan **A** to goal.met | \`/ark-loop\` | explore (unless A empty + design residual → single Shape path above) |
354
- | Ark **fitness** only (governed%, gates, baseline, install gaps) | \`/ark-coverage\` | Shape / design-weak (use single path above) |
354
+ | Ark **fitness** only (governed%, gates, baseline, install gaps) | \`/ark-coverage\` | leftover design work (use single path above) |
355
355
  | One design decision, 2–3 options | \`/ark-think\` | full Shape residual (use single path) |
356
356
  | Explain / HTML report tour | \`/ark-explain\` | explore |
357
357
  | Bump arkgate + refresh hosts | \`/ark-upgrade\` | — |
358
358
  | Optional runtime kernel evaluate | \`/ark-runtime\` | — |
359
359
 
360
- **Post-green door (Q01):** when doctor reports ENFORCE · design-weak, the **primary** next action is the single Shape path above — not a choice among explore / coverage / think. Doctor JSON: \`postGreenPath\` / \`primaryNextAction\`.
360
+ **Post-green door:** when doctor reports ENFORCE · leftover design work, the **primary** next action is the single Shape path above — not a choice among explore / coverage / think. Doctor JSON: \`postGreenPath\` / \`primaryNextAction\`.
361
361
 
362
362
  **Phases (brownfield honesty):** Align (contract truth) → Stabilize (real baseline) → Shape (golden pattern + pilot). Empty plan A after Stabilize still leaves Shape work — that is the single post-green path, not “healthy finished.”
363
363
 
@@ -406,14 +406,14 @@ ${projectionBlock}
406
406
 
407
407
  1. Status anytime: \`${doctorCmd}\` — one status light, one primary next action (control plane).
408
408
  2. Read the **Improvement compass** section (not a score). Name residual lenses in plain language when present (SoC, DIP, domain, …). Out-of-scope lenses (performance, app security tooling, full resilience) stay honest — do not invent Ark enforcement for them.
409
- 3. Before trusting MCP evidence: call \`ark_identity\` with \`project.expectedRoot\` set to this project's exact absolute root, then reuse that root plus the returned \`projectIdentity.projectId\` on every Ark MCP call. A descendant path is authoritative only with that matching id. Missing tool, non-\`matched\` binding, or wrong root means the process is stale: restart the host and use the local CLI meanwhile.
410
- 4. Day to day: call \`ark_manifest\` with the same project expectation; place new files with \`ark_place\`; validate after edits; run \`${checkCmd}\`. The \`ark://manifest\` resource is compatibility-only and always unverified/non-authoritative. On a gate deny, fix the architecture — do not weaken the contract.
409
+ 3. Before trusting MCP evidence: call \`ark_identity\` with \`project.expectedRoot\` set to this project's exact absolute root, then reuse that root plus the returned \`projectIdentity.projectId\` on every Ark MCP call. A descendant path is authoritative only with that matching id. Missing tool, non-\`matched\` binding, or wrong root means this is not proven to be the right project: restart the host and use the local CLI meanwhile.
410
+ 4. Day to day: call \`ark_manifest\` with the same project expectation; place new files with \`ark_place\`; validate after edits; run \`${checkCmd}\`. The \`ark://manifest\` resource is compatibility-only and always unverified. On a gate deny, fix the architecture — do not weaken \`ark.config.json\`.
411
411
  5. If MCP is unavailable: inspect \`ark.config.json\` and run \`${checkCmd}\`.
412
412
 
413
413
  **Single door when residual remains:**
414
414
  - **Edges debt** (import/capability violations) → fix with the gate / plan; skill pack only if doctor names a skill.
415
- - **Design-weak / residual shape lenses** (compass residual while edges may look green) → map first, then guided apply with user OK — never “you’re done” on green edges alone.
416
- - Empty plan A + residual lenses / design-weak → **not finished**.
415
+ - **Design leftover / residual shape lenses** (compass leftover while imports may look green) → map first, then guided apply with user OK — never “you’re done” on green imports alone.
416
+ - Empty plan A + leftover design work → **not finished**.
417
417
 
418
418
  **Two-axis done (never collapse):**
419
419
  1. **Architecture residual** — status / doctor / compass (scan). Prefer deep modules; name seams; test at the public interface.
@@ -9,6 +9,7 @@ import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { layerForFile } from '../ark-shared.mjs';
11
11
  import { detectContractFalseGreenRisk } from './field-install.mjs';
12
+ import { operatingModeTitle, LEFTOVER_DESIGN_LABEL } from './product-copy.mjs';
12
13
 
13
14
  /** Stable smell ids (doctor JSON + plan B + skills). */
14
15
  export const DESIGN_SMELL_IDS = Object.freeze([
@@ -476,20 +477,15 @@ export function isDesignWeak(smells, ctx = {}) {
476
477
  */
477
478
  export function summarizeDesignFitness(smells, ctx = {}) {
478
479
  const designWeak = isDesignWeak(smells, ctx);
479
- const mode =
480
- typeof ctx.operatingMode === 'string' &&
481
- /^(?:suggest|adapt|enforce)$/.test(ctx.operatingMode)
482
- ? ctx.operatingMode.toUpperCase()
483
- : null;
484
480
  return {
485
481
  status: designWeak ? 'design-weak' : smells.length > 0 ? 'smells-with-open-edges' : 'ok',
486
482
  designWeak,
487
483
  smellCount: Array.isArray(smells) ? smells.length : 0,
488
484
  ids: (smells || []).map((s) => s.id),
489
485
  label: designWeak
490
- ? `${mode ? `${mode} · ` : ''}design-weak edges clean; Shape residual remains (see designSmells / plan B)`
486
+ ? `${operatingModeTitle(ctx.operatingMode, true)} import rules check out; leftover design work remains (see designSmells / plan B)`
491
487
  : smells.length > 0
492
- ? 'Design smells present alongside open edge debt'
488
+ ? 'Design smells present alongside open import-rule debt'
493
489
  : 'No deterministic design smells detected',
494
490
  };
495
491
  }
@@ -12,6 +12,12 @@ import {
12
12
  import * as arkShared from '../ark-shared.mjs';
13
13
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
14
14
  import { describePackageVersionDualTruth } from './field-install.mjs';
15
+ import {
16
+ detectAgentHomeGaps,
17
+ agentHomeConcernIsActive,
18
+ agentHomeRefreshCommand,
19
+ } from './agent-homes.mjs';
20
+ import { operatingModeTitle } from './product-copy.mjs';
15
21
  export { summarizeRulesUnderContract };
16
22
 
17
23
  /** Optional S3 dual-match classifier when ark-shared exports it (soft dep for S5 landing). */
@@ -340,7 +346,7 @@ export function buildRemediationPlan(
340
346
  : 'No active violations — the architecture already meets its contract.';
341
347
  if (designWeak) {
342
348
  statement =
343
- 'No active edge violations — contract edges are clean, but design smells remain (design-weak). Shape residual is plan B only; not healthy finished.';
349
+ 'No active import-rule violations — imports check out, but design smells remain (leftover design work). Shape work is plan B only; not healthy finished.';
344
350
  }
345
351
  if (completeness !== ANALYSIS_COMPLETENESS.complete) statement = analysisIncompleteStatement(completeness);
346
352
 
@@ -523,6 +529,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
523
529
  }
524
530
  const gatesMissing = missingGates(root);
525
531
  const skillGaps = detectSkillGaps(root);
532
+ const agentHomeGaps = detectAgentHomeGaps(root);
526
533
  // Dual-truth: CLI version vs package.json pin (field residual after upgrade --no-install).
527
534
  const packageVersionTruth = describePackageVersionDualTruth(root);
528
535
  const staleRunners = staleRunnerGateFiles(root);
@@ -759,6 +766,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
759
766
  },
760
767
  gatesMissing,
761
768
  skillGaps,
769
+ ...(agentHomeGaps.length > 0 ? { agentHomeGaps } : {}),
762
770
  staleRunnerFiles: staleRunners,
763
771
  writePath: {
764
772
  activeHost: writePath.activeHost,
@@ -836,21 +844,18 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
836
844
  // modeTitle alone names the light — bodies must not re-prefix Suggest/Adapt/Enforce.
837
845
  const modeHelp = {
838
846
  suggest:
839
- 'thin or new tree; the contract is not yet the control plane. You do not pick this light. Next: ark start (preview), then ark start --apply; re-check with --doctor.',
847
+ 'thin or new tree; architecture config is not yet in charge. You do not pick this light. Next: ark start (preview), then ark start --apply; re-check with --doctor.',
840
848
  adapt:
841
- 'contract and tree still disagree, or debt is open. Write path does not fully protect you yet. You do not pick this light. Next: do doctor top action #1 (often /ark-adopt, /ark-contract, or /ark-autopilot).',
849
+ 'config and tree still disagree, or debt is open. The write path does not fully protect you yet. You do not pick this light. Next: do doctor top action #1 (often /ark-adopt, /ark-contract, or /ark-autopilot).',
842
850
  enforce:
843
- 'honest coverage and clean checked edges. You arrived here; you never turn Enforce on. Next: keep the host write path and CI check; only NEW violations should fail.',
851
+ 'honest coverage and clean checked imports. You arrived here; you never turn Enforce on. Next: keep the host write path and CI check; only NEW violations should fail.',
844
852
  };
845
- const modeTitle =
846
- designFitness.designWeak
847
- ? `${mode.toUpperCase()} · design-weak`
848
- : mode.toUpperCase();
853
+ const modeTitle = operatingModeTitle(mode, designFitness.designWeak);
849
854
  line(
850
855
  modeMark,
851
856
  `${modeTitle} — ${
852
857
  designFitness.designWeak
853
- ? 'checked edges are honest; design smells remain. Green is not elegant design. You do not pick this light. Next: one Shape door — /ark-explore shape-focus → dual-plan B; apply B only with /ark-autopilot and your OK. Empty plan A is not done.'
858
+ ? 'import rules check out; design smells remain. Green is not elegant design. You do not pick this light. Next: one Shape door — /ark-explore shape-focus → plan B; apply B only with /ark-autopilot and your OK. A clean import check is not done.'
854
859
  : modeHelp[mode]
855
860
  }`
856
861
  );
@@ -914,7 +919,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
914
919
  `Next pilot (one at a time): ${np.pilotTarget || np.pilot} [${np.smellId}] → re-doctor after change`
915
920
  );
916
921
  line(' ', color.dim(`success: ${np.successSignal}`));
917
- line(' ', color.dim('never multi-pilot batch; patternBets never mechanical-safe'));
922
+ line(' ', color.dim('never multi-pilot batch; pattern bets are never auto-applied'));
918
923
  }
919
924
  }
920
925
 
@@ -933,7 +938,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
933
938
  ok,
934
939
  `"${goldenPattern.name}" — ${goldenPattern.norm}` +
935
940
  (goldenPattern.newCodeHome ? ` Prefer: ${goldenPattern.newCodeHome}.` : '') +
936
- ' Advisory only — does not clear design-weak or replace the gate.'
941
+ ' Advisory only — does not clear leftover design work or replace the gate.'
937
942
  );
938
943
  } else if (goldenPattern.invalid) {
939
944
  console.log('');
@@ -1038,7 +1043,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
1038
1043
  'No active violations — coverage is still thin, so green is not yet honest enforcement'
1039
1044
  );
1040
1045
  } else if (designFitness.designWeak) {
1041
- line(warn, `None on checked edgesedges match the contract; design residual remains (${modeTitle}). Not healthy finished.`);
1046
+ line(warn, `None on checked importsimport rules match the config; leftover design work remains (${modeTitle}). Not healthy finished.`);
1042
1047
  } else {
1043
1048
  line(ok, 'None — the code matches the contract on checked edges');
1044
1049
  }
@@ -1157,6 +1162,25 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
1157
1162
  actions.push(codexHomeGap.catalogMetadataInvalid ? 'repair invalid Codex home catalog metadata after verifying the newest installed version' : 'refresh Codex home skills (--install-agent-gates --skills-only --codex-home --force)');
1158
1163
  }
1159
1164
  }
1165
+ for (const gap of agentHomeGaps) {
1166
+ const parts = [
1167
+ gap.missing > 0 ? `${gap.missing} missing` : null,
1168
+ gap.stale > 0 ? `${gap.stale} content-behind-package` : null,
1169
+ gap.catalogStateReason,
1170
+ ].filter(Boolean);
1171
+ const deferred = !agentHomeConcernIsActive(gap.host);
1172
+ const summary = `${gap.label} shared agent skills ${parts.join(', ')}`;
1173
+ if (deferred) {
1174
+ line(color.dim('·'), color.dim(`${summary} (deferred — not this session)`));
1175
+ } else {
1176
+ line(warn, summary);
1177
+ actions.push(
1178
+ gap.catalogMetadataInvalid
1179
+ ? `repair invalid ${gap.label} home catalog metadata after verifying the newest installed version`
1180
+ : `refresh ${gap.label} shared agent skills (${agentHomeRefreshCommand(root, gap)})`
1181
+ );
1182
+ }
1183
+ }
1160
1184
 
1161
1185
  console.log('');
1162
1186
  console.log(color.bold('Baseline'));
@@ -154,7 +154,7 @@ export function formatGoldenPatternNote(result) {
154
154
  let s = `Golden pattern (advisory for NEW code only): "${g.name}" — ${g.norm}`;
155
155
  if (g.newCodeHome) s += ` Prefer new files under ${g.newCodeHome}.`;
156
156
  if (g.examplePath) s += ` Example: ${g.examplePath}.`;
157
- s += ' Does not clear design-weak or replace the gate.';
157
+ s += ' Does not clear leftover design work or replace the gate.';
158
158
  return s;
159
159
  }
160
160