@wooojin/forgen 0.2.1 → 0.3.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 (124) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.ko.md +25 -14
  3. package/README.md +61 -17
  4. package/agents/analyst.md +48 -4
  5. package/agents/architect.md +39 -4
  6. package/agents/code-reviewer.md +107 -77
  7. package/agents/critic.md +47 -4
  8. package/agents/debugger.md +46 -4
  9. package/agents/designer.md +40 -4
  10. package/agents/executor.md +112 -30
  11. package/agents/explore.md +45 -5
  12. package/agents/git-master.md +48 -4
  13. package/agents/planner.md +121 -18
  14. package/agents/test-engineer.md +58 -4
  15. package/agents/verifier.md +92 -77
  16. package/commands/architecture-decision.md +127 -258
  17. package/commands/calibrate.md +225 -0
  18. package/commands/code-review.md +163 -178
  19. package/commands/compound.md +127 -68
  20. package/commands/deep-interview.md +212 -110
  21. package/commands/docker.md +68 -178
  22. package/commands/forge-loop.md +215 -0
  23. package/commands/learn.md +231 -0
  24. package/commands/retro.md +215 -0
  25. package/commands/ship.md +277 -0
  26. package/dist/cli.js +17 -9
  27. package/dist/core/auto-compound-runner.js +14 -0
  28. package/dist/core/config-injector.d.ts +2 -1
  29. package/dist/core/config-injector.js +2 -1
  30. package/dist/core/dashboard.d.ts +17 -0
  31. package/dist/core/dashboard.js +112 -2
  32. package/dist/core/harness.d.ts +6 -1
  33. package/dist/core/harness.js +75 -19
  34. package/dist/core/paths.d.ts +6 -1
  35. package/dist/core/paths.js +18 -2
  36. package/dist/core/spawn.d.ts +3 -2
  37. package/dist/core/spawn.js +27 -8
  38. package/dist/core/types.d.ts +34 -0
  39. package/dist/engine/compound-lifecycle.d.ts +4 -3
  40. package/dist/engine/compound-lifecycle.js +91 -46
  41. package/dist/engine/meta-learning/adaptive-thresholds.d.ts +20 -0
  42. package/dist/engine/meta-learning/adaptive-thresholds.js +126 -0
  43. package/dist/engine/meta-learning/extraction-tuner.d.ts +15 -0
  44. package/dist/engine/meta-learning/extraction-tuner.js +99 -0
  45. package/dist/engine/meta-learning/matcher-weight-tuner.d.ts +21 -0
  46. package/dist/engine/meta-learning/matcher-weight-tuner.js +151 -0
  47. package/dist/engine/meta-learning/runner.d.ts +14 -0
  48. package/dist/engine/meta-learning/runner.js +90 -0
  49. package/dist/engine/meta-learning/scope-promoter.d.ts +21 -0
  50. package/dist/engine/meta-learning/scope-promoter.js +84 -0
  51. package/dist/engine/meta-learning/session-quality-scorer.d.ts +61 -0
  52. package/dist/engine/meta-learning/session-quality-scorer.js +166 -0
  53. package/dist/engine/meta-learning/types.d.ts +114 -0
  54. package/dist/engine/meta-learning/types.js +43 -0
  55. package/dist/engine/solution-format.d.ts +2 -2
  56. package/dist/engine/solution-format.js +249 -34
  57. package/dist/engine/solution-index.d.ts +1 -1
  58. package/dist/engine/solution-matcher.d.ts +7 -1
  59. package/dist/engine/solution-matcher.js +114 -37
  60. package/dist/fgx.js +12 -8
  61. package/dist/hooks/context-guard.d.ts +5 -0
  62. package/dist/hooks/context-guard.js +118 -2
  63. package/dist/hooks/hooks-generator.d.ts +3 -0
  64. package/dist/hooks/hooks-generator.js +23 -6
  65. package/dist/hooks/keyword-detector.js +16 -100
  66. package/dist/hooks/skill-injector.d.ts +4 -3
  67. package/dist/hooks/skill-injector.js +6 -4
  68. package/dist/host/codex-adapter.d.ts +10 -0
  69. package/dist/host/codex-adapter.js +154 -0
  70. package/dist/mcp/solution-reader.d.ts +5 -5
  71. package/dist/mcp/solution-reader.js +34 -24
  72. package/dist/services/session.d.ts +19 -0
  73. package/dist/services/session.js +62 -0
  74. package/hooks/hooks.json +2 -2
  75. package/package.json +2 -1
  76. package/skills/architecture-decision/SKILL.md +113 -257
  77. package/skills/calibrate/SKILL.md +207 -0
  78. package/skills/code-review/SKILL.md +151 -178
  79. package/skills/compound/SKILL.md +126 -68
  80. package/skills/deep-interview/SKILL.md +210 -110
  81. package/skills/docker/SKILL.md +57 -179
  82. package/skills/forge-loop/SKILL.md +198 -0
  83. package/skills/learn/SKILL.md +216 -0
  84. package/skills/retro/SKILL.md +199 -0
  85. package/skills/ship/SKILL.md +259 -0
  86. package/agents/code-simplifier.md +0 -197
  87. package/agents/performance-reviewer.md +0 -172
  88. package/agents/qa-tester.md +0 -158
  89. package/agents/refactoring-expert.md +0 -168
  90. package/agents/scientist.md +0 -144
  91. package/agents/security-reviewer.md +0 -137
  92. package/agents/writer.md +0 -184
  93. package/commands/api-design.md +0 -268
  94. package/commands/ci-cd.md +0 -270
  95. package/commands/database.md +0 -263
  96. package/commands/debug-detective.md +0 -99
  97. package/commands/documentation.md +0 -276
  98. package/commands/ecomode.md +0 -51
  99. package/commands/frontend.md +0 -271
  100. package/commands/git-master.md +0 -90
  101. package/commands/incident-response.md +0 -292
  102. package/commands/migrate.md +0 -101
  103. package/commands/performance.md +0 -288
  104. package/commands/refactor.md +0 -105
  105. package/commands/security-review.md +0 -288
  106. package/commands/specify.md +0 -128
  107. package/commands/tdd.md +0 -183
  108. package/commands/testing-strategy.md +0 -265
  109. package/skills/api-design/SKILL.md +0 -262
  110. package/skills/ci-cd/SKILL.md +0 -264
  111. package/skills/database/SKILL.md +0 -257
  112. package/skills/debug-detective/SKILL.md +0 -95
  113. package/skills/documentation/SKILL.md +0 -270
  114. package/skills/ecomode/SKILL.md +0 -46
  115. package/skills/frontend/SKILL.md +0 -265
  116. package/skills/git-master/SKILL.md +0 -86
  117. package/skills/incident-response/SKILL.md +0 -286
  118. package/skills/migrate/SKILL.md +0 -96
  119. package/skills/performance/SKILL.md +0 -282
  120. package/skills/refactor/SKILL.md +0 -100
  121. package/skills/security-review/SKILL.md +0 -282
  122. package/skills/specify/SKILL.md +0 -122
  123. package/skills/tdd/SKILL.md +0 -178
  124. package/skills/testing-strategy/SKILL.md +0 -260
