@wooojin/forgen 0.3.1 → 0.4.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 (125) hide show
  1. package/.claude-plugin/plugin.json +7 -2
  2. package/CHANGELOG.md +164 -0
  3. package/README.ja.md +90 -7
  4. package/README.ko.md +44 -1
  5. package/README.md +128 -9
  6. package/README.zh.md +90 -7
  7. package/dist/cli.js +140 -8
  8. package/dist/core/auto-compound-runner.js +16 -5
  9. package/dist/core/dashboard.js +11 -4
  10. package/dist/core/doctor.d.ts +6 -1
  11. package/dist/core/doctor.js +85 -11
  12. package/dist/core/global-config.d.ts +2 -2
  13. package/dist/core/global-config.js +6 -14
  14. package/dist/core/harness.d.ts +3 -5
  15. package/dist/core/harness.js +34 -338
  16. package/dist/core/inspect-cli.js +65 -5
  17. package/dist/core/installer.d.ts +10 -0
  18. package/dist/core/installer.js +185 -0
  19. package/dist/core/paths.d.ts +0 -34
  20. package/dist/core/paths.js +0 -35
  21. package/dist/core/settings-injector.d.ts +13 -0
  22. package/dist/core/settings-injector.js +167 -0
  23. package/dist/core/settings-lock.d.ts +35 -2
  24. package/dist/core/settings-lock.js +65 -7
  25. package/dist/core/spawn.js +100 -39
  26. package/dist/core/state-gc.d.ts +49 -0
  27. package/dist/core/state-gc.js +163 -0
  28. package/dist/core/stats-cli.d.ts +15 -0
  29. package/dist/core/stats-cli.js +143 -0
  30. package/dist/core/uninstall.d.ts +1 -0
  31. package/dist/core/uninstall.js +36 -5
  32. package/dist/core/v1-bootstrap.js +11 -3
  33. package/dist/engine/classify-enforce-cli.d.ts +8 -0
  34. package/dist/engine/classify-enforce-cli.js +61 -0
  35. package/dist/engine/compound-cli.d.ts +27 -2
  36. package/dist/engine/compound-cli.js +69 -16
  37. package/dist/engine/compound-export.d.ts +15 -0
  38. package/dist/engine/compound-export.js +32 -5
  39. package/dist/engine/compound-loop.js +3 -2
  40. package/dist/engine/enforce-classifier.d.ts +31 -0
  41. package/dist/engine/enforce-classifier.js +123 -0
  42. package/dist/engine/learn-cli.js +52 -0
  43. package/dist/engine/lifecycle/bypass-detector.d.ts +34 -0
  44. package/dist/engine/lifecycle/bypass-detector.js +82 -0
  45. package/dist/engine/lifecycle/lifecycle-cli.d.ts +7 -0
  46. package/dist/engine/lifecycle/lifecycle-cli.js +102 -0
  47. package/dist/engine/lifecycle/meta-cli.d.ts +4 -0
  48. package/dist/engine/lifecycle/meta-cli.js +7 -0
  49. package/dist/engine/lifecycle/meta-reclassifier.d.ts +78 -0
  50. package/dist/engine/lifecycle/meta-reclassifier.js +351 -0
  51. package/dist/engine/lifecycle/orchestrator.d.ts +32 -0
  52. package/dist/engine/lifecycle/orchestrator.js +131 -0
  53. package/dist/engine/lifecycle/signals.d.ts +30 -0
  54. package/dist/engine/lifecycle/signals.js +142 -0
  55. package/dist/engine/lifecycle/trigger-t1-correction.d.ts +23 -0
  56. package/dist/engine/lifecycle/trigger-t1-correction.js +78 -0
  57. package/dist/engine/lifecycle/trigger-t2-violation.d.ts +18 -0
  58. package/dist/engine/lifecycle/trigger-t2-violation.js +42 -0
  59. package/dist/engine/lifecycle/trigger-t3-bypass.d.ts +17 -0
  60. package/dist/engine/lifecycle/trigger-t3-bypass.js +39 -0
  61. package/dist/engine/lifecycle/trigger-t4-decay.d.ts +18 -0
  62. package/dist/engine/lifecycle/trigger-t4-decay.js +40 -0
  63. package/dist/engine/lifecycle/trigger-t5-conflict.d.ts +16 -0
  64. package/dist/engine/lifecycle/trigger-t5-conflict.js +78 -0
  65. package/dist/engine/lifecycle/types.d.ts +52 -0
  66. package/dist/engine/lifecycle/types.js +7 -0
  67. package/dist/engine/match-eval-log.js +45 -0
  68. package/dist/engine/rule-toggle-cli.d.ts +13 -0
  69. package/dist/engine/rule-toggle-cli.js +76 -0
  70. package/dist/engine/solution-format.d.ts +0 -2
  71. package/dist/engine/solution-format.js +0 -4
  72. package/dist/engine/solution-matcher.d.ts +8 -0
  73. package/dist/engine/solution-matcher.js +7 -4
  74. package/dist/engine/solution-outcomes.d.ts +4 -0
  75. package/dist/engine/solution-outcomes.js +174 -97
  76. package/dist/engine/solution-writer.d.ts +8 -5
  77. package/dist/engine/solution-writer.js +43 -19
  78. package/dist/fgx.js +9 -2
  79. package/dist/forge/cli.js +7 -7
  80. package/dist/forge/evidence-processor.js +10 -2
  81. package/dist/hooks/context-guard.js +86 -1
  82. package/dist/hooks/hook-config.d.ts +9 -1
  83. package/dist/hooks/hook-config.js +25 -3
  84. package/dist/hooks/internal/run-lifecycle-check.d.ts +2 -0
  85. package/dist/hooks/internal/run-lifecycle-check.js +32 -0
  86. package/dist/hooks/notepad-injector.js +6 -3
  87. package/dist/hooks/permission-handler.d.ts +10 -2
  88. package/dist/hooks/permission-handler.js +31 -12
  89. package/dist/hooks/post-tool-use.js +62 -0
  90. package/dist/hooks/pre-tool-use.js +67 -5
  91. package/dist/hooks/secret-filter.d.ts +10 -0
  92. package/dist/hooks/secret-filter.js +26 -0
  93. package/dist/hooks/session-recovery.js +15 -7
  94. package/dist/hooks/shared/atomic-write.d.ts +8 -1
  95. package/dist/hooks/shared/atomic-write.js +17 -3
  96. package/dist/hooks/shared/hook-response.d.ts +11 -2
  97. package/dist/hooks/shared/hook-response.js +20 -7
  98. package/dist/hooks/shared/hook-timing.js +10 -1
  99. package/dist/hooks/shared/safe-regex.d.ts +25 -0
  100. package/dist/hooks/shared/safe-regex.js +50 -0
  101. package/dist/hooks/shared/stop-triggers.d.ts +19 -0
  102. package/dist/hooks/shared/stop-triggers.js +19 -0
  103. package/dist/hooks/solution-injector.d.ts +21 -0
  104. package/dist/hooks/solution-injector.js +60 -1
  105. package/dist/hooks/stop-guard.d.ts +84 -0
  106. package/dist/hooks/stop-guard.js +482 -0
  107. package/dist/mcp/solution-reader.d.ts +2 -0
  108. package/dist/mcp/solution-reader.js +28 -1
  109. package/dist/mcp/tools.js +24 -4
  110. package/dist/preset/preset-manager.js +12 -2
  111. package/dist/store/evidence-store.d.ts +15 -0
  112. package/dist/store/evidence-store.js +55 -6
  113. package/dist/store/profile-store.d.ts +9 -0
  114. package/dist/store/profile-store.js +25 -4
  115. package/dist/store/rule-lifecycle.d.ts +23 -0
  116. package/dist/store/rule-lifecycle.js +63 -0
  117. package/dist/store/rule-store.d.ts +21 -0
  118. package/dist/store/rule-store.js +133 -13
  119. package/dist/store/types.d.ts +83 -0
  120. package/dist/store/types.js +7 -1
  121. package/hooks/hook-registry.json +1 -0
  122. package/hooks/hooks.json +6 -1
  123. package/package.json +10 -2
  124. package/plugin.json +7 -2
  125. package/scripts/postinstall.js +52 -5
