@williambeto/ai-workflow 2.2.6 → 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-assets/docs/compatibility/provider-usage.md +8 -0
- package/dist-assets/docs/compatibility/runtime-matrix.md +1 -0
- package/dist-assets/templates/.antigravityignore.template +8 -0
- package/dist-assets/templates/ANTIGRAVITY.md.template +20 -0
- package/package.json +1 -1
- package/src/adapters/index.js +1 -0
- package/src/adapters/platforms/antigravity.js +382 -0
- package/src/adapters/platforms/codex.js +13 -0
- package/src/adapters/platforms/gemini.js +14 -1
- package/src/cli.js +2 -1
- package/src/commands/init.js +25 -2
- package/src/core/gates/merge-gate.js +74 -0
- package/src/core/templates.js +8 -0
- package/src/core/validation/evidence-collector.js +5 -1
- package/src/core/validation/quality-guard.js +8 -0
|
@@ -33,6 +33,14 @@ npx @williambeto/ai-workflow init --yes --gemini
|
|
|
33
33
|
|
|
34
34
|
Review `GEMINI.md` and `.gemini/`. Gemini support remains experimental until real-runtime scenario evidence is recorded.
|
|
35
35
|
|
|
36
|
+
## Antigravity
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @williambeto/ai-workflow init --yes --antigravity
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Review `ANTIGRAVITY.md` and `.agents/`. Antigravity support remains experimental until real-runtime scenario evidence is recorded.
|
|
43
|
+
|
|
36
44
|
## Multiple adapters
|
|
37
45
|
|
|
38
46
|
Flags may be combined, but each generated runtime increases maintenance surface. Generate only the integrations the consumer project actually uses.
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
| Codex | Supported with limitations | `.github/agents/`, `.agents/skills/`, `.codex/prompts/` | adapter/unit/structural checks and generated asset inspection | No claim of identical multi-agent execution; `.codex/` output is optional and only generated with `--codex` |
|
|
14
14
|
| Claude Code | Supported with limitations | `CLAUDE.md`, `.claude/rules/` | adapter/unit/structural checks and generated asset inspection | Rule loading and tool permissions depend on Claude Code configuration |
|
|
15
15
|
| Gemini CLI | Experimental | `GEMINI.md`, `.gemini/agents/`, `.gemini/skills/`, `.gemini/commands/` | adapter/unit/structural checks and generated asset inspection | Native discovery and command behavior require real Gemini CLI validation before `Supported` |
|
|
16
|
+
| Antigravity | Supported | `ANTIGRAVITY.md`, `.agents/agents/`, `.agents/skills/`, `.agents/commands/` | unit tests, validation suite, and WSL/Desktop consumer validation | Agents are only listed under `/agents` when actively running (instantiated via `invoke_subagent`) |
|
|
16
17
|
|
|
17
18
|
## Claim policy
|
|
18
19
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Antigravity Instructions — AI Workflow Kit
|
|
2
|
+
|
|
3
|
+
Use AGENTS.md and the relevant contracts.
|
|
4
|
+
|
|
5
|
+
```txt
|
|
6
|
+
readonly → Inspect → Report → Recommendation
|
|
7
|
+
quick → Branch recovery/check → Implement → Document if behavior or usage changed → Test/Validate → Evidence
|
|
8
|
+
standard → Mini spec → Mini review → Mini plan → Scoped PR plan → Implement → Document → Test/Validate → Evidence
|
|
9
|
+
full → Spec draft → Spec review → Technical plan → PR breakdown → Implementation → Documentation → Test/Validate → Evidence report
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Use professional taxonomy: Primary Agents, Skill-backed Specialist Roles, Skills, Commands.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Documentation, UI, security, and delivery artifact gates
|
|
16
|
+
|
|
17
|
+
- Explicit requirements, clean code, generated specs, and inline comments do not replace documentation.
|
|
18
|
+
- For user-facing UI, rendered or visual evidence is required when the runtime can inspect it; raw/default browser UI is `FAIL_QUALITY_GATE`.
|
|
19
|
+
- High/Critical security findings must be classified in evidence.
|
|
20
|
+
- Implementation work must create delivery artifacts proportional to mode: quick uses compact evidence, standard uses compact workflow artifacts, and full uses the complete 01..07 set.
|
package/package.json
CHANGED
package/src/adapters/index.js
CHANGED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { COMPLETION_STATUS_TEXT } from "../../core/statuses.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Maps OpenCode agent names to primary agent roles.
|
|
8
|
+
* @type {Record<string, string>}
|
|
9
|
+
*/
|
|
10
|
+
const PERSONA_MAPPING = {
|
|
11
|
+
"atlas": "Atlas",
|
|
12
|
+
"orion": "Orion",
|
|
13
|
+
"sage": "Sage",
|
|
14
|
+
"nexus": "Nexus",
|
|
15
|
+
"astra": "Astra",
|
|
16
|
+
"sage-validation": "Sage",
|
|
17
|
+
"orion-release": "Orion",
|
|
18
|
+
"orion-deploy": "Orion",
|
|
19
|
+
"nexus-discovery": "Nexus",
|
|
20
|
+
"phoenix": "Phoenix"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Antigravity Adapter - Transforms OpenCode .md to Antigravity Skill format and manages .agents/ integration.
|
|
25
|
+
*/
|
|
26
|
+
export class AntigravityAdapter {
|
|
27
|
+
constructor({ cwd }) {
|
|
28
|
+
this.cwd = cwd;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Transforms an OpenCode agent or specialized skill file to an Antigravity Skill.
|
|
33
|
+
* @param {string} filePath - Path to the source .md file.
|
|
34
|
+
* @returns {Promise<{content: string, name: string}>}
|
|
35
|
+
*/
|
|
36
|
+
async transform(filePath) {
|
|
37
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
38
|
+
const fileName = path.basename(filePath, ".md") === "SKILL"
|
|
39
|
+
? path.basename(path.dirname(filePath))
|
|
40
|
+
: path.basename(filePath, ".md");
|
|
41
|
+
|
|
42
|
+
const persona = PERSONA_MAPPING[fileName.toLowerCase()] || "Astra";
|
|
43
|
+
|
|
44
|
+
// Check if already has frontmatter
|
|
45
|
+
if (content.trim().startsWith("---")) {
|
|
46
|
+
return { content, name: fileName };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Extract description from the first paragraph after the title
|
|
50
|
+
const descriptionMatch = content.match(/^# .*\n\n(.*)/m);
|
|
51
|
+
const description = descriptionMatch ? descriptionMatch[1].trim() : `Specialized agent for ${fileName}.`;
|
|
52
|
+
|
|
53
|
+
const skillContent = `---
|
|
54
|
+
name: ${fileName}
|
|
55
|
+
description: ${description}
|
|
56
|
+
---
|
|
57
|
+
# ${persona} Persona
|
|
58
|
+
|
|
59
|
+
${content}
|
|
60
|
+
|
|
61
|
+
## Completion Contract (Mandatory)
|
|
62
|
+
Every task completion MUST provide the following payload:
|
|
63
|
+
1. **Status**: ${COMPLETION_STATUS_TEXT}
|
|
64
|
+
2. **Scope reviewed**: Brief summary of what was analyzed.
|
|
65
|
+
3. **Findings**: Evidence-based discoveries.
|
|
66
|
+
4. **Files affected**: List of all concrete paths modified or created.
|
|
67
|
+
5. **Validation/evidence**: Commands run and their results.
|
|
68
|
+
6. **Risks/notes**: Any caveats or technical debt introduced.
|
|
69
|
+
7. **Required manager action**: Clear next step for the orchestrator.
|
|
70
|
+
8. **Can manager continue**: [yes/no]
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
content: skillContent,
|
|
75
|
+
name: fileName
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Deploys transformed skills and agents to their respective directories.
|
|
81
|
+
* Also orchestrates the .antigravity/ native integration.
|
|
82
|
+
*/
|
|
83
|
+
async deploy(transformedItems, installRoot = ".ai-workflow") {
|
|
84
|
+
const agentsDir = path.join(this.cwd, installRoot, "opencode/agents");
|
|
85
|
+
const skillsDir = path.join(this.cwd, installRoot, "opencode/skills");
|
|
86
|
+
|
|
87
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
88
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
for (const item of transformedItems) {
|
|
91
|
+
const isAgent = PERSONA_MAPPING[item.name.toLowerCase()];
|
|
92
|
+
const targetDir = isAgent ? agentsDir : path.join(skillsDir, item.name);
|
|
93
|
+
const fileName = isAgent ? `${item.name}.md` : "SKILL.md";
|
|
94
|
+
|
|
95
|
+
const absoluteTargetDir = isAgent ? agentsDir : targetDir;
|
|
96
|
+
await fs.mkdir(absoluteTargetDir, { recursive: true });
|
|
97
|
+
await fs.writeFile(path.join(absoluteTargetDir, fileName), item.content);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Deploy native Antigravity extensions (.antigravity/)
|
|
101
|
+
await this.deployNativeExtensions(installRoot);
|
|
102
|
+
|
|
103
|
+
// Also deploy root ANTIGRAVITY.md if it doesn't exist
|
|
104
|
+
await this.deployInstructions();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Orchestrates the .agents/ directory with copies of .ai-workflow/ assets.
|
|
109
|
+
* Using hard copies instead of symlinks ensures native discovery across all platforms.
|
|
110
|
+
*/
|
|
111
|
+
async deployNativeExtensions(installRoot = ".ai-workflow") {
|
|
112
|
+
const antigravityDir = path.join(this.cwd, ".agents");
|
|
113
|
+
const antigravityAgents = path.join(antigravityDir, "agents");
|
|
114
|
+
const antigravitySkills = path.join(antigravityDir, "skills");
|
|
115
|
+
const antigravityCommands = path.join(antigravityDir, "commands");
|
|
116
|
+
|
|
117
|
+
await fs.mkdir(antigravityAgents, { recursive: true });
|
|
118
|
+
await fs.mkdir(antigravitySkills, { recursive: true });
|
|
119
|
+
await fs.mkdir(antigravityCommands, { recursive: true });
|
|
120
|
+
|
|
121
|
+
const getUrls = (targetPath) => {
|
|
122
|
+
const linuxUrl = pathToFileURL(targetPath).href;
|
|
123
|
+
const urls = [linuxUrl];
|
|
124
|
+
|
|
125
|
+
if (process.platform === "linux" && process.env.WSL_DISTRO_NAME) {
|
|
126
|
+
const distro = process.env.WSL_DISTRO_NAME;
|
|
127
|
+
const normalizedPath = targetPath.replaceAll("\\", "/");
|
|
128
|
+
urls.push(`file://wsl.localhost/${distro}${normalizedPath}`);
|
|
129
|
+
urls.push(`file:///wsl.localhost/${distro}${normalizedPath}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return urls.join("\n");
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const toolNames = [
|
|
136
|
+
"send_message",
|
|
137
|
+
"find_by_name",
|
|
138
|
+
"grep_search",
|
|
139
|
+
"view_file",
|
|
140
|
+
"list_dir",
|
|
141
|
+
"read_url_content",
|
|
142
|
+
"search_web",
|
|
143
|
+
"schedule",
|
|
144
|
+
"multi_replace_file_content",
|
|
145
|
+
"replace_file_content",
|
|
146
|
+
"write_to_file",
|
|
147
|
+
"run_command",
|
|
148
|
+
"manage_task",
|
|
149
|
+
"define_subagent",
|
|
150
|
+
"invoke_subagent",
|
|
151
|
+
"manage_subagents",
|
|
152
|
+
"call_mcp_tool"
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const systemPromptConfig = {
|
|
156
|
+
"includeSections": [
|
|
157
|
+
"user_information",
|
|
158
|
+
"mcp_servers",
|
|
159
|
+
"skills",
|
|
160
|
+
"subagent_reminder",
|
|
161
|
+
"messaging",
|
|
162
|
+
"artifacts",
|
|
163
|
+
"user_rules"
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// 1. Deploy Primary Agents to .agents/agents/{agent_name}/
|
|
168
|
+
const sourceAgentsDir = path.join(this.cwd, installRoot, "opencode/agents");
|
|
169
|
+
const agentFiles = await fs.readdir(sourceAgentsDir).catch(() => []);
|
|
170
|
+
|
|
171
|
+
const AGENTS_INFO = {
|
|
172
|
+
"atlas": { name: "Atlas", description: "Router and workflow coordinator for AI Workflow Kit" },
|
|
173
|
+
"nexus": { name: "Nexus", description: "Discovery, requirement, scope, and specification owner" },
|
|
174
|
+
"orion": { name: "Orion", description: "Planning, PR sequencing, release, and deployment strategy owner" },
|
|
175
|
+
"astra": { name: "Astra", description: "Implementation owner for incremental, scoped changes" },
|
|
176
|
+
"sage": { name: "Sage", description: "Audit, validation, evidence, and quality gate owner" },
|
|
177
|
+
"phoenix": { name: "Phoenix", description: "Remediation and regression recovery owner" }
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
for (const file of agentFiles) {
|
|
181
|
+
if (!file.endsWith(".md")) continue;
|
|
182
|
+
const lowercaseName = path.basename(file, ".md").toLowerCase();
|
|
183
|
+
const info = AGENTS_INFO[lowercaseName];
|
|
184
|
+
if (!info) continue;
|
|
185
|
+
|
|
186
|
+
const agentFolder = path.join(antigravityAgents, lowercaseName);
|
|
187
|
+
await fs.mkdir(agentFolder, { recursive: true });
|
|
188
|
+
|
|
189
|
+
// Copy Markdown file to the agent's folder
|
|
190
|
+
const content = await fs.readFile(path.join(sourceAgentsDir, file), "utf8");
|
|
191
|
+
const mdTarget = path.join(agentFolder, `${lowercaseName}.md`);
|
|
192
|
+
await fs.writeFile(mdTarget, content);
|
|
193
|
+
|
|
194
|
+
// Generate agent.json
|
|
195
|
+
const agentJson = {
|
|
196
|
+
name: lowercaseName,
|
|
197
|
+
description: info.description,
|
|
198
|
+
hidden: false,
|
|
199
|
+
config: {
|
|
200
|
+
customAgent: {
|
|
201
|
+
systemPromptSections: [
|
|
202
|
+
{
|
|
203
|
+
title: "Agent System Instructions",
|
|
204
|
+
content: `You are ${info.name}, the ${lowercaseName === "astra" ? "implementation" : lowercaseName} owner.\n\nFollow the contract instructions in:\n${getUrls(mdTarget)}`
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
toolNames,
|
|
208
|
+
systemPromptConfig
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
await fs.writeFile(path.join(agentFolder, "agent.json"), JSON.stringify(agentJson, null, 2));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 2. Deploy Subagents to .agents/agents/{subagent_name}/
|
|
216
|
+
const SUBAGENTS_INFO = {
|
|
217
|
+
"Architecture-Specialist": { description: "Select the simplest architecture that safely satisfies current requirements", skill: "architecture" },
|
|
218
|
+
"Backend-Engineer": { description: "Support safe PHP/Node/Python backend changes for APIs and persistence", skill: "backend-development" },
|
|
219
|
+
"Deployment-Specialist": { description: "Plan, review, or validate deployment, release, and production-readiness", skill: "deployment" },
|
|
220
|
+
"Design-Specialist": { description: "Apply practical design principles to keep solutions simple and maintainable", skill: "design-principles" },
|
|
221
|
+
"Docs-Engineer": { description: "Write or improve project documentation, READMEs, guides, and prompts", skill: "documentation" },
|
|
222
|
+
"Frontend-Engineer": { description: "Implement or review frontend changes involving UI, state, routing, and accessibility", skill: "frontend-development" },
|
|
223
|
+
"Full-Stack-Engineer": { description: "Coordinate frontend and backend changes as one coherent, testable increment", skill: "full-stack-development" },
|
|
224
|
+
"Token-Economist": { description: "Reduce context size while preserving information required for safe execution", skill: "optimize-tokens" },
|
|
225
|
+
"PR-Manager": { description: "Plan, implement, review, or validate small pull requests while preserving scope", skill: "pr-workflow" },
|
|
226
|
+
"Discovery-Analyst": { description: "Clarify ambiguous requests into actionable problem statements and success outcomes", skill: "product-discovery" },
|
|
227
|
+
"Product-Planner": { description: "Turn clarified needs into scoped requirements with acceptance-ready outcomes", skill: "product-planning" },
|
|
228
|
+
"Memory-Guardian": { description: "Manage discoverable project memory for durable decisions and recurring corrections", skill: "project-memory" },
|
|
229
|
+
"Prompt-Engineer": { description: "Expert in creating and refining prompts for AI agents and workflows", skill: "prompt-engineer" },
|
|
230
|
+
"QA-Engineer": { description: "Design, review, or improve tests, acceptance criteria, and validation evidence", skill: "qa-workflow" },
|
|
231
|
+
"Refactoring-Specialist": { description: "Improve code/document structure safely while preserving behavior", skill: "refactoring" },
|
|
232
|
+
"Release-Specialist": { description: "Prepare release readiness decisions with explicit gates and evidence", skill: "release-workflow" },
|
|
233
|
+
"SDD-Specialist": { description: "Convert approved requirements into explicit, testable specification artifacts", skill: "spec-driven-development" },
|
|
234
|
+
"Technical-Leader": { description: "Make technical decisions, review architecture, and identify trade-offs", skill: "technical-leadership" },
|
|
235
|
+
"UI-UX-Engineer": { description: "Improve usability and interface clarity without introducing unnecessary complexity", skill: "ui-ux-design" }
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
for (const [name, subInfo] of Object.entries(SUBAGENTS_INFO)) {
|
|
239
|
+
const lowercaseSubagentName = name.toLowerCase();
|
|
240
|
+
const subagentFolder = path.join(antigravityAgents, lowercaseSubagentName);
|
|
241
|
+
await fs.mkdir(subagentFolder, { recursive: true });
|
|
242
|
+
|
|
243
|
+
const skillPath = path.join(antigravitySkills, subInfo.skill, "SKILL.md");
|
|
244
|
+
|
|
245
|
+
const subagentJson = {
|
|
246
|
+
name: lowercaseSubagentName,
|
|
247
|
+
description: subInfo.description,
|
|
248
|
+
hidden: false,
|
|
249
|
+
config: {
|
|
250
|
+
customAgent: {
|
|
251
|
+
systemPromptSections: [
|
|
252
|
+
{
|
|
253
|
+
title: "Agent System Instructions",
|
|
254
|
+
content: `You are ${name}.\n\nFollow the contract instructions in:\n${getUrls(skillPath)}`
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
toolNames,
|
|
258
|
+
systemPromptConfig
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
await fs.writeFile(path.join(subagentFolder, "agent.json"), JSON.stringify(subagentJson, null, 2));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 3. Copy Skills
|
|
266
|
+
const sourceSkillsDir = path.join(this.cwd, installRoot, "opencode/skills");
|
|
267
|
+
const skillFolders = await fs.readdir(sourceSkillsDir).catch(() => []);
|
|
268
|
+
for (const folder of skillFolders) {
|
|
269
|
+
const skillSource = path.join(sourceSkillsDir, folder, "SKILL.md");
|
|
270
|
+
if (await this.exists(skillSource)) {
|
|
271
|
+
await fs.mkdir(path.join(antigravitySkills, folder), { recursive: true });
|
|
272
|
+
const content = await fs.readFile(skillSource, "utf8");
|
|
273
|
+
await fs.writeFile(path.join(antigravitySkills, folder, "SKILL.md"), content);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 4. Copy Commands (from dist-assets/commands)
|
|
278
|
+
const sourceCommandsDir = path.join(this.cwd, installRoot, "opencode/commands");
|
|
279
|
+
if (await this.exists(sourceCommandsDir)) {
|
|
280
|
+
const commandFiles = await fs.readdir(sourceCommandsDir).catch(() => []);
|
|
281
|
+
for (const file of commandFiles) {
|
|
282
|
+
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
283
|
+
const content = await fs.readFile(path.join(sourceCommandsDir, file), "utf8");
|
|
284
|
+
await fs.writeFile(path.join(antigravityCommands, file), content);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 5. Copy governance policy into .agents/docs/policies/ so the relative
|
|
289
|
+
// link ../../docs/policies/SKILLS_COMMON_GOVERNANCE.md in each
|
|
290
|
+
// .agents/skills/{skill}/SKILL.md resolves correctly.
|
|
291
|
+
const sourcePoliciesDir = path.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
292
|
+
const antigravityPolicies = path.join(antigravityDir, "docs", "policies");
|
|
293
|
+
if (await this.exists(sourcePoliciesDir)) {
|
|
294
|
+
await fs.mkdir(antigravityPolicies, { recursive: true });
|
|
295
|
+
const policyFiles = await fs.readdir(sourcePoliciesDir).catch(() => []);
|
|
296
|
+
for (const file of policyFiles) {
|
|
297
|
+
if (!file.endsWith(".md")) continue;
|
|
298
|
+
const content = await fs.readFile(path.join(sourcePoliciesDir, file), "utf8");
|
|
299
|
+
await fs.writeFile(path.join(antigravityPolicies, file), content);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 6. Inject project settings.json
|
|
304
|
+
const settingsPath = path.join(antigravityDir, "settings.json");
|
|
305
|
+
if (!(await this.exists(settingsPath))) {
|
|
306
|
+
const settings = {
|
|
307
|
+
model: { name: "auto" },
|
|
308
|
+
ui: { theme: "Ayu" }
|
|
309
|
+
};
|
|
310
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async exists(p) {
|
|
315
|
+
try {
|
|
316
|
+
await fs.access(p);
|
|
317
|
+
return true;
|
|
318
|
+
} catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Deploys the main ANTIGRAVITY.md and .antigravityignore instructions to the project root.
|
|
325
|
+
*/
|
|
326
|
+
async deployInstructions() {
|
|
327
|
+
const targetPath = path.join(this.cwd, "ANTIGRAVITY.md");
|
|
328
|
+
const ignorePath = path.join(this.cwd, ".antigravityignore");
|
|
329
|
+
|
|
330
|
+
// Deploy ANTIGRAVITY.md if it doesn't exist
|
|
331
|
+
try {
|
|
332
|
+
await fs.access(targetPath);
|
|
333
|
+
} catch {
|
|
334
|
+
const instructions = `# AI Workflow Kit - Engineering Governance
|
|
335
|
+
|
|
336
|
+
## Mandate: Atlas Authority Protocol
|
|
337
|
+
All architectural changes must align with the specifications issued by the **Specification Authority** (./specs).
|
|
338
|
+
|
|
339
|
+
## Ternary Architecture
|
|
340
|
+
- **Root:** Orchestration and config.
|
|
341
|
+
- **.ai-workflow/:** Implementation (The Muscle).
|
|
342
|
+
- **./specs:** Specifications (The Brain).
|
|
343
|
+
|
|
344
|
+
## Branch Gates
|
|
345
|
+
- **main Branch:** READ-ONLY.
|
|
346
|
+
- **Validation:** Merges require successful observed validation; full/release workflows may persist EVIDENCE.json.
|
|
347
|
+
|
|
348
|
+
## Primary Agents
|
|
349
|
+
- **Atlas (Orchestrator)**
|
|
350
|
+
- **Orion (Strategist)**
|
|
351
|
+
- **Sage (Auditor)**
|
|
352
|
+
- **Nexus (Spec Architect)**
|
|
353
|
+
- **Astra (Developer)**
|
|
354
|
+
- **Phoenix (Healer)**
|
|
355
|
+
|
|
356
|
+
## Operational Preferences
|
|
357
|
+
- Use \`token-economy\` and \`minimal-context\` skills.
|
|
358
|
+
- **Native Discovery**: This project uses workspace-specific extensions in \`.agents/\`.
|
|
359
|
+
- **Workflow Entry**: Use \`/atlas\` to begin any task.
|
|
360
|
+
`;
|
|
361
|
+
await fs.writeFile(targetPath, instructions);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Deploy .antigravityignore if it doesn't exist
|
|
365
|
+
try {
|
|
366
|
+
await fs.access(ignorePath);
|
|
367
|
+
} catch {
|
|
368
|
+
const ignoreContent = `.ai-workflow/
|
|
369
|
+
.ai-workflow-backups/
|
|
370
|
+
node_modules/
|
|
371
|
+
.git/
|
|
372
|
+
EVIDENCE.json
|
|
373
|
+
*.tgz
|
|
374
|
+
*.zip
|
|
375
|
+
*.tar
|
|
376
|
+
.agents/tmp/
|
|
377
|
+
.agents/history/
|
|
378
|
+
`;
|
|
379
|
+
await fs.writeFile(ignorePath, ignoreContent);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -87,6 +87,19 @@ Every task completion MUST provide the following payload:
|
|
|
87
87
|
const content = await fs.readFile(path.join(sourceCommandsDir, file), "utf8");
|
|
88
88
|
await fs.writeFile(path.join(codexPromptsDir, targetFileName), content);
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
// 4. Copy governance policy into .agents/docs/policies/ so the relative
|
|
92
|
+
// link ../../docs/policies/SKILLS_COMMON_GOVERNANCE.md in each
|
|
93
|
+
// .agents/skills/{skill}/SKILL.md resolves correctly.
|
|
94
|
+
const codexPoliciesDir = path.join(this.cwd, ".agents", "docs", "policies");
|
|
95
|
+
await fs.mkdir(codexPoliciesDir, { recursive: true });
|
|
96
|
+
const sourcePoliciesDir = path.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
97
|
+
const policyFiles = await fs.readdir(sourcePoliciesDir).catch(() => []);
|
|
98
|
+
for (const file of policyFiles) {
|
|
99
|
+
if (!file.endsWith(".md")) continue;
|
|
100
|
+
const policyContent = await fs.readFile(path.join(sourcePoliciesDir, file), "utf8");
|
|
101
|
+
await fs.writeFile(path.join(codexPoliciesDir, file), policyContent);
|
|
102
|
+
}
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
async exists(p) {
|
|
@@ -150,7 +150,20 @@ Every task completion MUST provide the following payload:
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// 4.
|
|
153
|
+
// 4. Copy governance policy into .gemini/docs/policies/ so the relative
|
|
154
|
+
// link ../../docs/policies/SKILLS_COMMON_GOVERNANCE.md in each
|
|
155
|
+
// .gemini/skills/{skill}/SKILL.md resolves correctly.
|
|
156
|
+
const geminiPoliciesDir = path.join(geminiDir, "docs", "policies");
|
|
157
|
+
await fs.mkdir(geminiPoliciesDir, { recursive: true });
|
|
158
|
+
const sourcePoliciesDir = path.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
159
|
+
const policyFiles = await fs.readdir(sourcePoliciesDir).catch(() => []);
|
|
160
|
+
for (const file of policyFiles) {
|
|
161
|
+
if (!file.endsWith(".md")) continue;
|
|
162
|
+
const content = await fs.readFile(path.join(sourcePoliciesDir, file), "utf8");
|
|
163
|
+
await fs.writeFile(path.join(geminiPoliciesDir, file), content);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 5. Inject project settings.json
|
|
154
167
|
const settingsPath = path.join(geminiDir, "settings.json");
|
|
155
168
|
if (!(await this.exists(settingsPath))) {
|
|
156
169
|
const settings = {
|
package/src/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ function printHelp() {
|
|
|
11
11
|
Usage:
|
|
12
12
|
ai-workflow execute "<request>" [--task=<slug>] [--request="<request>"]
|
|
13
13
|
ai-workflow run --spec-path=<path>
|
|
14
|
-
ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--gemini] [--claude] [--codex] [--profile=<profile>]
|
|
14
|
+
ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--gemini] [--claude] [--codex] [--antigravity] [--profile=<profile>]
|
|
15
15
|
ai-workflow collect-evidence [--task=<slug>] [--mode=<quick|standard|full>] [--dry-run]
|
|
16
16
|
ai-workflow doctor
|
|
17
17
|
|
|
@@ -46,6 +46,7 @@ function parseFlags(args) {
|
|
|
46
46
|
gemini: args.includes("--gemini"),
|
|
47
47
|
claude: args.includes("--claude"),
|
|
48
48
|
codex: args.includes("--codex"),
|
|
49
|
+
antigravity: args.includes("--antigravity"),
|
|
49
50
|
"dev-mode": args.includes("--dev-mode"),
|
|
50
51
|
specPath: specPathArg ? specPathArg.replace("--spec-path=", "") : undefined,
|
|
51
52
|
profile: profileVal || undefined,
|
package/src/commands/init.js
CHANGED
|
@@ -6,7 +6,7 @@ import { exists, writeFileSafe, readJson } from "../core/filesystem.js";
|
|
|
6
6
|
import { createManagedBackup, createManagedPathBackup } from "../core/backup.js";
|
|
7
7
|
import { mergeOpencodeConfig } from "../core/opencode-merge.js";
|
|
8
8
|
import { buildSymlinkEntries, isSymlinkTo, setupInternalSymlinks } from "../core/symlink-layout.js";
|
|
9
|
-
import { GeminiAdapter, ClaudeAdapter, CodexAdapter } from "../adapters/index.js";
|
|
9
|
+
import { GeminiAdapter, ClaudeAdapter, CodexAdapter, AntigravityAdapter } from "../adapters/index.js";
|
|
10
10
|
|
|
11
11
|
function summarize(actions) {
|
|
12
12
|
const createCount = actions.filter((a) => a.type === "create").length;
|
|
@@ -100,7 +100,7 @@ async function injectScripts(cwd) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
export async function runInit({
|
|
103
|
-
cwd, yes, force, dryRun, noInstall, noOverwrite, gemini, claude, codex, "dev-mode": devMode, profile }) {
|
|
103
|
+
cwd, yes, force, dryRun, noInstall, noOverwrite, gemini, claude, codex, antigravity, "dev-mode": devMode, profile }) {
|
|
104
104
|
// Self-execution guard
|
|
105
105
|
if (!devMode) {
|
|
106
106
|
try {
|
|
@@ -131,6 +131,7 @@ export async function runInit({
|
|
|
131
131
|
if (gemini) platforms.push("gemini");
|
|
132
132
|
if (claude) platforms.push("claude");
|
|
133
133
|
if (codex) platforms.push("codex");
|
|
134
|
+
if (antigravity) platforms.push("antigravity");
|
|
134
135
|
|
|
135
136
|
console.log(`ai-workflow: initializing in ${cwd}`);
|
|
136
137
|
console.log(`Profile: ${selectedProfile}`);
|
|
@@ -260,6 +261,9 @@ export async function runInit({
|
|
|
260
261
|
if (claude) {
|
|
261
262
|
generatedIgnoreEntries.push(".claude/");
|
|
262
263
|
}
|
|
264
|
+
if (antigravity) {
|
|
265
|
+
generatedIgnoreEntries.push(".agents/");
|
|
266
|
+
}
|
|
263
267
|
|
|
264
268
|
const gitignoreResult = await upsertGitignoreBlock(cwd, generatedIgnoreEntries);
|
|
265
269
|
const scriptInjected = await injectScripts(cwd);
|
|
@@ -303,6 +307,25 @@ export async function runInit({
|
|
|
303
307
|
await adapter.deploy(installRoot);
|
|
304
308
|
console.log("- codex: deployed AI Workflow Kit agents to .agents/, .codex/, and .github/ (native discovery enabled)");
|
|
305
309
|
}
|
|
310
|
+
|
|
311
|
+
if (antigravity) {
|
|
312
|
+
const adapter = new AntigravityAdapter({ cwd });
|
|
313
|
+
|
|
314
|
+
// Collect specialized skills from the new opencode/skills path
|
|
315
|
+
const skillFolders = await fs.readdir(skillsDir).catch(() => []);
|
|
316
|
+
const skillFiles = [];
|
|
317
|
+
for (const folder of skillFolders) {
|
|
318
|
+
const skillPath = path.join(skillsDir, folder, "SKILL.md");
|
|
319
|
+
if (await exists(skillPath)) {
|
|
320
|
+
skillFiles.push(skillPath);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const allFiles = [...agentFiles, ...skillFiles];
|
|
325
|
+
const transformed = await Promise.all(allFiles.map((f) => adapter.transform(f)));
|
|
326
|
+
await adapter.deploy(transformed, installRoot);
|
|
327
|
+
console.log(`- antigravity: deployed AI Workflow Kit agents to .agents/ (native discovery enabled)`);
|
|
328
|
+
}
|
|
306
329
|
}
|
|
307
330
|
|
|
308
331
|
console.log("Installation complete.");
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MergeGate verifies that commit history on integration branches respects
|
|
5
|
+
* the non-fast-forward (--no-ff) merge strategy, preserving feature branch
|
|
6
|
+
* topology instead of flattening/fast-forwarding.
|
|
7
|
+
*/
|
|
8
|
+
export class MergeGate {
|
|
9
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
10
|
+
this.cwd = cwd;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
runGit(command) {
|
|
14
|
+
return execSync(command, {
|
|
15
|
+
cwd: this.cwd,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18
|
+
}).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Checks the commit history of the repository/branch.
|
|
23
|
+
* @param {string} branchName The branch to check.
|
|
24
|
+
* @returns {{ status: "PASS" | "FAIL_QUALITY_GATE" | "ERROR", reason?: string }}
|
|
25
|
+
*/
|
|
26
|
+
check(branchName) {
|
|
27
|
+
try {
|
|
28
|
+
// Get the last 50 commits with parent hashes and subjects
|
|
29
|
+
const logOutput = this.runGit('git log -n 50 --format="%H|%P|%s"');
|
|
30
|
+
const lines = logOutput.split("\n").filter(Boolean);
|
|
31
|
+
|
|
32
|
+
const commits = lines.map((line) => {
|
|
33
|
+
const parts = line.split("|");
|
|
34
|
+
const hash = parts[0] || "";
|
|
35
|
+
const parentsStr = parts[1] || "";
|
|
36
|
+
const parents = parentsStr.split(" ").filter(Boolean);
|
|
37
|
+
const subject = parts[2] || "";
|
|
38
|
+
return { hash, parents, subject };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 1. Enforce that any commit containing merge-like patterns in its subject must have at least 2 parents
|
|
42
|
+
for (const commit of commits) {
|
|
43
|
+
const isMergeSubject = /merge\b/i.test(commit.subject) || /pull request/i.test(commit.subject);
|
|
44
|
+
if (isMergeSubject && commit.parents.length < 2) {
|
|
45
|
+
return {
|
|
46
|
+
status: "FAIL_QUALITY_GATE",
|
|
47
|
+
reason: `Linear or fast-forward merge detected for commit '${commit.hash.slice(0, 7)}' ("${commit.subject}"). Merges into integration branches must use the non-fast-forward (--no-ff) strategy to preserve topology.`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. If the current branch is an integration branch, ensure it contains at least one merge commit (2+ parents)
|
|
53
|
+
// to establish non-linear history, unless the repository has very few commits (e.g. initial setup).
|
|
54
|
+
const isIntegrationBranch = ["main", "master"].includes(branchName) || String(branchName).startsWith("release");
|
|
55
|
+
if (isIntegrationBranch && commits.length > 5) {
|
|
56
|
+
const hasMergeCommit = commits.some((c) => c.parents.length >= 2);
|
|
57
|
+
if (!hasMergeCommit) {
|
|
58
|
+
return {
|
|
59
|
+
status: "FAIL_QUALITY_GATE",
|
|
60
|
+
reason: `Integration branch '${branchName}' has a purely linear commit history. Integrated branches must use non-fast-forward (--no-ff) merges to preserve feature branches.`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { status: "PASS" };
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// If we are not inside a git repo or git is not available, we fail-closed or report error
|
|
68
|
+
return {
|
|
69
|
+
status: "ERROR",
|
|
70
|
+
reason: `Failed to check merge integrity: ${error.message}`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/core/templates.js
CHANGED
|
@@ -62,6 +62,14 @@ function buildRuntimeFiles({ includeFormalEvidence = false } = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// Copy governance policy into opencode/docs/policies/ so the relative link
|
|
66
|
+
// ../../docs/policies/SKILLS_COMMON_GOVERNANCE.md resolves correctly from
|
|
67
|
+
// opencode/skills/{skill}/SKILL.md for OpenCode and all platform adapters.
|
|
68
|
+
const opencodeGovernancePolicy = readPackageFile("dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md");
|
|
69
|
+
if (opencodeGovernancePolicy !== null) {
|
|
70
|
+
files["opencode/docs/policies/SKILLS_COMMON_GOVERNANCE.md"] = opencodeGovernancePolicy;
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
const commandFiles = discoverPackageFiles("dist-assets/commands");
|
|
66
74
|
for (const [relPath, content] of Object.entries(commandFiles)) {
|
|
67
75
|
const targetPath = relPath.replace(/^dist-assets\//, "opencode/");
|
|
@@ -42,9 +42,13 @@ export class EvidenceCollector {
|
|
|
42
42
|
if (task.presetStatus) {
|
|
43
43
|
return { name: task.name, command: task.command, kind, status: task.presetStatus, exitCode: null, summary: task.summary, output: "" };
|
|
44
44
|
}
|
|
45
|
+
const isWin32 = process.platform === "win32";
|
|
46
|
+
const isUncPath = typeof this.cwd === "string" && (this.cwd.startsWith("\\\\") || this.cwd.startsWith("//"));
|
|
47
|
+
const shellOption = isWin32 && isUncPath ? "powershell.exe" : true;
|
|
48
|
+
|
|
45
49
|
const result = spawnSync(task.command, {
|
|
46
50
|
cwd: this.cwd,
|
|
47
|
-
shell:
|
|
51
|
+
shell: shellOption,
|
|
48
52
|
encoding: "utf8",
|
|
49
53
|
timeout: this.timeout
|
|
50
54
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { MergeGate } from "../gates/merge-gate.js";
|
|
4
5
|
|
|
5
6
|
const PROTECTED_BRANCHES = new Set(["main", "master"]);
|
|
6
7
|
|
|
@@ -146,6 +147,13 @@ export class QualityGuard {
|
|
|
146
147
|
if (implementation && PROTECTED_BRANCHES.has(branch)) {
|
|
147
148
|
return { status: "FAIL_QUALITY_GATE", branch, reason: `Implementation changes are present on protected branch '${branch}'.` };
|
|
148
149
|
}
|
|
150
|
+
if (this.mode === "full") {
|
|
151
|
+
const mergeGate = new MergeGate({ cwd: this.cwd });
|
|
152
|
+
const mergeResult = mergeGate.check(branch);
|
|
153
|
+
if (mergeResult.status === "FAIL_QUALITY_GATE") {
|
|
154
|
+
return { status: "FAIL_QUALITY_GATE", branch, reason: mergeResult.reason };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
149
157
|
return { status: "PASS", branch };
|
|
150
158
|
}
|
|
151
159
|
|