arkgate 3.6.1 → 3.8.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 +122 -1145
- package/README.md +59 -19
- package/bin/ark-check-runtime.mjs +1598 -0
- package/bin/ark-check.mjs +32 -1565
- package/bin/ark-layer-match.mjs +2 -1
- package/bin/ark-mcp-runtime.mjs +1976 -0
- package/bin/ark-mcp.mjs +84 -1495
- package/bin/ark-shared.mjs +34 -38
- package/bin/ark.mjs +33 -66
- package/bin/lib/adapter-contract.mjs +161 -9
- package/bin/lib/agent-gates.mjs +1 -0
- package/bin/lib/analysis-completeness.mjs +28 -0
- package/bin/lib/analysis-engine.mjs +8 -8
- package/bin/lib/analysis-policy.mjs +27 -0
- package/bin/lib/architecture-scan.mjs +70 -304
- package/bin/lib/auto-patch.mjs +76 -8
- package/bin/lib/ci-and-commands.mjs +1 -1
- package/bin/lib/codex-home.mjs +43 -16
- package/bin/lib/design-delta.mjs +4 -0
- package/bin/lib/design-smells.mjs +67 -14
- package/bin/lib/doctor-advisories.mjs +23 -7
- package/bin/lib/doctor-plan.mjs +44 -47
- package/bin/lib/enforcement-state.mjs +2 -0
- package/bin/lib/github-enforcement.mjs +443 -0
- package/bin/lib/hook-templates.mjs +12 -148
- package/bin/lib/html-report-advisories.mjs +59 -0
- package/bin/lib/html-report-depth.mjs +9 -0
- package/bin/lib/html-report.mjs +5 -5
- package/bin/lib/install-migrate.mjs +83 -79
- package/bin/lib/managed-upgrade.mjs +622 -0
- package/bin/lib/mcp-adoption.mjs +3 -1
- package/bin/lib/parse-health.mjs +75 -0
- package/bin/lib/port-proof.mjs +2 -2
- package/bin/lib/prepare-change.mjs +68 -38
- package/bin/lib/prepare-write.mjs +7 -1
- package/bin/lib/reshape-decisions.mjs +284 -0
- package/bin/lib/resident-doctor-client.mjs +55 -0
- package/bin/lib/resident-hook.mjs +247 -0
- package/bin/lib/resolved-candidate-facts.mjs +1160 -0
- package/bin/lib/scan-files.mjs +19 -6
- package/bin/lib/snippet-analysis.mjs +119 -0
- package/bin/lib/source-policy.mjs +24 -0
- package/bin/lib/typescript-host.mjs +15 -18
- package/bin/lib/unavailable-analysis.mjs +76 -0
- package/bin/lib/upgrade-command.mjs +115 -0
- package/bin/lib/weakest-link.mjs +21 -179
- package/bin/lib/write-path-capabilities.mjs +167 -16
- package/bin/lib/write-path-detect.mjs +3 -2
- package/dist/eslint/index.cjs +3 -3
- package/dist/eslint/index.d.ts +3 -0
- package/dist/eslint/index.js +3 -3
- package/dist/index.cjs +7 -7
- package/dist/index.d.ts +1073 -141
- package/dist/index.js +7 -7
- package/docs/agent-guide.md +127 -52
- package/docs/ai-gates.md +100 -18
- package/docs/configuration.md +6 -0
- package/docs/demos/01-write-gate-self-correction.md +2 -2
- package/docs/enthusiast/README.md +10 -10
- package/docs/enthusiast/how-to-gallery-starter.md +2 -2
- package/docs/enthusiast/reference-commands.md +18 -1
- package/docs/enthusiast/tutorial-first-project.md +2 -2
- package/docs/package-surface.md +101 -14
- package/docs/typescript-support.md +118 -37
- package/package.json +33 -4
- package/schemas/ark.analysis-result.schema.json +159 -2
- package/schemas/ark.design-delta.schema.json +1 -0
- package/schemas/ark.enforcement-state.schema.json +84 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -0
- package/server.json +2 -2
- package/templates/skills/ark-autopilot.md +12 -0
- package/templates/skills/ark-explore.md +12 -5
- package/templates/skills/ark-fix.md +12 -2
- package/templates/skills/ark-loop.md +14 -1
- package/templates/skills/ark-runtime.md +15 -8
- package/templates/skills/ark-upgrade.md +122 -182
- package/bin/lib/ai-velocity.mjs +0 -293
- package/bin/lib/graph-cycles.mjs +0 -6
- package/bin/lib/safety-diagnostics.mjs +0 -284
- package/bin/lib/ts-resolve.mjs +0 -227
- package/dist/configTypes-DAPvBqK6.d.cts +0 -61
- package/dist/eslint/index.d.cts +0 -146
- package/dist/index.d.cts +0 -986
|
@@ -152,6 +152,46 @@ function ambientStateHtml(state) {
|
|
|
152
152
|
</section>`;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
function reshapeDecisionsHtml(memory) {
|
|
156
|
+
if (!memory) return '';
|
|
157
|
+
const lifecycle = memory.lifecycle ?? {};
|
|
158
|
+
const rows = [];
|
|
159
|
+
if (memory.decisionFile?.invalid) {
|
|
160
|
+
rows.push(
|
|
161
|
+
`<p><span class="tag warn">invalid</span> ${esc(memory.decisionFile.path)} is ignored; no reshape decision suppresses a pilot.</p>`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
for (const decision of memory.current ?? []) {
|
|
165
|
+
const tag = decision.verdict === 'accepted' ? 'ok' : 'warn';
|
|
166
|
+
const review = decision.reviewBy ? ` · review-by ${esc(decision.reviewBy)}` : '';
|
|
167
|
+
rows.push(
|
|
168
|
+
`<p><span class="tag ${tag}">${esc(decision.verdict)}</span> <b>${esc(decision.concept)}</b>${review} — ${esc(decision.reason)}${decision.suppressesPilot ? ' Pilot pressure suppressed; mirror facts remain visible.' : ' Pilot remains available.'}</p>`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if ((memory.currentCount ?? 0) > (memory.current?.length ?? 0)) {
|
|
172
|
+
rows.push(`<p class="muted">…(+${memory.currentCount - memory.current.length} more current decision(s))</p>`);
|
|
173
|
+
}
|
|
174
|
+
if ((lifecycle.expiredCount ?? 0) > 0) {
|
|
175
|
+
rows.push(`<p><span class="tag warn">expired</span> ${lifecycle.expiredCount} decision(s) no longer apply; pilot pressure is active again.</p>`);
|
|
176
|
+
}
|
|
177
|
+
if ((lifecycle.malformedCount ?? 0) > 0) {
|
|
178
|
+
rows.push(`<p><span class="tag warn">malformed</span> ${lifecycle.malformedCount} review-by date(s) are invalid; those decisions are ignored.</p>`);
|
|
179
|
+
}
|
|
180
|
+
if ((lifecycle.staleCount ?? 0) > 0) {
|
|
181
|
+
const stale = (lifecycle.stale ?? [])
|
|
182
|
+
.slice(0, 4)
|
|
183
|
+
.map((decision) => `<code>${esc(decision.concept)}</code>`)
|
|
184
|
+
.join(' · ');
|
|
185
|
+
const more = lifecycle.staleCount > 4 ? ` …(+${lifecycle.staleCount - 4} more)` : '';
|
|
186
|
+
rows.push(`<p><span class="tag warn">stale</span> ${lifecycle.staleCount} decision(s) have a changed anchor set and no longer apply: ${stale}${more}</p>`);
|
|
187
|
+
}
|
|
188
|
+
if ((lifecycle.undated ?? 0) > 0) {
|
|
189
|
+
rows.push(`<p class="muted">${lifecycle.undated} current decision(s) have no review-by date.</p>`);
|
|
190
|
+
}
|
|
191
|
+
if (rows.length === 0) return '';
|
|
192
|
+
return `<div data-advisory="reshapeDecisions"><h3>Reshape decisions <span class="muted">(explicit; pilot pressure only)</span></h3>${rows.join('\n')}</div>`;
|
|
193
|
+
}
|
|
194
|
+
|
|
155
195
|
function physicalCohesionHtml(pc) {
|
|
156
196
|
if (!pc) return '';
|
|
157
197
|
const findings = Array.isArray(pc.findings) ? pc.findings : [];
|
|
@@ -173,10 +213,28 @@ function physicalCohesionHtml(pc) {
|
|
|
173
213
|
<section data-advisory="physicalCohesion">
|
|
174
214
|
<h2>Physical cohesion <span class="muted">(advisory — facts, not a score; the verdict is unchanged)</span></h2>
|
|
175
215
|
${body}
|
|
216
|
+
${reshapeDecisionsHtml(pc.reshapeDecisions)}
|
|
176
217
|
${pilot}
|
|
177
218
|
</section>`;
|
|
178
219
|
}
|
|
179
220
|
|
|
221
|
+
function parseHealthHtml(health) {
|
|
222
|
+
if (!health) return '';
|
|
223
|
+
const files = Array.isArray(health.files) ? health.files : [];
|
|
224
|
+
const body = health.available === false
|
|
225
|
+
? '<p class="muted">Parse health was not available for this rendering — no clean claim is made.</p>'
|
|
226
|
+
: health.affectedFiles === 0
|
|
227
|
+
? `<p class="muted">No parse diagnostics found across ${health.scannedFiles ?? 0} governed file(s) scanned.</p>`
|
|
228
|
+
: `<p><span class="tag warn">${health.affectedFiles} affected</span> ${health.diagnosticCount} parse diagnostic(s) across ${health.scannedFiles} governed file(s).</p>` +
|
|
229
|
+
`<ul>${files.map((f) => `<li><code>${esc(f.file)}</code> — ${f.diagnosticCount} parse diagnostic(s)</li>`).join('')}</ul>` +
|
|
230
|
+
(health.truncated > 0 ? `<p class="muted">…(+${health.truncated} more affected file(s); doctor list capped)</p>` : '');
|
|
231
|
+
return `
|
|
232
|
+
<section data-advisory="parseHealth">
|
|
233
|
+
<h2>Parse health <span class="muted">(completeness evidence — affected syntax makes analysis partial)</span></h2>
|
|
234
|
+
${body}
|
|
235
|
+
</section>`;
|
|
236
|
+
}
|
|
237
|
+
|
|
180
238
|
/**
|
|
181
239
|
* Render every doctor advisory as report sections. Keys must cover everything
|
|
182
240
|
* `computeDoctorAdvisories` returns — the parity guard enforces it.
|
|
@@ -189,6 +247,7 @@ export function renderAdvisorySections(advisories, escape) {
|
|
|
189
247
|
contractHealthHtml(advisories.contractHealth),
|
|
190
248
|
ambientStateHtml(advisories.ambientState),
|
|
191
249
|
physicalCohesionHtml(advisories.physicalCohesion),
|
|
250
|
+
parseHealthHtml(advisories.parseHealth),
|
|
192
251
|
]
|
|
193
252
|
.filter(Boolean)
|
|
194
253
|
.join('\n');
|
|
@@ -106,6 +106,14 @@ export function renderWritePathAdoptionBlock(writePath) {
|
|
|
106
106
|
const gapNote = writePath.gap
|
|
107
107
|
? ` Gap: <b>${esc(writePath.gap.id)}</b> — ${esc(writePath.gap.message || '')}`
|
|
108
108
|
: '';
|
|
109
|
+
const state = writePath.enforcementState;
|
|
110
|
+
const value = (entry) => entry === true ? 'yes' : entry === false ? 'no' : String(entry);
|
|
111
|
+
const boundary = (label, entry) => entry
|
|
112
|
+
? `<li><b>${esc(label)}</b> — supported ${esc(value(entry.supported))}; analyzed ${esc(value(entry.analyzed))}; configured ${esc(value(entry.configured))}; installed ${esc(value(entry.installed))}; runtime observed ${esc(value(entry.runtimeObserved))}; operation ${esc(entry.operation ?? 'none')}; operation covered ${esc(value(entry.operationCoverage))}; active ${esc(value(entry.active))}; bypassable ${esc(value(entry.bypassable))}; required ${esc(value(entry.required))}; hard ${esc(value(entry.hard))}</li>`
|
|
113
|
+
: '';
|
|
114
|
+
const stateHtml = state
|
|
115
|
+
? `<ul class="senior-list" style="margin:.45rem 0 0">${boundary('Local write', state.localWrite)}${boundary('Advisory MCP', state.advisoryMcp)}${boundary('CI merge', state.ciMerge)}</ul>`
|
|
116
|
+
: '';
|
|
109
117
|
// invLine / gapNote already include escaped user content; only plain strings go through esc().
|
|
110
118
|
return `<div class="write-path-block" title="${esc(writePathModeHint(mode))}">
|
|
111
119
|
<p class="dim" style="margin:.65rem 0 .2rem;font-size:.84rem">
|
|
@@ -117,6 +125,7 @@ export function renderWritePathAdoptionBlock(writePath) {
|
|
|
117
125
|
<p class="kpi-hint" style="max-width:none;margin:0">
|
|
118
126
|
${esc(writePathModeHint(mode))} ${invLine}${esc(unknownNote)}${gapNote}
|
|
119
127
|
</p>
|
|
128
|
+
${stateHtml}
|
|
120
129
|
</div>`;
|
|
121
130
|
}
|
|
122
131
|
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -51,9 +51,9 @@ export function detectEnforcement(root) {
|
|
|
51
51
|
'.grok/hooks/ark-write-gate.json') ||
|
|
52
52
|
null;
|
|
53
53
|
return [
|
|
54
|
-
{ name: 'Write gate', where: writeGateFile, what: '
|
|
54
|
+
{ name: 'Write gate', where: writeGateFile, what: 'configured local gate; active blocking depends on observed host/runtime evidence' },
|
|
55
55
|
{ name: 'ESLint', where: eslintFile || null, what: 'flags violations in your editor' },
|
|
56
|
-
{ name: 'CI check', where: workflowsMentionArk(), what: '
|
|
56
|
+
{ name: 'CI check', where: workflowsMentionArk(), what: 'runs Ark; merge blocking requires provider-confirmed required status' },
|
|
57
57
|
{ name: 'Baseline', where: has('.ark-baseline.json') ? '.ark-baseline.json' : null, what: 'old violations frozen; new ones fail' },
|
|
58
58
|
].map((e) => ({ ...e, on: !!e.where }));
|
|
59
59
|
}
|
|
@@ -1061,8 +1061,8 @@ export function renderHtmlReport({
|
|
|
1061
1061
|
)}
|
|
1062
1062
|
${metricKpi(
|
|
1063
1063
|
`${gatesOn}/${enforcement.length}`,
|
|
1064
|
-
'Gates
|
|
1065
|
-
'Write hook, CI workflow, ESLint plugin, and baseline file — how many enforcement
|
|
1064
|
+
'Gates configured',
|
|
1065
|
+
'Write hook, CI workflow, ESLint plugin, and baseline file — how many enforcement surfaces are present, not proof that each is active or required.'
|
|
1066
1066
|
)}
|
|
1067
1067
|
${metricKpi(
|
|
1068
1068
|
`${violations.length}${suppressed ? ` · ${suppressed}Δ` : ''}`,
|
|
@@ -1222,7 +1222,7 @@ export function renderHtmlReport({
|
|
|
1222
1222
|
['Type-only violations', originSnapshot.typeOnlyViolations, currentSnapshot.typeOnlyViolations, ''],
|
|
1223
1223
|
['Layers', originSnapshot.layerCount, currentSnapshot.layerCount, ''],
|
|
1224
1224
|
['Deny rules', originSnapshot.denyRules, currentSnapshot.denyRules, ''],
|
|
1225
|
-
['Gates
|
|
1225
|
+
['Gates configured', originSnapshot.gatesOn, currentSnapshot.gatesOn, ''],
|
|
1226
1226
|
];
|
|
1227
1227
|
const originDate = (originSnapshot.generatedAt || '').slice(0, 10) || 'origin';
|
|
1228
1228
|
const nowDate = (currentSnapshot.generatedAt || '').slice(0, 10) || 'now';
|
|
@@ -125,6 +125,81 @@ export function warnLockfileConflict(root) {
|
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Canonical, side-effect-free catalog of project assets emitted by the gate installer.
|
|
130
|
+
* Upgrade consumes this same allowlist; a manifest can record identities but can never
|
|
131
|
+
* introduce a new write path.
|
|
132
|
+
*/
|
|
133
|
+
export function buildManagedAssetCatalog({ root, tools, compact = false, skillsOnly = false }) {
|
|
134
|
+
const selectedTools = tools instanceof Set ? tools : new Set(tools ?? []);
|
|
135
|
+
const assets = [];
|
|
136
|
+
const add = (relativePath, content, kind = 'gate', scope = 'whole-file') => {
|
|
137
|
+
assets.push({
|
|
138
|
+
relativePath,
|
|
139
|
+
content,
|
|
140
|
+
kind,
|
|
141
|
+
scope,
|
|
142
|
+
templateId: `${kind}:${relativePath}`,
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (!skillsOnly) {
|
|
147
|
+
const compactHost = compact ? [...selectedTools][0] ?? null : null;
|
|
148
|
+
add(
|
|
149
|
+
'AGENTS.md',
|
|
150
|
+
compact ? compactAgentInstructions(root, compactHost) : agentInstructions(root)
|
|
151
|
+
);
|
|
152
|
+
if (!compact || !compactHost || compactHost === 'claude') add('.mcp.json', mcpJson(root));
|
|
153
|
+
const deploy = detectDeployPathQuality(root);
|
|
154
|
+
add(
|
|
155
|
+
'.github/workflows/ark-check.yml',
|
|
156
|
+
githubWorkflow(packageManager(root), detectCiNode(root), {
|
|
157
|
+
hasLintScript: deploy.hasLintScript,
|
|
158
|
+
hasTypecheckScript: deploy.hasTypecheckScript,
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
if (selectedTools.has('cursor')) {
|
|
162
|
+
add('.cursor/mcp.json', mcpJson(root));
|
|
163
|
+
if (!compact) add('.cursor/rules/ark.mdc', cursorRule(root));
|
|
164
|
+
}
|
|
165
|
+
if (selectedTools.has('claude')) add('.claude/settings.json', claudeSettings(root));
|
|
166
|
+
if (selectedTools.has('codex')) {
|
|
167
|
+
add('.codex/hooks.json', codexHooks(root));
|
|
168
|
+
add('.codex/config.toml', codexProjectConfig(root), 'gate', 'toml-section');
|
|
169
|
+
if (!compact) add('docs/ark-codex-config.toml', codexTomlSnippet(root));
|
|
170
|
+
}
|
|
171
|
+
if (selectedTools.has('grok')) {
|
|
172
|
+
add('.grok/config.toml', grokProjectConfig(root));
|
|
173
|
+
add('.grok/hooks/ark-write-gate.json', grokHooks(root));
|
|
174
|
+
}
|
|
175
|
+
if (selectedTools.has('windsurf')) add('.windsurf/rules/ark.md', instructionRule(root));
|
|
176
|
+
if (selectedTools.has('cline')) add('.clinerules/ark.md', instructionRule(root));
|
|
177
|
+
if (selectedTools.has('copilot')) {
|
|
178
|
+
add('.github/copilot-instructions.md', instructionRule(root));
|
|
179
|
+
}
|
|
180
|
+
if (selectedTools.has('kiro')) add('.kiro/steering/ark.md', instructionRule(root));
|
|
181
|
+
if (selectedTools.has('roo')) add('.roo/rules/ark.md', instructionRule(root));
|
|
182
|
+
if (selectedTools.has('continue')) add('.continue/rules/ark.md', instructionRule(root));
|
|
183
|
+
if (selectedTools.has('gemini')) add('GEMINI.md', instructionRule(root));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const version = arkPackageVersion();
|
|
187
|
+
const skills = skillTemplates().map(([name, content]) => [name, stampSkill(content, version)]);
|
|
188
|
+
const skillPaths = new Set();
|
|
189
|
+
if (!compact) {
|
|
190
|
+
for (const tool of selectedTools) {
|
|
191
|
+
const target = SKILL_TOOL_TARGETS[tool];
|
|
192
|
+
if (!target) continue;
|
|
193
|
+
for (const [name, content] of skills) {
|
|
194
|
+
const relativePath = target(name);
|
|
195
|
+
skillPaths.add(relativePath);
|
|
196
|
+
add(relativePath, content, 'skill');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { assets, skills, skillPaths, version };
|
|
201
|
+
}
|
|
202
|
+
|
|
128
203
|
// --migrate-commands: rewrite ONLY the Ark command runner in existing gate files to the
|
|
129
204
|
// project's package manager (no --force clobber). Closes the upgrade gap where a repo that
|
|
130
205
|
// adopted before the package-manager-aware templates keeps a stale `npx`.
|
|
@@ -220,7 +295,6 @@ export function runInstallAgentGates(args) {
|
|
|
220
295
|
process.exitCode = 2;
|
|
221
296
|
return;
|
|
222
297
|
}
|
|
223
|
-
const pm = packageManager(root);
|
|
224
298
|
const hasCheckScript = hasCheckArchitectureScript(root);
|
|
225
299
|
const { tools, source } = args.compact && args.tools == null
|
|
226
300
|
? { tools: new Set(), source: 'compact-none' }
|
|
@@ -239,7 +313,6 @@ export function runInstallAgentGates(args) {
|
|
|
239
313
|
? 'no active host detected'
|
|
240
314
|
: 'default set — no agent config dirs found';
|
|
241
315
|
console.log(`Agent gates for: ${[...tools].sort().join(', ')} (${toolSource})`);
|
|
242
|
-
const templates = [];
|
|
243
316
|
// --skills-only refreshes just the canonical /ark-* skills, which are safe to
|
|
244
317
|
// overwrite (they track the package). The gate/instruction files (AGENTS.md,
|
|
245
318
|
// settings.json, CI workflow, rules) are the ones users customize, so a plain
|
|
@@ -253,84 +326,15 @@ export function runInstallAgentGates(args) {
|
|
|
253
326
|
`Added package.json script "typecheck": "${typecheckBootstrap.script}" (tsconfig present; local/CI parity).`
|
|
254
327
|
);
|
|
255
328
|
}
|
|
256
|
-
// Base gates: tool-agnostic contract + CI backstop, always written.
|
|
257
|
-
const compactHost = args.compact ? [...tools][0] ?? null : null;
|
|
258
|
-
templates.push([
|
|
259
|
-
'AGENTS.md',
|
|
260
|
-
args.compact ? compactAgentInstructions(root, compactHost) : agentInstructions(root),
|
|
261
|
-
]);
|
|
262
|
-
if (!args.compact || !compactHost || compactHost === 'claude') {
|
|
263
|
-
templates.push(['.mcp.json', mcpJson(root)]);
|
|
264
|
-
}
|
|
265
|
-
templates.push([
|
|
266
|
-
'.github/workflows/ark-check.yml',
|
|
267
|
-
(() => {
|
|
268
|
-
const deploy = detectDeployPathQuality(root);
|
|
269
|
-
return githubWorkflow(pm, detectCiNode(root), {
|
|
270
|
-
hasLintScript: deploy.hasLintScript,
|
|
271
|
-
hasTypecheckScript: deploy.hasTypecheckScript,
|
|
272
|
-
});
|
|
273
|
-
})(),
|
|
274
|
-
]);
|
|
275
|
-
if (tools.has('cursor')) {
|
|
276
|
-
templates.push(['.cursor/mcp.json', mcpJson(root)]);
|
|
277
|
-
if (!args.compact) templates.push(['.cursor/rules/ark.mdc', cursorRule(root)]);
|
|
278
|
-
}
|
|
279
|
-
if (tools.has('claude')) {
|
|
280
|
-
templates.push(['.claude/settings.json', claudeSettings(root)]);
|
|
281
|
-
}
|
|
282
|
-
if (tools.has('codex')) {
|
|
283
|
-
templates.push(['.codex/hooks.json', codexHooks(root)]);
|
|
284
|
-
templates.push(['.codex/config.toml', codexProjectConfig(root)]);
|
|
285
|
-
if (!args.compact) templates.push(['docs/ark-codex-config.toml', codexTomlSnippet(root)]);
|
|
286
|
-
}
|
|
287
|
-
if (tools.has('grok')) {
|
|
288
|
-
templates.push(['.grok/config.toml', grokProjectConfig(root)]);
|
|
289
|
-
templates.push(['.grok/hooks/ark-write-gate.json', grokHooks(root)]);
|
|
290
|
-
}
|
|
291
|
-
// Instruction-tier hosts: one shared rule text, host-specific path.
|
|
292
|
-
if (tools.has('windsurf')) {
|
|
293
|
-
templates.push(['.windsurf/rules/ark.md', instructionRule(root)]);
|
|
294
|
-
}
|
|
295
|
-
if (tools.has('cline')) {
|
|
296
|
-
templates.push(['.clinerules/ark.md', instructionRule(root)]);
|
|
297
|
-
}
|
|
298
|
-
if (tools.has('copilot')) {
|
|
299
|
-
templates.push(['.github/copilot-instructions.md', instructionRule(root)]);
|
|
300
|
-
}
|
|
301
|
-
if (tools.has('kiro')) {
|
|
302
|
-
templates.push(['.kiro/steering/ark.md', instructionRule(root)]);
|
|
303
|
-
}
|
|
304
|
-
if (tools.has('roo')) {
|
|
305
|
-
templates.push(['.roo/rules/ark.md', instructionRule(root)]);
|
|
306
|
-
}
|
|
307
|
-
if (tools.has('continue')) {
|
|
308
|
-
templates.push(['.continue/rules/ark.md', instructionRule(root)]);
|
|
309
|
-
}
|
|
310
|
-
// Gemini CLI reads GEMINI.md as its primary project context (it also reads
|
|
311
|
-
// AGENTS.md, but GEMINI.md wins when both are present), so the rule lives there.
|
|
312
|
-
if (tools.has('gemini')) {
|
|
313
|
-
templates.push(['GEMINI.md', instructionRule(root)]);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
// /ark-* skills for every detected tool that supports project-level commands.
|
|
317
|
-
// Stamp each with the shipping version so a later ark-check can flag skills
|
|
318
|
-
// left behind by an older Ark (see detectSkillGaps) without nagging about
|
|
319
|
-
// user edits to the body.
|
|
320
|
-
const version = arkPackageVersion();
|
|
321
|
-
const skills = skillTemplates().map(([name, content]) => [name, stampSkill(content, version)]);
|
|
322
|
-
const skillPaths = new Set();
|
|
323
|
-
if (!args.compact) {
|
|
324
|
-
for (const tool of tools) {
|
|
325
|
-
const target = SKILL_TOOL_TARGETS[tool];
|
|
326
|
-
if (!target) continue;
|
|
327
|
-
for (const [name, content] of skills) {
|
|
328
|
-
const relativePath = target(name);
|
|
329
|
-
skillPaths.add(relativePath);
|
|
330
|
-
templates.push([relativePath, content]);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
329
|
}
|
|
330
|
+
const catalog = buildManagedAssetCatalog({
|
|
331
|
+
root,
|
|
332
|
+
tools,
|
|
333
|
+
compact: args.compact,
|
|
334
|
+
skillsOnly: args.skillsOnly,
|
|
335
|
+
});
|
|
336
|
+
const { skills, skillPaths, version } = catalog;
|
|
337
|
+
const templates = catalog.assets.map(({ relativePath, content }) => [relativePath, content]);
|
|
334
338
|
|
|
335
339
|
// A compact router can be moved back from an explicit host removal. Delete the
|
|
336
340
|
// generic MCP file only when it exactly matches Ark's generated artifact.
|