@wooojin/forgen 0.4.7 → 0.4.9

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 (159) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/assets/dev-guide/be/README.md +226 -0
  4. package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
  5. package/assets/dev-guide/be/principles/common.md +433 -0
  6. package/assets/dev-guide/be/principles/go.md +469 -0
  7. package/assets/dev-guide/be/principles/node.md +388 -0
  8. package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
  9. package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
  10. package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
  11. package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
  12. package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
  13. package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
  14. package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
  15. package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
  16. package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
  17. package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
  18. package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
  19. package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
  20. package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
  21. package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
  22. package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
  23. package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
  24. package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
  25. package/assets/dev-guide/fe/README.md +197 -0
  26. package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
  27. package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
  28. package/assets/dev-guide/fe/principles/common.md +160 -0
  29. package/assets/dev-guide/fe/principles/react.md +183 -0
  30. package/assets/dev-guide/fe/principles/vue.md +196 -0
  31. package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
  32. package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
  33. package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
  34. package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
  35. package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
  36. package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
  37. package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
  38. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
  39. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
  40. package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
  41. package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
  42. package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
  43. package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
  44. package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
  45. package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
  46. package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
  47. package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
  48. package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
  49. package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
  50. package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
  51. package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
  52. package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
  53. package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
  54. package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
  55. package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
  56. package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
  57. package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
  58. package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
  59. package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
  60. package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
  61. package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
  62. package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
  63. package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
  64. package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
  65. package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
  66. package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
  67. package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
  68. package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
  69. package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
  70. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
  71. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
  72. package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
  73. package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
  74. package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
  75. package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
  76. package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
  77. package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
  78. package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
  79. package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
  80. package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
  81. package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
  82. package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
  83. package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
  84. package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
  85. package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
  86. package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
  87. package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
  88. package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
  89. package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
  90. package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
  91. package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
  92. package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
  93. package/dist/checks/self-score-deflation.js +6 -4
  94. package/dist/cli.js +47 -2
  95. package/dist/core/auto-compound-runner.js +6 -2
  96. package/dist/core/dashboard-cli.d.ts +12 -0
  97. package/dist/core/dashboard-cli.js +226 -0
  98. package/dist/core/dashboard.js +2 -2
  99. package/dist/core/dev-guide-injector.d.ts +26 -0
  100. package/dist/core/dev-guide-injector.js +137 -0
  101. package/dist/core/doctor.d.ts +10 -0
  102. package/dist/core/doctor.js +49 -8
  103. package/dist/core/harness.js +8 -2
  104. package/dist/core/init.js +53 -0
  105. package/dist/core/inspect-cli.js +4 -4
  106. package/dist/core/lifecycle-classifier.d.ts +23 -0
  107. package/dist/core/lifecycle-classifier.js +104 -0
  108. package/dist/core/migrate-evidence-host.js +1 -1
  109. package/dist/core/notify.js +7 -0
  110. package/dist/core/observability-backfill.d.ts +31 -0
  111. package/dist/core/observability-backfill.js +178 -0
  112. package/dist/core/observability-store.d.ts +58 -0
  113. package/dist/core/observability-store.js +195 -0
  114. package/dist/core/paths.d.ts +16 -2
  115. package/dist/core/paths.js +16 -2
  116. package/dist/core/session-store.d.ts +12 -1
  117. package/dist/core/session-store.js +77 -1
  118. package/dist/core/spawn.d.ts +17 -0
  119. package/dist/core/spawn.js +191 -8
  120. package/dist/core/statusline-cli.js +34 -1
  121. package/dist/core/v1-bootstrap.d.ts +7 -0
  122. package/dist/core/v1-bootstrap.js +28 -6
  123. package/dist/engine/compound-extractor.js +40 -1
  124. package/dist/engine/compound-loop.js +6 -0
  125. package/dist/engine/compound-retire.d.ts +20 -0
  126. package/dist/engine/compound-retire.js +85 -0
  127. package/dist/engine/learn-cli.js +2 -2
  128. package/dist/engine/lifecycle/bypass-detector.js +3 -2
  129. package/dist/engine/lifecycle/meta-reclassifier.js +1 -1
  130. package/dist/engine/lifecycle/signals.js +2 -2
  131. package/dist/engine/lifecycle/trigger-t1-correction.js +1 -1
  132. package/dist/engine/solution-candidate.js +1 -1
  133. package/dist/engine/solution-outcomes.js +1 -1
  134. package/dist/engine/solution-quarantine.js +1 -1
  135. package/dist/engine/solution-weakness.js +8 -2
  136. package/dist/forge/cli.js +1 -1
  137. package/dist/hooks/context-guard.js +25 -1
  138. package/dist/hooks/keyword-detector.js +1 -1
  139. package/dist/hooks/post-tool-use.js +48 -0
  140. package/dist/hooks/secret-filter.js +2 -2
  141. package/dist/hooks/shared/hook-response.js +1 -1
  142. package/dist/hooks/shared/hook-timing.js +3 -3
  143. package/dist/hooks/solution-injector.js +94 -1
  144. package/dist/hooks/stop-guard.js +3 -3
  145. package/dist/host/install-claude.d.ts +6 -2
  146. package/dist/host/install-claude.js +74 -2
  147. package/dist/host/install-codex.d.ts +4 -0
  148. package/dist/host/install-codex.js +72 -1
  149. package/dist/host/install-orchestrator.js +1 -0
  150. package/dist/mcp/tools.js +1 -1
  151. package/dist/preset/facet-catalog.js +2 -2
  152. package/dist/renderer/rule-renderer.js +7 -7
  153. package/dist/store/compound-usage-store.js +1 -1
  154. package/dist/store/implicit-feedback-store.js +2 -2
  155. package/dist/store/profile-store.d.ts +11 -0
  156. package/dist/store/profile-store.js +23 -0
  157. package/package.json +6 -6
  158. package/plugin.json +1 -1
  159. package/scripts/postinstall.js +134 -0
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Claude InstallPlan — feat/codex-support Phase 1 (P1-2)
3
3
  *
