arkgate 4.8.3 → 4.8.5

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 (70) hide show
  1. package/CHANGELOG.md +279 -1
  2. package/README.md +17 -7
  3. package/bin/ark-check-runtime.mjs +340 -5
  4. package/bin/ark-layer-match.mjs +170 -13
  5. package/bin/ark-mcp-runtime.mjs +9 -2
  6. package/bin/lib/agent-homes.mjs +41 -6
  7. package/bin/lib/analysis-completeness.mjs +86 -0
  8. package/bin/lib/analysis-engine.mjs +8 -8
  9. package/bin/lib/architecture-scan.mjs +2 -0
  10. package/bin/lib/ark-order-invariants.mjs +37 -0
  11. package/bin/lib/ark-order-sensors.mjs +4 -0
  12. package/bin/lib/arkrules-contract.mjs +8 -1
  13. package/bin/lib/check-args.mjs +68 -0
  14. package/bin/lib/config-contract.mjs +26 -0
  15. package/bin/lib/design-smells.mjs +85 -0
  16. package/bin/lib/diagnostic-catalog.mjs +9 -1
  17. package/bin/lib/first-run-help.mjs +16 -4
  18. package/bin/lib/invariant-coverage-io.mjs +175 -19
  19. package/bin/lib/invariant-coverage.mjs +110 -7
  20. package/bin/lib/literal-path-drift-io.mjs +569 -0
  21. package/bin/lib/literal-path-drift.mjs +761 -0
  22. package/bin/lib/policy-delta-io.mjs +5 -0
  23. package/bin/lib/remediation.mjs +25 -1
  24. package/bin/lib/rules-under-contract.mjs +5 -0
  25. package/bin/lib/scan-files.mjs +54 -0
  26. package/bin/lib/sensor-promote-cli.mjs +372 -0
  27. package/bin/lib/sensor-promote-io.mjs +246 -0
  28. package/bin/lib/sensor-promotion.mjs +363 -0
  29. package/bin/lib/skill-catalog-apply.mjs +18 -1
  30. package/bin/lib/skill-install.mjs +4 -3
  31. package/bin/lib/upgrade-whats-new.mjs +1 -1
  32. package/dist/{configTypes-dNJ2C0yx.d.ts → configTypes-dy5PfTqS.d.ts} +31 -0
  33. package/dist/{diagnosticCatalog-C5GgeyEE.d.ts → diagnosticCatalog-DMO30svh.d.ts} +75 -7
  34. package/dist/eslint/index.cjs +5 -5
  35. package/dist/eslint/index.d.ts +34 -1
  36. package/dist/eslint/index.js +5 -5
  37. package/dist/index.cjs +30 -30
  38. package/dist/index.d.ts +68 -5
  39. package/dist/index.js +29 -29
  40. package/dist/nestjs/index.cjs +5 -5
  41. package/dist/nestjs/index.d.ts +3 -3
  42. package/dist/nestjs/index.js +5 -5
  43. package/dist/order/index.cjs +1 -1
  44. package/dist/order/index.d.ts +11 -2
  45. package/dist/order/index.js +1 -1
  46. package/dist/runtime/index.cjs +11 -11
  47. package/dist/runtime/index.d.ts +30 -7
  48. package/dist/runtime/index.js +11 -11
  49. package/dist/{types-dK24fDZa.d.ts → types-BuM8WNqe.d.ts} +1 -1
  50. package/dist/{types-DeK7SYGC.d.ts → types-CzE6LMaW.d.ts} +2 -2
  51. package/docs/README.md +5 -4
  52. package/docs/agent-guide.md +186 -4
  53. package/docs/ai-gates.md +13 -2
  54. package/docs/arkorder.md +210 -0
  55. package/docs/configuration.md +88 -5
  56. package/docs/develop.md +4 -2
  57. package/docs/diagnostics.md +97 -1
  58. package/docs/enthusiast/how-to-agent-gates.md +1 -1
  59. package/docs/package-surface.md +37 -5
  60. package/docs/use.md +1 -1
  61. package/package.json +3 -2
  62. package/schemas/ark.config.schema.json +63 -0
  63. package/server.json +3 -3
  64. package/templates/agent-skills/README.md +1 -1
  65. package/templates/agent-skills/ark-adopt/SKILL.md +5 -0
  66. package/templates/agent-skills/ark-coverage/SKILL.md +1 -0
  67. package/templates/agent-skills/ark-upgrade/SKILL.md +1 -1
  68. package/templates/skills/ark-adopt.md +5 -0
  69. package/templates/skills/ark-coverage.md +1 -0
  70. package/templates/skills/ark-upgrade.md +1 -1
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Shared agent home skill catalogs (Claude / Grok), Codex-parity monotonic install.
3
- * Repo catalogs stay per-project; these homes are the machine floor (never downgrade).
2
+ * Shared agent home skill catalogs (Claude / Grok / Antigravity), Codex-parity
3
+ * monotonic install. Repo catalogs stay per-project; these homes are the
4
+ * machine floor (never downgrade). Antigravity's official global catalog is
5
+ * `~/.gemini/config/skills` — not pruned when a project `.agents/skills` exists.
4
6
  */
