gemstack-ai 1.0.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.
Files changed (137) hide show
  1. package/.agents/rules/01-gemstack-core.md +51 -0
  2. package/.agents/rules/02-gemstack-constitution.md +44 -0
  3. package/.agents/rules/03-gemstack-security.md +49 -0
  4. package/.agents/rules/04-gemstack-infrastructure.md +28 -0
  5. package/.agents/skills/gemstack-cso/SKILL.md +50 -0
  6. package/.agents/skills/gemstack-dashboard/SKILL.md +31 -0
  7. package/.agents/skills/gemstack-guard/SKILL.md +19 -0
  8. package/.agents/skills/gemstack-handoff/SKILL.md +21 -0
  9. package/.agents/skills/gemstack-heal/SKILL.md +19 -0
  10. package/.agents/skills/gemstack-investigate/SKILL.md +25 -0
  11. package/.agents/skills/gemstack-learn/SKILL.md +18 -0
  12. package/.agents/skills/gemstack-office-hours/SKILL.md +18 -0
  13. package/.agents/skills/gemstack-plan/SKILL.md +20 -0
  14. package/.agents/skills/gemstack-qa/SKILL.md +17 -0
  15. package/.agents/skills/gemstack-qa-visual/SKILL.md +17 -0
  16. package/.agents/skills/gemstack-resume/SKILL.md +18 -0
  17. package/.agents/skills/gemstack-review/SKILL.md +18 -0
  18. package/.agents/skills/gemstack-sandbox/SKILL.md +19 -0
  19. package/.agents/skills/gemstack-ship/SKILL.md +19 -0
  20. package/.agents/skills/gemstack-spec/SKILL.md +24 -0
  21. package/.agents/skills/gemstack-swarm/SKILL.md +18 -0
  22. package/.agents/skills/gemstack-tasks/SKILL.md +18 -0
  23. package/.gemstack/learnings.md +8 -0
  24. package/.gemstack/state.json +11 -0
  25. package/.gitattributes +19 -0
  26. package/.github/workflows/main-ci.yml +32 -0
  27. package/.github/workflows/pr-ci.yml +31 -0
  28. package/.github/workflows/publish.yml +52 -0
  29. package/.github/workflows/release-readiness.yml +43 -0
  30. package/CHANGELOG.md +35 -0
  31. package/CODE_OF_CONDUCT.md +49 -0
  32. package/CONTRIBUTING.md +62 -0
  33. package/LICENSE +21 -0
  34. package/MANUAL.md +99 -0
  35. package/README.md +130 -0
  36. package/RELEASE_NOTES.md +149 -0
  37. package/bin/gemstack +31 -0
  38. package/bin/gemstack-doctor +16 -0
  39. package/bin/gemstack-doctor.ps1 +38 -0
  40. package/bin/gemstack.ps1 +49 -0
  41. package/docs/antigravity.md +6 -0
  42. package/docs/handoff.md +9 -0
  43. package/docs/qa/latest-qa.md +25 -0
  44. package/docs/qa-browser.md +7 -0
  45. package/docs/quickstart.md +21 -0
  46. package/docs/release.md +29 -0
  47. package/docs/reviews/latest-review.md +24 -0
  48. package/docs/security/latest-security-audit.md +35 -0
  49. package/docs/security.md +17 -0
  50. package/docs/skills.md +17 -0
  51. package/docs/spec-driven-development.md +10 -0
  52. package/gemstack-ai-1.0.1.tgz +0 -0
  53. package/handoff.md +45 -0
  54. package/handoff_archive.md +61 -0
  55. package/package.json +25 -0
  56. package/scripts/ci/check-frontmatter.js +54 -0
  57. package/scripts/ci/check-mojibake.js +44 -0
  58. package/scripts/ci/check-package-contents.js +32 -0
  59. package/scripts/ci/check-template-clean.js +46 -0
  60. package/scripts/ci/smoke-cli.js +30 -0
  61. package/specs/004-release-automation/plan.md +52 -0
  62. package/specs/004-release-automation/spec.md +49 -0
  63. package/specs/004-release-automation/tasks.md +25 -0
  64. package/specs/005-the-wow-update/plan.md +29 -0
  65. package/specs/005-the-wow-update/spec.md +32 -0
  66. package/specs/005-the-wow-update/tasks.md +16 -0
  67. package/specs/README.md +12 -0
  68. package/specs/current/plan.md +83 -0
  69. package/specs/current/spec.md +57 -0
  70. package/specs/current/tasks.md +98 -0
  71. package/specs/templates/plan.md +38 -0
  72. package/specs/templates/spec.md +35 -0
  73. package/specs/templates/tasks.md +21 -0
  74. package/specs/v0.2-cli-distribution/plan.md +112 -0
  75. package/specs/v0.2-cli-distribution/spec.md +27 -0
  76. package/specs/v0.2-cli-distribution/tasks.md +115 -0
  77. package/specs/v0.3-ci-cd/plan.md +83 -0
  78. package/specs/v0.3-ci-cd/spec.md +57 -0
  79. package/specs/v0.3-ci-cd/tasks.md +98 -0
  80. package/src/cli.js +64 -0
  81. package/src/commands/doctor.js +50 -0
  82. package/src/commands/hooks.js +67 -0
  83. package/src/commands/init.js +90 -0
  84. package/src/commands/install.js +72 -0
  85. package/src/commands/list.js +20 -0
  86. package/src/commands/show.js +18 -0
  87. package/src/commands/update.js +88 -0
  88. package/src/lib/backup.js +29 -0
  89. package/src/lib/filesystem-safe.js +22 -0
  90. package/src/lib/gitignore.js +19 -0
  91. package/src/lib/logger.js +6 -0
  92. package/src/lib/manifest.js +25 -0
  93. package/src/lib/parser.js +27 -0
  94. package/src/mcp-server.js +101 -0
  95. package/template/.agents/rules/01-gemstack-core.md +51 -0
  96. package/template/.agents/rules/02-gemstack-constitution.md +44 -0
  97. package/template/.agents/rules/03-gemstack-security.md +49 -0
  98. package/template/.agents/rules/04-gemstack-infrastructure.md +28 -0
  99. package/template/.agents/skills/gemstack-cso/SKILL.md +50 -0
  100. package/template/.agents/skills/gemstack-dashboard/SKILL.md +31 -0
  101. package/template/.agents/skills/gemstack-guard/SKILL.md +19 -0
  102. package/template/.agents/skills/gemstack-handoff/SKILL.md +21 -0
  103. package/template/.agents/skills/gemstack-heal/SKILL.md +19 -0
  104. package/template/.agents/skills/gemstack-investigate/SKILL.md +25 -0
  105. package/template/.agents/skills/gemstack-learn/SKILL.md +18 -0
  106. package/template/.agents/skills/gemstack-office-hours/SKILL.md +18 -0
  107. package/template/.agents/skills/gemstack-plan/SKILL.md +20 -0
  108. package/template/.agents/skills/gemstack-qa/SKILL.md +17 -0
  109. package/template/.agents/skills/gemstack-qa-visual/SKILL.md +17 -0
  110. package/template/.agents/skills/gemstack-resume/SKILL.md +18 -0
  111. package/template/.agents/skills/gemstack-review/SKILL.md +18 -0
  112. package/template/.agents/skills/gemstack-sandbox/SKILL.md +19 -0
  113. package/template/.agents/skills/gemstack-ship/SKILL.md +19 -0
  114. package/template/.agents/skills/gemstack-spec/SKILL.md +24 -0
  115. package/template/.agents/skills/gemstack-swarm/SKILL.md +18 -0
  116. package/template/.agents/skills/gemstack-tasks/SKILL.md +18 -0
  117. package/template/.gemstack/learnings.md +3 -0
  118. package/template/.gemstack/state.json +4 -0
  119. package/template/docs/antigravity.md +6 -0
  120. package/template/docs/handoff.md +9 -0
  121. package/template/docs/qa/latest-qa.md +25 -0
  122. package/template/docs/qa-browser.md +7 -0
  123. package/template/docs/quickstart.md +21 -0
  124. package/template/docs/release.md +10 -0
  125. package/template/docs/reviews/latest-review.md +19 -0
  126. package/template/docs/security/latest-security-audit.md +37 -0
  127. package/template/docs/security.md +17 -0
  128. package/template/docs/skills.md +17 -0
  129. package/template/docs/spec-driven-development.md +10 -0
  130. package/template/handoff.md +11 -0
  131. package/template/handoff_archive.md +3 -0
  132. package/template/specs/current/plan.md +5 -0
  133. package/template/specs/current/spec.md +7 -0
  134. package/template/specs/current/tasks.md +3 -0
  135. package/template/specs/templates/plan.md +38 -0
  136. package/template/specs/templates/spec.md +35 -0
  137. package/template/specs/templates/tasks.md +21 -0
