@3lineas/grooveskills 1.5.0 → 1.7.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.
Files changed (27) hide show
  1. package/README.md +8 -0
  2. package/dist/{chunk-PLMYUKGW.js → chunk-E63GTCOE.js} +475 -146
  3. package/dist/chunk-E63GTCOE.js.map +1 -0
  4. package/dist/cli.js +23 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/main.js +1 -1
  7. package/package.json +3 -2
  8. package/skills/auth-rbac-policies/SKILL.md +100 -0
  9. package/skills/auth-rbac-policies/references/auth-adapters-examples.md +99 -0
  10. package/skills/caching-rate-limiting/SKILL.md +109 -0
  11. package/skills/caching-rate-limiting/references/cache-rate-limit-examples.md +129 -0
  12. package/skills/clean-architecture-ddd/SKILL.md +1 -0
  13. package/skills/controller-orchestration-queues/SKILL.md +109 -0
  14. package/skills/controller-orchestration-queues/references/controller-queue-examples.md +115 -0
  15. package/skills/docs-standards/SKILL.md +113 -0
  16. package/skills/docs-standards/references/adr-template-and-lifecycle.md +111 -0
  17. package/skills/docs-standards/references/code-comments-and-docstrings.md +147 -0
  18. package/skills/docs-standards/references/relative-paths-and-diagrams.md +123 -0
  19. package/skills/env-feature-flags/SKILL.md +94 -0
  20. package/skills/env-feature-flags/references/env-feature-flags-examples.md +93 -0
  21. package/skills/external-service-adapters/SKILL.md +148 -0
  22. package/skills/external-service-adapters/references/email-and-payment-examples.md +198 -0
  23. package/skills/storage-file-adapters/SKILL.md +125 -0
  24. package/skills/storage-file-adapters/references/storage-adapters-examples.md +164 -0
  25. package/skills/webhook-idempotency/SKILL.md +104 -0
  26. package/skills/webhook-idempotency/references/webhook-idempotency-examples.md +112 -0
  27. package/dist/chunk-PLMYUKGW.js.map +0 -1
@@ -94,6 +94,13 @@ var SKILL_CATALOG = [
94
94
  dependencies: [],
95
95
  directoryName: "ux-writing"
96
96
  },