4
- * `npm install` postinstall.js 의 *Claude 측* 4 작업을 module 로 분리.
4
+ * `npm install` postinstall.js 의 *Claude 측* 5 작업을 module 로 분리.
5
5
  * `forgen install claude` CLI 가 호출 + (P1-6 에서) postinstall.js 도 위임.
6
6
  *
7
- * 4 작업:
7
+ * 5 작업:
8
8
  * 1. Plugin cache: ~/.claude/plugins/cache/forgen-local/forgen/<ver>/ 작성 + installed_plugins.json 등록
9
9
  * 2. Slash commands: ~/.claude/commands/forgen/*.md 생성 (forgen-managed marker)
10
10
  * 3. Settings hooks injection: ~/.claude/settings.json 의 hooks 머지 (forgen entry idempotent)
11
11
  * 4. MCP register: ~/.claude.json 에 mcpServers.forgen-compound 추가
12
+ * 5. Dev-guide skills: ~/.claude/skills/forgen-<stack>-<skill>/ 설치 (forgen-managed only)
12
13
  *
13
14
  * 사용자 비-forgen 자산 보존 + 재실행 idempotent.
14
15
  */
@@ -31,5 +32,8 @@ export interface ClaudeInstallResult {
31
32
  hooksInjected: number;
32
33
  mcpRegistered: boolean;
33
34
  mcpAlreadyPresent: boolean;
35
+ skillsPath: string;
36
+ skillsInstalled: number;
37
+ skillsRemoved: number;
34
38
  }
35
39
  export declare function planClaudeInstall(opts: ClaudeInstallOptions): ClaudeInstallResult;
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Claude InstallPlan — feat/codex-support Phase 1 (P1-2)
3
3
  *
4
- * `npm install` postinstall.js 의 *Claude 측* 4 작업을 module 로 분리.
4
+ * `npm install` postinstall.js 의 *Claude 측* 5 작업을 module 로 분리.
5
5
  * `forgen install claude` CLI 가 호출 + (P1-6 에서) postinstall.js 도 위임.
6
6
  *
7
- * 4 작업:
7
+ * 5 작업:
8
8
  * 1. Plugin cache: ~/.claude/plugins/cache/forgen-local/forgen/<ver>/ 작성 + installed_plugins.json 등록
9
9
  * 2. Slash commands: ~/.claude/commands/forgen/*.md 생성 (forgen-managed marker)
10
10
  * 3. Settings hooks injection: ~/.claude/settings.json 의 hooks 머지 (forgen entry idempotent)
11
11
  * 4. MCP register: ~/.claude.json 에 mcpServers.forgen-compound 추가
12
+ * 5. Dev-guide skills: ~/.claude/skills/forgen-<stack>-<skill>/ 설치 (forgen-managed only)
12
13
  *
13
14
  * 사용자 비-forgen 자산 보존 + 재실행 idempotent.
14
15
  */
@@ -234,6 +235,72 @@ function registerMcpInClaudeJson(opts) {
234
235
  fs.writeFileSync(claudeJsonPath, `${JSON.stringify(claudeJson, null, 2)}\n`);
235
236
  return { registered: !alreadyPresent, alreadyPresent };
236
237
  }