@@ -0,0 +1,98 @@
1
+ # Tareas de Implementación: Gemstack v0.3 (CI/CD & Release Automation)
2
+
3
+ Este documento contiene el plan de ejecución ejectuable (checklist) para el ciclo v0.3.
4
+
5
+ ---
6
+
7
+ ### Fase 0: Preflight
8
+ - [ ] **T0.1: Asegurar Entorno Limpio**
9
+ - **Descripción:** Verificar con `git status` que no existan implementaciones previas, archivos colgados, etiquetas recién creadas ni subidas a NPM publicadas.
10
+ - **Archivos:** Ninguno
11
+ - **Criterio de Aceptación:** Working tree clean.
12
+ - **Riesgo:** Bajo.
13
+ - **Requiere Aprobación Humana:** No.
14
+
15
+ ### Fase 1: `.gitattributes`
16
+ - [ ] **T1.1: Estandarización de Line Endings**
17
+ - **Descripción:** Crear `.gitattributes` conservador para prevenir fallos de SHA-256 cross-platform. Ejecutar `git status` y pausar si se normaliza excesivamente.
18
+ - **Archivos:** `.gitattributes`
19
+ - **Criterio de Aceptación:** Archivo creado. El Churn de EOL no debe romper archivos binarios.
20
+ - **Riesgo:** Alto (Normalización masiva o corrupción de binarios si las reglas son muy globales).
21
+ - **Requiere Aprobación Humana:** **SÍ** (Si `git status` muestra más de 10 archivos modificados únicamente por EOL).
22
+
23
+ ### Fase 2: `scripts/ci/`
24
+ - [ ] **T2.1: Validadores Nativos Zero-Deps**
25
+ - **Descripción:** Implementar validadores de calidad.
26
+ 1. `check-frontmatter.js`: Revisa formato YAML en `.agents/skills/*/SKILL.md`.
27
+ 2. `check-template-clean.js`: Revisa ausencia de contexto privado y secretos en `template/`.
28
+ 3. `check-mojibake.js`: Busca caracteres rotos en UTF-8 (ej: `Ã`).
29
+ 4. `check-package-contents.js`: Realiza inspección tras `npm pack --dry-run`.
30
+ 5. `smoke-cli.js`: Prueba programática (e2e) en directorios temporales de todas las ramas del CLI (`help`, `doctor`, `init`, `update`).
31
+ - **Archivos:** `scripts/ci/*.js`
32
+ - **Criterio de Aceptación:** Cross-platform (Win/Mac/Linux), cero dependencias externas de npm.
33
+ - **Riesgo:** Moderado (Incompatibilidad en spawns de child processes en Windows vs Unix).
34
+ - **Requiere Aprobación Humana:** No.
35
+
36
+ ### Fase 3: Package Scripts
37
+ - [ ] **T3.1: Configuración de npm run alias**
38
+ - **Descripción:** Extender el bloque `scripts` en `package.json` insertando `ci:frontmatter`, `ci:template`, `ci:mojibake`, `ci:package`, `ci:smoke` y un atajo `ci:all`.
39
+ - **Archivos:** `package.json`
40
+ - **Criterio de Aceptación:** Ejecutar `npm run ci:all` lanza secuencialmente la batería de checks.
41
+ - **Riesgo:** Bajo.
42
+ - **Requiere Aprobación Humana:** No.
43
+
44
+ ### Fase 4: Workflow `pr-ci.yml`
45
+ - [ ] **T4.1: CI Eficiente para Pull Requests**
46
+ - **Descripción:** Workflow rápido basado en Ubuntu con Matrix en Node 18 y 20. Ejecuta las validaciones `ci:all`, `npm test` y el smoke test de la `demo-app`.
47
+ - **Archivos:** `.github/workflows/pr-ci.yml`
48
+ - **Criterio de Aceptación:** El YAML es sintácticamente válido para Actions y optimiza el uso de CPU.
49
+ - **Riesgo:** Bajo.
50
+ - **Requiere Aprobación Humana:** No.
51
+
52
+ ### Fase 5: Workflow `main-ci.yml`
53
+ - [ ] **T5.1: CI Exhaustivo para la Rama Principal**
54
+ - **Descripción:** Se lanza tras merges y pushes a `main`. Emplea una Matrix completa (Windows, macOS, Ubuntu) en Node 18 y 20.
55
+ - **Archivos:** `.github/workflows/main-ci.yml`
56
+ - **Criterio de Aceptación:** Todos los tests y utilidades CI se mapean multiplataforma de forma estricta.
57
+ - **Riesgo:** Medio (Desviación en OS, comandos bash que fallen en el runner de Windows).
58
+ - **Requiere Aprobación Humana:** No.
59
+
60
+ ### Fase 6: Workflow `release-readiness.yml`
61
+ - [ ] **T6.1: Generación y Validación de Tarball Manual**
62
+ - **Descripción:** Trigger exclusivo `workflow_dispatch`. Realiza un test cross-platform donde genera un `.tgz`, levanta un proyecto vacío en otra carpeta temporal, lo instala con NPM y realiza comprobaciones unitarias `npx gemstack` reales de punta a punta. Sube el `.tgz` como artefacto final.
63
+ - **Archivos:** `.github/workflows/release-readiness.yml`
64
+ - **Criterio de Aceptación:** NO efectúa tags, NPM publish, ni GitHub Releases automáticamente.
65
+ - **Riesgo:** Medio (Estructura de logs pesada o fallo en instalación del paquete local del tarball).
66
+ - **Requiere Aprobación Humana:** No.
67
+
68
+ ### Fase 7: Documentación
69
+ - [ ] **T7.1: Badge CI y Notas**
70
+ - **Descripción:** Agregar badge de CI a la cabecera de `README.md`. Preparar borrador de notas en `RELEASE_NOTES.md` o `CHANGELOG.md` describiendo la infraestructura CI.
71
+ - **Archivos:** `README.md`, `CHANGELOG.md`, `docs/release.md`
72
+ - **Criterio de Aceptación:** Información asertiva y documentada.
73
+ - **Riesgo:** Bajo.
74
+ - **Requiere Aprobación Humana:** No.
75
+
76
+ ### Fase 8: Validación Local
77
+ - [ ] **T8.1: Prueba del Suite Completo Local**
78
+ - **Descripción:** Correr `npm test`, `npm run ci:all`, comprobar si `demo-app` no sufre regresiones.
79
+ - **Archivos:** Ninguno.
80
+ - **Criterio de Aceptación:** Todos los scripts retornan un exit code `0`. Sin archivos `temp` huérfanos residuales.
81
+ - **Riesgo:** Medio (Tiempos de ejecución o errores no controlados si fallan limpiezas `tmp`).
82
+ - **Requiere Aprobación Humana:** **SÍ** (Aprobación si se detectan anomalías de basura en disco).
83
+
84
+ ### Fase 9: Revisión de Seguridad y QA Agentic
85
+ - [ ] **T9.1: Emulación de Audits `/review` y `/cso`**
86
+ - **Descripción:** Evaluar los workflows implementados contra vulnerabilidades (Supply Chain, Secret Leaks, Privilegios del GITHUB_TOKEN). Actualizar los docs.
87
+ - **Archivos:** `docs/reviews/latest-review.md`, `docs/security/latest-security-audit.md`
88
+ - **Criterio de Aceptación:** Análisis honesto registrando deficiencias o limitantes de la CI.
89
+ - **Riesgo:** Bajo.
90
+ - **Requiere Aprobación Humana:** No.
91
+
92
+ ### Fase 10: Commit, Push y Handoff
93
+ - [ ] **T10.1: Integración Final**
94
+ - **Descripción:** Confirmar árbol limpio mediante `git status -u`. Efectuar commit, push e incorporar memoria al `handoff.md`. Push final.
95
+ - **Archivos:** `handoff.md`
96
+ - **Criterio de Aceptación:** Sin rastros de builds. Versionado estable listo para activar en GitHub Actions.
97
+ - **Riesgo:** Bajo.
98
+ - **Requiere Aprobación Humana:** **SÍ** (Confirmar antes de commit y push).
package/src/cli.js ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ const parser = require('./lib/parser');
3
+ const logger = require('./lib/logger');
4
+ const initCommand = require('./commands/init');
5
+ const updateCommand = require('./commands/update');
6
+ const doctorCommand = require('./commands/doctor');
7
+ const listCommand = require('./commands/list');
8
+ const showCommand = require('./commands/show');
9
+ const hooksCommand = require('./commands/hooks');
10
+ const installCommand = require('./commands/install');
11
+
12
+ async function main() {
13
+ const { command, args, flags } = parser.parse(process.argv);
14
+
15
+ if (flags.help || command === 'help') {
16
+ console.log(`Gemstack CLI
17
+ Commands:
18
+ init Install Gemstack scaffolding
19
+ update Update Gemstack-owned files
20
+ doctor Check health of the installation
21
+ list List available skills
22
+ show Show content of a skill
23
+ handoff Show content of handoff.md
24
+ hooks Install native Git pre-commit hooks for active security
25
+ install Install a remote skill via URL
26
+ mcp Start the Gemstack MCP (Model Context Protocol) server over stdio
27
+ Options:
28
+ --dry-run Show changes without writing
29
+ --yes Skip confirmations
30
+ --force Force overwrite (update only)
31
+ --target Specify target directory`);
32
+ return;
33
+ }
34
+
35
+ try {
36
+ switch (command) {
37
+ case 'init': await initCommand(flags); break;
38
+ case 'update': await updateCommand(flags); break;
39
+ case 'doctor': await doctorCommand(flags); break;
40
+ case 'list': await listCommand(flags); break;
41
+ case 'show': await showCommand(args[0], flags); break;
42
+ case 'hooks': hooksCommand.installHooks(flags.target); break;
43
+ case 'install': await installCommand(args[0], flags); break;
44
+ case 'mcp': require('./mcp-server'); break;
45
+ case 'handoff': {
46
+ const fs = require('fs');
47
+ const path = require('path');
48
+ const fssafe = require('./lib/filesystem-safe');
49
+ const p = fssafe.resolveSafe(flags.target, 'handoff.md');
50
+ if (fs.existsSync(p)) console.log(fs.readFileSync(p, 'utf8'));
51
+ else logger.error('handoff.md not found');
52
+ break;
53
+ }
54
+ default:
55
+ logger.error(`Unknown command: ${command}`);
56
+ process.exit(1);
57
+ }
58
+ } catch (e) {
59
+ logger.error(e.message);
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ main();
@@ -0,0 +1,50 @@
1
+ const fs = require('fs');
2
+ const fssafe = require('../lib/filesystem-safe');
3
+ const logger = require('../lib/logger');
4
+
5
+ module.exports = async (flags) => {
6
+ const targetDir = flags.target;
7
+ logger.info(`Running doctor on ${targetDir}`);
8
+
9
+ const manifestPath = fssafe.resolveSafe(targetDir, '.gemstack/manifest.json');
10
+ if (!fs.existsSync(manifestPath)) {
11
+ logger.error('Manifest not found. Gemstack is not initialized here.');
12
+ process.exit(1);
13
+ }
14
+
15
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
16
+ logger.ok(`Manifest version: ${manifest.version}`);
17
+
18
+ let missing = 0;
19
+ let modified = 0;
20
+ const manifestLib = require('../lib/manifest');
21
+
22
+ (manifest.files || []).forEach(f => {
23
+ const p = fssafe.resolveSafe(targetDir, f.path);
24
+ if (!fs.existsSync(p)) {
25
+ logger.error(`Missing owned file: ${f.path}`);
26
+ missing++;
27
+ } else {
28
+ const destContent = fs.readFileSync(p);
29
+ const destCheck = manifestLib.getChecksum(destContent);
30
+ if (destCheck !== f.checksum) {
31
+ logger.warn(`Modified owned file: ${f.path}`);
32
+ modified++;
33
+ }
34
+ }
35
+ });
36
+
37
+ if (missing === 0 && modified === 0) {
38
+ logger.ok('All owned files are present and unmodified.');
39
+ } else {
40
+ logger.info(`Summary: ${missing} missing, ${modified} modified. Use 'gemstack update' if you want to restore defaults.`);
41
+ }
42
+
43
+ const giPath = fssafe.resolveSafe(targetDir, '.gitignore');
44
+ if (fs.existsSync(giPath) && fs.readFileSync(giPath, 'utf8').includes('# Gemstack')) {
45
+ logger.ok('.gitignore is patched.');
46
+ } else {
47
+ logger.warn('.gitignore is not patched.');
48
+ }
49
+ logger.ok('Doctor checks completed.');
50
+ };
@@ -0,0 +1,67 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const logger = require('../lib/logger');
4
+
5
+ const PRE_COMMIT_HOOK = `#!/bin/sh
6
+ # Gemstack - Git Pre-commit Hook
7
+ # Este hook previene que subas secretos o rompas la Ley de Seguridad (Rule 03).
8
+
9
+ echo "[Gemstack] Ejecutando análisis de seguridad local..."
10
+
11
+ # 1. Evitar que se suban archivos .env
12
+ if git diff --cached --name-only | grep -E "^\\.env|\\.env\\.local|\\.env\\.production" > /dev/null; then
13
+ echo "❌ ERROR (Gemstack): Estás intentando hacer commit de un archivo .env"
14
+ echo "Regla 03: Gestión de Secretos. Remuévelo usando 'git reset HEAD <archivo>' y añádelo al .gitignore."
15
+ exit 1
16
+ fi
17
+
18
+ # 2. Buscar marcadores de conflicto de merge olvidados
19
+ if git diff --cached -S"<<<<<<< HEAD" --quiet; then
20
+ # -quiet retorna 0 si encuentra coincidencias (no output)
21
+ :
22
+ else
23
+ # Si git diff encuenta "<<<<<<<"
24
+ if git diff --cached | grep -E "^\\+<<<<<<<" > /dev/null; then
25
+ echo "❌ ERROR (Gemstack): Hay marcadores de conflicto de git (<<<<<<<) en tus archivos."
26
+ echo "Resuélvelos antes de hacer commit."
27
+ exit 1
28
+ fi
29
+ fi
30
+
31
+ # 3. Buscar posibles llaves expuestas (Stripe, AWS, JWT genéricos) en código agregado
32
+ # Extrae las líneas añadidas y busca patrones peligrosos
33
+ if git diff --cached | grep -E "^\\+.*(sk_live_|sk_test_|AKIA[0-9A-Z]{16}|xoxb-|ghp_)" > /dev/null; then
34
+ echo "❌ ERROR (Gemstack/CSO): ¡Posible llave secreta detectada en el código!"
35
+ echo "Regla 03: Cero Exposición de Credenciales. Extrae el secreto a una variable de entorno."
36
+ exit 1
37
+ fi
38
+
39
+ echo "✅ [Gemstack] Código limpio. Committing..."
40
+ exit 0
41
+ `;
42
+
43
+ function installHooks(targetDir) {
44
+ const gitDir = path.join(targetDir, '.git');
45
+ const hooksDir = path.join(gitDir, 'hooks');
46
+ const preCommitPath = path.join(hooksDir, 'pre-commit');
47
+
48
+ if (!fs.existsSync(gitDir)) {
49
+ logger.info('No se detectó un repositorio git. Omitiendo instalación de hooks.');
50
+ return false;
51
+ }
52
+
53
+ try {
54
+ if (!fs.existsSync(hooksDir)) {
55
+ fs.mkdirSync(hooksDir, { recursive: true });
56
+ }
57
+
58
+ fs.writeFileSync(preCommitPath, PRE_COMMIT_HOOK, { mode: 0o755 });
59
+ logger.ok('Git hooks (pre-commit) instalados exitosamente.');
60
+ return true;
61
+ } catch (err) {
62
+ logger.error(`Error instalando git hooks: ${err.message}`);
63
+ return false;
64
+ }
65
+ }
66
+
67
+ module.exports = { installHooks };
@@ -0,0 +1,90 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const fssafe = require('../lib/filesystem-safe');
4
+ const logger = require('../lib/logger');
5
+ const manifestLib = require('../lib/manifest');
6
+ const backupLib = require('../lib/backup');
7
+ const gitignoreLib = require('../lib/gitignore');
8
+ const hooks = require('./hooks');
9
+
10
+ function walkDir(dir, callback) {
11
+ fs.readdirSync(dir).forEach(f => {
12
+ const dirPath = path.join(dir, f);
13
+ if (fs.statSync(dirPath).isDirectory()) walkDir(dirPath, callback);
14
+ else callback(dirPath);
15
+ });
16
+ }
17
+
18
+ module.exports = async (flags) => {
19
+ const targetDir = flags.target;
20
+ const templateDir = path.resolve(__dirname, '../../template');
21
+ if (!fs.existsSync(templateDir)) {
22
+ throw new Error('Template directory missing in Gemstack installation.');
23
+ }
24
+
25
+ logger.info(`Running init on ${targetDir}`);
26
+ const manifest = manifestLib.loadManifest(targetDir);
27
+ if (!manifest.files) manifest.files = [];
28
+
29
+ let conflicts = [];
30
+ let toCopy = [];
31
+
32
+ walkDir(templateDir, (filePath) => {
33
+ const relativePath = path.relative(templateDir, filePath).replace(/\\/g, '/');
34
+ const destPath = fssafe.resolveSafe(targetDir, relativePath);
35
+
36
+ if (fs.existsSync(destPath)) {
37
+ if (['handoff.md', 'handoff_archive.md', '.gemstack/learnings.md', '.gemstack/state.json'].includes(relativePath)) {
38
+ // Safe ignore
39
+ } else {
40
+ conflicts.push(relativePath);
41
+ }
42
+ } else {
43
+ toCopy.push(relativePath);
44
+ }
45
+ });
46
+
47
+ if (flags.dryRun) {
48
+ logger.ok(`Dry run: Will create ${toCopy.length} files.`);
49
+ if (conflicts.length > 0) logger.warn(`Conflicts found in ${conflicts.length} files. Requires --yes to backup & overwrite.`);
50
+ return;
51
+ }
52
+
53
+ if (conflicts.length > 0 && !flags.yes) {
54
+ logger.error(`Conflicts detected in ${conflicts.length} files. Aborting.`);
55
+ logger.info(`Run with --yes to backup and overwrite Gemstack-owned files, or --dry-run to inspect.`);
56
+ process.exit(1);
57
+ }
58
+
59
+ const sessionTimestamp = new Date().toISOString().replace(/[:.]/g, '-');
60
+
61
+ if (flags.yes) {
62
+ for (const rel of conflicts) {
63
+ backupLib.backupFile(targetDir, rel, flags.dryRun, sessionTimestamp);
64
+ toCopy.push(rel);
65
+ }
66
+ }
67
+
68
+ for (const rel of toCopy) {
69
+ const src = path.join(templateDir, rel);
70
+ const dest = fssafe.resolveSafe(targetDir, rel);
71
+ fssafe.ensureDir(path.dirname(dest));
72
+ fs.copyFileSync(src, dest);
73
+ logger.ok(`Created ${rel}`);
74
+
75
+ const content = fs.readFileSync(dest);
76
+ const ex = manifest.files.find(f => f.path === rel);
77
+ if (ex) ex.checksum = manifestLib.getChecksum(content);
78
+ else manifest.files.push({ path: rel, checksum: manifestLib.getChecksum(content) });
79
+ }
80
+
81
+ gitignoreLib.patchGitignore(targetDir, flags.dryRun);
82
+ manifestLib.saveManifest(targetDir, manifest, flags.dryRun);
83
+
84
+ // Attempt to install native Git hooks
85
+ if (!flags.dryRun) {
86
+ hooks.installHooks(targetDir);
87
+ }
88
+
89
+ logger.ok('Gemstack initialization complete.');
90
+ };
@@ -0,0 +1,72 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const https = require('https');
4
+ const fssafe = require('../lib/filesystem-safe');
5
+ const logger = require('../lib/logger');
6
+ const manifestLib = require('../lib/manifest');
7
+
8
+ async function fetchUrl(url) {
9
+ const response = await fetch(url);
10
+ if (!response.ok) {
11
+ throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
12
+ }
13
+ return await response.text();
14
+ }
15
+
16
+ function extractSkillName(content) {
17
+ const match = content.match(/^name:\s*(.+)$/m);
18
+ return match ? match[1].trim() : null;
19
+ }
20
+
21
+ module.exports = async (url, flags) => {
22
+ if (!url) {
23
+ logger.error("Debe proporcionar una URL. Ejemplo: gemstack install https://raw.githubusercontent.com/.../SKILL.md");
24
+ process.exit(1);
25
+ }
26
+
27
+ const targetDir = flags.target || process.cwd();
28
+ logger.info(`Descargando skill desde: ${url}`);
29
+
30
+ try {
31
+ const content = await fetchUrl(url);
32
+
33
+ // Ensure it's a valid skill file
34
+ if (!content.includes('---')) {
35
+ throw new Error('El archivo descargado no parece un SKILL válido (no tiene frontmatter).');
36
+ }
37
+
38
+ const skillName = extractSkillName(content);
39
+ if (!skillName) {
40
+ throw new Error('No se encontró "name: <nombre>" en el frontmatter del SKILL.');
41
+ }
42
+
43
+ // Calculate path
44
+ const relativePath = `.agents/skills/${skillName}/SKILL.md`;
45
+ const destPath = fssafe.resolveSafe(targetDir, relativePath);
46
+
47
+ if (flags.dryRun) {
48
+ logger.ok(`Dry run: Se instalaría el skill "${skillName}" en ${relativePath}`);
49
+ return;
50
+ }
51
+
52
+ // Install the skill
53
+ fssafe.ensureDir(path.dirname(destPath));
54
+ fs.writeFileSync(destPath, content, 'utf8');
55
+
56
+ // Update manifest
57
+ const manifest = manifestLib.loadManifest(targetDir);
58
+ if (!manifest.files) manifest.files = [];
59
+
60
+ const checksum = manifestLib.getChecksum(Buffer.from(content, 'utf8'));
61
+ const ex = manifest.files.find(f => f.path === relativePath);
62
+ if (ex) ex.checksum = checksum;
63
+ else manifest.files.push({ path: relativePath, checksum });
64
+
65
+ manifestLib.saveManifest(targetDir, manifest, false);
66
+
67
+ logger.ok(`✅ Skill "${skillName}" instalado exitosamente en ${relativePath}`);
68
+ } catch (e) {
69
+ logger.error(`Error instalando el skill: ${e.message}`);
70
+ process.exit(1);
71
+ }
72
+ };
@@ -0,0 +1,20 @@
1
+ const fs = require('fs');
2
+ const fssafe = require('../lib/filesystem-safe');
3
+ const logger = require('../lib/logger');
4
+
5
+ module.exports = async (flags) => {
6
+ const targetDir = flags.target;
7
+ const skillsDir = fssafe.resolveSafe(targetDir, '.agents/skills');
8
+
9
+ if (!fs.existsSync(skillsDir)) {
10
+ logger.error(`No skills directory found at ${skillsDir}`);
11
+ return;
12
+ }
13
+
14
+ logger.info('Installed Skills:');
15
+ fs.readdirSync(skillsDir).forEach(dir => {
16
+ if (dir.startsWith('gemstack-')) {
17
+ logger.ok(dir);
18
+ }
19
+ });
20
+ };
@@ -0,0 +1,18 @@
1
+ const fs = require('fs');
2
+ const fssafe = require('../lib/filesystem-safe');
3
+ const logger = require('../lib/logger');
4
+
5
+ module.exports = async (skill, flags) => {
6
+ if (!skill) {
7
+ logger.error('Must provide skill name');
8
+ process.exit(1);
9
+ }
10
+ const targetDir = flags.target;
11
+ const p = fssafe.resolveSafe(targetDir, `.agents/skills/${skill}/SKILL.md`);
12
+
13
+ if (!fs.existsSync(p)) {
14
+ logger.error(`Skill ${skill} not found at ${p}`);
15
+ return;
16
+ }
17
+ console.log(fs.readFileSync(p, 'utf8'));
18
+ };
@@ -0,0 +1,88 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const fssafe = require('../lib/filesystem-safe');
4
+ const logger = require('../lib/logger');
5
+ const manifestLib = require('../lib/manifest');
6
+ const backupLib = require('../lib/backup');
7
+
8
+ function walkDir(dir, callback) {
9
+ fs.readdirSync(dir).forEach(f => {
10
+ const dirPath = path.join(dir, f);
11
+ if (fs.statSync(dirPath).isDirectory()) walkDir(dirPath, callback);
12
+ else callback(dirPath);
13
+ });
14
+ }
15
+
16
+ module.exports = async (flags) => {
17
+ const targetDir = flags.target;
18
+ const templateDir = path.resolve(__dirname, '../../template');
19
+
20
+ logger.info(`Running update on ${targetDir}`);
21
+ const manifest = manifestLib.loadManifest(targetDir);
22
+ if (!manifest.files) manifest.files = [];
23
+ let toUpdate = [];
24
+
25
+ walkDir(templateDir, (filePath) => {
26
+ const relativePath = path.relative(templateDir, filePath).replace(/\\/g, '/');
27
+ const destPath = fssafe.resolveSafe(targetDir, relativePath);
28
+
29
+ const tmplContent = fs.readFileSync(filePath);
30
+ const tmplCheck = manifestLib.getChecksum(tmplContent);
31
+
32
+ const exManifest = manifest.files.find(f => f.path === relativePath);
33
+
34
+ if (fs.existsSync(destPath)) {
35
+ const destContent = fs.readFileSync(destPath);
36
+ const destCheck = manifestLib.getChecksum(destContent);
37
+
38
+ if (exManifest && destCheck !== exManifest.checksum && destCheck !== tmplCheck) {
39
+ if (!flags.force) {
40
+ logger.warn(`User modified ${relativePath}. Skipping. Use --force to override.`);
41
+ return;
42
+ }
43
+ }
44
+ if (tmplCheck !== destCheck) {
45
+ toUpdate.push(relativePath);
46
+ }
47
+ } else {
48
+ // File exists in template but not in target (new skill/file)
49
+ toUpdate.push(relativePath);
50
+ }
51
+ });
52
+
53
+ if (toUpdate.length === 0) {
54
+ logger.ok('Everything is up to date.');
55
+ return;
56
+ }
57
+
58
+ if (flags.dryRun) {
59
+ logger.ok(`Dry run: Will update ${toUpdate.length} files.`);
60
+ toUpdate.forEach(f => logger.info(` -> ${f}`));
61
+ return;
62
+ }
63
+
64
+ if (!flags.yes) {
65
+ logger.error(`Updates available for ${toUpdate.length} files. Aborting.`);
66
+ logger.info(`Run with --yes to backup and apply updates.`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const sessionTimestamp = new Date().toISOString().replace(/[:.]/g, '-');
71
+
72
+ for (const rel of toUpdate) {
73
+ backupLib.backupFile(targetDir, rel, flags.dryRun, sessionTimestamp);
74
+ const src = path.join(templateDir, rel);
75
+ const dest = fssafe.resolveSafe(targetDir, rel);
76
+ fssafe.ensureDir(path.dirname(dest));
77
+ fs.copyFileSync(src, dest);
78
+ logger.ok(`Updated ${rel}`);
79
+
80
+ const ex = manifest.files.find(f => f.path === rel);
81
+ const newChecksum = manifestLib.getChecksum(fs.readFileSync(dest));
82
+ if (ex) ex.checksum = newChecksum;
83
+ else manifest.files.push({ path: rel, checksum: newChecksum });
84
+ }
85
+
86
+ manifestLib.saveManifest(targetDir, manifest, flags.dryRun);
87
+ logger.ok('Gemstack update complete.');
88
+ };
@@ -0,0 +1,29 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const fssafe = require('./filesystem-safe');
4
+ const logger = require('./logger');
5
+
6
+ module.exports = {
7
+ backupFile: (targetDir, relativeFilePath, dryRun, sessionTimestamp) => {
8
+ const fullPath = fssafe.resolveSafe(targetDir, relativeFilePath);
9
+ if (!fs.existsSync(fullPath)) return;
10
+
11
+ const timestamp = sessionTimestamp || new Date().toISOString().replace(/[:.]/g, '-');
12
+ const backupDir = fssafe.resolveSafe(targetDir, `.gemstack/backups/${timestamp}`);
13
+ const backupDest = fssafe.resolveSafe(backupDir, relativeFilePath);
14
+
15
+ logger.info(`Backup: ${relativeFilePath}`);
16
+ if (!dryRun) {
17
+ fssafe.ensureDir(path.dirname(backupDest));
18
+ fs.copyFileSync(fullPath, backupDest);
19
+
20
+ const manifestPath = path.join(backupDir, 'manifest.json');
21
+ let manifest = [];
22
+ if (fs.existsSync(manifestPath)) {
23
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
24
+ }
25
+ manifest.push({ original: relativeFilePath, backup: backupDest, timestamp });
26
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
27
+ }
28
+ }
29
+ };
@@ -0,0 +1,22 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ module.exports = {
5
+ resolveSafe: (targetDir, relativePath) => {
6
+ const target = path.resolve(targetDir);
7
+ const candidate = path.resolve(targetDir, relativePath);
8
+ const rel = path.relative(target, candidate);
9
+
10
+ const isInside = rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
11
+
12
+ if (!isInside) {
13
+ throw new Error(`Path Traversal blocked: ${relativePath}`);
14
+ }
15
+ return candidate;
16
+ },
17
+ ensureDir: (dirPath) => {
18
+ if (!fs.existsSync(dirPath)) {
19
+ fs.mkdirSync(dirPath, { recursive: true });
20
+ }
21
+ }
22
+ };
@@ -0,0 +1,19 @@
1
+ const fs = require('fs');
2
+ const fssafe = require('./filesystem-safe');
3
+ const logger = require('./logger');
4
+
5
+ module.exports = {
6
+ patchGitignore: (targetDir, dryRun) => {
7
+ const giPath = fssafe.resolveSafe(targetDir, '.gitignore');
8
+ let content = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : '';
9
+ if (content.includes('# Gemstack')) {
10
+ return;
11
+ }
12
+
13
+ const block = `\n# Gemstack\n.gemstack/state.json\n.gemstack/backups/\n`;
14
+ logger.info(`Patching .gitignore`);
15
+ if (!dryRun) {
16
+ fs.appendFileSync(giPath, block);
17
+ }
18
+ }
19
+ };
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ info: (msg) => console.log(`[INFO] ${msg}`),
3
+ ok: (msg) => console.log(`[OK] ${msg}`),
4
+ error: (msg) => console.error(`[FAIL] ${msg}`),
5
+ warn: (msg) => console.warn(`[WARN] ${msg}`)
6
+ };