@christianmaf80/agentic-workflow 1.2.0-beta.1
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/LICENSE +15 -0
- package/README.es.md +88 -0
- package/README.md +88 -0
- package/bin/cli.js +44 -0
- package/dist/artifacts/index.md +28 -0
- package/dist/cli/commands/create.js +178 -0
- package/dist/cli/commands/init.js +212 -0
- package/dist/cli/commands/mcp-register.js +33 -0
- package/dist/cli/commands/restore.js +67 -0
- package/dist/cli/index.js +2 -0
- package/dist/core/context/manager.js +136 -0
- package/dist/core/index.js +1 -0
- package/dist/core/mapping/resolver.js +72 -0
- package/dist/core/migration/backup.js +14 -0
- package/dist/core/migration/detector.js +32 -0
- package/dist/core/migration/transformer.js +11 -0
- package/dist/core/utils/backup.js +41 -0
- package/dist/mcp/server.js +160 -0
- package/dist/rules/constitution/GEMINI.location.md +29 -0
- package/dist/rules/constitution/agent-system.md +77 -0
- package/dist/rules/constitution/agents-behavior.md +188 -0
- package/dist/rules/constitution/clean-code.md +153 -0
- package/dist/rules/constitution/index.md +29 -0
- package/dist/rules/constitution/project-architecture.md +57 -0
- package/dist/rules/index.md +26 -0
- package/dist/rules/roles/architect.md +40 -0
- package/dist/rules/roles/index.md +45 -0
- package/dist/rules/roles/neo.md +32 -0
- package/dist/rules/roles/qa.md +22 -0
- package/dist/rules/roles/researcher.md +22 -0
- package/dist/rules/roles/tooling.md +22 -0
- package/dist/templates/acceptance.md +64 -0
- package/dist/templates/agent-scores.md +25 -0
- package/dist/templates/agent-task.md +106 -0
- package/dist/templates/analysis.md +161 -0
- package/dist/templates/brief.md +96 -0
- package/dist/templates/changelog.md +30 -0
- package/dist/templates/closure.md +87 -0
- package/dist/templates/index.md +45 -0
- package/dist/templates/init.md +26 -0
- package/dist/templates/planning.md +157 -0
- package/dist/templates/research.md +89 -0
- package/dist/templates/results-acceptance.md +177 -0
- package/dist/templates/review.md +110 -0
- package/dist/templates/subtask-implementation.md +67 -0
- package/dist/templates/supplemental-report.md +30 -0
- package/dist/templates/task-metrics.md +39 -0
- package/dist/templates/task.md +151 -0
- package/dist/templates/todo-item.md +49 -0
- package/dist/templates/verification.md +77 -0
- package/dist/workflows/index.md +44 -0
- package/dist/workflows/init.md +61 -0
- package/dist/workflows/tasklifecycle-long/index.md +176 -0
- package/dist/workflows/tasklifecycle-long/phase-0-acceptance-criteria.md +161 -0
- package/dist/workflows/tasklifecycle-long/phase-1-research.md +151 -0
- package/dist/workflows/tasklifecycle-long/phase-2-analysis.md +136 -0
- package/dist/workflows/tasklifecycle-long/phase-3-planning.md +121 -0
- package/dist/workflows/tasklifecycle-long/phase-4-implementation.md +104 -0
- package/dist/workflows/tasklifecycle-long/phase-5-verification.md +82 -0
- package/dist/workflows/tasklifecycle-long/phase-6-results-acceptance.md +79 -0
- package/dist/workflows/tasklifecycle-long/phase-7-evaluation.md +85 -0
- package/dist/workflows/tasklifecycle-long/phase-8-commit-push.md +80 -0
- package/dist/workflows/tasklifecycle-short/index.md +67 -0
- package/dist/workflows/tasklifecycle-short/short-phase-1-brief.md +66 -0
- package/dist/workflows/tasklifecycle-short/short-phase-2-implementation.md +72 -0
- package/dist/workflows/tasklifecycle-short/short-phase-3-closure.md +67 -0
- package/package.json +53 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { resolveCorePath, resolveInstalledCorePath } from '../../core/mapping/resolver.js';
|
|
4
|
+
export async function registerMcpCommand(options) {
|
|
5
|
+
if (!options.path) {
|
|
6
|
+
throw new Error('Missing --path. Provide the full path to the MCP config file.');
|
|
7
|
+
}
|
|
8
|
+
const configPath = options.path;
|
|
9
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
10
|
+
const config = await readConfig(configPath);
|
|
11
|
+
config.mcpServers = config.mcpServers ?? {};
|
|
12
|
+
const corePath = (await resolveInstalledCorePath(process.cwd())) ?? await resolveCorePath();
|
|
13
|
+
const binPath = path.join(corePath, '../bin/cli.js');
|
|
14
|
+
config.mcpServers['agentic-workflow'] = {
|
|
15
|
+
command: 'node',
|
|
16
|
+
args: [binPath, 'mcp'],
|
|
17
|
+
env: {},
|
|
18
|
+
disabled: false
|
|
19
|
+
};
|
|
20
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log(`MCP server registered at: ${configPath}`);
|
|
23
|
+
}
|
|
24
|
+
async function readConfig(configPath) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = await fs.readFile(configPath, 'utf-8');
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { intro, outro, spinner, select, confirm } from '@clack/prompts';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export async function restoreCommand() {
|
|
5
|
+
intro('Agentic Workflow Recovery');
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const backupBaseDir = path.join(cwd, '.agent-backups');
|
|
8
|
+
try {
|
|
9
|
+
await fs.access(backupBaseDir);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
outro('No backups were found in .agent-backups/');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const backups = await fs.readdir(backupBaseDir);
|
|
16
|
+
if (backups.length === 0) {
|
|
17
|
+
outro('No backups available.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Sort backups by date (newest first)
|
|
21
|
+
const sortedBackups = backups.sort((a, b) => b.localeCompare(a));
|
|
22
|
+
const selectedBackup = await select({
|
|
23
|
+
message: 'Select the backup you want to restore:',
|
|
24
|
+
options: sortedBackups.map(b => ({ value: b, label: b })),
|
|
25
|
+
});
|
|
26
|
+
if (typeof selectedBackup === 'symbol') {
|
|
27
|
+
outro('Operation cancelled.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const shouldRestore = await confirm({
|
|
31
|
+
message: `Are you sure you want to restore the backup "${selectedBackup}"? The current .agent/ folder will be overwritten.`,
|
|
32
|
+
});
|
|
33
|
+
if (!shouldRestore || typeof shouldRestore === 'symbol') {
|
|
34
|
+
outro('Restoration cancelled.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const s = spinner();
|
|
38
|
+
s.start('Restoring backup...');
|
|
39
|
+
const backupPath = path.join(backupBaseDir, selectedBackup, '.agent');
|
|
40
|
+
const targetPath = path.join(cwd, '.agent');
|
|
41
|
+
try {
|
|
42
|
+
// Simple strategy: delete current and copy backup
|
|
43
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
44
|
+
await copyRecursive(backupPath, targetPath);
|
|
45
|
+
s.stop('Restoration complete.');
|
|
46
|
+
outro(`The system has been successfully restored from ${selectedBackup}`);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
s.stop('Restoration failed.');
|
|
50
|
+
console.error(error);
|
|
51
|
+
outro('Error during restoration.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function copyRecursive(src, dest) {
|
|
55
|
+
const stats = await fs.stat(src);
|
|
56
|
+
const isDirectory = stats.isDirectory();
|
|
57
|
+
if (isDirectory) {
|
|
58
|
+
await fs.mkdir(dest, { recursive: true });
|
|
59
|
+
const files = await fs.readdir(src);
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
await copyRecursive(path.join(src, file), path.join(dest, file));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await fs.copyFile(src, dest);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export class ContextManager {
|
|
4
|
+
projectRoot;
|
|
5
|
+
corePath;
|
|
6
|
+
constructor(projectRoot, corePath) {
|
|
7
|
+
this.projectRoot = projectRoot;
|
|
8
|
+
this.corePath = corePath;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resuelve un alias (ej: 'rules.constitution') a un conjunto de ficheros.
|
|
12
|
+
*/
|
|
13
|
+
async resolveAlias(aliasPath) {
|
|
14
|
+
const parts = aliasPath.split('.');
|
|
15
|
+
let currentPath = path.join(this.projectRoot, '.agent/index.md');
|
|
16
|
+
let currentContext = null;
|
|
17
|
+
// Si el alias empieza por 'agent.core', saltamos al core
|
|
18
|
+
if (aliasPath.startsWith('agent.core')) {
|
|
19
|
+
currentPath = path.join(this.corePath, 'index.md');
|
|
20
|
+
}
|
|
21
|
+
const files = [];
|
|
22
|
+
// Esta es una implementación simplificada que busca en los índices conocidos
|
|
23
|
+
// Para una implementación completa, se requeriría un crawler de YAML
|
|
24
|
+
if (aliasPath.includes('constitution')) {
|
|
25
|
+
const constitutions = ['GEMINI.location.md', 'project-architecture.md', 'clean-code.md', 'agent-system.md'];
|
|
26
|
+
for (const c of constitutions) {
|
|
27
|
+
const p = path.join(this.corePath, 'rules/constitution', c);
|
|
28
|
+
if (await this.exists(p)) {
|
|
29
|
+
files.push(await this.readFile(p, `constitution.${c.replace('.md', '')}`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (aliasPath.includes('roles')) {
|
|
34
|
+
const rolesDir = path.join(this.corePath, 'rules/roles');
|
|
35
|
+
const roleFiles = await fs.readdir(rolesDir);
|
|
36
|
+
for (const f of roleFiles) {
|
|
37
|
+
if (f.endsWith('.md') && f !== 'index.md') {
|
|
38
|
+
files.push(await this.readFile(path.join(rolesDir, f), `roles.${f.replace('.md', '')}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return files;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Crea un bundle de inicialización (Bootstrap) siguiendo las Best Practices.
|
|
46
|
+
*/
|
|
47
|
+
async bootstrapContext() {
|
|
48
|
+
const files = [];
|
|
49
|
+
// 1. Índice Maestro Local
|
|
50
|
+
const localIndex = path.join(this.projectRoot, '.agent/index.md');
|
|
51
|
+
files.push(await this.readFile(localIndex, 'agent.index'));
|
|
52
|
+
// 2. Local Domain Indexes (client-defined)
|
|
53
|
+
const localRulesIndex = path.join(this.projectRoot, '.agent/rules/index.md');
|
|
54
|
+
const localRolesIndex = path.join(this.projectRoot, '.agent/rules/roles/index.md');
|
|
55
|
+
const localWorkflowsIndex = path.join(this.projectRoot, '.agent/workflows/index.md');
|
|
56
|
+
const localArtifactsIndex = path.join(this.projectRoot, '.agent/artifacts/index.md');
|
|
57
|
+
const localTemplatesIndex = path.join(this.projectRoot, '.agent/templates/index.md');
|
|
58
|
+
if (await this.exists(localRulesIndex))
|
|
59
|
+
files.push(await this.readFile(localRulesIndex, 'agent.local.rules'));
|
|
60
|
+
if (await this.exists(localRolesIndex))
|
|
61
|
+
files.push(await this.readFile(localRolesIndex, 'agent.local.roles'));
|
|
62
|
+
if (await this.exists(localWorkflowsIndex))
|
|
63
|
+
files.push(await this.readFile(localWorkflowsIndex, 'agent.local.workflows'));
|
|
64
|
+
if (await this.exists(localArtifactsIndex))
|
|
65
|
+
files.push(await this.readFile(localArtifactsIndex, 'agent.local.artifacts'));
|
|
66
|
+
if (await this.exists(localTemplatesIndex))
|
|
67
|
+
files.push(await this.readFile(localTemplatesIndex, 'agent.local.templates'));
|
|
68
|
+
// 3. Índices del Core
|
|
69
|
+
const coreRulesIndex = path.join(this.corePath, 'rules/index.md');
|
|
70
|
+
const coreWorkflowsIndex = path.join(this.corePath, 'workflows/index.md');
|
|
71
|
+
const coreTemplatesIndex = path.join(this.corePath, 'templates/index.md');
|
|
72
|
+
const coreArtifactsIndex = path.join(this.corePath, 'artifacts/index.md');
|
|
73
|
+
if (await this.exists(coreRulesIndex))
|
|
74
|
+
files.push(await this.readFile(coreRulesIndex, 'agent.core.rules'));
|
|
75
|
+
if (await this.exists(coreWorkflowsIndex))
|
|
76
|
+
files.push(await this.readFile(coreWorkflowsIndex, 'agent.core.workflows'));
|
|
77
|
+
if (await this.exists(coreTemplatesIndex))
|
|
78
|
+
files.push(await this.readFile(coreTemplatesIndex, 'agent.core.templates'));
|
|
79
|
+
if (await this.exists(coreArtifactsIndex))
|
|
80
|
+
files.push(await this.readFile(coreArtifactsIndex, 'agent.core.artifacts'));
|
|
81
|
+
// 4. Constituciones Críticas
|
|
82
|
+
const constitutions = ['GEMINI.location.md', 'project-architecture.md', 'clean-code.md', 'agent-system.md'];
|
|
83
|
+
for (const c of constitutions) {
|
|
84
|
+
const p = path.join(this.corePath, 'rules/constitution', c);
|
|
85
|
+
if (await this.exists(p)) {
|
|
86
|
+
files.push(await this.readFile(p, `constitution.${c.replace('.md', '')}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 5. Rol Arquitecto
|
|
90
|
+
const archRole = path.join(this.corePath, 'rules/roles/architect.md');
|
|
91
|
+
if (await this.exists(archRole)) {
|
|
92
|
+
files.push(await this.readFile(archRole, 'roles.architect'));
|
|
93
|
+
}
|
|
94
|
+
return this.formatBundle(files, true);
|
|
95
|
+
}
|
|
96
|
+
async readFile(filePath, alias) {
|
|
97
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
98
|
+
return { path: filePath, content, alias };
|
|
99
|
+
}
|
|
100
|
+
async exists(p) {
|
|
101
|
+
try {
|
|
102
|
+
await fs.access(p);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
formatBundle(files, isBootstrap = false) {
|
|
110
|
+
let bundle = isBootstrap ? "# BOOTSTRAP CONTEXT BUNDLE\n\n" : "# CONTEXT BUNDLE\n\n";
|
|
111
|
+
// 1. Manifiesto de Carga (Best Practice)
|
|
112
|
+
bundle += "## MANIFIESTO DE CARGA\n";
|
|
113
|
+
bundle += "Este bundle contiene los siguientes dominios de contexto:\n";
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
bundle += `- [x] \`${file.alias || path.basename(file.path)}\` (v1.0)\n`;
|
|
116
|
+
}
|
|
117
|
+
bundle += "\n---\n\n";
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
bundle += `## FILE: ${file.alias || file.path}\n`;
|
|
120
|
+
bundle += `Path: \`file://${file.path}\`\n\n`;
|
|
121
|
+
bundle += "```markdown\n";
|
|
122
|
+
bundle += file.content;
|
|
123
|
+
if (!file.content.endsWith('\n'))
|
|
124
|
+
bundle += '\n';
|
|
125
|
+
bundle += "```\n\n---\n\n";
|
|
126
|
+
}
|
|
127
|
+
// 2. Siguientes Pasos / Discovery (Best Practice)
|
|
128
|
+
if (isBootstrap) {
|
|
129
|
+
bundle += "## SIGUIENTES PASOS (DISCOVERY)\n";
|
|
130
|
+
bundle += "El sistema está inicializado. Puedes profundizar en dominios específicos usando:\n";
|
|
131
|
+
bundle += "- `tool: hydrate_context({ alias: \"agent.core.rules\" })` para cargar todas las reglas.\n";
|
|
132
|
+
bundle += "- `tool: hydrate_context({ alias: \"agent.core.workflows\" })` para ver procesos disponibles.\n";
|
|
133
|
+
}
|
|
134
|
+
return bundle;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const AGENT_ROOT = '.agent';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
/**
|
|
6
|
+
* Resuelve la ruta absoluta del core del framework agentic-workflow.
|
|
7
|
+
* Busca las carpetas 'rules', 'workflows' y 'templates' desde la ubicación del script.
|
|
8
|
+
*/
|
|
9
|
+
export async function resolveCorePath() {
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
// Intentamos buscar el root del paquete subiendo niveles desde src/core/mapping
|
|
13
|
+
// En desarrollo: agent-workflow/src/core/mapping -> ../../..
|
|
14
|
+
// En producción (dist): agent-workflow/dist/core/mapping -> ../../..
|
|
15
|
+
return resolveCorePathFromDir(__dirname);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Intenta resolver el core instalado en node_modules desde el cwd.
|
|
19
|
+
* Devuelve null si no hay instalación local.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveInstalledCorePath(cwd = process.cwd()) {
|
|
22
|
+
try {
|
|
23
|
+
const directNodeModules = path.join(cwd, 'node_modules', '@christian-marino-alvarez', 'agentic-workflow');
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(path.join(directNodeModules, 'package.json'));
|
|
26
|
+
return await resolveCorePathFromPackageRoot(directNodeModules);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Fall back to node resolution from cwd.
|
|
30
|
+
}
|
|
31
|
+
const requireFromCwd = createRequire(path.join(cwd, 'package.json'));
|
|
32
|
+
const pkgPath = requireFromCwd.resolve('@christian-marino-alvarez/agentic-workflow/package.json');
|
|
33
|
+
const pkgRoot = path.dirname(pkgPath);
|
|
34
|
+
return await resolveCorePathFromPackageRoot(pkgRoot);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function resolveCorePathFromDir(startDir) {
|
|
41
|
+
let currentDir = startDir;
|
|
42
|
+
const maxRetries = 5;
|
|
43
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
44
|
+
const potentialRulesPath = path.join(currentDir, 'rules');
|
|
45
|
+
const potentialWorkflowsPath = path.join(currentDir, 'workflows');
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(potentialRulesPath);
|
|
48
|
+
await fs.access(potentialWorkflowsPath);
|
|
49
|
+
return currentDir; // Encontrado
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
currentDir = path.dirname(currentDir);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new Error('No se pudo localizar el core del framework (@cmarino/agentic-workflow). Asegúrate de que el paquete está correctamente instalado.');
|
|
56
|
+
}
|
|
57
|
+
async function resolveCorePathFromPackageRoot(pkgRoot) {
|
|
58
|
+
const candidates = [pkgRoot, path.join(pkgRoot, 'dist'), path.join(pkgRoot, 'src')];
|
|
59
|
+
for (const candidate of candidates) {
|
|
60
|
+
try {
|
|
61
|
+
const rulesPath = path.join(candidate, 'rules');
|
|
62
|
+
const workflowsPath = path.join(candidate, 'workflows');
|
|
63
|
+
await fs.access(rulesPath);
|
|
64
|
+
await fs.access(workflowsPath);
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Not a core root, try next.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
throw new Error('No se pudo localizar el core instalado en node_modules.');
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function createBackup(cwd) {
|
|
4
|
+
const agentDir = path.join(cwd, '.agent');
|
|
5
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
6
|
+
const backupDir = path.join(cwd, `.agent.backup_${timestamp}`);
|
|
7
|
+
try {
|
|
8
|
+
await fs.cp(agentDir, backupDir, { recursive: true });
|
|
9
|
+
return backupDir;
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function detectAgentSystem(cwd) {
|
|
4
|
+
const agentDir = path.join(cwd, '.agent');
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(agentDir);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return 'none';
|
|
10
|
+
}
|
|
11
|
+
// Comprobar si es el sistema actual (Portable)
|
|
12
|
+
try {
|
|
13
|
+
const rootIndex = await fs.readFile(path.join(agentDir, 'index.md'), 'utf-8');
|
|
14
|
+
if (rootIndex.includes('id: agent.index')) {
|
|
15
|
+
return 'current';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// No hay index.md global
|
|
20
|
+
}
|
|
21
|
+
// Comprobar si es legacy (Extensio)
|
|
22
|
+
try {
|
|
23
|
+
const rulesIndex = await fs.readFile(path.join(agentDir, 'rules', 'index.md'), 'utf-8');
|
|
24
|
+
if (rulesIndex.includes('id: rules.index')) {
|
|
25
|
+
return 'legacy';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// No hay rules/index.md
|
|
30
|
+
}
|
|
31
|
+
return 'legacy'; // Si existe .agent pero no es actual, lo tratamos como legacy para migrar
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import matter from 'gray-matter';
|
|
2
|
+
export function transformMarkdownContent(content, newMeta) {
|
|
3
|
+
const { data, content: body } = matter(content);
|
|
4
|
+
// Merge existing metadata with new metadata
|
|
5
|
+
const mergedData = {
|
|
6
|
+
...data,
|
|
7
|
+
...newMeta
|
|
8
|
+
};
|
|
9
|
+
// Return the stringified version with the triple dash separators
|
|
10
|
+
return matter.stringify(body, mergedData);
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Utility to manage backups of the .agent directory.
|
|
5
|
+
*/
|
|
6
|
+
export async function performBackup(cwd) {
|
|
7
|
+
const agentDir = path.join(cwd, '.agent');
|
|
8
|
+
const backupBaseDir = path.join(cwd, '.agent-backups');
|
|
9
|
+
// Check if .agent exists
|
|
10
|
+
try {
|
|
11
|
+
await fs.access(agentDir);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// No .agent folder, nothing to backup
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
// Create backup directory with timestamp
|
|
18
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
19
|
+
const backupDir = path.join(backupBaseDir, timestamp);
|
|
20
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
21
|
+
// Copy .agent to backupDir
|
|
22
|
+
await copyRecursive(agentDir, path.join(backupDir, '.agent'));
|
|
23
|
+
return backupDir;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Helper to copy directories recursively.
|
|
27
|
+
*/
|
|
28
|
+
async function copyRecursive(src, dest) {
|
|
29
|
+
const stats = await fs.stat(src);
|
|
30
|
+
const isDirectory = stats.isDirectory();
|
|
31
|
+
if (isDirectory) {
|
|
32
|
+
await fs.mkdir(dest, { recursive: true });
|
|
33
|
+
const files = await fs.readdir(src);
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
await copyRecursive(path.join(src, file), path.join(dest, file));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
await fs.copyFile(src, dest);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { performCreate } from "../cli/commands/create.js";
|
|
5
|
+
import { ContextManager } from "../core/context/manager.js";
|
|
6
|
+
import { resolveCorePath } from "../core/mapping/resolver.js";
|
|
7
|
+
/**
|
|
8
|
+
* Servidor MCP para el framework Agentic Workflow.
|
|
9
|
+
* Proporciona herramientas estructuradas para extender el sistema.
|
|
10
|
+
*/
|
|
11
|
+
export async function runMcpServer() {
|
|
12
|
+
const server = new Server({
|
|
13
|
+
name: "agentic-workflow-server",
|
|
14
|
+
version: "1.3.0",
|
|
15
|
+
}, {
|
|
16
|
+
capabilities: {
|
|
17
|
+
tools: {},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
// Configuración del Ciclo de Vida (Lifecycle)
|
|
21
|
+
const INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutos
|
|
22
|
+
let inactivityTimer;
|
|
23
|
+
const resetInactivityTimer = () => {
|
|
24
|
+
if (inactivityTimer)
|
|
25
|
+
clearTimeout(inactivityTimer);
|
|
26
|
+
inactivityTimer = setTimeout(() => {
|
|
27
|
+
console.error("MCP Server shutting down due to inactivity...");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}, INACTIVITY_TIMEOUT);
|
|
30
|
+
};
|
|
31
|
+
// Iniciar timer inicial
|
|
32
|
+
resetInactivityTimer();
|
|
33
|
+
/**
|
|
34
|
+
* Listado de herramientas disponibles.
|
|
35
|
+
*/
|
|
36
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
37
|
+
resetInactivityTimer();
|
|
38
|
+
return {
|
|
39
|
+
tools: [
|
|
40
|
+
{
|
|
41
|
+
name: "create_role",
|
|
42
|
+
description: "Crea un nuevo rol (agente) en el proyecto local bajo la carpeta espejo .agent/roles/.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
name: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Nombre del rol (ej: devops, qa, reviewer)",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["name"],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "create_workflow",
|
|
56
|
+
description: "Crea un nuevo workflow personalizado en el proyecto local bajo la carpeta espejo .agent/workflows/.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
name: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "Nombre del workflow (ej: refactor, release, triage)",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ["name"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "bootstrap_context",
|
|
70
|
+
description: "Genera un bundle consolidado con el índice maestro, índices de dominio, constitución base y roles esenciales en un solo paso (Best Practice).",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "hydrate_context",
|
|
78
|
+
description: "Carga dinámicamente el contenido de todos los ficheros asociados a un alias (ej: rules.constitution) de forma consolidada.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
alias: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "Alias del dominio a hidratar (ej: rules.constitution, workflows.modules)",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ["alias"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "stop_server",
|
|
92
|
+
description: "Detiene el servidor MCP inmediatamente.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* Manejador de llamadas a herramientas.
|
|
103
|
+
*/
|
|
104
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
105
|
+
resetInactivityTimer();
|
|
106
|
+
const { name, arguments: args } = request.params;
|
|
107
|
+
if (name === "create_role" || name === "create_workflow") {
|
|
108
|
+
const type = name === "create_role" ? "role" : "workflow";
|
|
109
|
+
const entityName = args.name;
|
|
110
|
+
if (!entityName) {
|
|
111
|
+
throw new Error("El nombre de la entidad es obligatorio.");
|
|
112
|
+
}
|
|
113
|
+
const result = await performCreate(type, entityName);
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: result.message,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
isError: !result.success,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (name === "bootstrap_context") {
|
|
125
|
+
const corePath = await resolveCorePath();
|
|
126
|
+
const manager = new ContextManager(process.cwd(), corePath);
|
|
127
|
+
const bundle = await manager.bootstrapContext();
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text", text: bundle }]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (name === "hydrate_context") {
|
|
133
|
+
const corePath = await resolveCorePath();
|
|
134
|
+
const manager = new ContextManager(process.cwd(), corePath);
|
|
135
|
+
const alias = args.alias;
|
|
136
|
+
const files = await manager.resolveAlias(alias);
|
|
137
|
+
if (files.length === 0) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: `No se encontraron ficheros para el alias: ${alias}` }],
|
|
140
|
+
isError: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const bundle = manager.formatBundle(files);
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: "text", text: bundle }]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (name === "stop_server") {
|
|
149
|
+
setTimeout(() => process.exit(0), 100);
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: "Servidor deteniéndose..." }]
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Herramienta no encontrada: ${name}`);
|
|
155
|
+
});
|
|
156
|
+
// Conexión mediante Standard Input/Output (Stdio)
|
|
157
|
+
const transport = new StdioServerTransport();
|
|
158
|
+
await server.connect(transport);
|
|
159
|
+
console.error("Agentic Workflow MCP Server running on stdio");
|
|
160
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# GEMINI LOCATION
|
|
6
|
+
|
|
7
|
+
This repository DOES NOT version `GEMINI.md`.
|
|
8
|
+
|
|
9
|
+
Google Antigravity loads `GEMINI.md` from the user path (outside the repository).
|
|
10
|
+
Therefore, the agentic system assumes that `GEMINI.md` exists and is accessible on the local machine.
|
|
11
|
+
|
|
12
|
+
## Rule
|
|
13
|
+
|
|
14
|
+
- `GEMINI.md` is PERMANENT and has absolute priority.
|
|
15
|
+
- This file only documents where `GEMINI.md` is located and how it integrates.
|
|
16
|
+
- `GEMINI.md` MUST NOT be copied or duplicated within the repository.
|
|
17
|
+
|
|
18
|
+
## Minimum Requirements
|
|
19
|
+
|
|
20
|
+
- The developer machine MUST have `GEMINI.md` available in the user path configured for Google Antigravity.
|
|
21
|
+
- If `GEMINI.md` is not available, any task MUST BE BLOCKED until the environment is corrected.
|
|
22
|
+
|
|
23
|
+
## Internal Reference
|
|
24
|
+
|
|
25
|
+
Repository rules reference the global constitution via:
|
|
26
|
+
|
|
27
|
+
`constitution.GEMINI_location`
|
|
28
|
+
|
|
29
|
+
This file acts as the unique versioned anchor point for the external `GEMINI.md` constitution.
|