arkgate 4.7.0 → 4.7.1
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 +33 -2
- package/README.md +3 -3
- package/bin/lib/agent-skills-package.mjs +29 -0
- package/bin/lib/check-args.mjs +1 -0
- package/bin/lib/doctor-next-actions.mjs +5 -1
- package/bin/lib/first-run-help.mjs +2 -2
- package/bin/lib/install-migrate.mjs +29 -57
- package/bin/lib/managed-upgrade.mjs +2 -7
- package/bin/lib/skill-catalog-apply.mjs +126 -0
- package/bin/lib/skill-install.mjs +264 -33
- package/bin/lib/skill-write.mjs +3 -0
- package/dist/index.cjs +25 -25
- package/dist/index.d.ts +19 -2
- package/dist/index.js +19 -19
- package/docs/README.md +3 -4
- package/docs/agent-guide.md +1 -1
- package/docs/ai-gates.md +2 -2
- package/docs/enthusiast/how-to-agent-gates.md +1 -1
- package/docs/package-surface.md +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-contract/SKILL.md +8 -7
- package/templates/skills/ark-contract.md +8 -7
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { arkCommand } from '../ark-shared.mjs';
|
|
9
|
+
import {
|
|
10
|
+
parseSkillDescriptionVersion,
|
|
11
|
+
stampSkillDescription,
|
|
12
|
+
stripSkillDescriptionVersion,
|
|
13
|
+
} from './agent-skills-package.mjs';
|
|
8
14
|
import { codexPromptsDir, codexSkillsDir } from './codex-home.mjs';
|
|
9
15
|
import { __packageRoot, isCompactRouterAgentsContent, readJson } from './gate-files.mjs';
|
|
10
16
|
|
|
@@ -194,22 +200,61 @@ export const KNOWN_TOOLS = [
|
|
|
194
200
|
// Codex: discovers Agent Skills directories with SKILL.md — repo path is the
|
|
195
201
|
// official `.agents/skills/<name>/SKILL.md` (not dead `.codex/prompts/*.md`).
|
|
196
202
|
// Home install uses `$CODEX_HOME/skills/<name>/SKILL.md` via --codex-home.
|
|
203
|
+
/** Project-canonical Agent Skills catalog (Codex, Cursor, Antigravity all read this). */
|
|
204
|
+
export const SKILL_CANONICAL_DIR = '.agents/skills';
|
|
205
|
+
|
|
206
|
+
export function canonicalSkillPath(name) {
|
|
207
|
+
return `${SKILL_CANONICAL_DIR}/${name}/SKILL.md`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Hosts that natively load `.agents/skills` — do not also copy bytes there under
|
|
212
|
+
* a second name. Cursor/Codex list every path they scan; two copies = two picker rows.
|
|
213
|
+
*/
|
|
214
|
+
export const SKILL_NATIVE_AGENTS_HOSTS = Object.freeze(['codex', 'cursor', 'antigravity']);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Hosts that do not scan `.agents/skills`. Adapter is a relative symlink to the
|
|
218
|
+
* canonical catalog so Grok/Claude/OpenCode see the same bytes.
|
|
219
|
+
* Cursor also scans `.claude/skills` — doctor warns; still one body + visible version.
|
|
220
|
+
*/
|
|
221
|
+
export const SKILL_ADAPTER_LINKS = Object.freeze({
|
|
222
|
+
claude: (name) => ({
|
|
223
|
+
link: `.claude/skills/${name}`,
|
|
224
|
+
target: `../../${SKILL_CANONICAL_DIR}/${name}`,
|
|
225
|
+
}),
|
|
226
|
+
grok: (name) => ({
|
|
227
|
+
link: `.grok/skills/${name}`,
|
|
228
|
+
target: `../../${SKILL_CANONICAL_DIR}/${name}`,
|
|
229
|
+
}),
|
|
230
|
+
opencode: (name) => ({
|
|
231
|
+
link: `.opencode/skills/${name}`,
|
|
232
|
+
target: `../../${SKILL_CANONICAL_DIR}/${name}`,
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
|
|
197
236
|
export const SKILL_TOOL_TARGETS = {
|
|
198
237
|
claude: (name) => `.claude/skills/${name}/SKILL.md`,
|
|
199
|
-
|
|
238
|
+
// Cursor 2026 Agent Skills: `.agents/skills` (not a second `.cursor/commands` copy).
|
|
239
|
+
cursor: (name) => canonicalSkillPath(name),
|
|
200
240
|
// Official Codex REPO skill scope (Agent Skills standard).
|
|
201
|
-
codex: (name) =>
|
|
202
|
-
// Grok Build: project skills at .grok/skills/<name>/SKILL.md (slash-invocable).
|
|
241
|
+
codex: (name) => canonicalSkillPath(name),
|
|
203
242
|
grok: (name) => `.grok/skills/${name}/SKILL.md`,
|
|
204
|
-
|
|
205
|
-
antigravity: (name) => `.agents/skills/${name}/SKILL.md`,
|
|
206
|
-
// OpenCode project skills under `.opencode/skills`.
|
|
243
|
+
antigravity: (name) => canonicalSkillPath(name),
|
|
207
244
|
opencode: (name) => `.opencode/skills/${name}/SKILL.md`,
|
|
208
245
|
windsurf: (name) => `.windsurf/workflows/${name}.md`,
|
|
209
246
|
cline: (name) => `.clinerules/workflows/${name}.md`,
|
|
210
247
|
copilot: (name) => `.github/prompts/${name}.prompt.md`,
|
|
211
248
|
};
|
|
212
249
|
|
|
250
|
+
/** Hosts whose catalog is the project `.agents/skills` tree (write once). */
|
|
251
|
+
export function usesCanonicalSkillCatalog(tool) {
|
|
252
|
+
return (
|
|
253
|
+
SKILL_NATIVE_AGENTS_HOSTS.includes(tool) ||
|
|
254
|
+
Object.prototype.hasOwnProperty.call(SKILL_ADAPTER_LINKS, tool)
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
213
258
|
// The version of the arkgate package these bins ship with. Used to
|
|
214
259
|
// stamp installed skills so a normal ark-check can tell "outdated skill from an
|
|
215
260
|
// older Ark" apart from "user-customized skill" — the stamp moves with the
|
|
@@ -223,8 +268,43 @@ export function arkPackageVersion() {
|
|
|
223
268
|
}
|
|
224
269
|
}
|
|
225
270
|
|
|
226
|
-
|
|
227
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Rewrite a YAML `description:` line with a visible `arkgate@<version>. ` prefix.
|
|
273
|
+
* Preserves quoting. Hosts show this string in the skill picker (unlike arkVersion).
|
|
274
|
+
*/
|
|
275
|
+
function stampDescriptionYamlLine(line, version) {
|
|
276
|
+
const match = String(line).match(/^(description:\s*)(.*)$/);
|
|
277
|
+
if (!match) return line;
|
|
278
|
+
let raw = match[2] ?? '';
|
|
279
|
+
const quoted =
|
|
280
|
+
(raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) ||
|
|
281
|
+
(raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2);
|
|
282
|
+
const quote = quoted ? raw[0] : '';
|
|
283
|
+
const value = quoted ? raw.slice(1, -1) : raw;
|
|
284
|
+
const stamped = stampSkillDescription(value, version);
|
|
285
|
+
if (!quoted) return `${match[1]}${stamped}`;
|
|
286
|
+
const escaped = stamped.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`);
|
|
287
|
+
return `${match[1]}${quote}${escaped}${quote}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function managedDescriptionYamlLine(line) {
|
|
291
|
+
const match = String(line).match(/^(description:\s*)(.*)$/);
|
|
292
|
+
if (!match) return line;
|
|
293
|
+
let raw = match[2] ?? '';
|
|
294
|
+
const quoted =
|
|
295
|
+
(raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) ||
|
|
296
|
+
(raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2);
|
|
297
|
+
const quote = quoted ? raw[0] : '';
|
|
298
|
+
const value = quoted ? raw.slice(1, -1) : raw;
|
|
299
|
+
const stripped = stripSkillDescriptionVersion(value);
|
|
300
|
+
if (stripped === value) return line;
|
|
301
|
+
if (!quoted) return `${match[1]}${stripped}`;
|
|
302
|
+
const escaped = stripped.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`);
|
|
303
|
+
return `${match[1]}${quote}${escaped}${quote}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Insert `arkVersion: <v>` and a visible `arkgate@<v>. ` description prefix.
|
|
307
|
+
// No frontmatter → returned unchanged. Idempotent for a given version
|
|
228
308
|
// and preserves the checked-out line ending on Windows.
|
|
229
309
|
export function stampSkill(content, version) {
|
|
230
310
|
if (!version) return content;
|
|
@@ -241,6 +321,12 @@ export function stampSkill(content, version) {
|
|
|
241
321
|
} else {
|
|
242
322
|
lines.splice(closeIdx, 0, `arkVersion: ${version}`);
|
|
243
323
|
}
|
|
324
|
+
const descIdx = lines.findIndex(
|
|
325
|
+
(line, i) => i > 0 && i < lines.indexOf('---', 1) && /^description:\s*/.test(line)
|
|
326
|
+
);
|
|
327
|
+
if (descIdx !== -1) {
|
|
328
|
+
lines[descIdx] = stampDescriptionYamlLine(lines[descIdx], version);
|
|
329
|
+
}
|
|
244
330
|
return lines.join(newline);
|
|
245
331
|
}
|
|
246
332
|
|
|
@@ -336,6 +422,9 @@ export function skillContentIdentity(content) {
|
|
|
336
422
|
if (end >= 0) {
|
|
337
423
|
for (let index = 1; index < end; index += 1) {
|
|
338
424
|
if (/^arkVersion:/.test(lines[index])) lines[index] = 'arkVersion:<managed>';
|
|
425
|
+
else if (/^description:\s*/.test(lines[index])) {
|
|
426
|
+
lines[index] = managedDescriptionYamlLine(lines[index]);
|
|
427
|
+
}
|
|
339
428
|
}
|
|
340
429
|
text = lines.join('\n');
|
|
341
430
|
}
|
|
@@ -379,7 +468,7 @@ export function skillContentMatchesTemplate(installedContent, templateContent) {
|
|
|
379
468
|
* }} input
|
|
380
469
|
* @returns {{
|
|
381
470
|
* action: 'write'|'skip',
|
|
382
|
-
* reason: 'missing'|'content-current'|'newer-home-version'|'unknown-source-version'|'existing-preserved'|'content-update',
|
|
471
|
+
* reason: 'missing'|'content-current'|'stamp-refresh'|'newer-home-version'|'unknown-source-version'|'existing-preserved'|'content-update',
|
|
383
472
|
* scope: 'repo'|'home',
|
|
384
473
|
* sourceVersion: string|null,
|
|
385
474
|
* installedVersion: string|null,
|
|
@@ -405,13 +494,9 @@ export function planSkillInstall(input) {
|
|
|
405
494
|
});
|
|
406
495
|
|
|
407
496
|
if (existingContent === null) return result('write', 'missing');
|
|
408
|
-
if (
|
|
409
|
-
existingContent === targetContent ||
|
|
410
|
-
skillContentIdentity(existingContent) === skillContentIdentity(targetContent)
|
|
411
|
-
) {
|
|
497
|
+
if (existingContent === targetContent) {
|
|
412
498
|
return result('skip', 'content-current');
|
|
413
499
|
}
|
|
414
|
-
|
|
415
500
|
if (scope === 'home') {
|
|
416
501
|
if (installedVersion && !sourceVersion) {
|
|
417
502
|
return result('skip', 'unknown-source-version', true, true);
|
|
@@ -424,6 +509,11 @@ export function planSkillInstall(input) {
|
|
|
424
509
|
return result('skip', 'newer-home-version', true, true);
|
|
425
510
|
}
|
|
426
511
|
}
|
|
512
|
+
if (skillContentIdentity(existingContent) === skillContentIdentity(targetContent)) {
|
|
513
|
+
// Body matches; only arkVersion / visible description prefix drifted.
|
|
514
|
+
// Refresh the stamp without --force so the picker shows arkgate@this-package.
|
|
515
|
+
return result('write', 'stamp-refresh');
|
|
516
|
+
}
|
|
427
517
|
|
|
428
518
|
if (!input.force) return result('skip', 'existing-preserved', true);
|
|
429
519
|
return result('write', 'content-update');
|
|
@@ -467,6 +557,113 @@ export function skillTemplates() {
|
|
|
467
557
|
.map((name) => [path.basename(name, '.md'), fs.readFileSync(path.join(dir, name), 'utf8')]);
|
|
468
558
|
}
|
|
469
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Point a host-native skills dir at the project canonical catalog.
|
|
562
|
+
* Relative symlink so clones keep working. Fallback copy when the OS refuses links.
|
|
563
|
+
* @returns {'linked'|'copied'|'current'|'skipped-customized'|'missing-canonical'}
|
|
564
|
+
*/
|
|
565
|
+
export function ensureSkillAdapterLink(root, name, adapter, force = false) {
|
|
566
|
+
const canonicalDir = path.join(root, SKILL_CANONICAL_DIR, name);
|
|
567
|
+
const canonicalFile = path.join(canonicalDir, 'SKILL.md');
|
|
568
|
+
if (!fs.existsSync(canonicalFile)) return 'missing-canonical';
|
|
569
|
+
const linkPath = path.join(root, adapter.link);
|
|
570
|
+
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
571
|
+
const existing = fs.lstatSync(linkPath, { throwIfNoEntry: false });
|
|
572
|
+
if (existing?.isSymbolicLink()) {
|
|
573
|
+
const current = fs.readlinkSync(linkPath).replaceAll('\\', '/');
|
|
574
|
+
if (current === adapter.target) return 'current';
|
|
575
|
+
fs.unlinkSync(linkPath);
|
|
576
|
+
} else if (existing) {
|
|
577
|
+
const adapterFile = path.join(linkPath, 'SKILL.md');
|
|
578
|
+
let adapterContent = null;
|
|
579
|
+
try {
|
|
580
|
+
adapterContent = fs.readFileSync(adapterFile, 'utf8');
|
|
581
|
+
} catch {
|
|
582
|
+
adapterContent = null;
|
|
583
|
+
}
|
|
584
|
+
const canonicalContent = fs.readFileSync(canonicalFile, 'utf8');
|
|
585
|
+
if (
|
|
586
|
+
!force &&
|
|
587
|
+
adapterContent &&
|
|
588
|
+
skillContentIdentity(adapterContent) !== skillContentIdentity(canonicalContent)
|
|
589
|
+
) {
|
|
590
|
+
return 'skipped-customized';
|
|
591
|
+
}
|
|
592
|
+
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
fs.symlinkSync(adapter.target, linkPath);
|
|
596
|
+
return 'linked';
|
|
597
|
+
} catch {
|
|
598
|
+
fs.cpSync(canonicalDir, linkPath, { recursive: true });
|
|
599
|
+
return 'copied';
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function linkSkillHostAdapters(root, tools, skillNames, force = false) {
|
|
604
|
+
const results = [];
|
|
605
|
+
for (const tool of tools) {
|
|
606
|
+
const adapterFor = SKILL_ADAPTER_LINKS[tool];
|
|
607
|
+
if (!adapterFor) continue;
|
|
608
|
+
for (const name of skillNames) {
|
|
609
|
+
const adapter = adapterFor(name);
|
|
610
|
+
results.push({
|
|
611
|
+
tool,
|
|
612
|
+
name,
|
|
613
|
+
status: ensureSkillAdapterLink(root, name, adapter, force),
|
|
614
|
+
relativePath: `${adapter.link}/SKILL.md`,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return results;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const HOME_ARK_SKILL_ROOTS = [
|
|
622
|
+
() => path.join(codexSkillsDir()),
|
|
623
|
+
() => path.join(os.homedir(), '.claude', 'skills'),
|
|
624
|
+
() => path.join(os.homedir(), '.grok', 'skills'),
|
|
625
|
+
];
|
|
626
|
+
|
|
627
|
+
function projectHasCanonicalCatalog(root, skillNames) {
|
|
628
|
+
return skillNames.some((name) =>
|
|
629
|
+
fs.existsSync(path.join(root, canonicalSkillPath(name)))
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Remove frozen `/ark-*` directories from agent home catalogs when the project
|
|
635
|
+
* already has `.agents/skills`. Codex/Cursor list user+repo; same name twice.
|
|
636
|
+
* Never deletes non-Ark skills.
|
|
637
|
+
*/
|
|
638
|
+
export function pruneHomeArkSkillDuplicates(root, skillNames = skillTemplateNames()) {
|
|
639
|
+
const names = skillNames.length ? skillNames : skillTemplateNames();
|
|
640
|
+
const removed = [];
|
|
641
|
+
if (!projectHasCanonicalCatalog(root, names)) {
|
|
642
|
+
return { ok: false, reason: 'no-project-catalog', removed };
|
|
643
|
+
}
|
|
644
|
+
for (const dirFn of HOME_ARK_SKILL_ROOTS) {
|
|
645
|
+
const dir = dirFn();
|
|
646
|
+
for (const name of names) {
|
|
647
|
+
const skillDir = path.join(dir, name);
|
|
648
|
+
const stat = fs.lstatSync(skillDir, { throwIfNoEntry: false });
|
|
649
|
+
if (!stat) continue;
|
|
650
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
651
|
+
removed.push(skillDir);
|
|
652
|
+
}
|
|
653
|
+
const catalog = path.join(dir, '.arkgate-catalog.json');
|
|
654
|
+
const pending = path.join(dir, '.arkgate-catalog.pending.json');
|
|
655
|
+
for (const meta of [catalog, pending]) {
|
|
656
|
+
if (fs.existsSync(meta)) {
|
|
657
|
+
fs.rmSync(meta, { force: true });
|
|
658
|
+
removed.push(meta);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return { ok: true, reason: 'pruned', removed };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export { parseSkillDescriptionVersion, stripSkillDescriptionVersion };
|
|
666
|
+
|
|
470
667
|
// Skill names only, silent on a missing templates dir — for the freshness
|
|
471
668
|
// advisory below, which must not print packaging warnings on every check run.
|
|
472
669
|
export function skillTemplateNames() {
|
|
@@ -732,8 +929,30 @@ export function assessCodexSkillParity(root) {
|
|
|
732
929
|
// signal, so it is not auto-detected (explicit --tools only), matching resolveTools.
|
|
733
930
|
export function detectCodexHomeGap(root) {
|
|
734
931
|
const parity = assessCodexSkillParity(root);
|
|
735
|
-
if (!parity
|
|
736
|
-
const { home, packageVersion, expectedCount, skillsDir } = parity;
|
|
932
|
+
if (!parity) return null;
|
|
933
|
+
const { home, packageVersion, expectedCount, skillsDir, repo } = parity;
|
|
934
|
+
const repoComplete =
|
|
935
|
+
Boolean(repo?.inPlay) && repo.missing === 0 && !repo.legacyPromptsOnly;
|
|
936
|
+
const homePresent = Boolean(home?.inPlay) && home.presentCount > 0;
|
|
937
|
+
if (repoComplete && homePresent) {
|
|
938
|
+
return {
|
|
939
|
+
missing: 0,
|
|
940
|
+
stale: 0,
|
|
941
|
+
legacyPromptsOnly: false,
|
|
942
|
+
hasLegacyPrompts: Boolean(home.hasLegacyPrompts),
|
|
943
|
+
presentCount: home.presentCount,
|
|
944
|
+
expectedCount,
|
|
945
|
+
packageVersion,
|
|
946
|
+
skillsDir,
|
|
947
|
+
catalogVersion: home.catalogVersion,
|
|
948
|
+
pendingRecoveryRequired: false,
|
|
949
|
+
catalogMetadataInvalid: false,
|
|
950
|
+
catalogStateReason: null,
|
|
951
|
+
preferProject: true,
|
|
952
|
+
duplicateHome: true,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
if (!parity.homeNeedsAttention) return null;
|
|
737
956
|
return {
|
|
738
957
|
missing: home.missing,
|
|
739
958
|
stale: home.stale,
|
|
@@ -751,6 +970,8 @@ export function detectCodexHomeGap(root) {
|
|
|
751
970
|
: home.pendingRecoveryRequired
|
|
752
971
|
? 'interrupted catalog commit'
|
|
753
972
|
: null,
|
|
973
|
+
preferProject: false,
|
|
974
|
+
duplicateHome: false,
|
|
754
975
|
};
|
|
755
976
|
}
|
|
756
977
|
|
|
@@ -971,24 +1192,34 @@ export function printSkillAndCodexGapHints(root, opts) {
|
|
|
971
1192
|
}
|
|
972
1193
|
}
|
|
973
1194
|
if (codexHomeGap) {
|
|
974
|
-
const parts = [];
|
|
975
|
-
if (codexHomeGap.legacyPromptsOnly) parts.push('legacy-prompts-only');
|
|
976
|
-
if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
|
|
977
|
-
if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} content-behind-package`);
|
|
978
|
-
if (codexHomeGap.pendingRecoveryRequired) parts.push('interrupted catalog commit');
|
|
979
|
-
if (codexHomeGap.catalogMetadataInvalid) parts.push('invalid catalog metadata');
|
|
980
1195
|
const deferred = !codexSessionActive;
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
(
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1196
|
+
if (codexHomeGap.duplicateHome) {
|
|
1197
|
+
const msg =
|
|
1198
|
+
`Codex home $CODEX_HOME/skills/ark-* duplicates project .agents/skills (picker shows two copies). ` +
|
|
1199
|
+
(deferred
|
|
1200
|
+
? 'Deferred unless you use Codex. '
|
|
1201
|
+
: '') +
|
|
1202
|
+
`Remove home copies: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --prune-home-duplicates')}`;
|
|
1203
|
+
console.log(deferred ? color.dim(msg) : color.yellow(msg));
|
|
1204
|
+
} else {
|
|
1205
|
+
const parts = [];
|
|
1206
|
+
if (codexHomeGap.legacyPromptsOnly) parts.push('legacy-prompts-only');
|
|
1207
|
+
if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
|
|
1208
|
+
if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} content-behind-package`);
|
|
1209
|
+
if (codexHomeGap.pendingRecoveryRequired) parts.push('interrupted catalog commit');
|
|
1210
|
+
if (codexHomeGap.catalogMetadataInvalid) parts.push('invalid catalog metadata');
|
|
1211
|
+
const deferredNote = deferred
|
|
1212
|
+
? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. '
|
|
1213
|
+
: ' ';
|
|
1214
|
+
const msg =
|
|
1215
|
+
`Codex home skill catalog (${codexSkillsDir()}) behind this Ark (${parts.join(', ')}).` +
|
|
1216
|
+
deferredNote +
|
|
1217
|
+
`Catalog is $CODEX_HOME/skills/<name>/SKILL.md (not flat prompts). ` +
|
|
1218
|
+
(codexHomeGap.catalogMetadataInvalid
|
|
1219
|
+
? 'Inspect the shared catalog metadata before retrying; invalid metadata fails safe.'
|
|
1220
|
+
: `When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`);
|
|
1221
|
+
console.log(deferred ? color.dim(msg) : color.yellow(msg));
|
|
1222
|
+
}
|
|
992
1223
|
}
|
|
993
1224
|
if (codexRepoSkillGap && codexSessionActive) {
|
|
994
1225
|
const parts = [];
|
package/bin/lib/skill-write.mjs
CHANGED
|
@@ -393,6 +393,9 @@ export function skillInstallNote(plan) {
|
|
|
393
393
|
if (plan.reason === 'content-current') {
|
|
394
394
|
return `scope=${scope}; body current; installed=${installed}; source=${source}; no write`;
|
|
395
395
|
}
|
|
396
|
+
if (plan.reason === 'stamp-refresh') {
|
|
397
|
+
return `scope=${scope}; stamp refresh; installed=${installed}; source=${source}`;
|
|
398
|
+
}
|
|
396
399
|
if (plan.reason === 'newer-home-version') {
|
|
397
400
|
return `scope=${scope}; CONFLICT installed=${installed} newer than source=${source}; downgrade blocked`;
|
|
398
401
|
}
|