From f4d7aff847039db4fc443c28cdaf7ac578c9aab7 Mon Sep 17 00:00:00 2001
From: Serhii Zhabskyi
Date: Sat, 28 Mar 2026 22:27:43 +0100
Subject: [PATCH 1/3] refactor(targets): register builtins via target
descriptors
Each target exports a TargetDescriptor from its index; catalog/registry consume path resolvers and import builders. Remove import-map-targets, slim map-targets/import-map. Simplify init detection, tweak schema and eslint. Add unit coverage for extend load, install sync/fetch/native scan, descriptor dispatch and paths; update e2e and integration matrices. Remove obsolete smoke and qa report; refresh lock and tasks.
Made-with: Cursor
---
.agentsmesh/.lock | 4 +-
eslint.config.js | 12 +-
package.json | 2 +-
src/cli/commands/init-detect.ts | 38 +--
src/cli/commands/init.ts | 25 +-
src/config/core/schema.ts | 2 +-
src/core/reference/import-map-targets.ts | 11 -
src/core/reference/import-map.ts | 52 +---
src/core/reference/map-targets.ts | 112 +-------
src/targets/catalog/builtin-targets.ts | 231 ++-------------
src/targets/catalog/registry.ts | 40 ++-
src/targets/catalog/target-descriptor.ts | 69 +++++
src/targets/catalog/target-ids.ts | 28 ++
src/targets/claude-code/index.ts | 34 +++
src/targets/cline/index.ts | 42 ++-
src/targets/codex-cli/index.ts | 42 ++-
src/targets/continue/index.ts | 38 ++-
src/targets/copilot/index.ts | 46 ++-
src/targets/cursor/index.ts | 34 +++
src/targets/gemini-cli/index.ts | 48 +++-
src/targets/junie/index.ts | 48 +++-
src/targets/windsurf/index.ts | 42 ++-
tasks/lessons.md | 3 +
tasks/reports/qa/qa-report-2026-03-23-2.md | 92 ------
tasks/todo.md | 190 +++++++++++++
.../agents-folder-structure-research.test.ts | 12 +-
tests/e2e/agents-last-run.md | 2 +-
tests/e2e/agents.e2e.test.ts | 41 +--
tests/e2e/extends-remote.e2e.test.ts | 2 +-
.../.claude/skills/api-generator/template.ts | 1 -
tests/e2e/generate-check.e2e.test.ts | 2 +-
...erate-reference-rewrite-matrix.e2e.test.ts | 16 +-
tests/e2e/helpers/assertions.ts | 16 +-
tests/e2e/helpers/reference-matrix.ts | 2 +-
tests/e2e/helpers/reference-targets.ts | 11 +-
tests/e2e/helpers/run-cli.ts | 2 +-
.../e2e/import-reference-rewrite.e2e.test.ts | 30 +-
tests/e2e/lint.e2e.test.ts | 2 +-
tests/e2e/merge.e2e.test.ts | 2 +-
tests/e2e/multi-extend-precedence.e2e.test.ts | 10 +-
tests/e2e/watch-features.e2e.test.ts | 6 +-
tests/import-generate-roundtrip.test.ts | 5 +-
.../extends-native.integration.test.ts | 49 ++--
tests/integration/lint.integration.test.ts | 2 +-
tests/unit/canonical/extend-load.test.ts | 232 ++++++++++++++++
tests/unit/canonical/extends.test.ts | 113 +++-----
tests/unit/canonical/mcp.test.ts | 5 +-
tests/unit/canonical/merge.test.ts | 3 +-
tests/unit/cli/commands/init-detect.test.ts | 53 ++++
tests/unit/config/conversions.test.ts | 1 +
tests/unit/config/lock.test.ts | 2 +
tests/unit/config/remote-fetcher.test.ts | 2 +-
tests/unit/core/descriptor-dispatch.test.ts | 98 +++++++
.../unit/core/link-rebaser-edge-cases.test.ts | 10 +-
tests/unit/core/reference-map.test.ts | 1 -
.../unit/install/fetch-install-source.test.ts | 196 +++++++++++++
.../install/gemini-install-commands.test.ts | 73 +++++
tests/unit/install/git-pin.test.ts | 8 +
tests/unit/install/install-conflicts.test.ts | 2 +-
tests/unit/install/install-sync.test.ts | 156 +++++++++++
tests/unit/install/name-generator.test.ts | 5 +
...native-install-scope.claude-cursor.test.ts | 8 +-
.../native-install-scope.codex.test.ts | 8 +-
...tall-scope.copilot-continue-gemini.test.ts | 8 +-
.../install/native-install-scope.helpers.ts | 2 +-
...l-scope.junie-cline-windsurf-codex.test.ts | 8 +-
tests/unit/install/native-path-pick.test.ts | 126 +++++++++
tests/unit/install/native-skill-scan.test.ts | 103 +++++++
tests/unit/install/pack-merge.test.ts | 2 +-
tests/unit/install/pack-reader.test.ts | 8 +-
tests/unit/install/pack-settings.test.ts | 3 +-
tests/unit/install/pack-writer.test.ts | 1 -
.../install/prepare-install-discovery.test.ts | 37 +--
tests/unit/install/resource-selection.test.ts | 15 -
tests/unit/smoke.test.ts | 7 -
.../targets/claude-code/generator.test.ts | 8 +-
.../claude-code/settings-helpers.test.ts | 7 +-
tests/unit/targets/cline/importer.test.ts | 6 +-
tests/unit/targets/cline/mcp-mapper.test.ts | 9 +-
.../unit/targets/cline/skills-helpers.test.ts | 7 +-
.../copilot/agents-skills-helpers.test.ts | 7 +-
.../unit/targets/copilot/hook-assets.test.ts | 2 +-
.../unit/targets/copilot/hook-parser.test.ts | 9 +-
tests/unit/targets/cursor/generator.test.ts | 7 +-
.../targets/cursor/settings-helpers.test.ts | 7 +-
.../targets/cursor/skills-helpers.test.ts | 5 +-
tests/unit/targets/descriptor-paths.test.ts | 262 ++++++++++++++++++
.../unit/targets/gemini-cli/generator.test.ts | 12 +-
.../targets/import-embedded-skill.test.ts | 13 +-
tests/unit/targets/registry.test.ts | 71 +++++
tests/unit/targets/target-ids.test.ts | 28 ++
tests/unit/targets/windsurf/generator.test.ts | 2 -
vitest.config.ts | 1 -
93 files changed, 2462 insertions(+), 795 deletions(-)
delete mode 100644 src/core/reference/import-map-targets.ts
create mode 100644 src/targets/catalog/target-descriptor.ts
create mode 100644 src/targets/catalog/target-ids.ts
delete mode 100644 tasks/reports/qa/qa-report-2026-03-23-2.md
create mode 100644 tests/unit/canonical/extend-load.test.ts
create mode 100644 tests/unit/cli/commands/init-detect.test.ts
create mode 100644 tests/unit/core/descriptor-dispatch.test.ts
create mode 100644 tests/unit/install/fetch-install-source.test.ts
create mode 100644 tests/unit/install/install-sync.test.ts
create mode 100644 tests/unit/install/native-skill-scan.test.ts
delete mode 100644 tests/unit/smoke.test.ts
create mode 100644 tests/unit/targets/descriptor-paths.test.ts
create mode 100644 tests/unit/targets/target-ids.test.ts
diff --git a/.agentsmesh/.lock b/.agentsmesh/.lock
index f6d18426..390195fd 100644
--- a/.agentsmesh/.lock
+++ b/.agentsmesh/.lock
@@ -1,9 +1,9 @@
# Auto-generated. DO NOT EDIT MANUALLY.
# Tracks the state of all config files for team conflict resolution.
-generated_at: 2026-03-28T10:40:11.652Z
+generated_at: 2026-03-28T20:42:47.814Z
generated_by: serhii
-lib_version: 0.2.5
+lib_version: 0.2.6
checksums:
agents/code-debugger.md: sha256:707132841c606f117c83491d53ce101be0117eb50abe2861bcf93bdd45a56daf
agents/code-documenter.md: sha256:faa66b16d2e86578985e817d60e6705ae0e34a716c1f5c29411739a6d659fb96
diff --git a/eslint.config.js b/eslint.config.js
index 1b2f8dd3..e22ea3ff 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -6,7 +6,7 @@ import importPlugin from 'eslint-plugin-import';
export default [
eslint.configs.recommended,
{
- files: ['src/**/*.ts'],
+ files: ['src/**/*.ts', 'tests/**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
@@ -15,6 +15,12 @@ export default [
console: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
+ fetch: 'readonly',
+ URL: 'readonly',
+ TextDecoder: 'readonly',
+ document: 'readonly',
+ Buffer: 'readonly',
+ NodeJS: 'readonly',
},
},
plugins: {
@@ -24,7 +30,7 @@ export default [
rules: {
'no-unused-vars': 'off', // use @typescript-eslint/no-unused-vars
'@typescript-eslint/no-explicit-any': 'error',
- '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': [
'warn',
{ allowExpressions: true },
@@ -37,6 +43,6 @@ export default [
},
},
{
- ignores: ['dist/', 'coverage/', 'node_modules/', '*.config.*'],
+ ignores: ['dist/', 'coverage/', 'node_modules/', '*.config.*', 'website/dist/', 'website/.astro/'],
},
];
diff --git a/package.json b/package.json
index c9af08df..2780fef6 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts",
- "lint": "eslint src/",
+ "lint": "eslint src tests",
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"prepare": "husky",
diff --git a/src/cli/commands/init-detect.ts b/src/cli/commands/init-detect.ts
index 2d9d5861..10fa4f8d 100644
--- a/src/cli/commands/init-detect.ts
+++ b/src/cli/commands/init-detect.ts
@@ -4,39 +4,13 @@
import { join } from 'node:path';
import { exists } from '../../utils/filesystem/fs.js';
+import { BUILTIN_TARGETS } from '../../targets/catalog/builtin-targets.js';
-/** AI tool indicators for detection. */
-export const TOOL_INDICATORS: Array<{ id: string; paths: string[] }> = [
- { id: 'claude-code', paths: ['CLAUDE.md', '.claude/rules', '.claude/commands'] },
- { id: 'cursor', paths: ['.cursor/rules', '.cursor/mcp.json'] },
- {
- id: 'copilot',
- paths: [
- '.github/copilot-instructions.md',
- '.github/copilot',
- '.github/instructions',
- '.github/prompts',
- '.github/skills',
- '.github/agents',
- '.github/hooks',
- ],
- },
- { id: 'continue', paths: ['.continue/rules', '.continue/skills', '.continue/mcpServers'] },
- {
- id: 'junie',
- paths: [
- '.junie/guidelines.md',
- '.junie/AGENTS.md',
- '.junie/skills',
- '.junie/mcp/mcp.json',
- '.aiignore',
- ],
- },
- { id: 'gemini-cli', paths: ['GEMINI.md', '.gemini'] },
- { id: 'cline', paths: ['.clinerules', '.cline'] },
- { id: 'codex-cli', paths: ['codex.md'] },
- { id: 'windsurf', paths: ['.windsurfrules', '.windsurf'] },
-];
+/** AI tool indicators for detection — derived from target descriptors. */
+export const TOOL_INDICATORS: Array<{ id: string; paths: string[] }> = BUILTIN_TARGETS.map((d) => ({
+ id: d.id,
+ paths: [...d.detectionPaths],
+}));
/**
* Detect existing AI tool configs in the project.
diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts
index d1ffdc96..db9a36a4 100644
--- a/src/cli/commands/init.ts
+++ b/src/cli/commands/init.ts
@@ -6,15 +6,7 @@
import { join } from 'node:path';
import { exists, readFileSafe, writeFileAtomic, mkdirp } from '../../utils/filesystem/fs.js';
import { logger } from '../../utils/output/logger.js';
-import { importFromClaudeCode } from '../../targets/claude-code/importer.js';
-import { importFromCursor } from '../../targets/cursor/importer.js';
-import { importFromCopilot } from '../../targets/copilot/importer.js';
-import { importFromContinue } from '../../targets/continue/importer.js';
-import { importFromJunie } from '../../targets/junie/importer.js';
-import { importFromGemini } from '../../targets/gemini-cli/importer.js';
-import { importFromCline } from '../../targets/cline/importer.js';
-import { importFromCodex } from '../../targets/codex-cli/importer.js';
-import { importFromWindsurf } from '../../targets/windsurf/importer.js';
+import { BUILTIN_TARGETS } from '../../targets/catalog/builtin-targets.js';
import type { ImportResult } from '../../core/types.js';
import {
buildConfig,
@@ -35,17 +27,10 @@ const CONFIG_FILENAME = 'agentsmesh.yaml';
const LOCAL_CONFIG_FILENAME = 'agentsmesh.local.yaml';
const GITIGNORE_ENTRIES = ['agentsmesh.local.yaml', '.agentsmeshcache', '.agentsmesh/.lock.tmp'];
-const IMPORTERS: Record Promise> = {
- 'claude-code': importFromClaudeCode,
- cursor: importFromCursor,
- copilot: importFromCopilot,
- continue: importFromContinue,
- junie: importFromJunie,
- 'gemini-cli': importFromGemini,
- cline: importFromCline,
- 'codex-cli': importFromCodex,
- windsurf: importFromWindsurf,
-};
+/** Importers derived from target descriptors — no manual registration needed. */
+const IMPORTERS: Record Promise> = Object.fromEntries(
+ BUILTIN_TARGETS.map((d) => [d.id, (root: string) => d.generators.importFrom(root)]),
+);
/**
* Append entries to .gitignore if not already present.
diff --git a/src/config/core/schema.ts b/src/config/core/schema.ts
index cca82b48..e9afb88e 100644
--- a/src/config/core/schema.ts
+++ b/src/config/core/schema.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { TARGET_IDS } from '../../targets/catalog/builtin-targets.js';
+import { TARGET_IDS } from '../../targets/catalog/target-ids.js';
const VALID_FEATURES = [
'rules',
diff --git a/src/core/reference/import-map-targets.ts b/src/core/reference/import-map-targets.ts
deleted file mode 100644
index 2f9975dc..00000000
--- a/src/core/reference/import-map-targets.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export {
- buildClaudeCodeImportPaths,
- buildCursorImportPaths,
- buildCopilotImportPaths,
- buildContinueImportPaths,
- buildJunieImportPaths,
- buildGeminiCliImportPaths,
- buildClineImportPaths,
- buildCodexCliImportPaths,
- buildWindsurfImportPaths,
-} from './import-map-builders.js';
diff --git a/src/core/reference/import-map.ts b/src/core/reference/import-map.ts
index 45f95921..f4e725d8 100644
--- a/src/core/reference/import-map.ts
+++ b/src/core/reference/import-map.ts
@@ -1,57 +1,13 @@
-import {
- buildClaudeCodeImportPaths,
- buildCursorImportPaths,
- buildCopilotImportPaths,
- buildContinueImportPaths,
- buildJunieImportPaths,
- buildGeminiCliImportPaths,
- buildClineImportPaths,
- buildCodexCliImportPaths,
- buildWindsurfImportPaths,
-} from './import-map-targets.js';
+import { getBuiltinTargetDefinition } from '../../targets/catalog/builtin-targets.js';
export async function buildImportReferenceMap(
target: string,
projectRoot: string,
): Promise
-AgentsMesh maintains a single canonical configuration in `.agentsmesh/` and syncs it bidirectionally to Claude Code, Cursor, Copilot, Continue, Junie, Gemini CLI, Cline, Codex CLI, and Windsurf. Rules, commands, agents, skills, MCP servers, hooks, ignore patterns, and permissions -- all from one source of truth.
+AgentsMesh maintains a single canonical configuration in `.agentsmesh/` and syncs it bidirectionally to Claude Code, Cursor, Copilot, Continue, Junie, Gemini CLI, Cline, Codex CLI, Windsurf, and Antigravity. Rules, commands, agents, skills, MCP servers, hooks, ignore patterns, and permissions -- all from one source of truth.
> **Full documentation: [samplexbro.github.io/agentsmesh](https://samplexbro.github.io/agentsmesh)**
@@ -80,19 +80,21 @@ That's it. Your `.agentsmesh/` directory is now the single source of truth, and
## Supported Tools
-| Feature | Claude Code | Cursor | Copilot | Gemini CLI | Cline | Codex CLI | Windsurf | Continue | Junie |
-|---------------|:-----------:|:-------:|:-------:|:----------:|:-------:|:---------:|:--------:|:--------:|:--------:|
-| Rules | Native | Native | Native | Native | Native | Native | Native | Native | Native |
-| Commands | Native | Native | Native | Native | Native | Embedded | Native | Embedded | Embedded |
-| Agents | Native | Native | Native | Native | Embedded| Native | Embedded | -- | Embedded |
-| Skills | Native | Native | Native | Native | Native | Native | Native | Embedded | Embedded |
-| MCP Servers | Native | Native | -- | Native | Native | Native | Partial | Native | Native |
-| Hooks | Native | Native | Partial | Partial | -- | -- | Native | -- | -- |
-| Ignore | Native | Native | -- | Native | Native | -- | Native | -- | Native |
-| Permissions | Native | Partial | -- | Partial | -- | -- | -- | -- | -- |
+| Feature | Claude Code | Cursor | Copilot | Gemini CLI | Cline | Codex CLI | Windsurf | Continue | Junie | Antigravity |
+|---------------|:-----------:|:-------:|:-------:|:----------:|:-------:|:---------:|:--------:|:--------:|:--------:|:-----------:|
+| Rules | Native | Native | Native | Native | Native | Native | Native | Native | Native | Native |
+| Commands | Native | Native | Native | Native | Native | Embedded | Native | Embedded | Embedded | Partial |
+| Agents | Native | Native | Native | Native | Embedded| Native | Embedded | -- | Embedded | -- |
+| Skills | Native | Native | Native | Native | Native | Native | Native | Embedded | Embedded | Native |
+| MCP Servers | Native | Native | -- | Native | Native | Native | Partial | Native | Native | -- |
+| Hooks | Native | Native | Partial | Partial | -- | -- | Native | -- | -- | -- |
+| Ignore | Native | Native | -- | Native | Native | -- | Native | -- | Native | -- |
+| Permissions | Native | Partial | -- | Partial | -- | -- | -- | -- | -- | -- |
See the [full feature matrix docs](https://samplexbro.github.io/agentsmesh/reference/supported-tools/) for details on native vs. embedded support.
+**Note:** The canonical root rule always lives at `.agentsmesh/rules/_root.md`. Some targets write that content to a tool-specific main file named `general` (for example `.continue/rules/general.md` and `.agents/rules/general.md` for Antigravity) instead of `_root.md` on disk.
+
---
## Documentation
diff --git a/agentsmesh.yaml b/agentsmesh.yaml
index 7fcccd2a..5fabdc44 100644
--- a/agentsmesh.yaml
+++ b/agentsmesh.yaml
@@ -9,6 +9,7 @@ targets:
- windsurf
- continue
- junie
+ - antigravity
features:
- rules
- commands
diff --git a/src/core/reference/import-map-builders.ts b/src/core/reference/import-map-builders.ts
index 5c2fe505..4de2a34f 100644
--- a/src/core/reference/import-map-builders.ts
+++ b/src/core/reference/import-map-builders.ts
@@ -1,5 +1,13 @@
import { basename } from 'node:path';
import { JUNIE_DOT_AGENTS, JUNIE_GUIDELINES } from '../../targets/junie/constants.js';
+import {
+ ANTIGRAVITY_RULES_ROOT,
+ ANTIGRAVITY_RULES_ROOT_LEGACY,
+ ANTIGRAVITY_RULES_DIR,
+ ANTIGRAVITY_WORKFLOWS_DIR,
+ ANTIGRAVITY_SKILLS_DIR,
+} from '../../targets/antigravity/constants.js';
+import { CONTINUE_ROOT_RULE, CONTINUE_ROOT_RULE_LEGACY } from '../../targets/continue/constants.js';
import {
addScopedAgentsMappings,
addSimpleFileMapping,
@@ -87,8 +95,12 @@ export async function buildContinueImportPaths(
refs: Map,
projectRoot: string,
): Promise {
+ refs.set(CONTINUE_ROOT_RULE, `${AB_RULES}/_root.md`);
+ refs.set(CONTINUE_ROOT_RULE_LEGACY, `${AB_RULES}/_root.md`);
for (const absPath of await listFiles(projectRoot, '.continue/rules')) {
- addSimpleFileMapping(refs, rel(projectRoot, absPath), AB_RULES, '.md');
+ const relPath = rel(projectRoot, absPath);
+ if (relPath === CONTINUE_ROOT_RULE || relPath === CONTINUE_ROOT_RULE_LEGACY) continue;
+ addSimpleFileMapping(refs, relPath, AB_RULES, '.md');
}
for (const absPath of await listFiles(projectRoot, '.continue/prompts')) {
refs.set(rel(projectRoot, absPath), `${AB_COMMANDS}/${basename(absPath, '.md')}.md`);
@@ -215,3 +227,22 @@ export async function buildWindsurfImportPaths(
addSkillLikeMapping(refs, rel(projectRoot, absPath), '.windsurf/skills');
}
}
+
+export async function buildAntigravityImportPaths(
+ refs: Map,
+ projectRoot: string,
+): Promise {
+ refs.set(ANTIGRAVITY_RULES_ROOT, `${AB_RULES}/_root.md`);
+ refs.set(ANTIGRAVITY_RULES_ROOT_LEGACY, `${AB_RULES}/_root.md`);
+ for (const absPath of await listFiles(projectRoot, ANTIGRAVITY_RULES_DIR)) {
+ const relPath = rel(projectRoot, absPath);
+ if (relPath === ANTIGRAVITY_RULES_ROOT || relPath === ANTIGRAVITY_RULES_ROOT_LEGACY) continue;
+ addSimpleFileMapping(refs, relPath, AB_RULES, '.md');
+ }
+ for (const absPath of await listFiles(projectRoot, ANTIGRAVITY_WORKFLOWS_DIR)) {
+ addSimpleFileMapping(refs, rel(projectRoot, absPath), AB_COMMANDS, '.md');
+ }
+ for (const absPath of await listFiles(projectRoot, ANTIGRAVITY_SKILLS_DIR)) {
+ addSkillLikeMapping(refs, rel(projectRoot, absPath), ANTIGRAVITY_SKILLS_DIR);
+ }
+}
diff --git a/src/targets/antigravity/constants.ts b/src/targets/antigravity/constants.ts
new file mode 100644
index 00000000..5ed2a453
--- /dev/null
+++ b/src/targets/antigravity/constants.ts
@@ -0,0 +1,13 @@
+export const ANTIGRAVITY_TARGET = 'antigravity';
+
+export const ANTIGRAVITY_DIR = '.agents';
+export const ANTIGRAVITY_RULES_DIR = `${ANTIGRAVITY_DIR}/rules`;
+/** Generated main workspace instructions (canonical root remains `.agentsmesh/rules/_root.md`). */
+export const ANTIGRAVITY_RULES_ROOT = `${ANTIGRAVITY_RULES_DIR}/general.md`;
+export const ANTIGRAVITY_RULES_ROOT_LEGACY = `${ANTIGRAVITY_RULES_DIR}/_root.md`;
+export const ANTIGRAVITY_SKILLS_DIR = `${ANTIGRAVITY_DIR}/skills`;
+export const ANTIGRAVITY_WORKFLOWS_DIR = `${ANTIGRAVITY_DIR}/workflows`;
+
+export const ANTIGRAVITY_CANONICAL_ROOT_RULE = '.agentsmesh/rules/_root.md';
+export const ANTIGRAVITY_CANONICAL_RULES_DIR = '.agentsmesh/rules';
+export const ANTIGRAVITY_CANONICAL_COMMANDS_DIR = '.agentsmesh/commands';
diff --git a/src/targets/antigravity/generator.ts b/src/targets/antigravity/generator.ts
new file mode 100644
index 00000000..a5055627
--- /dev/null
+++ b/src/targets/antigravity/generator.ts
@@ -0,0 +1,52 @@
+import { basename } from 'node:path';
+import type { CanonicalFiles } from '../../core/types.js';
+import { generateEmbeddedSkills } from '../import/embedded-skill.js';
+import {
+ ANTIGRAVITY_RULES_ROOT,
+ ANTIGRAVITY_RULES_DIR,
+ ANTIGRAVITY_WORKFLOWS_DIR,
+ ANTIGRAVITY_SKILLS_DIR,
+} from './constants.js';
+
+export interface AntigravityOutput {
+ path: string;
+ content: string;
+}
+
+export function generateRules(canonical: CanonicalFiles): AntigravityOutput[] {
+ const root = canonical.rules.find((r) => r.root);
+ if (!root) return [];
+
+ const outputs: AntigravityOutput[] = [
+ { path: ANTIGRAVITY_RULES_ROOT, content: root.body.trim() || '' },
+ ];
+
+ for (const rule of canonical.rules) {
+ if (rule.root) continue;
+ if (rule.targets.length > 0 && !rule.targets.includes('antigravity')) continue;
+ const slug = basename(rule.source, '.md');
+ outputs.push({
+ path: `${ANTIGRAVITY_RULES_DIR}/${slug}.md`,
+ content: rule.body.trim() || '',
+ });
+ }
+
+ return outputs;
+}
+
+export function generateWorkflows(canonical: CanonicalFiles): AntigravityOutput[] {
+ return canonical.commands.map((cmd) => {
+ const intro = cmd.description.trim();
+ const body = cmd.body.trim();
+ const content =
+ intro && body && !body.startsWith(intro) ? `${intro}\n\n${body}` : body || intro;
+ return {
+ path: `${ANTIGRAVITY_WORKFLOWS_DIR}/${cmd.name}.md`,
+ content,
+ };
+ });
+}
+
+export function generateSkills(canonical: CanonicalFiles): AntigravityOutput[] {
+ return generateEmbeddedSkills(canonical, ANTIGRAVITY_SKILLS_DIR);
+}
diff --git a/src/targets/antigravity/importer.ts b/src/targets/antigravity/importer.ts
new file mode 100644
index 00000000..18465068
--- /dev/null
+++ b/src/targets/antigravity/importer.ts
@@ -0,0 +1,145 @@
+import { basename, join } from 'node:path';
+import type { ImportResult } from '../../core/types.js';
+import { createImportReferenceNormalizer } from '../../core/reference/import-rewriter.js';
+import { readFileSafe, writeFileAtomic, mkdirp } from '../../utils/filesystem/fs.js';
+import { parseFrontmatter } from '../../utils/text/markdown.js';
+import { importEmbeddedSkills } from '../import/embedded-skill.js';
+import {
+ serializeImportedRuleWithFallback,
+ serializeImportedCommandWithFallback,
+} from '../import/import-metadata.js';
+import { importFileDirectory } from '../import/import-orchestrator.js';
+import {
+ ANTIGRAVITY_TARGET,
+ ANTIGRAVITY_RULES_ROOT,
+ ANTIGRAVITY_RULES_ROOT_LEGACY,
+ ANTIGRAVITY_RULES_DIR,
+ ANTIGRAVITY_WORKFLOWS_DIR,
+ ANTIGRAVITY_SKILLS_DIR,
+ ANTIGRAVITY_CANONICAL_ROOT_RULE,
+ ANTIGRAVITY_CANONICAL_RULES_DIR,
+ ANTIGRAVITY_CANONICAL_COMMANDS_DIR,
+} from './constants.js';
+
+async function importRootRule(
+ projectRoot: string,
+ results: ImportResult[],
+ normalize: (content: string, sourceFile: string, destinationFile: string) => string,
+): Promise {
+ const primary = join(projectRoot, ANTIGRAVITY_RULES_ROOT);
+ const legacy = join(projectRoot, ANTIGRAVITY_RULES_ROOT_LEGACY);
+ let srcPath = primary;
+ let content = await readFileSafe(primary);
+ if (content === null) {
+ srcPath = legacy;
+ content = await readFileSafe(legacy);
+ }
+ if (content === null) return;
+
+ const destPath = join(projectRoot, ANTIGRAVITY_CANONICAL_ROOT_RULE);
+ const { body } = parseFrontmatter(normalize(content, srcPath, destPath));
+ const output = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
+ await mkdirp(join(projectRoot, ANTIGRAVITY_CANONICAL_RULES_DIR));
+ await writeFileAtomic(destPath, output);
+ results.push({
+ fromTool: ANTIGRAVITY_TARGET,
+ fromPath: srcPath,
+ toPath: ANTIGRAVITY_CANONICAL_ROOT_RULE,
+ feature: 'rules',
+ });
+}
+
+async function importNonRootRules(
+ projectRoot: string,
+ results: ImportResult[],
+ normalize: (content: string, sourceFile: string, destinationFile: string) => string,
+): Promise {
+ const srcDir = join(projectRoot, ANTIGRAVITY_RULES_DIR);
+ const destDir = join(projectRoot, ANTIGRAVITY_CANONICAL_RULES_DIR);
+ results.push(
+ ...(await importFileDirectory({
+ srcDir,
+ destDir,
+ extensions: ['.md'],
+ fromTool: ANTIGRAVITY_TARGET,
+ normalize,
+ mapEntry: async ({ relativePath, normalizeTo }) => {
+ if (basename(relativePath) === 'general.md' || basename(relativePath) === '_root.md')
+ return null;
+ const destPath = join(destDir, relativePath);
+ const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath));
+ const output = await serializeImportedRuleWithFallback(
+ destPath,
+ {
+ root: false,
+ description:
+ typeof frontmatter.description === 'string' ? frontmatter.description : undefined,
+ globs: Array.isArray(frontmatter.globs) ? frontmatter.globs : undefined,
+ },
+ body,
+ );
+ return {
+ destPath,
+ toPath: `${ANTIGRAVITY_CANONICAL_RULES_DIR}/${relativePath}`,
+ feature: 'rules',
+ content: output,
+ };
+ },
+ })),
+ );
+}
+
+async function importWorkflows(
+ projectRoot: string,
+ results: ImportResult[],
+ normalize: (content: string, sourceFile: string, destinationFile: string) => string,
+): Promise {
+ const srcDir = join(projectRoot, ANTIGRAVITY_WORKFLOWS_DIR);
+ const destDir = join(projectRoot, ANTIGRAVITY_CANONICAL_COMMANDS_DIR);
+ results.push(
+ ...(await importFileDirectory({
+ srcDir,
+ destDir,
+ extensions: ['.md'],
+ fromTool: ANTIGRAVITY_TARGET,
+ normalize,
+ mapEntry: async ({ relativePath, normalizeTo }) => {
+ const destPath = join(destDir, relativePath);
+ const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath));
+ const normalized = await serializeImportedCommandWithFallback(
+ destPath,
+ {
+ hasDescription: typeof frontmatter.description === 'string',
+ description:
+ typeof frontmatter.description === 'string' ? frontmatter.description : undefined,
+ hasAllowedTools: false,
+ allowedTools: [],
+ },
+ body,
+ );
+ return {
+ destPath,
+ toPath: `${ANTIGRAVITY_CANONICAL_COMMANDS_DIR}/${relativePath}`,
+ feature: 'commands',
+ content: normalized,
+ };
+ },
+ })),
+ );
+}
+
+export async function importFromAntigravity(projectRoot: string): Promise {
+ const results: ImportResult[] = [];
+ const normalize = await createImportReferenceNormalizer(ANTIGRAVITY_TARGET, projectRoot);
+ await importRootRule(projectRoot, results, normalize);
+ await importNonRootRules(projectRoot, results, normalize);
+ await importWorkflows(projectRoot, results, normalize);
+ await importEmbeddedSkills(
+ projectRoot,
+ ANTIGRAVITY_SKILLS_DIR,
+ ANTIGRAVITY_TARGET,
+ results,
+ normalize,
+ );
+ return results;
+}
diff --git a/src/targets/antigravity/index.ts b/src/targets/antigravity/index.ts
new file mode 100644
index 00000000..ae3e5bed
--- /dev/null
+++ b/src/targets/antigravity/index.ts
@@ -0,0 +1,57 @@
+import type { TargetGenerators } from '../catalog/target.interface.js';
+import type { TargetDescriptor } from '../catalog/target-descriptor.js';
+import { generateRules, generateWorkflows, generateSkills } from './generator.js';
+import {
+ ANTIGRAVITY_RULES_ROOT,
+ ANTIGRAVITY_RULES_DIR,
+ ANTIGRAVITY_WORKFLOWS_DIR,
+} from './constants.js';
+import { importFromAntigravity } from './importer.js';
+import { lintRules } from './linter.js';
+import { buildAntigravityImportPaths } from '../../core/reference/import-map-builders.js';
+
+export const target: TargetGenerators = {
+ name: 'antigravity',
+ primaryRootInstructionPath: ANTIGRAVITY_RULES_ROOT,
+ generateRules,
+ generateCommands: generateWorkflows,
+ generateSkills,
+ importFrom: importFromAntigravity,
+};
+
+export const descriptor = {
+ id: 'antigravity',
+ generators: target,
+ capabilities: {
+ rules: 'native',
+ commands: 'partial',
+ agents: 'none',
+ skills: 'native',
+ mcp: 'none',
+ hooks: 'none',
+ ignore: 'none',
+ permissions: 'none',
+ },
+ emptyImportMessage:
+ 'No Antigravity config found (.agents/rules/, .agents/skills/, or .agents/workflows/).',
+ lintRules,
+ skillDir: '.agents/skills',
+ paths: {
+ rulePath(slug, _rule) {
+ return `${ANTIGRAVITY_RULES_DIR}/${slug}.md`;
+ },
+ commandPath(name, _config) {
+ return `${ANTIGRAVITY_WORKFLOWS_DIR}/${name}.md`;
+ },
+ agentPath(_name, _config) {
+ return null;
+ },
+ },
+ buildImportPaths: buildAntigravityImportPaths,
+ detectionPaths: [
+ '.agents/rules/general.md',
+ '.agents/rules/',
+ '.agents/skills/',
+ '.agents/workflows/',
+ ],
+} satisfies TargetDescriptor;
diff --git a/src/targets/antigravity/linter.ts b/src/targets/antigravity/linter.ts
new file mode 100644
index 00000000..c023dfec
--- /dev/null
+++ b/src/targets/antigravity/linter.ts
@@ -0,0 +1,14 @@
+import type { CanonicalFiles, LintDiagnostic } from '../../core/types.js';
+import { validateRules } from '../../core/lint/validate-rules.js';
+import { ANTIGRAVITY_TARGET } from './constants.js';
+
+export function lintRules(
+ canonical: CanonicalFiles,
+ projectRoot: string,
+ projectFiles: string[],
+): LintDiagnostic[] {
+ return validateRules(canonical, projectRoot, projectFiles).map((diagnostic) => ({
+ ...diagnostic,
+ target: ANTIGRAVITY_TARGET,
+ }));
+}
diff --git a/src/targets/catalog/builtin-targets.ts b/src/targets/catalog/builtin-targets.ts
index ed3a4a76..0aa7d5a0 100644
--- a/src/targets/catalog/builtin-targets.ts
+++ b/src/targets/catalog/builtin-targets.ts
@@ -16,6 +16,7 @@ import { descriptor as geminiCli } from '../gemini-cli/index.js';
import { descriptor as cline } from '../cline/index.js';
import { descriptor as codexCli } from '../codex-cli/index.js';
import { descriptor as windsurf } from '../windsurf/index.js';
+import { descriptor as antigravity } from '../antigravity/index.js';
type TargetFeature = keyof TargetCapabilities | 'settings';
type TargetGenerator = (canonical: CanonicalFiles) => { path: string; content: string }[];
@@ -33,6 +34,7 @@ export const BUILTIN_TARGETS: readonly TargetDescriptor[] = [
cline,
codexCli,
windsurf,
+ antigravity,
];
// Re-export from target-ids.ts for backward compatibility
diff --git a/src/targets/catalog/target-ids.ts b/src/targets/catalog/target-ids.ts
index 50eb6553..3fd18d3e 100644
--- a/src/targets/catalog/target-ids.ts
+++ b/src/targets/catalog/target-ids.ts
@@ -19,6 +19,7 @@ export const TARGET_IDS = [
'cline',
'codex-cli',
'windsurf',
+ 'antigravity',
] as const;
export type BuiltinTargetId = (typeof TARGET_IDS)[number];
diff --git a/src/targets/continue/constants.ts b/src/targets/continue/constants.ts
index 45b58318..12eab2f2 100644
--- a/src/targets/continue/constants.ts
+++ b/src/targets/continue/constants.ts
@@ -4,7 +4,9 @@ export const CONTINUE_RULES_DIR = '.continue/rules';
export const CONTINUE_PROMPTS_DIR = '.continue/prompts';
export const CONTINUE_MCP_DIR = '.continue/mcpServers';
export const CONTINUE_MCP_FILE = `${CONTINUE_MCP_DIR}/agentsmesh.json`;
-export const CONTINUE_ROOT_RULE = `${CONTINUE_RULES_DIR}/_root.md`;
+/** Generated main rule file (canonical root remains `.agentsmesh/rules/_root.md`). */
+export const CONTINUE_ROOT_RULE = `${CONTINUE_RULES_DIR}/general.md`;
+export const CONTINUE_ROOT_RULE_LEGACY = `${CONTINUE_RULES_DIR}/_root.md`;
export const CONTINUE_SKILLS_DIR = '.continue/skills';
export const CONTINUE_CANONICAL_RULES_DIR = '.agentsmesh/rules';
diff --git a/src/targets/continue/importer.ts b/src/targets/continue/importer.ts
index bc588a80..00114092 100644
--- a/src/targets/continue/importer.ts
+++ b/src/targets/continue/importer.ts
@@ -22,6 +22,10 @@ import {
CONTINUE_CANONICAL_MCP,
} from './constants.js';
+function isContinueRootRuleRelativePath(relativePath: string): boolean {
+ return relativePath === 'general.md' || relativePath === '_root.md';
+}
+
function readMcpServers(content: string, extension: string): Record {
const parsed =
extension === '.json'
@@ -73,13 +77,16 @@ async function importRules(
const source = await readFileSafe(srcPath);
if (!source) continue;
const relativePath = relative(rulesRoot, srcPath).replace(/\\/g, '/');
- const destPath = join(projectRoot, CONTINUE_CANONICAL_RULES_DIR, relativePath);
+ const canonicalRelative = isContinueRootRuleRelativePath(relativePath)
+ ? '_root.md'
+ : relativePath;
+ const destPath = join(projectRoot, CONTINUE_CANONICAL_RULES_DIR, canonicalRelative);
const { frontmatter, body } = parseFrontmatter(normalize(source, srcPath, destPath));
const canonicalFrontmatter: Record = {
description:
typeof frontmatter.description === 'string' ? frontmatter.description : undefined,
globs: Array.isArray(frontmatter.globs) ? frontmatter.globs : undefined,
- root: relativePath === '_root.md',
+ root: isContinueRootRuleRelativePath(relativePath),
};
if (canonicalFrontmatter.description === undefined) delete canonicalFrontmatter.description;
if (canonicalFrontmatter.globs === undefined) delete canonicalFrontmatter.globs;
@@ -88,7 +95,7 @@ async function importRules(
results.push({
fromTool: 'continue',
fromPath: srcPath,
- toPath: `${CONTINUE_CANONICAL_RULES_DIR}/${relativePath}`,
+ toPath: `${CONTINUE_CANONICAL_RULES_DIR}/${canonicalRelative}`,
feature: 'rules',
});
}
diff --git a/src/targets/cursor/generator.ts b/src/targets/cursor/generator.ts
index d94c5349..bdcf73ad 100644
--- a/src/targets/cursor/generator.ts
+++ b/src/targets/cursor/generator.ts
@@ -23,9 +23,9 @@ export interface RulesOutput {
}
/**
- * Generate .cursor/rules/_root.mdc from root rule + .cursor/rules/{slug}.mdc from non-root rules.
+ * Generate .cursor/rules/general.mdc from root rule + .cursor/rules/{slug}.mdc from non-root rules.
* @param canonical - Loaded canonical files
- * @returns _root.mdc (alwaysApply: true) + one .mdc per contextual rule (alwaysApply: false)
+ * @returns general.mdc (alwaysApply: true) + one .mdc per contextual rule (alwaysApply: false)
*/
export function generateRules(canonical: CanonicalFiles): RulesOutput[] {
const outputs: RulesOutput[] = [];
diff --git a/tests/agents-folder-structure-research.test.ts b/tests/agents-folder-structure-research.test.ts
index 514e405d..d4bc737f 100644
--- a/tests/agents-folder-structure-research.test.ts
+++ b/tests/agents-folder-structure-research.test.ts
@@ -2,7 +2,7 @@
* Agent folder structure tests — aligned with docs/agents-folder-structure-research.md
*
* Verifies that each agent generates the expected project paths per the research doc.
- * Covers all 7 agents: Claude Code, Cursor, Copilot, Gemini CLI, Cline, Codex CLI, Windsurf.
+ * Covers all 8 agents: Claude Code, Cursor, Copilot, Gemini CLI, Cline, Codex CLI, Windsurf, Antigravity.
*
* Implementation gaps (research doc paths we intentionally don't generate):
* - Claude: .claude/settings.local.json (user-specific)
@@ -1053,6 +1053,84 @@ describe('agents-folder-structure-research: Windsurf (docs §7)', () => {
* Windsurf: .windsurfrules/.windsurfignore (legacy), subdirectory AGENTS.md
*/
+describe('agents-folder-structure-research: Antigravity (docs §8)', () => {
+ const EXPECTED_PATHS = {
+ rootRule: '.agents/rules/general.md', // generated main instructions; canonical stays rules/_root.md
+ rulesDir: '.agents/rules/', // research: .agents/rules/*.md
+ workflowsDir: '.agents/workflows/', // research: .agents/workflows/*.md (experimental path)
+ skillsDir: '.agents/skills/', // research: .agents/skills//SKILL.md
+ };
+ // Gaps: no agents folder, no MCP file, no hooks, no ignore (only .gitignore per docs)
+
+ it('generates .agents/rules/general.md from root rule (plain body, no frontmatter)', async () => {
+ const results = await generate({
+ config: config({ targets: ['antigravity'], features: ['rules'] }),
+ canonical: canonicalWithRoot('# Antigravity Root'),
+ projectRoot: TEST_DIR,
+ });
+ const root = results.find((x) => x.path === EXPECTED_PATHS.rootRule);
+ expect(root).toBeDefined();
+ expect(root!.content).toContain('Antigravity Root');
+ expect(root!.content).not.toContain('root: true');
+ expect(root!.content).not.toContain('---');
+ });
+
+ it('generates .agents/rules/*.md for non-root rules', async () => {
+ const canonical = fullCanonical({
+ rootBody: '# Root',
+ nonRootRules: [
+ {
+ source: join(TEST_DIR, '.agentsmesh', 'rules', 'ts.md'),
+ body: 'TS rules',
+ targets: [],
+ },
+ ],
+ });
+ const results = await generate({
+ config: config({ targets: ['antigravity'], features: ['rules'] }),
+ canonical,
+ projectRoot: TEST_DIR,
+ });
+ const tsRule = results.find((x) => x.path === '.agents/rules/ts.md');
+ expect(tsRule).toBeDefined();
+ });
+
+ it('generates .agents/workflows/*.md from commands', async () => {
+ const canonical = fullCanonical({
+ rootBody: '# Root',
+ commands: [{ name: 'review', description: 'Review', body: 'Review steps.' }],
+ });
+ const results = await generate({
+ config: config({ targets: ['antigravity'], features: ['rules', 'commands'] }),
+ canonical,
+ projectRoot: TEST_DIR,
+ });
+ const workflow = results.find((x) => x.path === '.agents/workflows/review.md');
+ expect(workflow).toBeDefined();
+ expect(workflow!.content).toContain('Review steps.');
+ expect(workflow!.content).not.toContain('allowed-tools:');
+ expect(workflow!.content).not.toContain('x-agentsmesh');
+ });
+
+ it('generates .agents/skills/*/SKILL.md with YAML frontmatter', async () => {
+ const canonical = fullCanonical({
+ rootBody: '# Root',
+ skills: [{ name: 'typescript-pro', description: 'TypeScript skill', body: 'Use TS.' }],
+ });
+ const results = await generate({
+ config: config({ targets: ['antigravity'], features: ['rules', 'skills'] }),
+ canonical,
+ projectRoot: TEST_DIR,
+ });
+ const skill = results.find(
+ (x) => x.path === `${EXPECTED_PATHS.skillsDir}typescript-pro/SKILL.md`,
+ );
+ expect(skill).toBeDefined();
+ expect(skill!.content).toContain('name: typescript-pro');
+ expect(skill!.content).toContain('TypeScript skill');
+ });
+});
+
describe('agents-folder-structure-research: cross-tool matrix (docs quick matrix)', () => {
it('each agent produces its primary project instruction file when rules enabled', async () => {
const canonical = canonicalWithRoot('# Cross-tool root rule');
@@ -1064,6 +1142,7 @@ describe('agents-folder-structure-research: cross-tool matrix (docs quick matrix
'cline',
'codex-cli',
'windsurf',
+ 'antigravity',
] as const;
const primaryPaths: Record<(typeof targets)[number], string> = {
'claude-code': '.claude/CLAUDE.md',
@@ -1073,6 +1152,7 @@ describe('agents-folder-structure-research: cross-tool matrix (docs quick matrix
cline: 'AGENTS.md',
'codex-cli': 'AGENTS.md',
windsurf: 'AGENTS.md',
+ antigravity: '.agents/rules/general.md',
};
for (const target of targets) {
const results = await generate({
diff --git a/tests/e2e/agents-last-run.md b/tests/e2e/agents-last-run.md
index d8b7ef58..9d745899 100644
--- a/tests/e2e/agents-last-run.md
+++ b/tests/e2e/agents-last-run.md
@@ -1,6 +1,6 @@
# Agents E2E Last Run Report
-_Generated: 2026-03-28T18:03:38.432Z_
+_Generated: 2026-03-29T08:29:04.394Z_
## Initial — `.agentsmesh/agents/` (canonical fixture)
diff --git a/tests/e2e/continue-content-contract.e2e.test.ts b/tests/e2e/continue-content-contract.e2e.test.ts
index fb3e4576..d664d139 100644
--- a/tests/e2e/continue-content-contract.e2e.test.ts
+++ b/tests/e2e/continue-content-contract.e2e.test.ts
@@ -48,7 +48,7 @@ features:
'skills/api-generator/template.ts',
]);
- const rootRule = read(projectDir, '.continue/rules/_root.md');
+ const rootRule = read(projectDir, '.continue/rules/general.md');
expect(rootRule).toContain('description: Project-wide coding standards');
expect(rootRule).toContain('# Standards');
expect(rootRule).not.toContain('root:');
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/rules/general.md b/tests/e2e/fixtures/antigravity-project/.agents/rules/general.md
new file mode 100644
index 00000000..3c125382
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/rules/general.md
@@ -0,0 +1,3 @@
+# Project Rules
+
+Use TDD and keep edits minimal. Prefer small, focused changes over broad refactors.
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/rules/testing.md b/tests/e2e/fixtures/antigravity-project/.agents/rules/testing.md
new file mode 100644
index 00000000..4c349b3d
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/rules/testing.md
@@ -0,0 +1,4 @@
+# Testing Rules
+
+Write failing tests first. Verify behavior with unit, integration, and e2e coverage.
+Run the full test suite before marking any task complete.
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/rules/typescript.md b/tests/e2e/fixtures/antigravity-project/.agents/rules/typescript.md
new file mode 100644
index 00000000..c991c3a3
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/rules/typescript.md
@@ -0,0 +1,3 @@
+# TypeScript Rules
+
+Use strict TypeScript. No `any` types. Prefer `unknown` with narrowing.
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/skills/typescript-pro/SKILL.md b/tests/e2e/fixtures/antigravity-project/.agents/skills/typescript-pro/SKILL.md
new file mode 100644
index 00000000..9b4dd19c
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/skills/typescript-pro/SKILL.md
@@ -0,0 +1,7 @@
+---
+name: typescript-pro
+description: Helps with advanced TypeScript work
+---
+
+Use advanced TypeScript patterns when they improve correctness and maintainability.
+Prefer narrowing, type guards, and precise return types. See references/advanced-types.md.
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/skills/typescript-pro/references/advanced-types.md b/tests/e2e/fixtures/antigravity-project/.agents/skills/typescript-pro/references/advanced-types.md
new file mode 100644
index 00000000..d03f3a0b
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/skills/typescript-pro/references/advanced-types.md
@@ -0,0 +1,4 @@
+# Advanced TypeScript Types
+
+Use discriminated unions for complex state. Prefer mapped types over repeated interfaces.
+Leverage `satisfies` for type-checked literals without widening.
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/workflows/review.md b/tests/e2e/fixtures/antigravity-project/.agents/workflows/review.md
new file mode 100644
index 00000000..686817b9
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/workflows/review.md
@@ -0,0 +1,6 @@
+Review the current change set for correctness, regressions, and maintainability.
+
+1. Inspect changed files
+2. Check for correctness bugs
+3. Check for regression risks
+4. Summarize findings with severity
diff --git a/tests/e2e/fixtures/antigravity-project/.agents/workflows/test.md b/tests/e2e/fixtures/antigravity-project/.agents/workflows/test.md
new file mode 100644
index 00000000..b1256881
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/.agents/workflows/test.md
@@ -0,0 +1,6 @@
+Run the full test suite and report any failures.
+
+1. Run `pnpm test`
+2. Run `pnpm test:e2e`
+3. Run `pnpm lint` and `pnpm typecheck`
+4. Report failures with file and line numbers
diff --git a/tests/e2e/fixtures/antigravity-project/README.md b/tests/e2e/fixtures/antigravity-project/README.md
new file mode 100644
index 00000000..92bedcfe
--- /dev/null
+++ b/tests/e2e/fixtures/antigravity-project/README.md
@@ -0,0 +1,3 @@
+# Antigravity Fixture
+
+Fixture project for Antigravity import coverage.
diff --git a/tests/e2e/fixtures/canonical-full/agentsmesh.yaml b/tests/e2e/fixtures/canonical-full/agentsmesh.yaml
index 243ee41a..1e666072 100644
--- a/tests/e2e/fixtures/canonical-full/agentsmesh.yaml
+++ b/tests/e2e/fixtures/canonical-full/agentsmesh.yaml
@@ -1,3 +1,3 @@
version: 1
-targets: [claude-code, cursor, copilot, gemini-cli, cline, codex-cli, windsurf]
+targets: [claude-code, cursor, copilot, gemini-cli, cline, codex-cli, windsurf, antigravity]
features: [rules, commands, agents, skills, mcp, hooks, ignore, permissions]
diff --git a/tests/e2e/full-sync.e2e.test.ts b/tests/e2e/full-sync.e2e.test.ts
index b509e61b..f98fa190 100644
--- a/tests/e2e/full-sync.e2e.test.ts
+++ b/tests/e2e/full-sync.e2e.test.ts
@@ -28,6 +28,7 @@ describe('full-sync round trip preservation', () => {
'windsurf',
'copilot',
'gemini-cli',
+ 'antigravity',
];
for (const target of TARGETS_SUPPORTING_IMPORT) {
diff --git a/tests/e2e/generate-capabilities.e2e.test.ts b/tests/e2e/generate-capabilities.e2e.test.ts
index becfd82d..aa7303b3 100644
--- a/tests/e2e/generate-capabilities.e2e.test.ts
+++ b/tests/e2e/generate-capabilities.e2e.test.ts
@@ -542,12 +542,12 @@ features: [rules, commands, skills, mcp]
'skills/api-generator/template.ts',
]);
- fileContains(join(dir, '.continue', 'rules', '_root.md'), '# Standards');
+ fileContains(join(dir, '.continue', 'rules', 'general.md'), '# Standards');
fileContains(
- join(dir, '.continue', 'rules', '_root.md'),
+ join(dir, '.continue', 'rules', 'general.md'),
'description: Project-wide coding standards',
);
- fileNotContains(join(dir, '.continue', 'rules', '_root.md'), 'root:');
+ fileNotContains(join(dir, '.continue', 'rules', 'general.md'), 'root:');
fileContains(
join(dir, '.continue', 'rules', 'typescript.md'),
@@ -674,4 +674,41 @@ features: [rules, commands, agents, skills, mcp, ignore]
fileContains(join(dir, '.junie', 'agents', 'code-reviewer.md'), 'You are a code reviewer.');
fileNotContains(join(dir, '.junie', 'agents', 'code-reviewer.md'), 'name: code-reviewer');
});
+
+ it('generates Antigravity rules, workflows, and skills with doc-aligned formats', async () => {
+ dir = createCanonicalProject(`version: 1
+targets: [antigravity]
+features: [rules, commands, skills]
+`);
+ const result = await runCli('generate --targets antigravity', dir);
+ expect(result.exitCode, result.stderr).toBe(0);
+
+ // Root rule is emitted as plain markdown without frontmatter.
+ fileContains(join(dir, '.agents', 'rules', 'general.md'), '# Standards');
+ fileNotContains(join(dir, '.agents', 'rules', 'general.md'), 'root: true');
+ fileNotContains(join(dir, '.agents', 'rules', 'general.md'), '---');
+
+ // Non-root rules are plain markdown without canonical frontmatter.
+ fileContains(join(dir, '.agents', 'rules', 'typescript.md'), '# TypeScript');
+ fileNotContains(join(dir, '.agents', 'rules', 'typescript.md'), 'globs:');
+
+ // Commands map to .agents/workflows/ as plain markdown.
+ fileContains(join(dir, '.agents', 'workflows', 'review.md'), 'Review current changes');
+ fileNotContains(join(dir, '.agents', 'workflows', 'review.md'), 'allowed-tools:');
+ fileNotContains(join(dir, '.agents', 'workflows', 'review.md'), 'x-agentsmesh');
+
+ // Skills use native Antigravity SKILL.md format with required frontmatter.
+ fileContains(
+ join(dir, '.agents', 'skills', 'api-generator', 'SKILL.md'),
+ 'name: api-generator',
+ );
+ fileContains(
+ join(dir, '.agents', 'skills', 'api-generator', 'SKILL.md'),
+ 'description: Generate API endpoints',
+ );
+ fileContains(
+ join(dir, '.agents', 'skills', 'api-generator', 'references', 'route-checklist.md'),
+ 'response schema',
+ );
+ });
});
diff --git a/tests/e2e/helpers/canonical.ts b/tests/e2e/helpers/canonical.ts
index b735fc54..d3273abe 100644
--- a/tests/e2e/helpers/canonical.ts
+++ b/tests/e2e/helpers/canonical.ts
@@ -11,6 +11,7 @@ targets:
- cline
- codex-cli
- windsurf
+ - antigravity
features:
- rules
- commands
diff --git a/tests/e2e/helpers/reference-targets.ts b/tests/e2e/helpers/reference-targets.ts
index 3278fb35..58c4017b 100644
--- a/tests/e2e/helpers/reference-targets.ts
+++ b/tests/e2e/helpers/reference-targets.ts
@@ -60,7 +60,7 @@ export function outputPaths(target: TargetName): OutputPathGroups {
: target === 'copilot'
? ['.github/copilot-instructions.md']
: target === 'continue'
- ? ['.continue/rules/_root.md']
+ ? ['.continue/rules/general.md']
: target === 'junie'
? ['.junie/AGENTS.md']
: ['AGENTS.md'],
@@ -137,7 +137,7 @@ export function expectedRefs(target: TargetName, path?: string): Record {
]);
});
+ it('imports Antigravity rules, workflows, and skills', async () => {
+ dir = createTestProject('antigravity-project');
+ const result = await runCli('import --from antigravity', dir);
+ expect(result.exitCode, result.stderr).toBe(0);
+
+ fileContains(join(dir, '.agentsmesh', 'rules', '_root.md'), 'Project Rules');
+ fileContains(join(dir, '.agentsmesh', 'rules', 'typescript.md'), 'strict TypeScript');
+ fileContains(join(dir, '.agentsmesh', 'rules', 'testing.md'), 'failing tests first');
+ fileContains(
+ join(dir, '.agentsmesh', 'commands', 'review.md'),
+ 'Review the current change set',
+ );
+ fileContains(join(dir, '.agentsmesh', 'commands', 'test.md'), 'full test suite');
+ fileContains(
+ join(dir, '.agentsmesh', 'skills', 'typescript-pro', 'SKILL.md'),
+ 'typescript-pro',
+ );
+ fileContains(
+ join(dir, '.agentsmesh', 'skills', 'typescript-pro', 'references', 'advanced-types.md'),
+ 'discriminated unions',
+ );
+ });
+
it('imports Windsurf fallback root and ignore when only AGENTS.md and .codeiumignore exist', async () => {
dir = createTestProject('windsurf-agents-project');
const result = await runCli('import --from windsurf', dir);
diff --git a/tests/e2e/import-reference-rewrite.e2e.test.ts b/tests/e2e/import-reference-rewrite.e2e.test.ts
index 4bcf8875..3fa670d7 100644
--- a/tests/e2e/import-reference-rewrite.e2e.test.ts
+++ b/tests/e2e/import-reference-rewrite.e2e.test.ts
@@ -224,3 +224,54 @@ describe('import reference normalization', () => {
},
);
});
+
+describe('import reference normalization — antigravity', () => {
+ let dir = '';
+
+ afterEach(() => {
+ if (dir) cleanup(dir);
+ dir = '';
+ });
+
+ it('normalizes imported references for antigravity using the canonical-full fixture', async () => {
+ dir = createTestProject('canonical-full');
+ appendReferenceVariants(dir);
+
+ const generateResult = await runCli('generate --targets antigravity', dir);
+ expect(generateResult.exitCode, generateResult.stderr).toBe(0);
+
+ rmSync(join(dir, '.agentsmesh'), { recursive: true, force: true });
+
+ const importResult = await runCli('import --from antigravity', dir);
+ expect(importResult.exitCode, importResult.stderr).toBe(0);
+
+ const rootPath = join(dir, '.agentsmesh', 'rules', '_root.md');
+ const commandPath = join(dir, '.agentsmesh', 'commands', 'review.md');
+ const skillPath = join(dir, '.agentsmesh', 'skills', 'api-generator', 'SKILL.md');
+
+ fileExists(rootPath);
+ fileExists(commandPath);
+ fileExists(skillPath);
+
+ const rootContent = readFileSync(rootPath, 'utf-8');
+ const commandContent = readFileSync(commandPath, 'utf-8');
+ const skillContent = readFileSync(skillPath, 'utf-8');
+
+ // References must be rewritten from .agents/ back to canonical .agentsmesh/ form
+ expect(rootContent).toContain('.agentsmesh/rules/typescript.md');
+ expect(rootContent).toContain('.agentsmesh/commands/review.md');
+ expect(rootContent).toContain('.agentsmesh/skills/api-generator/SKILL.md');
+ expect(rootContent).not.toContain('.agents/rules/');
+ expect(rootContent).not.toContain('.agents/skills/');
+ expect(rootContent).not.toContain('.agents/workflows/');
+ expect(rootContent).toContain('✓ / ✗');
+ assertExternalRefs(rootContent);
+ assertDocs(rootContent);
+
+ expect(commandContent).toContain('.agentsmesh/skills/api-generator/SKILL.md');
+ expect(commandContent).not.toContain('.agents/');
+
+ expect(skillContent).toContain('.agentsmesh/rules/typescript.md');
+ expect(skillContent).not.toContain('.agents/');
+ });
+});
diff --git a/tests/e2e/last-target-reference-roundtrip.e2e.test.ts b/tests/e2e/last-target-reference-roundtrip.e2e.test.ts
index 597ac9ac..c4bc6401 100644
--- a/tests/e2e/last-target-reference-roundtrip.e2e.test.ts
+++ b/tests/e2e/last-target-reference-roundtrip.e2e.test.ts
@@ -75,7 +75,7 @@ describe('last target markdown reference round trips', () => {
const generateResult = await runCli('generate --targets continue', dir);
expect(generateResult.exitCode, generateResult.stderr).toBe(0);
- const rootPath = join(dir, '.continue', 'rules', '_root.md');
+ const rootPath = join(dir, '.continue', 'rules', 'general.md');
const commandPath = join(dir, '.continue', 'prompts', 'review.md');
const skillPath = join(dir, '.continue', 'skills', 'api-gen', 'SKILL.md');
fileExists(rootPath);
@@ -85,7 +85,7 @@ describe('last target markdown reference round trips', () => {
const generatedRoot = readFileSync(rootPath, 'utf-8');
const generatedCommand = readFileSync(commandPath, 'utf-8');
- expect(generatedRoot).toContain('.continue/rules/_root.md');
+ expect(generatedRoot).toContain('.continue/rules/general.md');
expect(generatedRoot).toContain('.continue/rules/typescript.md');
expect(generatedRoot).toContain('.continue/prompts/review.md');
expect(generatedRoot).toContain('.continue/skills/api-gen/SKILL.md');
@@ -93,7 +93,7 @@ describe('last target markdown reference round trips', () => {
expect(generatedRoot).not.toContain('../../docs/some-doc.md');
expect(generatedRoot).not.toContain('.agentsmesh/skills/');
- expect(generatedCommand).toContain('.continue/rules/_root.md');
+ expect(generatedCommand).toContain('.continue/rules/general.md');
expect(generatedCommand).toContain('.continue/skills/api-gen/SKILL.md');
expect(generatedCommand).toContain('docs/some-doc.md');
expect(generatedCommand).not.toContain('../../docs/some-doc.md');
diff --git a/tests/import-generate-roundtrip.test.ts b/tests/import-generate-roundtrip.test.ts
index 88bff745..27d85960 100644
--- a/tests/import-generate-roundtrip.test.ts
+++ b/tests/import-generate-roundtrip.test.ts
@@ -25,6 +25,7 @@ import { importFromCline } from '../src/targets/cline/importer.js';
import { importFromCodex } from '../src/targets/codex-cli/importer.js';
import { serializeCanonicalRuleToCodexRulesFile } from '../src/targets/codex-cli/codex-rules-embed.js';
import { importFromWindsurf } from '../src/targets/windsurf/importer.js';
+import { importFromAntigravity } from '../src/targets/antigravity/importer.js';
import type { BuiltinTargetId } from '../src/targets/catalog/target-ids.js';
const TEST_DIR = join(tmpdir(), 'am-roundtrip-test');
@@ -272,7 +273,7 @@ describe('import: Continue (rules + embedded commands + skills + mcp)', () => {
mkdirSync(join(TEST_DIR, '.continue', 'prompts'), { recursive: true });
mkdirSync(join(TEST_DIR, '.continue', 'skills', 'api-gen', 'references'), { recursive: true });
mkdirSync(join(TEST_DIR, '.continue', 'mcpServers'), { recursive: true });
- writeFileSync(join(TEST_DIR, '.continue', 'rules', '_root.md'), '# Root\n');
+ writeFileSync(join(TEST_DIR, '.continue', 'rules', 'general.md'), '# Root\n');
writeFileSync(
join(TEST_DIR, '.continue', 'rules', 'typescript.md'),
'---\nglobs:\n - "**/*.ts"\n---\n\nTS rules',
@@ -499,6 +500,25 @@ describe('import: Windsurf (rules + workflows + skills + hooks + mcp + ignore)',
});
});
+describe('import: Antigravity (rules, workflows, skills)', () => {
+ it('imports rules, workflows as commands, and skills', async () => {
+ mkdirSync(join(TEST_DIR, '.agents', 'rules'), { recursive: true });
+ writeFileSync(join(TEST_DIR, '.agents', 'rules', '_root.md'), '# Root\n\nProject rules.');
+ writeFileSync(join(TEST_DIR, '.agents', 'rules', 'ts.md'), '# TypeScript\n\nStrict mode.');
+ mkdirSync(join(TEST_DIR, '.agents', 'workflows'), { recursive: true });
+ writeFileSync(join(TEST_DIR, '.agents', 'workflows', 'deploy.md'), '# Deploy\n\nDeploy steps.');
+ mkdirSync(join(TEST_DIR, '.agents', 'skills', 'qa'), { recursive: true });
+ writeFileSync(
+ join(TEST_DIR, '.agents', 'skills', 'qa', 'SKILL.md'),
+ '---\ndescription: QA\n---\n\nQA.',
+ );
+
+ const results = await importFromAntigravity(TEST_DIR);
+ const features = [...new Set(results.map((r) => r.feature))].sort();
+ expect(features).toEqual(['commands', 'rules', 'skills']);
+ });
+});
+
// ── GENERATE TESTS: from full canonical, verify all outputs for each agent ──
describe('generate: full canonical → all agents produce all supported outputs', () => {
@@ -661,7 +681,7 @@ describe('generate: full canonical → all agents produce all supported outputs'
expect(paths).toEqual([
'.continue/mcpServers/agentsmesh.json',
'.continue/prompts/review.md',
- '.continue/rules/_root.md',
+ '.continue/rules/general.md',
'.continue/rules/typescript.md',
'.continue/skills/qa/SKILL.md',
]);
@@ -685,7 +705,22 @@ describe('generate: full canonical → all agents produce all supported outputs'
]);
});
- it('all 9 agents together produce all expected files', async () => {
+ it('Antigravity generates rules + workflows + skills', async () => {
+ const results = await generate({
+ config: allFeaturesConfig(['antigravity']),
+ canonical,
+ projectRoot: TEST_DIR,
+ });
+ const paths = results.map((r) => r.path).sort();
+ expect(paths).toEqual([
+ '.agents/rules/general.md',
+ '.agents/rules/typescript.md',
+ '.agents/skills/qa/SKILL.md',
+ '.agents/workflows/review.md',
+ ]);
+ });
+
+ it('all 10 agents together produce all expected files', async () => {
const allTargets: BuiltinTargetId[] = [
'claude-code',
'cursor',
@@ -696,6 +731,7 @@ describe('generate: full canonical → all agents produce all supported outputs'
'cline',
'codex-cli',
'windsurf',
+ 'antigravity',
];
const results = await generate({
config: allFeaturesConfig(allTargets),
@@ -705,8 +741,11 @@ describe('generate: full canonical → all agents produce all supported outputs'
const allPaths = results.map((r) => r.path).sort();
expect(allPaths).toEqual([
+ '.agents/rules/general.md',
+ '.agents/rules/typescript.md',
'.agents/skills/am-command-review/SKILL.md',
'.agents/skills/qa/SKILL.md',
+ '.agents/workflows/review.md',
'.aiignore',
'.claude/CLAUDE.md',
'.claude/agents/reviewer.md',
@@ -728,7 +767,7 @@ describe('generate: full canonical → all agents produce all supported outputs'
'.codex/instructions/typescript.md',
'.continue/mcpServers/agentsmesh.json',
'.continue/prompts/review.md',
- '.continue/rules/_root.md',
+ '.continue/rules/general.md',
'.continue/rules/typescript.md',
'.continue/skills/qa/SKILL.md',
'.cursor/agents/reviewer.md',
diff --git a/tests/integration/import.integration.test.ts b/tests/integration/import.integration.test.ts
index e0560d87..27d58334 100644
--- a/tests/integration/import.integration.test.ts
+++ b/tests/integration/import.integration.test.ts
@@ -149,7 +149,7 @@ features: [rules, commands, mcp]
);
execSync(`node ${CLI_PATH} generate`, { cwd: TEST_DIR });
- const rootRule = readFileSync(join(TEST_DIR, '.continue', 'rules', '_root.md'), 'utf-8');
+ const rootRule = readFileSync(join(TEST_DIR, '.continue', 'rules', 'general.md'), 'utf-8');
const promptFile = readFileSync(join(TEST_DIR, '.continue', 'prompts', 'review.md'), 'utf-8');
const mcpJson = readFileSync(
join(TEST_DIR, '.continue', 'mcpServers', 'agentsmesh.json'),
diff --git a/tests/unit/core/engine.test.ts b/tests/unit/core/engine.test.ts
index 4bbe5412..ffbda011 100644
--- a/tests/unit/core/engine.test.ts
+++ b/tests/unit/core/engine.test.ts
@@ -1137,7 +1137,7 @@ describe('generate Continue', () => {
canonical,
projectRoot: TEST_DIR,
});
- const root = results.find((r) => r.path === '.continue/rules/_root.md');
+ const root = results.find((r) => r.path === '.continue/rules/general.md');
expect(root).toBeDefined();
expect(root?.content).toContain('## AgentsMesh Generation Contract');
expect(root?.content).toContain('Use TypeScript');
diff --git a/tests/unit/install/native-install-scope.copilot-continue-gemini.test.ts b/tests/unit/install/native-install-scope.copilot-continue-gemini.test.ts
index af8ca8e6..d7b891a6 100644
--- a/tests/unit/install/native-install-scope.copilot-continue-gemini.test.ts
+++ b/tests/unit/install/native-install-scope.copilot-continue-gemini.test.ts
@@ -80,7 +80,7 @@ describe('stageNativeInstallScope Copilot, Continue, and Gemini CLI', () => {
name: 'continue-rules-folder',
target: 'continue',
path: '.continue/rules',
- files: { '.continue/rules/_root.md': '---\nname: Root\n---\n\nUse TS.\n' },
+ files: { '.continue/rules/general.md': '---\nname: Root\n---\n\nUse TS.\n' },
features: ['rules'],
pick: { rules: ['_root'] },
},
diff --git a/tests/unit/targets/antigravity/generator.test.ts b/tests/unit/targets/antigravity/generator.test.ts
new file mode 100644
index 00000000..d0257af9
--- /dev/null
+++ b/tests/unit/targets/antigravity/generator.test.ts
@@ -0,0 +1,197 @@
+import { describe, it, expect } from 'vitest';
+import type { CanonicalFiles } from '../../../../src/core/types.js';
+import {
+ generateRules,
+ generateWorkflows,
+ generateSkills,
+} from '../../../../src/targets/antigravity/generator.js';
+import {
+ ANTIGRAVITY_RULES_ROOT,
+ ANTIGRAVITY_RULES_DIR,
+ ANTIGRAVITY_WORKFLOWS_DIR,
+ ANTIGRAVITY_SKILLS_DIR,
+} from '../../../../src/targets/antigravity/constants.js';
+
+function makeCanonical(overrides: Partial = {}): CanonicalFiles {
+ return {
+ rules: [],
+ commands: [],
+ agents: [],
+ skills: [],
+ mcp: null,
+ permissions: null,
+ hooks: null,
+ ignore: [],
+ ...overrides,
+ };
+}
+
+describe('generateRules (antigravity)', () => {
+ it('generates root rule as .agents/rules/general.md with plain body only', () => {
+ const canonical = makeCanonical({
+ rules: [
+ {
+ source: '/proj/.agentsmesh/rules/_root.md',
+ root: true,
+ targets: [],
+ description: 'Root',
+ globs: [],
+ body: '# Project Rules\n\nUse TDD.',
+ },
+ ],
+ });
+ const results = generateRules(canonical);
+ expect(results).toHaveLength(1);
+ expect(results[0]?.path).toBe(ANTIGRAVITY_RULES_ROOT);
+ expect(results[0]?.content).toContain('Use TDD.');
+ expect(results[0]?.content).not.toContain('root: true');
+ expect(results[0]?.content).not.toContain('---');
+ });
+
+ it('generates non-root rules as .agents/rules/{slug}.md with plain body', () => {
+ const canonical = makeCanonical({
+ rules: [
+ {
+ source: '/proj/.agentsmesh/rules/_root.md',
+ root: true,
+ targets: [],
+ description: '',
+ globs: [],
+ body: '# Root',
+ },
+ {
+ source: '/proj/.agentsmesh/rules/typescript.md',
+ root: false,
+ targets: [],
+ description: 'TypeScript rules',
+ globs: ['src/**/*.ts'],
+ body: 'Use strict TypeScript.',
+ },
+ ],
+ });
+ const results = generateRules(canonical);
+ const tsRule = results.find((r) => r.path === `${ANTIGRAVITY_RULES_DIR}/typescript.md`);
+ expect(tsRule).toBeDefined();
+ expect(tsRule!.content).toContain('Use strict TypeScript.');
+ expect(tsRule!.content).not.toContain('globs:');
+ });
+
+ it('skips non-root rules targeting other agents only', () => {
+ const canonical = makeCanonical({
+ rules: [
+ {
+ source: '/proj/.agentsmesh/rules/_root.md',
+ root: true,
+ targets: [],
+ description: '',
+ globs: [],
+ body: '# Root',
+ },
+ {
+ source: '/proj/.agentsmesh/rules/cursor-only.md',
+ root: false,
+ targets: ['cursor'],
+ description: '',
+ globs: [],
+ body: 'Cursor only.',
+ },
+ ],
+ });
+ const results = generateRules(canonical);
+ expect(results.some((r) => r.path.includes('cursor-only'))).toBe(false);
+ });
+
+ it('returns empty array when no root rule exists', () => {
+ const canonical = makeCanonical({
+ rules: [
+ {
+ source: '/proj/.agentsmesh/rules/typescript.md',
+ root: false,
+ targets: [],
+ description: '',
+ globs: [],
+ body: 'Use TypeScript.',
+ },
+ ],
+ });
+ expect(generateRules(canonical)).toHaveLength(0);
+ });
+});
+
+describe('generateWorkflows (antigravity)', () => {
+ it('projects canonical commands into .agents/workflows/{name}.md as plain markdown', () => {
+ const canonical = makeCanonical({
+ commands: [
+ {
+ source: '/proj/.agentsmesh/commands/review.md',
+ name: 'review',
+ description: 'Review workflow',
+ allowedTools: ['Read', 'Bash(git diff)'],
+ body: 'Review the current diff.',
+ },
+ ],
+ });
+ const results = generateWorkflows(canonical);
+ expect(results).toHaveLength(1);
+ expect(results[0]?.path).toBe(`${ANTIGRAVITY_WORKFLOWS_DIR}/review.md`);
+ expect(results[0]?.content).toContain('Review the current diff.');
+ expect(results[0]?.content).not.toContain('allowed-tools:');
+ expect(results[0]?.content).not.toContain('x-agentsmesh');
+ });
+
+ it('returns empty array when no commands exist', () => {
+ expect(generateWorkflows(makeCanonical())).toHaveLength(0);
+ });
+
+ it('includes description as intro when body does not start with it', () => {
+ const canonical = makeCanonical({
+ commands: [
+ {
+ source: '/proj/.agentsmesh/commands/test.md',
+ name: 'test',
+ description: 'Run tests before merging',
+ allowedTools: [],
+ body: '1. Run `pnpm test`\n2. Fix failures',
+ },
+ ],
+ });
+ const results = generateWorkflows(canonical);
+ expect(results[0]?.content).toContain('Run tests before merging');
+ expect(results[0]?.content).toContain('pnpm test');
+ });
+});
+
+describe('generateSkills (antigravity)', () => {
+ it('generates skills under .agents/skills with SKILL.md and supporting files', () => {
+ const canonical = makeCanonical({
+ skills: [
+ {
+ source: '/proj/.agentsmesh/skills/typescript-pro/SKILL.md',
+ name: 'typescript-pro',
+ description: 'Helps with advanced TypeScript work',
+ body: 'Use advanced TypeScript patterns.',
+ supportingFiles: [
+ {
+ absolutePath: '/proj/.agentsmesh/skills/typescript-pro/references/advanced-types.md',
+ relativePath: 'references/advanced-types.md',
+ content: '# Advanced Types',
+ },
+ ],
+ },
+ ],
+ });
+ const results = generateSkills(canonical);
+ expect(results.map((r) => r.path).sort()).toEqual([
+ `${ANTIGRAVITY_SKILLS_DIR}/typescript-pro/SKILL.md`,
+ `${ANTIGRAVITY_SKILLS_DIR}/typescript-pro/references/advanced-types.md`,
+ ]);
+ const skillMd = results.find((r) => r.path.endsWith('SKILL.md'));
+ expect(skillMd?.content).toContain('name: typescript-pro');
+ expect(skillMd?.content).toContain('description: Helps with advanced TypeScript work');
+ expect(skillMd?.content).toContain('Use advanced TypeScript patterns.');
+ });
+
+ it('returns empty array when no skills exist', () => {
+ expect(generateSkills(makeCanonical())).toHaveLength(0);
+ });
+});
diff --git a/tests/unit/targets/antigravity/importer.test.ts b/tests/unit/targets/antigravity/importer.test.ts
new file mode 100644
index 00000000..de319f25
--- /dev/null
+++ b/tests/unit/targets/antigravity/importer.test.ts
@@ -0,0 +1,128 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
+import { join } from 'node:path';
+import { tmpdir } from 'node:os';
+import { importFromAntigravity } from '../../../../src/targets/antigravity/importer.js';
+import {
+ ANTIGRAVITY_RULES_ROOT,
+ ANTIGRAVITY_RULES_DIR,
+ ANTIGRAVITY_WORKFLOWS_DIR,
+ ANTIGRAVITY_SKILLS_DIR,
+} from '../../../../src/targets/antigravity/constants.js';
+
+const TEST_DIR = join(tmpdir(), 'am-antigravity-importer-test');
+
+beforeEach(() => mkdirSync(TEST_DIR, { recursive: true }));
+afterEach(() => rmSync(TEST_DIR, { recursive: true, force: true }));
+
+describe('importFromAntigravity — rules', () => {
+ it('imports .agents/rules/general.md as canonical root rule', async () => {
+ mkdirSync(join(TEST_DIR, ANTIGRAVITY_RULES_DIR), { recursive: true });
+ writeFileSync(join(TEST_DIR, ANTIGRAVITY_RULES_ROOT), '# Project Rules\n\nUse TDD.');
+
+ const results = await importFromAntigravity(TEST_DIR);
+ expect(results).toHaveLength(1);
+ expect(results[0]).toMatchObject({
+ fromTool: 'antigravity',
+ toPath: '.agentsmesh/rules/_root.md',
+ feature: 'rules',
+ });
+ expect(readFileSync(join(TEST_DIR, '.agentsmesh', 'rules', '_root.md'), 'utf-8')).toContain(
+ 'root: true',
+ );
+ expect(readFileSync(join(TEST_DIR, '.agentsmesh', 'rules', '_root.md'), 'utf-8')).toContain(
+ 'Use TDD.',
+ );
+ });
+
+ it('imports legacy .agents/rules/_root.md as canonical root when general.md is absent', async () => {
+ mkdirSync(join(TEST_DIR, ANTIGRAVITY_RULES_DIR), { recursive: true });
+ writeFileSync(join(TEST_DIR, ANTIGRAVITY_RULES_DIR, '_root.md'), '# Legacy Root\n\nBody.');
+
+ const results = await importFromAntigravity(TEST_DIR);
+ expect(results.filter((r) => r.feature === 'rules')).toHaveLength(1);
+ expect(results[0]).toMatchObject({
+ fromTool: 'antigravity',
+ toPath: '.agentsmesh/rules/_root.md',
+ feature: 'rules',
+ });
+ expect(readFileSync(join(TEST_DIR, '.agentsmesh', 'rules', '_root.md'), 'utf-8')).toContain(
+ 'Body.',
+ );
+ });
+
+ it('imports non-root rules from .agents/rules/ (excluding general.md and _root.md)', async () => {
+ mkdirSync(join(TEST_DIR, ANTIGRAVITY_RULES_DIR), { recursive: true });
+ writeFileSync(join(TEST_DIR, ANTIGRAVITY_RULES_ROOT), '# Root\n');
+ writeFileSync(
+ join(TEST_DIR, ANTIGRAVITY_RULES_DIR, 'typescript.md'),
+ '# TypeScript Rules\n\nUse strict mode.',
+ );
+
+ const results = await importFromAntigravity(TEST_DIR);
+ const tsResult = results.find((r) => r.toPath === '.agentsmesh/rules/typescript.md');
+ expect(tsResult).toBeDefined();
+ expect(tsResult?.feature).toBe('rules');
+ expect(
+ readFileSync(join(TEST_DIR, '.agentsmesh', 'rules', 'typescript.md'), 'utf-8'),
+ ).toContain('strict mode');
+ expect(
+ readFileSync(join(TEST_DIR, '.agentsmesh', 'rules', 'typescript.md'), 'utf-8'),
+ ).toContain('root: false');
+ });
+
+ it('returns empty when no .agents/rules/ directory exists', async () => {
+ const results = await importFromAntigravity(TEST_DIR);
+ expect(results).toHaveLength(0);
+ });
+});
+
+describe('importFromAntigravity — workflows (commands)', () => {
+ it('imports .agents/workflows/*.md as canonical commands', async () => {
+ mkdirSync(join(TEST_DIR, ANTIGRAVITY_WORKFLOWS_DIR), { recursive: true });
+ writeFileSync(
+ join(TEST_DIR, ANTIGRAVITY_WORKFLOWS_DIR, 'review.md'),
+ 'Review the current diff for quality.',
+ );
+
+ const results = await importFromAntigravity(TEST_DIR);
+ expect(results.filter((r) => r.feature === 'commands')).toHaveLength(1);
+ const canonical = readFileSync(join(TEST_DIR, '.agentsmesh', 'commands', 'review.md'), 'utf-8');
+ expect(canonical).toContain('Review the current diff for quality.');
+ });
+});
+
+describe('importFromAntigravity — skills', () => {
+ it('imports .agents/skills/ into canonical skills with supporting files', async () => {
+ mkdirSync(join(TEST_DIR, ANTIGRAVITY_SKILLS_DIR, 'typescript-pro', 'references'), {
+ recursive: true,
+ });
+ writeFileSync(
+ join(TEST_DIR, ANTIGRAVITY_SKILLS_DIR, 'typescript-pro', 'SKILL.md'),
+ '---\ndescription: Advanced TypeScript\n---\n\nUse advanced patterns.',
+ );
+ writeFileSync(
+ join(TEST_DIR, ANTIGRAVITY_SKILLS_DIR, 'typescript-pro', 'references', 'advanced-types.md'),
+ '# Advanced Types\n',
+ );
+
+ const results = await importFromAntigravity(TEST_DIR);
+ expect(results.filter((r) => r.feature === 'skills')).toHaveLength(2);
+ expect(
+ readFileSync(join(TEST_DIR, '.agentsmesh', 'skills', 'typescript-pro', 'SKILL.md'), 'utf-8'),
+ ).toContain('description: Advanced TypeScript');
+ expect(
+ readFileSync(
+ join(
+ TEST_DIR,
+ '.agentsmesh',
+ 'skills',
+ 'typescript-pro',
+ 'references',
+ 'advanced-types.md',
+ ),
+ 'utf-8',
+ ),
+ ).toContain('# Advanced Types');
+ });
+});
diff --git a/tests/unit/targets/continue/generator.test.ts b/tests/unit/targets/continue/generator.test.ts
index f4213f01..bb0d24e1 100644
--- a/tests/unit/targets/continue/generator.test.ts
+++ b/tests/unit/targets/continue/generator.test.ts
@@ -9,6 +9,7 @@ import {
import {
CONTINUE_MCP_FILE,
CONTINUE_PROMPTS_DIR,
+ CONTINUE_ROOT_RULE,
CONTINUE_RULES_DIR,
CONTINUE_SKILLS_DIR,
} from '../../../../src/targets/continue/constants.js';
@@ -53,7 +54,7 @@ describe('generateRules (continue)', () => {
const results = generateRules(canonical);
expect(results).toHaveLength(2);
expect(results.map((result) => result.path).sort()).toEqual([
- `${CONTINUE_RULES_DIR}/_root.md`,
+ CONTINUE_ROOT_RULE,
`${CONTINUE_RULES_DIR}/typescript.md`,
]);
expect(results[0]?.content).not.toContain('root:');
diff --git a/tests/unit/targets/continue/importer.test.ts b/tests/unit/targets/continue/importer.test.ts
index 36a4553d..cd4b9ff0 100644
--- a/tests/unit/targets/continue/importer.test.ts
+++ b/tests/unit/targets/continue/importer.test.ts
@@ -16,7 +16,26 @@ beforeEach(() => mkdirSync(TEST_DIR, { recursive: true }));
afterEach(() => rmSync(TEST_DIR, { recursive: true, force: true }));
describe('importFromContinue — rules', () => {
- it('imports .continue/rules/_root.md as the canonical root rule', async () => {
+ it('imports .continue/rules/general.md as the canonical root rule', async () => {
+ mkdirSync(join(TEST_DIR, CONTINUE_RULES_DIR), { recursive: true });
+ writeFileSync(
+ join(TEST_DIR, CONTINUE_RULES_DIR, 'general.md'),
+ '---\nname: Project Rules\n---\n\nUse TypeScript.',
+ );
+
+ const results = await importFromContinue(TEST_DIR);
+ expect(results).toHaveLength(1);
+ expect(results[0]).toMatchObject({
+ fromTool: 'continue',
+ toPath: '.agentsmesh/rules/_root.md',
+ feature: 'rules',
+ });
+ const content = readFileSync(join(TEST_DIR, '.agentsmesh', 'rules', '_root.md'), 'utf-8');
+ expect(content).toContain('root: true');
+ expect(content).toContain('Use TypeScript.');
+ });
+
+ it('imports legacy .continue/rules/_root.md as the canonical root rule', async () => {
mkdirSync(join(TEST_DIR, CONTINUE_RULES_DIR), { recursive: true });
writeFileSync(
join(TEST_DIR, CONTINUE_RULES_DIR, '_root.md'),
diff --git a/tests/unit/targets/descriptor-paths.test.ts b/tests/unit/targets/descriptor-paths.test.ts
index 8244d42f..d05c3389 100644
--- a/tests/unit/targets/descriptor-paths.test.ts
+++ b/tests/unit/targets/descriptor-paths.test.ts
@@ -8,6 +8,7 @@ import { descriptor as geminiCli } from '../../../src/targets/gemini-cli/index.j
import { descriptor as cline } from '../../../src/targets/cline/index.js';
import { descriptor as codexCli } from '../../../src/targets/codex-cli/index.js';
import { descriptor as windsurf } from '../../../src/targets/windsurf/index.js';
+import { descriptor as antigravity } from '../../../src/targets/antigravity/index.js';
import { TARGET_IDS } from '../../../src/targets/catalog/target-ids.js';
import type { ValidatedConfig } from '../../../src/config/core/schema.js';
import type { CanonicalRule } from '../../../src/core/types.js';
@@ -84,6 +85,11 @@ describe('descriptor.paths.rulePath', () => {
const rule = makeRule('example');
expect(windsurf.paths.rulePath('example', rule)).toBe('.windsurf/rules/example.md');
});
+
+ it('antigravity: returns .agents/rules/{slug}.md', () => {
+ const rule = makeRule('example');
+ expect(antigravity.paths.rulePath('example', rule)).toBe('.agents/rules/example.md');
+ });
});
describe('descriptor.paths.commandPath', () => {
@@ -138,6 +144,10 @@ describe('descriptor.paths.commandPath', () => {
it('windsurf: returns .windsurf/workflows/{name}.md', () => {
expect(windsurf.paths.commandPath('deploy', config)).toBe('.windsurf/workflows/deploy.md');
});
+
+ it('antigravity: returns .agents/workflows/{name}.md', () => {
+ expect(antigravity.paths.commandPath('deploy', config)).toBe('.agents/workflows/deploy.md');
+ });
});
describe('descriptor.paths.agentPath', () => {
@@ -202,6 +212,10 @@ describe('descriptor.paths.agentPath', () => {
});
expect(windsurf.paths.agentPath('reviewer', configWithConversionOff)).toBeNull();
});
+
+ it('antigravity: returns null (agents: none)', () => {
+ expect(antigravity.paths.agentPath('reviewer', config)).toBeNull();
+ });
});
describe('descriptor metadata', () => {
@@ -215,6 +229,7 @@ describe('descriptor metadata', () => {
cline,
codexCli,
windsurf,
+ antigravity,
];
const allFeatureKeys = [
@@ -228,9 +243,9 @@ describe('descriptor metadata', () => {
'permissions',
] as const;
- it('all 9 descriptors have ids matching TARGET_IDS', () => {
+ it('all 10 descriptors have ids matching TARGET_IDS', () => {
const descriptorIds = allDescriptors.map((d) => d.id);
- expect(descriptorIds).toHaveLength(9);
+ expect(descriptorIds).toHaveLength(10);
for (const id of descriptorIds) {
expect(TARGET_IDS).toContain(id);
}
diff --git a/tests/unit/targets/target-ids.test.ts b/tests/unit/targets/target-ids.test.ts
index fb544c67..6d32fde3 100644
--- a/tests/unit/targets/target-ids.test.ts
+++ b/tests/unit/targets/target-ids.test.ts
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
import { TARGET_IDS, isBuiltinTargetId } from '../../../src/targets/catalog/target-ids.js';
describe('TARGET_IDS', () => {
- it('contains exactly the 9 known target IDs', () => {
+ it('contains exactly the 10 known target IDs', () => {
expect([...TARGET_IDS]).toStrictEqual([
'claude-code',
'cursor',
@@ -13,6 +13,7 @@ describe('TARGET_IDS', () => {
'cline',
'codex-cli',
'windsurf',
+ 'antigravity',
]);
});
});
diff --git a/website/src/content/docs/reference/supported-tools.mdx b/website/src/content/docs/reference/supported-tools.mdx
index 443c9dc4..6319f1a6 100644
--- a/website/src/content/docs/reference/supported-tools.mdx
+++ b/website/src/content/docs/reference/supported-tools.mdx
@@ -1,10 +1,10 @@
---
draft: false
title: Supported Tools Matrix
-description: Complete feature-target compatibility matrix for all 9 AI coding tools supported by AgentsMesh — Claude Code, Cursor, Copilot, Gemini CLI, Cline, Codex CLI, Windsurf, Continue, and Junie.
+description: Complete feature-target compatibility matrix for all 10 AI coding tools supported by AgentsMesh — Claude Code, Cursor, Copilot, Gemini CLI, Cline, Codex CLI, Windsurf, Continue, Junie, and Antigravity.
---
-AgentsMesh supports 9 AI coding tools. This page documents what each tool supports natively, what is embedded/projected, and what is not supported.
+AgentsMesh supports 10 AI coding tools. This page documents what each tool supports natively, what is embedded/projected, and what is not supported.
## Legend
@@ -17,16 +17,16 @@ AgentsMesh supports 9 AI coding tools. This page documents what each tool suppor
## Feature matrix
-| Feature | Claude Code | Cursor | Copilot | Gemini CLI | Cline | Codex CLI | Windsurf | Continue | Junie |
-|---------|:-----------:|:------:|:-------:|:----------:|:-----:|:---------:|:--------:|:--------:|:-----:|
-| Rules | Native | Native | Native | Native | Native | Native | Native | Native | Native |
-| Commands | Native | Native | Native | Native | Native | Embedded | Native | Embedded | Embedded |
-| Agents | Native | Native | Native | Native | Embedded | Native | Embedded | — | Embedded |
-| Skills | Native | Native | Native | Native | Native | Native | Native | Embedded | Embedded |
-| MCP Servers | Native | Native | — | Native | Native | Native | Partial | Native | Native |
-| Hooks | Native | Native | Partial | Partial | — | — | Native | — | — |
-| Ignore | Native | Native | — | Native | Native | — | Native | — | Native |
-| Permissions | Native | Partial | — | Partial | — | — | — | — | — |
+| Feature | Claude Code | Cursor | Copilot | Gemini CLI | Cline | Codex CLI | Windsurf | Continue | Junie | Antigravity |
+|---------|:-----------:|:------:|:-------:|:----------:|:-----:|:---------:|:--------:|:--------:|:-----:|:-----------:|
+| Rules | Native | Native | Native | Native | Native | Native | Native | Native | Native | Native |
+| Commands | Native | Native | Native | Native | Native | Embedded | Native | Embedded | Embedded | Partial |
+| Agents | Native | Native | Native | Native | Embedded | Native | Embedded | — | Embedded | — |
+| Skills | Native | Native | Native | Native | Native | Native | Native | Embedded | Embedded | Native |
+| MCP Servers | Native | Native | — | Native | Native | Native | Partial | Native | Native | — |
+| Hooks | Native | Native | Partial | Partial | — | — | Native | — | — | — |
+| Ignore | Native | Native | — | Native | Native | — | Native | — | Native | — |
+| Permissions | Native | Partial | — | Partial | — | — | — | — | — | — |
## Tool details
@@ -161,7 +161,7 @@ Codex CLI uses a single `AGENTS.md` file. All features are embedded into this fi
| Feature | Notes |
|---------|-------|
-| Rules | `.continue/rules/*.md` (frontmatter stripped) |
+| Rules | `.continue/rules/*.md` (frontmatter stripped); root emits as `.continue/rules/general.md` (canonical remains `.agentsmesh/rules/_root.md`) |
| Commands | Embedded as skills |
| Agents | — Not supported |
| Skills | Embedded with metadata |
@@ -186,3 +186,22 @@ Codex CLI uses a single `AGENTS.md` file. All features are embedded into this fi
| Hooks | — Not supported |
| Ignore | `.junie/ignore` |
| Permissions | — Not supported |
+
+---
+
+### Antigravity
+
+**Output directory:** `.agents/`
+
+Antigravity uses a rules-first model with native skill support. Commands map to workflows (experimental path per public docs).
+
+| Feature | Format | Notes |
+|---------|--------|-------|
+| Rules | `.agents/rules/*.md` | Plain markdown; root instructions emit as `.agents/rules/general.md` (canonical remains `.agentsmesh/rules/_root.md`) |
+| Commands | `.agents/workflows/*.md` | Partial — workflow path is experimental/inferred |
+| Agents | — | No documented project file surface |
+| Skills | `.agents/skills/*/SKILL.md` | Native open-standard format with YAML frontmatter |
+| MCP Servers | — | Unclear project-file contract in public docs |
+| Hooks | — | Not supported |
+| Ignore | — | Not supported (only `.gitignore` per docs) |
+| Permissions | — | Not supported |
From 1e920e64fb89bd1a5bb70e9a7bcefd8ffd99516b Mon Sep 17 00:00:00 2001
From: Serhii Zhabskyi
Date: Sun, 29 Mar 2026 12:21:16 +0200
Subject: [PATCH 3/3] chore(release): 0.2.8
- Bump version and changelog for Antigravity, Continue general.md rules,
and target-descriptor registration.
- Sync package description/keywords with supported targets.
- Fix Continue e2e tree expectations for rules/general.md.
- Refresh lock and e2e last-run report after generate.
Made-with: Cursor
---
.agentsmesh/.lock | 4 ++--
CHANGELOG.md | 6 ++++++
package.json | 5 +++--
tasks/lessons.md | 1 +
tests/e2e/agents-last-run.md | 2 +-
tests/e2e/continue-content-contract.e2e.test.ts | 2 +-
tests/e2e/generate-capabilities.e2e.test.ts | 2 +-
tests/integration/lint.integration.test.ts | 2 +-
8 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/.agentsmesh/.lock b/.agentsmesh/.lock
index 1e1aa3d0..e8287543 100644
--- a/.agentsmesh/.lock
+++ b/.agentsmesh/.lock
@@ -1,9 +1,9 @@
# Auto-generated. DO NOT EDIT MANUALLY.
# Tracks the state of all config files for team conflict resolution.
-generated_at: 2026-03-29T08:57:28.949Z
+generated_at: 2026-03-29T10:20:37.442Z
generated_by: serhii
-lib_version: 0.2.6
+lib_version: 0.2.8
checksums:
agents/code-debugger.md: sha256:707132841c606f117c83491d53ce101be0117eb50abe2861bcf93bdd45a56daf
agents/code-documenter.md: sha256:faa66b16d2e86578985e817d60e6705ae0e34a716c1f5c29411739a6d659fb96
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4dd2772b..20002502 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.2.8
+
+### Patch Changes
+
+- Add Antigravity as a supported target, emit Continue root rules as `.continue/rules/general.md` (while still importing legacy `_root.md`), register built-in targets through target descriptors, and align Continue e2e contracts with the new rule filename.
+
## 0.2.6
### Patch Changes
diff --git a/package.json b/package.json
index 2780fef6..a8b16a1f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "agentsmesh",
- "version": "0.2.6",
- "description": "One canonical source for AI coding agent rules, commands, skills, MCP, hooks, and permissions — synced across Claude Code, Cursor, Copilot, Continue, Junie, Gemini CLI, Cline, Codex CLI, and Windsurf.",
+ "version": "0.2.8",
+ "description": "One canonical source for AI coding agent rules, commands, skills, MCP, hooks, and permissions — synced across Claude Code, Cursor, Copilot, Continue, Junie, Gemini CLI, Cline, Codex CLI, Windsurf, and Antigravity.",
"type": "module",
"main": "./dist/cli.js",
"author": "sampleXbro",
@@ -63,6 +63,7 @@
"cline",
"codex",
"windsurf",
+ "antigravity",
"developer-tools",
"devtools",
"code-assistant",
diff --git a/tasks/lessons.md b/tasks/lessons.md
index 91c93901..912acb4e 100644
--- a/tasks/lessons.md
+++ b/tasks/lessons.md
@@ -1,5 +1,6 @@
# Lessons Learned
+- **`execSync` shell must exist on Linux CI**: A lint integration test passed `shell: '/bin/zsh'` to merge stderr; GitHub Actions `ubuntu-latest` has no `/bin/zsh` → `spawnSync /bin/zsh ENOENT`. Root cause: macOS dev shells were assumed for all runners. Rule: for shell features like `2>&1` in integration tests, use `/bin/sh` (or omit `shell` and avoid shell-only syntax), never hardcode zsh-only paths unless the job installs zsh.
- **When test fixtures grow required manifest fields, update callback expectations in the same pass**: I added `source_kind` and `features` to `InstallManifestEntry` fixtures in `tests/unit/install/install-sync.test.ts`, which made standalone TypeScript clean, but the full Vitest run still failed because the `reinstall` assertions were still expecting the old `{ name, source }` payload. Root cause: I repaired the typed fixture shape without sweeping downstream expectations that compare the same object at runtime. Rule: whenever a test helper or fixture type gains required fields, update all `toHaveBeenCalledWith` / deep-equality assertions that observe that payload before calling the pass complete.
- **Repo typecheck that excludes tests must be supplemented when touching typed test fixtures**: A user still saw `TS2741` in `tests/unit/canonical/extend-load.test.ts` even though `pnpm typecheck` was green, because `tsconfig.json` excludes `**/*.test.ts` and the failing `ResolvedExtend` fixture only existed in a test file. Root cause: I relied on the repo typecheck gate without checking whether the touched file was inside the compiler include set. Rule: whenever a fix touches typed test files in this repo, run a standalone `tsc --noEmit ... ` (or an equivalent test-aware TS config) on the affected tests, especially for editor-reported TS errors.
- **If ESLint rules target tests, the lint script must target tests too**: Warning cleanup surfaced unused vars and return-type warnings in `tests/` even though `pnpm lint` was green, because the script only ran `eslint src/` while `eslint.config.js` defined rules for `tests/**/*.ts` as well. Root cause: lint command scope drifted from the configured file scope, so test warnings accumulated outside the default verification path. Rule: whenever ESLint config includes `tests/**` or other first-class source trees, keep the `lint` script aligned with those paths and ignore only generated artifacts explicitly.
diff --git a/tests/e2e/agents-last-run.md b/tests/e2e/agents-last-run.md
index 9d745899..b425971a 100644
--- a/tests/e2e/agents-last-run.md
+++ b/tests/e2e/agents-last-run.md
@@ -1,6 +1,6 @@
# Agents E2E Last Run Report
-_Generated: 2026-03-29T08:29:04.394Z_
+_Generated: 2026-03-29T10:20:16.744Z_
## Initial — `.agentsmesh/agents/` (canonical fixture)
diff --git a/tests/e2e/continue-content-contract.e2e.test.ts b/tests/e2e/continue-content-contract.e2e.test.ts
index d664d139..59140262 100644
--- a/tests/e2e/continue-content-contract.e2e.test.ts
+++ b/tests/e2e/continue-content-contract.e2e.test.ts
@@ -38,7 +38,7 @@ features:
'prompts/',
'prompts/review.md',
'rules/',
- 'rules/_root.md',
+ 'rules/general.md',
'rules/typescript.md',
'skills/',
'skills/api-generator/',
diff --git a/tests/e2e/generate-capabilities.e2e.test.ts b/tests/e2e/generate-capabilities.e2e.test.ts
index aa7303b3..56e07497 100644
--- a/tests/e2e/generate-capabilities.e2e.test.ts
+++ b/tests/e2e/generate-capabilities.e2e.test.ts
@@ -532,7 +532,7 @@ features: [rules, commands, skills, mcp]
'prompts/',
'prompts/review.md',
'rules/',
- 'rules/_root.md',
+ 'rules/general.md',
'rules/typescript.md',
'skills/',
'skills/api-generator/',
diff --git a/tests/integration/lint.integration.test.ts b/tests/integration/lint.integration.test.ts
index c26591d7..90f5d4ce 100644
--- a/tests/integration/lint.integration.test.ts
+++ b/tests/integration/lint.integration.test.ts
@@ -74,7 +74,7 @@ Lib rules
const out = execSync(`node ${CLI_PATH} lint 2>&1`, {
cwd: TEST_DIR,
encoding: 'utf-8',
- shell: '/bin/zsh',
+ shell: '/bin/sh',
});
expect(out).toContain('match 0 files');
expect(out).toContain('warning');