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 +24 -1
- package/README.md +12 -10
- package/bin/ark-check-runtime.mjs +9 -1
- package/bin/lib/agent-homes.mjs +296 -0
- package/bin/lib/agent-projection.mjs +1 -1
- package/bin/lib/ci-and-commands.mjs +7 -7
- package/bin/lib/design-smells.mjs +3 -7
- package/bin/lib/doctor-plan.mjs +36 -12
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/host-support-matrix.mjs +4 -4
- package/bin/lib/html-report-depth.mjs +7 -8
- package/bin/lib/html-report.mjs +2 -1
- package/bin/lib/install-migrate.mjs +12 -0
- package/bin/lib/post-green-path.mjs +3 -2
- package/bin/lib/product-copy.mjs +32 -0
- package/bin/lib/skill-write.mjs +1 -1
- package/bin/lib/upgrade-whats-new.mjs +16 -0
- package/dist/index.cjs +19 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -22
- package/docs/README.md +3 -2
- package/docs/agent-guide.md +10 -7
- package/docs/ai-gates.md +19 -0
- package/docs/develop.md +3 -3
- package/docs/package-surface.md +2 -2
- package/docs/product-voice.md +80 -74
- package/docs/use.md +7 -5
- package/package.json +2 -2
- package/server.json +3 -3
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-autopilot/SKILL.md +1 -1
- package/templates/agent-skills/ark-explore/SKILL.md +5 -5
- package/templates/agent-skills/ark-upgrade/SKILL.md +2 -1
- package/templates/skills/ark-autopilot.md +1 -1
- package/templates/skills/ark-explore.md +5 -5
- package/templates/skills/ark-upgrade.md +2 -1
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:
|
|
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
|
|
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.
|
|
20
|
-
> [4.
|
|
19
|
+
> **ArkGate 4.6.0** is prepared — clearer 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
|
|
47
|
-
dependency inversion — **not a score**).
|
|
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** |
|
|
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
|
|
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.
|
|
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
|
|
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 /
|
|
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\` |
|
|
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
|
|
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
|
|
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
|
|
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
|
|
416
|
-
- Empty plan A +
|
|
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
|
-
? `${
|
|
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
|
|
488
|
+
? 'Design smells present alongside open import-rule debt'
|
|
493
489
|
: 'No deterministic design smells detected',
|
|
494
490
|
};
|
|
495
491
|
}
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -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
|
|
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;
|
|
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
|
-
'
|
|
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
|
|
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
|
-
? '
|
|
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;
|
|
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
|
|
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
|
|
1046
|
+
line(warn, `None on checked imports — import 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
|
|
157
|
+
s += ' Does not clear leftover design work or replace the gate.';
|
|
158
158
|
return s;
|
|
159
159
|
}
|
|
160
160
|
|