arkgate 4.2.0 → 4.3.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +86 -4
  2. package/README.md +20 -6
  3. package/bin/ark-mcp-runtime.mjs +64 -0
  4. package/bin/ark-shared.mjs +16 -4
  5. package/bin/ark.mjs +55 -1
  6. package/bin/lib/adapter-contract.mjs +88 -5
  7. package/bin/lib/agent-projection-command.mjs +396 -0
  8. package/bin/lib/agent-projection.mjs +319 -0
  9. package/bin/lib/agent-skills-package.mjs +266 -0
  10. package/bin/lib/baseline-key.mjs +32 -0
  11. package/bin/lib/ci-and-commands.mjs +44 -0
  12. package/bin/lib/diagnostic-catalog.mjs +155 -0
  13. package/bin/lib/physical-cohesion.mjs +2 -1
  14. package/bin/lib/status-command.mjs +369 -0
  15. package/bin/lib/status-manifest.mjs +394 -0
  16. package/dist/eslint/index.cjs +3 -3
  17. package/dist/eslint/index.js +3 -3
  18. package/dist/index.cjs +46 -11
  19. package/dist/index.d.ts +729 -6
  20. package/dist/index.js +46 -11
  21. package/docs/README.md +6 -6
  22. package/docs/agent-guide.md +112 -14
  23. package/docs/configuration.md +7 -0
  24. package/docs/develop.md +8 -0
  25. package/docs/diagnostics.md +606 -0
  26. package/docs/package-surface.md +19 -8
  27. package/docs/product-voice.md +45 -0
  28. package/docs/use.md +23 -0
  29. package/package.json +11 -1
  30. package/schemas/ark.analysis-result.schema.json +14 -1
  31. package/schemas/ark.status-manifest.schema.json +244 -0
  32. package/server.json +2 -2
  33. package/templates/agent-skills/README.md +59 -0
  34. package/templates/agent-skills/ark-adopt/SKILL.md +171 -0
  35. package/templates/agent-skills/ark-architect/SKILL.md +175 -0
  36. package/templates/agent-skills/ark-autopilot/SKILL.md +242 -0
  37. package/templates/agent-skills/ark-contract/SKILL.md +136 -0
  38. package/templates/agent-skills/ark-coverage/SKILL.md +167 -0
  39. package/templates/agent-skills/ark-explain/SKILL.md +210 -0
  40. package/templates/agent-skills/ark-explore/SKILL.md +377 -0
  41. package/templates/agent-skills/ark-fix/SKILL.md +185 -0
  42. package/templates/agent-skills/ark-loop/SKILL.md +180 -0
  43. package/templates/agent-skills/ark-place/SKILL.md +162 -0
  44. package/templates/agent-skills/ark-runtime/SKILL.md +120 -0
  45. package/templates/agent-skills/ark-think/SKILL.md +133 -0
  46. package/templates/agent-skills/ark-upgrade/SKILL.md +218 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * ACS04 — version-matched agent contract projection (Tooling I/O).