97
+ {
98
+ id: "docs-standards",
99
+ name: "Documentation & ADR Standards",
100
+ description: "English documentation standard, relative POSIX links, resilient Mermaid diagrams, ADR lifecycle, and intent-focused docstrings",
101
+ dependencies: [],
102
+ directoryName: "docs-standards"
103
+ },
97
104
  {
98
105
  id: "typescript-clean",
99
106
  name: "TypeScript Clean Code (TS 5.5+)",
@@ -136,6 +143,55 @@ var SKILL_CATALOG = [
136
143
  dependencies: [],
137
144
  directoryName: "clean-architecture-ddd"
138
145
  },
146
+ {
147
+ id: "external-service-adapters",
148
+ name: "External Service Adapters & Wrappers",
149
+ description: "Standardized adapter and wrapper architecture for external services (payment gateways, emails, SMS, cloud storage)",
150
+ dependencies: [],
151
+ directoryName: "external-service-adapters"
152
+ },
153
+ {
154
+ id: "storage-file-adapters",
155
+ name: "Storage & File Adapters (S3, R2, Supabase)",
156
+ description: "Standardized wrapper & adapter architecture for object storage, uploads, presigned URLs, and streaming without repositories",
157
+ dependencies: [],
158
+ directoryName: "storage-file-adapters"
159
+ },
160
+ {
161
+ id: "auth-rbac-policies",
162
+ name: "Auth Adapters & RBAC/ABAC Policies",
163
+ description: "Authentication adapter architecture and pure domain authorization policies (RBAC/ABAC) with AuthenticatedActor contract",
164
+ dependencies: [],
165
+ directoryName: "auth-rbac-policies"
166
+ },
167
+ {
168
+ id: "controller-orchestration-queues",
169
+ name: "Controller Orchestration & Queue Adapters",
170
+ description: "Strict orchestration rules for Controllers, atomic Actions, DB Transactions, and background Queue Adapters (Cloudflare, BullMQ, SQS)",
171
+ dependencies: [],
172
+ directoryName: "controller-orchestration-queues"
173
+ },
174
+ {
175
+ id: "webhook-idempotency",
176
+ name: "Webhook Verifiers & Idempotency Guards",
177
+ description: "Incoming webhook HMAC verification adapters and atomic idempotency guards guaranteeing at-most-once processing",
178
+ dependencies: [],
179
+ directoryName: "webhook-idempotency"
180
+ },
181
+ {
182
+ id: "caching-rate-limiting",
183
+ name: "Cache-Aside & Rate Limiting Adapters",
184
+ description: "Cache-aside remember wrapper and interchangeable sliding-window rate limiter adapters (Redis, Cloudflare KV, In-Memory)",
185
+ dependencies: [],
186
+ directoryName: "caching-rate-limiting"
187
+ },
188
+ {
189
+ id: "env-feature-flags",
190
+ name: "Fail-Fast Env & Feature Flags Adapters",
191
+ description: "Fail-fast environment schema validation with Valibot and decoupled feature flags adapters (PostHog, LaunchDarkly, Unleash)",
192
+ dependencies: [],
193
+ directoryName: "env-feature-flags"
194
+ },
139
195
  {
140
196
  id: "testing-vitest",
141
197
  name: "Vitest Testing Standards",
@@ -194,8 +250,16 @@ function detectProjectContext(targetPath) {
194
250
  "odd-ai",
195
251
  "best-practices",
196
252
  "ux-writing",
253
+ "docs-standards",
197
254
  "error-handling-logging",
198
- "clean-architecture-ddd"
255
+ "clean-architecture-ddd",
256
+ "external-service-adapters",
257
+ "storage-file-adapters",
258
+ "auth-rbac-policies",
259
+ "controller-orchestration-queues",
260
+ "webhook-idempotency",
261
+ "caching-rate-limiting",
262
+ "env-feature-flags"
199
263
  ];
200
264
  for (const skill of SKILL_CATALOG) {
201
265
  if (skill.dependencies.length === 0) {
@@ -221,7 +285,7 @@ function detectProjectContext(targetPath) {
221
285
 
222
286
  // src/domains/ui/ui-orchestrator.ts
223
287
  import * as p5 from "@clack/prompts";
224
- import pc2 from "picocolors";
288
+ import pc3 from "picocolors";
225
289
 
226
290
  // src/domains/ui/banners.ts
227
291
  import pc from "picocolors";
@@ -326,7 +390,7 @@ function verifySkillExists(targetPath, skillDirectoryName, agents) {
326
390
  import { join as join11 } from "path";
327
391
 
328
392
  // src/domains/adapters/skill-reference-builder.ts
329
- import { existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync } from "fs";
393
+ import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync, readFileSync as readFileSync3, rmSync, unlinkSync } from "fs";
330
394
  import { join as join5 } from "path";
331
395
 
332
396
  // src/domains/project-store/project-paths.ts
@@ -347,9 +411,46 @@ function getProjectConfigPath(targetPath) {
347
411
  return join4(getProjectStoreDir(targetPath), "config.json");
348
412
  }
349
413
 
414
+ // src/domains/fs/safe-file-writer.ts
415
+ import { chmodSync, existsSync as existsSync3, writeFileSync } from "fs";
416
+
417
+ // src/errors/file-permission-error.ts
418
+ var FilePermissionError = class extends Error {
419
+ code = "EACCES";
420
+ filePath;
421
+ suggestedFixCommand;
422
+ constructor(filePath) {
423
+ super(`No tienes permisos de escritura para modificar '${filePath}'.`);
424
+ this.name = "FilePermissionError";
425
+ this.filePath = filePath;
426
+ this.suggestedFixCommand = `chmod u+w "${filePath}"`;
427
+ }
428
+ };
429
+
430
+ // src/domains/fs/safe-file-writer.ts
431
+ function safeWriteFileSync(filePath, content) {
432
+ try {
433
+ writeFileSync(filePath, content, "utf-8");
434
+ } catch (error) {
435
+ const err = error;
436
+ if (err.code === "EACCES" || err.code === "EPERM") {
437
+ if (existsSync3(filePath)) {
438
+ try {
439
+ chmodSync(filePath, 420);
440
+ writeFileSync(filePath, content, "utf-8");
441
+ return;
442
+ } catch {
443
+ }
444
+ }
445
+ throw new FilePermissionError(filePath);
446
+ }
447
+ throw error;
448
+ }
449
+ }
450
+
350
451
  // src/domains/adapters/skill-reference-builder.ts
351
452
  function removeExistingPathSafely(path) {
352
- const fileExists = existsSync3(path);
453
+ const fileExists = existsSync4(path);
353
454
  if (!fileExists) {
354
455
  try {
355
456
  const lstat = lstatSync2(path);
@@ -375,7 +476,7 @@ function removeExistingPathSafely(path) {
375
476
  }
376
477
  }
377
478
  function extractSkillDescription(canonicalSkillMdPath, fallback) {
378
- if (!existsSync3(canonicalSkillMdPath)) {
479
+ if (!existsSync4(canonicalSkillMdPath)) {
379
480
  return fallback;
380
481
  }
381
482
  try {
@@ -389,8 +490,14 @@ function extractSkillDescription(canonicalSkillMdPath, fallback) {
389
490
  }
390
491
  return fallback;
391
492
  }
392
- function writeAgentSkillReference(targetPath, agentRelativeSkillsDir, skill) {
493
+ function writeAgentSkillReference(targetPath, agentRelativeSkillsDir, skill, transaction) {
393
494
  const agentSkillFolder = join5(targetPath, agentRelativeSkillsDir, skill.directoryName);
495
+ const targetFile = join5(agentSkillFolder, "SKILL.md");
496
+ if (transaction) {
497
+ transaction.recordCreatedDir(join5(targetPath, agentRelativeSkillsDir));
498
+ transaction.recordCreatedDir(agentSkillFolder);
499
+ transaction.recordBeforeWrite(targetFile);
500
+ }
394
501
  removeExistingPathSafely(agentSkillFolder);
395
502
  mkdirSync(agentSkillFolder, { recursive: true });
396
503
  const canonicalSkillMd = join5(getProjectSkillsDir(targetPath), skill.directoryName, "SKILL.md");
@@ -409,16 +516,19 @@ Canonical skill specification stored in \`.grooveskills/\`.
409
516
 
410
517
  > **Instruction**: Read \`.grooveskills/skills/${skill.directoryName}/SKILL.md\` before executing tasks matching this skill.
411
518
  `;
412
- const targetFile = join5(agentSkillFolder, "SKILL.md");
413
- writeFileSync(targetFile, referenceContent, "utf-8");
519
+ safeWriteFileSync(targetFile, referenceContent);
414
520
  return targetFile;
415
521
  }
416
- function writeCursorRuleReference(targetPath, cursorRelativeRulesDir, skill) {
522
+ function writeCursorRuleReference(targetPath, cursorRelativeRulesDir, skill, transaction) {
417
523
  const rulesDir = join5(targetPath, cursorRelativeRulesDir);
418
- if (!existsSync3(rulesDir)) {
524
+ const targetFile = join5(rulesDir, `${skill.id}.mdc`);
525
+ if (transaction) {
526
+ transaction.recordCreatedDir(rulesDir);
527
+ transaction.recordBeforeWrite(targetFile);
528
+ }
529
+ if (!existsSync4(rulesDir)) {
419
530
  mkdirSync(rulesDir, { recursive: true });
420
531
  }
421
- const targetFile = join5(rulesDir, `${skill.id}.mdc`);
422
532
  removeExistingPathSafely(targetFile);
423
533
  const canonicalSkillMd = join5(getProjectSkillsDir(targetPath), skill.directoryName, "SKILL.md");
424
534
  const description = extractSkillDescription(canonicalSkillMd, skill.description);
@@ -437,7 +547,7 @@ Canonical rule specification stored in \`.grooveskills/\`.
437
547
 
438
548
  > **Instruction**: Read \`.grooveskills/skills/${skill.directoryName}/SKILL.md\` before executing tasks matching this rule.
439
549
  `;
440
- writeFileSync(targetFile, referenceContent, "utf-8");
550
+ safeWriteFileSync(targetFile, referenceContent);
441
551
  return targetFile;
442
552
  }
443
553
 
@@ -446,8 +556,8 @@ var AntigravityAdapter = class {
446
556
  id = "antigravity";
447
557
  name = "Antigravity";
448
558
  targetRelativeDir = ".agents/skills";
449
- async installSkill(targetPath, skill, _skillSourceDir) {
450
- const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill);
559
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
560
+ const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill, transaction);
451
561
  return [file];
452
562
  }
453
563
  };
@@ -457,8 +567,8 @@ var ClaudeAdapter = class {
457
567
  id = "claude";
458
568
  name = "Claude Code";
459
569
  targetRelativeDir = ".claude/skills";
460
- async installSkill(targetPath, skill, _skillSourceDir) {
461
- const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill);
570
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
571
+ const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill, transaction);
462
572
  return [file];
463
573
  }
464
574
  };
@@ -468,8 +578,8 @@ var CursorAdapter = class {
468
578
  id = "cursor";
469
579
  name = "Cursor";
470
580
  targetRelativeDir = ".cursor/rules";
471
- async installSkill(targetPath, skill, _skillSourceDir) {
472
- const file = writeCursorRuleReference(targetPath, this.targetRelativeDir, skill);
581
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
582
+ const file = writeCursorRuleReference(targetPath, this.targetRelativeDir, skill, transaction);
473
583
  return [file];
474
584
  }
475
585
  };
@@ -479,35 +589,35 @@ var GeminiAdapter = class {
479
589
  id = "gemini";
480
590
  name = "Gemini Code Assist";
481
591
  targetRelativeDir = ".gemini/skills";
482
- async installSkill(targetPath, skill, _skillSourceDir) {
483
- const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill);
592
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
593
+ const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill, transaction);
484
594
  return [file];
485
595
  }
486
596
  };
487
597
 
488
598
  // src/domains/project-store/sync-project-store.ts
489
- import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "fs";
599
+ import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "fs";
490
600
  import { join as join7 } from "path";
491
601
 
492
602
  // src/domains/installer/resolve-skills-source.ts
493
603
  import { fileURLToPath } from "url";
494
604
  import { dirname, resolve, join as join6 } from "path";
495
- import { existsSync as existsSync4 } from "fs";
605
+ import { existsSync as existsSync5 } from "fs";
496
606
  function resolveSkillsSourceRoot() {
497
607
  const currentFilePath = fileURLToPath(import.meta.url);
498
608
  const currentDir = dirname(currentFilePath);
499
609
  const candidateFromDist = resolve(currentDir, "../skills");
500
- if (existsSync4(candidateFromDist)) {
610
+ if (existsSync5(candidateFromDist)) {
501
611
  return candidateFromDist;
502
612
  }
503
613
  const candidateFromSrc = resolve(currentDir, "../../../skills");
504
- if (existsSync4(candidateFromSrc)) {
614
+ if (existsSync5(candidateFromSrc)) {
505
615
  return candidateFromSrc;
506
616
  }
507
617
  let searchDir = currentDir;
508
618
  for (let i = 0; i < 5; i++) {
509
619
  const candidate = join6(searchDir, "skills");
510
- if (existsSync4(candidate)) {
620
+ if (existsSync5(candidate)) {
511
621
  return candidate;
512
622
  }
513
623
  const parent = dirname(searchDir);
@@ -522,14 +632,17 @@ function resolveSkillsSourceRoot() {
522
632
  }
523
633
 
524
634
  // src/domains/project-store/sync-project-store.ts
525
- function syncProjectStore(targetPath, selectedSkillIds, _selectedAgents) {
635
+ function syncProjectStore(targetPath, selectedSkillIds, _selectedAgents, transaction) {
526
636
  const bundledSkillsDir = resolveSkillsSourceRoot();
527
637
  const projectSkillsDir = getProjectSkillsDir(targetPath);
528
- if (!existsSync5(projectSkillsDir)) {
638
+ if (transaction) {
639
+ transaction.recordCreatedDir(projectSkillsDir);
640
+ }
641
+ if (!existsSync6(projectSkillsDir)) {
529
642
  mkdirSync2(projectSkillsDir, { recursive: true });
530
643
  }
531
644
  const legacyCursorRules = join7(targetPath, ".grooveskills", "cursor-rules");
532
- if (existsSync5(legacyCursorRules)) {
645
+ if (existsSync6(legacyCursorRules)) {
533
646
  try {
534
647
  rmSync2(legacyCursorRules, { recursive: true, force: true });
535
648
  } catch {
@@ -542,14 +655,17 @@ function syncProjectStore(targetPath, selectedSkillIds, _selectedAgents) {
542
655
  }
543
656
  const sourceSkillDir = join7(bundledSkillsDir, skill.directoryName);
544
657
  const destSkillDir = join7(projectSkillsDir, skill.directoryName);
545
- if (existsSync5(sourceSkillDir)) {
658
+ if (transaction) {
659
+ transaction.recordCreatedDir(destSkillDir);
660
+ }
661
+ if (existsSync6(sourceSkillDir)) {
546
662
  cpSync(sourceSkillDir, destSkillDir, { recursive: true });
547
663
  }
548
664
  }
549
665
  }
550
666
 
551
667
  // src/domains/harness/install-harness.ts
552
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
668
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
553
669
  import { join as join10 } from "path";
554
670
 
555
671
  // src/domains/harness/harness-languages.ts
@@ -558,31 +674,41 @@ var SUPPORTED_LANGUAGES = {
558
674
  code: "es",
559
675
  name: "Spanish",
560
676
  nativeName: "Espa\xF1ol",
561
- instruction: "Todos los mensajes, validaciones, errores, CTAs y comunicaciones con el usuario deben redactarse estrictamente en Espa\xF1ol. El tono debe ser emp\xE1tico, amable, persuasivo y profesional."
677
+ instruction: "Todos los mensajes, validaciones, errores, CTAs y comunicaciones con el usuario deben redactarse estrictamente en Espa\xF1ol. El tono debe ser emp\xE1tico, amable, persuasivo y profesional.",
678
+ onboardingPromptTitle: "Prompt de inicializaci\xF3n para tu Agente IA (Copia y pega en el chat):",
679
+ onboardingPrompt: "Hola. Acabo de configurar GrooveSkills Harness en este proyecto. Por favor lee `.grooveskills/memory/tasks/00-onboarding.md`, `.grooveskills/memory/context.md` y las skills instaladas en `.agents/skills/`. Hazme preguntas interactivas paso a paso para detectar vac\xEDos de arquitectura, aclarar objetivos y adaptar el proyecto a mis necesidades antes de continuar."
562
680
  },
563
681
  en: {
564
682
  code: "en",
565
683
  name: "English",
566
684
  nativeName: "English",
567
- instruction: "All user-facing messages, form validations, errors, CTAs, and notifications must be written strictly in English. Tone must be empathetic, friendly, persuasive, and professional."
685
+ instruction: "All user-facing messages, form validations, errors, CTAs, and notifications must be written strictly in English. Tone must be empathetic, friendly, persuasive, and professional.",
686
+ onboardingPromptTitle: "Initialization prompt for your AI Agent (Copy and paste into your chat):",
687
+ onboardingPrompt: "Hello. I just configured GrooveSkills Harness in this project. Please review `.grooveskills/memory/tasks/00-onboarding.md`, `.grooveskills/memory/context.md`, and the skills installed in `.agents/skills/`. Ask me step-by-step interactive questions to identify architectural gaps, clarify priorities, and adapt the project to my needs before continuing."
568
688
  },
569
689
  pt: {
570
690
  code: "pt",
571
691
  name: "Portuguese",
572
692
  nativeName: "Portugu\xEAs",
573
- instruction: "Todas as mensagens, valida\xE7\xF5es, erros, CTAs e comunica\xE7\xF5es voltadas para o usu\xE1rio devem ser redigidas estritamente em Portugu\xEAs. O tom deve ser emp\xE1tico, amig\xE1vel, persuasivo e profissional."
693
+ instruction: "Todas as mensagens, valida\xE7\xF5es, erros, CTAs e comunica\xE7\xF5es voltadas para o usu\xE1rio devem ser redigidas estritamente em Portugu\xEAs. O tom deve ser emp\xE1tico, amig\xE1vel, persuasivo e profissional.",
694
+ onboardingPromptTitle: "Prompt de inicializa\xE7\xE3o para o seu Agente de IA (Copie e cole no chat):",
695
+ onboardingPrompt: "Ol\xE1. Acabei de configurar o GrooveSkills Harness neste projeto. Por favor leia `.grooveskills/memory/tasks/00-onboarding.md`, `.grooveskills/memory/context.md` e as skills instaladas em `.agents/skills/`. Fa\xE7a-me perguntas interativas passo a passo para mapear lacunas de arquitetura, alinhar prioridades e adaptar o projeto \xE0s minhas necessidades antes de prosseguir."
574
696
  },
575
697
  it: {
576
698
  code: "it",
577
699
  name: "Italian",
578
700
  nativeName: "Italiano",
579
- instruction: "Tutti i messaggi, le convalide, gli errori, i pulsanti/CTA e le comunicazioni rivolte all'utente devono essere scritti rigorosamente in Italiano. Il tono deve essere empatico, cordiale, persuasivo e professionale."
701
+ instruction: "Tutti i messaggi, le convalide, gli errori, i pulsanti/CTA e le comunicazioni rivolte all'utente devono essere scritti rigorosamente in Italiano. Il tono deve essere empatico, cordiale, persuasivo e professionale.",
702
+ onboardingPromptTitle: "Prompt di inizializzazione per il tuo Agente AI (Copia e incolla nella chat):",
703
+ onboardingPrompt: "Ciao. Ho appena configurato GrooveSkills Harness in questo progetto. Ti prego di leggere `.grooveskills/memory/tasks/00-onboarding.md`, `.grooveskills/memory/context.md` e le skills installate in `.agents/skills/`. Fammi domande interattive passo dopo passo per identificare lacune architetturali, allineare le priorit\xE0 e adattare il progetto alle mie esigenze prima di continuare."
580
704
  },
581
705
  fr: {
582
706
  code: "fr",
583
707
  name: "French",
584
708
  nativeName: "Fran\xE7ais",
585
- instruction: "Tous les messages, validations, erreurs, boutons d'appel \xE0 l'action (CTA) et notifications destin\xE9s \xE0 l'utilisateur doivent \xEAtre r\xE9dig\xE9s strictement en Fran\xE7ais. Le ton doit \xEAtre empathique, courtois, persuasif et professionnel."
709
+ instruction: "Tous les messages, validations, erreurs, boutons d'appel \xE0 l'action (CTA) et notifications destin\xE9s \xE0 l'utilisateur doivent \xEAtre r\xE9dig\xE9s strictement en Fran\xE7ais. Le ton doit \xEAtre empathique, courtois, persuasif et professionnel.",
710
+ onboardingPromptTitle: "Prompt d'initialisation pour votre Agent IA (Copiez et collez dans le chat) :",
711
+ onboardingPrompt: "Bonjour. Je viens de configurer GrooveSkills Harness sur ce projet. Veuillez consulter `.grooveskills/memory/tasks/00-onboarding.md`, `.grooveskills/memory/context.md` et les skills install\xE9es dans `.agents/skills/`. Posez-moi des questions interactives \xE9tape par \xE9tape pour identifier les lacunes d'architecture, pr\xE9ciser les priorit\xE9s et adapter le projet \xE0 mes besoins avant de continuer."
586
712
  }
587
713
  };
588
714
 
@@ -664,20 +790,23 @@ createdAt: ${now}
664
790
  # 00 \u2014 Initial Project Onboarding & Architecture Exploration: ${projectName}
665
791
 
666
792
  ## Outcome Hypothesis
667
- By scanning dependencies, entry points, configuration manifests, and module layout, the AI agent will gain a verified architectural understanding of \`${projectName}\`, proven by filling out \`.grooveskills/memory/context.md\` and validating the essential build/typecheck commands.
793
+ By scanning dependencies, entry points, configuration manifests, and module layout, the AI agent will gain a verified architectural understanding of \`${projectName}\`, proven by filling out \`.grooveskills/memory/context.md\`, formulating interactive alignment questions with the user, and validating the essential build/typecheck commands.
668
794
 
669
795
  ## Scope
670
- - In scope: Inspect project manifests, entry points, directory conventions, and script execution.
671
- - Out of scope: Refactoring business logic or modifying functional code.
796
+ - In scope: Inspect project manifests, entry points, directory conventions, ask interactive alignment questions, and verify script execution.
797
+ - Out of scope: Refactoring business logic or modifying functional code before alignment.
672
798
 
673
799
  ## Checklist
674
800
  - [ ] Verify package manager and essential scripts (\`build\`, \`test\`, \`typecheck\`).
675
801
  - [ ] Map high-level module architecture and data flow in \`.grooveskills/memory/context.md\`.
802
+ - [ ] Ask the user step-by-step interactive questions to clarify goals, domain scope, and adapt project conventions.
803
+ - [ ] Document agreed technical decisions in \`.grooveskills/memory/decisions.md\`.
676
804
  - [ ] Verify test runner or static checks pass cleanly.
677
805
  - [ ] Switch status to \`completed\` once project context is fully verified.
678
806
 
679
807
  ## Evidence
680
808
  - Observed command runs and outputs during the initial onboarding pass.
809
+ - User answers to alignment questions captured in memory.
681
810
 
682
811
  ## Next
683
812
  - Once onboarding is marked completed, proceed with domain tasks following the 3-lane ODD protocol.
@@ -736,11 +865,11 @@ This project uses the **GrooveSkills Harness** located in \`.grooveskills/\`.
736
865
  }
737
866
 
738
867
  // src/domains/harness/scan-project-onboarding.ts
739
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
868
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
740
869
  import { basename, join as join8 } from "path";
741
870
  function detectProjectName(targetPath) {
742
871
  const packageJsonPath = join8(targetPath, "package.json");
743
- if (existsSync6(packageJsonPath)) {
872
+ if (existsSync7(packageJsonPath)) {
744
873
  try {
745
874
  const raw = readFileSync4(packageJsonPath, "utf-8");
746
875
  const parsed = JSON.parse(raw);
@@ -751,7 +880,7 @@ function detectProjectName(targetPath) {
751
880
  }
752
881
  }
753
882
  const cargoPath = join8(targetPath, "Cargo.toml");
754
- if (existsSync6(cargoPath)) {
883
+ if (existsSync7(cargoPath)) {
755
884
  try {
756
885
  const cargoRaw = readFileSync4(cargoPath, "utf-8");
757
886
  const nameMatch = cargoRaw.match(/name\s*=\s*"([^"]+)"/);
@@ -762,7 +891,7 @@ function detectProjectName(targetPath) {
762
891
  }
763
892
  }
764
893
  const pyprojectPath = join8(targetPath, "pyproject.toml");
765
- if (existsSync6(pyprojectPath)) {
894
+ if (existsSync7(pyprojectPath)) {
766
895
  try {
767
896
  const pyRaw = readFileSync4(pyprojectPath, "utf-8");
768
897
  const pyMatch = pyRaw.match(/name\s*=\s*"([^"]+)"/);
@@ -775,32 +904,32 @@ function detectProjectName(targetPath) {
775
904
  return basename(targetPath) || "project";
776
905
  }
777
906
  function detectPackageManager(targetPath) {
778
- if (existsSync6(join8(targetPath, "pnpm-lock.yaml"))) {
907
+ if (existsSync7(join8(targetPath, "pnpm-lock.yaml"))) {
779
908
  return "pnpm";
780
909
  }
781
- if (existsSync6(join8(targetPath, "bun.lockb")) || existsSync6(join8(targetPath, "bun.lock"))) {
910
+ if (existsSync7(join8(targetPath, "bun.lockb")) || existsSync7(join8(targetPath, "bun.lock"))) {
782
911
  return "bun";
783
912
  }
784
- if (existsSync6(join8(targetPath, "yarn.lock"))) {
913
+ if (existsSync7(join8(targetPath, "yarn.lock"))) {
785
914
  return "yarn";
786
915
  }
787
- if (existsSync6(join8(targetPath, "package-lock.json"))) {
916
+ if (existsSync7(join8(targetPath, "package-lock.json"))) {
788
917
  return "npm";
789
918
  }
790
- if (existsSync6(join8(targetPath, "Cargo.lock")) || existsSync6(join8(targetPath, "Cargo.toml"))) {
919
+ if (existsSync7(join8(targetPath, "Cargo.lock")) || existsSync7(join8(targetPath, "Cargo.toml"))) {
791
920
  return "cargo";
792
921
  }
793
- if (existsSync6(join8(targetPath, "uv.lock"))) {
922
+ if (existsSync7(join8(targetPath, "uv.lock"))) {
794
923
  return "uv";
795
924
  }
796
- if (existsSync6(join8(targetPath, "poetry.lock"))) {
925
+ if (existsSync7(join8(targetPath, "poetry.lock"))) {
797
926
  return "poetry";
798
927
  }
799
928
  return "npm";
800
929
  }
801
930
  function detectCommands(targetPath, pkgManager) {
802
931
  const packageJsonPath = join8(targetPath, "package.json");
803
- if (!existsSync6(packageJsonPath)) {
932
+ if (!existsSync7(packageJsonPath)) {
804
933
  return {};
805
934
  }
806
935
  try {
@@ -835,7 +964,7 @@ function detectCommands(targetPath, pkgManager) {
835
964
  }
836
965
  function detectPathAliases(targetPath) {
837
966
  const tsconfigPath = join8(targetPath, "tsconfig.json");
838
- if (!existsSync6(tsconfigPath)) {
967
+ if (!existsSync7(tsconfigPath)) {
839
968
  return {};
840
969
  }
841
970
  try {
@@ -855,7 +984,7 @@ function detectPathAliases(targetPath) {
855
984
  }
856
985
  function detectSourceDirectories(targetPath) {
857
986
  const srcPath = join8(targetPath, "src");
858
- const scanTarget = existsSync6(srcPath) ? srcPath : targetPath;
987
+ const scanTarget = existsSync7(srcPath) ? srcPath : targetPath;
859
988
  try {
860
989
  const entries = readdirSync2(scanTarget, { withFileTypes: true });
861
990
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !name.startsWith(".") && name !== "node_modules" && name !== "dist");
@@ -879,36 +1008,39 @@ function scanProjectOnboarding(targetPath) {
879
1008
  }
880
1009
 
881
1010
  // src/domains/harness/upsert-agent-instructions.ts
882
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1011
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
883
1012
  import { join as join9 } from "path";
884
1013
  var START_MARKER = "<!-- grooveskills:harness:start -->";
885
1014
  var END_MARKER = "<!-- grooveskills:harness:end -->";
886
- function applyHarnessBlock(filePath, language = "es") {
1015
+ function applyHarnessBlock(filePath, language = "es", transaction) {
1016
+ if (transaction) {
1017
+ transaction.recordBeforeWrite(filePath);
1018
+ }
887
1019
  const block = buildAgentHarnessBlock(language);
888
- if (!existsSync7(filePath)) {
889
- writeFileSync2(filePath, `${block}
890
- `, "utf-8");
1020
+ if (!existsSync8(filePath)) {
1021
+ safeWriteFileSync(filePath, `${block}
1022
+ `);
891
1023
  return;
892
1024
  }
893
1025
  const existingContent = readFileSync5(filePath, "utf-8");
894
1026
  if (existingContent.includes(START_MARKER) && existingContent.includes(END_MARKER)) {
895
1027
  const regex = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}`, "g");
896
1028
  const updated = existingContent.replace(regex, block);
897
- writeFileSync2(filePath, updated, "utf-8");
1029
+ safeWriteFileSync(filePath, updated);
898
1030
  return;
899
1031
  }
900
1032
  const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
901
- writeFileSync2(filePath, `${existingContent}${separator}${block}
902
- `, "utf-8");
1033
+ safeWriteFileSync(filePath, `${existingContent}${separator}${block}
1034
+ `);
903
1035
  }
904
- function upsertAgentInstructions(targetPath, selectedAgents, language = "es") {
1036
+ function upsertAgentInstructions(targetPath, selectedAgents, language = "es", transaction) {
905
1037
  const updatedFiles = [];
906
1038
  const agentsMdPath = join9(targetPath, "AGENTS.md");
907
- applyHarnessBlock(agentsMdPath, language);
1039
+ applyHarnessBlock(agentsMdPath, language, transaction);
908
1040
  updatedFiles.push(agentsMdPath);
909
1041
  if (selectedAgents.includes("claude")) {
910
1042
  const claudeMdPath = join9(targetPath, "CLAUDE.md");
911
- applyHarnessBlock(claudeMdPath, language);
1043
+ applyHarnessBlock(claudeMdPath, language, transaction);
912
1044
  updatedFiles.push(claudeMdPath);
913
1045
  }
914
1046
  return updatedFiles;
@@ -920,57 +1052,77 @@ function installHarness(options) {
920
1052
  const tasksDir = getProjectTasksDir(options.targetPath);
921
1053
  const language = options.language ?? "es";
922
1054
  const forceOnboarding = Boolean(options.forceOnboarding);
923
- if (!existsSync8(tasksDir)) {
1055
+ const tx = options.transaction;
1056
+ if (tx) {
1057
+ tx.recordCreatedDir(memoryDir);
1058
+ tx.recordCreatedDir(tasksDir);
1059
+ }
1060
+ if (!existsSync9(tasksDir)) {
924
1061
  mkdirSync3(tasksDir, { recursive: true });
925
1062
  }
926
1063
  const createdFiles = [];
927
1064
  const contextPath = join10(memoryDir, "context.md");
928
- const shouldWriteContext = !existsSync8(contextPath) || forceOnboarding;
1065
+ const shouldWriteContext = !existsSync9(contextPath) || forceOnboarding;
929
1066
  if (shouldWriteContext) {
1067
+ if (tx) {
1068
+ tx.recordBeforeWrite(contextPath);
1069
+ }
930
1070
  const profile = scanProjectOnboarding(options.targetPath);
931
1071
  const contextContent = buildContextTemplate(
932
1072
  options.detectedDependencies,
933
1073
  language,
934
1074
  profile
935
1075
  );
936
- writeFileSync3(contextPath, contextContent, "utf-8");
1076
+ safeWriteFileSync(contextPath, contextContent);
937
1077
  createdFiles.push(contextPath);
938
1078
  const onboardingTaskPath = join10(tasksDir, "00-onboarding.md");
939
- writeFileSync3(
1079
+ if (tx) {
1080
+ tx.recordBeforeWrite(onboardingTaskPath);
1081
+ }
1082
+ safeWriteFileSync(
940
1083
  onboardingTaskPath,
941
- buildOnboardingTaskTemplate(profile.projectName),
942
- "utf-8"
1084
+ buildOnboardingTaskTemplate(profile.projectName)
943
1085
  );
944
1086
  createdFiles.push(onboardingTaskPath);
945
1087
  }
946
1088
  const decisionsPath = join10(memoryDir, "decisions.md");
947
- if (!existsSync8(decisionsPath)) {
948
- writeFileSync3(decisionsPath, buildDecisionsTemplate(language), "utf-8");
1089
+ if (!existsSync9(decisionsPath)) {
1090
+ if (tx) {
1091
+ tx.recordBeforeWrite(decisionsPath);
1092
+ }
1093
+ safeWriteFileSync(decisionsPath, buildDecisionsTemplate(language));
949
1094
  createdFiles.push(decisionsPath);
950
1095
  }
951
1096
  const templateTaskPath = join10(tasksDir, "_template.md");
952
- if (!existsSync8(templateTaskPath)) {
953
- writeFileSync3(templateTaskPath, buildTaskTemplate(), "utf-8");
1097
+ if (!existsSync9(templateTaskPath)) {
1098
+ if (tx) {
1099
+ tx.recordBeforeWrite(templateTaskPath);
1100
+ }
1101
+ safeWriteFileSync(templateTaskPath, buildTaskTemplate());
954
1102
  createdFiles.push(templateTaskPath);
955
1103
  }
956
1104
  const instructions = upsertAgentInstructions(
957
1105
  options.targetPath,
958
1106
  options.selectedAgents,
959
- language
1107
+ language,
1108
+ tx
960
1109
  );
961
1110
  createdFiles.push(...instructions);
962
- return createdFiles;
1111
+ return {
1112
+ createdFiles,
1113
+ onboardingInitialized: shouldWriteContext
1114
+ };
963
1115
  }
964
1116
 
965
1117
  // src/domains/harness/write-project-config.ts
966
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1118
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
967
1119
  function readExistingConfig(configPath) {
968
1120
  const fallback = {
969
1121
  skills: [],
970
1122
  agents: [],
971
1123
  harness: false
972
1124
  };
973
- if (!existsSync9(configPath)) {
1125
+ if (!existsSync10(configPath)) {
974
1126
  return fallback;
975
1127
  }
976
1128
  try {
@@ -986,8 +1138,11 @@ function readExistingConfig(configPath) {
986
1138
  return fallback;
987
1139
  }
988
1140
  }
989
- function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled, language) {
1141
+ function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled, language, transaction) {
990
1142
  const configPath = getProjectConfigPath(targetPath);
1143
+ if (transaction) {
1144
+ transaction.recordBeforeWrite(configPath);
1145
+ }
991
1146
  const existing = readExistingConfig(configPath);
992
1147
  const mergedSkills = Array.from(/* @__PURE__ */ new Set([...existing.skills, ...newSkills]));
993
1148
  const mergedAgents = Array.from(/* @__PURE__ */ new Set([...existing.agents, ...newAgents]));
@@ -1001,11 +1156,84 @@ function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled, la
1001
1156
  skills: mergedSkills,
1002
1157
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1003
1158
  };
1004
- writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
1005
- `, "utf-8");
1159
+ safeWriteFileSync(configPath, `${JSON.stringify(config, null, 2)}
1160
+ `);
1006
1161
  return configPath;
1007
1162
  }
1008
1163
 
1164
+ // src/domains/fs/install-transaction.ts
1165
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
1166
+ var InstallTransaction = class {
1167
+ originalFiles = /* @__PURE__ */ new Map();
1168
+ createdDirs = [];
1169
+ recordBeforeWrite(filePath) {
1170
+ if (this.originalFiles.has(filePath)) {
1171
+ return;
1172
+ }
1173
+ if (existsSync11(filePath)) {
1174
+ try {
1175
+ this.originalFiles.set(filePath, readFileSync7(filePath, "utf-8"));
1176
+ } catch {
1177
+ this.originalFiles.set(filePath, null);
1178
+ }
1179
+ } else {
1180
+ this.originalFiles.set(filePath, null);
1181
+ }
1182
+ }
1183
+ recordCreatedDir(dirPath) {
1184
+ if (!existsSync11(dirPath) && !this.createdDirs.includes(dirPath)) {
1185
+ this.createdDirs.push(dirPath);
1186
+ }
1187
+ }
1188
+ rollback() {
1189
+ const restoredFiles = [];
1190
+ const removedFiles = [];
1191
+ const removedDirs = [];
1192
+ for (const [filePath, content] of this.originalFiles.entries()) {
1193
+ try {
1194
+ if (content === null) {
1195
+ if (existsSync11(filePath)) {
1196
+ rmSync3(filePath, { force: true });
1197
+ removedFiles.push(filePath);
1198
+ }
1199
+ } else {
1200
+ safeWriteFileSync(filePath, content);
1201
+ restoredFiles.push(filePath);
1202
+ }
1203
+ } catch {
1204
+ }
1205
+ }
1206
+ const sortedDirs = [...this.createdDirs].sort((a, b) => b.length - a.length);
1207
+ for (const dir of sortedDirs) {
1208
+ try {
1209
+ if (existsSync11(dir) && readdirSync3(dir).length === 0) {
1210
+ rmSync3(dir, { recursive: true, force: true });
1211
+ removedDirs.push(dir);
1212
+ }
1213
+ } catch {
1214
+ }
1215
+ }
1216
+ return { restoredFiles, removedFiles, removedDirs };
1217
+ }
1218
+ };
1219
+
1220
+ // src/errors/install-transaction-error.ts
1221
+ var InstallTransactionError = class extends Error {
1222
+ stage;
1223
+ causeError;
1224
+ rollbackReport;
1225
+ suggestedFix;
1226
+ constructor(options) {
1227
+ const rawMessage = options.cause instanceof Error ? options.cause.message : String(options.cause);
1228
+ super(`Error durante la etapa '${options.stage}': ${rawMessage}`);
1229
+ this.name = "InstallTransactionError";
1230
+ this.stage = options.stage;
1231
+ this.causeError = options.cause;
1232
+ this.rollbackReport = options.rollbackReport;
1233
+ this.suggestedFix = options.suggestedFix;
1234
+ }
1235
+ };
1236
+
1009
1237
  // src/domains/installer/install-orchestrator.ts
1010
1238
  var ADAPTER_REGISTRY = {
1011
1239
  antigravity: new AntigravityAdapter(),
@@ -1013,7 +1241,20 @@ var ADAPTER_REGISTRY = {
1013
1241
  cursor: new CursorAdapter(),
1014
1242
  gemini: new GeminiAdapter()
1015
1243
  };
1244
+ function formatSuggestedFix(cause) {
1245
+ if (cause instanceof FilePermissionError) {
1246
+ return cause.suggestedFixCommand;
1247
+ }
1248
+ const err = cause;
1249
+ if (err?.code === "EACCES" || err?.code === "EPERM") {
1250
+ if (err.path) {
1251
+ return `chmod u+w "${err.path}"`;
1252
+ }
1253
+ }
1254
+ return void 0;
1255
+ }
1016
1256
  async function installSkills(options) {
1257
+ const transaction = new InstallTransaction();
1017
1258
  const skillsToInstall = [...options.selectedSkillIds];
1018
1259
  if (!skillsToInstall.includes("groove-harness")) {
1019
1260
  skillsToInstall.push("groove-harness");
@@ -1021,47 +1262,90 @@ async function installSkills(options) {
1021
1262
  if (!skillsToInstall.includes("odd-ai")) {
1022
1263
  skillsToInstall.push("odd-ai");
1023
1264
  }
1024
- syncProjectStore(options.targetPath, skillsToInstall, options.selectedAgents);
1025
- const projectSkillsDir = getProjectSkillsDir(options.targetPath);
1026
- const createdFiles = [];
1027
- for (const skillId of skillsToInstall) {
1028
- const skill = SKILL_CATALOG.find((item) => item.id === skillId);
1029
- if (!skill) {
1030
- continue;
1031
- }
1032
- const skillSourceDir = join11(projectSkillsDir, skill.directoryName);
1033
- for (const agentId of options.selectedAgents) {
1034
- const adapter = ADAPTER_REGISTRY[agentId];
1035
- if (!adapter) {
1265
+ let currentStage = "Sincronizaci\xF3n del almac\xE9n de skills (.grooveskills/skills)";
1266
+ try {
1267
+ syncProjectStore(options.targetPath, skillsToInstall, options.selectedAgents, transaction);
1268
+ const projectSkillsDir = getProjectSkillsDir(options.targetPath);
1269
+ const createdFiles = [];
1270
+ currentStage = "Configuraci\xF3n de referencias de agentes (.agents, .claude, etc.)";
1271
+ for (const skillId of skillsToInstall) {
1272
+ const skill = SKILL_CATALOG.find((item) => item.id === skillId);
1273
+ if (!skill) {
1036
1274
  continue;
1037
1275
  }
1038
- const files = await adapter.installSkill(options.targetPath, skill, skillSourceDir);
1039
- createdFiles.push(...files);
1276
+ const skillSourceDir = join11(projectSkillsDir, skill.directoryName);
1277
+ for (const agentId of options.selectedAgents) {
1278
+ const adapter = ADAPTER_REGISTRY[agentId];
1279
+ if (!adapter) {
1280
+ continue;
1281
+ }
1282
+ const files = await adapter.installSkill(
1283
+ options.targetPath,
1284
+ skill,
1285
+ skillSourceDir,
1286
+ transaction
1287
+ );
1288
+ createdFiles.push(...files);
1289
+ }
1040
1290
  }
1291
+ currentStage = "Instalaci\xF3n del Groove Harness (contexto, decisiones, tareas e instrucciones)";
1292
+ const harnessResult = installHarness({
1293
+ targetPath: options.targetPath,
1294
+ selectedAgents: options.selectedAgents,
1295
+ selectedSkillIds: skillsToInstall,
1296
+ detectedDependencies: options.detectedDependencies || [],
1297
+ language: options.language,
1298
+ forceOnboarding: options.forceOnboarding,
1299
+ transaction
1300
+ });
1301
+ createdFiles.push(...harnessResult.createdFiles);
1302
+ currentStage = "Persistencia de la configuraci\xF3n del proyecto (.grooveskills/config.json)";
1303
+ const configPath = writeProjectConfig(
1304
+ options.targetPath,
1305
+ skillsToInstall,
1306
+ options.selectedAgents,
1307
+ true,
1308
+ options.language,
1309
+ transaction
1310
+ );
1311
+ createdFiles.push(configPath);
1312
+ return {
1313
+ installedSkills: skillsToInstall,
1314
+ configuredAgents: options.selectedAgents,
1315
+ createdFiles,
1316
+ harnessInitialized: harnessResult.onboardingInitialized,
1317
+ language: options.language
1318
+ };
1319
+ } catch (cause) {
1320
+ const rollbackReport = transaction.rollback();
1321
+ const suggestedFix = formatSuggestedFix(cause);
1322
+ throw new InstallTransactionError({
1323
+ stage: currentStage,
1324
+ cause,
1325
+ rollbackReport,
1326
+ suggestedFix
1327
+ });
1041
1328
  }
1042
- const harnessFiles = installHarness({
1043
- targetPath: options.targetPath,
1044
- selectedAgents: options.selectedAgents,
1045
- selectedSkillIds: skillsToInstall,
1046
- detectedDependencies: options.detectedDependencies || [],
1047
- language: options.language,
1048
- forceOnboarding: options.forceOnboarding
1049
- });
1050
- createdFiles.push(...harnessFiles);
1051
- const configPath = writeProjectConfig(
1052
- options.targetPath,
1053
- skillsToInstall,
1054
- options.selectedAgents,
1055
- true,
1056
- options.language
1057
- );
1058
- createdFiles.push(configPath);
1329
+ }
1330
+
1331
+ // src/domains/ui/render-onboarding-prompt.ts
1332
+ import pc2 from "picocolors";
1333
+ function formatOnboardingPrompt(language = "es") {
1334
+ const langConfig = SUPPORTED_LANGUAGES[language] ?? SUPPORTED_LANGUAGES.es;
1335
+ const title = langConfig.onboardingPromptTitle;
1336
+ const prompt = langConfig.onboardingPrompt;
1337
+ const separator = pc2.cyan("\u2500".repeat(60));
1338
+ const formattedOutput = [
1339
+ separator,
1340
+ pc2.bold(pc2.yellow(title)),
1341
+ "",
1342
+ pc2.green(prompt),
1343
+ separator
1344
+ ].join("\n");
1059
1345
  return {
1060
- installedSkills: skillsToInstall,
1061
- configuredAgents: options.selectedAgents,
1062
- createdFiles,
1063
- harnessInitialized: true,
1064
- language: options.language
1346
+ title,
1347
+ prompt,
1348
+ formattedOutput
1065
1349
  };
1066
1350
  }
1067
1351
 
@@ -1072,7 +1356,7 @@ async function resolveLanguageSelection(harnessExists, configuredLanguage, force
1072
1356
  }
1073
1357
  const langChoice = await promptLanguage(configuredLanguage ?? "es");
1074
1358
  if (p5.isCancel(langChoice)) {
1075
- p5.cancel("Installation cancelled. Have a wonderful day!");
1359
+ p5.cancel("Instalaci\xF3n cancelada. \xA1Que tengas un excelente d\xEDa!");
1076
1360
  process.exit(0);
1077
1361
  }
1078
1362
  return langChoice;
@@ -1087,31 +1371,53 @@ async function resolveOverwriteConfirmation(targetPath, selectedSkills, selected
1087
1371
  }
1088
1372
  const confirm2 = await promptConfirmOverwrite();
1089
1373
  if (p5.isCancel(confirm2) || !confirm2) {
1090
- p5.cancel("Installation stopped to protect existing skills.");
1374
+ p5.cancel("Instalaci\xF3n detenida para proteger las skills existentes.");
1091
1375
  process.exit(0);
1092
1376
  }
1093
1377
  return Boolean(confirm2);
1094
1378
  }
1379
+ function renderInstallFailure(error) {
1380
+ const { stage, causeError, rollbackReport, suggestedFix } = error;
1381
+ const rawMsg = causeError instanceof Error ? causeError.message : String(causeError);
1382
+ const lines = [
1383
+ `${pc3.bold("Fase afectada:")} ${pc3.yellow(stage)}`,
1384
+ `${pc3.bold("Causa:")} ${pc3.red(rawMsg)}`,
1385
+ "",
1386
+ pc3.bold("Reporte de Rollback autom\xE1tico:"),
1387
+ ` \u2022 Archivos restaurados a su estado previo: ${pc3.cyan(String(rollbackReport.restoredFiles.length))}`,
1388
+ ` \u2022 Archivos temporales eliminados: ${pc3.cyan(String(rollbackReport.removedFiles.length))}`,
1389
+ ` \u2022 Directorios vac\xEDos eliminados: ${pc3.cyan(String(rollbackReport.removedDirs.length))}`
1390
+ ];
1391
+ if (suggestedFix) {
1392
+ lines.push(
1393
+ "",
1394
+ pc3.bold("Soluci\xF3n recomendada:"),
1395
+ ` Ejecuta en tu terminal: ${pc3.green(suggestedFix)}`
1396
+ );
1397
+ }
1398
+ p5.note(lines.join("\n"), "Detalles de la interrupci\xF3n y reversi\xF3n");
1399
+ p5.outro(pc3.yellow("Tu proyecto no sufri\xF3 alteraciones y se preserv\xF3 intacto. Corrige la causa y vuelve a intentarlo cuando gustes."));
1400
+ }
1095
1401
  async function runInteractiveUi(context, forceOnboarding = false) {
1096
1402
  console.log(renderBanner());
1097
- p5.intro(pc2.magenta("Welcome to GrooveSkills! Let's tailor your AI agents with clean architectural standards."));
1403
+ p5.intro(pc3.magenta("\xA1Bienvenido a GrooveSkills! Configuremos tus agentes de IA con est\xE1ndares limpios de arquitectura."));
1098
1404
  if (context.detectedDependencies.length > 0) {
1099
- const harnessInfo = context.harnessExists && !forceOnboarding ? pc2.green(`Active (.grooveskills/memory, lang: ${context.configuredLanguage ?? "es"})`) : pc2.cyan("Will be initialized");
1405
+ const harnessInfo = context.harnessExists && !forceOnboarding ? pc3.green(`Activo (.grooveskills/memory, idioma: ${context.configuredLanguage ?? "es"})`) : pc3.cyan("Ser\xE1 inicializado");
1100
1406
  p5.note(
1101
- `Detected dependencies: ${context.detectedDependencies.map((d) => pc2.bold(d)).join(", ")}
1102
- Active AI folders: ${context.detectedAgents.map((a) => pc2.green(a)).join(", ") || "None (will be created)"}
1407
+ `Dependencias detectadas: ${context.detectedDependencies.map((d) => pc3.bold(d)).join(", ")}
1408
+ Carpetas de IA activas: ${context.detectedAgents.map((a) => pc3.green(a)).join(", ") || "Ninguna (se crear\xE1n)"}
1103
1409
  GrooveSkills Harness: ${harnessInfo}`,
1104
- "Project Environment"
1410
+ "Entorno del Proyecto"
1105
1411
  );
1106
1412
  }
1107
1413
  const selectedSkills = await promptSkills(context.suggestedSkillIds);
1108
1414
  if (p5.isCancel(selectedSkills)) {
1109
- p5.cancel("Installation cancelled. Have a wonderful day!");
1415
+ p5.cancel("Instalaci\xF3n cancelada. \xA1Que tengas un excelente d\xEDa!");
1110
1416
  process.exit(0);
1111
1417
  }
1112
1418
  const selectedAgents = await promptAgents(context.detectedAgents);
1113
1419
  if (p5.isCancel(selectedAgents)) {
1114
- p5.cancel("Installation cancelled. Have a wonderful day!");
1420
+ p5.cancel("Instalaci\xF3n cancelada. \xA1Que tengas un excelente d\xEDa!");
1115
1421
  process.exit(0);
1116
1422
  }
1117
1423
  const selectedLanguage = await resolveLanguageSelection(
@@ -1125,26 +1431,42 @@ GrooveSkills Harness: ${harnessInfo}`,
1125
1431
  selectedAgents
1126
1432
  );
1127
1433
  const s = p5.spinner();
1128
- s.start("Installing and adapting skills across your configured AI agents...");
1129
- const result = await installSkills({
1130
- targetPath: context.targetPath,
1131
- selectedSkillIds: selectedSkills,
1132
- selectedAgents,
1133
- forceOverwrite,
1134
- enableHarness: true,
1135
- detectedDependencies: context.detectedDependencies,
1136
- language: selectedLanguage,
1137
- forceOnboarding
1138
- });
1139
- s.stop(pc2.green("All skills and harness have been successfully configured!"));
1140
- const harnessStatus = result.harnessInitialized ? pc2.green(`Initialized in .grooveskills/ (Language: ${selectedLanguage})`) : pc2.cyan(`Preserved in .grooveskills/ (Language: ${selectedLanguage})`);
1141
- p5.note(
1142
- `Configured skills: ${result.installedSkills.map((s2) => pc2.bold(s2)).join(", ")}
1143
- Target agents: ${result.configuredAgents.map((a) => pc2.cyan(a)).join(", ")}
1434
+ s.start("Instalando y adaptando skills para tus agentes de IA configurados...");
1435
+ try {
1436
+ const result = await installSkills({
1437
+ targetPath: context.targetPath,
1438
+ selectedSkillIds: selectedSkills,
1439
+ selectedAgents,
1440
+ forceOverwrite,
1441
+ enableHarness: true,
1442
+ detectedDependencies: context.detectedDependencies,
1443
+ language: selectedLanguage,
1444
+ forceOnboarding
1445
+ });
1446
+ s.stop(pc3.green("\xA1Todas las skills y el harness fueron configurados exitosamente!"));
1447
+ const harnessStatus = result.harnessInitialized ? pc3.green(`Inicializado en .grooveskills/ (Idioma: ${selectedLanguage})`) : pc3.cyan(`Preservado en .grooveskills/ (Idioma: ${selectedLanguage})`);
1448
+ p5.note(
1449
+ `Skills configuradas: ${result.installedSkills.map((sk) => pc3.bold(sk)).join(", ")}
1450
+ Agentes objetivo: ${result.configuredAgents.map((a) => pc3.cyan(a)).join(", ")}
1144
1451
  GrooveSkills Harness: ${harnessStatus}`,
1145
- "Installation Summary"
1146
- );
1147
- p5.outro(pc2.green("Your AI programming pair is now empowered with your team's architectural habits! \u{1F680}"));
1452
+ "Resumen de Instalaci\xF3n"
1453
+ );
1454
+ if (result.harnessInitialized) {
1455
+ const promptRender = formatOnboardingPrompt(selectedLanguage);
1456
+ p5.note(
1457
+ pc3.green(promptRender.prompt),
1458
+ promptRender.title
1459
+ );
1460
+ }
1461
+ p5.outro(pc3.green("\xA1Tus agentes de IA ahora est\xE1n potenciados con los h\xE1bitos arquitect\xF3nicos de tu equipo! \u{1F680}"));
1462
+ } catch (error) {
1463
+ s.stop(pc3.yellow("La instalaci\xF3n no pudo completarse."));
1464
+ if (error instanceof InstallTransactionError) {
1465
+ renderInstallFailure(error);
1466
+ return;
1467
+ }
1468
+ throw error;
1469
+ }
1148
1470
  }
1149
1471
 
1150
1472
  // src/main.ts
@@ -1157,7 +1479,7 @@ async function runGrooveSkills(options = {}) {
1157
1479
  const isDirectInit = Boolean(options.forceOnboarding) && hasAgents;
1158
1480
  if (hasSkills && hasAgents || isDirectHarness || isDirectInit) {
1159
1481
  const language = options.language ?? context.configuredLanguage ?? "es";
1160
- await installSkills({
1482
+ const result = await installSkills({
1161
1483
  targetPath,
1162
1484
  selectedSkillIds: options.skills || [],
1163
1485
  selectedAgents: options.agents || [],
@@ -1167,12 +1489,19 @@ async function runGrooveSkills(options = {}) {
1167
1489
  language,
1168
1490
  forceOnboarding: options.forceOnboarding
1169
1491
  });
1492
+ if (result.harnessInitialized) {
1493
+ const promptRender = formatOnboardingPrompt(language);
1494
+ console.log(`
1495
+ ${promptRender.formattedOutput}
1496
+ `);
1497
+ }
1170
1498
  return;
1171
1499
  }
1172
1500
  await runInteractiveUi(context, Boolean(options.forceOnboarding));
1173
1501
  }
1174
1502
 
1175
1503
  export {
1504
+ InstallTransactionError,
1176
1505
  runGrooveSkills
1177
1506
  };
1178
- //# sourceMappingURL=chunk-PLMYUKGW.js.map
1507
+ //# sourceMappingURL=chunk-E63GTCOE.js.map