@@ -679,7 +679,21 @@ function applyMcpToClaudeJson() {
679
679
  const claudeJsonPath = join(HOME, '.claude.json');
680
680
  let claudeJson = {};
681
681
  if (existsSync(claudeJsonPath)) {
682
- try { claudeJson = JSON.parse(readFileSync(claudeJsonPath, 'utf-8')); } catch { /* ignore */ }
682
+ try {
683
+ claudeJson = JSON.parse(readFileSync(claudeJsonPath, 'utf-8'));
684
+ } catch (e) {
685
+ // Audit fix #10 (2026-04-21): silent parse-failure used to let the
686
+ // merged write below overwrite the user's malformed-but-original
687
+ // ~/.claude.json, wiping all their other plugin configs. Preserve
688
+ // and skip instead.
689
+ const corruptPath = `${claudeJsonPath}.corrupt-${Date.now()}`;
690
+ try { copyFileSync(claudeJsonPath, corruptPath); } catch { /* best-effort */ }
691
+ console.error(
692
+ `[forgen] ${claudeJsonPath} failed to parse; preserved at ${corruptPath}. ` +
693
+ `Skipping MCP registration.`,
694
+ );
695
+ return false;
696
+ }
683
697
  }
684
698
 
685
699
  const mcpServers = claudeJson.mcpServers ?? {};
@@ -689,7 +703,11 @@ function applyMcpToClaudeJson() {
689
703
  };
690
704
  claudeJson.mcpServers = mcpServers;
691
705
 
692
- writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
706
+ // Atomic write — tmp file + rename so a partial write never leaves
707
+ // ~/.claude.json in a torn state for any concurrent reader.
708
+ const tmpPath = `${claudeJsonPath}.tmp.${process.pid}`;
709
+ writeFileSync(tmpPath, JSON.stringify(claudeJson, null, 2));
710
+ renameSync(tmpPath, claudeJsonPath);
693
711
  fixOwnership(claudeJsonPath);
694
712
  return true;
695
713
  }