238
+ // ── 5. Dev-guide skills ────────────────────────────────────────────────
239
+ const FORGEN_SKILL_PREFIX = 'forgen-';
240
+ function installDevGuideSkills(opts) {
241
+ const { pkgRoot, skillsDir, dryRun } = opts;
242
+ const devGuideRoot = path.join(pkgRoot, 'assets', 'dev-guide');
243
+ // Collect all SKILL.md entries: assets/dev-guide/{tier}/skills/{stack}/{skill}/SKILL.md
244
+ const entries = [];
245
+ if (fs.existsSync(devGuideRoot)) {
246
+ for (const tier of fs.readdirSync(devGuideRoot)) {
247
+ const skillsBase = path.join(devGuideRoot, tier, 'skills');
248
+ if (!fs.existsSync(skillsBase))
249
+ continue;
250
+ for (const stack of fs.readdirSync(skillsBase)) {
251
+ const stackDir = path.join(skillsBase, stack);
252
+ if (!fs.statSync(stackDir).isDirectory())
253
+ continue;
254
+ for (const skill of fs.readdirSync(stackDir)) {
255
+ const skillMd = path.join(stackDir, skill, 'SKILL.md');
256
+ if (fs.existsSync(skillMd)) {
257
+ entries.push({ name: `${FORGEN_SKILL_PREFIX}${stack}-${skill}`, src: skillMd });
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+ if (dryRun) {
264
+ return { skillsPath: skillsDir, skillsInstalled: entries.length, skillsRemoved: 0 };
265
+ }
266
+ fs.mkdirSync(skillsDir, { recursive: true });
267
+ // Remove stale forgen-* skill dirs (idempotent re-install, do not touch user's own skills)
268
+ let removed = 0;
269
+ for (const entry of fs.readdirSync(skillsDir)) {
270
+ if (!entry.startsWith(FORGEN_SKILL_PREFIX))
271
+ continue;
272
+ const fullPath = path.join(skillsDir, entry);
273
+ if (fs.statSync(fullPath).isDirectory()) {
274
+ fs.rmSync(fullPath, { recursive: true, force: true });
275
+ removed += 1;
276
+ }
277
+ }
278
+ // Install each skill via symlink → cpSync fallback (mirrors plugin cache pattern)
279
+ let installed = 0;
280
+ for (const { name, src } of entries) {
281
+ const targetDir = path.join(skillsDir, name);
282
+ fs.mkdirSync(targetDir, { recursive: true });
283
+ const targetFile = path.join(targetDir, 'SKILL.md');
284
+ let linked = false;
285
+ let symlinkErr = null;
286
+ try {
287
+ fs.symlinkSync(src, targetFile, 'file');
288
+ linked = true;
289
+ }
290
+ catch (e) {
291
+ symlinkErr = e;
292
+ }
293
+ if (!linked && symlinkErr) {
294
+ const code = symlinkErr.code ?? 'UNKNOWN';
295
+ process.stderr.write(`[forgen] symlink ${src} → ${targetFile} failed (${code}); falling back to copyFile.\n`);
296
+ }
297
+ if (!linked) {
298
+ fs.copyFileSync(src, targetFile);
299
+ }
300
+ installed += 1;
301
+ }
302
+ return { skillsPath: skillsDir, skillsInstalled: installed, skillsRemoved: removed };
303
+ }
237
304
  // ── public ─────────────────────────────────────────────────────────────
238
305
  export function planClaudeInstall(opts) {
239
306
  if (!opts.pkgRoot || !fs.existsSync(opts.pkgRoot)) {
@@ -249,12 +316,14 @@ export function planClaudeInstall(opts) {
249
316
  const slashCommandsDir = path.join(claudeDir, 'commands', 'forgen');
250
317
  const settingsPath = path.join(claudeDir, 'settings.json');
251
318
  const claudeJsonPath = path.join(homeDir, '.claude.json');
319
+ const skillsDir = path.join(claudeDir, 'skills');
252
320
  const pluginCacheWritten = writePluginCache({ pkgRoot: opts.pkgRoot, cacheDir, pluginsDir, version, dryRun });
253
321
  const slashCommandsCount = writeSlashCommands({ pkgRoot: opts.pkgRoot, targetDir: slashCommandsDir, dryRun });
254
322
  const hooksInjected = injectHooksIntoSettings({ pkgRoot: opts.pkgRoot, settingsPath, dryRun });
255
323
  const mcp = registerMcp
256
324
  ? registerMcpInClaudeJson({ pkgRoot: opts.pkgRoot, claudeJsonPath, dryRun })
257
325
  : { registered: false, alreadyPresent: false };
326
+ const skills = installDevGuideSkills({ pkgRoot: opts.pkgRoot, skillsDir, dryRun });
258
327
  return {
259
328
  homeDir,
260
329
  pluginCachePath: cacheDir,
@@ -265,5 +334,8 @@ export function planClaudeInstall(opts) {
265
334
  hooksInjected,
266
335
  mcpRegistered: mcp.registered,
267
336
  mcpAlreadyPresent: mcp.alreadyPresent,
337
+ skillsPath: skills.skillsPath,
338
+ skillsInstalled: skills.skillsInstalled,
339
+ skillsRemoved: skills.skillsRemoved,
268
340
  };
269
341
  }
@@ -40,5 +40,9 @@ export interface CodexInstallResult {
40
40
  /** P3-3: AGENTS.md (cwd) 에 forgen rule block 인젝션 여부 */
41
41
  agentsMdPath: string;
42
42
  agentsMdInjected: boolean;
43
+ /** v0.4.9: dev-guide skills (~/.codex/skills) 설치 결과 */
44
+ devGuideSkillsPath: string;
45
+ devGuideSkillsInstalled: number;
46
+ devGuideSkillsRemoved: number;
43
47
  }
44
48
  export declare function planCodexInstall(opts: CodexInstallOptions): CodexInstallResult;
@@ -150,6 +150,12 @@ export function planCodexInstall(opts) {
150
150
  // pkgRoot 의 git repo root 의 AGENTS.md, 또는 explicit override.
151
151
  const agentsMdPath = opts.agentsMdPath ?? resolveAgentsMdPath(opts.pkgRoot);
152
152
  const agentsResult = upsertForgenRulesInAgentsMd({ agentsMdPath, pkgRoot: opts.pkgRoot, dryRun: opts.dryRun ?? false });
153
+ // 8) v0.4.9: dev-guide skills → ~/.codex/skills/forgen-<stack>-<skill>/SKILL.md
154
+ const devGuideResult = installDevGuideSkillsToCodex({
155
+ pkgRoot: opts.pkgRoot,
156
+ codexHome,
157
+ dryRun: opts.dryRun ?? false,
158
+ });
153
159
  return {
154
160
  codexHome,
155
161
  hooksPath,
@@ -163,8 +169,73 @@ export function planCodexInstall(opts) {
163
169
  skillsPath,
164
170
  agentsMdPath,
165
171
  agentsMdInjected: agentsResult.injected,
172
+ devGuideSkillsPath: devGuideResult.devGuideSkillsPath,
173
+ devGuideSkillsInstalled: devGuideResult.devGuideSkillsInstalled,
174
+ devGuideSkillsRemoved: devGuideResult.devGuideSkillsRemoved,
166
175
  };
167
176
  }
177
+ // ── v0.4.9: dev-guide skills → ~/.codex/skills ────────────────────────
178
+ // dev-guide prefix pattern: forgen-<stack>-<skill> (e.g. forgen-react-fe-build)
179
+ // 반드시 stack 이 react|vue|node|go 인 것만 매칭 — forgen 자체 commands 보존
180
+ const DEV_GUIDE_SKILL_PATTERN = /^forgen-(react|vue|node|go)-/;
181
+ function installDevGuideSkillsToCodex(opts) {
182
+ const devGuideRoot = path.join(opts.pkgRoot, 'assets', 'dev-guide');
183
+ const codexSkillsDir = path.join(opts.codexHome, 'skills');
184
+ if (!fs.existsSync(devGuideRoot)) {
185
+ return { devGuideSkillsPath: codexSkillsDir, devGuideSkillsInstalled: 0, devGuideSkillsRemoved: 0 };
186
+ }
187
+ // Collect entries: assets/dev-guide/{tier}/skills/{stack}/{skill}/SKILL.md
188
+ const entries = [];
189
+ for (const tier of fs.readdirSync(devGuideRoot)) {
190
+ const skillsBase = path.join(devGuideRoot, tier, 'skills');
191
+ if (!fs.existsSync(skillsBase))
192
+ continue;
193
+ for (const stack of fs.readdirSync(skillsBase)) {
194
+ const stackDir = path.join(skillsBase, stack);
195
+ if (!fs.statSync(stackDir).isDirectory())
196
+ continue;
197
+ for (const skill of fs.readdirSync(stackDir)) {
198
+ const skillMd = path.join(stackDir, skill, 'SKILL.md');
199
+ if (fs.existsSync(skillMd)) {
200
+ entries.push({ name: `forgen-${stack}-${skill}`, src: skillMd });
201
+ }
202
+ }
203
+ }
204
+ }
205
+ if (opts.dryRun) {
206
+ return { devGuideSkillsPath: codexSkillsDir, devGuideSkillsInstalled: entries.length, devGuideSkillsRemoved: 0 };
207
+ }
208
+ fs.mkdirSync(codexSkillsDir, { recursive: true });
209
+ // Stale cleanup: dev-guide pattern 만 정리 (forgen 자체 commands 보존)
210
+ let removed = 0;
211
+ for (const entry of fs.readdirSync(codexSkillsDir)) {
212
+ if (DEV_GUIDE_SKILL_PATTERN.test(entry)) {
213
+ try {
214
+ fs.rmSync(path.join(codexSkillsDir, entry), { recursive: true, force: true });
215
+ removed++;
216
+ }
217
+ catch { /* best-effort */ }
218
+ }
219
+ }
220
+ // Install via symlink → copyFileSync fallback
221
+ let installed = 0;
222
+ for (const { name, src } of entries) {
223
+ const dstDir = path.join(codexSkillsDir, name);
224
+ fs.mkdirSync(dstDir, { recursive: true });
225
+ const dst = path.join(dstDir, 'SKILL.md');
226
+ let linked = false;
227
+ try {
228
+ fs.symlinkSync(src, dst, 'file');
229
+ linked = true;
230
+ }
231
+ catch { /* fallback */ }
232
+ if (!linked) {
233
+ fs.copyFileSync(src, dst);
234
+ }
235
+ installed++;
236
+ }
237
+ return { devGuideSkillsPath: codexSkillsDir, devGuideSkillsInstalled: installed, devGuideSkillsRemoved: removed };
238
+ }
168
239
  // ── P3-3: Codex skills install ────────────────────────────────────────
169
240
  function installCodexSkills(opts) {
170
241
  const { sourceDir, targetDir, dryRun } = opts;
@@ -202,7 +273,7 @@ function installCodexSkills(opts) {
202
273
  return { installed: count };
203
274
  }
204
275
  // ── P3-3: AGENTS.md inject ────────────────────────────────────────────
205
- function resolveAgentsMdPath(pkgRoot) {
276
+ function resolveAgentsMdPath(_pkgRoot) {
206
277
  // Phase 3 critic fix: pkgRoot 기반 walk-up 은 `npm install -g` 시 시스템 디렉토리
207
278
  // (예: /usr/local/lib/node_modules/forgen) 에 fallback AGENTS.md 작성 위험.
208
279
  // *cwd 기반* 으로 변경 — 사용자 작업 디렉토리의 git root, 없으면 cwd 자체.
@@ -109,6 +109,7 @@ export function renderResult(result, dryRun) {
109
109
  lines.push(` slash commands: ${result.claude.slashCommandsCount} → ${result.claude.slashCommandsPath}`);
110
110
  lines.push(` settings.json hooks: ${result.claude.hooksInjected}`);
111
111
  lines.push(` MCP: ${result.claude.mcpAlreadyPresent ? 'already present' : (result.claude.mcpRegistered ? 'registered' : 'skipped')}`);
112
+ lines.push(` skills: ${result.claude.skillsInstalled ?? 0} installed → ${result.claude.skillsPath ?? ''}`);
112
113
  }
113
114
  if (result.codex) {
114
115
  lines.push('');
package/dist/mcp/tools.js CHANGED
@@ -239,7 +239,7 @@ export function registerTools(server) {
239
239
  }],
240
240
  };
241
241
  }
242
- catch (e) {
242
+ catch (_e) {
243
243
  return {
244
244
  content: [{
245
245
  type: 'text',
@@ -29,8 +29,8 @@ export const COMMUNICATION_CENTROIDS = {
29
29
  '상세형': { verbosity: 0.85, structure: 0.80, teaching_bias: 0.80 },
30
30
  };
31
31
  // ── Defaults (backward compat) ──
32
- export const DEFAULT_JUDGMENT_FACETS = JUDGMENT_CENTROIDS['균형형'];
33
- export const DEFAULT_COMMUNICATION_FACETS = COMMUNICATION_CENTROIDS['균형형'];
32
+ export const DEFAULT_JUDGMENT_FACETS = JUDGMENT_CENTROIDS.균형형;
33
+ export const DEFAULT_COMMUNICATION_FACETS = COMMUNICATION_CENTROIDS.균형형;
34
34
  // ── Utilities ──
35
35
  export function qualityCentroid(pack) {
36
36
  return { ...QUALITY_CENTROIDS[pack] };
@@ -171,28 +171,28 @@ export function renderRules(rules, state, profile, ctx = DEFAULT_CONTEXT) {
171
171
  for (const name of SECTION_ORDER)
172
172
  sections.set(name, []);
173
173
  for (const rule of hardRules) {
174
- sections.get('Must Not').push(ruleToText(rule));
174
+ sections.get('Must Not')?.push(ruleToText(rule));
175
175
  }
176
176
  for (const rule of otherRules) {
177
177
  const section = CATEGORY_TO_SECTION[rule.category] ?? 'Working Defaults';
178
- sections.get(section).push(ruleToText(rule));
178
+ sections.get(section)?.push(ruleToText(rule));
179
179
  }
180
180
  // 5. trust policy + pack 기본 규칙 주입
181
181
  if (ctx.include_pack_summary) {
182
- sections.get('Working Defaults').unshift(`Trust: ${trustPolicySummary(state.effective_trust_policy)}`);
182
+ sections.get('Working Defaults')?.unshift(`Trust: ${trustPolicySummary(state.effective_trust_policy)}`);
183
183
  // judgment pack 기본 규칙
184
184
  for (const rule of judgmentPackRules(state.judgment_pack)) {
185
- sections.get('Working Defaults').push(rule);
185
+ sections.get('Working Defaults')?.push(rule);
186
186
  }
187
187
  // communication pack 기본 규칙
188
188
  for (const rule of communicationPackRules(state.communication_pack)) {
189
- sections.get('How To Report').push(rule);
189
+ sections.get('How To Report')?.push(rule);
190
190
  }
191
191
  // 4축 facet 극단값 → 추가 규칙 (12-bucket pack 위에 연속 값 차별화).
192
192
  // 각 facet 0.5 default 이고 자동 갱신 ±0.1 단위이므로, 0.85/0.15 임계값은
193
193
  // 여러 세션에 걸친 강한 신호 누적 후에만 발화한다.
194
194
  for (const fr of facetDrivenRules(profile)) {
195
- sections.get(fr.section).push(fr.rule);
195
+ sections.get(fr.section)?.push(fr.rule);
196
196
  }
197
197
  }
198
198
  // 6. 섹션 조립 (AI-optimized: 간결한 태그 형식)
@@ -200,7 +200,7 @@ export function renderRules(rules, state, profile, ctx = DEFAULT_CONTEXT) {
200
200
  let totalChars = 0;
201
201
  let totalRules = 0;
202
202
  for (const name of SECTION_ORDER) {
203
- const items = sections.get(name);
203
+ const items = sections.get(name) ?? [];
204
204
  if (items.length === 0)
205
205
  continue;
206
206
  const header = `## ${name}`;
@@ -27,7 +27,7 @@ export function recordUsage(name, via = 'mcp') {
27
27
  try {
28
28
  fs.mkdirSync(STATE_DIR, { recursive: true });
29
29
  const entry = { at: new Date().toISOString(), name, via };
30
- fs.appendFileSync(COMPOUND_USAGE_LOG, JSON.stringify(entry) + '\n');
30
+ fs.appendFileSync(COMPOUND_USAGE_LOG, `${JSON.stringify(entry)}\n`);
31
31
  }
32
32
  catch {
33
33
  // fail-open: 신호 수집 실패가 사용자 경험을 방해하면 안 됨
@@ -67,7 +67,7 @@ export function appendImplicitFeedback(entry) {
67
67
  return false;
68
68
  try {
69
69
  fs.mkdirSync(STATE_DIR, { recursive: true });
70
- fs.appendFileSync(IMPLICIT_FEEDBACK_LOG, JSON.stringify(normalized) + '\n');
70
+ fs.appendFileSync(IMPLICIT_FEEDBACK_LOG, `${JSON.stringify(normalized)}\n`);
71
71
  return true;
72
72
  }
73
73
  catch {
@@ -147,7 +147,7 @@ export function migrateImplicitFeedbackLog() {
147
147
  }
148
148
  // atomic replace via temp file
149
149
  const tmp = `${IMPLICIT_FEEDBACK_LOG}.migrate.${process.pid}`;
150
- fs.writeFileSync(tmp, out.length > 0 ? out.join('\n') + '\n' : '');
150
+ fs.writeFileSync(tmp, out.length > 0 ? `${out.join('\n')}\n` : '');
151
151
  fs.renameSync(tmp, IMPLICIT_FEEDBACK_LOG);
152
152
  return { migrated, dropped };
153
153
  }
@@ -18,6 +18,17 @@ export declare function saveProfile(profile: Profile): void;
18
18
  * whether to run `runLegacyCutover`).
19
19
  */
20
20
  export declare function profileExists(): boolean;
21
+ /**
22
+ * profile.json 이 존재하지만 parse 실패 / v1 shape 위반인 경우, 사용자가
23
+ * 다음 onboarding 으로 매끄럽게 복구되도록 corrupt 파일을 timestamp 백업
24
+ * 으로 옆에 치워둔다. 백업 경로를 반환.
25
+ *
26
+ * v0.4.8 — bootstrapV1Session 의 loadProfile()=null early-return 보강.
27
+ * 이전엔 needsOnboarding=true 만 반환했고 corrupt 파일이 그대로 남아
28
+ * 다음 실행 때도 동일 분기로 빠지면서 사용자가 정체 원인을 모른 채
29
+ * onboarding 안내만 반복 받는 패턴이었음.
30
+ */
31
+ export declare function backupCorruptProfile(): string | null;
21
32
  export declare function isV1Profile(data: unknown): data is Profile;
22
33
  /**
23
34
  * D2 fix (2026-04-27): explicit_correction 누적 시 해당 축의 confidence 를 점진
@@ -66,6 +66,29 @@ export function saveProfile(profile) {
66
66
  export function profileExists() {
67
67
  return fs.existsSync(FORGE_PROFILE);
68
68
  }
69
+ /**
70
+ * profile.json 이 존재하지만 parse 실패 / v1 shape 위반인 경우, 사용자가
71
+ * 다음 onboarding 으로 매끄럽게 복구되도록 corrupt 파일을 timestamp 백업
72
+ * 으로 옆에 치워둔다. 백업 경로를 반환.
73
+ *
74
+ * v0.4.8 — bootstrapV1Session 의 loadProfile()=null early-return 보강.
75
+ * 이전엔 needsOnboarding=true 만 반환했고 corrupt 파일이 그대로 남아
76
+ * 다음 실행 때도 동일 분기로 빠지면서 사용자가 정체 원인을 모른 채
77
+ * onboarding 안내만 반복 받는 패턴이었음.
78
+ */
79
+ export function backupCorruptProfile() {
80
+ if (!fs.existsSync(FORGE_PROFILE))
81
+ return null;
82
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
83
+ const backupPath = `${FORGE_PROFILE}.corrupt-${ts}`;
84
+ try {
85
+ fs.renameSync(FORGE_PROFILE, backupPath);
86
+ return backupPath;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
69
92
  export function isV1Profile(data) {
70
93
  if (!data || typeof data !== 'object')
71
94
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooojin/forgen",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "preferGlobal": true,
5
5
  "main": "dist/lib.js",
6
6
  "types": "./dist/lib.d.ts",
@@ -47,19 +47,19 @@
47
47
  ],
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "https://github.com/forgen-team/forgen.git"
50
+ "url": "git+https://github.com/forgen-team/forgen.git"
51
51
  },
52
52
  "engines": {
53
- "node": ">=20.0.0"
53
+ "node": ">=22.0.0"
54
54
  },
55
55
  "type": "module",
56
56
  "workspaces": [
57
57
  "packages/*"
58
58
  ],
59
59
  "bin": {
60
- "forgen": "./dist/cli.js",
61
- "fgx": "./dist/fgx.js",
62
- "forgen-mcp": "./dist/mcp/server.js"
60
+ "forgen": "dist/cli.js",
61
+ "fgx": "dist/fgx.js",
62
+ "forgen-mcp": "dist/mcp/server.js"
63
63
  },
64
64
  "files": [
65
65
  "dist/",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://claude.ai/schemas/claude-plugin.json",
3
3
  "name": "forgen",
4
- "version": "0.4.7",
4
+ "version": "0.4.9",
5
5
  "description": "Claude Code harness — the more you use Claude, the better it gets",
6
6
  "author": {
7
7
  "name": "jang-ujin",
@@ -746,6 +746,125 @@ function cleanLegacyMcpFromSettings(settings) {
746
746
  }
747
747
  }
748
748
 
749
+ /**
750
+ * dev-guide 스킬 14개 자동 설치 — assets/dev-guide/{fe,be}/skills/{stack}/{skill}/SKILL.md
751
+ * → ~/.claude/skills/forgen-<stack>-<skill>/SKILL.md
752
+ *
753
+ * forgen- prefix 로 사용자 own skills 와 격리. idempotent 재실행 시 forgen-* 만 정리 후 재설치.
754
+ */
755
+ function installDevGuideSkills(home) {
756
+ const devGuideRoot = join(PKG_ROOT, 'assets', 'dev-guide');
757
+ if (!existsSync(devGuideRoot)) {
758
+ console.log('[forgen] dev-guide assets not found — skipping skill install');
759
+ return 0;
760
+ }
761
+
762
+ const userSkillsDir = join(home, '.claude', 'skills');
763
+ mkdirSync(userSkillsDir, { recursive: true });
764
+
765
+ // 1. 기존 forgen-* 디렉토리 정리 (사용자 own 보존)
766
+ let removed = 0;
767
+ for (const entry of readdirSync(userSkillsDir)) {
768
+ if (entry.startsWith('forgen-')) {
769
+ try { rmSync(join(userSkillsDir, entry), { recursive: true, force: true }); removed++; } catch { /* best-effort */ }
770
+ }
771
+ }
772
+
773
+ // 2. assets/dev-guide/{side}/skills/{stack}/{skill}/SKILL.md 수집
774
+ let installed = 0;
775
+ for (const side of ['fe', 'be']) {
776
+ const sideSkillsDir = join(devGuideRoot, side, 'skills');
777
+ if (!existsSync(sideSkillsDir)) continue;
778
+ for (const stack of readdirSync(sideSkillsDir)) {
779
+ const stackDir = join(sideSkillsDir, stack);
780
+ if (!statSync(stackDir).isDirectory()) continue;
781
+ for (const skill of readdirSync(stackDir)) {
782
+ const skillSrc = join(stackDir, skill);
783
+ const skillFile = join(skillSrc, 'SKILL.md');
784
+ if (!existsSync(skillFile)) continue;
785
+
786
+ // 대상: ~/.claude/skills/forgen-<stack>-<skill>/SKILL.md
787
+ const dstDir = join(userSkillsDir, `forgen-${stack}-${skill}`);
788
+ mkdirSync(dstDir, { recursive: true });
789
+ const dst = join(dstDir, 'SKILL.md');
790
+
791
+ // symlink → fallback cpSync
792
+ try {
793
+ if (existsSync(dst)) rmSync(dst);
794
+ symlinkSync(skillFile, dst, 'file');
795
+ } catch {
796
+ cpSync(skillFile, dst);
797
+ }
798
+ installed++;
799
+ }
800
+ }
801
+ }
802
+
803
+ // 3. sudo 케이스 ownership 회복
804
+ fixOwnership(userSkillsDir);
805
+
806
+ console.log(`[forgen] dev-guide skills: ${installed} installed${removed > 0 ? ` (${removed} stale removed)` : ''}`);
807
+ return installed;
808
+ }
809
+
810
+ /**
811
+ * v0.4.9: dev-guide 14 skills → ~/.codex/skills/forgen-<stack>-<skill>/SKILL.md
812
+ * forgen 자체 commands(forgen-compound 등) 와 prefix 겹치지 않도록
813
+ * DEV_GUIDE_SKILL_PATTERN(/^forgen-(react|vue|node|go)-/) 으로 stale 정리.
814
+ */
815
+ const DEV_GUIDE_SKILL_PATTERN = /^forgen-(react|vue|node|go)-/;
816
+
817
+ function installDevGuideSkillsToCodex(home) {
818
+ const devGuideRoot = join(PKG_ROOT, 'assets', 'dev-guide');
819
+ if (!existsSync(devGuideRoot)) {
820
+ console.log('[forgen] dev-guide assets not found — codex skill install skipped');
821
+ return 0;
822
+ }
823
+
824
+ const codexSkillsDir = join(home, '.codex', 'skills');
825
+ mkdirSync(codexSkillsDir, { recursive: true });
826
+
827
+ // 1. stale forgen-<stack>-<skill> 정리 (forgen 자체 commands 보존)
828
+ let removed = 0;
829
+ for (const entry of readdirSync(codexSkillsDir)) {
830
+ if (DEV_GUIDE_SKILL_PATTERN.test(entry)) {
831
+ try { rmSync(join(codexSkillsDir, entry), { recursive: true, force: true }); removed++; } catch { /* best-effort */ }
832
+ }
833
+ }
834
+
835
+ // 2. assets/dev-guide/{tier}/skills/{stack}/{skill}/SKILL.md 수집
836
+ let installed = 0;
837
+ for (const tier of readdirSync(devGuideRoot)) {
838
+ const sideSkillsDir = join(devGuideRoot, tier, 'skills');
839
+ if (!existsSync(sideSkillsDir)) continue;
840
+ for (const stack of readdirSync(sideSkillsDir)) {
841
+ const stackDir = join(sideSkillsDir, stack);
842
+ if (!statSync(stackDir).isDirectory()) continue;
843
+ for (const skill of readdirSync(stackDir)) {
844
+ const skillFile = join(stackDir, skill, 'SKILL.md');
845
+ if (!existsSync(skillFile)) continue;
846
+
847
+ const dstDir = join(codexSkillsDir, `forgen-${stack}-${skill}`);
848
+ mkdirSync(dstDir, { recursive: true });
849
+ const dst = join(dstDir, 'SKILL.md');
850
+
851
+ // symlink → fallback cpSync
852
+ try {
853
+ if (existsSync(dst)) rmSync(dst);
854
+ symlinkSync(skillFile, dst, 'file');
855
+ } catch {
856
+ cpSync(skillFile, dst);
857
+ }
858
+ installed++;
859
+ }
860
+ }
861
+ }
862
+
863
+ fixOwnership(codexSkillsDir);
864
+ console.log(`[forgen] codex dev-guide skills: ${installed} installed${removed > 0 ? ` (${removed} stale removed)` : ''}`);
865
+ return installed;
866
+ }
867
+
749
868
  // ── Main ──
750
869
 
751
870
  /**
@@ -927,6 +1046,21 @@ async function main() {
927
1046
  console.error(`[forgen] starter pack failed: ${err?.message ?? err}`);
928
1047
  }
929
1048
 
1049
+ // ── 8b. dev-guide 스킬 자동 설치 (claude) ──
1050
+ try {
1051
+ installDevGuideSkills(HOME);
1052
+ } catch (e) {
1053
+ // postinstall 의 "fail-open" 원칙 — npm install 깨뜨리지 않음
1054
+ console.log(`[forgen] dev-guide skills 설치 스킵 (${e?.message ?? e})`);
1055
+ }
1056
+
1057
+ // ── 8c. dev-guide 스킬 자동 설치 (codex) ──
1058
+ try {
1059
+ installDevGuideSkillsToCodex(HOME);
1060
+ } catch (e) {
1061
+ console.log(`[forgen] codex dev-guide skills 설치 스킵 (${e?.message ?? e})`);
1062
+ }
1063
+
930
1064
  // sudo 실행 시 파일 소유권을 실제 유저로 변경
931
1065
  fixOwnership(join(HOME, '.claude'), join(HOME, '.forgen'));
932
1066