3
+ *
4
+ * Gathers package version + ark.config layers + diagnostic short list, then
5
+ * builds/merges the non-authoritative projection via Domain pure helpers.
6
+ * Never prompts. Projection is never a gate input.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ import {
13
+ AGENT_PROJECTION_NON_ENFORCEMENT_LABEL,
14
+ ARK_AGENT_PROJECTION_SCHEMA_VERSION,
15
+ DEFAULT_AGENT_PROJECTION_RULE_IDS,
16
+ buildAgentProjectionBlock,
17
+ buildAgentProjectionMeta,
18
+ extractAgentProjectionBlock,
19
+ mergeAgentProjectionDocument,
20
+ parseAgentProjectionStamp,
21
+ projectionMatchesPackageVersion,
22
+ } from './agent-projection.mjs';
23
+ import { getDiagnosticCatalogEntry } from './diagnostic-catalog.mjs';
24
+ import {
25
+ arkCheckCommand,
26
+ loadConfigLayersForAgents,
27
+ } from './ci-and-commands.mjs';
28
+ import { isSelfHostedLibraryAgents } from './gate-files.mjs';
29
+ import { resolveEffectiveProjectRoot } from './project-root.mjs';
30
+ import { arkPackageVersion } from './skill-install.mjs';
31
+
32
+ function packageVersionFallback() {
33
+ try {
34
+ const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
35
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
36
+ return typeof pkg.version === 'string' ? pkg.version : 'unknown';
37
+ } catch {
38
+ return 'unknown';
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Resolve catalog short-list entries (ruleId + title) from the public catalog.
44
+ * @param {readonly string[]|null|undefined} [ruleIds]
45
+ */
46
+ export function resolveProjectionCatalogShortList(ruleIds) {
47
+ const ids =
48
+ Array.isArray(ruleIds) && ruleIds.length > 0 ? ruleIds : DEFAULT_AGENT_PROJECTION_RULE_IDS;
49
+ return ids.map((ruleId) => {
50
+ const entry = getDiagnosticCatalogEntry(ruleId);
51
+ return {
52
+ ruleId,
53
+ title: entry?.title ?? ruleId,
54
+ };
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Pure-ish facts for Domain build (after path/config load).
60
+ * @param {{
61
+ * root?: string,
62
+ * config?: string,
63
+ * arkgateVersion?: string,
64
+ * host?: string|null,
65
+ * profile?: 'compact'|'full'|null,
66
+ * layers?: Array<{name?: string, patterns?: string[], intentPrefixes?: string[], prefixes?: string[]}>|null,
67
+ * checkCommand?: string|null,
68
+ * }} [options]
69
+ */
70
+ export function collectAgentProjectionFacts(options = {}) {
71
+ const startRoot = path.resolve(options.root || process.cwd());
72
+ const configName = options.config || 'ark.config.json';
73
+ let resolvedRoot = startRoot;
74
+ try {
75
+ const resolved = resolveEffectiveProjectRoot(startRoot, {
76
+ configName,
77
+ writeMode: false,
78
+ });
79
+ resolvedRoot = path.resolve(resolved.root || startRoot);
80
+ } catch {
81
+ resolvedRoot = startRoot;
82
+ }
83
+
84
+ const version =
85
+ (typeof options.arkgateVersion === 'string' && options.arkgateVersion.trim()) ||
86
+ arkPackageVersion() ||
87
+ packageVersionFallback();
88
+
89
+ let layers = options.layers;
90
+ if (layers === undefined) {
91
+ layers = loadConfigLayersForAgents(resolvedRoot);
92
+ }
93
+
94
+ const layerSummaries = Array.isArray(layers)
95
+ ? layers.map((layer) => ({
96
+ name: layer.name ?? layer.layer ?? 'Unknown',
97
+ patterns: layer.patterns ?? [],
98
+ intentPrefixes: layer.intentPrefixes ?? layer.prefixes ?? [],
99
+ }))
100
+ : [];
101
+
102
+ const profile =
103
+ options.profile === 'compact' || options.profile === 'full'
104
+ ? options.profile
105
+ : null;
106
+
107
+ const checkCommand =
108
+ typeof options.checkCommand === 'string' && options.checkCommand.trim()
109
+ ? options.checkCommand.trim()
110
+ : arkCheckCommand(resolvedRoot);
111
+
112
+ const host =
113
+ typeof options.host === 'string' && options.host.trim()
114
+ ? options.host.trim().toLowerCase()
115
+ : null;
116
+
117
+ return {
118
+ arkgateVersion: version,
119
+ checkCommand,
120
+ layers: layerSummaries,
121
+ catalogShortList: resolveProjectionCatalogShortList(),
122
+ host,
123
+ profile: profile ?? 'full',
124
+ diagnosticsDocsPath: 'docs/diagnostics.md',
125
+ resolvedRoot,
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Build projection block + meta for a project (no write).
131
+ * @param {Parameters<typeof collectAgentProjectionFacts>[0]} [options]
132
+ */
133
+ export function buildProjectAgentProjection(options = {}) {
134
+ const facts = collectAgentProjectionFacts(options);
135
+ const block = buildAgentProjectionBlock(facts);
136
+ const meta = buildAgentProjectionMeta(facts);
137
+ return { facts, block, meta };
138
+ }
139
+
140
+ /**
141
+ * Plan a merge into AGENTS.md (default path) without writing.
142
+ * @param {{
143
+ * root?: string,
144
+ * config?: string,
145
+ * arkgateVersion?: string,
146
+ * host?: string|null,
147
+ * profile?: 'compact'|'full'|null,
148
+ * agentsPath?: string,
149
+ * targetRelativePath?: string,
150
+ * }} [options]
151
+ */
152
+ export function planAgentProjectionRefresh(options = {}) {
153
+ const built = buildProjectAgentProjection(options);
154
+ const root = built.facts.resolvedRoot;
155
+ const relativePath = options.targetRelativePath || options.agentsPath || 'AGENTS.md';
156
+ const absolutePath = path.isAbsolute(relativePath)
157
+ ? relativePath
158
+ : path.join(root, relativePath);
159
+
160
+ let existing = null;
161
+ if (fs.existsSync(absolutePath)) {
162
+ try {
163
+ existing = fs.readFileSync(absolutePath, 'utf8');
164
+ } catch {
165
+ existing = null;
166
+ }
167
+ }
168
+
169
+ const selfHosted = Boolean(existing && isSelfHostedLibraryAgents(existing));
170
+ const merge = mergeAgentProjectionDocument(existing, built.block);
171
+ const stamp = parseAgentProjectionStamp(built.block);
172
+ const existingStamp = existing ? parseAgentProjectionStamp(existing) : { arkgateVersion: null };
173
+ const versionMatch = projectionMatchesPackageVersion(built.block, built.facts.arkgateVersion);
174
+ const existingVersionMatch = existing
175
+ ? projectionMatchesPackageVersion(existing, built.facts.arkgateVersion)
176
+ : false;
177
+ const extracted = existing ? extractAgentProjectionBlock(existing) : { block: null };
178
+
179
+ return {
180
+ root,
181
+ path: absolutePath,
182
+ relativePath: path.isAbsolute(relativePath)
183
+ ? path.relative(root, relativePath) || 'AGENTS.md'
184
+ : relativePath,
185
+ selfHosted,
186
+ action: merge.action,
187
+ wouldWrite: merge.action !== 'unchanged',
188
+ contentIdentity: merge.contentIdentity,
189
+ previousBlockPresent: extracted.block != null,
190
+ preservedOutsideBlock: merge.preservedOutsideBlock,
191
+ packageVersion: built.facts.arkgateVersion,
192
+ stampedVersion: stamp.arkgateVersion,
193
+ existingStampedVersion: existingStamp.arkgateVersion,
194
+ versionMatch,
195
+ existingVersionMatch,
196
+ nonAuthoritative: true,
197
+ nonEnforcementLabel: AGENT_PROJECTION_NON_ENFORCEMENT_LABEL,
198
+ schemaVersion: ARK_AGENT_PROJECTION_SCHEMA_VERSION,
199
+ meta: built.meta,
200
+ block: built.block,
201
+ nextContent: merge.content,
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Apply a planned projection refresh (write file).
207
+ * @param {ReturnType<typeof planAgentProjectionRefresh>} plan
208
+ * @param {{ write?: boolean }} [opts]
209
+ */
210
+ export function applyAgentProjectionRefresh(plan, opts = {}) {
211
+ const shouldWrite = opts.write !== false;
212
+ if (!plan.wouldWrite) {
213
+ return { wrote: false, action: plan.action, path: plan.path };
214
+ }
215
+ if (!shouldWrite) {
216
+ return { wrote: false, action: plan.action, path: plan.path, dryRun: true };
217
+ }
218
+ fs.mkdirSync(path.dirname(plan.path), { recursive: true });
219
+ fs.writeFileSync(plan.path, plan.nextContent.endsWith('\n') ? plan.nextContent : `${plan.nextContent}\n`);
220
+ return { wrote: true, action: plan.action, path: plan.path };
221
+ }
222
+
223
+ /**
224
+ * CLI entry for `ark agents-md`. Never prompts.
225
+ *
226
+ * Flags (via args object):
227
+ * - write / apply: merge projection into AGENTS.md
228
+ * - check: exit 1 when stamp missing or version ≠ package (Ark-owned or block present)
229
+ * - stdout: print projection block only
230
+ * - json: machine-readable plan / result
231
+ *
232
+ * @param {{
233
+ * root?: string,
234
+ * config?: string,
235
+ * json?: boolean,
236
+ * write?: boolean,
237
+ * apply?: boolean,
238
+ * check?: boolean,
239
+ * stdout?: boolean,
240
+ * host?: string,
241
+ * profile?: 'compact'|'full',
242
+ * arkgateVersion?: string,
243
+ * writeOut?: (line: string) => void,
244
+ * writeErr?: (line: string) => void,
245
+ * }} args
246
+ * @returns {number} exit code
247
+ */
248
+ export function runAgentProjectionCommand(args = {}) {
249
+ const writeOut = args.writeOut ?? ((line) => console.log(line));
250
+ const writeErr = args.writeErr ?? ((line) => console.error(line));
251
+ const asJson = args.json === true || process.env.CI === '1' || process.env.CI === 'true';
252
+ const doWrite = args.write === true || args.apply === true;
253
+ const checkOnly = args.check === true;
254
+ const stdoutOnly = args.stdout === true;
255
+
256
+ try {
257
+ const plan = planAgentProjectionRefresh({
258
+ root: args.root,
259
+ config: args.config,
260
+ host: args.host,
261
+ profile: args.profile,
262
+ arkgateVersion: args.arkgateVersion,
263
+ });
264
+
265
+ if (stdoutOnly) {
266
+ if (asJson) {
267
+ writeOut(
268
+ JSON.stringify(
269
+ {
270
+ schemaVersion: plan.schemaVersion,
271
+ arkgateVersion: plan.packageVersion,
272
+ nonAuthoritative: true,
273
+ meta: plan.meta,
274
+ block: plan.block,
275
+ },
276
+ null,
277
+ 2
278
+ )
279
+ );
280
+ } else {
281
+ writeOut(plan.block.endsWith('\n') ? plan.block.slice(0, -1) : plan.block);
282
+ }
283
+ return 0;
284
+ }
285
+
286
+ if (checkOnly) {
287
+ // Drift: missing projection when file exists, or stamped version ≠ package.
288
+ const hasFile = fs.existsSync(plan.path);
289
+ const hasBlock = plan.previousBlockPresent;
290
+ let ok = true;
291
+ const reasons = [];
292
+ if (hasFile && hasBlock && !plan.existingVersionMatch) {
293
+ ok = false;
294
+ reasons.push(
295
+ `projection stamp ${plan.existingStampedVersion ?? '(none)'} ≠ package ${plan.packageVersion}`
296
+ );
297
+ }
298
+ if (hasFile && !hasBlock && !plan.selfHosted) {
299
+ // Consumer AGENTS without projection is drift for version-matched installs.
300
+ ok = false;
301
+ reasons.push('AGENTS.md is missing the managed agent-projection block');
302
+ }
303
+ // Self-hosted mother-repo may omit the block until maintainers insert it; not a hard fail
304
+ // unless a block is present with the wrong version.
305
+ if (plan.selfHosted && hasBlock && !plan.existingVersionMatch) {
306
+ ok = false;
307
+ }
308
+
309
+ if (asJson) {
310
+ writeOut(
311
+ JSON.stringify(
312
+ {
313
+ ok,
314
+ check: true,
315
+ reasons,
316
+ packageVersion: plan.packageVersion,
317
+ existingStampedVersion: plan.existingStampedVersion,
318
+ previousBlockPresent: plan.previousBlockPresent,
319
+ selfHosted: plan.selfHosted,
320
+ nonAuthoritative: true,
321
+ path: plan.relativePath,
322
+ },
323
+ null,
324
+ 2
325
+ )
326
+ );
327
+ } else if (ok) {
328
+ writeOut(
329
+ `agent projection OK — package ${plan.packageVersion}` +
330
+ (hasBlock ? ` (stamped ${plan.existingStampedVersion})` : ' (no block; self-hosted or absent)')
331
+ );
332
+ } else {
333
+ writeErr(`agent projection drift: ${reasons.join('; ')}`);
334
+ writeErr(`Fix: ark agents-md --write (regenerates the managed block; never a gate input)`);
335
+ }
336
+ return ok ? 0 : 1;
337
+ }
338
+
339
+ let applyResult = { wrote: false, action: plan.action, path: plan.path };
340
+ if (doWrite) {
341
+ applyResult = applyAgentProjectionRefresh(plan, { write: true });
342
+ }
343
+
344
+ if (asJson) {
345
+ writeOut(
346
+ JSON.stringify(
347
+ {
348
+ schemaVersion: plan.schemaVersion,
349
+ arkgateVersion: plan.packageVersion,
350
+ nonAuthoritative: true,
351
+ path: plan.relativePath,
352
+ action: applyResult.action,
353
+ wrote: applyResult.wrote,
354
+ wouldWrite: plan.wouldWrite,
355
+ contentIdentity: plan.contentIdentity,
356
+ preservedOutsideBlock: plan.preservedOutsideBlock,
357
+ selfHosted: plan.selfHosted,
358
+ meta: plan.meta,
359
+ ...(doWrite ? {} : { preview: true, block: plan.block }),
360
+ },
361
+ null,
362
+ 2
363
+ )
364
+ );
365
+ return 0;
366
+ }
367
+
368
+ if (doWrite) {
369
+ if (applyResult.wrote) {
370
+ writeOut(
371
+ `Wrote agent projection (${applyResult.action}) → ${plan.relativePath} · arkgate@${plan.packageVersion}`
372
+ );
373
+ writeOut('Non-authoritative: enforcement remains ark-check / hooks / CI.');
374
+ } else {
375
+ writeOut(
376
+ `Agent projection unchanged (${plan.action}) → ${plan.relativePath} · arkgate@${plan.packageVersion}`
377
+ );
378
+ }
379
+ } else {
380
+ writeOut(
381
+ `Agent projection preview · arkgate@${plan.packageVersion} · action=${plan.action}` +
382
+ (plan.wouldWrite ? ' (pass --write to apply)' : ' (already current)')
383
+ );
384
+ writeOut(` path: ${plan.relativePath}`);
385
+ writeOut(` contentIdentity: ${plan.contentIdentity}`);
386
+ writeOut(' nonAuthoritative: true (not a gate input)');
387
+ if (plan.selfHosted) {
388
+ writeOut(' note: self-hosted library AGENTS — merge updates the managed block only');
389
+ }
390
+ }
391
+ return 0;
392
+ } catch (error) {
393
+ writeErr(error instanceof Error ? error.message : String(error));
394
+ return 2;
395
+ }
396
+ }