@@ -13,8 +13,8 @@
13
13
  * - 인덱스 캐시는 isIndexStale()에 의존 (resetIndexCache 미사용)
14
14
  * → 디렉토리 mtime이 변하지 않으면 캐시 재사용 (성능)
15
15
  */
16
- import type { SolutionDirConfig } from '../engine/solution-index.js';
17
16
  import type { SolutionStatus, SolutionType } from '../engine/solution-format.js';
17
+ import type { SolutionDirConfig } from '../engine/solution-index.js';
18
18
  export interface SearchOptions {
19
19
  dirs?: SolutionDirConfig[];
20
20
  type?: SolutionType;
@@ -25,7 +25,7 @@ export interface ListOptions {
25
25
  dirs?: SolutionDirConfig[];
26
26
  status?: SolutionStatus;
27
27
  type?: SolutionType;
28
- scope?: 'me' | 'team' | 'project';
28
+ scope?: 'me' | 'team' | 'project' | 'universal';
29
29
  sort?: 'confidence' | 'updated' | 'name';
30
30
  }
31
31
  export interface SolutionSummary {
@@ -33,7 +33,7 @@ export interface SolutionSummary {
33
33
  status: SolutionStatus;
34
34
  confidence: number;
35
35
  type: SolutionType;
36
- scope: 'me' | 'team' | 'project';
36
+ scope: 'me' | 'team' | 'project' | 'universal';
37
37
  tags: string[];
38
38
  }
39
39
  export interface SearchResult extends SolutionSummary {
@@ -45,7 +45,7 @@ export interface SolutionDetail {
45
45
  status: SolutionStatus;
46
46
  confidence: number;
47
47
  type: SolutionType;
48
- scope: 'me' | 'team' | 'project';
48
+ scope: 'me' | 'team' | 'project' | 'universal';
49
49
  tags: string[];
50
50
  identifiers: string[];
51
51
  context: string;
@@ -55,7 +55,7 @@ export interface SolutionStats {
55
55
  total: number;
56
56
  byStatus: Record<SolutionStatus, number>;
57
57
  byType: Record<SolutionType, number>;
58
- byScope: Record<'me' | 'team' | 'project', number>;
58
+ byScope: Record<'me' | 'team' | 'project' | 'universal', number>;
59
59
  }
60
60
  /**
61
61
  * 기본 솔루션 디렉토리 목록 생성.
@@ -16,14 +16,13 @@
16
16
  import * as fs from 'node:fs';
17
17
  import * as path from 'node:path';
18
18
  import { ME_SOLUTIONS, PACKS_DIR } from '../core/paths.js';
19
- import { getOrBuildIndex } from '../engine/solution-index.js';
20
- import { extractTags, expandCompoundTags, expandQueryBigrams } from '../engine/solution-format.js';
21
- import { parseSolutionV3 } from '../engine/solution-format.js';
19
+ import { logMatchDecision } from '../engine/match-eval-log.js';
22
20
  import { maskBlockedTokens } from '../engine/phrase-blocklist.js';
23
- import { mutateSolutionFile } from '../engine/solution-writer.js';
21
+ import { expandCompoundTags, expandQueryBigrams, extractTags, parseSolutionV3, } from '../engine/solution-format.js';
22
+ import { getOrBuildIndex } from '../engine/solution-index.js';
24
23
  import { calculateRelevance, shouldRejectByR4T3Rules } from '../engine/solution-matcher.js';
24
+ import { mutateSolutionFile } from '../engine/solution-writer.js';
25
25
  import { defaultNormalizer } from '../engine/term-normalizer.js';
26
- import { logMatchDecision } from '../engine/match-eval-log.js';
27
26
  import { filterSolutionContent } from '../hooks/prompt-injection-filter.js';
28
27
  // ── 디렉토리 해석 ──
29
28
  /**
@@ -31,9 +30,7 @@ import { filterSolutionContent } from '../hooks/prompt-injection-filter.js';
31
30
  * MCP 서버에서 cwd를 전달받으면 project 스코프도 포함.
32
31
  */
33
32
  export function defaultSolutionDirs(cwd) {
34
- const dirs = [
35
- { dir: ME_SOLUTIONS, scope: 'me' },
36
- ];
33
+ const dirs = [{ dir: ME_SOLUTIONS, scope: 'me' }];
37
34
  // 팩 디렉토리 스캔 — 하위에 solutions/ 디렉토리가 있는 팩만 포함
38
35
  // PR2c-2 (M7 fix): readdirSync 결과를 정렬해 결정적 순서 보장.
39
36
  // 정렬 안 하면 같은 팩 집합도 파일시스템 순서에 따라 다른 cache key/precedence
@@ -107,12 +104,15 @@ export function searchSolutions(query, options) {
107
104
  // R4-T2: pass `queryTags` (already masked above) so the union
108
105
  // denominator inside calculateRelevance uses the post-mask set, in
109
106
  // sync with the matching step.
110
- const result = calculateRelevance(queryTags, entry.tags, entry.confidence, { normalizedPromptTags, solutionTagsExpanded: entryTagsExpanded });
107
+ const result = calculateRelevance(queryTags, entry.tags, entry.confidence, {
108
+ normalizedPromptTags,
109
+ solutionTagsExpanded: entryTagsExpanded,
110
+ });
111
111
  // 태그 매칭 + 이름 매칭: 솔루션 이름에 쿼리 단어가 포함되면 boost
112
112
  // Compute name match FIRST so R4-T3 cannot silently drop a candidate
113
113
  // with strong name-match evidence.
114
114
  const nameWords = entry.name.toLowerCase().split(/[-_]/);
115
- const nameMatchCount = queryTags.filter(t => nameWords.includes(t)).length;
115
+ const nameMatchCount = queryTags.filter((t) => nameWords.includes(t)).length;
116
116
  const nameBoost = nameMatchCount * 0.1;
117
117
  // R4-T3: orchestration-layer specificity guards (mirror of the
118
118
  // matching block in solution-matcher.rankCandidates). Reject single-
@@ -121,9 +121,9 @@ export function searchSolutions(query, options) {
121
121
  // bypass the R4-T3 gate — a nameMatchCount > 0 is strong evidence.
122
122
  let tagRelevance = result.relevance;
123
123
  let tagMatches = result.matchedTags;
124
- if (nameMatchCount === 0
125
- && tagMatches.length > 0
126
- && shouldRejectByR4T3Rules(queryTags, tagMatches)) {
124
+ if (nameMatchCount === 0 &&
125
+ tagMatches.length > 0 &&
126
+ shouldRejectByR4T3Rules(queryTags, tagMatches)) {
127
127
  tagRelevance = 0;
128
128
  tagMatches = [];
129
129
  }
@@ -137,7 +137,10 @@ export function searchSolutions(query, options) {
137
137
  scope: entry.scope,
138
138
  tags: entry.tags,
139
139
  relevance: tagRelevance + nameBoost,
140
- matchedTags: [...tagMatches, ...queryTags.filter(t => nameWords.includes(t) && !tagMatches.includes(t))],
140
+ matchedTags: [
141
+ ...tagMatches,
142
+ ...queryTags.filter((t) => nameWords.includes(t) && !tagMatches.includes(t)),
143
+ ],
141
144
  });
142
145
  }
143
146
  results.sort((a, b) => b.relevance - a.relevance);
@@ -152,12 +155,12 @@ export function searchSolutions(query, options) {
152
155
  source: 'mcp',
153
156
  rawQuery: query,
154
157
  normalizedQuery: normalizedPromptTags,
155
- candidates: top.map(r => ({
158
+ candidates: top.map((r) => ({
156
159
  name: r.name,
157
160
  relevance: r.relevance,
158
161
  matchedTerms: r.matchedTags,
159
162
  })),
160
- rankedTopN: top.slice(0, 5).map(r => r.name),
163
+ rankedTopN: top.slice(0, 5).map((r) => r.name),
161
164
  });
162
165
  }
163
166
  return top;
@@ -167,7 +170,7 @@ export function searchSolutions(query, options) {
167
170
  export function listSolutions(options) {
168
171
  const dirs = options?.dirs ?? defaultSolutionDirs();
169
172
  const index = getOrBuildIndex(dirs);
170
- let entries = index.entries.map(e => ({
173
+ let entries = index.entries.map((e) => ({
171
174
  name: e.name,
172
175
  status: e.status,
173
176
  confidence: e.confidence,
@@ -176,11 +179,11 @@ export function listSolutions(options) {
176
179
  tags: e.tags,
177
180
  }));
178
181
  if (options?.status)
179
- entries = entries.filter(e => e.status === options.status);
182
+ entries = entries.filter((e) => e.status === options.status);
180
183
  if (options?.type)
181
- entries = entries.filter(e => e.type === options.type);
184
+ entries = entries.filter((e) => e.type === options.type);
182
185
  if (options?.scope)
183
- entries = entries.filter(e => e.scope === options.scope);
186
+ entries = entries.filter((e) => e.scope === options.scope);
184
187
  const sort = options?.sort ?? 'confidence';
185
188
  if (sort === 'confidence') {
186
189
  entries.sort((a, b) => b.confidence - a.confidence);
@@ -201,7 +204,7 @@ export function listSolutions(options) {
201
204
  export function readSolution(name, options) {
202
205
  const dirs = options?.dirs ?? defaultSolutionDirs();
203
206
  const index = getOrBuildIndex(dirs);
204
- const entry = index.entries.find(e => e.name === name);
207
+ const entry = index.entries.find((e) => e.name === name);
205
208
  if (!entry)
206
209
  return null;
207
210
  let fileContent;
@@ -231,7 +234,7 @@ export function readSolution(name, options) {
231
234
  // Pull(MCP) 경로: evidence에 기여 — sessions + reflected 카운트 증가
232
235
  // PR2b: solution-writer.mutateSolutionFile로 통합. lock + fresh re-read로 race 방지.
233
236
  if (!options?.skipEvidence) {
234
- mutateSolutionFile(entry.filePath, sol => {
237
+ mutateSolutionFile(entry.filePath, (sol) => {
235
238
  sol.frontmatter.evidence.sessions += 1;
236
239
  sol.frontmatter.evidence.reflected += 1;
237
240
  return true;
@@ -258,8 +261,15 @@ export function getSolutionStats(options) {
258
261
  total: index.entries.length,
259
262
  // retired는 인덱스에서 제외되므로 항상 0 (solution-index.ts:73)
260
263
  byStatus: { experiment: 0, candidate: 0, verified: 0, mature: 0, retired: 0 },
261
- byType: { pattern: 0, solution: 0, decision: 0, troubleshoot: 0, 'anti-pattern': 0, convention: 0 },
262
- byScope: { me: 0, team: 0, project: 0 },
264
+ byType: {
265
+ pattern: 0,
266
+ solution: 0,
267
+ decision: 0,
268
+ troubleshoot: 0,
269
+ 'anti-pattern': 0,
270
+ convention: 0,
271
+ },
272
+ byScope: { me: 0, team: 0, project: 0, universal: 0 },
263
273
  };
264
274
  for (const entry of index.entries) {
265
275
  if (entry.status in stats.byStatus)
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Launch-time runtime selection helpers.
3
+ *
4
+ * 기본 동작:
5
+ * - --runtime claude|codex 플래그 우선
6
+ * - 설정되지 않으면 FORGEN_RUNTIME 환경변수 사용
7
+ * - 환경변수 미설정 시 claude 기본값
8
+ *
9
+ * 목표:
10
+ * - launch context(런타임 + 정제된 args)를 단일 타입으로 통일
11
+ * - CLI/fgx에서 수집한 런타임 값을 Harness, Spawn, Hook Generator에 일관되게 전달
12
+ */
13
+ import { type LaunchContext } from '../core/types.js';
14
+ /**
15
+ * CLI 인자를 파싱해 런타임 결정 + 런타임 플래그 제거
16
+ * - --runtime codex
17
+ * - --runtime=codex
18
+ */
19
+ export declare function resolveLaunchContext(args: string[]): LaunchContext;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Launch-time runtime selection helpers.
3
+ *
4
+ * 기본 동작:
5
+ * - --runtime claude|codex 플래그 우선
6
+ * - 설정되지 않으면 FORGEN_RUNTIME 환경변수 사용
7
+ * - 환경변수 미설정 시 claude 기본값
8
+ *
9
+ * 목표:
10
+ * - launch context(런타임 + 정제된 args)를 단일 타입으로 통일
11
+ * - CLI/fgx에서 수집한 런타임 값을 Harness, Spawn, Hook Generator에 일관되게 전달
12
+ */
13
+ /** 런타임 정규화: 외부 문자열을 내부 enum으로 변환 */
14
+ function parseRuntime(raw) {
15
+ if (!raw)
16
+ return null;
17
+ switch (raw.trim().toLowerCase()) {
18
+ case 'claude':
19
+ return 'claude';
20
+ case 'codex':
21
+ return 'codex';
22
+ default:
23
+ return null;
24
+ }
25
+ }
26
+ const DEFAULT_RUNTIME = 'claude';
27
+ /**
28
+ * CLI 인자를 파싱해 런타임 결정 + 런타임 플래그 제거
29
+ * - --runtime codex
30
+ * - --runtime=codex
31
+ */
32
+ export function resolveLaunchContext(args) {
33
+ const runtimeFromEnv = parseRuntime(process.env.FORGEN_RUNTIME);
34
+ const result = {
35
+ runtime: runtimeFromEnv ?? DEFAULT_RUNTIME,
36
+ args: [],
37
+ runtimeSource: runtimeFromEnv ? 'env' : 'default',
38
+ };
39
+ for (let i = 0; i < args.length; i += 1) {
40
+ const arg = args[i];
41
+ if (arg === '--runtime') {
42
+ const next = args[i + 1];
43
+ const parsed = parseRuntime(next);
44
+ if (parsed) {
45
+ result.runtime = parsed;
46
+ result.runtimeSource = 'flag';
47
+ i += 1;
48
+ }
49
+ continue;
50
+ }
51
+ if (arg.startsWith('--runtime=')) {
52
+ const parsed = parseRuntime(arg.slice('--runtime='.length));
53
+ if (parsed) {
54
+ result.runtime = parsed;
55
+ result.runtimeSource = 'flag';
56
+ }
57
+ continue;
58
+ }
59
+ result.args.push(arg);
60
+ }
61
+ return result;
62
+ }
package/hooks/hooks.json CHANGED
@@ -151,7 +151,7 @@
151
151
  "hooks": [
152
152
  {
153
153
  "type": "command",
154
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/subagent-tracker.js\" start",
154
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/subagent-tracker.js\" \"start\"",
155
155
  "timeout": 2
156
156
  }
157
157
  ]
@@ -163,7 +163,7 @@
163
163
  "hooks": [
164
164
  {
165
165
  "type": "command",
166
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/subagent-tracker.js\" stop",
166
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/subagent-tracker.js\" \"stop\"",
167
167
  "timeout": 2
168
168
  }
169
169
  ]
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@wooojin/forgen",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
+ "preferGlobal": true,
4
5
  "main": "dist/lib.js",
5
6
  "types": "./dist/lib.d.ts",
6
7
  "exports": {