5
7
  import fs from 'node:fs';
6
8
  import os from 'node:os';
@@ -23,7 +25,7 @@ import {
23
25
  skillInstallLine,
24
26
  } from './skill-write.mjs';
25
27
 
26
- /** @typedef {'claude'|'grok'} AgentHomeHost */
28
+ /** @typedef {'claude'|'grok'|'antigravity'} AgentHomeHost */
27
29
 
28
30
  const HOSTS = {
29
31
  claude: {
@@ -40,6 +42,14 @@ const HOSTS = {
40
42
  defaultDirName: '.grok',
41
43
  flag: '--grok-home',
42
44
  },
45
+ antigravity: {
46
+ id: 'antigravity',
47
+ label: 'Antigravity',
48
+ envKey: 'ANTIGRAVITY_HOME',
49
+ // Official global catalog is ~/.gemini/config/skills (docs/skills).
50
+ defaultDirName: path.join('.gemini', 'config'),
51
+ flag: '--antigravity-home',
52
+ },
43
53
  };
44
54
 
45
55
  export function agentHomeHostIds() {
@@ -62,6 +72,18 @@ export function grokSkillsDir(env = process.env, homeDir = os.homedir()) {
62
72
  return path.join(grokHomeDir(env, homeDir), 'skills');
63
73
  }
64
74
 
75
+ export function antigravityHomeDir(env = process.env, homeDir = os.homedir()) {
76
+ return resolveHomeDir(HOSTS.antigravity, env, homeDir);
77
+ }
78
+
79
+ export function antigravitySkillsDir(env = process.env, homeDir = os.homedir()) {
80
+ return path.join(antigravityHomeDir(env, homeDir), 'skills');
81
+ }
82
+
83
+ export function usesDefaultAntigravityHome(env = process.env, homeDir = os.homedir()) {
84
+ return usesDefaultHome(HOSTS.antigravity, env, homeDir);
85
+ }
86
+
65
87
  export function usesDefaultClaudeHome(env = process.env, homeDir = os.homedir()) {
66
88
  return usesDefaultHome(HOSTS.claude, env, homeDir);
67
89
  }
@@ -85,7 +107,8 @@ function usesDefaultHome(spec, env, homeDir) {
85
107
  }
86
108
 
87
109
  function skillsDirFor(host, env = process.env) {
88
- return host === 'grok' ? grokSkillsDir(env) : claudeSkillsDir(env);
110
+ const spec = HOSTS[host];
111
+ return path.join(resolveHomeDir(spec, env), 'skills');
89
112
  }
90
113
 
91
114
  function readHomeCatalogFloor(skillsDir) {
@@ -124,7 +147,7 @@ function homeInPlay(parity, catalogState) {
124
147
  }
125
148
 
126
149
  /**
127
- * Detect Claude/Grok user-home ark-* catalogs that lag this package.
150
+ * Detect Claude/Grok/Antigravity user-home ark-* catalogs that lag this package.
128
151
  * Absent homes are not debt. Stamp-only body-match is not content-behind
129
152
  * (assessSkillCatalogParity already treats identity match as current).
130
153
  *
@@ -199,11 +222,13 @@ export function detectAgentHomeGaps(root, env = process.env) {
199
222
  /**
200
223
  * Claude home is loaded by Claude Code and often by Cursor. Treat both as in-session.
201
224
  * Grok home is urgent only on a Grok session (or when ARK_ACTIVE_HOST=grok).
225
+ * Antigravity global catalog is urgent on an Antigravity session (agy / ANTIGRAVITY).
202
226
  */
203
227
  export function agentHomeConcernIsActive(host, env = process.env) {
204
228
  const active = detectActiveAgentHost(env);
205
229
  if (host === 'claude') return active === 'claude' || active === 'cursor' || !active;
206
230
  if (host === 'grok') return active === 'grok' || !active;
231
+ if (host === 'antigravity') return active === 'antigravity' || !active;
207
232
  return true;
208
233
  }
209
234
 
@@ -223,6 +248,7 @@ export function agentHomeRefreshCommand(root, gap) {
223
248
  * force?: boolean,
224
249
  * claudeHome?: boolean,
225
250
  * grokHome?: boolean,
251
+ * antigravityHome?: boolean,
226
252
  * agentHomes?: boolean,
227
253
  * json?: boolean,
228
254
  * env?: NodeJS.ProcessEnv,
@@ -233,13 +259,22 @@ export function installRequestedAgentHomes(args) {
233
259
  const env = args.env ?? process.env;
234
260
  const wantClaude = Boolean(args.claudeHome || args.agentHomes);
235
261
  const wantGrok = Boolean(args.grokHome || args.agentHomes);
236
- if (!wantClaude && !wantGrok) return [];
262
+ const wantAntigravity = Boolean(args.antigravityHome || args.agentHomes);
263
+ if (!wantClaude && !wantGrok && !wantAntigravity) return [];
237
264
  const skills = args.skills ?? skillTemplates();
238
265
  const version = args.version ?? arkPackageVersion();
239
266
  const installed = [];
240
267
  const targets = [
241
268
  wantClaude ? { host: 'claude', spec: HOSTS.claude, dir: claudeSkillsDir(env), usesDefault: usesDefaultClaudeHome(env) } : null,
242
269
  wantGrok ? { host: 'grok', spec: HOSTS.grok, dir: grokSkillsDir(env), usesDefault: usesDefaultGrokHome(env) } : null,
270
+ wantAntigravity
271
+ ? {
272
+ host: 'antigravity',
273
+ spec: HOSTS.antigravity,
274
+ dir: antigravitySkillsDir(env),
275
+ usesDefault: usesDefaultAntigravityHome(env),
276
+ }
277
+ : null,
243
278
  ].filter(Boolean);
244
279
 
245
280
  for (const target of targets) {
@@ -26,3 +26,89 @@ export function analysisIncompleteStatement(completeness) {
26
26
  ? 'Analysis incomplete: governed parse diagnostics prevent a complete architecture verdict.'
27
27
  : 'Analysis unavailable: no API-compatible TypeScript host could produce architecture evidence.';
28
28
  }
29
+
30
+ /** Public diagnostic id for a verdict asked to certify an empty file set. */
31
+ export const EMPTY_ANALYSIS_RULE_ID = 'ANALYSIS_COVERS_NO_FILES';
32
+
33
+ /**
34
+ * Refuse a verdict over zero governed files when the tree has source to govern.
35
+ *
36
+ * Every rule is vacuously satisfied on an empty set, so a green here certifies
37
+ * nothing while reading exactly like a green over a governed tree. That is a
38
+ * false green — the one failure mode CI trusts.
39
+ *
40
+ * The refusal separates ArkGate's own limitation from a fact about the repo, the
41
+ * same way the coverage budget does. Two states are a mismatch and refuse:
42
+ *
43
+ * - source exists under the analyzed root and the contract governs none of it —
44
+ * the contract does not describe this tree;
45
+ * - the analyzed root is not the root the caller asked for, because the contract
46
+ * was found outside it and its directory was adopted — ArkGate checked a
47
+ * different tree and found nothing in it.
48
+ *
49
+ * No governable source anywhere under the root, on the root the caller asked
50
+ * for, is a genuinely greenfield repo: `--init` is designed to land a contract
51
+ * before the code arrives, that is not a mismatch to refuse, and `--plan` /
52
+ * `--doctor` already carry the `empty-scope` adoption gap for it.
53
+ *
54
+ * Report modes (`--plan`, `--doctor`, `--coverage`) are how a user diagnoses and
55
+ * fixes an empty scope, so callers must not apply this refusal to them. It
56
+ * belongs to the verdict path. (`--adopt-contract` and `--suggest-include` never
57
+ * reach it: their handlers return earlier.)
58
+ *
59
+ * `ungovernedSourceCount` must come from a probe the contract cannot steer — see
60
+ * `countUngovernedSourceFiles` in scan-files.mjs. Feeding it a count that honours
61
+ * `config.exclude` reopens the false green through `exclude: ["**"]`.
62
+ *
63
+ * @param {{
64
+ * governedFileCount?: number,
65
+ * ungovernedSourceCount?: number,
66
+ * ungovernedSourceCap?: number,
67
+ * root?: string,
68
+ * requestedRoot?: string,
69
+ * configPath?: string,
70
+ * configWalkedUp?: boolean,
71
+ * }} [input]
72
+ * @returns {{ ruleId: string, message: string, nextAction: string } | null}
73
+ */
74
+ export function emptyAnalysisRefusal(input = {}) {
75
+ const count = Number(input.governedFileCount);
76
+ if (!Number.isFinite(count) || count !== 0) return null;
77
+
78
+ const ungoverned = Math.max(0, Number(input.ungovernedSourceCount) || 0);
79
+ const cap = Math.max(0, Number(input.ungovernedSourceCap) || 0);
80
+ const root = String(input.root ?? '');
81
+ const configPath = String(input.configPath ?? '');
82
+ const requestedRoot = String(input.requestedRoot ?? '');
83
+ const movedRoot =
84
+ input.configWalkedUp === true && requestedRoot.length > 0 && requestedRoot !== root;
85
+
86
+ // Greenfield on the root the caller asked for: nothing to govern, nothing to mistake.
87
+ if (ungoverned === 0 && !movedRoot) return null;
88
+
89
+ // The probe stops at a cap, so say "at least N" rather than claim a census it never took.
90
+ const counted = cap > 0 && ungoverned >= cap ? `at least ${ungoverned}` : `${ungoverned}`;
91
+
92
+ const evidence = movedRoot
93
+ ? `${counted} source file(s) exist under ${root}, which is not the ${requestedRoot} this ` +
94
+ `run was asked for — the contract at ${configPath} lives outside it`
95
+ : `${counted} source file(s) exist under ${root} and none of them matched the include and ` +
96
+ `layer patterns in ${configPath}`;
97
+
98
+ const message =
99
+ `Analysis covered 0 files: ${evidence}. Every rule is vacuously satisfied on an empty set, ` +
100
+ 'so a pass here would certify nothing.';
101
+
102
+ const scopeHint = movedRoot
103
+ ? `This run analyzed ${root}, not the ${requestedRoot} you asked for: the contract at ` +
104
+ `${configPath} lives outside ${requestedRoot}, and ark-check adopted the directory holding ` +
105
+ 'it as the project root. Pass a contract inside the tree you want checked.'
106
+ : `Point --root at the tree the contract describes (this run analyzed ${root}), or fix the ` +
107
+ `include / exclude / layer patterns in ${configPath} so they match real files.`;
108
+
109
+ const nextAction =
110
+ `${scopeHint} \`npx arkgate-check --root . --plan\` and \`--coverage\` report the empty scope ` +
111
+ 'without refusing, and `--adopt-contract --write` proposes an include that matches this tree.';
112
+
113
+ return { ruleId: EMPTY_ANALYSIS_RULE_ID, message, nextAction };
114
+ }