arkgate 4.6.7 → 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 +140 -0
- package/README.md +20 -9
- package/SECURITY.md +1 -1
- package/bin/ark-check-runtime.mjs +13 -1
- package/bin/ark-mcp-runtime.mjs +65 -3
- package/bin/lib/adapter-contract.mjs +17 -36
- package/bin/lib/agent-skills-package.mjs +29 -0
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/ark-run-doctor.mjs +144 -0
- package/bin/lib/ark-run-facts.mjs +472 -0
- package/bin/lib/ark-run-report.mjs +57 -0
- package/bin/lib/ark-run-sensors.mjs +309 -0
- package/bin/lib/check-args.mjs +1 -0
- package/bin/lib/config-contract.mjs +86 -11
- package/bin/lib/diagnostic-catalog.mjs +8 -0
- package/bin/lib/doctor-advisories.mjs +45 -8
- package/bin/lib/doctor-human.mjs +10 -0
- package/bin/lib/doctor-next-actions.mjs +5 -1
- package/bin/lib/doctor-plan.mjs +20 -16
- package/bin/lib/extra-merge-teeth.mjs +187 -0
- package/bin/lib/first-run-help.mjs +2 -2
- package/bin/lib/html-report-advisories.mjs +2 -0
- package/bin/lib/html-report-depth.mjs +22 -2
- package/bin/lib/html-report.mjs +16 -0
- package/bin/lib/install-migrate.mjs +29 -57
- package/bin/lib/managed-upgrade.mjs +2 -7
- package/bin/lib/remediation.mjs +132 -0
- package/bin/lib/resolved-candidate-facts.mjs +67 -2
- package/bin/lib/rules-under-contract.mjs +37 -89
- 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/bin/lib/snippet-analysis.mjs +43 -2
- package/bin/lib/status-command.mjs +28 -0
- package/bin/lib/status-manifest.mjs +23 -0
- package/dist/{configTypes-l6XiwiC1.d.ts → configTypes-CgJimx9o.d.ts} +17 -3
- package/dist/eslint/index.cjs +6 -2
- package/dist/eslint/index.d.ts +70 -2
- package/dist/eslint/index.js +6 -2
- package/dist/index.cjs +35 -35
- package/dist/index.d.ts +804 -272
- package/dist/index.js +35 -35
- package/docs/README.md +3 -3
- package/docs/agent-guide.md +22 -16
- package/docs/ai-gates.md +15 -2
- package/docs/configuration.md +24 -11
- package/docs/develop.md +12 -3
- package/docs/diagnostics.md +75 -0
- package/docs/enthusiast/README.md +4 -3
- package/docs/enthusiast/how-to-agent-gates.md +1 -1
- package/docs/package-surface.md +16 -13
- package/docs/product-voice.md +6 -3
- package/docs/threat-model.md +1 -1
- package/docs/use.md +5 -4
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +41 -2
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +47 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +23 -2
- package/templates/agent-skills/ark-contract/SKILL.md +8 -7
- package/templates/agent-skills/ark-place/SKILL.md +26 -2
- package/templates/agent-skills/ark-runtime/SKILL.md +66 -24
- package/templates/skills/ark-adopt.md +23 -2
- package/templates/skills/ark-contract.md +8 -7
- package/templates/skills/ark-place.md +26 -2
- package/templates/skills/ark-runtime.md +66 -24
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
|
|
11
|
-
import { isScanExcludedRelative } from '../ark-shared.mjs';
|
|
11
|
+
import { globToRegExp, isScanExcludedRelative } from '../ark-shared.mjs';
|
|
12
12
|
import {
|
|
13
13
|
AMBIENT_CAPABILITY_ENTRIES,
|
|
14
14
|
collectCapabilityUses,
|
|
@@ -35,6 +35,11 @@ import {
|
|
|
35
35
|
} from './ast-scan.mjs';
|
|
36
36
|
import { provePortProofInject } from './port-proof.mjs';
|
|
37
37
|
import { extractClassShapesFromSource } from './arkrules-sensors.mjs';
|
|
38
|
+
import {
|
|
39
|
+
extractArkRunDeclarationsFromSource,
|
|
40
|
+
extractArkRunKernelCallsFromSource,
|
|
41
|
+
extractArkRunManagedNewsFromSource,
|
|
42
|
+
} from './ark-run-facts.mjs';
|
|
38
43
|
import {
|
|
39
44
|
collectGovernedFiles,
|
|
40
45
|
isGovernableSourceFile,
|
|
@@ -998,6 +1003,12 @@ export function resolveCandidateFacts({
|
|
|
998
1003
|
const safetyUses = [];
|
|
999
1004
|
/** ADR 0013 class-shape facts for ArkRules structure sensors. */
|
|
1000
1005
|
const classShapes = [];
|
|
1006
|
+
/** ADR 0022 / RN03 — syntax evidence only; sensors emit in RN04. */
|
|
1007
|
+
const arkRunKernelCalls = [];
|
|
1008
|
+
const arkRunManagedNews = [];
|
|
1009
|
+
const arkRunCompositionRootHits = [];
|
|
1010
|
+
const arkRunDeclarations = [];
|
|
1011
|
+
const compositionRootPatterns = [...(config.arkRun?.compositionRoots ?? [])];
|
|
1001
1012
|
|
|
1002
1013
|
for (const candidate of candidateFiles) {
|
|
1003
1014
|
const sourceFile = ts.createSourceFile(
|
|
@@ -1087,6 +1098,56 @@ export function resolveCandidateFacts({
|
|
|
1087
1098
|
} catch {
|
|
1088
1099
|
// Never fail the resolver for shape extraction; sensors stay silent on this file.
|
|
1089
1100
|
}
|
|
1101
|
+
try {
|
|
1102
|
+
arkRunKernelCalls.push(
|
|
1103
|
+
...extractArkRunKernelCallsFromSource(candidate.path, candidate.content)
|
|
1104
|
+
);
|
|
1105
|
+
} catch {
|
|
1106
|
+
// Never fail the resolver for ArkRun call extraction.
|
|
1107
|
+
}
|
|
1108
|
+
try {
|
|
1109
|
+
arkRunDeclarations.push(
|
|
1110
|
+
...extractArkRunDeclarationsFromSource(candidate.path, candidate.content)
|
|
1111
|
+
);
|
|
1112
|
+
} catch {
|
|
1113
|
+
// Never fail the resolver for ArkRun declaration extraction.
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const admittedTypeNames = new Set(classShapes.map((shape) => shape.className));
|
|
1119
|
+
for (const candidate of candidateFiles) {
|
|
1120
|
+
if (!/\.(tsx?|mts|cts)$/i.test(candidate.path)) continue;
|
|
1121
|
+
try {
|
|
1122
|
+
arkRunManagedNews.push(
|
|
1123
|
+
...extractArkRunManagedNewsFromSource(
|
|
1124
|
+
candidate.path,
|
|
1125
|
+
candidate.content,
|
|
1126
|
+
admittedTypeNames
|
|
1127
|
+
)
|
|
1128
|
+
);
|
|
1129
|
+
} catch {
|
|
1130
|
+
// Never fail the resolver for managed-new extraction.
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (compositionRootPatterns.length > 0) {
|
|
1135
|
+
const factoryFiles = new Set(
|
|
1136
|
+
arkRunKernelCalls.filter((call) => call.kind === 'factory').map((call) => call.file)
|
|
1137
|
+
);
|
|
1138
|
+
for (const candidate of candidateFiles) {
|
|
1139
|
+
for (const pattern of compositionRootPatterns) {
|
|
1140
|
+
try {
|
|
1141
|
+
if (!globToRegExp(pattern).test(candidate.path)) continue;
|
|
1142
|
+
} catch {
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
arkRunCompositionRootHits.push({
|
|
1146
|
+
file: candidate.path,
|
|
1147
|
+
matchedRoot: pattern,
|
|
1148
|
+
hasKernelFactory: factoryFiles.has(candidate.path),
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1090
1151
|
}
|
|
1091
1152
|
}
|
|
1092
1153
|
|
|
@@ -1152,7 +1213,7 @@ export function resolveCandidateFacts({
|
|
|
1152
1213
|
|
|
1153
1214
|
const projectPackageName = readPackageName(canonicalRoot, observeInput);
|
|
1154
1215
|
return createTrustedResolvedCandidateFacts({
|
|
1155
|
-
schemaVersion: '1.
|
|
1216
|
+
schemaVersion: '1.2',
|
|
1156
1217
|
completeness: completenessReasons.length === 0 ? 'complete' : 'partial',
|
|
1157
1218
|
completenessReasons,
|
|
1158
1219
|
resolverIdentity: RESOLVED_FACTS_RESOLVER_IDENTITY,
|
|
@@ -1169,5 +1230,9 @@ export function resolveCandidateFacts({
|
|
|
1169
1230
|
intentReferences,
|
|
1170
1231
|
safetyUses,
|
|
1171
1232
|
classShapes,
|
|
1233
|
+
arkRunKernelCalls,
|
|
1234
|
+
arkRunManagedNews,
|
|
1235
|
+
arkRunCompositionRootHits,
|
|
1236
|
+
arkRunDeclarations,
|
|
1172
1237
|
});
|
|
1173
1238
|
}
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
import { loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
|
|
8
8
|
import { evaluateInvariantCoverage } from './invariant-coverage.mjs';
|
|
9
9
|
import { loadInvariantCoverageInputs } from './invariant-coverage-io.mjs';
|
|
10
|
+
import {
|
|
11
|
+
EXTRA_MERGE_TEETH_GOVERNED_FLOOR,
|
|
12
|
+
composeMergePlanesHonesty,
|
|
13
|
+
demoteExtraPlaneTeethUnderClassificationFloor,
|
|
14
|
+
} from './extra-merge-teeth.mjs';
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Cap long catalogs in doctor JSON (and HTML, which consumes the same summary).
|
|
@@ -16,12 +21,11 @@ const COVERED_SAMPLE_MAX = 24;
|
|
|
16
21
|
const STRUCTURE_CATALOG_MAX = 40;
|
|
17
22
|
const UNCOVERED_CATALOG_MAX = 30;
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
export const EXTRA_MERGE_TEETH_GOVERNED_FLOOR = 50;
|
|
24
|
+
export { EXTRA_MERGE_TEETH_GOVERNED_FLOOR };
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* P1M / extraMergeTeeth: under the classification floor, demote enforced ArkRules
|
|
24
|
-
*
|
|
28
|
+
* and ArkRun findings so merge matches doctor stamp (layer graph only).
|
|
25
29
|
* Unknown classification (null/null) → do not demote (contract-only callers).
|
|
26
30
|
*
|
|
27
31
|
* @param {object[]} violations
|
|
@@ -29,28 +33,7 @@ export const EXTRA_MERGE_TEETH_GOVERNED_FLOOR = 50;
|
|
|
29
33
|
* @returns {object[]}
|
|
30
34
|
*/
|
|
31
35
|
export function demoteArkRuleTeethUnderClassificationFloor(violations, classification = {}) {
|
|
32
|
-
|
|
33
|
-
const governed =
|
|
34
|
-
typeof classification.governedPercent === 'number' ? classification.governedPercent : null;
|
|
35
|
-
const populated =
|
|
36
|
-
typeof classification.populatedLayerCount === 'number'
|
|
37
|
-
? classification.populatedLayerCount
|
|
38
|
-
: null;
|
|
39
|
-
if (governed == null && populated == null) return violations;
|
|
40
|
-
const allowsTeeth =
|
|
41
|
-
(governed ?? 0) >= EXTRA_MERGE_TEETH_GOVERNED_FLOOR && (populated ?? 0) >= 1;
|
|
42
|
-
if (allowsTeeth) return violations;
|
|
43
|
-
for (const v of violations) {
|
|
44
|
-
const isArkRule =
|
|
45
|
-
v?.arkruleId != null ||
|
|
46
|
-
(typeof v?.ruleId === 'string' &&
|
|
47
|
-
(v.ruleId.startsWith('ARKRULE') || v.ruleId.startsWith('arkrule')));
|
|
48
|
-
if (isArkRule && v.failsStrict !== false) {
|
|
49
|
-
v.failsStrict = false;
|
|
50
|
-
if (v.severity === 'error') v.severity = 'warning';
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return violations;
|
|
36
|
+
return demoteExtraPlaneTeethUnderClassificationFloor(violations, classification);
|
|
54
37
|
}
|
|
55
38
|
|
|
56
39
|
/**
|
|
@@ -63,6 +46,18 @@ export function demoteArkRuleTeethUnderClassificationFloor(violations, classific
|
|
|
63
46
|
* classifiedFiles?: number | null,
|
|
64
47
|
* }} [classification] layer-plane coverage (when known)
|
|
65
48
|
*/
|
|
49
|
+
function arkRunMergeInput(config, residualCount = 0) {
|
|
50
|
+
const extra = config?.arkRun;
|
|
51
|
+
if (!extra || typeof extra !== 'object') {
|
|
52
|
+
return { present: false, mode: null, residualCount: 0 };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
present: true,
|
|
56
|
+
mode: extra.mode === 'enforced' || extra.mode === 'advisory' ? extra.mode : null,
|
|
57
|
+
residualCount: Number(residualCount) || 0,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
66
61
|
export function summarizeRulesUnderContract(root, config, facts, classification) {
|
|
67
62
|
if (!config?.arkRules || Object.keys(config.arkRules).length === 0) {
|
|
68
63
|
return {
|
|
@@ -71,6 +66,11 @@ export function summarizeRulesUnderContract(root, config, facts, classification)
|
|
|
71
66
|
invariants: 0,
|
|
72
67
|
coveredInvariants: 0,
|
|
73
68
|
uncoveredInvariants: 0,
|
|
69
|
+
mergePlanes: composeMergePlanesHonesty({
|
|
70
|
+
classification,
|
|
71
|
+
arkRules: { active: false },
|
|
72
|
+
arkRun: arkRunMergeInput(config),
|
|
73
|
+
}),
|
|
74
74
|
notAScore: true,
|
|
75
75
|
note: 'No arkRules map — intra-layer ArkRules are opt-in.',
|
|
76
76
|
};
|
|
@@ -162,73 +162,21 @@ export function summarizeRulesUnderContract(root, config, facts, classification)
|
|
|
162
162
|
const invariantAdvisory = invariants - invariantEnforced;
|
|
163
163
|
const coveredInvariants = coverage.coverage.filter((c) => c.covered).length;
|
|
164
164
|
const uncoveredInvariants = coverage.coverage.filter((c) => !c.covered).length;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
:
|
|
174
|
-
|
|
175
|
-
classification && typeof classification.populatedLayerCount === 'number'
|
|
176
|
-
? classification.populatedLayerCount
|
|
177
|
-
: classification && typeof classification.classifiedFiles === 'number'
|
|
178
|
-
? classification.classifiedFiles > 0
|
|
179
|
-
? 1
|
|
180
|
-
: 0
|
|
181
|
-
: null;
|
|
182
|
-
const classificationKnown = governedPercent != null || populatedLayerCount != null;
|
|
183
|
-
const classificationAllowsTeeth = !classificationKnown
|
|
184
|
-
? true
|
|
185
|
-
: (governedPercent ?? 0) >= EXTRA_MERGE_TEETH_GOVERNED_FLOOR &&
|
|
186
|
-
(populatedLayerCount ?? 0) >= 1;
|
|
187
|
-
const extraMergeTeeth = hasEnforcedTeeth && classificationAllowsTeeth;
|
|
188
|
-
const teethDeferredForClassification =
|
|
189
|
-
hasEnforcedTeeth && classificationKnown && !classificationAllowsTeeth;
|
|
190
|
-
// P1-M — which plane can fail merge (layers vs enforced structure vs invariants).
|
|
191
|
-
const mergePlanes = {
|
|
192
|
-
layers: {
|
|
193
|
-
role: 'inter-layer-edges',
|
|
194
|
-
alwaysOnGate: true,
|
|
195
|
-
note: 'Import/export layer graph — the default merge plane. Absent arkRules changes nothing here.',
|
|
196
|
-
},
|
|
197
|
-
structureSensors: {
|
|
198
|
-
role: 'intra-layer-heuristics',
|
|
199
|
-
total: structureRules,
|
|
200
|
-
enforced: structureEnforced,
|
|
201
|
-
advisory: structureAdvisory,
|
|
202
|
-
note: 'Structure sensors are heuristics (prefer false negatives). Only mode:enforced fails merge; noisy sensors stay advisory by default. Advisory-only packs never add merge teeth (FG-ARKRULES-ADVISORY-ONLY).',
|
|
203
|
-
},
|
|
204
|
-
invariants: {
|
|
205
|
-
role: 'catalog-plus-coverage',
|
|
206
|
-
total: invariants,
|
|
207
|
-
enforced: invariantEnforced,
|
|
208
|
-
advisory: invariantAdvisory,
|
|
165
|
+
const mergePlanes = composeMergePlanesHonesty({
|
|
166
|
+
classification,
|
|
167
|
+
arkRules: {
|
|
168
|
+
active: true,
|
|
169
|
+
structureEnforced,
|
|
170
|
+
structureTotal: structureRules,
|
|
171
|
+
structureAdvisory,
|
|
172
|
+
invariantEnforced,
|
|
173
|
+
invariantTotal: invariants,
|
|
174
|
+
invariantAdvisory,
|
|
209
175
|
covered: coveredInvariants,
|
|
210
176
|
uncovered: uncoveredInvariants,
|
|
211
|
-
note: 'Invariants are catalog + coverage evidence, not a business runtime. Enforced + proven-uncovered fails merge; absence of enforced rules adds no extra teeth.',
|
|
212
177
|
},
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
extraMergeTeeth,
|
|
216
|
-
...(classificationKnown
|
|
217
|
-
? {
|
|
218
|
-
classificationGate: {
|
|
219
|
-
governedPercent: governedPercent ?? null,
|
|
220
|
-
populatedLayerCount: populatedLayerCount ?? null,
|
|
221
|
-
floorPercent: EXTRA_MERGE_TEETH_GOVERNED_FLOOR,
|
|
222
|
-
allowsTeeth: classificationAllowsTeeth,
|
|
223
|
-
},
|
|
224
|
-
}
|
|
225
|
-
: {}),
|
|
226
|
-
failMergeWhen: extraMergeTeeth
|
|
227
|
-
? 'Layer graph failures plus enforced structure/invariant findings (advisory sensors never fail merge alone).'
|
|
228
|
-
: teethDeferredForClassification
|
|
229
|
-
? `Layer graph only — enforced ArkRules structure/invariant findings are demoted under the teeth floor (need ≥${EXTRA_MERGE_TEETH_GOVERNED_FLOOR}% governed and ≥1 populated layer); they do not merge-block until classification is honest.`
|
|
230
|
-
: 'Layer graph only — no enforced ArkRules structure/invariant teeth on this tree. Advisory packs do not arm merge teeth.',
|
|
231
|
-
};
|
|
178
|
+
arkRun: arkRunMergeInput(config),
|
|
179
|
+
});
|
|
232
180
|
|
|
233
181
|
return {
|
|
234
182
|
active: true,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HS post-write skill catalog: adapters, home skip/prune, Claude/Grok home skip.
|
|
3
|
+
* Kept out of install-migrate so that file stays inside its module budget.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { arkCommand } from '../ark-shared.mjs';
|
|
8
|
+
import { installRequestedAgentHomes } from './agent-homes.mjs';
|
|
9
|
+
import { codexSkillsDir } from './codex-home.mjs';
|
|
10
|
+
import {
|
|
11
|
+
canonicalSkillPath,
|
|
12
|
+
linkSkillHostAdapters,
|
|
13
|
+
pruneHomeArkSkillDuplicates,
|
|
14
|
+
skillTemplateNames,
|
|
15
|
+
} from './skill-install.mjs';
|
|
16
|
+
import { installSkillCatalog, skillInstallLine } from './skill-write.mjs';
|
|
17
|
+
|
|
18
|
+
function skillName(skill) {
|
|
19
|
+
return Array.isArray(skill) ? skill[0] : skill?.name || skill;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function projectCatalogReady(root, skillNames) {
|
|
23
|
+
return skillNames.some((name) => fs.existsSync(path.join(root, canonicalSkillPath(name))));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function applySkillCatalogFollowup({
|
|
27
|
+
root,
|
|
28
|
+
tools,
|
|
29
|
+
skills,
|
|
30
|
+
version,
|
|
31
|
+
args,
|
|
32
|
+
}) {
|
|
33
|
+
const skillNames = skills.map(skillName);
|
|
34
|
+
if (!args.compact && skillNames.length > 0) {
|
|
35
|
+
const adapterResults = linkSkillHostAdapters(root, tools, skillNames, Boolean(args.force));
|
|
36
|
+
for (const row of adapterResults) {
|
|
37
|
+
if (row.status === 'linked' || row.status === 'copied') {
|
|
38
|
+
console.log(
|
|
39
|
+
` ${row.status.padEnd(7)} ${row.relativePath} (adapter → .agents/skills/${row.name})`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (args.pruneHomeDuplicates) {
|
|
45
|
+
const pruned = pruneHomeArkSkillDuplicates(
|
|
46
|
+
root,
|
|
47
|
+
skillNames.length ? skillNames : skillTemplateNames()
|
|
48
|
+
);
|
|
49
|
+
if (!pruned.ok) {
|
|
50
|
+
console.log(' skip --prune-home-duplicates (no project .agents/skills catalog yet)');
|
|
51
|
+
} else if (pruned.removed.length === 0) {
|
|
52
|
+
console.log(' skip --prune-home-duplicates (no home ark-* copies)');
|
|
53
|
+
} else {
|
|
54
|
+
console.log(` pruned ${pruned.removed.length} home ark-* path(s) (project catalog is enough)`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const homeResults = [];
|
|
59
|
+
if (args.codexHome) {
|
|
60
|
+
const dir = codexSkillsDir();
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(
|
|
63
|
+
`Codex home skills (scope=home-shared; source=${version ? `arkgate@${version}` : 'arkgate@unknown'}; target=${dir}/<name>/SKILL.md):`
|
|
64
|
+
);
|
|
65
|
+
console.log(
|
|
66
|
+
' Compatibility: monotonic downgrade protection requires every shared-catalog writer ' +
|
|
67
|
+
'to use ArkGate 4.2.0+; pre-4.2 --codex-home ignores this catalog. Upgrade legacy repos first.'
|
|
68
|
+
);
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(` FAILED to create ${dir} (${error.message})`);
|
|
73
|
+
homeResults.push({ relativePath: dir, status: 'failed' });
|
|
74
|
+
}
|
|
75
|
+
if (homeResults.length === 0) {
|
|
76
|
+
const hasCatalog = skills.some((skill) =>
|
|
77
|
+
fs.existsSync(path.join(root, '.agents', 'skills', skillName(skill), 'SKILL.md'))
|
|
78
|
+
);
|
|
79
|
+
if (hasCatalog) {
|
|
80
|
+
console.log(
|
|
81
|
+
' Skip home write — project .agents/skills is the catalog. Codex lists user+repo; a home copy duplicates every /ark-*.'
|
|
82
|
+
);
|
|
83
|
+
console.log(
|
|
84
|
+
` Remove leftover home copies: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --prune-home-duplicates')}`
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
for (const result of installSkillCatalog({
|
|
88
|
+
directory: dir,
|
|
89
|
+
skills,
|
|
90
|
+
packageVersion: version,
|
|
91
|
+
force: args.force,
|
|
92
|
+
scope: 'home',
|
|
93
|
+
})) {
|
|
94
|
+
console.log(skillInstallLine(result));
|
|
95
|
+
homeResults.push(result);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const catalogReady = projectCatalogReady(root, skillNames);
|
|
102
|
+
if ((args.claudeHome || args.grokHome || args.agentHomes) && catalogReady) {
|
|
103
|
+
if (!args.json) {
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(
|
|
106
|
+
'Skip Claude/Grok home skill write — project .agents/skills + adapters are the catalog. Home ark-* copies override or duplicate.'
|
|
107
|
+
);
|
|
108
|
+
console.log(
|
|
109
|
+
` Remove leftover home copies: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --prune-home-duplicates')}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
installRequestedAgentHomes({
|
|
114
|
+
root,
|
|
115
|
+
skills,
|
|
116
|
+
version,
|
|
117
|
+
force: args.force,
|
|
118
|
+
claudeHome: args.claudeHome,
|
|
119
|
+
grokHome: args.grokHome,
|
|
120
|
+
agentHomes: args.agentHomes,
|
|
121
|
+
json: args.json,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { skillNames, homeResults };
|
|
126
|
+
}
|