@3lineas/grooveskills 1.6.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.
@@ -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+)",
@@ -243,6 +250,7 @@ function detectProjectContext(targetPath) {
243
250
  "odd-ai",
244
251
  "best-practices",
245
252
  "ux-writing",
253
+ "docs-standards",
246
254
  "error-handling-logging",
247
255
  "clean-architecture-ddd",
248
256
  "external-service-adapters",
@@ -382,7 +390,7 @@ function verifySkillExists(targetPath, skillDirectoryName, agents) {
382
390
  import { join as join11 } from "path";
383
391
 
384
392
  // src/domains/adapters/skill-reference-builder.ts
385
- 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";
386
394
  import { join as join5 } from "path";
387
395
 
388
396
  // src/domains/project-store/project-paths.ts
@@ -403,9 +411,46 @@ function getProjectConfigPath(targetPath) {
403
411
  return join4(getProjectStoreDir(targetPath), "config.json");
404
412
  }
405
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
+
406
451
  // src/domains/adapters/skill-reference-builder.ts
407
452
  function removeExistingPathSafely(path) {
408
- const fileExists = existsSync3(path);
453
+ const fileExists = existsSync4(path);
409
454
  if (!fileExists) {
410
455
  try {
411
456
  const lstat = lstatSync2(path);
@@ -431,7 +476,7 @@ function removeExistingPathSafely(path) {
431
476
  }
432
477
  }
433
478
  function extractSkillDescription(canonicalSkillMdPath, fallback) {
434
- if (!existsSync3(canonicalSkillMdPath)) {
479
+ if (!existsSync4(canonicalSkillMdPath)) {
435
480
  return fallback;
436
481
  }
437
482
  try {
@@ -445,8 +490,14 @@ function extractSkillDescription(canonicalSkillMdPath, fallback) {
445
490
  }
446
491
  return fallback;
447
492
  }
448
- function writeAgentSkillReference(targetPath, agentRelativeSkillsDir, skill) {
493
+ function writeAgentSkillReference(targetPath, agentRelativeSkillsDir, skill, transaction) {
449
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
+ }
450
501
  removeExistingPathSafely(agentSkillFolder);
451
502
  mkdirSync(agentSkillFolder, { recursive: true });
452
503
  const canonicalSkillMd = join5(getProjectSkillsDir(targetPath), skill.directoryName, "SKILL.md");
@@ -465,16 +516,19 @@ Canonical skill specification stored in \`.grooveskills/\`.
465
516
 
466
517
  > **Instruction**: Read \`.grooveskills/skills/${skill.directoryName}/SKILL.md\` before executing tasks matching this skill.
467
518
  `;
468
- const targetFile = join5(agentSkillFolder, "SKILL.md");
469
- writeFileSync(targetFile, referenceContent, "utf-8");
519
+ safeWriteFileSync(targetFile, referenceContent);
470
520
  return targetFile;
471
521
  }
472
- function writeCursorRuleReference(targetPath, cursorRelativeRulesDir, skill) {
522
+ function writeCursorRuleReference(targetPath, cursorRelativeRulesDir, skill, transaction) {
473
523
  const rulesDir = join5(targetPath, cursorRelativeRulesDir);
474
- 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)) {
475
530
  mkdirSync(rulesDir, { recursive: true });
476
531
  }
477
- const targetFile = join5(rulesDir, `${skill.id}.mdc`);
478
532
  removeExistingPathSafely(targetFile);
479
533
  const canonicalSkillMd = join5(getProjectSkillsDir(targetPath), skill.directoryName, "SKILL.md");
480
534
  const description = extractSkillDescription(canonicalSkillMd, skill.description);
@@ -493,7 +547,7 @@ Canonical rule specification stored in \`.grooveskills/\`.
493
547
 
494
548
  > **Instruction**: Read \`.grooveskills/skills/${skill.directoryName}/SKILL.md\` before executing tasks matching this rule.
495
549
  `;
496
- writeFileSync(targetFile, referenceContent, "utf-8");
550
+ safeWriteFileSync(targetFile, referenceContent);
497
551
  return targetFile;
498
552
  }
499
553
 
@@ -502,8 +556,8 @@ var AntigravityAdapter = class {
502
556
  id = "antigravity";
503
557
  name = "Antigravity";
504
558
  targetRelativeDir = ".agents/skills";
505
- async installSkill(targetPath, skill, _skillSourceDir) {
506
- const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill);
559
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
560
+ const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill, transaction);
507
561
  return [file];
508
562
  }
509
563
  };
@@ -513,8 +567,8 @@ var ClaudeAdapter = class {
513
567
  id = "claude";
514
568
  name = "Claude Code";
515
569
  targetRelativeDir = ".claude/skills";
516
- async installSkill(targetPath, skill, _skillSourceDir) {
517
- const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill);
570
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
571
+ const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill, transaction);
518
572
  return [file];
519
573
  }
520
574
  };
@@ -524,8 +578,8 @@ var CursorAdapter = class {
524
578
  id = "cursor";
525
579
  name = "Cursor";
526
580
  targetRelativeDir = ".cursor/rules";
527
- async installSkill(targetPath, skill, _skillSourceDir) {
528
- const file = writeCursorRuleReference(targetPath, this.targetRelativeDir, skill);
581
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
582
+ const file = writeCursorRuleReference(targetPath, this.targetRelativeDir, skill, transaction);
529
583
  return [file];
530
584
  }
531
585
  };
@@ -535,35 +589,35 @@ var GeminiAdapter = class {
535
589
  id = "gemini";
536
590
  name = "Gemini Code Assist";
537
591
  targetRelativeDir = ".gemini/skills";
538
- async installSkill(targetPath, skill, _skillSourceDir) {
539
- const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill);
592
+ async installSkill(targetPath, skill, _skillSourceDir, transaction) {
593
+ const file = writeAgentSkillReference(targetPath, this.targetRelativeDir, skill, transaction);
540
594
  return [file];
541
595
  }
542
596
  };
543
597
 
544
598
  // src/domains/project-store/sync-project-store.ts
545
- 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";
546
600
  import { join as join7 } from "path";
547
601
 
548
602
  // src/domains/installer/resolve-skills-source.ts
549
603
  import { fileURLToPath } from "url";
550
604
  import { dirname, resolve, join as join6 } from "path";
551
- import { existsSync as existsSync4 } from "fs";
605
+ import { existsSync as existsSync5 } from "fs";
552
606
  function resolveSkillsSourceRoot() {
553
607
  const currentFilePath = fileURLToPath(import.meta.url);
554
608
  const currentDir = dirname(currentFilePath);
555
609
  const candidateFromDist = resolve(currentDir, "../skills");
556
- if (existsSync4(candidateFromDist)) {
610
+ if (existsSync5(candidateFromDist)) {
557
611
  return candidateFromDist;
558
612
  }
559
613
  const candidateFromSrc = resolve(currentDir, "../../../skills");
560
- if (existsSync4(candidateFromSrc)) {
614
+ if (existsSync5(candidateFromSrc)) {
561
615
  return candidateFromSrc;
562
616
  }
563
617
  let searchDir = currentDir;
564
618
  for (let i = 0; i < 5; i++) {
565
619
  const candidate = join6(searchDir, "skills");
566
- if (existsSync4(candidate)) {
620
+ if (existsSync5(candidate)) {
567
621
  return candidate;
568
622
  }
569
623
  const parent = dirname(searchDir);
@@ -578,14 +632,17 @@ function resolveSkillsSourceRoot() {
578
632
  }
579
633
 
580
634
  // src/domains/project-store/sync-project-store.ts
581
- function syncProjectStore(targetPath, selectedSkillIds, _selectedAgents) {
635
+ function syncProjectStore(targetPath, selectedSkillIds, _selectedAgents, transaction) {
582
636
  const bundledSkillsDir = resolveSkillsSourceRoot();
583
637
  const projectSkillsDir = getProjectSkillsDir(targetPath);
584
- if (!existsSync5(projectSkillsDir)) {
638
+ if (transaction) {
639
+ transaction.recordCreatedDir(projectSkillsDir);
640
+ }
641
+ if (!existsSync6(projectSkillsDir)) {
585
642
  mkdirSync2(projectSkillsDir, { recursive: true });
586
643
  }
587
644
  const legacyCursorRules = join7(targetPath, ".grooveskills", "cursor-rules");
588
- if (existsSync5(legacyCursorRules)) {
645
+ if (existsSync6(legacyCursorRules)) {
589
646
  try {
590
647
  rmSync2(legacyCursorRules, { recursive: true, force: true });
591
648
  } catch {
@@ -598,14 +655,17 @@ function syncProjectStore(targetPath, selectedSkillIds, _selectedAgents) {
598
655
  }
599
656
  const sourceSkillDir = join7(bundledSkillsDir, skill.directoryName);
600
657
  const destSkillDir = join7(projectSkillsDir, skill.directoryName);
601
- if (existsSync5(sourceSkillDir)) {
658
+ if (transaction) {
659
+ transaction.recordCreatedDir(destSkillDir);
660
+ }
661
+ if (existsSync6(sourceSkillDir)) {
602
662
  cpSync(sourceSkillDir, destSkillDir, { recursive: true });
603
663
  }
604
664
  }
605
665
  }
606
666
 
607
667
  // src/domains/harness/install-harness.ts
608
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
668
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
609
669
  import { join as join10 } from "path";
610
670
 
611
671
  // src/domains/harness/harness-languages.ts
@@ -805,11 +865,11 @@ This project uses the **GrooveSkills Harness** located in \`.grooveskills/\`.
805
865
  }
806
866
 
807
867
  // src/domains/harness/scan-project-onboarding.ts
808
- 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";
809
869
  import { basename, join as join8 } from "path";
810
870
  function detectProjectName(targetPath) {
811
871
  const packageJsonPath = join8(targetPath, "package.json");
812
- if (existsSync6(packageJsonPath)) {
872
+ if (existsSync7(packageJsonPath)) {
813
873
  try {
814
874
  const raw = readFileSync4(packageJsonPath, "utf-8");
815
875
  const parsed = JSON.parse(raw);
@@ -820,7 +880,7 @@ function detectProjectName(targetPath) {
820
880
  }
821
881
  }
822
882
  const cargoPath = join8(targetPath, "Cargo.toml");
823
- if (existsSync6(cargoPath)) {
883
+ if (existsSync7(cargoPath)) {
824
884
  try {
825
885
  const cargoRaw = readFileSync4(cargoPath, "utf-8");
826
886
  const nameMatch = cargoRaw.match(/name\s*=\s*"([^"]+)"/);
@@ -831,7 +891,7 @@ function detectProjectName(targetPath) {
831
891
  }
832
892
  }
833
893
  const pyprojectPath = join8(targetPath, "pyproject.toml");
834
- if (existsSync6(pyprojectPath)) {
894
+ if (existsSync7(pyprojectPath)) {
835
895
  try {
836
896
  const pyRaw = readFileSync4(pyprojectPath, "utf-8");
837
897
  const pyMatch = pyRaw.match(/name\s*=\s*"([^"]+)"/);
@@ -844,32 +904,32 @@ function detectProjectName(targetPath) {
844
904
  return basename(targetPath) || "project";
845
905
  }
846
906
  function detectPackageManager(targetPath) {
847
- if (existsSync6(join8(targetPath, "pnpm-lock.yaml"))) {
907
+ if (existsSync7(join8(targetPath, "pnpm-lock.yaml"))) {
848
908
  return "pnpm";
849
909
  }
850
- if (existsSync6(join8(targetPath, "bun.lockb")) || existsSync6(join8(targetPath, "bun.lock"))) {
910
+ if (existsSync7(join8(targetPath, "bun.lockb")) || existsSync7(join8(targetPath, "bun.lock"))) {
851
911
  return "bun";
852
912
  }
853
- if (existsSync6(join8(targetPath, "yarn.lock"))) {
913
+ if (existsSync7(join8(targetPath, "yarn.lock"))) {
854
914
  return "yarn";
855
915
  }
856
- if (existsSync6(join8(targetPath, "package-lock.json"))) {
916
+ if (existsSync7(join8(targetPath, "package-lock.json"))) {
857
917
  return "npm";
858
918
  }
859
- if (existsSync6(join8(targetPath, "Cargo.lock")) || existsSync6(join8(targetPath, "Cargo.toml"))) {
919
+ if (existsSync7(join8(targetPath, "Cargo.lock")) || existsSync7(join8(targetPath, "Cargo.toml"))) {
860
920
  return "cargo";
861
921
  }
862
- if (existsSync6(join8(targetPath, "uv.lock"))) {
922
+ if (existsSync7(join8(targetPath, "uv.lock"))) {
863
923
  return "uv";
864
924
  }
865
- if (existsSync6(join8(targetPath, "poetry.lock"))) {
925
+ if (existsSync7(join8(targetPath, "poetry.lock"))) {
866
926
  return "poetry";
867
927
  }
868
928
  return "npm";
869
929
  }
870
930
  function detectCommands(targetPath, pkgManager) {
871
931
  const packageJsonPath = join8(targetPath, "package.json");
872
- if (!existsSync6(packageJsonPath)) {
932
+ if (!existsSync7(packageJsonPath)) {
873
933
  return {};
874
934
  }
875
935
  try {
@@ -904,7 +964,7 @@ function detectCommands(targetPath, pkgManager) {
904
964
  }
905
965
  function detectPathAliases(targetPath) {
906
966
  const tsconfigPath = join8(targetPath, "tsconfig.json");
907
- if (!existsSync6(tsconfigPath)) {
967
+ if (!existsSync7(tsconfigPath)) {
908
968
  return {};
909
969
  }
910
970
  try {
@@ -924,7 +984,7 @@ function detectPathAliases(targetPath) {
924
984
  }
925
985
  function detectSourceDirectories(targetPath) {
926
986
  const srcPath = join8(targetPath, "src");
927
- const scanTarget = existsSync6(srcPath) ? srcPath : targetPath;
987
+ const scanTarget = existsSync7(srcPath) ? srcPath : targetPath;
928
988
  try {
929
989
  const entries = readdirSync2(scanTarget, { withFileTypes: true });
930
990
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !name.startsWith(".") && name !== "node_modules" && name !== "dist");
@@ -948,36 +1008,39 @@ function scanProjectOnboarding(targetPath) {
948
1008
  }
949
1009
 
950
1010
  // src/domains/harness/upsert-agent-instructions.ts
951
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1011
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
952
1012
  import { join as join9 } from "path";
953
1013
  var START_MARKER = "<!-- grooveskills:harness:start -->";
954
1014
  var END_MARKER = "<!-- grooveskills:harness:end -->";
955
- function applyHarnessBlock(filePath, language = "es") {
1015
+ function applyHarnessBlock(filePath, language = "es", transaction) {
1016
+ if (transaction) {
1017
+ transaction.recordBeforeWrite(filePath);
1018
+ }
956
1019
  const block = buildAgentHarnessBlock(language);
957
- if (!existsSync7(filePath)) {
958
- writeFileSync2(filePath, `${block}
959
- `, "utf-8");
1020
+ if (!existsSync8(filePath)) {
1021
+ safeWriteFileSync(filePath, `${block}
1022
+ `);
960
1023
  return;
961
1024
  }
962
1025
  const existingContent = readFileSync5(filePath, "utf-8");
963
1026
  if (existingContent.includes(START_MARKER) && existingContent.includes(END_MARKER)) {
964
1027
  const regex = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}`, "g");
965
1028
  const updated = existingContent.replace(regex, block);
966
- writeFileSync2(filePath, updated, "utf-8");
1029
+ safeWriteFileSync(filePath, updated);
967
1030
  return;
968
1031
  }
969
1032
  const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
970
- writeFileSync2(filePath, `${existingContent}${separator}${block}
971
- `, "utf-8");
1033
+ safeWriteFileSync(filePath, `${existingContent}${separator}${block}
1034
+ `);
972
1035
  }
973
- function upsertAgentInstructions(targetPath, selectedAgents, language = "es") {
1036
+ function upsertAgentInstructions(targetPath, selectedAgents, language = "es", transaction) {
974
1037
  const updatedFiles = [];
975
1038
  const agentsMdPath = join9(targetPath, "AGENTS.md");
976
- applyHarnessBlock(agentsMdPath, language);
1039
+ applyHarnessBlock(agentsMdPath, language, transaction);
977
1040
  updatedFiles.push(agentsMdPath);
978
1041
  if (selectedAgents.includes("claude")) {
979
1042
  const claudeMdPath = join9(targetPath, "CLAUDE.md");
980
- applyHarnessBlock(claudeMdPath, language);
1043
+ applyHarnessBlock(claudeMdPath, language, transaction);
981
1044
  updatedFiles.push(claudeMdPath);
982
1045
  }
983
1046
  return updatedFiles;
@@ -989,43 +1052,60 @@ function installHarness(options) {
989
1052
  const tasksDir = getProjectTasksDir(options.targetPath);
990
1053
  const language = options.language ?? "es";
991
1054
  const forceOnboarding = Boolean(options.forceOnboarding);
992
- if (!existsSync8(tasksDir)) {
1055
+ const tx = options.transaction;
1056
+ if (tx) {
1057
+ tx.recordCreatedDir(memoryDir);
1058
+ tx.recordCreatedDir(tasksDir);
1059
+ }
1060
+ if (!existsSync9(tasksDir)) {
993
1061
  mkdirSync3(tasksDir, { recursive: true });
994
1062
  }
995
1063
  const createdFiles = [];
996
1064
  const contextPath = join10(memoryDir, "context.md");
997
- const shouldWriteContext = !existsSync8(contextPath) || forceOnboarding;
1065
+ const shouldWriteContext = !existsSync9(contextPath) || forceOnboarding;
998
1066
  if (shouldWriteContext) {
1067
+ if (tx) {
1068
+ tx.recordBeforeWrite(contextPath);
1069
+ }
999
1070
  const profile = scanProjectOnboarding(options.targetPath);
1000
1071
  const contextContent = buildContextTemplate(
1001
1072
  options.detectedDependencies,
1002
1073
  language,
1003
1074
  profile
1004
1075
  );
1005
- writeFileSync3(contextPath, contextContent, "utf-8");
1076
+ safeWriteFileSync(contextPath, contextContent);
1006
1077
  createdFiles.push(contextPath);
1007
1078
  const onboardingTaskPath = join10(tasksDir, "00-onboarding.md");
1008
- writeFileSync3(
1079
+ if (tx) {
1080
+ tx.recordBeforeWrite(onboardingTaskPath);
1081
+ }
1082
+ safeWriteFileSync(
1009
1083
  onboardingTaskPath,
1010
- buildOnboardingTaskTemplate(profile.projectName),
1011
- "utf-8"
1084
+ buildOnboardingTaskTemplate(profile.projectName)
1012
1085
  );
1013
1086
  createdFiles.push(onboardingTaskPath);
1014
1087
  }
1015
1088
  const decisionsPath = join10(memoryDir, "decisions.md");
1016
- if (!existsSync8(decisionsPath)) {
1017
- writeFileSync3(decisionsPath, buildDecisionsTemplate(language), "utf-8");
1089
+ if (!existsSync9(decisionsPath)) {
1090
+ if (tx) {
1091
+ tx.recordBeforeWrite(decisionsPath);
1092
+ }
1093
+ safeWriteFileSync(decisionsPath, buildDecisionsTemplate(language));
1018
1094
  createdFiles.push(decisionsPath);
1019
1095
  }
1020
1096
  const templateTaskPath = join10(tasksDir, "_template.md");
1021
- if (!existsSync8(templateTaskPath)) {
1022
- writeFileSync3(templateTaskPath, buildTaskTemplate(), "utf-8");
1097
+ if (!existsSync9(templateTaskPath)) {
1098
+ if (tx) {
1099
+ tx.recordBeforeWrite(templateTaskPath);
1100
+ }
1101
+ safeWriteFileSync(templateTaskPath, buildTaskTemplate());
1023
1102
  createdFiles.push(templateTaskPath);
1024
1103
  }
1025
1104
  const instructions = upsertAgentInstructions(
1026
1105
  options.targetPath,
1027
1106
  options.selectedAgents,
1028
- language
1107
+ language,
1108
+ tx
1029
1109
  );
1030
1110
  createdFiles.push(...instructions);
1031
1111
  return {
@@ -1035,14 +1115,14 @@ function installHarness(options) {
1035
1115
  }
1036
1116
 
1037
1117
  // src/domains/harness/write-project-config.ts
1038
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1118
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
1039
1119
  function readExistingConfig(configPath) {
1040
1120
  const fallback = {
1041
1121
  skills: [],
1042
1122
  agents: [],
1043
1123
  harness: false
1044
1124
  };
1045
- if (!existsSync9(configPath)) {
1125
+ if (!existsSync10(configPath)) {
1046
1126
  return fallback;
1047
1127
  }
1048
1128
  try {
@@ -1058,8 +1138,11 @@ function readExistingConfig(configPath) {
1058
1138
  return fallback;
1059
1139
  }
1060
1140
  }
1061
- function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled, language) {
1141
+ function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled, language, transaction) {
1062
1142
  const configPath = getProjectConfigPath(targetPath);
1143
+ if (transaction) {
1144
+ transaction.recordBeforeWrite(configPath);
1145
+ }
1063
1146
  const existing = readExistingConfig(configPath);
1064
1147
  const mergedSkills = Array.from(/* @__PURE__ */ new Set([...existing.skills, ...newSkills]));
1065
1148
  const mergedAgents = Array.from(/* @__PURE__ */ new Set([...existing.agents, ...newAgents]));
@@ -1073,11 +1156,84 @@ function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled, la
1073
1156
  skills: mergedSkills,
1074
1157
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1075
1158
  };
1076
- writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
1077
- `, "utf-8");
1159
+ safeWriteFileSync(configPath, `${JSON.stringify(config, null, 2)}
1160
+ `);
1078
1161
  return configPath;
1079
1162
  }
1080
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
+
1081
1237
  // src/domains/installer/install-orchestrator.ts
1082
1238
  var ADAPTER_REGISTRY = {
1083
1239
  antigravity: new AntigravityAdapter(),
@@ -1085,7 +1241,20 @@ var ADAPTER_REGISTRY = {
1085
1241
  cursor: new CursorAdapter(),
1086
1242
  gemini: new GeminiAdapter()
1087
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
+ }
1088
1256
  async function installSkills(options) {
1257
+ const transaction = new InstallTransaction();
1089
1258
  const skillsToInstall = [...options.selectedSkillIds];
1090
1259
  if (!skillsToInstall.includes("groove-harness")) {
1091
1260
  skillsToInstall.push("groove-harness");
@@ -1093,48 +1262,70 @@ async function installSkills(options) {
1093
1262
  if (!skillsToInstall.includes("odd-ai")) {
1094
1263
  skillsToInstall.push("odd-ai");
1095
1264
  }
1096
- syncProjectStore(options.targetPath, skillsToInstall, options.selectedAgents);
1097
- const projectSkillsDir = getProjectSkillsDir(options.targetPath);
1098
- const createdFiles = [];
1099
- for (const skillId of skillsToInstall) {
1100
- const skill = SKILL_CATALOG.find((item) => item.id === skillId);
1101
- if (!skill) {
1102
- continue;
1103
- }
1104
- const skillSourceDir = join11(projectSkillsDir, skill.directoryName);
1105
- for (const agentId of options.selectedAgents) {
1106
- const adapter = ADAPTER_REGISTRY[agentId];
1107
- 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) {
1108
1274
  continue;
1109
1275
  }
1110
- const files = await adapter.installSkill(options.targetPath, skill, skillSourceDir);
1111
- 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
+ }
1112
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
+ });
1113
1328
  }
1114
- const harnessResult = installHarness({
1115
- targetPath: options.targetPath,
1116
- selectedAgents: options.selectedAgents,
1117
- selectedSkillIds: skillsToInstall,
1118
- detectedDependencies: options.detectedDependencies || [],
1119
- language: options.language,
1120
- forceOnboarding: options.forceOnboarding
1121
- });
1122
- createdFiles.push(...harnessResult.createdFiles);
1123
- const configPath = writeProjectConfig(
1124
- options.targetPath,
1125
- skillsToInstall,
1126
- options.selectedAgents,
1127
- true,
1128
- options.language
1129
- );
1130
- createdFiles.push(configPath);
1131
- return {
1132
- installedSkills: skillsToInstall,
1133
- configuredAgents: options.selectedAgents,
1134
- createdFiles,
1135
- harnessInitialized: harnessResult.onboardingInitialized,
1136
- language: options.language
1137
- };
1138
1329
  }
1139
1330
 
1140
1331
  // src/domains/ui/render-onboarding-prompt.ts
@@ -1165,7 +1356,7 @@ async function resolveLanguageSelection(harnessExists, configuredLanguage, force
1165
1356
  }
1166
1357
  const langChoice = await promptLanguage(configuredLanguage ?? "es");
1167
1358
  if (p5.isCancel(langChoice)) {
1168
- p5.cancel("Installation cancelled. Have a wonderful day!");
1359
+ p5.cancel("Instalaci\xF3n cancelada. \xA1Que tengas un excelente d\xEDa!");
1169
1360
  process.exit(0);
1170
1361
  }
1171
1362
  return langChoice;
@@ -1180,31 +1371,53 @@ async function resolveOverwriteConfirmation(targetPath, selectedSkills, selected
1180
1371
  }
1181
1372
  const confirm2 = await promptConfirmOverwrite();
1182
1373
  if (p5.isCancel(confirm2) || !confirm2) {
1183
- p5.cancel("Installation stopped to protect existing skills.");
1374
+ p5.cancel("Instalaci\xF3n detenida para proteger las skills existentes.");
1184
1375
  process.exit(0);
1185
1376
  }
1186
1377
  return Boolean(confirm2);
1187
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
+ }
1188
1401
  async function runInteractiveUi(context, forceOnboarding = false) {
1189
1402
  console.log(renderBanner());
1190
- p5.intro(pc3.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."));
1191
1404
  if (context.detectedDependencies.length > 0) {
1192
- const harnessInfo = context.harnessExists && !forceOnboarding ? pc3.green(`Active (.grooveskills/memory, lang: ${context.configuredLanguage ?? "es"})`) : pc3.cyan("Will be initialized");
1405
+ const harnessInfo = context.harnessExists && !forceOnboarding ? pc3.green(`Activo (.grooveskills/memory, idioma: ${context.configuredLanguage ?? "es"})`) : pc3.cyan("Ser\xE1 inicializado");
1193
1406
  p5.note(
1194
- `Detected dependencies: ${context.detectedDependencies.map((d) => pc3.bold(d)).join(", ")}
1195
- Active AI folders: ${context.detectedAgents.map((a) => pc3.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)"}
1196
1409
  GrooveSkills Harness: ${harnessInfo}`,
1197
- "Project Environment"
1410
+ "Entorno del Proyecto"
1198
1411
  );
1199
1412
  }
1200
1413
  const selectedSkills = await promptSkills(context.suggestedSkillIds);
1201
1414
  if (p5.isCancel(selectedSkills)) {
1202
- p5.cancel("Installation cancelled. Have a wonderful day!");
1415
+ p5.cancel("Instalaci\xF3n cancelada. \xA1Que tengas un excelente d\xEDa!");
1203
1416
  process.exit(0);
1204
1417
  }
1205
1418
  const selectedAgents = await promptAgents(context.detectedAgents);
1206
1419
  if (p5.isCancel(selectedAgents)) {
1207
- p5.cancel("Installation cancelled. Have a wonderful day!");
1420
+ p5.cancel("Instalaci\xF3n cancelada. \xA1Que tengas un excelente d\xEDa!");
1208
1421
  process.exit(0);
1209
1422
  }
1210
1423
  const selectedLanguage = await resolveLanguageSelection(
@@ -1218,33 +1431,42 @@ GrooveSkills Harness: ${harnessInfo}`,
1218
1431
  selectedAgents
1219
1432
  );
1220
1433
  const s = p5.spinner();
1221
- s.start("Installing and adapting skills across your configured AI agents...");
1222
- const result = await installSkills({
1223
- targetPath: context.targetPath,
1224
- selectedSkillIds: selectedSkills,
1225
- selectedAgents,
1226
- forceOverwrite,
1227
- enableHarness: true,
1228
- detectedDependencies: context.detectedDependencies,
1229
- language: selectedLanguage,
1230
- forceOnboarding
1231
- });
1232
- s.stop(pc3.green("All skills and harness have been successfully configured!"));
1233
- const harnessStatus = result.harnessInitialized ? pc3.green(`Initialized in .grooveskills/ (Language: ${selectedLanguage})`) : pc3.cyan(`Preserved in .grooveskills/ (Language: ${selectedLanguage})`);
1234
- p5.note(
1235
- `Configured skills: ${result.installedSkills.map((s2) => pc3.bold(s2)).join(", ")}
1236
- Target agents: ${result.configuredAgents.map((a) => pc3.cyan(a)).join(", ")}
1237
- GrooveSkills Harness: ${harnessStatus}`,
1238
- "Installation Summary"
1239
- );
1240
- if (result.harnessInitialized) {
1241
- const promptRender = formatOnboardingPrompt(selectedLanguage);
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})`);
1242
1448
  p5.note(
1243
- pc3.green(promptRender.prompt),
1244
- promptRender.title
1449
+ `Skills configuradas: ${result.installedSkills.map((sk) => pc3.bold(sk)).join(", ")}
1450
+ Agentes objetivo: ${result.configuredAgents.map((a) => pc3.cyan(a)).join(", ")}
1451
+ GrooveSkills Harness: ${harnessStatus}`,
1452
+ "Resumen de Instalaci\xF3n"
1245
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;
1246
1469
  }
1247
- p5.outro(pc3.green("Your AI programming pair is now empowered with your team's architectural habits! \u{1F680}"));
1248
1470
  }
1249
1471
 
1250
1472
  // src/main.ts
@@ -1279,6 +1501,7 @@ ${promptRender.formattedOutput}
1279
1501
  }
1280
1502
 
1281
1503
  export {
1504
+ InstallTransactionError,
1282
1505
  runGrooveSkills
1283
1506
  };
1284
- //# sourceMappingURL=chunk-HUG3MG3P.js.map
1507
+ //# sourceMappingURL=chunk-E63GTCOE.js.map