@oz1307/forcefully-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +15 -0
- package/.agents/skills/documentation/SKILL.md +16 -0
- package/.agents/skills/implementation/SKILL.md +12 -0
- package/.agents/skills/orchestrator/SKILL.md +14 -0
- package/.agents/skills/validator/SKILL.md +12 -0
- package/.claude/rules/documentation.md +9 -0
- package/.claude/rules/implementation.md +9 -0
- package/.claude/rules/orchestrator.md +10 -0
- package/.claude/rules/validator.md +8 -0
- package/.cursor/rules/documentation.mdc +11 -0
- package/.cursor/rules/implementation.mdc +12 -0
- package/.cursor/rules/orchestrator.mdc +12 -0
- package/.cursor/rules/validator.mdc +11 -0
- package/.cursorrules +5 -0
- package/.github/copilot-instructions.md +7 -0
- package/CLAUDE.md +24 -0
- package/README.md +122 -0
- package/bin/cli.js +672 -0
- package/docs/01-product/business-rules.md +6 -0
- package/docs/01-product/terminology.md +6 -0
- package/docs/01-product/vision.md +10 -0
- package/docs/03-features/overview.md +6 -0
- package/docs/04-api/contracts.md +6 -0
- package/docs/05-database/schema.md +6 -0
- package/docs/08-development/architecture.md +6 -0
- package/docs/08-development/standards.md +9 -0
- package/docs/09-ai/agents/documentation.md +8 -0
- package/docs/09-ai/agents/implementation.md +9 -0
- package/docs/09-ai/agents/orchestrator.md +10 -0
- package/docs/09-ai/agents/validator.md +8 -0
- package/docs/09-ai/coding-rules.md +7 -0
- package/docs/11-knowledge-base/definitions.md +6 -0
- package/docs/11-knowledge-base/logs/implementation-logs.md +9 -0
- package/docs/README.md +23 -0
- package/lib/ai.js +273 -0
- package/lib/templates.js +427 -0
- package/package.json +33 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
import * as p from '@clack/prompts';
|
|
8
|
+
import {
|
|
9
|
+
generateVisionDoc,
|
|
10
|
+
generateBusinessRulesDoc,
|
|
11
|
+
generateTerminologyDoc,
|
|
12
|
+
generateFeaturesDoc,
|
|
13
|
+
generateApiContractsDoc,
|
|
14
|
+
generateDatabaseSchemaDoc,
|
|
15
|
+
generateArchitectureDoc,
|
|
16
|
+
generateDefinitionsDoc,
|
|
17
|
+
generateValidationQuestion,
|
|
18
|
+
refineSingleDoc
|
|
19
|
+
} from '../lib/ai.js';
|
|
20
|
+
import * as templates from '../lib/templates.js';
|
|
21
|
+
|
|
22
|
+
// Load existing environment variables
|
|
23
|
+
dotenv.config();
|
|
24
|
+
const localAgentsEnv = path.join(process.cwd(), '.env.agents');
|
|
25
|
+
if (fs.existsSync(localAgentsEnv)) {
|
|
26
|
+
dotenv.config({ path: localAgentsEnv, override: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const IGNORE_DIRS = new Set([
|
|
30
|
+
'node_modules',
|
|
31
|
+
'.git',
|
|
32
|
+
'.agents',
|
|
33
|
+
'.claude',
|
|
34
|
+
'.cursor',
|
|
35
|
+
'dist',
|
|
36
|
+
'build',
|
|
37
|
+
'out',
|
|
38
|
+
'docs',
|
|
39
|
+
'coverage',
|
|
40
|
+
'.nyc_output',
|
|
41
|
+
'package-lock.json',
|
|
42
|
+
'yarn.lock',
|
|
43
|
+
'pnpm-lock.yaml'
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const INTERESTING_EXTS = new Set([
|
|
47
|
+
'.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.rb', '.php', '.java', '.cs', '.json', '.md'
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Scan the repository recursively to build a file tree.
|
|
52
|
+
*/
|
|
53
|
+
function scanRepository(dir, baseDir = dir, depth = 0) {
|
|
54
|
+
let fileTree = '';
|
|
55
|
+
let fileCount = 0;
|
|
56
|
+
let folderCount = 0;
|
|
57
|
+
|
|
58
|
+
function recurse(currentDir, currentDepth) {
|
|
59
|
+
if (currentDepth > 4 || fileCount > 150) return;
|
|
60
|
+
|
|
61
|
+
let files;
|
|
62
|
+
try {
|
|
63
|
+
files = fs.readdirSync(currentDir);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (IGNORE_DIRS.has(file)) continue;
|
|
70
|
+
|
|
71
|
+
const fullPath = path.join(currentDir, file);
|
|
72
|
+
let stat;
|
|
73
|
+
try {
|
|
74
|
+
stat = fs.statSync(fullPath);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const indent = ' '.repeat(currentDepth);
|
|
80
|
+
|
|
81
|
+
if (stat.isDirectory()) {
|
|
82
|
+
fileTree += `${indent}📁 ${file}/\n`;
|
|
83
|
+
folderCount++;
|
|
84
|
+
recurse(fullPath, currentDepth + 1);
|
|
85
|
+
} else {
|
|
86
|
+
fileTree += `${indent}📄 ${file}\n`;
|
|
87
|
+
fileCount++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
recurse(dir, depth);
|
|
93
|
+
return { fileTree, fileCount, folderCount };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gathers content snippets from files matching specific keywords.
|
|
98
|
+
*/
|
|
99
|
+
function gatherSnippetContext(baseDir, keywords, excludeDirs = IGNORE_DIRS) {
|
|
100
|
+
let content = '';
|
|
101
|
+
let count = 0;
|
|
102
|
+
|
|
103
|
+
function recurse(currentDir) {
|
|
104
|
+
if (count >= 6) return;
|
|
105
|
+
|
|
106
|
+
let files;
|
|
107
|
+
try {
|
|
108
|
+
files = fs.readdirSync(currentDir);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
if (excludeDirs.has(file)) continue;
|
|
115
|
+
|
|
116
|
+
const fullPath = path.join(currentDir, file);
|
|
117
|
+
let stat;
|
|
118
|
+
try {
|
|
119
|
+
stat = fs.statSync(fullPath);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (stat.isDirectory()) {
|
|
125
|
+
recurse(fullPath);
|
|
126
|
+
} else {
|
|
127
|
+
const ext = path.extname(file).toLowerCase();
|
|
128
|
+
if (INTERESTING_EXTS.has(ext) && stat.size < 50000) {
|
|
129
|
+
const lowerName = file.toLowerCase();
|
|
130
|
+
const matches = keywords.some(keyword => lowerName.includes(keyword));
|
|
131
|
+
if (matches) {
|
|
132
|
+
try {
|
|
133
|
+
const fileContent = fs.readFileSync(fullPath, 'utf8');
|
|
134
|
+
const relative = path.relative(baseDir, fullPath);
|
|
135
|
+
// Grab first 150 lines
|
|
136
|
+
const lines = fileContent.split('\n').slice(0, 150).join('\n');
|
|
137
|
+
content += `\n--- File: ${relative} ---\n${lines}\n`;
|
|
138
|
+
count++;
|
|
139
|
+
if (count >= 6) return;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Ignore
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
recurse(baseDir);
|
|
150
|
+
return content;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Ensures a directory exists.
|
|
155
|
+
*/
|
|
156
|
+
function ensureDir(dirPath) {
|
|
157
|
+
if (!fs.existsSync(dirPath)) {
|
|
158
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Writes a file.
|
|
164
|
+
*/
|
|
165
|
+
function writeFile(filePath, content) {
|
|
166
|
+
ensureDir(path.dirname(filePath));
|
|
167
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Appends api key to .env.agents and updates .gitignore.
|
|
172
|
+
*/
|
|
173
|
+
function saveApiKeyToEnv(provider, apiKey) {
|
|
174
|
+
const envFile = path.join(process.cwd(), '.env.agents');
|
|
175
|
+
const varName = `${provider.toUpperCase()}_API_KEY`;
|
|
176
|
+
|
|
177
|
+
let envContent = '';
|
|
178
|
+
if (fs.existsSync(envFile)) {
|
|
179
|
+
envContent = fs.readFileSync(envFile, 'utf8');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const lines = envContent.split('\n').filter(line => !line.trim().startsWith(varName));
|
|
183
|
+
lines.push(`${varName}=${apiKey}`);
|
|
184
|
+
fs.writeFileSync(envFile, lines.join('\n').trim() + '\n', 'utf8');
|
|
185
|
+
|
|
186
|
+
// Update .gitignore
|
|
187
|
+
const gitignoreFile = path.join(process.cwd(), '.gitignore');
|
|
188
|
+
let gitignoreContent = '';
|
|
189
|
+
if (fs.existsSync(gitignoreFile)) {
|
|
190
|
+
gitignoreContent = fs.readFileSync(gitignoreFile, 'utf8');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!gitignoreContent.includes('.env.agents')) {
|
|
194
|
+
fs.writeFileSync(gitignoreFile, gitignoreContent.trim() + '\n.env.agents\n', 'utf8');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Common helper to resolve AI configuration from env or prompt.
|
|
200
|
+
*/
|
|
201
|
+
async function resolveAIConfig(message = 'Choose an AI service to scan and onboard your codebase:') {
|
|
202
|
+
let provider = process.env.FORCEFULLY_PROVIDER;
|
|
203
|
+
let apiKey = '';
|
|
204
|
+
|
|
205
|
+
if (process.env.GEMINI_API_KEY) {
|
|
206
|
+
provider = 'gemini';
|
|
207
|
+
apiKey = process.env.GEMINI_API_KEY;
|
|
208
|
+
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
209
|
+
provider = 'claude';
|
|
210
|
+
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
211
|
+
} else if (process.env.OPENAI_API_KEY) {
|
|
212
|
+
provider = 'openai';
|
|
213
|
+
apiKey = process.env.OPENAI_API_KEY;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (provider && apiKey) {
|
|
217
|
+
p.note(`Found existing credentials for: ${provider} via environment variables or .env.agents.`, 'Credentials Info');
|
|
218
|
+
return { provider, apiKey };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const selectedProvider = await p.select({
|
|
222
|
+
message,
|
|
223
|
+
options: [
|
|
224
|
+
{ value: 'gemini', label: 'Google Gemini (gemini-2.5-flash)' },
|
|
225
|
+
{ value: 'claude', label: 'Anthropic Claude (claude-3-5-sonnet)' },
|
|
226
|
+
{ value: 'openai', label: 'OpenAI (gpt-4o-mini)' }
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (p.isCancel(selectedProvider)) {
|
|
231
|
+
p.cancel('Setup cancelled.');
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
provider = selectedProvider;
|
|
236
|
+
const envVar = `${provider.toUpperCase()}_API_KEY`;
|
|
237
|
+
|
|
238
|
+
const inputKey = await p.password({
|
|
239
|
+
message: `Enter your ${envVar}:`,
|
|
240
|
+
validate: (value) => {
|
|
241
|
+
if (!value || value.trim().length === 0) return 'API key is required.';
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (p.isCancel(inputKey)) {
|
|
246
|
+
p.cancel('Setup cancelled.');
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
apiKey = inputKey;
|
|
251
|
+
|
|
252
|
+
const saveEnv = await p.confirm({
|
|
253
|
+
message: 'Would you like to save this key to a local .env.agents file (automatically added to .gitignore)?',
|
|
254
|
+
initialValue: true
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (p.isCancel(saveEnv)) {
|
|
258
|
+
p.cancel('Setup cancelled.');
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (saveEnv) {
|
|
263
|
+
saveApiKeyToEnv(provider, apiKey);
|
|
264
|
+
p.log.success('Saved API Key to .env.agents and updated .gitignore');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { provider, apiKey };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function main() {
|
|
271
|
+
console.clear();
|
|
272
|
+
p.intro('🚀 ForcefullyAgent Initializer');
|
|
273
|
+
|
|
274
|
+
// Step 1: Resolve AI provider and API Key
|
|
275
|
+
const { provider, apiKey } = await resolveAIConfig();
|
|
276
|
+
|
|
277
|
+
// Step 2: Codebase Scanning
|
|
278
|
+
const scanSpinner = p.spinner();
|
|
279
|
+
scanSpinner.start('Scanning repository structure and source files...');
|
|
280
|
+
const cwd = process.cwd();
|
|
281
|
+
const { fileTree, fileCount, folderCount } = scanRepository(cwd);
|
|
282
|
+
|
|
283
|
+
// Read package metadata and readme if present
|
|
284
|
+
let packageJsonContent = '{}';
|
|
285
|
+
try {
|
|
286
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
287
|
+
if (fs.existsSync(pkgPath)) {
|
|
288
|
+
packageJsonContent = fs.readFileSync(pkgPath, 'utf8');
|
|
289
|
+
}
|
|
290
|
+
} catch (e) {}
|
|
291
|
+
|
|
292
|
+
let readmeContent = '';
|
|
293
|
+
try {
|
|
294
|
+
const readmePath = path.join(cwd, 'README.md');
|
|
295
|
+
if (fs.existsSync(readmePath)) {
|
|
296
|
+
readmeContent = fs.readFileSync(readmePath, 'utf8');
|
|
297
|
+
}
|
|
298
|
+
} catch (e) {}
|
|
299
|
+
|
|
300
|
+
// Gather specific code file types for deep-dive analysis
|
|
301
|
+
const dbFilesCode = gatherSnippetContext(cwd, ['model', 'schema', 'db', 'migration', 'database', 'table', 'orm']);
|
|
302
|
+
const routerFilesCode = gatherSnippetContext(cwd, ['route', 'router', 'api', 'controller', 'handler', 'endpoint']);
|
|
303
|
+
const logicFilesCode = gatherSnippetContext(cwd, ['service', 'logic', 'helper', 'utils', 'lib', 'util', 'core']);
|
|
304
|
+
const mainFilesCode = gatherSnippetContext(cwd, ['index', 'main', 'app', 'server']);
|
|
305
|
+
|
|
306
|
+
scanSpinner.stop(`Scan complete. Analyzed ${fileCount} files. Deep-scanned database models, routes, and logic scripts.`);
|
|
307
|
+
|
|
308
|
+
// Step 3: Write Rule Configurations
|
|
309
|
+
const configSpinner = p.spinner();
|
|
310
|
+
configSpinner.start('Writing AI agent rule files...');
|
|
311
|
+
|
|
312
|
+
writeFile(path.join(cwd, '.agents', 'AGENTS.md'), templates.getAntigravityAgentsRules());
|
|
313
|
+
writeFile(path.join(cwd, '.agents', 'skills', 'orchestrator', 'SKILL.md'), templates.getAntigravitySkill('orchestrator'));
|
|
314
|
+
writeFile(path.join(cwd, '.agents', 'skills', 'implementation', 'SKILL.md'), templates.getAntigravitySkill('implementation'));
|
|
315
|
+
writeFile(path.join(cwd, '.agents', 'skills', 'documentation', 'SKILL.md'), templates.getAntigravitySkill('documentation'));
|
|
316
|
+
writeFile(path.join(cwd, '.agents', 'skills', 'validator', 'SKILL.md'), templates.getAntigravitySkill('validator'));
|
|
317
|
+
|
|
318
|
+
writeFile(path.join(cwd, 'CLAUDE.md'), templates.getClaudeInstructions());
|
|
319
|
+
writeFile(path.join(cwd, '.claude', 'rules', 'orchestrator.md'), templates.getClaudeRule('orchestrator'));
|
|
320
|
+
writeFile(path.join(cwd, '.claude', 'rules', 'implementation.md'), templates.getClaudeRule('implementation'));
|
|
321
|
+
writeFile(path.join(cwd, '.claude', 'rules', 'documentation.md'), templates.getClaudeRule('documentation'));
|
|
322
|
+
writeFile(path.join(cwd, '.claude', 'rules', 'validator.md'), templates.getClaudeRule('validator'));
|
|
323
|
+
|
|
324
|
+
writeFile(path.join(cwd, '.cursorrules'), templates.getCursorRulesLegacy());
|
|
325
|
+
writeFile(path.join(cwd, '.cursor', 'rules', 'orchestrator.mdc'), templates.getCursorRule('orchestrator'));
|
|
326
|
+
writeFile(path.join(cwd, '.cursor', 'rules', 'implementation.mdc'), templates.getCursorRule('implementation'));
|
|
327
|
+
writeFile(path.join(cwd, '.cursor', 'rules', 'documentation.mdc'), templates.getCursorRule('documentation'));
|
|
328
|
+
writeFile(path.join(cwd, '.cursor', 'rules', 'validator.mdc'), templates.getCursorRule('validator'));
|
|
329
|
+
|
|
330
|
+
writeFile(path.join(cwd, '.github', 'copilot-instructions.md'), templates.getCopilotInstructions());
|
|
331
|
+
|
|
332
|
+
configSpinner.stop('AI agent rule files written.');
|
|
333
|
+
p.log.success('✔ Generated agent rule configurations.');
|
|
334
|
+
|
|
335
|
+
// Step 4: Multi-Doc Deep-Dive AI Generation
|
|
336
|
+
const docSpinner = p.spinner();
|
|
337
|
+
docSpinner.start('Connecting to AI to generate deep-dive documentation files one by one...');
|
|
338
|
+
|
|
339
|
+
// Helper to generate docs sequentially using AI or templates fallback
|
|
340
|
+
async function generateDoc(docPath, generateFunc, args, fallbackContent) {
|
|
341
|
+
docSpinner.message(`Generating deep-dive ${path.basename(docPath)}...`);
|
|
342
|
+
try {
|
|
343
|
+
const content = await generateFunc(args);
|
|
344
|
+
if (content && content.trim().length > 10) {
|
|
345
|
+
writeFile(docPath, content);
|
|
346
|
+
return content;
|
|
347
|
+
}
|
|
348
|
+
} catch (e) {
|
|
349
|
+
// Fallback
|
|
350
|
+
}
|
|
351
|
+
writeFile(docPath, fallbackContent);
|
|
352
|
+
return fallbackContent;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Pre-scaffold README
|
|
356
|
+
writeFile(path.join(cwd, 'docs', 'README.md'), templates.getDocsReadme());
|
|
357
|
+
|
|
358
|
+
const dummyAnalysisResult = {
|
|
359
|
+
vision: 'Product vision detailed analysis.',
|
|
360
|
+
businessRules: '- Analyzed business rules list.',
|
|
361
|
+
terminology: '- Glossary definitions.',
|
|
362
|
+
features: '- Discovered system features list.',
|
|
363
|
+
apiContracts: 'System API endpoints list.',
|
|
364
|
+
databaseSchema: 'System database models catalog.',
|
|
365
|
+
architecture: 'Folder and architectural structure.',
|
|
366
|
+
definitions: '- Support dictionary rules.'
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// vision.md
|
|
370
|
+
await generateDoc(
|
|
371
|
+
path.join(cwd, 'docs', '01-product', 'vision.md'),
|
|
372
|
+
generateVisionDoc,
|
|
373
|
+
{ provider, apiKey, fileTree, packageJson: packageJsonContent, readme: readmeContent },
|
|
374
|
+
templates.getVisionTemplate(dummyAnalysisResult)
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
// business-rules.md
|
|
378
|
+
await generateDoc(
|
|
379
|
+
path.join(cwd, 'docs', '01-product', 'business-rules.md'),
|
|
380
|
+
generateBusinessRulesDoc,
|
|
381
|
+
{ provider, apiKey, fileTree, logicFilesCode },
|
|
382
|
+
templates.getBusinessRulesTemplate(dummyAnalysisResult)
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// terminology.md
|
|
386
|
+
const terminologyContent = await generateDoc(
|
|
387
|
+
path.join(cwd, 'docs', '01-product', 'terminology.md'),
|
|
388
|
+
generateTerminologyDoc,
|
|
389
|
+
{ provider, apiKey, fileTree, modelFilesCode: dbFilesCode },
|
|
390
|
+
templates.getTerminologyTemplate(dummyAnalysisResult)
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
// overview.md
|
|
394
|
+
await generateDoc(
|
|
395
|
+
path.join(cwd, 'docs', '03-features', 'overview.md'),
|
|
396
|
+
generateFeaturesDoc,
|
|
397
|
+
{ provider, apiKey, fileTree, mainFilesCode },
|
|
398
|
+
templates.getFeaturesTemplate(dummyAnalysisResult)
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
// contracts.md
|
|
402
|
+
await generateDoc(
|
|
403
|
+
path.join(cwd, 'docs', '04-api', 'contracts.md'),
|
|
404
|
+
generateApiContractsDoc,
|
|
405
|
+
{ provider, apiKey, routerFilesCode },
|
|
406
|
+
templates.getApiContractsTemplate(dummyAnalysisResult)
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// schema.md
|
|
410
|
+
await generateDoc(
|
|
411
|
+
path.join(cwd, 'docs', '05-database', 'schema.md'),
|
|
412
|
+
generateDatabaseSchemaDoc,
|
|
413
|
+
{ provider, apiKey, dbFilesCode },
|
|
414
|
+
templates.getDatabaseSchemaTemplate(dummyAnalysisResult)
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// architecture.md
|
|
418
|
+
await generateDoc(
|
|
419
|
+
path.join(cwd, 'docs', '08-development', 'architecture.md'),
|
|
420
|
+
generateArchitectureDoc,
|
|
421
|
+
{ provider, apiKey, fileTree, packageJson: packageJsonContent, configFilesCode: mainFilesCode },
|
|
422
|
+
templates.getArchitectureTemplate(dummyAnalysisResult)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// definitions.md
|
|
426
|
+
await generateDoc(
|
|
427
|
+
path.join(cwd, 'docs', '11-knowledge-base', 'definitions.md'),
|
|
428
|
+
generateDefinitionsDoc,
|
|
429
|
+
{ provider, apiKey, fileTree, terminologyContent },
|
|
430
|
+
templates.getDefinitionsTemplate(dummyAnalysisResult)
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Scaffolding standards and agent pages
|
|
434
|
+
writeFile(path.join(cwd, 'docs', '08-development', 'standards.md'), templates.getDevelopmentStandardsTemplate());
|
|
435
|
+
writeFile(path.join(cwd, 'docs', '09-ai', 'agents', 'orchestrator.md'), templates.getAgentDoc('orchestrator'));
|
|
436
|
+
writeFile(path.join(cwd, 'docs', '09-ai', 'agents', 'implementation.md'), templates.getAgentDoc('implementation'));
|
|
437
|
+
writeFile(path.join(cwd, 'docs', '09-ai', 'agents', 'documentation.md'), templates.getAgentDoc('documentation'));
|
|
438
|
+
writeFile(path.join(cwd, 'docs', '09-ai', 'agents', 'validator.md'), templates.getAgentDoc('validator'));
|
|
439
|
+
writeFile(path.join(cwd, 'docs', '09-ai', 'coding-rules.md'), templates.getCodingRules());
|
|
440
|
+
writeFile(path.join(cwd, 'docs', '11-knowledge-base', 'logs', 'implementation-logs.md'), templates.getImplementationLogsTemplate());
|
|
441
|
+
|
|
442
|
+
docSpinner.stop('Multi-doc deep-dive documentation files generated.');
|
|
443
|
+
p.log.success('✔ Scaffolded and generated detailed repository specification documents under docs/.');
|
|
444
|
+
|
|
445
|
+
p.outro('🎉 ForcefullyAgent has been successfully configured! Any AI assistant (Antigravity, Claude Code, Cursor, or Copilot) will now automatically operate under the 4-agent orchestration protocol.');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function validateSetup() {
|
|
449
|
+
p.intro('🔍 ForcefullyAgent Configuration Validator');
|
|
450
|
+
|
|
451
|
+
const cwd = process.cwd();
|
|
452
|
+
const filesToCheck = [
|
|
453
|
+
{ path: '.agents/AGENTS.md', label: 'Antigravity Rules (AGENTS.md)' },
|
|
454
|
+
{ path: '.agents/skills/orchestrator/SKILL.md', label: 'Antigravity Orchestrator Skill' },
|
|
455
|
+
{ path: '.agents/skills/implementation/SKILL.md', label: 'Antigravity Implementation Skill' },
|
|
456
|
+
{ path: '.agents/skills/documentation/SKILL.md', label: 'Antigravity Documentation Skill' },
|
|
457
|
+
{ path: '.agents/skills/validator/SKILL.md', label: 'Antigravity Validator Skill' },
|
|
458
|
+
{ path: 'CLAUDE.md', label: 'Claude Instructions (CLAUDE.md)' },
|
|
459
|
+
{ path: '.claude/rules/orchestrator.md', label: 'Claude Orchestrator Rule' },
|
|
460
|
+
{ path: '.claude/rules/implementation.md', label: 'Claude Implementation Rule' },
|
|
461
|
+
{ path: '.claude/rules/documentation.md', label: 'Claude Documentation Rule' },
|
|
462
|
+
{ path: '.claude/rules/validator.md', label: 'Claude Validator Rule' },
|
|
463
|
+
{ path: '.cursorrules', label: 'Cursor Rules Legacy (.cursorrules)' },
|
|
464
|
+
{ path: '.cursor/rules/orchestrator.mdc', label: 'Cursor Orchestrator Rule' },
|
|
465
|
+
{ path: '.cursor/rules/implementation.mdc', label: 'Cursor Implementation Rule' },
|
|
466
|
+
{ path: '.cursor/rules/documentation.mdc', label: 'Cursor Documentation Rule' },
|
|
467
|
+
{ path: '.cursor/rules/validator.mdc', label: 'Cursor Validator Rule' },
|
|
468
|
+
{ path: '.github/copilot-instructions.md', label: 'Copilot Instructions' },
|
|
469
|
+
{ path: 'docs/README.md', label: 'Docs Index (README.md)' },
|
|
470
|
+
{ path: 'docs/01-product/vision.md', label: 'Product Vision' },
|
|
471
|
+
{ path: 'docs/01-product/business-rules.md', label: 'Product Business Rules' },
|
|
472
|
+
{ path: 'docs/01-product/terminology.md', label: 'Product Terminology Glossary' },
|
|
473
|
+
{ path: 'docs/03-features/overview.md', label: 'Features Overview' },
|
|
474
|
+
{ path: 'docs/04-api/contracts.md', label: 'API Endpoint Contracts' },
|
|
475
|
+
{ path: 'docs/05-database/schema.md', label: 'Database Schema Details' },
|
|
476
|
+
{ path: 'docs/08-development/architecture.md', label: 'Development Architecture' },
|
|
477
|
+
{ path: 'docs/08-development/standards.md', label: 'Development Standards' },
|
|
478
|
+
{ path: 'docs/09-ai/agents/orchestrator.md', label: 'Agent Orchestrator Specification' },
|
|
479
|
+
{ path: 'docs/09-ai/agents/implementation.md', label: 'Agent Implementation Specification' },
|
|
480
|
+
{ path: 'docs/09-ai/agents/documentation.md', label: 'Agent Documentation Specification' },
|
|
481
|
+
{ path: 'docs/09-ai/agents/validator.md', label: 'Agent Validator Specification' },
|
|
482
|
+
{ path: 'docs/09-ai/coding-rules.md', label: 'AI Coding Rules' },
|
|
483
|
+
{ path: 'docs/11-knowledge-base/definitions.md', label: 'Knowledge Base Definitions' },
|
|
484
|
+
{ path: 'docs/11-knowledge-base/logs/implementation-logs.md', label: 'Implementation Logs' }
|
|
485
|
+
];
|
|
486
|
+
|
|
487
|
+
let missingCount = 0;
|
|
488
|
+
const s = p.spinner();
|
|
489
|
+
s.start('Validating agent and documentation configurations...');
|
|
490
|
+
|
|
491
|
+
const results = [];
|
|
492
|
+
for (const item of filesToCheck) {
|
|
493
|
+
const fullPath = path.join(cwd, item.path);
|
|
494
|
+
const exists = fs.existsSync(fullPath);
|
|
495
|
+
if (!exists) {
|
|
496
|
+
missingCount++;
|
|
497
|
+
}
|
|
498
|
+
results.push({ ...item, exists });
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
s.stop('Validation checks complete.');
|
|
502
|
+
|
|
503
|
+
for (const item of results) {
|
|
504
|
+
if (item.exists) {
|
|
505
|
+
p.log.success(`[PASS] ${item.label} matches target path: ${item.path}`);
|
|
506
|
+
} else {
|
|
507
|
+
p.log.error(`[FAIL] ${item.label} is missing! Expected at: ${item.path}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (missingCount === 0) {
|
|
512
|
+
p.outro('🎉 Configuration validation SUCCESSful! All 4-agent orchestration rules and documentation folders are correctly configured.');
|
|
513
|
+
process.exit(0);
|
|
514
|
+
} else {
|
|
515
|
+
p.log.warn(`Validation failed with ${missingCount} missing file(s).`);
|
|
516
|
+
p.outro('❌ Verification FAILED. Please run `npx forcefully-agent` to regenerate the missing configurations.');
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Runs the interactive Documentation Validator structured validation loop for Product Owners.
|
|
523
|
+
*/
|
|
524
|
+
async function runAudit() {
|
|
525
|
+
console.clear();
|
|
526
|
+
p.intro('⚖️ Documentation Validator Agent (Interactive Spec Audit)');
|
|
527
|
+
|
|
528
|
+
const cwd = process.cwd();
|
|
529
|
+
|
|
530
|
+
const filesMap = {
|
|
531
|
+
vision: { path: 'docs/01-product/vision.md', label: 'Product Vision' },
|
|
532
|
+
businessRules: { path: 'docs/01-product/business-rules.md', label: 'Business Rules' },
|
|
533
|
+
terminology: { path: 'docs/01-product/terminology.md', label: 'Terminology Glossary' },
|
|
534
|
+
features: { path: 'docs/03-features/overview.md', label: 'Features Overview' }
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
for (const key in filesMap) {
|
|
538
|
+
const fullPath = path.join(cwd, filesMap[key].path);
|
|
539
|
+
if (!fs.existsSync(fullPath)) {
|
|
540
|
+
p.log.error(`Required specification document is missing: ${filesMap[key].path}`);
|
|
541
|
+
p.outro('❌ Audit cancelled. Run npx forcefully-agent first to initialize specifications.');
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const { provider, apiKey } = await resolveAIConfig('Choose AI provider to audit documents:');
|
|
547
|
+
|
|
548
|
+
while (true) {
|
|
549
|
+
const selection = await p.select({
|
|
550
|
+
message: 'Which part of the documentation would you like to validate?',
|
|
551
|
+
options: [
|
|
552
|
+
{ value: 'vision', label: '1. Product Vision (vision.md)' },
|
|
553
|
+
{ value: 'businessRules', label: '2. Business Rules (business-rules.md)' },
|
|
554
|
+
{ value: 'terminology', label: '3. Terminology Glossary (terminology.md)' },
|
|
555
|
+
{ value: 'features', label: '4. Features Overview (overview.md)' },
|
|
556
|
+
{ value: 'exit', label: '5. [Exit Audit]' }
|
|
557
|
+
]
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
if (p.isCancel(selection) || selection === 'exit') {
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const docTarget = filesMap[selection];
|
|
565
|
+
const fullPath = path.join(cwd, docTarget.path);
|
|
566
|
+
const docContent = fs.readFileSync(fullPath, 'utf8');
|
|
567
|
+
|
|
568
|
+
// Generate validation question and snippet using AI
|
|
569
|
+
const qSpinner = p.spinner();
|
|
570
|
+
qSpinner.start(`Analyzing ${docTarget.label} to select validation target...`);
|
|
571
|
+
let snippet = '';
|
|
572
|
+
let question = '';
|
|
573
|
+
try {
|
|
574
|
+
const result = await generateValidationQuestion({ provider, apiKey, docName: docTarget.label, docContent });
|
|
575
|
+
snippet = result.snippet;
|
|
576
|
+
question = result.question;
|
|
577
|
+
qSpinner.stop('Validation target formulated.');
|
|
578
|
+
} catch (e) {
|
|
579
|
+
qSpinner.stop('Failed to analyze document.');
|
|
580
|
+
p.log.error(e.message);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Display the selected targeted snippet instead of the full file
|
|
585
|
+
p.note(snippet, `Target Snippet from ${docTarget.label}`);
|
|
586
|
+
|
|
587
|
+
// Prompt user for confirmation/corrections on this snippet
|
|
588
|
+
const answer = await p.text({
|
|
589
|
+
message: `${question}\n\nIs this correct? (Press Enter or type "yes" to confirm, or type your corrections to edit it):`,
|
|
590
|
+
placeholder: 'Press enter to confirm...'
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
if (p.isCancel(answer)) {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const isConfirmed = !answer || answer.trim() === '' || ['yes', 'correct', 'looks good', 'ok', 'y'].includes(answer.trim().toLowerCase());
|
|
598
|
+
|
|
599
|
+
if (isConfirmed) {
|
|
600
|
+
p.log.success(`✔ Specification validated: ${docTarget.path} is accurate. No changes needed.`);
|
|
601
|
+
|
|
602
|
+
const logsPath = path.join(cwd, 'docs/11-knowledge-base/logs/implementation-logs.md');
|
|
603
|
+
let existingLogs = '';
|
|
604
|
+
if (fs.existsSync(logsPath)) {
|
|
605
|
+
existingLogs = fs.readFileSync(logsPath, 'utf8');
|
|
606
|
+
}
|
|
607
|
+
const newLogEntry = `
|
|
608
|
+
## [${new Date().toISOString().split('T')[0]}] Specification Validation Audit
|
|
609
|
+
- **Agent**: Documentation Validator Agent
|
|
610
|
+
- **Document**: ${docTarget.path} (${docTarget.label})
|
|
611
|
+
- **Segment Audited**: ${snippet}
|
|
612
|
+
- **Result**: Segment validated as up-to-date by Product Owner. No refinement required.
|
|
613
|
+
`;
|
|
614
|
+
writeFile(logsPath, existingLogs.trim() + '\n' + newLogEntry);
|
|
615
|
+
} else {
|
|
616
|
+
// Process PO corrections and refine doc using AI
|
|
617
|
+
const rSpinner = p.spinner();
|
|
618
|
+
rSpinner.start('Processing corrections and refining specifications...');
|
|
619
|
+
try {
|
|
620
|
+
const { refinedContent } = await refineSingleDoc({
|
|
621
|
+
provider,
|
|
622
|
+
apiKey,
|
|
623
|
+
docName: docTarget.label,
|
|
624
|
+
docContent,
|
|
625
|
+
correction: `Regarding the segment "${snippet}", apply the following feedback: ${answer}`
|
|
626
|
+
});
|
|
627
|
+
rSpinner.stop('Feedback processed.');
|
|
628
|
+
|
|
629
|
+
writeFile(fullPath, refinedContent);
|
|
630
|
+
p.log.success(`✔ Refinement complete: ${docTarget.path} updated successfully.`);
|
|
631
|
+
|
|
632
|
+
const logsPath = path.join(cwd, 'docs/11-knowledge-base/logs/implementation-logs.md');
|
|
633
|
+
let existingLogs = '';
|
|
634
|
+
if (fs.existsSync(logsPath)) {
|
|
635
|
+
existingLogs = fs.readFileSync(logsPath, 'utf8');
|
|
636
|
+
}
|
|
637
|
+
const newLogEntry = `
|
|
638
|
+
## [${new Date().toISOString().split('T')[0]}] Specification Refinement Audit
|
|
639
|
+
- **Agent**: Documentation Validator Agent
|
|
640
|
+
- **Document**: ${docTarget.path} (${docTarget.label})
|
|
641
|
+
- **Segment Audited**: ${snippet}
|
|
642
|
+
- **Product Owner Feedback**: ${answer}
|
|
643
|
+
- **Result**: Specification file refined and updated based on feedback.
|
|
644
|
+
`;
|
|
645
|
+
writeFile(logsPath, existingLogs.trim() + '\n' + newLogEntry);
|
|
646
|
+
} catch (e) {
|
|
647
|
+
rSpinner.stop('Refinement failed.');
|
|
648
|
+
p.log.error(e.message);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
console.log('\n');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
p.outro('🎉 Specification audit session closed. All validation records are logged and active.');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (process.argv.includes('validate')) {
|
|
658
|
+
validateSetup().catch((err) => {
|
|
659
|
+
console.error('Fatal error running validation:', err);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
});
|
|
662
|
+
} else if (process.argv.includes('audit')) {
|
|
663
|
+
runAudit().catch((err) => {
|
|
664
|
+
console.error('Fatal error running documentation audit:', err);
|
|
665
|
+
process.exit(1);
|
|
666
|
+
});
|
|
667
|
+
} else {
|
|
668
|
+
main().catch((err) => {
|
|
669
|
+
console.error('Fatal error running ForcefullyAgent:', err);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
});
|
|
672
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Product Vision
|
|
2
|
+
|
|
3
|
+
This document details the product purpose, target audience, and engineering goals.
|
|
4
|
+
|
|
5
|
+
## Core Vision
|
|
6
|
+
Product vision. Default scaffolded.
|
|
7
|
+
|
|
8
|
+
## Objectives
|
|
9
|
+
- Standardized AI-first development flow.
|
|
10
|
+
- Maintain simple and minimal codebases.
|