arkgate 4.5.6 → 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 +47 -1
- package/README.md +16 -14
- package/bin/ark-check-runtime.mjs +15 -5
- package/bin/ark-mcp-runtime.mjs +82 -61
- package/bin/ark.mjs +2 -2
- package/bin/lib/agent-gates.mjs +2 -0
- package/bin/lib/agent-homes.mjs +296 -0
- package/bin/lib/agent-projection.mjs +1 -1
- package/bin/lib/ci-and-commands.mjs +9 -8
- package/bin/lib/design-smells.mjs +3 -7
- package/bin/lib/doctor-plan.mjs +36 -12
- package/bin/lib/gate-files.mjs +1 -1
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +13 -11
- package/bin/lib/host-support-matrix.mjs +32 -12
- package/bin/lib/html-report-depth.mjs +7 -8
- package/bin/lib/html-report.mjs +2 -1
- package/bin/lib/install-migrate.mjs +36 -0
- package/bin/lib/managed-upgrade.mjs +6 -1
- package/bin/lib/mcp-adoption.mjs +6 -1
- package/bin/lib/mcp-process-package.mjs +95 -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/start-preview.mjs +5 -1
- package/bin/lib/upgrade-whats-new.mjs +16 -0
- package/bin/lib/write-path-capabilities.mjs +62 -1
- 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 +18 -13
- package/docs/ai-gates.md +43 -14
- package/docs/develop.md +3 -3
- package/docs/enthusiast/how-to-agent-gates.md +4 -3
- package/docs/package-surface.md +3 -3
- 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
|
@@ -70,9 +70,27 @@ export const HOST_SUPPORT_MATRIX = Object.freeze({
|
|
|
70
70
|
true,
|
|
71
71
|
true
|
|
72
72
|
),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// Cursor: official preToolUse deny / exit 2 is a hard block for matched tools when
|
|
74
|
+
// `.cursor/hooks.json` is installed + trusted. Claim hard only for Write|StrReplace;
|
|
75
|
+
// Shell/Tab/human edits still rely on CI. Repair envelope may emit; Write updated_input
|
|
76
|
+
// reinjection is not guaranteed on Cursor (agent_message + retry is the supported path).
|
|
77
|
+
cursor: hostProfile(
|
|
78
|
+
'Cursor',
|
|
79
|
+
'.cursor/hooks.json',
|
|
80
|
+
'preToolUse `Write` / `StrReplace`',
|
|
81
|
+
['Write', 'StrReplace'],
|
|
82
|
+
true,
|
|
83
|
+
false,
|
|
84
|
+
{
|
|
85
|
+
repairEnvelopeEmitted: true,
|
|
86
|
+
operationCoverage: {
|
|
87
|
+
Write: true,
|
|
88
|
+
StrReplace: true,
|
|
89
|
+
shell: false,
|
|
90
|
+
'pre-commit': false,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
),
|
|
76
94
|
codex: hostProfile(
|
|
77
95
|
'OpenAI Codex',
|
|
78
96
|
'.codex/hooks.json',
|
|
@@ -134,14 +152,15 @@ export function renderHostSupportMatrixMarkdown() {
|
|
|
134
152
|
const rows = HOST_SUPPORT_HOSTS.map((host) => {
|
|
135
153
|
const profile = HOST_SUPPORT_MATRIX[host];
|
|
136
154
|
const capabilities = profile.capabilities;
|
|
137
|
-
// Fail-closed honesty:
|
|
138
|
-
//
|
|
155
|
+
// Fail-closed honesty: Codex/OpenCode never claim hard write; CI is required-status.
|
|
156
|
+
// Cursor claims hard only for listed preToolUse ops when hooks are installed + trusted.
|
|
157
|
+
// hookSurface already includes "PreToolUse/preToolUse …" — do not prefix again.
|
|
139
158
|
let local;
|
|
140
159
|
if (capabilities['hard-write']) {
|
|
141
160
|
local = `**Hard** block for listed ops (${profile.hookSurface}) when installed + trusted`;
|
|
142
161
|
} else if (host === 'codex') {
|
|
143
162
|
local =
|
|
144
|
-
'**Advisory / best-effort** at write (not equivalent to Claude/Grok hard block)';
|
|
163
|
+
'**Advisory / best-effort** at write (not equivalent to Claude/Grok/Cursor hard block)';
|
|
145
164
|
} else if (host === 'opencode') {
|
|
146
165
|
local =
|
|
147
166
|
'**Advisory / best-effort** at write (MCP + optional plugin; not a hard boundary)';
|
|
@@ -169,7 +188,8 @@ ${rows}
|
|
|
169
188
|
|
|
170
189
|
**Read the CI column:** for every host, the repository-wide hard guarantee is a **required**
|
|
171
190
|
GitHub **status context** that runs the CLI — not “CI file present,” and not the CLI binary name alone.
|
|
172
|
-
|
|
191
|
+
Codex/OpenCode never get a fake hard write claim. Cursor hard write covers only listed
|
|
192
|
+
\`preToolUse\` ops when \`.cursor/hooks.json\` is installed and trusted — Shell/Tab/human edits still rely on CI.
|
|
173
193
|
|
|
174
194
|
This table describes the supported profile **after its files are installed and the host loads/trusts them**. A hard local boundary covers only the listed hook operations; alternate tools, direct filesystem writes, and human edits still rely on CI. MCP validation is advisory because the agent must call it. The CI check blocks a merge only when the repository makes that status required. Repair **envelopes** may be emitted without reinjection being guaranteed; silent auto-apply never happens. Run \`arkgate-check --doctor\` (or \`ark-check --doctor\`) for the evidence actually detected in the current repository.`;
|
|
175
195
|
}
|
|
@@ -203,19 +223,19 @@ export function doctorWritePathHonestyMessage(activeHost, hardWriteActive) {
|
|
|
203
223
|
// EH07: distinguish CLI command (arkgate-check / ark-check) from the GitHub required status context name.
|
|
204
224
|
const mergeBoundary =
|
|
205
225
|
'Required CI hard merge boundary = a required GitHub status context that runs arkgate-check --strict-merge (alias ark-check --strict-merge)';
|
|
206
|
-
if (host === 'cursor') {
|
|
207
|
-
return `Cursor: write
|
|
226
|
+
if (host === 'cursor' && !hardWriteActive) {
|
|
227
|
+
return `Cursor: pre-write block is supported for Write/StrReplace when .cursor/hooks.json is installed + trusted; without runtime-observed hook evidence, the block is unverified. ${mergeBoundary}.`;
|
|
208
228
|
}
|
|
209
229
|
if (host === 'codex') {
|
|
210
|
-
return `Codex:
|
|
230
|
+
return `Codex: edits are warning only (not blocked) at write time. ${mergeBoundary}.`;
|
|
211
231
|
}
|
|
212
232
|
if (host === 'opencode') {
|
|
213
|
-
return `OpenCode:
|
|
233
|
+
return `OpenCode: edits are warning only (not blocked). ${mergeBoundary}.`;
|
|
214
234
|
}
|
|
215
235
|
if ((host === 'claude' || host === 'grok' || host === 'antigravity') && !hardWriteActive) {
|
|
216
236
|
const label =
|
|
217
237
|
host === 'claude' ? 'Claude' : host === 'grok' ? 'Grok' : 'Antigravity';
|
|
218
|
-
return `${label}:
|
|
238
|
+
return `${label}: pre-write block is supported for listed ops when installed + trusted; without runtime-observed hook evidence, the block is unverified. ${mergeBoundary}.`;
|
|
219
239
|
}
|
|
220
240
|
return null;
|
|
221
241
|
}
|
|
@@ -24,6 +24,7 @@ import { describePackageVersionDualTruth } from './field-install.mjs';
|
|
|
24
24
|
import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
|
|
25
25
|
import { buildDeepModuleCoachAdvisory } from './deep-module-coach.mjs';
|
|
26
26
|
import { computePhysicalCohesion } from './physical-cohesion.mjs';
|
|
27
|
+
import { POST_GREEN_LEDE, operatingModeTitle } from './product-copy.mjs';
|
|
27
28
|
|
|
28
29
|
function esc(value) {
|
|
29
30
|
return String(value)
|
|
@@ -383,13 +384,11 @@ export function renderDesignDepthStrip(depth = {}) {
|
|
|
383
384
|
|
|
384
385
|
const mode = String(depth.mode || '').toLowerCase();
|
|
385
386
|
const title = designWeak
|
|
386
|
-
? mode
|
|
387
|
-
|
|
388
|
-
: `${(mode || 'edges').toUpperCase()} · design-weak`
|
|
389
|
-
: 'Design smells (edges still open)';
|
|
387
|
+
? operatingModeTitle(mode || 'enforce', true)
|
|
388
|
+
: 'Design smells (imports still open)';
|
|
390
389
|
const lede = designWeak
|
|
391
|
-
?
|
|
392
|
-
: 'Design smells exist alongside open
|
|
390
|
+
? POST_GREEN_LEDE
|
|
391
|
+
: 'Design smells exist alongside open import-rule debt. Fix imports first; treat smells as leftover design work after green.';
|
|
393
392
|
|
|
394
393
|
const smellItems = smells
|
|
395
394
|
.slice(0, 6)
|
|
@@ -411,7 +410,7 @@ export function renderDesignDepthStrip(depth = {}) {
|
|
|
411
410
|
? `<div class="pilot-card">
|
|
412
411
|
<h3 style="margin-top:.85rem">Next pilot (one at a time)</h3>
|
|
413
412
|
<p class="dim" style="margin:.15rem 0 .4rem;font-size:.86rem">
|
|
414
|
-
Judgment only — never
|
|
413
|
+
Judgment only — never auto-applied · never multi-pilot batch
|
|
415
414
|
</p>
|
|
416
415
|
<ul class="senior-list">
|
|
417
416
|
<li><b>Smell</b> · <code>${esc(pilot.smellId || pilot.id || '—')}</code></li>
|
|
@@ -489,7 +488,7 @@ export function renderDesignCleanNote(depth = {}) {
|
|
|
489
488
|
return `<div class="section card design-strip is-clean" id="design-depth">
|
|
490
489
|
<div class="design-head">
|
|
491
490
|
<span class="badge design-ok" title="No deterministic design smells with clean edges">Design depth · OK</span>
|
|
492
|
-
<span class="dim" style="font-size:.86rem">No design
|
|
491
|
+
<span class="dim" style="font-size:.86rem">No leftover design work detected</span>
|
|
493
492
|
</div>
|
|
494
493
|
<p class="dim" style="margin:.45rem 0 0;font-size:.88rem">
|
|
495
494
|
Edges and deterministic design sensors agree. Keep placing new code on the golden path;
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveOperatingMode,
|
|
12
12
|
} from '../ark-shared.mjs';
|
|
13
13
|
import { collectAdoptionGaps, arkCheckCommand } from './agent-gates.mjs';
|
|
14
|
+
import { LEFTOVER_DESIGN_BADGE } from './product-copy.mjs';
|
|
14
15
|
import { CORE_LAYER_NAMES } from './core-layers.mjs';
|
|
15
16
|
import {
|
|
16
17
|
renderBaselineSignalLegend,
|
|
@@ -525,7 +526,7 @@ export function renderHtmlReport({
|
|
|
525
526
|
const designSmells = Array.isArray(depth.designSmells) ? depth.designSmells : [];
|
|
526
527
|
const designWeakBadge =
|
|
527
528
|
designFitness?.designWeak === true
|
|
528
|
-
? ` <span class="badge design" title="
|
|
529
|
+
? ` <span class="badge design" title="Import rules can be green while leftover design work remains. Not a FAIL.">${LEFTOVER_DESIGN_BADGE}</span>`
|
|
529
530
|
: '';
|
|
530
531
|
const designStripHtml =
|
|
531
532
|
renderDesignDepthStrip({
|
|
@@ -25,9 +25,11 @@ import {
|
|
|
25
25
|
claudeSettings,
|
|
26
26
|
codexHooks,
|
|
27
27
|
codexProjectConfig,
|
|
28
|
+
cursorHooks,
|
|
28
29
|
grokHooks,
|
|
29
30
|
grokProjectConfig,
|
|
30
31
|
mergeAntigravityArkHook,
|
|
32
|
+
mergeCursorArkHook,
|
|
31
33
|
mergeOpencodeArkMcp,
|
|
32
34
|
opencodeProjectConfig,
|
|
33
35
|
} from './hook-templates.mjs';
|
|
@@ -78,6 +80,7 @@ import {
|
|
|
78
80
|
RUNNER_BEFORE_ARK,
|
|
79
81
|
} from './mcp-adoption.mjs';
|
|
80
82
|
import { inspectCodexInstallActivation, printCodexActivationHandoff, reportPartialInstall } from './install-activation.mjs';
|
|
83
|
+
import { installRequestedAgentHomes } from './agent-homes.mjs';
|
|
81
84
|
import {
|
|
82
85
|
hasHardWriteHook,
|
|
83
86
|
validateHardWriteRequest,
|
|
@@ -194,6 +197,9 @@ export function buildManagedAssetCatalog({ root, tools, compact = false, skillsO
|
|
|
194
197
|
);
|
|
195
198
|
if (selectedTools.has('cursor')) {
|
|
196
199
|
add('.cursor/mcp.json', mcpJson(root));
|
|
200
|
+
// whole-file for managed upgrade/manifest (json-merge is install-time only via
|
|
201
|
+
// mergeCursorArkHook below — same pattern as Antigravity hooks).
|
|
202
|
+
add('.cursor/hooks.json', cursorHooks(root));
|
|
197
203
|
if (!compact) add('.cursor/rules/ark.mdc', cursorRule(root));
|
|
198
204
|
}
|
|
199
205
|
if (selectedTools.has('claude')) add('.claude/settings.json', claudeSettings(root));
|
|
@@ -516,6 +522,25 @@ export function runInstallAgentGates(args) {
|
|
|
516
522
|
if (merged === existing) return { relativePath, status: 'skipped' };
|
|
517
523
|
return writeTemplate(root, relativePath, merged, true);
|
|
518
524
|
}
|
|
525
|
+
if (relativePath === '.cursor/hooks.json') {
|
|
526
|
+
const fullPath = path.join(root, relativePath);
|
|
527
|
+
let existing = '';
|
|
528
|
+
try {
|
|
529
|
+
existing = fs.readFileSync(fullPath, 'utf8');
|
|
530
|
+
} catch {
|
|
531
|
+
// Missing hooks file → write generated Cursor preToolUse gate.
|
|
532
|
+
}
|
|
533
|
+
if (!existing) {
|
|
534
|
+
return writeTemplate(root, relativePath, content, true);
|
|
535
|
+
}
|
|
536
|
+
const merged = mergeCursorArkHook(existing, content);
|
|
537
|
+
if (merged == null) {
|
|
538
|
+
return { relativePath, status: 'skipped-non-ark' };
|
|
539
|
+
}
|
|
540
|
+
if (merged === existing) return { relativePath, status: 'skipped' };
|
|
541
|
+
// Upsert Ark preToolUse without requiring --force; never wipe sibling hooks.
|
|
542
|
+
return writeTemplate(root, relativePath, merged, true);
|
|
543
|
+
}
|
|
519
544
|
if (relativePath === '.agents/hooks.json') {
|
|
520
545
|
const fullPath = path.join(root, relativePath);
|
|
521
546
|
let existing = '';
|
|
@@ -623,6 +648,17 @@ export function runInstallAgentGates(args) {
|
|
|
623
648
|
}
|
|
624
649
|
}
|
|
625
650
|
|
|
651
|
+
installRequestedAgentHomes({
|
|
652
|
+
root,
|
|
653
|
+
skills,
|
|
654
|
+
version,
|
|
655
|
+
force: args.force,
|
|
656
|
+
claudeHome: args.claudeHome,
|
|
657
|
+
grokHome: args.grokHome,
|
|
658
|
+
agentHomes: args.agentHomes,
|
|
659
|
+
json: args.json,
|
|
660
|
+
});
|
|
661
|
+
|
|
626
662
|
// Optional legacy/home fallback. Normal Codex installs use the project-scoped
|
|
627
663
|
// .codex/config.toml above, avoiding cross-project primary binding conflicts.
|
|
628
664
|
//
|
|
@@ -126,7 +126,12 @@ function hasArkText(root, relativePath) {
|
|
|
126
126
|
|
|
127
127
|
const HOST_SIGNALS = {
|
|
128
128
|
claude: ['.claude/settings.json', '.claude/skills/ark-upgrade/SKILL.md'],
|
|
129
|
-
cursor: [
|
|
129
|
+
cursor: [
|
|
130
|
+
'.cursor/mcp.json',
|
|
131
|
+
'.cursor/hooks.json',
|
|
132
|
+
'.cursor/rules/ark.mdc',
|
|
133
|
+
'.cursor/commands/ark-upgrade.md',
|
|
134
|
+
],
|
|
130
135
|
codex: ['.codex/hooks.json', '.codex/config.toml', '.agents/skills/ark-upgrade/SKILL.md'],
|
|
131
136
|
grok: ['.grok/config.toml', '.grok/hooks/ark-write-gate.json', '.grok/skills/ark-upgrade/SKILL.md'],
|
|
132
137
|
antigravity: ['.agents/hooks.json', '.agents/skills/ark-upgrade/SKILL.md'],
|
package/bin/lib/mcp-adoption.mjs
CHANGED
|
@@ -27,7 +27,12 @@ export const COMMAND_GATE_TEXT_FILES = [
|
|
|
27
27
|
'.grok/hooks/ark-write-gate.json', '.grok/config.toml', '.codex/config.toml',
|
|
28
28
|
'.agents/hooks.json',
|
|
29
29
|
];
|
|
30
|
-
export const COMMAND_GATE_JSON_FILES = [
|
|
30
|
+
export const COMMAND_GATE_JSON_FILES = [
|
|
31
|
+
'.mcp.json',
|
|
32
|
+
'.cursor/mcp.json',
|
|
33
|
+
'.cursor/hooks.json',
|
|
34
|
+
'opencode.json',
|
|
35
|
+
];
|
|
31
36
|
// Primary CLI names (product) + one-major aliases. migrate-commands must strip ALL of these
|
|
32
37
|
// before re-emitting a single preferred bin — otherwise a partial rename leaves
|
|
33
38
|
// args: ["ark-mcp", "arkgate-mcp", ...] which breaks stdio MCP hosts.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FX06 — MCP process package vs project install honesty.
|
|
3
|
+
*
|
|
4
|
+
* Pure-ish helpers so unit tests need no MCP server. Production MCP runtime
|
|
5
|
+
* calls buildProcessPackageHonesty with the process version + project root.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve installed arkgate version for a project root (shallow node_modules,
|
|
13
|
+
* then Node module resolution for hoisted monorepos).
|
|
14
|
+
* @param {string} root
|
|
15
|
+
* @returns {string|null}
|
|
16
|
+
*/
|
|
17
|
+
export function readProjectInstalledArkgateVersion(root) {
|
|
18
|
+
const resolvedRoot = path.resolve(root);
|
|
19
|
+
try {
|
|
20
|
+
const shallow = path.join(resolvedRoot, 'node_modules', 'arkgate', 'package.json');
|
|
21
|
+
if (fs.existsSync(shallow)) {
|
|
22
|
+
const v = JSON.parse(fs.readFileSync(shallow, 'utf8')).version;
|
|
23
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
/* fall through */
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const requireFromProject = createRequire(path.join(resolvedRoot, 'package.json'));
|
|
30
|
+
const pkgJson = requireFromProject.resolve('arkgate/package.json');
|
|
31
|
+
const v = JSON.parse(fs.readFileSync(pkgJson, 'utf8')).version;
|
|
32
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {{
|
|
40
|
+
* processVersion?: string|null,
|
|
41
|
+
* projectInstalledVersion?: string|null,
|
|
42
|
+
* root?: string,
|
|
43
|
+
* }} input
|
|
44
|
+
* @returns {{
|
|
45
|
+
* schemaVersion: '1.0',
|
|
46
|
+
* notAScore: true,
|
|
47
|
+
* processArkgateVersion: string|null,
|
|
48
|
+
* projectInstalledVersion: string|null,
|
|
49
|
+
* processPackageMismatch: boolean,
|
|
50
|
+
* processStale: boolean,
|
|
51
|
+
* nextAction: string,
|
|
52
|
+
* }}
|
|
53
|
+
*/
|
|
54
|
+
export function buildProcessPackageHonesty(input = {}) {
|
|
55
|
+
const processVersion =
|
|
56
|
+
typeof input.processVersion === 'string' && input.processVersion.trim()
|
|
57
|
+
? input.processVersion.trim()
|
|
58
|
+
: null;
|
|
59
|
+
let projectInstalledVersion =
|
|
60
|
+
input.projectInstalledVersion !== undefined
|
|
61
|
+
? input.projectInstalledVersion
|
|
62
|
+
: null;
|
|
63
|
+
if (
|
|
64
|
+
projectInstalledVersion === null &&
|
|
65
|
+
input.projectInstalledVersion === undefined &&
|
|
66
|
+
typeof input.root === 'string' &&
|
|
67
|
+
input.root
|
|
68
|
+
) {
|
|
69
|
+
projectInstalledVersion = readProjectInstalledArkgateVersion(input.root);
|
|
70
|
+
}
|
|
71
|
+
if (typeof projectInstalledVersion === 'string') {
|
|
72
|
+
projectInstalledVersion = projectInstalledVersion.trim() || null;
|
|
73
|
+
} else if (projectInstalledVersion !== null) {
|
|
74
|
+
projectInstalledVersion = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const mismatch =
|
|
78
|
+
processVersion != null &&
|
|
79
|
+
projectInstalledVersion != null &&
|
|
80
|
+
processVersion !== projectInstalledVersion;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
schemaVersion: '1.0',
|
|
84
|
+
notAScore: true,
|
|
85
|
+
processArkgateVersion: processVersion,
|
|
86
|
+
projectInstalledVersion,
|
|
87
|
+
processPackageMismatch: mismatch,
|
|
88
|
+
processStale: mismatch,
|
|
89
|
+
nextAction: mismatch
|
|
90
|
+
? '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.'
|
|
91
|
+
: projectInstalledVersion == null
|
|
92
|
+
? 'Project has no resolvable node_modules/arkgate; install the package or use CLI from a project that pins arkgate.'
|
|
93
|
+
: 'Process package version matches project install for this MCP root.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* Plan B stays never mechanical-safe.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { POST_GREEN_HUMAN } from './product-copy.mjs';
|
|
10
|
+
|
|
9
11
|
/** Stable product id for JSON / tests. */
|
|
10
12
|
export const POST_GREEN_PATH_ID = 'clarify-for-ai';
|
|
11
13
|
|
|
@@ -16,8 +18,7 @@ export const POST_GREEN_PRIMARY_SKILL = '/ark-explore';
|
|
|
16
18
|
* Canonical human / agent next-action string (single door).
|
|
17
19
|
* Chained: explore shape-focus then autopilot only to apply B with user OK.
|
|
18
20
|
*/
|
|
19
|
-
export const POST_GREEN_PRIMARY_ACTION =
|
|
20
|
-
'Shape residual (design-weak): edges are clean, design is not finished. Map with /ark-explore shape-focus → dual-plan B; apply B only via /ark-autopilot with your OK. Empty plan A is not done; pattern bets are never mechanical-safe.';
|
|
21
|
+
export const POST_GREEN_PRIMARY_ACTION = POST_GREEN_HUMAN;
|
|
21
22
|
|
|
22
23
|
/** Short label for tables / metrics. */
|
|
23
24
|
export const POST_GREEN_PRIMARY_SHORT =
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-facing product copy (4.6). JSON field names stay stable; this module
|
|
3
|
+
* owns labels people read in doctor, HTML, and compact router.
|
|
4
|
+
*
|
|
5
|
+
* Brands kept: ArkGate, ArkRules. Dialect (design-weak, hard write, …) maps to
|
|
6
|
+
* common software words. See docs/product-voice.md.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Status-light leftover-design qualifier (was “design-weak”). */
|
|
10
|
+
export const LEFTOVER_DESIGN_LABEL = 'leftover design work';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Operating-mode title for humans (and doctor JSON `designFitness.label` prefix).
|
|
14
|
+
* @param {string|null|undefined} mode suggest|adapt|enforce
|
|
15
|
+
* @param {boolean} leftoverDesign
|
|
16
|
+
*/
|
|
17
|
+
export function operatingModeTitle(mode, leftoverDesign) {
|
|
18
|
+
const light = String(mode || 'enforce').toUpperCase();
|
|
19
|
+
return leftoverDesign ? `${light} · ${LEFTOVER_DESIGN_LABEL}` : light;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Short HTML/doctor badge text. */
|
|
23
|
+
export const LEFTOVER_DESIGN_BADGE = LEFTOVER_DESIGN_LABEL;
|
|
24
|
+
|
|
25
|
+
export const POST_GREEN_HUMAN =
|
|
26
|
+
'Imports check out, but the design is still messy. Map leftover work with /ark-explore shape-focus, then apply one small refactor via /ark-autopilot with your OK. A clean import check is not done; pattern bets are never auto-applied.';
|
|
27
|
+
|
|
28
|
+
export const POST_GREEN_LEDE =
|
|
29
|
+
'Import rules are clean, but leftover design work remains. That does not fail the check — it only means “done” is still wrong until you tidy shape.';
|
|
30
|
+
|
|
31
|
+
export const HARD_WRITE_HUMAN = 'pre-write block';
|
|
32
|
+
export const ADVISORY_WRITE_HUMAN = 'warning only (not blocked)';
|
package/bin/lib/skill-write.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export const HOME_SKILL_PENDING_CATALOG = '.arkgate-catalog.pending.json';
|
|
|
16
16
|
const HOME_SKILL_LOCK = '.arkgate-install.lock';
|
|
17
17
|
const HOME_SKILL_CATALOG_SCHEMA = '1.0';
|
|
18
18
|
const HOME_SKILL_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
19
|
-
const HOME_SKILL_LOCK_ATTEMPTS =
|
|
19
|
+
const HOME_SKILL_LOCK_ATTEMPTS = 200;
|
|
20
20
|
const HOME_SKILL_LOCK_RETRY_MS = 25;
|
|
21
21
|
const UUID_PATTERN =
|
|
22
22
|
/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
claudeSettings,
|
|
11
11
|
codexHooks,
|
|
12
12
|
codexProjectConfig,
|
|
13
|
+
cursorHooks,
|
|
13
14
|
grokHooks,
|
|
14
15
|
grokProjectConfig,
|
|
15
16
|
opencodeProjectConfig,
|
|
@@ -30,7 +31,10 @@ const COMPACT_HOST_TEMPLATES = {
|
|
|
30
31
|
['.agents/hooks.json', antigravityHooks(root)],
|
|
31
32
|
['.mcp.json', mcpJson(root)],
|
|
32
33
|
],
|
|
33
|
-
cursor: (root) => [
|
|
34
|
+
cursor: (root) => [
|
|
35
|
+
['.cursor/mcp.json', mcpJson(root)],
|
|
36
|
+
['.cursor/hooks.json', cursorHooks(root)],
|
|
37
|
+
],
|
|
34
38
|
codex: (root) => [
|
|
35
39
|
['.codex/hooks.json', codexHooks(root)],
|
|
36
40
|
['.codex/config.toml', codexProjectConfig(root)],
|
|
@@ -33,6 +33,22 @@ export function buildUpgradeWhatsNewSuggestions() {
|
|
|
33
33
|
neverGateInput: true,
|
|
34
34
|
title: 'Suggested improvements — try or inspect after this package',
|
|
35
35
|
items: [
|
|
36
|
+
{
|
|
37
|
+
id: 'plain-language-doctor',
|
|
38
|
+
title: 'Doctor in plain language',
|
|
39
|
+
try: 'npx arkgate-check --doctor',
|
|
40
|
+
inspect: 'Status light + leftover design work (not a score)',
|
|
41
|
+
why:
|
|
42
|
+
'Doctor and the HTML report use common words (import rules, leftover design work, pre-write block). ArkGate and ArkRules stay as product names.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'shared-agent-homes',
|
|
46
|
+
title: 'Shared agent skills (home)',
|
|
47
|
+
try: 'npx arkgate-check --install-agent-gates --skills-only --agent-homes --force',
|
|
48
|
+
inspect: 'doctor.agentHomeGaps (Claude/Grok ~/.*/skills when those catalogs exist)',
|
|
49
|
+
why:
|
|
50
|
+
'Project skills follow this pin. Shared homes stay on the newest ArkGate on the machine (additive; never downgrade). Orphan 2.x global skills stop coaching the wrong version.',
|
|
51
|
+
},
|
|
36
52
|
{
|
|
37
53
|
id: 'deep-module-coach',
|
|
38
54
|
title: 'Deep-module coach (hot paths + deepening)',
|
|
@@ -216,6 +216,9 @@ function requiredWriteOperations(relativePath) {
|
|
|
216
216
|
if (relativePath === '.agents/hooks.json' || relativePath.startsWith('.agents/')) {
|
|
217
217
|
return ['write_to_file', 'replace_file_content', 'multi_replace_file_content'];
|
|
218
218
|
}
|
|
219
|
+
if (relativePath === '.cursor/hooks.json' || relativePath.startsWith('.cursor/hooks')) {
|
|
220
|
+
return ['Write', 'StrReplace'];
|
|
221
|
+
}
|
|
219
222
|
return ['Write', 'Edit', 'MultiEdit'];
|
|
220
223
|
}
|
|
221
224
|
|
|
@@ -284,6 +287,51 @@ function collectPreToolUseGroups(parsed) {
|
|
|
284
287
|
return [];
|
|
285
288
|
}
|
|
286
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Cursor hooks.json (version 1): { hooks: { preToolUse: [{ command, matcher, failClosed }] } }
|
|
292
|
+
* Flat command entries — not Claude's nested `{ hooks: [{ type, command }] }` groups.
|
|
293
|
+
*/
|
|
294
|
+
function cursorHookEvidence(root) {
|
|
295
|
+
const relativePath = '.cursor/hooks.json';
|
|
296
|
+
const text = readText(path.join(root, relativePath));
|
|
297
|
+
let hooks = [];
|
|
298
|
+
try {
|
|
299
|
+
const parsed = JSON.parse(text);
|
|
300
|
+
const entries = Array.isArray(parsed?.hooks?.preToolUse) ? parsed.hooks.preToolUse : [];
|
|
301
|
+
hooks = entries
|
|
302
|
+
.filter((entry) => entry && typeof entry === 'object' && (!entry.type || entry.type === 'command'))
|
|
303
|
+
.map((entry) => ({
|
|
304
|
+
hook: entry,
|
|
305
|
+
invocation: commandArkMcpInvocation(entry?.command),
|
|
306
|
+
operations: matcherOperations(relativePath, entry?.matcher),
|
|
307
|
+
}))
|
|
308
|
+
.filter((entry) => entry.invocation);
|
|
309
|
+
} catch {
|
|
310
|
+
hooks = [];
|
|
311
|
+
}
|
|
312
|
+
const hardHooks = hooks.filter((entry) => entry.invocation.binArgs.includes('--hook'));
|
|
313
|
+
const required = requiredWriteOperations(relativePath);
|
|
314
|
+
const hard = required.every((operation) =>
|
|
315
|
+
hardHooks.some((entry) => entry.operations.includes(operation))
|
|
316
|
+
);
|
|
317
|
+
const repair =
|
|
318
|
+
hard &&
|
|
319
|
+
required.every((operation) =>
|
|
320
|
+
hardHooks.some(
|
|
321
|
+
({ hook, invocation, operations }) =>
|
|
322
|
+
operations.includes(operation) &&
|
|
323
|
+
(invocation.binArgs.includes('--hook-repair') ||
|
|
324
|
+
/^(?:1|true|yes|on)$/i.test(
|
|
325
|
+
String(hook.env?.ARK_HOOK_REPAIR ?? invocation.environment.ARK_HOOK_REPAIR ?? '')
|
|
326
|
+
))
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
return {
|
|
330
|
+
hard: hard ? [relativePath] : [],
|
|
331
|
+
repair: repair ? [relativePath] : [],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
287
335
|
function hookEvidence(root, relativePath) {
|
|
288
336
|
const text = readText(path.join(root, relativePath));
|
|
289
337
|
let hooks = [];
|
|
@@ -396,6 +444,7 @@ export function detectWritePathInventory(root) {
|
|
|
396
444
|
const claudeHook = hookEvidence(root, '.claude/settings.json');
|
|
397
445
|
const grokHook = hookEvidence(root, '.grok/hooks/ark-write-gate.json');
|
|
398
446
|
const antigravityHook = hookEvidence(root, '.agents/hooks.json');
|
|
447
|
+
const cursorHook = cursorHookEvidence(root);
|
|
399
448
|
const hosts = {
|
|
400
449
|
claude: hostRecord(
|
|
401
450
|
claudeHook.hard,
|
|
@@ -416,7 +465,19 @@ export function detectWritePathInventory(root) {
|
|
|
416
465
|
antigravityHook.repair,
|
|
417
466
|
merge
|
|
418
467
|
),
|
|
419
|
-
cursor: hostRecord(
|
|
468
|
+
cursor: hostRecord(
|
|
469
|
+
cursorHook.hard,
|
|
470
|
+
[
|
|
471
|
+
...mcpEvidence(root, '.cursor/mcp.json'),
|
|
472
|
+
// Shared project MCP is also a valid advisory surface for Cursor sessions.
|
|
473
|
+
...mcpEvidence(root, '.mcp.json'),
|
|
474
|
+
],
|
|
475
|
+
// EH07: hooks may emit --hook-repair envelopes, but Cursor Write updated_input
|
|
476
|
+
// reinjection is not package-guaranteed — keep inventory repair-payload false
|
|
477
|
+
// (envelope honesty lives on support.repair-envelope-emitted).
|
|
478
|
+
[],
|
|
479
|
+
merge
|
|
480
|
+
),
|
|
420
481
|
// Codex 0.123+ emits PreToolUse for the native apply_patch handler, but some
|
|
421
482
|
// Code Mode hosts execute deferred nested writes without dispatching that
|
|
422
483
|
// project hook. Keep the installed hook as best-effort protection; do not
|