@@ -721,9 +739,29 @@ function main() {
721
739
  ensureDirectories();
722
740
 
723
741
  // ── 1. settings.json 한 번 읽기 ──
742
+ //
743
+ // Audit finding #2/#10 (2026-04-21): prior `catch { settings = {} }`
744
+ // silently swallowed parse failures and let the merged write below
745
+ // clobber the user's malformed-but-original settings.json — a data
746
+ // loss path triggered by any partial edit or external corruption.
747
+ // Now: preserve the corrupt file as `.corrupt-<ts>`, abort the
748
+ // settings write (and subsequent MCP registration + ownership fix
749
+ // that also touches ~/.claude.json), surface the warning via npm's
750
+ // postinstall output. The outer try/catch at the bottom ensures
751
+ // `npm install` still succeeds; only the settings mutation is
752
+ // skipped.
724
753
  let settings = {};
725
754
  if (existsSync(SETTINGS_PATH)) {
726
- try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch { /* 파싱 실패 시 빈 설정 */ }
755
+ try {
756
+ settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
757
+ } catch (e) {
758
+ const corruptPath = `${SETTINGS_PATH}.corrupt-${Date.now()}`;
759
+ try { copyFileSync(SETTINGS_PATH, corruptPath); } catch { /* best-effort */ }
760
+ throw new Error(
761
+ `[forgen] ${SETTINGS_PATH} failed to parse: ${e?.message ?? e}. ` +
762
+ `Preserved corrupt copy at ${corruptPath}. Skipping settings injection to avoid data loss.`,
763
+ );
764
+ }
727
765
  }
728
766
 
729
767
  // ── 2. 플러그인 등록 (installed_plugins.json + skills) ──
@@ -787,10 +825,19 @@ function main() {
787
825
  console.error(`[forgen] MCP server registration failed: ${err?.message ?? err}`);
788
826
  }
789
827
 
790
- // ── 7. settings.json 한 번 쓰기 ──
828
+ // ── 7. settings.json 한 번 쓰기 (atomic) ──
829
+ //
830
+ // Audit fix #10 (2026-04-21): prior direct writeFileSync could leave
831
+ // settings.json truncated / partially-written if the process was
832
+ // interrupted (sudo shell SIGINT, npm exit). Tmp + rename is atomic
833
+ // on POSIX within the same directory. Lock is NOT acquired here —
834
+ // postinstall is serialized by npm and we don't want to introduce a
835
+ // cross-module dependency from a build-time script.
791
836
  try {
792
837
  mkdirSync(dirname(SETTINGS_PATH), { recursive: true });
793
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
838
+ const tmpPath = `${SETTINGS_PATH}.tmp.${process.pid}`;
839
+ writeFileSync(tmpPath, JSON.stringify(settings, null, 2));
840
+ renameSync(tmpPath, SETTINGS_PATH);
794
841
  } catch (err) {
795
842
  console.error(`[forgen] settings.json write failed: ${err?.message ?? err}`);
796
843
  }